mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-29 11:34:40 +00:00
* feat(serve): persist prompt terminal ledger for cold-load reconciliation Turn terminal events (turn_complete / turn_error) were synthesized by the ACP bridge and published over SSE only, so a prompt that was in flight when the daemon died could never be resolved after a restart: the cold load replay emits transcript chunks and carries no terminal evidence, leaving promptId-keyed orchestrators stuck on "unknown". Each session now owns an append-only sidecar ledger next to its transcript. The bridge appends one in_flight record at prompt admission and one terminal record at the single publishPromptTerminal exit (covering the close/kill/channel-crash/daemon-shutdown flushes) through an injected synchronous sink. Ledger writes are best-effort and never block prompt execution or teardown, and records carry only ids, states, and timestamps — no prompt text, user content, or paths. On a cold session load the serve layer reconciles prompts left dangling by a dead daemon: it classifies the transcript tail with the existing turn-interruption detector and appends a completed (stop reason reconstructed_from_transcript) or interrupted (code daemon_lost) verdict, guarded by an attribution check so an unattributable tail stays unknown (fail-closed). The load response gains an optional promptTerminals field with the trailing 64 terminal records, omitted entirely when the ledger holds no terminal evidence, and archive/unarchive move the sidecar alongside the transcript so evidence survives storage lifecycle. Design: docs/design/2026-08-19-prompt-terminal-ledger-design.md * fix(serve): tighten ledger reconciliation fail-closed semantics and complete sidecar lifecycle Address review findings on the prompt terminal ledger: - reconcile: fail closed on multiple dangling prompts (no synthesized terminal for the newest either); attribute the oldest dangling prompt only when the attribution guard skips settled admissions (fixes the [A if, B if, B cancelled] misattribution veto), the transcript's last write postdates the admission (temporal evidence), and a clean verdict is upgraded to interrupted when the model tail holds any functionCall part, id or not (id-less tool-call guard covering the detectTurnInterruption wire-pairing blind spot) - lifecycle: removeSessionFiles deletes the ledger in both archive states; archive/unarchive move it through a single getPromptLedgerPathForState helper with merge semantics when the destination already exists (append-and-unlink instead of a permanent split); move warnings carry full source and destination paths in both directions - scans: DataProcessor.scanChatFiles and usageHistoryService.rebuildFromSessionJsonl exclude .ledger.jsonl sidecars (the ledger is not a transcript) - writer: appendPromptLedgerRecord seals a torn tail before appending so a torn fragment cannot fuse with (and destroy) the next record - tests: pin the new behavior across multi-dangling fail-closed, settled-then-queued attribution, valid interleave migration, temporal veto, id-less tool-call guard, sidecar lifecycle (move/merge/warn-only delete), torn-tail sealing, queued-admission flush on shutdown, active-prompt and resume load contracts, and ledger exclusion from insight scans - docs: sync the design doc's reconciliation algorithm, lifecycle, and fail-closed invariants * perf(serve): read only the ledger tail for load-response promptTerminals readRecentPromptTerminals ran on every POST /session/:id/load (including attached hot loads) and synchronously read and JSON-parsed the entire ledger — a multi-megabyte event-loop stall for long sessions on the per-request hot path. Add a tailBytes option to readPromptLedgerRecords that reads a trailing byte window (the first window line is always dropped: the window start can tear a line in half). The load path now reads a 256 KiB window, which holds hundreds of ~150-byte records against the 64-terminal response cap; sessions whose ledger outgrows the window return a best-effort trailing subset, which the response contract already allows. * fix(serve): close wrong-terminal attribution classes in cold-load reconciliation Strengthen the reconcile attribution evidence per review round 2: measure the temporal evidence on the same api-history projection the verdict uses, fail closed on a compression checkpoint written after the target's admission, and require the visible tail to postdate every other prompt's settled terminal (FIFO evidence). Also fix a TS18048 narrowing gap in the window test, make the seal test assert the raw file layout, and restructure the window test so the call-site tailBytes wiring is actually observable. * fix(serve): keep ChatRecord import inline so lint-staged cannot merge it into a type-only import * fix(serve): fail-closed reconciliation on millisecond clock equality and deadline-overlapped turns * fix(serve): TOCTOU fence before ledger append and documented residual attribution risk * test(serve): pin the ledger race fixture on the transcript timeline * feat(serve): bind cold-load evidence to the admission via a dispatch marker * test(core): pin the ledger sidecar exclusion in usage rebuild * fix(serve): create ledger sidecar owner-only and fence marker-era compression by position Round-7 review Criticals: - appendPromptLedgerRecord created the sidecar with umask-default permissions (0o644) while the adjacent transcript is owner-only; the ledger now follows the 0o600 convention at creation time. - Marker-bearing admissions fence post-admission compression by marker position instead of wall clock, so a backward clock step cannot hide a compression reset that voids the evidence chain. - Design doc: the residual-risk claim is corrected — the dispatch marker binds ordering, not ownership; the two ownership classes that survive it (recordless predecessor with continued writes, ledger-less cross-client writer) are documented, pending writer identity on transcript records (#9483). --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> |
||
|---|---|---|
| .. | ||
| insight | ||
| prompt-processors | ||
| test-commands | ||
| tips | ||
| BuiltinCommandLoader.test.ts | ||
| BuiltinCommandLoader.ts | ||
| BundledSkillLoader.test.ts | ||
| BundledSkillLoader.ts | ||
| command-factory.test.ts | ||
| command-factory.ts | ||
| command-migration-tool.test.ts | ||
| command-migration-tool.ts | ||
| commandMetadata.test.ts | ||
| commandMetadata.ts | ||
| CommandService.test.ts | ||
| CommandService.ts | ||
| commandUtils.test.ts | ||
| commandUtils.ts | ||
| FileCommandLoader-extension.test.ts | ||
| FileCommandLoader-markdown.test.ts | ||
| FileCommandLoader.test.ts | ||
| FileCommandLoader.ts | ||
| markdown-command-parser.test.ts | ||
| markdown-command-parser.ts | ||
| McpPromptLoader.test.ts | ||
| McpPromptLoader.ts | ||
| notificationService.test.ts | ||
| notificationService.ts | ||
| prompt-stash.test.ts | ||
| prompt-stash.ts | ||
| review-worktree-lease.test.ts | ||
| review-worktree-lease.ts | ||
| saved-workflow-loader.test.ts | ||
| saved-workflow-loader.ts | ||
| setup-github.test.ts | ||
| setup-github.ts | ||
| skill-args-file.test.ts | ||
| skill-args-file.ts | ||
| SkillCommandLoader.test.ts | ||
| SkillCommandLoader.ts | ||
| types.ts | ||
| voice-keyterms.ts | ||
| voice-model.ts | ||
| voice-service.test.ts | ||
| voice-service.ts | ||
| voice-settings.ts | ||
| voice-transcriber.ts | ||