Conversation
Signed-off-by: luw2007 <luw2007@gmail.com>
Signed-off-by: luw2007 <luw2007@gmail.com>
huangruiteng
left a comment
There was a problem hiding this comment.
审查对象:04cb337690c591bc50eb64b417230d84585b457b
动机
这个 PR 修复 packaged Chat 的一个明确断链:首次收到 workspace_selection_required error 时,页面能从 gate payload 提取 candidates 并显示 workspace 按钮;但 proposal 持久化并在重载后经 workspaceProposal 重建时,projection 丢失了 candidates 和原始 regenerate request。结果是同一 typed gate 在 reload 前后表现不同——重载后被误标为“Host confirmation required”,用户也无法选择 workspace。PR 把现有 fresh-error 语义复用到 stored proposal path,目标和范围都清晰。
改动思路
改动没有发明新的 backend authority:proposal.gate.candidates 仍是服务端候选来源,goal.create 的 normalized_parameters/context 仍是 regenerate request 的基础。新增 helper 统一过滤 candidate shape;workspaceProposal 对 gated goal.create 保存 sourceRequest 和 workspaceCandidates;drawer 有候选时显示真实 proposal title/impact 和按钮,点击仍调用既有 onSelectWorkspaceCandidate,由 action preview 服务重新校验并生成带 workspace_ref 的确认预览。
测试新增一条 persisted proposal 的 packaged browser 路径:加载已有 gated proposal、打开 drawer、点击 Workspace 1、断言新 preview 的 workspace_ref,并拒绝 host-only 文案。这是对真实发布 bundle 的有效回归。不过当前 exact head 的 generated bundle 是基于旧 main 构建的,已经不能和现在的发布资产集合干净合并。
具体改动
personal-workspace-page.tsx抽取workspaceCandidatesFromGate,并让 storedworkspaceProposal复用 candidate parsing;gatedgoal.create还原sourceRequest。context-drawer.tsx在 candidate-bearing gate 上展示 proposal title/impact,已有 workspace button/callback 保持不变。typed-actions.mjs增加 51 行 packaged reload/click/regenerate regression。loopx/web/chat/asset-retention.json、hashed JS 和index.html是本次 source rebuild 的发布产物。
关键代码讲解
workspaceCandidatesFromGate(personal-workspace-page.tsx:544)只接受同时含 stringworkspace_ref/label的 candidate,非法 payload 不进入 UI。workspaceProposal(personal-workspace-page.tsx:560)现在从 persisted gate 恢复 candidates;在 gatedgoal.create上保留 action kind、context、idempotency key、normalized parameters 和 summary,让选择按钮能走既有 regenerate API。- drawer 的 gated renderer(
context-drawer.tsx:980)用 candidate presence 区分 workspace-selection 与 host-only gate;onSelectWorkspaceCandidate仍以 immutable spread 方式覆盖所选workspace_ref,没有绕过 preview/apply gate。 - packaged browser case 以 stored
loopx_chat_action_proposal_v1开局,并同时验证 UI 文案与发给 service 的参数,覆盖了此次缺失的 reload seam。
对主干的风险
[P1] 当前 exact head 的 packaged Chat 资产无法与 current main 集成。 对 origin/main@6c1a4d2cc37280a1d652b4bf67afd9c7d69ce19e 和本 head 执行 merge-tree,出现:
loopx/web/chat/asset-retention.jsoncontent conflict;index-LRc2f6MH.js -> index-DZ4wXv1S.js与 main 删除发生 rename/delete;index-DZ4wXv1S.jsmodify/delete;loopx/web/chat/index.htmlcontent conflict。
GitHub 也把 PR 标为 DIRTY。手工选择一侧的 hash/retention 不能证明 bundle 与合并后源码一致。最小修复是 rebase latest main,在合并后的 source 上重新运行 npm run build:chat,提交新的 hashed asset、retention 和 index,再跑 source + packaged browser smokes。更新 branch 会改变 exact head,因此需要重新审查。
本 head 上我验证了 loopx-chat-actions-smoke 通过,packaged typed-actions browser scenario 也通过;说明源码修复和当前 bundle 自洽。当前没有任何远端 check run,所以除了冲突外仍缺 GitHub required-check 证据。默认/权限边界是隔离的:没有 candidates 时仍走原 host-only 文案;选择 workspace 只 regenerate preview,不授予 apply/write authority。91 additions/27 deletions 的范围与问题相称,future-facing 新抽象不需要扩大。
我的整体评价
源码设计是最小、复用既有 owner、且 packaged test 覆盖到真实回载路径的好修复;我没有发现其 gate/authority 语义本身需要重做。但 PR 当前不能干净进入主干,且生成资产正是产品交付的一部分,不能把冲突当作普通“之后再更新”。因此本轮结论是 REQUEST_CHANGES:rebase current main、从 merged source 重建 bundle、跑完两条 smoke 和 required CI 后,再对新 exact head 复审。
English verdict: REQUEST_CHANGES for exact head 04cb337690c591bc50eb64b417230d84585b457b. The persisted workspace-gate fix and packaged browser path pass locally, but the PR is DIRTY: current main conflicts with its retention file, index, and hashed bundle. Rebase, rebuild build:chat, rerun source/packaged validation, and request review on the new head.
Problem
A persisted gated
goal.createproposal retained workspace candidates in the service payload, but the dashboard projection discarded them after reload. The drawer then mislabeled workspace selection as host-only confirmation and exposed no workspace button.Fix
workspace_refin the packaged browser smokeValidation
npm run build:chatpython3 examples/loopx-chat-actions-smoke.pyLOOPX_PERSONAL_WORKSPACE_SCENARIO=typed-actions LOOPX_PERSONAL_WORKSPACE_PACKAGED=1 node examples/personal-workspace-browser-smoke.mjsloopx canary premerge --from-git-diff(passed; 4 selected, 0 failures, 0 manual holds)Future-facing pass: unnecessary. The existing gate payload and workspace-selection callback already own the contract; this change only preserves that data through reload.