fix(chat): restore workspace choices for gated goals - #4363
huangruiteng merged 5 commits into
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.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> # Conflicts: # apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx # loopx/web/chat/asset-retention.json # loopx/web/chat/assets/index-DZ4wXv1S.js # loopx/web/chat/index.html
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
一个已保存的 goal.create 工作区选择门槛在刷新后仍由服务端返回候选,但前端恢复投影把候选丢掉,抽屉误写成宿主确认,用户没有按钮继续创建 Goal。这个 PR 修复的不是单次预览,而是中断后的继续工作:刷新回来仍能选择、重新看预览、自己确认并读回结果。
改动思路
服务端的 gated proposal 继续是权威输入;前端只把其中的 gate.candidates 映射进已有的 WorkspaceActionPreview,并从原 proposal 重建已有选择回调需要的请求。工作区按钮仍走原 onSelectWorkspaceCandidate 路径,选择只触发重新预览,不直接写 Goal。抽屉依候选是否存在展示选择语义,其他宿主门槛和操作执行的分支仍走各自原有逻辑;不新增一套持久状态。
具体改动
确切 head 485ada0ef49d02a49243d0eeb0f4ddf99878a552 相对当前主干只改三处。personal-workspace-page.tsx 抽出候选解析,供即时门槛和持久 proposal 读回共用;恢复时补上候选与稳定的 regenerate 请求。context-drawer.tsx 在确有工作区候选时展示该请求的标题/影响和候选按钮,不再误导为宿主确认。typed-actions.mjs 用打包版与源码浏览器走完整恢复链,验证用户确认之前没有写入,确认后恰好一次写入并在再次刷新后从动作 API 读回已应用回执。
关键代码讲解
workspaceCandidatesFromGate仅接受带字符串workspace_ref与label的候选,让即时门槛和持久 proposal 使用同一解析规则。workspaceProposal在 gatedgoal.create恢复时带出sourceRequest和候选;普通 proposal 不会凭空出现选择按钮。ContextDrawer根据候选决定门槛文案,并沿用选择回调;真正的创建动作仍需用户看过新预览后再确认。- 浏览器用例先刷新,再选择、检验新
workspace_ref、确认并再次刷新读回;这比只断言按钮存在更能证明用户可继续完成目标。
对主干的风险
最大风险是恢复的请求使用错误工作区或绕过二次确认。测试在选中候选后核对新预览的 workspace_ref,并在确认前核对持久写入数不变,确认后恰好增加一次;再次刷新从动作列表读回 applied 和已核验回执。普通 host gate 以及 operation.execute 分支没有改写,原 typed-actions 场景继续运行。验证使用合成动作服务,证明前端请求、状态与 DOM 流程,不替代真实服务部署或最终发布测试。若门槛 payload 契约变化,最小修复应在这一个候选解析点完成并重跑该浏览器场景。最终合入前验证:npm run build 通过;打包版 Personal Workspace 全部 13 个浏览器场景通过(包括本修复及语言回归);源代码与打包版定向 typed-actions、动作 API smoke 均通过;risk-based premerge 选中 5 项全部通过,另有 3 项 diff 检查通过,失败、跳过、手动 hold 均为 0。
我的整体评价
用户从“刷新后卡死”变成可以完成一次有审阅的 Goal 创建,长线工作不会因为页面重载丢失已给出的候选。三文件范围沿用现有 proposal 与选择回调,新增的解析函数有即时/恢复两个真实调用点,没有新的状态权威。源码、打包版、动作接口和确认后读回都通过;最终标签仍需整版验证。我批准当前确切 head 合入。
English verdict: APPROVE - exact head 485ada0; restored workspace gate survives reload, selection regenerates without write, confirmed apply writes once and receipt survives readback.
|
@luw2007, thank you for spotting the reload gap in gated Goal creation and for contributing this fix. We integrated it with the current Workspace flow, verified that a saved proposal can be refreshed, selected, previewed, confirmed once, and read back, then merged it into the release line. I am sorry this PR sat without enough attention from me for so long. I should have reviewed and followed up sooner. Your contribution makes an interrupted Goal-creation flow usable again, and it will be credited in the v1.2.0 release notes. Thank you for your patience and for helping improve LoopX. |
A persisted gated
goal.createproposal retained workspace candidates in the service payload, but the dashboard dropped them after reload. The drawer then mislabeled workspace selection as host confirmation and left the user unable to proceed.The fork branch now projects the stored candidates and a stable regenerate request through the existing workspace action model. Selecting a workspace regenerates the preview without writing; the user can inspect and confirm the resulting Goal creation. Obsolete tracked Chat build assets were dropped because current
mainbuilds the packaged bundle.Validation on the integrated head:
npm run buildpassed TypeScript and both current production builds.typed-actionsbrowser scenarios passed. The regression covers refresh, stored gate recovery, workspace selection, no write before confirmation, regeneratedworkspace_ref, one confirmed write, and applied receipt readback after another refresh.uv run --extra test python examples/loopx-chat-actions-smoke.pypassed.git diff origin/main...HEAD --checkpassed. Exact-head canary/CI status is recorded separately on this PR.Future-facing pass: the existing gate payload, proposal projection, and selection callback already own this flow. No new state authority or framework was introduced.