Skip to content

fix(workspace): turn Agent Todo proposals into confirmable previews - #5266

Open
songoow wants to merge 2 commits into
loopx-project:mainfrom
songoow:codex/workspace-chat-todo-proposals
Open

songoow wants to merge 2 commits into
loopx-project:mainfrom
songoow:codex/workspace-chat-todo-proposals

Conversation

@songoow

@songoow songoow commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Goal And Delivered Outcome

  • Outcome basis / optional anchor: reproduced defect (no issue).
  • Goal/source and gap: the Chat agent's turn prompt asks for proposals: [{kind: "todo", text, priority, rationale}], and the Chat service emits them in turn.completed (and proposal.ready). Since the Personal Workspace promotion (feat(control-plane): promote the personal Agent workspace #3274), dashboard-page.tsx wrote them into a legacy proposalsByContext state that nothing reads. The reply said it had found a reviewable step, but no step could be seen, confirmed or recovered after a reload.
  • Observable before → after: in a Goal conversation where the Agent answers with one Todo proposal, before only the message was shown and the Goal state stayed unchanged; after a 新任务 · 待你确认 card appears under the answer, survives a reload, and 确认并应用 writes the Todo through the existing typed action apply with a read-back receipt.
  • Issue/task and intended base: none; base main.

Scope And Continuation

  • Completed scope and remaining work: complete within this scope.
    • Each Goal-conversation Todo proposal becomes a typed todo.create preview (goal_id, text, priority) created through the existing preview path, so the owner still confirms every write. Cards are left in the conversation (select: false); a single protected-action preview keeps opening the drawer as before.
    • The idempotency key is chat-todo-proposal:<turn id>:<index>, and the action store returns the stored preview for a repeated key, so observing one completion twice cannot offer a duplicate.
    • A recovered Turn stores its previews only for the Session's own Goal. The previous code fell back to the selected Goal or model.goals[0], which would have attached a manager proposal to an unrelated Goal once wired to writes.
    • Manager answers without a target Goal create nothing.
    • Removed the dead legacy card state, its preview/approve/settle handlers and state labels (−160 lines).
  • Slice boundary / successor: the manager-channel hint 请进入要修改的 Goal… is stored in lines, which the workspace shows only for an empty answer, so it is still not visible; a manager proposal has no target Goal to preview. Left unchanged here.

Validation

  • Tested revision: c2f128f
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
static passed npx tsc --noEmit in apps/presentation/dashboard.
integration passed New scenario LOOPX_PERSONAL_WORKSPACE_SCENARIO=chat-todo-proposal node examples/personal-workspace-browser-smoke.mjs (2 runs): manager proposal → no preview; Goal proposal → exactly one todo.create preview with Goal and priority and a Turn-derived key, card visible without opening the drawer, still visible after reload, applied on confirmation. The browser fixture now forwards scripted proposals in turn.completed as the service does; it previously always sent [], which is why no scenario could catch this.
regression_parity passed Failing-before check: with the source change reverted the scenario never sees the proposal card.
integration passed 16 scenarios that pass on main also pass here (including typed-actions, team-plan, steward-journey, which share the preview path and the fixture).
real_entrypoint passed loopx serve-status + loopx chat on an isolated synthetic registry with a stub Codex app-server answering with a Todo proposal: card shown, survived reload, 确认并应用 → apply 200, drawer 操作已完成,结果状态已通过读回验证。, and the Goal state file gained - [ ] [P1] … with a loopx:todo marker.
integration not_run Six scenarios (goal-draft, capability-scope, steward-group-trigger, conversation-input, automation-cadence, steward-model-settings) already fail on a clean main checkout.
  • Coverage and gaps: the send path, the recovery path's Goal ownership rule and the manager negative case are covered; the recovery path's direct store call is covered by code review and the real run only (no scenario reconnects a Turn that carries proposals).

Frontend / Visual Evidence

  • UI impact: changed
  • Before: the answer 我找到一个可评审的步骤。 with nothing under it.
  • After: a 新任务 · 待你确认 card under the answer; opening it shows the existing typed preview with 确认并应用 / 稍后 / 拒绝.
  • States and viewports shown: desktop 1440×900 candidate and applied states (screenshots available on request; not attachable from the CLI).
  • Source data: synthetic
  • Attention review: the card is the step the answer refers to, uses the existing proposal row, and does not open the drawer unasked.

Type of Change

  • Bug fix
  • Test update

LoopX Area

  • Public docs or presentation surface (README, protocols, dashboard)

Technical Direction

  • Direction / acceptance reference, when applicable: Operator surface and IM integration.

Shared-authority RFC fixture impact

N/A

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths.
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

Future-facing refactor pass: applied — the legacy proposal-card path was the second, unrendered owner of Agent Todo proposals; removing it leaves the typed action store as the single owner. Considered having the Chat service create the previews itself on proposal.ready; deferred because it would change a service contract that also serves Lark and CLI clients.

The Chat agent prompt asks for Todo proposals and the service emits them in
turn.completed, but the Personal Workspace wrote them into a legacy
proposalsByContext state that nothing rendered since loopx-project#3274. The reply said
it found a reviewable step while no step could be seen or confirmed.

In a Goal conversation each Todo proposal now becomes a typed todo.create
preview (goal, text and priority) keyed by its Turn, so a repeated
completion reuses the stored preview. The cards appear in the conversation
without opening the drawer, survive a reload, and still require explicit
confirmation. A recovered Turn stores its previews for the Session's own
Goal only, instead of falling back to the selected or first Goal. Manager
answers without a target Goal create nothing.

Remove the unused legacy card state, its preview/approve/settle handlers
and state labels.

Signed-off-by: song <liusongstep@gmail.com>
The browser fixture now forwards scripted proposals in turn.completed, as
the Chat service does. A new chat-todo-proposal scenario requires one
typed preview per Goal proposal that survives a reload and applies on
confirmation, and no preview for a manager answer without a target Goal.

Signed-off-by: song <liusongstep@gmail.com>

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.

1 participant