Skip to content

feat(chat): ordinary authorized workspace conversations without implicit Goals - #5540

Open
huangruiteng wants to merge 3 commits into
mainfrom
codex/personal-native-bots-20261004
Open

huangruiteng wants to merge 3 commits into
mainfrom
codex/personal-native-bots-20261004

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Chat currently requires a Goal or the global manager, so an ordinary workspace question inherits a role it did not select. This change adds a host-authorized, read-only project context and a secondary Settings → Project conversation entry point. Two messages and a process restart retain the same Core Session and native thread without creating a Goal or collecting portfolio context.

The existing typed conversation owner resolves the workspace reference and grant; Python observes configured roots and performs IO. Sessions persist the exact context. Unknown, missing, or retargeted roots, Goal-bearing project requests, LoopX execution, and external audiences fail closed. The UI supports open/resume, new/close, canonical history, durable admission readback, and exact stop; an admission failure retains the draft and request identity.

This is an independently usable local workspace slice from current main. Lark project audience binding, timely later-input admission, authorized Agent selection, live Lark/model qualification and mobile Bot journeys remain open in the existing RFC. This PR does not include or depend on the unmerged transport scheduling changes in #5538 and does not qualify a deployment.

Validation:

  • 115 relevant Python tests passed; the final malformed-input update was rechecked with all 3 ordinary-project tests passing.
  • Focused typed-owner tests, control-plane and dashboard type checks, packaged Chat build, CSS token check, Ruff/Mypy configured checks, semantic inventory/drift checks, CLI output-budget regression, and risk-selected premerge canary passed.
  • Full TypeScript suite: 3998 passed / 3999 total. The Mac Python-discovery test a worktree venv wins over an unusable system python3 fails because the fixture resolves Homebrew Python; reproduced on unchanged local checkout 1e9e82b03 (not the exact PR baseline 99839aeb8). The resolver and test are unchanged in this PR.
  • Actual packaged browser with the real owning backend and synthetic native protocol fixture: continuous messages, reload/resume, exact stop, 390px viewport, workspace revocation rejection with preserved draft, and retry after restoration passed. This proves the product/Core journey, not real model or Lark transport behavior.
  • A separate real installed Codex host canary on this source head passed: two synthetic messages retained context in one native thread; a fresh controller resumed the same Core Session/native thread and retained the earlier code. No Goal file was created. This qualifies local native model continuity only; live Lark and mobile remain untested.
  • Runtime/authority changes require maintainer review and merge; no self-merge.

Public-safe synthetic visual evidence:

Workspace conversation

Narrow viewport

Grant rejection and retained draft

Current exact-head qualification (1e0bedc160a5db421741cf26ff6cc8830e8fd51c):

  • Moved the project context IO companion into the native Chat capability package; the top-level module budget remains 147. The ordinary-project tests (3) and module/census/maintainability checks (27) passed after this fix.
  • CI is not green: all four Python shards and the merge gate fail. Five selected fingerprint/source-grant failures were reproduced unchanged on the exact baseline 99839aeb8; this attribution does not cover every CI failure.
  • Release Artifacts build also failed while waiting for “需要宿主确认” in the existing typed-actions browser scenario. The Python workflow's Chat bundle browser and dashboard acceptance checks passed. The separate release-browser failure remains an installation hold; it was not waived or declared fixed.
  • Native owner-only Lark project admission is a separate stacked successor, not part of this local-workspace diff. Runtime/authority changes remain for maintainer review and merge.

Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
@huangruiteng
huangruiteng requested a review from maxliux5 as a code owner October 3, 2026 18:32
Signed-off-by: huangruiteng <huangrt01@163.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=xhigh

评审精确 head:1e0bedc160a5db421741cf26ff6cc8830e8fd51c;独立基础版本:99839aeb8fed5fae38a5d319391cd050672a6508。

[P1] 迟到的会话刷新会把新工作区输入发到旧工作区。 具体位置:apps/presentation/dashboard/src/features/personal-workspace/project-conversation-panel.tsx:39–44,下一次发送在第62行使用被覆盖的 session_id。打开 A 并发一条消息,点击“刷新会话状态”,在响应返回前选择 B,再让 A 响应返回;页面显示 B,却恢复 A 的记录。随后发送 B 的消息,真实 HTTP POST 和持久 Turn 都落到 A Session。这是打包前端与真实本地后端的独立复现,外部模型是协议 fixture。最小修复是按项目、Session 和请求代次过滤/取消过时刷新,并在发送前核对 session.project_ref;增加延迟 A 响应、A→B→A、关闭/新会话的回归用例。

动机

只想讨论一个已授权项目的用户,需要普通聊天和连续上下文。

此前用户必须先建 Goal 或进入管家对话;当前版本提供工作区聊天入口,但切换到 B 后可能被迟到的 A 刷新响应带回旧会话。

后端能连续两轮并恢复同一原生会话,伪造和撤销授权会拒绝;打包界面已复现选择 B 却向 A 会话发送的回归。

不验收 Lark 私聊、跨主机附着会话、写权限、团队委派或长期 Goal。

改动思路

普通项目对话复用当前 Chat 原生 Session/Turn、read-only adapter 和结果流。host 只提供已配置根目录的 filesystem observation,用户选择稳定 project_ref;TS context owner 将其与当前 root grant、local-owner 受众和只读权限逐项匹配。HTTP 不接受用户用路径或文字造 grant。保存的上下文必须在每次 Turn 前重新确认,Root 消失、symlink 改指或权限撤销不能沿用旧授权。

我检查了基础和 head 的 Goal/manager/native Chat 创建、恢复、peer context、Session store 和 Settings companion 调用方。新 native_chat/project_context.py 是 host 观察 adapter,通用决定仍在 TS 的 conversation_scope/project_conversation;不另造 Goal、portfolio identity、任务账本或模型 runner。secondary Settings 的最短有效路径是选择当前工作区后直接发送:缺 Session 时自动打开/恢复,不强制用户额外确认已经给出的选择。打开/新建/关闭为可选会话管理。

具体改动

完整26文件 diff +589/−24,另有三张公开合成 UI 图片:共享 typed scope/resolver 与 effect 注册;原生 Session 创建/恢复/store/turn 前的权限复核及只读 adapter 处理;HTTP projects/create 路由;frontend typed summary 的 nullable Goal 和 activity projection 伴随调整;Settings panel134行和CSS26行;三项 native HTTP 测试、两项 TS 测试;双语 RFC及既有 IO manifest 行位置刷新。所有表面都在本次完整 diff 内评估,问题不是缺少 frontend 入口,而是入口的异步身份处理有缺陷。

关键代码讲解

  • ChatProjectContexts(project_context.py:25):只从 host 配置 roots 生成观察,保存初始 canonical target 防 symlink retarget;session_context 校验 Goal 必须为 null、当前完整 context 和 channel 必须匹配,objective 明确排除写入、Goal、委派、调度和 portfolio。
  • resolveProjectConversation(project_conversation.ts:14)和复用的 normalizeProjectContext:要求唯一 exact authorized ref、合法绝对根目录、local_owner/workspace_read;保存 context 不同就拒绝。peer context 的私有访问资格只接受既有 Goal/portfolio scope,普通 project variant 不变成 registered peer 或全局管家。
  • ChatRuntimeController.open_session(chat_runtime.py:553):显式 project_ref 沿既有 Session 生命周期创建或恢复,工作目录/objective 从 fresh grant 派生;跳过 Goal 附着选择。enqueue/preparation 均复核,外部 Lark origin 和 LoopX execution 不能借普通对话扩大执行权。真实 backend fixture 验证两轮和 controller 重启接续同一 upstream thread。
  • ProjectConversationPanel.refresh(:39):只检查组件仍 mounted,然后无条件写 session/messages/turnId。工作区 select 可在刷新期间变更;第62行 send 直接沿用这个 session。mounted 不能说明响应仍属于当前工作区。这是开头 P1 的根因;单靠后端合法 grant 不会发现“两个都已授权、但选择的是另一个”的错误。

依据 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,不可变版本 99839aeb8fed5fae38a5d319391cd050672a6508:Scope 明确要求 A→B→A 和 late response 只更新原 source/session/Turn;本 head not_met。Result 要保留结果的原会话语义;旧记录覆盖新选择的情形 not_met。Authority 的伪造/撤销/源改变拒绝路径在后端已验证。Packaging 只取得当前源代码打包 frontstage 的有界证据,仍为 deferred;Acceptance 未引入新 ledger,既有 canonical dedupe 保留,本轮未完成丢失网络响应的完整资格。RFC 的本 PR 编辑没有覆盖掉先前的 Scope 要求。

对主干的风险

我用当前 head 重新构建并验证 Chat bundle,包括 frontend tsc,通过 ego-browser 操作打包 /chat/ 的真实 Settings 入口,连接隔离的 ChatHTTPServer/ChatRuntimeController/原生 Session 文件。外部 Codex app-server 采用仓库协议 fixture,没有真实模型或账户调用。先完成 A 正常消息,再只延迟真实 A GET 的返回(未伪造数据),切到 B,释放 A 响应并发送。浏览器 selected 是 B,POST 却指向 A Session;独立读取持久 project_context 和 Turn 确认其仍归 A。正常 B control 使用 B 自己的 Session,桌面与390px窄屏均检查了包含对话内容的整个 viewport;窄屏正常流程可用,不能抵消异步身份错误。

三项普通项目原生测试通过:两轮与重启连续性、不生成隐藏 Goal/portfolio、伪造字段/ref、撤销、外部受众和 symlink retarget 拒绝;两项 TS 测试通过。完整语义漂移与相邻 store/input、HTTP CORS、delegation wake scope 组 179 passed;control-plane typecheck、changed Python Ruff、CSS token 检查和 diff hygiene 通过。三个 commit 均有 DCO sign-off,公开截图为合成内容,未发现私有本机路径或 Goal 资料。一次查询不存在的 dashboard typecheck npm script 是 reviewer 准备错误;成功 package build 已执行真实 frontend tsc。按配置没有查询 CI。

最强反例恰好说明为什么上述 green backend tests 不足:它们直接调用 HTTP/context,不经过 React 的待返回 refresh。当前不存在修复后的 passing race receipt。完整相同 baseline 的公共 API/旧 Goal 分支执行矩阵、所有新增 root/recovery 变体、paid model/installed host、Lark DM 和跨 host 行为未测;没有宣称 observable equivalence 或全部 GQ 已完成。这里也没有 PostgreSQL store refactor,不涉及真实数据库迁移验收。

我的整体评价

REQUEST_CHANGES。共享 owner、权限分层和有用的普通对话入口是合理设计;backend 增量能连续执行并保留正确 read grant。但所选项目和 Session 不是同一个身份时,会混入旧项目上下文,执行用户没选择的工作区请求,这是当前 slice 的用户旅程回归。

最小修复保持现有 typed owner 和 store:用 current project/Session generation 或取消标识阻止迟到 hydration,在 send 前验证 project_ref;用户切换、新会话、关闭/停止均使过时回调失效。用真实打包入口重跑开头负例,要求 A 的迟到结果不改变 B,下一次 send 只能创建或使用 B Session。相邻未来重构检查支持这个窄生命周期 fence,不需要第二个聊天 store 或抽象框架。修复后再按新 exact head 完整复核。

English verdict: REQUEST_CHANGES - 1e0bedc. P1: a late Session-A refresh repopulates state after selecting Workspace-B, so the next B-intended message is posted and persisted to A's native Session. Reproduced on the rebuilt packaged frontend with real local HTTP/store and a fake external model protocol; independent B control passes. Native3, TS2 and adjacent179 cases pass, but they do not cover this race. Fence hydration and sends by selected project/session identity; CI was not consulted.

async function refresh(sessionId: string) {
const current = await fetchChatSession(sessionId);
if (!mounted.current) return;
setSession(current.session);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Fence this hydration by the selected project/Session generation. Refreshing A does not mark the panel busy, so the user can select B while the real GET is pending; its late response restores A here, and send() then posts the B-intended input to A. Reproduced on the packaged frontend with real native HTTP/Session files. Ignore or abort obsolete results, verify session.project_ref before sending, and cover delayed A response after A→B (including A→B→A and close/new-Session variants).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants