Files
deepseek-harness/apps/cli/tui.cordis.yml
T
Turtle f290a8b851 refactor(cli)!: one shared base config with per-surface overlays
`dsh` shipped two config trees that were 43 rows the same: apps/cli/cordis.yml
composed web as 74 flat rows, while the TUI booted examples/tui-agent/cordis.yml
whose single `@deepseek-ai/dsh-tui-demo` row mounted twelve plugins behind a
twenty-key pass-through Config. Neither file was what its location claimed —
apps/cli hardcoded the "example" as the product default and the "demo" bundle
was the application — and every capability change had to be made twice.

- apps/cli/base.cordis.yml holds the 43 shared rows; tui.cordis.yml and
  web.cordis.yml are patch lists stating only what differs per surface
- overlays apply as SIBLING patch lists at one include level, because include
  patches never cross an include boundary. Precedence: base < surface <
  (--config | personal ~/.dsh/config.yaml) < launcher flag/profile patches
- `--config` now applies an overlay INSTEAD OF the personal one, so a demo or
  test tree never inherits the user's route; new `--config-replace` boots a file
  as the entire tree (the old `--config` behaviour). Both survive /resume
- vendor/include: index each `insert`ed row as it is added so a later patch can
  configure or disable it. Upstream built the id index once before the patch
  loop, leaving every surface-only row — the whole TUI front door — silently
  unpatchable from user config. Logged as local modification 8
- session identity moves to dsh-agent-loop's CONFIGURED_AGENT_IDENTITIES_KEY;
  dsh-tui's MAIN_SESSION_ID_KEY is deleted (only the bundle read it)
- delete examples/tui-agent, examples/cordis-agent, packages/examples/tui-demo;
  TUI tests → apps/cli/tests, cordis e2e → packages/cordis/tool-cordis/tests,
  examples/code-mode survives as an overlay leaf
- `dsh web` gains --config, threaded into AppCLIEntry as an extra overlay

Three latent defects surfaced and are fixed here: the TUI captured the optional
sessionQuery service once at construction and could permanently disable /resume
when it won the mount race; the session-store root silently reverted to a
project-local ./.sessions; --config-replace was dropped by the resume handoff.

Verified by booting each tree through the real Loader (TUI 55 entries, web 75,
zero unsettled) rather than reading YAML. All eight terminal snapshots replay
byte-identically; 14/14 PTY smoke, 112/112 snapshots, 25/25 doc-sync, hygiene
and lint clean.
2026-07-29 21:15:42 +08:00

132 lines
5.2 KiB
YAML

# `dsh` (the default surface) — the full-screen TUI, as a patch list over
# `base.cordis.yml`. The launcher includes the base and applies this file, then
# any `--config` overlay, then the personal `~/.dsh/config.yaml`, as sibling
# patch lists at ONE include level: patches never cross an include boundary, so
# stacking overlays as nested includes would silently stop reaching base rows.
#
# A patch replaces the targeted row's whole `config`, so each row below restates
# every key it owns. A patch whose `id` matches no row is skipped with a Loader
# warning, which is deliberate: one personal overlay is shared across surfaces,
# so a row that exists only under `web` must not fail the TUI's boot.
#
# The launcher owns session identity and the exit line, and provides both on the
# boot context rather than through config, so no key here — and no overlay
# replacing one — can drop `--resume`.
# ── surface-specific values the base deliberately omits ─────────────────────
# `main` is the agent the TUI drives. `provider`/`model` are the route `dsh
# login` rewrites and a personal overlay repoints; `cwd` anchors the session to
# the invoking directory, which is also what scopes `/resume` to this workspace.
- id: agent-loop
config:
agents:
- id: main
provider: deepseek
model: deepseek-v4-pro
cwd: !!js process.cwd()
# Keep the persona to identity and behavior; tool plugins own tool guidance.
# The loop resolves {{model}} from this agent's configuration.
- id: system-prompt
config:
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.
# Shipped default: full thinking at max effort on every request (wire-only
# defaults; they never enter the request header).
- id: llm-deepseek
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
thinking: enabled
reasoningEffort: max
# This single-session app resolves relative paths from the process cwd.
- id: fs-local
config:
cwd: !!js process.cwd()
# The shipped TUI presents the native tool registry. `examples/code-mode` is the
# overlay that switches this row to the `run_code` transport.
- id: tools
config:
mode: native
# ── TUI-only rows ───────────────────────────────────────────────────────────
- insert:
# Development-only hot reload; it depends on Loader internals, so it stays a
# surface row rather than joining the shared base.
- id: hmr
name: '@cordisjs/plugin-hmr'
config:
root: ['.']
# Relational runtime checks over the authoritative event streams; each
# companion registers the assertions its own package owns.
- id: invariants
name: '@deepseek-ai/dsh-invariants'
- id: session-invariant
name: '@deepseek-ai/dsh-session/invariant'
- id: agent-invariant
name: '@deepseek-ai/dsh-agent/invariant'
- id: scope-invariant
name: '@deepseek-ai/dsh-scope/invariant'
- id: agent-loop-invariant
name: '@deepseek-ai/dsh-agent-loop/invariant'
- id: session-checkpoint-policy
name: '@deepseek-ai/dsh-session-checkpoint-policy'
# The derived query index behind `/resume`. The launcher owns the session
# store location, so it provides the resolved index path on the boot context
# (`launcherSessionQueryPath`); the index and the logs it indexes therefore
# cannot diverge. The project-local fallback applies when no launcher sets it.
- id: session-query-sqlite
name: '@deepseek-ai/dsh-session-query-sqlite'
config:
path: !!js launcherSessionQueryPath ?? './.sessions/session-query.db'
- id: session-reference
name: '@deepseek-ai/dsh-session-reference'
# Refuses write/edit inside the dsh checkout this launcher runs from, on that
# checkout's own branch, until the session loads dsh-customize. Inert
# everywhere else, so an ordinary project sees no change.
- id: source-guard
name: '@deepseek-ai/dsh-source-guard'
- id: tool-result-prune
name: '@deepseek-ai/dsh-compact-tool-result-prune'
# Persisted same-session goals reach the model and the slash menu here; the
# domain, driver, and `/goal` command are in the base.
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
# A separate fixed consumer demonstrates fresh-agent Ralph iteration without
# changing the workflow tool or same-session goal behavior.
- id: tool-ralph
name: '@deepseek-ai/dsh-tool-ralph'
# The keyboard-backed provider behind ask_user_question and the plan-mode
# review, and the front door it renders inside.
- id: tui-prompt
name: '@deepseek-ai/dsh-tui/prompt'
# The TUI renders exactly the agent the agent-loop row bound, so it reads the
# same launcher-owned identity rather than restating one.
- id: tui
name: '@deepseek-ai/dsh-tui'
config:
sessionId: !!js configuredAgentIdentities?.main?.id ?? 'main'
showReasoning: true
maxToolOutputLines: 6
- id: tool-ask-user
name: '@deepseek-ai/dsh-tool-ask-user'