Merge refreshed typed Code Mode results into result card fix
# Conflicts: # .agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml # .agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml # examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl # examples/tui-agent/tests/snapshots/code-mode/terminal.expected.txt
This commit is contained in:
@@ -1,23 +1,78 @@
|
||||
# tui-agent
|
||||
|
||||
The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows and fresh-agent Ralph iteration, `todo_write`, timeout/spill policy, and [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo).
|
||||
The full-screen interactive coding agent: DeepSeek V4, local bash and filesystem tools, compaction, subagents, workflows and fresh-agent Ralph iteration, plan mode (`/plan` enters and `exit_plan_mode` reviews the exit), timeout/spill policy, and JSONL persistence through [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo), loaded from `cordis.yml`. The sibling [`headless-agent`](../headless-agent/README.md) runs the same capability class as a one-shot pipe-friendly task, and [`acp-agent`](../acp-agent/README.md) serves it over JSON-RPC.
|
||||
|
||||
## 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:tui
|
||||
```
|
||||
|
||||
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`.
|
||||
Both the demo script and the installable `dsh` CLI ([`apps/cli`](../../apps/cli/README.md)) boot this example's `cordis.yml` as the shipped default config; `dsh` additionally applies the personal overlay from `~/.dsh` and uses the invoking directory as the workspace.
|
||||
|
||||
The TUI renders Markdown history, reasoning, tool-owned terminal/diff/generic cards, token totals, and the latest todo list. Long tool bodies keep a head/tail preview; Ctrl+O expands or collapses every card. Enter submits or steers while the agent runs, Ctrl+R toggles reasoning, Escape cancels, and `/help` lists commands. `/model` opens a keyboard selector for the current provider catalog; use Up/Down and Enter, or `/model <model>` and `/model <provider>/<model>` for direct selection. `ask_user_question` opens a wide bottom-left keyboard panel with batch progress and numbered options.
|
||||
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`.
|
||||
|
||||
Run `pnpm run demo:code-mode tui` for the Code Mode overlay.
|
||||
The `todo_write` task tracker is opt-in and not in the shipped config: add `@deepseek-ai/dsh-tool-todo` to `cordis.yml` (or a personal-config overlay under `~/.dsh`) to expose it. Once loaded, the model records a whole-list plan to the session log and the TUI renders it.
|
||||
|
||||
## Composition
|
||||
The TUI renders Markdown history, reasoning, tool-owned terminal/diff/generic cards, token totals, and — when `todo_write` is loaded — the latest plan. Long tool bodies keep a head/tail preview; Ctrl+O expands or collapses every card. Enter submits or steers while the agent runs, Ctrl+R toggles reasoning, Escape cancels, and `/help` lists commands. `/plan` selects plan mode for the next step; `/plan <message>` also submits the message into that step. `/status` expands the current session's identity, activity counts, exact token/cache buckets, context use, and timestamps without interrupting a running turn. `/model` opens a keyboard selector for the current provider catalog; use Up/Down and Enter, or `/model <model>` and `/model <provider>/<model>` for direct selection. `ask_user_question` opens a wide bottom-left keyboard panel with batch progress and numbered options.
|
||||
|
||||
[`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.
|
||||
### Resuming a prior session
|
||||
|
||||
Each run starts a fresh session by default (its event log lands under `./.sessions/`). To **continue** a previous conversation, pass its id to the installed `dsh` CLI — the `main` agent then rehydrates the persisted log instead of starting fresh, so the model sees the earlier turns as history:
|
||||
|
||||
```sh
|
||||
dsh --resume <prior-session-id>
|
||||
```
|
||||
|
||||
The TUI prints this exact command on exit and lists it under `/resume`, so resuming is copy-paste. The flag sets `RESUME_SESSION_ID`, wired through `cordis.yml` (`resumeSessionId: !!js process.env.RESUME_SESSION_ID`); the env var still works directly for the uninstalled demo (`RESUME_SESSION_ID=<prior-session-id> pnpm run demo:tui`), and with neither set 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.
|
||||
|
||||
## 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 TUI (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 pi-tui channel, the pre-created `main` agent) live inside the [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-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:tui` 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 |
|
||||
| `tui-agent` (`@deepseek-ai/dsh-tui-demo`) | the app bundle: the agent-spine demo + JSONL persistence + the pi-tui channel + a pre-created `main` agent |
|
||||
| `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 |
|
||||
| `plan-mode` | the plugin-owned `/plan [message]` command, plan-mode prompt policy, tool restrictions, and reviewed `exit_plan_mode` transition |
|
||||
| `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`)
|
||||
|
||||
The UI-independent with-key suites assemble the full stack programmatically through `tests/harness.ts` (no PTY, no Loader):
|
||||
|
||||
- `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 the auto-compaction listener fires MID-SESSION. Verifies the WORLD — a `compact/start…end` pair landed in the real log, the surface shrank (a replace node shadowed older nodes), and the agent still produced a correct final answer after compaction.
|
||||
- `tests/todo-write.e2e.ts` — loads the opt-in `todo_write` tool, then a real model drives it and the test verifies the resulting `todo/write` session event.
|
||||
- `tests/code-mode.e2e.ts` — 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.
|
||||
|
||||
These self-skip without `DEEPSEEK_API_KEY`. The keyless `tests/tui-keyless-smoke.e2e.ts` boots the real Loader tree in a PTY (the one sanctioned PTY surface): the base boot + `/plan` + `/exit`, a scripted-LLM conversation with a question dialog and tool round-trip, the Code Mode overlay welcome line, and the resume-failure exit path.
|
||||
|
||||
## 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 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.
|
||||
`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.
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
model: deepseek-v4-pro
|
||||
resumeSessionId: !!js process.env.RESUME_SESSION_ID
|
||||
persistenceRoot: './.sessions'
|
||||
resumeCommand: 'dsh --resume {session}'
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
tools:
|
||||
|
||||
@@ -23,6 +23,8 @@ flowchart LR
|
||||
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_session_title_llm["session-title-llm<br/>@deepseek-ai/dsh-session-title-first-message-llm"]
|
||||
cfg --> plugin_tui_session_title_llm
|
||||
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"]
|
||||
@@ -45,8 +47,8 @@ flowchart LR
|
||||
cfg --> plugin_tui_tool_workflow
|
||||
plugin_tui_tool_ralph["tool-ralph<br/>@deepseek-ai/dsh-tool-ralph"]
|
||||
cfg --> plugin_tui_tool_ralph
|
||||
plugin_tui_tool_todo["tool-todo<br/>@deepseek-ai/dsh-tool-todo"]
|
||||
cfg --> plugin_tui_tool_todo
|
||||
plugin_tui_plan_mode["plan-mode<br/>@deepseek-ai/dsh-plan-mode"]
|
||||
cfg --> plugin_tui_plan_mode
|
||||
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"]
|
||||
@@ -69,6 +71,7 @@ flowchart LR
|
||||
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
|
||||
| `bash` | `@deepseek-ai/dsh-bash-local` |
|
||||
| `tui-agent` | `@deepseek-ai/dsh-tui-demo` |
|
||||
| `session-title-llm` | `@deepseek-ai/dsh-session-title-first-message-llm` |
|
||||
| `token-meter` | `@deepseek-ai/dsh-token-meter` |
|
||||
| `tool-result-prune` | `@deepseek-ai/dsh-compact-tool-result-prune` |
|
||||
| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
|
||||
@@ -80,7 +83,7 @@ flowchart LR
|
||||
| `workflow-workerthread` | `@deepseek-ai/dsh-workflow-workerthread` |
|
||||
| `tool-workflow` | `@deepseek-ai/dsh-tool-workflow` |
|
||||
| `tool-ralph` | `@deepseek-ai/dsh-tool-ralph` |
|
||||
| `tool-todo` | `@deepseek-ai/dsh-tool-todo` |
|
||||
| `plan-mode` | `@deepseek-ai/dsh-plan-mode` |
|
||||
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
|
||||
| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
|
||||
| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
|
||||
|
||||
@@ -1,52 +1,86 @@
|
||||
# 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`.
|
||||
# Full-screen TUI coding agent with swappable DeepSeek and local-bash backends.
|
||||
# `dsh-tui-demo` supplies the agent spine, workspace instructions, generic
|
||||
# task controls, JSONL persistence, the pi-tui front door, and `main`.
|
||||
# HMR remains a leaf because it requires Loader internals; `demo:tui` 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. Shipped default: full thinking at max effort on
|
||||
# every request (wire-only defaults; they never enter the request header).
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
config:
|
||||
apiKey: !!js process.env.DEEPSEEK_API_KEY
|
||||
baseURL: !!js process.env.DEEPSEEK_BASE_URL
|
||||
thinking: enabled
|
||||
reasoningEffort: max
|
||||
|
||||
# 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 TUI's `main` agent.
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: deepseek
|
||||
model: deepseek-v4-flash
|
||||
model: deepseek-v4-pro
|
||||
# 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'
|
||||
# Printed on exit and listed by `/resume`; `{session}` fills the live id.
|
||||
# `dsh --resume <id>` sets RESUME_SESSION_ID above, so run it from this cwd.
|
||||
resumeCommand: 'dsh --resume {session}'
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
welcome: 'TUI agent ready. Give it a coding task.'
|
||||
ui:
|
||||
showReasoning: true
|
||||
maxToolOutputLines: 6
|
||||
# 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.
|
||||
|
||||
# Model-made session titles on the first-message cadence: replaces the spine's
|
||||
# deterministic fallback title with a short model summary. The TUI renders the
|
||||
# logged `session/title` as the banner subtitle and the terminal window title.
|
||||
# Omitting provider/model inherits the main request's exact route.
|
||||
- id: session-title-llm
|
||||
name: '@deepseek-ai/dsh-session-title-first-message-llm'
|
||||
config:
|
||||
targetWords: 5
|
||||
targetCjkCharacters: 10
|
||||
maxInputBytes: 4096
|
||||
maxOutputTokens: 64
|
||||
timeoutMs: 60000
|
||||
|
||||
# Replay-aware request pressure with one service-wide context window.
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
- 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'
|
||||
|
||||
@@ -72,6 +106,9 @@
|
||||
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:
|
||||
@@ -85,9 +122,26 @@
|
||||
- id: tool-ralph
|
||||
name: '@deepseek-ai/dsh-tool-ralph'
|
||||
|
||||
- id: tool-todo
|
||||
name: '@deepseek-ai/dsh-tool-todo'
|
||||
# Plan mode gives the TUI a plugin-owned /plan [message] command; the exit
|
||||
# review rides the TUI's user-interaction provider.
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
section: |
|
||||
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
||||
|
||||
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
||||
|
||||
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed only to keep the request shape stable. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
||||
|
||||
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
||||
|
||||
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
||||
|
||||
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
||||
|
||||
# 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:
|
||||
@@ -99,12 +153,24 @@
|
||||
- id: tool-fs
|
||||
name: '@deepseek-ai/dsh-tool-fs'
|
||||
|
||||
# Bash-backed discovery tools (glob/grep): fixed ripgrep commands through the
|
||||
# local bash executor above — 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'
|
||||
|
||||
|
||||
@@ -5,6 +5,14 @@ import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
const CONTROL_PROBE = '\u001b]2;MODEL_CONTROLLED\u0007\u001b[999CMODEL_CURSOR\u009b31mMODEL_C1'
|
||||
const INITIAL_TEXT = `I need one decision before I continue. ${CONTROL_PROBE}`
|
||||
const FINAL_TEXT = 'Decision received. Scripted TUI run complete.'
|
||||
// The `skill` scenario types `/skill:scripted-skill`; the manual-invocation front
|
||||
// door delivers the loaded skill as a user turn wrapped in `<skill name="…">`. The
|
||||
// body marker below lives in the fixture skill, so echoing it back proves the whole
|
||||
// block (name attribute plus body) reached the model, not just the command text.
|
||||
const SKILL_BLOCK_OPEN = '<skill name="scripted-skill">'
|
||||
const SKILL_BODY_MARKER = 'SCRIPTED SKILL BODY MARKER'
|
||||
const SKILL_RECEIVED_TEXT = 'Scripted skill body received.'
|
||||
const TITLE_TEXT = 'scripted session title'
|
||||
|
||||
function textChunks(text: string): StreamChunk[] {
|
||||
return [
|
||||
@@ -16,7 +24,7 @@ function textChunks(text: string): StreamChunk[] {
|
||||
]
|
||||
}
|
||||
|
||||
/** Keyless two-step adapter for the real-PTY TUI conversation test. */
|
||||
/** Keyless adapter for the real-PTY TUI tests: the two-step conversation and the `/skill:` round-trip. */
|
||||
class ScriptedTuiAdapter extends LlmAdapter {
|
||||
override listModels(provider: string): Promise<readonly LlmModelInfo[]> {
|
||||
return Promise.resolve([
|
||||
@@ -30,10 +38,30 @@ class ScriptedTuiAdapter extends LlmAdapter {
|
||||
}
|
||||
|
||||
override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
// The session-title provider's auxiliary request carries no tool schemas,
|
||||
// unlike every agent turn; answer it with a fixed title so the PTY test can
|
||||
// assert the logged title reaches the terminal window title.
|
||||
if ((options.tools?.length ?? 0) === 0) {
|
||||
for (const chunk of textChunks(TITLE_TEXT)) yield chunk
|
||||
return
|
||||
}
|
||||
if (options.model !== 'tui-scripted-model-pro' || !options.system?.includes('tui-scripted-model-pro')) {
|
||||
throw new Error('the scripted TUI request did not apply the selected model to routing and prompt variables')
|
||||
}
|
||||
const hasToolResult = options.messages.at(-1)?.content.some(block => block.type === 'tool-result') ?? false
|
||||
const lastMessage = options.messages.at(-1)
|
||||
const lastText = (lastMessage?.content ?? [])
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('\n')
|
||||
if (lastText.includes(SKILL_BLOCK_OPEN)) {
|
||||
const ack = lastText.includes(SKILL_BODY_MARKER)
|
||||
? SKILL_RECEIVED_TEXT
|
||||
: 'Scripted skill block arrived without its body.'
|
||||
for (const chunk of textChunks(ack)) yield chunk
|
||||
return
|
||||
}
|
||||
|
||||
const hasToolResult = lastMessage?.content.some(block => block.type === 'tool-result') ?? false
|
||||
if (hasToolResult) {
|
||||
for (const chunk of textChunks(FINAL_TEXT)) yield chunk
|
||||
return
|
||||
|
||||
@@ -15,15 +15,35 @@
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
- id: plan-mode
|
||||
name: '@deepseek-ai/dsh-plan-mode'
|
||||
config:
|
||||
section: 'Stay in plan mode for this scripted TUI test.'
|
||||
|
||||
- id: tui-agent
|
||||
name: '@deepseek-ai/dsh-tui-demo'
|
||||
config:
|
||||
provider: tui-scripted
|
||||
model: tui-scripted-model
|
||||
persistenceRoot: './.sessions'
|
||||
# The smoke's log inspection reads plain `.jsonl`; keep the scripted
|
||||
# fixture uncompressed like the other snapshot-facing configs.
|
||||
persistenceCompression: none
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
welcome: 'scripted TUI ready.'
|
||||
persona: 'Scripted model {{model}}.'
|
||||
ui:
|
||||
showReasoning: true
|
||||
|
||||
# Model-made session titles, as in the shipped cordis.yml: the scripted adapter
|
||||
# answers the tool-less title request with a fixed string so the PTY test can
|
||||
# assert the logged title reaches the terminal window title.
|
||||
- id: session-title-llm
|
||||
name: '@deepseek-ai/dsh-session-title-first-message-llm'
|
||||
config:
|
||||
targetWords: 5
|
||||
targetCjkCharacters: 10
|
||||
maxInputBytes: 4096
|
||||
maxOutputTokens: 64
|
||||
timeoutMs: 10000
|
||||
|
||||
@@ -10,6 +10,10 @@ node, launch_args_json, launch_env_json, cwd, actions_json, expected_exit, timeo
|
||||
env = os.environ.copy()
|
||||
env.update(json.loads(launch_env_json))
|
||||
env.update({"COLUMNS": "100", "LINES": "30"})
|
||||
# Deterministic banner: a developer shell's COLORTERM=truecolor would switch the
|
||||
# banner to the per-letter gradient (one SGR per letter), breaking literal
|
||||
# DEEPSEEK assertions. The gradient path has its own unit and snapshot coverage.
|
||||
env.pop("COLORTERM", None)
|
||||
actions = json.loads(actions_json)
|
||||
pid, fd = pty.fork()
|
||||
if pid == 0:
|
||||
@@ -63,12 +67,19 @@ export interface TuiPtySmokeOptions {
|
||||
readonly label: string
|
||||
readonly tempDirPrefix: string
|
||||
readonly binScript: string
|
||||
readonly configPath: string
|
||||
/** Config argument; ignored when {@link configArgs} is set. */
|
||||
readonly configPath?: string
|
||||
/** Full argument vector for the bin (e.g. `[]` for a bin with a built-in default config). */
|
||||
readonly configArgs?: readonly string[]
|
||||
readonly tsconfigPath: string
|
||||
readonly actions?: readonly TuiPtyAction[]
|
||||
readonly env?: Readonly<NodeJS.ProcessEnv>
|
||||
readonly expectedExitCode?: number
|
||||
readonly timeoutMs?: number
|
||||
/** Seed the isolated workspace (`cwd`, with `$DSH_HOME` at `.dsh` and the agents home at `.agents`) before launch. */
|
||||
readonly prepare?: (cwd: string) => Promise<void>
|
||||
/** Inspect the workspace after a passing run, before the temp dir is removed. */
|
||||
readonly inspect?: (cwd: string) => Promise<void>
|
||||
}
|
||||
|
||||
function definedEnv(env: NodeJS.ProcessEnv): Record<string, string> {
|
||||
@@ -135,6 +146,9 @@ async function runWindowsPtySmoke(
|
||||
env: definedEnv({
|
||||
...process.env,
|
||||
...launch.env,
|
||||
// Match the POSIX driver: no COLORTERM, so the banner never takes the
|
||||
// truecolor gradient path under a developer's shell.
|
||||
COLORTERM: undefined,
|
||||
COLUMNS: '100',
|
||||
LINES: '30',
|
||||
}),
|
||||
@@ -175,9 +189,13 @@ export async function runTuiPtySmoke(options: TuiPtySmokeOptions): Promise<strin
|
||||
const cwd = await mkdtemp(join(tmpdir(), options.tempDirPrefix))
|
||||
const timeoutMs = options.timeoutMs ?? 25_000
|
||||
try {
|
||||
await options.prepare?.(cwd)
|
||||
const launch = resolveExampleLaunch({
|
||||
srcBin: options.binScript,
|
||||
configArgs: [options.configPath],
|
||||
configArgs: options.configArgs !== undefined
|
||||
? [...options.configArgs]
|
||||
/* v8 ignore next -- every caller passes configPath or configArgs; the fallback keeps the type total */
|
||||
: [options.configPath ?? './cordis.yml'],
|
||||
tsconfigPath: options.tsconfigPath,
|
||||
exposeInternals: true,
|
||||
env: {
|
||||
@@ -186,10 +204,12 @@ export async function runTuiPtySmoke(options: TuiPtySmokeOptions): Promise<strin
|
||||
...options.env,
|
||||
},
|
||||
})
|
||||
if (process.platform === 'win32') {
|
||||
return await runWindowsPtySmoke(launch, cwd, options, timeoutMs)
|
||||
}
|
||||
return await runPosixPtySmoke(launch, cwd, options, timeoutMs)
|
||||
const output = process.platform === 'win32'
|
||||
? await runWindowsPtySmoke(launch, cwd, options, timeoutMs)
|
||||
: await runPosixPtySmoke(launch, cwd, options, timeoutMs)
|
||||
// Inspect the workspace before `finally` removes it (e.g. the session log).
|
||||
await options.inspect?.(cwd)
|
||||
return output
|
||||
} finally {
|
||||
await rm(cwd, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -1,73 +1,63 @@
|
||||
terminal 100x36 buffer=normal length=36 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Use the bash tool to — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=27 bufferRow=27
|
||||
cursor hidden column=1 viewportRow=25 bufferRow=25
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Use the bash tool to"
|
||||
style 1-20 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Use the bash tool to │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-21 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ You "
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
8| "▌ Use the bash tool to run exactly: echo TERMINAL_OK. Then reply with the single word DONE and stop."
|
||||
6| "▌ Use the bash tool to run exactly: echo TERMINAL_OK. Then reply with the single word DONE and stop."
|
||||
style 0-0 fg=bright-blue
|
||||
9| "▌ "
|
||||
7| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
10| <blank>
|
||||
11| " Reasoning "
|
||||
8| <blank>
|
||||
9| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
12| " The user wants me to run a simple bash command and then reply with \"DONE\". "
|
||||
10| " The user wants me to run a simple bash command and then reply with \"DONE\". "
|
||||
style 1-74 fg=bright-black italic
|
||||
13| <blank>
|
||||
14| "▌ "
|
||||
11| <blank>
|
||||
12| "▌ "
|
||||
style 0-0 fg=green
|
||||
15| "▌ ✓ echo TERMINAL_OK "
|
||||
13| "▌ ✓ echo TERMINAL_OK "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-19 bold
|
||||
16| "▌ Echo TERMINAL_OK to verify terminal access "
|
||||
14| "▌ Echo TERMINAL_OK to verify terminal access "
|
||||
style 0-0 fg=green
|
||||
style 2-43 fg=bright-black
|
||||
17| "▌ TERMINAL_OK "
|
||||
15| "▌ TERMINAL_OK "
|
||||
style 0-0 fg=green
|
||||
18| "▌ [exit 0] "
|
||||
16| "▌ [exit 0] "
|
||||
style 0-0 fg=green
|
||||
style 2-9 dim
|
||||
19| "▌ "
|
||||
17| "▌ "
|
||||
style 0-0 fg=green
|
||||
20| <blank>
|
||||
21| " Reasoning "
|
||||
18| <blank>
|
||||
19| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
22| " The command ran successfully and output \"TERMINAL_OK\". I should now reply with just \"DONE\". "
|
||||
20| " The command ran successfully and output \"TERMINAL_OK\". I should now reply with just \"DONE\". "
|
||||
style 1-91 fg=bright-black italic
|
||||
23| <blank>
|
||||
24| " Assistant "
|
||||
21| <blank>
|
||||
22| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
25| " DONE "
|
||||
23| " DONE "
|
||||
24| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
25| " "
|
||||
style 1-1 inverse
|
||||
26| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
27| " "
|
||||
style 1-1 inverse
|
||||
28| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
29| "/tmp/dsh-tui-snapshot-bash-te ↑3.0k ↓115 3% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-28 dim
|
||||
style 42-99 dim
|
||||
30-35| <blank>
|
||||
27| "deepseek-v4-flash /workspace/project ↑3.0k ↓115 cache 48% 3% contex"
|
||||
style 0-88 dim
|
||||
style 91-99 dim
|
||||
28-35| <blank>
|
||||
|
||||
@@ -1,98 +1,89 @@
|
||||
terminal 100x36 buffer=normal length=38 base=2 viewport=2
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Using ONE run_code program: call — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=35
|
||||
cursor hidden column=1 viewportRow=31 bufferRow=33
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Using ONE run_code program: call"
|
||||
style 1-32 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Using ONE run_code program: call │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-33 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ You "
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
8| "▌ Using ONE run_code program: call the bash tool twice — exactly echo CODE_ONE then exactly echo "
|
||||
6| "▌ Using ONE run_code program: call the bash tool twice — exactly echo CODE_ONE then exactly echo "
|
||||
style 0-0 fg=bright-blue
|
||||
style 65-77 fg=cyan
|
||||
style 92-99 fg=cyan
|
||||
9| "▌ CODE_TWO. Inside that same program, console.log exactly captured output, then return the two "
|
||||
7| "▌ CODE_TWO. Inside that same program, console.log exactly captured output, then return the two "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=cyan
|
||||
style 58-72 fg=cyan
|
||||
10| "▌ outputs joined with a plus sign. Reply with that joined string only and stop. "
|
||||
8| "▌ outputs joined with a plus sign. Reply with that joined string only and stop. "
|
||||
style 0-0 fg=bright-blue
|
||||
11| "▌ "
|
||||
9| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
12| <blank>
|
||||
13| " Reasoning "
|
||||
10| <blank>
|
||||
11| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
14| " The user wants me to write a single run_code program that: "
|
||||
12| " The user wants me to write a single run_code program that: "
|
||||
style 1-58 fg=bright-black italic
|
||||
15| " 1. Calls bash tool twice - first with echo CODE_ONE, then with echo CODE_TWO "
|
||||
13| " 1. Calls bash tool twice - first with echo CODE_ONE, then with echo CODE_TWO "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-38 fg=bright-black italic
|
||||
style 39-51 fg=cyan
|
||||
style 52-63 fg=bright-black italic
|
||||
style 64-76 fg=cyan
|
||||
16| " 2. console.log exactly captured output "
|
||||
14| " 2. console.log exactly captured output "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-23 fg=bright-black italic
|
||||
style 24-38 fg=cyan
|
||||
17| " 3. Return the two outputs joined with a plus sign "
|
||||
15| " 3. Return the two outputs joined with a plus sign "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-49 fg=bright-black italic
|
||||
18| " "
|
||||
19| " Let me write this carefully. "
|
||||
16| " "
|
||||
17| " Let me write this carefully. "
|
||||
style 1-28 fg=bright-black italic
|
||||
20| <blank>
|
||||
21| "▌ "
|
||||
18| <blank>
|
||||
19| "▌ "
|
||||
style 0-0 fg=green
|
||||
22| "▌ ✓ const result1 = await tools.bash({ command: \"echo CODE_ONE\", description: \"First echo\" }); "
|
||||
20| "▌ ✓ const result1 = await tools.bash({ command: \"echo CODE_ONE\", description: \"First echo\" }); "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-99 bold
|
||||
23| "▌ cons "
|
||||
21| "▌ cons "
|
||||
style 0-0 fg=green
|
||||
style 2-5 bold
|
||||
24| "▌ captured output "
|
||||
22| "▌ captured output "
|
||||
style 0-0 fg=green
|
||||
25| "▌ CODE_ONE+CODE_TWO "
|
||||
23| "▌ CODE_ONE+CODE_TWO "
|
||||
style 0-0 fg=green
|
||||
26| "▌ "
|
||||
24| "▌ "
|
||||
style 0-0 fg=green
|
||||
27| <blank>
|
||||
28| " Reasoning "
|
||||
25| <blank>
|
||||
26| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
29| " The user asked me to reply with that joined string only and stop. The joined string is "
|
||||
27| " The user asked me to reply with that joined string only and stop. The joined string is "
|
||||
style 1-99 fg=bright-black italic
|
||||
30| " CODE_ONE+CODE_TWO. "
|
||||
28| " CODE_ONE+CODE_TWO. "
|
||||
style 1-17 fg=cyan
|
||||
style 18-18 fg=bright-black italic
|
||||
31| <blank>
|
||||
32| " Assistant "
|
||||
29| <blank>
|
||||
30| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
33| " CODE_ONE+CODE_TWO "
|
||||
31| " CODE_ONE+CODE_TWO "
|
||||
32| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
33| " "
|
||||
style 1-1 inverse
|
||||
34| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
35| " "
|
||||
style 1-1 inverse
|
||||
36| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
37| "/tmp/dsh-tui-snapshot-code-mo ↑4.1k ↓227 4% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-28 dim
|
||||
style 42-99 dim
|
||||
35| "deepseek-v4-flash /workspace/project ↑4.1k ↓227 cache 53% 4% context tools:"
|
||||
style 0-79 dim
|
||||
style 82-99 dim
|
||||
36-37| <blank>
|
||||
|
||||
@@ -1,116 +1,106 @@
|
||||
terminal 100x36 buffer=normal length=50 base=14 viewport=14
|
||||
terminal 100x36 buffer=normal length=48 base=12 viewport=12
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Run this advanced flow exactly — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=47
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=45
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Run this advanced flow exactly"
|
||||
style 1-30 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Run this advanced flow exactly │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-31 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ You "
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
8| "▌ Run this advanced flow exactly once: mount a no-op Cordis plugin named snapshot-marker; use "
|
||||
6| "▌ Run this advanced flow exactly once: mount a no-op Cordis plugin named snapshot-marker; use "
|
||||
style 0-0 fg=bright-blue
|
||||
9| "▌ run_code to inspect the live dynamic mounts through tools.cordis_inspect; delegate once to a "
|
||||
7| "▌ run_code to inspect the live dynamic mounts through tools.cordis_inspect; delegate once to a "
|
||||
style 0-0 fg=bright-blue
|
||||
10| "▌ direct spawn child; run one workflow that delegates to another spawn child; unmount dyn-1; then "
|
||||
8| "▌ direct spawn child; run one workflow that delegates to another spawn child; unmount dyn-1; then "
|
||||
style 0-0 fg=bright-blue
|
||||
11| "▌ reply with exactly ADVANCED_ACP_OK. "
|
||||
9| "▌ reply with exactly ADVANCED_ACP_OK. "
|
||||
style 0-0 fg=bright-blue
|
||||
10| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
11| <blank>
|
||||
12| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
13| <blank>
|
||||
14| "▌ "
|
||||
style 0-0 fg=green
|
||||
15| "▌ ✓ Mount plugin into live cordis runtime "
|
||||
13| "▌ ✓ Mount plugin into live cordis runtime "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-40 bold
|
||||
16| "▌ mounted dyn-1 (plugin \"snapshot-marker\", state: active) "
|
||||
14| "▌ mounted dyn-1 (plugin \"snapshot-marker\", state: active) "
|
||||
style 0-0 fg=green
|
||||
15| "▌ "
|
||||
style 0-0 fg=green
|
||||
16| <blank>
|
||||
17| "▌ "
|
||||
style 0-0 fg=green
|
||||
18| <blank>
|
||||
19| "▌ "
|
||||
style 0-0 fg=green
|
||||
20| "▌ ✓ return await tools.cordis_inspect({ what: 'dynamic' }) "
|
||||
18| "▌ ✓ return await tools.cordis_inspect({ what: 'dynamic' }) "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-57 bold
|
||||
21| "▌ ## dynamic "
|
||||
19| "▌ ## dynamic "
|
||||
style 0-0 fg=green
|
||||
22| "▌ - dyn-1: snapshot-marker [active] "
|
||||
20| "▌ - dyn-1: snapshot-marker [active] "
|
||||
style 0-0 fg=green
|
||||
21| "▌ "
|
||||
style 0-0 fg=green
|
||||
22| <blank>
|
||||
23| "▌ "
|
||||
style 0-0 fg=green
|
||||
24| <blank>
|
||||
25| "▌ "
|
||||
style 0-0 fg=green
|
||||
26| "▌ ✓ subagent "
|
||||
24| "▌ ✓ subagent "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-11 bold
|
||||
27| "▌ DIRECT_CHILD_OK "
|
||||
25| "▌ DIRECT_CHILD_OK "
|
||||
style 0-0 fg=green
|
||||
26| "▌ "
|
||||
style 0-0 fg=green
|
||||
27| <blank>
|
||||
28| "▌ "
|
||||
style 0-0 fg=green
|
||||
29| <blank>
|
||||
30| "▌ "
|
||||
style 0-0 fg=green
|
||||
31| "▌ ✓ workflow: advanced-acp-snapshot "
|
||||
29| "▌ ✓ workflow: advanced-acp-snapshot "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-34 bold
|
||||
32| "▌ workflow \"advanced-acp-snapshot\" completed (1 agent). "
|
||||
30| "▌ workflow \"advanced-acp-snapshot\" completed (1 agent). "
|
||||
style 0-0 fg=green
|
||||
33| "▌ Return value: "
|
||||
31| "▌ Return value: "
|
||||
style 0-0 fg=green
|
||||
34| "▌ { "
|
||||
32| "▌ { "
|
||||
style 0-0 fg=green
|
||||
35| "▌ \"reply\": \"WORKFLOW_CHILD_OK\" "
|
||||
33| "▌ \"reply\": \"WORKFLOW_CHILD_OK\" "
|
||||
style 0-0 fg=green
|
||||
36| "▌ } "
|
||||
34| "▌ } "
|
||||
style 0-0 fg=green
|
||||
35| "▌ "
|
||||
style 0-0 fg=green
|
||||
36| <blank>
|
||||
37| "▌ "
|
||||
style 0-0 fg=green
|
||||
38| <blank>
|
||||
39| "▌ "
|
||||
style 0-0 fg=green
|
||||
40| "▌ ✓ Unmount dyn-1 "
|
||||
38| "▌ ✓ Unmount dyn-1 "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-16 bold
|
||||
41| "▌ unmounted dyn-1 (plugin \"snapshot-marker\") "
|
||||
39| "▌ unmounted dyn-1 (plugin \"snapshot-marker\") "
|
||||
style 0-0 fg=green
|
||||
42| "▌ "
|
||||
40| "▌ "
|
||||
style 0-0 fg=green
|
||||
43| <blank>
|
||||
44| " Assistant "
|
||||
41| <blank>
|
||||
42| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
45| " ADVANCED_ACP_OK "
|
||||
43| " ADVANCED_ACP_OK "
|
||||
44| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
45| " "
|
||||
style 1-1 inverse
|
||||
46| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
47| " "
|
||||
style 1-1 inverse
|
||||
48| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
49| "/tmp/dsh-tui-snapshot-cordis-dyn ↑18 ↓18 8% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-31 dim
|
||||
style 42-99 dim
|
||||
47| "deepseek-v4-flash /workspace/project ↑18 ↓18 cache 0% 8% cont"
|
||||
style 0-90 dim
|
||||
style 93-99 dim
|
||||
|
||||
@@ -1,106 +1,96 @@
|
||||
terminal 100x36 buffer=normal length=47 base=11 viewport=11
|
||||
terminal 100x36 buffer=normal length=45 base=9 viewport=9
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Use the workflow tool exactly — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=44
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=42
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Use the workflow tool exactly"
|
||||
style 1-29 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Use the workflow tool exactly │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-30 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ You "
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
8| "▌ Use the workflow tool exactly once, with args omitted, meta set to { \"name\": \"snapshot-flow\", "
|
||||
6| "▌ Use the workflow tool exactly once, with args omitted, meta set to { \"name\": \"snapshot-flow\", "
|
||||
style 0-0 fg=bright-blue
|
||||
9| "▌ \"description\": \"one child for the snapshot\" }, and this EXACT script body (copy it verbatim): "
|
||||
7| "▌ \"description\": \"one child for the snapshot\" }, and this EXACT script body (copy it verbatim): "
|
||||
style 0-0 fg=bright-blue
|
||||
10| "▌ phase('Run') "
|
||||
8| "▌ phase('Run') "
|
||||
style 0-0 fg=bright-blue
|
||||
11| "▌ const reply = await agent('Reply with exactly the word WF_CHILD_OK and nothing else.') "
|
||||
9| "▌ const reply = await agent('Reply with exactly the word WF_CHILD_OK and nothing else.') "
|
||||
style 0-0 fg=bright-blue
|
||||
12| "▌ return { reply } "
|
||||
10| "▌ return { reply } "
|
||||
style 0-0 fg=bright-blue
|
||||
13| "▌ After the workflow returns, reply with the single word WORKFLOW_DONE and stop. Do not use any "
|
||||
11| "▌ After the workflow returns, reply with the single word WORKFLOW_DONE and stop. Do not use any "
|
||||
style 0-0 fg=bright-blue
|
||||
14| "▌ other tool. "
|
||||
12| "▌ other tool. "
|
||||
style 0-0 fg=bright-blue
|
||||
15| "▌ "
|
||||
13| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
16| <blank>
|
||||
17| " Reasoning "
|
||||
14| <blank>
|
||||
15| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
18| " The user wants me to use the workflow tool exactly once with specific parameters. Let me carefully "
|
||||
16| " The user wants me to use the workflow tool exactly once with specific parameters. Let me carefully "
|
||||
style 1-99 fg=bright-black italic
|
||||
19| " follow the instructions: "
|
||||
17| " follow the instructions: "
|
||||
style 1-24 fg=bright-black italic
|
||||
20| " "
|
||||
21| " 1. args omitted (so I don't include it) "
|
||||
18| " "
|
||||
19| " 1. args omitted (so I don't include it) "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-39 fg=bright-black italic
|
||||
22| " 2. meta = { \"name\": \"snapshot-flow\", \"description\": \"one child for the snapshot\" } "
|
||||
20| " 2. meta = { \"name\": \"snapshot-flow\", \"description\": \"one child for the snapshot\" } "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-82 fg=bright-black italic
|
||||
23| " 3. script = as given verbatim "
|
||||
21| " 3. script = as given verbatim "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-29 fg=bright-black italic
|
||||
24| " 4. After it returns, reply with \"WORKFLOW_DONE\" "
|
||||
22| " 4. After it returns, reply with \"WORKFLOW_DONE\" "
|
||||
style 1-3 fg=bright-blue
|
||||
style 4-47 fg=bright-black italic
|
||||
25| " "
|
||||
26| " Let me do exactly that. "
|
||||
23| " "
|
||||
24| " Let me do exactly that. "
|
||||
style 1-23 fg=bright-black italic
|
||||
27| <blank>
|
||||
28| "▌ "
|
||||
25| <blank>
|
||||
26| "▌ "
|
||||
style 0-0 fg=green
|
||||
29| "▌ ✓ workflow: snapshot-flow "
|
||||
27| "▌ ✓ workflow: snapshot-flow "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-26 bold
|
||||
30| "▌ workflow \"snapshot-flow\" completed (1 agent). "
|
||||
28| "▌ workflow \"snapshot-flow\" completed (1 agent). "
|
||||
style 0-0 fg=green
|
||||
31| "▌ Return value: "
|
||||
29| "▌ Return value: "
|
||||
style 0-0 fg=green
|
||||
32| "▌ { "
|
||||
30| "▌ { "
|
||||
style 0-0 fg=green
|
||||
33| "▌ \"reply\": \"WF_CHILD_OK\" "
|
||||
31| "▌ \"reply\": \"WF_CHILD_OK\" "
|
||||
style 0-0 fg=green
|
||||
34| "▌ } "
|
||||
32| "▌ } "
|
||||
style 0-0 fg=green
|
||||
35| "▌ "
|
||||
33| "▌ "
|
||||
style 0-0 fg=green
|
||||
36| <blank>
|
||||
37| " Reasoning "
|
||||
34| <blank>
|
||||
35| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
38| " The workflow returned successfully with the reply \"WF_CHILD_OK\". Now I need to reply with exactly "
|
||||
36| " The workflow returned successfully with the reply \"WF_CHILD_OK\". Now I need to reply with exactly "
|
||||
style 1-99 fg=bright-black italic
|
||||
39| " \"WORKFLOW_DONE\" and stop. "
|
||||
37| " \"WORKFLOW_DONE\" and stop. "
|
||||
style 1-25 fg=bright-black italic
|
||||
40| <blank>
|
||||
41| " Assistant "
|
||||
38| <blank>
|
||||
39| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
42| " WORKFLOW_DONE "
|
||||
40| " WORKFLOW_DONE "
|
||||
41| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
42| " "
|
||||
style 1-1 inverse
|
||||
43| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
44| " "
|
||||
style 1-1 inverse
|
||||
45| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
46| "/tmp/dsh-tui-snapshot-dynamic ↑3.5k ↓227 3% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-28 dim
|
||||
style 42-99 dim
|
||||
44| "deepseek-v4-flash /workspace/project ↑3.5k ↓227 cache 47% 3% context "
|
||||
style 0-86 dim
|
||||
style 89-99 dim
|
||||
|
||||
@@ -3,23 +3,16 @@ lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Reply with exactly the word: — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=28 bufferRow=28
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Reply with exactly the word: │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-29 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Reply with exactly the word:"
|
||||
style 1-28 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| " Entering plan mode (applies from the next step). "
|
||||
style 1-48 fg=bright-black
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
@@ -64,7 +57,7 @@ buffer
|
||||
style 1-1 inverse
|
||||
29| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
30| "/tmp/dsh-tui-snapshot-multi-tu ↑2.9k ↓41 3% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-29 dim
|
||||
style 42-99 dim
|
||||
30| "deepseek-v4-flash /workspace/project ↑2.9k ↓41 cache 49% 3% co"
|
||||
style 0-92 dim
|
||||
style 95-99 dim
|
||||
31-35| <blank>
|
||||
|
||||
@@ -1,91 +1,81 @@
|
||||
terminal 100x36 buffer=normal length=39 base=3 viewport=3
|
||||
terminal 100x36 buffer=normal length=37 base=1 viewport=1
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Use the read tool twice — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=36
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=34
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Use the read tool twice"
|
||||
style 1-23 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Use the read tool twice │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-24 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ You "
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
8| "▌ Use the read tool twice in the same assistant message: read a.txt and b.txt. Then reply DONE. "
|
||||
6| "▌ Use the read tool twice in the same assistant message: read a.txt and b.txt. Then reply DONE. "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
8| <blank>
|
||||
9| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
10| <blank>
|
||||
11| "▌ "
|
||||
style 0-0 fg=green
|
||||
12| "▌ ✓ Read a.txt "
|
||||
10| "▌ ✓ Read a.txt "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-13 bold
|
||||
13| "▌ <path>/workspace/project/a.txt</path> "
|
||||
11| "▌ <path>/workspace/project/a.txt</path> "
|
||||
style 0-0 fg=green
|
||||
14| "▌ <type>file</type> "
|
||||
12| "▌ <type>file</type> "
|
||||
style 0-0 fg=green
|
||||
15| "▌ <content> "
|
||||
13| "▌ <content> "
|
||||
style 0-0 fg=green
|
||||
16| "▌ 1: alpha "
|
||||
14| "▌ 1: alpha "
|
||||
style 0-0 fg=green
|
||||
17| "▌ "
|
||||
15| "▌ "
|
||||
style 0-0 fg=green
|
||||
18| "▌ (End of file - total 1 lines) "
|
||||
16| "▌ (End of file - total 1 lines) "
|
||||
style 0-0 fg=green
|
||||
19| "▌ </content> "
|
||||
17| "▌ </content> "
|
||||
style 0-0 fg=green
|
||||
18| "▌ "
|
||||
style 0-0 fg=green
|
||||
19| <blank>
|
||||
20| "▌ "
|
||||
style 0-0 fg=green
|
||||
21| <blank>
|
||||
22| "▌ "
|
||||
style 0-0 fg=green
|
||||
23| "▌ ✓ Read b.txt "
|
||||
21| "▌ ✓ Read b.txt "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-13 bold
|
||||
24| "▌ <path>/workspace/project/b.txt</path> "
|
||||
22| "▌ <path>/workspace/project/b.txt</path> "
|
||||
style 0-0 fg=green
|
||||
25| "▌ <type>file</type> "
|
||||
23| "▌ <type>file</type> "
|
||||
style 0-0 fg=green
|
||||
26| "▌ <content> "
|
||||
24| "▌ <content> "
|
||||
style 0-0 fg=green
|
||||
27| "▌ 1: beta "
|
||||
25| "▌ 1: beta "
|
||||
style 0-0 fg=green
|
||||
28| "▌ "
|
||||
26| "▌ "
|
||||
style 0-0 fg=green
|
||||
29| "▌ (End of file - total 1 lines) "
|
||||
27| "▌ (End of file - total 1 lines) "
|
||||
style 0-0 fg=green
|
||||
30| "▌ </content> "
|
||||
28| "▌ </content> "
|
||||
style 0-0 fg=green
|
||||
31| "▌ "
|
||||
29| "▌ "
|
||||
style 0-0 fg=green
|
||||
32| <blank>
|
||||
33| " Assistant "
|
||||
30| <blank>
|
||||
31| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
34| " DONE "
|
||||
32| " DONE "
|
||||
33| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
34| " "
|
||||
style 1-1 inverse
|
||||
35| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
36| " "
|
||||
style 1-1 inverse
|
||||
37| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
38| "/tmp/dsh-tui-snapshot-parallel-fi ↑20 ↓6 3% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-32 dim
|
||||
style 42-99 dim
|
||||
36| "deepseek-v4-flash /workspace/project ↑20 ↓6 cache 0% 3% context t"
|
||||
style 0-84 dim
|
||||
style 87-99 dim
|
||||
|
||||
@@ -1,81 +1,72 @@
|
||||
terminal 100x36 buffer=normal length=36 base=0 viewport=0
|
||||
lifecycle started=1 stopped=0 progress=inactive
|
||||
title "Use the todo_write tool to — DSH TUI snapshot"
|
||||
cursor hidden column=1 viewportRow=33 bufferRow=33
|
||||
cursor hidden column=1 viewportRow=31 bufferRow=31
|
||||
buffer
|
||||
0| "╭──────────────────────────────────────────────────────────────────────────────────────────────────╮"
|
||||
style 0-99 fg=bright-blue
|
||||
1| "│ DEEPSEEK HARNESS │"
|
||||
0| " DEEPSEEK HARNESS"
|
||||
style 1-8 fg=bright-blue bold
|
||||
style 10-16 bold
|
||||
1| " Use the todo_write tool to"
|
||||
style 1-26 fg=bright-black
|
||||
2| " deepseek-v4-flash • main-session"
|
||||
style 1-34 dim
|
||||
3| <blank>
|
||||
4| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-9 fg=bright-blue bold
|
||||
style 11-17 bold
|
||||
style 99-99 fg=bright-blue
|
||||
2| "│ Use the todo_write tool to │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-27 fg=bright-black
|
||||
style 99-99 fg=bright-blue
|
||||
3| "│ deepseek-v4-flash • main-session │"
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-35 dim
|
||||
style 99-99 fg=bright-blue
|
||||
4| "╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
style 0-99 fg=bright-blue
|
||||
5| <blank>
|
||||
6| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
7| "▌ You "
|
||||
5| "▌ You "
|
||||
style 0-0 fg=bright-blue
|
||||
style 2-4 fg=bright-blue bold
|
||||
8| "▌ Use the todo_write tool to record a plan with exactly three todos: \"read the code\" (in_progress), "
|
||||
6| "▌ Use the todo_write tool to record a plan with exactly three todos: \"read the code\" (in_progress), "
|
||||
style 0-0 fg=bright-blue
|
||||
9| "▌ \"write the fix\" (pending), \"run the tests\" (pending). Send all three in one todo_write call. Then "
|
||||
7| "▌ \"write the fix\" (pending), \"run the tests\" (pending). Send all three in one todo_write call. Then "
|
||||
style 0-0 fg=bright-blue
|
||||
10| "▌ reply with the single word DONE and stop. "
|
||||
8| "▌ reply with the single word DONE and stop. "
|
||||
style 0-0 fg=bright-blue
|
||||
11| "▌ "
|
||||
9| "▌ "
|
||||
style 0-0 fg=bright-blue
|
||||
12| <blank>
|
||||
13| " Reasoning "
|
||||
10| <blank>
|
||||
11| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
14| " The user wants me to use the todo_write tool to record a plan with exactly three todos in the "
|
||||
12| " The user wants me to use the todo_write tool to record a plan with exactly three todos in the "
|
||||
style 1-99 fg=bright-black italic
|
||||
15| " specified statuses, then reply with \"DONE\". "
|
||||
13| " specified statuses, then reply with \"DONE\". "
|
||||
style 1-43 fg=bright-black italic
|
||||
16| <blank>
|
||||
17| "▌ "
|
||||
14| <blank>
|
||||
15| "▌ "
|
||||
style 0-0 fg=green
|
||||
18| "▌ ✓ Update todo list "
|
||||
16| "▌ ✓ Update todo list "
|
||||
style 0-0 fg=green
|
||||
style 2-2 fg=green bold
|
||||
style 3-19 bold
|
||||
19| "▌ Updated todo list: 2 pending, 1 in progress, 0 completed. "
|
||||
17| "▌ Updated todo list: 2 pending, 1 in progress, 0 completed. "
|
||||
style 0-0 fg=green
|
||||
20| "▌ "
|
||||
18| "▌ "
|
||||
style 0-0 fg=green
|
||||
21| <blank>
|
||||
22| " Reasoning "
|
||||
19| <blank>
|
||||
20| " Reasoning "
|
||||
style 1-9 fg=bright-black italic
|
||||
23| " The todos have been written successfully. Now I just need to reply with the single word \"DONE\". "
|
||||
21| " The todos have been written successfully. Now I just need to reply with the single word \"DONE\". "
|
||||
style 1-95 fg=bright-black italic
|
||||
24| <blank>
|
||||
25| " Assistant "
|
||||
22| <blank>
|
||||
23| " Assistant "
|
||||
style 1-9 fg=bright-magenta bold
|
||||
26| " DONE "
|
||||
27| <blank>
|
||||
28| "Plan"
|
||||
24| " DONE "
|
||||
25| <blank>
|
||||
26| "Plan"
|
||||
style 0-3 fg=bright-blue bold
|
||||
29| " ● read the code"
|
||||
27| " ● read the code"
|
||||
style 2-2 fg=yellow
|
||||
30| " ○ write the fix"
|
||||
28| " ○ write the fix"
|
||||
style 2-2 dim
|
||||
31| " ○ run the tests"
|
||||
29| " ○ run the tests"
|
||||
style 2-2 dim
|
||||
30| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
31| " "
|
||||
style 1-1 inverse
|
||||
32| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
33| " "
|
||||
style 1-1 inverse
|
||||
34| "────────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||
style 0-99 dim
|
||||
35| "/tmp/dsh-tui-snapshot-todo-pl ↑3.1k ↓145 3% context tools:compact deepseek-v4-flash(reasoning:on)"
|
||||
style 0-28 dim
|
||||
style 42-99 dim
|
||||
33| "deepseek-v4-flash /workspace/project ↑3.1k ↓145 cache 47% 3% context tools:"
|
||||
style 0-79 dim
|
||||
style 82-99 dim
|
||||
34-35| <blank>
|
||||
|
||||
@@ -1,45 +1,118 @@
|
||||
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import { runTuiPtySmoke } from './pty-harness.ts'
|
||||
import { runTuiPtySmoke, type TuiPtySmokeOptions } from './pty-harness.ts'
|
||||
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/tui-demo/src/bin.ts', import.meta.url))
|
||||
const dshBinScript = fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url))
|
||||
const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
|
||||
const codeModeConfigPath = fileURLToPath(new URL('../code-mode.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))
|
||||
|
||||
/**
|
||||
* Seed the harness workspace: personal files land in the isolated Harness home
|
||||
* (`.dsh`), skill bundles under the agents home's `skills/` root — the same
|
||||
* trees `$DSH_HOME` / `$DSH_AGENTS_HOME` point the child at.
|
||||
*/
|
||||
function seedWorkspace(
|
||||
files: { personal?: Record<string, string>; skills?: Record<string, string> },
|
||||
): (cwd: string) => Promise<void> {
|
||||
return async (cwd) => {
|
||||
for (const [name, content] of Object.entries(files.personal ?? {})) {
|
||||
const file = join(cwd, '.dsh', name)
|
||||
await mkdir(dirname(file), { recursive: true })
|
||||
await writeFile(file, content)
|
||||
}
|
||||
for (const [name, content] of Object.entries(files.skills ?? {})) {
|
||||
const file = join(cwd, '.agents', 'skills', name)
|
||||
await mkdir(dirname(file), { recursive: true })
|
||||
await writeFile(file, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The rendered system prompt from the first `request/header` in the workspace's persisted session log. */
|
||||
async function readLoggedSystemPrompt(cwd: string): Promise<string> {
|
||||
const sessionsDir = join(cwd, '.sessions')
|
||||
const entries = await readdir(sessionsDir, { recursive: true })
|
||||
// A single keyless run writes one session log; the source section is global, so any log carries it.
|
||||
const logRelPath = entries.find(name => name.endsWith('.jsonl'))
|
||||
if (logRelPath === undefined) throw new Error(`no session log written under ${sessionsDir}`)
|
||||
const lines = (await readFile(join(sessionsDir, logRelPath), 'utf8')).split('\n').filter(Boolean)
|
||||
for (const line of lines) {
|
||||
const event = JSON.parse(line) as { type: string; data: { header?: { system?: string } } }
|
||||
if (event.type === 'request/header') return event.data.header?.system ?? ''
|
||||
}
|
||||
throw new Error(`session log ${logRelPath} has no request/header event`)
|
||||
}
|
||||
|
||||
/** Shared defaults: the keyless key, the tui-demo bin, and the live cordis.yml. */
|
||||
function smoke(overrides: Partial<TuiPtySmokeOptions> & { label: string }): Promise<string> {
|
||||
return runTuiPtySmoke({
|
||||
tempDirPrefix: 'tui-agent-smoke-',
|
||||
binScript,
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
env: { DEEPSEEK_API_KEY: 'keyless-tui-no-call' },
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
// The scripted conversation switches to the pro model first: the scripted
|
||||
// adapter proves routing + prompt variables by rejecting tool-ful calls on any
|
||||
// other route (see fixtures/tui-scripted-llm.ts).
|
||||
const SELECT_PRO_MODEL = [
|
||||
{ waitFor: 'scripted TUI ready.', send: '/model\r' },
|
||||
{ waitFor: 'Select model', send: '\x1b[B\r' },
|
||||
] as const
|
||||
|
||||
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 runTuiPtySmoke({
|
||||
it('boots pi-tui, sweeps the borderless banner in, enters plan mode, and restores the terminal', async () => {
|
||||
// With no configured welcome the borderless banner sweeps in left-to-right;
|
||||
// the detail line's session id (`main-session-<uuid>`) renders only once
|
||||
// the sweep reaches it, so it marks a settled banner.
|
||||
const output = await smoke({
|
||||
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' }],
|
||||
actions: [
|
||||
{ waitFor: 'main-session-', send: '/plan\r' },
|
||||
{ waitFor: 'Entering plan mode (applies from the next step).', send: '/exit\r' },
|
||||
],
|
||||
})
|
||||
expect(output).toContain('DEEPSEEK')
|
||||
expect(output).toContain('TUI agent ready.')
|
||||
expect(output).toContain('HARNESS')
|
||||
expect(output).toContain('main-session-')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step).')
|
||||
// Borderless: no box-drawing frame around the banner.
|
||||
expect(output).not.toContain('╭')
|
||||
expect(output).not.toContain('╮')
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('switches models, streams a response, answers a user-question dialog, and exits cleanly', async () => {
|
||||
const output = await runTuiPtySmoke({
|
||||
const output = await smoke({
|
||||
label: 'tui-agent conversation',
|
||||
tempDirPrefix: 'tui-agent-conversation-',
|
||||
binScript,
|
||||
configPath: scriptedConfigPath,
|
||||
tsconfigPath,
|
||||
actions: [
|
||||
{ waitFor: 'scripted TUI ready.', send: '/model\r' },
|
||||
{ waitFor: 'Select model', send: '\x1b[B\r' },
|
||||
{ waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.', send: 'exercise the TUI\r' },
|
||||
...SELECT_PRO_MODEL,
|
||||
{ waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.', send: '/plan exercise the TUI\r' },
|
||||
{ waitFor: 'How should the scripted run proceed?', send: '\r' },
|
||||
{ waitFor: 'Decision received. Scripted TUI run complete.', send: '/exit\r' },
|
||||
{ waitFor: 'Decision received. Scripted TUI run complete.', send: '' },
|
||||
// Session title: the first user message drives the first-message-llm
|
||||
// provider's tool-less title call; the scripted adapter answers it, the
|
||||
// accepted title lands in the log, and the TUI renders the terminal
|
||||
// window title as `<session title> — <configured title>` via OSC 0.
|
||||
// Gating /status on it keeps the assertion race-free; the diagnostics
|
||||
// card is then exercised through the same real Loader/PTY composition.
|
||||
{ waitFor: 'scripted session title — DeepSeek Harness', send: '/status\r' },
|
||||
{ waitFor: 'Session status', send: '/exit\r' },
|
||||
],
|
||||
})
|
||||
expect(output).toContain('I need one decision before I continue.')
|
||||
expect(output).toContain('Entering plan mode (applies from the next step).')
|
||||
expect(output).toContain(String.raw`\x1b]2;MODEL_CONTROLLED\x07`)
|
||||
expect(output).toContain(String.raw`\x1b[999CMODEL_CURSOR`)
|
||||
expect(output).toContain(String.raw`\x9b31mMODEL_C1`)
|
||||
@@ -47,16 +120,68 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
expect(output).not.toContain('\u001B[999CMODEL_CURSOR')
|
||||
expect(output).not.toContain('\u009B31mMODEL_C1')
|
||||
expect(output).toContain('Safe')
|
||||
expect(output).toContain('\u001B]0;scripted session title — DeepSeek Harness\u0007')
|
||||
expect(output).toContain('Session status')
|
||||
expect(output).toContain('Title')
|
||||
expect(output).toContain('scripted session title')
|
||||
expect(output).toContain('Model')
|
||||
expect(output).toContain('tui-scripted/tui-scripted-model-pro')
|
||||
expect(output).toContain('KV cache')
|
||||
expect(output).toContain('Context')
|
||||
expect(output).toContain('128,000')
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('loads a local skill via /skill: and delivers its body to the model as a user turn', async () => {
|
||||
// The whole manual-invocation path in one keyless boot: `ctx.get('skills')`
|
||||
// resolves in the shipped tree, the client-side `/skill:` command parses,
|
||||
// the local provider loads `scripted-skill` from the agents home, and the
|
||||
// rendered `<skill name="…">` block reaches the model — proven by the
|
||||
// scripted adapter echoing the fixture's body marker only when it arrives.
|
||||
const output = await smoke({
|
||||
label: 'tui-agent skill',
|
||||
tempDirPrefix: 'tui-agent-skill-',
|
||||
configPath: scriptedConfigPath,
|
||||
prepare: seedWorkspace({
|
||||
skills: {
|
||||
'scripted-skill/SKILL.md': [
|
||||
'---',
|
||||
'name: scripted-skill',
|
||||
'description: Keyless PTY proof that the skill command loads a local skill into the conversation.',
|
||||
'---',
|
||||
'',
|
||||
'SCRIPTED SKILL BODY MARKER',
|
||||
'',
|
||||
].join('\n'),
|
||||
},
|
||||
}),
|
||||
actions: [
|
||||
...SELECT_PRO_MODEL,
|
||||
{ waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.', send: '/skill:scripted-skill\r' },
|
||||
{ waitFor: 'Scripted skill body received.', send: '/exit\r' },
|
||||
],
|
||||
})
|
||||
expect(output).toContain('Scripted skill body received.')
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('boots the Code Mode overlay tree, renders its banner, and exits cleanly', async () => {
|
||||
// The overlay's only keyless composition proof: the include+patch tree,
|
||||
// worker code runtime, and one-tool registry all mount before the banner.
|
||||
const output = await smoke({
|
||||
label: 'tui-agent code mode',
|
||||
tempDirPrefix: 'tui-agent-code-mode-',
|
||||
configPath: codeModeConfigPath,
|
||||
actions: [{ waitFor: 'TUI Code Mode ready.', send: '/exit\r' }],
|
||||
})
|
||||
expect(output).toContain('TUI Code Mode ready.')
|
||||
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 runTuiPtySmoke({
|
||||
const output = await smoke({
|
||||
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',
|
||||
@@ -66,3 +191,98 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
|
||||
expect(output).toContain('ui-tui: session "missing-session" failed to start:')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
})
|
||||
|
||||
describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
|
||||
it('boots the shipped default config with no arguments and no personal overlay', async () => {
|
||||
const output = await smoke({
|
||||
label: 'dsh default boot',
|
||||
tempDirPrefix: 'dsh-default-boot-',
|
||||
binScript: dshBinScript,
|
||||
configArgs: [],
|
||||
actions: [{ waitFor: 'main-session-', send: '/exit\r' }],
|
||||
})
|
||||
expect(output).toContain('DEEPSEEK')
|
||||
expect(output).toContain('main-session-')
|
||||
expect(output).not.toContain('╭')
|
||||
expect(output).not.toContain('╮')
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('applies the personal overlay: config.yaml patches the tree and .env feeds its !!js', async () => {
|
||||
// The whole personal-config chain in one boot: the personal .env supplies
|
||||
// the variable, config.yaml patches the tui-agent entry with a `!!js`
|
||||
// reference to it, and the banner renders the patched welcome verbatim.
|
||||
const output = await smoke({
|
||||
label: 'dsh personal overlay',
|
||||
tempDirPrefix: 'dsh-personal-overlay-',
|
||||
binScript: dshBinScript,
|
||||
configArgs: [],
|
||||
prepare: seedWorkspace({
|
||||
personal: {
|
||||
'.env': 'DSH_PERSONAL_WELCOME=PERSONAL OVERLAY READY.\n',
|
||||
'config.yaml': [
|
||||
'- id: tui-agent',
|
||||
" name: '@deepseek-ai/dsh-tui-demo'",
|
||||
' config:',
|
||||
' provider: deepseek',
|
||||
' model: deepseek-v4-flash',
|
||||
' workspaceContext: false',
|
||||
' welcome: !!js process.env.DSH_PERSONAL_WELCOME',
|
||||
'',
|
||||
].join('\n'),
|
||||
},
|
||||
}),
|
||||
actions: [{ waitFor: 'PERSONAL OVERLAY READY.', send: '/exit\r' }],
|
||||
})
|
||||
expect(output).toContain('PERSONAL OVERLAY READY.')
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('fails loud instead of booting when the personal config.yaml is invalid', async () => {
|
||||
const output = await smoke({
|
||||
label: 'dsh invalid personal config',
|
||||
tempDirPrefix: 'dsh-invalid-personal-',
|
||||
binScript: dshBinScript,
|
||||
configArgs: [],
|
||||
prepare: seedWorkspace({ personal: { 'config.yaml': 'id: not-a-list\n' } }),
|
||||
expectedExitCode: 1,
|
||||
})
|
||||
expect(output).toContain('must be a top-level YAML array of loader patch entries')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('routes the --resume flag into the config resume intake, failing loud on a missing id', async () => {
|
||||
// The flag path end to end: apps/cli parses `--resume missing-session` and
|
||||
// sets RESUME_SESSION_ID, the shipped config's `!!js` reads it, and the
|
||||
// resume fails loud — proving the printed `dsh --resume <id>` hint reaches
|
||||
// the same intake as the env var.
|
||||
const output = await smoke({
|
||||
label: 'dsh resume flag failure',
|
||||
tempDirPrefix: 'dsh-resume-flag-',
|
||||
binScript: dshBinScript,
|
||||
configArgs: ['--resume', 'missing-session'],
|
||||
expectedExitCode: 1,
|
||||
})
|
||||
expect(output).toContain('ui-tui: session "missing-session" failed to start:')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('tells the model where its own source lives, in the system prompt it sends', async () => {
|
||||
// The launcher resolves the checkout root three hops up from apps/cli/{src,lib};
|
||||
// this test file sits an equal depth under the same root, so the same hop applies.
|
||||
const sourceRoot = fileURLToPath(new URL('../../..', import.meta.url))
|
||||
let loggedSystem = ''
|
||||
await smoke({
|
||||
label: 'dsh source-path prompt',
|
||||
tempDirPrefix: 'dsh-source-path-',
|
||||
binScript: dshBinScript,
|
||||
configArgs: [scriptedConfigPath],
|
||||
actions: [
|
||||
...SELECT_PRO_MODEL,
|
||||
{ waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.', send: 'exercise the TUI\r' },
|
||||
{ waitFor: 'How should the scripted run proceed?', send: '\r' },
|
||||
{ waitFor: 'Decision received. Scripted TUI run complete.', send: '/exit\r' },
|
||||
],
|
||||
inspect: async (cwd) => { loggedSystem = await readLoggedSystemPrompt(cwd) },
|
||||
})
|
||||
expect(loggedSystem).toContain(`Your own source code is the checkout at ${sourceRoot}; you can read it there to learn how dsh works and how to extend it.`)
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import { installLlmReplay, parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
|
||||
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
|
||||
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -45,8 +46,15 @@ interface Scenario {
|
||||
expectedTools: string[]
|
||||
expectedEventCounts?: Record<string, number>
|
||||
childSessions?: number
|
||||
enterPlanMode?: boolean
|
||||
recorded: boolean
|
||||
seedWorkspace?: boolean
|
||||
/**
|
||||
* Load the opt-in `todo_write` tool for this scenario. The shipped tui-agent
|
||||
* config omits it, so only the todo-plan scenario (the enabled-path proof)
|
||||
* mounts it; the rest cover the default, todo-free composition.
|
||||
*/
|
||||
enableTodo?: boolean
|
||||
}
|
||||
|
||||
const SCENARIOS: Scenario[] = [
|
||||
@@ -54,6 +62,8 @@ const SCENARIOS: Scenario[] = [
|
||||
name: 'multi-turn-conversation',
|
||||
composition: 'native',
|
||||
expectedTools: [],
|
||||
expectedEventCounts: { 'plan/mode': 1 },
|
||||
enterPlanMode: true,
|
||||
recorded: true,
|
||||
},
|
||||
{
|
||||
@@ -62,6 +72,7 @@ const SCENARIOS: Scenario[] = [
|
||||
expectedTools: ['todo_write'],
|
||||
expectedEventCounts: { 'todo/write': 1 },
|
||||
recorded: true,
|
||||
enableTodo: true,
|
||||
},
|
||||
{
|
||||
name: 'bash-terminal-card',
|
||||
@@ -196,7 +207,9 @@ async function mountScenarioContext(
|
||||
await ctx.plugin(FsPolicy)
|
||||
await ctx.plugin(ToolFs)
|
||||
await ctx.plugin(UserInteractionService)
|
||||
await ctx.plugin(ToolTodo)
|
||||
// todo_write is opt-in: only the todo-plan scenario mounts it, matching the shipped
|
||||
// config that omits it. The other scenarios prove the default todo-free composition.
|
||||
if (scenario.enableTodo === true) await ctx.plugin(ToolTodo)
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
|
||||
await ctx.plugin(ToolSubagent, { provider: 'spawn', toolName: 'subagent', enableRunInBackground: false })
|
||||
@@ -204,6 +217,9 @@ async function mountScenarioContext(
|
||||
await ctx.plugin(ToolWorkflow)
|
||||
await ctx.plugin(ToolRalph)
|
||||
await ctx.plugin(CommandService)
|
||||
if (scenario.enterPlanMode === true) {
|
||||
await ctx.plugin(PlanModeService, { section: 'Snapshot plan mode instructions.' })
|
||||
}
|
||||
if (scenario.composition === 'code' || scenario.composition === 'advanced') {
|
||||
await ctx.plugin(WorkerCodeRuntime, {})
|
||||
}
|
||||
@@ -268,7 +284,17 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
})
|
||||
await settleTerminal(terminal)
|
||||
|
||||
for (const prompt of prompts) {
|
||||
let remainingPrompts = prompts
|
||||
if (scenario.enterPlanMode === true) {
|
||||
const firstPrompt = prompts[0]!
|
||||
terminal.send(`/plan ${firstPrompt}`)
|
||||
terminal.send('\r')
|
||||
await agent.whenIdle()
|
||||
await settleTerminal(terminal)
|
||||
remainingPrompts = prompts.slice(1)
|
||||
}
|
||||
|
||||
for (const prompt of remainingPrompts) {
|
||||
terminal.send(prompt)
|
||||
terminal.send('\r')
|
||||
await agent.whenIdle()
|
||||
@@ -280,6 +306,18 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
|
||||
for (const [type, count] of Object.entries(scenario.expectedEventCounts ?? {})) {
|
||||
expect(events.filter(event => event.type === type), `${scenario.name} must emit ${type}`).toHaveLength(count)
|
||||
}
|
||||
if (scenario.enterPlanMode === true) {
|
||||
expect(ctx.planMode.get(agent)).toEqual({ active: true })
|
||||
const planMode = events.find(event => event.type === 'plan/mode')
|
||||
const firstHeader = events.find(event => event.type === 'request/header')
|
||||
if (planMode === undefined || firstHeader === undefined) {
|
||||
throw new Error('plan-mode command snapshot needs plan/mode before its first request/header')
|
||||
}
|
||||
expect(planMode.seq).toBeLessThan(firstHeader.seq)
|
||||
expect(firstHeader.data.header.system).toContain('Snapshot plan mode instructions.')
|
||||
const firstMessage = events.find(event => event.type === 'user/message')
|
||||
expect(firstMessage?.data.content).toEqual([{ type: 'text', text: prompts[0] }])
|
||||
}
|
||||
expect(events.filter(event => event.type === 'tool/result').every(event => !event.data.isError)).toBe(true)
|
||||
expect(events.filter(event => event.type === 'turn/end').every(event => event.data.reason.kind !== 'error')).toBe(true)
|
||||
if (scenario.name === 'dynamic-workflow' || scenario.name === 'cordis-dynamic-toolchain') {
|
||||
|
||||
Reference in New Issue
Block a user