Skip to content

fix: close steer stop and recovery ownership gaps - #740

Merged
wibus-wee merged 2 commits into
mainfrom
fix/steer-stop-recovery-ownership
Sep 16, 2026
Merged

wibus-wee merged 2 commits into
mainfrom
fix/steer-stop-recovery-ownership

Conversation

@wibus-wee

Copy link
Copy Markdown
Member

Related issue

Closes #477
Closes #666

Problem / pressure

Stop could leave a steer holding the session operation queue while preparation, configuration, or the provider verdict remained unresolved. Late steer results could also overwrite a newer activation or make terminal history appear active again. Unknown delivery needed an explicit recovery state without blind replay.

Summary

  • Separate local steer waits from submitted raw ACP/configuration work. Stop and prompt completion release the local lane while the execution owner drains or confirms termination.
  • Track exact steer outcomes and recovery activations in daemon-owned steerTurnStatuses, preserving newer producer activations and preventing late results from rewinding pointers.
  • Add terminal delivery_unknown history/UI state with an explicit resend confirmation warning about possible duplicate work; unknown outcomes are never auto-dispatched.
  • Keep renderer ACKs presentation-only and guard history projections against late terminal-state resurrection.
  • Add bilingual draft Spec updates, implementation Notes, deterministic CLI/shared/components regression coverage, and localized UI copy.

Visual explanation

sequenceDiagram
    participant U as Stop / completion
    participant E as Execution owner
    participant A as ACP adapter
    participant H as History + metadata
    participant W as Watcher / UI

    U->>E: abort local steer wait
    E->>A: keep submitted request owned
    E->>E: drain raw prompt/steer/config or terminate
    A-->>E: applied / not-applied / unknown
    E->>H: project by exact userTurnId
    H-->>W: wake and reconcile
    W->>W: dispatch only proven refusal
    W->>U: explicit warning before unknown resend
Loading

Before / after

Before After
Stop could leave preparation/application waits occupying the steer lane. Local waits end on Stop while submitted work remains owned and drained.
Refused or late steers could reuse producer pointers and affect newer input. Recovery is exact-id and daemon-owned; producer activations are never rewound.
Unknown delivery stayed indistinguishable from an indefinitely pending steer. delivery_unknown is terminal for dispatch and offers only an explicit, warned fresh send.
Delayed ACKs could write processing over terminal history. ACKs are presentation hints; execution-owned projections are guarded by status and identity.

Test plan

  • apps/cli: 263 focused tests passed across AgentClient, dispatch logic/watcher, and execution service.
  • packages/components: 55 focused tests passed.
  • packages/shared: 70 focused tests passed.
  • Components/shared typechecks passed.
  • Oxfmt format checks, oxlint (0 errors), i18n, and public-boundary checks passed.
  • CLI typecheck remains blocked by pre-existing missing exports in the DSH submodule (DEEPSEEK_HARNESS_PROFILE_*, createDeepSeekHarness*).
  • pnpm run docs check still reports the repository's pre-existing two broken DSH usage-test links.
  • Live provider testing was not run.

Context handoff

Instructions for reviewing agents

  • Review focus: Inspect ownership transitions in apps/cli/src/session/session-execution-service.ts, exact-id reconciliation in the dispatch watcher, and the shared history terminal guards.
  • Decisions to challenge: Confirm that unknown must remain non-replayable and that steerTurnStatuses is the right durable activation index instead of rewinding latestUserMsgId.
  • Plausible failures / evidence gaps: Provider-specific late notification ordering and process death before a verdict is persisted still lack live-provider/crash testing.

Authoring context

  • User goal / directives: Close the remaining Stop and orphaned-steer recovery paths for the two linked issues and prepare the change for review.
  • Constraints / non-goals: Preserve the existing applied/not-applied/unknown adapter evidence; do not add a provider receipt ledger or claim universal exactly-once delivery.
  • Risk-bearing decisions: Execution owns raw request draining and steer status projection; the renderer cannot republish daemon-owned recovery or revive terminal history.
  • Destructive or irreversible behavior: Unknown resend creates a new user turn only after confirmation and may repeat provider work; no automatic resend or history deletion was added.
  • Deliberately not done or tested: No live provider, crash-recovery, or DSH submodule repair was included because those are outside this change and the checkout has a known DSH mismatch.
  • Unknowns / confidence: Deterministic boundary tests cover the changed races and passed; confidence is high for local state transitions, with provider/network timing remaining the main residual risk.

@wibus-wee
wibus-wee force-pushed the fix/steer-stop-recovery-ownership branch from 4057899 to 7f2e513 Compare September 16, 2026 02:32
@wibus-wee
wibus-wee marked this pull request as ready for review September 16, 2026 03:47
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-16T03:53:36.420489Z 05c12f7 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05c12f7097

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

);
await this.deps.workspaceDocument.repo.upsertDocMeta?.(roomId, recoveryPatch);
if (pendingUserMsgId)
await this.deps.executionService.acknowledgeSteerTurn(sessionId, pendingUserMsgId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the existing missing-history tombstone

When lastMissingHistoryUserMsgId already protects turn C and a late refused steer B remains in steerTurnStatuses, getPendingUserTurnActivationId selects B after excluding C. This recovery path then replaces the single tombstone with B and acknowledges B here, so C becomes active again through latestUserMsgId; if C's history subsequently arrives, it can be dispatched despite already being reported undelivered, potentially duplicating work after an explicit resend. Keep the existing tombstone and defer B, or represent all missing-history acknowledgements without evicting another turn.

AGENTS.md reference: apps/cli/src/session/AGENTS.md:L33-L37

Useful? React with 👍 / 👎.

.enum([
'pending',
'pending_apply',
'delivery_unknown',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve unknown-delivery semantics for older clients

When a new daemon writes delivery_unknown into a session that is open in an older renderer, that renderer's isSessionHistoryDelivered treats every status except pending and pending_apply as delivered, while its editable-tail planner blocks only pending_apply or _lodyDeliveryKind: 'steer'. Unknown projections intentionally do not set that delivery marker, so an older client shows the turn as delivered and permits Edit & Resend without the duplicate-work warning. Store this outcome in a representation older readers handle safely, or version/gate the durable status before writing it.

Useful? React with 👍 / 👎.

sessionId: SessionIdSchema,
userTurnId: z.string().trim().min(1),
applied: z.boolean(),
recoveryOwned: z.boolean().optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Negotiate recovery ownership before emitting the field

In mixed-version operation, a new daemon now adds recoveryOwned to rejected steer responses, but an older Streams client validates them with the previous strict response schema and therefore converts the whole response to null. For promotion-failed, that prevents the older renderer's only recovery attempt and leaves the proven-undelivered turn stranded in pending_apply; local older clients that bypass this parser can instead run the obsolete pointer-writing fallback. Advertise/version this semantic through MachineMeta.protocolCapabilities and emit it only to compatible callers.

AGENTS.md reference: packages/shared/AGENTS.md:L71-L75

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Timed-out steer remains pending_apply after its target turn is stopped [Bug] Stop during steer can block the next user message

1 participant