瀏覽代碼

Remove the stdio agent

Tianyi Cui 2 月之前
父節點
當前提交
4cadf096ce
共有 100 個文件被更改,包括 824 次插入 和 1185 次删除
  1. 9 8
      .agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md
  2. 2 0
      .agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md
  3. 2 2
      .agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md
  4. 1 1
      .agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md
  5. 1 1
      .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md
  6. 3 3
      .agents/notes/implemented/feature/2026-06-25-ask-user-question.md
  7. 1 1
      .agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md
  8. 1 1
      .agents/notes/implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md
  9. 2 2
      .agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml
  10. 4 4
      .agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md
  11. 4 4
      .agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md
  12. 4 2
      .agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md
  13. 1 1
      .agents/notes/implemented/process/2026-07-06-node-engine-floor.md
  14. 2 0
      .agents/notes/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md
  15. 6 0
      .agents/notes/implemented/simplification/2026-07-20-remove-stdio-agent.i18n.yaml
  16. 44 0
      .agents/notes/implemented/simplification/2026-07-20-remove-stdio-agent.md
  17. 44 0
      .agents/notes/implemented/simplification/2026-07-20-remove-stdio-agent.zh.md
  18. 2 2
      .agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml
  19. 1 1
      .agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md
  20. 1 1
      .agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.zh.md
  21. 2 2
      .agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.i18n.yaml
  22. 6 6
      .agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.md
  23. 6 6
      .agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.zh.md
  24. 1 1
      .agents/skills/dsh-pre-push-checks/SKILL.md
  25. 8 9
      AGENTS.md
  26. 2 2
      README.i18n.yaml
  27. 2 3
      README.md
  28. 2 3
      README.zh.md
  29. 1 1
      docs/architecture.md
  30. 7 6
      docs/capability-seams.md
  31. 42 82
      docs/config-catalog.md
  32. 2 2
      docs/cookbook/extension-cookbook.i18n.yaml
  33. 1 1
      docs/cookbook/extension-cookbook.md
  34. 1 1
      docs/cookbook/extension-cookbook.zh.md
  35. 1 1
      docs/core-data-structures/user-interaction.md
  36. 2 2
      docs/development.i18n.yaml
  37. 4 10
      docs/development.md
  38. 4 10
      docs/development.zh.md
  39. 6 6
      docs/event-producer-consumer.md
  40. 0 1
      docs/graph-atlas.md
  41. 3 3
      docs/i18n/translation-prompt.md
  42. 14 22
      docs/module-graph.md
  43. 1 1
      docs/postmortem/0001-acp-default-export-drops-inject.md
  44. 2 2
      docs/tool-catalog.md
  45. 2 2
      docs/user/develop/practice/llm-adapter.i18n.yaml
  46. 4 2
      docs/user/develop/practice/llm-adapter.md
  47. 4 2
      docs/user/develop/practice/llm-adapter.zh.md
  48. 2 2
      docs/user/guide/config.i18n.yaml
  49. 9 3
      docs/user/guide/config.md
  50. 9 3
      docs/user/guide/config.zh.md
  51. 2 2
      docs/user/guide/index.i18n.yaml
  52. 4 2
      docs/user/guide/index.md
  53. 4 2
      docs/user/guide/index.zh.md
  54. 2 2
      docs/user/guide/quickstart.i18n.yaml
  55. 12 59
      docs/user/guide/quickstart.md
  56. 14 61
      docs/user/guide/quickstart.zh.md
  57. 1 1
      examples/AGENTS.md
  58. 8 16
      examples/README.md
  59. 1 1
      examples/cordis-agent/README.md
  60. 6 6
      examples/cordis-agent/composition.md
  61. 3 3
      examples/cordis-agent/cordis.yml
  62. 9 13
      examples/cordis-agent/tests/keyless-smoke.e2e.ts
  63. 12 21
      examples/echo-agent/README.md
  64. 7 10
      examples/echo-agent/composition.md
  65. 4 21
      examples/echo-agent/cordis.yml
  66. 1 1
      examples/echo-agent/package.json
  67. 16 22
      examples/echo-agent/tests/echo.e2e.ts
  68. 2 3
      examples/echo-agent/tests/fixtures/context/time-context/cordis.yml
  69. 16 0
      examples/echo-agent/tests/fixtures/context/time-context/driver.ts
  70. 1 1
      examples/headless-agent/README.md
  71. 0 0
      examples/headless-agent/tests/code-mode.e2e.ts
  72. 0 0
      examples/headless-agent/tests/coding-task.e2e.ts
  73. 0 0
      examples/headless-agent/tests/compaction.e2e.ts
  74. 0 0
      examples/headless-agent/tests/full-loop.e2e.ts
  75. 1 1
      examples/headless-agent/tests/harness.ts
  76. 0 0
      examples/headless-agent/tests/resume.e2e.ts
  77. 0 0
      examples/headless-agent/tests/todo-write.e2e.ts
  78. 2 1
      examples/package.json
  79. 0 68
      examples/repl-agent/README.md
  80. 0 33
      examples/repl-agent/code-mode.cordis.yml
  81. 0 91
      examples/repl-agent/composition.md
  82. 0 143
      examples/repl-agent/cordis.yml
  83. 0 7
      examples/repl-agent/package.json
  84. 0 27
      examples/repl-agent/tests/code-mode-keyless-smoke.e2e.ts
  85. 0 28
      examples/repl-agent/tests/keyless-smoke.e2e.ts
  86. 6 6
      examples/tui-agent/README.md
  87. 10 9
      examples/tui-agent/code-mode.cordis.yml
  88. 70 7
      examples/tui-agent/composition.md
  89. 109 28
      examples/tui-agent/cordis.yml
  90. 3 5
      examples/tui-agent/tests/fixtures/tui-scripted.cordis.yml
  91. 127 0
      examples/tui-agent/tests/pty-harness.ts
  92. 36 142
      examples/tui-agent/tests/tui-keyless-smoke.e2e.ts
  93. 2 6
      knip.json
  94. 3 4
      package.json
  95. 1 1
      packages/README.md
  96. 23 79
      packages/context/time-context/tests/time-context.e2e.ts
  97. 1 1
      packages/cordis/tool-cordis/src/sandbox.ts
  98. 16 16
      packages/core/agent-loop/tests/config-session-id.spec.ts
  99. 2 2
      packages/examples/README.md
  100. 2 2
      packages/examples/acp-demo/README.md

+ 9 - 8
.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md

@@ -6,29 +6,29 @@ Status: implemented
 
 An example folder is supposed to be *thin* — the variable wiring of a demo, not the demo's machinery. Before this change it was thick. Each example carried a hand-rolled `start.ts` boot bootstrap, an infra preamble (`timer`, and — for the stdio demos — `logger` + `hmr`), nested includes of three shared YAML fragments (`base.yml` / `base-core.yml` / `acp-agent/acp-tail.yml`), and per-example `agent-loop`/persistence/system-prompt config. The actual app — the spine of services every agent needs — was spread across the leaf and those includes.
 
-The leaf configs also owned a coupled front door. ACP requires stdout purity and creates agents through `session/new`; stdio requires a console logger and a pre-created `main`. Prose warnings were the only guard against combining these incorrectly, while three `start.ts` files duplicated the Loader bootstrap and lifecycle code.
+The leaf configs also owned coupled front doors. ACP requires stdout purity and creates agents through `session/new`; terminal and Headless apps pre-create `main` but have different process I/O contracts. Prose warnings were the only guard against combining these incorrectly, while three `start.ts` files duplicated the Loader bootstrap and lifecycle code.
 
 ## Decision
 
 Each example is now **mostly an invocation of an app package**, splitting the wiring along the existing [interface / implementation / consumer seam](2026-06-13-capability-seams.md): the **app package owns the composition**, the leaf `cordis.yml` owns only the **swappable choices** (which LLM adapter, which bash executor, model, prompt, persistence root).
 
 - **`@deepseek-ai/dsh-agent-spine-demo`** ([packages/examples/agent-spine-demo](../../../../packages/examples/agent-spine-demo)) composes the providerless, executor-less, UI-less spine and forwards the loop's agent-list config. Its dependency on the concrete loop is intentional because this package composes the spine rather than extending it; swapping the loop means supplying another bundle.
-- **`@deepseek-ai/dsh-stdio-demo`** ([packages/examples/stdio-demo](../../../../packages/examples/stdio-demo)) and **`@deepseek-ai/dsh-acp-demo`** ([packages/examples/acp-demo](../../../../packages/examples/acp-demo)) bake in their front doors. Stdio includes `ui-stdio`, a console logger, and `main`; ACP includes the bridge and JSONL persistence but no stdout logger or pre-created agent. Leaves may add plugins, but the safe composition is now the default artifact.
-- **`start.ts` is gone.** Each app package exposes a `bin` (`dsh-stdio-demo` / `dsh-acp-demo`); the `demo:*` scripts invoke it (e.g. `dsh-stdio-demo ./cordis.yml`). The Loader-boot tail, `.env` loading, and fail-loud guards live in the shared [`@deepseek-ai/dsh-app-boot`](../../../../packages/ui/app-boot) package (unit-tested under the per-file coverage gate — see [share the app bins' boot glue](../simplification/2026-07-04-share-app-bin-boot-glue.md)); each bin is a thin self-executing composition over those helpers plus its app-specific lifecycle (the ACP bin: snapshot-mode selection and stdin-dispose). The `bin.ts` files themselves stay coverage-excluded (self-executing CLI entries, like the old `start.ts`) and are driven by the keyless Loader-path tests.
-- **Each leaf `cordis.yml` collapses** to backends + config: the LLM adapter (`llm-deepseek` with apiKey/models, or `llm-replay`), the bash executor (`bash-local`), `hmr` for the stdio demos (see the amendment below), and one app entry carrying the app's config (model, system prompt, persistence root — surfaced as the app package's own `Config`, which routes each value to wherever the app wires it: stdio onto its pre-created agent, acp onto the bridge plugin).
-- **echo-agent folds onto `dsh-stdio-demo`**, swapping the LLM backend to the local `mock-llm` and adding the local `echo-tool` (plus `bash-local`, which the spine's `tool-bash` injects) at the leaf — the clean demonstration of "swap the backend, keep the app". `mock-llm.ts` / `echo-tool.ts` stay as example-local teaching plugins.
+- **`@deepseek-ai/dsh-tui-demo`**, **`@deepseek-ai/dsh-cli-demo`**, and **`@deepseek-ai/dsh-acp-demo`** bake in their process roles. TUI includes the full-screen UI and a pre-created `main`; Headless includes the one-shot driver and a pre-created `main`; ACP includes the bridge and no pre-created agent. All three include JSONL persistence and omit stdout loggers.
+- **`start.ts` is gone.** Each app package exposes a bin; the `demo:*` scripts invoke it. Loader boot, `.env` loading, and fail-loud guards live in the shared [`@deepseek-ai/dsh-app-boot`](../../../../packages/ui/app-boot) package (unit-tested under the per-file coverage gate — see [share the app bins' boot glue](../simplification/2026-07-04-share-app-bin-boot-glue.md)); the thin self-executing entries are driven by keyless Loader-path tests.
+- **Each leaf `cordis.yml` collapses** to backends, optional product tools, and one app entry carrying the app config. TUI and Headless route model/session choices onto a pre-created agent; ACP routes the initial provider/model onto its bridge.
+- **echo-agent loads `dsh-cli-demo`**, swapping the LLM backend to the local `mock-llm` and adding the local `echo-tool` at the leaf. `mock-llm.ts` and `echo-tool.ts` stay as example-local teaching plugins.
 - **`base.yml`, `base-core.yml`, and `acp-agent/acp-tail.yml` are retired** — the spine they shared now lives in `dsh-agent-spine-demo`.
 
 `bash-local` and the LLM adapter stay **leaf choices**: the bundle ships `tool-bash` (the consumer schema), the leaf picks the executor implementation, so a sandboxed executor or replay adapter swaps in without touching the app.
 
 ### Amendment on implementation: `hmr` stays a leaf entry
 
-The proposal listed `hmr` among the stdio app's baked-in front-door cluster. Validating against the code, baking `hmr` into the `dsh-stdio-demo` package fights cordis in two ways, so it ships as a **leaf `cordis.yml` entry** instead:
+The proposal listed `hmr` among the interactive app's baked-in front-door cluster. Validating against the code, baking `hmr` into the app package fights Cordis in two ways, so it ships as a **leaf `cordis.yml` entry** instead:
 
 1. `@cordisjs/plugin-hmr` is a Loader-only, subprocess-only dev plugin — its constructor throws without `node --expose-internals` + a live `loader` service, so it can only run in the real `demo:*`/bin subprocess, never in the in-process unit/coverage tier.
 2. The in-process test tier (vitest) cannot even *import* the vendored `hmr` module (its class-decorator `@Inject` form fails under Vite's transform), so a package whose `apply` statically imported it could never satisfy the per-file 100% coverage gate on its headline function.
 
-Crucially, `hmr` is **not** a stdout-purity footgun the way the console logger is — a stray `hmr` in the ACP config would not corrupt the JSON-RPC frames — so leaving it at the leaf costs none of the safety the coupling argument is about. The **logger** (the real coupling) stays baked in: the stdio app includes it, the ACP app omits it.
+Crucially, `hmr` is not a stdout-purity footgun: a stray entry in the ACP config does not corrupt JSON-RPC frames. Every shipped app omits a stdout console logger; the app or protocol driver alone owns stdout.
 
 ## Alternatives considered
 
@@ -39,7 +39,7 @@ The old `base*.yml`/`acp-tail.yml` includes already deduped the *config*, but a
 ## Verification
 
 - Example directories contain only their config, README, and tests: `start.ts`, the infrastructure preamble, and the shared YAML includes are gone.
-- `demo:echo`, `demo:repl`, and `demo:acp` invoke the app-package bins.
+- `demo:echo`, `demo:tui`, `demo:headless`, and `demo:acp` invoke the app-package bins.
 - Each new package has a README and per-file 100% coverage; each app package also has a keyless real-Loader-path bin smoke that catches export-shape failures described in [postmortem 0001](../../../../docs/postmortem/0001-acp-default-export-drops-inject.md).
 - The ACP replay transcript remains unchanged because the plugin set and load order did not change.
 
@@ -53,3 +53,4 @@ The old `base*.yml`/`acp-tail.yml` includes already deduped the *config*, but a
 - Supersedes [Make the shared example base providerless](../../rejected/architecture/2026-06-20-providerless-example-base.md): renaming `base.yml` to the providerless core is moot once the spine moves into `dsh-agent-spine-demo` and the `base*.yml` files are deleted.
 - Builds on the [capability-seams](2026-06-13-capability-seams.md) interface/implementation/consumer split — backends and presentation stay leaf choices; the spine is the shared bundle.
 - Complements [Reorganize packages into a modular hierarchy](2026-06-20-package-hierarchy.md): the new app/core packages slot into existing groups under that hierarchy (`core` for the reusable spine bundle, `ui` for the app-specific front doors).
+- The later [remove-stdio-agent decision](../simplification/2026-07-20-remove-stdio-agent.md) owns the final TUI/Headless split and removal of the line-oriented app.

+ 2 - 0
.agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md

@@ -2,6 +2,8 @@
 
 Status: implemented
 
+The later [fold-stdio-helper](../simplification/2026-07-04-fold-stdio-ui-helper.md) decision superseded the original `support/ui-stdio` placement, and the [remove-stdio-agent](../simplification/2026-07-20-remove-stdio-agent.md) decision subsequently removed that surface entirely. The uniform depth-two hierarchy remains the decision owned here.
+
 ## Problem
 
 `packages/` was flat: 18 packages all sat at `packages/<name>/`, so a package's location said nothing about whether it was core product API, a swappable capability seam, a provider adapter, a product integration, or example/test support. The package README carried a `FIXME(package-hierarchy)` and `scripts/publint-all.ts` a `TODO(package-inventory)` flagging exactly this. Core packages, provider integrations, capability seams, example UI support, and snapshot-only replay support all looked equally foundational.

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md

@@ -8,7 +8,7 @@ The assembled system prompt had four defects, all of one family: facts the harne
 
 **The model could not know its own name.** `AgentOptions.model` drives every request, but no prompt text carried it — and nothing COULD carry it: sections in `dsh-system-prompt` were context-global while the model name is per-agent, and `assemble()` took no per-agent input at all.
 
-**Tool guidance was hand-written prose in leaf YAML.** The bash/subagent/todo_write usage guidance lived in the `systemPrompt` strings of `examples/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml` — two drifting copies (the ACP one was already abridged) — while `dsh-tool-fs` and `dsh-tool-web` owned their guidance as `ctx.systemPrompt.section()` contributions. Loading or dropping a tool plugin meant editing every deployment's persona by hand; both YAMLs carried a `FIXME(config-comments)` apologizing for a symptom of the split, and the stdio welcome banner hand-enumerated the tool set too.
+**Tool guidance was hand-written prose in leaf YAML.** The bash/subagent/todo_write usage guidance lived in the coding-agent and ACP persona strings — two drifting copies (the ACP one was already abridged) — while `dsh-tool-fs` and `dsh-tool-web` owned their guidance as `ctx.systemPrompt.section()` contributions. Loading or dropping a tool plugin meant editing every deployment's persona by hand; both YAMLs carried a `FIXME(config-comments)` apologizing for a symptom of the split, and the old terminal welcome banner hand-enumerated the tool set too.
 
 **The persona rendered after tool guidance.** The loop string-joined `agent.options.systemPrompt` AFTER the assembled sections, so the model read "Use the read tool…" before "You are a coding agent" — backwards relative to the identity-first convention (Claude Code, Codex) and a second composition path besides the section pipeline.
 
@@ -56,7 +56,7 @@ Per-tool semantics and selection guidance live in tool descriptions. Prompt sect
 
 ## Shipped invariants
 
-- The repl-agent prompt renders identity, persona with the interpolated model, then fs/bash/web guidance through one assembly path.
+- The tui-agent prompt renders identity, persona with the interpolated model, then fs/bash/web guidance through one assembly path.
 - Fork and fresh subagent descriptions reflect whether the provider inherits completed conversation turns; the tool appears, disappears, and is reworded with provider lifecycle changes.
 - Unknown, valueless, malformed, or unbalanced variable references name the section and throw; duplicate section, variable, and tool registrations also throw.
 - Snapshot replay is prompt-independent: it keys recorded chunk streams by turn and step without comparing the outgoing request.

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md

@@ -162,7 +162,7 @@ Those cases can consume `ctx.spillStore` directly in later work. They are not pa
 - `dsh-spill-local` unit tests cover `saveText`, `encodeSegment` sanitization (separators/tilde/whole-segment dots/empty), the session-hash directory, owner-only permissions, distinct paths per save, the configured/private root, and a storage-failure rejection.
 - `dsh-spill-policy` unit tests drive real tools through `ctx.tools.execute`: disabled-mode no-op, oversized-text replacement, small/non-text passthrough, `read` skip, best-effort fallback (save failure / no backend / no owner), and downstream-composition (bounding a replaced result, preserving `additionalContexts`).
 - `dsh-tool-web` integration drives `web_fetch` through `ctx.tools.execute` with the real `spill-local` backend + policy, proving the model-facing text changes only by the deliberate spill notice while the spill file holds the full formatted result.
-- The `repl-agent` example loads `spill-local` + `spill-policy`, so its keyless Loader smoke exercises the real load path (the namespace-plugin export shape + `inject`).
+- The `tui-agent` example loads `spill-local` + `spill-policy`, so its keyless Loader/PTY smoke exercises the real load path (the namespace-plugin export shape + `inject`).
 
 ## Consequences
 

+ 1 - 1
.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md

@@ -116,7 +116,7 @@ Two failure paths, both documented:
 - **`SessionEventMap`** gains `compact/start` / `compact/summary` / `compact/end` by declaration merging (merge-extensible); `SurfaceEventType` is **not** touched. These are session events, not cordis `Events`, so the event-taxonomy gate needs no entry.
 - **`dsh-compact`** owns `toolPairingBalancedBefore(session, seq)` and `toolPairingBalancedAfter(session, seq)`, the cached surface-edge checks that `compactRegion` and `compactIfNeeded` use to avoid splitting a tool-call/result pair. The cache validates current membership by seq and answers both edges from one per-cut balance sequence; stale or missing seqs and orphan results reject.
 - **`dsh-session`** validates positional replacement, complete provenance, and content-only single-node `tool/result` rewrites through its one surface manager. `dsh-invariants` treats fresh appended tool results as executions that require an open step and pending call; validated replacements remain turn-enclosed rewrites.
-- **Wiring**: `examples/repl-agent/cordis.yml` loads zero-config `dsh-token-meter`, `dsh-compact-tool-result-prune`, then `dsh-compact-basic`; service-wide defaults make the composition usable without repeated numeric policy.
+- **Wiring**: `examples/tui-agent/cordis.yml` loads zero-config `dsh-token-meter`, `dsh-compact-tool-result-prune`, then `dsh-compact-basic`; service-wide defaults make the composition usable without repeated numeric policy.
 
 ## Testing
 

+ 3 - 3
.agents/notes/implemented/feature/2026-06-25-ask-user-question.md

@@ -20,7 +20,7 @@ Providers return `{ answers: [{ id, selected, custom? }] }`. `selected` is alway
 
 ## UI mappings
 
-`dsh-stdio-demo`'s in-package readline module renders each question, shows each option's `description` on the next line, supports comma/space-separated numeric choices for `multi_select`, accepts free-form custom answers, and rejects pending questions on abort, provider disposal, or stdin EOF. A batched request is asked in order and resolved as one answer object. The stdio provider serializes simultaneous requests with an internal queue so only one prompt owns stdin at a time.
+`dsh-tui` renders each question as a keyboard overlay, shows option descriptions, supports single- and multi-select choices plus free-form custom answers, and rejects pending questions on abort, provider disposal, or terminal shutdown. Batched and simultaneous requests are queued so one overlay owns keyboard focus at a time.
 
 `dsh-acp` provides the same seam for ACP sessions. It resolves the calling `Agent` through `ownedRecord`, requiring the forward session-map record at `agent.session.id` to own that exact agent object, and calls ACP `unstable_createElicitation` with a session-scoped form for each question. Single-select options become a `choice` string enum; `multi_select` options become a `choice` array enum; optionless questions use a required `custom` text field. If the client returns both `choice` and non-empty `custom`, the custom answer wins. ACP `decline`/`cancel`, a missing answer, a missing session, and a client without elicitation support all become structured `UserInteractionError`s.
 
@@ -42,8 +42,8 @@ ACP elicitation is currently marked unstable in the SDK. The fallback is still s
 
 The feature gives the model a powerful pause primitive, so prompt guidance matters. The tool description tells the model to ask concise questions and use options when possible. Product policy can later wrap `tools/execute` to restrict when the tool is allowed, but the loop should not special-case it.
 
-`dsh-user-interaction` and `dsh-tool-ask-user` both live in `packages/ui` because they form one product-facing human-interaction capability. `agent-core` does not load either the tool or a provider. `stdio-agent` opts into the seam, its readline provider, and the model-facing tool. `acp-agent` keeps only the `userInteraction` seam/provider by default: ACP elicitation support is still client-dependent, so an ACP leaf must opt into the model-facing tool deliberately once its client can complete elicitation requests.
+`dsh-user-interaction` and `dsh-tool-ask-user` both live in `packages/ui` because they form one product-facing human-interaction capability. `agent-core` does not load either the tool or a provider. `dsh-tui-demo` opts into the seam, TUI provider, and model-facing tool. `acp-agent` keeps only the `userInteraction` seam/provider by default: ACP elicitation support is still client-dependent, so an ACP leaf must opt into the model-facing tool deliberately once its client can complete elicitation requests.
 
 ## Testing
 
-Unit coverage pins provider registration/disposal, duplicate-provider rejection, abort-before-provider, empty-question rejection, structured tool errors through `ctx.tools.execute()`, batched answers, multi-select answers, custom answers, and the model schema including the removal of `value`, `recommended`, `allow_custom`, and `desc`. `dsh-stdio-demo` tests cover option descriptions, queued requests, EOF/abort cleanup, optionless free-form input, invalid option reprompts, duplicate multi-select numbers, and batched question flows. ACP bridge tests drive a real in-memory ACP connection with the real `ask_user_question` tool and verify selected-option, custom-overrides-choice, multi-select, and optionless free-form elicitation paths continue the agent loop.
+Unit coverage pins provider registration/disposal, duplicate-provider rejection, abort-before-provider, empty-question rejection, structured tool errors through `ctx.tools.execute()`, batched answers, multi-select answers, custom answers, and the model schema including the removal of `value`, `recommended`, `allow_custom`, and `desc`. TUI tests cover option descriptions, queued requests, shutdown/abort cleanup, optionless free-form input, invalid choices, duplicate multi-select selections, and batched question flows. ACP bridge tests drive a real in-memory ACP connection with the real `ask_user_question` tool and verify selected-option, custom-overrides-choice, multi-select, and optionless free-form elicitation paths continue the agent loop.

+ 1 - 1
.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md

@@ -21,7 +21,7 @@ The system-prompt assembly owns the canonical model-facing tool order, exactly w
 
 Scope is deliberately narrow: this fixes the REGISTRATION-ORDER race, not plugin behavior. A `system-prompt/assemble` listener may still add, remove, or rearrange tools — same as it may edit sections after their sort — and owns the determinism of what it emits; the waterfall contract already demands deterministic listeners (the reconstructability invariant would catch a listener that diverges between build and replay).
 
-Config plumbing follows the `persona` precedent, and `toolOrder` sits beside it: the app configs (`dsh-stdio-demo`, `dsh-acp-demo`) accept the key and forward it through `dsh-agent-spine-demo` (whose schema is the intersection of the owners' schemas) to the `SystemPrompt` child. One schemastery footnote is load-bearing: a schemastery array defaults to `[]`, but an omitted `toolOrder` must stay ABSENT (= lexicographic) rather than become an explicitly-configured empty list (invalid — it lacks the rest entry), so every schema on the chain forces the default to `undefined`.
+Config plumbing follows the `persona` precedent, and `toolOrder` sits beside it: the TUI, Headless, and ACP app configs accept the key and forward it through `dsh-agent-spine-demo` (whose schema is the intersection of the owners' schemas) to the `SystemPrompt` child. One schemastery footnote is load-bearing: a schemastery array defaults to `[]`, but an omitted `toolOrder` must stay ABSENT (= lexicographic) rather than become an explicitly-configured empty list (invalid — it lacks the rest entry), so every schema on the chain forces the default to `undefined`.
 
 ## Alternatives considered
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md

@@ -155,7 +155,7 @@ If the complete logical result fits under the inline cap, no formatted spill art
 - The tools execute through `ctx.bash.resolve(request)` → `ctx.bash.run(spec)`, forward `exec.signal`, never call `ctx.bash.start()`, and never expose a bash task id. The bash request workdir comes from `exec.agent?.session.header.cwd` when available; the resolved `spec.workdir` drives execution and relative-path display.
 - The tools request `stdoutMaxBytes: rawOutputMaxBytes` from the bash seam, parse only untruncated stdout within that cap, and treat over-cap or still-truncated raw output as a clear search failure; raw `rg` output is never exposed to the model.
 - Oversized complete formatted results are saved through `ctx.spillStore.saveText()` when available while inline results stay bounded; spill failure, a missing backend, or a missing owner preserves the inline result and reports the unsaved remainder — never an `isError`.
-- The package README, the generated config catalog, and exported JSDoc document the Config fields and `SEARCH_*` codes; the repl-agent example ships the conditional tool plugin (the acp-agent tree waits on the snapshot re-record above); the fs group README records the `rg` availability and co-located bash/filesystem deployment requirements.
+- The package README, the generated config catalog, and exported JSDoc document the Config fields and `SEARCH_*` codes; the tui-agent example ships the conditional tool plugin (the acp-agent tree waits on the snapshot re-record above); the fs group README records the `rg` availability and co-located bash/filesystem deployment requirements.
 
 ## Risks
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-2026-07-17-dedicated-full-screen-tui-front-door.md: 178b5ea44be67f820a8ea7fed8acb987dffb3f80
-2026-07-17-dedicated-full-screen-tui-front-door.zh.md: ac055bad1b7a692c7a980430fdbd1e34737a9994
+2026-07-17-dedicated-full-screen-tui-front-door.md: c7bd01011121f04683afde1d05b046970e497a71
+2026-07-17-dedicated-full-screen-tui-front-door.zh.md: 5de443f367725a116e70d368657562b6732904e7

+ 4 - 4
.agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.md

@@ -6,7 +6,7 @@ English | [中文](2026-07-17-dedicated-full-screen-tui-front-door.zh.md)
 
 ## Problem
 
-The line-oriented `@deepseek-ai/dsh-stdio` front door works in pipes and ordinary terminals, but a full-screen coding interface must own raw input, differential screen drawing, cursor state, overlays, and terminal restoration. Combining those contracts in one UI plugin couples the pipe-safe path to a TTY-only lifecycle and makes it unclear which terminal behavior a composition selects.
+At the time this front door was introduced, the line-oriented agent handled pipes and ordinary terminals, but a full-screen coding interface had to own raw input, differential screen drawing, cursor state, overlays, and terminal restoration. Combining those contracts in one UI plugin would have coupled a stream-oriented path to a TTY-only lifecycle. The later [remove-stdio-agent decision](../simplification/2026-07-20-remove-stdio-agent.md) removes that redundant line agent; this Note continues to own the TUI design.
 
 The interactive channel must remain a Cordis plugin over the same agent, session, tool, and user-interaction services as every other front door. It needs to resume durable history, follow compaction replacements, display tool-owned presentation, and restore the terminal on startup failure and disposal. A standalone chat application or a second agent composition would duplicate behavior outside the plugin graph.
 
@@ -14,7 +14,7 @@ The interactive channel must remain a Cordis plugin over the same agent, session
 
 DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior.
 
-The app layer selects a concrete terminal front door before mounting it. `@deepseek-ai/dsh-stdio-demo` can resolve `auto` from the two process streams, while the `repl-agent` and `tui-agent` leaves explicitly select readline and TUI respectively. The TUI leaf reuses the repl-agent backend and tool composition through an asserted include patch, so the three runnable agent leaves remain symmetric without duplicating deployment choices.
+The app layer has one terminal front door. `@deepseek-ai/dsh-tui-demo` mounts the TUI before the configured agent, and `examples/tui-agent` owns the interactive coding composition and Code Mode overlay directly. Non-interactive tasks use `@deepseek-ai/dsh-cli-demo`; ACP remains a separate editor protocol.
 
 The selected front door receives the exact generated or resumed `SessionId` used by the pre-created agent. It mounts before the agent composition, waits for the matching root agent, and enters full-screen mode only after that agent exists. A matching `agent-loop/config-start-failed` event is therefore reported before screen takeover and exits with status 1.
 
@@ -42,7 +42,7 @@ The implemented [TUI terminal-state snapshot Agent Note](../testing/2026-07-18-t
 
 ## Consequences
 
-- Interactive terminal work gains a stateful Markdown, card, plan, and question interface without changing the line-oriented protocol used by pipes and automation.
-- The TUI carries a pi-tui dependency and a strict TTY requirement; non-TTY deployments select `@deepseek-ai/dsh-stdio` at composition time.
+- Interactive terminal work has a stateful Markdown, card, plan, and question interface with no second terminal protocol to keep aligned.
+- The TUI carries a pi-tui dependency and a strict TTY requirement; non-TTY deployments use the Headless app or a structured protocol.
 - Session projection makes resume and compaction consistent with the durable conversation, but one configured session owns the transcript and editor.
 - Tool packages extend terminal cards through their existing presentation methods without adding tool-specific branches to the TUI.

+ 4 - 4
.agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 ## 问题
 
-逐行输出的 `@deepseek-ai/dsh-stdio` 入口适用于管道和普通终端,但全屏编码界面必须负责原始输入、差分绘制、光标状态、浮层和终端恢复。把这两类契约合并到一个 UI 插件中,会迫使管道安全路径依赖仅适用于 TTY 的生命周期,也使组合无法明确表达所选终端行为。
+在本入口引入时,面向行的 agent 负责 pipe 与普通终端,但全屏 coding 界面必须负责原始输入、差分绘制、光标状态、浮层和终端恢复。把这两类契约合并到一个 UI 插件中,会迫使面向 stream 的路径依赖仅适用于 TTY 的生命周期。后续的[移除 stdio agent 决策](../simplification/2026-07-20-remove-stdio-agent.md)移除了这一重复的面向行 agent;本 Note 继续负责 TUI 设计。
 
 交互通道必须继续作为 Cordis 插件,使用与其他入口相同的 agent(智能体)、会话、工具和用户交互服务。它需要恢复持久历史、跟随压缩替换、显示工具自有的呈现内容,并在启动失败和资源释放时恢复终端。独立聊天应用或第二套 agent 组合会在插件图之外重复实现这些行为。
 
@@ -14,7 +14,7 @@ Status: implemented
 
 DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现;agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY;条件不满足时会失败,不会静默切换为逐行输出。
 
-应用组合层在挂载前选择具体的终端入口。`@deepseek-ai/dsh-stdio-demo` 可以根据两个进程流通过 `auto` 作出选择,`repl-agent` 和 `tui-agent` 叶节点则分别明确选择 readline 与 TUI。TUI 叶节点通过带断言的 include patch 复用 repl-agent 的后端和工具组合,使三个可运行的 agent 叶节点保持对称,同时避免重复部署选项。
+应用组合层只有一个终端入口。`@deepseek-ai/dsh-tui-demo` 在已配置 agent 之前挂载 TUI,`examples/tui-agent` 直接拥有交互式 coding 组装及其 Code Mode overlay。非交互任务使用 `@deepseek-ai/dsh-cli-demo`;ACP 仍是独立的编辑器协议。
 
 所选入口接收预创建 agent 使用的同一个新建或恢复 `SessionId`。入口先于 agent 组合挂载,等待相符的根 agent 出现,然后才进入全屏模式。因此,相符的 `agent-loop/config-start-failed` 事件会在接管屏幕前报告,并以状态码 1 退出。
 
@@ -42,7 +42,7 @@ agent 空闲时,编辑器输入调用 `agent.send()`;轮次运行中则调
 
 ## 后果
 
-- 交互式终端获得带状态的 Markdown、卡片、计划和提问界面,同时不会改变管道与自动化使用的逐行协议。
-- TUI 会引入 pi-tui 依赖并严格要求 TTY;非 TTY 部署在组合时选择 `@deepseek-ai/dsh-stdio`。
+- 交互式终端拥有带状态的 Markdown、卡片、计划和提问界面,无需再对齐第二套终端协议。
+- TUI 会引入 pi-tui 依赖并严格要求 TTY;非 TTY 部署使用 Headless app 或结构化协议。
 - 会话投影使恢复和压缩与持久会话保持一致,但只有一个已配置会话拥有 transcript 和编辑器。
 - 工具包通过既有呈现方法扩展终端卡片,无需在 TUI 中增加工具专用分支。

+ 4 - 2
.agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md

@@ -26,7 +26,7 @@ Every graph page declares one maintenance mode:
 
 ### First shipped index
 
-The first index links ten relationship surfaces. Package topology and tool-package affordances live in the existing generated catalogs that already own those facts; the remaining focused diagrams are generated by `scripts/gen-doc-graphs.ts`.
+The index links twelve relationship surfaces. Package topology and tool-package affordances live in the existing generated catalogs that already own those facts; the remaining focused diagrams are generated by `scripts/gen-doc-graphs.ts`.
 
 | Graph | Maintenance mode | Source of truth |
 |---|---|---|
@@ -34,7 +34,9 @@ The first index links ten relationship surfaces. Package topology and tool-packa
 | [tool schema catalog and package map](../../../../docs/tool-catalog.md) | generated | boot-harvested tool schemas plus tool-package service/effect metadata |
 | [capability seams and core services](../../../../docs/capability-seams.md) | hybrid generated | Cordis service declarations plus a role manifest in `gen-doc-graphs.ts` |
 | [echo-agent app composition](../../../../examples/echo-agent/composition.md) | hybrid generated | `examples/echo-agent/cordis.yml` plugin list plus curated app/bundle expansion |
-| [repl-agent app composition](../../../../examples/repl-agent/composition.md) | hybrid generated | `examples/repl-agent/cordis.yml` plugin list plus curated app/bundle expansion |
+| [tui-agent app composition](../../../../examples/tui-agent/composition.md) | hybrid generated | `examples/tui-agent/cordis.yml` plugin list plus curated app/bundle expansion |
+| [headless-agent app composition](../../../../examples/headless-agent/composition.md) | hybrid generated | `examples/headless-agent/cordis.yml` plugin list plus curated app/bundle expansion |
+| [cordis-agent app composition](../../../../examples/cordis-agent/composition.md) | hybrid generated | `examples/cordis-agent/cordis.yml` plugin list plus curated app/bundle expansion |
 | [acp-agent app composition](../../../../examples/acp-agent/composition.md) | hybrid generated | `examples/acp-agent/cordis.yml` plugin list plus curated app/bundle expansion |
 | [event producer/consumer matrix](../../../../docs/event-producer-consumer.md) | hybrid generated | Cordis event declarations, AST-scanned `ctx.on/emit/parallel/serial/waterfall` sites, and explicit dynamic dispatch overrides |
 | [agent turn and step lifecycle](../../../../docs/agent-lifecycle.md) | curated | architecture.md loop lifecycle, Cordis catalog links, and session event semantics |

+ 1 - 1
.agents/notes/implemented/process/2026-07-06-node-engine-floor.md

@@ -13,7 +13,7 @@ Set `engines.node` to `^22.19.0 || >=24.0.0` and test the keyless CI compatibili
 Two Node features gate the source runtime:
 
 - **`node:sqlite`** — `packages/session-persistence/session-persistence-sqlite` does a top-level `import { DatabaseSync } from 'node:sqlite'`. The module dropped its `--experimental-sqlite` flag requirement at **22.13** (LTS) and **23.4** (Current); before those, importing it throws at load.
-- **Native TypeScript type-stripping** — the `packages/examples/stdio-demo/tests/built-bin.e2e.ts` smoke boots the published `lib/bin.js` under plain `node` (no tsx) and loads the example's `.ts` plugins (`mock-llm.ts`, `echo-tool.ts`). Type-stripping is the default from **22.18** (LTS) and **23.6** (Current); before those it needs `--experimental-strip-types`.
+- **Native TypeScript type-stripping** — the built-mode `examples/echo-agent/tests/echo.e2e.ts` smoke boots `dsh-cli-demo`'s published `lib/bin.js` under plain `node` (no tsx) and loads the example's `.ts` plugins (`mock-llm.ts`, `echo-tool.ts`). Type-stripping is the default from **22.18** (LTS) and **23.6** (Current); before those it needs `--experimental-strip-types`.
 
 Those source features clear on the 22.x line at **22.18**, but the installed Pi adapter dependency raises the advertised LTS floor. `@deepseek-ai/dsh-llm-pi-ai` depends on `@earendil-works/pi-ai@0.79.3`, whose package declares `engines.node >=22.19.0`, so the LTS floor is **22.19**. The 24.x branch remains `>=24.0.0`. The disjoint range excludes Node 23 entirely: Node 23.0–23.5 still has at least one flagged source feature, and the 23 line is non-LTS/EOL, so advertising `>=23.6` would add a dead release line and a CI leg no deployment should use.
 

+ 2 - 0
.agents/notes/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md

@@ -2,6 +2,8 @@
 
 Status: implemented
 
+The later [remove-stdio-agent decision](2026-07-20-remove-stdio-agent.md) supersedes this package-placement decision and removes the folded package, app, and line-oriented surface entirely.
+
 ## Problem
 
 The readline UI was a whole package (`@deepseek-ai/dsh-ui-stdio` under `packages/support/`) whose only runtime importer was the app package `@deepseek-ai/dsh-stdio-demo`. The examples reach the readline UI by loading the app, never by composing the helper themselves; every other repo reference was mechanical or descriptive surface that existed BECAUSE the package boundary existed — manifest and tsconfig entries, generated module-graph rows, dependency-graph and README rows, and doc comments naming the package. The ui group README recorded the support placement rationale ("exists chiefly for the examples and the coverage gate — `ui/` is reserved for surfaces shipped as product"), which left a standing tension: a shipped product app depending on a support package documented as NOT product surface.

+ 6 - 0
.agents/notes/implemented/simplification/2026-07-20-remove-stdio-agent.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# side as of the last confirmed-consistent state. Both languages carry equal authority;
+# after editing either side, bring the other along and re-record with:
+#   pnpm run verify-translation-pairing --write
+2026-07-20-remove-stdio-agent.md: e4dcd371490e0810900134ab9a05f6f894ca06d0
+2026-07-20-remove-stdio-agent.zh.md: 9f0c2348de27b9c724e6f656881ec316bf48005f

+ 44 - 0
.agents/notes/implemented/simplification/2026-07-20-remove-stdio-agent.md

@@ -0,0 +1,44 @@
+# Agent Note: Remove the line-oriented stdio agent
+
+Status: implemented
+
+English | [中文](2026-07-20-remove-stdio-agent.zh.md)
+
+## Problem
+
+DeepSeek Harness had two terminal agents after the full-screen TUI shipped. `@deepseek-ai/dsh-tui` owned the interactive coding experience, while `@deepseek-ai/dsh-stdio` retained a line-oriented multi-turn chat protocol for ordinary streams. The latter was no longer a distinct product need: interactive users use the TUI, and scripts need a bounded Headless task with explicit output and exit semantics rather than prompts mixed with model and tool output.
+
+The redundant surface extended beyond one UI plugin. `@deepseek-ai/dsh-stdio-demo` selected between two terminal modes, `examples/repl-agent` owned a second copy of the coding composition, `demo:repl` exposed it, Loader and built-bin tests drove its prompt protocol, and the SDK generator offered a `stdio` interface that could create new users of the obsolete package. Keeping any of those paths would preserve the line agent indirectly.
+
+Standard input and output are also used as transport by ACP, the SDK JSON-RPC bridge, subprocesses, and test fixtures. Those byte channels are protocol boundaries, not the line-oriented agent, so removing every generic use of process streams would conflate unrelated designs.
+
+## Decision
+
+The line-oriented agent is removed without a compatibility package or mode alias. The `packages/ui/stdio` plugin, `@deepseek-ai/dsh-stdio-demo` package identity, `examples/repl-agent` leaf, `demo:repl` command, prompt/render tests, and supporting manifest, catalog, graph, and documentation entries are deleted.
+
+The two remaining application roles are explicit:
+
+- [`@deepseek-ai/dsh-tui-demo`](../../../../packages/examples/tui-demo/README.md) is the only terminal-interactive app. `examples/tui-agent` owns the complete coding composition and its Code Mode overlay directly; it no longer includes or patches another terminal leaf.
+- [`@deepseek-ai/dsh-cli-demo`](../../../../packages/examples/cli-demo/README.md) owns non-interactive execution. `examples/headless-agent` owns the real-model one-shot composition and generic real-agent e2e suites, while `examples/echo-agent` supplies the keyless mock task and CI smoke.
+
+The SDK project model and create/config workflows replace the `stdio` run-interface option with `tui`; generated TUI projects compose `@deepseek-ai/dsh-tui` and continue to create or resume one exact session. No old option is accepted because the repository is pre-release and has no compatibility promise.
+
+ACP and JSON-RPC retain their stdio transports. Child-process `stdio` settings and stream-reading APIs also remain where they describe operating-system I/O rather than the removed agent.
+
+## Verification
+
+TUI Loader coverage runs the real app under a pseudo-terminal in both source and built modes. Headless Loader coverage proves the mock tool round trip, multi-turn test drivers exercise a single app-owned agent without a UI protocol, and the CLI built-bin suite pins text, JSON, stream-JSON, persistence, failure, and signal behavior. Generated package/config/module graphs reject stale package references.
+
+## Alternatives considered
+
+- **Keep the line agent only for pipes** — rejected because Headless already has a clearer bounded-task contract, format-pure stdout, durable completion, and process exit status.
+- **Keep the package as a compatibility wrapper over Headless** — rejected because a multi-turn prompt protocol cannot honestly preserve its behavior by delegating to a one-shot CLI, and the pre-release policy favors the correct public surface.
+- **Let the TUI fall back when streams are not TTYs** — rejected because silent interface changes hide deployment mistakes; the TUI fails loud and callers select Headless explicitly.
+- **Remove every use of the term or mechanism stdio** — rejected because ACP and JSON-RPC intentionally use standard I/O as a framed transport and do not expose the removed line agent.
+
+## Consequences
+
+- Terminal interaction has one owner, one app package, one coding leaf, and one test strategy.
+- Automation has an explicit task/result contract rather than prompt parsing or EOF-driven conversation control.
+- Existing line-agent configurations and SDK `--interface=stdio` invocations fail instead of being translated.
+- The TUI requires a TTY pair; non-interactive environments use Headless, ACP, or JSON-RPC according to their protocol needs.

+ 44 - 0
.agents/notes/implemented/simplification/2026-07-20-remove-stdio-agent.zh.md

@@ -0,0 +1,44 @@
+# Agent Note: 移除面向行的 stdio agent
+
+Status: implemented
+
+[English](2026-07-20-remove-stdio-agent.md) | 中文
+
+## 问题
+
+全屏 TUI 交付后,DeepSeek Harness 同时存在两个终端 agent。`@deepseek-ai/dsh-tui` 负责交互式 coding 体验,而 `@deepseek-ai/dsh-stdio` 仍为普通 stream 保留面向行的多轮聊天协议。后者已不再对应独立的产品需求:交互用户使用 TUI;脚本需要的是具有明确输出和退出语义的有界 Headless 任务,而不是与模型和工具输出混在一起的提示符。
+
+重复 surface 不只涉及一个 UI 插件。`@deepseek-ai/dsh-stdio-demo` 在两种终端模式间选择,`examples/repl-agent` 维护第二份 coding 组装,`demo:repl` 对外暴露它,Loader 与 built-bin 测试驱动其提示符协议,SDK 生成器还提供可以创建旧包新用户的 `stdio` interface。保留其中任何路径,都会间接保留面向行的 agent。
+
+ACP、SDK JSON-RPC bridge、子进程和测试 fixture 同样使用标准输入输出作为 transport。这些字节通道是协议边界,并不是面向行的 agent;因此,删除所有通用进程 stream 用法会混淆彼此无关的设计。
+
+## 决策
+
+移除面向行的 agent,不提供兼容 package 或 mode alias。删除 `packages/ui/stdio` 插件、`@deepseek-ai/dsh-stdio-demo` package identity、`examples/repl-agent` 叶节点、`demo:repl` 命令、提示符/渲染测试,以及相关 manifest、catalog、graph 和文档条目。
+
+保留的两个应用角色均改为显式选择:
+
+- [`@deepseek-ai/dsh-tui-demo`](../../../../packages/examples/tui-demo/README.md) 是唯一的终端交互式 app。`examples/tui-agent` 直接拥有完整 coding 组装及其 Code Mode overlay,不再 include 或 patch 另一个终端叶节点。
+- [`@deepseek-ai/dsh-cli-demo`](../../../../packages/examples/cli-demo/README.md) 负责非交互式执行。`examples/headless-agent` 拥有真实模型的单次组装和通用真实 agent e2e suite,`examples/echo-agent` 则提供 keyless mock 任务与 CI smoke。
+
+SDK project model 与 create/config workflow 将 `stdio` run-interface 选项替换为 `tui`;生成的 TUI 工程组合 `@deepseek-ai/dsh-tui`,并继续创建或恢复一个确切 session。仓库处于 pre-release 阶段且没有兼容性承诺,因此不会接受旧选项。
+
+ACP 和 JSON-RPC 保留各自的 stdio transport。描述操作系统 I/O 而非已移除 agent 的子进程 `stdio` 设置与 stream 读取 API 也继续保留。
+
+## 验证
+
+TUI Loader 覆盖在 source 与 built 两种模式下通过伪终端运行真实 app。Headless Loader 覆盖验证 mock 工具往返;多轮测试 driver 在没有 UI 协议的情况下驱动同一个 app-owned agent;CLI built-bin suite 固定 text、JSON、stream-JSON、持久化、失败和 signal 行为。生成的 package/config/module graph 会拒绝陈旧的 package 引用。
+
+## 曾考虑的替代方案
+
+- **仅为 pipe 保留面向行的 agent**:不予采纳,因为 Headless 已提供更清晰的有界任务契约、格式纯净的 stdout、持久完成边界和进程退出状态。
+- **保留 package,并将其作为 Headless 的兼容 wrapper**:不予采纳,因为多轮提示符协议无法通过委托给单次 CLI 来诚实地保持行为,而且 pre-release 策略优先选择正确的公开 surface。
+- **让 TUI 在 stream 不是 TTY 时回退**:不予采纳,因为静默切换 interface 会掩盖部署错误;TUI 会快速失败,由调用方显式选择 Headless。
+- **移除 stdio 这个术语或机制的所有用法**:不予采纳,因为 ACP 与 JSON-RPC 有意使用标准 I/O 作为分帧 transport,并不暴露已移除的面向行 agent。
+
+## 后果
+
+- 终端交互只有一个 owner、一个 app package、一个 coding 叶节点和一套测试策略。
+- 自动化使用显式 task/result 契约,不再解析提示符或通过 EOF 控制对话。
+- 现有面向行的 agent 配置和 SDK `--interface=stdio` 调用会直接失败,不会被转换。
+- TUI 要求成对的 TTY;非交互环境根据协议需要使用 Headless、ACP 或 JSON-RPC。

+ 2 - 2
.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-2026-07-18-tui-terminal-state-snapshots.md: 192e872ab63cf4ff8a121ea0a2ee9345379cfa26
-2026-07-18-tui-terminal-state-snapshots.zh.md: 9766a8087632daa1be0dcfb191696dbad354ff68
+2026-07-18-tui-terminal-state-snapshots.md: 7277e6be6d88a73ea3abbd277e314715c8abc050
+2026-07-18-tui-terminal-state-snapshots.zh.md: 068a8b8a7f469f40492924bf94757bc0e25c3404

+ 1 - 1
.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md

@@ -21,7 +21,7 @@ TUI coverage has four complementary layers:
 3. `examples/tui-agent/tests/tui.snapshot.ts` replays committed JSONL session logs through the production agent loop and real tools, then compares the resulting semantic terminal state.
 4. `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` boots the real Loader composition in a PTY, drives a scripted conversation through streaming and `ask_user_question`, and verifies startup, input, exit, failure reporting, and terminal restoration.
 
-The runnable TUI has its own `examples/tui-agent` leaf beside the readline `repl-agent` and `acp-agent` leaves. It reuses the repl-agent backend and tool composition through an asserted include patch while fixing the shared terminal app to `ui.mode: tui`; TUI snapshots and PTY tests live with that leaf.
+The runnable TUI has its own `examples/tui-agent` leaf beside the Headless and ACP leaves. It owns the interactive coding backends and tools directly and loads `@deepseek-ai/dsh-tui-demo`; TUI snapshots and PTY tests live with that leaf. The [line-agent removal](../simplification/2026-07-20-remove-stdio-agent.md) owns this consolidation.
 
 ### Recorded-session replay
 

+ 1 - 1
.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.zh.md

@@ -21,7 +21,7 @@ TUI 覆盖分为四个互补层次:
 3. `examples/tui-agent/tests/tui.snapshot.ts` 通过生产 agent loop 和真实工具回放已提交的 JSONL 会话日志,再比较生成的语义终端状态。
 4. `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` 在 PTY 中启动真实 Loader 组合,驱动一段经过流式输出和 `ask_user_question` 的脚本化会话,并验证启动、输入、退出、失败报告和终端恢复。
 
-可运行 TUI 在 `examples/tui-agent` 中拥有独立叶节点,与 readline `repl-agent` 和 `acp-agent` 叶节点并列。它通过带断言的 include patch 复用 repl-agent 的后端与工具组合,只把共享终端应用固定为 `ui.mode: tui`;TUI 快照和 PTY 测试也归属这个叶节点。
+可运行 TUI 在 `examples/tui-agent` 中拥有独立叶节点,与 Headless 和 ACP 叶节点并列。它直接拥有交互式 coding 后端与工具,并加载 `@deepseek-ai/dsh-tui-demo`;TUI 快照和 PTY 测试也归属这个叶节点。[面向行 agent 的移除决策](../simplification/2026-07-20-remove-stdio-agent.md)负责此次整合。
 
 ### 已录制会话回放
 

+ 2 - 2
.agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-2026-07-14-sdk-developer-projects.md: 1be9abcad1e51a1b9a1406f21ce60073427576e0
-2026-07-14-sdk-developer-projects.zh.md: a8ba1d658f78484a46a7148a4e2ff1b073a3e9f2
+2026-07-14-sdk-developer-projects.md: aa5cf64d7dd33dea229d74c2ae45a9244ee70e3c
+2026-07-14-sdk-developer-projects.zh.md: 8f7d1de5b16f38019c802f07eda701cee72deb4f

+ 6 - 6
.agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.md

@@ -44,7 +44,7 @@ The table is the developer-visible support set for this phase. A `required` feat
 | Feature | Create state | Feature options | Constraints and relationships |
 |---|---|---|---|
 | `provider` | required | `deepseek` (default) / `custom` | DeepSeek collects an API key; custom also collects a base URL, and a CLI option may override the model name |
-| `app` | required | `stdio` (default) / `acp` / `embed` | Selects the run interface |
+| `app` | required | `tui` (default) / `acp` / `embed` | Selects the run interface |
 | `spine` | required | `default` | Timer, the LLM seam, session storage, system prompt, the tool registry, the agent registry, and the agent loop |
 | `bash` | required | `local` (default) / `sandbox` | The two feature options are exclusive and independent of the run interface, and both install the model-facing bash tool; sandbox installs the local sandbox provider and sandboxed bash backend |
 | `persistence` | required | `jsonl` (default) / `sqlite` | Every project selects exactly one persistence backend |
@@ -59,9 +59,9 @@ The table is the developer-visible support set for this phase. A `required` feat
 | `hooks` | optional | `claude` (default) / `codex`, multiple | Each feature option creates a separate editable configuration file |
 | `guard` | optional | `repeat-tool` | Provides repeated-tool-call reminders |
 | `timeout-policy` | optional | `default` | Applies a uniform policy to tools that declare timeout budgets |
-| `ask-user` | optional | `default` | Provides the `ask_user_question` tool; only `acp` and `stdio` can select it because those two feature options provide the injected user-interaction service |
+| `ask-user` | optional | `default` | Provides the `ask_user_question` tool; only `acp` and `tui` can select it because those two feature options provide the injected user-interaction service |
 
-Both `bash` feature options apply to ACP, stdio, and embed and are not selected by the run interface. The sandbox feature option writes no active config key and therefore keeps `dsh-bash-sandbox`'s `read-only` default. Generated `cordis.yml` includes a commented example that developers can change explicitly to `workspace-write`:
+Both `bash` feature options apply to ACP, TUI, and embed and are not selected by the run interface. The sandbox feature option writes no active config key and therefore keeps `dsh-bash-sandbox`'s `read-only` default. Generated `cordis.yml` includes a commented example that developers can change explicitly to `workspace-write`:
 
 ```yaml
 - id: bash
@@ -72,11 +72,11 @@ Both `bash` feature options apply to ACP, stdio, and embed and are not selected
   #   workspaceRoot: !!js process.cwd()
 ```
 
-Feature contributions reference only single-plugin npm packages and never bundle packages such as `agent-spine-demo`, `stdio-demo`, or `acp-demo`. Plugins outside the table are not managed by create in this phase; advanced developers may still compose them by editing the ordinary project files directly.
+Feature contributions reference only single-plugin npm packages and never bundle packages such as `agent-spine-demo`, `tui-demo`, or `acp-demo`. Plugins outside the table are not managed by create in this phase; advanced developers may still compose them by editing the ordinary project files directly.
 
 ## Generated project
 
-With default answers, an npm project uses the DeepSeek provider, the stdio interface, local bash, JSONL persistence, and the preselected hmr, fs, todo, and skill features. Its initial tree is:
+With default answers, an npm project uses the DeepSeek provider, the TUI interface, local bash, JSONL persistence, and the preselected hmr, fs, todo, and skill features. Its initial tree is:
 
 ```text
 my-agent/
@@ -106,7 +106,7 @@ Generated `package.json` provides the following scripts. `dev`, `build`, `start`
 
 `dsh-sdk start` and `dsh-sdk dev` accept a module target and forward arguments after `--` unchanged to the project entrypoint. Generic argument parsing uses Node `parseArgs()` with zero schema: valued flags use `--key=value`, bare flags become `true`, and `--no-*` becomes `false`.
 
-- Stdio projects pass the selected model through `--model=<name>` and create or resume an agent according to optional `--resume=<session-id>`;
+- TUI projects pass the selected model through `--model=<name>` and create or resume an agent according to optional `--resume=<session-id>`;
 - ACP uses protocol `session/load`
 - Embed uses the model written into the generated code.
 

+ 6 - 6
.agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.zh.md

@@ -44,7 +44,7 @@ create 还提供一次 `none / plugin / tool` 选择。`plugin` 固定生成 `pl
 | 功能 | create 状态 | 功能选项 | 限制与关系 |
 |---|---|---|---|
 | `provider` | required | `deepseek`(默认)/ `custom` | DeepSeek 收集 API key;custom 另收集 base URL,模型名可由 CLI 参数覆盖 |
-| `app` | required | `stdio`(默认)/ `acp` / `embed` | 选择运行接口 |
+| `app` | required | `tui`(默认)/ `acp` / `embed` | 选择运行接口 |
 | `spine` | required | `default` | timer、LLM seam、会话存储、系统提示词、工具注册表、agent 注册表,以及 agent loop |
 | `bash` | required | `local`(默认)/ `sandbox` | 两个功能选项互斥、与运行接口正交,且都安装面向模型的 bash 工具;sandbox 安装本地沙箱提供方和沙箱 bash 后端 |
 | `persistence` | required | `jsonl`(默认)/ `sqlite` | 每个工程恰好选择一个持久化后端 |
@@ -59,9 +59,9 @@ create 还提供一次 `none / plugin / tool` 选择。`plugin` 固定生成 `pl
 | `hooks` | optional | `claude`(默认)/ `codex`,可多选 | 各功能选项生成独立的可编辑配置文件 |
 | `guard` | optional | `repeat-tool` | 提供重复工具调用提醒 |
 | `timeout-policy` | optional | `default` | 对声明超时预算的工具执行统一策略 |
-| `ask-user` | optional | `default` | 提供 `ask_user_question` 工具;注入的 user-interaction 服务由 acp/stdio 两个功能选项提供,因此仅这两个接口可选 |
+| `ask-user` | optional | `default` | 提供 `ask_user_question` 工具;注入的 user-interaction 服务由 acp/tui 两个功能选项提供,因此仅这两个接口可选 |
 
-`bash` 的两个功能选项都适用于 ACP、stdio 和 embed,不由运行接口决定。sandbox 功能选项不写任何生效的配置键,因而沿用 `dsh-bash-sandbox` 的 `read-only` 默认值;生成的 `cordis.yml` 保留注释示例,开发者可以显式改为 `workspace-write`:
+`bash` 的两个功能选项都适用于 ACP、TUI 和 embed,不由运行接口决定。sandbox 功能选项不写任何生效的配置键,因而沿用 `dsh-bash-sandbox` 的 `read-only` 默认值;生成的 `cordis.yml` 保留注释示例,开发者可以显式改为 `workspace-write`:
 
 ```yaml
 - id: bash
@@ -72,11 +72,11 @@ create 还提供一次 `none / plugin / tool` 选择。`plugin` 固定生成 `pl
   #   workspaceRoot: !!js process.cwd()
 ```
 
-功能贡献只引用单插件 NPM 包,绝不引用 `agent-spine-demo`、`stdio-demo`、`acp-demo` 这类组合 NPM 包。表格之外的插件不由本期 create 管理;开发者仍可直接编辑普通工程文件进行高级组合。
+功能贡献只引用单插件 NPM 包,绝不引用 `agent-spine-demo`、`tui-demo`、`acp-demo` 这类组合 NPM 包。表格之外的插件不由本期 create 管理;开发者仍可直接编辑普通工程文件进行高级组合。
 
 ## 生成工程
 
-使用默认答案创建 npm 工程时,provider 为 DeepSeek,运行接口为 stdio,bash 为 local,持久化为 JSONL,hmr、fs、todo 与 skill 处于选中状态。初始目录树为:
+使用默认答案创建 npm 工程时,provider 为 DeepSeek,运行接口为 TUI,bash 为 local,持久化为 JSONL,hmr、fs、todo 与 skill 处于选中状态。初始目录树为:
 
 ```text
 my-agent/
@@ -106,7 +106,7 @@ my-agent/
 
 `dsh-sdk start` 与 `dsh-sdk dev` 可以接收模块 target,并把 `--` 后的参数原样转发给工程入口。通用参数解析使用 Node `parseArgs()` 的零 schema 模式:带值 flag 采用 `--key=value`,bare flag 转换为 `true`,`--no-*` 转换为 `false`。
 
-- stdio 工程通过 `--model=<name>` 传入所选 model,并根据可选的 `--resume=<session-id>` 创建或恢复 agent;
+- TUI 工程通过 `--model=<name>` 传入所选 model,并根据可选的 `--resume=<session-id>` 创建或恢复 agent;
 - acp 使用协议 `session/load`
 - embed 使用生成代码中的 model。
 

+ 1 - 1
.agents/skills/dsh-pre-push-checks/SKILL.md

@@ -54,7 +54,7 @@ pnpm run test:snapshot
 Run built-bin smoke tests after `pnpm run build` when app packages, app boot, package runtime imports, bin entries, loader behavior, or published artifact paths change.
 
 ```sh
-pnpm exec vitest run --config vitest.e2e.config.ts packages/examples/stdio-demo/tests/built-bin.e2e.ts packages/examples/cli-demo/tests/built-bin.e2e.ts packages/examples/acp-demo/tests/built-bin.e2e.ts
+DSH_EXAMPLE_MODE=lib pnpm exec vitest run --config vitest.e2e.config.ts examples/echo-agent/tests/echo.e2e.ts examples/tui-agent/tests/tui-keyless-smoke.e2e.ts packages/examples/cli-demo/tests/built-bin.e2e.ts packages/examples/acp-demo/tests/built-bin.e2e.ts
 ```
 
 Run real e2e when behavior depends on a real model/API, tool-use loop, ACP integration, prompt injection, or end-to-end agent UX. If `.env` is available, use it; do not print secrets.

+ 8 - 9
AGENTS.md

@@ -27,8 +27,8 @@ packages/    @deepseek-ai/dsh-<pkg> workspaces at packages/<group>/<pkg>/
   cordis/      self-referential toolset: the agent inspects/mounts plugins in its own runtime
   hooks/       Claude Code / Codex hook bridges + shared wire-protocol library
   session-persistence/  persistence seam + JSONL/SQLite backends
-  ui/          ACP/stdio/TUI/JSON-RPC bridges; boot, approval, interaction plugins
-  examples/    demo bundles (agent-spine + stdio/CLI/ACP/JSON-RPC bins) leaves load
+  ui/          ACP/TUI/JSON-RPC bridges; boot, approval, interaction plugins
+  examples/    demo bundles (agent-spine + TUI/CLI/ACP/JSON-RPC bins) leaves load
   support/     dev/test infrastructure packages
   util/        zero-dependency utilities
 python/      Python SDK and bundled runtime (see python/README.md)
@@ -58,9 +58,8 @@ pnpm run build          # tsc emits lib/types, tsdown bundles runtime
 pnpm run hygiene        # knip + publint + workspace constraints + NodeNext consumer check
 pnpm run doc-sync       # all documentation gates; see the doc-sync script in package.json
 pnpm run website:build  # VitePress build (doubles as the site's dead-link check)
-pnpm run demo:echo      # mock-model REPL, no key needed
-pnpm run demo:repl      # real REPL coding agent (needs DEEPSEEK_API_KEY)
-pnpm run demo:headless -- "task" # one-shot agent (needs DEEPSEEK_API_KEY)
+pnpm run demo:echo "task"    # mock-model headless agent, no key needed
+pnpm run demo:headless "task" # one-shot agent (needs DEEPSEEK_API_KEY)
 pnpm run demo:tui       # full-screen TUI coding agent (needs DEEPSEEK_API_KEY)
 pnpm run demo:cordis    # self-referential demo: the agent modifies its own runtime (needs key)
 pnpm run demo:acp       # ACP server agent (needs DEEPSEEK_API_KEY)
@@ -86,12 +85,12 @@ pnpm run website:build
 pnpm run verify-module-graph
 pnpm run build
 pnpm run hygiene
-out=$(printf 'echo ci smoke\n' | pnpm run demo:echo 2>&1)
-printf '%s\n' "$out" | grep -q '\[tool call\] echo({"text":"ci smoke"})'
-printf '%s\n' "$out" | grep -q '\[tool result\] ECHO: CI SMOKE'
+out=$(pnpm run demo:echo --output-format stream-json -- "echo ci smoke" 2>&1)
+printf '%s\n' "$out" | grep -q '"type":"tool/call"'
+printf '%s\n' "$out" | grep -q 'ECHO: CI SMOKE'
 test -n "$(find .sessions -path '.sessions/cwd-*/main-session-*.jsonl' -type f -print -quit)"
 rm -rf .sessions
-pnpm exec vitest run --config vitest.e2e.config.ts packages/examples/stdio-demo/tests/built-bin.e2e.ts packages/examples/cli-demo/tests/built-bin.e2e.ts packages/examples/acp-demo/tests/built-bin.e2e.ts packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts
+DSH_EXAMPLE_MODE=lib pnpm exec vitest run --config vitest.e2e.config.ts examples/echo-agent/tests/echo.e2e.ts examples/tui-agent/tests/tui-keyless-smoke.e2e.ts packages/examples/cli-demo/tests/built-bin.e2e.ts packages/examples/acp-demo/tests/built-bin.e2e.ts packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts
 ```
 
 `test:coverage`, not `test`, is the gate ([why](docs/testing.md)); report only commands actually run.

+ 2 - 2
README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-README.md: ef9a3a8832d1eaa35ec5f0fed1780ab27e8ff37c
-README.zh.md: a30d6db4b04f23559c36a7aba80b4feb2962a1c6
+README.md: 4ce9391e9286391a601d59d8401870c9ca8c79f3
+README.zh.md: c4f996762b36d2ebe00cd256e2f18b0a62550ba8

+ 2 - 3
README.md

@@ -11,10 +11,9 @@ This monorepo is built on the [Cordis](https://github.com/cordiverse/cordis) fra
 ```sh
 pnpm install
 pnpm run test          # vitest
-pnpm run demo:echo     # keyless mock-model REPL
-pnpm run demo:repl     # readline coding agent (needs DEEPSEEK_API_KEY)
+pnpm run demo:echo "task"    # keyless mock-model headless agent
 pnpm run demo:tui      # full-screen TUI coding agent (needs DEEPSEEK_API_KEY)
-pnpm run demo:headless -- "task" # one-shot coding agent (needs DEEPSEEK_API_KEY)
+pnpm run demo:headless "task" # one-shot coding agent (needs DEEPSEEK_API_KEY)
 pnpm run demo:cordis   # self-referential agent demo (needs DEEPSEEK_API_KEY)
 pnpm run demo:acp      # ACP server agent demo (needs DEEPSEEK_API_KEY)
 ```

+ 2 - 3
README.zh.md

@@ -11,10 +11,9 @@
 ```sh
 pnpm install
 pnpm run test          # vitest
-pnpm run demo:echo     # keyless mock-model REPL
-pnpm run demo:repl     # readline coding agent (needs DEEPSEEK_API_KEY)
+pnpm run demo:echo "task"    # keyless mock-model headless agent
 pnpm run demo:tui      # full-screen TUI coding agent (needs DEEPSEEK_API_KEY)
-pnpm run demo:headless -- "task" # one-shot coding agent (needs DEEPSEEK_API_KEY)
+pnpm run demo:headless "task" # one-shot coding agent (needs DEEPSEEK_API_KEY)
 pnpm run demo:cordis   # self-referential agent demo (needs DEEPSEEK_API_KEY)
 pnpm run demo:acp      # ACP server agent demo (needs DEEPSEEK_API_KEY)
 ```

+ 1 - 1
docs/architecture.md

@@ -157,7 +157,7 @@ Some seams bend the template deliberately: LLM combines interface and consumer b
 
 ### Bundles And Apps
 
-`dsh-agent-spine-demo` bundles the default spine ([README](../packages/examples/agent-spine-demo/README.md)). `dsh-stdio-demo` selects `dsh-tui` for interactive terminals and line-oriented `dsh-stdio` for pipes; `dsh-cli-demo` runs one persisted headless turn with format-pure stdout; `dsh-acp-demo` adds stdout-pure ACP over JSON-RPC ([ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies its default only without an explicit config channel and drives `dsh-jsonrpc` over line-delimited JSON-RPC ([Python SDK](../python/README.md)). Deployments remain thin leaves with swappable backends and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)).
+`dsh-agent-spine-demo` bundles the default spine ([README](../packages/examples/agent-spine-demo/README.md)). `dsh-tui-demo` owns the interactive full-screen terminal; `dsh-cli-demo` runs one persisted headless turn with format-pure stdout; `dsh-acp-demo` adds stdout-pure ACP over JSON-RPC ([ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK supplies its default only without an explicit config channel and drives `dsh-jsonrpc` over line-delimited JSON-RPC ([Python SDK](../python/README.md)). Deployments remain thin leaves with swappable backends and optional product tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)).
 
 ### Where New Behavior Goes
 

+ 7 - 6
docs/capability-seams.md

@@ -47,11 +47,12 @@ flowchart LR
   pkg_tool_todo["tool-todo"]
   pkg_user_interaction["user-interaction"]
   svc_userInteraction["ctx.userInteraction<br/>Human question/answer seam"]
-  pkg_stdio_demo["stdio-demo"]
+  pkg_tui["tui"]
   pkg_skill["skill"]
   svc_skills["ctx.skills<br/>Skill provider registry"]
   pkg_skill_local["skill-local"]
   svc_agents["ctx.agents<br/>Agent service"]
+  pkg_tui_demo["tui-demo"]
   svc_agentLoop["ctx.agentLoop<br/>Concrete loop driver"]
   pkg_agent_spine_demo["agent-spine-demo"]
   pkg_bash["bash"]
@@ -133,7 +134,6 @@ flowchart LR
   pkg_skill_local --> svc_skills
   pkg_spill --> svc_spillStore
   pkg_spill_local --> svc_spillStore
-  pkg_stdio_demo --> svc_userInteraction
   pkg_subagent --> svc_subagents
   pkg_subagent_acp --> svc_subagents
   pkg_subagent_fork --> svc_subagents
@@ -143,6 +143,7 @@ flowchart LR
   pkg_token_meter --> svc_tokenMeter
   pkg_tool_bash --> svc_bashEnv
   pkg_tools --> svc_tools
+  pkg_tui --> svc_userInteraction
   pkg_user_interaction --> svc_userInteraction
   pkg_web --> svc_web
   pkg_web_fetch_local --> svc_web
@@ -156,8 +157,8 @@ flowchart LR
   svc_agents --> pkg_agent_loop
   svc_agents --> pkg_cli_demo
   svc_agents --> pkg_invariants
-  svc_agents --> pkg_stdio_demo
   svc_agents --> pkg_subagent_inprocess
+  svc_agents --> pkg_tui_demo
   svc_approval --> pkg_tool_bash
   svc_approval --> pkg_tools
   svc_bash --> pkg_hooks_claude
@@ -208,8 +209,8 @@ flowchart LR
   svc_tools --> pkg_tool_todo
   svc_tools --> pkg_tool_web
   svc_userInteraction --> pkg_acp
-  svc_userInteraction --> pkg_stdio_demo
   svc_userInteraction --> pkg_tool_ask_user
+  svc_userInteraction --> pkg_tui
   svc_web --> pkg_tool_web
   svc_workflows --> pkg_tool_workflow
   svc_fs -. event gate .-> pkg_fs_policy
@@ -225,9 +226,9 @@ flowchart LR
 | `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | - | - | - | Resolves live and optional persisted logs into one logical corpus for exact reads and relationship traces. |
 | `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. |
 | `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/cordis/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web), [`acp`](../packages/ui/acp) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. |
-| `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/ui/user-interaction) | [`stdio-demo`](../packages/examples/stdio-demo), [`acp`](../packages/ui/acp) | [`tool-ask-user`](../packages/ui/tool-ask-user), [`stdio-demo`](../packages/examples/stdio-demo), [`acp`](../packages/ui/acp) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. |
+| `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/ui/user-interaction) | [`tui`](../packages/ui/tui), [`acp`](../packages/ui/acp) | [`tool-ask-user`](../packages/ui/tool-ask-user), [`tui`](../packages/ui/tui), [`acp`](../packages/ui/acp) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. |
 | `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. |
-| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`stdio-demo`](../packages/examples/stdio-demo), [`invariants`](../packages/support/invariants) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
+| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`tui-demo`](../packages/examples/tui-demo), [`invariants`](../packages/support/invariants) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
 | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
 | `ctx.bash` | `seam` | [`bash`](../packages/bash/bash) | [`bash-local`](../packages/bash/bash-local), [`bash-sandbox`](../packages/bash/bash-sandbox) | [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors replace bash-local without touching them. |
 | `ctx.bashEnv` | `core` | [`tool-bash`](../packages/bash/tool-bash) | - | - | - | Plugins declare effect-scoped DSH_* facts; tool-bash collects one trusted snapshot per execution and the executor rebuilds the namespace. |

+ 42 - 82
docs/config-catalog.md

@@ -860,88 +860,6 @@ export interface Config {
 
 Source: [`packages/spill/spill-policy/src/index.ts:45`](../packages/spill/spill-policy/src/index.ts)
 
-## `@deepseek-ai/dsh-stdio`
-
-Requires: `agents` · `userInteraction`
-
-```ts config-catalog
-/** Serializable plugin configuration (cordis-native, schemastery). */
-export interface Config {
-  /** Banner printed once on start, before the first `> ` prompt. */
-  welcome?: string
-  /** Exact shared agent/session identity stdin drives. Defaults to `'main'`. */
-  sessionId?: string
-}
-```
-
-Source: [`packages/ui/stdio/src/index.ts:33`](../packages/ui/stdio/src/index.ts)
-
-## `@deepseek-ai/dsh-stdio-demo`
-
-```ts config-catalog
-/**
- * App config: the swappable per-demo values, each routed to where the app wires
- * it. `provider`/`model`/`resumeSessionId` configure the pre-created `main` agent (through
- * {@link @deepseek-ai/dsh-agent-spine-demo}'s forwarded `agents` list); `persona` is
- * the deployment persona (forwarded to the system-prompt plugin); `toolOrder`
- * is the explicit model-facing tool order (forwarded to the system-prompt plugin);
- * fresh sessions use `process.cwd()` as their workspace cwd; resumed sessions
- * keep their persisted cwd. `persistenceRoot` is the JSONL backend's directory;
- * `welcome` is the UI banner and `ui` configures terminal mode/presentation.
- */
-export interface Config {
-  /** Provider route for the `main` agent. */
-  provider: string
-  /** Model name for the `main` agent (must have a registered adapter). */
-  model: string
-  /** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
-  maxParallelToolCalls?: number
-  /** Deployment persona (the system-prompt plugin's `persona` config). */
-  persona?: string
-  /** Explicit model-facing tool order (the system-prompt plugin's `toolOrder` config; see dsh-system-prompt). */
-  toolOrder?: string[]
-  /** Tool-registry config — its presentation `mode` (forwarded through agent-spine-demo; see dsh-tools). */
-  tools?: ToolsConfig
-  /** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
-  dshHome?: string
-  /** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
-  persistenceRoot?: string
-  /** stdin-chat banner printed once on start. Defaults to `'ready.'`. */
-  welcome?: string
-  /** Terminal front-door selection and pi-tui presentation settings. */
-  ui?: UiConfig
-  /** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
-  skills?: agentCore.SkillConfig
-  /** Model-facing bash tool config forwarded through agent-core. */
-  toolBash?: NonNullable<agentCore.Config['toolBash']>
-  /** Generic background-task controls forwarded through agent-core; set false to omit their tool surface. */
-  toolTasks?: NonNullable<agentCore.Config['toolTasks']>
-  /**
-   * If set, the pre-created agent RESUMES this persisted session id instead of
-   * starting fresh. Sourced from an env var in the leaf `cordis.yml`
-   * (`resumeSessionId: !!js process.env.RESUME_SESSION_ID`).
-   */
-  resumeSessionId?: string
-  /** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
-  workspaceContext: agentCore.Config['workspaceContext']
-}
-
-/** App-level terminal selection with nested TUI presentation settings. */
-export interface UiConfig {
-  /** Select a concrete front door or infer it from the process streams. */
-  mode?: TerminalMode
-  /** Settings forwarded only when the pi-tui front door is selected. */
-  tui?: uiTui.TuiConfig
-}
-
-/** Terminal front door selected by the app bundle. */
-export type TerminalMode = 'auto' | 'readline' | 'tui'
-```
-
-Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`uiTui`](../packages/ui/tui/src/index.ts)
-
-Source: [`packages/examples/stdio-demo/src/index.ts:75`](../packages/examples/stdio-demo/src/index.ts)
-
 ## `@deepseek-ai/dsh-subagent-acp`
 
 Requires: `subagents`
@@ -1320,6 +1238,48 @@ export interface TuiConfig {
 
 Source: [`packages/ui/tui/src/index.ts:100`](../packages/ui/tui/src/index.ts)
 
+## `@deepseek-ai/dsh-tui-demo`
+
+```ts config-catalog
+/** App config routed to the spine, TUI, configured agent, and JSONL backend. */
+export interface Config {
+  /** Provider route for the `main` agent. */
+  provider: string
+  /** Model name for the `main` agent; a matching adapter must be registered. */
+  model: string
+  /** Bundled agent-loop concurrency cap; `1` is serial and omission uses its default. */
+  maxParallelToolCalls?: number
+  /** Deployment persona forwarded to the system-prompt plugin. */
+  persona?: string
+  /** Explicit model-facing tool order forwarded to the system-prompt plugin. */
+  toolOrder?: string[]
+  /** Tool-registry presentation config forwarded through agent-spine-demo. */
+  tools?: ToolsConfig
+  /** DeepSeek Harness home directory exposed to bash and used for local skill discovery. */
+  dshHome?: string
+  /** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
+  persistenceRoot?: string
+  /** TUI subtitle rendered on start. Defaults to `ready.`. */
+  welcome?: string
+  /** Full-screen TUI presentation settings. */
+  ui?: uiTui.TuiConfig
+  /** Skill registry, local-provider, and model-facing consumer config. */
+  skills?: agentCore.SkillConfig
+  /** Model-facing bash tool config forwarded through agent-spine-demo. */
+  toolBash?: NonNullable<agentCore.Config['toolBash']>
+  /** Generic background-task controls forwarded through agent-spine-demo; set false to omit them. */
+  toolTasks?: NonNullable<agentCore.Config['toolTasks']>
+  /** Persisted session id to resume instead of creating a fresh session. */
+  resumeSessionId?: string
+  /** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
+  workspaceContext: agentCore.Config['workspaceContext']
+}
+```
+
+Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`uiTui`](../packages/ui/tui/src/index.ts)
+
+Source: [`packages/examples/tui-demo/src/index.ts:28`](../packages/examples/tui-demo/src/index.ts)
+
 ## `@deepseek-ai/dsh-user-approval`
 
 ```ts config-catalog

+ 2 - 2
docs/cookbook/extension-cookbook.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-extension-cookbook.md: 37793e4e76bf5171c759ca78be473912101bd9f4
-extension-cookbook.zh.md: 8f170f225b55721c78ef27c0e87e481b5cb00f64
+extension-cookbook.md: 811eeb04d1730a8062454f932477d1e05275f3cf
+extension-cookbook.zh.md: a1c22aeffcd337401bed9444ebd52ebd5b524595

+ 1 - 1
docs/cookbook/extension-cookbook.md

@@ -87,7 +87,7 @@ export function apply(ctx: Context) {
 
 ## Runnable wirings
 
-Six runnable leaves load their plugin trees from `cordis.yml`: [`examples/echo-agent`](../../examples/echo-agent) (mock model + echo tool, `pnpm run demo:echo`), [`examples/repl-agent`](../../examples/repl-agent) (DeepSeek V4 + coding tools through a line-oriented readline REPL, `pnpm run demo:repl`), [`examples/tui-agent`](../../examples/tui-agent) (the same coding composition through full-screen pi-tui, `pnpm run demo:tui`), [`examples/headless-agent`](../../examples/headless-agent) (the same capability class behind a one-shot task and DSH-native output, `pnpm run demo:headless -- "task"`), [`examples/cordis-agent`](../../examples/cordis-agent) (self-inspection and dynamic plugin mounting, `pnpm run demo:cordis`), and [`examples/acp-agent`](../../examples/acp-agent) (an ACP server over JSON-RPC stdio, `pnpm run demo:acp`). The terminal leaves load [`@deepseek-ai/dsh-stdio-demo`](../../packages/examples/stdio-demo), the headless leaf loads [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo), the ACP leaf loads [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and all three app packages share [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo).
+Five runnable leaves load their plugin trees from `cordis.yml`: [`examples/echo-agent`](../../examples/echo-agent) (keyless mock model + echo tool through Headless, `pnpm run demo:echo "task"`), [`examples/tui-agent`](../../examples/tui-agent) (DeepSeek coding tools through the full-screen TUI, `pnpm run demo:tui`), [`examples/headless-agent`](../../examples/headless-agent) (the coding capabilities behind a one-shot task and DSH-native output, `pnpm run demo:headless "task"`), [`examples/cordis-agent`](../../examples/cordis-agent) (self-inspection and dynamic plugin mounting through the TUI, `pnpm run demo:cordis`), and [`examples/acp-agent`](../../examples/acp-agent) (an ACP server over JSON-RPC stdio, `pnpm run demo:acp`). Interactive leaves load [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo), non-interactive leaves load [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo), the ACP leaf loads [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo), and all three app packages share [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo).
 
 ## The feature → mechanism map
 

+ 1 - 1
docs/cookbook/extension-cookbook.zh.md

@@ -87,7 +87,7 @@ export function apply(ctx: Context) {
 
 ## 可运行的组装示例
 
-六个可运行叶子从 `cordis.yml` 加载各自的插件树:[`examples/echo-agent`](../../examples/echo-agent)(mock 模型 + echo 工具,`pnpm run demo:echo`)、[`examples/repl-agent`](../../examples/repl-agent)(DeepSeek V4 + coding 工具,通过面向行的 readline REPL 交互,`pnpm run demo:repl`)、[`examples/tui-agent`](../../examples/tui-agent)(通过全屏 pi-tui 复用相同的 coding 组装,`pnpm run demo:tui`)、[`examples/headless-agent`](../../examples/headless-agent)(同类能力通过单次任务和 DSH 原生输出运行,`pnpm run demo:headless -- "task"`)、[`examples/cordis-agent`](../../examples/cordis-agent)(自我检查和动态插件挂载,`pnpm run demo:cordis`)与 [`examples/acp-agent`](../../examples/acp-agent)(通过 JSON-RPC stdio 暴露的 ACP 服务器,`pnpm run demo:acp`)。终端叶子加载 [`@deepseek-ai/dsh-stdio-demo`](../../packages/examples/stdio-demo),headless 叶子加载 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo),ACP 叶子加载 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),三个 app 包都通过 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 共享主干。
+五个可运行叶子从 `cordis.yml` 加载各自的插件树:[`examples/echo-agent`](../../examples/echo-agent)(通过 Headless 运行的 keyless mock 模型 + echo 工具,`pnpm run demo:echo "task"`)、[`examples/tui-agent`](../../examples/tui-agent)(通过全屏 TUI 运行的 DeepSeek coding 工具,`pnpm run demo:tui`)、[`examples/headless-agent`](../../examples/headless-agent)(通过单次任务和 DSH 原生输出运行的 coding 能力,`pnpm run demo:headless "task"`)、[`examples/cordis-agent`](../../examples/cordis-agent)(通过 TUI 进行自我检查和动态插件挂载,`pnpm run demo:cordis`)与 [`examples/acp-agent`](../../examples/acp-agent)(通过 JSON-RPC stdio 暴露的 ACP 服务器,`pnpm run demo:acp`)。交互式叶子加载 [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo),非交互式叶子加载 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo),ACP 叶子加载 [`@deepseek-ai/dsh-acp-demo`](../../packages/examples/acp-demo),三个 app 包都通过 [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) 共享主干。
 
 ## 功能→机制映射
 

+ 1 - 1
docs/core-data-structures/user-interaction.md

@@ -1,6 +1,6 @@
 # User Interaction
 
-The user-interaction seam of [dsh-user-interaction](../../packages/ui/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`: `dsh-stdio-demo` selects keyboard-driven `dsh-tui` overlays or `dsh-stdio` readline prompts, and `dsh-acp` maps questions to ACP form elicitations.
+The user-interaction seam of [dsh-user-interaction](../../packages/ui/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`: `dsh-tui` uses keyboard-driven overlays, and `dsh-acp` maps questions to ACP form elicitations.
 
 Source: [`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts)
 

+ 2 - 2
docs/development.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-development.md: 94eb4f03329b574862a1ac1de2f8c1d4db4f4a0a
-development.zh.md: b533aff43a66ff7cfc5dc61e5b9b224a01c51f12
+development.md: 6517a14f094a5098c26815e04f81e3f8ea1ceff9
+development.zh.md: bb70970679aaa8ffaed927b746002277cb422f6b

+ 4 - 10
docs/development.md

@@ -9,7 +9,7 @@ This onboarding guide helps project contributors get started with the local envi
 - Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).
 - Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.
 - Git.
-- Optional: a DeepSeek API key for the REPL/ACP agent demos and real-API e2e tests.
+- Optional: a DeepSeek API key for the TUI/Headless/ACP agent demos and real-API e2e tests.
 
 ## First-time setup
 
@@ -102,19 +102,13 @@ When changing package public behavior, update the relevant README or JSDoc in th
 
 ## Demos
 
-The echo demo does not need API credentials:
+The Headless echo demo does not need API credentials:
 
 ```sh
-pnpm run demo:echo
+pnpm run demo:echo "echo hello"
 ```
 
-The repl-agent demo uses the line-oriented readline front door and needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
-
-```sh
-pnpm run demo:repl
-```
-
-The full-screen TUI reuses the repl-agent composition through the pi-tui front door and needs the same credentials:
+The full-screen interactive coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:
 
 ```sh
 pnpm run demo:tui

+ 4 - 10
docs/development.zh.md

@@ -9,7 +9,7 @@
 - Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。
 - 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。
 - Git。
-- 可选:一个 DeepSeek API key,用于 REPL/ACP(Agent Client Protocol) agent(智能体)演示和真实 API 的 e2e 测试。
+- 可选:一个 DeepSeek API key,用于 TUI/Headless/ACP(Agent Client Protocol) agent(智能体)演示和真实 API 的 e2e 测试。
 
 ## 首次搭建
 
@@ -102,19 +102,13 @@ pnpm run hygiene        # knip, publint, workspace constraints, and NodeNext dec
 
 ## 演示
 
-echo 演示不需要 API 凭证:
+Headless echo 演示不需要 API 凭证:
 
 ```sh
-pnpm run demo:echo
+pnpm run demo:echo "echo hello"
 ```
 
-repl-agent 示例使用面向行的 readline 前端,并需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
-
-```sh
-pnpm run demo:repl
-```
-
-全屏 TUI 通过 pi-tui 前端复用 repl-agent 组装,并需要相同的凭证:
+全屏交互式 coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:
 
 ```sh
 pnpm run demo:tui

+ 6 - 6
docs/event-producer-consumer.md

@@ -7,9 +7,9 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 
 | Event | Mode | Declared in | Dispatchers | Listeners |
 | --- | --- | --- | --- | --- |
-| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:362`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
-| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:150`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
-| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:159`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
+| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:362`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
+| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:150`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
+| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:159`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`tui`](../packages/ui/tui) |
 | `agent/error` | `emit` | [`packages/core/agent/src/types.ts:314`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`tui`](../packages/ui/tui) |
 | `agent/post-step` | `serial` | [`packages/core/agent/src/types.ts:267`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic) |
 | `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:207`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`time-context`](../packages/context/time-context), [`user-approval`](../packages/ui/user-approval) |
@@ -18,8 +18,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 | `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:229`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`acp`](../packages/ui/acp) |
 | `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:281`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic) |
 | `agent/session-prefix` | `waterfall` | [`packages/core/agent/src/types.ts:244`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) |
-| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:191`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`stdio`](../packages/ui/stdio) |
-| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:168`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`invariants`](../packages/support/invariants), [`stdio`](../packages/ui/stdio), [`tui`](../packages/ui/tui) |
+| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:191`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
+| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:168`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`invariants`](../packages/support/invariants), [`tui`](../packages/ui/tui) |
 | `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:255`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - |
 | `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:291`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
 | `agent/turn-stop` | `serial` | [`packages/core/agent/src/types.ts:301`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
@@ -30,7 +30,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 | `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:43`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`invariants`](../packages/support/invariants), [`llm-replay`](../packages/support/llm-replay) |
 | `session/created` | `emit` | [`packages/core/session/src/index.ts:47`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence) |
 | `session/disposed` | `emit` | [`packages/core/session/src/index.ts:57`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`session-persistence`](../packages/session-persistence/session-persistence) |
-| `session/event` | `emit` | [`packages/core/session/src/index.ts:69`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio`](../packages/ui/stdio), [`token-meter`](../packages/llm/token-meter), [`tui`](../packages/ui/tui), [`workspace-context`](../packages/context/workspace-context) |
+| `session/event` | `emit` | [`packages/core/session/src/index.ts:69`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`cli-demo`](../packages/examples/cli-demo), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`token-meter`](../packages/llm/token-meter), [`tui`](../packages/ui/tui), [`workspace-context`](../packages/context/workspace-context) |
 | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:79`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) |
 | `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:112`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) |
 | `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:86`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |

+ 0 - 1
docs/graph-atlas.md

@@ -13,7 +13,6 @@ The process decision behind this index is recorded in [the documentation graph A
 | [tool schema catalog and package map](tool-catalog.md) | `generated` |
 | [capability seams and core services](capability-seams.md) | `hybrid generated` |
 | [echo-agent app composition](../examples/echo-agent/composition.md) | `hybrid generated` |
-| [repl-agent app composition](../examples/repl-agent/composition.md) | `hybrid generated` |
 | [tui-agent app composition](../examples/tui-agent/composition.md) | `hybrid generated` |
 | [headless-agent app composition](../examples/headless-agent/composition.md) | `hybrid generated` |
 | [cordis-agent app composition](../examples/cordis-agent/composition.md) | `hybrid generated` |

+ 3 - 3
docs/i18n/translation-prompt.md

@@ -123,9 +123,9 @@ Follow the Good versions; these sentence-level examples illustrate error categor
 - Good: `A green gate does not mean the translation is correct.`
 
 ### Code block comments — never translate
-- Source code block contains: `# readline coding agent (needs DEEPSEEK_API_KEY)`
-- Bad: `# readline 编码 agent(需要 DEEPSEEK_API_KEY)`
-- Good: `# readline coding agent (needs DEEPSEEK_API_KEY)` (byte-identical)
+- Source code block contains: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)`
+- Bad: `# 全屏 TUI coding agent(需要 DEEPSEEK_API_KEY)`
+- Good: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)` (byte-identical)
 
 ### Language switcher — English to Chinese
 - Source: `English | [中文](README.zh.md)`

+ 14 - 22
docs/module-graph.md

@@ -108,7 +108,6 @@ flowchart TD
     pkg_app_boot["app-boot"]
     pkg_jsonrpc["jsonrpc"]
     pkg_permission["permission"]
-    pkg_stdio["stdio"]
     pkg_tool_ask_user["tool-ask-user"]
     pkg_tui["tui"]
     pkg_user_approval["user-approval"]
@@ -127,7 +126,7 @@ flowchart TD
     pkg_agent_spine_demo["agent-spine-demo"]
     pkg_cli_demo["cli-demo"]
     pkg_jsonrpc_demo["jsonrpc-demo"]
-    pkg_stdio_demo["stdio-demo"]
+    pkg_tui_demo["tui-demo"]
   end
   subgraph group_guard["packages/guard"]
     pkg_repeat_tool_guard["repeat-tool-guard"]
@@ -395,11 +394,6 @@ flowchart TD
   pkg_jsonrpc --> pkg_scope
   pkg_jsonrpc --> pkg_session
   pkg_jsonrpc --> pkg_subagent
-  pkg_stdio --> pkg_agent
-  pkg_stdio --> pkg_agent_loop
-  pkg_stdio --> pkg_llm
-  pkg_stdio --> pkg_session
-  pkg_stdio --> pkg_user_interaction
   pkg_tui --> pkg_agent
   pkg_tui --> pkg_agent_loop
   pkg_tui --> pkg_llm
@@ -449,19 +443,18 @@ flowchart TD
   pkg_cli_demo --> pkg_session_persistence_jsonl
   pkg_cli_demo --> pkg_tools
   pkg_cli_demo --> pkg_workspace_context
-  pkg_stdio_demo --> pkg_agent
-  pkg_stdio_demo --> pkg_agent_loop
-  pkg_stdio_demo --> pkg_agent_spine_demo
-  pkg_stdio_demo --> pkg_app_boot
-  pkg_stdio_demo --> pkg_llm
-  pkg_stdio_demo --> pkg_session
-  pkg_stdio_demo --> pkg_session_persistence_jsonl
-  pkg_stdio_demo --> pkg_stdio
-  pkg_stdio_demo --> pkg_tool_ask_user
-  pkg_stdio_demo --> pkg_tools
-  pkg_stdio_demo --> pkg_tui
-  pkg_stdio_demo --> pkg_user_interaction
-  pkg_stdio_demo --> pkg_workspace_context
+  pkg_tui_demo --> pkg_agent
+  pkg_tui_demo --> pkg_agent_loop
+  pkg_tui_demo --> pkg_agent_spine_demo
+  pkg_tui_demo --> pkg_app_boot
+  pkg_tui_demo --> pkg_llm
+  pkg_tui_demo --> pkg_session
+  pkg_tui_demo --> pkg_session_persistence_jsonl
+  pkg_tui_demo --> pkg_tool_ask_user
+  pkg_tui_demo --> pkg_tools
+  pkg_tui_demo --> pkg_tui
+  pkg_tui_demo --> pkg_user_interaction
+  pkg_tui_demo --> pkg_workspace_context
 ```
 
 | Package | Group | Depends on |
@@ -550,7 +543,6 @@ flowchart TD
 | [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
 | [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
 | [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
-| [`stdio`](../packages/ui/stdio) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`user-interaction`](../packages/ui/user-interaction) |
 | [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
 | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`home`](../packages/util/home), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tool-bash`](../packages/bash/tool-bash), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
 | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
@@ -558,4 +550,4 @@ flowchart TD
 | [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) |
 | [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/ui/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |
 | [`cli-demo`](../packages/examples/cli-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) |
-| [`stdio-demo`](../packages/examples/stdio-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`stdio`](../packages/ui/stdio), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |
+| [`tui-demo`](../packages/examples/tui-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) |

+ 1 - 1
docs/postmortem/0001-acp-default-export-drops-inject.md

@@ -24,7 +24,7 @@ The ACP server could not create or load a single session — the two RPCs an edi
 
 ## Root cause #1 — `export default apply` drops the plugin's `inject` (broke `session/new`)
 
-`packages/ui/acp/src/index.ts` is a *namespace plugin*: it exports `name`, `inject`, `Config`, and `apply` as separate named exports — the same shape as every other plugin in the repo (`invariants`, `llm-deepseek`, `tool-bash`, `stdio-chat`, …). But it *also* ended with one extra line no other plugin had:
+`packages/ui/acp/src/index.ts` is a *namespace plugin*: it exports `name`, `inject`, `Config`, and `apply` as separate named exports — the same shape as every other plugin in the repo (`invariants`, `llm-deepseek`, `tool-bash`, `tui`, …). But it *also* ended with one extra line no other plugin had:
 
 ```ts ignore-check
 export const name = 'acp'

+ 2 - 2
docs/tool-catalog.md

@@ -22,7 +22,7 @@ This table connects model-visible tool names to the plugin package and service s
 | `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after successful file operations`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The tool schemas above are identical with or without the policy plugin. |
 | `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.bash`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are conditional bash-backed discovery tools: they register only when ctx.bash can find `rg`, then run fixed ripgrep commands through ctx.bash as ordinary foreground calls (never background tasks). Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. |
 | `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.skills` | `tool/call`, `tool/result` | - | - |
-| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. |
+| `@deepseek-ai/dsh-tool-subagent` | `subagent` | `ctx.tools`, `ctx.subagents` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/tui-agent/cordis.yml` and `examples/acp-agent/cordis.yml`. |
 | `@deepseek-ai/dsh-tool-tasks` | `task_kill`, `task_list`, `task_output` | `ctx.tools`, `ctx.tasks`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `context/message via agent.inject() for background completion notices` | - | The kind-agnostic background-task control surface: a background bash command and a background subagent are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers' `ctx.tasks.start()`. |
 | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan. |
 | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflows`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
@@ -448,7 +448,7 @@ Delegate a self-contained task to a subagent (a separate agent that works in its
 
 Source: [`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/tool-subagent/src/index.ts)
 
-The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/repl-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.
+The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped example agents load this package once per subagent backend, so the model additionally sees `subagent_fork` (bound to the fork backend) with an identical schema — see `examples/tui-agent/cordis.yml` and `examples/acp-agent/cordis.yml`.
 
 ## `@deepseek-ai/dsh-tool-tasks`
 

+ 2 - 2
docs/user/develop/practice/llm-adapter.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-llm-adapter.md: f34fc9e1d5b59a323bb562764821ef910025880e
-llm-adapter.zh.md: 3c781ae8a1a011e2f73d5f6de43f6f75e1fb549f
+llm-adapter.md: 83296e54220c668410fe69d689199171251a7787
+llm-adapter.zh.md: 89b7185690dbdfe33cbafe7b0ee4c3e83cfe0df8

+ 4 - 2
docs/user/develop/practice/llm-adapter.md

@@ -131,10 +131,12 @@ The first argument lists the model names handled by the adapter. If `cordis.yml`
       - my-model-v1
       - my-model-v2
 
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
   config:
+    provider: my-llm
     model: my-model-v1  # References the model registered above.
+    workspaceContext: false
 ```
 
 ## Reference implementations

+ 4 - 2
docs/user/develop/practice/llm-adapter.zh.md

@@ -131,10 +131,12 @@ ctx.llm.registerAdapter(['model-name-1', 'model-name-2'], adapter)
       - my-model-v1
       - my-model-v2
 
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
   config:
+    provider: my-llm
     model: my-model-v1  # References the model registered above.
+    workspaceContext: false
 ```
 
 ## 实战参考

+ 2 - 2
docs/user/guide/config.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-config.md: a3f56018fd43cc803c1710f97c29a77340a0b257
-config.zh.md: af661b9d7ef72e4085551202169e975bd0c3ec99
+config.md: 0616f163f995b152d7a28841506027558de2c32c
+config.zh.md: fa91445ae88456a61a4736ce8b71aed482227ce8

+ 9 - 3
docs/user/guide/config.md

@@ -9,7 +9,8 @@ Harness uses `cordis.yml` to describe which plugins an agent loads and the confi
 The repository examples are runnable configurations and the most reliable starting points for a new project:
 
 - [echo-agent](../../../examples/echo-agent/cordis.yml) uses a local mock model and needs no API key.
-- [repl-agent](../../../examples/repl-agent/cordis.yml) combines the DeepSeek model, Bash, filesystem, compaction, subagents, and workflows.
+- [tui-agent](../../../examples/tui-agent/cordis.yml) combines the DeepSeek model, Bash, filesystem, compaction, subagents, workflows, and the interactive TUI.
+- [headless-agent](../../../examples/headless-agent/cordis.yml) exposes the coding composition as a one-shot task.
 - [acp-agent](../../../examples/acp-agent/cordis.yml) connects to editor clients over ACP.
 
 A minimal configuration is a list of plugin entries:
@@ -22,10 +23,15 @@ A minimal configuration is a list of plugin entries:
     models:
       - deepseek-v4-flash
 
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: bash
+  name: '@deepseek-ai/dsh-bash-local'
+
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
   config:
+    provider: deepseek
     model: deepseek-v4-flash
+    workspaceContext: false
 ```
 
 ## Plugin entries

+ 9 - 3
docs/user/guide/config.zh.md

@@ -9,7 +9,8 @@ Harness 使用 `cordis.yml` 描述 Agent 加载哪些插件以及每个插件的
 仓库中的示例就是可以运行的配置,也是新项目最可靠的起点:
 
 - [echo-agent](../../../examples/echo-agent/cordis.yml) 使用本地 mock 模型,不需要 API key。
-- [repl-agent](../../../examples/repl-agent/cordis.yml) 组合 DeepSeek 模型、Bash、文件系统、压缩、子代理和工作流。
+- [tui-agent](../../../examples/tui-agent/cordis.yml) 组合 DeepSeek 模型、Bash、文件系统、压缩、子代理、工作流和交互式 TUI。
+- [headless-agent](../../../examples/headless-agent/cordis.yml) 以单次任务形式暴露 coding 组装。
 - [acp-agent](../../../examples/acp-agent/cordis.yml) 通过 ACP 接入编辑器客户端。
 
 最小配置由一组插件条目组成:
@@ -22,10 +23,15 @@ Harness 使用 `cordis.yml` 描述 Agent 加载哪些插件以及每个插件的
     models:
       - deepseek-v4-flash
 
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: bash
+  name: '@deepseek-ai/dsh-bash-local'
+
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
   config:
+    provider: deepseek
     model: deepseek-v4-flash
+    workspaceContext: false
 ```
 
 ## 插件条目

+ 2 - 2
docs/user/guide/index.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-index.md: a20b1041e13b01b6b1d01a5baa8975d3e68c6aa0
-index.zh.md: 56ec50352218e2e28ad2dd7a6ef387376de75606
+index.md: b698b8aeee6cebff374e20ca0f76ddc9e75213c0
+index.zh.md: 337d246baa12ccf6d7a9656d1ea3b06002554c13

+ 4 - 2
docs/user/guide/index.md

@@ -14,10 +14,12 @@ Harness implements every capability an AI agent needs—including LLM calls, too
   config:
     apiKey: !!js process.env.DEEPSEEK_API_KEY
 
-# Select the application template
-- name: '@deepseek-ai/dsh-stdio-demo'
+# Select the interactive application
+- name: '@deepseek-ai/dsh-tui-demo'
   config:
+    provider: deepseek
     model: deepseek-v4-flash
+    workspaceContext: false
 ```
 
 ## Who it is for

+ 4 - 2
docs/user/guide/index.zh.md

@@ -14,10 +14,12 @@ Harness 将一个 AI Agent(智能体) 所需要的所有能力——LLM 调
   config:
     apiKey: !!js process.env.DEEPSEEK_API_KEY
 
-# Select the application template
-- name: '@deepseek-ai/dsh-stdio-demo'
+# Select the interactive application
+- name: '@deepseek-ai/dsh-tui-demo'
   config:
+    provider: deepseek
     model: deepseek-v4-flash
+    workspaceContext: false
 ```
 
 ## 适合谁

+ 2 - 2
docs/user/guide/quickstart.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write
-quickstart.md: acae2ac095e057971043c2bcece7a52d3ebc1c2c
-quickstart.zh.md: 54643fe54e62dbbd3696362cb43ff8569577c53b
+quickstart.md: 62e899adfa33e566083b8224b9bdf77c1038557a
+quickstart.zh.md: 382c9685ebe0c919c2fd039484898b44e9264aa7

+ 12 - 59
docs/user/guide/quickstart.md

@@ -7,91 +7,44 @@ This guide gets an agent running in five minutes.
 ## Prerequisites
 
 - [Node.js](https://nodejs.org/) ^22.19 or >= 24
-- [pnpm](https://pnpm.io/) 11 (use Corepack to select the repository-pinned version)
+- [pnpm](https://pnpm.io/) 11 through Corepack
 
 ```sh
-# Check versions
-node -v   # v22.19.x, or v24.x and newer
+node -v
 corepack enable
-pnpm -v   # 11.x
+pnpm -v
 ```
 
-## Step 1: run echo-agent
-
-echo-agent needs no API key and runs after dependencies are installed.
+## Step 1: run the keyless Headless demo
 
 ```sh
-# Clone the repository
 git clone https://github.com/deepseek-harness/deepseek-harness.git
 cd deepseek-harness
-
-# Install dependencies
 pnpm install
-
-# Start echo-agent
-pnpm run demo:echo
-```
-
-The process prints:
-
-```
-echo-agent ready. Type a message ("echo <text>" triggers the tool).
->
-```
-
-Enter:
-
-```
-> echo hello world
-```
-
-The model issues a tool call, and the echo tool returns the text in uppercase:
-
-```
-[tool call] echo({"text":"hello world"})
-[tool result] ECHO: HELLO WORLD
+pnpm run demo:echo "echo hello world"
 ```
 
-Your local environment is ready.
-
-## Step 2: use a real model
-
-Next, connect a real DeepSeek model and run the complete command-line agent.
+The local mock model calls the `echo` tool, which returns the text in uppercase, and the final response is printed without opening an interactive UI. Use `--output-format stream-json` when you need the canonical event stream.
 
-### Get an API key
+## Step 2: use a real model in the TUI
 
-Get an API key from [DeepSeek Platform](https://platform.deepseek.com/).
-
-### Configure the environment
-
-Create a gitignored `.env` file in the repository root:
+Get an API key from [DeepSeek Platform](https://platform.deepseek.com/) and create the gitignored repository-root `.env`:
 
 ```sh
 DEEPSEEK_API_KEY=sk-your-key-here
 ```
 
-### Start repl-agent
+Start the interactive coding agent:
 
 ```sh
-pnpm run demo:repl
+pnpm run demo:tui
 ```
 
-```
-agent REPL ready. Give it a coding task.
->
-```
-
-This is a complete coding assistant that can read and write files, run commands, and delegate subtasks.
-
-Try a task:
-
-```
-> Create hello.js in the current directory, print "Hello from Harness!", and run it
-```
+The full-screen agent can read and write files, run commands, delegate subtasks, and track a plan. Try: `Create hello.js in the current directory, print "Hello from Harness!", and run it`.
 
 ## What happened
 
-echo-agent and repl-agent use the same application framework (`@deepseek-ai/dsh-stdio-demo`). Their `cordis.yml` files select different plugins and configuration. Custom agents use the same composition model.
+echo-agent uses the Headless `@deepseek-ai/dsh-cli-demo` app; tui-agent uses the interactive `@deepseek-ai/dsh-tui-demo` app. Both load the same providerless agent spine, while their `cordis.yml` files select the model and capability plugins appropriate to each surface.
 
 ## Next steps
 

+ 14 - 61
docs/user/guide/quickstart.zh.md

@@ -7,93 +7,46 @@
 ## 环境准备
 
 - [Node.js](https://nodejs.org/) ^22.19 或 >= 24
-- [pnpm](https://pnpm.io/) 11(建议通过 Corepack 使用仓库固定的版本)
+- 通过 Corepack 使用 [pnpm](https://pnpm.io/) 11
 
 ```sh
-# Check versions
-node -v   # v22.19.x, or v24.x and newer
+node -v
 corepack enable
-pnpm -v   # 11.x
+pnpm -v
 ```
 
-## 第一步:运行 echo-agent
-
-echo-agent 不需要 API key,装好依赖就能跑。
+## 第一步:运行 keyless Headless 演示
 
 ```sh
-# Clone the repository
 git clone https://github.com/deepseek-harness/deepseek-harness.git
 cd deepseek-harness
-
-# Install dependencies
 pnpm install
-
-# Start echo-agent
-pnpm run demo:echo
-```
-
-启动后你会看到:
-
-```
-echo-agent ready. Type a message ("echo <text>" triggers the tool).
->
-```
-
-试着输入:
-
-```
-> echo hello world
-```
-
-你会看到模型发起了一次 tool call(工具调用),echo 工具将文本转为大写并返回:
-
-```
-[tool call] echo({"text":"hello world"})
-[tool result] ECHO: HELLO WORLD
+pnpm run demo:echo "echo hello world"
 ```
 
-恭喜!环境没问题。
-
-## 第二步:使用真实模型调用
-
-接下来接入真实的 DeepSeek 模型,跑一个完整的命令行 Agent。
+本地 mock 模型会调用 `echo` 工具,由工具返回大写文本,最终回复在不打开交互式 UI 的情况下直接输出。需要规范事件流时可使用 `--output-format stream-json`。
 
-### 获取 API Key
+## 第二步:在 TUI 中使用真实模型
 
-前往 [DeepSeek Platform](https://platform.deepseek.com/) 获取你的 API key。
-
-### 配置环境变量
-
-在仓库根目录创建 `.env` 文件(已被 gitignore):
+前往 [DeepSeek Platform](https://platform.deepseek.com/) 获取 API key,并创建已被 Git 忽略的仓库根目录 `.env`:
 
 ```sh
 DEEPSEEK_API_KEY=sk-your-key-here
 ```
 
-### 启动 repl-agent
+启动交互式 coding agent:
 
 ```sh
-pnpm run demo:repl
+pnpm run demo:tui
 ```
 
-```
-agent REPL ready. Give it a coding task.
->
-```
-
-这就是一个完整的编程助手,它能读写文件、跑命令、拆分子任务。
-
-试着给它一个任务:
-
-```
-> Create hello.js in the current directory, print "Hello from Harness!", and run it
-```
+这个全屏 Agent 可以读写文件、运行命令、分配子任务和跟踪计划。可以尝试:`Create hello.js in the current directory, print "Hello from Harness!", and run it`。
 
 ## 回头看
 
-echo-agent 和 repl-agent 用的是同一个应用框架(`@deepseek-ai/dsh-stdio-demo`),区别只在 `cordis.yml`——换了哪些插件、填了什么配置。你以后定制自己的 Agent 也是同样的方式。
+echo-agent 使用 Headless `@deepseek-ai/dsh-cli-demo` app,tui-agent 使用交互式 `@deepseek-ai/dsh-tui-demo` app。二者加载同一个 providerless agent spine,并通过各自的 `cordis.yml` 为对应 surface 选择模型和能力插件。
 
 ## 下一步
 
-- [配置文件](./config.md) — 了解 `cordis.yml` 的完整语法
-- [开发插件](../develop/basic/) — 编写你自己的 tool 或后端
+- [配置文件](./config.md) — 了解 `cordis.yml` 的格式
+- [开发插件](../develop/basic/) — 编写自己的 tool 或后端

+ 1 - 1
examples/AGENTS.md

@@ -13,7 +13,7 @@ Each example has both:
 
 Mock-only examples require only the keyless tier; state that exception in the test.
 
-Keyless stdio smokes use `@deepseek-ai/dsh-loader-smoke`; tests supply paths, environment, input, and assertions. Every checked-in test Cordis config lives under its corresponding `examples/<agent>/` leaf. Map a package-owned config to `examples/<agent>/tests/fixtures/<group>/<package>/cordis.yml`, keep its driver and assertions package-local, and declare every package it names in both root `tsconfig.json` references and `examples/package.json`.
+Keyless process smokes use `@deepseek-ai/dsh-loader-smoke` for Loader launch resolution; terminal tests wrap that launch in a pseudo-terminal. Tests supply paths, environment, input, and assertions. Every checked-in test Cordis config lives under its corresponding `examples/<agent>/` leaf. Map a package-owned config to `examples/<agent>/tests/fixtures/<group>/<package>/cordis.yml`, keep its driver and assertions package-local, and declare every package it names in both root `tsconfig.json` references and `examples/package.json`.
 
 Do not inventory example tests here; the `tests/` trees and root scripts are authoritative.
 

+ 8 - 16
examples/README.md

@@ -1,37 +1,29 @@
 # Examples
 
-Runnable demos (not workspaces) that showcase how the harness is wired. Each example is a **thin leaf**: a `cordis.yml` that picks the swappable backends (an LLM adapter, a bash executor), loads one app package, and may add optional product tools or demo-only mocks. The composition — the spine, the front-door cluster, and the boot glue — lives in the app packages ([`@deepseek-ai/dsh-stdio-demo`](../packages/examples/stdio-demo), [`@deepseek-ai/dsh-cli-demo`](../packages/examples/cli-demo), [`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo)) and the [`@deepseek-ai/dsh-agent-spine-demo`](../packages/examples/agent-spine-demo) bundle they share. There is no `start.ts`; the `demo:*` scripts invoke each app package's `bin`.
+Runnable demos (not workspaces) that showcase how the harness is wired. Each example is a **thin leaf**: a `cordis.yml` that picks swappable backends, loads one app package, and may add optional product tools or demo-only mocks. The composition and boot glue live in [`@deepseek-ai/dsh-tui-demo`](../packages/examples/tui-demo), [`@deepseek-ai/dsh-cli-demo`](../packages/examples/cli-demo), [`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo), and their shared [`@deepseek-ai/dsh-agent-spine-demo`](../packages/examples/agent-spine-demo) bundle. There is no `start.ts`; the `demo:*` scripts invoke each app package's bin.
 
 ## echo-agent
 
-A mock model + echo tool on the stdio chat app — the all-mock skeleton. The leaf swaps `dsh-stdio-demo`'s LLM backend to a local `mock-echo` adapter and adds a local `echo` tool. Demonstrates:
+A mock model + echo tool on the headless one-shot app — the all-mock skeleton. It demonstrates:
 
-- A thin leaf `cordis.yml` loading the `@deepseek-ai/dsh-stdio-demo` app
+- A thin leaf `cordis.yml` loading the `@deepseek-ai/dsh-cli-demo` app
 - Registering a mock `LlmAdapter` (streaming scripted responses)
 - Registering a tool via `ctx.tools.register()`
-- "Swap the backend, keep the app" — the only difference from `repl-agent` is the adapter
+- A network-free Headless task with text or DSH-native JSON output
 
-Run with: `pnpm run demo:echo`. When prompted, type "echo <something>" to trigger a tool call round-trip.
-
-## repl-agent
-
-A coding agent with DeepSeek V4, the `read`/`write`/`edit` filesystem tools, the bash tool suite, `subagent` delegation, and the `todo_write` task tracker on the `@deepseek-ai/dsh-stdio-demo` app's readline front door.
-
-Run with: `pnpm run demo:repl` (needs `DEEPSEEK_API_KEY` in the environment or a gitignored repo-root `.env`). See [repl-agent/README.md](repl-agent/README.md) for details.
-
-Run the Code Mode overlay with `pnpm run demo:code-mode`, or pass `acp` for the ACP example. See the [Code Mode example](repl-agent/README.md#code-mode) for its composition and a sample task.
+Run with: `pnpm run demo:echo "echo hello"`. The task prefix `echo ` triggers a tool-call round trip.
 
 ## headless-agent
 
 A non-interactive agent demo that accepts one positional task, runs one complete model/tool turn on the `@deepseek-ai/dsh-cli-demo` app, persists a fresh session, prints `text`, `json`, or `stream-json`, and exits.
 
-Run with: `pnpm run demo:headless -- "task"` (needs `DEEPSEEK_API_KEY`). See [headless-agent/README.md](headless-agent/README.md) for the output contract, safety boundaries, and snapshot suite.
+Run with: `pnpm run demo:headless "task"` (needs `DEEPSEEK_API_KEY`). See [headless-agent/README.md](headless-agent/README.md) for the output contract, safety boundaries, and snapshot suite.
 
 ## tui-agent
 
-The full-screen terminal sibling of `repl-agent`: it reuses the same coding backends and tools while forcing the shared terminal app to `dsh-tui`. It is the home of TUI PTY and snapshot scenarios.
+The interactive coding agent: DeepSeek V4, filesystem and bash tools, subagents, workflows, `todo_write`, compaction, and the full-screen TUI. It is also the home of TUI PTY and snapshot scenarios.
 
-Run with: `pnpm run demo:tui` (needs `DEEPSEEK_API_KEY`). See [tui-agent/README.md](tui-agent/README.md) for controls and composition.
+Run with: `pnpm run demo:tui` (needs `DEEPSEEK_API_KEY`). Run its Code Mode overlay with `pnpm run demo:code-mode`. See [tui-agent/README.md](tui-agent/README.md) for controls and composition.
 
 ## jsonrpc-agent
 

+ 1 - 1
examples/cordis-agent/README.md

@@ -1,6 +1,6 @@
 # cordis-agent
 
-The self-referential harness demo: the coding spine (DeepSeek V4 + local bash on the stdio chat app) plus [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md), which hands the model three tools over the **live cordis runtime it is running inside** — inspect it, mount new plugins into it, and dispose them again. The `ctx.fs` and `ctx.web` services are mounted (provider-only, no model-facing file/web tools) so the plugins the agent writes have real capabilities to build on; Node built-ins are trapped in the sandbox and redirect to those services. The design (sandbox semantics, mount lifecycle, cross-mount composition, caveats) lives in [the toolset Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
+The self-referential harness demo: the DeepSeek V4 coding spine on the full-screen TUI plus [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md), which hands the model three tools over the **live cordis runtime it is running inside** — inspect it, mount new plugins into it, and dispose them again. The `ctx.fs` and `ctx.web` services are mounted (provider-only, no model-facing file/web tools) so the plugins the agent writes have real capabilities to build on; Node built-ins are trapped in the sandbox and redirect to those services. The design (sandbox semantics, mount lifecycle, cross-mount composition, caveats) lives in [the toolset Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md).
 
 ## Run it
 

+ 6 - 6
examples/cordis-agent/composition.md

@@ -20,11 +20,11 @@ flowchart LR
   cfg --> plugin_cordis_web
   plugin_cordis_web_fetch_local["web-fetch-local<br/>@deepseek-ai/dsh-web-fetch-local"]
   cfg --> plugin_cordis_web_fetch_local
-  plugin_cordis_stdio_agent["stdio-agent<br/>@deepseek-ai/dsh-stdio-demo"]
-  cfg --> plugin_cordis_stdio_agent
-  plugin_cordis_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
-  plugin_cordis_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
-  plugin_cordis_stdio_agent --> frontdoor_stdio["dsh-tui (TTY) / dsh-stdio (pipes)<br/>pre-created main agent"]
+  plugin_cordis_tui_agent["tui-agent<br/>@deepseek-ai/dsh-tui-demo"]
+  cfg --> plugin_cordis_tui_agent
+  plugin_cordis_tui_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
+  plugin_cordis_tui_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
+  plugin_cordis_tui_agent --> frontdoor_tui["@deepseek-ai/dsh-tui<br/>pre-created main agent"]
   bundle_agent_core --> spine_llm["ctx.llm"]
   bundle_agent_core --> spine_sessions["ctx.sessions"]
   bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
@@ -41,7 +41,7 @@ flowchart LR
 | `fs-local` | `@deepseek-ai/dsh-fs-local` |
 | `web` | `@deepseek-ai/dsh-web` |
 | `web-fetch-local` | `@deepseek-ai/dsh-web-fetch-local` |
-| `stdio-agent` | `@deepseek-ai/dsh-stdio-demo` |
+| `tui-agent` | `@deepseek-ai/dsh-tui-demo` |
 | `tool-cordis` | `@deepseek-ai/dsh-tool-cordis` |
 
 Source config: [`examples/cordis-agent/cordis.yml`](cordis.yml).

+ 3 - 3
examples/cordis-agent/cordis.yml

@@ -1,4 +1,4 @@
-# Self-referential stdio demo: the coding spine plus tools to inspect the live
+# Self-referential TUI demo: the coding spine plus tools to inspect the live
 # service/plugin/tool/mount/API/event state, mount a model-written plugin under
 # `cordis-dynamic`, and quiescently unmount it. The app bin loads the gitignored
 # root `.env` before reading the required DeepSeek key and optional base URL.
@@ -45,8 +45,8 @@
   name: '@deepseek-ai/dsh-web-fetch-local'
 
 # The app bundle pre-creates the self-referential demo's `main` agent.
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
   config:
     provider: deepseek
     model: deepseek-v4-flash

+ 9 - 13
examples/cordis-agent/tests/keyless-smoke.e2e.ts

@@ -1,27 +1,23 @@
 import { fileURLToPath } from 'node:url'
 import { describe, expect, it } from 'vitest'
-import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
+import { LOADER_SMOKE_TEST_TIMEOUT_MS } from '@deepseek-ai/dsh-loader-smoke'
+import { runTuiPtySmoke } from '../../tui-agent/tests/pty-harness.ts'
 
-/**
- * Keyless Loader-path smoke for examples/cordis-agent: boot the real tree,
- * including tool-cordis resolved by package name, then close stdin without a
- * prompt and assert the banner. The dummy key never reaches a model call.
- */
-
-const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/src/bin.ts', import.meta.url))
+const binScript = fileURLToPath(new URL('../../../packages/examples/tui-demo/src/bin.ts', import.meta.url))
 const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
 const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
 
-describe('cordis-agent keyless smoke (real cordis.yml via the Loader)', () => {
-  it('boots the full plugin tree incl. tool-cordis, prints its banner, and exits cleanly on EOF', async () => {
-    const { stdout } = await runLoaderSmoke({
+describe('cordis-agent keyless smoke (real Loader tree in a PTY)', () => {
+  it('boots the full tool-cordis tree and exits cleanly through the TUI', async () => {
+    const output = await runTuiPtySmoke({
       label: 'cordis-agent',
-      tempDirPrefix: 'cordis-smoke-',
+      tempDirPrefix: 'cordis-agent-smoke-',
       binScript,
       configPath,
       tsconfigPath,
       env: { DEEPSEEK_API_KEY: 'keyless-smoke-no-call' },
+      actions: [{ waitFor: 'cordis-agent ready.', send: '/exit\r' }],
     })
-    expect(stdout).toContain('cordis-agent ready.')
+    expect(output).toContain('cordis-agent ready.')
   }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 })

+ 12 - 21
examples/echo-agent/README.md

@@ -1,34 +1,25 @@
 # echo-agent
 
-Runnable demo: stdin chat with a scripted mock model and an echo tool. The all-mock skeleton — "swap the backend, keep the app".
+Network-free Headless demo with a scripted mock model and an echo tool.
 
 ## What it shows
 
-This example is just a leaf `cordis.yml`: it loads the [`@deepseek-ai/dsh-stdio-demo`](../../packages/examples/stdio-demo) app (which bundles the whole [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) spine, JSONL persistence, the TTY-selected `dsh-tui`/`dsh-stdio` front doors, and a pre-created `main` agent), and swaps in two example-local backends plus `hmr`:
+The leaf loads [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo), which supplies the shared spine, JSONL persistence, one fresh `main` agent, and the one-shot CLI driver. Two local plugins provide the demo behavior:
 
-- `mock-llm.ts` — a mock `LlmAdapter` that streams scripted responses and calls the `echo` tool when the user types "echo <something>". Registered with `ctx.llm.registerAdapter(['mock-echo'], …)`.
-- `echo-tool.ts` — a tool registered via `ctx.tools.register(defineTool(…))` with typed `execute` args; echoes text back uppercased.
+- `mock-llm.ts` registers a scripted `LlmAdapter`; a task beginning with `echo ` requests the tool.
+- `echo-tool.ts` registers a typed tool that returns the input uppercased.
 
-Swapping `mock-llm` for the real `llm-deepseek` adapter is all that separates this from `repl-agent` — the same app, a different backend.
-
-## Plugin files
-
-| File | Role | Key patterns demonstrated |
-|---|---|---|
-| `src/mock-llm.ts` | `LlmAdapter` registration | `ctx.llm.registerAdapter(['mock-echo'], …)`, streaming chunks with the proper `block-start`/`block-end` protocol |
-| `src/echo-tool.ts` | Tool registration | `ctx.tools.register(defineTool(…))` with typed `execute` args, returning `ContentBlock[]` |
-| `cordis.yml` | Leaf wiring | the two backends + `hmr` + one `@deepseek-ai/dsh-stdio-demo` entry carrying the app config |
-
-The spine, UI, persistence, and boot glue all live in `@deepseek-ai/dsh-stdio-demo` and the bundle it loads — this folder holds only the demo-specific mocks and the leaf wiring.
+| File | Role |
+|---|---|
+| `src/mock-llm.ts` | Streaming mock adapter |
+| `src/echo-tool.ts` | Model-facing echo tool |
+| `cordis.yml` | Mock plugins, local providers, and one `@deepseek-ai/dsh-cli-demo` entry |
 
 ## Run
 
 ```sh
-pnpm run demo:echo
-# or:
-node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/echo-agent/cordis.yml
+pnpm run demo:echo "echo hello world"
+pnpm run demo:echo --output-format stream-json -- "echo hello world"
 ```
 
-Type a message and press Enter. "echo <text>" triggers a tool call round-trip (the mock model requests the `echo` tool, which echoes the text uppercased, and the next model step acknowledges it).
-
-The session is persisted under `.sessions/` relative to the directory you launch the demo from. `pnpm run demo:echo` runs from the repo root, so the logs land in `<repo-root>/.sessions/cwd-<hash>/` (one `.jsonl` log per session). Clean up with: `rm -rf .sessions`
+The first command prints the final canned response. `stream-json` also exposes the canonical `tool/call` and `tool/result` events. Sessions persist under `.sessions/` relative to the launch directory; remove that generated directory when finished.

+ 7 - 10
examples/echo-agent/composition.md

@@ -3,13 +3,11 @@
 
 # Echo Agent App Composition
 
-The echo demo swaps in a local mock LLM and teaching echo tool, then loads the stdio app package for the shared spine and terminal front door.
+The echo demo swaps in a local mock LLM and teaching echo tool, then loads the headless one-shot app package.
 
 ```mermaid
 flowchart LR
   cfg["examples/echo-agent<br/>cordis.yml"]
-  plugin_echo_hmr["hmr<br/>@cordisjs/plugin-hmr"]
-  cfg --> plugin_echo_hmr
   plugin_echo_mock_llm["mock-llm<br/>./src/mock-llm.ts"]
   cfg --> plugin_echo_mock_llm
   plugin_echo_echo_tool["echo-tool<br/>./src/echo-tool.ts"]
@@ -18,11 +16,11 @@ flowchart LR
   cfg --> plugin_echo_bash
   plugin_echo_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
   cfg --> plugin_echo_fs_local
-  plugin_echo_stdio_agent["stdio-agent<br/>@deepseek-ai/dsh-stdio-demo"]
-  cfg --> plugin_echo_stdio_agent
-  plugin_echo_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
-  plugin_echo_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
-  plugin_echo_stdio_agent --> frontdoor_stdio["dsh-tui (TTY) / dsh-stdio (pipes)<br/>pre-created main agent"]
+  plugin_echo_cli_agent["cli-agent<br/>@deepseek-ai/dsh-cli-demo"]
+  cfg --> plugin_echo_cli_agent
+  plugin_echo_cli_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
+  plugin_echo_cli_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
+  plugin_echo_cli_agent --> frontdoor_cli["one-shot driver<br/>format-pure stdout<br/>fresh top-level agent"]
   bundle_agent_core --> spine_llm["ctx.llm"]
   bundle_agent_core --> spine_sessions["ctx.sessions"]
   bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
@@ -31,12 +29,11 @@ flowchart LR
 
 | Plugin id | Package / module |
 | --- | --- |
-| `hmr` | `@cordisjs/plugin-hmr` |
 | `mock-llm` | `./src/mock-llm.ts` |
 | `echo-tool` | `./src/echo-tool.ts` |
 | `bash` | `@deepseek-ai/dsh-bash-local` |
 | `fs-local` | `@deepseek-ai/dsh-fs-local` |
-| `stdio-agent` | `@deepseek-ai/dsh-stdio-demo` |
+| `cli-agent` | `@deepseek-ai/dsh-cli-demo` |
 
 Source config: [`examples/echo-agent/cordis.yml`](cordis.yml).
 

+ 4 - 21
examples/echo-agent/cordis.yml

@@ -1,43 +1,26 @@
-# Stdio agent with the network-free `mock-echo` adapter and example-local `echo`
-# tool. The app bundle supplies the spine; this leaf selects backends, HMR, and app config.
-# No API key: the `mock-echo` adapter never touches the network.
+# Headless agent with the network-free `mock-echo` adapter and example-local
+# `echo` tool. No API key is needed because the adapter never touches the network.
 
-# Hot-module reload for the dev/demo loop (a leaf entry, not baked into
-# dsh-stdio-demo — it needs `node --expose-internals`, which `demo:echo` passes).
-- id: hmr
-  name: '@cordisjs/plugin-hmr'
-  config:
-    root: ['.']
-
-# Example-local model and tool plugins resolve relative to this file.
 - id: mock-llm
   name: './src/mock-llm.ts'
 
 - id: echo-tool
   name: './src/echo-tool.ts'
 
-# Local bash executor: agent-spine-demo ships the `tool-bash` consumer schema, so the
-# leaf provides the executor it runs on (the echo demo doesn't drive bash, but
-# the tool is part of the shared spine).
 - id: bash
   name: '@deepseek-ai/dsh-bash-local'
 
-# Local filesystem provider for agent-spine-demo's workspace-context loader. This
-# does not expose model-facing read/write/edit tools in the echo demo.
 - id: fs-local
   name: '@deepseek-ai/dsh-fs-local'
   config:
     cwd: !!js process.cwd()
 
-# The app pre-creates `main` on the mock model and supplies persistence plus
-# TTY-selected `dsh-tui`/`dsh-stdio` front doors; readline mode also owns logging.
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: cli-agent
+  name: '@deepseek-ai/dsh-cli-demo'
   config:
     provider: mock
     model: mock-echo
     persona: 'You are echo-agent, a demo agent.'
-    welcome: 'echo-agent ready. Type a message ("echo <text>" triggers the tool).'
     persistenceRoot: './.sessions'
     workspaceContext:
       maxBytes: 65536

+ 1 - 1
examples/echo-agent/package.json

@@ -3,5 +3,5 @@
   "private": true,
   "version": "0.0.1",
   "type": "module",
-  "description": "Runnable demo: stdin chat with a scripted mock model + echo tool"
+  "description": "Runnable headless demo: scripted mock model + echo tool"
 }

+ 16 - 22
examples/echo-agent/tests/echo.e2e.ts

@@ -1,43 +1,37 @@
 import { fileURLToPath } from 'node:url'
 import { describe, expect, it } from 'vitest'
 import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
+import type { SessionEvent } from '@deepseek-ai/dsh-session'
 
-/**
- * Keyless-by-nature Loader-path coverage for examples/echo-agent. The real
- * tree uses its deterministic mock model, so this suite is both the boot smoke
- * and the complete behavior proof for the example.
- */
-
-const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/src/bin.ts', import.meta.url))
+const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
 const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
 const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
 
-async function runEcho(stdinLines: readonly string[]): Promise<string> {
+async function runEcho(task: string, outputFormat: 'text' | 'stream-json' = 'text'): Promise<string> {
   const { stdout } = await runLoaderSmoke({
     label: 'echo-agent',
     tempDirPrefix: 'echo-smoke-',
     binScript,
     configPath,
+    binArgs: ['--config', configPath, '--output-format', outputFormat, task],
     tsconfigPath,
-    stdinLines,
   })
   return stdout
 }
 
-describe('echo-agent keyless smoke (real cordis.yml via the Loader)', () => {
-  it('boots, prints its welcome banner, and exits cleanly on stdin EOF', async () => {
-    expect(await runEcho([])).toContain('echo-agent ready.')
-  }, LOADER_SMOKE_TEST_TIMEOUT_MS)
-
-  it('runs the echo tool round-trip for an "echo …" line', async () => {
-    const stdout = await runEcho(['echo hello world'])
-    expect(stdout).toContain('[tool call] echo')
-    expect(stdout).toContain('[tool result] ECHO: HELLO WORLD')
+describe('echo-agent keyless smoke (Headless through the real Loader tree)', () => {
+  it('runs the echo tool round-trip and exposes both events in stream-json', async () => {
+    const lines = (await runEcho('echo hello world', 'stream-json'))
+      .trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
+    const events = lines.slice(0, -1).map(line => line['event'] as SessionEvent)
+    expect(events.some(event => event.type === 'tool/call' && event.data.name === 'echo')).toBe(true)
+    expect(JSON.stringify(events.find(event => event.type === 'tool/result'))).toContain('ECHO: HELLO WORLD')
+    expect(lines.at(-1)).toMatchObject({ type: 'result', success: true })
   }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 
-  it('streams a direct canned reply for a non-echo line', async () => {
-    const stdout = await runEcho(['just chatting'])
-    expect(stdout).toContain('just chatting')
-    expect(stdout).not.toContain('[tool call]')
+  it('prints the final canned reply for a direct one-shot task', async () => {
+    const stdout = await runEcho('just chatting')
+    expect(stdout).toContain('You said: "just chatting"')
+    expect(stdout).not.toContain('tool/call')
   }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 })

+ 2 - 3
examples/echo-agent/tests/fixtures/context/time-context/cordis.yml

@@ -8,12 +8,11 @@
 - id: time-context
   name: '@deepseek-ai/dsh-time-context'
 
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: cli-agent
+  name: '@deepseek-ai/dsh-cli-demo'
   config:
     provider: mock
     model: mock-echo
     persona: 'Test the time-context plugin.'
-    welcome: 'time-context e2e ready.'
     persistenceRoot: './.sessions'
     workspaceContext: false

+ 16 - 0
examples/echo-agent/tests/fixtures/context/time-context/driver.ts

@@ -0,0 +1,16 @@
+#!/usr/bin/env node
+/** Test driver that sends two turns through one headless Loader composition. */
+
+import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
+import { runOneShot } from '@deepseek-ai/dsh-cli-demo/src/cli.ts'
+
+const configPath = process.argv[2]
+if (configPath === undefined) throw new Error('time-context driver requires a config path')
+
+const ctx = await boot('time-context-e2e', resolveConfigPath(configPath, undefined))
+try {
+  await runOneShot(ctx, { task: 'first' })
+  await runOneShot(ctx, { task: 'second' })
+} finally {
+  await ctx.fiber.dispose()
+}

+ 1 - 1
examples/headless-agent/README.md

@@ -8,7 +8,7 @@ Headless one-shot agent wiring: DeepSeek V4 + local bash and filesystem tools +
 # repo root .env (gitignored) or exported env:
 #   DEEPSEEK_API_KEY=sk-…
 #   DEEPSEEK_BASE_URL=https://…   # optional; defaults to the public API
-pnpm run demo:headless -- "fix the failing test in this workspace"
+pnpm run demo:headless "fix the failing test in this workspace"
 pnpm run demo:headless --output-format json -- "summarize the implementation"
 pnpm run demo:headless --output-format stream-json -- "run the focused tests"
 ```

+ 0 - 0
examples/repl-agent/tests/code-mode.e2e.ts → examples/headless-agent/tests/code-mode.e2e.ts


+ 0 - 0
examples/repl-agent/tests/coding-task.e2e.ts → examples/headless-agent/tests/coding-task.e2e.ts


+ 0 - 0
examples/repl-agent/tests/compaction.e2e.ts → examples/headless-agent/tests/compaction.e2e.ts


+ 0 - 0
examples/repl-agent/tests/full-loop.e2e.ts → examples/headless-agent/tests/full-loop.e2e.ts


+ 1 - 1
examples/repl-agent/tests/harness.ts → examples/headless-agent/tests/harness.ts

@@ -15,7 +15,7 @@ import { BasicCompactService } from '@deepseek-ai/dsh-compact-basic'
 import type { BasicCompactConfig } from '@deepseek-ai/dsh-compact-basic'
 
 /**
- * Shared harness for the repl-agent e2e suites: the full plugin stack
+ * Shared harness for the headless-agent e2e suites: the full plugin stack
  * with the real DeepSeek adapter and the real bash + todo_write tools. Lives
  * outside the *.e2e.ts pattern so importing it never re-registers another
  * file's tests.

+ 0 - 0
examples/repl-agent/tests/resume.e2e.ts → examples/headless-agent/tests/resume.e2e.ts


+ 0 - 0
examples/repl-agent/tests/todo-write.e2e.ts → examples/headless-agent/tests/todo-write.e2e.ts


+ 2 - 1
examples/package.json

@@ -9,6 +9,7 @@
     "@cordisjs/plugin-include": "workspace:*",
     "@deepseek-ai/dsh-acp-demo": "workspace:*",
     "@deepseek-ai/dsh-agent-spine-demo": "workspace:*",
+    "@deepseek-ai/dsh-app-boot": "workspace:*",
     "@deepseek-ai/dsh-bash-local": "workspace:*",
     "@deepseek-ai/dsh-bash-sandbox": "workspace:*",
     "@deepseek-ai/dsh-cli-demo": "workspace:*",
@@ -31,7 +32,7 @@
     "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*",
     "@deepseek-ai/dsh-spill-local": "workspace:*",
     "@deepseek-ai/dsh-spill-policy": "workspace:*",
-    "@deepseek-ai/dsh-stdio-demo": "workspace:*",
+    "@deepseek-ai/dsh-tui-demo": "workspace:*",
     "@deepseek-ai/dsh-subagent": "workspace:*",
     "@deepseek-ai/dsh-subagent-fork": "workspace:*",
     "@deepseek-ai/dsh-subagent-spawn": "workspace:*",

+ 0 - 68
examples/repl-agent/README.md

@@ -1,68 +0,0 @@
-# repl-agent
-
-The repl-agent wiring: DeepSeek V4 + the `read`/`write`/`edit` filesystem tools + the bash tool suite + subagent delegation + workflows + `todo_write` + readline chat + JSONL persistence, loaded from `cordis.yml`. The sibling [`tui-agent`](../tui-agent/README.md) fixes the same agent composition to the full-screen terminal front door.
-
-## Run it
-
-```sh
-# repo root .env (gitignored) or exported env:
-#   DEEPSEEK_API_KEY=sk-…
-#   DEEPSEEK_BASE_URL=https://…   # optional; defaults to the public API
-pnpm run demo:repl
-```
-
-Type a coding task. The agent works through the `read`/`write`/`edit` filesystem tools for ordinary file operations and `bash` (+ the generic `task_output` / `task_list` / `task_kill` for background tasks) for shell commands, searches, and test runs, each in a fresh `bash -c` (the system prompt tells the model to pass `workdir` instead of `cd`). Both the fs tools and bash resolve relative paths against the session workspace. It can also delegate with `subagent`/`subagent_fork` and track multi-step work with `todo_write`.
-
-The REPL renders reasoning, tool calls/results, and the latest todo list as line-oriented output suitable for terminals and pipes. Use `pnpm run demo:tui` for the interactive Markdown/card interface.
-
-### Resuming a prior session
-
-Each run starts a fresh session by default (its event log lands under `./.sessions/`). To **continue** a previous conversation, set `RESUME_SESSION_ID` to that session's id — the `main` agent then rehydrates the persisted log instead of starting fresh, so the model sees the earlier turns as history:
-
-```sh
-RESUME_SESSION_ID=<prior-session-id> pnpm run demo:repl
-```
-
-The id is wired through `cordis.yml` (`resumeSessionId: !!js process.env.RESUME_SESSION_ID`); unset, the agent starts a new session. A missing or unreadable id starts no agent and emits `agent-loop/config-start-failed`: the TUI prints the failure and exits nonzero, while readline reports any dropped queued input and allows piped EOF to finish. Unset it or choose an existing session id.
-
-## Code Mode
-
-[`code-mode.cordis.yml`](code-mode.cordis.yml) overlays the same tree with the worker-thread runtime and `tools: { mode: code }`. The model receives one `run_code` transport plus a generated TypeScript SDK for the visible tools; only program output returns to model context. Use `mode: both` to expose native calls alongside `run_code`. See the [Code Mode Agent Note](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md) for the execution contract.
-
-```sh
-pnpm run demo:code-mode        # this overlay under the REPL (default UI)
-pnpm run demo:code-mode acp    # the acp-agent example's same-shaped overlay
-```
-
-Try a task that spans several tool calls, e.g.:
-
-> Count the lines of every `*.md` file under docs/ and write the three largest to summary.txt.
-
-and watch the transcript: one `run_code` call, a program looping over tools, and a result the model curated instead of five round-trips of raw tool output.
-
-## What each leaf entry demonstrates
-
-This example is a thin leaf `cordis.yml`: it picks the swappable backends, loads one app package, and adds product tools that are intentionally outside the shared spine. The spine (sessions, system-prompt, tools, agents, invariants, `agent-loop`) and the front-door cluster (JSONL persistence, the selected terminal channel, the pre-created `main` agent) live inside the [`@deepseek-ai/dsh-stdio-demo`](../../packages/examples/stdio-demo) app and the [`@deepseek-ai/dsh-agent-spine-demo`](../../packages/examples/agent-spine-demo) bundle it loads; the leaf wires the backends and model-facing optional tools:
-
-| Entry | Demonstrates |
-|---|---|
-| `hmr` (`@cordisjs/plugin-hmr`) | the dev/demo edit-reload loop — a **leaf** entry (not baked into the app) because it is Loader-only and needs `node --expose-internals`, which `demo:repl` passes |
-| `llm-deepseek` | real `LlmAdapter` via config (`!!js process.env.…` secrets); swap one line to `@deepseek-ai/dsh-llm-pi-ai` for the library-backed twin |
-| `bash` (`dsh-bash-local`) | the executor implementation — the swappable half of the bash seam. The model-facing `bash` schema (`tool-bash`) and generic `task_*` controls (`tool-tasks`) come from `dsh-agent-spine-demo`, so only the executor is a leaf choice |
-| `stdio-agent` (`@deepseek-ai/dsh-stdio-demo`) | the app bundle: the agent-spine demo + JSONL persistence + the configured terminal channel + a pre-created `main` agent. This leaf fixes `ui.mode` to `readline`; `tui-agent` owns the corresponding TUI leaf |
-| `token-meter`, `tool-result-prune`, `compact-basic` | replay-aware pressure, model-free oversized tool-result pruning, and LLM summary compaction. Pruning runs only after a compaction trigger qualifies and can avoid the summarization call |
-| `subagent`, `subagent-spawn`, `subagent-fork` | the subagent provider registry plus the two in-process backends: a fresh child and a child seeded with the parent's completed-turn prefix |
-| `tool-subagent`, `tool-subagent-fork` | two model-facing `dsh-tool-subagent` loads, each bound to a different provider and exposed under a distinct tool name (`subagent`, `subagent_fork`) |
-| `workflow-workerthread`, `tool-workflow` | the worker-thread workflow engine and its model-facing `workflow` tool, with child calls routed through the spawn backend |
-| `tool-todo` | the model-facing `todo_write` tool; writes the whole task list to the session log and renders as a persistent TUI plan or readline checklist |
-| `fs-local`, `fs-policy`, `tool-fs` | the filesystem stack: the local `ctx.fs` provider, the read-before-write/edit policy gate (on the `fs/*` event gate), and the model-facing `read`/`write`/`edit` tools. Relative paths resolve against the session workspace |
-
-## End-to-end tests (`pnpm run test:e2e`, key-gated)
-
-- `tests/full-loop.e2e.ts` — the canary: real model runs `echo e2e-ok` through the real bash tool; asserts `tool/call`/`tool/result` session events and the final answer.
-- `tests/coding-task.e2e.ts` — the swebench-style smoke: a temp dir holds `add.js` (with `a - b` where `a + b` belongs) and a failing `add.test.js`; the agent must fix the bug and verify. The test re-runs `node add.test.js` ITSELF and inspects the files — agent claims are not trusted.
-- `tests/resume.e2e.ts` — durable continuity across processes: run 1 tells the real model a secret code and persists the turn to a temp JSONL root, then the whole context is disposed; run 2 is a fresh context over the same root that RESUMES the session id and asks the model to recall the code. The recall can only come from the rehydrated log.
-- `tests/compaction.e2e.ts` — the compaction smoke: a real multi-step bash task runs with a deliberately tiny context window so automatic pruning or summary compaction fires mid-session. It verifies the world: a replayable surface replacement lands, summary brackets are complete when summarization is needed, the surface shrinks, and the agent still produces a correct final answer.
-- `tests/todo-write.e2e.ts` — a real model drives the real `todo_write` tool and the test verifies the resulting `todo/write` session event.
-
-These self-skip without `DEEPSEEK_API_KEY`. `tests/code-mode.e2e.ts` is the with-key Code Mode proof — a real model, a two-tool task, asserting the wire tool list was exactly `[run_code]`, the `tool/code-dispatch` events landed under the parent call, and the curated answer came back. The keyless Loader smokes run in the default e2e gate: `tests/keyless-smoke.e2e.ts` and `tests/code-mode-keyless-smoke.e2e.ts`.

+ 0 - 33
examples/repl-agent/code-mode.cordis.yml

@@ -1,33 +0,0 @@
-# Code Mode adds `ctx.codeRuntime` and changes the registry to one wire tool,
-# `run_code`, plus a generated SDK for bash/read/write/edit/subagent/todo_write.
-# `demo:code-mode` selects this overlay; the ACP example has the same UI-specific
-# shape. A config patch replaces the whole app config, so unchanged base fields
-# are restated; only `tools`, `welcome`, and the persona's second paragraph differ.
-- id: base
-  name: '@cordisjs/plugin-include'
-  config:
-    path: ./cordis.yml
-    patches:
-      - id: stdio-agent
-        name: '@deepseek-ai/dsh-stdio-demo'
-        config:
-          provider: deepseek
-          model: deepseek-v4-flash
-          resumeSessionId: !!js process.env.RESUME_SESSION_ID
-          persistenceRoot: './.sessions'
-          workspaceContext:
-            maxBytes: 65536
-          tools:
-            mode: code
-          welcome: 'code-mode agent ready. Give it a multi-tool task.'
-          ui:
-            mode: readline
-          persona: |
-            You are a coding agent powered by the {{model}} model.
-
-            You work by writing TypeScript programs for run_code: batch related
-            tool work into one program, loop and branch where it helps, and print
-            or return ONLY the findings that matter.
-      - insert:
-          - id: code-runtime
-            name: '@deepseek-ai/dsh-code-runtime-worker'

+ 0 - 91
examples/repl-agent/composition.md

@@ -1,91 +0,0 @@
-<!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.
-     Run `pnpm run gen-doc-graphs` to regenerate. -->
-
-# REPL Agent App Composition
-
-The REPL agent demo adds the real DeepSeek adapter, filesystem tools, todo_write, tool-result pruning, compaction, and both subagent transports on top of the stdio app package.
-
-```mermaid
-flowchart LR
-  cfg["examples/repl-agent<br/>cordis.yml"]
-  plugin_repl_hmr["hmr<br/>@cordisjs/plugin-hmr"]
-  cfg --> plugin_repl_hmr
-  plugin_repl_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
-  cfg --> plugin_repl_llm_deepseek
-  plugin_repl_bash["bash<br/>@deepseek-ai/dsh-bash-local"]
-  cfg --> plugin_repl_bash
-  plugin_repl_stdio_agent["stdio-agent<br/>@deepseek-ai/dsh-stdio-demo"]
-  cfg --> plugin_repl_stdio_agent
-  plugin_repl_stdio_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
-  plugin_repl_stdio_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
-  plugin_repl_stdio_agent --> frontdoor_stdio["@deepseek-ai/dsh-stdio<br/>pre-created main agent"]
-  bundle_agent_core --> spine_llm["ctx.llm"]
-  bundle_agent_core --> spine_sessions["ctx.sessions"]
-  bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
-  bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
-  plugin_repl_token_meter["token-meter<br/>@deepseek-ai/dsh-token-meter"]
-  cfg --> plugin_repl_token_meter
-  plugin_repl_tool_result_prune["tool-result-prune<br/>@deepseek-ai/dsh-compact-tool-result-prune"]
-  cfg --> plugin_repl_tool_result_prune
-  plugin_repl_compact_basic["compact-basic<br/>@deepseek-ai/dsh-compact-basic"]
-  cfg --> plugin_repl_compact_basic
-  plugin_repl_subagent["subagent<br/>@deepseek-ai/dsh-subagent"]
-  cfg --> plugin_repl_subagent
-  plugin_repl_subagent_spawn["subagent-spawn<br/>@deepseek-ai/dsh-subagent-spawn"]
-  cfg --> plugin_repl_subagent_spawn
-  plugin_repl_subagent_fork["subagent-fork<br/>@deepseek-ai/dsh-subagent-fork"]
-  cfg --> plugin_repl_subagent_fork
-  plugin_repl_tool_subagent["tool-subagent<br/>@deepseek-ai/dsh-tool-subagent"]
-  cfg --> plugin_repl_tool_subagent
-  plugin_repl_tool_subagent_fork["tool-subagent-fork<br/>@deepseek-ai/dsh-tool-subagent"]
-  cfg --> plugin_repl_tool_subagent_fork
-  plugin_repl_workflow_workerthread["workflow-workerthread<br/>@deepseek-ai/dsh-workflow-workerthread"]
-  cfg --> plugin_repl_workflow_workerthread
-  plugin_repl_tool_workflow["tool-workflow<br/>@deepseek-ai/dsh-tool-workflow"]
-  cfg --> plugin_repl_tool_workflow
-  plugin_repl_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
-  cfg --> plugin_repl_tool_todo
-  plugin_repl_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
-  cfg --> plugin_repl_fs_local
-  plugin_repl_fs_policy["fs-policy<br/>@deepseek-ai/dsh-fs-policy"]
-  cfg --> plugin_repl_fs_policy
-  plugin_repl_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
-  cfg --> plugin_repl_tool_fs
-  plugin_repl_tool_fs_search["tool-fs-search<br/>@deepseek-ai/dsh-tool-fs-search"]
-  cfg --> plugin_repl_tool_fs_search
-  plugin_repl_timeout_policy["timeout-policy<br/>@deepseek-ai/dsh-timeout-policy"]
-  cfg --> plugin_repl_timeout_policy
-  plugin_repl_spill_local["spill-local<br/>@deepseek-ai/dsh-spill-local"]
-  cfg --> plugin_repl_spill_local
-  plugin_repl_spill_policy["spill-policy<br/>@deepseek-ai/dsh-spill-policy"]
-  cfg --> plugin_repl_spill_policy
-```
-
-| Plugin id | Package / module |
-| --- | --- |
-| `hmr` | `@cordisjs/plugin-hmr` |
-| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
-| `bash` | `@deepseek-ai/dsh-bash-local` |
-| `stdio-agent` | `@deepseek-ai/dsh-stdio-demo` |
-| `token-meter` | `@deepseek-ai/dsh-token-meter` |
-| `tool-result-prune` | `@deepseek-ai/dsh-compact-tool-result-prune` |
-| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
-| `subagent` | `@deepseek-ai/dsh-subagent` |
-| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` |
-| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` |
-| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
-| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` |
-| `workflow-workerthread` | `@deepseek-ai/dsh-workflow-workerthread` |
-| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
-| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
-| `fs-local` | `@deepseek-ai/dsh-fs-local` |
-| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
-| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
-| `tool-fs-search` | `@deepseek-ai/dsh-tool-fs-search` |
-| `timeout-policy` | `@deepseek-ai/dsh-timeout-policy` |
-| `spill-local` | `@deepseek-ai/dsh-spill-local` |
-| `spill-policy` | `@deepseek-ai/dsh-spill-policy` |
-
-Source config: [`examples/repl-agent/cordis.yml`](cordis.yml).
-
-Maintenance mode: hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source.

+ 0 - 143
examples/repl-agent/cordis.yml

@@ -1,143 +0,0 @@
-# Readline coding REPL with swappable DeepSeek and local-bash backends.
-# `dsh-stdio-demo` supplies the agent spine, workspace instructions, generic
-# task controls, JSONL persistence, the line-oriented front door, and `main`.
-# HMR remains a leaf because it requires Loader internals; `demo:repl` passes
-# `--expose-internals`. The app bin loads the gitignored root `.env`; this file
-# reads `DEEPSEEK_API_KEY` and optional `DEEPSEEK_BASE_URL` through `!!js`.
-
-# Hot-module reload for the dev/demo loop (needs `node --expose-internals`).
-- id: hmr
-  name: '@cordisjs/plugin-hmr'
-  config:
-    root: ['.']
-
-# The native DeepSeek adapter.
-- id: llm-deepseek
-  name: '@deepseek-ai/dsh-llm-deepseek'
-  config:
-    apiKey: !!js process.env.DEEPSEEK_API_KEY
-    baseURL: !!js process.env.DEEPSEEK_BASE_URL
-
-# Local executor for the app bundle's bash tool.
-- id: bash
-  name: '@deepseek-ai/dsh-bash-local'
-  config:
-    timeoutMs: 60000
-
-# The app bundle pre-creates the REPL's `main` agent.
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
-  config:
-    provider: deepseek
-    model: deepseek-v4-flash
-    # Set RESUME_SESSION_ID to continue a prior persisted session (the ids live
-    # under ./.sessions); unset starts a fresh session each run.
-    resumeSessionId: !!js process.env.RESUME_SESSION_ID
-    persistenceRoot: './.sessions'
-    workspaceContext:
-      maxBytes: 65536
-    welcome: 'agent REPL ready. Give it a coding task.'
-    ui:
-      mode: readline
-    # Keep the persona to identity and behavior; tool plugins own tool guidance.
-    # The loop resolves {{model}} from this agent's configuration.
-    persona: |
-      You are a coding agent powered by the {{model}} model.
-
-      Verify your work by running the code or tests. Keep answers brief and
-      factual.
-
-# Replay-aware request pressure with one service-wide context window.
-- id: token-meter
-  name: '@deepseek-ai/dsh-token-meter'
-
-# Prune oversized tool output without a model call before summary compaction.
-- id: tool-result-prune
-  name: '@deepseek-ai/dsh-compact-tool-result-prune'
-
-# Summarize an older range after measured pressure or a canonical provider overflow.
-# Service-wide policy provides pressure, retention, and one overflow-retry default.
-- id: compact-basic
-  name: '@deepseek-ai/dsh-compact-basic'
-
-# Expose fresh-child `spawn` and completed-prefix `fork` through independent
-# in-process backends. Each tool instance needs a distinct `toolName`; the registry
-# rejects duplicates. These leaves follow the app because it provides `ctx.agents` and `ctx.tools`.
-- id: subagent
-  name: '@deepseek-ai/dsh-subagent'
-
-- id: subagent-spawn
-  name: '@deepseek-ai/dsh-subagent-spawn'
-  config:
-    providerName: spawn
-
-- id: subagent-fork
-  name: '@deepseek-ai/dsh-subagent-fork'
-  config:
-    providerName: fork
-
-- id: tool-subagent
-  name: '@deepseek-ai/dsh-tool-subagent'
-  config:
-    provider: spawn
-    toolName: subagent
-
-- id: tool-subagent-fork
-  name: '@deepseek-ai/dsh-tool-subagent'
-  config:
-    provider: fork
-    toolName: subagent_fork
-
-
-# The worker-thread workflow engine fans a model-written JavaScript script's
-# `agent()` calls out through the spawn backend; the adjacent tool exposes it to the model.
-- id: workflow-workerthread
-  name: '@deepseek-ai/dsh-workflow-workerthread'
-  config:
-    provider: spawn
-
-- id: tool-workflow
-  name: '@deepseek-ai/dsh-tool-workflow'
-# `todo_write` replaces the logged whole list and renders as a stdio checklist or ACP plan.
-- id: tool-todo
-  name: '@deepseek-ai/dsh-tool-todo'
-
-# Policy loads before the model-facing filesystem tools so writes and edits require
-# an observed file. This single-session app resolves relative paths from the process cwd.
-- id: fs-local
-  name: '@deepseek-ai/dsh-fs-local'
-  config:
-    cwd: !!js process.cwd()
-
-- id: fs-policy
-  name: '@deepseek-ai/dsh-fs-policy'
-
-- id: tool-fs
-  name: '@deepseek-ai/dsh-tool-fs'
-
-# Bash-backed discovery tools (glob/grep): if the local bash executor above
-# can find rg, register fixed ripgrep commands — not ctx.fs. Capped results
-# save the complete formatted list through the spill backend below
-# (ctx.spillStore, optional).
-- id: tool-fs-search
-  name: '@deepseek-ai/dsh-tool-fs-search'
-
-# The tool-call timeout enforcer: arms each declared ToolDefinition.timeoutMs
-# (the search tools above declare 30s) as a deadline on exec.signal. Without
-# it a declared budget is advisory and only the bash executor's own timeout
-# backstop applies.
-- id: timeout-policy
-  name: '@deepseek-ai/dsh-timeout-policy'
-
-# Tool-output spill stack: a local backend that saves oversized tool text under
-# a private session-scoped dir, and the tools/post-execute policy that replaces
-# an over-budget plain-text result with a preview + the spill locator/retrieval
-# hint. A leaf pair after the app (needs ctx.tools). The policy is a no-op until
-# a tool returns more than maxInlineBytes of plain text.
-- id: spill-local
-  name: '@deepseek-ai/dsh-spill-local'
-
-- id: spill-policy
-  name: '@deepseek-ai/dsh-spill-policy'
-  config:
-    maxInlineBytes: 50000

+ 0 - 7
examples/repl-agent/package.json

@@ -1,7 +0,0 @@
-{
-  "name": "repl-agent-example",
-  "private": true,
-  "version": "0.0.1",
-  "type": "module",
-  "description": "Runnable demo: an agent REPL UI with DeepSeek V4 and coding tools"
-}

+ 0 - 27
examples/repl-agent/tests/code-mode-keyless-smoke.e2e.ts

@@ -1,27 +0,0 @@
-import { fileURLToPath } from 'node:url'
-import { describe, expect, it } from 'vitest'
-import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
-
-/**
- * Keyless Loader-path smoke for the Code Mode overlay: boot the real include
- * tree through stdio-agent and `code-mode.cordis.yml`, then close stdin without
- * a prompt and assert the banner. No model or `run_code` turn runs.
- */
-
-const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/src/bin.ts', import.meta.url))
-const configPath = fileURLToPath(new URL('../code-mode.cordis.yml', import.meta.url))
-const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
-
-describe('code-mode overlay keyless smoke (real code-mode.cordis.yml via the Loader)', () => {
-  it('boots the Code Mode plugin tree, prints its banner, and exits cleanly on EOF', async () => {
-    const { stdout } = await runLoaderSmoke({
-      label: 'code-mode overlay',
-      tempDirPrefix: 'code-mode-smoke-',
-      binScript,
-      configPath,
-      tsconfigPath,
-      env: { DEEPSEEK_API_KEY: 'keyless-smoke-no-call' },
-    })
-    expect(stdout).toContain('code-mode agent ready.')
-  }, LOADER_SMOKE_TEST_TIMEOUT_MS)
-})

+ 0 - 28
examples/repl-agent/tests/keyless-smoke.e2e.ts

@@ -1,28 +0,0 @@
-import { fileURLToPath } from 'node:url'
-import { describe, expect, it } from 'vitest'
-import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
-
-/**
- * Keyless Loader-path smoke for examples/repl-agent: boot the real example
- * through the stdio-agent bin and its `cordis.yml`, then close stdin without a
- * prompt and assert the banner. The dummy key satisfies adapter construction;
- * immediate EOF guarantees there is no model call.
- */
-
-const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/src/bin.ts', import.meta.url))
-const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
-const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
-
-describe('repl-agent keyless smoke (real cordis.yml via the Loader)', () => {
-  it('boots the full plugin tree, prints its banner, and exits cleanly on EOF', async () => {
-    const { stdout } = await runLoaderSmoke({
-      label: 'repl-agent',
-      tempDirPrefix: 'repl-smoke-',
-      binScript,
-      configPath,
-      tsconfigPath,
-      env: { DEEPSEEK_API_KEY: 'keyless-smoke-no-call' },
-    })
-    expect(stdout).toContain('agent REPL ready.')
-  }, LOADER_SMOKE_TEST_TIMEOUT_MS)
-})

+ 6 - 6
examples/tui-agent/README.md

@@ -1,6 +1,6 @@
 # tui-agent
 
-The full-screen terminal counterpart to the [`repl-agent`](../repl-agent/README.md) readline REPL and [`acp-agent`](../acp-agent/README.md) server. It reuses the coding agent's backends and tool composition, then fixes the shared terminal app to the `dsh-tui` front door.
+The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows, `todo_write`, timeout/spill policy, and [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo).
 
 ## Run it
 
@@ -8,16 +8,16 @@ The full-screen terminal counterpart to the [`repl-agent`](../repl-agent/README.
 pnpm run demo:tui
 ```
 
-The command needs `DEEPSEEK_API_KEY` in the environment or the gitignored repository-root `.env`. Set `RESUME_SESSION_ID` to reopen a persisted conversation under `./.sessions`.
+The command needs `DEEPSEEK_API_KEY` in the environment or gitignored repository-root `.env`. Set `RESUME_SESSION_ID` to reopen a persisted conversation under `./.sessions`.
 
-The TUI renders Markdown history, reasoning, tool-owned terminal/diff/generic cards, token totals, and the latest todo list. Enter submits or steers while the agent is running; Ctrl+O expands cards, Ctrl+R toggles reasoning, Escape cancels, and `/help` lists commands. `ask_user_question` opens a keyboard-driven overlay.
+The TUI renders Markdown history, reasoning, tool-owned terminal/diff/generic cards, token totals, and the latest todo list. Enter submits or steers while the agent runs; Ctrl+O expands cards, Ctrl+R toggles reasoning, Escape cancels, and `/help` lists commands. `ask_user_question` opens a keyboard-driven overlay.
 
-Run `pnpm run demo:code-mode tui` for the sibling Code Mode overlay.
+Run `pnpm run demo:code-mode tui` for the Code Mode overlay.
 
 ## Composition
 
-[`cordis.yml`](cordis.yml) includes the readline repl-agent leaf so the LLM, bash, filesystem, compaction, subagent, workflow, todo, timeout, and spill choices have one owner. Its asserted patch replaces only the terminal app config and forces `ui.mode: tui`; [`code-mode.cordis.yml`](code-mode.cordis.yml) applies the same front-door patch to the repl-agent Code Mode overlay.
+[`cordis.yml`](cordis.yml) owns the interactive coding composition directly. [`code-mode.cordis.yml`](code-mode.cordis.yml) includes that leaf and replaces the tool presentation mode while adding the code runtime. Non-interactive automation uses the sibling [headless-agent](../headless-agent/README.md) composition.
 
 ## Snapshot tests
 
-`tests/snapshots/<scenario>/session.jsonl` supplies recorded user prompts and model chunks; sibling child logs drive subagents and workflows. The keyless suite executes those scripts through the real loop and tool implementations, then compares readable expected terminal cell/style output. Use `pnpm run test:snapshot:refresh` for presentation-only changes and `pnpm run test:snapshot:record` with a DeepSeek key when a recorded model journey changes. The implemented [TUI snapshot Agent Note](../../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md) owns the scenario matrix and the split between recorded journeys, transient package snapshots, and PTY coverage.
+`tests/snapshots/<scenario>/session.jsonl` supplies recorded user prompts and model chunks; sibling child logs drive subagents and workflows. The keyless suite executes those scripts through the real loop and tools, then compares readable terminal cell/style output. Use `pnpm run test:snapshot:refresh` for presentation-only changes and `pnpm run test:snapshot:record` with a DeepSeek key when a recorded model journey changes. The implemented [TUI snapshot Agent Note](../../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md) owns the scenario matrix.

+ 10 - 9
examples/tui-agent/code-mode.cordis.yml

@@ -1,12 +1,12 @@
-# Code Mode keeps the TUI front door while reusing the repl-agent overlay's
-# worker runtime and one-tool registry composition.
+# Code Mode keeps the TUI composition while adding the worker runtime and
+# reducing the model-facing registry to the `run_code` transport.
 - id: base
   name: '@cordisjs/plugin-include'
   config:
-    path: ../repl-agent/code-mode.cordis.yml
+    path: ./cordis.yml
     patches:
-      - id: stdio-agent
-        name: '@deepseek-ai/dsh-stdio-demo'
+      - id: tui-agent
+        name: '@deepseek-ai/dsh-tui-demo'
         config:
           provider: deepseek
           model: deepseek-v4-flash
@@ -18,13 +18,14 @@
             mode: code
           welcome: 'TUI Code Mode ready. Give it a multi-tool task.'
           ui:
-            mode: tui
-            tui:
-              showReasoning: true
-              maxToolOutputLines: 12
+            showReasoning: true
+            maxToolOutputLines: 12
           persona: |
             You are a coding agent powered by the {{model}} model.
 
             You work by writing TypeScript programs for run_code: batch related
             tool work into one program, loop and branch where it helps, and print
             or return ONLY the findings that matter.
+      - insert:
+          - id: code-runtime
+            name: '@deepseek-ai/dsh-code-runtime-worker'

+ 70 - 7
examples/tui-agent/composition.md

@@ -3,25 +3,88 @@
 
 # TUI Agent App Composition
 
-The TUI agent reuses the repl-agent backend and tool composition while fixing the shared terminal app to the full-screen dsh-tui front door.
+The TUI agent combines the real DeepSeek adapter, coding tools, compaction, subagents, and workflows with the full-screen terminal app package.
 
 ```mermaid
 flowchart LR
   cfg["examples/tui-agent<br/>cordis.yml"]
-  plugin_tui_base["base<br/>@deepseek-ai/dsh-stdio-demo"]
-  cfg --> plugin_tui_base
-  plugin_tui_base --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
-  plugin_tui_base --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
-  plugin_tui_base --> frontdoor_stdio["@deepseek-ai/dsh-tui<br/>pre-created main agent"]
+  plugin_tui_hmr["hmr<br/>@cordisjs/plugin-hmr"]
+  cfg --> plugin_tui_hmr
+  plugin_tui_llm_deepseek["llm-deepseek<br/>@deepseek-ai/dsh-llm-deepseek"]
+  cfg --> plugin_tui_llm_deepseek
+  plugin_tui_bash["bash<br/>@deepseek-ai/dsh-bash-local"]
+  cfg --> plugin_tui_bash
+  plugin_tui_tui_agent["tui-agent<br/>@deepseek-ai/dsh-tui-demo"]
+  cfg --> plugin_tui_tui_agent
+  plugin_tui_tui_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
+  plugin_tui_tui_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
+  plugin_tui_tui_agent --> frontdoor_tui["@deepseek-ai/dsh-tui<br/>pre-created main agent"]
   bundle_agent_core --> spine_llm["ctx.llm"]
   bundle_agent_core --> spine_sessions["ctx.sessions"]
   bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
   bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
+  plugin_tui_token_meter["token-meter<br/>@deepseek-ai/dsh-token-meter"]
+  cfg --> plugin_tui_token_meter
+  plugin_tui_tool_result_prune["tool-result-prune<br/>@deepseek-ai/dsh-compact-tool-result-prune"]
+  cfg --> plugin_tui_tool_result_prune
+  plugin_tui_compact_basic["compact-basic<br/>@deepseek-ai/dsh-compact-basic"]
+  cfg --> plugin_tui_compact_basic
+  plugin_tui_subagent["subagent<br/>@deepseek-ai/dsh-subagent"]
+  cfg --> plugin_tui_subagent
+  plugin_tui_subagent_spawn["subagent-spawn<br/>@deepseek-ai/dsh-subagent-spawn"]
+  cfg --> plugin_tui_subagent_spawn
+  plugin_tui_subagent_fork["subagent-fork<br/>@deepseek-ai/dsh-subagent-fork"]
+  cfg --> plugin_tui_subagent_fork
+  plugin_tui_tool_subagent["tool-subagent<br/>@deepseek-ai/dsh-tool-subagent"]
+  cfg --> plugin_tui_tool_subagent
+  plugin_tui_tool_subagent_fork["tool-subagent-fork<br/>@deepseek-ai/dsh-tool-subagent"]
+  cfg --> plugin_tui_tool_subagent_fork
+  plugin_tui_workflow_workerthread["workflow-workerthread<br/>@deepseek-ai/dsh-workflow-workerthread"]
+  cfg --> plugin_tui_workflow_workerthread
+  plugin_tui_tool_workflow["tool-workflow<br/>@deepseek-ai/dsh-tool-workflow"]
+  cfg --> plugin_tui_tool_workflow
+  plugin_tui_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
+  cfg --> plugin_tui_tool_todo
+  plugin_tui_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
+  cfg --> plugin_tui_fs_local
+  plugin_tui_fs_policy["fs-policy<br/>@deepseek-ai/dsh-fs-policy"]
+  cfg --> plugin_tui_fs_policy
+  plugin_tui_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
+  cfg --> plugin_tui_tool_fs
+  plugin_tui_tool_fs_search["tool-fs-search<br/>@deepseek-ai/dsh-tool-fs-search"]
+  cfg --> plugin_tui_tool_fs_search
+  plugin_tui_timeout_policy["timeout-policy<br/>@deepseek-ai/dsh-timeout-policy"]
+  cfg --> plugin_tui_timeout_policy
+  plugin_tui_spill_local["spill-local<br/>@deepseek-ai/dsh-spill-local"]
+  cfg --> plugin_tui_spill_local
+  plugin_tui_spill_policy["spill-policy<br/>@deepseek-ai/dsh-spill-policy"]
+  cfg --> plugin_tui_spill_policy
 ```
 
 | Plugin id | Package / module |
 | --- | --- |
-| `base` | `@deepseek-ai/dsh-stdio-demo` |
+| `hmr` | `@cordisjs/plugin-hmr` |
+| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
+| `bash` | `@deepseek-ai/dsh-bash-local` |
+| `tui-agent` | `@deepseek-ai/dsh-tui-demo` |
+| `token-meter` | `@deepseek-ai/dsh-token-meter` |
+| `tool-result-prune` | `@deepseek-ai/dsh-compact-tool-result-prune` |
+| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
+| `subagent` | `@deepseek-ai/dsh-subagent` |
+| `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` |
+| `subagent-fork` | `@deepseek-ai/dsh-subagent-fork` |
+| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
+| `tool-subagent-fork` | `@deepseek-ai/dsh-tool-subagent` |
+| `workflow-workerthread` | `@deepseek-ai/dsh-workflow-workerthread` |
+| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
+| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
+| `fs-local` | `@deepseek-ai/dsh-fs-local` |
+| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
+| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
+| `tool-fs-search` | `@deepseek-ai/dsh-tool-fs-search` |
+| `timeout-policy` | `@deepseek-ai/dsh-timeout-policy` |
+| `spill-local` | `@deepseek-ai/dsh-spill-local` |
+| `spill-policy` | `@deepseek-ai/dsh-spill-policy` |
 
 Source config: [`examples/tui-agent/cordis.yml`](cordis.yml).
 

+ 109 - 28
examples/tui-agent/cordis.yml

@@ -1,28 +1,109 @@
-# Full-screen TUI front door over the same repl-agent composition used by the
-# readline REPL. The include keeps backends and optional tools aligned; the
-# patch owns only the terminal-specific app config.
-- id: base
-  name: '@cordisjs/plugin-include'
-  config:
-    path: ../repl-agent/cordis.yml
-    patches:
-      - id: stdio-agent
-        name: '@deepseek-ai/dsh-stdio-demo'
-        config:
-          provider: deepseek
-          model: deepseek-v4-flash
-          resumeSessionId: !!js process.env.RESUME_SESSION_ID
-          persistenceRoot: './.sessions'
-          workspaceContext:
-            maxBytes: 65536
-          welcome: 'TUI agent ready. Give it a coding task.'
-          ui:
-            mode: tui
-            tui:
-              showReasoning: true
-              maxToolOutputLines: 12
-          persona: |
-            You are a coding agent powered by the {{model}} model.
-
-            Verify your work by running the code or tests. Keep answers brief and
-            factual.
+# Full-screen coding agent with swappable DeepSeek and local capability backends.
+# `dsh-tui-demo` supplies the spine, workspace instructions, generic task controls,
+# JSONL persistence, the TUI front door, and `main`. HMR remains a leaf because
+# it requires Loader internals; `demo:tui` passes `--expose-internals`.
+
+- id: hmr
+  name: '@cordisjs/plugin-hmr'
+  config:
+    root: ['.']
+
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  config:
+    apiKey: !!js process.env.DEEPSEEK_API_KEY
+    baseURL: !!js process.env.DEEPSEEK_BASE_URL
+
+- id: bash
+  name: '@deepseek-ai/dsh-bash-local'
+  config:
+    timeoutMs: 60000
+
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
+  config:
+    provider: deepseek
+    model: deepseek-v4-flash
+    resumeSessionId: !!js process.env.RESUME_SESSION_ID
+    persistenceRoot: './.sessions'
+    workspaceContext:
+      maxBytes: 65536
+    welcome: 'TUI agent ready. Give it a coding task.'
+    ui:
+      showReasoning: true
+      maxToolOutputLines: 12
+    persona: |
+      You are a coding agent powered by the {{model}} model.
+
+      Verify your work by running the code or tests. Keep answers brief and
+      factual.
+
+- id: token-meter
+  name: '@deepseek-ai/dsh-token-meter'
+
+- id: tool-result-prune
+  name: '@deepseek-ai/dsh-compact-tool-result-prune'
+
+- id: compact-basic
+  name: '@deepseek-ai/dsh-compact-basic'
+
+- id: subagent
+  name: '@deepseek-ai/dsh-subagent'
+
+- id: subagent-spawn
+  name: '@deepseek-ai/dsh-subagent-spawn'
+  config:
+    providerName: spawn
+
+- id: subagent-fork
+  name: '@deepseek-ai/dsh-subagent-fork'
+  config:
+    providerName: fork
+
+- id: tool-subagent
+  name: '@deepseek-ai/dsh-tool-subagent'
+  config:
+    provider: spawn
+    toolName: subagent
+
+- id: tool-subagent-fork
+  name: '@deepseek-ai/dsh-tool-subagent'
+  config:
+    provider: fork
+    toolName: subagent_fork
+
+- id: workflow-workerthread
+  name: '@deepseek-ai/dsh-workflow-workerthread'
+  config:
+    provider: spawn
+
+- id: tool-workflow
+  name: '@deepseek-ai/dsh-tool-workflow'
+
+- id: tool-todo
+  name: '@deepseek-ai/dsh-tool-todo'
+
+- id: fs-local
+  name: '@deepseek-ai/dsh-fs-local'
+  config:
+    cwd: !!js process.cwd()
+
+- id: fs-policy
+  name: '@deepseek-ai/dsh-fs-policy'
+
+- id: tool-fs
+  name: '@deepseek-ai/dsh-tool-fs'
+
+- id: tool-fs-search
+  name: '@deepseek-ai/dsh-tool-fs-search'
+
+- id: timeout-policy
+  name: '@deepseek-ai/dsh-timeout-policy'
+
+- id: spill-local
+  name: '@deepseek-ai/dsh-spill-local'
+
+- id: spill-policy
+  name: '@deepseek-ai/dsh-spill-policy'
+  config:
+    maxInlineBytes: 50000

+ 3 - 5
examples/tui-agent/tests/fixtures/tui-scripted.cordis.yml

@@ -12,8 +12,8 @@
   config:
     cwd: !!js process.cwd()
 
-- id: stdio-agent
-  name: '@deepseek-ai/dsh-stdio-demo'
+- id: tui-agent
+  name: '@deepseek-ai/dsh-tui-demo'
   config:
     provider: tui-scripted
     model: tui-scripted-model
@@ -22,6 +22,4 @@
       maxBytes: 65536
     welcome: 'scripted TUI ready.'
     ui:
-      mode: tui
-      tui:
-        showReasoning: true
+      showReasoning: true

+ 127 - 0
examples/tui-agent/tests/pty-harness.ts

@@ -0,0 +1,127 @@
+import { spawn } from 'node:child_process'
+import { mkdtemp, rm } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
+
+const PTY_DRIVER = String.raw`
+import errno, json, os, pty, select, signal, sys, time
+node, launch_args_json, launch_env_json, cwd, actions_json, expected_exit, timeout_seconds = sys.argv[1:]
+env = os.environ.copy()
+env.update(json.loads(launch_env_json))
+env.update({"COLUMNS": "100", "LINES": "30"})
+actions = json.loads(actions_json)
+pid, fd = pty.fork()
+if pid == 0:
+    os.chdir(cwd)
+    os.execvpe(node, [node, *json.loads(launch_args_json)], env)
+
+output = bytearray()
+action_index = 0
+deadline = time.monotonic() + float(timeout_seconds)
+status = None
+while time.monotonic() < deadline:
+    ready, _, _ = select.select([fd], [], [], 0.05)
+    if ready:
+        try:
+            chunk = os.read(fd, 65536)
+        except OSError as error:
+            if error.errno != errno.EIO:
+                raise
+            chunk = b""
+        if chunk:
+            output.extend(chunk)
+    while action_index < len(actions) and actions[action_index]["waitFor"].encode() in output:
+        os.write(fd, actions[action_index]["send"].encode())
+        action_index += 1
+    waited, candidate = os.waitpid(pid, os.WNOHANG)
+    if waited == pid:
+        status = candidate
+        break
+
+if status is None:
+    os.kill(pid, signal.SIGKILL)
+    _, status = os.waitpid(pid, 0)
+sys.stdout.buffer.write(output)
+if action_index != len(actions):
+    sys.stderr.write(f"completed {action_index}/{len(actions)} PTY actions before timeout\n")
+    sys.exit(124)
+actual_exit = os.waitstatus_to_exitcode(status)
+if actual_exit != int(expected_exit):
+    sys.stderr.write(f"expected exit {expected_exit}, got {actual_exit}\n")
+    sys.exit(125)
+`
+
+/** One terminal action sent after its marker has rendered. */
+interface TuiPtyAction {
+  readonly waitFor: string
+  readonly send: string
+}
+
+/** Inputs for a keyless real-Loader TUI process smoke. */
+export interface TuiPtySmokeOptions {
+  readonly label: string
+  readonly tempDirPrefix: string
+  readonly binScript: string
+  readonly configPath: string
+  readonly tsconfigPath: string
+  readonly actions?: readonly TuiPtyAction[]
+  readonly env?: Readonly<NodeJS.ProcessEnv>
+  readonly expectedExitCode?: number
+  readonly timeoutMs?: number
+}
+
+/**
+ * Boot an example in a real pseudo-terminal, drive marker-gated input, and
+ * return the captured terminal bytes after the expected process exit.
+ * @param options - launch paths, environment, actions, and expected exit code.
+ * @returns complete pseudo-terminal output.
+ */
+export async function runTuiPtySmoke(options: TuiPtySmokeOptions): Promise<string> {
+  const cwd = await mkdtemp(join(tmpdir(), options.tempDirPrefix))
+  const timeoutMs = options.timeoutMs ?? 25_000
+  try {
+    const launch = resolveExampleLaunch({
+      srcBin: options.binScript,
+      configArgs: [options.configPath],
+      tsconfigPath: options.tsconfigPath,
+      exposeInternals: true,
+      env: {
+        DSH_HOME: join(cwd, '.dsh'),
+        DSH_AGENTS_HOME: join(cwd, '.agents'),
+        ...options.env,
+      },
+    })
+    return await new Promise((resolve, reject) => {
+      const child = spawn('python3', [
+        '-c',
+        PTY_DRIVER,
+        launch.command,
+        JSON.stringify(launch.args),
+        JSON.stringify(launch.env),
+        cwd,
+        JSON.stringify(options.actions ?? []),
+        String(options.expectedExitCode ?? 0),
+        String(timeoutMs / 1_000),
+      ], { stdio: ['ignore', 'pipe', 'pipe'] })
+      let stdout = ''
+      let stderr = ''
+      child.stdout.setEncoding('utf8')
+      child.stdout.on('data', (chunk: string) => { stdout += chunk })
+      child.stderr.setEncoding('utf8')
+      child.stderr.on('data', (chunk: string) => { stderr += chunk })
+      const timer = setTimeout(() => {
+        child.kill('SIGKILL')
+        reject(new Error(`${options.label} PTY driver did not exit. stdout:\n${stdout}\nstderr:\n${stderr}`))
+      }, timeoutMs + 5_000)
+      child.once('error', (error) => { clearTimeout(timer); reject(error) })
+      child.once('exit', (code) => {
+        clearTimeout(timer)
+        if (code === 0) resolve(stdout)
+        else reject(new Error(`${options.label} PTY driver exited ${String(code)}. stdout:\n${stdout}\nstderr:\n${stderr}`))
+      })
+    })
+  } finally {
+    await rm(cwd, { recursive: true, force: true })
+  }
+}

+ 36 - 142
examples/tui-agent/tests/tui-keyless-smoke.e2e.ts

@@ -1,157 +1,42 @@
-import { spawn } from 'node:child_process'
-import { mkdtemp, rm } from 'node:fs/promises'
-import { tmpdir } from 'node:os'
-import { join } from 'node:path'
 import { fileURLToPath } from 'node:url'
 import { describe, expect, it } from 'vitest'
-import { LOADER_SMOKE_TEST_TIMEOUT_MS, resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
+import { LOADER_SMOKE_TEST_TIMEOUT_MS } from '@deepseek-ai/dsh-loader-smoke'
+import { runTuiPtySmoke } from './pty-harness.ts'
 
-const binScript = fileURLToPath(new URL('../../../packages/examples/stdio-demo/src/bin.ts', import.meta.url))
+const binScript = fileURLToPath(new URL('../../../packages/examples/tui-demo/src/bin.ts', import.meta.url))
 const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
 const scriptedConfigPath = fileURLToPath(new URL('./fixtures/tui-scripted.cordis.yml', import.meta.url))
 const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
 
-const PTY_DRIVER = String.raw`
-import errno, json, os, pty, select, signal, sys, time
-node, launch_args_json, launch_env_json, cwd, resume_session_id, scenario = sys.argv[1:]
-env = os.environ.copy()
-env.update(json.loads(launch_env_json))
-env.update({
-    "COLUMNS": "100",
-    "LINES": "30",
-})
-if resume_session_id:
-    env["RESUME_SESSION_ID"] = resume_session_id
-pid, fd = pty.fork()
-if pid == 0:
-    os.chdir(cwd)
-    os.execvpe(node, [node, *json.loads(launch_args_json)], env)
-
-output = bytearray()
-answered_question = False
-sent_prompt = False
-sent_exit = False
-deadline = time.monotonic() + 25
-status = None
-while time.monotonic() < deadline:
-    ready, _, _ = select.select([fd], [], [], 0.05)
-    if ready:
-        try:
-            chunk = os.read(fd, 65536)
-        except OSError as error:
-            if error.errno != errno.EIO:
-                raise
-            chunk = b""
-        if chunk:
-            output.extend(chunk)
-    if scenario == "conversation" and not sent_prompt and b"scripted TUI ready." in output:
-        os.write(fd, b"exercise the TUI\r")
-        sent_prompt = True
-    if scenario == "conversation" and sent_prompt and not answered_question and b"How should the scripted run proceed?" in output:
-        os.write(fd, b"\r")
-        answered_question = True
-    if scenario == "conversation" and answered_question and not sent_exit and b"Decision received. Scripted TUI run complete." in output:
-        os.write(fd, b"/exit\r")
-        sent_exit = True
-    if scenario == "boot" and not sent_exit and b"TUI agent ready." in output:
-        os.write(fd, b"/exit\r")
-        sent_exit = True
-    waited, candidate = os.waitpid(pid, os.WNOHANG)
-    if waited == pid:
-        status = candidate
-        break
-
-if status is None:
-    os.kill(pid, signal.SIGKILL)
-    _, status = os.waitpid(pid, 0)
-sys.stdout.buffer.write(output)
-if scenario == "resume-failure":
-    if b'ui-tui: session "missing-session" failed to start:' not in output:
-        sys.stderr.write("TUI did not render the startup failure before timeout\n")
-        sys.exit(126)
-    if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 1:
-        sys.stderr.write("TUI startup failure did not exit with status 1\n")
-        sys.exit(127)
-elif scenario == "conversation":
-    if not sent_prompt:
-        sys.stderr.write("TUI did not render the scripted welcome marker before timeout\n")
-        sys.exit(128)
-    if not answered_question:
-        sys.stderr.write("TUI did not render the user-question dialog before timeout\n")
-        sys.exit(129)
-    if not sent_exit:
-        sys.stderr.write("TUI did not finish the scripted tool round-trip before timeout\n")
-        sys.exit(130)
-    if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 0:
-        sys.stderr.write("TUI scripted conversation did not exit cleanly\n")
-        sys.exit(131)
-else:
-    if not sent_exit:
-        sys.stderr.write("TUI did not render its welcome marker before timeout\n")
-        sys.exit(124)
-    if not os.WIFEXITED(status) or os.WEXITSTATUS(status) != 0:
-        sys.stderr.write("TUI child did not exit cleanly\n")
-        sys.exit(125)
-`
-
-interface TuiLoaderSmokeOptions {
-  config?: string
-  resumeSessionId?: string
-  scenario?: 'boot' | 'conversation' | 'resume-failure'
-}
-
-async function runTuiLoaderSmoke(options: TuiLoaderSmokeOptions = {}): Promise<string> {
-  const cwd = await mkdtemp(join(tmpdir(), 'tui-agent-smoke-'))
-  try {
-    const launch = resolveExampleLaunch({
-      srcBin: binScript,
-      configArgs: [options.config ?? configPath],
-      tsconfigPath,
-      exposeInternals: true,
-      env: {
-        DEEPSEEK_API_KEY: 'keyless-tui-no-call',
-        DSH_HOME: join(cwd, '.dsh'),
-        DSH_AGENTS_HOME: join(cwd, '.agents'),
-      },
-    })
-    return await new Promise((resolve, reject) => {
-      const child = spawn('python3', [
-        '-c',
-        PTY_DRIVER,
-        launch.command,
-        JSON.stringify(launch.args),
-        JSON.stringify(launch.env),
-        cwd,
-        options.resumeSessionId ?? '',
-        options.scenario ?? 'boot',
-      ], { stdio: ['ignore', 'pipe', 'pipe'] })
-      let stdout = ''
-      let stderr = ''
-      child.stdout.setEncoding('utf8')
-      child.stdout.on('data', (chunk: string) => { stdout += chunk })
-      child.stderr.setEncoding('utf8')
-      child.stderr.on('data', (chunk: string) => { stderr += chunk })
-      child.once('error', reject)
-      child.once('exit', (code) => {
-        if (code === 0) resolve(stdout)
-        else reject(new Error(`TUI PTY smoke exited ${String(code)}. stdout:\n${stdout}\nstderr:\n${stderr}`))
-      })
-    })
-  } finally {
-    await rm(cwd, { recursive: true, force: true })
-  }
-}
-
 describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
   it('boots pi-tui, renders the configured banner, accepts /exit, and restores the terminal', async () => {
-    const output = await runTuiLoaderSmoke()
+    const output = await runTuiPtySmoke({
+      label: 'tui-agent boot',
+      tempDirPrefix: 'tui-agent-smoke-',
+      binScript,
+      configPath,
+      tsconfigPath,
+      env: { DEEPSEEK_API_KEY: 'keyless-tui-no-call' },
+      actions: [{ waitFor: 'TUI agent ready.', send: '/exit\r' }],
+    })
     expect(output).toContain('DEEPSEEK')
     expect(output).toContain('TUI agent ready.')
     expect(output).toContain('\u001B[?2004l')
   }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 
   it('streams a response, answers a user-question dialog, completes the tool round-trip, and exits cleanly', async () => {
-    const output = await runTuiLoaderSmoke({ config: scriptedConfigPath, scenario: 'conversation' })
+    const output = await runTuiPtySmoke({
+      label: 'tui-agent conversation',
+      tempDirPrefix: 'tui-agent-conversation-',
+      binScript,
+      configPath: scriptedConfigPath,
+      tsconfigPath,
+      actions: [
+        { waitFor: 'scripted TUI ready.', send: 'exercise the TUI\r' },
+        { waitFor: 'How should the scripted run proceed?', send: '\r' },
+        { waitFor: 'Decision received. Scripted TUI run complete.', send: '/exit\r' },
+      ],
+    })
     expect(output).toContain('I need one decision before I continue.')
     expect(output).toContain(String.raw`\x1b]2;MODEL_CONTROLLED\x07`)
     expect(output).toContain(String.raw`\x1b[999CMODEL_CURSOR`)
@@ -159,14 +44,23 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
     expect(output).not.toContain('\u001B]2;MODEL_CONTROLLED\u0007')
     expect(output).not.toContain('\u001B[999CMODEL_CURSOR')
     expect(output).not.toContain('\u009B31mMODEL_C1')
-    expect(output).toContain('How should the scripted run proceed?')
     expect(output).toContain('Safe')
-    expect(output).toContain('Decision received. Scripted TUI run complete.')
     expect(output).toContain('\u001B[?2004l')
   }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 
   it('prints a config-resume failure and exits instead of leaving a blank terminal', async () => {
-    const output = await runTuiLoaderSmoke({ resumeSessionId: 'missing-session', scenario: 'resume-failure' })
+    const output = await runTuiPtySmoke({
+      label: 'tui-agent resume failure',
+      tempDirPrefix: 'tui-agent-resume-',
+      binScript,
+      configPath,
+      tsconfigPath,
+      env: {
+        DEEPSEEK_API_KEY: 'keyless-tui-no-call',
+        RESUME_SESSION_ID: 'missing-session',
+      },
+      expectedExitCode: 1,
+    })
     expect(output).toContain('ui-tui: session "missing-session" failed to start:')
   }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 })

+ 2 - 6
knip.json

@@ -121,18 +121,14 @@
       "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"],
       "project": ["src/**/*.ts", "tests/**/*.ts"]
     },
-    "packages/examples/stdio-demo": {
-      "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"],
+    "packages/examples/tui-demo": {
+      "entry": ["tests/**/*.spec.ts"],
       "project": ["src/**/*.ts", "tests/**/*.ts"]
     },
     "packages/examples/cli-demo": {
       "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"],
       "project": ["src/**/*.ts", "tests/**/*.ts"]
     },
-    "packages/ui/stdio": {
-      "entry": ["tests/**/*.spec.ts"],
-      "project": ["src/**/*.ts", "tests/**/*.ts"]
-    },
     "packages/ui/tui": {
       "entry": ["tests/**/*.spec.ts", "tests/**/*.snapshot.ts"],
       "project": ["src/**/*.ts", "tests/**/*.ts"]

+ 3 - 4
package.json

@@ -78,12 +78,11 @@
     "constraints": "tsx scripts/check-workspace-constraints.ts",
     "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-events && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-package-readme-model-experience && pnpm run verify-mermaid && pnpm run verify-agent-note-classification && pnpm run verify-agent-note-format && pnpm run verify-type-equiv && pnpm run verify-translation-prompt && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets && pnpm run verify-package-readme-limitations && pnpm run docs:check",
     "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-runtime-closure",
-    "demo:echo": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/echo-agent/cordis.yml",
-    "demo:repl": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/repl-agent/cordis.yml",
+    "demo:echo": "node --expose-internals --import tsx packages/examples/cli-demo/src/bin.ts --config examples/echo-agent/cordis.yml",
     "demo:headless": "node --expose-internals --import tsx packages/examples/cli-demo/src/bin.ts --config examples/headless-agent/cordis.yml",
-    "demo:tui": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/tui-agent/cordis.yml",
+    "demo:tui": "node --expose-internals --import tsx packages/examples/tui-demo/src/bin.ts examples/tui-agent/cordis.yml",
     "demo:code-mode": "node scripts/demo-code-mode.mjs",
-    "demo:cordis": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/cordis-agent/cordis.yml",
+    "demo:cordis": "node --expose-internals --import tsx packages/examples/tui-demo/src/bin.ts examples/cordis-agent/cordis.yml",
     "demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml",
     "postinstall": "node scripts/install-lefthook.mjs"
   },

+ 1 - 1
packages/README.md

@@ -31,7 +31,7 @@ Packages live at `packages/<group>/<pkg>/`; groups are containers, while names r
 | [`session-query/`](session-query/README.md) | Session retrieval: logical corpus, bounded reads, lineage, and event relationships | Product — stable surface |
 | [`sdk/`](sdk/README.md) | Project SDK tooling | Product — stable surface |
 | [`ui/`](ui/README.md) | Editor/client integration surfaces: ACP bridge, JSON-RPC SDK server, user-approval/user-interaction seams, ask-user tool | Product — stable surface |
-| [`examples/`](examples/README.md) | Demo bundles (agent-spine + stdio/one-shot CLI/ACP/JSON-RPC bins) the leaves load | Support — example infra |
+| [`examples/`](examples/README.md) | Demo bundles (agent-spine + TUI/one-shot CLI/ACP/JSON-RPC bins) the leaves load | Support — example infra |
 | [`support/`](support/README.md) | Support infrastructure (testkits, invariants, replay, Loader smokes) | Support — lower compatibility expectations |
 | [`util/`](util/README.md) | Low-level zero-dependency utilities shared across groups (`Branded<B>`, Harness home/path helpers, timeout, retention) | Support — small, stable, harness-dep-free |
 

+ 23 - 79
packages/context/time-context/tests/time-context.e2e.ts

@@ -1,34 +1,21 @@
-import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
-import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises'
-import { tmpdir } from 'node:os'
+import { readFile, readdir } from 'node:fs/promises'
 import { join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-import { afterEach, describe, expect, it } from 'vitest'
+import { describe, expect, it } from 'vitest'
 import { type SessionEvent } from '@deepseek-ai/dsh-session'
-import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
+import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
 
 // Keep the Loader config under examples so both modes exercise the same deployable
 // topology: local fixture source plus bare plugins owned by the examples workspace.
-const binScript = fileURLToPath(new URL('../../../examples/stdio-demo/src/bin.ts', import.meta.url))
+const driver = fileURLToPath(new URL(
+  '../../../../examples/echo-agent/tests/fixtures/context/time-context/driver.ts',
+  import.meta.url,
+))
 const configPath = fileURLToPath(new URL(
   '../../../../examples/echo-agent/tests/fixtures/context/time-context/cordis.yml',
   import.meta.url,
 ))
 const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
-const PROCESS_TIMEOUT_MS = 30_000
-const TEST_TIMEOUT_MS = PROCESS_TIMEOUT_MS + 15_000
-const FIRST_REPLY = '[main turn 1] You said: "Time sampled while preparing turn 1, step 1:'
-const SECOND_REPLY = '[main turn 2] You said: "Time sampled while preparing turn 2, step 1:'
-
-let child: ChildProcessWithoutNullStreams | undefined
-let workdir: string | undefined
-
-afterEach(async () => {
-  if (child !== undefined && child.exitCode === null) child.kill('SIGKILL')
-  child = undefined
-  if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
-  workdir = undefined
-})
 
 async function jsonlFiles(dir: string): Promise<string[]> {
   const entries = await readdir(dir, { withFileTypes: true })
@@ -40,68 +27,25 @@ async function jsonlFiles(dir: string): Promise<string[]> {
   return paths.flat()
 }
 
-async function runTwoTurns(): Promise<{ stdout: string; stderr: string }> {
-  workdir = await mkdtemp(join(tmpdir(), 'time-context-e2e-'))
-  const cwd = workdir
-  return new Promise((resolve, reject) => {
-    const launch = resolveExampleLaunch({
-      srcBin: binScript,
-      configArgs: [configPath],
+describe('time-context through a real headless cordis.yml', () => {
+  it('uses the process zone and persists one ordered context event per request', async () => {
+    let events: SessionEvent[] = []
+    const { stderr } = await runLoaderSmoke({
+      label: 'time-context headless smoke',
+      tempDirPrefix: 'time-context-e2e-',
+      binScript: driver,
+      libBinScript: driver,
+      configPath,
       tsconfigPath: repoTsconfig,
-      exposeInternals: true,
-      env: {
-        TZ: 'Asia/Shanghai',
-        DSH_HOME: join(cwd, '.dsh'),
-        DSH_AGENTS_HOME: join(cwd, '.agents'),
+      env: { TZ: 'Asia/Shanghai' },
+      inspect: async (cwd) => {
+        const logs = await jsonlFiles(join(cwd, '.sessions'))
+        expect(logs).toHaveLength(1)
+        const lines = (await readFile(logs[0] as string, 'utf8')).trimEnd().split('\n')
+        events = lines.slice(1).map(line => JSON.parse(line) as SessionEvent)
       },
     })
-    const proc = spawn(launch.command, launch.args, {
-      cwd,
-      env: { ...process.env, ...launch.env },
-      stdio: ['pipe', 'pipe', 'pipe'],
-    })
-    child = proc
-    let stdout = ''
-    let stderr = ''
-    let sentSecond = false
-    proc.stdout.setEncoding('utf8')
-    proc.stdout.on('data', (chunk: string) => {
-      stdout += chunk
-      if (!sentSecond && stdout.includes(FIRST_REPLY) && stdout.includes('Try "echo <something>" to see a tool call.\n> ')) {
-        sentSecond = true
-        proc.stdin.end('second\n')
-      }
-    })
-    proc.stderr.setEncoding('utf8')
-    proc.stderr.on('data', (chunk: string) => { stderr += chunk })
-
-    const timer = setTimeout(() => {
-      proc.kill('SIGKILL')
-      reject(new Error(`time-context e2e did not exit within ${PROCESS_TIMEOUT_MS / 1_000}s. stdout:\n${stdout}\nstderr:\n${stderr}`))
-    }, PROCESS_TIMEOUT_MS)
-
-    proc.on('exit', (code) => {
-      clearTimeout(timer)
-      if (code === 0) resolve({ stdout, stderr })
-      else reject(new Error(`time-context e2e exited ${code}. stdout:\n${stdout}\nstderr:\n${stderr}`))
-    })
-    proc.on('error', (error) => { clearTimeout(timer); reject(error) })
-    proc.stdin.write('first\n')
-  })
-}
-
-describe('time-context through a real cordis.yml and stdio process', () => {
-  it('uses the process zone and persists one ordered context event per request', async () => {
-    const { stdout, stderr } = await runTwoTurns()
     expect(stderr).not.toContain('UNHANDLED')
-    expect(stdout).toContain('time-context e2e ready.')
-    expect(stdout).toContain(FIRST_REPLY)
-    expect(stdout).toContain(SECOND_REPLY)
-
-    const logs = await jsonlFiles(join(workdir as string, '.sessions'))
-    expect(logs).toHaveLength(1)
-    const lines = (await readFile(logs[0] as string, 'utf8')).trimEnd().split('\n')
-    const events = lines.slice(1).map(line => JSON.parse(line) as SessionEvent)
     expect(events.filter(event => event.type === 'turn/end')).toHaveLength(2)
 
     const contexts = events.filter(event => event.type === 'context/message')
@@ -127,5 +71,5 @@ describe('time-context through a real cordis.yml and stdio process', () => {
 
     const headers = events.filter(event => event.type === 'request/header')
     expect(JSON.stringify(headers)).not.toContain('Time sampled while preparing')
-  }, TEST_TIMEOUT_MS)
+  }, LOADER_SMOKE_TEST_TIMEOUT_MS)
 })

+ 1 - 1
packages/cordis/tool-cordis/src/sandbox.ts

@@ -15,7 +15,7 @@ import { sandboxDefineTool, sandboxRegisterTool } from './guard.ts'
  * A write-through console for one sandbox, tagging every line with the mount
  * id. Write-through (host stdout/stderr), NOT buffered into the tool result:
  * a mounted listener fires long after the mount call returned, and its output
- * must land somewhere the user can see — for the stdio demo, the terminal.
+ * must land somewhere the user can see — for a terminal front door, the host terminal.
  */
 function taggedConsole(id: string): Record<'log' | 'info' | 'warn' | 'error' | 'debug', (...args: unknown[]) => void> {
   const tag = `[cordis:${id}]`

+ 16 - 16
packages/core/agent-loop/tests/config-session-id.spec.ts

@@ -47,9 +47,9 @@ describe('config-driven session id', () => {
   it('accepts one exact fresh id and rejects it alongside a resume id', async () => {
     const exact = await makeCoreContext()
     await exact.plugin(AgentLoop, {
-      agents: [{ id: 'main', sessionId: SessionId('stdio-exact'), model: 'mock' }],
+      agents: [{ id: 'main', sessionId: SessionId('config-exact'), model: 'mock' }],
     })
-    expect(exact.agents.get(SessionId('stdio-exact'))?.session.id).toBe('stdio-exact')
+    expect(exact.agents.get(SessionId('config-exact'))?.session.id).toBe('config-exact')
     await exact.fiber.dispose()
 
     const conflicting = await makeCoreContext()
@@ -89,13 +89,13 @@ describe('config-driven session id', () => {
     const ctx = await makeCoreContext()
     await ctx.plugin(SessionPersistenceJsonl, { root })
     ctx.llm.registerAdapter(['mock'], new MockAdapter([textResponse('first'), textResponse('second')]))
-    const config = { agents: [{ id: 'main', sessionId: SessionId('stdio-exact-reload'), model: 'mock' }] }
+    const config = { agents: [{ id: 'main', sessionId: SessionId('config-exact-reload'), model: 'mock' }] }
 
     const firstLoop = await ctx.plugin(AgentLoop, config)
     let first: Agent | undefined
     for (let i = 0; i < 50 && first === undefined; i++) {
       await new Promise(resolve => setTimeout(resolve, 5))
-      first = ctx.agents.get(SessionId('stdio-exact-reload'))
+      first = ctx.agents.get(SessionId('config-exact-reload'))
     }
     expect(first).toBeDefined()
     first!.send([{ type: 'text', text: 'remember me' }], { source: { kind: 'user' } })
@@ -106,14 +106,14 @@ describe('config-driven session id', () => {
     let second: Agent | undefined
     for (let i = 0; i < 50 && second === undefined; i++) {
       await new Promise(resolve => setTimeout(resolve, 5))
-      second = ctx.agents.get(SessionId('stdio-exact-reload'))
+      second = ctx.agents.get(SessionId('config-exact-reload'))
     }
     expect(second).toBeDefined()
     expect(JSON.stringify(second!.session.deriveMessages())).toContain('remember me')
     second!.send([{ type: 'text', text: 'continue' }], { source: { kind: 'user' } })
     await waitForIdle(ctx, second!)
     await ctx.sessions.flush(second!.session)
-    const loaded = await ctx.sessionPersistence.load(SessionId('stdio-exact-reload'))
+    const loaded = await ctx.sessionPersistence.load(SessionId('config-exact-reload'))
     expect(loaded.events.filter(event => event.type === 'turn/start')).toHaveLength(2)
 
     await secondLoop.dispose()
@@ -125,7 +125,7 @@ describe('config-driven session id', () => {
     dirs.push(root)
     const ctx = await makeCoreContext()
     await ctx.plugin(SessionPersistenceJsonl, { root })
-    const sessionId = SessionId('stdio-exact-overlap')
+    const sessionId = SessionId('config-exact-overlap')
     const config = { agents: [{ id: 'main', sessionId, model: 'mock' }] }
     const firstLoop = await ctx.plugin(AgentLoop, config)
     await expect.poll(() => ctx.agents.get(sessionId)).toBeDefined()
@@ -169,7 +169,7 @@ describe('config-driven session id', () => {
     dirs.push(root)
     const ctx = await makeCoreContext()
     await ctx.plugin(SessionPersistenceJsonl, { root })
-    const sessionId = SessionId('stdio-exact-cancel')
+    const sessionId = SessionId('config-exact-cancel')
     const config = { agents: [{ id: 'main', sessionId, model: 'mock' }] }
     const firstLoop = await ctx.plugin(AgentLoop, config)
     await expect.poll(() => ctx.agents.get(sessionId)).toBeDefined()
@@ -213,20 +213,20 @@ describe('config-driven session id', () => {
     const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => undefined)
 
     await ctx.plugin(AgentLoop, {
-      agents: [{ id: 'main', sessionId: SessionId('stdio-exact-failure'), model: 'mock' }],
+      agents: [{ id: 'main', sessionId: SessionId('config-exact-failure'), model: 'mock' }],
     })
 
     await expect.poll(() => warn).toHaveBeenCalledWith(expect.stringContaining(
-      'config-driven restore of "stdio-exact-failure" failed: Error: persistence index failed',
+      'config-driven restore of "config-exact-failure" failed: Error: persistence index failed',
     ))
-    expect(failures).toEqual([{ sessionId: SessionId('stdio-exact-failure'), error: failure }])
+    expect(failures).toEqual([{ sessionId: SessionId('config-exact-failure'), error: failure }])
     expect(warn).toHaveBeenCalledWith(
       'agent "main": config-start-failed listener threw: Error: failure observer failed',
     )
     await expect.poll(() => warn).toHaveBeenCalledWith(
       'agent "main": config-start-failed listener rejected: Error: async failure observer failed',
     )
-    expect(ctx.agents.get(SessionId('stdio-exact-failure'))).toBeUndefined()
+    expect(ctx.agents.get(SessionId('config-exact-failure'))).toBeUndefined()
     warn.mockRestore()
     await ctx.fiber.dispose()
   })
@@ -251,12 +251,12 @@ describe('config-driven session id', () => {
     const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => undefined)
 
     await ctx.plugin(AgentLoop, {
-      agents: [{ id: 'main', sessionId: SessionId('stdio-exact-unrenderable'), model: 'mock' }],
+      agents: [{ id: 'main', sessionId: SessionId('config-exact-unrenderable'), model: 'mock' }],
     })
 
     await expect.poll(() => failures).toEqual([unrenderable])
     expect(warn).toHaveBeenCalledWith(
-      'agent "main": config-driven restore of "stdio-exact-unrenderable" failed: <unrenderable thrown value>',
+      'agent "main": config-driven restore of "config-exact-unrenderable" failed: <unrenderable thrown value>',
     )
     expect(warn).toHaveBeenCalledWith(
       'agent "main": config-start-failed listener threw: <unrenderable thrown value>',
@@ -281,7 +281,7 @@ describe('config-driven session id', () => {
       ctx.on('agent-loop/config-start-failed', (_sessionId, error) => { failures.push(error) })
 
       const loop = await ctx.plugin(AgentLoop, {
-        agents: [{ id: 'main', sessionId: SessionId('stdio-exact-dispose'), model: 'mock' }],
+        agents: [{ id: 'main', sessionId: SessionId('config-exact-dispose'), model: 'mock' }],
       })
       let disposed = false
       const disposal = loop.dispose().then(() => { disposed = true })
@@ -291,7 +291,7 @@ describe('config-driven session id', () => {
       if (outcome === 'resolve') listing.resolve([])
       else listing.reject(new Error('startup cancelled by teardown'))
       await disposal
-      expect(ctx.agents.get(SessionId('stdio-exact-dispose'))).toBeUndefined()
+      expect(ctx.agents.get(SessionId('config-exact-dispose'))).toBeUndefined()
       expect(failures).toEqual([])
       expect(warn).not.toHaveBeenCalled()
       warn.mockRestore()

+ 2 - 2
packages/examples/README.md

@@ -5,12 +5,12 @@ Pre-composed plugin bundles a thin leaf `cordis.yml` loads instead of assembling
 | Package | npm name | Role |
 |---|---|---|
 | `agent-spine-demo/` | `@deepseek-ai/dsh-agent-spine-demo` | The executor-less/UI-less agent spine as one bundle plugin (`timer` + `llm` + sessions + system-prompt + tools + skills + agents + invariants + `tool-bash` + workspace-context + `tool-skill` + `agent-loop`) |
-| `stdio-demo/` | `@deepseek-ai/dsh-stdio-demo` | Terminal chat app: the spine + JSONL persistence + TTY-selected `dsh-tui`/`dsh-stdio` front door + a pre-created `main` agent, with a boot `bin` |
+| `tui-demo/` | `@deepseek-ai/dsh-tui-demo` | Full-screen terminal app: the spine + JSONL persistence + `dsh-tui` + a pre-created `main` agent, with a boot `bin` |
 | `cli-demo/` | `@deepseek-ai/dsh-cli-demo` | Headless one-shot app: the spine + JSONL persistence + a pre-created `main` agent, with text and DSH-native JSON output |
 | `acp-demo/` | `@deepseek-ai/dsh-acp-demo` | ACP server app: the spine + JSONL persistence + the [`acp`](../ui/acp/README.md) bridge (no stdout logger), with a boot `bin` |
 | `jsonrpc-demo/` | `@deepseek-ai/dsh-jsonrpc-demo` | Bin-only runtime that boots an external `cordis.yml` for the stdio JSON-RPC SDK client |
 
-`agent-spine-demo` is the shared bundle; `stdio-demo`, `cli-demo`, and `acp-demo` compose it with terminal, headless one-shot, and ACP front doors and own their boot bins. `jsonrpc-demo` mounts no composition of its own — it boots whatever tree the deployment's `cordis.yml` names, and is what the Python SDK runtime launches.
+`agent-spine-demo` is the shared bundle; `tui-demo`, `cli-demo`, and `acp-demo` compose it with full-screen terminal, headless one-shot, and ACP front doors and own their boot bins. `jsonrpc-demo` mounts no composition of its own — it boots whatever tree the deployment's `cordis.yml` names, and is what the Python SDK runtime launches.
 
 These are **not** product API. The spine pieces they bundle live in [`core/`](../core/README.md), the bridges/channels/boot-glue in [`ui/`](../ui/README.md), and the swappable backends (LLM adapter, bash executor) in their capability groups; a demo bundle just picks one concrete composition of them. Swap or fork one freely.
 

+ 2 - 2
packages/examples/acp-demo/README.md

@@ -2,7 +2,7 @@
 
 The **ACP server app**: a Cordis app plugin that composes the default agent spine ([`@deepseek-ai/dsh-agent-spine-demo`](../agent-spine-demo/README.md)) with the front-door cluster an [Agent Client Protocol](../../ui/acp/README.md) server needs, and a `bin` that boots a leaf `cordis.yml` speaking ACP JSON-RPC on stdio.
 
-It is the structured counterpart to [`@deepseek-ai/dsh-stdio-demo`](../stdio-demo/README.md): both consume the same spine, but this one bakes in the OPPOSITE front-door cluster.
+It is the structured counterpart to [`@deepseek-ai/dsh-tui-demo`](../tui-demo/README.md): both consume the same spine, but ACP creates sessions from its client and reserves stdout for its wire protocol.
 
 ## What it bakes in — and what it deliberately omits
 
@@ -19,7 +19,7 @@ stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it
 | ~~console logger~~ | **omitted** — it writes to stdout and would corrupt the protocol frames ([the stdout-purity footgun](../../ui/acp/README.md)) |
 | ~~`hmr`~~ | **omitted** — the editor owns the subprocess |
 
-Because the package wires no logger entry, an ACP leaf has **nothing to get wrong by default**: it only picks backends, so the common mistake — copying a console-logger entry from the stdio config — has no place here. (A leaf author technically *can* still add `@cordisjs/plugin-logger-console` as a sibling entry; the package can't forbid that. So the rule stands: never add a stdout logger to an ACP leaf — stdout is the JSON-RPC channel. Use a stderr exporter if you need logs.)
+Because the package wires no logger entry, an ACP leaf has **nothing to get wrong by default**: it only picks backends. A leaf author can still add `@cordisjs/plugin-logger-console` as a sibling entry, so the rule remains: never add a stdout logger to an ACP leaf; use a stderr exporter instead.
 
 ## Config
 

部分文件因文件數量過多而無法顯示