fix(app): preserve conversational intent and plan App-first continuity - #5064
Conversation
huangruiteng
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Exact head: f59279b4f2a09e62868bf79c5080e9ca11837cc1;完整评审范围为 immutable merge base 27f0fc93b806925cb151df4ffac26e3aebdbff2c 到该 head 的 47 个文件。结论:REQUEST_CHANGES,有一个 P2 阻塞项。
动机
普通问题“解释一下 monitor 的工作原理”会被浏览器关键词规则转成定时检查预览,引用、否定、复合请求也会受到影响。本 PR 删除并行语义解释器,让完整原文到达当前经理或 Goal 的 Chat;显式配置仍经既有 typed preview/apply 边界。这解决了可复现的入口错误,也减少了持续对话中丢失意图、被迫重写请求的成本。
以 App Conversation / Async Inbox RFC 的阶段边界判断:本次交付是对话入口修复、显式配置和活动呈现;跨 Host 持续执行、异步 inbox 迁移、真实双周期团队接力仍是计划。本评审没有把这些未来验收算成已实现,也没有要求这次完成整个路线图。入口阶段独立有价值,但新经理简报的执行状态还不够可信。
改动思路
普通输入走 sendMessage → onSendMessage → Chat,语义动作由已有 Agent/runtime 返回。Goal、Todo、Heartbeat、monitor 按钮打开受控字段表单,再调用已有 action owner;浏览器负责明确字段和呈现,不再根据一句话创建另一套意图规则。只读来源、workspace/agent 选择、preview 与确认 apply 继续由既有边界约束。
活动信息从真实 session facts 派生,managed execution 与 attached claim 分别保留自己的时间来源,侧栏、标题和简报复用读模型。Goal 身份不再依赖行位置;旧草稿按存储时间排序、后台日程折叠,异常和确认入口仍可达。没有新增 inbox 存储或第二个 Goal 权威。
关键代码讲解
PersonalWorkspacePage.sendMessage,1532 行:保留选定 agent、Goal 和图片输入;运行中的 LoopX 消息仍走既有 queue/inbox/correction 路径,普通输入交给 Chat。失败恢复 composer 和附件,Agent 返回显式语义预览时才进入createPreview,没有 keyword fallback。WorkspaceActionForm,21 行:四种显式动作共享 dialog、长度/间隔校验、权限选择和失败保留。preview()只生成已有 typed request;真正写入仍在确认 apply。文本执行边界不再被当成权限分类器。goalExecutionFromSessions,60 行:筛选当前 Goal、未关闭 session、非 idle turn,并区分 managed 与 attached。当前 head 已修复“旧 managed 活动 + 新 attached claim”共享新时钟的问题;30 分钟静默与 2 分钟领取分别呈现,不再因此亮起 live ring。ManagerBrief,44 行:聚合需要处理、执行、最近完成和已安排的派生事实。这里仍把 attached-only 的kind: running当成执行事实,是下述阻塞项的直接消费者。compareProposalRecency,22 行:复用 action store 的时间/id 排序语义,统一 dedupe 与 timeline 的最新草稿选择;旧 ready 草稿可折叠,确认、失败和已执行结果保持可访问。
具体改动
全量差异覆盖 App 数据投影、page/router、显式 action form、结构化 Todo resume、共享活动 model/view、侧栏/标题/经理简报、timeline 草稿和日程层级、双语文案/CSS;配套修改 canonical browser scenarios、focused contract/smoke 和构建脚本,并更新 App RFC、semantic handoff/presentation/TS 迁移、路线图及 golden queries 的阶段关系。经理快照被明确标为快照,未伪装成模型回答。没有后端或 Lark 行为改动:action API、运行时和 authority owner 未变,浏览器入口是本次实际修复的边界。
[P2] attached claim 仍被经理简报计入“正在执行”。 manager-brief.tsx:48 只按 execution.kind === "running" 过滤,而读模型对只有 attached claim、没有 managed execution 活动的 session 也返回该 kind。原生 ChatSessionStore 创建 queued turn、执行 attached claim,再经真实 sessions HTTP 读回后,开发版和打包版都显示:侧栏“宿主已领取 · …领取于 2 分钟前”,无 live ring;经理简报却显示“正在执行 1”。领取只能证明 Host 取走待办,不能证明已经开始执行。首屏这一计数会让用户误以为工作已在推进。
最小修复:在现有共享读模型/简报消费者中明确区分“仅领取”和“执行中”,使仅领取不会进入 executing 计数,并保留用户查看待接续工作的入口;也可把整个分组改成准确的未结束工作语义,但必须同时调整标题、计数与事实说明。回归应覆盖 attached-only 无活动、未领取排队、旧 managed+新 claim、以及新 managed+旧 claim,在中英文标题和计数上独立断言。当前 goal-activity browser scenario 把 claim-only 包含进简报行数,因此现有绿色用例并未防住这一错误。
对主干的风险
独立 base/head 同输入比较中,旧版把 monitor 解释、被引用的 heartbeat、英文否定 Goal/daily 请求转成配置预览;当前四个对照输入均完整进入 Chat 一次、零浏览器预览、零写入。完整 conversation-input scenario 的中英文十类请求、经理/Goal session 隔离、显式权限、过长字段、非法间隔、503 后保留重试、composer 保留、390px/Escape 取消均通过。原生 actions smoke 验证 preview/apply/readback,但其 Agent 执行器是测试替身;它不证明真实模型理解或外部 Host 接续。
验证结果:dashboard 构建和 packaged manifest 校验通过;18 个开发版 + 18 个打包版 browser scenarios 通过;session active-turn/executor-profile/project-coordination 共 39 个原生 Python 测试通过;结构化 resume、localization、action lifecycle 和伴随排序/theme/drawer 检查通过。额外原生 sessions 投影覆盖十种状态,包含相反方向的 mixed clock、关闭/idle/未知来源及大量不相关 session。开发与打包表现一致。完整差异 diff --check、公开/私有边界检查通过,未发现凭据或私有运行材料。
语义与 CI 对齐
按仓库现有 presentation/semantic owner 判断,不给新的页面读模型执行权威:删除 prose/substring 意图分类是正确方向;单一 typed action owner、只读 gate 和明确确认语义保持。当前 P2 是派生投影把 claim 扩大为执行事实,应在已有 TypeScript 边界修复,不需要新的状态 journal。此 PR 没有 optional/default-off 声明;自然语言入口默认改变已通过新 scenario 和 RFC 公开说明。依当前 review packet,未查询或等待远程 CI。
未来改动便利性检查:已删除重复语义解释器、统一 proposal recency 和活动 presenter;剩余的相关小改动是让 claim/execution 的类型与消费者一致。本评审不认证真实 attached adoption、跨 Host 双周期团队协作、首屏 owner approval 或发布验收。steward journey 输出的 readiness/correction/recovery/return gaps 仍是未完成验收,不能由 browser fixtures 消除。
我的整体评价
对话入口修复和显式配置是可验证、可逆的有效阶段,完整范围与当前阶段相称。旧 mixed activity 时钟问题在此 head 已修复,应保留这项成果。经理首屏对“领取”和“正在执行”的剩余混淆影响用户判断持续工作状态,因此暂不批准;完成上述既有边界的小修复并重跑原生 claim-only 与双向 mixed cases 后复审。
English verdict: REQUEST_CHANGES — f59279b4f2a09e62868bf79c5080e9ca11837cc1; attached-only claims still count as “Running now” in the manager brief. Full builds, 18 development + 18 packaged scenarios and 39 native session tests pass; additional native store/HTTP counterexamples reproduce the P2. The mixed-clock live-ring defect is fixed at this head.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Mirror the chat action normalization limits in the explicit Task and Goal forms so an over-long draft is explained and cannot be previewed, instead of failing at the preview boundary. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Open agent Todos and quota eligibility now read as Queued instead of Advancing. The workspace polls the chat session owner and marks a Goal as Running only while one of its sessions holds an active turn; an unreadable session list reads as unknown rather than running. Goals get a stable identity mark, and only verified execution carries the live accent. Active monitors read as Enabled. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
The manager home opens with three verified tiles above the full Goal lanes: what needs the owner, what is executing now (from active session turns, with the observed activity time), and recently completed work. When the session owner cannot be read, the executing tile says so instead of reporting zero or guessing. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
A claimed turn names its attached host. After fifteen minutes without a recorded event it is shown as silent rather than live, without claiming it was interrupted. Queued work on a Goal with an open attached session says live state belongs to the host, because host-initiated work is not visible to the session list. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
The chat API route rebuilt the namespace map per request, so an applied steward configuration was lost on the next read and the readback check only passed when it won a render race. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ed claim time Bound host threads from the status projection now mark a queued Goal as host-owned; attached sessions are no longer the only source. An attached claimed turn reports its claim time without a live ring, because the claim is the only activity fact LoopX has for it. Host surface ids map exactly instead of by substring. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Scheduled checks, heartbeats and routine runs fold into one Background work line. Drafts use owner-facing kind and status labels instead of raw action strings, and only the newest draft awaiting the owner stays open. Needs-you items, waiting or failed runs and non-draft proposals remain visible. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
`ChannelTimeline` folded every ready draft except the last one in the array and kept that one on the first screen. The array order is not recency: the store returns proposals newest-first by (`updated_at`, `proposal_id`), the workspace restores exactly that order, and a draft created in this session is appended last. A restored workspace therefore folded the newest draft and offered the oldest one -- the opposite of the intended "the conversation leads" reading, and the card the owner would confirm first was the stale one. Read recency from the record instead of the position. The stored times now reach the row through the typed adapter, a focused `proposal-recency.ts` owns the comparison (mirroring the store's own tie-break so the answer matches a real list), and `dedupeProposals` keeps the newest record for a subject rather than whichever duplicate happened to be last. The team-plan fixture gave two stored drafts the same timestamp, which real stored actions never do and which made the Goal view nondeterministic; the manager-channel plan is now explicitly the earlier draft. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The browser fixture answered `/api/actions` in insertion order, which no real `ChatActionStore.list` ever returns, so the seventeen scenarios could pass while the workspace read the wrong draft as newest. Sort the fixture's list response by (`updated_at`, `proposal_id`) newest first, exactly as the store does. Add a `newest-draft` scenario over two real stored drafts: a restore in store order, a reload and re-entry, a list served deliberately out of contract order, and a draft created in this session appended last. Each case asserts the newest draft keeps the conversation, the older one is folded with its count, and the fold still opens the drawer by keyboard. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
`goalExecutionFromSessions` took the newest recorded time across every active session and then asked whether *all* of them were attached. One Goal holding a silent managed turn and a freshly claimed attached turn therefore reported `hostClaimed=false, quiet=false`, and the sidebar, header and daily brief showed a live ring with "executing · Codex App host · 2 minutes ago" while the only recent fact was a claim. Aggregate by session mode instead: managed turns carry execution activity (`lastActivityAt`, and therefore `quiet`), attached turns carry a separate `claimedAt`. `hostClaimed` is true only when no managed turn is open, and the presenter reports the claim as a claim with no live accent, so a claim can no longer make a silent managed turn look live. `goal-activity.test.mjs` pins the read model (claim-only, silent-managed, stale-managed with a fresh claim, fresh-managed with an old claim, newest of several managed turns, closed and unbound sessions) and runs through the new `smoke:goal-activity` script; the browser scenario adds both mixed Goals and asserts the rendered line, the live-mark count and the brief text. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
* feat(status): observe bound Codex thread activity for the App status route Add a read-only host thread observer contract with typed states (turn_open, idle, archived, unknown with a typed reason) and a Codex adapter that reads the local thread store and rollout turn markers. Unrecognized store or record shapes are unknown, never an open turn. Only the App status route attaches the result; other collect_status callers are unchanged. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(app): show observed host thread turns as execution An open host-recorded turn with a recent event runs the Goal with the live ring; silent turns disclose their silence and turns without an event for six hours are not execution. Observed idle threads read as idle instead of deferring to the host. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> * fix(status): keep partial and malformed host observations honest Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> * fix(status): preserve claim clocks and escaped event semantics Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --------- Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
A Goal whose only execution fact is an attached host claim was still counted as "Running now" in the manager brief and as "执行中" in the home lane, so the first screen implied work was already advancing. Move the claim/execution decision into one shared read-model helper and route every consumer through it: the brief renders a claim-only Goal in its own "宿主已领取" tile, the home board gives it a matching lane, and timeline run rows no longer spin for a claim. The claim-only Goal keeps a clickable entry point with its claim time instead of borrowing execution. The browser scenario now asserts the four mixtures (attached-only, queued without a claim, stale managed with a fresh claim, fresh managed with an old claim) in Chinese and English, plus the tile and lane counts and widths. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
b33c440 to
40bb86c
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
全量评审:App 对话意图与诚实的活动呈现
精确 head:40bb86cd2aef7d4372d2a618ca28b1da64cb2aff。评审范围为 immutable merge base
de03bf84e8d7f356d50ecc921deee163dd0e14fe 到该 head 的 52 个文件、+2785/−863
全量差异。上一轮在 f59279b4f 上给出 REQUEST_CHANGES,只有一项 P2:attached-only claim
仍被经理简报计入“正在执行”。本 head 在同一 PR 上修掉该 P2,并合入 #5081 的绑定线程
观测。复审结论:通过,无阻塞项。
动机
可复现的入口缺陷是普通问题(例如“解释一下 monitor 的工作原理”)会被浏览器关键词规则
改写成定时检查预览,引用、否定和复合请求同样受影响。这类改写让用户以为自己在下指令,
又必须重写一遍才能表达意图,持续对话成本随时间累积。本 PR 让完整原文到达当前经理或
Goal 的 Chat,把语义解释交回既有 Agent,显式配置仍走既有 typed preview/apply 边界;
同时把活动呈现收敛到一条共享的“领取 / 执行”规则上。
交付判定为 justified_increment。相对已接受的 App-first 路线图,本次关闭的是“对话入口
被并行解释器劫持”和“宿主领取被当成执行”这两个可复现缺口;跨 Host 持续执行、异步 inbox
迁移、真实双周期团队接力仍是计划验收,本评审没有把它们算成已实现,也没有要求这个
阶段完成整条路线图。long_horizon 判定为 accepted_tradeoff:入口阶段独立可用、可逆,
剩余缺口已有明确 owner 与下一步;user_experience 判定为 improved:首屏不再把
“宿主已领取”说成“正在执行”,用户能区分 LoopX 在执行、宿主已取走、只是排队三种状态。
改动思路
入口侧的权威边界没有变化:普通输入走 sendMessage → onSendMessage → Chat,由既有
Agent/runtime 解释;Goal、Todo、Heartbeat、monitor 等显式动作打开受控字段表单,再调用
既有 action owner。浏览器只负责明确字段与呈现,不再根据一句话创建第二套意图规则,
因此删除了并行语义解释器而不是再造一个 dispatcher。
活动侧的权威仍然只有 session owner 和宿主线程记录两类事实。goalExecution 分别读取
managed turn 的活动时间、attached claim 的领取时间和宿主观测到的 open turn;本 PR 的
新改动把“哪一类算执行”的判断从各消费者里抽成一条共享规则 goalWorkKind,让经理简报、
home lane 和 timeline run 行都从同一个派生化结论取值。这是派生投影,不是新的持久权威:
没有新增状态字段、没有第二个 journal,也没有让 claim 变成执行事实。
所有权取舍上,claim-only Goal 现在有自己的桶:简报里是独立的“宿主已领取”tile,home
board 里是独立的 lane,卡片继续显示“宿主已领取 · 领取于…”。这样既修正了计数,也保留
了查看待接续工作的入口。#5081 引入的宿主观测保持只读:只有 host 自己记录的 open turn
(且事件在 6 小时内)才算执行,未识别的记录形状读作 unknown 而不是 open turn。
具体改动
变更分类:production 为 goal-activity.ts、manager-brief.tsx、
personal-workspace-model.ts、personal-workspace-page.tsx、personal-workspace.css、
i18n.tsx、goal-activity-view.tsx、data/status.ts、dashboard-page.tsx,加上
#5081 的 loopx/chat_status_api.py、loopx/codex_app_thread_activity.py、
loopx/control_plane/agents/host_thread_activity.py;tests_or_fixtures 为
goal-activity.test.mjs、personal-workspace-contract.test.mjs、
examples/personal-workspace-browser/*(含 fixture 与 goal-activity scenario)、
tests/test_host_thread_activity.py;docs 为 App conversation RFC、TS 迁移、
semantic handoff、presentation、路线图、golden queries 与
docs/integrations/attached-agent-session-broker.md。机械搬移集中在删除
personal-workspace-router.ts 与其 smoke,并用 todo-resume-condition.ts 承接仍然需要的
结构化解析;没有 generated 资源改动。新增文案只在既有 i18n 词汇表内扩展。
关键代码讲解
goalWorkKind(goal-activity.ts,153 行):把“未结束工作是执行还是仅领取”变成
typed 分类executing | claimed | none。它只读execution,kind !== "running"
返回 none,hostClaimed返回 claimed,否则 executing。输入是读模型派生的
WorkspaceGoalExecution,关键不变量是“只有 managed turn 或宿主观测到的 open turn
才算执行”。消费者是简报、lane 与 timeline;无失败分支,缺失事实即 none。presentGoalActivity(goal-activity.ts,158 行):现在用同一个goalWorkKind
决定标签与 live,不再自己重复一套kind === "running"判断。claimed 返回
activity.hostClaimed且 live 为 false;executing 才可能点亮 live。消费者是
GoalActivityChip、GoalIdentityMark与简报行。ManagerBrief(manager-brief.tsx,44 行):running改为只收 executing,
claimed单独成桶,queued用goalWorkKind === "none"判定“已安排但没有活动 turn”。
这是上一轮 P2 的直接消费者,也是本次修复的核心:claim-only Goal 不再进入执行计数。workspaceHomeLaneForGoal(personal-workspace-model.ts,515 行):从
execution?.kind === "running"改为按goalWorkKind分流,claimed进入新 lane
而不是 borrowed running lane;需修复保持优先进入 running。activeHomeLanes与defaultTimeline(personal-workspace-page.tsx,115 / 378 行):
渲染新增的“宿主已领取”lane,并把 timeline run 行状态从kind === "running"改为
goalWorkKind === "executing",避免 claim 让 run 行转圈。
对主干的风险
最强的回归场景仍是上一轮 P2:一个只有 attached claim、没有 managed 活动的 Goal。
触发状态是 ChatSessionStore 里存在 queued turn、宿主取走待办并留下 claim 时间。
修复前 manager-brief.tsx 只按 execution.kind === "running" 过滤,简报显示
“正在执行 1”,而侧栏同一 Goal 显示“宿主已领取”,两处自相矛盾。修复后该 Goal 进入
“宿主已领取”tile 与 claimed lane,执行计数只剩真正有 managed turn 的 Goal;
minimum repair 就是这条共享规则,rollback 只需回退本次提交,不涉及持久状态或迁移。
破坏半径限于 App 首屏派生呈现:简报 tile、home lane、timeline run 行与两条 i18n 文案。
没有后端、权限、存储或 CLI 契约改动,也没有默认行为翻转(执行计数本来就应该只数执行的
工作)。可观测性由 browser scenario 提供:断言 zh 与 en 两侧的标题、计数与行内容。
验证证据(均为本地、在精确 head 上执行):npm run smoke:personal-workspace
18 个开发版 scenario 通过,npm run build 后 npm run smoke:personal-workspace-packaged
18 个打包版 scenario 通过;smoke:goal-activity 读模型不变量、smoke:goal-order、
smoke:proposal-recency、smoke:usage-progress、workspace-theme.test.mjs、
personal-workspace-contract.test.mjs 通过;npx tsc --noEmit 通过;
uv run --extra test python -m pytest tests/test_host_thread_activity.py -q 35 passed;
loopx canary premerge --from-git-diff 全部 Direct/Catalog/Risk/Boundary 检查通过。
goal-activity scenario 覆盖四种混合:attached-only 无活动、未领取排队、旧 managed+新
claim、新 managed+旧 claim,并在中英文分别断言标题与计数;同时断言四个 tile 在桌面与
390px 下都不溢出容器。
负向走查:未领取的 Multi Agent Projection 仍显示“已安排 … 执行情况以宿主为准”,
不进入执行计数;503 导致 session owner 不可读时简报显示“暂时读不到执行状态”,
不产生任何 live;宿主观测为 partial/unrecognized 时不会把线程读成 idle。
错误归属:本轮没有任何必需的失败或跳过;之前一次 canary 失败是仓库根目录缺少
npm ci 依赖导致的环境缺项,安装后同一命令通过,与改动路径无关。
语义与 CI 对齐
该改动复用既有词汇而不是新造契约:activity.hostClaimed、activity.running、
home lane id 与 i18n 都是在既有 vocabulary 上扩展一个 claimed 桶,规则本身收敛为
typed 枚举 GoalWorkKind,符合仓库“状态分类规则进入 typed enum / transition helper”
的要求。没有 substring denylist,也没有把机器义务写成“指引”。公开协议名没有声称比
实现更宽的 actor 生命周期:#5081 的宿主观测是只读读取,不授予宿主新的 authority。
默认开 / 关:本 PR 不引入可选能力,因此 default-off 隔离不适用,已核对无 feature
gate 或默认关闭声明。CI 方面按 packet 的 wait_for_ci=false 未查询远程 CI,只记录
本地验证结果与上面的失败归属。
我的整体评价
problem_context 的两个维度:long_horizon 为 accepted_tradeoff(入口修复 + 诚实活动是
独立可用、可逆的阶段,跨 Host 持续执行等仍是计划验收);user_experience 为 improved
(首屏不再把领取说成执行,三种状态可区分且有可达入口)。observable_semantics 为
intentional_change_validated:claim-only Goal 的落点从“正在执行”改为“宿主已领取”,
基线与 head 的对照由同一 scenario 覆盖,没有未披露的漂移。code_volume 为 necessary:
本次修复只加约 200 行、删掉约 43 行,且删除了一份重复的分类规则而不是新增抽象;
compatibility_assessment 为 not_applicable,因为没有跨版本或持久契约变化。
change_proportionality 为 proportionate,authority_semantics 与 default_off_isolation
不适用,semantic_alignment 为 aligned(复用既有词汇,扩展一个桶)。typed_state_rule
以 GoalWorkKind 落地。
残余风险:本评审不认证真实跨 Host 双周期协作、异步 inbox 迁移或首屏 owner approval;
browser fixture 是合成数据,真实宿主 adoption 仍属未来验收。合并前仍需
--check-merge-readiness 与精确 head 的最终重读。整体评价:上一轮唯一阻塞项已在
本 head 修复,完整差异覆盖入口修复、显式配置、诚实活动与 #5081 宿主观测,验证与
阶段边界相称。
English verdict: APPROVE - 40bb86c. The previously
blocking P2 is fixed: a claim-only Goal now leaves the "Running now" brief and the
"执行中" lane through one shared typed rule. 18 development and 18 packaged browser
scenarios, the read-model/contract smokes, tsc, 35 host-thread tests and the premerge
canary pass locally; remote CI was not consulted per the packet.
Problem and result
An ordinary question such as “解释一下 monitor 的工作原理” was intercepted by browser keyword rules and turned into a scheduling preview. Goal creation, Todo changes and assignment had the same parallel intent parser. Free text now reaches the selected conversation intact; the conversation Agent owns interpretation and existing runtime/action review owns execution.
Explicit Goal, task and scheduling controls use editable forms and the existing reviewed preview/apply path. They preserve unsent chat text, validate fields and keep configuration on preview failure. Goal permissions are selected explicitly, preserving the existing workspace-write-on-confirmation default; boundary prose no longer silently sets permissions. Status-only shows a labelled snapshot. Converting a reply to a task preserves its text for review instead of extracting a sentence by keywords.
Product direction
The App-first integration RFC connects the overall roadmap, presentation, semantic handoff, TS migration and golden queries. Managed and attached work should continue inside LoopX with one execution driver, truthful activity, steering/stop/recovery and results returned to the original conversation. Generic async inbox work reuses existing coordination owners; Lark keeps provider-specific transport concerns. Public visual research informs hierarchy, not runtime claims.
This PR qualifies the entry repair, not full Agent task completion, attached-host continuity or live two-cycle team collaboration. Those remain explicit planned acceptance. No backend authority or persisted message contract changes.
Validation
conversation-input: ten Chinese/English/quoted/compound requests, scoped Chat delivery without manufactured actions; explicit forms, invalid input, failed preview recovery, preserved chat draft, Escape and 390px layout.typed-actionsbrowser scenario: Goal creation/apply/readback, task operations, Heartbeat activation gate, scheduling and semantic protected-action preview.chat-recoveryscenario, including images and refresh recovery.Visual review: the initial home/navigation composition is unchanged. Forms appear only after explicit controls; desktop and narrow views retain the Goal identity, necessary fields, review/cancel and visible failures. Whole-viewport checks retain the original conversation behind the form. Browser scenarios generate reproducible visual evidence.
Future-facing pass: retired the browser intent classifier and magic composer templates, kept the structured Todo parser at its owning editor, and reused the existing action service. No second dispatcher or inbox store.