Merge branch 'worktree-config-settings-seam' into worktree-llm-dynamic-config

# Conflicts:
#	docs/capability-seams.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/module-graph.md
#	examples/tui-agent/cordis.yml
#	packages/README.i18n.yaml
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.zh.md
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm-pi-ai/README.zh.md
#	packages/settings/settings-local/src/index.ts
#	packages/util/README.i18n.yaml
#	packages/util/README.md
#	packages/util/README.zh.md
#	scripts/doc-budgets.manifest.json
This commit is contained in:
Yichen Jiang
2026-07-30 14:33:36 +08:00
1355 changed files with 58514 additions and 9928 deletions

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write examples/README.md
README.md: 7f12178d1b67f1ebfac6f4f0e31403c54106e98f
README.zh.md: 72ab92602d0a53cabdbfa8bc34838061df25d1c7
README.zh.md: c7c1bf76593661616464558e554d57340d7c03b1

View File

@@ -2,11 +2,11 @@
[English](README.md) | 中文
展示 harness 如何接线的可运行演示(不是 workspace。每个示例都是一个 **轻量叶节点**一份选择可替换后端、加载一个应用包package并可添加可选产品工具的 `cordis.yml`。组合和启动粘合代码位于 [`@deepseek-ai/dsh-tui-demo`](../packages/examples/tui-demo)、[`@deepseek-ai/dsh-cli-demo`](../packages/examples/cli-demo)、[`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo) 及它们共享的 [`@deepseek-ai/dsh-agent-spine-demo`](../packages/examples/agent-spine-demo) 组合包中。没有 `start.ts`;终端 `demo:*` 脚本通过 [`dsh`](../apps/cli/README.md) CLI命令行界面启动该 CLI 挂载 `tui-demo` 组合包无头ACPAgent Client Protocol脚本则调用 `cli-demo`/`acp-demo` bin。
展示 harness 如何组装的可运行演示(不是 workspace。每个示例都是一个 **轻量叶节点**一份选择可替换后端、加载一个应用包package并可添加可选产品工具的 `cordis.yml`。组合和启动粘合代码位于 [`@deepseek-ai/dsh-tui-demo`](../packages/examples/tui-demo)、[`@deepseek-ai/dsh-cli-demo`](../packages/examples/cli-demo)、[`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo) 及它们共享的 [`@deepseek-ai/dsh-agent-spine-demo`](../packages/examples/agent-spine-demo) 组合包中。没有 `start.ts`;终端 `demo:*` 脚本通过 [`dsh`](../apps/cli/README.md) CLI命令行界面启动该 CLI 挂载 `tui-demo` 组合包无头ACPAgent Client Protocol脚本则调用 `cli-demo`/`acp-demo` bin。
## headless-agent
非交互式 agent智能体演示接受一个位置任务`@deepseek-ai/dsh-cli-demo` 应用上运行一个完整模型/工具轮次,持久化新会话,打印 `text``json``stream-json`,然后退出。
非交互式 agent智能体演示接受一个位置参数形式的任务,在 `@deepseek-ai/dsh-cli-demo` 应用上运行一个完整模型/工具轮次,持久化新会话,打印 `text``json``stream-json`,然后退出。
运行:`pnpm run demo:headless "task"`(需要 `DEEPSEEK_API_KEY`)。输出契约、安全边界和快照套件详见 [headless-agent/README.md](headless-agent/README.md)。
@@ -18,18 +18,18 @@
## jsonrpc-agent
通过 Python SDK 驱动的无人值守编码 agentJSON-RPC stdio、仅前台 `bash``read`/`write`/`edit`、一个前台 `subagent``todo_write`、JSONL 持久化和压缩。它不包含终端 UI、stdout 日志、批准、skill 和后台任务控制。详见 [jsonrpc-agent/README.md](jsonrpc-agent/README.md)。
通过 Python SDK 驱动的无人值守编码 agentJSON-RPC stdio、仅前台 `bash``read`/`write`/`edit`、一个前台 `subagent``todo_write`、JSONL 持久化和压缩。它不包含终端 UI、stdout 日志、批准、skill(技能)和后台任务控制。详见 [jsonrpc-agent/README.md](jsonrpc-agent/README.md)。
## cordis-agent
**自指** 演示:编码主干加 [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis),其三个工具(`cordis_inspect`/`cordis_mount`/`cordis_unmount`)使 agent 可以检查当前 DSH 进程、挂载模型编写的临时 Plugin(事件监听器、一个全新工具,或一个供另一临时 Plugin 注入的服务),并再次卸载它们。这些 Plugin 只存在于内存中,共享一个内部 `cordis-dynamic` fiber 子树;`ctx.fs`/`ctx.web` 仅作为它们可用的能力提供方。
**自指** 演示:编码主干加 [`@deepseek-ai/dsh-tool-cordis`](../packages/cordis/tool-cordis),其三个工具(`cordis_inspect`/`cordis_mount`/`cordis_unmount`)使 agent 可以检查当前 DSH 进程、挂载模型编写的临时插件(事件监听器、一个全新工具,或一个供另一临时插件注入的服务),并再次卸载它们。这些插件只存在于内存中,共享一个内部 `cordis-dynamic` fiber 子树;`ctx.fs`/`ctx.web` 仅作为它们可用的能力提供方。
使用 `pnpm run demo:cordis` 运行 TUI使用 `pnpm run demo:cordis web``http://127.0.0.1:3081` 启动浏览器 UI或使用 `pnpm run demo:cordis acp` 启动 ACP 服务器(三者均需 `DEEPSEEK_API_KEY`)。分阶段演示脚本详见 [cordis-agent/README.md](cordis-agent/README.md),设计与沙箱注意事项详见[工具集 Agent Note](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
使用 `pnpm run demo:cordis` 运行 TUI使用 `pnpm run demo:cordis web``http://127.0.0.1:3081` 启动浏览器 UI或使用 `pnpm run demo:cordis acp` 启动 ACP 服务器(三者均需 `DEEPSEEK_API_KEY`)。分阶段演示脚本详见 [cordis-agent/README.md](cordis-agent/README.md),设计与沙箱注意事项详见[工具集 Agent Noteagent 决策记录)](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
## acp-agent
作为 **Agent Client Protocol (ACP)** 自动化服务器通过 JSON-RPC stdio 公开的 agent由 [`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo) 提供。程序化客户端可以创建新会话、发送文本提示词、消费已提交的 assistant 文本、回答一次性权限请求并取消工作。它拥有 ACP 无密钥快照套件。
一个通过 JSON-RPC stdio 公开、作为 **Agent Client Protocol (ACP)** 自动化服务器运行的 agent由 [`@deepseek-ai/dsh-acp-demo`](../packages/examples/acp-demo) 提供。程序化客户端可以创建新会话、发送文本提示词、消费已提交的 assistant 文本、回答一次性权限请求并取消工作。它拥有 ACP 无密钥快照套件。
运行:`pnpm run demo:acp`(需要 `DEEPSEEK_API_KEY``pnpm run demo:code-mode acp` 通过 `code-mode.cordis.yml` 覆盖以 Code Mode 启动同一服务器。协议与快照测试契约详见 [acp-agent/README.md](acp-agent/README.md)。
默认 `cordis.yml` 组合 [`@deepseek-ai/dsh-sandbox-local`](../packages/sandbox/sandbox-local)、[`@deepseek-ai/dsh-bash-sandbox`](../packages/bash/bash-sandbox) 和 [`@deepseek-ai/dsh-user-approval`](../packages/ui/user-approval)。`workspace-write` 将 bash 和文件系统变更限制在每个会话 workspace 中;范围更广的重试会通过 ACP 成为一次性机器权限请求。
默认 `cordis.yml` 组合 [`@deepseek-ai/dsh-sandbox-local`](../packages/sandbox/sandbox-local)、[`@deepseek-ai/dsh-bash-sandbox`](../packages/bash/bash-sandbox) 和 [`@deepseek-ai/dsh-user-approval`](../packages/ui/user-approval)。`workspace-write` 将 bash 和文件系统变更限制在每个会话 workspace 中;请求更广泛沙箱权限的重试会通过 ACP 触发一次性机器权限请求。

View File

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

View File

@@ -2,29 +2,29 @@
[English](README.md) | 中文
通过 JSON-RPC stdio 提供的自动化导向 [Agent Client Protocol](https://agentclientprotocol.com) 服务器。它面向 agent智能体、subagent 提供方和其他程序化客户端,而非产品 UI。
通过 JSON-RPC stdio 提供的面向自动化 [Agent Client ProtocolACP](https://agentclientprotocol.com) 服务器。它面向 parent agent智能体、subagent 提供方和其他程序化客户端,而非产品 UI。
```sh
pnpm run demo:acp # needs DEEPSEEK_API_KEY (repo-root .env or env)
pnpm run demo:code-mode acp # same protocol with the Code Mode tool transport
```
该叶节点加载 ACP 应用、DeepSeek 适配器、受沙箱限制的 bash 与文件系统栈、一次性批准策略、压缩compaction、subagent、工作流、钩子、派生会话查询索引和重复守卫。应用为每次 `session/new` 创建一个新 agent将会话持久化到 JSONL并保持 stdout 只含协议内容。[`session-query.cordis.yml`](session-query.cordis.yml) 为其专用快照显式选用 workspace 授权的查询工具和通用超时/溢出策略;[`fs.cordis.yml`](fs.cordis.yml) 为文件系统场景添加溢出存储,[`code-mode.cordis.yml`](code-mode.cordis.yml) 添加 `run_code` 及其生成的 TypeScript SDK[`web.cordis.yml`](web.cordis.yml) 则为 web-fetch 快照添加 web seam、本地抓取提供方、`web_fetch` 与一个回环 HTML fixture 服务器。
该叶节点加载 ACP 应用、DeepSeek 适配器、受沙箱限制的 bash 与文件系统栈、一次性批准策略、压缩compaction、subagent、工作流、钩子、派生会话查询索引和重复守卫。应用为每次 `session/new` 创建一个新 agent将会话持久化到 JSONL并保持 stdout 只含协议内容。[`session-query.cordis.yml`](session-query.cordis.yml) 为其专用快照显式选用 workspace 授权的查询工具和通用超时/溢出策略;[`fs.cordis.yml`](fs.cordis.yml) 为文件系统场景添加溢出存储,[`code-mode.cordis.yml`](code-mode.cordis.yml) 添加 `run_code` 及其生成的 TypeScript SDK[`web.cordis.yml`](web.cordis.yml) 则为 web-fetch 快照添加 web seam、本地抓取提供方、`web_fetch` 与一个回环 HTML fixture(测试前置数据)服务器。
## 协议通道
Stdout 只携带以换行分隔的 ACP JSON-RPC。`@deepseek-ai/dsh-acp-demo` 不安装 stdout logger叶节点的附加项必须使用 stderr 输出诊断信息。
Stdout 只携带以换行分隔的 ACP JSON-RPC。`@deepseek-ai/dsh-acp-demo` 不安装 stdout logger叶节点新增的组件必须使用 stderr 输出诊断信息。
自动化契约(支持的方法、基线提示词内容、已提交文本输出,以及有意缺少的 UI 界面)位于 [`@deepseek-ai/dsh-acp`](../../packages/acp/acp/README.md)。
## 会话 workspace 与权限
每次 `session/new` 都提供一个绝对 `cwd`。受沙箱限制的 bash 文件系统变更会根据该会话 cwd 解析 `workspace-write`,因此并发会话可以使用不同的项目根目录;平台临时根目录仍是共享可写暂存空间(参见[沙箱契约](../../packages/sandbox/sandbox/README.md))。`DSH_PERMISSION_MODE` 在部署和测试中选择 `workspace-write``danger-full-access`
每次 `session/new` 都提供一个绝对 `cwd`。受沙箱限制的 bash 文件系统修改会以该会话 cwd 为基准应用 `workspace-write`,因此并发会话可以使用不同的项目根目录;平台临时根目录仍是共享可写暂存空间(参见[沙箱契约](../../packages/sandbox/sandbox/README.md))。`DSH_PERMISSION_MODE` 在部署和测试中选择 `workspace-write``danger-full-access`
`workspace-write` 下,模型请求扩大沙箱权限的重试会触发 `session/request_permission`,选项为 `allow_once``reject_once`。客户端以程序方式决策;解除对话框或答案不可用时会失败闭合。选定结果仅适用于该次重试,并通过常规工具结果/审计路径记录。服务器绝不公开权限选择器,也不持久化客户端策略。
`workspace-write` 下,如果模型重试请求更广泛的沙箱访问权限,就会触发 `session/request_permission`,选项为 `allow_once``reject_once`。客户端以程序方式决策;客户端放弃选择或无法给出答复时,系统会按拒绝处理。选定结果仅适用于该次重试,并通过常规工具结果/审计路径记录。服务器绝不公开权限选择器,也不持久化客户端策略。
## 快照测试
此示例拥有 ACP 快照套件。它会启动真实自动化服务器,通过 `dsh-llm-replay` 回放已提交的模型流,并比较规范化后的协议输出与重新持久化的会话日志。录制使用真实模型;刷新会复用已提交的回放输入。覆盖场景包括抛出/挂起行为,可选 `workspace/` fixture(测试前置数据)则为外部状态检查预置环境
此示例拥有 ACP 快照套件。它会启动真实自动化服务器,通过 `dsh-llm-replay` 回放已提交的模型流,并比较规范化后的协议输出与重新持久化的会话日志。录制使用真实模型;刷新会复用已提交的回放输入。覆盖配置涵盖抛错/挂起行为,可选 `workspace/` fixture 则为环境状态检查预置状态
大多数场景锁定后端行为,而非 ACP 专用行为;[仅面向自动化的 ACP 决策](../../.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md#snapshot-boundary)说明了为何该覆盖仍与传输层耦合。

View File

@@ -1,5 +1,6 @@
import { fileURLToPath } from 'node:url'
import { readFileSync } from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { homedir } from 'node:os'
import { expect, it } from 'vitest'
@@ -45,6 +46,15 @@ const WEB_CONFIG = fileURLToPath(new URL('../web.cordis.yml', import.meta.url))
const SNAPSHOTS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'snapshots')
const PACKED_CHUNKS_SOURCE = 'hook-cc-pretool-deny'
async function prepareDelimiterPathWorkspace(cwd: string): Promise<void> {
const dir = join(cwd, 'scope</system-reminder>')
await mkdir(dir, { recursive: true })
await Promise.all([
writeFile(join(dir, 'AGENTS.md'), 'Delimiter path snapshot instruction.\n'),
writeFile(join(dir, 'task.txt'), 'delimiter path snapshot task\n'),
])
}
// FIXME: Migrate backend-oriented scenarios to the headless stream-json suite;
// this ACP suite should eventually retain only automation-protocol contracts.
@@ -160,11 +170,13 @@ const SCENARIOS: Scenario[] = [
{ name: 'repeat-tool-guard', hasModelTurn: true, recorded: false },
// Authored replay: a root AGENTS.md pins the session prefix, then a read in
// nested/ discovers its narrower AGENTS.md as a raw, metadata-bearing
// injected user/message. Both AGENTS.md fixtures are symlinks to a sibling
// injected user/message. Both portable AGENTS.md fixtures are symlinks to a sibling
// AGENTS.canonical.md, so this scenario also guards that discovery follows a
// symlinked instruction file to its target's content. The scenario-specific
// config keeps home/root discovery hermetic, and the resulting prefix needs
// its own pinned header class.
// symlinked instruction file to its target's content. A second nested path
// containing a literal closing tag is created at runtime: Git cannot check
// that name out on Windows, so this delimiter-injection case is POSIX-only.
// The scenario-specific config keeps home/root discovery hermetic, and the
// resulting prefix needs its own pinned header class.
{
name: 'workspace-context',
hasModelTurn: true,
@@ -174,6 +186,8 @@ const SCENARIOS: Scenario[] = [
headerClass: 'workspace-context',
toolSchemasSource: 'text-turn',
configPath: WORKSPACE_CONTEXT_CONFIG,
prepareWorkspace: prepareDelimiterPathWorkspace,
posixOnly: true,
},
{ name: 'cancel', hasModelTurn: true, recorded: false, overridden: true },
// Cancelling a live bash call relies on POSIX process-group termination;
@@ -212,10 +226,16 @@ const SCENARIOS: Scenario[] = [
headerClass: 'advanced',
configPath: ADVANCED_CONFIG,
},
// Prompt-submit blocks are authored keylessly. Admission rejects before a
// turn opens, so only the ACP stop reason is observable and no log is harvested.
// Prompt-submit blocks are authored keylessly with malformed matcher fields,
// which these matcherless events must ignore. Admission rejects before a turn
// opens, so only the ACP stop reason is observable and no log is harvested.
{ name: 'hook-cc-promptsubmit-block', hasModelTurn: false, recorded: false },
{ name: 'hook-codex-promptsubmit-block', hasModelTurn: false, recorded: false },
// Each invalid matcher follows a runnable prompt blocker. Reaching the replay
// model without any hook audit rows proves config loading is atomic through
// the real Loader/app path, rather than retaining the earlier valid group.
{ name: 'hook-cc-invalid-matcher', hasModelTurn: true, recorded: false },
{ name: 'hook-codex-invalid-matcher', hasModelTurn: true, recorded: false },
// The mid-turn seams fire during a real model turn, so each is recorded with its hook active
// (the model's reaction to a deny/block/force-continue is part of the captured transcript).
// SessionStart/SubagentStart are excluded because detached injection races log

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
{
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Reply with exactly the word: PONG. Do not use any tools." }
]
}

View File

@@ -0,0 +1,18 @@
{"type":"session","version":0,"id":"539aa64c-7f37-40ff-abd8-ed45b717be1b","createdAt":1783600629539,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":1783600629541,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1783600629541,"data":{"content":[{"type":"text","text":"Reply with exactly the word: PONG. Do not use any tools."}],"source":{"kind":"user"},"role":"user","id":"5a36df87-da8e-480d-8e0f-61cd2b93bbb8"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1783600629541,"data":{"title":"Reply with exactly the word:","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":1783600629542,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1783600629542,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":1783600630819,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":6,"time0":1783600630820,"data":{"turn":1,"step":1,"index":0,"dt":[2,30,0,0,0,33,1,40,0,0,0,0,0,18,0,36,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","P","ONG","\""," and"," not"," use"," any"," tools","."]}}
{"type":"assistant/chunk","seq":26,"time":1783600630980,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"assistant/chunk","seq":27,"time":1783600630980,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":1,"text":"P"}}}
{"type":"assistant/chunk","seq":28,"time":1783600631006,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":1,"text":"ONG"}}}
{"type":"assistant/chunk","seq":29,"time":1783600631008,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."}}}}
{"type":"assistant/chunk","seq":30,"time":1783600631009,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"PONG"}}}}
{"type":"assistant/chunk","seq":31,"time":1783600631009,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}}}}
{"type":"assistant/chunk","seq":32,"time":1783600631009,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":33,"time":1783600631011,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"7452a358-8038-4583-9ceb-66564f665bfb"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"surfaceOp":"append"}
{"type":"step/end","seq":34,"time":1783600631011,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":35,"time":1783600631011,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1,4 @@
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"PONG"}}}}
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}

View File

@@ -0,0 +1,19 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "echo 'must not run' >&2; exit 2" }
]
}
],
"PreToolUse": [
{
"matcher": "[",
"hooks": [
{ "type": "command", "command": "exit 2" }
]
}
]
}
}

View File

@@ -2,6 +2,7 @@
"hooks": {
"UserPromptSubmit": [
{
"matcher": "[",
"hooks": [
{ "type": "command", "command": "echo 'blocked by policy hook' >&2; exit 2" }
]

View File

@@ -0,0 +1,7 @@
{
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Reply with exactly the word: PONG. Do not use any tools." }
]
}

View File

@@ -0,0 +1,18 @@
{"type":"session","version":0,"id":"539aa64c-7f37-40ff-abd8-ed45b717be1b","createdAt":1783600629539,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":1783600629541,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1783600629541,"data":{"content":[{"type":"text","text":"Reply with exactly the word: PONG. Do not use any tools."}],"source":{"kind":"user"},"role":"user","id":"56715824-b0da-4a73-8d6c-0caa590995e6"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1783600629541,"data":{"title":"Reply with exactly the word:","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"step/start","seq":3,"time":1783600629542,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":4,"time":1783600629542,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":5,"time":1783600630819,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}
{"type":"reasoning-chunks","seq0":6,"time0":1783600630820,"data":{"turn":1,"step":1,"index":0,"dt":[2,30,0,0,0,33,1,40,0,0,0,0,0,18,0,36,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","P","ONG","\""," and"," not"," use"," any"," tools","."]}}
{"type":"assistant/chunk","seq":26,"time":1783600630980,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"text"}}}
{"type":"assistant/chunk","seq":27,"time":1783600630980,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":1,"text":"P"}}}
{"type":"assistant/chunk","seq":28,"time":1783600631006,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":1,"text":"ONG"}}}
{"type":"assistant/chunk","seq":29,"time":1783600631008,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."}}}}
{"type":"assistant/chunk","seq":30,"time":1783600631009,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"PONG"}}}}
{"type":"assistant/chunk","seq":31,"time":1783600631009,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}}}}
{"type":"assistant/chunk","seq":32,"time":1783600631009,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":33,"time":1783600631011,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"2d9d88d1-b684-491f-9d5f-73721b7fd5ed"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"surfaceOp":"append"}
{"type":"step/end","seq":34,"time":1783600631011,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":35,"time":1783600631011,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1,4 @@
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"PONG"}}}}
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}

View File

@@ -0,0 +1,19 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "echo 'must not run' >&2; exit 2" }
]
}
],
"PreToolUse": [
{
"matcher": "[",
"hooks": [
{ "type": "command", "command": "exit 2" }
]
}
]
}
}

View File

@@ -2,6 +2,7 @@
"hooks": {
"UserPromptSubmit": [
{
"matcher": "[",
"hooks": [
{ "type": "command", "command": "echo 'blocked by codex policy hook' >&2; exit 2" }
]

View File

@@ -2,7 +2,7 @@
{"type":"turn/start","seq":0,"time":1783654655602,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1783654655603,"data":{"content":[{"type":"text","text":"Load the snapshot-skill skill with the skill tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"9c670f1c-3508-4b98-9cae-21f363652d6e"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1783654655603,"data":{"title":"Load the snapshot-skill skill with","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"user/message","seq":3,"time":1784903324926,"data":{"content":[{"type":"text","text":"<system-reminder>\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n<available_skills>\n- `snapshot-skill`: Exercise project skill discovery and loading in snapshot tests.\n</available_skills>\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\n</system-reminder>"}],"source":{"kind":"plugin","plugin":"dsh-tool-skill"},"role":"user","id":"4f537803-7424-41eb-887f-f39676b89187"},"surfaceOp":"append"}
{"type":"user/message","seq":3,"time":1784903324926,"data":{"content":[{"type":"text","text":"<system-reminder>\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n<available_skills>\n- `model-only-skill`: Prove user-disabled skills remain available to the model.\n- `snapshot-skill`: Exercise project skill discovery and loading in snapshot tests.\n</available_skills>\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\n</system-reminder>"}],"source":{"kind":"plugin","plugin":"dsh-tool-skill"},"role":"user","id":"4f537803-7424-41eb-887f-f39676b89187"},"surfaceOp":"append"}
{"type":"step/start","seq":4,"time":1784903324927,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":5,"time":1784903324928,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":6,"time":1783654655609,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}}

View File

@@ -0,0 +1,7 @@
---
name: model-only-skill
description: Prove user-disabled skills remain available to the model.
user-invocable: false
---
Follow these model-only snapshot instructions.

View File

@@ -0,0 +1,7 @@
---
name: user-only-skill
description: Prove model-disabled skills stay outside the model catalog.
disable-model-invocation: true
---
Follow these user-only snapshot instructions.

View File

@@ -2,6 +2,6 @@
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Read nested/task.txt with the read tool, then reply DONE." }
{ "op": "prompt", "text": "Read nested/task.txt, then read scope</system-reminder>/task.txt with the read tool, then reply DONE." }
]
}

View File

@@ -9,6 +9,16 @@
{ "type": "finish", "reason": { "kind": "tool-calls" } }
]
},
{
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
{ "type": "tool-call-delta", "index": 0, "id": "call_workspace_delimiter_read", "name": "read", "argumentsDelta": "{\"file_path\":\"scope</system-reminder>/task.txt\"}" },
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_workspace_delimiter_read", "name": "read", "arguments": "{\"file_path\":\"scope</system-reminder>/task.txt\"}" } },
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
{ "type": "finish", "reason": { "kind": "tool-calls" } }
]
},
{
"kind": "chunks",
"chunks": [

View File

@@ -1,8 +1,8 @@
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":1783778297065,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":1783778297066,"data":{"content":[{"type":"text","text":"Read nested/task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"7bee8c9d-684e-42e2-a906-54479a4360c0"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1783778297066,"data":{"title":"Read nested/task.txt with the read","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"user/message","seq":3,"time":1784903339799,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nRoot snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2e18766c26603608f321508caae00ea8f4434d59"}]},"role":"user","id":"b3f5afcf-3483-4f42-95db-cca54076be3d"},"surfaceOp":"append"}
{"type":"user/message","seq":1,"time":1783778297066,"data":{"content":[{"type":"text","text":"Read nested/task.txt, then read scope</system-reminder>/task.txt with the read tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"b1792d71-b916-463d-9ef0-b349e37d914d"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":1783778297066,"data":{"title":"Read nested/task.txt, then read scope</s","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"user/message","seq":3,"time":1784903339799,"data":{"content":[{"type":"text","text":"<system-reminder>\nThe following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nRoot snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","baseline":true,"changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"2e18766c26603608f321508caae00ea8f4434d59"}]},"role":"user","id":"ba197665-164f-48dc-b408-afa76e228ed6"},"surfaceOp":"append"}
{"type":"step/start","seq":4,"time":1784903339799,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":5,"time":1784903339800,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"assistant/chunk","seq":6,"time":1783778297070,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -10,17 +10,28 @@
{"type":"assistant/chunk","seq":8,"time":1783778297070,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}}}}
{"type":"assistant/chunk","seq":9,"time":1783778297070,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":10,"time":1784903339801,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":11,"time":1784903339801,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"11b21c20-5425-41ad-8fa0-d8b89cc40f87"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[6,7,8,9,10],"surfaceOp":"append"}
{"type":"assistant/message","seq":11,"time":1784903339801,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"fdc0fbd1-b483-49ff-861d-1c0332d13596"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[6,7,8,9,10],"surfaceOp":"append"}
{"type":"tool/call","seq":12,"time":1784903339802,"data":{"turn":1,"step":1,"callId":"call_workspace_read","name":"read","arguments":"{\"file_path\":\"nested/task.txt\"}"}}
{"type":"tool/result","seq":13,"time":1784903339813,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_workspace_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_read","content":[{"type":"text","text":"<path>{{cwd}}/nested/task.txt</path>\n<type>file</type>\n<content>\n1: snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"7cbf28e2-a9f0-4cca-874c-2987a3507e24"}},"sourceEventSeqs":[12],"surfaceOp":"append"}
{"type":"user/message","seq":14,"time":1784903339813,"data":{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nNested snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","changes":[{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"c446df9a85c7e73a3055f394a4822a19ac9ead5a"}]},"role":"user","id":"73cb82c7-85c5-4d87-bb6c-cad10b7ef6de"},"surfaceOp":"append"}
{"type":"tool/result","seq":13,"time":1784903339813,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_workspace_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_read","content":[{"type":"text","text":"<path>{{cwd}}/nested/task.txt</path>\n<type>file</type>\n<content>\n1: snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"9027e8f1-572e-45f2-9c92-c78227adc42a"}},"sourceEventSeqs":[12],"surfaceOp":"append"}
{"type":"user/message","seq":14,"time":1784903339813,"data":{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: nested/AGENTS.md\n\nThese instructions apply to work under `nested`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nNested snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","changes":[{"action":"set","scope":"nested\u0000AGENTS.md","path":"nested/AGENTS.md","digest":"c446df9a85c7e73a3055f394a4822a19ac9ead5a"}]},"role":"user","id":"939dbe9f-7df8-48af-b36c-3b546fd5d95e"},"surfaceOp":"append"}
{"type":"step/end","seq":15,"time":1784903339813,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":16,"time":1784903339820,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":17,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":18,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"DONE"}}}
{"type":"assistant/chunk","seq":19,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
{"type":"assistant/chunk","seq":20,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
{"type":"assistant/chunk","seq":21,"time":1784903339821,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":22,"time":1784903339821,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"52b0df24-16f6-4b82-b351-0c4af707da21"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[17,18,19,20,21],"surfaceOp":"append"}
{"type":"step/end","seq":23,"time":1784903339821,"data":{"turn":1,"step":2}}
{"type":"turn/end","seq":24,"time":1784903339822,"data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"assistant/chunk","seq":17,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":18,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"call_workspace_delimiter_read","name":"read","argumentsDelta":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}}
{"type":"assistant/chunk","seq":19,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}}}
{"type":"assistant/chunk","seq":20,"time":1783778297073,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":21,"time":1784903339821,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":22,"time":1784903339821,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"a9d0e5a8-e1ae-4b09-933d-882400f5f13a"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[17,18,19,20,21],"surfaceOp":"append"}
{"type":"tool/call","seq":23,"time":1785233046380,"data":{"turn":1,"step":2,"callId":"call_workspace_delimiter_read","name":"read","arguments":"{\"file_path\":\"scope</system-reminder>/task.txt\"}"}}
{"type":"tool/result","seq":24,"time":1785233046389,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_workspace_delimiter_read"},"content":[{"type":"tool-result","toolCallId":"call_workspace_delimiter_read","content":[{"type":"text","text":"<path>{{cwd}}/scope</system-reminder>/task.txt</path>\n<type>file</type>\n<content>\n1: delimiter path snapshot task\n\n(End of file - total 1 lines)\n</content>"}],"isError":false}],"role":"user","id":"31c9f547-39d5-4fd8-903a-2b4625fb3b8e"}},"sourceEventSeqs":[23],"surfaceOp":"append"}
{"type":"user/message","seq":25,"time":1785233046389,"data":{"content":[{"type":"text","text":"<system-reminder>\nAdditional instructions from: scope<\\/system-reminder>/AGENTS.md\n\nThese instructions apply to work under `scope<\\/system-reminder>`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.\n\nDelimiter path snapshot instruction.\n\n</system-reminder>"}],"source":{"kind":"workspace-instructions","changes":[{"action":"set","scope":"scope</system-reminder>\u0000AGENTS.md","path":"scope</system-reminder>/AGENTS.md","digest":"38803cd13e2dff9105ba5fbbc703fe27e989e26e"}]},"role":"user","id":"149d4be0-a33b-4478-be5a-8d1e4f9ec7cc"},"surfaceOp":"append"}
{"type":"step/end","seq":26,"time":1785233046389,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":27,"time":1785233046397,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":28,"time":1785233046398,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":29,"time":1785233046398,"data":{"turn":1,"step":3,"chunk":{"type":"text-delta","index":0,"text":"DONE"}}}
{"type":"assistant/chunk","seq":30,"time":1785233046398,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
{"type":"assistant/chunk","seq":31,"time":1785233046398,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
{"type":"assistant/chunk","seq":32,"time":1785233046398,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":33,"time":1785233046398,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"c5718cf9-802e-47e9-8e64-3353598ea5ee"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[28,29,30,31,32],"surfaceOp":"append"}
{"type":"step/end","seq":34,"time":1785233046398,"data":{"turn":1,"step":3}}
{"type":"turn/end","seq":35,"time":1785233046398,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write examples/cordis-agent/README.md
README.md: 55970e932bc16d8361932daa9ea55af83ef73d33
README.zh.md: c2873b6de96a8b47ad8ea4fb2cf03a7501406300
README.zh.md: a8ec332d8b3673d6656663eb3bfd7d37a4e328f6

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
自指 harness 演示:在全屏 TUI 上运行 DeepSeek V4 编码主干,并加载 [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md)。后者让模型检查当前 DSH 进程、挂载仅存于内存的临时 Plugin并再次卸载它们。临时 Plugin 可跨 turn 保持活跃,但会在卸载、工具集卸载或 DSH 重启后消失;它们不创建文件或配置,也可能影响同一进程中的其他 session`ctx.fs``ctx.web` 是这些 Plugin 可用的 provider-only 能力。设计详见[工具集 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
自指 harness 演示:在全屏 TUI 上运行 DeepSeek V4 编码主干,并加载 [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md)。后者让模型检查当前 DSH 进程、挂载仅存于内存的临时插件,并卸载它们。临时插件可跨轮次保持活跃,但会在卸载、工具集卸载或 DSH 重启后消失;它们不创建文件或配置,也可能影响同一进程中的其他会话`ctx.fs``ctx.web` 仅以能力提供方形式加载,供这些插件使用。设计详见[工具集 Agent Noteagent 决策记录)](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。
## 运行
@@ -15,7 +15,7 @@ pnpm run demo:cordis web # browser UI at http://127.0.0.1:3081
pnpm run demo:cordis acp # ACP server
```
预期演示分阶段进行:先验证监听器链,再让 agent 扩展自身:
预期演示分阶段进行:先验证监听器链,再让 agent(智能体)扩展自身:
```
> Mount a temporary Plugin that listens to the 'agent/status' event and logs every status change, then run `echo hi` with bash.
@@ -30,8 +30,8 @@ pnpm run demo:cordis acp # ACP server
[tool call] cordis_unmount({"id": "dyn-1"})
```
请求 `cordis_inspect` 并使用 `what: "api"``what: "events"`,即可查看编写 Plugin 代码所用的生成服务/事件资料。还可挂载两个协作临时 Plugin(一个中调用 `ctx.provide`,另一个中使用 `inject`),观察 Cordis 如何暂停并恢复消费方。
请求 `cordis_inspect` 并使用 `what: "api"``what: "events"`,即可查看编写插件代码所用的生成服务/事件资料。还可挂载两个协作临时插件(一个中调用 `ctx.provide`,另一个中使用 `inject`),观察 Cordis 如何暂停并恢复消费方。
## 端到端测试
`tests/keyless-smoke.e2e.ts` 使用虚拟密钥通过 Loader 启动真实 `cordis.yml`,并断言横幅、包名解析 EOF 后干净退出。`tests/cordis-tools.e2e.ts` 是带密钥的冒烟测试:真实模型挂载一个临时状态 listener,测试验证其带标记的 console 行;然后创建并使用 `reverse_text` 工具,再通过 provide/inject 组合两个临时 Plugin。[`packages/cordis/tool-cordis`](../../packages/cordis/tool-cordis) 在每文件 100% 覆盖率门禁下承载单元覆盖
`tests/keyless-smoke.e2e.ts` 使用虚拟密钥通过 Loader 启动真实 `cordis.yml`,并断言横幅、包名解析,以及收到 EOF 后正常退出。`tests/cordis-tools.e2e.ts` 是带密钥的冒烟测试:真实模型挂载一个临时状态监听器,测试验证其带标记的控制台输出行;然后创建并使用 `reverse_text` 工具,再通过 provide/inject 组合两个临时插件。[`packages/cordis/tool-cordis`](../../packages/cordis/tool-cordis) 包含相关单元测试,并受逐文件 100% 覆盖率门禁约束

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
# pnpm run verify-translation-pairing --write examples/headless-agent/README.md
README.md: 445804a2611e5e8093eadf345ad10a2a7984c012
README.zh.md: 68ec718afe0b2aca276be2689cbae74167ee1c7b
README.zh.md: 956bc82e77f79c3f05e4b51297fd5365e6e89be1

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
无头单次 agent智能体接线DeepSeek V4 + 本地 bash 与文件系统工具 + subagent 委托 + 工作流与新 agent Ralph 迭代 + `todo_write` + JSONL 持久化,并以 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo) 作为应用入口。
无头单次 agent智能体接线DeepSeek V4 + 本地 bash 与文件系统工具 + subagent 委托 + 工作流与新 agent Ralph 迭代 + `todo_write` + JSONL 持久化,并以 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo) 作为应用入口。
## 运行
@@ -15,12 +15,12 @@ pnpm run demo:headless --output-format json -- "summarize the implementation"
pnpm run demo:headless --output-format stream-json -- "run the focused tests"
```
必须提供且只能提供一个非空位置任务;含空格的任务需要加引号。没有 `-p` 标志。`text` 打印最后一条包含文本的 assistant 消息,`json` 打印一条 DSH 原生结果记录,`stream-json` 则在该记录之前发出顶层会话的规范任务轮次事件。子会话只通过父工具事件和结果对外显示。
必须提供一个且仅一个非空的任务位置参数;含空格的任务需要加引号。没有 `-p` 标志。`text` 打印最后一条包含文本的 assistant 消息,`json` 打印一条 DSH 原生结果记录,`stream-json` 则在该记录之前发出顶层会话的规范任务轮次事件。子会话只通过父会话的工具事件和结果对外显示。
每次调用都会创建并持久化新会话,在一个轮次中运行所有模型和工具步骤,然后刷新、释放并退出。这是非交互式自动化:没有提示符、批准、恢复、第二轮次或 stdin 上下文。已配置工具可以修改启动 workspace、运行命令、spawn 子 agent并消耗提供方 token。
每次调用都会创建并持久化新会话,在一个轮次中运行所有模型和工具步骤,然后刷写持久化数据、执行 dispose资源释放退出。这是非交互式自动化:没有提示符、批准、恢复、第二轮次或 stdin 上下文。已配置工具可以修改启动时所在的工作区、运行命令、spawn 子 agent并消耗提供方 token。
## 高级与快照接线
[`advanced.cordis.yml`](advanced.cordis.yml) 在已交付叶节点上添加 Code Mode 和 Cordis 工具。[`advanced.cordis.snapshot.yml`](advanced.cordis.snapshot.yml) 只将实时 LLM大语言模型替换为回放。[`tests/`](tests/) 下的测试拥有无密钥真实 Loader 冒烟测试、密钥门控的外部状态验证冒烟测试,以及带父子会话 fixture测试前置数据`stream-json` 回放快照。
[`advanced.cordis.yml`](advanced.cordis.yml) 在已交付叶节点上添加 Code Mode 和 Cordis 工具。[`advanced.cordis.snapshot.yml`](advanced.cordis.snapshot.yml) 只将实时 LLM大语言模型替换为回放。[`tests/`](tests/) 下涵盖无密钥真实 Loader 冒烟测试、密钥门控的外部状态验证冒烟测试,以及带父子会话 fixture测试前置数据`stream-json` 回放快照。
包级 [CLI 契约](../../packages/examples/cli-demo/README.md)记录输出记录、退出状态、取消、持久化以及模型token 影响。
这份包package级 [CLI命令行界面契约](../../packages/examples/cli-demo/README.md) 说明输出记录、退出状态、取消、持久化以及模型token 影响。

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
# pnpm run verify-translation-pairing --write examples/jsonrpc-agent/README.md
README.md: 6ee4e9d824315bde76b7a534679f018df9a6d3e8
README.zh.md: dc9b6233e7074e7a9b13bf10bcd2f310b0ad7bf3
README.zh.md: 43290fc3659750724679a370be5b33ffdca2a5cb

View File

@@ -2,16 +2,16 @@
[English](README.md) | 中文
面向 Python SDK 内置 JSON-RPC 运行时的无人值守编码 agent智能体组合。它有意不加载终端 UI、console logger、批准界面或用户交互工具,因为 stdout 属于 SDK 协议,轮次由 SDK 驱动。
面向 Python SDK 内置 JSON-RPC 运行时的无人值守编码 agent智能体组合。它有意不加载终端 UI、控制台日志记录器、批准界面或用户交互工具,因为 stdout 属于 SDK 协议,轮次由 SDK 驱动。
面向模型的工具为:
- `bash`,仅前台
- `read``write``edit`
- `subagent`,使用一个前台进程内 spawn 提供方
- `subagent`,使用一个进程内以前台方式运行的 spawn 提供方
- `todo_write`
周边运行时还加载 JSONL 会话持久化和自动上下文压缩compaction`maxTokensAsSuccess` 将受 token 上限限制的模型轮次保留为已接受的评估结果,同时保留其 `max-tokens` 原因。
周边运行时还加载 JSONL 会话持久化和自动上下文压缩(context compaction`maxTokensAsSuccess` 将受 token 上限限制的模型轮次保留为已接受的评估结果,同时保留其 `max-tokens` 原因。
## 运行时环境
@@ -24,4 +24,4 @@
| `DSH_SESSION_ROOT` | JSONL 轨迹目录 |
| `DSH_SYSTEM_PROMPT` | 由部署提供的编码人格 |
通过 Python SDK 的 `cordis` 选项或 `DSH_CORDIS_CONFIG` 传入配置路径。内置可执行文件已携带此文件命名的每个插件;目标机器无需 Node.js。
通过 Python SDK 的 `cordis` 选项或 `DSH_CORDIS_CONFIG` 传入配置路径。内置可执行文件已携带此文件中指定的每个插件;目标机器无需 Node.js。

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write examples/tui-agent/README.md
README.md: ea8695d37ea247a38644392a4572c1ea9855fd44
README.zh.md: b3f6dc18536b159379eac7433367ccf2cd8fcc53
README.zh.md: c6acd39d8713816d870c00fa8597754d0d09880a

View File

@@ -2,7 +2,7 @@
[English](README.md) | 中文
全屏交互式编码 agent智能体DeepSeek V4、本地 bash 与文件系统工具、压缩compaction、subagent、工作流与新 agent Ralph 迭代、plan mode`/plan` 进入,`exit_plan_mode` 评审退出)、超时/溢出策略,以及通过 [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo) 提供的 JSONL 持久化;该应用从 `cordis.yml` 加载。同级 [`headless-agent`](../headless-agent/README.md) 以适合单次管道的任务形式运行同一能力类,[`acp-agent`](../acp-agent/README.md) 则通过 JSON-RPC 提供该能力。
全屏交互式编码 agent智能体DeepSeek V4、本地 bash 与文件系统工具、压缩compaction、subagent、工作流与新 agent Ralph 迭代、plan mode`/plan` 进入,`exit_plan_mode` 评审退出)、超时/溢出策略,以及通过 [`@deepseek-ai/dsh-tui-demo`](../../packages/examples/tui-demo) 提供的 JSONL 持久化;该应用从 `cordis.yml` 加载。同级 [`headless-agent`](../headless-agent/README.md) 以适合管道调用的单次任务形式运行同一能力类,[`acp-agent`](../acp-agent/README.md) 则通过 JSON-RPC 提供该能力。
## 运行
@@ -13,13 +13,13 @@
pnpm run demo:tui
```
演示脚本和可安装的 `dsh` CLI[`apps/cli`](../../apps/cli/README.md))都会作为已交付的默认配置启动此示例的 `cordis.yml``dsh` 还会应用 `~/.dsh` 中的个人覆盖,并将调用目录作为 workspace
演示脚本和可安装的 `dsh` CLI命令行界面,见 [`apps/cli`](../../apps/cli/README.md))都会此示例的 `cordis.yml` 作为已交付的默认配置启动`dsh` 还会应用 `~/.dsh` 中的个人覆盖,并将调用目录作为工作区
输入一项编码任务。agent 使用 `read`/`write`/`edit` 文件系统工具处理常规文件操作,使用 `bash`(加上面向后台任务的通用 `task_output`/`task_list`/`task_kill`)执行 shell 命令、搜索和测试。每次操作都在新的 `bash -c` 中运行(系统提示词要求模型传递 `workdir`,而不是使用 `cd`)。fs 工具和 bash 都会根据会话 workspace 解析相对路径。agent 还可以通过 `subagent`/`subagent_fork` 委托。
输入一项编码任务。agent 使用 `read`/`write`/`edit` 文件系统工具处理常规文件操作,使用 `bash`(加上面向后台任务的通用 `task_output`/`task_list`/`task_kill`)执行 shell 命令、搜索和测试。每次 bash 调用都在新的 `bash -c` 中运行(系统提示词要求模型传递 `workdir`,而不是使用 `cd`)。文件系统工具和 bash 都会相对于会话工作区解析相对路径。agent 还可以通过 `subagent`/`subagent_fork` 委托。
`todo_write` 任务跟踪器是选用的,不在已交付配置中:请将 `@deepseek-ai/dsh-tool-todo` 添加到 `cordis.yml`(或在 `~/.dsh` 下使用个人配置覆盖以公开该工具。加载后模型会把整表计划记录到会话日志TUI 则渲染它。
TUI 渲染 Markdown 历史、推理、工具有的终端diff通用卡片、token 总量,以及加载 `todo_write` 时的最新计划。较长的工具正文保留首尾预览Ctrl+O 展开或折叠所有卡片。Enter 用于提交,或在 agent 运行时进行 steering中途引导Ctrl+R 切换推理Escape 取消,`/help` 列出命令。`/plan` 为下一步骤选择 plan mode`/plan <message>` 还会将消息提交到该步骤,`/plan off` 则在没有模型输入的情况下选择默认 mode。`/status` 会展开当前会话的标识、活动计数、精确 token缓存 bucket、上下文用量和时间戳而不中断正在运行的轮次。`/model` 打开当前提供方目录的键盘选择器;使用 Up/Down 聚焦模型,使用 Shift+Tab 循环切换为该模型公布的推理强度,再用 Enter 选择;也可以使用 `/model <model>``/model <provider>/<model>` 直接选择。`ask_user_question` 会打开一个位于左下方的宽键盘面板,包含批次进度和编号选项。
TUI 渲染 Markdown 历史、推理reasoning、工具有的终端diff通用卡片、token 总量,以及加载 `todo_write` 时的最新计划。较长的工具正文保留首尾预览Ctrl+O 展开或折叠所有卡片。Enter 用于提交,或在 agent 运行时进行 steering中途引导Ctrl+R 切换推理Escape 取消,`/help` 列出命令。`/plan` 为下一步骤选择 plan mode`/plan <message>` 还会将消息提交到该步骤,`/plan off` 则在没有模型输入的情况下选择默认 mode。`/status` 会展开当前会话的标识、活动计数、精确 token缓存 bucket、上下文用量和时间戳而不中断正在运行的轮次。`/model` 打开当前提供方目录的键盘选择器;使用 Up/Down 聚焦模型,使用 Shift+Tab 循环切换为该模型公布的推理强度,再用 Enter 选择;也可以使用 `/model <model>``/model <provider>/<model>` 直接选择。`ask_user_question` 会打开一个位于左下方的宽键盘面板,包含批次进度和编号选项。
### 恢复早先的会话
@@ -29,11 +29,11 @@ TUI 渲染 Markdown 历史、推理、工具所有的终端diff通用卡
dsh --resume <prior-session-id>
```
`/resume` 打开可搜索键盘选择器,显示标题、活动、上一轮结果、模型路由、持久 goal 阶段和实时/已持久化状态。已安装的 `dsh` 宿主会刷新并释放当前应用,然后以 `dsh --resume <id>` 替换进程。TUI 仍会在退出时打印该命令,并在自定义宿主无法移交时显示它。`dsh --resume <id>` 在启动上下文中提供 id`cordis.yml` 会读取它(`resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"`没有标志时agent 会开始新会话。缺失或无法读取的 id 不会启动 agent而会发出 `agent-loop/config-start-failed`TUI 打印失败并以非零状态退出。选择器没有跨进程会话锁,因此拥有并发宿主的部署必须自行协调会话所有权。
`/resume` 打开可搜索键盘选择器,显示标题、活动、上一轮结果、模型路由、持久化目标阶段和实时/已持久化状态。已安装的 `dsh` 宿主会等待刷写完成,对当前应用执行 dispose资源释放,然后以 `dsh --resume <id>` 替换进程。TUI 仍会在退出时打印该命令,并在自定义宿主无法移交时显示它。`dsh --resume <id>` 在启动上下文中提供 id`cordis.yml` 会读取它(`resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"`没有标志时agent 会开始新会话。缺失或无法读取的 id 不会启动 agent而会发出 `agent-loop/config-start-failed`TUI 打印失败并以非零状态退出。选择器没有跨进程会话锁,因此拥有并发宿主的部署必须自行协调会话所有权。
## Code Mode
[`code-mode.cordis.yml`](code-mode.cordis.yml) 在同一树上覆盖 worker 线程运行时和 `tools: { mode: code }`。模型会收到一个 `run_code` 传输工具,加上一份为可见工具生成的 TypeScript SDK只有程序输出会返回模型上下文。使用 `mode: both` 可在 `run_code` 旁同时公开原生调用。执行契约详见 [Code Mode Agent Note](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)。
[`code-mode.cordis.yml`](code-mode.cordis.yml) 在同一树上覆盖 worker 线程运行时和 `tools: { mode: code }`。模型会收到一个 `run_code` 传输工具,加上一份为可见工具生成的 TypeScript SDK只有程序输出会返回模型上下文。使用 `mode: both` 可在 `run_code` 旁同时公开原生调用。执行契约详见 [Code Mode Agent Noteagent 决策记录)](../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)。
```sh
pnpm run demo:code-mode # this overlay under the TUI (default UI)
@@ -54,27 +54,27 @@ pnpm run demo:code-mode acp # the acp-agent example's same-shaped overlay
|---|---|
| `hmr` (`@cordisjs/plugin-hmr`) | 开发/演示的编辑-重载循环:它是 **叶节点** 配置项(不内置到应用),因为它依赖 Loader 的内部模块访问 |
| `llm-deepseek` | 默认原生适配器 |
| `bash` (`dsh-bash-local`) | 执行器实现bash seam 可替换一半。面向模型的 `bash` schema`tool-bash`)和通用 `task_*` 控制(`tool-tasks`)由 `dsh-agent-spine-demo` 提供,因此叶节点只选择执行器 |
| `bash` (`dsh-bash-local`) | 执行器实现bash seam 可替换的实现侧。面向模型的 `bash` schema`tool-bash`)和通用 `task_*` 控制(`tool-tasks`)由 `dsh-agent-spine-demo` 提供,因此叶节点只选择执行器 |
| `tui-agent` (`@deepseek-ai/dsh-tui-demo`) | 应用组合包agent-spine 演示 + JSONL 持久化 + pi-tui 通道 + 预创建的 `main` agent |
| `subagent`, `subagent-spawn`, `subagent-fork` | subagent 提供方注册表加两个进程内后端:新子 agent以及用父 agent 已完成轮次前缀播种的子 agent |
| `tool-subagent`, `tool-subagent-fork` | 两次面向模型的 `dsh-tool-subagent` 加载,每次绑定不同提供方,并以不同工具名(`subagent``subagent_fork`)公开 |
| `workflow-workerthread`, `tool-workflow` | worker 线程工作流引擎及其面向模型的 `workflow` 工具,子调用通过 spawn 后端路由 |
| `plan-mode` | 插件拥有的 `/plan [message]` 进入命令和 `/plan off` 退出命令、plan-mode 提示词策略、工具限制,以及经评审的 `exit_plan_mode` 转换 |
| `fs-local`, `fs-policy`, `tool-fs` | 文件系统栈:本地 `ctx.fs` 提供方、先读后写/编辑策略门禁(位于 `fs/*` 事件门禁),以及面向模型的 `read`/`write`/`edit` 工具。相对路径根据会话 workspace 解析 |
| `fs-local`, `fs-policy`, `tool-fs` | 文件系统栈:本地 `ctx.fs` 提供方、先读后写/编辑策略门禁(位于 `fs/*` 事件门禁),以及面向模型的 `read`/`write`/`edit` 工具。相对路径相对于会话工作区解析 |
## 端到端测试(`pnpm run test:e2e`
与 UI 无关的带密钥套件通过 `tests/harness.ts` 以程序方式组装完整栈(无 PTY、无 Loader
- `tests/full-loop.e2e.ts`canary 测试:真实模型通过真实 bash 工具运行 `echo e2e-ok`;断言 `tool/call`/`tool/result` 会话事件和最终答案。
- `tests/coding-task.e2e.ts`:类 swebench 冒烟测试:临时目录包含 `add.js`(其中 `a - b` 写在本应是 `a + b` 的位置)和失败的 `add.test.js`agent 必须修复错误并验证。测试会自行重新运行 `node add.test.js` 并检查文件,不信任 agent 的声称
- `tests/resume.e2e.ts`:跨进程持久连续性:第一次运行告诉真实模型一个密码并将轮次持久化到临时 JSONL 根目录,然后释放整个上下文;第二次运行在同一根目录上创建新上下文,恢复会话 id 并要求模型回忆密码。只有重新水化的日志能够提供该回忆。
- `tests/compaction.e2e.ts`:压缩冒烟测试:一项真实多步 bash 任务在故意设得很小的上下文窗口中运行,使自动压缩监听器在会话中途触发。测试验证外部状态:真实日志中出现 `compact/start…end` 对,表层缩减(替换节点遮蔽旧节点),且 agent 在压缩后仍给出正确最终答案。
- `tests/todo-write.e2e.ts`:加载选用 `todo_write` 工具,由真实模型驱动,测试验证产生的 `todo/write` 会话事件。
- `tests/code-mode.e2e.ts`:带密钥 Code Mode 证明:使用真实模型和双工具任务,断言线上工具列表精确为 `[run_code]``tool/code-dispatch` 事件位于父调用下,且筛选后的答案已返回。
- `tests/coding-task.e2e.ts`:类 swebench 冒烟测试:临时目录包含 `add.js`(其中 `a - b` 写在本应是 `a + b` 的位置)和失败的 `add.test.js`agent 必须修复错误并验证。测试会自行重新运行 `node add.test.js` 并检查文件,不信任 agent 的说法
- `tests/resume.e2e.ts`:跨进程持久连续性:第一次运行告诉真实模型一个密码并将轮次持久化到临时 JSONL 根目录,然后 dispose 整个上下文;第二次运行在同一根目录上创建新上下文,恢复会话 id 并要求模型回忆密码。只有重新水化的日志能够提供该回忆。
- `tests/compaction.e2e.ts`:压缩冒烟测试:一项真实多步 bash 任务在故意设得很小的上下文窗口中运行,使自动压缩监听器在会话中途触发。测试验证外部状态:真实日志中出现 `compact/start…end` 对,模型可见内容缩减(一个替换节点遮蔽了较旧节点),且 agent 在压缩后仍给出正确最终答案。
- `tests/todo-write.e2e.ts`:加载选用 `todo_write` 工具,由真实模型驱动,测试验证产生的 `todo/write` 会话事件。
- `tests/code-mode.e2e.ts`:带密钥 Code Mode 证明:使用真实模型和双工具任务,断言协议层工具列表精确为 `[run_code]``tool/code-dispatch` 事件位于父调用下,且筛选后的答案已返回。
这些测试在没有 `DEEPSEEK_API_KEY` 时自行跳过。无密钥 `tests/tui-keyless-smoke.e2e.ts` 通过 PTY 启动真实 Loader 树(唯一获准的 PTY 界面):基础启动 + `/plan` + `/exit`,一次带问题对话框和工具往返的脚本 LLM 对话Code Mode 覆盖欢迎行,以及恢复失败退出路径。
这些测试在没有 `DEEPSEEK_API_KEY` 时自行跳过。无密钥 `tests/tui-keyless-smoke.e2e.ts` 通过 PTY 启动真实 Loader 树(唯一获准的 PTY 界面):基础启动 + `/plan` + `/exit`,一次带问题对话框和工具往返的脚本 LLM(大语言模型)对话Code Mode 覆盖配置的欢迎行,以及恢复失败退出路径。
## 快照测试
`tests/snapshots/<scenario>/session.jsonl` 提供已录制的用户提示词和模型分片;同级子日志驱动 subagent 和工作流。无密钥套件通过真实循环和工具实现执行这些脚本,然后比较可读的预期终端单元格/样式输出。使用 `pnpm run test:snapshot:refresh` 刷新仅展示变更;已录制模型程改变时,使用 DeepSeek 密钥运行 `pnpm run test:snapshot:record`。已实现的 [TUI 快照 Agent Note](../../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md) 拥有场景矩阵,以及已录制旅程、瞬时包快照与 PTY 覆盖之间的分工。
`tests/snapshots/<scenario>/session.jsonl` 提供已录制的用户提示词和模型分片;同级子日志驱动 subagent 和工作流。无密钥套件通过真实循环和工具实现执行这些脚本,然后比较可读的预期终端单元格/样式输出。对于仅涉及展示的变更,使用 `pnpm run test:snapshot:refresh`;已录制模型程改变时,使用 DeepSeek 密钥运行 `pnpm run test:snapshot:record`。已实现的 [TUI 快照 Agent Note](../../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md) 规定了场景矩阵,以及已录制旅程、包级瞬态快照与 PTY 覆盖之间的分工。

View File

@@ -1,6 +1,7 @@
import { mkdirSync, writeFileSync } from 'node:fs'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { dirname, join } from 'node:path'
import { execa } from 'execa'
import { resolveExampleLaunch, type ExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
@@ -36,7 +37,16 @@ while time.monotonic() < deadline:
if chunk:
output.extend(chunk)
while action_index < len(actions) and actions[action_index]["waitFor"].encode() in output:
os.write(fd, actions[action_index]["send"].encode())
action = actions[action_index]
if "writeFile" in action:
target = os.path.join(cwd, action["writeFile"]["path"])
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, "w", encoding="utf-8") as handle:
handle.write(action["writeFile"]["content"])
if "send" in action:
os.write(fd, action["send"].encode())
else:
os.write(fd, action["send"].encode())
action_index += 1
waited, candidate = os.waitpid(pid, os.WNOHANG)
if waited == pid:
@@ -56,11 +66,14 @@ if actual_exit != int(expected_exit):
sys.exit(125)
`
/** One terminal action sent after its marker has rendered. */
interface TuiPtyAction {
readonly waitFor: string
readonly send: string
}
/** One terminal input or workspace mutation performed after its marker renders. */
type TuiPtyAction =
| { readonly waitFor: string; readonly send: string }
| {
readonly waitFor: string
readonly writeFile: { readonly path: string; readonly content: string }
readonly send?: string
}
/** Inputs for a keyless real-Loader TUI process smoke. */
export interface TuiPtySmokeOptions {
@@ -157,7 +170,16 @@ async function runWindowsPtySmoke(
terminal.onData((chunk) => {
output += chunk
while (actionIndex < actions.length && output.includes(actions[actionIndex]!.waitFor)) {
terminal.write(actions[actionIndex]!.send)
const action = actions[actionIndex]!
if ('writeFile' in action) {
const target = join(cwd, action.writeFile.path)
mkdirSync(dirname(target), { recursive: true })
writeFileSync(target, action.writeFile.content)
const input = action.send
if (input !== undefined) terminal.write(input)
} else {
terminal.write(action.send)
}
actionIndex += 1
}
})

View File

@@ -4,29 +4,30 @@ title "Use the bash tool to — DSH TUI snapshot"
cursor hidden column=7 viewportRow=24 bufferRow=24
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Use the bash tool to"
style 1-20 fg=bright-black
style 1-20 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Use the bash tool to run exactly: echo TERMINAL_OK. Then reply with the single word DONE and stop. "
6| <blank>
7| "Assistant "
style 0-8 fg=bright-magenta bold underline
8| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
9| "The user wants me to run a simple bash command and then reply with \"DONE\". "
style 0-73 fg=bright-black italic
style 0-73 dim italic
10| <blank>
11| "● Tool / bash / Echo TERMINAL_OK to verify terminal access"
style 0-57 fg=green
12| "$ echo TERMINAL_OK "
style 0-17 fg=cyan
style 0-17 dim
13| "TERMINAL_OK "
style 0-10 dim
14| "[exit 0] "
style 0-7 dim
15| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
@@ -35,20 +36,20 @@ buffer
17| "Assistant "
style 0-8 fg=bright-magenta bold underline
18| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
19| "The command ran successfully and output \"TERMINAL_OK\". I should now reply with just \"DONE\". "
style 0-90 fg=bright-black italic
style 0-90 dim italic
20| "DONE "
21| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
22| <blank>
23| "/workspace/project deepseek-v4-flash ↑3.0k ↓115 cache 48% 3% contex"
style 0-46 fg=bright-blue bold
style 49-65 fg=bright-black
style 68-88 fg=bright-black
style 91-99 fg=bright-black
style 0-46 fg=bright-magenta bold
style 49-65 dim
style 68-88 dim
style 91-99 dim
24| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
25-35| <blank>

View File

@@ -4,15 +4,15 @@ title "Using ONE run_code program: call — DSH TUI snapshot"
cursor hidden column=7 viewportRow=26 bufferRow=26
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Using ONE run_code program: call"
style 1-32 fg=bright-black
style 1-32 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Using ONE run_code program: call the bash tool exactly once with the command seq 1 200 | awk "
style 77-99 fg=cyan
6| "'{printf \"line %04d: the quick brown fox jumps over the lazy dog\\n\", $1}', then return ONLY the "
@@ -22,36 +22,38 @@ buffer
9| "Assistant "
style 0-8 fg=bright-magenta bold underline
10| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
11| "The user wants me to write a single run_code program that calls bash exactly once with a specific "
style 0-99 fg=bright-black italic
style 0-99 dim italic
12| "command, then returns only the number of lines in its output. "
style 0-60 fg=bright-black italic
style 0-60 dim italic
13| <blank>
14| "● Tool / run_code"
style 0-16 fg=green
15| "Count lines in seq/awk output "
style 0-99 dim
16| "200 "
style 0-99 dim
17| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
18| <blank>
19| "Assistant "
style 0-8 fg=bright-magenta bold underline
20| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
21| "The result is 200 lines. The user wants me to reply with just that number and stop. "
style 0-82 fg=bright-black italic
style 0-82 dim italic
22| "200 "
23| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
24| <blank>
25| "/workspace/project deepseek-v4-flash ↑123 ↓208 cache 99% 3% c"
style 0-52 fg=bright-blue bold
style 55-71 fg=bright-black
style 74-93 fg=bright-black
style 96-99 fg=bright-black
style 0-52 fg=bright-magenta bold
style 55-71 dim
style 74-93 dim
style 96-99 dim
26| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
27-35| <blank>

View File

@@ -4,15 +4,15 @@ title "Using ONE run_code program: call — DSH TUI snapshot"
cursor hidden column=7 viewportRow=35 bufferRow=61
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Using ONE run_code program: call"
style 1-32 fg=bright-black
style 1-32 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Using ONE run_code program: call the bash tool twice — exactly echo CODE_ONE then exactly echo "
style 63-75 fg=cyan
style 90-99 fg=cyan
@@ -24,37 +24,37 @@ buffer
9| "Assistant "
style 0-8 fg=bright-magenta bold underline
10| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
11| "The user wants me to write a single run_code program that: "
style 0-35 fg=bright-black italic
style 0-35 dim italic
style 36-43 fg=cyan
style 44-57 fg=bright-black italic
style 44-57 dim italic
12| "1. Calls bash tool twice - first with echo CODE_ONE, then with echo CODE_TWO "
style 0-2 fg=bright-blue
style 3-8 fg=bright-black italic
style 0-2 fg=bright-magenta
style 3-8 dim italic
style 9-12 fg=cyan
style 13-37 fg=bright-black italic
style 13-37 dim italic
style 38-50 fg=cyan
style 51-62 fg=bright-black italic
style 51-62 dim italic
style 63-75 fg=cyan
13| "2. console.log exactly captured output "
style 0-2 fg=bright-blue
style 0-2 fg=bright-magenta
style 3-13 fg=cyan
style 14-22 fg=bright-black italic
style 14-22 dim italic
style 23-37 fg=cyan
14| "3. Returns the two outputs joined with a plus sign "
style 0-2 fg=bright-blue
style 3-49 fg=bright-black italic
style 0-2 fg=bright-magenta
style 3-49 dim italic
15| " "
16| "Let me think about the structure. The bash tool returns an object with stdout/stderr. I need to "
style 0-37 fg=bright-black italic
style 0-37 dim italic
style 38-41 fg=cyan
style 42-99 fg=bright-black italic
style 42-99 dim italic
17| "extract the stdout text from each call. "
style 0-38 fg=bright-black italic
style 0-38 dim italic
18| " "
19| "Looking at the bash output type: "
style 0-31 fg=bright-black italic
style 0-31 dim italic
20| " "
21| " "
22| " { "
@@ -90,51 +90,54 @@ buffer
37| " "
38| " "
39| "So I need to access .stdout.text from each result. "
style 0-19 fg=bright-black italic
style 0-19 dim italic
style 20-31 fg=cyan
style 32-49 fg=bright-black italic
style 32-49 dim italic
40| " "
41| "Let me write the program. "
style 0-24 fg=bright-black italic
style 0-24 dim italic
42| <blank>
43| "● Tool / run_code"
style 0-16 fg=green
44| "Call bash twice, log, join outputs "
style 0-99 dim
45| "captured output "
style 0-99 dim
46| "CODE_ONE+CODE_TWO "
style 0-99 dim
47| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
48| <blank>
49| "Assistant "
style 0-8 fg=bright-magenta bold underline
50| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
51| "The program ran successfully. The output shows: "
style 0-46 fg=bright-black italic
style 0-46 dim italic
52| "- captured output (from console.log) "
style 0-1 fg=bright-blue
style 0-1 fg=bright-magenta
style 2-16 fg=cyan
style 17-35 fg=bright-black italic
style 17-35 dim italic
53| "- CODE_ONE+CODE_TWO (the returned joined string) "
style 0-1 fg=bright-blue
style 0-1 fg=bright-magenta
style 2-18 fg=cyan
style 19-47 fg=bright-black italic
style 19-47 dim italic
54| " "
55| "The user asked me to reply with that joined string only and stop. So I'll reply with just "
style 0-99 fg=bright-black italic
style 0-99 dim italic
56| "CODE_ONE+CODE_TWO. "
style 0-16 fg=cyan
style 17-17 fg=bright-black italic
style 17-17 dim italic
57| "CODE_ONE+CODE_TWO "
58| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
59| <blank>
60| "/workspace/project deepseek-v4-flash ↑182 ↓446 cache 98% 4% context"
style 0-37 fg=bright-blue bold
style 40-56 fg=bright-black
style 59-78 fg=bright-black
style 81-90 fg=bright-black
style 0-37 fg=bright-magenta bold
style 40-56 dim
style 59-78 dim
style 81-90 dim
61| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse

View File

@@ -4,15 +4,15 @@ title "Run this advanced flow exactly — DSH TUI snapshot"
cursor hidden column=7 viewportRow=35 bufferRow=58
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Run this advanced flow exactly"
style 1-30 fg=bright-black
style 1-30 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use "
6| "run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a "
7| "direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply "
@@ -24,8 +24,11 @@ buffer
12| "● Tool / cordis_mount"
style 0-20 fg=green
13| "Mount temporary Cordis Plugin "
style 0-99 dim
14| "Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH "
style 0-99 dim
15| "restarts). "
style 0-99 dim
16| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
17| <blank>
@@ -35,13 +38,16 @@ buffer
20| "● Tool / run_code"
style 0-16 fg=green
21| "Verify the temporary marker Plugin "
style 0-99 dim
22| " "
23| "Temporary Plugins "
style 0-16 fg=bright-blue bold
style 0-16 fg=bright-magenta bold dim
24| " "
25| "- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: "
style 0-1 fg=bright-blue
style 0-1 fg=bright-magenta dim
style 2-99 dim
26| " until unmounted or DSH restarts "
style 0-99 dim
27| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
28| <blank>
@@ -51,6 +57,7 @@ buffer
31| "● Tool / subagent"
style 0-16 fg=green
32| "DIRECT_CHILD_OK "
style 0-99 dim
33| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
34| <blank>
@@ -60,11 +67,17 @@ buffer
37| "● Tool / workflow"
style 0-16 fg=green
38| "workflow: advanced-acp-snapshot "
style 0-99 dim
39| "workflow \"advanced-acp-snapshot\" completed (1 agent). "
style 0-99 dim
40| "Return value: "
style 0-99 dim
41| "{ "
style 0-99 dim
42| " \"reply\": \"WORKFLOW_CHILD_OK\" "
style 0-99 dim
43| "} "
style 0-99 dim
44| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
45| <blank>
@@ -74,7 +87,9 @@ buffer
48| "● Tool / cordis_unmount"
style 0-22 fg=green
49| "Unmount temporary Cordis Plugin dyn-1 "
style 0-99 dim
50| "Temporary Plugin dyn-1 was unmounted and removed. "
style 0-99 dim
51| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
52| <blank>
@@ -85,11 +100,11 @@ buffer
style 0-46 dim
56| <blank>
57| "/workspace/project deepseek-v4-flash ↑18 ↓18 cache 0% 8% cont"
style 0-52 fg=bright-blue bold
style 55-71 fg=bright-black
style 74-90 fg=bright-black
style 93-99 fg=bright-black
style 0-52 fg=bright-magenta bold
style 55-71 dim
style 74-90 dim
style 93-99 dim
58| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse

View File

@@ -4,15 +4,15 @@ title "Use the workflow tool exactly — DSH TUI snapshot"
cursor hidden column=7 viewportRow=35 bufferRow=42
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Use the workflow tool exactly"
style 1-29 fg=bright-black
style 1-29 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Use the workflow tool exactly once, with args omitted, meta set to { \"name\": \"snapshot-flow\", "
6| "\"description\": \"one child for the snapshot\" }, and this EXACT script body (copy it verbatim): "
7| "phase('Run') "
@@ -24,57 +24,63 @@ buffer
13| "Assistant "
style 0-8 fg=bright-magenta bold underline
14| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
15| "The user wants me to use the workflow tool exactly once with specific parameters. Let me carefully "
style 0-99 fg=bright-black italic
style 0-99 dim italic
16| "follow the instructions: "
style 0-23 fg=bright-black italic
style 0-23 dim italic
17| " "
18| "1. args omitted (so I don't include it) "
style 0-2 fg=bright-blue
style 3-38 fg=bright-black italic
style 0-2 fg=bright-magenta
style 3-38 dim italic
19| "2. meta = { \"name\": \"snapshot-flow\", \"description\": \"one child for the snapshot\" } "
style 0-2 fg=bright-blue
style 3-81 fg=bright-black italic
style 0-2 fg=bright-magenta
style 3-81 dim italic
20| "3. script = as given verbatim "
style 0-2 fg=bright-blue
style 3-28 fg=bright-black italic
style 0-2 fg=bright-magenta
style 3-28 dim italic
21| "4. After it returns, reply with \"WORKFLOW_DONE\" "
style 0-2 fg=bright-blue
style 3-46 fg=bright-black italic
style 0-2 fg=bright-magenta
style 3-46 dim italic
22| " "
23| "Let me do exactly that. "
style 0-22 fg=bright-black italic
style 0-22 dim italic
24| <blank>
25| "● Tool / workflow"
style 0-16 fg=green
26| "workflow: snapshot-flow "
style 0-99 dim
27| "workflow \"snapshot-flow\" completed (1 agent). "
style 0-99 dim
28| "Return value: "
style 0-99 dim
29| "{ "
style 0-99 dim
30| " \"reply\": \"WF_CHILD_OK\" "
style 0-99 dim
31| "} "
style 0-99 dim
32| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
33| <blank>
34| "Assistant "
style 0-8 fg=bright-magenta bold underline
35| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
36| "The workflow returned successfully with the reply \"WF_CHILD_OK\". Now I need to reply with exactly "
style 0-99 fg=bright-black italic
style 0-99 dim italic
37| "\"WORKFLOW_DONE\" and stop. "
style 0-24 fg=bright-black italic
style 0-24 dim italic
38| "WORKFLOW_DONE "
39| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
40| <blank>
41| "/workspace/project deepseek-v4-flash ↑3.5k ↓227 cache 47% 3% context"
style 0-44 fg=bright-blue bold
style 47-63 fg=bright-black
style 66-86 fg=bright-black
style 89-98 fg=bright-black
style 0-44 fg=bright-magenta bold
style 47-63 dim
style 66-86 dim
style 89-98 dim
42| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse

View File

@@ -4,59 +4,59 @@ title "Reply with exactly the word: — DSH TUI snapshot"
cursor hidden column=7 viewportRow=30 bufferRow=30
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Reply with exactly the word:"
style 1-28 fg=bright-black
style 1-28 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Reply with exactly the word: ONE. No tools. "
6| <blank>
7| "Plan mode on. Use /plan off to leave. "
style 0-36 fg=bright-black
style 0-36 dim
8| <blank>
9| "Assistant "
style 0-8 fg=bright-magenta bold underline
10| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
11| "The user wants me to reply with exactly the word \"ONE\" and use no tools. "
style 0-71 fg=bright-black italic
style 0-71 dim italic
12| "ONE "
13| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
14| <blank>
15| "Context · plan-mode "
15| "Context · plan-mode"
style 0-18 dim
16| "The user switched this session back to the default mode. "
style 0-55 fg=bright-black
style 0-55 dim
17| <blank>
18| "Plan mode off. "
style 0-13 fg=bright-black
style 0-13 dim
19| <blank>
20| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
21| "Reply with exactly the word: TWO. No tools. "
22| <blank>
23| "Assistant "
style 0-8 fg=bright-magenta bold underline
24| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
25| "The user wants me to reply with exactly the word \"TWO\" and no tools. "
style 0-67 fg=bright-black italic
style 0-67 dim italic
26| "TWO "
27| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
28| <blank>
29| "/workspace/project deepseek-v4-flash ↑2.9k ↓41 cache 49% 3% co"
style 0-51 fg=bright-blue bold
style 54-70 fg=bright-black
style 73-92 fg=bright-black
style 95-99 fg=bright-black
style 0-51 fg=bright-magenta bold
style 54-70 dim
style 73-92 dim
style 95-99 dim
30| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
31-35| <blank>

View File

@@ -4,15 +4,15 @@ title "Use the read tool twice — DSH TUI snapshot"
cursor hidden column=7 viewportRow=27 bufferRow=27
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Use the read tool twice"
style 1-23 fg=bright-black
style 1-23 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Use the read tool twice in the same assistant message: read a.txt and b.txt. Then reply DONE. "
6| <blank>
7| "Assistant "
@@ -21,16 +21,22 @@ buffer
9| "● Tool / read"
style 0-12 fg=green
10| "Read a.txt "
style 0-99 dim
11| "1: alpha "
style 0-99 dim
12| " "
13| "(End of file - total 1 lines) "
style 0-99 dim
14| <blank>
15| "● Tool / read"
style 0-12 fg=green
16| "Read b.txt "
style 0-99 dim
17| "1: beta "
style 0-99 dim
18| " "
19| "(End of file - total 1 lines) "
style 0-99 dim
20| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
21| <blank>
@@ -41,12 +47,12 @@ buffer
style 0-46 dim
25| <blank>
26| "/workspace/project deepseek-v4-flash ↑20 ↓6 cache 0% 3% context"
style 0-47 fg=bright-blue bold
style 50-66 fg=bright-black
style 69-84 fg=bright-black
style 87-96 fg=bright-black
style 0-47 fg=bright-magenta bold
style 50-66 dim
style 69-84 dim
style 87-96 dim
27| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
28-35| <blank>

View File

@@ -0,0 +1,5 @@
{"type":"session","version":0,"id":"31f63cc0-0198-4ab2-bfde-79a4eb4f1867","createdAt":1783352180000,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"assistant/chunk","seq":0,"time":1783352180001,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":1,"time":1783352180002,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"USER-ONLY SKILL LOADED"}}}
{"type":"assistant/chunk","seq":2,"time":1783352180003,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"USER-ONLY SKILL LOADED"}}}}
{"type":"assistant/chunk","seq":3,"time":1783352180004,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}

View File

@@ -0,0 +1,157 @@
=== skill autocomplete ===
terminal 100x36 buffer=normal length=36 base=0 viewport=0
lifecycle started=1 stopped=0 progress=inactive
title "DSH TUI snapshot"
cursor hidden column=13 viewportRow=5 bufferRow=5
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Recorded replay: skill-invocation-policy"
style 1-40 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "/workspace/project deepseek-v4-flash ↑0 ↓0 0% context"
style 0-51 fg=bright-magenta bold
style 54-70 dim
style 73-77 dim
style 80-89 dim
5| " dsh > /skill "
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 13-13 inverse
6| " → skill:user-only-skill (project) — User-only assembled snapshot skill. "
style 7-78 fg=bright-magenta
7-35| <blank>
=== loaded exact invocation ===
terminal 100x36 buffer=normal length=36 base=0 viewport=0
lifecycle started=1 stopped=0 progress=inactive
title "<skill name=\"user-only-skill\"> Reference — DSH TUI snapshot"
cursor hidden column=7 viewportRow=30 bufferRow=30
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " <skill name=\"user-only-skill\"> Reference"
style 1-40 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-magenta bold underline
5| "<skill name=\"user-only-skill\"> "
6| "References in this skill are relative to "
7| "/workspace/project/.agents/skills/user-only-skill. "
8| " "
9| "USER-ONLY BODY "
10| "</skill> "
11| <blank>
12| "Context · dsh-tool-skill"
style 0-23 dim
13| "A skill is a reusable set of task-specific instructions. The following skills are available in this "
style 0-99 dim
14| "session: "
style 0-7 dim
15| " "
16| "<available_skills> "
style 0-17 dim
17| "- `model-only-skill`: Model-only assembled snapshot skill. "
style 0-57 dim
18| "</available_skills> "
style 0-18 dim
19| " "
20| "If the user names a skill, or the task clearly matches a skill's description, call the `skill` tool "
style 0-99 dim
21| "with the exact skill name before taking task actions. Load all applicable skills, then follow their "
style 0-99 dim
22| "full instructions. This catalog contains summaries only; do not infer or follow a skill's "
style 0-99 dim
23| "instructions until it has been loaded. "
style 0-37 dim
24| <blank>
25| "Assistant "
style 0-8 fg=bright-magenta bold underline
26| "USER-ONLY SKILL LOADED "
27| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
28| <blank>
29| "/workspace/project deepseek-v4-flash ↑0 ↓0 3% context"
style 0-51 fg=bright-magenta bold
style 54-70 dim
style 73-77 dim
style 80-89 dim
30| " dsh ◍ "
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
31-35| <blank>
=== denied exact invocation ===
terminal 100x36 buffer=normal length=36 base=0 viewport=0
lifecycle started=1 stopped=0 progress=inactive
title "<skill name=\"user-only-skill\"> Reference — DSH TUI snapshot"
cursor hidden column=7 viewportRow=32 bufferRow=32
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " <skill name=\"user-only-skill\"> Reference"
style 1-40 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-magenta bold underline
5| "<skill name=\"user-only-skill\"> "
6| "References in this skill are relative to "
7| "/workspace/project/.agents/skills/user-only-skill. "
8| " "
9| "USER-ONLY BODY "
10| "</skill> "
11| <blank>
12| "Context · dsh-tool-skill"
style 0-23 dim
13| "A skill is a reusable set of task-specific instructions. The following skills are available in this "
style 0-99 dim
14| "session: "
style 0-7 dim
15| " "
16| "<available_skills> "
style 0-17 dim
17| "- `model-only-skill`: Model-only assembled snapshot skill. "
style 0-57 dim
18| "</available_skills> "
style 0-18 dim
19| " "
20| "If the user names a skill, or the task clearly matches a skill's description, call the `skill` tool "
style 0-99 dim
21| "with the exact skill name before taking task actions. Load all applicable skills, then follow their "
style 0-99 dim
22| "full instructions. This catalog contains summaries only; do not infer or follow a skill's "
style 0-99 dim
23| "instructions until it has been loaded. "
style 0-37 dim
24| <blank>
25| "Assistant "
style 0-8 fg=bright-magenta bold underline
26| "USER-ONLY SKILL LOADED "
27| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
28| <blank>
29| "Skill \"model-only-skill\" is not available for user invocation. "
style 0-61 fg=yellow
30| <blank>
31| "/workspace/project deepseek-v4-flash ↑0 ↓0 3% context"
style 0-51 fg=bright-magenta bold
style 54-70 dim
style 73-77 dim
style 80-89 dim
32| " dsh ◍ "
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
33-35| <blank>

View File

@@ -0,0 +1,7 @@
---
name: model-only-skill
description: Model-only assembled snapshot skill.
user-invocable: false
---
MODEL-ONLY BODY MUST NOT LOAD

View File

@@ -0,0 +1,7 @@
---
name: user-only-skill
description: User-only assembled snapshot skill.
disable-model-invocation: true
---
USER-ONLY BODY

View File

@@ -4,15 +4,15 @@ title "Use the todo_write tool to — DSH TUI snapshot"
cursor hidden column=7 viewportRow=31 bufferRow=31
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-blue bold
style 1-8 fg=bright-magenta bold
style 10-16 bold
1| " Use the todo_write tool to"
style 1-26 fg=bright-black
style 1-26 dim
2| " main-session"
style 1-12 dim
3| <blank>
4| "You "
style 0-2 fg=bright-blue bold underline
style 0-2 fg=bright-magenta bold underline
5| "Use the todo_write tool to record a plan with exactly three todos: \"read the code\" (in_progress), "
6| "\"write the fix\" (pending), \"run the tests\" (pending). Send all three in one todo_write call. Then "
7| "reply with the single word DONE and stop. "
@@ -20,31 +20,33 @@ buffer
9| "Assistant "
style 0-8 fg=bright-magenta bold underline
10| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
11| "The user wants me to use the todo_write tool to record a plan with exactly three todos in the "
style 0-99 fg=bright-black italic
style 0-99 dim italic
12| "specified statuses, then reply with \"DONE\". "
style 0-42 fg=bright-black italic
style 0-42 dim italic
13| <blank>
14| "● Tool / todo_write"
style 0-18 fg=green
15| "Update todo list "
style 0-99 dim
16| "Updated todo list: 2 pending, 1 in progress, 0 completed. "
style 0-99 dim
17| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
18| <blank>
19| "Assistant "
style 0-8 fg=bright-magenta bold underline
20| "Reasoning "
style 0-8 fg=bright-black italic
style 0-8 dim italic
21| "The todos have been written successfully. Now I just need to reply with the single word \"DONE\". "
style 0-94 fg=bright-black italic
style 0-94 dim italic
22| "DONE "
23| "Model wait 0.0s · Completed 2026-07-21 12:00:00 "
style 0-46 dim
24-25| <blank>
26| "Plan"
style 0-3 fg=bright-blue bold
style 0-3 fg=bright-magenta bold
27| " ● read the code"
style 2-2 fg=yellow
28| " ○ write the fix"
@@ -52,12 +54,12 @@ buffer
29| " ○ run the tests"
style 2-2 dim
30| "/workspace/project deepseek-v4-flash ↑3.1k ↓145 cache 47% 3% context"
style 0-37 fg=bright-blue bold
style 40-56 fg=bright-black
style 59-79 fg=bright-black
style 82-91 fg=bright-black
style 0-37 fg=bright-magenta bold
style 40-56 dim
style 59-79 dim
style 82-91 dim
31| " dsh ◍ "
style 1-3 fg=bright-blue bold
style 5-6 fg=bright-black
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse
32-35| <blank>

View File

@@ -217,11 +217,12 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
}, 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')`
// The whole user-only 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.
// and the local provider admits a model-disabled skill by the omitted
// `user-invocable` default. 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-',
@@ -232,6 +233,7 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
'---',
'name: scripted-skill',
'description: Keyless PTY proof that the skill command loads a local skill into the conversation.',
'disable-model-invocation: true',
'---',
'',
'SCRIPTED SKILL BODY MARKER',
@@ -250,6 +252,36 @@ describe('tui-agent keyless smoke (real Loader tree in a PTY)', () => {
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('adds a watched local skill to live /skill: autocomplete without restarting', async () => {
const skill = [
'---',
'name: hot-added-skill',
'description: HOT_ADDED_COMPLETION_MARKER',
'---',
'',
'Hot-added body.',
'',
].join('\n')
const output = await smoke({
label: 'tui-agent hot-added skill autocomplete',
tempDirPrefix: 'tui-agent-hot-skill-',
configPath: scriptedConfigPath,
actions: [
{
waitFor: 'scripted TUI ready.',
writeFile: {
path: '.agents/skills/hot-added-skill/SKILL.md',
content: skill,
},
send: '/skill:hot',
},
{ waitFor: 'HOT_ADDED_COMPLETION_MARKER', send: '\x03/exit\r' },
],
})
expect(output).toContain('HOT_ADDED_COMPLETION_MARKER')
expect(output).toContain('\u001B[?2004l')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('fuzzy-completes an @file path without reading or submitting the file', async () => {
const output = await smoke({
label: 'tui-agent file autocomplete',

View File

@@ -41,6 +41,7 @@ const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi
type SnapshotMode = 'replay' | 'record' | 'refresh'
type Composition = 'native' | 'code' | 'advanced'
type ScenarioInteraction = 'skill-invocation-policy'
interface Scenario {
name: string
@@ -65,6 +66,8 @@ interface Scenario {
* preview + locator while the program value stays whole.
*/
spillMaxInlineBytes?: number
/** Run scenario-specific terminal input instead of replaying recorded user prompts. */
interaction?: ScenarioInteraction
}
const SCENARIOS: Scenario[] = [
@@ -98,6 +101,14 @@ const SCENARIOS: Scenario[] = [
recorded: true,
seedWorkspace: true,
},
{
name: 'skill-invocation-policy',
composition: 'native',
expectedTools: [],
recorded: false,
seedWorkspace: true,
interaction: 'skill-invocation-policy',
},
{
name: 'code-mode',
composition: 'code',
@@ -269,9 +280,10 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
const dir = scenarioDir(scenario)
const fixtureFile = join(dir, 'session.jsonl')
const childFiles = childFixturePaths(scenario)
const fixture = await readFile(fixtureFile, 'utf8')
const prompts = userPrompts(fixture)
expect(prompts.length, `${scenario.name} must carry at least one recorded user prompt`).toBeGreaterThan(0)
const prompts = userPrompts(await readFile(fixtureFile, 'utf8'))
if (scenario.interaction === undefined) {
expect(prompts.length, `${scenario.name} must carry at least one recorded user prompt`).toBeGreaterThan(0)
}
const cwd = await mkdtemp(join(SNAPSHOT_TMP_ROOT, `dsh-tui-snapshot-${scenario.name}-`))
const displayCwd = `/tmp/${basename(cwd)}`
@@ -310,6 +322,63 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
})
await settleTerminal(terminal)
let interactionSnapshot: string | undefined
if (scenario.interaction === 'skill-invocation-policy') {
terminal.send('/skill')
await settleTerminal(terminal)
const discovery = normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,
)
expect(discovery).toContain('user-only-skill')
expect(discovery).not.toContain('model-only-skill')
terminal.send('\x03')
await settleTerminal(terminal)
const skillContext = ctx
const skillTurnEnded = new Promise<void>((resolve) => {
const detach = skillContext.on('session/event', (session, event) => {
if (session !== agent.session || event.type !== 'turn/end') return
detach()
resolve()
})
})
terminal.send('/skill:user-only-skill')
terminal.send('\r')
await skillTurnEnded
await agent.whenIdle()
await settleTerminal(terminal)
const loaded = normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,
)
expect(loaded).toContain('USER-ONLY SKILL LOADED')
terminal.send('/skill:model-only-skill')
terminal.send('\r')
await settleTerminal(terminal)
const denied = normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,
)
expect(denied).toContain('model-only-skill')
expect(denied).toContain('not available for user invocation.')
expect(denied).not.toContain('MODEL-ONLY BODY MUST NOT LOAD')
interactionSnapshot = [
'=== skill autocomplete ===',
discovery,
'',
'=== loaded exact invocation ===',
loaded,
'',
'=== denied exact invocation ===',
denied,
].join('\n')
}
let remainingPrompts = prompts
if (scenario.enterPlanMode === true) {
const firstPrompt = prompts[0]!
@@ -392,7 +461,7 @@ async function runScenario(scenario: Scenario): Promise<ScenarioResult> {
}
expect(terminal.themeViolations(), `${scenario.name} must remain theme-agnostic`).toEqual([])
const snapshot = normalizeTerminalSnapshot(
const snapshot = interactionSnapshot ?? normalizeTerminalSnapshot(
await terminal.snapshot({ includeScrollback: true }),
cwd,
displayCwd,