Skip to content

feat(app): decide User gates in the drawer with one confirmation - #5415

Merged
huangruiteng merged 9 commits into
mainfrom
codex/app-gate-one-click-decision
Oct 1, 2026
Merged

huangruiteng merged 9 commits into
mainfrom
codex/app-gate-one-click-decision

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Delivery brief

Owners could not decide a User gate in the App without copying a CLI command. This change connects the existing request drawer to canonical User completion: choose Approve/Reject/Withdraw, confirm once, and read back the recorded outcome and its effect on the linked work.

The Chat action adapter reuses the existing completion owner, terminal review basis/CAS and replay operation id. TypeScript presentation derives the frame only from a verified matching receipt. A pure module shares the existing decision/resume types with the frontend. Missing or ambiguous dependent information stays unknown.

Behavior and boundaries

  • Approve/reject/cancel now preview through canonical dry run and apply only after confirmation. Approve can resume the eligible linked target; reject/cancel leave it blocked.
  • Defer records no decision and remains held; non-gate approve is rejected at preview. Hard-lease actor attribution, stale preview/regenerate and replay protections remain enforced.
  • App/Chat entrypoints change. CLI and Lark reuse their existing routes; run operator gates remain explanation-only. Recording a decision does not execute bookings, payments or grant unrelated authority.
  • The disposable workspace demo now links each gate to its dependent and decision scope. CLI advance uses the same completion owner, without a second manual status writer. Its manifest becomes v3; preserve old directories and use a new empty directory for this demo.
  • The old browser defer scenario now exercises actual confirmed withdrawal. The registry I/O census updates one source line after adapter wiring.

Validation

  • 17 decision/demo tests and 55 related canonical tests passed; legacy, file/hard-lease and SQLite decision paths, stale/regenerate, invalid input/actor, defer, old proposals and repeat apply are covered.
  • Identical synthetic inputs ran through ChatActionService at immutable main and final head. Main held all decisions; head records the explicit outcomes, persists evidence/owner and preserves idempotent replay. An independent apply oracle fails on the old hold and passes on the fixed head.
  • Packaged App on an isolated real registry: approval records source done/approve and resumes only its linked target; canonical readback and refreshed task page confirm the result.
  • Packaged typed-actions and bilingual attention-details browser scenarios passed. These use synthetic HTTP fixtures; they do not prove external Agent execution.
  • Chat actions smoke, decision-frame smoke, canonical TS completion tests, control-plane typecheck, Python lint/mypy, verified Chat bundle, full semantic vocabulary smoke passed. The final risk-selected premerge gate passed all 19 selected checks, with no failures or manual holds.

The initial semantic check found a stale census line; regenerated and reviewed its single-line change, then reran successfully. No paid models, benchmark jobs or external effects were launched. CI is not polled under the current review policy; merge requires published exact-head review, valid exact-scope quality receipt and current readiness.

Future-facing pass: removed the demo's duplicate status writer and reused the pure existing type owner. Persisted pre-basis proposal compatibility remains intentionally stale/regenerate rather than silently changing replay semantics.

@mergify

mergify Bot commented Oct 1, 2026

Copy link
Copy Markdown

This pull request has merge conflicts with main and cannot be merged
until they are resolved. Please rebase or merge the base branch, @huangruiteng.

Choose the remote for the base repository, not an out-of-date fork.
For a fork clone, first inspect git remote -v; upstream must point
to https://github.com/loopx-project/loopx.git. If it is absent, add it
with git remote add upstream https://github.com/loopx-project/loopx.git.
Then run:

git fetch upstream
git rebase upstream/main
# Resolve each conflict, git add the resolved files, then git rebase --continue.
git push --force-with-lease origin HEAD

For a same-repository clone whose origin points to
https://github.com/loopx-project/loopx.git, use origin instead of
upstream for fetch/rebase. If you prefer merging the base, use
git merge <base-remote>/main and push normally.

Keep the DCO Signed-off-by trailer on every commit when you rebase.
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Oct 1, 2026
huangruiteng and others added 3 commits October 1, 2026 22:53
…tion

gate.resolve approve/reject/cancel previews now dry-run the canonical
complete_goal_todo(role=user, decision_outcome=...) owner, bind the
terminal review basis, and apply with the reviewed operation id. The
receipt carries the provider's decision_outcome and unblock_resume state.
Defer records no decision and stays held as decision_outcome_required.

Signed-off-by: huangruiteng <huangrt01@163.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
compileActionReviewPlan adds a decisionFrame for gate.resolve decisions:
the chosen decision, and after a verified apply the dependent-work effect
projected from the canonical ResumeState. Unknown or mismatched receipts
stay 'unknown'; decisions retry the original reviewed operation.

Signed-off-by: huangruiteng <huangrt01@163.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The request drawer offers Approve / Reject (Withdraw in the menu) for typed
User gates, previews one decision, confirms once, and reads back the
recorded outcome and its effect on waiting work. Removes the CLI copy hint
and the proposal-level reject/defer controls for decisions; run operator
gates and non-gate requests stay explanation-only.

Also repairs browser smokes that drifted on main: the attention fixture
now carries updated_at for the request-content join, and the operation
fixture uses a relative expiry, a complete Lark delivery receipt, and the
visible-timeline placement of awaiting operations.

Signed-off-by: huangruiteng <huangrt01@163.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@huangruiteng
huangruiteng force-pushed the codex/app-gate-one-click-decision branch from 7121ea6 to 8fd55d0 Compare October 1, 2026 14:57
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Rebased onto main (674ee9a). The only conflict, examples/personal-workspace-browser/typed-actions.mjs, was resolved toward main: #5266 already carries the equivalent live-expiry fixture and the visibility fallback. On head 8fd55d0: tsc, build:chat and the workspace contract test pass; tests/control_plane/test_chat_gate_decisions.py passes (15 tests); loopx-chat-actions-smoke and dashboard-attention-details-browser-smoke pass; the packaged typed-actions and confirmed-operations scenarios pass.

@mergify mergify Bot removed the needs-rebase Mergify: the pull request has merge conflicts with its base branch label Oct 1, 2026
Signed-off-by: huangruiteng <huangrt01@163.com>
…types

Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>
Signed-off-by: huangruiteng <huangrt01@163.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

English verdict: APPROVE - the explicit owner decision uses canonical completion, preserves replay and fencing, and has real packaged App readback.

Exact head: d0f9b51. No blocking finding remains after refinement.

动机

普通用户要在 App 内处理一个待批准请求,原来的流程却要求复制命令、切到终端,再回到页面刷新。主干虽有完整的 User completion owner,App 的 gate.resolve 仍停在 canonical_authority_required。本次把已有审批能力接到用户入口,交付的是可持久化、可恢复关联工作的完整决定流程,而不是只增加一个按钮。

改动思路

批准、拒绝和撤回先走 canonical dry run,确认时才执行同一个 completion owner;已有 terminal review basis、CAS 和 operation id 分别防止过期确认与重复副作用。TypeScript presentation owner 从经过验证的 receipt 投影结果,React 只展示请求、决定和后续工作的影响。审批的权限仍来自对这个具体请求的一次明确确认,不授权预订、支付或任意外部执行。CLI 与 Lark 继续使用各自已有入口,无需改设置,也没有新增隐式启用的能力。

具体改动

关键代码讲解

  • ChatTodoActionMixin._run_gate_resolve 把具体 Todo、决定、证据和执行者归属交给 complete_goal_todo(role="user")。_apply_reviewed_todo_edit 复用原有更新路径,处理 provider revision、预览失效及原操作重放,避免 App 另设审批状态机。
  • compileDecisionReviewFrame 只信任已验证且决定一致的 receipt。修复了两个过度推断:缺少 resume state、无法区分目标和决定范围的缺失,都显示 unknown;它们不能证明“没有等待中的工作”。已知的 resumed、其他审批仍未完成、拒绝、撤回各有明确展示。
  • ContextDrawer 允许 User gate 的批准、拒绝和菜单中的撤回,然后只确认一次。确认后显示持久化结果,移除确认按钮。非 gate 请求和运行期 operator gate 保留解释路径;撤回是源请求的 cancel 决定,不再把 proposal defer 冒充成请求决定。
  • demo.workspace.prepare 为六个演示 gate 写入明确的 dependent 和 decision_scope,后续任务携带匹配的 required scopes。advance 删除手动打开任务的第二条写入路径,审批及 CLI 演示统一依赖 canonical owner。演示清单升级到 v3,旧目录保留,重新演示使用新空目录。

另将现有 DecisionOutcome/ResumeState 放进纯类型模块,供 canonical owner、presentation 和 React 共用,避免前端类型检查引入后端 Node 依赖。修复旧浏览器测试仍点击已删除“稍后决定”的漂移,改为实际撤回流程,并检查预览无写入、确认恰好一次及结果回读。

对主干的风险

默认行为变化明确披露:approve/reject/cancel 现在能确认写入;defer 不记录决定,保持 held;对 user_action 的 approve 在预览时拒绝。未注册的 hard-lease actor、过期预览及旧无 basis proposal 不能绕过 canonical owner。没有新的存储 provider、数据库迁移或外部执行授权。

同一合成 fixture 在不可变主干与本 head 经 ChatActionService 执行:主干全部决定停在旧 hold;新 head 在 legacy、file/hard-lease、SQLite 下批准恢复指定目标,拒绝与撤回维持阻塞,证据持久化、owner 不变、重复 apply 不再写入。旧主干运行独立“必须能确认决定”的断言确实失败,新 head 通过。真实打包 App 在隔离 registry 中确认批准,并从 canonical Todo 和刷新后的任务页分别确认 source done、target open;浏览器的其他测试使用合成 HTTP fixture,不把它们称为真实 Agent 执行。

语义与 CI 对齐

复用已有 approve/reject/cancel 和 canonical ResumeState 词表;dependentEffect 是本地只读投影,gate_resolved 是 Chat receipt 的本地结果。语义 advisory 只是有限语法提示,完整 vocabulary smoke 和 premerge 才提供对应验证。按当前 wait_for_ci=false 策略使用本地验证,不查询或等待远端 CI。

我的整体评价

这是一段有价值且范围合理的 App 审批交付:不再让普通用户换入口重输已知信息,同时保留一次对具体决定的确认。持续性方面,关联任务能进入待执行,旧预览有重新生成恢复路径,重试不重复写入;体验方面,请求与决定的影响在同一个抽屉可见,刷新后状态仍成立。限定的未来维护整理已应用到类型 owner 和演示单一 writer,未扩展成权限或 provider 重构。

验证:17 个决定/演示测试、55 个相关 canonical 测试、Chat actions smoke、TS 类型检查与 completion 测试、decision frame smoke、Python lint/mypy、打包构建、打包 typed-actions/attention 浏览器场景、真实 App 的审批与 reload,以及风险选择的 premerge。无未解决阻塞;没有启动付费模型或长期 Agent 执行,不能据此声称外部预订已经完成。所有变化均有可独立回退的提交,私有运行日志留在本地。

Signed-off-by: huangruiteng <huangrt01@163.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

English verdict: APPROVE - the explicit owner decision uses canonical completion, preserves replay and fencing, and has real packaged App readback.

Exact head: f01403b. No blocking finding remains after refinement.

动机

普通用户要在 App 内处理一个待批准请求,原来的流程却要求复制命令、切到终端,再回到页面刷新。主干虽有完整的 User completion owner,App 的 gate.resolve 仍停在 canonical_authority_required。本次把已有审批能力接到用户入口,交付的是可持久化、可恢复关联工作的完整决定流程,而不是只增加一个按钮。

改动思路

批准、拒绝和撤回先走 canonical dry run,确认时才执行同一个 completion owner;已有 terminal review basis、CAS 和 operation id 分别防止过期确认与重复副作用。TypeScript presentation owner 从经过验证的 receipt 投影结果,React 只展示请求、决定和后续工作的影响。审批的权限仍来自对这个具体请求的一次明确确认,不授权预订、支付或任意外部执行。CLI 与 Lark 继续使用各自已有入口,无需改设置,也没有新增隐式启用的能力。

具体改动

关键代码讲解

  • ChatTodoActionMixin._run_gate_resolve 把具体 Todo、决定、证据和执行者归属交给 complete_goal_todo(role="user")。_apply_reviewed_todo_edit 复用原有更新路径,处理 provider revision、预览失效及原操作重放,避免 App 另设审批状态机。
  • compileDecisionReviewFrame 只信任已验证且决定一致的 receipt。修复了两个过度推断:缺少 resume state、无法区分目标和决定范围的缺失,都显示 unknown;它们不能证明“没有等待中的工作”。已知的 resumed、其他审批仍未完成、拒绝、撤回各有明确展示。
  • ContextDrawer 允许 User gate 的批准、拒绝和菜单中的撤回,然后只确认一次。确认后显示持久化结果,移除确认按钮。非 gate 请求和运行期 operator gate 保留解释路径;撤回是源请求的 cancel 决定,不再把 proposal defer 冒充成请求决定。
  • demo.workspace.prepare 为六个演示 gate 写入明确的 dependent 和 decision_scope,后续任务携带匹配的 required scopes。advance 删除手动打开任务的第二条写入路径,审批及 CLI 演示统一依赖 canonical owner。演示清单升级到 v3,旧目录保留,重新演示使用新空目录。

另将现有 DecisionOutcome/ResumeState 放进纯类型模块,供 canonical owner、presentation 和 React 共用,避免前端类型检查引入后端 Node 依赖。修复旧浏览器测试仍点击已删除“稍后决定”的漂移,改为实际撤回流程,并检查预览无写入、确认恰好一次及结果回读。

对主干的风险

默认行为变化明确披露:approve/reject/cancel 现在能确认写入;defer 不记录决定,保持 held;对 user_action 的 approve 在预览时拒绝。未注册的 hard-lease actor、过期预览及旧无 basis proposal 不能绕过 canonical owner。没有新的存储 provider、数据库迁移或外部执行授权。

同一合成 fixture 在不可变主干与本 head 经 ChatActionService 执行:主干全部决定停在旧 hold;新 head 在 legacy、file/hard-lease、SQLite 下批准恢复指定目标,拒绝与撤回维持阻塞,证据持久化、owner 不变、重复 apply 不再写入。旧主干运行独立“必须能确认决定”的断言确实失败,新 head 通过。真实打包 App 在隔离 registry 中确认批准,并从 canonical Todo 和刷新后的任务页分别确认 source done、target open;浏览器的其他测试使用合成 HTTP fixture,不把它们称为真实 Agent 执行。

语义与 CI 对齐

复用已有 approve/reject/cancel 和 canonical ResumeState 词表;dependentEffect 是本地只读投影,gate_resolved 是 Chat receipt 的本地结果。语义 advisory 只是有限语法提示,完整 vocabulary smoke 和 premerge 才提供对应验证。按当前 wait_for_ci=false 策略使用本地验证,不查询或等待远端 CI。

我的整体评价

这是一段有价值且范围合理的 App 审批交付:不再让普通用户换入口重输已知信息,同时保留一次对具体决定的确认。持续性方面,关联任务能进入待执行,旧预览有重新生成恢复路径,重试不重复写入;体验方面,请求与决定的影响在同一个抽屉可见,刷新后状态仍成立。限定的未来维护整理已应用到类型 owner 和演示单一 writer,未扩展成权限或 provider 重构。

验证:17 个决定/演示测试、55 个相关 canonical 测试、Chat actions smoke、TS 类型检查与 completion 测试、decision frame smoke、Python lint/mypy、打包构建、打包 typed-actions/attention 浏览器场景、真实 App 的审批与 reload,以及风险选择的 premerge。无未解决阻塞;没有启动付费模型或长期 Agent 执行,不能据此声称外部预订已经完成。所有变化均有可独立回退的提交,私有运行日志留在本地。

@huangruiteng
huangruiteng merged commit 8813205 into main Oct 1, 2026
7 checks passed
@huangruiteng
huangruiteng deleted the codex/app-gate-one-click-decision branch October 1, 2026 18:37
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.

1 participant