Skip to content

fix: reconcile CLI and Desktop messages by canonical identity - #3803

Draft
Astro-Han wants to merge 7 commits into
mainfrom
fix/canonical-message-projection
Draft

fix: reconcile CLI and Desktop messages by canonical identity#3803
Astro-Han wants to merge 7 commits into
mainfrom
fix/canonical-message-projection

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Refs #3633

This is replacement PR 2 after #3721 and #3716 landed on main.

  • CLI and Desktop generate one stable messageId before ordinary send or Follow Up admission, render that LocalIntent immediately, and submit the same identity through turn.message.submit.
  • Runtime Host remains the only delivery/lifecycle authority. turn_started, steering, followup, outcome_unknown, and message_admission.retracted are Host facts; clients only project their presentation consequences.
  • Canonical transcript and live steering rows reconcile the transient row in place by messageId. Definite rejection and exact retraction remove it; an unknown outcome preserves it for later transcript reconciliation.
  • Sparse transcript replacement preserves message order instead of appending transients after later durable output. Host-started Turn attach, reconnect, terminal refresh, and subsequent canonical advance keep one stable row.
  • Desktop preload now exposes separate contracts for ordinary submitMessage admission and exact-Turn send. WorkHub and Side Conversation no longer inspect a widened ordinary-send result, while fix(desktop): bind Side Conversation events to Host admission #3716 retains its independent fork, Stop, event, and disposal authority.
  • Desktop ordinary sends no longer arm/rebind a renderer-owned live Turn or manufacture an empty Turn. Explicit orchestration still uses exact turn.start.
  • CLI Enter, mid-turn steering, and Alt+Enter Follow Up share one submission seam. The old steer/queueMessage fallback outcome, CLI delivery retry ownership, and obsolete fallback-only tests are removed.

Before: optimistic rows and Host rows could have different identities, stale transcript replacement could erase or reorder the tail, and one Desktop send contract mixed ordinary admission with exact-Turn semantics. After: the submitted message is immediately visible once, stays at one timeline position, and changes only when the Host supplies canonical, definite-rejection, or exact-retraction evidence.

Verification

  • Rebased onto live origin/main at e9d314fd8a15a906d1323c599a1bd300a312d174.
  • Built affected workspaces: @maka/core, @maka/storage, @maka/runtime, @maka/runtime-host, @maka/ui, @maka/desktop main, and maka-agent.
  • Latest post-review round: 49 focused Desktop tests and 271 focused CLI tests passed.
  • Earlier branch validation covered 393 focused tests across Desktop composer/Follow Up admission, queue/remount projection, streaming handoff, UI materialization, CLI transcript/TUI long-running behavior, Runtime Host driver, and root-turn coordinator.
  • Coverage includes active Turn send, immediate Follow Up, two successive sends, canonical transcript advance, stale terminal events, definite rejection, outcome_unknown, exact retraction, sparse replacement ordering, Host-started attach, remount/reconnect queue echo, and cross-client follow-up identities.
  • Desktop preload/main/renderer/Storybook typechecks, scoped Biome, and git diff --check passed. Repository-wide tests were not run locally; CI owns that coverage.

Authority boundaries

  • No Runtime Host storage, settlement, recovery, provider/root proof, or durable lifecycle authority changed.
  • Transcript remains presentation authority; Host queue projection describes only the live queue and is never treated as delivery or deletion proof.
  • CLI and Desktop own LocalIntent presentation only. They do not own delivery, retry, settlement, or recovery.
  • fix(desktop): bind Side Conversation events to Host admission #3716 Side Conversation/Quote Companion fork, admission, Stop, event ownership, and disposal/retry semantics are unchanged.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex authored the implementation, tests, simplification audit, and PR text under human review. Claude was consulted for an independent architecture critique. Material commits retain Generated-by: Codex trailers.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update on bda2eccf2d:

[P2] busy-race fallback leaves ghost transient row

When renderer thinks idle but host is busy (another window/Bot/Goal continuation holds root), startTurn throws session_busy and falls back to submitMessage({messageId: renderer turnId}). Host then creates a new turnId for the turn while the durable message id stays as renderer turnId. The IPC returns host turnId without messageId, and renderer uses host id for optimistic row — never matches durable id → permanent duplicate (transient ghost).

Fix: return messageId on the turn_started branch and prefer it as transient key; add test for busy→turn_started path.

Note: PR is still draft; this review is preparatory and pending re-review after head stabilizes.

简体中文busy 情况下回退导致重复行。

@Astro-Han
Astro-Han force-pushed the fix/canonical-message-projection branch from bda2ecc to f5ff581 Compare August 25, 2026 19:57
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