Commit Graph
6049 Commits
Author SHA1 Message Date
_Kerman af3311e1f6 test: close per-file coverage gaps opened by the message-machine refactor
Downstream packages lost the tests that exercised their agent-loop-facing
edges when the loop was rewritten. Restore 100% per-file coverage with
behavior tests through public seams: llm-retry config validation and
cancellation races, goal replay drift/staleness/teardown edges, plan-mode
disposed-flush, workspace-context empty-change commits, api-proxy
synchronous send failures, acp-snapshot spill-path extraction and refresh
write-back, ACP injection-triggered turns, cli-demo and tui inbox
lifecycle edges, and agent-loop retry/settlement/lifecycle branches.
The only source changes are narrowly-justified v8 ignore annotations on
invariant guards and one redundant-guard removal (workspace-context).
2026-07-26 11:49:19 +08:00
_Kerman 8432c68b6a docs: sync bilingual pairs and static gates with the message-machine refactor
The branch edited the English side of 17 doc pairs without their Chinese
counterparts. Bring every .zh.md up to date (loop.ts references now point
at agent.ts, send/steer/inject unification, context/message folded into
user/message, HookContext replaced by UserMessageData in tool decision
blocks, plan/skill delivery moved to agent/step) and re-record the pairing
hashes. Deduplicate type-equiv.manifest.json entries duplicated by the
master merge and drop entries for symbols the refactor removed. Complete
the missing JSDoc on extractSnapshotSpillPaths.
2026-07-26 11:48:06 +08:00
_Kerman 4284530c7e fix(plan-mode): flush pending mode only at the in-turn agent/step seam
Prompt admission runs before any turn opens, so the agent/prompt-submit
flush listener could only ever append plan/mode outside an open turn —
invariant-vetoed and retried at agent/step when the session invariant is
mounted, silently out-of-turn in the durable log when it is not. agent/step
fires inside the open turn before every request derivation (including
turn 1 step 1), so it is the sole flush point and no behavior is lost: a
pending idle switch lands as the first step's in-turn plan/mode. Tests pin
the new contract: admission never appends; the first step boundary flushes.
2026-07-26 11:47:39 +08:00
_Kerman 2154034a0b fix(agent-loop): let a concluding tool result beat same-step steering
concludesTurn is terminal: a completed step now carries the concluded fact
to the driver, which ends the turn immediately instead of letting steering
that arrived during the tool batch (or from an agent/stopping listener)
reopen it. The steering is not lost — it drained into the log as
steering/message before the decision point, so it feeds the next turn's
request. Also annotates the driver's unreachable invariant guards
(exhaustiveness default, abort-slot ownership) for the coverage gate.
2026-07-26 11:47:25 +08:00
imccyu 08251eb180 Merge branch 'master' into worktree-ws-more 2026-07-26 11:20:05 +08:00
Tianyi Cui 33df5d060a Merge branch 'code-mode-ui/shiki' into code-mode-ui/trajectory-spans 2026-07-26 10:57:20 +08:00
Tianyi Cui d5bf00b300 docs: regenerate catalogs and register CodeDispatchLog type-equiv on the stacked tree
The static CI gates run per-branch on the merged tree: regen the cordis
catalog/api, config, persistence, and doc-graph outputs that PR3/PR4's
source changes shifted, and add the CodeDispatchLog manifest entries for
the tools.md pair's new type-equiv block.
2026-07-26 10:56:57 +08:00
Tianyi Cui e715d6cc59 feat(web): Code Mode sub-calls in the trajectory and waterfall views
Trajectory: the layout fold interleaves one subtool cell per sub-dispatch
after its parent Tool cell (assistant-block calls, orphan results, and
running calls alike), indexes sequential across the interleave; settled
durations come from the start/settle pair, running sub-calls show the
em dash. New Sub tag (business tint) + 28px indent.

Waterfall: deriveSubSpans folds the dispatch index into per-turn lanes
with REAL wall time — each parent's window is first start → last settle
and every lane's offset/width is its fraction of it, so parallel
sub-calls visibly overlap; running lanes extend to the window end at
reduced opacity. Lanes draw under the owning turn row.

Both views read codeDispatches through the standard snapshot hook; no
new wire data, replay renders identically to live. Specs pin interleave
order, durations, the running arms, window fractions, and the rendered
lane.
2026-07-26 10:48:33 +08:00
Tianyi Cui 104e83109f fix: address review — plain fences while streaming
ds-review-bot finding: a growing fence retokenized on every chunk
(quadratic main-thread work). MarkdownText gains a streaming flag —
the streaming partial renders fences through the plain arm and the
finalize swap highlights once; AssistantMarkdown threads its existing
flag. (The zh Agent Note pair the review also flagged landed earlier
on this branch.) New spec pins plain-while-streaming and
highlighted-after-finalize.
2026-07-26 10:43:52 +08:00
Tianyi Cui 173901033b Merge branch 'code-mode-ui/dispatch-spill' into code-mode-ui/shiki 2026-07-26 10:40:40 +08:00
Tianyi Cui 7b58346b3c fix: address review — log shaping off the program-facing path
ds-review-bot finding: awaiting shapeDispatchLog before resolving the
binding let a slow spill backend delay the program and occupy a
dispatch slot. The settle now resolves the program immediately; the
shaped append runs as tracked side work (logWork) drained at run
settlement, so every tool/code-dispatch event still lands inside the
open turn. New spec pins the contract: with a hung spill backend the
second dispatch starts and the program completes both calls, and both
settle events land once released.
2026-07-26 10:40:03 +08:00
Tianyi Cui f02641e8e5 Merge branch 'code-mode-ui/live-parallel' into code-mode-ui/dispatch-spill
# Conflicts:
#	packages/core/tools/src/code-mode.ts
2026-07-26 10:38:50 +08:00
Tianyi Cui a786911293 fix: address review — non-vacuous error assertion, package contracts
ds-review-bot findings: the browser e2e's error-state check now requires
at least one error sub-row (was >= 0); the runtime README documents the
codeDispatches snapshot contract and the ui-conversation README the code
variant + nested sub-row semantics.
2026-07-26 10:36:35 +08:00
Tianyi Cui 5bf4d573f7 fix: address review — staged scheduler, start-time reclassification, catalogs
ds-review-bot findings on the parallel bridge: sub-dispatches now run
through the registry's staged scheduler view (the loop's own seam) —
ordered prepare (pre-execute/guards) at submission-ordered start time,
only the dispatch/body stage overlapping, and a head-of-line commit
cursor running post-execute, context deferral, and the settle event in
submission order (new spec pins post order + context order under
out-of-order completion). Queued dispatches reclassify via
executionMode() immediately before starting, so a registry mutation
while queued flips them exclusive (native lazy-reclassification
semantics). Config and tool catalogs regenerated; the tool-catalog
metadata now names the start/settle pair and the scheduling contract.
2026-07-26 10:33:48 +08:00
Tianyi Cui b25a60c0a6 Merge branch 'code-mode-ui/dispatch-spill' into code-mode-ui/shiki 2026-07-26 10:22:15 +08:00
Tianyi Cui 52f6de60d6 Merge branch 'code-mode-ui/live-parallel' into code-mode-ui/dispatch-spill 2026-07-26 10:21:50 +08:00
Tianyi Cui 86819ecbbb Merge branch 'code-mode-ui/web-ui-v1' into code-mode-ui/live-parallel
# Conflicts:
#	.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml
2026-07-26 10:21:01 +08:00
Tianyi Cui e542a89db7 Merge branch 'code-mode-ui/host-foundation' into code-mode-ui/web-ui-v1 2026-07-26 10:20:06 +08:00
Tianyi Cui 99a9fa8509 fix: address review — python smoke caller, contract prose, fixture header hygiene
ds-review-bot findings: the packaged Python runtime smoke's scripted
run_code call gains the required description; the ToolDefinition JSDoc
and the Code Mode foundation note (both languages, pair re-recorded) now
state both required parameters; the cordis-dynamic-toolchain fixture's
request/header line is re-compacted so the header-scrub hygiene guard
passes (my earlier patch had re-spaced it). The TUI terminal fixture was
already regenerated from keyless replay in the previous commit.
2026-07-26 10:18:53 +08:00
Tianyi Cui bb3dc50a4b feat(web): shiki syntax highlighting for code surfaces
One highlighter for the client: a synchronous fine-grained shiki core
(JS regex engine, no WASM) in ui-primitives with an explicit grammar
allowlist (typescript, shellscript, json — aliases resolve, unknown
languages take a geometry-identical plain arm). The shared CodeBlock
component owns both arms; markdown fences, the run_code expanded
program body (typescript), and the details panel Input (json) all
route through it. Token colors live in a new ui-theme shiki.css sheet
as --shiki-* custom properties (light/dark blocks), wired through the
shell's base.css chain — tokens-only styling holds; shiki's generated
span tree is the sanctioned innerHTML path (static output, no user
HTML). jsdom specs pin token spans, aliases, both fallbacks, and the
fence route; the built-bundle snapshot asserts the highlighted program
under the code row.
2026-07-26 09:52:37 +08:00
Tianyi Cui 4987261d55 feat(spill): bound the durable copy of Code Mode sub-dispatch results
New tools/code-dispatch-log waterfall (run via registry.shapeDispatchLog,
contained — a throwing listener falls back to the unshaped content) lets
listeners reshape the tool/code-dispatch event's content before the
bridge appends it. dsh-spill-policy registers a second arm sharing the
model-facing arm's exact replacement pipeline (same maxInlineBytes cap,
preview + locator, within-cap invariant, best-effort fallbacks), with
artifacts labeled dispatch under the sub-call id. The program's value is
untouched; read sub-calls ARE bounded (a log copy is not model context,
and read produces the biggest logs). Resolves the tools README's
uncapped-dispatch-log Known Limitation.
2026-07-26 09:01:03 +08:00
Tianyi Cui 2d2bf87192 fix(process): CI follow-ups — built-bin closures and gate-safe path prose
- The acp-demo and cli-demo built-bin e2e consumers link a pruned package
  closure; add process/process and process/process-local so the built bins
  boot now that bash-local injects ctx.processes.
- verify-package-paths scans backticked packages/* references, so the
  timeout-note's historical mention of the deleted run.ts path is reworded
  (en+zh, both sides now carry the same historical-vs-current split; the
  earlier zh edit had silently missed its match); pair re-recorded.
2026-07-26 08:17:18 +08:00
Tianyi Cui fb0d4ba564 fix(process): close review gaps from Codex round 1
- Mount LocalProcessManager in the sandbox e2e compositions (bwrap/landlock/
  seatbelt + the spine multi-project e2e) and add the spine demo's
  dsh-process-local devDependency, so SandboxBashExecutor's new inject
  resolves when those suites are enabled.
- Extend the Windows test/coverage skip to packages/process/* — the POSIX
  process-group suite moved there from packages/bash.
- Update the stale disposal contract: the bash seam JSDoc, BashProcess JSDoc,
  and core bash doc (en+zh) now state that composition teardown (the process
  manager's disposal) owns kill-and-await, and an executor-only reload leaves
  background processes running.
- Record ctx.processes in the architecture capability table and extension map
  (en+zh) and the root AGENTS.md layout tree; reword the timeout-library note
  so it describes where the plumbing and classification live today.
2026-07-26 07:54:42 +08:00
Tianyi Cui 1f0b8cc4c1 Merge tasks-seam review fix into the process-seam branch 2026-07-26 07:28:02 +08:00
Tianyi Cui 3b91135923 fix(tasks): fail loud when the abstract seam is mounted directly
Review finding (Codex round 1): abstract erases at runtime and
@deepseek-ai/dsh-tasks used to be the mountable registry, so a stale
composition row would register a ctx.tasks with no method implementations
and fail far from the misconfiguration. The seam constructor now rejects
direct mounts with a load-time pointer at dsh-tasks-local; the seam suite
pins the fence, the Agent Note cost paragraph records the actual behavior,
and the stale tool-pty README requirement line names the implementation
package.
2026-07-26 07:25:47 +08:00
Tianyi Cui e20545c9ee fix(tasks): fail loud when the abstract seam is mounted directly
Review finding (Codex round 1): abstract erases at runtime and
@deepseek-ai/dsh-tasks used to be the mountable registry, so a stale
composition row would register a ctx.tasks with no method implementations
and fail far from the misconfiguration. The seam constructor now rejects
direct mounts with a load-time pointer at dsh-tasks-local; the seam suite
pins the fence, the Agent Note cost paragraph records the actual behavior,
and the stale tool-pty README requirement line names the implementation
package.
2026-07-26 07:20:45 +08:00
Tianyi Cui 0d6bfd8856 refactor(process): split the process manager out of the bash executor
New process/ capability family: @deepseek-ai/dsh-process owns ctx.processes —
abstract ProcessManager.spawn(spec) over a fully-explicit ProcessSpawnSpec —
plus the shared DSH_* managed-environment and CollectedOutput vocabulary;
@deepseek-ai/dsh-process-local carries the former bash-local run.ts plumbing
(detached groups, tail-keep spill-backed output, credential scrub, kill
escalation, kill-and-join disposal) with no config of its own.

dsh-bash-local becomes a consumer: it keeps command defaulting, the fused
deadline timedOut/aborted classification, the model-friendly terminal env
(now merged through the ordinary env channel), and the [stderr]-marked
background read merge, and spawns through ctx.processes. Background-process
lifetime moves to the manager, so an executor reload no longer kills live
background work; a background spawn failure is injected once into the read
path instead of being buffered as fake stderr. dsh-bash re-exports the moved
vocabulary so bash consumers keep one import root; dsh-bash-sandbox only
redeclares the inherited inject.

Every composition loading a bash executor now loads dsh-process-local (CLI,
examples, python bundled runtime, create-sdk bash feature, inline test
configs).
2026-07-26 06:59:01 +08:00
Tianyi Cui 8a79679489 feat(tools): live dispatch lifecycle + native-contract parallel sub-calls in Code Mode
The bridge replaces its serialization queue with a pool that reuses the
native concurrency contract: submissions classify through
registry.executionMode (fail-closed isConcurrencySafe), start strictly in
submission order, overlap up to the validated maxParallelSubCalls config
(default 10; 1 restores serial), and exclusive calls drain the pool, run
alone, and bar later calls. Each started sub-call logs a
tool/code-dispatch-start event at pool entry; the existing
tool/code-dispatch settles the pair (started ⇔ settles exactly once;
abandoned queued calls log neither). SDK prompt guidance now states the
true Promise.all contract — re-recorded across every code/both-mode
snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the
required description arg).

Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts
land the running shape (rows wear the native running ring), settles
replace in place preserving start order, callTime pairs to the start
time. Fixture emits start/settle pairs; jsdom pins the running sub-row;
runtime specs pin in-place settlement and out-of-order completion.
2026-07-26 06:02:36 +08:00
Tianyi Cui 698b391bd6 refactor(tasks): split the task registry into seam and local implementation
The tasks/ family now matches the capability-seam shape: @deepseek-ai/dsh-tasks
keeps the abstract TaskService (ctx.tasks contract, vocabulary types, snapshot
invariant companion) and the new @deepseek-ai/dsh-tasks-local carries the
process-local registry (LocalTaskService: in-memory store, settlement,
owner-cleanup effects, teardown, TASK_WAIT_TIMEOUT). Compositions and test
harnesses now load dsh-tasks-local; producers, TaskKindMap merges, and
dsh-tool-tasks keep importing the seam only.

Producer misconfiguration diagnostics name dsh-tasks-local because loading the
implementation is the fix. The registry behavior suite moves to tasks-local;
the seam keeps a stub-subclass registration test and the probe-based invariant
suite.
2026-07-26 05:13:39 +08:00
Tianyi Cui 226dc7a249 docs: translate remaining READMEs 2026-07-26 05:06:39 +08:00
Chinesezjc 99805b6340 fix(gui): merge adaptations to master's apiproxy defaults and deleted test hook
The origin/master merge introduced a required workspaceRoot on
ApiProxyDefaults and deleted the ui-conversation test hook.ts helper.
Add workspaceRoot to the new todo-projection api-proxy test and bind
the todo-panel spec's selector hook via bindSnapshotSelector directly,
matching the sibling specs.
2026-07-26 04:30:58 +08:00
imccyu a04ce7afbb refactor(client): shared declaration-aware registration deferral
The five settings-surface registrants carried near-identical
spec-check/ledger-judge/subscribe scaffolding (three jscpd clones);
ui-slots now owns deferRegistration() — ledger-judged presence, refresh
for registrant-localized labels, one-call disposal — and every
registrant shrinks to its registration body.
2026-07-26 04:15:56 +08:00
Tianyi Cui 07770f90f9 Merge remote-tracking branch 'origin/master' into web-e2e-interactions
# Conflicts:
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md
#	.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md
#	apps/web/tests/scaffold.ts
2026-07-26 04:10:37 +08:00
Tianyi Cui 13f7c62318 feat(web): render Code Mode sub-calls as native rows nested under the run_code row
The client indexes tool/code-dispatch events into
ConversationSnapshot.codeDispatches (parent callId -> ToolResultNode-shaped
sub-calls; live mux and history replay build the identical index). ChatView
renders each run_code parent as the new code variant (description summary,
program as the expanded monospace body) with its sub-dispatches as
always-visible indented rows — every sub-row dispatches through the SAME
keyed conversation.chat.toolview hole with the same GenericToolCard
fallback, so custom registrations (bash sample) take over sub-rows exactly
as top-level rows. The details panel resolves sub-callIds to full logged
args and complete output through the native path.

Evidence: fixture turn 64 + built-bundle jsdom snapshot, real-machinery
jsdom suites (nesting, error state, details, running parent, reference
stability), and a recorded code-mode browser e2e round (keyless replay +
aria golden). Scaffold gains a toolsMode patch knob.
2026-07-26 04:02:38 +08:00
Chinesezjc 012b712820 Merge remote-tracking branch 'origin/master' into web2-todo
# Conflicts:
#	.agents/notes/implemented/feature/2026-06-29-todo-write-tool.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-29-todo-write-tool.md
#	.agents/notes/implemented/feature/2026-06-29-todo-write-tool.zh.md
#	apps/web/tests/smoke-fixture.e2e.ts
#	examples/acp-agent/tests/snapshots/model-switching/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/plan-mode/tool-schemas.expected.json
#	packages/client/runtime/README.md
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/src/client/sessions/conversation.ts
#	packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx
#	packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx
#	packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx
#	packages/client/ui-conversation/tests/chat-view.spec.tsx
#	packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx
#	packages/client/ui-conversation/tests/skeleton-branches.spec.tsx
#	packages/client/ui-conversation/tests/skeleton.spec.tsx
#	packages/todo/tool-todo/README.md
2026-07-26 03:33:57 +08:00
Tianyi Cui 90d91c3cf9 docs(llm-replay): cover the patches form in the overrideFile contract
The ReplayConfig.overrideFile JSDoc still described only whole-script
replacement; it now names both sidecar forms and links ReplayOverrideDoc
(config catalog regenerated: source line shifted).
2026-07-26 03:31:21 +08:00
Tianyi Cui bb0bcf6250 fix(llm): honor a carried failure snapshot on any Error
markLlmAdapterFailure gated the own-`failure` data property on instanceof
HarnessError, which drops the validated facts exactly when class identity is
lost — two copies of this package in one process (e.g. a source-plane replay
harness throwing into a lib-plane boot) make the replay-thrown LlmError's
SERVER/AUTH code arrive as UNKNOWN and defeat llm-retry's retryable-code
match. The snapshot is already validated field-by-field and cross-checked
against the error's own code, so honor it on any Error.
2026-07-26 03:28:13 +08:00
Tianyi Cui 2e52c0670c feat(llm-replay): indexed override patches for error injection
The override sidecar now accepts { patches: [{ at, entry }] } alongside the
legacy whole-script ReplayEntry[] replacement: the JSONL-derived script is
kept and only the named call indexes are swapped (at == length appends, for
a retry attempt following an injected transient throw). Out-of-range or
non-integer indexes fail loud with the derived length in the diagnostic.
This is the mock-LLM error capability the web e2e scenarios drive: 'call N
throws AUTH/SERVER, everything else replays as recorded'.
2026-07-26 03:26:41 +08:00
imccyu c5d323e8e5 fix(gui): follow-ups for the feature-owned settings surfaces
The ui-layout apply bench provides a real LocaleService before the
theme plugin boots (ui-theme now injects slots/locale to register its
Appearance row); drop locale's unused clsx dependency.
2026-07-26 03:22:35 +08:00
Chinesezjc 2d04fb977b Merge remote-tracking branch 'origin/master' into fix/intent-draft-sync-echo 2026-07-26 03:05:58 +08:00
Tianyi Cui 5e4026d768 test(llm-mock-server): await disconnect outcome 2026-07-26 02:59:19 +08:00
imccyu 0c9f310008 test(gui): apply-level suites for the feature-owned settings rows
Locale and ui-theme apply coverage on a real Context + SlotCore:
dictionary assembly, declaration-aware registration both ways,
inject-time getter re-sync, service write-back through the event flow,
HMR collapse recovery, and teardown reclamation. Four settings-surface
packages sit at full per-file coverage.
2026-07-26 02:57:54 +08:00
imccyu a3ee5dd8a0 docs(gui): regenerate graphs and sync the note's English pair
Generated docs follow the ui-models rename and the removed
ui-settings-general package; the English note side picks up the
feature-owner self-registration doctrine.
2026-07-26 02:54:29 +08:00
imccyu 23a60ade67 refactor(gui): features register their own settings surfaces
Settings collaboration direction (recorded in the note): the shell only
provides composition faces — feature plugins register themselves. The
General section moves into the ui-settings shell (order 0, skeleton
rows) and declares the settings.general.item list slot; locale registers
the Language row and ui-theme the Appearance row (each with its own
store mirror, dictionaries, and ledger-judged deferral); the
ui-settings-general package is gone. ui-settings-models becomes
ui-models — a feature package that contributes its Settings section
rather than a settings-owned satellite. The item-slot SlotMap entry is
authored in the ui-settings contract and repeated verbatim in
locale/ui-theme (reference-cycle avoidance; declaration merging keeps
the copies identical).
2026-07-26 02:51:36 +08:00
Tianyi Cui d60dea9f55 feat(tools): run_code description param + native-parity dispatch logging + web code-mode seam
run_code gains a required bash-style description parameter: presentCall
titles the card with it and moves the program to rawInput, so every
surface gets a readable label. tool/code-dispatch now logs each
sub-call's complete content/isError (the tool/result vocabulary),
replacing the bounded resultSummary and deleting the summarize/cwd
machinery — a UI renders sub-calls through the identical path as native
results. The dsh config tree mounts the worker code runtime and reads
DSH_TOOLS_MODE (temporary seam until per-session mode selection lands).

Session format stays v0 (pre-release churn). Code-mode ACP/TUI fixtures
re-recorded; TUI presenter pin refreshed; catalogs regenerated. Keyless
web smoke pins the code-mode wire contract (tools=[run_code] + SDK
prompt section).
2026-07-26 02:43:34 +08:00
Tianyi Cui a439ed4e43 Merge branch 'master' into fix-update-builderror 2026-07-26 02:39:35 +08:00
imccyu b2d8815a34 test(llm-mock-server): poll for the client-closed outcome
The fixed 5ms sleep raced slow CI runners (the server observes the socket
close asynchronously); vi.waitFor polls until the outcome lands.
2026-07-26 02:29:13 +08:00
imccyu 6cc68baddf Merge remote-tracking branch 'origin/master' into worktree-ws-more 2026-07-26 02:26:27 +08:00
Chinesezjc 6105cc17e4 Merge remote-tracking branch 'origin/master' into fix/intent-draft-sync-echo 2026-07-26 02:23:30 +08:00
Chinesezjc 50cf90bf6e fix(web): flush the intent draft into the list snapshot in the same tick
The hero composer renders the frontend Session Intent's retained prompt
from the sessions list snapshot, but updateIntent only reached that
snapshot through the intent watch's microtask-deferred markDirty. React
therefore rolled the controlled textarea back during the change tick,
which corrupted IME composition (Pinyin "nihao" committed fragments
like "nnini hni hani hao你好") and jumped the caret on plain typing.

SessionManager.updateIntent now calls notifyNow after
updatePendingPrompt, per the Notifier channel rule for direct echoes of
user gestures. The workspace-flow snapshot helper asserts the same-tick
echo instead of waiting for it, and a runtime unit test pins the
contract at the manager seam.
2026-07-26 02:21:58 +08:00