From 47e3e6f0ed40dfe8f424456899a25ad3e084ab75 Mon Sep 17 00:00:00 2001 From: fz Date: Wed, 12 Aug 2026 21:47:28 +0800 Subject: [PATCH 001/119] feat(web): rename code preset to PTC mode --- apps/cli/config/agent-presets/code/preset.yml | 2 +- .../snapshots/agent-preset-authoring/created.expected.md | 8 ++++---- .../snapshots/agent-preset-authoring/damaged.expected.md | 8 ++++---- .../snapshots/agent-preset-authoring/section.expected.md | 8 ++++---- packages/client/ui-agent-preset/src/client/locales.ts | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/cli/config/agent-presets/code/preset.yml b/apps/cli/config/agent-presets/code/preset.yml index 17eaccb871..fc0836f479 100644 --- a/apps/cli/config/agent-presets/code/preset.yml +++ b/apps/cli/config/agent-presets/code/preset.yml @@ -1,3 +1,3 @@ -name: 代码模式 +name: PTC 模式 description: 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 order: 2 diff --git a/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md b/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md index 40b9497831..ed674dfd57 100644 --- a/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md +++ b/apps/web/tests/snapshots/agent-preset-authoring/created.expected.md @@ -35,13 +35,13 @@ - img - text: 复制 - listitem: - - 'button "设为默认: 代码模式"': - - text: 代码模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 + - 'button "设为默认: PTC 模式"': + - text: PTC 模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 - code: code - - 'button "查看: 代码模式"': + - 'button "查看: PTC 模式"': - img - text: 查看 - - 'button "复制: 代码模式"': + - 'button "复制: PTC 模式"': - img - text: 复制 - listitem: diff --git a/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md b/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md index c3e9035098..688fb65822 100644 --- a/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md +++ b/apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md @@ -35,13 +35,13 @@ - img - text: 复制 - listitem: - - 'button "设为默认: 代码模式"': - - text: 代码模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 + - 'button "设为默认: PTC 模式"': + - text: PTC 模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 - code: code - - 'button "查看: 代码模式"': + - 'button "查看: PTC 模式"': - img - text: 查看 - - 'button "复制: 代码模式"': + - 'button "复制: PTC 模式"': - img - text: 复制 - listitem: diff --git a/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md b/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md index e411e8ea28..b4bd5f44e6 100644 --- a/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md +++ b/apps/web/tests/snapshots/agent-preset-authoring/section.expected.md @@ -35,13 +35,13 @@ - img - text: 复制 - listitem: - - 'button "设为默认: 代码模式"': - - text: 代码模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 + - 'button "设为默认: PTC 模式"': + - text: PTC 模式 内置 具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。 - code: code - - 'button "查看: 代码模式"': + - 'button "查看: PTC 模式"': - img - text: 查看 - - 'button "复制: 代码模式"': + - 'button "复制: PTC 模式"': - img - text: 复制 - listitem: diff --git a/packages/client/ui-agent-preset/src/client/locales.ts b/packages/client/ui-agent-preset/src/client/locales.ts index 2a3dee06f3..54acc298a1 100644 --- a/packages/client/ui-agent-preset/src/client/locales.ts +++ b/packages/client/ui-agent-preset/src/client/locales.ts @@ -100,7 +100,7 @@ export const zh: Record = { view: '查看', presetStandardName: '标准模式', presetStandardDescription: '功能完整的编码 Agent,支持文件编辑、Shell、文件与网页检索、Skills、计划、目标、子代理和工作流。', - presetCodeName: '代码模式', + presetCodeName: 'PTC 模式', presetCodeDescription: '具备标准模式的全部能力,并通过 Code Mode SDK 呈现工具,让模型用一个 TypeScript 程序组合多步操作。', presetMinimalName: '极简模式', presetMinimalDescription: '仅提供持久 bash 与 str_replace_editor 的双工具编码 Agent。', From 1ca6f68b173d8a4c2625ab060270ad08737aed48 Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:07:47 -0700 Subject: [PATCH 002/119] fix(web): guide clickable file references --- ...6-08-07-web-inline-file-mentions.i18n.yaml | 4 +-- .../2026-08-07-web-inline-file-mentions.md | 10 ++++-- .../2026-08-07-web-inline-file-mentions.zh.md | 10 ++++-- apps/web/tests/shipped-composition.e2e.ts | 18 ++++++++--- .../file-reference-prompt.expected.md | 1 + docs/config-catalog.md | 2 +- .../client/ui-deliverables/README.i18n.yaml | 4 +-- packages/client/ui-deliverables/README.md | 17 ++++++++-- packages/client/ui-deliverables/README.zh.md | 17 ++++++++-- packages/client/ui-deliverables/package.json | 4 ++- packages/client/ui-deliverables/src/index.ts | 31 +++++++++++++++---- .../client/ui-deliverables/src/invariant.ts | 7 ++--- .../tests/produced-files.client.spec.tsx | 5 +-- .../tests/prompt.client.spec.ts | 30 ++++++++++++++++++ packages/client/ui-deliverables/tsconfig.json | 3 ++ pnpm-lock.yaml | 3 ++ .../verify-package-readme-model-experience.ts | 1 - 17 files changed, 130 insertions(+), 37 deletions(-) create mode 100644 apps/web/tests/snapshots/web-runtime-context/file-reference-prompt.expected.md create mode 100644 packages/client/ui-deliverables/tests/prompt.client.spec.ts diff --git a/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.i18n.yaml b/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.i18n.yaml index b86faaa501..b97dae4740 100644 --- a/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md -2026-08-07-web-inline-file-mentions.md: cb51e6cfa5eebe0341b50c7638d51ac2d8641ba7 -2026-08-07-web-inline-file-mentions.zh.md: 160d3838fc8d02e57aa4371cb9d8a9ec7ccf6b5b +2026-08-07-web-inline-file-mentions.md: 0c7341e9aec688a0661fedb18731a1c172215f45 +2026-08-07-web-inline-file-mentions.zh.md: a9ef53933647feb27f9dde2d454166cf17de4fbe diff --git a/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md b/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md index cb51e6cfa5..0c7341e9ae 100644 --- a/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md +++ b/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md @@ -4,11 +4,11 @@ Status: implemented English | [中文](2026-08-07-web-inline-file-mentions.zh.md) -> Scope: linking inline-code tokens in the closing message's prose to the files the turn produced. Not in scope: recognizing paths in plain prose, linking files the turn did not produce, and mentions in streaming or mid-turn messages. +> Scope: guiding final responses to name primary output files as inline code, then linking those tokens to files the turn changed. Not in scope: recognizing paths in plain prose, linking files absent from successful mutation locations, and mentions in streaming or mid-turn messages. ## Problem -The produced-files row lists a turn's output, but the closing message usually also *names* the file in prose — as inline code, like `` `deepseek-homepage.html` `` — and that mention was inert text. The reader's eye lands on the sentence first; the affordance sat one row below it. +The produced-files row lists a turn's output, but the closing message usually also *names* the file in prose — as inline code, like `` `deepseek-homepage.html` `` — and that mention was inert text. The reader's eye lands on the sentence first; the affordance sat one row below it. The model was not told that this exact inline-code spelling activates the Web file opener, so producing the useful reference depended on habit. ## Decision @@ -16,13 +16,17 @@ The produced-files row lists a turn's output, but the closing message usually al **The renderer owns no vocabulary, and the provider is the deliverables plugin.** `MarkdownText` takes an optional `MarkdownFileMentions` resolver and consults it for inline-code tokens — after URL promotion, which wins, and never inside an anchor, where a button cannot nest. What names a file is decided behind the optional `chatFileMentions` service ui-conversation reaches via `ctx.get`: ui-deliverables provides it beside its turn-tail chain entry, so one cordis.yml line composes the row and the prose links in or out together, and ui-primitives gains no session concepts. Mentions apply to settled renders only — the streaming cache must not bake in handlers that could go stale, and the vocabulary is not final until the turn closes. The consumer memoizes the resolver on the closing seq rather than the growing transcript, so a settled message's cached parse survives stream appends. +**The provider also owns the model guidance for its accepted syntax.** The ui-deliverables Node half registers a static `ui:deliverable-file-references` section that asks the model to mention primary files from successful creation or modification calls in its final response and to write those and any other changed-file references as Markdown inline code, using the exact file-tool path or a basename only when it is unique within the Turn. The guidance deliberately says nothing about unrelated local-path formats. The shipped Web patch is the only composition that loads ui-deliverables, so the guidance exists exactly where the renderer exists; removing the package removes both. The row remains the correctness path when the model omits a mention, and the matching vocabulary still rejects anything not recorded by a successful mutation. + ## Alternatives considered - **Path-shaped regex over all prose** — links `package.json` mentioned abstractly and examples that were never written; every false positive is a click that opens nothing or the wrong file. The vocabulary approach cannot produce a dead link. - **Linking suffix matches (`out/index.html` mentioned as `index.html` in a subdirectory listing)** — deferred; exact path and unique basename cover the observed closing-message shapes, and a wider matcher can loosen later without breaking the seam. - **Resolving in ui-primitives against a passed path list** — puts matching policy in the generic renderer, where other consumers would inherit it unasked. The resolver contract keeps policy with the owner. - **Threading the vocabulary through the turn-tail chain** — the chain is a render dispatch below the message; mentions decorate markdown inside it, which only data reaching MarkdownText can do. The optional service is that data path, and its absence is the off state. +- **Registering the guidance in dsh-web-app** — makes the app bundle describe a feature-specific rendering syntax and allows the renderer and its prompt to drift or be composed independently. The feature package's existing Node half gives one cordis.yml row joint ownership. +- **Adding a post-turn model step to identify the output** — adds latency and another generation even though the final response already has the necessary file-tool history. One static prompt paragraph stays in the reusable prefix and asks the existing final generation to emit the accepted spelling. ## Consequences -The mention and the row are two affordances for one fact (full path as `title` on both); the mention itself wears the markdown sheet's anchor language — link-blue at rest, hover underline — because an at-rest underline collides with monospace descenders inside the code chip. `apps/web/tests/produced-file-mentions.e2e.ts` pins the assembled behavior with a built write-turn seed: unique basename links, ambiguous and unknown tokens stay inert; it does not click, for the produced-files restraint (the opener launches a real application). Mentions in mid-turn narration stay inert even for files the turn later produces, because the vocabulary attaches to the closing message only. The window-prepend edge — a window that starts mid-turn later gaining earlier same-turn writes — leaves a mention unlinked until remount, never wrongly linked. +The mention and the row are two affordances for one fact (full path as `title` on both); the mention itself wears the markdown sheet's anchor language — link-blue at rest, hover underline — because an at-rest underline collides with monospace descenders inside the code chip. The prompt section is constant for the package mount and therefore remains cacheable across Turns. The keyless shipped-Web composition snapshot pins the exact model-visible paragraph, while `apps/web/tests/produced-file-mentions.e2e.ts` pins the assembled rendering with a built write-turn seed: unique basename links, ambiguous and unknown tokens stay inert. Files created indirectly by terminal commands remain outside the vocabulary, even when the model names them. Mentions in mid-turn narration stay inert even for files the turn later produces, because the vocabulary attaches to the closing message only. The window-prepend edge — a window that starts mid-turn later gaining earlier same-turn writes — leaves a mention unlinked until remount, never wrongly linked. diff --git a/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.zh.md b/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.zh.md index 160d3838fc..a9ef539336 100644 --- a/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.zh.md +++ b/.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.zh.md @@ -4,11 +4,11 @@ Status: implemented [English](2026-08-07-web-inline-file-mentions.md) | 中文 -> 范围:把收尾消息正文中的行内代码 token 链接到本轮产出的文件。不在范围内:识别普通正文中的路径、链接本轮未产出的文件,以及流式或轮次中途消息里的提及。 +> 范围:引导最终回复以行内代码点名主要输出文件,再把这些 token 链接到本轮变更的文件。不在范围内:识别普通正文中的路径、链接成功修改位置中不存在的文件,以及流式或轮次中途消息里的提及。 ## 问题 -产物行列出了一轮的输出,但收尾消息通常也会在正文里*点名*文件——以行内代码形式,如 `` `deepseek-homepage.html` ``——而这个提及是死文本。读者的视线先落在句子上;可点击的交互却在下面一行。 +产物行列出了一轮的输出,但收尾消息通常也会在正文里*点名*文件——以行内代码形式,如 `` `deepseek-homepage.html` ``——而这个提及是死文本。读者的视线先落在句子上;可点击的交互却在下面一行。模型并不知道这种精确的行内代码写法会启用 Web 文件打开器,因此能否产生有用引用取决于模型习惯。 ## 决定 @@ -16,13 +16,17 @@ Status: implemented **渲染器不持有词表,提供方是 deliverables 插件。**`MarkdownText` 接受可选的 `MarkdownFileMentions` 解析器,对行内代码 token 询问它——URL 提升优先于解析器,且绝不在锚点内部(按钮不能嵌套在链接里)。什么算文件名的决定藏在 ui-conversation 经 `ctx.get` 触达的可选 `chatFileMentions` 服务背后:ui-deliverables 在其 turn-tail chain 注册项旁提供该服务,因此 cordis.yml 中的一行同时把产物行和正文链接组合进来或去掉,ui-primitives 不引入任何会话概念。提及只作用于已定稿的渲染——流式缓存不能固化可能过期的 handler,而且词表在轮次收尾前并不最终。消费方按收尾 seq 而非不断增长的 transcript(文本记录)记忆化解析器,因此已定稿消息的缓存解析在流式追加中得以保留。 +**提供方也拥有其所接受语法的模型指引。**ui-deliverables 的 Node 侧注册静态段落 `ui:deliverable-file-references`,要求模型在最终回复中点名来自成功创建或修改调用的主要文件,并将这些文件以及正文中提到的其他本轮变更文件写成 Markdown 行内代码:使用文件工具采用的精确路径,或仅在 basename 能唯一指代本轮文件时使用 basename。该指引刻意不涉及无关的本地路径格式。正式提供的组合中只有 Web patch 加载 ui-deliverables,因此提示词只存在于渲染器存在的地方;移除该包会同时移除两者。模型遗漏提及时,文件行仍承担正确性兜底;匹配词表仍拒绝成功修改记录之外的所有文件。 + ## 考虑过的替代方案 - **对全部正文跑路径形状的正则**——会把随口提到的 `package.json` 和从未写过的示例都链接上;每个误报都会带来一次点击,结果要么什么也没打开,要么打开了错误文件。词表方案不可能产生死链。 - **链接后缀匹配(子目录列表里把 `out/index.html` 写作 `index.html`)**——暂缓;精确路径加唯一 basename 已覆盖观察到的收尾消息形态,之后放宽匹配器不会破坏这道 seam。 - **在 ui-primitives 里对传入的路径列表做解析**——把匹配策略放进通用渲染器,其他消费方会被动继承。解析器约定让策略留在持有者手里。 - **经由 turn-tail chain 传递词表**——chain 是消息下方的渲染派发;提及要装饰的是消息内部的 markdown,只有抵达 MarkdownText 的数据才做得到。可选服务就是那条数据通路,它的缺席即关闭态。 +- **在 dsh-web-app 中注册指引**——会让应用组合包描述某个功能特有的渲染语法,也允许渲染器与提示词分别组合或产生漂移。该功能包现有的 Node 侧让 cordis.yml 中的一项可以共同持有两者。 +- **增加一次轮次结束后的模型调用来识别输出**——即使最终回复已经拥有所需的文件工具历史,仍会增加延迟和一次生成。一个静态提示词段落可以留在可复用前缀中,并要求现有的最终生成输出渲染器接受的写法。 ## 后果 -提及与产物行是同一事实的两个交互面(两者都以完整路径作 `title`);提及本身采用 markdown 样式表的锚点语言——静止为链接蓝、悬停出下划线——因为静止下划线在 code 胶囊里会压住等宽字的下伸部。`apps/web/tests/produced-file-mentions.e2e.ts` 用构造的写入轮次 seed 钉住组装后的行为:唯一 basename 成链,歧义与未知 token 保持死文本;它不执行点击,沿用产物行测试不启动外部应用的限制(opener 会启动真实应用)。轮次中途叙述里的提及即使命名了本轮后来产出的文件也保持死文本,因为词表只挂在收尾消息上。窗口前插的边界——从轮次中途开始的窗口后来补入了同轮更早的写入——只会让提及在重挂载前暂不成链,绝不会错链。 +提及与产物行是同一事实的两个交互面(两者都以完整路径作 `title`);提及本身采用 markdown 样式表的锚点语言——静止为链接蓝、悬停出下划线——因为静止下划线在 code 胶囊里会压住等宽字的下伸部。提示词段落在本包加载期间保持不变,因此可跨 Turn 复用缓存。无密钥的正式 Web 组合快照钉住模型可见段落的精确文本,`apps/web/tests/produced-file-mentions.e2e.ts` 则用构造的写入轮次 seed 钉住组装后的渲染行为:唯一 basename 成链,歧义与未知 token 保持死文本。终端命令间接创建的文件即使被模型点名,仍不在词表中。轮次中途叙述里的提及即使命名了本轮后来产出的文件也保持死文本,因为词表只挂在收尾消息上。窗口前插的边界——从轮次中途开始的窗口后来补入了同轮更早的写入——只会让提及在重挂载前暂不成链,绝不会错链。 diff --git a/apps/web/tests/shipped-composition.e2e.ts b/apps/web/tests/shipped-composition.e2e.ts index ee00353ef8..aa43d350fb 100644 --- a/apps/web/tests/shipped-composition.e2e.ts +++ b/apps/web/tests/shipped-composition.e2e.ts @@ -1,9 +1,11 @@ // Boots the shipped Web composition over the built dist this lane already uses // and asserts what that composition produces: the model-visible tool catalog -// and the sandbox/approval knobs it ships with. No browser and no model call — -// these are composition facts, and the browser scenarios in this lane cover the -// surface itself. +// and file-reference guidance plus the sandbox/approval knobs it ships with. +// No browser and no model call — these are composition facts, and the browser +// scenarios in this lane cover the surface itself. +import { readFileSync } from 'node:fs' import { tmpdir } from 'node:os' +import { fileURLToPath } from 'node:url' import { afterEach, expect, it } from 'vitest' import { CallId } from '@deepseek-ai/dsh-llm' import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox' @@ -15,8 +17,13 @@ import type {} from '@deepseek-ai/dsh-user-approval' import type {} from '@deepseek-ai/dsh-permission' import type {} from '@deepseek-ai/dsh-agent-presets' import type {} from '@deepseek-ai/dsh-commands' +import type {} from '@deepseek-ai/dsh-system-prompt' import { launchWebScaffold, type WebScaffold } from './scaffold.ts' +const FILE_REFERENCE_PROMPT = fileURLToPath(new URL( + './snapshots/web-runtime-context/file-reference-prompt.expected.md', import.meta.url, +)) + /** * The catalog the shipped Web composition puts in front of the model, minus the * ripgrep-dependent pair below. The absences are deliberate, not incidental @@ -66,7 +73,7 @@ afterEach(async () => { scaffold = undefined }) -it('assembles the shipped Web catalog with the confined access default', async () => { +it('assembles the shipped Web catalog, file-reference guidance, and confined access default', async () => { scaffold = await launchWebScaffold() const ctx = scaffold.ctx // The catalog belongs to an AGENT, not to the process: every model-facing row @@ -85,6 +92,9 @@ it('assembles the shipped Web catalog with the confined access default', async ( // The packaged ripgrep binary ships with the dependency, so the pair is a // fixed roster member on every host. expect(names.filter(name => RIPGREP_TOOLS.includes(name))).toEqual(RIPGREP_TOOLS) + const fileReferenceSection = (await ctx.systemPrompt.assemble({ scope: handle.agent })).sections + .find(section => section.name === 'ui:deliverable-file-references') + expect(fileReferenceSection?.text).toBe(readFileSync(FILE_REFERENCE_PROMPT, 'utf8').trimEnd()) } finally { await handle.dispose() } diff --git a/apps/web/tests/snapshots/web-runtime-context/file-reference-prompt.expected.md b/apps/web/tests/snapshots/web-runtime-context/file-reference-prompt.expected.md new file mode 100644 index 0000000000..6561157aec --- /dev/null +++ b/apps/web/tests/snapshots/web-runtime-context/file-reference-prompt.expected.md @@ -0,0 +1 @@ +When you successfully create or modify files, mention the primary outputs in your final response. To make those and any other changed-file references clickable in Web, format them as Markdown inline code using the exact file-tool path, or a basename when unique among the files changed in that turn. diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 3eb59072b7..a10089b686 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -2806,7 +2806,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-client-ui-agent-preset` ([`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts)) - `@deepseek-ai/dsh-client-ui-command` ([`packages/client/ui-command/src/index.ts`](../packages/client/ui-command/src/index.ts)) - `@deepseek-ai/dsh-client-ui-conversation` ([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts)) -- `@deepseek-ai/dsh-client-ui-deliverables` ([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) +- `@deepseek-ai/dsh-client-ui-deliverables` — requires `systemPrompt` ([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker` ([`packages/client/ui-directory-picker/src/index.ts`](../packages/client/ui-directory-picker/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker-native` ([`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts)) - `@deepseek-ai/dsh-client-ui-feedback` ([`packages/client/ui-feedback/src/index.ts`](../packages/client/ui-feedback/src/index.ts)) diff --git a/packages/client/ui-deliverables/README.i18n.yaml b/packages/client/ui-deliverables/README.i18n.yaml index cfa85256ec..70b3f889bf 100644 --- a/packages/client/ui-deliverables/README.i18n.yaml +++ b/packages/client/ui-deliverables/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-deliverables/README.md -README.md: 9cfcde9510c5d6814d5b42fd58a0520ea37b8679 -README.zh.md: 4e09524e269df31cdabce0d5f6e6900d23305265 +README.md: e7118eac75f31b3ffc3f2434371dba06f029d241 +README.zh.md: 0c41170ef38331166f6ad87df0c3e5976200eaf9 diff --git a/packages/client/ui-deliverables/README.md b/packages/client/ui-deliverables/README.md index 9cfcde9510..e7118eac75 100644 --- a/packages/client/ui-deliverables/README.md +++ b/packages/client/ui-deliverables/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Produced-files feature owner: registers the deliverables row a finished turn ends with into the chat view's `conversation.chat.turnTail` hole. All policy lives here; removing this plugin's line from cordis.yml removes the surface entirely, and the owning view renders an empty hole at zero cost. +Produced-files and clickable-reference feature owner. The Node half registers final-response guidance with the system-prompt registry; the browser half registers the deliverables row a finished turn ends with into the chat view's `conversation.chat.turnTail` hole and links matching inline-code references in the closing prose. The shipped Web patch is the only composition that loads this package. Removing its one cordis.yml entry removes the guidance, row, and prose links together. `deliverablesDefinition` folds each Turn's successful mutation calls into engine-published `DeliverablesTurnData`; `producedForClosing` reads that data with the closing Assistant seq. The vocabulary is the mutation tools' own follow-along `locations`, never the closing prose: a produced file is listed whether or not the model remembered to name it. A mutation is recognized by render intent, not tool name — a diff card, or a generic card whose `kind` is `edit` (the shape `str_replace_editor`'s insert presents) — so a new mutation tool joins by declaring what it does. Reads, deletes, and failed calls contribute nothing; a path appears once per Turn in first-seen order. The Conversation Location index owns Turn membership, so a Turn that mutates and then ends without content text cannot spill into the next Turn's row. @@ -10,15 +10,26 @@ Produced-files feature owner: registers the deliverables row a finished turn end The closing prose carries the same vocabulary. This plugin provides the `chatFileMentions` service the chat view consults per closing message: `producedFileMentions` resolves an inline-code token by exact path, or by being exactly the basename of exactly one produced path — a basename two paths share stays inert rather than guessing, so a mention link can never open the wrong file or 404. A resolved mention keeps its code chip and takes the markdown sheet's link language — link-blue at rest, underlined on hover, exactly like URL-promoted inline code — with the full path as its `title`; mentions never render inside anchors or streaming text. Decision record: the [inline file mentions Agent Note](../../../.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md). +The Node half registers the static `ui:deliverable-file-references` system-prompt section. It asks the model to mention the primary files it successfully created or modified and to write those and any other changed-file references as Markdown inline code, using the exact file-tool path or a basename only when unique within the Turn. The guidance makes the renderer's accepted syntax explicit; it does not govern unrelated path discussions or widen the renderer's successful-mutation vocabulary. + ## Model Experience -None, as the row is a pure client derivation over already-logged tool metadata and nothing here reaches a model request. +### Clickable file-reference guidance + +#### What the model sees + +One fixed paragraph instructs the model to name primary files from successful creation or modification calls in its final response and to format those and any other changed-file references as exact-path or unique-basename Markdown inline code, such as `out/report.html`. + +#### Token effect + +One fixed prompt paragraph whenever this package is loaded; no tool schema, tool result, or per-Turn context is added. #### KV Cache effect -None; this package neither assembles nor sends provider requests. +The section is static at order 190 for the lifetime of the package mount, so it remains in the reusable prompt prefix and does not change across Turns. ## Known Limitations and Deferred Work - **Mention matching is exact path or unique basename only.** A suffix mention (`out/index.html` written as `index.html` resolves; `deep/out/index.html` written as `out/index.html` does not) stays inert; widening the matcher is deferred until a real closing-message shape needs it. +- **Files created indirectly by terminal commands remain outside the matching vocabulary.** Naming such a file in inline code does not make it clickable unless a successful mutation location also records that path. - **Native folder handoff targets the Host desktop.** A browser reached through a non-loopback authority omits the action, as does a deployment reporting no native opener. SSH forwarding that makes a remote Host look loopback-local must set the gateway's `nativeOpen: false`; so must a headless macOS/Windows Host, a WSL deployment without working Windows interop, or any Linux desktop whose display/opener probe is a false positive. Identifying the operator-visible desktop remains deployment policy. diff --git a/packages/client/ui-deliverables/README.zh.md b/packages/client/ui-deliverables/README.zh.md index 4e09524e26..0c41170ef3 100644 --- a/packages/client/ui-deliverables/README.zh.md +++ b/packages/client/ui-deliverables/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -产出文件功能的属主:把已完成轮次末尾的产出文件行注册到 chat 视图的 `conversation.chat.turnTail` slot 中。全部策略都在本包内;从 cordis.yml 中删去本插件那一行即可整体移除该界面,属主视图无需额外开销即可渲染空 slot。 +产出文件与可点击文件引用功能的属主。Node 侧向系统提示词 registry 注册最终回复指引;浏览器侧把已完成轮次末尾的产出文件行注册到 chat 视图的 `conversation.chat.turnTail` slot,并将收尾正文中匹配的行内代码引用转换为链接。正式提供的组合中只有 Web patch 加载本包;从 cordis.yml 中删去这一项会同时移除提示词、文件行与正文链接。 `deliverablesDefinition` 把每个轮次中成功的修改调用折叠进引擎发布的 `DeliverablesTurnData`;`producedForClosing` 结合收尾 Assistant 的 seq 读取这份数据。依据的是修改工具自身附带的 `locations`,而不是收尾正文:无论模型是否记得点名,产出文件都会被列出。修改操作按渲染意图而非工具名识别:diff 卡片,或 `kind` 为 `edit` 的通用卡片(即 `str_replace_editor` 的 insert 操作所呈现的形态);因此新的修改工具只需声明自身行为即可加入。读取、删除和失败的调用不贡献任何条目;同一路径在一个轮次内按首见顺序只出现一次。Conversation Location 索引负责维护轮次归属关系,因此一个轮次即使先修改文件、随后没有正文内容就结束,也不会溢进下一个轮次的行里。 @@ -10,15 +10,26 @@ 收尾正文承载同一份词表。本插件提供供 chat 视图按收尾消息查询的 `chatFileMentions` 服务:`producedFileMentions` 按精确路径解析行内代码 token,或当 token 恰好等于某条产出路径的 basename,且这样的路径仅有一条时解析——两条路径共享同一 basename 时,文本保持不可点击而不作猜测,因此提及链接永远不会打开错误的文件,也不会导致 404。解析成功的提及保留代码标签,并采用 Markdown 样式表的链接样式:静止时为链接蓝色,悬停时显示下划线,与 URL 提升的行内代码完全一致——完整路径作为其 `title`;提及绝不会渲染在链接内部或流式文本中。决策记录:[行内文件提及 Agent Note](../../../.agents/notes/implemented/feature/2026-08-07-web-inline-file-mentions.md)。 +Node 侧注册静态系统提示词段落 `ui:deliverable-file-references`。它要求模型点名成功创建或修改的主要文件,并将这些文件以及正文中提到的其他本轮变更文件写成 Markdown 行内代码:使用文件工具采用的精确路径,或仅在 basename 能唯一指代本轮文件时使用 basename。该提示词只向模型说明渲染器接受的语法;它不约束无关的路径讨论,也不会扩大渲染器的成功修改词表。 + ## 模型体验 -无。该行是对已记录工具元数据的纯客户端派生,这里没有任何内容进入模型请求。 +### 可点击文件引用指引 + +#### 模型看到的内容 + +一段固定提示词要求模型在最终回复中点名成功创建或修改的主要文件,并将这些文件以及正文中提到的其他本轮变更文件写成采用精确路径或唯一 basename 的 Markdown 行内代码,例如 `out/report.html`。 + +#### Token 影响 + +加载本包时增加一段固定提示词;不增加工具 schema、工具结果或按 Turn 变化的上下文。 #### KV Cache 影响 -无;该包既不组装也不发送提供方请求。 +该段落在本包加载期间始终以顺序 190 保持静态,因此留在可复用的提示词前缀中,不会随 Turn 改变。 ## 已知限制与暂缓事项 - **提及匹配只认精确路径或唯一 basename。**后缀式提及(`out/index.html` 写作 `index.html` 可解析;`deep/out/index.html` 写作 `out/index.html` 则不行)保持不可点击;等真实的收尾消息形态产生需求后再放宽匹配规则。 +- **终端命令间接创建的文件仍不在匹配词表内。**除非某个成功修改位置也记录了该路径,否则在行内代码中点名这类文件不会使其可点击。 - **原生文件夹交接以 Host 桌面为目标。**经非 loopback 权威访问的浏览器会省略该操作,报告没有原生打开器的部署也一样。若 SSH 转发让远端 Host 看似处于本机 loopback,部署必须为网关设置 `nativeOpen: false`;无界面的 macOS/Windows Host、Windows interop 不可用的 WSL,或 display/opener 探测误报的 Linux 桌面也必须这样配置。识别操作者实际可见的桌面仍属于部署策略。 diff --git a/packages/client/ui-deliverables/package.json b/packages/client/ui-deliverables/package.json index 4d7ea0d314..2a0559fc7b 100644 --- a/packages/client/ui-deliverables/package.json +++ b/packages/client/ui-deliverables/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-client-ui-deliverables", - "description": "Produced-files turn tail: the deliverables row a finished turn ends with", + "description": "Produced-files turn tail and clickable final-response file references for Web", "version": "0.0.1-rc.2", "publishConfig": { "access": "restricted" @@ -55,6 +55,7 @@ "@deepseek-ai/dsh-client-ui-conversation": "workspace:^", "@deepseek-ai/dsh-client-ui-slots": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/cordis": "workspace:^" }, "devDependencies": { @@ -65,6 +66,7 @@ "@deepseek-ai/dsh-client-ui-conversation": "workspace:^", "@deepseek-ai/dsh-client-ui-slots": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", "@types/react": "~18.3.1", "@deepseek-ai/cordis": "workspace:^" }, diff --git a/packages/client/ui-deliverables/src/index.ts b/packages/client/ui-deliverables/src/index.ts index c6ea80fa21..6e6c387803 100644 --- a/packages/client/ui-deliverables/src/index.ts +++ b/packages/client/ui-deliverables/src/index.ts @@ -1,9 +1,28 @@ /** - * Deliverables plugin, node half. Pure UI plugin: the empty apply exists so - * the plugin appears in the host cordis.yml / Loader; the browser half ships - * via exports["./client"], discovered through the package.json dsh.client - * declaration. + * Deliverables plugin, node half. Registers the response-format guidance that + * lets the browser half recognize final-response file references. The browser + * half ships via exports["./client"], discovered through the package.json + * dsh.client declaration. */ -/** Host plugin body — no host-side behavior for this surface plugin. */ -export function apply(): void {} +import type { Context } from '@deepseek-ai/cordis' +import type {} from '@deepseek-ai/dsh-system-prompt' + +/** Services required for the model guidance paired with the browser renderer. */ +export const inject = ['systemPrompt'] + +/** Stable final-response guidance owned by the matching renderer. */ +const FILE_REFERENCE_PROMPT = 'When you successfully create or modify files, mention the primary outputs in your final response. ' + + 'To make those and any other changed-file references clickable in Web, format them as Markdown inline code using the exact file-tool path, or a basename when unique among the files changed in that turn.' + +/** + * Register model guidance for the file-reference renderer shipped by this package. + * @param ctx - host context carrying the system-prompt registry. + */ +export function apply(ctx: Context): void { + ctx.systemPrompt.section({ + name: 'ui:deliverable-file-references', + order: 190, + text: FILE_REFERENCE_PROMPT, + }) +} diff --git a/packages/client/ui-deliverables/src/invariant.ts b/packages/client/ui-deliverables/src/invariant.ts index d14e474ca3..7fa5eaa216 100644 --- a/packages/client/ui-deliverables/src/invariant.ts +++ b/packages/client/ui-deliverables/src/invariant.ts @@ -15,10 +15,9 @@ export const name = 'client-ui-deliverables-invariant' export const inject = ['invariants'] /** - * No runtime invariant: one slot registration and one dictionary - * registration, both effect-owned with disposal proven by the HMR-safety - * spec — the plugin emits no cordis events and owns no cross-plugin mutable - * state. + * No runtime invariant: the prompt section, slot, dictionary, event + * definition, and optional service registrations are effect-owned with + * disposal proven by their plugin specs; this package owns no mutable state. */ const install: InvariantInstaller = () => {} diff --git a/packages/client/ui-deliverables/tests/produced-files.client.spec.tsx b/packages/client/ui-deliverables/tests/produced-files.client.spec.tsx index 468dd242fa..89cbfb8a58 100644 --- a/packages/client/ui-deliverables/tests/produced-files.client.spec.tsx +++ b/packages/client/ui-deliverables/tests/produced-files.client.spec.tsx @@ -27,7 +27,6 @@ import { type DeliverablesTurnData, } from '../src/client/turn-deliverables.ts' import { apply, inject } from '../src/client/index.ts' -import { apply as applyNode } from '../src/index.ts' import { apply as applyInvariant } from '../src/invariant.ts' import { en, zh } from '../src/client/locales.ts' @@ -438,9 +437,7 @@ describe('producedFileMentions resolver', () => { }) describe('package shells', () => { - it('the node half mounts inert and the invariant companion registers ownership', async () => { - // The node half is deliberately inert; mounting it must simply not throw. - applyNode() + it('the invariant companion registers ownership', async () => { const registered: string[] = [] const ctx = new Context() ctx.provide('invariants') diff --git a/packages/client/ui-deliverables/tests/prompt.client.spec.ts b/packages/client/ui-deliverables/tests/prompt.client.spec.ts new file mode 100644 index 0000000000..2ae6f0f1e3 --- /dev/null +++ b/packages/client/ui-deliverables/tests/prompt.client.spec.ts @@ -0,0 +1,30 @@ +/** Node-half coverage for the model guidance paired with Web file references. */ + +import { Context } from '@deepseek-ai/cordis' +import { afterEach, describe, expect, it } from 'vitest' +import SystemPrompt from '@deepseek-ai/dsh-system-prompt' +import { apply, inject } from '../src/index.ts' + +let ctx: Context | undefined + +afterEach(async () => { + await ctx?.fiber.dispose() + ctx = undefined +}) + +describe('ui-deliverables node plugin', () => { + it('registers final-response file-reference guidance only while mounted', async () => { + ctx = new Context() + await ctx.plugin(SystemPrompt, { persona: '' }) + const mounted = ctx.plugin({ apply, inject }) + await mounted.await() + + const section = (await ctx.systemPrompt.assemble()).sections + .find(entry => entry.name === 'ui:deliverable-file-references') + expect(section?.text).toMatchInlineSnapshot('"When you successfully create or modify files, mention the primary outputs in your final response. To make those and any other changed-file references clickable in Web, format them as Markdown inline code using the exact file-tool path, or a basename when unique among the files changed in that turn."') + + await mounted.dispose() + expect((await ctx.systemPrompt.assemble()).sections + .some(entry => entry.name === 'ui:deliverable-file-references')).toBe(false) + }) +}) diff --git a/packages/client/ui-deliverables/tsconfig.json b/packages/client/ui-deliverables/tsconfig.json index 3fa938986f..5a403871cd 100644 --- a/packages/client/ui-deliverables/tsconfig.json +++ b/packages/client/ui-deliverables/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../support/invariants" + }, + { + "path": "../../core/system-prompt" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 364a560e38..8ca3fabc56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2213,6 +2213,9 @@ importers: '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt '@types/react': specifier: ~18.3.1 version: 18.3.31 diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 813175b307..a4a8234fc2 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -73,7 +73,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/ui-feedback': { kind: 'none', reason: 'Browser-side controls over the message-feedback sidecar; ratings and notes never enter the Session log, model context, or telemetry.' }, 'packages/client/ui-tool': { kind: 'none', reason: 'Browser-side Tool presentation layer; renders logged calls without changing model context.' }, - 'packages/client/ui-deliverables': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, 'packages/client/ui-task': { kind: 'none', reason: 'Browser-side read-only projection of ctx.tasks records; dsh-tool-tasks owns the model-facing behavior.' }, 'packages/client/ui-workflow-run': { kind: 'none', reason: 'Browser-side UI plugin layer; renders durable workflow records without changing model context.' }, 'packages/client/ui-slash': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' }, From 7c4c118456a3113456e87c666c16a874310258fd Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 12 Aug 2026 22:54:27 +0800 Subject: [PATCH 003/119] fix(web): reclaim overlay scrollbar gutter --- ...-composer-tab-gutter-reservation.i18n.yaml | 4 +- ...6-08-04-composer-tab-gutter-reservation.md | 2 + ...8-04-composer-tab-gutter-reservation.zh.md | 2 + ...-overlay-seat-width-compensation.i18n.yaml | 6 ++ ...omposer-overlay-seat-width-compensation.md | 36 +++++++++ ...oser-overlay-seat-width-compensation.zh.md | 36 +++++++++ apps/web/tests/composer-tab-geometry.e2e.ts | 76 ++++++++++--------- .../geometry.expected.md | 14 ++-- .../skeleton/ConversationRoot.module.css | 21 +++-- .../client/ui-theme/src/styles/scrollbar.css | 5 ++ 10 files changed, 153 insertions(+), 49 deletions(-) create mode 100644 .agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml create mode 100644 .agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md create mode 100644 .agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml index 8a9b6236ae..0082a6f991 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md -2026-08-04-composer-tab-gutter-reservation.md: 3b28c35c1f11676e41cabde76d1b0d16c688f034 -2026-08-04-composer-tab-gutter-reservation.zh.md: c357dd06c52a834c18d2e8a25246d4ad003db548 +2026-08-04-composer-tab-gutter-reservation.md: 2c2813371af073eee4bd80ce7813e77af3c0c319 +2026-08-04-composer-tab-gutter-reservation.zh.md: e450def2482218028875a48fce8842c88b5ba82e diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md index 3b28c35c1f..2c2813371a 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md +++ b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md @@ -2,6 +2,8 @@ Status: implemented +> Superseded for overlay views by [the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md): the overlay branch no longer reserves a gutter, and the seat compensates for the bar instead. Chat keeps the reservation described here. + English | [中文](2026-08-04-composer-tab-gutter-reservation.zh.md) ## Problem diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md index c357dd06c5..e450def248 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md @@ -2,6 +2,8 @@ Status: implemented +> 对覆盖视图已被 [座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md) 取代:覆盖分支不再预留滚动条槽,座位改为补偿滚动条宽度。Chat 仍保留本文所述的预留。 + [English](2026-08-04-composer-tab-gutter-reservation.md) | 中文 ## 问题 diff --git a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml new file mode 100644 index 0000000000..fd88ecbc93 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md +2026-08-12-composer-overlay-seat-width-compensation.md: 85281ed4301046672ff4c02320b196a397e6d163 +2026-08-12-composer-overlay-seat-width-compensation.zh.md: 7bcf060e651a828068ef207caaebd01fa2ffd7ae diff --git a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md new file mode 100644 index 0000000000..85281ed430 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md @@ -0,0 +1,36 @@ +# Agent Note: The overlay composer seat compensates for the bar instead of reserving a gutter + +Status: implemented + +English | [中文](2026-08-12-composer-overlay-seat-width-compensation.zh.md) + +## Problem + +The [composer-tab gutter reservation](2026-08-04-composer-tab-gutter-reservation.md) made the column's scroller reserve a scrollbar gutter unconditionally, so the composer seat measured the same width in Chat and in a view with a composer overlay. The cost was paid by every overlay view: the view's content column ended 8px short of the column's right edge, because the scroller reserved a gutter for a bar it never draws — the trajectory ledger owns its own scrollers and the outer box never scrolls. + +The trajectory table made that cost visible: its full-width row divider lines stopped 8px short of the pane edge, leaving a strip of whitespace at the right of every line and of the whole content column. + +## Decision + +The reservation now belongs to Chat alone. The overlay branch declares `scrollbar-gutter: auto`, so the view's content spans the full column; the overlay composer seat (absolutely positioned against the padding box) gives back the bar's width with `right: var(--dsh-scrollbar-width)`, so the input card still measures the same width as Chat's seat and does not move between tabs. + +The compensation value is not a literal: ui-theme's scrollbar.css defines `--dsh-scrollbar-width` (8px on the WebKit path) beside the `::-webkit-scrollbar` rule it mirrors, and the seat reads that variable. A change to the sheet's bar width reaches the compensation in the same reviewable diff as the bar itself. + +## Alternatives considered + +**Keep the unconditional reservation and shrink every overlay view.** The pre-fix behavior. It keeps one declaration for both tabs but taxes every overlay view with an 8px content column, which the trajectory ledger surfaced as visible whitespace. Rejected because the overlay views own their scrolling; they should not pay for Chat's bar. + +**Reserve on the overlay branch too and let the view bleed into the gutter.** More moving parts for the same result: the gutter would still exist on a box that never scrolls, and the view would have to break out of the content box to reclaim its width. + +**Accept the 4px card shift.** Dropping the reservation without compensating the seat would move the input card on every tab switch, which is exactly the symptom the earlier note fixed. Rejected: the card position is a deliberate cross-tab invariant. + +## Consequences + +- Chat keeps its reserved gutter and its stable card position; nothing changes on that tab. +- Overlay views (trajectory) span the full column; the trajectory ledger's divider lines reach the pane edge. +- The input card still holds one horizontal position across the Chat and Trajectory tabs, now by two mechanisms instead of one: Chat reserves, the overlay seat compensates. +- `--dsh-scrollbar-width` becomes a public ui-theme variable read outside ui-theme; the scrollbar-styles spec's indirection checks only scan `--dsh-scrollbar-thumb{,-hover}` rebinds, so the width variable is not covered by the pair gate. + +## Testing + +`apps/web/tests/composer-tab-geometry.e2e.ts` still asserts the card holds its position across tabs and now also asserts the split: Chat's scroller keeps `scrollbar-gutter: stable` and a nonzero band, while the overlay branch resolves `auto` with a zero band. The control cascade changed with the mechanism: it now drops the seat's `right` compensation (instead of dropping a gutter Chat never had on that branch) and measures the same 4px shift, proving the equal rectangles are not a tab switch that never reached layout. The committed golden records both states. diff --git a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md new file mode 100644 index 0000000000..7bcf060e65 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md @@ -0,0 +1,36 @@ +# Agent Note: 覆盖视图的 composer 座位改为补偿滚动条宽度,不再预留滚动条槽 + +Status: implemented + +[English](2026-08-12-composer-overlay-seat-width-compensation.md) | 中文 + +## 问题 + +[composer 标签页滚动条槽预留](2026-08-04-composer-tab-gutter-reservation.md) 让会话列滚动容器无条件预留一条滚动条槽,使 composer 座位在 Chat 与带 composer 覆盖的视图中测得相同宽度。代价由每个覆盖视图承担:视图内容列比列右边缘窄 8px,因为滚动容器为一条它从不绘制的滚动条预留了槽——trajectory 台账由视图内部自己的滚动容器滚动,外层盒子从不滚动。 + +trajectory 表格让这个代价显形:整行分隔线在面板右边缘前 8px 处停止,每条线右侧以及整个内容列右侧都留下一条空白带。 + +## 决策 + +预留现在只属于 Chat。覆盖分支声明 `scrollbar-gutter: auto`,视图内容占满整列;覆盖分支的 composer 座位(相对 padding box 绝对定位)用 `right: var(--dsh-scrollbar-width)` 让出滚动条宽度,使输入卡仍与 Chat 座位测得相同宽度,切换标签页时不移动。 + +补偿值不是字面量:ui-theme 的 scrollbar.css 在它镜像的 `::-webkit-scrollbar` 规则旁定义 `--dsh-scrollbar-width`(WebKit 路径 8px),座位读取该变量。样式表滚动条宽度一变,补偿就会与滚动条本身出现在同一次可审阅的 diff 中。 + +## 备选方案 + +**保留无条件预留,压缩每个覆盖视图。** 修复前行为。两个标签页一条声明,但每个覆盖视图都要付出 8px 内容列,trajectory 台账将其显现为可见空白。已拒绝:覆盖视图自己滚动,不应为 Chat 的滚动条买单。 + +**覆盖分支也预留,并让视图渗入滚动条槽。** 同样结果下更多活动部件:从不滚动的盒子上仍存在滚动条槽,视图还得突破内容盒才能取回宽度。 + +**接受 4px 卡片位移。** 去掉预留却不补偿座位,会在每次切换标签页时移动输入卡——正是前一份 note 修复的症状。已拒绝:卡片位置是刻意保持的跨标签页不变量。 + +## 后果 + +- Chat 保留滚动条槽与稳定的卡片位置;该标签页无任何变化。 +- 覆盖视图(trajectory)占满整列;trajectory 台账的分隔线到达面板右边缘。 +- 输入卡在 Chat 与 Trajectory 标签页间仍保持同一水平位置,现在由两种机制而非一种达成:Chat 预留,覆盖座位补偿。 +- `--dsh-scrollbar-width` 成为 ui-theme 对外、且被 ui-theme 之外读取的变量;scrollbar-styles 规格的间接层检查只扫描 `--dsh-scrollbar-thumb{,-hover}` 重绑,宽度变量不受成对门禁覆盖。 + +## 测试 + +`apps/web/tests/composer-tab-geometry.e2e.ts` 仍断言输入卡在标签页间保持位置,并新增断言拆分:Chat 滚动容器保持 `scrollbar-gutter: stable` 与非零槽宽,覆盖分支解析为 `auto` 且槽宽为零。控制级联随机制改变:现在移除座位的 `right` 补偿(而非移除该分支上 Chat 从未有过的槽),测得同样的 4px 位移,证明相等的矩形并非从未到达布局的标签页切换。提交的 golden 记录两种状态。 diff --git a/apps/web/tests/composer-tab-geometry.e2e.ts b/apps/web/tests/composer-tab-geometry.e2e.ts index bb43e20406..b491b1257c 100644 --- a/apps/web/tests/composer-tab-geometry.e2e.ts +++ b/apps/web/tests/composer-tab-geometry.e2e.ts @@ -11,12 +11,13 @@ // gets an absolutely positioned seat instead, laid out against the padding box, // which the scrollbar never reduces. // -// Without a shared reservation the two tabs disagree by exactly the bar's -// width for as long as the transcript overflows: the card jumps sideways on -// every tab switch, and inside Chat alone at the moment a growing transcript -// starts to scroll. The column reserves the gutter unconditionally -// (`scrollbar-gutter: stable`) and states the overlay branch as a scroll -// container on the same axes, so both edges are the same edge. +// The column handles the two edges without reserving the gutter on both: Chat +// keeps `scrollbar-gutter: stable` so its seat's content box never jumps as the +// transcript starts to scroll; the overlay branch does NOT reserve (the view +// owns its own scrollers, so a reserved gutter would only narrow the view's +// content by the bar's width), and the overlay seat instead gives back the +// bar's width (`right: var(--dsh-scrollbar-width)`) so both seats measure the +// same width and the card does not move. // // Only a real engine can show this. The seat's geometry is layout: jsdom gives // every element a zero-sized box and reports no scrollbar at all, so a unit spec @@ -33,12 +34,11 @@ // configuration: ui-theme's scrollbar.css gives `::-webkit-scrollbar` a width, // and a bar that occupies layout space is what the product actually draws. // -// The scenario runs that unreserved cascade in the page — `scrollbar-gutter: auto` -// on the scroller, `overflow: hidden` on the overlay branch — and measures the -// same two tabs through it, which is what keeps the equal rectangles above from -// being explained by a tab switch that never reached the layout. It is the -// reported symptom as a number: the card moves 4px, half the 8px band, on each -// edge. +// The scenario runs that unreserved cascade in the page — the overlay seat's +// `right` compensation dropped to 0 — and measures the same two tabs through +// it, which is what keeps the equal rectangles above from being explained by a +// tab switch that never reached the layout. It is the reported symptom as a +// number: the card moves 4px, half the 8px band, on each edge. // // Zero model calls: a seeded cold session renders from its log, and switching // tabs asks the host for nothing. A stray stream would fail loud with NO_ADAPTER. @@ -62,9 +62,9 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/composer-tab-geometry', * Absolute coordinates are deliberately absent: they depend on the sidebar's * laid-out width and on font metrics, so committing them would produce a fixture * that has to be re-recorded per platform. What is recorded is the distance - * between the two tabs' rectangles, which is zero when the reservation holds and + * between the two tabs' rectangles, which is zero when the compensation holds and * the bar's width when it does not — including under the control, so the golden - * carries the shift the unreserved cascade produces rather than only its absence. + * carries the shift the uncompensated cascade produces rather than only its absence. */ const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md') const MODE = webSnapshotMode() @@ -114,15 +114,15 @@ async function setMeasuredViewport( } /** - * The unreserved cascade, injected into the page: the reservation dropped and - * the overlay branch forced to a hidden box. `!important` beats the module + * The uncompensated cascade, injected into the page: the overlay seat's `right` + * compensation dropped to 0, so it measures the full padding box while Chat's + * seat still rides the reserved content box. `!important` beats the module * rules without a rebuild, and the id lets the control be lifted again in the * same session. */ const CONTROL_STYLE_ID = 'composer-tab-geometry-control' const CONTROL_CSS = ` -[data-conversation-scroll] { scrollbar-gutter: auto !important; } -[data-conversation-scroll]:has([data-conversation-composer-overlay]) { overflow: hidden !important; } +[data-conversation-scroll]:has([data-conversation-composer-overlay]) > [data-composer-seat] { right: 0 !important; } ` /** The column scroller and the input card as the browser lays them out, in one tab. */ @@ -221,9 +221,11 @@ async function compareTabs(page: Page): Promise { } /** - * Run the unreserved cascade in the page for one measurement, then lift it. + * Run the uncompensated cascade in the page for one measurement, then lift it: + * the overlay seat's `right` compensation dropped to 0, so it measures the + * full padding box while Chat's seat still rides the reserved content box. * @param page - the page under test. - * @returns the comparison as the column lays out without the reservation. + * @returns the comparison as the column lays out without the compensation. */ async function compareTabsWithoutReservation(page: Page): Promise { await page.evaluate(({ id, css }) => { @@ -268,7 +270,7 @@ async function openSeededSession(page: Page): Promise { * Render the golden body. * @param wide - comparison at the viewport where the card sits at its width cap. * @param narrow - comparison at the viewport where the card shrinks with the column. - * @param control - comparison at the wide viewport with the reservation removed. + * @param control - comparison at the wide viewport with the compensation removed. * @returns the golden body, without a trailing newline. */ function renderGeometry(wide: TabComparison, narrow: TabComparison, control: TabComparison): string { @@ -291,7 +293,7 @@ function renderGeometry(wide: TabComparison, narrow: TabComparison, control: Tab '', ...section(`Wide viewport (${String(WIDE_VIEWPORT.width)}px, card at its cap)`, wide), ...section(`Narrow viewport (${String(NARROW_VIEWPORT.width)}px, card shrinking with the column)`, narrow), - ...section('Wide viewport, reservation removed in the page (control)', control), + ...section('Wide viewport, seat compensation removed in the page (control)', control), ].join('\n').trimEnd() } @@ -322,22 +324,25 @@ describe('web e2e: input card position across view tabs', () => { await scaffold?.close() }) - it('reserves the same gutter in both tabs while the transcript scrolls', async () => { + it('reserves the gutter in Chat and lets Trajectory own its width', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-band')) await setMeasuredViewport(page, WIDE_VIEWPORT, false) // Vacuity guard, in two parts. A transcript that does not overflow gives // Chat no scrollbar, and a hidden or overlaid bar gives it no width; either - // would make the tabs agree without the reservation doing anything. + // would make the tabs agree without the compensation doing anything. await expect.poll(async () => (await measureTab(page)).scrolls, { timeout: 10_000 }).toBe(true) const comparison = await compareTabs(page) expect(comparison.chat.band).toBeGreaterThan(0) - // The reservation reaches both states, which is the whole point: the same - // band, on a box that scrolls and on one that only holds a view. + // Chat keeps the unconditional reservation so its seat's content box never + // jumps as the transcript starts to scroll. expect(comparison.chat.gutter).toBe('stable') - expect(comparison.trajectory.gutter).toBe('stable') - expect(comparison.trajectory.band).toBe(comparison.chat.band) + // The overlay branch does NOT reserve: the view owns its own scrollers, so + // a reserved gutter would only narrow the view's content by the bar's + // width. The seat compensates instead, which the next test asserts. + expect(comparison.trajectory.gutter).toBe('auto') + expect(comparison.trajectory.band).toBe(0) // Declared as a scroll container on both axes rather than left to compute: - // `overflow: hidden` would drop the reservation in WebKit, and a `visible` + // `overflow: hidden` would drop any reservation in WebKit, and a `visible` // horizontal axis computes to `auto` beside a scrolling one. expect(comparison.trajectory.overflowY).toBe('auto') expect(comparison.trajectory.overflowX).toBe('hidden') @@ -378,20 +383,21 @@ describe('web e2e: input card position across view tabs', () => { expect(tripwire.pageErrors).toEqual([]) }, 60_000) - it('moves the card again once the reservation is removed in the page', async () => { + it('moves the card again once the seat compensation is removed in the page', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-control')) await setMeasuredViewport(page, WIDE_VIEWPORT, false) // The control: without it, equal rectangles could also mean the tab switch - // never reached the layout. Under the unreserved cascade the Chat scroller - // keeps its bar and the Trajectory branch becomes a hidden box with none, - // and the card moves by half the band on each edge. + // never reached the layout. Under the unreserved cascade the overlay seat + // loses its `right` compensation and measures the full padding box, so the + // card moves by half the band on each edge. Chat's own reservation is + // untouched — that is the side that must not change. const comparison = await compareTabsWithoutReservation(page) - expect(comparison.chat.gutter).toBe('auto') + expect(comparison.chat.gutter).toBe('stable') expect(comparison.chat.band).toBeGreaterThan(0) expect(comparison.trajectory.band).toBe(0) expect(comparison.leftShift).toBe(comparison.chat.band / 2) expect(comparison.rightShift).toBe(comparison.chat.band / 2) - // Restoring the sheet restores the reservation, so the control cannot leak + // Restoring the sheet restores the compensation, so the control cannot leak // into the remaining measurements. const restored = await compareTabs(page) expect(restored.leftShift).toBe(0) diff --git a/apps/web/tests/snapshots/composer-tab-geometry/geometry.expected.md b/apps/web/tests/snapshots/composer-tab-geometry/geometry.expected.md index 9735019508..e95adaaa6f 100644 --- a/apps/web/tests/snapshots/composer-tab-geometry/geometry.expected.md +++ b/apps/web/tests/snapshots/composer-tab-geometry/geometry.expected.md @@ -5,9 +5,9 @@ - Chat: scrollbar-gutter stable, overflow hidden/auto - Chat scroller scrolls: true - Chat reserved band: 8px -- Trajectory: scrollbar-gutter stable, overflow hidden/auto +- Trajectory: scrollbar-gutter auto, overflow hidden/auto - Trajectory scroller scrolls: false -- Trajectory reserved band: 8px +- Trajectory reserved band: 0px - input card left edge moves between tabs: 0px - input card right edge moves between tabs: 0px - input card width changes between tabs: 0px @@ -17,19 +17,19 @@ - Chat: scrollbar-gutter stable, overflow hidden/auto - Chat scroller scrolls: true - Chat reserved band: 8px -- Trajectory: scrollbar-gutter stable, overflow hidden/auto +- Trajectory: scrollbar-gutter auto, overflow hidden/auto - Trajectory scroller scrolls: false -- Trajectory reserved band: 8px +- Trajectory reserved band: 0px - input card left edge moves between tabs: 0px - input card right edge moves between tabs: 0px - input card width changes between tabs: 0px -## Wide viewport, reservation removed in the page (control) +## Wide viewport, seat compensation removed in the page (control) -- Chat: scrollbar-gutter auto, overflow hidden/auto +- Chat: scrollbar-gutter stable, overflow hidden/auto - Chat scroller scrolls: true - Chat reserved band: 8px -- Trajectory: scrollbar-gutter auto, overflow hidden/hidden +- Trajectory: scrollbar-gutter auto, overflow hidden/auto - Trajectory scroller scrolls: false - Trajectory reserved band: 0px - input card left edge moves between tabs: 4px diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css index 1b72470feb..c6618fc624 100644 --- a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css @@ -272,12 +272,17 @@ .scrollBody:has([data-conversation-composer-overlay]) { position: relative; /* A clipping box nothing scrolls out of, stated as a scroll container on both - axes rather than `overflow: hidden`: WebKit honours the reservation above - only in the `overflow-y: auto` form, and a single-axis scroller computes - the other axis to `auto` - ([decision](../../../../../../.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). */ + axes rather than `overflow: hidden`: a single-axis scroller computes the + other axis to `auto`, and the seat's absolute positioning below needs the + box to stay a containing block in every engine. */ overflow-x: hidden; overflow-y: auto; + /* The view owns its own scrollers (the trajectory ledger scrolls inside the + view), so this box never scrolls here; reserving a gutter would only + narrow the view's content by the bar's width. Chat keeps the reservation + on the same element, so the seat below compensates with the bar's width + ([decision](../../../../../../.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). */ + scrollbar-gutter: auto; } .scrollBody:has([data-conversation-composer-overlay]) > .viewArea { @@ -288,7 +293,13 @@ .scrollBody:has([data-conversation-composer-overlay]) > .composerSeat { position: absolute; - right: 0; + /* Chat's seat rides this box's content box, which its own scrolling shortens + by the bar's width; the overlay seat is against the padding box, so it + must give back the same width to keep the input card in place across + tabs. Reads ui-theme's --dsh-scrollbar-width, which mirrors the WebKit + bar's layout width; Firefox's thin bar resolves to the same 8px on the + platforms this app targets, so the compensation holds there too. */ + right: var(--dsh-scrollbar-width); bottom: 0; left: 0; } diff --git a/packages/client/ui-theme/src/styles/scrollbar.css b/packages/client/ui-theme/src/styles/scrollbar.css index 31b5a8aabe..71f67d4f57 100644 --- a/packages/client/ui-theme/src/styles/scrollbar.css +++ b/packages/client/ui-theme/src/styles/scrollbar.css @@ -17,6 +17,11 @@ body { --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l1); --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l1); + /* The WebKit bar's layout width, mirrored by the ::-webkit-scrollbar rule + below. A surface that must align itself beside a space-consuming bar + (ConversationRoot's overlay composer seat) reads this instead of + hardcoding the number. */ + --dsh-scrollbar-width: 8px; } /* The two paths are mutually exclusive, and the gate is load-bearing rather From de97ea0ba8d40b1c81b81e15a1f6f8a691edb8b3 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 12 Aug 2026 22:54:38 +0800 Subject: [PATCH 004/119] fix(sidebar): synchronize collapsed rail motion --- ...psed-sidebar-shared-entry-motion.i18n.yaml | 6 ++++ ...2-collapsed-sidebar-shared-entry-motion.md | 33 +++++++++++++++++ ...ollapsed-sidebar-shared-entry-motion.zh.md | 33 +++++++++++++++++ .agents/notes/archived/manifest.json | 3 ++ packages/client/ui-sidebar/README.i18n.yaml | 4 +-- packages/client/ui-sidebar/README.md | 2 ++ packages/client/ui-sidebar/README.zh.md | 2 ++ .../src/client/SidebarRoot.module.css | 35 +++++++++++++------ .../ui-sidebar/src/client/SidebarRoot.tsx | 13 +++---- .../tests/sidebar-styles.client.spec.ts | 28 +++++++++++++-- packages/client/ui-workspace/README.i18n.yaml | 4 +-- packages/client/ui-workspace/README.md | 2 +- packages/client/ui-workspace/README.zh.md | 2 +- .../src/client/WorkspaceBrowser.module.css | 4 ++- .../src/client/WorkspaceBrowser.tsx | 10 +++--- .../tests/browser-styles.client.spec.ts | 6 ++++ 16 files changed, 157 insertions(+), 30 deletions(-) create mode 100644 .agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.i18n.yaml create mode 100644 .agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md create mode 100644 .agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md diff --git a/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.i18n.yaml new file mode 100644 index 0000000000..971bc98a0c --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md +2026-08-12-collapsed-sidebar-shared-entry-motion.md: c5bc18973db693cf9ba60800fbcf7720593dbad5 +2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md: b87ef6322d548061cc2e29c0e8f9896098c09041 diff --git a/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md b/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md new file mode 100644 index 0000000000..c5bc18973d --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md @@ -0,0 +1,33 @@ +# Agent Note: Collapsed sidebar upper controls share one entry motion + +Status: implemented +Archived: 2026-08-12 + +English | [中文](2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md) + +## Problem + +The collapsed sidebar rail renders four upper controls owned by two packages: the shell owns the toggle and New Session, while the workspace region owns add and search. Their opacity timing matched, but their geometry did not. Right-aligned controls moved with the narrowing column while left-aligned controls stayed fixed, so add appeared slower than search even under the same fade. + +The bottom settings control has a different role. It is pinned to the rail foot and must not join the upper controls' horizontal entry. + +## Decision + +At the rail settle point, the four upper 36px controls start from one left-anchored layout and share one `150ms` animation from `translateX(49px)` to their final 10px inset. The shell applies the translation to its toggle and New Session seats and once to the workspace region, so add and search inherit the same path without nested transforms. Opacity uses the same animation timeline. + +The settings seat uses a separate opacity-only keyframe with the same duration and easing. A page that starts collapsed renders the rail without an entry animation, and reduced-motion mode disables both keyframes. + +## Alternatives considered + +**Keep every rail control fixed at its final inset.** This removes the mismatch, but it also removes the requested horizontal entry from the four upper controls. + +**Animate each workspace button independently.** This would duplicate shell timing inside `ui-workspace` and could apply both a region and child transform. Translating the registered region once keeps animation ownership in the sidebar shell. + +**Translate the settings control with the upper controls.** Rejected because settings is a bottom-pinned foot action, not part of the upper control sequence. + +## Consequences + +- Toggle, New Session, add, and search follow the same horizontal coordinates throughout collapse. +- Settings fades at its final horizontal coordinate. +- Static collapsed renders retain their final geometry without startup motion. +- Style tests pin the shared animation assignments, translation distance, base anchors, and settings exception. diff --git a/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md b/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md new file mode 100644 index 0000000000..b87ef6322d --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 收起侧栏的上方控件共用同一进入动画 + +Status: implemented +Archived: 2026-08-12 + +[English](2026-08-12-collapsed-sidebar-shared-entry-motion.md) | 中文 + +## Problem + +收起侧栏轨道的四个上方控件由两个包渲染:外壳持有侧栏切换与新建会话,Workspace 区域持有添加和搜索。它们的透明度时序相同,但几何行为不同。右对齐控件会随栏变窄而移动,左对齐控件则保持不动,因此添加即使使用相同淡入,视觉上仍比搜索慢。 + +底部设置控件承担不同角色。它固定在轨道页脚,不能参与上方控件的横向进入。 + +## Decision + +轨道落位时,四个 36px 上方控件从同一个左对齐布局开始,共用一段 `150ms` 动画,从 `translateX(49px)` 移动到最终 10px 内边距。外壳把位移分别应用于侧栏切换、新建会话,并只对 Workspace 区域应用一次,因此添加与搜索会继承同一路径,不产生嵌套变换。透明度使用同一条动画时间线。 + +设置控件使用时长与缓动相同、但只改变透明度的独立关键帧。页面初始即为收起状态时不会播放进入动画;减少动态效果模式会禁用两段关键帧。 + +## Alternatives considered + +**把每个轨道控件固定在最终内边距。** 这能消除不一致,但也会移除四个上方控件所需的横向进入效果。 + +**分别为每个 Workspace 按钮添加动画。** 这会在 `ui-workspace` 中重复外壳时序,还可能同时应用区域与子控件变换。只移动一次已注册区域,可以让动画继续由侧栏外壳持有。 + +**让设置控件随上方控件一起移动。** 不予采纳,因为设置是固定在底部的页脚操作,不属于上方控件序列。 + +## Consequences + +- 侧栏切换、新建会话、添加与搜索在整个收起过程中使用相同横坐标。 +- 设置在最终横坐标上淡入。 +- 静态收起渲染保持最终几何,不播放启动动画。 +- 样式测试固定共用动画分配、位移距离、基础锚点与设置例外。 diff --git a/.agents/notes/archived/manifest.json b/.agents/notes/archived/manifest.json index a8377c346f..f8786470c7 100644 --- a/.agents/notes/archived/manifest.json +++ b/.agents/notes/archived/manifest.json @@ -97,6 +97,9 @@ "bug-fix/2026-08-10-web-favicon-dark-mode.i18n.yaml": "sha256:859c4399f9a017a68ba89552fdafa05e73c0599d94cee9551c84ea5b749a14f3", "bug-fix/2026-08-10-web-favicon-dark-mode.md": "sha256:4d17e247abd76ae3aed5fb4e075fd66a2838292f89f7021c82a79fe37ed905e6", "bug-fix/2026-08-10-web-favicon-dark-mode.zh.md": "sha256:7bbff8a3b7061c127afcc75cd2a8043b02a999b78c0180edd8f7e4807fcfe71d", + "bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.i18n.yaml": "sha256:3ce4f6e39e173fc304bf64deca9c95bcddc1dbb492e065ca8c267a7a40788588", + "bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md": "sha256:7b169aa4543edfc965de5a8b7b9e60aa9d9d5218693cd0b57908e2d482280723", + "bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md": "sha256:88db36c698800bf55c3c7531d6f92665576d978c29c15ff7d74215fb93376cb1", "feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918", "feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b", "feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69", diff --git a/packages/client/ui-sidebar/README.i18n.yaml b/packages/client/ui-sidebar/README.i18n.yaml index 562396d88f..ac3531b205 100644 --- a/packages/client/ui-sidebar/README.i18n.yaml +++ b/packages/client/ui-sidebar/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-sidebar/README.md -README.md: 9974118f69901de985e012e1b62f95a0bcee64c2 -README.zh.md: 11b0aa142cf62626ab6105e2c405d506e35349b0 +README.md: 7cc4fe0a722fe5f8cf0e983a0e3fdb30cd31bb64 +README.zh.md: 96b6f60f871758c126e6c387c6ce3f0993039af0 diff --git a/packages/client/ui-sidebar/README.md b/packages/client/ui-sidebar/README.md index 9974118f69..7cc4fe0a72 100644 --- a/packages/client/ui-sidebar/README.md +++ b/packages/client/ui-sidebar/README.md @@ -8,6 +8,8 @@ New Session starts the runtime's page-local frontend Session Intent. The runtime `SidebarRootComponentProps` composes the layout owner share, the global `useSessions` and `useWorkspaces` hooks, the declared `sidebar.workspaces` and `sidebar.settings` child slots, and injected `startSession` plus sidebar-toggle callbacks. There is no plugin store. +During a live collapse, the shell holds the expanded content at its current width while it fades out for 150ms. The four upper controls—the shell toggle and New Session plus add and search rendered through `sidebar.workspaces`—then share one 150ms fade and 49px leftward translation into the 56px rail, ending with the layout's 300ms column slide; every 36px control box follows the same path to the rail's 10px left inset. The bottom-pinned `sidebar.settings` control shares the fade timing but has no horizontal translation. A page that starts collapsed renders the rail statically, and reduced-motion mode disables both transitions. + Scrollbars in the column are a pointer affordance: the shell rebinds ui-theme's [scrollbar indirection](../ui-theme/README.md) to `transparent` whenever the pointer is outside it, and keeps the thumb drawn for 2s after the pointer leaves, so a list nobody is pointing at carries no bar. The reservation that keeps rows from moving belongs to the scrolling region ([ui-workspace](../ui-workspace/README.md)), so revealing a thumb never reflows. The foot is the `sidebar.settings` seat: the sidebar renders only the bottom-pinned layout slot and shares its column state (`wide`); ui-settings registers the trigger row and settings panel there. diff --git a/packages/client/ui-sidebar/README.zh.md b/packages/client/ui-sidebar/README.zh.md index 11b0aa142c..96b6f60f87 100644 --- a/packages/client/ui-sidebar/README.zh.md +++ b/packages/client/ui-sidebar/README.zh.md @@ -8,6 +8,8 @@ New Session 会启动运行时的页面局部前端 Session Intent。运行时 `SidebarRootComponentProps` 组合布局 owner share、全局 `useSessions` 和 `useWorkspaces` 钩子、已声明的 `sidebar.workspaces` 与 `sidebar.settings` 子 slot,以及注入的 `startSession` 与侧边栏切换回调。这里没有插件 store。 +实时收起时,外壳会把展开内容固定在当前宽度,并用 150ms 将其淡出。随后,上方四个控件——外壳的侧栏切换与新建会话,以及通过 `sidebar.workspaces` 渲染的添加和搜索——共用一次 150ms 的淡入和 49px 左移,在布局的 300ms 栏滑动结束时一起进入 56px 轨道;每个 36px 控件盒都会沿同一条路径到达轨道左侧 10px 的内边距。固定在底部的 `sidebar.settings` 控件只共用淡入时序,不发生横向位移。页面初始即为收起状态时会静态渲染轨道;减少动态效果模式会禁用两段过渡。 + 栏内的滚动条是一种指针可供性:只要指针不在栏内,外壳就把 ui-theme 的[滚动条间接层](../ui-theme/README.md)重新绑定为 `transparent`;指针离开后滑块再保留 2 秒,因此没人指向的列表不会带着滚动条。避免行位移的空间预留属于滚动区域本身([ui-workspace](../ui-workspace/README.md)),所以显示滑块不会引起重排。 页脚承载 `sidebar.settings`:侧边栏只渲染固定在底部的布局 slot,并共享其栏状态(`wide`);ui-settings 在此注册触发行和设置面板。 diff --git a/packages/client/ui-sidebar/src/client/SidebarRoot.module.css b/packages/client/ui-sidebar/src/client/SidebarRoot.module.css index 17333b5ccc..a0740d50fa 100644 --- a/packages/client/ui-sidebar/src/client/SidebarRoot.module.css +++ b/packages/client/ui-sidebar/src/client/SidebarRoot.module.css @@ -58,24 +58,35 @@ from { opacity: 0; } } -/* Rail controls hold hidden while the column slides shut, then fade in over - the slide's tail: .railIn applies at settle (150ms into the 0.3s AppFrame - track transition), so a 100ms delay + 150ms fade starts just before the - slide ends (250ms) and finishes at 400ms; `backwards` keeps them at - opacity 0 through the delay. Only a live collapse gets .railIn — a - refresh straight into the collapsed state renders statically. */ +/* At the 150ms rail settle, the four upper controls enter from the former + rail right edge over the remaining 150ms of the AppFrame track transition. + The bottom-pinned settings seat shares their opacity timeline but stays + horizontally fixed. Only a live collapse gets .railIn; a cold collapsed + render stays static. */ .railIn .iconButton, .railIn .newSession, +.railIn .regionArea { + animation: rail-in 150ms var(--ds-ease-in-out) backwards; +} + .railIn .footArea { - animation: rail-in 150ms var(--ds-ease-in-out) 100ms backwards; + animation: rail-fade-in 150ms var(--ds-ease-in-out) backwards; } @keyframes rail-in { + from { + opacity: 0; + transform: translateX(49px); + } +} + +@keyframes rail-fade-in { from { opacity: 0; } } -/* Logo row (figma pad (4,8,4,8)): brand left, panel toggle right-anchored — - the toggle is the rail's expand control and slides in with the right edge. */ +/* Logo row (figma pad (4,8,4,8)): expanded keeps the panel toggle at the + right edge; the rail gives every control the same base left anchor before + the shared entry translation is applied. */ .logoRow { flex: none; display: flex; @@ -93,6 +104,7 @@ height: 36px; padding: 0; margin-bottom: 12px; + justify-content: flex-start; } /* Brand group (figma I133:7632): the full wordmark rides the text ink @@ -185,6 +197,8 @@ } .collapsed .newSession { + align-self: flex-start; + width: 36px; height: 36px; padding: 0; margin: 0 0 12px; @@ -239,7 +253,8 @@ .fading > *, .railIn .iconButton, .railIn .newSession, - .railIn .footArea { + .railIn .footArea, + .railIn .regionArea { transition: none; animation: none; } diff --git a/packages/client/ui-sidebar/src/client/SidebarRoot.tsx b/packages/client/ui-sidebar/src/client/SidebarRoot.tsx index f464066f56..5f392f93b3 100644 --- a/packages/client/ui-sidebar/src/client/SidebarRoot.tsx +++ b/packages/client/ui-sidebar/src/client/SidebarRoot.tsx @@ -2,12 +2,13 @@ * Sidebar shell: column geometry only. Collapse is a slide plus crossfade: * content freezes at its expanded width (inline style) and fades out in place * while the sliding column (AppFrame grid tracks) clips it — nothing reflows - * mid-slide. At settle the wide-only content unmounts and the control rows - * snap to the 56px rail (one icon each, same top-down order) fading in as the - * slide ends. The workspace/session browsing region between the New Session - * button and the foot is the `sidebar.workspaces` registrant's, and the foot - * is the `sidebar.settings` registrant's; the shell hands them the wide flag - * (plus an expand request callback for the browser). + * mid-slide. At settle the wide-only content unmounts and the four upper + * controls enter the 56px rail from the same horizontal offset (one icon each, + * same top-down order) on one fade that ends with the slide. The bottom-pinned + * settings control only fades. The workspace/session browsing region between + * the New Session button and the foot is the `sidebar.workspaces` registrant's, + * and the foot is the `sidebar.settings` registrant's; the shell hands them the + * wide flag (plus an expand request callback for the browser). * * The column also owns whether the scroll regions nested in it draw a * scrollbar at all: the shell tracks the pointer and rebinds ui-theme's diff --git a/packages/client/ui-sidebar/tests/sidebar-styles.client.spec.ts b/packages/client/ui-sidebar/tests/sidebar-styles.client.spec.ts index c4abce1911..19e7a54380 100644 --- a/packages/client/ui-sidebar/tests/sidebar-styles.client.spec.ts +++ b/packages/client/ui-sidebar/tests/sidebar-styles.client.spec.ts @@ -1,4 +1,4 @@ -/** Sidebar shell inset contract shared with the nested workspace browser. */ +/** Sidebar shell style contracts shared with its slot-owned controls. */ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' @@ -25,7 +25,7 @@ function declarations(selector: string): Map | undefined { return undefined } -describe('SidebarRoot.module.css inset', () => { +describe('SidebarRoot.module.css', () => { it('shares and cancels the wide shell trailing padding structurally', () => { const root = declarations('.root') expect(root?.get('--dsh-sidebar-inline-padding')).toBe('12px') @@ -39,4 +39,28 @@ describe('SidebarRoot.module.css inset', () => { expect(declarations('.collapsed .regionArea')?.get('padding-left')).toBe('0') expect(declarations('.collapsed .regionArea')?.get('margin-right')).toBe('0') }) + + it('moves the four upper controls while the settings seat only fades', () => { + const animation = 'rail-in 150ms var(--ds-ease-in-out) backwards' + for (const selector of [ + '.railIn .iconButton', + '.railIn .newSession', + '.railIn .regionArea', + ]) { + expect(declarations(selector)?.get('animation')).toBe(animation) + } + expect(declarations('.railIn .footArea')?.get('animation')).toBe( + 'rail-fade-in 150ms var(--ds-ease-in-out) backwards', + ) + expect(css).toMatch( + /@keyframes rail-in\s*\{\s*from\s*\{\s*opacity: 0;\s*transform: translateX\(49px\);\s*}\s*}/, + ) + expect(css).toMatch(/@keyframes rail-fade-in\s*\{\s*from\s*\{\s*opacity: 0;\s*}\s*}/) + }) + + it('gives shell rail controls the same base anchor for their shared translation', () => { + expect(declarations('.collapsed .logoRow')?.get('justify-content')).toBe('flex-start') + expect(declarations('.collapsed .newSession')?.get('align-self')).toBe('flex-start') + expect(declarations('.collapsed .newSession')?.get('width')).toBe('36px') + }) }) diff --git a/packages/client/ui-workspace/README.i18n.yaml b/packages/client/ui-workspace/README.i18n.yaml index 7a50937c41..6d5eff023b 100644 --- a/packages/client/ui-workspace/README.i18n.yaml +++ b/packages/client/ui-workspace/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md -README.md: 9d7d4d77cc064146f1fdaed615509215c64308fc -README.zh.md: ca35d7cd2e7ff176f4ea40d1e9a3a6d1a7457462 +README.md: 8878aa49dcccd60ddcde5f0a9563bbfbd969c9c0 +README.zh.md: c83f1c53d5471dd8d52b933163a573eb98a70c2e diff --git a/packages/client/ui-workspace/README.md b/packages/client/ui-workspace/README.md index 9d7d4d77cc..8878aa49dc 100644 --- a/packages/client/ui-workspace/README.md +++ b/packages/client/ui-workspace/README.md @@ -6,7 +6,7 @@ Shared Workspace browser and picker plugin. `WorkspaceBrowser` fills the sidebar The browser renders grouped or flat Session rows from the global runtime hooks and owns Workspace add/rename/reorder plus Session reorder. A Workspace remembers whether it is closed or showing Sessions; an open Workspace shows five Sessions by default, offers a transient **Show more** control for the remainder, and returns to five after the whole Workspace is closed and reopened. Creating a Session from a Workspace row first opens that group so the new row remains visible when the Session state arrives. Once the Workspace list baseline is ready, browser-persisted expansion and Session-order records retain only current Workspace ids plus Ungrouped and the flat-list account. View options combine grouping with one browser-persisted Session order per account: real Workspaces initialize from `WorkspaceView.sessionIds`, while Ungrouped and the cross-Workspace flat list initialize from recency. **Manual** and **Last updated** apply in either presentation. Entering Last updated performs a complete recency sort and later user prompts or steers promote their Session once, while entering Manual preserves every current position and disables later promotion. Dragging edits the current order in either mode; Manual-mode drags for real Workspaces also update the Host Session account, while Ungrouped and flat-list orders remain browser-local because neither has one Workspace account. Flat rows omit the empty leading status slot because they have no parent hierarchy, but retain it when a Session status is visible. Workspace drag order is Host-durable in either Session order mode. -Collapsed search is one header action beside the view and add actions. Activating it expands the field across the header; an outside click collapses only a query that is empty after trimming, while the clear control always resets and collapses it. A non-blank search query replaces either browsing mode with one flat result list: case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. The English search input and its defensive request path remove NUL, cap the query at the wire schema's 500 UTF-16 code units without splitting a surrogate pair, and preserve the existing debounce and cancellation behavior. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20, asks the user to narrow broader queries, and opens the selected Session without clearing the query or jumping to a specific event. +Collapsed search is one header action beside the view and add actions. In the rail, add and search render as 36px controls on the shell's shared horizontal entry path. Activating search expands the field across the header; an outside click collapses only a query that is empty after trimming, while the clear control always resets and collapses it. A non-blank search query replaces either browsing mode with one flat result list: case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. The English search input and its defensive request path remove NUL, cap the query at the wire schema's 500 UTF-16 code units without splitting a surrogate pair, and preserve the existing debounce and cancellation behavior. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20, asks the user to narrow broader queries, and opens the selected Session without clearing the query or jumping to a specific event. The picker lists real Host Workspace entities through the global `useWorkspaces` hook. Selecting a Workspace invokes the slot owner's `onPick` callback to retarget the frontend Session object. Distinct canonical paths remain separate id-keyed Workspaces when their basenames and display titles match; the sidebar hover detail exposes the full path. Each registration declares a **directory-flow child hole** (`single` kind: `conversation.hero.workspace.directoryFlow` / `sidebar.workspaces.directoryFlow`) that the composed picker package's client half fills with its picking interaction — the [`-native`](../../host/directory-picker-native/README.md) backend's renderless OS-chooser driver today, an in-app browsing dialog under a `-browse` composition. The flat **Add workspace...** action renders only while the surface's hole is occupied (occupancy read per menu render; an empty hole means the composition has no picking affordance — the seam's documented no-flow default, under which the sidebar header drops its add button rather than offering a dead one). This package owns the trigger and the adoption: the occupant reports one picked path per open through the hole's owner conversation (`open`/`busy`/`onPicked`/`onCancel`/`onError`), and the owner adopts it through the object layer, selecting the committed Workspace only after its list projection has refreshed; cancellation is silent, and errors land in the retryable folder dialog whose **Choose again** reopens the flow. Adding has exactly one route: the occupant's own create-folder affordance already covers a brand-new directory, so no separate create-by-name dialog exists. A menu only appears where there is something to choose between — with no Workspace listed, the anchor gesture raises the flow directly instead of a one-row popover, and it waits for the list baseline before treating an empty list as final. The runtime Session and Workspace services own materialization. The Workspace row's Delete action opens a confirmation that states the retention boundary, blocks duplicate submission, and keeps failures open; success removes the group while its Sessions remain under Ungrouped. The Session row's Rename action opens the same browser-owned dialog pattern prefilled with the row's display title: no client-side conflict rule exists (the host normalizes and may reject with `title-invalid`, rendered in the dialog alert), and confirming an unchanged title is deliberately allowed — it pins the current automatic title against regeneration. The Session row's Archive action commits without a confirmation dialog (non-destructive: the log and the workspace accounting slot remain) through `ctx.workspaces.archiveSession`; the row disappears from every grouping surface — workspace groups, Ungrouped, content search, and the flat list — when the archive-set echo lands, and failures are console diagnostics that leave the tree unchanged. A blank New Session row is a pure placeholder: it renders no row menu and no time label (nothing has happened in it yet), so rename, fork, and archive first apply once the first prompt lands. diff --git a/packages/client/ui-workspace/README.zh.md b/packages/client/ui-workspace/README.zh.md index ca35d7cd2e..c83f1c53d5 100644 --- a/packages/client/ui-workspace/README.zh.md +++ b/packages/client/ui-workspace/README.zh.md @@ -6,7 +6,7 @@ 该浏览器通过全局运行时钩子将 Session 行渲染为分组或扁平形式,并负责 Workspace 添加/重命名/重排序以及 Session 重排序。每个 Workspace 会记住自身是关闭还是显示 Session;打开后默认显示五条 Session,其余条目通过临时的**展开其余**控件显示,而关闭并重新打开整个 Workspace 后会恢复为五条。从 Workspace 行创建 Session 时会先打开该分组,使 Session 状态到达后新行保持可见。Workspace 列表基线就绪后,浏览器持久化的展开状态与 Session 顺序记录只保留当前 Workspace id、Ungrouped 和单列表记账。视图选项把分组方式和每个记账各自的一份浏览器持久化 Session 顺序放在一起:真实 Workspace 从 `WorkspaceView.sessionIds` 初始化,Ungrouped 和跨 Workspace 的单列表则从最近更新时间顺序初始化。**手动排序**和**最近更新**在两种呈现方式下都可用。进入最近更新时会执行一次完整的时间排序,后续 user prompt 或 steer 会将对应 Session 置顶一次;进入手动排序则保留所有当前位置并停用后续置顶。两种模式下的拖拽都会编辑当前顺序;真实 Workspace 在手动模式下的拖拽还会更新 Host Session 记账,而 Ungrouped 和单列表因没有单一 Workspace 记账,其顺序始终只保存在浏览器本地。单列表没有父级层次,因此不显示空的左侧状态槽;Session 存在可见状态时仍保留该槽。无论采用哪种 Session 顺序,Workspace 拖拽顺序都由 Host 持久化。 -折叠搜索是视图和添加操作旁的一枚区头按钮。激活后,输入框会扩展并占据区头;点击外部只会收起经清除首尾空白后为空的查询,而清除控件总会重置并收起搜索。非空白查询会以单一扁平结果列表替代任一浏览模式:不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。英文搜索输入框及其防御性请求路径会移除 NUL,将查询限制在传输 schema 规定的 500 个 UTF-16 代码单元内且不会拆分代理项对,并保留现有的防抖与取消行为。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果,并会在查询过宽时提示用户缩小范围;打开所选 Session 时既不会清除查询,也不会跳转至特定事件。 +折叠搜索是视图和添加操作旁的一枚区头按钮。在轨道中,添加和搜索会渲染为沿外壳共用横向进入路径移动的 36px 控件。激活搜索后,输入框会扩展并占据区头;点击外部只会收起经清除首尾空白后为空的查询,而清除控件总会重置并收起搜索。非空白查询会以单一扁平结果列表替代任一浏览模式:不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。英文搜索输入框及其防御性请求路径会移除 NUL,将查询限制在传输 schema 规定的 500 个 UTF-16 代码单元内且不会拆分代理项对,并保留现有的防抖与取消行为。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果,并会在查询过宽时提示用户缩小范围;打开所选 Session 时既不会清除查询,也不会跳转至特定事件。 该选择器通过全局 `useWorkspaces` hook 列出真实的 Host Workspace 实体。选择 Workspace 会调用 slot owner 的 `onPick` 回调,重新定位前端 Session 对象。不同的规范化路径即使 basename 和显示标题相同,仍会作为由 id 区分的独立 Workspace;侧边栏的悬停详情会显示完整路径。每个注册各自声明一个**目录流子 slot**(`single` kind:`conversation.hero.workspace.directoryFlow`/`sidebar.workspaces.directoryFlow`),由组合的选择器包 client half 填入其选取交互——今天是 [`-native`](../../host/directory-picker-native/README.md) 后端的无渲染 OS 选择器驱动,`-browse` 组合下则是应用内浏览对话框。平铺显示的 **添加工作区…** 操作仅在当前界面的 slot 被占用时渲染(每次菜单渲染读取占用状态;slot 为空意味着该组合没有目录选择能力——seam 文档化的无流程默认行为,此时侧边栏区头直接不渲染添加按钮,而非留下一个点了没反应的按钮)。本包持有触发与接纳:占用方通过 slot 的属主交互约定(`open`/`busy`/`onPicked`/`onCancel`/`onError`)每次打开上报一个所选路径,owner 通过对象层接纳它,并等待 Workspace 列表投影刷新后才选中已提交的 Workspace;取消操作不会显示提示,错误落入可重试的文件夹对话框,其 **重新选择** 会重新打开流程。添加只有一条路径:占用者自带的新建文件夹能力已经覆盖了全新目录,因此不再单设按名称创建的对话框。菜单只在确有多个目标可选时出现——没有 Workspace 可列时,锚点手势直接拉起流程,而不是弹出只有一行的浮层;在列表基线落地前,空列表不算最终结果。运行时 Session 与 Workspace 服务负责物化。Workspace 行内的 Delete 操作会打开确认框,说明保留边界、阻止重复提交,并在失败时保持打开;成功后,该分组会被移除,其 Session 则留在 Ungrouped 下。Session 行内的 Rename 操作打开同款浏览器持有的对话框,并以该行的显示标题预填:客户端不设名称冲突规则(host 负责规范化,可能以 `title-invalid` 拒绝,错误渲染在对话框告警区);确认未修改的标题是有意允许的——这正是把当前自动标题钉住、不再被重新生成覆盖的手势。Session 行内的 Archive 操作不经确认对话框直接提交(非破坏性:日志和 workspace 记账席位保持不变),通过 `ctx.workspaces.archiveSession` 归档;归档集合回声落地后,该行从所有分组视图——workspace 分组、Ungrouped、内容搜索和平铺列表——中消失,失败只作为控制台诊断输出,树保持不变。空白的「新会话」行只是占位符:不渲染行菜单和时间标签(其中还没有发生任何事),重命名、fork 和归档都从首条提示词落地后才可用。 diff --git a/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css b/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css index a160f2ffd7..4d19163322 100644 --- a/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css +++ b/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css @@ -240,11 +240,13 @@ /* Rail variant (own .rail class from the wide owner prop — the region never reads the shell's class names): the two icon controls stack as 36x36 - circles matching the shell's rail rhythm. */ + circles matching the shell's rail rhythm. Both use the rail's shared base + left anchor so the outer shell can translate the whole region uniformly. */ .rail .sectionHeader { gap: 0; padding-left: 0; margin-bottom: 12px; + justify-content: flex-start; } .rail .headerActions { diff --git a/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx b/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx index e9d6ed192f..ebd0e11a33 100644 --- a/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx +++ b/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx @@ -3,11 +3,11 @@ * `sidebar.workspaces` hole: section header (title + view options + add * workspace), search, the grouped tree or flat list, and the workspace * dialogs. Wide state renders the full browser; rail state renders the two - * region icons (search / add workspace), each requesting shell expansion - * through the owner share. Adding is the header button's one action, so it - * raises the directory flow with no menu in between; the flow and its error - * dialog live in WorkspacePicker (same package — direct composition, no slot - * between them). + * region icons (search / add workspace) as 36px controls on the shell's shared + * rail entry path, each requesting expansion through the owner share. Adding is the header + * button's one action, so it raises the directory flow with no menu in + * between; the flow and its error dialog live in WorkspacePicker (same + * package — direct composition, no slot between them). */ import { useEffect, useMemo, useRef, useState } from 'react' import clsx from 'clsx' diff --git a/packages/client/ui-workspace/tests/browser-styles.client.spec.ts b/packages/client/ui-workspace/tests/browser-styles.client.spec.ts index d66baef917..5554c977bb 100644 --- a/packages/client/ui-workspace/tests/browser-styles.client.spec.ts +++ b/packages/client/ui-workspace/tests/browser-styles.client.spec.ts @@ -107,4 +107,10 @@ describe('WorkspaceBrowser.module.css list', () => { expect(rowDeclarations('.sessionRow.selected')?.get('background')) .toBe('var(--dsw-alias-interactive-bg-hover)') }) + + it('pins both rail controls to the shared left anchor during the column slide', () => { + expect(declarations('.rail .sectionHeader')?.get('justify-content')).toBe('flex-start') + expect(declarations('.rail .iconButton')?.get('width')).toBe('36px') + expect(declarations('.rail .search')?.get('width')).toBe('36px') + }) }) From ece86450804ef4343a9db400445b82a7ccc68290 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 12 Aug 2026 23:28:15 +0800 Subject: [PATCH 005/119] fix: suppress runtime context in minimal profiles --- ...nimal-preset-owns-rl-composition.i18n.yaml | 4 +- ...8-10-minimal-preset-owns-rl-composition.md | 4 +- ...0-minimal-preset-owns-rl-composition.zh.md | 4 +- ...0-current-sandbox-policy-context.i18n.yaml | 4 +- ...26-07-30-current-sandbox-policy-context.md | 2 +- ...07-30-current-sandbox-policy-context.zh.md | 2 +- ...l-profiles-bare-two-tool-runtime.i18n.yaml | 4 +- ...-minimal-profiles-bare-two-tool-runtime.md | 8 +- ...nimal-profiles-bare-two-tool-runtime.zh.md | 8 +- .../agent-presets/minimal/agent.cordis.yml | 7 +- apps/web/tests/minimal-preset.snapshot.ts | 3 + docs/config-catalog.i18n.yaml | 4 +- docs/config-catalog.md | 15 +- docs/config-catalog.zh.md | 15 +- docs/subsystems/system-prompt.i18n.yaml | 4 +- docs/subsystems/system-prompt.md | 10 +- docs/subsystems/system-prompt.zh.md | 10 +- examples/jsonrpc-agent/README.i18n.yaml | 4 +- examples/jsonrpc-agent/README.md | 2 +- examples/jsonrpc-agent/README.zh.md | 2 +- examples/jsonrpc-agent/minimal.cordis.yml | 4 +- examples/jsonrpc-agent/tests/sdk.snapshot.ts | 33 ++-- .../notifications.expected.jsonl | 143 +++++++++--------- .../snapshots/persistent-tools/session.jsonl | 143 +++++++++--------- packages/core/system-prompt/README.i18n.yaml | 4 +- packages/core/system-prompt/README.md | 7 +- packages/core/system-prompt/README.zh.md | 7 +- packages/core/system-prompt/src/index.ts | 49 ++++-- .../core/system-prompt/tests/scoped.spec.ts | 17 +++ .../system-prompt/tests/system-prompt.spec.ts | 19 +++ .../agent-spine-demo/README.i18n.yaml | 4 +- packages/examples/agent-spine-demo/README.md | 4 +- .../examples/agent-spine-demo/README.zh.md | 4 +- .../examples/agent-spine-demo/src/index.ts | 11 +- .../agent-spine-demo/tests/agent-core.spec.ts | 9 ++ packages/preset/persona/README.i18n.yaml | 4 +- packages/preset/persona/README.md | 5 +- packages/preset/persona/README.zh.md | 5 +- packages/preset/persona/src/index.ts | 4 + packages/preset/persona/tests/persona.spec.ts | 37 +++++ .../tool-cordis/src/api-catalog.ts | 4 + 41 files changed, 400 insertions(+), 233 deletions(-) diff --git a/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.i18n.yaml index cca3debb14..0b27ea5d9f 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md -2026-08-10-minimal-preset-owns-rl-composition.md: d83083de74f1d3203822d82e4e04560819412b48 -2026-08-10-minimal-preset-owns-rl-composition.zh.md: 36aeea0d8af6e371eddd6f09e36bb48a0348959e +2026-08-10-minimal-preset-owns-rl-composition.md: 65d24f9a03eedffac34f0c0141a8e2f643a48b7b +2026-08-10-minimal-preset-owns-rl-composition.zh.md: 72eaa0e5bba46743abd336e64316c886fa58631a diff --git a/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md b/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md index d83083de74..65d24f9a03 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md +++ b/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md @@ -14,13 +14,13 @@ The split also hid other drift. The preset mounted one-shot Bash rather than the The shipped Web `minimal` preset is the sole Web owner of the RL agent composition. It declares an entry-local PTY registry and local backend, persistent `bash` with the RL environment description and 300-second timeout, and `str_replace_editor`. Tool presentation remains a deployment choice. The later [bare two-tool runtime decision](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md) supersedes this note's original compaction and filesystem-provider choices: the current preset mounts an entry-local `fs-local` provider and no compaction backend. The editor accepts no `requireAbsolutePath` setting because absolute paths are its unconditional contract. -The preset persona is exactly `You are a helpful software engineer assistant.` and sets `complete: true`. A complete `PromptSection` participates in ordinary assembly so tools, contexts, variables, and cooperative listeners still resolve; after the `system-prompt/assemble` waterfall, the prompt registry restores a detached copy of that section as the sole system-prompt section. Multiple effective complete sections reject assembly. This final registry constraint prevents harness identity, Web orientation, tool guidance, or an assembly listener from appending prompt text. +The preset persona is exactly `You are a helpful software engineer assistant.`, sets `complete: true`, and suppresses runtime context for its agent scope. A complete `PromptSection` participates in ordinary assembly so tools, variables, and cooperative listeners still resolve; after the `system-prompt/assemble` waterfall, the prompt registry restores a detached copy of that section as the sole system-prompt section and discards every dynamic context contribution. Multiple effective complete sections reject assembly. These final registry constraints prevent harness identity, Web orientation, tool guidance, an assembly listener, sandbox policy, approval policy, delegation, or another dynamic context provider from adding model input. The process-wide `core-web.cordis.yml` patch is absent. Browser UI, workspace attachment, persistence, subprocess, sandbox, permission, model routing, and other cross-session services remain host-owned. Selecting `minimal` changes one agent's model-facing composition and shadows the host filesystem provider only for that agent, without changing other sessions in the Web process. ## Verification -System-prompt and persona package tests prove final complete-section enforcement, including waterfall mutation and duplicate rejection. The shipped-preset composition test asserts the exact prompt, Bash description, absolute editor schema, and two-tool catalog under the default native presentation. The keyless Web replay sends a real request through a `minimal` agent while global identity, Web-orientation text, and a test section are registered, asserts the entry-local filesystem is bare and compaction is absent, then executes two persistent Bash calls to prove environment and cwd state survive and executes the editor through an absolute path. +System-prompt and persona package tests prove final complete-section and runtime-context suppression, including waterfall mutation and duplicate rejection. The shipped-preset composition test asserts the exact prompt, Bash description, absolute editor schema, and two-tool catalog under the default native presentation. The keyless Web replay sends a real request through a `minimal` agent while global identity, Web-orientation text, dynamic policy contexts, and a test section are registered, asserts that no runtime-context snapshot exists, the entry-local filesystem is bare, and compaction is absent, then executes two persistent Bash calls to prove environment and cwd state survive and executes the editor through an absolute path. The standalone [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) is the complete two-tool composition for the bundled JSON-RPC runtime. The [bare two-tool runtime decision](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md) owns its launch-specific environment configuration, bare filesystem, and absence of compaction. Its keyless SDK replay asserts the assembled system prompt and two-tool catalog, executes persistent Bash across calls, and exercises the editor; the Python SDK tutorial provides the runnable entry point. diff --git a/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md b/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md index 36aeea0d8a..72eaa0e5bb 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md @@ -14,13 +14,13 @@ Status: implemented 随附的 Web `minimal` preset 是 RL agent 组合在 Web 中的唯一所有者。它声明 entry 本地的 PTY 注册表与本地后端、带 RL 环境描述且超时为 300 秒的持久 `bash`,以及 `str_replace_editor`。工具呈现仍由部署选择。后续的[裸双工具运行时决策](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md)取代了本记录最初的压缩与文件系统提供方选择:当前 preset 挂载 entry 本地的 `fs-local` 提供方,不挂载压缩后端。编辑器不接受 `requireAbsolutePath` 设置,因为要求绝对路径是它的无条件约定。 -preset persona 恰好是 `You are a helpful software engineer assistant.`,并设置 `complete: true`。complete `PromptSection` 参与常规组装,因此工具、上下文、变量和协作式监听器仍会解析;`system-prompt/assemble` waterfall(瀑布式事件)结束后,提示词注册表会将该段落的独立副本恢复为唯一的系统提示词段落。存在多个有效 complete 段时,组装会被拒绝。这项最终注册表约束可防止 harness 身份、Web 定位、工具引导或组装监听器追加提示词文本。 +preset persona 恰好是 `You are a helpful software engineer assistant.`,它设置 `complete: true`,并为其 agent 作用域抑制 runtime context。complete `PromptSection` 参与常规组装,因此工具、变量和协作式监听器仍会解析;`system-prompt/assemble` waterfall(瀑布式事件)结束后,提示词注册表会将该段落的独立副本恢复为唯一的系统提示词段落,并丢弃每个动态上下文贡献。存在多个有效 complete 段时,组装会被拒绝。这些最终注册表约束可防止 harness 身份、Web 定位、工具引导、组装监听器、沙箱策略、批准策略、委派或其他动态上下文提供方添加模型输入。 进程级 `core-web.cordis.yml` patch 不再存在。浏览器 UI、workspace 附加、持久化、子进程、沙箱、权限、模型路由及其他跨会话服务仍由宿主持有。选择 `minimal` 会改变一个 agent 面向模型的组合,并且仅为该 agent 遮蔽宿主文件系统提供方,不会改变 Web 进程中的其他会话。 ## 验证 -系统提示词与 persona 包测试证明了 complete 段的最终约束,包括 waterfall 修改与重复项拒绝。交付 preset 组合测试在默认原生呈现下断言精确的提示词、Bash 描述、要求绝对路径的编辑器 schema 和双工具目录。无密钥 Web 回放通过 `minimal` agent 发送一个真实请求,同时注册全局身份、Web 定位文本和一个测试段落;它断言 entry 本地文件系统是裸后端且压缩不存在,随后执行两次持久 Bash 调用,证明环境与 cwd 状态能够保留,并通过绝对路径执行编辑器。 +系统提示词与 persona 包测试证明了 complete 段最终约束与 runtime-context 抑制,包括 waterfall 修改与重复项拒绝。交付 preset 组合测试在默认原生呈现下断言精确的提示词、Bash 描述、要求绝对路径的编辑器 schema 和双工具目录。无密钥 Web 回放通过 `minimal` agent 发送一个真实请求,同时注册全局身份、Web 定位文本、动态策略上下文和一个测试段落;它断言不存在 runtime-context 快照、entry 本地文件系统是裸后端且压缩不存在,随后执行两次持久 Bash 调用,证明环境与 cwd 状态能够保留,并通过绝对路径执行编辑器。 独立的 [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) 是内置 JSON-RPC 运行时的完整双工具组合。[裸双工具运行时决策](../feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md)说明其启动方式专属的环境配置、裸文件系统和无压缩选择。其无密钥 SDK 回放会断言组装后的系统提示词与双工具目录,跨调用执行持久 Bash,并使用编辑器;Python SDK 教程提供可运行的入口。 diff --git a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml index 5455415389..695a161749 100644 --- a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md -2026-07-30-current-sandbox-policy-context.md: ab231d19aec4852369c0f1a13fc50c69ab8128f1 -2026-07-30-current-sandbox-policy-context.zh.md: 4ac493dcfff5c286d569a46c34686dc4b8746618 +2026-07-30-current-sandbox-policy-context.md: b4c657ba52ff26e2e295e3635201424a155738d0 +2026-07-30-current-sandbox-policy-context.zh.md: bd32b021ef8622dc6843a163b21714a263bc8bed diff --git a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md index ab231d19ae..b4c657ba52 100644 --- a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md +++ b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.md @@ -12,7 +12,7 @@ The sandbox policy already enforced and logged each session's file-effect mode, `dsh-sandbox-policy`, the owner of mode and workspace-root resolution, registers one `sandbox:policy` cache-safe context contribution. Every agent request resolves the active session directly through `ctx.sandboxPolicy.resolve({ session })`; there is no denial-history scan or process-local “last told” state. -The policy contribution is capability-neutral and present for every agent session. It does not maintain a second inventory of mounted backends or tools; model-visible schemas remain the authority for available operations, while the context conditions its claims on any available operation that the DSH file sandbox enforces. The [capability-neutral policy context decision](../simplification/2026-07-31-capability-neutral-sandbox-policy-context.md) supersedes the earlier family-registration mechanism while retaining this note's cache-safe delivery and durable snapshot design. +The policy contribution is capability-neutral and available to every agent session by default. A composition may suppress the complete runtime-context channel when its model interface intentionally excludes dynamic context; this does not disable policy enforcement. The contribution does not maintain a second inventory of mounted backends or tools; model-visible schemas remain the authority for available operations, while the context conditions its claims on any available operation that the DSH file sandbox enforces. The [capability-neutral policy context decision](../simplification/2026-07-31-capability-neutral-sandbox-policy-context.md) supersedes the earlier family-registration mechanism while retaining this note's cache-safe delivery and durable snapshot design. The contribution states only facts shared by every enforcement dialect. `read-only` says an available sandbox-enforced operation cannot modify files in the standing mode and directs the model to try an available tool normally, then follow any denial and escalation guidance that tool returns. `workspace-write` states the canonical session workspace with non-exclusive wording and summarizes, without enumerating, that some platform temporary areas may also be writable. `danger-full-access` says the DSH file sandbox does not restrict file modifications by available operations. Backend-selected temporary paths, `/dev/null`, runner readiness, exact tool availability, and other policy domains are absent because `resolve()` cannot establish them at request assembly. diff --git a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md index 4ac493dcff..bd32b021ef 100644 --- a/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md +++ b/.agents/notes/implemented/feature/2026-07-30-current-sandbox-policy-context.zh.md @@ -12,7 +12,7 @@ Status: implemented `dsh-sandbox-policy` 负责解析模式与工作区根目录,并注册一项缓存安全的 `sandbox:policy` 上下文贡献。每次 agent(智能体)请求都通过 `ctx.sandboxPolicy.resolve({ session })` 直接解析当前会话;不存在拒绝历史扫描或进程本地的「上次告知」状态。 -该策略贡献不依赖具体能力,并存在于每个 agent 会话中。它不会另行维护一份已挂载后端或工具清单;模型可见的 schema 仍是可用操作的权威来源,而上下文会将其声明限定在 DSH 文件沙箱所强制执行的任何可用操作上。[不依赖具体能力的策略上下文决策](../simplification/2026-07-31-capability-neutral-sandbox-policy-context.md)取代了较早的家族注册机制,同时保留本 Agent Note 的缓存安全交付与持久快照设计。 +该策略贡献不依赖具体能力,并默认可用于每个 agent 会话。当模型接口有意排除动态上下文时,组合可以抑制完整的 runtime-context 通道;这不会禁用策略强制机制。该贡献不会另行维护一份已挂载后端或工具清单;模型可见的 schema 仍是可用操作的权威来源,而上下文会将其声明限定在 DSH 文件沙箱所强制执行的任何可用操作上。[不依赖具体能力的策略上下文决策](../simplification/2026-07-31-capability-neutral-sandbox-policy-context.md)取代了较早的家族注册机制,同时保留本 Agent Note 的缓存安全交付与持久快照设计。 该贡献只说明所有强制执行方言所共有的事实。`read-only` 表明受沙箱强制执行的可用操作在常驻模式下无法修改文件,并指示模型正常尝试可用工具,随后遵循该工具返回的任何拒绝与升权引导。`workspace-write` 用非排他措辞说明规范化的会话工作区,并概述某些平台临时区域可能也可写,而不逐一列举。`danger-full-access` 表明 DSH 文件沙箱不会限制可用操作修改文件。后端选择的临时路径、`/dev/null`、runner 就绪状态、确切的工具可用情况和其他策略领域都不会出现,因为 `resolve()` 无法在请求组装时确定它们。 diff --git a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml index aae790e624..f9814a5866 100644 --- a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md -2026-08-11-minimal-profiles-bare-two-tool-runtime.md: cf48c71214420443411bf91253b5e3a1c72a2c6c -2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: 5cfe819e1cd18f9c9a3c8d63ab9bf8e34b888a24 +2026-08-11-minimal-profiles-bare-two-tool-runtime.md: df61189c3072aff9c55154fa99b43ca50df50c3d +2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: 54548b493e3d7eb5c34c455f658138a1107ca5fe diff --git a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md index cf48c71214..df61189c30 100644 --- a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md +++ b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md @@ -12,7 +12,7 @@ The two launch paths also have different configuration owners. Web mounts a per- ## Decision -Both shipped minimal profiles expose exactly persistent `bash` and `str_replace_editor`, mount no context-compaction provider, and run the editor against `@deepseek-ai/dsh-fs-local`. The Web preset isolates `ctx.fs` inside the agent entry and mounts `fs-local` beside the editor, so other Web agents retain the host filesystem provider. Its persona remains the fixed complete prompt owned by the earlier [minimal-preset composition decision](../bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md). +Both shipped minimal profiles expose exactly persistent `bash` and `str_replace_editor`, mount no context-compaction provider, suppress every `dsh-system-prompt` runtime-context contribution for fresh sessions, and run the editor against `@deepseek-ai/dsh-fs-local`. The Web preset isolates `ctx.fs` inside the agent entry and mounts `fs-local` beside the editor, so other Web agents retain the host filesystem provider. Its persona remains the fixed complete prompt owned by the earlier [minimal-preset composition decision](../bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md) and applies runtime-context suppression only to that agent scope. The standalone spine forwards the same setting to its process-owned system-prompt service. Sandbox and approval services remain mounted and enforce their policies; only their model-facing dynamic context is absent. The standalone [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) remains a complete JSON-RPC process composition. It mounts `dsh-jsonrpc`, the local PTY and subprocess services required by persistent Bash, `fs-local`, the two tool consumers, and uncompressed JSONL persistence. It does not mount `token-meter`, `compact-basic`, `fs-sandbox`, or `fs-policy`. Persistent Bash still consumes the deployment's danger-full-access sandbox policy; the editor is not confined by that policy. @@ -20,9 +20,9 @@ The standalone [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal ## Verification -The Web replay boots the complete Web host, creates the agent through the preset service, and asserts that the scoped filesystem is bare, no scoped compaction service exists, and the assembled request contains exactly the fixed prompt and two tools. It then executes persistent Bash and the editor against the real scoped services. +The Web replay boots the complete Web host, creates the agent through the preset service, and asserts that the scoped filesystem is bare, no scoped compaction service exists, no system-prompt-owned runtime-context message was appended, and the assembled request contains exactly the fixed prompt and two tools. It then executes persistent Bash and the editor against the real scoped services. -The SDK replay boots the real JSON-RPC agent process through the SDK client, injects an environment-selected prompt, asserts the assembled prompt and exact two-tool catalog, and executes both tools. Python SDK bundled-runtime coverage initializes the standalone configuration through each available packaged carrier with environment-selected model, model capacity, and prompt values. Cordis validation checks that both configurations resolve their declared plugins and configuration fields. +The SDK replay boots the real JSON-RPC agent process through the SDK client, injects an environment-selected prompt, asserts the assembled prompt, exact two-tool catalog, and absence of every system-prompt-owned runtime-context message, and executes both tools. Python SDK bundled-runtime coverage initializes the standalone configuration through each available packaged carrier with environment-selected model, model capacity, and prompt values. Cordis validation checks that both configurations resolve their declared plugins and configuration fields. ## Alternatives considered @@ -36,4 +36,4 @@ The SDK replay boots the real JSON-RPC agent process through the SDK client, inj ## Consequences -Minimal sessions never summarize or replace earlier history; callers must keep turns within the selected model's context capacity. The editor can address any absolute path visible to the runtime process, independently of the persistent shell's sandbox policy. The two launch paths share their model-facing tool and no-compaction guarantees while retaining different prompt and model configuration appropriate to their owners. The Python SDK path continues to communicate only through the bundled stdio JSON-RPC runtime. +Minimal sessions never summarize or replace earlier history and never add a runtime-context snapshot; callers must keep turns within the selected model's context capacity and must not rely on model-visible narration of standing sandbox or approval policy. The editor can address any absolute path visible to the runtime process, independently of the persistent shell's sandbox policy. The two launch paths share their model-facing tool, no-context, and no-compaction guarantees while retaining different prompt and model configuration appropriate to their owners. The Python SDK path continues to communicate only through the bundled stdio JSON-RPC runtime. diff --git a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md index 5cfe819e1c..54548b493e 100644 --- a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md +++ b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md @@ -12,7 +12,7 @@ Web `minimal` preset 与独立 JSON-RPC minimal 组合对外提供持久 `bash` ## 决策 -两种随附 minimal profile 都只对外提供持久 `bash` 与 `str_replace_editor`,不挂载上下文压缩提供方,并让编辑器使用 `@deepseek-ai/dsh-fs-local`。Web preset 在 agent entry 内隔离 `ctx.fs`,将 `fs-local` 与编辑器一起挂载,因此其他 Web agent 仍使用宿主文件系统提供方。其 persona 继续采用较早的 [minimal preset 组合决策](../bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md)所拥有的固定 complete 提示词。 +两种随附 minimal profile 都只对外提供持久 `bash` 与 `str_replace_editor`,不挂载上下文压缩提供方,为新建会话抑制每个 `dsh-system-prompt` runtime-context 贡献,并让编辑器使用 `@deepseek-ai/dsh-fs-local`。Web preset 在 agent entry 内隔离 `ctx.fs`,将 `fs-local` 与编辑器一起挂载,因此其他 Web agent 仍使用宿主文件系统提供方。其 persona 继续采用较早的 [minimal preset 组合决策](../bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md)所拥有的固定 complete 提示词,并仅为该 agent 作用域实施 runtime-context 抑制。独立 spine 将同一设置转发给其进程拥有的 system-prompt 服务。沙箱与批准服务仍保持挂载并强制其策略;只有它们面向模型的动态上下文缺席。 独立的 [`minimal.cordis.yml`](../../../../examples/jsonrpc-agent/minimal.cordis.yml) 仍是完整的 JSON-RPC 进程组合。它挂载 `dsh-jsonrpc`、持久 Bash 所需的本地 PTY 和子进程服务、`fs-local`、两个工具消费方,以及未压缩的 JSONL 持久化。它不挂载 `token-meter`、`compact-basic`、`fs-sandbox` 或 `fs-policy`。持久 Bash 仍消费部署的 danger-full-access 沙箱策略;编辑器不受该策略限制。 @@ -20,9 +20,9 @@ Web `minimal` preset 与独立 JSON-RPC minimal 组合对外提供持久 `bash` ## 验证 -Web 回放会启动完整 Web 宿主,通过 preset 服务创建 agent,并断言作用域文件系统为裸后端、不存在作用域压缩服务,而且组装请求只包含固定提示词与两个工具。随后,它通过真实作用域服务执行持久 Bash 和编辑器。 +Web 回放会启动完整 Web 宿主,通过 preset 服务创建 agent,并断言作用域文件系统为裸后端、不存在作用域压缩服务、没有追加 system-prompt 拥有的 runtime-context 消息,而且组装请求只包含固定提示词与两个工具。随后,它通过真实作用域服务执行持久 Bash 和编辑器。 -SDK 回放通过 SDK 客户端启动真实 JSON-RPC agent 进程,注入由环境选择的提示词,断言组装提示词与精确双工具目录,并执行两个工具。Python SDK 内置运行时覆盖会通过每种可用的打包载体,使用环境选择的模型、模型容量和提示词值初始化独立配置。Cordis 校验会检查两份配置能否解析声明的插件和配置字段。 +SDK 回放通过 SDK 客户端启动真实 JSON-RPC agent 进程,注入由环境选择的提示词,断言组装提示词与精确双工具目录,另外断言不存在任何 system-prompt 拥有的 runtime-context 消息,并执行两个工具。Python SDK 内置运行时覆盖会通过每种可用的打包载体,使用环境选择的模型、模型容量和提示词值初始化独立配置。Cordis 校验会检查两份配置能否解析声明的插件和配置字段。 ## 考虑过的替代方案 @@ -36,4 +36,4 @@ SDK 回放通过 SDK 客户端启动真实 JSON-RPC agent 进程,注入由环 ## 后果 -Minimal 会话不会摘要或替换较早历史;调用方必须让会话轮次保持在所选模型的上下文容量内。编辑器可以访问运行时进程可见的任何绝对路径,且不受持久 shell 沙箱策略影响。两条启动路径共享面向模型的工具与无压缩保证,同时保留适合各自所有者的不同提示词和模型配置。Python SDK 路径继续仅通过内置 stdio JSON-RPC 运行时通信。 +Minimal 会话不会摘要或替换较早历史,也不会添加 runtime-context 快照;调用方必须让会话轮次保持在所选模型的上下文容量内,且不得依赖模型可见的常驻沙箱或批准策略说明。编辑器可以访问运行时进程可见的任何绝对路径,且不受持久 shell 沙箱策略影响。两条启动路径共享面向模型的工具、无上下文与无压缩保证,同时保留适合各自所有者的不同提示词和模型配置。Python SDK 路径继续仅通过内置 stdio JSON-RPC 运行时通信。 diff --git a/apps/cli/config/agent-presets/minimal/agent.cordis.yml b/apps/cli/config/agent-presets/minimal/agent.cordis.yml index 80c1cecb83..07e8aa173d 100644 --- a/apps/cli/config/agent-presets/minimal/agent.cordis.yml +++ b/apps/cli/config/agent-presets/minimal/agent.cordis.yml @@ -1,15 +1,16 @@ # The `minimal` agent preset: a fixed-prompt, two-tool coding-agent composition. # # The persona is the complete system prompt, so global identity, Web orientation, -# tool guidance, and later assembly listeners cannot add prompt text. The model -# composes only the persistent `bash` and `str_replace_editor` tools. Context -# compaction is deliberately absent. +# tool guidance, and later assembly listeners cannot add prompt text. Runtime +# context snapshots are suppressed for this preset, and the model composes only +# persistent `bash` and `str_replace_editor`. Context compaction is absent. - id: persona name: '@deepseek-ai/dsh-persona' config: text: You are a helpful software engineer assistant. complete: true + includeRuntimeContext: false # The PTY registry is an agent-owned service, so it lives in an entry-local # realm. The backend still consumes the host sandbox policy and subprocess diff --git a/apps/web/tests/minimal-preset.snapshot.ts b/apps/web/tests/minimal-preset.snapshot.ts index 51d6e074dc..0f66b0458e 100644 --- a/apps/web/tests/minimal-preset.snapshot.ts +++ b/apps/web/tests/minimal-preset.snapshot.ts @@ -55,6 +55,9 @@ describe('minimal agent preset', () => { const requestHeader = agentHandle.agent.session.requestHeader() if (requestHeader === undefined) throw new Error('the minimal agent issued no model request') + expect(agentHandle.agent.session.events.some(event => event.type === 'user/message' + && event.data.source.kind === 'plugin' + && event.data.source.plugin === '@deepseek-ai/dsh-system-prompt')).toBe(false) const presetFileSystem = scaffold.ctx.agentPresets.serviceFor(agentHandle.agent, 'fs') expect(presetFileSystem).toBeDefined() expect(presetFileSystem?.sandboxMode).toBeUndefined() diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 33fae42ac2..5cc1fd5f76 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/config-catalog.md -config-catalog.md: f075a5c22049df1696a1849cd88bd3b783adde21 -config-catalog.zh.md: 952a77df3ba279155b4364a083917f816efd2d63 +config-catalog.md: cdfff44e2de274247e6a30074459349a446932f3 +config-catalog.zh.md: 2b90c6862d26b7d362606db528e298a5b760f061 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index f075a5c220..cdfff44e2d 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -168,9 +168,10 @@ Source: [`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/ag /** * Bundle config: each field forwarded verbatim to the child that owns it — * `agents` to the agent loop (an app that pre-creates no agents, like the ACP - * bridge, simply omits it), `includeHarnessIdentity`, `persona`, and `toolOrder` - * to the system-prompt plugin (the fixed opener, deployment persona, and explicit - * model-facing tool order), the `tools` object to the tool registry (its presentation `mode`), + * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`, + * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener, + * dynamic-context policy, deployment persona, and explicit model-facing tool + * order), the `tools` object to the tool registry (its presentation `mode`), * `dshHome` to bash environment and local skill discovery, `sessionTitle` to * the fallback title service, `skills` to the * skill registry/local provider/tool consumer, `workspaceContext` to the @@ -194,6 +195,8 @@ export interface Config { maxParallelToolCalls?: AgentLoopConfig['maxParallelToolCalls'] /** Whether the system prompt includes the fixed Harness identity (default true). */ includeHarnessIdentity?: SystemPromptConfig['includeHarnessIdentity'] + /** Whether model history includes dynamic runtime-context snapshots (default true). */ + includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext'] /** The deployment persona (see dsh-system-prompt's `Config`). */ persona?: SystemPromptConfig['persona'] /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */ @@ -247,7 +250,7 @@ export interface GoalConfig { Depends on: [`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillLocal`](../packages/skill/skill-local/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`TasksConfig`](#deepseek-aidsh-tasks-local) · [`toolBash`](../packages/bash/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`toolTasks`](../packages/tasks/tool-tasks/src/index.ts) · [`workspaceContext`](../packages/context/workspace-context/src/index.ts) -Source: [`packages/examples/agent-spine-demo/src/index.ts:91`](../packages/examples/agent-spine-demo/src/index.ts) +Source: [`packages/examples/agent-spine-demo/src/index.ts:92`](../packages/examples/agent-spine-demo/src/index.ts) ## `@deepseek-ai/dsh-agent-tool-mode` @@ -1237,6 +1240,8 @@ export interface Config { text: string /** Make this persona the complete system prompt, suppressing every other section. */ complete?: boolean + /** Suppress dynamic runtime-context snapshots for this persona's agent scope. */ + includeRuntimeContext?: boolean } ``` @@ -2049,6 +2054,8 @@ Source: [`packages/e2b/subprocess-e2b/src/index.ts:25`](../packages/e2b/subproce export interface Config { /** Include the fixed DeepSeek Harness identity before the deployment persona (default true). */ includeHarnessIdentity?: boolean + /** Include dynamic runtime-context snapshots in model history (default true). */ + includeRuntimeContext?: boolean /** * Deployment-wide order-0 persona template. A scoped section named * `deployment:persona` shadows it; `{{variable}}` references are strict. diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 952a77df3b..2b90c6862d 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -170,9 +170,10 @@ export type PresetTrust = 'system' | 'user' /** * Bundle config: each field forwarded verbatim to the child that owns it — * `agents` to the agent loop (an app that pre-creates no agents, like the ACP - * bridge, simply omits it), `includeHarnessIdentity`, `persona`, and `toolOrder` - * to the system-prompt plugin (the fixed opener, deployment persona, and explicit - * model-facing tool order), the `tools` object to the tool registry (its presentation `mode`), + * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`, + * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener, + * dynamic-context policy, deployment persona, and explicit model-facing tool + * order), the `tools` object to the tool registry (its presentation `mode`), * `dshHome` to bash environment and local skill discovery, `sessionTitle` to * the fallback title service, `skills` to the * skill registry/local provider/tool consumer, `workspaceContext` to the @@ -196,6 +197,8 @@ export interface Config { maxParallelToolCalls?: AgentLoopConfig['maxParallelToolCalls'] /** Whether the system prompt includes the fixed Harness identity (default true). */ includeHarnessIdentity?: SystemPromptConfig['includeHarnessIdentity'] + /** Whether model history includes dynamic runtime-context snapshots (default true). */ + includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext'] /** The deployment persona (see dsh-system-prompt's `Config`). */ persona?: SystemPromptConfig['persona'] /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */ @@ -249,7 +252,7 @@ export interface GoalConfig { 依赖:[`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillLocal`](../packages/skill/skill-local/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`TasksConfig`](#deepseek-aidsh-tasks-local) · [`toolBash`](../packages/bash/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`toolTasks`](../packages/tasks/tool-tasks/src/index.ts) · [`workspaceContext`](../packages/context/workspace-context/src/index.ts) -来源:[`packages/examples/agent-spine-demo/src/index.ts:91`](../packages/examples/agent-spine-demo/src/index.ts) +来源:[`packages/examples/agent-spine-demo/src/index.ts:92`](../packages/examples/agent-spine-demo/src/index.ts) ## `@deepseek-ai/dsh-agent-tool-mode` @@ -1239,6 +1242,8 @@ export interface Config { text: string /** Make this persona the complete system prompt, suppressing every other section. */ complete?: boolean + /** Suppress dynamic runtime-context snapshots for this persona's agent scope. */ + includeRuntimeContext?: boolean } ``` @@ -2051,6 +2056,8 @@ export interface Config { export interface Config { /** Include the fixed DeepSeek Harness identity before the deployment persona (default true). */ includeHarnessIdentity?: boolean + /** Include dynamic runtime-context snapshots in model history (default true). */ + includeRuntimeContext?: boolean /** * Deployment-wide order-0 persona template. A scoped section named * `deployment:persona` shadows it; `{{variable}}` references are strict. diff --git a/docs/subsystems/system-prompt.i18n.yaml b/docs/subsystems/system-prompt.i18n.yaml index 3ac169b4e0..0b69a5ca8c 100644 --- a/docs/subsystems/system-prompt.i18n.yaml +++ b/docs/subsystems/system-prompt.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/system-prompt.md -system-prompt.md: f03d11866bcdcf3bcb5c5fe96931b059cb8d0336 -system-prompt.zh.md: 23b3e0b20cc4772baf22727d2e57866aa7cbbeb0 +system-prompt.md: 8a6006d0b96552b52d0b050c304a11a95add8d9e +system-prompt.zh.md: 02be9eed795d3a6c8e43403048f135d493b6509d diff --git a/docs/subsystems/system-prompt.md b/docs/subsystems/system-prompt.md index f03d11866b..8a6006d0b9 100644 --- a/docs/subsystems/system-prompt.md +++ b/docs/subsystems/system-prompt.md @@ -117,6 +117,14 @@ section(section: PromptSection): () => void */ context(context: PromptContext): () => void +/** + * Suppress every dynamic runtime-context contribution in the calling + * context's scope without changing the services that own or enforce those + * facts. Multiple suppressors remain independently disposable. + * @returns the exact Cordis effect disposer. + */ +suppressRuntimeContext(): () => void + /** * Register a tool-schema provider in the calling context's scope. Global and * matching scoped providers both contribute; returning the reserved @@ -148,7 +156,7 @@ variable(name: string, provider: (context: AssembleContext) => string | undefine async assemble(context: AssembleContext = {}): Promise ``` -Source: [`packages/core/system-prompt/src/index.ts:334`](../../packages/core/system-prompt/src/index.ts) +Source: [`packages/core/system-prompt/src/index.ts:338`](../../packages/core/system-prompt/src/index.ts) diff --git a/docs/subsystems/system-prompt.zh.md b/docs/subsystems/system-prompt.zh.md index 23b3e0b20c..02be9eed79 100644 --- a/docs/subsystems/system-prompt.zh.md +++ b/docs/subsystems/system-prompt.zh.md @@ -117,6 +117,14 @@ section(section: PromptSection): () => void */ context(context: PromptContext): () => void +/** + * Suppress every dynamic runtime-context contribution in the calling + * context's scope without changing the services that own or enforce those + * facts. Multiple suppressors remain independently disposable. + * @returns the exact Cordis effect disposer. + */ +suppressRuntimeContext(): () => void + /** * Register a tool-schema provider in the calling context's scope. Global and * matching scoped providers both contribute; returning the reserved @@ -148,7 +156,7 @@ variable(name: string, provider: (context: AssembleContext) => string | undefine async assemble(context: AssembleContext = {}): Promise ``` -Source: [`packages/core/system-prompt/src/index.ts:334`](../../packages/core/system-prompt/src/index.ts) +Source: [`packages/core/system-prompt/src/index.ts:338`](../../packages/core/system-prompt/src/index.ts) diff --git a/examples/jsonrpc-agent/README.i18n.yaml b/examples/jsonrpc-agent/README.i18n.yaml index d5c070643d..fb045ee0a1 100644 --- a/examples/jsonrpc-agent/README.i18n.yaml +++ b/examples/jsonrpc-agent/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write examples/jsonrpc-agent/README.md -README.md: 967f3f499962bf1fd1873fc16ac8fd8075b0df3b -README.zh.md: f84ab95132e820cf0fcf45bff4ae30d9bccb55c1 +README.md: 249a9cb8a3c6256ee92cca0dabeb7723cbca91d0 +README.zh.md: 64a5e607bd37e97b60cb0a4f6ee89a7c766d7464 diff --git a/examples/jsonrpc-agent/README.md b/examples/jsonrpc-agent/README.md index 967f3f4999..249a9cb8a3 100644 --- a/examples/jsonrpc-agent/README.md +++ b/examples/jsonrpc-agent/README.md @@ -30,7 +30,7 @@ Pass the config path through the Python SDK's `cordis` option or `DSH_CORDIS_CON ## Minimal variant -[`minimal.cordis.yml`](minimal.cordis.yml) is the complete standalone counterpart of the Web `minimal` preset. `DSH_SYSTEM_PROMPT` selects its system prompt, with `You are a helpful software engineer assistant.` as the fallback, and no context-compaction plugin is mounted. Its model-facing tools are exactly: +[`minimal.cordis.yml`](minimal.cordis.yml) is the complete standalone counterpart of the Web `minimal` preset. `DSH_SYSTEM_PROMPT` selects its system prompt, with `You are a helpful software engineer assistant.` as the fallback. It suppresses every system-prompt runtime-context contribution for fresh sessions and mounts no context-compaction plugin. Its model-facing tools are exactly: - owner-scoped persistent `bash` - `str_replace_editor` with `view`, `create`, `str_replace`, and `insert` diff --git a/examples/jsonrpc-agent/README.zh.md b/examples/jsonrpc-agent/README.zh.md index f84ab95132..64a5e607bd 100644 --- a/examples/jsonrpc-agent/README.zh.md +++ b/examples/jsonrpc-agent/README.zh.md @@ -30,7 +30,7 @@ ## 极简变体 -[`minimal.cordis.yml`](minimal.cordis.yml) 是 Web `minimal` preset 的完整独立版本。`DSH_SYSTEM_PROMPT` 选择它的系统提示词,未设置时使用 `You are a helpful software engineer assistant.`,且不挂载上下文压缩插件。面向模型的工具严格只有: +[`minimal.cordis.yml`](minimal.cordis.yml) 是 Web `minimal` preset 的完整独立版本。`DSH_SYSTEM_PROMPT` 选择它的系统提示词,未设置时使用 `You are a helpful software engineer assistant.`。它为新建会话抑制每个 system-prompt runtime-context 贡献,且不挂载上下文压缩插件。面向模型的工具严格只有: - 所有者作用域内持久化的 `bash` - 提供 `view`、`create`、`str_replace` 与 `insert` 的 `str_replace_editor` diff --git a/examples/jsonrpc-agent/minimal.cordis.yml b/examples/jsonrpc-agent/minimal.cordis.yml index bd9e513c43..69b019fbff 100644 --- a/examples/jsonrpc-agent/minimal.cordis.yml +++ b/examples/jsonrpc-agent/minimal.cordis.yml @@ -1,6 +1,7 @@ # Complete unattended minimal-agent composition for the Python SDK. The model # sees one deployment-selected system prompt and only the owner-scoped -# persistent Bash and string-replace editor tools. Context compaction is absent. +# persistent Bash and string-replace editor tools. Runtime-context injection and +# context compaction are absent. - id: jsonrpc name: '@deepseek-ai/dsh-jsonrpc' @@ -47,6 +48,7 @@ name: '@deepseek-ai/dsh-agent-spine-demo' config: includeHarnessIdentity: false + includeRuntimeContext: false persona: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer assistant.' workspaceContext: false skills: diff --git a/examples/jsonrpc-agent/tests/sdk.snapshot.ts b/examples/jsonrpc-agent/tests/sdk.snapshot.ts index 68de880773..2e5fae4cfb 100644 --- a/examples/jsonrpc-agent/tests/sdk.snapshot.ts +++ b/examples/jsonrpc-agent/tests/sdk.snapshot.ts @@ -77,8 +77,8 @@ interface SdkScenario { expectedSystem?: string /** Exact model-facing descriptions for selected tools. */ expectedToolDescriptions?: Readonly> - /** Stable policy-context clauses the real assembled request must include or omit. */ - policyContext?: { includes: readonly string[]; excludes: readonly string[] } + /** Expected runtime-context state in the real assembled request. */ + runtimeContext?: false | { includes: readonly string[]; excludes: readonly string[] } } const SCENARIOS: SdkScenario[] = [ @@ -111,10 +111,7 @@ const SCENARIOS: SdkScenario[] = [ expectedTools: { bash: ['command'], str_replace_editor: ['command', 'path'] }, expectedSystem: MINIMAL_SYSTEM_PROMPT, expectedToolDescriptions: { bash: MINIMAL_BASH_DESCRIPTION }, - policyContext: { - includes: ['Current DSH file policy: danger-full-access.', 'file modifications by available operations'], - excludes: ['write and edit tools', 'terminal sessions', 'one-shot bash commands'], - }, + runtimeContext: false, }, ] @@ -182,8 +179,8 @@ function assembledSystem(log: PersistedLog): string { return system } -function assembledPolicyContext(log: PersistedLog): string { - const contexts = log.content.trimEnd().split('\n').flatMap((line) => { +function assembledRuntimeContexts(log: PersistedLog): string[] { + return log.content.trimEnd().split('\n').flatMap((line) => { const event = JSON.parse(line) as { type?: string data?: { source?: { kind?: string; plugin?: string }; content?: Array<{ type?: string; text?: unknown }> } @@ -193,8 +190,6 @@ function assembledPolicyContext(log: PersistedLog): string { || event.data.source.plugin !== '@deepseek-ai/dsh-system-prompt') return [] return event.data.content?.flatMap(block => block.type === 'text' && typeof block.text === 'string' ? [block.text] : []) ?? [] }) - if (contexts.length !== 1) throw new Error(`session log has ${String(contexts.length)} runtime-context snapshots; expected one`) - return contexts[0] as string } function contextOf(logs: readonly { content: string; header: Record }[], cwd: string): NormalizeContext { @@ -447,14 +442,20 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => { if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`) expect(assembledToolDescriptions(parent)).toMatchObject(scenario.expectedToolDescriptions) } - if (scenario.policyContext !== undefined) { + if (scenario.runtimeContext !== undefined) { const parent = ordered[0] if (parent === undefined) throw new Error(`${scenario.name} has no parent session log`) - const context = assembledPolicyContext(parent) - for (const clause of scenario.policyContext.includes) expect(context).toContain(clause) - for (const clause of scenario.policyContext.excludes) expect(context).not.toContain(clause) - const system = assembledSystem(parent) - for (const clause of scenario.policyContext.includes) expect(system).not.toContain(clause) + const contexts = assembledRuntimeContexts(parent) + if (scenario.runtimeContext === false) { + expect(contexts).toEqual([]) + } else { + expect(contexts).toHaveLength(1) + const context = contexts[0] as string + for (const clause of scenario.runtimeContext.includes) expect(context).toContain(clause) + for (const clause of scenario.runtimeContext.excludes) expect(context).not.toContain(clause) + const system = assembledSystem(parent) + for (const clause of scenario.runtimeContext.includes) expect(system).not.toContain(clause) + } } if (scenario.children > 0) { expect(notifications.some(n => n.method === 'subagent.started')).toBe(true) diff --git a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl index ea8f220308..550d3495f9 100644 --- a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl @@ -4,76 +4,75 @@ {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":2,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}}} {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":4,"time":0,"data":{"content":[{"type":"text","text":"Prove that bash state persists. Then create {{cwd}}/note.txt with a tab-indented line, view it, replace that literal tab-indented line, and make the persistent shell exit with code 9."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":5,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":6,"time":0,"data":{"title":"Prove that bash state persists.","messageSeqs":[4],"source":{"kind":"fallback"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":7,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":8,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-1","name":"bash","argumentsDelta":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":14,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":15,"time":0,"data":{"turn":1,"step":1,"callId":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":16,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"bash-1"},"content":[{"type":"tool-result","toolCallId":"bash-1","content":[{"type":"text","text":"COUNT=1 CWD=/tmp"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":17,"time":0,"data":{"turn":1,"step":1}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":18,"time":0,"data":{"turn":1,"step":2}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-2","name":"bash","argumentsDelta":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":23,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":24,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":25,"time":0,"data":{"turn":1,"step":2,"callId":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":26,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"bash-2"},"content":[{"type":"tool-result","toolCallId":"bash-2","content":[{"type":"text","text":"COUNT=2 CWD=/tmp"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[25],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":27,"time":0,"data":{"turn":1,"step":2}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":28,"time":0,"data":{"turn":1,"step":3}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":33,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":34,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":35,"time":0,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":36,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: {{cwd}}/note.txt"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[35],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":37,"time":0,"data":{"turn":1,"step":3}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":38,"time":0,"data":{"turn":1,"step":4}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":39,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":40,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-view","name":"str_replace_editor","argumentsDelta":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":41,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":43,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":44,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[39,40,41,42,43],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":45,"time":0,"data":{"turn":1,"step":4,"callId":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":46,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-view"},"content":[{"type":"tool-result","toolCallId":"editor-view","content":[{"type":"text","text":"Here's the content of {{cwd}}/note.txt with line numbers (which has a total of 3 lines):\n 1 target:\n 2 \told\n 3 \n"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[45],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":47,"time":0,"data":{"turn":1,"step":4}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":48,"time":0,"data":{"turn":1,"step":5}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":52,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":53,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":54,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[49,50,51,52,53],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":55,"time":0,"data":{"turn":1,"step":5,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":56,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file {{cwd}}/note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[55],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":57,"time":0,"data":{"turn":1,"step":5}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":58,"time":0,"data":{"turn":1,"step":6}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":60,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-exit","name":"bash","argumentsDelta":"{\"command\":\"exit 9\"}"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":61,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":62,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":63,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":64,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[59,60,61,62,63],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":65,"time":0,"data":{"turn":1,"step":6,"callId":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":66,"time":0,"data":{"turn":1,"step":6,"message":{"source":{"kind":"tool","callId":"bash-exit"},"content":[{"type":"tool-result","toolCallId":"bash-exit","content":[{"type":"text","text":"exit\n[shell exited: code 9]\nThe persistent bash shell was reset; the next bash call starts from the workspace with a fresh current directory and environment."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[65],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":67,"time":0,"data":{"turn":1,"step":6}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":68,"time":0,"data":{"turn":1,"step":7}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":69,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":70,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"text-delta","index":0,"text":"PERSISTENT_TOOLS_OK"}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":71,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PERSISTENT_TOOLS_OK"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":72,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":73,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":74,"time":0,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"PERSISTENT_TOOLS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":75,"time":0,"data":{"turn":1,"step":7}}}} -{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":76,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":5,"time":0,"data":{"title":"Prove that bash state persists.","messageSeqs":[4],"source":{"kind":"fallback"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":6,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/context","seq":7,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-1","name":"bash","argumentsDelta":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"bash-1"},"content":[{"type":"tool-result","toolCallId":"bash-1","content":[{"type":"text","text":"COUNT=1 CWD=/tmp"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-2","name":"bash","argumentsDelta":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":20,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":21,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":22,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":23,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":24,"time":0,"data":{"turn":1,"step":2,"callId":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":25,"time":0,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"bash-2"},"content":[{"type":"tool-result","toolCallId":"bash-2","content":[{"type":"text","text":"COUNT=2 CWD=/tmp"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":26,"time":0,"data":{"turn":1,"step":2}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":27,"time":0,"data":{"turn":1,"step":3}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":31,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":32,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":33,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[28,29,30,31,32],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":34,"time":0,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":35,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: {{cwd}}/note.txt"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[34],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":36,"time":0,"data":{"turn":1,"step":3}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":37,"time":0,"data":{"turn":1,"step":4}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":38,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":39,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-view","name":"str_replace_editor","argumentsDelta":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":40,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":41,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":42,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":43,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[38,39,40,41,42],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":44,"time":0,"data":{"turn":1,"step":4,"callId":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":45,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-view"},"content":[{"type":"tool-result","toolCallId":"editor-view","content":[{"type":"text","text":"Here's the content of {{cwd}}/note.txt with line numbers (which has a total of 3 lines):\n 1 target:\n 2 \told\n 3 \n"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[44],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":46,"time":0,"data":{"turn":1,"step":4}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":47,"time":0,"data":{"turn":1,"step":5}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":52,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":53,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[48,49,50,51,52],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":54,"time":0,"data":{"turn":1,"step":5,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":55,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file {{cwd}}/note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[54],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":56,"time":0,"data":{"turn":1,"step":5}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":57,"time":0,"data":{"turn":1,"step":6}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":58,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-exit","name":"bash","argumentsDelta":"{\"command\":\"exit 9\"}"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":60,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":61,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":62,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":63,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[58,59,60,61,62],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":64,"time":0,"data":{"turn":1,"step":6,"callId":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":65,"time":0,"data":{"turn":1,"step":6,"message":{"source":{"kind":"tool","callId":"bash-exit"},"content":[{"type":"tool-result","toolCallId":"bash-exit","content":[{"type":"text","text":"exit\n[shell exited: code 9]\nThe persistent bash shell was reset; the next bash call starts from the workspace with a fresh current directory and environment."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[64],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":66,"time":0,"data":{"turn":1,"step":6}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":67,"time":0,"data":{"turn":1,"step":7}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":68,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":69,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"text-delta","index":0,"text":"PERSISTENT_TOOLS_OK"}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":70,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PERSISTENT_TOOLS_OK"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":71,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":72,"time":0,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":73,"time":0,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"PERSISTENT_TOOLS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[68,69,70,71,72],"surfaceOp":"append"}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":74,"time":0,"data":{"turn":1,"step":7}}}} +{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":75,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}}} {"method":"session.status","params":{"sessionId":"{{sessionId}}","status":"idle"}} diff --git a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl index be559eeb9a..77fe6f03a6 100644 --- a/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl +++ b/examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl @@ -4,75 +4,74 @@ {"type":"agent/inbox/spliced","seq":2,"time":1785821461907,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","seq":3,"time":1785331618312,"data":{"turn":1,"step":1}} {"type":"user/message","seq":4,"time":1785498592368,"data":{"content":[{"type":"text","text":"Prove that bash state persists. Then create {{cwd}}/note.txt with a tab-indented line, view it, replace that literal tab-indented line, and make the persistent shell exit with code 9."}],"source":{"kind":"user"},"role":"user","id":"9a08e199-69d7-4b85-bfa4-27b41a92672a"},"surfaceOp":"append"} -{"type":"user/message","seq":5,"time":1785730508088,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."}]},"role":"user","id":"bb38bdc2-276e-46ec-87a1-089732acbc8d"},"surfaceOp":"append"} -{"type":"session/title","seq":6,"time":1785730508088,"data":{"title":"Prove that bash state persists.","messageSeqs":[4],"source":{"kind":"fallback"}}} -{"type":"request/header","seq":7,"time":1785498592370,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} -{"type":"request/context","seq":8,"time":1785730508089,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} -{"type":"assistant/chunk","seq":9,"time":1785331618326,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} -{"type":"assistant/chunk","seq":10,"time":1785331618326,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-1","name":"bash","argumentsDelta":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}} -{"type":"assistant/chunk","seq":11,"time":1785331618326,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} -{"type":"assistant/chunk","seq":12,"time":1785498592372,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":13,"time":1785730508090,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":14,"time":1785730508090,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0d064526-8eff-482d-8525-ac478e1d1791"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"} -{"type":"tool/call","seq":15,"time":1785730508090,"data":{"turn":1,"step":1,"callId":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}} -{"type":"tool/result","seq":16,"time":1785730508425,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"bash-1"},"content":[{"type":"tool-result","toolCallId":"bash-1","content":[{"type":"text","text":"COUNT=1 CWD=/tmp"}],"isError":false}],"role":"user","id":"2c01f81a-01ea-47e2-bf92-f7825b7cc69f"}},"sourceEventSeqs":[15],"surfaceOp":"append"} -{"type":"step/end","seq":17,"time":1785730508425,"data":{"turn":1,"step":1}} -{"type":"step/start","seq":18,"time":1785730508425,"data":{"turn":1,"step":2}} -{"type":"assistant/chunk","seq":19,"time":1785331618652,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} -{"type":"assistant/chunk","seq":20,"time":1785331618652,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-2","name":"bash","argumentsDelta":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}} -{"type":"assistant/chunk","seq":21,"time":1785331618652,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} -{"type":"assistant/chunk","seq":22,"time":1785498592702,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":23,"time":1785730508426,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":24,"time":1785730508426,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ba4078ce-0e18-419a-b720-339918aecf26"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"} -{"type":"tool/call","seq":25,"time":1785730508426,"data":{"turn":1,"step":2,"callId":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}} -{"type":"tool/result","seq":26,"time":1785730508537,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"bash-2"},"content":[{"type":"tool-result","toolCallId":"bash-2","content":[{"type":"text","text":"COUNT=2 CWD=/tmp"}],"isError":false}],"role":"user","id":"6e3ad5e1-1149-44d5-bd20-d9cc0139c747"}},"sourceEventSeqs":[25],"surfaceOp":"append"} -{"type":"step/end","seq":27,"time":1785730508537,"data":{"turn":1,"step":2}} -{"type":"step/start","seq":28,"time":1785730508537,"data":{"turn":1,"step":3}} -{"type":"assistant/chunk","seq":29,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} -{"type":"assistant/chunk","seq":30,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}} -{"type":"assistant/chunk","seq":31,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}} -{"type":"assistant/chunk","seq":32,"time":1785498592812,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":33,"time":1785730508538,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":34,"time":1785730508538,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"e5769b2d-ea91-42fe-a78f-2f7f408f545e"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[29,30,31,32,33],"surfaceOp":"append"} -{"type":"tool/call","seq":35,"time":1785730508538,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}} -{"type":"tool/result","seq":36,"time":1785730508551,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: {{cwd}}/note.txt"}],"isError":false}],"role":"user","id":"af41060c-7007-4ada-89d6-8b15a0e8be7c"}},"sourceEventSeqs":[35],"surfaceOp":"append"} -{"type":"step/end","seq":37,"time":1785730508551,"data":{"turn":1,"step":3}} -{"type":"step/start","seq":38,"time":1785730508552,"data":{"turn":1,"step":4}} -{"type":"assistant/chunk","seq":39,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} -{"type":"assistant/chunk","seq":40,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-view","name":"str_replace_editor","argumentsDelta":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}} -{"type":"assistant/chunk","seq":41,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}} -{"type":"assistant/chunk","seq":42,"time":1785498592825,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":43,"time":1785730508552,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":44,"time":1785730508552,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0e9afabb-10a6-444c-ae22-fcdbb5e14695"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[39,40,41,42,43],"surfaceOp":"append"} -{"type":"tool/call","seq":45,"time":1785730508553,"data":{"turn":1,"step":4,"callId":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}} -{"type":"tool/result","seq":46,"time":1785730508554,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-view"},"content":[{"type":"tool-result","toolCallId":"editor-view","content":[{"type":"text","text":"Here's the content of {{cwd}}/note.txt with line numbers (which has a total of 3 lines):\n 1 target:\n 2 \told\n 3 \n"}],"isError":false}],"role":"user","id":"a4472b37-6311-4880-bce2-cc369f9bc34b"}},"sourceEventSeqs":[45],"surfaceOp":"append"} -{"type":"step/end","seq":47,"time":1785730508554,"data":{"turn":1,"step":4}} -{"type":"step/start","seq":48,"time":1785730508554,"data":{"turn":1,"step":5}} -{"type":"assistant/chunk","seq":49,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} -{"type":"assistant/chunk","seq":50,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}} -{"type":"assistant/chunk","seq":51,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}} -{"type":"assistant/chunk","seq":52,"time":1785498592826,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":53,"time":1785730508555,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":54,"time":1785730508555,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ba189070-d46e-461e-969b-9bca032bb154"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[49,50,51,52,53],"surfaceOp":"append"} -{"type":"tool/call","seq":55,"time":1785730508555,"data":{"turn":1,"step":5,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}} -{"type":"tool/result","seq":56,"time":1785730508564,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file {{cwd}}/note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"17331db9-174b-4699-9c9e-3140921956c4"}},"sourceEventSeqs":[55],"surfaceOp":"append"} -{"type":"step/end","seq":57,"time":1785730508564,"data":{"turn":1,"step":5}} -{"type":"step/start","seq":58,"time":1785730508565,"data":{"turn":1,"step":6}} -{"type":"assistant/chunk","seq":59,"time":1785331618804,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} -{"type":"assistant/chunk","seq":60,"time":1785331618804,"data":{"turn":1,"step":6,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-exit","name":"bash","argumentsDelta":"{\"command\":\"exit 9\"}"}}} -{"type":"assistant/chunk","seq":61,"time":1785331618804,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}}}} -{"type":"assistant/chunk","seq":62,"time":1785498592838,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":63,"time":1785730508565,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":64,"time":1785730508565,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"7fa07d0f-e70a-460d-b685-bf8a63b6a8a0"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[59,60,61,62,63],"surfaceOp":"append"} -{"type":"tool/call","seq":65,"time":1785730508565,"data":{"turn":1,"step":6,"callId":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}} -{"type":"tool/result","seq":66,"time":1785730508641,"data":{"turn":1,"step":6,"message":{"source":{"kind":"tool","callId":"bash-exit"},"content":[{"type":"tool-result","toolCallId":"bash-exit","content":[{"type":"text","text":"exit\n[shell exited: code 9]\nThe persistent bash shell was reset; the next bash call starts from the workspace with a fresh current directory and environment."}],"isError":false}],"role":"user","id":"ccb91a28-4034-49bf-967d-450f68f7f9b8"}},"sourceEventSeqs":[65],"surfaceOp":"append"} -{"type":"step/end","seq":67,"time":1785730508641,"data":{"turn":1,"step":6}} -{"type":"step/start","seq":68,"time":1785730508642,"data":{"turn":1,"step":7}} -{"type":"assistant/chunk","seq":69,"time":1785331618807,"data":{"turn":1,"step":7,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} -{"type":"assistant/chunk","seq":70,"time":1785331618807,"data":{"turn":1,"step":7,"chunk":{"type":"text-delta","index":0,"text":"PERSISTENT_TOOLS_OK"}}} -{"type":"assistant/chunk","seq":71,"time":1785331618807,"data":{"turn":1,"step":7,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PERSISTENT_TOOLS_OK"}}}} -{"type":"assistant/chunk","seq":72,"time":1785498592934,"data":{"turn":1,"step":7,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} -{"type":"assistant/chunk","seq":73,"time":1785730508642,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","seq":74,"time":1785730508642,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"PERSISTENT_TOOLS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"43efc58a-46a1-4813-995f-1dc489438942"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[69,70,71,72,73],"surfaceOp":"append"} -{"type":"step/end","seq":75,"time":1785730508642,"data":{"turn":1,"step":7}} -{"type":"turn/end","seq":76,"time":1785730508642,"data":{"turn":1,"reason":{"kind":"completed"}}} +{"type":"session/title","seq":5,"time":1785498592368,"data":{"title":"Prove that bash state persists.","messageSeqs":[4],"source":{"kind":"fallback"}}} +{"type":"request/header","seq":6,"time":1786547087174,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} +{"type":"request/context","seq":7,"time":1786547087175,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} +{"type":"assistant/chunk","seq":8,"time":1786547087175,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":9,"time":1786547087175,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-1","name":"bash","argumentsDelta":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}} +{"type":"assistant/chunk","seq":10,"time":1785331618326,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} +{"type":"assistant/chunk","seq":11,"time":1785331618326,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":12,"time":1785331618326,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/message","seq":13,"time":1786547087175,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0d064526-8eff-482d-8525-ac478e1d1791"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"} +{"type":"tool/call","seq":14,"time":1786547087176,"data":{"turn":1,"step":1,"callId":"bash-1","name":"bash","arguments":"{\"command\":\"cd /tmp && export DSH_EXAMPLE_COUNT=1 && printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}} +{"type":"tool/result","seq":15,"time":1786547094340,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"bash-1"},"content":[{"type":"tool-result","toolCallId":"bash-1","content":[{"type":"text","text":"COUNT=1 CWD=/tmp"}],"isError":false}],"role":"user","id":"2c01f81a-01ea-47e2-bf92-f7825b7cc69f"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"step/end","seq":16,"time":1786547094340,"data":{"turn":1,"step":1}} +{"type":"step/start","seq":17,"time":1786547094341,"data":{"turn":1,"step":2}} +{"type":"assistant/chunk","seq":18,"time":1786547094341,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":19,"time":1786547094341,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-2","name":"bash","argumentsDelta":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}} +{"type":"assistant/chunk","seq":20,"time":1785331618652,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}}}} +{"type":"assistant/chunk","seq":21,"time":1785331618652,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":22,"time":1785331618652,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/message","seq":23,"time":1786547094341,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ba4078ce-0e18-419a-b720-339918aecf26"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"} +{"type":"tool/call","seq":24,"time":1786547094341,"data":{"turn":1,"step":2,"callId":"bash-2","name":"bash","arguments":"{\"command\":\"DSH_EXAMPLE_COUNT=$((DSH_EXAMPLE_COUNT + 1)); printf \\\"COUNT=%s CWD=%s\\\\n\\\" \\\"$DSH_EXAMPLE_COUNT\\\" \\\"$PWD\\\"\"}"}} +{"type":"tool/result","seq":25,"time":1786547097878,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"bash-2"},"content":[{"type":"tool-result","toolCallId":"bash-2","content":[{"type":"text","text":"COUNT=2 CWD=/tmp"}],"isError":false}],"role":"user","id":"6e3ad5e1-1149-44d5-bd20-d9cc0139c747"}},"sourceEventSeqs":[24],"surfaceOp":"append"} +{"type":"step/end","seq":26,"time":1786547097878,"data":{"turn":1,"step":2}} +{"type":"step/start","seq":27,"time":1786547097878,"data":{"turn":1,"step":3}} +{"type":"assistant/chunk","seq":28,"time":1786547097878,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":29,"time":1786547097878,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}} +{"type":"assistant/chunk","seq":30,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}}}} +{"type":"assistant/chunk","seq":31,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":32,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/message","seq":33,"time":1786547097878,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"e5769b2d-ea91-42fe-a78f-2f7f408f545e"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[28,29,30,31,32],"surfaceOp":"append"} +{"type":"tool/call","seq":34,"time":1786547097879,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"target:\\n\\told\\n\"}"}} +{"type":"tool/result","seq":35,"time":1786547097885,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: {{cwd}}/note.txt"}],"isError":false}],"role":"user","id":"af41060c-7007-4ada-89d6-8b15a0e8be7c"}},"sourceEventSeqs":[34],"surfaceOp":"append"} +{"type":"step/end","seq":36,"time":1786547097885,"data":{"turn":1,"step":3}} +{"type":"step/start","seq":37,"time":1786547097885,"data":{"turn":1,"step":4}} +{"type":"assistant/chunk","seq":38,"time":1786547097886,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":39,"time":1786547097886,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-view","name":"str_replace_editor","argumentsDelta":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}} +{"type":"assistant/chunk","seq":40,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}}}} +{"type":"assistant/chunk","seq":41,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":42,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/message","seq":43,"time":1786547097886,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0e9afabb-10a6-444c-ae22-fcdbb5e14695"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[38,39,40,41,42],"surfaceOp":"append"} +{"type":"tool/call","seq":44,"time":1786547097886,"data":{"turn":1,"step":4,"callId":"editor-view","name":"str_replace_editor","arguments":"{\"command\":\"view\",\"path\":\"{{cwd}}/note.txt\"}"}} +{"type":"tool/result","seq":45,"time":1786547097887,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-view"},"content":[{"type":"tool-result","toolCallId":"editor-view","content":[{"type":"text","text":"Here's the content of {{cwd}}/note.txt with line numbers (which has a total of 3 lines):\n 1 target:\n 2 \told\n 3 \n"}],"isError":false}],"role":"user","id":"a4472b37-6311-4880-bce2-cc369f9bc34b"}},"sourceEventSeqs":[44],"surfaceOp":"append"} +{"type":"step/end","seq":46,"time":1786547097887,"data":{"turn":1,"step":4}} +{"type":"step/start","seq":47,"time":1786547097887,"data":{"turn":1,"step":5}} +{"type":"assistant/chunk","seq":48,"time":1786547097887,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":49,"time":1786547097887,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}} +{"type":"assistant/chunk","seq":50,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}}}} +{"type":"assistant/chunk","seq":51,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":52,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/message","seq":53,"time":1786547097887,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"ba189070-d46e-461e-969b-9bca032bb154"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[48,49,50,51,52],"surfaceOp":"append"} +{"type":"tool/call","seq":54,"time":1786547097887,"data":{"turn":1,"step":5,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"\\told\",\"new_str\":\"\\tnew\"}"}} +{"type":"tool/result","seq":55,"time":1786547097892,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file {{cwd}}/note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"17331db9-174b-4699-9c9e-3140921956c4"}},"sourceEventSeqs":[54],"surfaceOp":"append"} +{"type":"step/end","seq":56,"time":1786547097892,"data":{"turn":1,"step":5}} +{"type":"step/start","seq":57,"time":1786547097892,"data":{"turn":1,"step":6}} +{"type":"assistant/chunk","seq":58,"time":1786547097893,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} +{"type":"assistant/chunk","seq":59,"time":1786547097893,"data":{"turn":1,"step":6,"chunk":{"type":"tool-call-delta","index":0,"id":"bash-exit","name":"bash","argumentsDelta":"{\"command\":\"exit 9\"}"}}} +{"type":"assistant/chunk","seq":60,"time":1785331618804,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}}}} +{"type":"assistant/chunk","seq":61,"time":1785331618804,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":62,"time":1785331618804,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} +{"type":"assistant/message","seq":63,"time":1786547097893,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"7fa07d0f-e70a-460d-b685-bf8a63b6a8a0"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[58,59,60,61,62],"surfaceOp":"append"} +{"type":"tool/call","seq":64,"time":1786547097893,"data":{"turn":1,"step":6,"callId":"bash-exit","name":"bash","arguments":"{\"command\":\"exit 9\"}"}} +{"type":"tool/result","seq":65,"time":1786547098003,"data":{"turn":1,"step":6,"message":{"source":{"kind":"tool","callId":"bash-exit"},"content":[{"type":"tool-result","toolCallId":"bash-exit","content":[{"type":"text","text":"exit\n[shell exited: code 9]\nThe persistent bash shell was reset; the next bash call starts from the workspace with a fresh current directory and environment."}],"isError":false}],"role":"user","id":"ccb91a28-4034-49bf-967d-450f68f7f9b8"}},"sourceEventSeqs":[64],"surfaceOp":"append"} +{"type":"step/end","seq":66,"time":1786547098003,"data":{"turn":1,"step":6}} +{"type":"step/start","seq":67,"time":1786547098003,"data":{"turn":1,"step":7}} +{"type":"assistant/chunk","seq":68,"time":1786547098003,"data":{"turn":1,"step":7,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} +{"type":"assistant/chunk","seq":69,"time":1786547098003,"data":{"turn":1,"step":7,"chunk":{"type":"text-delta","index":0,"text":"PERSISTENT_TOOLS_OK"}}} +{"type":"assistant/chunk","seq":70,"time":1785331618807,"data":{"turn":1,"step":7,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PERSISTENT_TOOLS_OK"}}}} +{"type":"assistant/chunk","seq":71,"time":1785331618807,"data":{"turn":1,"step":7,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} +{"type":"assistant/chunk","seq":72,"time":1785331618807,"data":{"turn":1,"step":7,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} +{"type":"assistant/message","seq":73,"time":1786547098003,"data":{"turn":1,"step":7,"message":{"role":"assistant","content":[{"type":"text","text":"PERSISTENT_TOOLS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"43efc58a-46a1-4813-995f-1dc489438942"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[68,69,70,71,72],"surfaceOp":"append"} +{"type":"step/end","seq":74,"time":1786547098003,"data":{"turn":1,"step":7}} +{"type":"turn/end","seq":75,"time":1786547098004,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/packages/core/system-prompt/README.i18n.yaml b/packages/core/system-prompt/README.i18n.yaml index f805ee37b0..fd428146d6 100644 --- a/packages/core/system-prompt/README.i18n.yaml +++ b/packages/core/system-prompt/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/system-prompt/README.md -README.md: cedda783d549633f5be9765a9a074e968d99500d -README.zh.md: ab3dbffa099549cb1d5cc2713d903038e917e695 +README.md: 6d0b43322ea0ccf4dddef404c4c862e014819696 +README.zh.md: 08da218ef2fe07ffa63f246dff5382474070c4d9 diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index cedda783d5..6d0b43322e 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -9,6 +9,7 @@ System prompt assembly registry. Plugins contribute ordered sections, tool schem | Key | Default | Meaning | |---|---|---| | `includeHarnessIdentity` | `true` | Include the fixed `You are an AI agent powered by the DeepSeek Harness SDK.` order-−100 opener. Set false only when a compatibility deployment owns the complete system prompt. | +| `includeRuntimeContext` | `true` | Include ordered dynamic contexts in assembly. When false, context providers are not evaluated and contexts added by `system-prompt/assemble` listeners are discarded after the waterfall; other services and their enforcement remain active. | | `persona` | `''` | The global deployment-persona default: the ONE config-authored prompt fragment, rendered as the order-0 `deployment:persona` section unless an agent-scoped contribution shadows it. A template — complete `{{…}}` groups are interpreted strictly against the registered variables (the shipped loop registers `{{model}}`/`{{cwd}}`), with no escape syntax for literal braces yet. Empty ⇒ the section is dropped at render. | | `toolOrder` | — | Explicit model-facing tool order, as a list of `ToolSchema.name`s with one `''` rest entry (`TOOL_ORDER_REST`): listed tools take their listed position, unlisted tools land at the rest entry in lexicographic name order. Absent ⇒ plain lexicographic name order. Applied to the collected tools BEFORE the `system-prompt/assemble` waterfall — like the sections' `order` sort, it canonicalizes what the registry contributed (registration order is a plugin-load artifact), and a waterfall listener that mutates the list owns the determinism of what it emits. Misconfiguration fails loud: a list without exactly one rest entry, or with duplicates, throws at load; a listed name with no registered tool rejects every `assemble()`; a tool provider returning the reserved rest-entry name also rejects. Under the shipped loop the turn fails before any model request. Why a central list and not per-plugin weights: [Explicit model-facing tool order](../../../.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md). | @@ -17,9 +18,11 @@ System prompt assembly registry. Plugins contribute ordered sections, tool schem ### Public API - `ctx.systemPrompt.section(section: PromptSection): () => void` Contribute a section. The layer is the calling context's scope: `agent.ctx` contributes to that agent alone, shadowing a same-named global section there. A `complete: true` section becomes the exact complete prompt after the assembly waterfall; more than one effective complete section rejects assembly. Duplicate names within one layer and non-finite orders throw. Disposed with the calling fiber. +- `ctx.systemPrompt.context(context: PromptContext): () => void` Contribute ordered dynamic context for the calling scope. Providers are evaluated for each eligible assembly and become a sourced runtime-context snapshot in model history under the shipped loop. +- `ctx.systemPrompt.suppressRuntimeContext(): () => void` Suppress every dynamic-context contribution for the calling scope. Multiple registrations compose independently; disposing the returned effect restores context when no suppressor remains. - `ctx.systemPrompt.tools(provider: (context: AssembleContext) => ToolProviderResult): () => void` Contribute tool schemas, evaluated at each assembly with that assembly's context. `ToolProviderResult` = `{ schemas, knownNames? }`: `schemas` is the post-restriction visible set; `knownNames` is the pre-restriction universe used by `toolOrder`. A provider must not return a schema named `TOOL_ORDER_REST`. Scoped providers are consulted only for their scope's assemblies. Disposed with the calling fiber. - `ctx.systemPrompt.variable(name: string, provider: (context) => string | undefined): () => void` Contribute a prompt variable, referenced from section text as `{{name}}`. Scoped variables shadow a same-named global for that agent. Duplicate-in-layer or unreferenceable names throw; `undefined` means "no value for this assembly". Disposed with the calling fiber. -- `ctx.systemPrompt.assemble(context?: AssembleContext): Promise` Assemble the prompt for one caller: the global layer merged with `context.scope`'s layer, with tool schemas detached before the transform waterfall. Runs through the scope-filtered `system-prompt/assemble` waterfall, then restores an effective complete section as the sole prompt section. An optional `context.signal` explicitly controls this assembly request; providers and listeners may cooperate with it but must not retain it for another turn. Rejects for multiple complete sections, when a configured `toolOrder` names a tool outside the providers' `knownNames` universe, or when a provider returns the reserved rest-entry name. +- `ctx.systemPrompt.assemble(context?: AssembleContext): Promise` Assemble the prompt for one caller: the global layer merged with `context.scope`'s layer, with tool schemas detached before the transform waterfall. Runs through the scope-filtered `system-prompt/assemble` waterfall, then restores an effective complete section as the sole prompt section and enforces any active runtime-context suppressor. An optional `context.signal` explicitly controls this assembly request; providers and listeners may cooperate with it but must not retain it for another turn. Rejects for multiple complete sections, when a configured `toolOrder` names a tool outside the providers' `knownNames` universe, or when a provider returns the reserved rest-entry name. ### Live events @@ -49,7 +52,7 @@ Design rationale: [the prompt-variables Agent Note](../../../.agents/notes/imple #### What the model sees -By default every assembly starts with the harness identity below, then the configured persona and ordered plugin sections after strict variable interpolation. `includeHarnessIdentity: false` omits only that fixed opener. Empty sections disappear; scoped sections and variables can shadow globals for one agent. The `system-prompt/assemble` waterfall determines the delivered prompt and tool schemas unless one effective section declares itself complete; that exact section then becomes the whole system prompt while the waterfall's contexts, tools, and variables remain. +By default every assembly starts with the harness identity below, then the configured persona and ordered plugin sections after strict variable interpolation. `includeHarnessIdentity: false` omits only that fixed opener. Empty sections disappear; scoped sections and variables can shadow globals for one agent. The `system-prompt/assemble` waterfall determines the delivered prompt and tool schemas unless one effective section declares itself complete; that exact section then becomes the whole system prompt while the waterfall's contexts, tools, and variables remain. Ordered dynamic contexts are separate from system-prompt sections and become sourced user-role snapshots only when present. `includeRuntimeContext: false` or a scoped suppressor removes all such contexts, including listener additions, without disabling the services that own the underlying policy or state. ##### Harness identity diff --git a/packages/core/system-prompt/README.zh.md b/packages/core/system-prompt/README.zh.md index ab3dbffa09..08da218ef2 100644 --- a/packages/core/system-prompt/README.zh.md +++ b/packages/core/system-prompt/README.zh.md @@ -9,6 +9,7 @@ | 键 | 默认值 | 含义 | |---|---|---| | `includeHarnessIdentity` | `true` | 是否包含顺序为 −100 的固定开场白 `You are an AI agent powered by the DeepSeek Harness SDK.`。仅当兼容部署拥有完整系统提示词时设为 false。 | +| `includeRuntimeContext` | `true` | 是否在组装中包含有序动态上下文。设为 false 时不会求值上下文提供方,并会在 waterfall 后丢弃 `system-prompt/assemble` 监听器添加的上下文;其他服务及其强制机制仍然生效。 | | `persona` | `''` | 全局部署 persona 默认值:唯一由配置提供的提示词片段,渲染为顺序为 0 的 `deployment:persona` 段,除非 agent 作用域的贡献将其遮蔽。它是模板,完整的 `{{…}}` 组会严格按已注册变量解释(随附循环注册 `{{model}}`/`{{cwd}}`),目前没有表达字面量花括号的转义语法。为空 ⇒ 渲染时删除该段。 | | `toolOrder` | 无 | 显式的面向模型工具顺序:一个 `ToolSchema.name` 列表,包含一个 `''` 其余项(`TOOL_ORDER_REST`)。已列工具占据列出的位置;未列工具按名称字典序落在其余项位置。缺席 ⇒ 直接按名称字典序排列。在 `system-prompt/assemble` waterfall(瀑布式事件)之前应用于已收集工具;与段的 `order` 排序一样,它会规范化注册表贡献的内容(注册顺序是插件加载产物),而修改列表的 waterfall 监听器拥有其输出的确定性。配置错误会明确失败:列表没有恰好一个其余项或存在重复项,会在加载时抛出;已列名称没有对应已注册工具,会使每次 `assemble()` 被拒绝;工具提供方返回保留的其余项名称也会被拒绝。在随附循环下,轮次会在任何模型请求前失败。为何采用中心列表而非每插件权重,见[显式面向模型工具顺序](../../../.agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md)。 | @@ -17,9 +18,11 @@ ### 公开 API - `ctx.systemPrompt.section(section: PromptSection): () => void`:贡献一个段。层由调用上下文的作用域决定:`agent.ctx` 只为该 agent 贡献,并在该处遮蔽同名全局段。一个 `complete: true` 段会在组装 waterfall 之后成为精确的完整提示词;有效 complete 段超过一个时,组装会被拒绝。同一层中的重复名称和非有限顺序会抛出。随调用 fiber 一并 dispose(资源释放)。 +- `ctx.systemPrompt.context(context: PromptContext): () => void`:为调用作用域贡献有序动态上下文。每次符合条件的组装都会求值提供方,并在随附循环下成为模型历史中带来源的 runtime-context 快照。 +- `ctx.systemPrompt.suppressRuntimeContext(): () => void`:抑制调用作用域的所有动态上下文贡献。多个注册会独立组合;只有当不再存在抑制器时,dispose 返回的 effect 才会恢复上下文。 - `ctx.systemPrompt.tools(provider: (context: AssembleContext) => ToolProviderResult): () => void`:贡献工具 schema;每次组装时使用该次组装的上下文求值。`ToolProviderResult` = `{ schemas, knownNames? }`:`schemas` 是限制后的可见集合;`knownNames` 是限制前由 `toolOrder` 使用的全集。提供方不得返回名为 `TOOL_ORDER_REST` 的 schema。带作用域提供方只在其作用域的组装中查询。随调用 fiber 一并 dispose。 - `ctx.systemPrompt.variable(name: string, provider: (context) => string | undefined): () => void`:贡献提示词变量,在段文本中以 `{{name}}` 引用。带作用域变量会为该 agent 遮蔽同名全局变量。同层重复或无法引用的名称会抛出;`undefined` 表示「本次组装没有值」。随调用 fiber 一并 dispose。 -- `ctx.systemPrompt.assemble(context?: AssembleContext): Promise`:为一个调用方组装提示词:将全局层与 `context.scope` 的层合并,并在变换 waterfall 前分离工具 schema。它经过按作用域筛选的 `system-prompt/assemble` waterfall,之后将一个有效的 complete 段恢复为唯一的提示词段落。可选的 `context.signal` 显式控制本次组装请求;提供方与监听器可以配合该信号,但不得将它保留给另一轮次。存在多个 complete 段、已配置的 `toolOrder` 指名提供方 `knownNames` 全集以外的工具,或提供方返回保留的其余项名称时,调用会被拒绝。 +- `ctx.systemPrompt.assemble(context?: AssembleContext): Promise`:为一个调用方组装提示词:将全局层与 `context.scope` 的层合并,并在变换 waterfall 前分离工具 schema。它经过按作用域筛选的 `system-prompt/assemble` waterfall,之后将一个有效的 complete 段恢复为唯一的提示词段落,并实施任何活动的 runtime-context 抑制器。可选的 `context.signal` 显式控制本次组装请求;提供方与监听器可以配合该信号,但不得将它保留给另一轮次。存在多个 complete 段、已配置的 `toolOrder` 指名提供方 `knownNames` 全集以外的工具,或提供方返回保留的其余项名称时,调用会被拒绝。 @@ -51,7 +54,7 @@ #### 模型看到的内容 -默认情况下,每次组装都从下方 harness 身份开始,然后在严格变量插值后追加已配置 persona 与有序插件段。`includeHarnessIdentity: false` 仅省略这个固定开场白。空段会消失;带作用域的段和变量可以为一个 agent 遮蔽全局项。`system-prompt/assemble` waterfall 决定交付的提示词与工具 schema,除非一个有效段声明自身为 complete;此时,该确切段落会成为完整的系统提示词,而 waterfall 得到的上下文、工具和变量保持不变。 +默认情况下,每次组装都从下方 harness 身份开始,然后在严格变量插值后追加已配置 persona 与有序插件段。`includeHarnessIdentity: false` 仅省略这个固定开场白。空段会消失;带作用域的段和变量可以为一个 agent 遮蔽全局项。`system-prompt/assemble` waterfall 决定交付的提示词与工具 schema,除非一个有效段声明自身为 complete;此时,该确切段落会成为完整的系统提示词,而 waterfall 得到的上下文、工具和变量保持不变。有序动态上下文与系统提示词段落分离,只在存在时才会成为带来源的 user 角色快照。`includeRuntimeContext: false` 或带作用域的抑制器会移除所有这类上下文,包括监听器添加的内容,但不会禁用拥有底层策略或状态的服务。 ##### harness 身份 diff --git a/packages/core/system-prompt/src/index.ts b/packages/core/system-prompt/src/index.ts index 45a613cd8f..b7cb579491 100644 --- a/packages/core/system-prompt/src/index.ts +++ b/packages/core/system-prompt/src/index.ts @@ -186,6 +186,8 @@ function compareToolNames(a: ToolSchema, b: ToolSchema): number { export interface Config { /** Include the fixed DeepSeek Harness identity before the deployment persona (default true). */ includeHarnessIdentity?: boolean + /** Include dynamic runtime-context snapshots in model history (default true). */ + includeRuntimeContext?: boolean /** * Deployment-wide order-0 persona template. A scoped section named * `deployment:persona` shadows it; `{{variable}}` references are strict. @@ -302,6 +304,7 @@ type VariableProvider = (context: AssembleContext) => string | undefined class PromptLayer implements ScopeLayer { readonly sections: NamedEntries readonly contexts: NamedEntries + readonly runtimeContextSuppressors = new AnonymousEntries() readonly toolProviders = new AnonymousEntries() readonly variables: NamedEntries @@ -325,6 +328,7 @@ class PromptLayer implements ScopeLayer { isEmpty(): boolean { return this.sections.isEmpty() && this.contexts.isEmpty() + && this.runtimeContextSuppressors.isEmpty() && this.toolProviders.isEmpty() && this.variables.isEmpty() } @@ -334,6 +338,7 @@ class PromptLayer implements ScopeLayer { export class SystemPrompt extends Service { static Config: z = z.object({ includeHarnessIdentity: z.boolean().default(true), + includeRuntimeContext: z.boolean().default(true), persona: z.string().default(''), // Preserve omission because an explicit empty order lacks the rest marker. toolOrder: z.array(z.string()).default(undefined as unknown as string[]), @@ -362,6 +367,7 @@ export class SystemPrompt extends Service { // The fallback narrows the optional input type; the schema already defaults it. text: config.persona ?? '', }) + if (!(config.includeRuntimeContext ?? true)) this.suppressRuntimeContext() } /** @@ -400,6 +406,20 @@ export class SystemPrompt extends Service { ) } + /** + * Suppress every dynamic runtime-context contribution in the calling + * context's scope without changing the services that own or enforce those + * facts. Multiple suppressors remain independently disposable. + * @returns the exact Cordis effect disposer. + */ + suppressRuntimeContext(): () => void { + return this.layers.effect( + this.ctx, + layer => layer.runtimeContextSuppressors.append(true), + { label: 'systemPrompt.suppressRuntimeContext()' }, + ) + } + /** * Register a tool-schema provider in the calling context's scope. Global and * matching scoped providers both contribute; returning the reserved @@ -446,13 +466,16 @@ export class SystemPrompt extends Service { // Keep configuration failures on the declared asynchronous error path. async assemble(context: AssembleContext = {}): Promise { const scope = context.scope + const scopeLayers = this.layers.chainLayers(scope) + const runtimeContextSuppressed = !this.layers.global.runtimeContextSuppressors.isEmpty() + || scopeLayers.some(layer => !layer.runtimeContextSuppressors.isEmpty()) // Scoped variables shadow globals. const variables: Record = {} for (const [name, provider] of this.layers.global.variables.entries()) { variables[name] = provider(context) } // Scope-chain variables, farthest first, so the nearest scope wins a name. - for (const layer of this.layers.chainLayers(scope)) { + for (const layer of scopeLayers) { for (const [name, provider] of layer.variables.entries()) { variables[name] = provider(context) } @@ -463,7 +486,7 @@ export class SystemPrompt extends Service { // Validate order against pre-restriction names while collecting visible schemas. const providers = [ ...this.layers.global.toolProviders.values(), - ...this.layers.chainLayers(scope).flatMap(layer => [...layer.toolProviders.values()]), + ...scopeLayers.flatMap(layer => [...layer.toolProviders.values()]), ] const collected: ToolSchema[] = [] const knownNames = new Set() @@ -495,12 +518,14 @@ export class SystemPrompt extends Service { }) const assembly: PromptAssembly = { sections, - contexts: [...contextByName.values()] - .sort((a, b) => a.order - b.order) - .map(entry => ({ - name: entry.name, - text: typeof entry.text === 'function' ? entry.text(context) : entry.text, - })), + contexts: runtimeContextSuppressed + ? [] + : [...contextByName.values()] + .sort((a, b) => a.order - b.order) + .map(entry => ({ + name: entry.name, + text: typeof entry.text === 'function' ? entry.text(context) : entry.text, + })), tools: orderTools(collected, this.toolOrder, knownNames), variables, } @@ -508,8 +533,12 @@ export class SystemPrompt extends Service { scopeTarget(this, scope), 'system-prompt/assemble', assembly, context, () => Promise.resolve(assembly), ) - if (completeSection === undefined) return transformed - return { ...transformed, sections: [completeSection] } + if (completeSection === undefined && !runtimeContextSuppressed) return transformed + return { + ...transformed, + sections: completeSection === undefined ? transformed.sections : [completeSection], + contexts: runtimeContextSuppressed ? [] : transformed.contexts, + } } } diff --git a/packages/core/system-prompt/tests/scoped.spec.ts b/packages/core/system-prompt/tests/scoped.spec.ts index 1498966cc6..360fef3a14 100644 --- a/packages/core/system-prompt/tests/scoped.spec.ts +++ b/packages/core/system-prompt/tests/scoped.spec.ts @@ -142,6 +142,23 @@ describe('scoped cache-safe context', () => { expect(renderContextSnapshot(await ctx.systemPrompt.assemble({ scope: scopeKeyOf(scope) }))) .toContain('global policy') }) + + it('suppresses all context for one scope and restores it when disposed', async () => { + const ctx = await mount() + const scope = await mintScope(ctx, 'suppressed-context') + const key = scopeKeyOf(scope) + ctx.systemPrompt.context({ name: 'policy', order: 1, text: 'global policy' }) + const dispose = scope.ctx.systemPrompt.suppressRuntimeContext() + + const suppressed = await ctx.systemPrompt.assemble({ scope: key }) + expect(suppressed.contexts).toEqual([]) + const global = await ctx.systemPrompt.assemble() + expect(renderContextSnapshot(global)).toContain('global policy') + + dispose() + expect(renderContextSnapshot(await ctx.systemPrompt.assemble({ scope: key }))) + .toContain('global policy') + }) }) describe('scoped tool providers and toolOrder × restriction', () => { diff --git a/packages/core/system-prompt/tests/system-prompt.spec.ts b/packages/core/system-prompt/tests/system-prompt.spec.ts index 8ab6c37ea7..c0aa68c494 100644 --- a/packages/core/system-prompt/tests/system-prompt.spec.ts +++ b/packages/core/system-prompt/tests/system-prompt.spec.ts @@ -49,6 +49,25 @@ describe('SystemPrompt', () => { expect(renderPrompt(assembly)).toBe('You are a helpful software engineer assistant.') }) + it('can suppress runtime context without evaluating providers or accepting waterfall additions', async () => { + const ctx = new Context() + await ctx.plugin(SystemPrompt, { includeRuntimeContext: false }) + let providerCalls = 0 + ctx.systemPrompt.context({ + name: 'policy', + order: 0, + text: () => `policy ${++providerCalls}`, + }) + ctx.on('system-prompt/assemble', async (assembly, _context, next) => { + assembly.contexts.push({ name: 'late', text: 'late context' }) + return next() + }) + + const assembly = await ctx.systemPrompt.assemble() + expect(assembly.contexts).toEqual([]) + expect(providerCalls).toBe(0) + }) + it('tolerates a schema-bypassing direct construction (persona omitted)', async () => { // ctx.plugin validates + defaults the config first; a direct construction // skips the schema, so the ctor's `?? ''` narrowing is what fires. diff --git a/packages/examples/agent-spine-demo/README.i18n.yaml b/packages/examples/agent-spine-demo/README.i18n.yaml index 150ddae9ff..b38119060f 100644 --- a/packages/examples/agent-spine-demo/README.i18n.yaml +++ b/packages/examples/agent-spine-demo/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/examples/agent-spine-demo/README.md -README.md: 789715e53038f610d1e2db79cf56f9aabd681fac -README.zh.md: 7a861297d76d18d5e55539334ca8e7ee5ffef640 +README.md: d45a128e786da18d2e2a41c1e4c048270a9c5cc7 +README.zh.md: ae9c523b53a13eae89a670b9177ed996b4fbb928 diff --git a/packages/examples/agent-spine-demo/README.md b/packages/examples/agent-spine-demo/README.md index 789715e530..d45a128e78 100644 --- a/packages/examples/agent-spine-demo/README.md +++ b/packages/examples/agent-spine-demo/README.md @@ -55,11 +55,11 @@ This applies the [Service Definition / Service provider / Consumer separation](. ```ts import type { Config } from '@deepseek-ai/dsh-agent-spine-demo' -// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, tasks?, toolTasks?, goals?, invariants? } +// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, includeRuntimeContext?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, tasks?, toolTasks?, goals?, invariants? } // workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults. ``` -The bundle forwards each field to the child that owns it. App packages supply any pre-created agents: headless and JSON-RPC compositions create `main`, while the ACP app creates agents on demand at `session/new`. Prompt, tool, title, skill, workspace-context, invariant, goal, and task settings retain the schemas and defaults documented by their owning packages; `tasks.maxConcurrentTasksPerOwner` configures the local provider independently of the model-facing `toolTasks` controls. `pickSpineConfig()` copies only fields owned by this bundle, and conflicting `dshHome` values fail during composition. +The bundle forwards each field to the child that owns it. App packages supply any pre-created agents: headless and JSON-RPC compositions create `main`, while the ACP app creates agents on demand at `session/new`. `includeRuntimeContext: false` is forwarded to `dsh-system-prompt` and suppresses all dynamic context snapshots for fresh sessions without disabling their policy services. Prompt, tool, title, skill, workspace-context, invariant, goal, and task settings retain the schemas and defaults documented by their owning packages; `tasks.maxConcurrentTasksPerOwner` configures the local provider independently of the model-facing `toolTasks` controls. `pickSpineConfig()` copies only fields owned by this bundle, and conflicting `dshHome` values fail during composition. For example, `{ invariants: { enabled: true, package_allowlist: ['^@deepseek-ai/dsh-'], package_blocklist: ['agent-loop$'] } }` keeps the package-owned companions mounted but suppresses the blocked owner. Blocklist matches override allowlist matches; see [`dsh-invariants`](../../support/invariants/README.md) for regex and lifecycle rules. diff --git a/packages/examples/agent-spine-demo/README.zh.md b/packages/examples/agent-spine-demo/README.zh.md index 7a861297d7..ae9c523b53 100644 --- a/packages/examples/agent-spine-demo/README.zh.md +++ b/packages/examples/agent-spine-demo/README.zh.md @@ -55,11 +55,11 @@ ```ts import type { Config } from '@deepseek-ai/dsh-agent-spine-demo' -// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, tasks?, toolTasks?, goals?, invariants? } +// { agents?, maxParallelToolCalls?, includeHarnessIdentity?, includeRuntimeContext?, persona?, toolOrder?, tools?, dshHome?, sessionTitle?, skills?, workspaceContext, toolBash?, tasks?, toolTasks?, goals?, invariants? } // workspaceContext requires { maxBytes } or false; the other owner schemas supply defaults. ``` -组合包将每个字段转发给拥有它的子节点。应用包提供预创建的 agent:无头和 JSON-RPC 组合会创建 `main`,ACP 应用则在 `session/new` 按需创建 agent。提示词、工具、标题、skill、工作区上下文、不变式、目标和任务设置沿用其所属包记录的 schema 与默认值;`tasks.maxConcurrentTasksPerOwner` 配置本地 Service provider,并与面向模型的 `toolTasks` 控制工具相互独立。`pickSpineConfig()` 只复制该组合包拥有的字段,`dshHome` 值冲突会在组合时失败。 +组合包将每个字段转发给拥有它的子节点。应用包提供预创建的 agent:无头和 JSON-RPC 组合会创建 `main`,ACP 应用则在 `session/new` 按需创建 agent。`includeRuntimeContext: false` 会转发给 `dsh-system-prompt`,为新建会话抑制所有动态上下文快照,但不禁用其策略服务。提示词、工具、标题、skill、工作区上下文、不变式、目标和任务设置沿用其所属包记录的 schema 与默认值;`tasks.maxConcurrentTasksPerOwner` 配置本地 Service provider,并与面向模型的 `toolTasks` 控制工具相互独立。`pickSpineConfig()` 只复制该组合包拥有的字段,`dshHome` 值冲突会在组合时失败。 例如,`{ invariants: { enabled: true, package_allowlist: ['^@deepseek-ai/dsh-'], package_blocklist: ['agent-loop$'] } }` 会让包拥有的配套插件保持挂载,但抑制被阻止的拥有者。Blocklist 匹配优先于 allowlist 匹配;正则表达式与生命周期规则见 [`dsh-invariants`](../../support/invariants/README.md)。 diff --git a/packages/examples/agent-spine-demo/src/index.ts b/packages/examples/agent-spine-demo/src/index.ts index 2b7644e3cc..4e9ba4fb1f 100644 --- a/packages/examples/agent-spine-demo/src/index.ts +++ b/packages/examples/agent-spine-demo/src/index.ts @@ -69,9 +69,10 @@ export interface GoalConfig { /** * Bundle config: each field forwarded verbatim to the child that owns it — * `agents` to the agent loop (an app that pre-creates no agents, like the ACP - * bridge, simply omits it), `includeHarnessIdentity`, `persona`, and `toolOrder` - * to the system-prompt plugin (the fixed opener, deployment persona, and explicit - * model-facing tool order), the `tools` object to the tool registry (its presentation `mode`), + * bridge, simply omits it), `includeHarnessIdentity`, `includeRuntimeContext`, + * `persona`, and `toolOrder` to the system-prompt plugin (the fixed opener, + * dynamic-context policy, deployment persona, and explicit model-facing tool + * order), the `tools` object to the tool registry (its presentation `mode`), * `dshHome` to bash environment and local skill discovery, `sessionTitle` to * the fallback title service, `skills` to the * skill registry/local provider/tool consumer, `workspaceContext` to the @@ -95,6 +96,8 @@ export interface Config { maxParallelToolCalls?: AgentLoopConfig['maxParallelToolCalls'] /** Whether the system prompt includes the fixed Harness identity (default true). */ includeHarnessIdentity?: SystemPromptConfig['includeHarnessIdentity'] + /** Whether model history includes dynamic runtime-context snapshots (default true). */ + includeRuntimeContext?: SystemPromptConfig['includeRuntimeContext'] /** The deployment persona (see dsh-system-prompt's `Config`). */ persona?: SystemPromptConfig['persona'] /** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */ @@ -180,6 +183,7 @@ export function pickSpineConfig(config: Omit): Omit { it('can omit the bundled bash tool and Harness identity for a compatibility deployment', async () => { const ctx = await mount({ includeHarnessIdentity: false, + includeRuntimeContext: false, persona: 'You are a helpful software engineer assistant.', workspaceContext: false, skills: { enabled: false }, @@ -720,6 +721,8 @@ describe('dsh-agent-spine-demo bundle', () => { }, true) expect(ctx.tools.schemas()).toEqual([]) + ctx.systemPrompt.context({ name: 'policy', order: 0, text: 'hidden policy' }) + expect((await ctx.systemPrompt.assemble()).contexts).toEqual([]) expect(renderPrompt(await ctx.systemPrompt.assemble())) .toBe('You are a helpful software engineer assistant.') @@ -729,7 +732,9 @@ describe('dsh-agent-spine-demo bundle', () => { it('picks shared spine config without leaking entry-point fields', () => { const appConfig = { model: 'entrypoint-only', + maxParallelToolCalls: 3, includeHarnessIdentity: false, + includeRuntimeContext: false, persona: 'You are merged.', toolOrder: ['zulu'], tools: { mode: 'native' as const }, @@ -741,10 +746,13 @@ describe('dsh-agent-spine-demo bundle', () => { tasks: { maxConcurrentTasksPerOwner: 4 }, toolTasks: false as const, invariants: { enabled: false }, + goals: false as const, } expect(agentCore.pickSpineConfig(appConfig)).toEqual({ + maxParallelToolCalls: appConfig.maxParallelToolCalls, includeHarnessIdentity: appConfig.includeHarnessIdentity, + includeRuntimeContext: appConfig.includeRuntimeContext, persona: appConfig.persona, toolOrder: appConfig.toolOrder, tools: appConfig.tools, @@ -756,6 +764,7 @@ describe('dsh-agent-spine-demo bundle', () => { tasks: appConfig.tasks, toolTasks: appConfig.toolTasks, invariants: appConfig.invariants, + goals: appConfig.goals, }) expect(agentCore.pickSpineConfig({ workspaceContext: false })).toEqual({ workspaceContext: false }) }) diff --git a/packages/preset/persona/README.i18n.yaml b/packages/preset/persona/README.i18n.yaml index c839c7dcda..0323741aed 100644 --- a/packages/preset/persona/README.i18n.yaml +++ b/packages/preset/persona/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/preset/persona/README.md -README.md: 742141e65fa8d50b89e6b74e6d21aa8c5bfe98cd -README.zh.md: 61112d1091e428d8c92aa056b7aaa272d3bbbc1d +README.md: 978d01ddbd52095bc2486d27d08fde2d4a8fa79b +README.zh.md: d36af6c028f1258d044608535c6da4eaf2fb92e8 diff --git a/packages/preset/persona/README.md b/packages/preset/persona/README.md index 742141e65f..978d01ddbd 100644 --- a/packages/preset/persona/README.md +++ b/packages/preset/persona/README.md @@ -16,8 +16,9 @@ Mounting this row outside an agent scope collides with the registry's own `deplo |---|---|---| | `text` | required | Persona prose rendered as the `deployment:persona` section | | `complete` | `false` | Restore this persona after assembly as the only system-prompt section | +| `includeRuntimeContext` | `true` | Include dynamic runtime-context snapshots for this agent scope; false suppresses every context contribution without disabling its owning services | -`text` is a template, like any prompt section: complete `{{…}}` groups resolve strictly against registered prompt variables when the prompt renders, not when it assembles. Empty text still occupies the slot, so it shadows the deployment persona away entirely and then disappears at render. With `complete: true`, assembly still resolves contexts, tools, variables, and cooperative listeners, then the prompt registry restores this exact persona as the sole section; no identity, tool guidance, or listener can append prompt text. +`text` is a template, like any prompt section: complete `{{…}}` groups resolve strictly against registered prompt variables when the prompt renders, not when it assembles. Empty text still occupies the slot, so it shadows the deployment persona away entirely and then disappears at render. With `complete: true`, assembly still resolves contexts, tools, variables, and cooperative listeners, then the prompt registry restores this exact persona as the sole section; no identity, tool guidance, or listener can append prompt text. With `includeRuntimeContext: false`, context providers are not evaluated for this scope and contexts added by assembly listeners are discarded. ## Model Experience @@ -25,7 +26,7 @@ Mounting this row outside an agent scope collides with the registry's own `deplo #### What the model sees -The `deployment:persona` section at order 0, immediately after the harness identity opener, carrying exactly this row's configured `text` with prompt variables resolved. For an agent whose preset mounts this row, it replaces whatever persona the deployment configured. In complete mode, the model sees only this rendered section as its system prompt. +The `deployment:persona` section at order 0, immediately after the harness identity opener, carrying exactly this row's configured `text` with prompt variables resolved. For an agent whose preset mounts this row, it replaces whatever persona the deployment configured. In complete mode, the model sees only this rendered section as its system prompt. Runtime context remains enabled by default. When disabled, a fresh agent receives no runtime-context snapshot from sandbox policy, approval policy, delegation, or another system-prompt context provider. #### Token effect diff --git a/packages/preset/persona/README.zh.md b/packages/preset/persona/README.zh.md index 61112d1091..d36af6c028 100644 --- a/packages/preset/persona/README.zh.md +++ b/packages/preset/persona/README.zh.md @@ -16,8 +16,9 @@ |---|---|---| | `text` | 必填 | 作为 `deployment:persona` 段落渲染的人设文本 | | `complete` | `false` | 组装后将此人设恢复为唯一的系统提示词段落 | +| `includeRuntimeContext` | `true` | 是否为此 agent 作用域包含动态 runtime-context 快照;false 会抑制所有上下文贡献,但不禁用拥有它们的服务 | -`text` 与任何提示词段落一样是模板:完整的 `{{…}}` 组在提示词**渲染**时(而非组装时)严格解析为已注册的提示词变量。空文本同样占据该槽位,因此会把部署级人设整个遮蔽掉,然后在渲染时消失。启用 `complete: true` 时,组装仍会解析上下文、工具、变量和协作式监听器,之后提示词注册表将这份确切人设恢复为唯一段落;身份、工具引导或监听器都无法追加提示词文本。 +`text` 与任何提示词段落一样是模板:完整的 `{{…}}` 组在提示词**渲染**时(而非组装时)严格解析为已注册的提示词变量。空文本同样占据该槽位,因此会把部署级人设整个遮蔽掉,然后在渲染时消失。启用 `complete: true` 时,组装仍会解析上下文、工具、变量和协作式监听器,之后提示词注册表将这份确切人设恢复为唯一段落;身份、工具引导或监听器都无法追加提示词文本。启用 `includeRuntimeContext: false` 时,此作用域的上下文提供方不会被求值,组装监听器添加的上下文也会被丢弃。 ## 模型体验 @@ -25,7 +26,7 @@ #### What the model sees -位于 order 0 的 `deployment:persona` 段落,紧随 harness 身份开场白之后,携带本行配置的 `text`,其中的提示词变量已解析。对于其 preset 挂载了本行的 agent,它会替换部署所配置的任何人设。在完整模式下,模型只会看到这个渲染后的段落作为系统提示词。 +位于 order 0 的 `deployment:persona` 段落,紧随 harness 身份开场白之后,携带本行配置的 `text`,其中的提示词变量已解析。对于其 preset 挂载了本行的 agent,它会替换部署所配置的任何人设。在完整模式下,模型只会看到这个渲染后的段落作为系统提示词。Runtime context 默认保持启用。禁用后,新建 agent 不会收到来自沙箱策略、批准策略、委派或其他 system-prompt 上下文提供方的 runtime-context 快照。 #### Token effect diff --git a/packages/preset/persona/src/index.ts b/packages/preset/persona/src/index.ts index 69a53678e9..f45b549656 100644 --- a/packages/preset/persona/src/index.ts +++ b/packages/preset/persona/src/index.ts @@ -40,12 +40,15 @@ export interface Config { text: string /** Make this persona the complete system prompt, suppressing every other section. */ complete?: boolean + /** Suppress dynamic runtime-context snapshots for this persona's agent scope. */ + includeRuntimeContext?: boolean } /** Runtime schema for the persona row. */ export const Config: z = z.object({ text: z.string().required(), complete: z.boolean().default(false), + includeRuntimeContext: z.boolean().default(true), }) /** @@ -61,4 +64,5 @@ export function apply(ctx: Context, config: Config): void { text: config.text, ...(config.complete ? { complete: true } : {}), }), 'persona.section()') + if (!(config.includeRuntimeContext ?? true)) ctx.systemPrompt.suppressRuntimeContext() } diff --git a/packages/preset/persona/tests/persona.spec.ts b/packages/preset/persona/tests/persona.spec.ts index 3fcc30d3a5..3343f7a6f9 100644 --- a/packages/preset/persona/tests/persona.spec.ts +++ b/packages/preset/persona/tests/persona.spec.ts @@ -102,4 +102,41 @@ describe('the persona row', () => { expect(assembly.sections).toEqual([{ name: PERSONA_SECTION, text: 'Only this.' }]) expect(renderPrompt(assembly)).toBe('Only this.') }) + + it('can suppress runtime context for its scope without changing the global assembly', async () => { + const ctx = await harness('deployment identity') + const key: ScopeKey = { agent: 'a1' } + const scope = createScope(ctx, key) + ctx.systemPrompt.context({ name: 'policy', order: 1, text: 'global policy' }) + + const fiber = await scope.ctx.plugin(Persona, { + text: 'Only this.', + includeRuntimeContext: false, + }) + const suppressed = await ctx.systemPrompt.assemble({ scope: key }) + expect(suppressed.contexts).toEqual([]) + const global = await ctx.systemPrompt.assemble() + expect(global.contexts).toEqual([ + { name: 'policy', text: 'global policy' }, + ]) + + await fiber.dispose() + expect((await ctx.systemPrompt.assemble({ scope: key })).contexts).toEqual([ + { name: 'policy', text: 'global policy' }, + ]) + }) + + it('keeps runtime context by default when apply bypasses schema defaults', async () => { + const ctx = await harness('deployment identity') + const key: ScopeKey = { agent: 'a1' } + ctx.systemPrompt.context({ name: 'policy', order: 1, text: 'global policy' }) + + await ctx.plugin(Object.assign((inner: Context) => { + Persona.apply(createScope(inner, key).ctx, { text: 'Scoped identity.' }) + }, { inject: ['systemPrompt'] })) + + expect((await ctx.systemPrompt.assemble({ scope: key })).contexts).toEqual([ + { name: 'policy', text: 'global policy' }, + ]) + }) }) diff --git a/packages/self-modification/tool-cordis/src/api-catalog.ts b/packages/self-modification/tool-cordis/src/api-catalog.ts index 6170c9a196..5a648cada1 100644 --- a/packages/self-modification/tool-cordis/src/api-catalog.ts +++ b/packages/self-modification/tool-cordis/src/api-catalog.ts @@ -1130,6 +1130,10 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ signature: 'context(context: PromptContext): () => void', jsDoc: '/**\n * Register ordered dynamic context in the calling context\'s scope. Scoped\n * entries shadow global entries with the same name.\n * @param context - the context contribution to register.\n * @returns the exact Cordis effect disposer.\n */', }, + { + signature: 'suppressRuntimeContext(): () => void', + jsDoc: '/**\n * Suppress every dynamic runtime-context contribution in the calling\n * context\'s scope without changing the services that own or enforce those\n * facts. Multiple suppressors remain independently disposable.\n * @returns the exact Cordis effect disposer.\n */', + }, { signature: 'tools(provider: (context: AssembleContext) => ToolProviderResult): () => void', jsDoc: '/**\n * Register a tool-schema provider in the calling context\'s scope. Global and\n * matching scoped providers both contribute; returning the reserved\n * {@link TOOL_ORDER_REST} name makes assembly fail.\n * @param provider - evaluated for each assembly with its context.\n * @returns the exact Cordis effect disposer.\n */', From 63fecf2534c02e3c13f73c0567e593db1deca1e9 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 12 Aug 2026 23:30:51 +0800 Subject: [PATCH 006/119] fix: name run_code's required description argument in its model-facing prose The transport schema requires both `code` and `description`, but the tool description and both SDK instruction flavors described the call as passing a program. `description` was reachable only through the parameter schema, so a model following the prose emitted `{code}` alone and lost the whole written program to an INVALID_ARGS rejection. The length and format guidance stays in RUN_CODE_DESCRIPTION_PARAM_DESCRIPTION alone, so the schema and the prompt cannot drift. Fixes #2426 --- .../feature/2026-06-15-code-mode.i18n.yaml | 4 +-- .../feature/2026-06-15-code-mode.md | 2 ++ .../feature/2026-06-15-code-mode.zh.md | 2 ++ docs/tool-catalog.i18n.yaml | 4 +-- docs/tool-catalog.md | 2 +- docs/tool-catalog.zh.md | 2 +- .../system-prompt.expected.md | 2 +- .../tool-schemas.expected.json | 2 +- .../both-mode-turn/system-prompt.expected.md | 2 +- .../both-mode-turn/tool-schemas.expected.json | 2 +- .../code-mode-turn/system-prompt.expected.md | 2 +- .../code-mode-turn/tool-schemas.expected.json | 2 +- .../advanced-toolchain/session.1.jsonl | 10 +++---- .../advanced-toolchain/session.2.jsonl | 10 +++---- .../advanced-toolchain/session.jsonl | 28 +++++++++---------- packages/core/tools/README.i18n.yaml | 4 +-- packages/core/tools/README.md | 2 +- packages/core/tools/README.zh.md | 2 +- packages/core/tools/src/code-mode.ts | 14 ++++++---- packages/core/tools/src/py-types.ts | 2 +- packages/core/tools/src/ts-types.ts | 2 +- packages/core/tools/tests/code-mode.spec.ts | 5 ++++ packages/core/tools/tests/py-types.spec.ts | 9 ++++++ packages/core/tools/tests/ts-types.spec.ts | 9 ++++++ 24 files changed, 77 insertions(+), 48 deletions(-) diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml index 60e70c2ba5..477957599b 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-15-code-mode.md -2026-06-15-code-mode.md: f9bde7a85d9cf26d3d6670744c955380fa807e3b -2026-06-15-code-mode.zh.md: 84fc5da63d6049e3533f753cf495aad37b075479 +2026-06-15-code-mode.md: 343ad4cf8191542016b492d6a16cdbf6c26b385c +2026-06-15-code-mode.zh.md: 668def6cf7e74288c3e16253c4ebefb4781db633 diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.md index f9bde7a85d..343ad4cf81 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.md +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.md @@ -87,6 +87,8 @@ The worker runtime provides containment, not a security boundary: model code can The SDK instructs the model to write an async body in the loaded runtime's language (an erasable-TypeScript body by default; a Python `async` body under a Python runtime — see the [language-dispatch note](2026-07-31-code-mode-language-dispatch.md)), call tools through `await tools.name(args)`, catch rejected tool calls when needed, and return or log only the output that should re-enter context. Both flavors state the same contract in their own primitive: independent read-only calls MAY overlap under `Promise.all` (TypeScript) or `asyncio.gather` (Python), mutating calls run alone in submission order, and dependent work sequences with `await`. The declaration prefix can be as large as native schemas, especially in `'both'`, but remains stable for provider caching. +The transport's own `description` and both SDK instruction flavors open by naming `code` and `description` as the call's two required arguments. Prose that describes the call as passing a program leaves the second argument discoverable only through the parameter schema, and a model that emits `{code}` alone loses the whole written program to an `INVALID_ARGS` rejection. + ## Consequences Deployments switching to `'code'` must update any native-only `toolOrder`. Assembly listeners own the integrity of any rewritten protocol messages. Sub-dispatch starts in submission order under a bounded overlap pool, while per-call contexts retain their source, envelope, and metadata through the outer result. diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md index 84fc5da63d..668def6cf7 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md @@ -87,6 +87,8 @@ worker 运行时只能约束程序的运行,而不构成安全边界:模型 SDK 指示模型编写一个所加载运行时语言的异步函数体(默认可擦除 TypeScript;Python 运行时下为 Python `async` 函数体——见[语言分发 note](2026-07-31-code-mode-language-dispatch.md)),通过 `await tools.name(args)` 调用工具,在需要时捕获被拒绝的工具调用,并仅 return 或 log 应重新进入上下文的输出。两种 flavor 用各自的原语陈述同一约定:相互独立的只读调用可以(MAY)在 `Promise.all`(TypeScript)或 `asyncio.gather`(Python)下重叠,有副作用的调用按提交顺序单独运行,有依赖的工作用 `await` 排序。声明前缀可能与原生 schema 一样大,尤其在 `'both'` 下,但对提供方缓存保持稳定。 +传输自身的 `description` 与两种 flavor 的 SDK 说明都以点名 `code` 和 `description` 这两个必填参数开头。把该调用描述成「传入一个程序」的散文会让第二个参数只能从参数 schema 中发现,而只发出 `{code}` 的模型会因 `INVALID_ARGS` 被拒,连同已写好的整个程序一起丢失。 + ## 后果 切换到 `'code'` 的部署必须更新任何仅限 native 的 `toolOrder`。组装监听器有责任维护任何被重写的协议消息的完整性。子分发在有界的重叠池下按提交顺序启动,而每次调用的上下文会通过外层结果保留其 source、信封与元数据。 diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index c9726f4ec5..882fb0818d 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/tool-catalog.md -tool-catalog.md: 898c5700eddfe49083b2ce0e3e04761298b28bbb -tool-catalog.zh.md: 3b17cf4e3b1b74b0735783cfe899c9c693146c38 +tool-catalog.md: b90a17c7a028b8212fbbcabe2490029de0e44d77 +tool-catalog.zh.md: fff2af027415ca0031926bdf79ce41f006d80a50 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 898c5700ed..b90a17c7a0 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -116,7 +116,7 @@ ask_user_question pauses the tool call until the active UI provider returns a hu ### `run_code` -Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it. +Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it. ```json { diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 3b17cf4e3b..fff2af0274 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -118,7 +118,7 @@ ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类 ### `run_code` -针对可用工具执行 TypeScript 程序。请编写异步函数的**函数体**(仅使用可擦除语法;支持顶层 `await` 和 `return`),并根据系统提示词中的声明,以 `await tools.name(args)` 形式调用工具。只有打印或返回的内容会传回,请谨慎筛选。 +针对可用工具执行 TypeScript 程序。接受两个必填参数:`code`,即异步函数的**函数体**(仅使用可擦除语法;支持顶层 `await` 和 `return`);以及 `description`,简要说明该程序做什么。请根据系统提示词中的声明,以 `await tools.name(args)` 形式调用工具。只有打印或返回的内容会传回,请谨慎筛选。 ```json { diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md index 3349deeb59..26ffef13f5 100644 --- a/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.expected.md @@ -25,7 +25,7 @@ Use subagent in the background by default. Start independent delegations togethe ## Writing code for run_code -Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: +`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program: - Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue. diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.expected.json b/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.expected.json index c5821f832b..e08c449f27 100644 --- a/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.expected.json +++ b/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.expected.json @@ -251,7 +251,7 @@ }, { "name": "run_code", - "description": "Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.", + "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.", "parameters": { "type": "object", "properties": { diff --git a/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.expected.md index 3771a70950..8501657bbf 100644 --- a/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.expected.md @@ -25,7 +25,7 @@ Use subagent in the background by default. Start independent delegations togethe ## Writing code for run_code -Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: +`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program: - Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue. diff --git a/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.expected.json b/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.expected.json index df0be9cab8..b1d7504245 100644 --- a/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.expected.json +++ b/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.expected.json @@ -194,7 +194,7 @@ }, { "name": "run_code", - "description": "Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.", + "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.", "parameters": { "type": "object", "properties": { diff --git a/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md index f3994dc95b..9dc989f1e3 100644 --- a/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md +++ b/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.expected.md @@ -27,7 +27,7 @@ Use subagent in the background by default. Start independent delegations togethe ## Writing code for run_code -Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: +`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program: - Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue. diff --git a/examples/acp-agent/tests/snapshots/code-mode-turn/tool-schemas.expected.json b/examples/acp-agent/tests/snapshots/code-mode-turn/tool-schemas.expected.json index a9ee29aa7a..2a94d503e2 100644 --- a/examples/acp-agent/tests/snapshots/code-mode-turn/tool-schemas.expected.json +++ b/examples/acp-agent/tests/snapshots/code-mode-turn/tool-schemas.expected.json @@ -2,7 +2,7 @@ "initial": [ { "name": "run_code", - "description": "Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.", + "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.", "parameters": { "type": "object", "properties": { diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl index 7f0414c53a..11f396a0cc 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl @@ -1,19 +1,19 @@ {"type":"session","version":0,"id":"22222222-2222-4222-8222-222222222222","createdAt":1783950001000,"cwd":"{{cwd}}","parentSession":"11111111-1111-4111-8111-111111111111","origin":"subagent","delegationDepth":1} -{"type":"agent/inbox/spliced","seq":0,"time":1785498583877,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"55db095e-ab6a-4e9f-aa74-53974c457c15"}]}} +{"type":"agent/inbox/spliced","seq":0,"time":1785498583877,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"2de15c96-aba9-4d7c-8426-33d171bb1bf4"}]}} {"type":"turn/start","seq":1,"time":1785821454445,"data":{"turn":1}} {"type":"agent/inbox/spliced","seq":2,"time":1785821454445,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"subagent/descriptor","seq":3,"time":1785821454466,"data":{"version":2,"mode":"one-shot","provider":"spawn","label":"Check direct child"}} {"type":"step/start","seq":4,"time":1785730501506,"data":{"turn":1,"step":1}} -{"type":"user/message","seq":5,"time":1785730501506,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"55db095e-ab6a-4e9f-aa74-53974c457c15"},"surfaceOp":"append"} -{"type":"user/message","seq":6,"time":1786373992181,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"cb0df0e2-55b0-4981-9a30-8c1da928e8b8"},"surfaceOp":"append"} +{"type":"user/message","seq":5,"time":1785730501506,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"2de15c96-aba9-4d7c-8426-33d171bb1bf4"},"surfaceOp":"append"} +{"type":"user/message","seq":6,"time":1786373992181,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"e1d05065-3965-454d-860b-5bc847a3526a"},"surfaceOp":"append"} {"type":"session/title","seq":7,"time":1786373992181,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}} -{"type":"request/header","seq":8,"time":1785498583897,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou are headless-agent, a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.\n\nVerify your work by running the code or tests. Keep answers brief and factual.\n\n\nUse the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.\n\nUse the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.\n\nUse the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.\n\nCheck the [exit code: N] marker on every bash result; investigate failures before moving on.\n\nTrack every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.\n\nUse the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.\n\nUse the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.\n\nUse subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.\n\n## Writing code for run_code\n\nPass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program:\n\n- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools[\"my-tool\"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.\n- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.\n- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.\n- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need.\n\nThe available tools:\n\n```ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\ninterface ToolArgsMap {\n /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. */\n bash: {\n /** The bash command to execute. */\n command: string;\n /** Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\". */\n description: string;\n /** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */\n timeoutMs?: number;\n /** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */\n workdir?: string;\n /** Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies. */\n run_in_background?: boolean;\n } & Record;\n /** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */\n cordis_inspect: {\n /** Limit the report to one section. Omit for all sections. */\n what?: \"services\" | \"plugins\" | \"tools\" | \"temporary\" | \"api\" | \"events\";\n /** Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\". */\n name?: string;\n } & Record;\n /** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */\n cordis_mount: {\n /** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */\n code: string;\n } & Record;\n /** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */\n cordis_unmount: {\n /** The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart. */\n id: string;\n } & Record;\n /** Edit an existing UTF-8 text file by replacing literal text. */\n edit: {\n /** Path to edit, resolved by the filesystem backend. */\n file_path: string;\n /** Literal text to replace. Must match exactly. */\n old_string: string;\n /** Literal replacement text. Use an empty string to delete the match. */\n new_string: string;\n /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */\n replace_all?: boolean;\n } & Record;\n /** Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op. */\n interrupt_agent: {\n /** The agent id of the running agent to interrupt. */\n agent_id: string;\n } & Record;\n /** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */\n ralph: {\n /** The immutable completion objective for every fresh Ralph round. */\n objective: string;\n /** Optional positive safe-integer round cap, bounded by the deployment ceiling. */\n maxRounds?: number;\n } & Record;\n /** Read a UTF-8 text file and return line-numbered content. */\n read: {\n /** Path to read, resolved by the filesystem backend. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n } & Record;\n /** Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered. */\n send_message: {\n /** The subagent id returned when the background subagent was started. */\n subagent_id: string;\n /** The message to deliver to the subagent. */\n message: string;\n } & Record;\n /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */\n skill: {\n /** The exact skill name from the available skills list. */\n name: string;\n } & Record;\n /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */\n subagent: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */\n prompt: string;\n /** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */\n run_in_background?: boolean;\n } & Record;\n /** Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result. */\n subagent_fork: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */\n prompt: string;\n } & Record;\n /** Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops. */\n task_kill: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Optional short reason, recorded in the log and forwarded to the task. */\n reason?: string;\n } & Record;\n /** List your background tasks (running and finished) with their ids, kinds, and statuses. */\n task_list: Record;\n /** Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */\n task_output: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive. */\n wait?: boolean;\n /** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */\n timeout_ms?: number;\n } & Record;\n /** Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). */\n todo_write: {\n /** The COMPLETE task list, replacing any previous list. */\n todos: ({\n /** What the task is — a short imperative line. */\n content: string;\n /** pending (not started) | in_progress (now) | completed (done). */\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n } & Record;\n /** Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn. The workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly. - `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages. - `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`. - `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim. Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`. Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes. */\n workflow: {\n /** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `). */\n script: string;\n /** The workflow identity block (plain JSON — never code). */\n meta: {\n /** Short kebab-case workflow name. */\n name: string;\n /** One-line description of what the workflow does. */\n description: string;\n /** Optional guidance on when this workflow applies. */\n whenToUse?: string;\n /** Optional phase declarations matched by phase() calls. */\n phases?: ({\n /** The phase title phase() calls match by exact string. */\n title: string;\n /** Optional one-line description of the phase. */\n detail?: string;\n /** Optional provider override this phase is expected to use. */\n provider?: string;\n /** Optional model override this phase is expected to use. */\n model?: string;\n } & Record)[];\n } & Record;\n /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}). */\n args?: Record;\n } & Record;\n /** Create or fully replace a UTF-8 text file. */\n write: {\n /** Path to write, resolved by the filesystem backend. */\n file_path: string;\n /** Full UTF-8 text content to write. */\n content: string;\n } & Record;\n}\n\ninterface ToolOutputMap {\n bash: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"foreground\";\n exitCode: number | null;\n signal: string | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n stderr: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n sandbox?: {\n mode: string;\n denied: boolean;\n enforcement?: string;\n runnerFailed?: boolean;\n };\n };\n cordis_inspect: string;\n cordis_mount: {\n id: string;\n pluginName: string;\n state: \"pending\" | \"loading\" | \"active\" | \"failed\" | \"disposed\" | \"unloading\";\n provides: string[];\n waitingFor: string[];\n };\n cordis_unmount: {\n id: string;\n pluginName: string;\n };\n edit: {\n path: string;\n before: string;\n after: string;\n };\n interrupt_agent: {\n accepted: boolean;\n };\n ralph: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n read: {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n };\n send_message: {\n messageId: string;\n };\n skill: {\n name: string;\n provider: string;\n resourceBase?: {\n kind: \"directory\";\n path: string;\n } | {\n kind: \"url\";\n url: string;\n } | {\n kind: \"opaque\";\n description: string;\n };\n content: string;\n };\n subagent: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n subagent_fork: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n task_kill: {\n outcome: \"cancellation-requested\" | \"already-finished\";\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n task_list: ({\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n })[];\n task_output: {\n text: string;\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n todo_write: {\n todos: ({\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n counts: {\n pending: number;\n inProgress: number;\n completed: number;\n };\n };\n workflow: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n write: {\n path: string;\n operation: \"create\" | \"update\";\n before: string | null;\n after: string;\n };\n}\n\ntype ToolName = keyof ToolOutputMap\n\ndeclare class ToolCallError extends Error {\n readonly name: \"ToolCallError\";\n readonly toolName: ToolName;\n}\n\ndeclare const tools: {\n [K in ToolName]: (args: ToolArgsMap[K]) => Promise;\n}\n```","tools":[{"name":"bash","description":"Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.","parameters":{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute."},"description":{"type":"string","description":"Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."},"timeoutMs":{"type":"number","description":"Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."},"workdir":{"type":"string","description":"Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."},"run_in_background":{"type":"boolean","description":"Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."}},"required":["command","description"]}},{"name":"cordis_inspect","description":"Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.","parameters":{"type":"object","properties":{"what":{"type":"string","description":"Limit the report to one section. Omit for all sections.","enum":["services","plugins","tools","temporary","api","events"]},"name":{"type":"string","description":"Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."}}}},{"name":"cordis_mount","description":"Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."}},"required":["code"]}},{"name":"cordis_unmount","description":"Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.","parameters":{"type":"object","properties":{"id":{"type":"string","description":"The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."}},"required":["id"]}},{"name":"edit","description":"Edit an existing UTF-8 text file by replacing literal text.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to edit, resolved by the filesystem backend."},"old_string":{"type":"string","description":"Literal text to replace. Must match exactly."},"new_string":{"type":"string","description":"Literal replacement text. Use an empty string to delete the match."},"replace_all":{"type":"boolean","description":"Replace all matches. Defaults to false; when false, old_string must appear exactly once."}},"required":["file_path","old_string","new_string"]}},{"name":"interrupt_agent","description":"Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.","parameters":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent id of the running agent to interrupt."}},"required":["agent_id"]}},{"name":"ralph","description":"Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.","parameters":{"type":"object","properties":{"objective":{"type":"string","description":"The immutable completion objective for every fresh Ralph round."},"maxRounds":{"type":"number","description":"Optional positive safe-integer round cap, bounded by the deployment ceiling."}},"required":["objective"]}},{"name":"read","description":"Read a UTF-8 text file and return line-numbered content.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to read, resolved by the filesystem backend."},"offset":{"type":"number","description":"1-based first line to return. Defaults to 1."},"limit":{"type":"number","description":"Maximum number of lines to return. Defaults to 2000."}},"required":["file_path"]}},{"name":"run_code","description":"Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"The program: the body of an async TypeScript function."},"description":{"type":"string","description":"Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."}},"required":["code","description"]}},{"name":"send_message","description":"Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.","parameters":{"type":"object","properties":{"subagent_id":{"type":"string","description":"The subagent id returned when the background subagent was started."},"message":{"type":"string","description":"The message to deliver to the subagent."}},"required":["subagent_id","message"]}},{"name":"skill","description":"Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.","parameters":{"type":"object","properties":{"name":{"type":"string","description":"The exact skill name from the available skills list."}},"required":["name"]}},{"name":"subagent","description":"Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."},"run_in_background":{"type":"boolean","description":"Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."}},"required":["description","prompt"]}},{"name":"subagent_fork","description":"Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."}},"required":["description","prompt"]}},{"name":"task_kill","description":"Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"reason":{"type":"string","description":"Optional short reason, recorded in the log and forwarded to the task."}},"required":["task_id"]}},{"name":"task_list","description":"List your background tasks (running and finished) with their ids, kinds, and statuses.","parameters":{"type":"object","properties":{}}},{"name":"task_output","description":"Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"wait":{"type":"boolean","description":"Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."},"timeout_ms":{"type":"number","description":"Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."}},"required":["task_id"]}},{"name":"todo_write","description":"Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).","parameters":{"type":"object","properties":{"todos":{"type":"array","description":"The COMPLETE task list, replacing any previous list.","items":{"type":"object","additionalProperties":false,"properties":{"content":{"type":"string","description":"What the task is — a short imperative line."},"status":{"type":"string","description":"pending (not started) | in_progress (now) | completed (done).","enum":["pending","in_progress","completed"]}},"required":["content","status"]}}},"required":["todos"]}},{"name":"workflow","description":"Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.","parameters":{"type":"object","properties":{"script":{"type":"string","description":"The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)."},"meta":{"type":"object","description":"The workflow identity block (plain JSON — never code).","additionalProperties":true,"properties":{"name":{"type":"string","description":"Short kebab-case workflow name."},"description":{"type":"string","description":"One-line description of what the workflow does."},"whenToUse":{"type":"string","description":"Optional guidance on when this workflow applies."},"phases":{"type":"array","description":"Optional phase declarations matched by phase() calls.","items":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","description":"The phase title phase() calls match by exact string."},"detail":{"type":"string","description":"Optional one-line description of the phase."},"provider":{"type":"string","description":"Optional provider override this phase is expected to use."},"model":{"type":"string","description":"Optional model override this phase is expected to use."}},"required":["title"]}}},"required":["name","description"]},"args":{"type":"object","description":"Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).","additionalProperties":true}},"required":["script","meta"]}},{"name":"write","description":"Create or fully replace a UTF-8 text file.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to write, resolved by the filesystem backend."},"content":{"type":"string","description":"Full UTF-8 text content to write."}},"required":["file_path","content"]}}]},"reason":"initial"}} +{"type":"request/header","seq":8,"time":1785498583897,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou are headless-agent, a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.\n\nVerify your work by running the code or tests. Keep answers brief and factual.\n\n\nUse the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.\n\nUse the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.\n\nUse the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.\n\nCheck the [exit code: N] marker on every bash result; investigate failures before moving on.\n\nTrack every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.\n\nUse the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.\n\nUse the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.\n\nUse subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.\n\n## Writing code for run_code\n\n`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program:\n\n- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools[\"my-tool\"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.\n- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.\n- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.\n- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need.\n\nThe available tools:\n\n```ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\ninterface ToolArgsMap {\n /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. */\n bash: {\n /** The bash command to execute. */\n command: string;\n /** Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\". */\n description: string;\n /** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */\n timeoutMs?: number;\n /** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */\n workdir?: string;\n /** Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies. */\n run_in_background?: boolean;\n } & Record;\n /** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */\n cordis_inspect: {\n /** Limit the report to one section. Omit for all sections. */\n what?: \"services\" | \"plugins\" | \"tools\" | \"temporary\" | \"api\" | \"events\";\n /** Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\". */\n name?: string;\n } & Record;\n /** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */\n cordis_mount: {\n /** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */\n code: string;\n } & Record;\n /** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */\n cordis_unmount: {\n /** The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart. */\n id: string;\n } & Record;\n /** Edit an existing UTF-8 text file by replacing literal text. */\n edit: {\n /** Path to edit, resolved by the filesystem backend. */\n file_path: string;\n /** Literal text to replace. Must match exactly. */\n old_string: string;\n /** Literal replacement text. Use an empty string to delete the match. */\n new_string: string;\n /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */\n replace_all?: boolean;\n } & Record;\n /** Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op. */\n interrupt_agent: {\n /** The agent id of the running agent to interrupt. */\n agent_id: string;\n } & Record;\n /** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */\n ralph: {\n /** The immutable completion objective for every fresh Ralph round. */\n objective: string;\n /** Optional positive safe-integer round cap, bounded by the deployment ceiling. */\n maxRounds?: number;\n } & Record;\n /** Read a UTF-8 text file and return line-numbered content. */\n read: {\n /** Path to read, resolved by the filesystem backend. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n } & Record;\n /** Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered. */\n send_message: {\n /** The subagent id returned when the background subagent was started. */\n subagent_id: string;\n /** The message to deliver to the subagent. */\n message: string;\n } & Record;\n /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */\n skill: {\n /** The exact skill name from the available skills list. */\n name: string;\n } & Record;\n /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */\n subagent: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */\n prompt: string;\n /** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */\n run_in_background?: boolean;\n } & Record;\n /** Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result. */\n subagent_fork: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */\n prompt: string;\n } & Record;\n /** Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops. */\n task_kill: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Optional short reason, recorded in the log and forwarded to the task. */\n reason?: string;\n } & Record;\n /** List your background tasks (running and finished) with their ids, kinds, and statuses. */\n task_list: Record;\n /** Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */\n task_output: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive. */\n wait?: boolean;\n /** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */\n timeout_ms?: number;\n } & Record;\n /** Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). */\n todo_write: {\n /** The COMPLETE task list, replacing any previous list. */\n todos: ({\n /** What the task is — a short imperative line. */\n content: string;\n /** pending (not started) | in_progress (now) | completed (done). */\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n } & Record;\n /** Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn. The workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly. - `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages. - `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`. - `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim. Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`. Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes. */\n workflow: {\n /** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `). */\n script: string;\n /** The workflow identity block (plain JSON — never code). */\n meta: {\n /** Short kebab-case workflow name. */\n name: string;\n /** One-line description of what the workflow does. */\n description: string;\n /** Optional guidance on when this workflow applies. */\n whenToUse?: string;\n /** Optional phase declarations matched by phase() calls. */\n phases?: ({\n /** The phase title phase() calls match by exact string. */\n title: string;\n /** Optional one-line description of the phase. */\n detail?: string;\n /** Optional provider override this phase is expected to use. */\n provider?: string;\n /** Optional model override this phase is expected to use. */\n model?: string;\n } & Record)[];\n } & Record;\n /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}). */\n args?: Record;\n } & Record;\n /** Create or fully replace a UTF-8 text file. */\n write: {\n /** Path to write, resolved by the filesystem backend. */\n file_path: string;\n /** Full UTF-8 text content to write. */\n content: string;\n } & Record;\n}\n\ninterface ToolOutputMap {\n bash: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"foreground\";\n exitCode: number | null;\n signal: string | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n stderr: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n sandbox?: {\n mode: string;\n denied: boolean;\n enforcement?: string;\n runnerFailed?: boolean;\n };\n };\n cordis_inspect: string;\n cordis_mount: {\n id: string;\n pluginName: string;\n state: \"pending\" | \"loading\" | \"active\" | \"failed\" | \"disposed\" | \"unloading\";\n provides: string[];\n waitingFor: string[];\n };\n cordis_unmount: {\n id: string;\n pluginName: string;\n };\n edit: {\n path: string;\n before: string;\n after: string;\n };\n interrupt_agent: {\n accepted: boolean;\n };\n ralph: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n read: {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n };\n send_message: {\n messageId: string;\n };\n skill: {\n name: string;\n provider: string;\n resourceBase?: {\n kind: \"directory\";\n path: string;\n } | {\n kind: \"url\";\n url: string;\n } | {\n kind: \"opaque\";\n description: string;\n };\n content: string;\n };\n subagent: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n subagent_fork: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n task_kill: {\n outcome: \"cancellation-requested\" | \"already-finished\";\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n task_list: ({\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n })[];\n task_output: {\n text: string;\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n todo_write: {\n todos: ({\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n counts: {\n pending: number;\n inProgress: number;\n completed: number;\n };\n };\n workflow: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n write: {\n path: string;\n operation: \"create\" | \"update\";\n before: string | null;\n after: string;\n };\n}\n\ntype ToolName = keyof ToolOutputMap\n\ndeclare class ToolCallError extends Error {\n readonly name: \"ToolCallError\";\n readonly toolName: ToolName;\n}\n\ndeclare const tools: {\n [K in ToolName]: (args: ToolArgsMap[K]) => Promise;\n}\n```","tools":[{"name":"bash","description":"Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.","parameters":{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute."},"description":{"type":"string","description":"Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."},"timeoutMs":{"type":"number","description":"Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."},"workdir":{"type":"string","description":"Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."},"run_in_background":{"type":"boolean","description":"Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."}},"required":["command","description"]}},{"name":"cordis_inspect","description":"Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.","parameters":{"type":"object","properties":{"what":{"type":"string","description":"Limit the report to one section. Omit for all sections.","enum":["services","plugins","tools","temporary","api","events"]},"name":{"type":"string","description":"Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."}}}},{"name":"cordis_mount","description":"Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."}},"required":["code"]}},{"name":"cordis_unmount","description":"Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.","parameters":{"type":"object","properties":{"id":{"type":"string","description":"The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."}},"required":["id"]}},{"name":"edit","description":"Edit an existing UTF-8 text file by replacing literal text.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to edit, resolved by the filesystem backend."},"old_string":{"type":"string","description":"Literal text to replace. Must match exactly."},"new_string":{"type":"string","description":"Literal replacement text. Use an empty string to delete the match."},"replace_all":{"type":"boolean","description":"Replace all matches. Defaults to false; when false, old_string must appear exactly once."}},"required":["file_path","old_string","new_string"]}},{"name":"interrupt_agent","description":"Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.","parameters":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent id of the running agent to interrupt."}},"required":["agent_id"]}},{"name":"ralph","description":"Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.","parameters":{"type":"object","properties":{"objective":{"type":"string","description":"The immutable completion objective for every fresh Ralph round."},"maxRounds":{"type":"number","description":"Optional positive safe-integer round cap, bounded by the deployment ceiling."}},"required":["objective"]}},{"name":"read","description":"Read a UTF-8 text file and return line-numbered content.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to read, resolved by the filesystem backend."},"offset":{"type":"number","description":"1-based first line to return. Defaults to 1."},"limit":{"type":"number","description":"Maximum number of lines to return. Defaults to 2000."}},"required":["file_path"]}},{"name":"run_code","description":"Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"The program: the body of an async TypeScript function."},"description":{"type":"string","description":"Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."}},"required":["code","description"]}},{"name":"send_message","description":"Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.","parameters":{"type":"object","properties":{"subagent_id":{"type":"string","description":"The subagent id returned when the background subagent was started."},"message":{"type":"string","description":"The message to deliver to the subagent."}},"required":["subagent_id","message"]}},{"name":"skill","description":"Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.","parameters":{"type":"object","properties":{"name":{"type":"string","description":"The exact skill name from the available skills list."}},"required":["name"]}},{"name":"subagent","description":"Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."},"run_in_background":{"type":"boolean","description":"Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."}},"required":["description","prompt"]}},{"name":"subagent_fork","description":"Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."}},"required":["description","prompt"]}},{"name":"task_kill","description":"Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"reason":{"type":"string","description":"Optional short reason, recorded in the log and forwarded to the task."}},"required":["task_id"]}},{"name":"task_list","description":"List your background tasks (running and finished) with their ids, kinds, and statuses.","parameters":{"type":"object","properties":{}}},{"name":"task_output","description":"Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"wait":{"type":"boolean","description":"Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."},"timeout_ms":{"type":"number","description":"Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."}},"required":["task_id"]}},{"name":"todo_write","description":"Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).","parameters":{"type":"object","properties":{"todos":{"type":"array","description":"The COMPLETE task list, replacing any previous list.","items":{"type":"object","additionalProperties":false,"properties":{"content":{"type":"string","description":"What the task is — a short imperative line."},"status":{"type":"string","description":"pending (not started) | in_progress (now) | completed (done).","enum":["pending","in_progress","completed"]}},"required":["content","status"]}}},"required":["todos"]}},{"name":"workflow","description":"Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.","parameters":{"type":"object","properties":{"script":{"type":"string","description":"The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)."},"meta":{"type":"object","description":"The workflow identity block (plain JSON — never code).","additionalProperties":true,"properties":{"name":{"type":"string","description":"Short kebab-case workflow name."},"description":{"type":"string","description":"One-line description of what the workflow does."},"whenToUse":{"type":"string","description":"Optional guidance on when this workflow applies."},"phases":{"type":"array","description":"Optional phase declarations matched by phase() calls.","items":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","description":"The phase title phase() calls match by exact string."},"detail":{"type":"string","description":"Optional one-line description of the phase."},"provider":{"type":"string","description":"Optional provider override this phase is expected to use."},"model":{"type":"string","description":"Optional model override this phase is expected to use."}},"required":["title"]}}},"required":["name","description"]},"args":{"type":"object","description":"Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).","additionalProperties":true}},"required":["script","meta"]}},{"name":"write","description":"Create or fully replace a UTF-8 text file.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to write, resolved by the filesystem backend."},"content":{"type":"string","description":"Full UTF-8 text content to write."}},"required":["file_path","content"]}}]},"reason":"initial"}} {"type":"request/context","seq":9,"time":1785730501507,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","seq":10,"time":1783957884564,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} {"type":"assistant/chunk","seq":11,"time":1783957884564,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}} {"type":"assistant/chunk","seq":12,"time":1783957884564,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}}} {"type":"assistant/chunk","seq":13,"time":1785498583897,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":14,"time":1785730501507,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","seq":15,"time":1785730501507,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"bcfce159-0aa7-45ef-8239-8222d55d3a8c"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} +{"type":"assistant/message","seq":15,"time":1785730501507,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3818bc0b-162c-4732-9479-38fd3f7b8346"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} {"type":"step/end","seq":16,"time":1785730501507,"data":{"turn":1,"step":1}} {"type":"turn/end","seq":17,"time":1785730501507,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl index 10ede98385..0e451f53c4 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl @@ -1,19 +1,19 @@ {"type":"session","version":0,"id":"33333333-3333-4333-8333-333333333333","createdAt":1783950002000,"cwd":"{{cwd}}","parentSession":"11111111-1111-4111-8111-111111111111","origin":"subagent","delegationDepth":1} -{"type":"agent/inbox/spliced","seq":0,"time":1785498584048,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"1ce5ce75-bd55-4372-8466-99b3f5b245bb"}]}} +{"type":"agent/inbox/spliced","seq":0,"time":1785498584048,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"e4481091-3d3d-410a-bdfd-ba2b2357bfbe"}]}} {"type":"turn/start","seq":1,"time":1785821454599,"data":{"turn":1}} {"type":"agent/inbox/spliced","seq":2,"time":1785821454599,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"subagent/descriptor","seq":3,"time":1785821454618,"data":{"version":2,"mode":"one-shot","provider":"spawn"}} {"type":"step/start","seq":4,"time":1785730501645,"data":{"turn":1,"step":1}} -{"type":"user/message","seq":5,"time":1785730501645,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"1ce5ce75-bd55-4372-8466-99b3f5b245bb"},"surfaceOp":"append"} -{"type":"user/message","seq":6,"time":1786373992411,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"f310d4b7-47c3-4d53-a6f2-91ebe89ecd8e"},"surfaceOp":"append"} +{"type":"user/message","seq":5,"time":1785730501645,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"e4481091-3d3d-410a-bdfd-ba2b2357bfbe"},"surfaceOp":"append"} +{"type":"user/message","seq":6,"time":1786373992411,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"bbafb446-3003-4973-a9dd-a6d41fda6507"},"surfaceOp":"append"} {"type":"session/title","seq":7,"time":1786373992411,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}} -{"type":"request/header","seq":8,"time":1785498584067,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou are headless-agent, a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.\n\nVerify your work by running the code or tests. Keep answers brief and factual.\n\n\nUse the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.\n\nUse the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.\n\nUse the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.\n\nCheck the [exit code: N] marker on every bash result; investigate failures before moving on.\n\nTrack every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.\n\nUse the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.\n\nUse the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.\n\nUse subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.\n\n## Writing code for run_code\n\nPass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program:\n\n- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools[\"my-tool\"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.\n- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.\n- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.\n- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need.\n\nThe available tools:\n\n```ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\ninterface ToolArgsMap {\n /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. */\n bash: {\n /** The bash command to execute. */\n command: string;\n /** Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\". */\n description: string;\n /** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */\n timeoutMs?: number;\n /** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */\n workdir?: string;\n /** Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies. */\n run_in_background?: boolean;\n } & Record;\n /** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */\n cordis_inspect: {\n /** Limit the report to one section. Omit for all sections. */\n what?: \"services\" | \"plugins\" | \"tools\" | \"temporary\" | \"api\" | \"events\";\n /** Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\". */\n name?: string;\n } & Record;\n /** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */\n cordis_mount: {\n /** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */\n code: string;\n } & Record;\n /** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */\n cordis_unmount: {\n /** The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart. */\n id: string;\n } & Record;\n /** Edit an existing UTF-8 text file by replacing literal text. */\n edit: {\n /** Path to edit, resolved by the filesystem backend. */\n file_path: string;\n /** Literal text to replace. Must match exactly. */\n old_string: string;\n /** Literal replacement text. Use an empty string to delete the match. */\n new_string: string;\n /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */\n replace_all?: boolean;\n } & Record;\n /** Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op. */\n interrupt_agent: {\n /** The agent id of the running agent to interrupt. */\n agent_id: string;\n } & Record;\n /** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */\n ralph: {\n /** The immutable completion objective for every fresh Ralph round. */\n objective: string;\n /** Optional positive safe-integer round cap, bounded by the deployment ceiling. */\n maxRounds?: number;\n } & Record;\n /** Read a UTF-8 text file and return line-numbered content. */\n read: {\n /** Path to read, resolved by the filesystem backend. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n } & Record;\n /** Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered. */\n send_message: {\n /** The subagent id returned when the background subagent was started. */\n subagent_id: string;\n /** The message to deliver to the subagent. */\n message: string;\n } & Record;\n /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */\n skill: {\n /** The exact skill name from the available skills list. */\n name: string;\n } & Record;\n /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */\n subagent: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */\n prompt: string;\n /** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */\n run_in_background?: boolean;\n } & Record;\n /** Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result. */\n subagent_fork: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */\n prompt: string;\n } & Record;\n /** Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops. */\n task_kill: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Optional short reason, recorded in the log and forwarded to the task. */\n reason?: string;\n } & Record;\n /** List your background tasks (running and finished) with their ids, kinds, and statuses. */\n task_list: Record;\n /** Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */\n task_output: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive. */\n wait?: boolean;\n /** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */\n timeout_ms?: number;\n } & Record;\n /** Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). */\n todo_write: {\n /** The COMPLETE task list, replacing any previous list. */\n todos: ({\n /** What the task is — a short imperative line. */\n content: string;\n /** pending (not started) | in_progress (now) | completed (done). */\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n } & Record;\n /** Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn. The workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly. - `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages. - `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`. - `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim. Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`. Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes. */\n workflow: {\n /** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `). */\n script: string;\n /** The workflow identity block (plain JSON — never code). */\n meta: {\n /** Short kebab-case workflow name. */\n name: string;\n /** One-line description of what the workflow does. */\n description: string;\n /** Optional guidance on when this workflow applies. */\n whenToUse?: string;\n /** Optional phase declarations matched by phase() calls. */\n phases?: ({\n /** The phase title phase() calls match by exact string. */\n title: string;\n /** Optional one-line description of the phase. */\n detail?: string;\n /** Optional provider override this phase is expected to use. */\n provider?: string;\n /** Optional model override this phase is expected to use. */\n model?: string;\n } & Record)[];\n } & Record;\n /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}). */\n args?: Record;\n } & Record;\n /** Create or fully replace a UTF-8 text file. */\n write: {\n /** Path to write, resolved by the filesystem backend. */\n file_path: string;\n /** Full UTF-8 text content to write. */\n content: string;\n } & Record;\n}\n\ninterface ToolOutputMap {\n bash: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"foreground\";\n exitCode: number | null;\n signal: string | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n stderr: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n sandbox?: {\n mode: string;\n denied: boolean;\n enforcement?: string;\n runnerFailed?: boolean;\n };\n };\n cordis_inspect: string;\n cordis_mount: {\n id: string;\n pluginName: string;\n state: \"pending\" | \"loading\" | \"active\" | \"failed\" | \"disposed\" | \"unloading\";\n provides: string[];\n waitingFor: string[];\n };\n cordis_unmount: {\n id: string;\n pluginName: string;\n };\n edit: {\n path: string;\n before: string;\n after: string;\n };\n interrupt_agent: {\n accepted: boolean;\n };\n ralph: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n read: {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n };\n send_message: {\n messageId: string;\n };\n skill: {\n name: string;\n provider: string;\n resourceBase?: {\n kind: \"directory\";\n path: string;\n } | {\n kind: \"url\";\n url: string;\n } | {\n kind: \"opaque\";\n description: string;\n };\n content: string;\n };\n subagent: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n subagent_fork: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n task_kill: {\n outcome: \"cancellation-requested\" | \"already-finished\";\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n task_list: ({\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n })[];\n task_output: {\n text: string;\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n todo_write: {\n todos: ({\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n counts: {\n pending: number;\n inProgress: number;\n completed: number;\n };\n };\n workflow: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n write: {\n path: string;\n operation: \"create\" | \"update\";\n before: string | null;\n after: string;\n };\n}\n\ntype ToolName = keyof ToolOutputMap\n\ndeclare class ToolCallError extends Error {\n readonly name: \"ToolCallError\";\n readonly toolName: ToolName;\n}\n\ndeclare const tools: {\n [K in ToolName]: (args: ToolArgsMap[K]) => Promise;\n}\n```","tools":[{"name":"bash","description":"Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.","parameters":{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute."},"description":{"type":"string","description":"Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."},"timeoutMs":{"type":"number","description":"Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."},"workdir":{"type":"string","description":"Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."},"run_in_background":{"type":"boolean","description":"Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."}},"required":["command","description"]}},{"name":"cordis_inspect","description":"Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.","parameters":{"type":"object","properties":{"what":{"type":"string","description":"Limit the report to one section. Omit for all sections.","enum":["services","plugins","tools","temporary","api","events"]},"name":{"type":"string","description":"Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."}}}},{"name":"cordis_mount","description":"Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."}},"required":["code"]}},{"name":"cordis_unmount","description":"Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.","parameters":{"type":"object","properties":{"id":{"type":"string","description":"The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."}},"required":["id"]}},{"name":"edit","description":"Edit an existing UTF-8 text file by replacing literal text.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to edit, resolved by the filesystem backend."},"old_string":{"type":"string","description":"Literal text to replace. Must match exactly."},"new_string":{"type":"string","description":"Literal replacement text. Use an empty string to delete the match."},"replace_all":{"type":"boolean","description":"Replace all matches. Defaults to false; when false, old_string must appear exactly once."}},"required":["file_path","old_string","new_string"]}},{"name":"interrupt_agent","description":"Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.","parameters":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent id of the running agent to interrupt."}},"required":["agent_id"]}},{"name":"ralph","description":"Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.","parameters":{"type":"object","properties":{"objective":{"type":"string","description":"The immutable completion objective for every fresh Ralph round."},"maxRounds":{"type":"number","description":"Optional positive safe-integer round cap, bounded by the deployment ceiling."}},"required":["objective"]}},{"name":"read","description":"Read a UTF-8 text file and return line-numbered content.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to read, resolved by the filesystem backend."},"offset":{"type":"number","description":"1-based first line to return. Defaults to 1."},"limit":{"type":"number","description":"Maximum number of lines to return. Defaults to 2000."}},"required":["file_path"]}},{"name":"run_code","description":"Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"The program: the body of an async TypeScript function."},"description":{"type":"string","description":"Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."}},"required":["code","description"]}},{"name":"send_message","description":"Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.","parameters":{"type":"object","properties":{"subagent_id":{"type":"string","description":"The subagent id returned when the background subagent was started."},"message":{"type":"string","description":"The message to deliver to the subagent."}},"required":["subagent_id","message"]}},{"name":"skill","description":"Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.","parameters":{"type":"object","properties":{"name":{"type":"string","description":"The exact skill name from the available skills list."}},"required":["name"]}},{"name":"subagent","description":"Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."},"run_in_background":{"type":"boolean","description":"Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."}},"required":["description","prompt"]}},{"name":"subagent_fork","description":"Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."}},"required":["description","prompt"]}},{"name":"task_kill","description":"Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"reason":{"type":"string","description":"Optional short reason, recorded in the log and forwarded to the task."}},"required":["task_id"]}},{"name":"task_list","description":"List your background tasks (running and finished) with their ids, kinds, and statuses.","parameters":{"type":"object","properties":{}}},{"name":"task_output","description":"Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"wait":{"type":"boolean","description":"Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."},"timeout_ms":{"type":"number","description":"Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."}},"required":["task_id"]}},{"name":"todo_write","description":"Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).","parameters":{"type":"object","properties":{"todos":{"type":"array","description":"The COMPLETE task list, replacing any previous list.","items":{"type":"object","additionalProperties":false,"properties":{"content":{"type":"string","description":"What the task is — a short imperative line."},"status":{"type":"string","description":"pending (not started) | in_progress (now) | completed (done).","enum":["pending","in_progress","completed"]}},"required":["content","status"]}}},"required":["todos"]}},{"name":"workflow","description":"Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.","parameters":{"type":"object","properties":{"script":{"type":"string","description":"The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)."},"meta":{"type":"object","description":"The workflow identity block (plain JSON — never code).","additionalProperties":true,"properties":{"name":{"type":"string","description":"Short kebab-case workflow name."},"description":{"type":"string","description":"One-line description of what the workflow does."},"whenToUse":{"type":"string","description":"Optional guidance on when this workflow applies."},"phases":{"type":"array","description":"Optional phase declarations matched by phase() calls.","items":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","description":"The phase title phase() calls match by exact string."},"detail":{"type":"string","description":"Optional one-line description of the phase."},"provider":{"type":"string","description":"Optional provider override this phase is expected to use."},"model":{"type":"string","description":"Optional model override this phase is expected to use."}},"required":["title"]}}},"required":["name","description"]},"args":{"type":"object","description":"Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).","additionalProperties":true}},"required":["script","meta"]}},{"name":"write","description":"Create or fully replace a UTF-8 text file.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to write, resolved by the filesystem backend."},"content":{"type":"string","description":"Full UTF-8 text content to write."}},"required":["file_path","content"]}}]},"reason":"initial"}} +{"type":"request/header","seq":8,"time":1785498584067,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou are headless-agent, a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.\n\nVerify your work by running the code or tests. Keep answers brief and factual.\n\n\nUse the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.\n\nUse the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.\n\nUse the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.\n\nCheck the [exit code: N] marker on every bash result; investigate failures before moving on.\n\nTrack every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.\n\nUse the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.\n\nUse the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.\n\nUse subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.\n\n## Writing code for run_code\n\n`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program:\n\n- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools[\"my-tool\"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.\n- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.\n- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.\n- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need.\n\nThe available tools:\n\n```ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\ninterface ToolArgsMap {\n /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. */\n bash: {\n /** The bash command to execute. */\n command: string;\n /** Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\". */\n description: string;\n /** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */\n timeoutMs?: number;\n /** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */\n workdir?: string;\n /** Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies. */\n run_in_background?: boolean;\n } & Record;\n /** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */\n cordis_inspect: {\n /** Limit the report to one section. Omit for all sections. */\n what?: \"services\" | \"plugins\" | \"tools\" | \"temporary\" | \"api\" | \"events\";\n /** Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\". */\n name?: string;\n } & Record;\n /** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */\n cordis_mount: {\n /** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */\n code: string;\n } & Record;\n /** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */\n cordis_unmount: {\n /** The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart. */\n id: string;\n } & Record;\n /** Edit an existing UTF-8 text file by replacing literal text. */\n edit: {\n /** Path to edit, resolved by the filesystem backend. */\n file_path: string;\n /** Literal text to replace. Must match exactly. */\n old_string: string;\n /** Literal replacement text. Use an empty string to delete the match. */\n new_string: string;\n /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */\n replace_all?: boolean;\n } & Record;\n /** Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op. */\n interrupt_agent: {\n /** The agent id of the running agent to interrupt. */\n agent_id: string;\n } & Record;\n /** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */\n ralph: {\n /** The immutable completion objective for every fresh Ralph round. */\n objective: string;\n /** Optional positive safe-integer round cap, bounded by the deployment ceiling. */\n maxRounds?: number;\n } & Record;\n /** Read a UTF-8 text file and return line-numbered content. */\n read: {\n /** Path to read, resolved by the filesystem backend. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n } & Record;\n /** Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered. */\n send_message: {\n /** The subagent id returned when the background subagent was started. */\n subagent_id: string;\n /** The message to deliver to the subagent. */\n message: string;\n } & Record;\n /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */\n skill: {\n /** The exact skill name from the available skills list. */\n name: string;\n } & Record;\n /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */\n subagent: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */\n prompt: string;\n /** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */\n run_in_background?: boolean;\n } & Record;\n /** Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result. */\n subagent_fork: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */\n prompt: string;\n } & Record;\n /** Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops. */\n task_kill: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Optional short reason, recorded in the log and forwarded to the task. */\n reason?: string;\n } & Record;\n /** List your background tasks (running and finished) with their ids, kinds, and statuses. */\n task_list: Record;\n /** Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */\n task_output: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive. */\n wait?: boolean;\n /** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */\n timeout_ms?: number;\n } & Record;\n /** Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). */\n todo_write: {\n /** The COMPLETE task list, replacing any previous list. */\n todos: ({\n /** What the task is — a short imperative line. */\n content: string;\n /** pending (not started) | in_progress (now) | completed (done). */\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n } & Record;\n /** Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn. The workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly. - `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages. - `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`. - `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim. Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`. Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes. */\n workflow: {\n /** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `). */\n script: string;\n /** The workflow identity block (plain JSON — never code). */\n meta: {\n /** Short kebab-case workflow name. */\n name: string;\n /** One-line description of what the workflow does. */\n description: string;\n /** Optional guidance on when this workflow applies. */\n whenToUse?: string;\n /** Optional phase declarations matched by phase() calls. */\n phases?: ({\n /** The phase title phase() calls match by exact string. */\n title: string;\n /** Optional one-line description of the phase. */\n detail?: string;\n /** Optional provider override this phase is expected to use. */\n provider?: string;\n /** Optional model override this phase is expected to use. */\n model?: string;\n } & Record)[];\n } & Record;\n /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}). */\n args?: Record;\n } & Record;\n /** Create or fully replace a UTF-8 text file. */\n write: {\n /** Path to write, resolved by the filesystem backend. */\n file_path: string;\n /** Full UTF-8 text content to write. */\n content: string;\n } & Record;\n}\n\ninterface ToolOutputMap {\n bash: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"foreground\";\n exitCode: number | null;\n signal: string | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n stderr: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n sandbox?: {\n mode: string;\n denied: boolean;\n enforcement?: string;\n runnerFailed?: boolean;\n };\n };\n cordis_inspect: string;\n cordis_mount: {\n id: string;\n pluginName: string;\n state: \"pending\" | \"loading\" | \"active\" | \"failed\" | \"disposed\" | \"unloading\";\n provides: string[];\n waitingFor: string[];\n };\n cordis_unmount: {\n id: string;\n pluginName: string;\n };\n edit: {\n path: string;\n before: string;\n after: string;\n };\n interrupt_agent: {\n accepted: boolean;\n };\n ralph: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n read: {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n };\n send_message: {\n messageId: string;\n };\n skill: {\n name: string;\n provider: string;\n resourceBase?: {\n kind: \"directory\";\n path: string;\n } | {\n kind: \"url\";\n url: string;\n } | {\n kind: \"opaque\";\n description: string;\n };\n content: string;\n };\n subagent: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n subagent_fork: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n task_kill: {\n outcome: \"cancellation-requested\" | \"already-finished\";\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n task_list: ({\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n })[];\n task_output: {\n text: string;\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n todo_write: {\n todos: ({\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n counts: {\n pending: number;\n inProgress: number;\n completed: number;\n };\n };\n workflow: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n write: {\n path: string;\n operation: \"create\" | \"update\";\n before: string | null;\n after: string;\n };\n}\n\ntype ToolName = keyof ToolOutputMap\n\ndeclare class ToolCallError extends Error {\n readonly name: \"ToolCallError\";\n readonly toolName: ToolName;\n}\n\ndeclare const tools: {\n [K in ToolName]: (args: ToolArgsMap[K]) => Promise;\n}\n```","tools":[{"name":"bash","description":"Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.","parameters":{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute."},"description":{"type":"string","description":"Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."},"timeoutMs":{"type":"number","description":"Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."},"workdir":{"type":"string","description":"Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."},"run_in_background":{"type":"boolean","description":"Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."}},"required":["command","description"]}},{"name":"cordis_inspect","description":"Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.","parameters":{"type":"object","properties":{"what":{"type":"string","description":"Limit the report to one section. Omit for all sections.","enum":["services","plugins","tools","temporary","api","events"]},"name":{"type":"string","description":"Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."}}}},{"name":"cordis_mount","description":"Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."}},"required":["code"]}},{"name":"cordis_unmount","description":"Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.","parameters":{"type":"object","properties":{"id":{"type":"string","description":"The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."}},"required":["id"]}},{"name":"edit","description":"Edit an existing UTF-8 text file by replacing literal text.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to edit, resolved by the filesystem backend."},"old_string":{"type":"string","description":"Literal text to replace. Must match exactly."},"new_string":{"type":"string","description":"Literal replacement text. Use an empty string to delete the match."},"replace_all":{"type":"boolean","description":"Replace all matches. Defaults to false; when false, old_string must appear exactly once."}},"required":["file_path","old_string","new_string"]}},{"name":"interrupt_agent","description":"Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.","parameters":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent id of the running agent to interrupt."}},"required":["agent_id"]}},{"name":"ralph","description":"Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.","parameters":{"type":"object","properties":{"objective":{"type":"string","description":"The immutable completion objective for every fresh Ralph round."},"maxRounds":{"type":"number","description":"Optional positive safe-integer round cap, bounded by the deployment ceiling."}},"required":["objective"]}},{"name":"read","description":"Read a UTF-8 text file and return line-numbered content.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to read, resolved by the filesystem backend."},"offset":{"type":"number","description":"1-based first line to return. Defaults to 1."},"limit":{"type":"number","description":"Maximum number of lines to return. Defaults to 2000."}},"required":["file_path"]}},{"name":"run_code","description":"Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"The program: the body of an async TypeScript function."},"description":{"type":"string","description":"Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."}},"required":["code","description"]}},{"name":"send_message","description":"Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.","parameters":{"type":"object","properties":{"subagent_id":{"type":"string","description":"The subagent id returned when the background subagent was started."},"message":{"type":"string","description":"The message to deliver to the subagent."}},"required":["subagent_id","message"]}},{"name":"skill","description":"Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.","parameters":{"type":"object","properties":{"name":{"type":"string","description":"The exact skill name from the available skills list."}},"required":["name"]}},{"name":"subagent","description":"Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."},"run_in_background":{"type":"boolean","description":"Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."}},"required":["description","prompt"]}},{"name":"subagent_fork","description":"Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."}},"required":["description","prompt"]}},{"name":"task_kill","description":"Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"reason":{"type":"string","description":"Optional short reason, recorded in the log and forwarded to the task."}},"required":["task_id"]}},{"name":"task_list","description":"List your background tasks (running and finished) with their ids, kinds, and statuses.","parameters":{"type":"object","properties":{}}},{"name":"task_output","description":"Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"wait":{"type":"boolean","description":"Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."},"timeout_ms":{"type":"number","description":"Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."}},"required":["task_id"]}},{"name":"todo_write","description":"Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).","parameters":{"type":"object","properties":{"todos":{"type":"array","description":"The COMPLETE task list, replacing any previous list.","items":{"type":"object","additionalProperties":false,"properties":{"content":{"type":"string","description":"What the task is — a short imperative line."},"status":{"type":"string","description":"pending (not started) | in_progress (now) | completed (done).","enum":["pending","in_progress","completed"]}},"required":["content","status"]}}},"required":["todos"]}},{"name":"workflow","description":"Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.","parameters":{"type":"object","properties":{"script":{"type":"string","description":"The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)."},"meta":{"type":"object","description":"The workflow identity block (plain JSON — never code).","additionalProperties":true,"properties":{"name":{"type":"string","description":"Short kebab-case workflow name."},"description":{"type":"string","description":"One-line description of what the workflow does."},"whenToUse":{"type":"string","description":"Optional guidance on when this workflow applies."},"phases":{"type":"array","description":"Optional phase declarations matched by phase() calls.","items":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","description":"The phase title phase() calls match by exact string."},"detail":{"type":"string","description":"Optional one-line description of the phase."},"provider":{"type":"string","description":"Optional provider override this phase is expected to use."},"model":{"type":"string","description":"Optional model override this phase is expected to use."}},"required":["title"]}}},"required":["name","description"]},"args":{"type":"object","description":"Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).","additionalProperties":true}},"required":["script","meta"]}},{"name":"write","description":"Create or fully replace a UTF-8 text file.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to write, resolved by the filesystem backend."},"content":{"type":"string","description":"Full UTF-8 text content to write."}},"required":["file_path","content"]}}]},"reason":"initial"}} {"type":"request/context","seq":9,"time":1785730501646,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","seq":10,"time":1783957884701,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} {"type":"assistant/chunk","seq":11,"time":1783957884701,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}} {"type":"assistant/chunk","seq":12,"time":1783957884701,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}}} {"type":"assistant/chunk","seq":13,"time":1785498584067,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":14,"time":1785730501646,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","seq":15,"time":1785730501646,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"1d8ec7e3-56a0-4d7c-99af-146add0a2de1"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} +{"type":"assistant/message","seq":15,"time":1785730501646,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0a72dd70-1910-4d7d-981c-dc8784aa99f8"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"} {"type":"step/end","seq":16,"time":1785730501646,"data":{"turn":1,"step":1}} {"type":"turn/end","seq":17,"time":1785730501646,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl index 3671d32fb6..cf12af4a5e 100644 --- a/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl +++ b/examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl @@ -1,20 +1,20 @@ {"type":"session","version":0,"id":"11111111-1111-4111-8111-111111111111","createdAt":1783950000000,"cwd":"{{cwd}}","delegationDepth":0} -{"type":"agent/inbox/spliced","seq":0,"time":1785498583746,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"7b9df077-cb93-4354-b6f6-f46bb5d55c31"}]}} +{"type":"agent/inbox/spliced","seq":0,"time":1785498583746,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"b87ed956-7342-4b8b-a7da-71cca4a08137"}]}} {"type":"turn/start","seq":1,"time":1785821454304,"data":{"turn":1}} {"type":"agent/inbox/spliced","seq":2,"time":1785821454304,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","seq":3,"time":1783957884486,"data":{"turn":1,"step":1}} -{"type":"user/message","seq":4,"time":1785498583779,"data":{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"7b9df077-cb93-4354-b6f6-f46bb5d55c31"},"surfaceOp":"append"} +{"type":"user/message","seq":4,"time":1785498583779,"data":{"content":[{"type":"text","text":"Run this advanced flow exactly once: try a no-op temporary Cordis Plugin named snapshot-marker; use run_code to inspect the live temporary Plugins through tools.cordis_inspect; delegate once to a direct spawn child; run one workflow that delegates to another spawn child; stop dyn-1; then reply with exactly ADVANCED_HEADLESS_OK."}],"source":{"kind":"user"},"role":"user","id":"b87ed956-7342-4b8b-a7da-71cca4a08137"},"surfaceOp":"append"} {"type":"session/title","seq":5,"time":1785498583779,"data":{"title":"Run this advanced flow exactly","messageSeqs":[4],"source":{"kind":"fallback"}}} -{"type":"request/header","seq":6,"time":1785498583782,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou are headless-agent, a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.\n\nVerify your work by running the code or tests. Keep answers brief and factual.\n\n\nUse the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.\n\nUse the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.\n\nUse the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.\n\nCheck the [exit code: N] marker on every bash result; investigate failures before moving on.\n\nTrack every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.\n\nUse the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.\n\nUse the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.\n\nUse subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.\n\n## Writing code for run_code\n\nPass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program:\n\n- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools[\"my-tool\"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.\n- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.\n- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.\n- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need.\n\nThe available tools:\n\n```ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\ninterface ToolArgsMap {\n /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. */\n bash: {\n /** The bash command to execute. */\n command: string;\n /** Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\". */\n description: string;\n /** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */\n timeoutMs?: number;\n /** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */\n workdir?: string;\n /** Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies. */\n run_in_background?: boolean;\n } & Record;\n /** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */\n cordis_inspect: {\n /** Limit the report to one section. Omit for all sections. */\n what?: \"services\" | \"plugins\" | \"tools\" | \"temporary\" | \"api\" | \"events\";\n /** Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\". */\n name?: string;\n } & Record;\n /** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */\n cordis_mount: {\n /** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */\n code: string;\n } & Record;\n /** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */\n cordis_unmount: {\n /** The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart. */\n id: string;\n } & Record;\n /** Edit an existing UTF-8 text file by replacing literal text. */\n edit: {\n /** Path to edit, resolved by the filesystem backend. */\n file_path: string;\n /** Literal text to replace. Must match exactly. */\n old_string: string;\n /** Literal replacement text. Use an empty string to delete the match. */\n new_string: string;\n /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */\n replace_all?: boolean;\n } & Record;\n /** Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op. */\n interrupt_agent: {\n /** The agent id of the running agent to interrupt. */\n agent_id: string;\n } & Record;\n /** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */\n ralph: {\n /** The immutable completion objective for every fresh Ralph round. */\n objective: string;\n /** Optional positive safe-integer round cap, bounded by the deployment ceiling. */\n maxRounds?: number;\n } & Record;\n /** Read a UTF-8 text file and return line-numbered content. */\n read: {\n /** Path to read, resolved by the filesystem backend. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n } & Record;\n /** Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered. */\n send_message: {\n /** The subagent id returned when the background subagent was started. */\n subagent_id: string;\n /** The message to deliver to the subagent. */\n message: string;\n } & Record;\n /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */\n skill: {\n /** The exact skill name from the available skills list. */\n name: string;\n } & Record;\n /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */\n subagent: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */\n prompt: string;\n /** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */\n run_in_background?: boolean;\n } & Record;\n /** Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result. */\n subagent_fork: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */\n prompt: string;\n } & Record;\n /** Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops. */\n task_kill: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Optional short reason, recorded in the log and forwarded to the task. */\n reason?: string;\n } & Record;\n /** List your background tasks (running and finished) with their ids, kinds, and statuses. */\n task_list: Record;\n /** Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */\n task_output: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive. */\n wait?: boolean;\n /** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */\n timeout_ms?: number;\n } & Record;\n /** Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). */\n todo_write: {\n /** The COMPLETE task list, replacing any previous list. */\n todos: ({\n /** What the task is — a short imperative line. */\n content: string;\n /** pending (not started) | in_progress (now) | completed (done). */\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n } & Record;\n /** Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn. The workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly. - `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages. - `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`. - `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim. Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`. Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes. */\n workflow: {\n /** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `). */\n script: string;\n /** The workflow identity block (plain JSON — never code). */\n meta: {\n /** Short kebab-case workflow name. */\n name: string;\n /** One-line description of what the workflow does. */\n description: string;\n /** Optional guidance on when this workflow applies. */\n whenToUse?: string;\n /** Optional phase declarations matched by phase() calls. */\n phases?: ({\n /** The phase title phase() calls match by exact string. */\n title: string;\n /** Optional one-line description of the phase. */\n detail?: string;\n /** Optional provider override this phase is expected to use. */\n provider?: string;\n /** Optional model override this phase is expected to use. */\n model?: string;\n } & Record)[];\n } & Record;\n /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}). */\n args?: Record;\n } & Record;\n /** Create or fully replace a UTF-8 text file. */\n write: {\n /** Path to write, resolved by the filesystem backend. */\n file_path: string;\n /** Full UTF-8 text content to write. */\n content: string;\n } & Record;\n}\n\ninterface ToolOutputMap {\n bash: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"foreground\";\n exitCode: number | null;\n signal: string | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n stderr: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n sandbox?: {\n mode: string;\n denied: boolean;\n enforcement?: string;\n runnerFailed?: boolean;\n };\n };\n cordis_inspect: string;\n cordis_mount: {\n id: string;\n pluginName: string;\n state: \"pending\" | \"loading\" | \"active\" | \"failed\" | \"disposed\" | \"unloading\";\n provides: string[];\n waitingFor: string[];\n };\n cordis_unmount: {\n id: string;\n pluginName: string;\n };\n edit: {\n path: string;\n before: string;\n after: string;\n };\n interrupt_agent: {\n accepted: boolean;\n };\n ralph: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n read: {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n };\n send_message: {\n messageId: string;\n };\n skill: {\n name: string;\n provider: string;\n resourceBase?: {\n kind: \"directory\";\n path: string;\n } | {\n kind: \"url\";\n url: string;\n } | {\n kind: \"opaque\";\n description: string;\n };\n content: string;\n };\n subagent: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n subagent_fork: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n task_kill: {\n outcome: \"cancellation-requested\" | \"already-finished\";\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n task_list: ({\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n })[];\n task_output: {\n text: string;\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n todo_write: {\n todos: ({\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n counts: {\n pending: number;\n inProgress: number;\n completed: number;\n };\n };\n workflow: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n write: {\n path: string;\n operation: \"create\" | \"update\";\n before: string | null;\n after: string;\n };\n}\n\ntype ToolName = keyof ToolOutputMap\n\ndeclare class ToolCallError extends Error {\n readonly name: \"ToolCallError\";\n readonly toolName: ToolName;\n}\n\ndeclare const tools: {\n [K in ToolName]: (args: ToolArgsMap[K]) => Promise;\n}\n```","tools":[{"name":"bash","description":"Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.","parameters":{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute."},"description":{"type":"string","description":"Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."},"timeoutMs":{"type":"number","description":"Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."},"workdir":{"type":"string","description":"Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."},"run_in_background":{"type":"boolean","description":"Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."}},"required":["command","description"]}},{"name":"cordis_inspect","description":"Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.","parameters":{"type":"object","properties":{"what":{"type":"string","description":"Limit the report to one section. Omit for all sections.","enum":["services","plugins","tools","temporary","api","events"]},"name":{"type":"string","description":"Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."}}}},{"name":"cordis_mount","description":"Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."}},"required":["code"]}},{"name":"cordis_unmount","description":"Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.","parameters":{"type":"object","properties":{"id":{"type":"string","description":"The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."}},"required":["id"]}},{"name":"edit","description":"Edit an existing UTF-8 text file by replacing literal text.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to edit, resolved by the filesystem backend."},"old_string":{"type":"string","description":"Literal text to replace. Must match exactly."},"new_string":{"type":"string","description":"Literal replacement text. Use an empty string to delete the match."},"replace_all":{"type":"boolean","description":"Replace all matches. Defaults to false; when false, old_string must appear exactly once."}},"required":["file_path","old_string","new_string"]}},{"name":"interrupt_agent","description":"Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.","parameters":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent id of the running agent to interrupt."}},"required":["agent_id"]}},{"name":"ralph","description":"Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.","parameters":{"type":"object","properties":{"objective":{"type":"string","description":"The immutable completion objective for every fresh Ralph round."},"maxRounds":{"type":"number","description":"Optional positive safe-integer round cap, bounded by the deployment ceiling."}},"required":["objective"]}},{"name":"read","description":"Read a UTF-8 text file and return line-numbered content.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to read, resolved by the filesystem backend."},"offset":{"type":"number","description":"1-based first line to return. Defaults to 1."},"limit":{"type":"number","description":"Maximum number of lines to return. Defaults to 2000."}},"required":["file_path"]}},{"name":"run_code","description":"Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"The program: the body of an async TypeScript function."},"description":{"type":"string","description":"Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."}},"required":["code","description"]}},{"name":"send_message","description":"Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.","parameters":{"type":"object","properties":{"subagent_id":{"type":"string","description":"The subagent id returned when the background subagent was started."},"message":{"type":"string","description":"The message to deliver to the subagent."}},"required":["subagent_id","message"]}},{"name":"skill","description":"Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.","parameters":{"type":"object","properties":{"name":{"type":"string","description":"The exact skill name from the available skills list."}},"required":["name"]}},{"name":"subagent","description":"Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."},"run_in_background":{"type":"boolean","description":"Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."}},"required":["description","prompt"]}},{"name":"subagent_fork","description":"Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."}},"required":["description","prompt"]}},{"name":"task_kill","description":"Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"reason":{"type":"string","description":"Optional short reason, recorded in the log and forwarded to the task."}},"required":["task_id"]}},{"name":"task_list","description":"List your background tasks (running and finished) with their ids, kinds, and statuses.","parameters":{"type":"object","properties":{}}},{"name":"task_output","description":"Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"wait":{"type":"boolean","description":"Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."},"timeout_ms":{"type":"number","description":"Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."}},"required":["task_id"]}},{"name":"todo_write","description":"Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).","parameters":{"type":"object","properties":{"todos":{"type":"array","description":"The COMPLETE task list, replacing any previous list.","items":{"type":"object","additionalProperties":false,"properties":{"content":{"type":"string","description":"What the task is — a short imperative line."},"status":{"type":"string","description":"pending (not started) | in_progress (now) | completed (done).","enum":["pending","in_progress","completed"]}},"required":["content","status"]}}},"required":["todos"]}},{"name":"workflow","description":"Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.","parameters":{"type":"object","properties":{"script":{"type":"string","description":"The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)."},"meta":{"type":"object","description":"The workflow identity block (plain JSON — never code).","additionalProperties":true,"properties":{"name":{"type":"string","description":"Short kebab-case workflow name."},"description":{"type":"string","description":"One-line description of what the workflow does."},"whenToUse":{"type":"string","description":"Optional guidance on when this workflow applies."},"phases":{"type":"array","description":"Optional phase declarations matched by phase() calls.","items":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","description":"The phase title phase() calls match by exact string."},"detail":{"type":"string","description":"Optional one-line description of the phase."},"provider":{"type":"string","description":"Optional provider override this phase is expected to use."},"model":{"type":"string","description":"Optional model override this phase is expected to use."}},"required":["title"]}}},"required":["name","description"]},"args":{"type":"object","description":"Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).","additionalProperties":true}},"required":["script","meta"]}},{"name":"write","description":"Create or fully replace a UTF-8 text file.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to write, resolved by the filesystem backend."},"content":{"type":"string","description":"Full UTF-8 text content to write."}},"required":["file_path","content"]}}]},"reason":"initial"}} +{"type":"request/header","seq":6,"time":1785498583782,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou are headless-agent, a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.\n\nVerify your work by running the code or tests. Keep answers brief and factual.\n\n\nUse the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.\n\nUse the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.\n\nUse the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.\n\nCheck the [exit code: N] marker on every bash result; investigate failures before moving on.\n\nTrack every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.\n\nUse the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls.\n\nUse the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out.\n\nUse subagent in the background by default. Start independent delegations together in one assistant message and continue useful work while they run. Set `run_in_background: false` only when your next action depends on that subagent's result. When a background run settles, the runtime sends you a notice containing its outcome and any final assistant message.\n\n## Writing code for run_code\n\n`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program:\n\n- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools[\"my-tool\"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.\n- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.\n- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.\n- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need.\n\nThe available tools:\n\n```ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\ninterface ToolArgsMap {\n /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. */\n bash: {\n /** The bash command to execute. */\n command: string;\n /** Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\". */\n description: string;\n /** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */\n timeoutMs?: number;\n /** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */\n workdir?: string;\n /** Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies. */\n run_in_background?: boolean;\n } & Record;\n /** Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc. */\n cordis_inspect: {\n /** Limit the report to one section. Omit for all sections. */\n what?: \"services\" | \"plugins\" | \"tools\" | \"temporary\" | \"api\" | \"events\";\n /** Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\". */\n name?: string;\n } & Record;\n /** Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime. */\n cordis_mount: {\n /** JavaScript body returning a temporary Plugin; evaluated now and saved nowhere. */\n code: string;\n } & Record;\n /** Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins. */\n cordis_unmount: {\n /** The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart. */\n id: string;\n } & Record;\n /** Edit an existing UTF-8 text file by replacing literal text. */\n edit: {\n /** Path to edit, resolved by the filesystem backend. */\n file_path: string;\n /** Literal text to replace. Must match exactly. */\n old_string: string;\n /** Literal replacement text. Use an empty string to delete the match. */\n new_string: string;\n /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */\n replace_all?: boolean;\n } & Record;\n /** Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op. */\n interrupt_agent: {\n /** The agent id of the running agent to interrupt. */\n agent_id: string;\n } & Record;\n /** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */\n ralph: {\n /** The immutable completion objective for every fresh Ralph round. */\n objective: string;\n /** Optional positive safe-integer round cap, bounded by the deployment ceiling. */\n maxRounds?: number;\n } & Record;\n /** Read a UTF-8 text file and return line-numbered content. */\n read: {\n /** Path to read, resolved by the filesystem backend. */\n file_path: string;\n /** 1-based first line to return. Defaults to 1. */\n offset?: number;\n /** Maximum number of lines to return. Defaults to 2000. */\n limit?: number;\n } & Record;\n /** Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered. */\n send_message: {\n /** The subagent id returned when the background subagent was started. */\n subagent_id: string;\n /** The message to deliver to the subagent. */\n message: string;\n } & Record;\n /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */\n skill: {\n /** The exact skill name from the available skills list. */\n name: string;\n } & Record;\n /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */\n subagent: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */\n prompt: string;\n /** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */\n run_in_background?: boolean;\n } & Record;\n /** Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result. */\n subagent_fork: {\n /** A short (3-5 word) description of the delegated task, for display. */\n description: string;\n /** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */\n prompt: string;\n } & Record;\n /** Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops. */\n task_kill: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Optional short reason, recorded in the log and forwarded to the task. */\n reason?: string;\n } & Record;\n /** List your background tasks (running and finished) with their ids, kinds, and statuses. */\n task_list: Record;\n /** Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */\n task_output: {\n /** Task id returned by the tool that started the background work. */\n task_id: string;\n /** Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive. */\n wait?: boolean;\n /** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */\n timeout_ms?: number;\n } & Record;\n /** Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). */\n todo_write: {\n /** The COMPLETE task list, replacing any previous list. */\n todos: ({\n /** What the task is — a short imperative line. */\n content: string;\n /** pending (not started) | in_progress (now) | completed (done). */\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n } & Record;\n /** Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn. The workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly. - `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages. - `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`. - `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim. Misused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`. Constraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes. */\n workflow: {\n /** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `). */\n script: string;\n /** The workflow identity block (plain JSON — never code). */\n meta: {\n /** Short kebab-case workflow name. */\n name: string;\n /** One-line description of what the workflow does. */\n description: string;\n /** Optional guidance on when this workflow applies. */\n whenToUse?: string;\n /** Optional phase declarations matched by phase() calls. */\n phases?: ({\n /** The phase title phase() calls match by exact string. */\n title: string;\n /** Optional one-line description of the phase. */\n detail?: string;\n /** Optional provider override this phase is expected to use. */\n provider?: string;\n /** Optional model override this phase is expected to use. */\n model?: string;\n } & Record)[];\n } & Record;\n /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}). */\n args?: Record;\n } & Record;\n /** Create or fully replace a UTF-8 text file. */\n write: {\n /** Path to write, resolved by the filesystem backend. */\n file_path: string;\n /** Full UTF-8 text content to write. */\n content: string;\n } & Record;\n}\n\ninterface ToolOutputMap {\n bash: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"foreground\";\n exitCode: number | null;\n signal: string | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n stderr: {\n text: string;\n truncated: boolean;\n spillPath?: string;\n };\n sandbox?: {\n mode: string;\n denied: boolean;\n enforcement?: string;\n runnerFailed?: boolean;\n };\n };\n cordis_inspect: string;\n cordis_mount: {\n id: string;\n pluginName: string;\n state: \"pending\" | \"loading\" | \"active\" | \"failed\" | \"disposed\" | \"unloading\";\n provides: string[];\n waitingFor: string[];\n };\n cordis_unmount: {\n id: string;\n pluginName: string;\n };\n edit: {\n path: string;\n before: string;\n after: string;\n };\n interrupt_agent: {\n accepted: boolean;\n };\n ralph: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n read: {\n path: string;\n offset: number;\n lines: {\n number: number;\n text: string;\n }[];\n totalLines: number;\n };\n send_message: {\n messageId: string;\n };\n skill: {\n name: string;\n provider: string;\n resourceBase?: {\n kind: \"directory\";\n path: string;\n } | {\n kind: \"url\";\n url: string;\n } | {\n kind: \"opaque\";\n description: string;\n };\n content: string;\n };\n subagent: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n subagent_fork: {\n kind: \"background\";\n taskId: string;\n } | {\n kind: \"continuable\";\n subagentId: string;\n } | {\n kind: \"foreground\";\n runId: string;\n output: JsonValue[];\n };\n task_kill: {\n outcome: \"cancellation-requested\" | \"already-finished\";\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n task_list: ({\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n })[];\n task_output: {\n text: string;\n task: {\n id: string;\n kind: string;\n label: string;\n status: \"running\" | \"stopping\" | \"completed\" | \"killed\" | \"failed\";\n detail?: string;\n startedAt: number;\n finishedAt?: number;\n };\n };\n todo_write: {\n todos: ({\n content: string;\n status: \"pending\" | \"in_progress\" | \"completed\";\n })[];\n counts: {\n pending: number;\n inProgress: number;\n completed: number;\n };\n };\n workflow: {\n runId: string;\n agentsStarted: number;\n result: JsonValue;\n };\n write: {\n path: string;\n operation: \"create\" | \"update\";\n before: string | null;\n after: string;\n };\n}\n\ntype ToolName = keyof ToolOutputMap\n\ndeclare class ToolCallError extends Error {\n readonly name: \"ToolCallError\";\n readonly toolName: ToolName;\n}\n\ndeclare const tools: {\n [K in ToolName]: (args: ToolArgsMap[K]) => Promise;\n}\n```","tools":[{"name":"bash","description":"Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.","parameters":{"type":"object","properties":{"command":{"type":"string","description":"The bash command to execute."},"description":{"type":"string","description":"Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."},"timeoutMs":{"type":"number","description":"Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."},"workdir":{"type":"string","description":"Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."},"run_in_background":{"type":"boolean","description":"Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."}},"required":["command","description"]}},{"name":"cordis_inspect","description":"Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: `services` (every provided ctx service and the plugin fiber that owns it), `plugins` (all live plugin fibers with their lifecycle states), `tools` (the model-facing tools currently registered, i.e. what you can call), `temporary` (only temporary Plugins created by cordis_mount: id, name, state, provided services, awaited services, and lifetime), `api` (method signatures AND argument/return type shapes for every LIVE service — read this before writing plugin code that calls a service), `events` (every harness event with its dispatch mode and exact signature — pick listener targets here). Temporary Plugins exist only in memory, remain active across later turns, and disappear after cordis_unmount, toolset unload, or DSH restart; they are not restored automatically. The `temporary` section is a subset of `plugins`. Omit `what` to get all six sections. With `what:\"api\"` or `what:\"events\"`, pass an exact `name` to narrow to one service/event and include its original source JSDoc.","parameters":{"type":"object","properties":{"what":{"type":"string","description":"Limit the report to one section. Omit for all sections.","enum":["services","plugins","tools","temporary","api","events"]},"name":{"type":"string","description":"Exact service key or event name whose original JSDoc to include; valid only with what:\"api\" or what:\"events\"."}}}},{"name":"cordis_mount","description":"Mount a temporary Cordis Plugin in the current DSH process. This creates an in-memory runtime Plugin, not an installed or configured Plugin. It remains active across later turns until cordis_unmount, toolset unload, or DSH restart. It does not create files, install a package, change cordis.yml or personal/project config, survive restart, or automatically become permanent. To keep it, ask the Agent to implement an SDK Plugin or installable profile bundle through the regular development workflow. It may affect other sessions in the same process; the sandbox is not a security boundary, and injected services reach the real runtime. `code` runs now as the body of an async JavaScript function in an isolated sandbox and MUST `return` a plugin. Two forms: FUNCTION form `return (ctx) => { … }` — declares no inject, so it can register tools, listen to events, and provide services, but reaching ANY service (e.g. ctx.bash) throws; use it only when you need no services. OBJECT form `return { name?, inject: ['bash', 'llm', …], apply(ctx) { … } }` — declares dependencies, and cordis activates the plugin only after the services exist; PREFER this form. You may reach ONLY the services you list in inject: an undeclared service throws even if it exists, because an undeclared dependency would not be cleaned up if its provider is unmounted. BEFORE calling a service from your code, read cordis_inspect what:\"api\" — it lists method signatures AND the type shapes of their arguments/returns (do not guess a field's type; e.g. a bash run's stdout is an object, not a string). Inside `apply`, use the standard cordis API: `ctx.on(event, listener)` to observe events (see cordis_inspect what:\"events\"), or call `harness.registerTool(ctx, harness.defineTool({ name, description, parameters: { text: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render(_args, value) { return [{ type: 'text', text: value }] } }, async execute(args) { return args.text } }))` to give yourself a new tool — it becomes callable on your NEXT step. Tool parameters: each key IS a property — { type: 'string'|'number'|'integer'|'boolean'|'null'|'object'|'array'|'json', required?: true, description?, enum?, const?, items?, properties? }; every direct DSL object declares additionalProperties: true|false, and oneOf: [schema, schema, ...] replaces type for an exact-one union. A raw JSON-Schema { type: 'object', properties, required?: […] } wrapper is also accepted with open-by-default objects. A tool's `execute` MUST return the lossless JSON value declared by `output.schema`; `output.render(args, value)` separately returns Native/model content blocks. Temporary Plugins can COMPOSE: one Plugin may `ctx.provide('name', value)` a service and another may declare `inject: ['name']` to consume it — the consumer stays pending until the provider exists and returns to pending when the provider is unmounted. Everything registered inside `apply` is cleaned up automatically by cordis_unmount. Sandbox globals: `console` (tagged `[cordis:]`, writes through to the harness terminal), `harness.defineTool`, `harness.registerTool`, `btoa`, `atob`, `TextEncoder`, `TextDecoder`. Node APIs are DISABLED — do filesystem/network/timer work through the cordis services, never Node built-ins: `require`, `setTimeout`/`setInterval`, and `fetch` throw redirect errors; `process` and `Buffer` are undefined. Instead use inject: ['fs'] + ctx.fs for files, inject: ['web'] + ctx.web for HTTP, inject: ['bash'] + ctx.bash for processes, and inject: ['timer'] + ctx.setTimeout/ctx.setInterval for timing (fiber effects, auto-cleaned when unmounted) — cordis_inspect what:\"api\" shows what THIS runtime provides. Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a trailing `next` callback which MUST be called — returning without `next()` SHORT-CIRCUITS the call; prefer plain notification events unless you intend to intercept. (2) Never await something that only resolves after the current turn (your code runs INSIDE a tool call of that turn — it would deadlock). (3) Your `ctx` is a restricted façade: you can register tools, observe events, provide/consume services, and use timers, but framework internals (ctx.root, ctx.fiber, ctx.extend, ctx.plugin, …) are withheld. It is not a security boundary though — the services you inject (e.g. ctx.bash) reach the real runtime.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"JavaScript body returning a temporary Plugin; evaluated now and saved nowhere."}},"required":["code"]}},{"name":"cordis_unmount","description":"Unmount a current-process temporary Plugin created by cordis_mount. Waits for its tools, listeners, services, timers, and other owned effects to clean up completely. Only dyn-N temporary ids are accepted; this cannot remove Loader, configured, or installed Plugins.","parameters":{"type":"object","properties":{"id":{"type":"string","description":"The temporary Plugin id returned by cordis_mount (for example \"dyn-1\"); valid only in this process and invalid after unmount or restart."}},"required":["id"]}},{"name":"edit","description":"Edit an existing UTF-8 text file by replacing literal text.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to edit, resolved by the filesystem backend."},"old_string":{"type":"string","description":"Literal text to replace. Must match exactly."},"new_string":{"type":"string","description":"Literal replacement text. Use an empty string to delete the match."},"replace_all":{"type":"boolean","description":"Replace all matches. Defaults to false; when false, old_string must appear exactly once."}},"required":["file_path","old_string","new_string"]}},{"name":"interrupt_agent","description":"Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.","parameters":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent id of the running agent to interrupt."}},"required":["agent_id"]}},{"name":"ralph","description":"Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.","parameters":{"type":"object","properties":{"objective":{"type":"string","description":"The immutable completion objective for every fresh Ralph round."},"maxRounds":{"type":"number","description":"Optional positive safe-integer round cap, bounded by the deployment ceiling."}},"required":["objective"]}},{"name":"read","description":"Read a UTF-8 text file and return line-numbered content.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to read, resolved by the filesystem backend."},"offset":{"type":"number","description":"1-based first line to return. Defaults to 1."},"limit":{"type":"number","description":"Maximum number of lines to return. Defaults to 2000."}},"required":["file_path"]}},{"name":"run_code","description":"Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.","parameters":{"type":"object","properties":{"code":{"type":"string","description":"The program: the body of an async TypeScript function."},"description":{"type":"string","description":"Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."}},"required":["code","description"]}},{"name":"send_message","description":"Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.","parameters":{"type":"object","properties":{"subagent_id":{"type":"string","description":"The subagent id returned when the background subagent was started."},"message":{"type":"string","description":"The message to deliver to the subagent."}},"required":["subagent_id","message"]}},{"name":"skill","description":"Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.","parameters":{"type":"object","properties":{"name":{"type":"string","description":"The exact skill name from the available skills list."}},"required":["name"]}},{"name":"subagent","description":"Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."},"run_in_background":{"type":"boolean","description":"Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."}},"required":["description","prompt"]}},{"name":"subagent_fork","description":"Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This call waits for the subagent and returns its result.","parameters":{"type":"object","properties":{"description":{"type":"string","description":"A short (3-5 word) description of the delegated task, for display."},"prompt":{"type":"string","description":"The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."}},"required":["description","prompt"]}},{"name":"task_kill","description":"Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"reason":{"type":"string","description":"Optional short reason, recorded in the log and forwarded to the task."}},"required":["task_id"]}},{"name":"task_list","description":"List your background tasks (running and finished) with their ids, kinds, and statuses.","parameters":{"type":"object","properties":{}}},{"name":"task_output","description":"Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.","parameters":{"type":"object","properties":{"task_id":{"type":"string","description":"Task id returned by the tool that started the background work."},"wait":{"type":"boolean","description":"Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."},"timeout_ms":{"type":"number","description":"Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."}},"required":["task_id"]}},{"name":"todo_write","description":"Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).","parameters":{"type":"object","properties":{"todos":{"type":"array","description":"The COMPLETE task list, replacing any previous list.","items":{"type":"object","additionalProperties":false,"properties":{"content":{"type":"string","description":"What the task is — a short imperative line."},"status":{"type":"string","description":"pending (not started) | in_progress (now) | completed (done).","enum":["pending","in_progress","completed"]}},"required":["content","status"]}}},"required":["todos"]}},{"name":"workflow","description":"Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.","parameters":{"type":"object","properties":{"script":{"type":"string","description":"The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)."},"meta":{"type":"object","description":"The workflow identity block (plain JSON — never code).","additionalProperties":true,"properties":{"name":{"type":"string","description":"Short kebab-case workflow name."},"description":{"type":"string","description":"One-line description of what the workflow does."},"whenToUse":{"type":"string","description":"Optional guidance on when this workflow applies."},"phases":{"type":"array","description":"Optional phase declarations matched by phase() calls.","items":{"type":"object","additionalProperties":true,"properties":{"title":{"type":"string","description":"The phase title phase() calls match by exact string."},"detail":{"type":"string","description":"Optional one-line description of the phase."},"provider":{"type":"string","description":"Optional provider override this phase is expected to use."},"model":{"type":"string","description":"Optional model override this phase is expected to use."}},"required":["title"]}}},"required":["name","description"]},"args":{"type":"object","description":"Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).","additionalProperties":true}},"required":["script","meta"]}},{"name":"write","description":"Create or fully replace a UTF-8 text file.","parameters":{"type":"object","properties":{"file_path":{"type":"string","description":"Path to write, resolved by the filesystem backend."},"content":{"type":"string","description":"Full UTF-8 text content to write."}},"required":["file_path","content"]}}]},"reason":"initial"}} {"type":"request/context","seq":7,"time":1785730501403,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"assistant/chunk","seq":8,"time":1783950000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} {"type":"assistant/chunk","seq":9,"time":1783950000008,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}} {"type":"assistant/chunk","seq":10,"time":1783950000009,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}}}} {"type":"assistant/chunk","seq":11,"time":1785498583784,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":12,"time":1785730501404,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":13,"time":1785730501404,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3d281cb7-5491-4819-98ac-fe5499d13344"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"} +{"type":"assistant/message","seq":13,"time":1785730501404,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0b0e5f02-7f85-4522-9d9d-a239c09bb14b"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[8,9,10,11,12],"surfaceOp":"append"} {"type":"tool/call","seq":14,"time":1785730501404,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\":\"return { name: 'snapshot-marker', apply() {} }\"}"}} -{"type":"tool/result","seq":15,"time":1785730501413,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-mount"},"content":[{"type":"tool-result","toolCallId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false}],"role":"user","id":"ef790863-ba32-4cec-95fa-84759f23b65b"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"tool/result","seq":15,"time":1785730501413,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"advanced-mount"},"content":[{"type":"tool-result","toolCallId":"advanced-mount","content":[{"type":"text","text":"Temporary Plugin dyn-1 is running (plugin \"snapshot-marker\"; available until unmounted or DSH restarts)."}],"isError":false}],"role":"user","id":"13429f8e-5f31-4883-8e04-62a480a29d1a"}},"sourceEventSeqs":[14],"surfaceOp":"append"} {"type":"step/end","seq":16,"time":1785730501413,"data":{"turn":1,"step":1}} {"type":"step/start","seq":17,"time":1785730501423,"data":{"turn":1,"step":2}} {"type":"assistant/chunk","seq":18,"time":1783950000017,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} @@ -22,11 +22,11 @@ {"type":"assistant/chunk","seq":20,"time":1783950000019,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}}}} {"type":"assistant/chunk","seq":21,"time":1785498583804,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":22,"time":1785730501424,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":23,"time":1785730501424,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3c8fb0fe-4ad9-4619-8583-656112a508c9"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"} +{"type":"assistant/message","seq":23,"time":1785730501424,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"15c14e30-7b10-4421-8388-91af6c5ec8d0"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[18,19,20,21,22],"surfaceOp":"append"} {"type":"tool/call","seq":24,"time":1785730501424,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.cordis_inspect({ what: 'temporary' })\", \"description\": \"Run the scripted inspection program\"}"}} {"type":"tool/code-dispatch-start","seq":25,"time":1785730501473,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"}}} {"type":"tool/code-dispatch","seq":26,"time":1785730501474,"data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"cordis_inspect","arguments":{"what":"temporary"},"isError":false,"content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}]}} -{"type":"tool/result","seq":27,"time":1785730501475,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}],"isError":false}],"role":"user","id":"5157b02e-5ba5-4672-ac18-37b38a5283ac"}},"sourceEventSeqs":[24],"surfaceOp":"append"} +{"type":"tool/result","seq":27,"time":1785730501475,"data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"advanced-code"},"content":[{"type":"tool-result","toolCallId":"advanced-code","content":[{"type":"text","text":"## Temporary Plugins\n- Temporary Plugin dyn-1: snapshot-marker [running] — provides: none; waiting for: none; lifetime: until unmounted or DSH restarts"}],"isError":false}],"role":"user","id":"e14ca026-504e-4507-b996-b66cfc9f9875"}},"sourceEventSeqs":[24],"surfaceOp":"append"} {"type":"step/end","seq":28,"time":1785730501475,"data":{"turn":1,"step":2}} {"type":"step/start","seq":29,"time":1785730501483,"data":{"turn":1,"step":3}} {"type":"assistant/chunk","seq":30,"time":1783950000029,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} @@ -34,9 +34,9 @@ {"type":"assistant/chunk","seq":32,"time":1785037378923,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}}}} {"type":"assistant/chunk","seq":33,"time":1785498583869,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":34,"time":1785730501484,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":35,"time":1785730501484,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"cae8cb67-2cf4-41c0-9112-69c583169796"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[30,31,32,33,34],"surfaceOp":"append"} +{"type":"assistant/message","seq":35,"time":1785730501484,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"3f25768e-3d1a-4a29-9663-b849a682c17e"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[30,31,32,33,34],"surfaceOp":"append"} {"type":"tool/call","seq":36,"time":1785730501484,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\":\"Check direct child\",\"prompt\":\"Reply with exactly DIRECT_CHILD_OK and nothing else.\",\"run_in_background\":false}"}} -{"type":"tool/result","seq":37,"time":1785730501508,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"1b549053-598f-4595-b7b7-61794756da33"}},"sourceEventSeqs":[36],"surfaceOp":"append"} +{"type":"tool/result","seq":37,"time":1785730501508,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"advanced-direct-child"},"content":[{"type":"tool-result","toolCallId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false}],"role":"user","id":"a427a45e-8365-40fc-9f88-476b05aae2c4"}},"sourceEventSeqs":[36],"surfaceOp":"append"} {"type":"step/end","seq":38,"time":1785730501508,"data":{"turn":1,"step":3}} {"type":"step/start","seq":39,"time":1785730501521,"data":{"turn":1,"step":4}} {"type":"assistant/chunk","seq":40,"time":1783957884594,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} @@ -44,13 +44,13 @@ {"type":"assistant/chunk","seq":42,"time":1785037378946,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}}}} {"type":"assistant/chunk","seq":43,"time":1785498583919,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":44,"time":1785730501522,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":45,"time":1785730501522,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"bc51af07-d025-4345-912b-9e533e639085"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[40,41,42,43,44],"surfaceOp":"append"} +{"type":"assistant/message","seq":45,"time":1785730501522,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0fb1adfd-fde9-4660-972f-803a385b6687"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[40,41,42,43,44],"surfaceOp":"append"} {"type":"tool/call","seq":46,"time":1785730501522,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\":\"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\",\"meta\":{\"name\":\"advanced-headless-snapshot\",\"description\":\"exercise one workflow child through the headless agent\"}}"}} {"type":"tool-workflow/run-start","seq":47,"time":1786359174028,"data":{"runId":"668432bb-f01c-41e7-841e-30d8deab7b55","name":"advanced-headless-snapshot"}} {"type":"tool-workflow/agent-start","seq":48,"time":1786359174210,"data":{"runId":"668432bb-f01c-41e7-841e-30d8deab7b55","seq":1,"label":"workflow-child","phase":"Delegate","childId":"33333333-3333-4333-8333-333333333333"}} {"type":"tool-workflow/agent-end","seq":49,"time":1786359174230,"data":{"runId":"668432bb-f01c-41e7-841e-30d8deab7b55","seq":1,"outcome":"completed"}} {"type":"tool-workflow/run-end","seq":50,"time":1786359174232,"data":{"runId":"668432bb-f01c-41e7-841e-30d8deab7b55","stopReason":"completed"}} -{"type":"tool/result","seq":51,"time":1786359174232,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-headless-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"6ea4325f-622f-4381-8f12-3a6fe5606a50"}},"sourceEventSeqs":[46],"surfaceOp":"append"} +{"type":"tool/result","seq":51,"time":1786359174232,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-headless-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"9fe92917-9724-4129-8973-9193ed5ff2f1"}},"sourceEventSeqs":[46],"surfaceOp":"append"} {"type":"step/end","seq":52,"time":1786359174233,"data":{"turn":1,"step":4}} {"type":"step/start","seq":53,"time":1786359174239,"data":{"turn":1,"step":5}} {"type":"assistant/chunk","seq":54,"time":1785730501661,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}} @@ -58,9 +58,9 @@ {"type":"assistant/chunk","seq":56,"time":1786359174239,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}}}} {"type":"assistant/chunk","seq":57,"time":1786359174239,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":58,"time":1786359174239,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":59,"time":1786359174239,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"bda6f601-43d0-4c07-b561-4d50322d645b"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[54,55,56,57,58],"surfaceOp":"append"} +{"type":"assistant/message","seq":59,"time":1786359174239,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"4e132ffc-e437-4f02-bbd4-01a7f7809070"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[54,55,56,57,58],"surfaceOp":"append"} {"type":"tool/call","seq":60,"time":1786359174239,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\":\"dyn-1\"}"}} -{"type":"tool/result","seq":61,"time":1786359174243,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"d31a7b99-409f-4d2c-9584-93cc453c12b5"}},"sourceEventSeqs":[60],"surfaceOp":"append"} +{"type":"tool/result","seq":61,"time":1786359174243,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"755a3021-cd56-437f-b9fb-7db74d116c9d"}},"sourceEventSeqs":[60],"surfaceOp":"append"} {"type":"step/end","seq":62,"time":1786359174243,"data":{"turn":1,"step":5}} {"type":"step/start","seq":63,"time":1786359174248,"data":{"turn":1,"step":6}} {"type":"assistant/chunk","seq":64,"time":1785730501679,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}} @@ -68,6 +68,6 @@ {"type":"assistant/chunk","seq":66,"time":1786359174249,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_HEADLESS_OK"}}}} {"type":"assistant/chunk","seq":67,"time":1786359174249,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}} {"type":"assistant/chunk","seq":68,"time":1786359174249,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","seq":69,"time":1786359174249,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_HEADLESS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"4ae96a19-c45a-4096-befc-47004b88fc3e"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[64,65,66,67,68],"surfaceOp":"append"} +{"type":"assistant/message","seq":69,"time":1786359174249,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_HEADLESS_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"72d6c5d3-85b7-4d62-b9df-253834141892"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[64,65,66,67,68],"surfaceOp":"append"} {"type":"step/end","seq":70,"time":1786359174249,"data":{"turn":1,"step":6}} {"type":"turn/end","seq":71,"time":1786359174249,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index 7011884239..421a19ec48 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/tools/README.md -README.md: 44eb25b79436a75f08406102fc1e3734e59b1001 -README.zh.md: a47e7c54b0cd3fc3c5146a9a8be4d1406f9ef8aa +README.md: 3dddeb51a9f5b98a01a5e0170dacde471be67094 +README.zh.md: deb01899fa909e5d430415fa98ea49bd543b2749 diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 44eb25b794..3dddeb51a9 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -155,7 +155,7 @@ Code Mode exposes the generated [`run_code` schema](../../../docs/tool-catalog.m ```markdown ## Writing code for run_code -Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: +`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program: - Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue. diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index a47e7c54b0..deb01899fa 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -155,7 +155,7 @@ Code Mode 会公开生成的 [`run_code` schema](../../../docs/tool-catalog.md#d ```markdown ## Writing code for run_code -Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: +`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program: - Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue. diff --git a/packages/core/tools/src/code-mode.ts b/packages/core/tools/src/code-mode.ts index 42c55ece03..c997e7d2c2 100644 --- a/packages/core/tools/src/code-mode.ts +++ b/packages/core/tools/src/code-mode.ts @@ -45,10 +45,11 @@ interface RunCodeFlavor { */ const TYPESCRIPT_FLAVOR: RunCodeFlavor = { description: - 'Execute a TypeScript program against the available tools. Write the BODY of an ' - + 'async function (erasable syntax only; top-level `await` and `return` work) and ' - + 'call tools as `await tools.name(args)` per the declarations in the system prompt. ' - + 'Only what you print or return comes back — curate it.', + 'Execute a TypeScript program against the available tools. Takes two required ' + + 'arguments: `code`, the BODY of an async function (erasable syntax only; top-level ' + + '`await` and `return` work), and `description`, a short summary of what the program ' + + 'does. Call tools as `await tools.name(args)` per the declarations in the system ' + + 'prompt. Only what you print or return comes back — curate it.', codeDescription: 'The program: the body of an async TypeScript function.', } @@ -59,8 +60,9 @@ const TYPESCRIPT_FLAVOR: RunCodeFlavor = { */ const PYTHON_FLAVOR: RunCodeFlavor = { description: - 'Execute a Python program against the available tools. Write the BODY of an ' - + 'async function (top-level `await` and `return` work) and call tools as ' + 'Execute a Python program against the available tools. Takes two required ' + + 'arguments: `code`, the BODY of an async function (top-level `await` and `return` ' + + 'work), and `description`, a short summary of what the program does. Call tools as ' + '`await tools.name(args)` per the declarations in the system prompt. Answer ' + 'with `print(...)` and/or `return ` — only that comes back, so curate it.', codeDescription: 'The program: the body of an async Python function.', diff --git a/packages/core/tools/src/py-types.ts b/packages/core/tools/src/py-types.ts index 4898ec80e1..6141df14ed 100644 --- a/packages/core/tools/src/py-types.ts +++ b/packages/core/tools/src/py-types.ts @@ -733,7 +733,7 @@ export function jsonSchemaToPy(schema: unknown): string { /** The fixed model-facing usage contract rendered above the declarations. */ const SDK_INSTRUCTIONS = `## Writing code for run_code -Pass \`run_code\` the body of an async Python function (top-level \`await\` and \`return\` both work). At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program: +\`run_code\` takes two required arguments: \`code\` — the body of an async Python function (top-level \`await\` and \`return\` both work) — and \`description\`, a short summary of what the program does. At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program: - Call tools as \`await tools.name(args)\` — subscript access for exotic, reserved, or underscore-leading names: \`await tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON. - A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try/except\` to handle and continue. diff --git a/packages/core/tools/src/ts-types.ts b/packages/core/tools/src/ts-types.ts index 9b0d096a22..bbd5cd7666 100644 --- a/packages/core/tools/src/ts-types.ts +++ b/packages/core/tools/src/ts-types.ts @@ -249,7 +249,7 @@ export function jsonSchemaToTs(schema: unknown, indent = 0): string { /** The fixed model-facing usage contract rendered above the declarations (see the Code Mode Agent Note's "What the model sees"). */ const SDK_INSTRUCTIONS = `## Writing code for run_code -Pass \`run_code\` the body of an async TypeScript function (erasable syntax only — no \`enum\` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: +\`run_code\` takes two required arguments: \`code\` — the body of an async TypeScript function (erasable syntax only — no \`enum\` or namespaces; type annotations are advisory, the code runs type-stripped) — and \`description\`, a short summary of what the program does. Inside the program: - Call tools as \`await tools.name(args)\` — quoted access for exotic names: \`tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON. - A FAILED tool call rejects with \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose \`message\` is human-readable — \`try/catch\` it to handle and continue. diff --git a/packages/core/tools/tests/code-mode.spec.ts b/packages/core/tools/tests/code-mode.spec.ts index 4379f7e0b2..edd456154a 100644 --- a/packages/core/tools/tests/code-mode.spec.ts +++ b/packages/core/tools/tests/code-mode.spec.ts @@ -399,6 +399,10 @@ describe('mode-aware wire contribution', () => { const runCodeSchema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME) expect(runCodeSchema?.description).toContain('Execute a TypeScript program') expect(runCodeSchema?.description).toContain('BODY of an') + // Both required arguments are named here, not only in the parameter + // schema: prose that describes the call as "pass the program" is what + // leads a model to emit `{code}` alone and fail INVALID_ARGS. + expect(runCodeSchema?.description).toContain('`description`') const codeParam = (runCodeSchema?.parameters as { properties: { code: { description: string } } }).properties.code expect(codeParam.description).toBe('The program: the body of an async TypeScript function.') }) @@ -410,6 +414,7 @@ describe('mode-aware wire contribution', () => { const runCodeSchema = assembly.tools.find(tool => tool.name === RUN_CODE_NAME) expect(runCodeSchema?.description).toContain('Execute a Python program') expect(runCodeSchema?.description).toContain('`return `') + expect(runCodeSchema?.description).toContain('`description`') expect(runCodeSchema?.description).not.toContain('TypeScript') const codeParam = (runCodeSchema?.parameters as { properties: { code: { description: string } } }).properties.code expect(codeParam.description).toBe('The program: the body of an async Python function.') diff --git a/packages/core/tools/tests/py-types.spec.ts b/packages/core/tools/tests/py-types.spec.ts index 3439cb6a37..0caf9daab6 100644 --- a/packages/core/tools/tests/py-types.spec.ts +++ b/packages/core/tools/tests/py-types.spec.ts @@ -166,6 +166,15 @@ describe('renderToolsSdkPy', () => { expect(text).toContain('tools: Tools') }) + it('names both required call arguments, not just the program', () => { + // The schema requires `code` AND `description`; instructions that mention + // only the program let a model emit `{code}` alone and fail INVALID_ARGS. + const text = renderToolsSdkPy([bash]) + expect(text).toContain('`code`') + expect(text).toContain('`description`') + expect(text).toContain('two required arguments') + }) + it('renders required as plain fields and optional as NotRequired, with per-field description comments', () => { const tool: ToolSdkSchema = { name: 'search', diff --git a/packages/core/tools/tests/ts-types.spec.ts b/packages/core/tools/tests/ts-types.spec.ts index 4c4954752d..8b4e145060 100644 --- a/packages/core/tools/tests/ts-types.spec.ts +++ b/packages/core/tools/tests/ts-types.spec.ts @@ -148,6 +148,15 @@ describe('renderToolsSdk', () => { expect(text).toContain('lossless JSON') }) + it('names both required call arguments, not just the program', () => { + // The schema requires `code` AND `description`; instructions that mention + // only the program let a model emit `{code}` alone and fail INVALID_ARGS. + const text = renderToolsSdk([bash]) + expect(text).toContain('`code`') + expect(text).toContain('`description`') + expect(text).toContain('two required arguments') + }) + it('is deterministic: same tool set, byte-identical text regardless of input order', () => { expect(renderToolsSdk([bash, exotic])).toBe(renderToolsSdk([exotic, bash])) // Equal names sort stably (the comparator's equal arm). From d6fc55ad863916ade46102860bfc1ced516c8205 Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Wed, 12 Aug 2026 08:39:09 -0700 Subject: [PATCH 007/119] docs: refresh generated references --- docs/config-catalog.i18n.yaml | 4 ++-- docs/config-catalog.zh.md | 2 +- docs/module-graph.i18n.yaml | 4 ++-- docs/module-graph.md | 3 ++- docs/module-graph.zh.md | 3 ++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 33fae42ac2..bb542a0ab7 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/config-catalog.md -config-catalog.md: f075a5c22049df1696a1849cd88bd3b783adde21 -config-catalog.zh.md: 952a77df3ba279155b4364a083917f816efd2d63 +config-catalog.md: bdec02cae2afd13bbe9ef041d95c6c30164f8fc0 +config-catalog.zh.md: 11769b07652859058d16ec7309411e2f05eca254 diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 952a77df3b..11769b0765 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -2807,7 +2807,7 @@ export interface Config { - `@deepseek-ai/dsh-client-ui-agent-preset`([`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts)) - `@deepseek-ai/dsh-client-ui-command`([`packages/client/ui-command/src/index.ts`](../packages/client/ui-command/src/index.ts)) - `@deepseek-ai/dsh-client-ui-conversation`([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts)) -- `@deepseek-ai/dsh-client-ui-deliverables`([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) +- `@deepseek-ai/dsh-client-ui-deliverables` — 需要 `systemPrompt`([`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker`([`packages/client/ui-directory-picker/src/index.ts`](../packages/client/ui-directory-picker/src/index.ts)) - `@deepseek-ai/dsh-client-ui-directory-picker-native`([`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts)) - `@deepseek-ai/dsh-client-ui-feedback`([`packages/client/ui-feedback/src/index.ts`](../packages/client/ui-feedback/src/index.ts)) diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index b528a4356d..7f7c5d902f 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/module-graph.md -module-graph.md: 5197a184f2be283e3bb57de91d4a4d3cb22e51b2 -module-graph.zh.md: 3b312b731d32ed71674d800c35a5868e0b5d94ee +module-graph.md: bee4af67d5c2895bb03f584daca7fc7cf43a5cd3 +module-graph.zh.md: 506645d45fae4b5dcd301e11c4b2b40a9361bb09 diff --git a/docs/module-graph.md b/docs/module-graph.md index 5197a184f2..bee4af67d5 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -1269,6 +1269,7 @@ flowchart TD pkg_client_ui_deliverables --> pkg_client_ui_conversation pkg_client_ui_deliverables --> pkg_client_ui_slots pkg_client_ui_deliverables --> pkg_invariants + pkg_client_ui_deliverables --> pkg_system_prompt pkg_client_ui_feedback --> pkg_api_remotes pkg_client_ui_feedback --> pkg_client_connection pkg_client_ui_feedback --> pkg_client_locale @@ -1579,7 +1580,7 @@ flowchart TD | [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) | | [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) | | [`client-ui-command`](../packages/client/ui-command) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants) | -| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | +| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt) | | [`client-ui-feedback`](../packages/client/ui-feedback) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`message-feedback`](../packages/feedback/message-feedback), [`type-meta`](../packages/typert/type-meta) | | [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | | [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`plan-mode`](../packages/plan/plan-mode) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 3b312b731d..506645d45f 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -1271,6 +1271,7 @@ flowchart TD pkg_client_ui_deliverables --> pkg_client_ui_conversation pkg_client_ui_deliverables --> pkg_client_ui_slots pkg_client_ui_deliverables --> pkg_invariants + pkg_client_ui_deliverables --> pkg_system_prompt pkg_client_ui_feedback --> pkg_api_remotes pkg_client_ui_feedback --> pkg_client_connection pkg_client_ui_feedback --> pkg_client_locale @@ -1581,7 +1582,7 @@ flowchart TD | [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) | | [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) | | [`client-ui-command`](../packages/client/ui-command) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants) | -| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | +| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt) | | [`client-ui-feedback`](../packages/client/ui-feedback) | `client` | [`api-remotes`](../packages/api/remotes), [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`message-feedback`](../packages/feedback/message-feedback), [`type-meta`](../packages/typert/type-meta) | | [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | | [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`plan-mode`](../packages/plan/plan-mode) | From 238e900776ff0743f0a0b6a83414c526e46e844a Mon Sep 17 00:00:00 2001 From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com> Date: Wed, 12 Aug 2026 08:43:25 -0700 Subject: [PATCH 008/119] ci: retrigger after stale merge ref From 3ef05f8a8524dc1dc754f4d0bd7d343f520c2d27 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 12 Aug 2026 23:51:46 +0800 Subject: [PATCH 009/119] review: address ds-review-bot findings on composer geometry - Rewrite the 08-04 gutter-reservation note in place: the reservation is Chat's alone and the overlay branch points to the seat-width compensation note; the Testing section now describes the uncompensated control. - Cross-reference the earlier rejection of the seat inset in the 08-12 note and record the engine-resolved-vs-fixed-8px drift as accepted residual cost instead of asserting Firefox resolves 8px. - Pair --dsh-scrollbar-width with the mirrored ::-webkit-scrollbar rule and the compensation consumer in the scrollbar-styles gate; document the variable in the ui-theme README. - Rename the e2e control to compareTabsWithoutCompensation and correct the vacuity-guard comment; rewrap the WorkspaceBrowser doc comment. --- ...-composer-tab-gutter-reservation.i18n.yaml | 4 +- ...6-08-04-composer-tab-gutter-reservation.md | 16 ++--- ...8-04-composer-tab-gutter-reservation.zh.md | 16 ++--- ...-overlay-seat-width-compensation.i18n.yaml | 4 +- ...omposer-overlay-seat-width-compensation.md | 7 +- ...oser-overlay-seat-width-compensation.zh.md | 7 +- apps/web/tests/composer-tab-geometry.e2e.ts | 28 ++++---- .../skeleton/ConversationRoot.module.css | 6 +- packages/client/ui-theme/README.i18n.yaml | 4 +- packages/client/ui-theme/README.md | 2 +- packages/client/ui-theme/README.zh.md | 2 +- .../tests/scrollbar-styles.client.spec.ts | 70 +++++++++++++++++-- .../src/client/WorkspaceBrowser.tsx | 8 +-- 13 files changed, 120 insertions(+), 54 deletions(-) diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml index 0082a6f991..ccae7043a7 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md -2026-08-04-composer-tab-gutter-reservation.md: 2c2813371af073eee4bd80ce7813e77af3c0c319 -2026-08-04-composer-tab-gutter-reservation.zh.md: e450def2482218028875a48fce8842c88b5ba82e +2026-08-04-composer-tab-gutter-reservation.md: 8bd9fb2d86982d82b44a82c55b7303fcd9a5bf4d +2026-08-04-composer-tab-gutter-reservation.zh.md: 4b70aeb1d3777384907c345968971fcf75b3e74d diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md index 2c2813371a..8bd9fb2d86 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md +++ b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md @@ -2,8 +2,6 @@ Status: implemented -> Superseded for overlay views by [the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md): the overlay branch no longer reserves a gutter, and the seat compensates for the bar instead. Chat keeps the reservation described here. - English | [中文](2026-08-04-composer-tab-gutter-reservation.zh.md) ## Problem @@ -16,13 +14,11 @@ So for as long as the transcript overflowed — the ordinary state of any sessio ## Decision -`.scrollBody` declares `scrollbar-gutter: stable` unconditionally, and the overlay branch declares the same box a scroll container on both axes — `overflow-x: hidden; overflow-y: auto` — instead of `overflow: hidden`. +`.scrollBody` declares `scrollbar-gutter: stable` for the Chat state, and the overlay branch overrides it with `scrollbar-gutter: auto` while staying a scroll container on both axes — `overflow-x: hidden; overflow-y: auto`. The reservation is Chat's alone: it holds the seat's content box at the same width whether or not the transcript overflows, so the card never jumps as a growing transcript starts to scroll, nor between the hero phase and the first scrolling turn. The overlay branch reserves nothing — the view owns its own scrollers, so a gutter there would only narrow the view's content — and its seat compensates for the bar instead ([the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md)). -The two halves are one change. The reservation is what makes both states measure against the same width; declaring the overlay branch a scroll container is what makes the reservation reach it. `stable` rather than `auto` because `auto` reserves only while the box actually overflows, and the difference between overflowing and not is precisely the difference between the two tabs — an `auto` gutter would state the bug rather than fix it. +`stable` rather than `auto` because `auto` reserves only while the box actually overflows, and the difference between overflowing and not is precisely the difference between Chat's two phases — an `auto` gutter would state the bug rather than fix it. -The overlay state is a scroll container that nothing scrolls: the view fills it (`flex: 1 1 0` with its own clip) and the seat is out of flow, so no gesture and no clipping behavior changes. What changes is which declarations the engine honours. WebKit applies `scrollbar-gutter` to an `overflow-y: auto` box and ignores it on a hidden one — measured on this app's own composer layers and recorded in [the composer scrollport note](2026-07-31-composer-text-layers-share-one-scrollport.md) — so a reservation left on a hidden box would hold in Chromium and silently not in Safari. - -The horizontal axis is declared rather than left to compute: a box that scrolls on one axis computes `visible` on the other to `auto`, and would grow a horizontal scrollbar of its own the first time a view's content reached past the column. +The reservation lives on an `overflow-y: auto` box, and that form is load-bearing: WebKit applies `scrollbar-gutter` to an `overflow-y: auto` box and ignores it on a hidden one — measured on this app's own composer layers and recorded in [the composer scrollport note](2026-07-31-composer-text-layers-share-one-scrollport.md) — so a reservation on a hidden box would hold in Chromium and silently not in Safari. The overlay branch keeps its `overflow-y: auto` form too, as a clipping box nothing scrolls out of: a single-axis scroller computes the other axis to `auto`, so the horizontal axis is declared `hidden` rather than left to compute, and would otherwise grow a horizontal scrollbar of its own the first time a view's content reached past the column. The reservation is worth what it costs only because the bar takes layout space here at all, which is not the browser's default behavior but this client's: `::-webkit-scrollbar` carries a width in ui-theme's sheet ([themed scrollbars](2026-07-28-themed-scrollbars-and-reserved-gutter.md)), and the sidebar's session list already reserves its own gutter for the same reason. @@ -39,7 +35,7 @@ The reservation is worth what it costs only because the bar takes layout space h ## Consequences - Chat's content column is permanently 8px narrower — in the hero phase and while the transcript is short as well, where no bar is drawn. That is the trade: one card position at every content height, instead of the widest possible column. -- The fix covers three transitions with one declaration, because all three are the same difference: Chat ↔ Trajectory, short ↔ scrolling transcript within Chat, and hero ↔ first scrolling turn. +- The card holds one position across three transitions, by two mechanisms: the reservation keeps Chat's seat at one width across its own phases (short ↔ scrolling transcript, hero ↔ first scrolling turn), and the overlay seat's compensation matches it on the Chat ↔ Trajectory transition ([the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md)). - The overlay state is now a scroll container. Nothing in it can overflow today; a future view that let its content exceed the column would scroll this box instead of clipping, and would need its own clip the way the Trajectory view already has one. - The committed golden records the reserved band, so a change to the sheet's `::-webkit-scrollbar` width — the value that decides how wide the reservation is — arrives as a reviewable diff in this scenario as well as in the sidebar's. @@ -47,6 +43,6 @@ The reservation is worth what it costs only because the bar takes layout space h `apps/web/tests/composer-tab-geometry.e2e.ts` measures the input card's rectangle in both tabs, at a viewport where the card sits at its width cap and one where it shrinks with the column, and asserts the two rectangles are the same rectangle. Only a real engine reports this: jsdom gives every element a zero-sized box and no scrollbar, so a unit spec could assert the declarations exist but not that the two states land in the same place. For the same reason no CSS-text spec accompanies it — it would restate the declarations without adding a fact the browser lane does not already establish. -The scenario launches chromium without Playwright's default `--hide-scrollbars`, which is load-bearing: under that argument a bar consumes no layout width, both tabs agree before this change as much as after it, and every comparison in the file holds vacuously. Measured, the pre-fix cascade leaves both bands at 0 under the argument, and at 8 and 0 with it dropped. +The scenario launches chromium without Playwright's default `--hide-scrollbars`, which is load-bearing: under that argument a bar consumes no layout width, so the tabs agree with and without the compensation and every comparison in the file holds vacuously. Measured, both bands sit at 0 under the argument and at 8 and 0 with it dropped. -The pre-fix cascade is then applied in the page — `scrollbar-gutter: auto` on the scroller, `overflow: hidden` on the overlay branch — and the same two tabs measured through it, which is what separates a card that does not move from a tab switch that never reached the layout. It reproduces the reported symptom as a number: 4px on each edge, half the 8px band. The golden records that control beside the fixed state, so the fixture carries the difference the change removes rather than only its absence. +The uncompensated cascade is then applied in the page — the overlay seat's `right` compensation dropped to 0 via `!important`, Chat's reservation untouched — and the same two tabs measured through it, which is what separates a card that does not move from a tab switch that never reached the layout. It reproduces the reported symptom as a number: 4px on each edge, half the 8px band. The golden records that control beside the fixed state, so the fixture carries the difference the change removes rather than only its absence. diff --git a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md index e450def248..4b70aeb1d3 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md @@ -2,8 +2,6 @@ Status: implemented -> 对覆盖视图已被 [座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md) 取代:覆盖分支不再预留滚动条槽,座位改为补偿滚动条宽度。Chat 仍保留本文所述的预留。 - [English](2026-08-04-composer-tab-gutter-reservation.md) | 中文 ## 问题 @@ -16,13 +14,11 @@ composer 座位在组件树中只有一个节点、一个位置,但它究竟 ## 决策 -`.scrollBody` 无条件声明 `scrollbar-gutter: stable`,overlay 分支则把同一个盒子在两个轴向上都声明为滚动容器——`overflow-x: hidden; overflow-y: auto`——而不再是 `overflow: hidden`。 +`.scrollBody` 为 Chat 状态声明 `scrollbar-gutter: stable`,覆盖分支则将其覆盖为 `scrollbar-gutter: auto`,同时保持为双轴滚动容器——`overflow-x: hidden; overflow-y: auto`。这条预留只属于 Chat:它让座位的内容盒在 transcript 是否溢出时都保持同一宽度,因此卡片不会在 transcript 增长到开始滚动的那一刻跳动,也不会在 hero 态与第一个可滚动轮次之间跳动。覆盖分支不预留任何槽位——视图自己滚动,槽位只会白白收窄视图内容——它的座位改为补偿滚动条宽度([座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md))。 -这两半是同一处改动。预留使两种状态依附于同一个宽度;把 overlay 分支声明为滚动容器,才使这条预留真正抵达它。选 `stable` 而非 `auto`,是因为 `auto` 只在盒子确实溢出时才预留,而「溢出与否」恰恰就是两个标签页之间的那点差别——`auto` 的写法只是把缺陷重述一遍,并不能修掉它。 +选 `stable` 而非 `auto`,是因为 `auto` 只在盒子确实溢出时才预留,而「溢出与否」恰恰就是 Chat 两种相位之间的那点差别——`auto` 的写法只是把缺陷重述一遍,并不能修掉它。 -overlay 状态是一个没有任何东西会去滚动它的滚动容器:视图把它填满(`flex: 1 1 0`,且自带裁剪),座位不在常规流中,因此没有任何手势与裁剪行为发生变化。变化的是引擎会认哪些声明。WebKit 对 `overflow-y: auto` 的盒子应用 `scrollbar-gutter`,对 hidden 的盒子则忽略它——这是在本应用 composer 自身的图层上实测所得,并记录于 [composer 滚动视口记录](2026-07-31-composer-text-layers-share-one-scrollport.md)——所以把预留留在一个 hidden 盒子上,会在 Chromium 上成立,在 Safari 上悄无声息地不成立。 - -横向轴是显式声明的,而不是交给推导:单轴滚动的盒子会把另一轴的 `visible` 计算为 `auto`,于是只要某个视图的内容第一次伸出列外,它就会长出自己的横向滚动条。 +这条预留位于 `overflow-y: auto` 的盒子上,而这个形式是承重的:WebKit 对 `overflow-y: auto` 的盒子应用 `scrollbar-gutter`,对 hidden 的盒子则忽略它——这是在本应用 composer 自身的图层上实测所得,并记录于 [composer 滚动视口记录](2026-07-31-composer-text-layers-share-one-scrollport.md)——所以把预留放在 hidden 盒子上,会在 Chromium 上成立,在 Safari 上悄无声息地不成立。覆盖分支同样保留 `overflow-y: auto` 的形式,作为没有任何内容会滚出去的裁剪盒:单轴滚动的盒子会把另一轴的 `visible` 计算为 `auto`,因此横向轴显式声明为 `hidden` 而不是交给推导,否则某个视图的内容第一次伸出列外时,它就会长出自己的横向滚动条。 这条预留之所以值回它的代价,前提是滚动条在这里确实占布局空间——这并非浏览器的默认行为,而是本客户端的选择:ui-theme 的样式表给 `::-webkit-scrollbar` 声明了宽度([滚动条主题化](2026-07-28-themed-scrollbars-and-reserved-gutter.md)),侧边栏的会话列表也正是出于同一原因预留了自己的滚动条槽。 @@ -39,7 +35,7 @@ overlay 状态是一个没有任何东西会去滚动它的滚动容器:视图 ## 后果 - Chat 的内容列永久变窄 8px——hero 态与 transcript 尚短、根本不绘制滚动条时同样如此。这就是这笔交易:以最宽的列换取卡片在任何内容高度下都只有一个位置。 -- 一条声明覆盖三种切换,因为这三者本就是同一个差异:Chat ↔ Trajectory、Chat 内部 transcript 较短 ↔ transcript 可滚动,以及 hero ↔ 第一个可滚动轮次。 +- 卡片在三种切换下保持同一位置,由两种机制达成:预留让 Chat 的座位在自身各相位间保持同一宽度(transcript 较短 ↔ 可滚动、hero ↔ 第一个可滚动轮次),Chat ↔ Trajectory 的切换则由覆盖座位的补偿来对齐([座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md))。 - overlay 状态现在是一个滚动容器。今天其中没有任何内容会溢出;将来若有视图允许自身内容超出会话列,这个盒子会滚动而不是裁剪,那个视图就需要像 Trajectory 视图那样自带裁剪。 - 提交的 golden 记录了预留条带,因此样式表中 `::-webkit-scrollbar` 宽度的变化——决定这条预留有多宽的那个值——会在本场景中与在侧边栏场景中一样,以可评审的 diff 形式出现。 @@ -47,6 +43,6 @@ overlay 状态是一个没有任何东西会去滚动它的滚动容器:视图 `apps/web/tests/composer-tab-geometry.e2e.ts` 在两个标签页下测量输入卡片的矩形,分别取卡片处于宽度上限的视口与卡片随列收缩的视口,并断言这两个矩形是同一个矩形。只有真实引擎能报告这件事:jsdom 给每个元素的盒子尺寸都是零,也没有滚动条,因此单元测试只能断言那些声明存在,无法断言两种状态落在同一位置。出于同一原因,本次没有附带读取 CSS 文本的单元测试——它只会把声明复述一遍,并不会补上浏览器车道尚未确立的事实。 -该场景启动 chromium 时去掉了 Playwright 默认的 `--hide-scrollbars`,这一点是承重的:带上该参数时滚动条不占任何布局宽度,两个标签页在改动前后同样一致,文件中的每一处比较都会空洞地通过。实测:带上该参数时,改动前的层叠让两条预留带的宽度都是 0;去掉它则分别是 8 与 0。 +该场景启动 chromium 时去掉了 Playwright 默认的 `--hide-scrollbars`,这一点是承重的:带上该参数时滚动条不占任何布局宽度,因此两个标签页在有补偿与无补偿时同样一致,文件中的每一处比较都会空洞地通过。实测:带上该参数时两条预留带的宽度都是 0;去掉它则分别是 8 与 0。 -随后,改动前的层叠会被注入页面——滚动容器上 `scrollbar-gutter: auto`,overlay 分支上 `overflow: hidden`——并在其下测量同样的两个标签页,这正是把「卡片确实没动」与「标签页切换根本没到达布局」区分开的那一步。它把上报的症状复现为一个数字:每条边 4px,恰是 8px 带宽的一半。golden 把这份对照与修复后的状态并排记录,因此 fixture(测试前置数据)承载的是这次改动所消除的那个差值,而不仅仅是它的缺席。 +随后,未补偿的级联会被注入页面——通过 `!important` 把覆盖座位的 `right` 补偿降为 0,Chat 的预留保持不变——并在其下测量同样的两个标签页,这正是把「卡片确实没动」与「标签页切换根本没到达布局」区分开的那一步。它把上报的症状复现为一个数字:每条边 4px,恰是 8px 带宽的一半。golden 把这份对照与修复后的状态并排记录,因此 fixture(测试前置数据)承载的是这次改动所消除的那个差值,而不仅仅是它的缺席。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml index fd88ecbc93..a1904a4b03 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md -2026-08-12-composer-overlay-seat-width-compensation.md: 85281ed4301046672ff4c02320b196a397e6d163 -2026-08-12-composer-overlay-seat-width-compensation.zh.md: 7bcf060e651a828068ef207caaebd01fa2ffd7ae +2026-08-12-composer-overlay-seat-width-compensation.md: 0ec4d1272ac1adab5b724dccf44f567e15cc3368 +2026-08-12-composer-overlay-seat-width-compensation.zh.md: 2f66771240fe62e15c481342d55a82dd09cfb864 diff --git a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md index 85281ed430..0ec4d1272a 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md +++ b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md @@ -14,7 +14,7 @@ The trajectory table made that cost visible: its full-width row divider lines st The reservation now belongs to Chat alone. The overlay branch declares `scrollbar-gutter: auto`, so the view's content spans the full column; the overlay composer seat (absolutely positioned against the padding box) gives back the bar's width with `right: var(--dsh-scrollbar-width)`, so the input card still measures the same width as Chat's seat and does not move between tabs. -The compensation value is not a literal: ui-theme's scrollbar.css defines `--dsh-scrollbar-width` (8px on the WebKit path) beside the `::-webkit-scrollbar` rule it mirrors, and the seat reads that variable. A change to the sheet's bar width reaches the compensation in the same reviewable diff as the bar itself. +The compensation value is not a literal: ui-theme's scrollbar.css defines `--dsh-scrollbar-width` (8px on the WebKit path) beside the `::-webkit-scrollbar` rule it mirrors, and the seat reads that variable. The scrollbar-styles spec pairs the variable with the mirrored rule and with the compensation consumer, so a change to the sheet's bar width without the variable — or to the variable without the consumer — fails the gate, not just review. ## Alternatives considered @@ -24,12 +24,15 @@ The compensation value is not a literal: ui-theme's scrollbar.css defines `--dsh **Accept the 4px card shift.** Dropping the reservation without compensating the seat would move the input card on every tab switch, which is exactly the symptom the earlier note fixed. Rejected: the card position is a deliberate cross-tab invariant. +**Inset the overlay seat by the bar's width.** The [gutter-reservation note](2026-08-04-composer-tab-gutter-reservation.md) rejected exactly this, and this note adopts it; what changed is the rejection's premise. The number was the engine's, not ours — the WebKit path draws the sheet's 8px bar while the Firefox path draws whatever `scrollbar-width: thin` resolves to — so a hardcoded inset would line the two states up in Chromium and drift elsewhere. The overlay branch reserved an engine-resolved gutter of its own back then, so an inset had to match that width exactly. Today the overlay branch reserves nothing, so the compensation is the overlay side's only mechanism, and the literal half of the rejection is answered by making the 8px a variable that mirrors the `::-webkit-scrollbar` rule in the same diff. The Firefox half remains: Chat reserves the engine-resolved width while the compensation stays fixed, and the residual drift where the two differ is recorded as an accepted cost in Consequences. + ## Consequences - Chat keeps its reserved gutter and its stable card position; nothing changes on that tab. - Overlay views (trajectory) span the full column; the trajectory ledger's divider lines reach the pane edge. - The input card still holds one horizontal position across the Chat and Trajectory tabs, now by two mechanisms instead of one: Chat reserves, the overlay seat compensates. -- `--dsh-scrollbar-width` becomes a public ui-theme variable read outside ui-theme; the scrollbar-styles spec's indirection checks only scan `--dsh-scrollbar-thumb{,-hover}` rebinds, so the width variable is not covered by the pair gate. +- Chat reserves the engine-resolved width while the overlay seat compensates a fixed 8px. Where the two differ — the Firefox path resolves `scrollbar-width: thin` per platform, and the e2e runs only on Chromium — the card drifts by half the difference on tab switch. Accepted residual cost, recorded here rather than asserted away: no measurement of the Firefox thin width on the target platforms exists in this change. +- `--dsh-scrollbar-width` becomes a public ui-theme variable read outside ui-theme; the scrollbar-styles spec pairs it with the mirrored `::-webkit-scrollbar` width rule and with the compensation consumer, closing the indirection-gate gap the variable would otherwise leave. ## Testing diff --git a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md index 7bcf060e65..2f66771240 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.zh.md @@ -14,7 +14,7 @@ trajectory 表格让这个代价显形:整行分隔线在面板右边缘前 8p 预留现在只属于 Chat。覆盖分支声明 `scrollbar-gutter: auto`,视图内容占满整列;覆盖分支的 composer 座位(相对 padding box 绝对定位)用 `right: var(--dsh-scrollbar-width)` 让出滚动条宽度,使输入卡仍与 Chat 座位测得相同宽度,切换标签页时不移动。 -补偿值不是字面量:ui-theme 的 scrollbar.css 在它镜像的 `::-webkit-scrollbar` 规则旁定义 `--dsh-scrollbar-width`(WebKit 路径 8px),座位读取该变量。样式表滚动条宽度一变,补偿就会与滚动条本身出现在同一次可审阅的 diff 中。 +补偿值不是字面量:ui-theme 的 scrollbar.css 在它镜像的 `::-webkit-scrollbar` 规则旁定义 `--dsh-scrollbar-width`(WebKit 路径 8px),座位读取该变量。scrollbar-styles 规格把该变量与其镜像规则、以及补偿消费者配对检查,因此样式表滚动条宽度一变却不同步变量——或变量一变却不同步消费者——都会让门禁失败,而不只是评审时发现。 ## 备选方案 @@ -24,12 +24,15 @@ trajectory 表格让这个代价显形:整行分隔线在面板右边缘前 8p **接受 4px 卡片位移。** 去掉预留却不补偿座位,会在每次切换标签页时移动输入卡——正是前一份 note 修复的症状。已拒绝:卡片位置是刻意保持的跨标签页不变量。 +**把 overlay 座位按滚动条宽度内缩。** [滚动条槽预留 note](2026-08-04-composer-tab-gutter-reservation.md) 当初否决的正是这个方案,本 note 采纳了它;变的是否决的前提。这个数字属于引擎而不属于我们——WebKit 路径绘制样式表里的 8px 滚动条,Firefox 路径绘制 `scrollbar-width: thin` 解析出的宽度——因此硬编码的内缩会让两种状态在 Chromium 上对齐、在别处继续漂移。当初 overlay 分支自己预留的是引擎解析出的槽宽,内缩必须精确匹配那个宽度。如今 overlay 分支不预留任何槽位,补偿成为覆盖侧唯一的机制;否决的字面量那一半,通过把 8px 变成与 `::-webkit-scrollbar` 规则同处一个 diff 的变量来回应。Firefox 那一半仍然存在:Chat 预留引擎解析宽度,补偿保持固定 8px,两者不等之处的残余漂移作为接受的代价记录在后果中。 + ## 后果 - Chat 保留滚动条槽与稳定的卡片位置;该标签页无任何变化。 - 覆盖视图(trajectory)占满整列;trajectory 台账的分隔线到达面板右边缘。 - 输入卡在 Chat 与 Trajectory 标签页间仍保持同一水平位置,现在由两种机制而非一种达成:Chat 预留,覆盖座位补偿。 -- `--dsh-scrollbar-width` 成为 ui-theme 对外、且被 ui-theme 之外读取的变量;scrollbar-styles 规格的间接层检查只扫描 `--dsh-scrollbar-thumb{,-hover}` 重绑,宽度变量不受成对门禁覆盖。 +- Chat 预留引擎解析宽度,覆盖座位补偿固定的 8px。两者不等之处——Firefox 路径按平台解析 `scrollbar-width: thin`,而 e2e 只在 Chromium 上运行——卡片在切换标签页时会漂移半个差值。这是接受的残余代价,如实记录于此而不断言消除:本次改动并未提供目标平台 Firefox thin 宽度的实测。 +- `--dsh-scrollbar-width` 成为 ui-theme 对外、且被 ui-theme 之外读取的变量;scrollbar-styles 规格把它与镜像的 `::-webkit-scrollbar` 宽度规则、以及补偿消费者配对检查,补上了该变量本会留下的间接层门禁缺口。 ## 测试 diff --git a/apps/web/tests/composer-tab-geometry.e2e.ts b/apps/web/tests/composer-tab-geometry.e2e.ts index b491b1257c..0f7b01c7ff 100644 --- a/apps/web/tests/composer-tab-geometry.e2e.ts +++ b/apps/web/tests/composer-tab-geometry.e2e.ts @@ -28,13 +28,13 @@ // The browser is launched WITHOUT Playwright's default `--hide-scrollbars`, // which is load-bearing rather than incidental. Under that argument a scroll // container's bar consumes no layout width at all, so the two tabs agree with -// and without the reservation and every comparison below holds vacuously — -// measured: the unreserved cascade leaves both tabs' bands at 0 there, against -// 8 and 0 with the argument dropped. Dropping it is also the faithful +// and without the compensation and every comparison below holds vacuously — +// measured: the uncompensated cascade leaves both tabs' bands at 0 there, +// against 8 and 0 with the argument dropped. Dropping it is also the faithful // configuration: ui-theme's scrollbar.css gives `::-webkit-scrollbar` a width, // and a bar that occupies layout space is what the product actually draws. // -// The scenario runs that unreserved cascade in the page — the overlay seat's +// The scenario runs that uncompensated cascade in the page — the overlay seat's // `right` compensation dropped to 0 — and measures the same two tabs through // it, which is what keeps the equal rectangles above from being explained by a // tab switch that never reached the layout. It is the reported symptom as a @@ -227,7 +227,7 @@ async function compareTabs(page: Page): Promise { * @param page - the page under test. * @returns the comparison as the column lays out without the compensation. */ -async function compareTabsWithoutReservation(page: Page): Promise { +async function compareTabsWithoutCompensation(page: Page): Promise { await page.evaluate(({ id, css }) => { const style = document.createElement('style') style.id = id @@ -327,9 +327,13 @@ describe('web e2e: input card position across view tabs', () => { it('reserves the gutter in Chat and lets Trajectory own its width', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-band')) await setMeasuredViewport(page, WIDE_VIEWPORT, false) - // Vacuity guard, in two parts. A transcript that does not overflow gives - // Chat no scrollbar, and a hidden or overlaid bar gives it no width; either - // would make the tabs agree without the compensation doing anything. + // Vacuity guard. The scenario must be able to fail: on an engine that + // does not implement `scrollbar-gutter`, Chat reserves nothing and the + // overlay seat's fixed compensation stands alone, manufacturing an 8px + // deviation the equal-rectangle assertions would catch. `stable` reserves + // even without overflow, so a short transcript is not a vacuous case; the + // poll still pins the measurement to the overflowing state the product + // ships. await expect.poll(async () => (await measureTab(page)).scrolls, { timeout: 10_000 }).toBe(true) const comparison = await compareTabs(page) expect(comparison.chat.band).toBeGreaterThan(0) @@ -356,7 +360,7 @@ describe('web e2e: input card position across view tabs', () => { await setMeasuredViewport(page, WIDE_VIEWPORT, false) const comparison = await compareTabs(page) // The reported symptom as a number. At this viewport the card sits at its - // width cap, so the unreserved cascade's shift shows up as a centring + // width cap, so the uncompensated cascade's shift shows up as a centring // difference — half the band on each edge — rather than as a width change. expect(comparison.leftShift).toBe(0) expect(comparison.rightShift).toBe(0) @@ -387,11 +391,11 @@ describe('web e2e: input card position across view tabs', () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-control')) await setMeasuredViewport(page, WIDE_VIEWPORT, false) // The control: without it, equal rectangles could also mean the tab switch - // never reached the layout. Under the unreserved cascade the overlay seat + // never reached the layout. Under the uncompensated cascade the overlay seat // loses its `right` compensation and measures the full padding box, so the // card moves by half the band on each edge. Chat's own reservation is // untouched — that is the side that must not change. - const comparison = await compareTabsWithoutReservation(page) + const comparison = await compareTabsWithoutCompensation(page) expect(comparison.chat.gutter).toBe('stable') expect(comparison.chat.band).toBeGreaterThan(0) expect(comparison.trajectory.band).toBe(0) @@ -411,7 +415,7 @@ describe('web e2e: input card position across view tabs', () => { await setMeasuredViewport(page, NARROW_VIEWPORT, true) const narrow = await compareTabs(page) await setMeasuredViewport(page, WIDE_VIEWPORT, false) - const control = await compareTabsWithoutReservation(page) + const control = await compareTabsWithoutCompensation(page) await compareOrRefreshGolden(GEOMETRY_EXPECTED, renderGeometry(wide, narrow, control), MODE) expect(tripwire.pageErrors).toEqual([]) }, 60_000) diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css index c6618fc624..20ade47c8a 100644 --- a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css @@ -297,8 +297,10 @@ by the bar's width; the overlay seat is against the padding box, so it must give back the same width to keep the input card in place across tabs. Reads ui-theme's --dsh-scrollbar-width, which mirrors the WebKit - bar's layout width; Firefox's thin bar resolves to the same 8px on the - platforms this app targets, so the compensation holds there too. */ + bar's layout width. Chat reserves the engine-resolved width, so on an + engine where the resolved width differs from this fixed 8px the card + drifts by half the difference — accepted residual risk, recorded in + ([decision](../../../../../../.agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md)). */ right: var(--dsh-scrollbar-width); bottom: 0; left: 0; diff --git a/packages/client/ui-theme/README.i18n.yaml b/packages/client/ui-theme/README.i18n.yaml index e2bdc28ecb..00c049e8df 100644 --- a/packages/client/ui-theme/README.i18n.yaml +++ b/packages/client/ui-theme/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-theme/README.md -README.md: df4d5e0370962bf6f2a8ac0a7b88d669225dc5c5 -README.zh.md: fb8e937979a5168803be78a49e1bc1e7eacd7a47 +README.md: c24f2ae5692fdd05134ce780d21569b87570d1c7 +README.zh.md: 518b8930027c88bd85139a8a7325cea1fd40b40c diff --git a/packages/client/ui-theme/README.md b/packages/client/ui-theme/README.md index df4d5e0370..c24f2ae569 100644 --- a/packages/client/ui-theme/README.md +++ b/packages/client/ui-theme/README.md @@ -8,7 +8,7 @@ When the host composition includes an HTTP server, the host half injects a synch `src/styles/` holds five sheets, all imported by the web shell's `base.css`: `base.css`, `design-platform.css`, `scrollbar.css`, `gradient-shadow-text.css`, and `shiki.css`. `scrollbar.css` is the sole consumer of the `--dsw-alias-scrollbar-*` tokens and must follow `design-platform.css`, which declares them. -Scrollbar rebinding contract: `scrollbar.css` binds `--dsh-scrollbar-thumb` and `--dsh-scrollbar-thumb-hover` on `body` to the l1 (base-surface) tokens, and both rendering paths read that pair. An elevated surface (menu, popover, dialog) sets `--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2)` and `--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2)` on its own container; one rebind retints whichever path the engine took. The pair's other legal target is `transparent`, which draws no thumb at all — [ui-sidebar](../ui-sidebar/README.md) rebinds its column that way while the pointer is elsewhere. A rebind to the l1 pair is not a rebind; it restates the base-surface default. +Scrollbar rebinding contract: `scrollbar.css` binds `--dsh-scrollbar-thumb` and `--dsh-scrollbar-thumb-hover` on `body` to the l1 (base-surface) tokens, and both rendering paths read that pair. An elevated surface (menu, popover, dialog) sets `--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2)` and `--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2)` on its own container; one rebind retints whichever path the engine took. The pair's other legal target is `transparent`, which draws no thumb at all — [ui-sidebar](../ui-sidebar/README.md) rebinds its column that way while the pointer is elsewhere. A rebind to the l1 pair is not a rebind; it restates the base-surface default. `--dsh-scrollbar-width` mirrors the WebKit bar's layout width for surfaces that align themselves beside a space-consuming bar — [ui-conversation](../ui-conversation/README.md) reads it for the overlay composer seat's `right` offset — and the scrollbar-styles spec pairs it with the mirrored rule and the consumer. The two paths are mutually exclusive by construction. `scrollbar-width`/`scrollbar-color` sit inside `@supports not selector(::-webkit-scrollbar)` because a non-`auto` value of either makes Chromium and Safari discard every `::-webkit-scrollbar*` rule for that element, `::-webkit-scrollbar-thumb:hover` included — declaring both unconditionally leaves `--dsh-scrollbar-thumb-hover` with no rendering anywhere. Firefox therefore takes the standard properties and WebKit-based engines take the pseudo-elements, so the hover token only ever renders through the pseudo-element path. Reasoning and the measured computed values: [the scrollbar Agent Note](../../../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.md). diff --git a/packages/client/ui-theme/README.zh.md b/packages/client/ui-theme/README.zh.md index fb8e937979..518b893002 100644 --- a/packages/client/ui-theme/README.zh.md +++ b/packages/client/ui-theme/README.zh.md @@ -8,7 +8,7 @@ `src/styles/` 下有五张样式表,全部由 web 壳的 `base.css` 导入:`base.css`、`design-platform.css`、`scrollbar.css`、`gradient-shadow-text.css` 与 `shiki.css`。`scrollbar.css` 是 `--dsw-alias-scrollbar-*` token 的唯一消费方,必须排在声明这些 token 的 `design-platform.css` 之后。 -滚动条重新绑定约定:`scrollbar.css` 在 `body` 上把 `--dsh-scrollbar-thumb` 与 `--dsh-scrollbar-thumb-hover` 绑定到 l1(基础表面)token,两条渲染路径都读取这一组变量。高层级表面(菜单、浮层、对话框)在自己的容器上设置 `--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2)` 与 `--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2)`;一次重新绑定即可为引擎实际走的那条路径换色。这组变量的另一个合法目标是 `transparent`,即完全不绘制滑块——[ui-sidebar](../ui-sidebar/README.md) 在指针不在栏内时就这样重新绑定自己的列。绑回 l1 那组不算重新绑定,它只是重述基础表面的默认值。 +滚动条重新绑定约定:`scrollbar.css` 在 `body` 上把 `--dsh-scrollbar-thumb` 与 `--dsh-scrollbar-thumb-hover` 绑定到 l1(基础表面)token,两条渲染路径都读取这一组变量。高层级表面(菜单、浮层、对话框)在自己的容器上设置 `--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2)` 与 `--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2)`;一次重新绑定即可为引擎实际走的那条路径换色。这组变量的另一个合法目标是 `transparent`,即完全不绘制滑块——[ui-sidebar](../ui-sidebar/README.md) 在指针不在栏内时就这样重新绑定自己的列。绑回 l1 那组不算重新绑定,它只是重述基础表面的默认值。`--dsh-scrollbar-width` 镜像 WebKit 滚动条的布局宽度,供需要与占布局宽度的滚动条对齐的表面使用——[ui-conversation](../ui-conversation/README.md) 用它作为覆盖 composer 座位 `right` 偏移——scrollbar-styles 规格把它与镜像规则及消费者配对检查。 两条路径在构造上互斥。`scrollbar-width`/`scrollbar-color` 写在 `@supports not selector(::-webkit-scrollbar)` 之内,因为这两个属性中的任一个只要取非 `auto` 值,Chromium 与 Safari 就会丢弃该元素上的全部 `::-webkit-scrollbar*` 规则,`::-webkit-scrollbar-thumb:hover` 也在其中——若无条件地同时声明,`--dsh-scrollbar-thumb-hover` 在任何引擎上都不会被渲染。因此 Firefox 走标准属性,WebKit 系引擎走伪元素,hover token 只经由伪元素这条路径渲染。相关原理与实测计算值见[滚动条 Agent Note](../../../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.md)。 diff --git a/packages/client/ui-theme/tests/scrollbar-styles.client.spec.ts b/packages/client/ui-theme/tests/scrollbar-styles.client.spec.ts index b6ed3f0c90..e9234846eb 100644 --- a/packages/client/ui-theme/tests/scrollbar-styles.client.spec.ts +++ b/packages/client/ui-theme/tests/scrollbar-styles.client.spec.ts @@ -2,10 +2,11 @@ * Scrollbar stylesheet contract, asserted against the CSS text on disk: every * --dsw-alias-scrollbar-* token design-platform.css defines has a consumer, * scrollbar.css binds the base-surface pair through the rebindable - * indirection, and elevated surfaces rebind that indirection in complete - * pairs. The expected token set is scanned out of design-platform.css, so - * adding, renaming, or dropping a scrollbar token moves these assertions with - * it. + * indirection, the width variable mirrors the ::-webkit-scrollbar rule for + * consumers that align beside the bar, and elevated surfaces rebind that + * indirection in complete pairs. The expected token set is scanned out of + * design-platform.css, so adding, renaming, or dropping a scrollbar token + * moves these assertions with it. */ import { readdirSync, readFileSync } from 'node:fs' import { join } from 'node:path' @@ -341,6 +342,67 @@ describe('scrollbar.css base-surface binding', () => { }) }) +describe('scrollbar.css width variable', () => { + const WIDTH_VARIABLE = `${INDIRECTION_PREFIX}width` + + it('defines the width variable on body as a static length', () => { + // The overlay seat compensation reads a fixed number, not a second + // indirection: the mirror check below compares the WebKit rule against + // this value, so a var()-to-var() chain would compare one indirection to + // another instead of pinning the number. + const value = scrollbarRules + .filter(rule => rule.selectors.includes('body')) + .flatMap(rule => rule.declarations) + .findLast(([property]) => property === WIDTH_VARIABLE)?.[1] + expect(value, WIDTH_VARIABLE).toBeDefined() + expect(value, WIDTH_VARIABLE).toMatch(/^\d+(?:\.\d+)?px$/) + }) + + it('mirrors the ::-webkit-scrollbar width rule with the variable value', () => { + // The compensation stays aligned with the WebKit bar only while both read + // the same number. A change to one side without the other puts the overlay + // seat a band off from Chat on WebKit engines. + const variableValue = scrollbarRules + .filter(rule => rule.selectors.includes('body')) + .flatMap(rule => rule.declarations) + .findLast(([property]) => property === WIDTH_VARIABLE)?.[1] + const webkitWidth = scrollbarRules + .filter(rule => rule.selectors.includes('::-webkit-scrollbar')) + .flatMap(rule => rule.declarations) + .findLast(([property]) => property === 'width')?.[1] + expect(webkitWidth, '::-webkit-scrollbar width').toBeDefined() + expect(webkitWidth).toBe(variableValue) + }) + + it('every reader of the width variable outside ui-theme references a defined variable', () => { + // The consumer is ConversationRoot's overlay composer seat + // (`right: var(--dsh-scrollbar-width)`); a rename in scrollbar.css without + // the consumer, or a typo in the consumer, leaves the value + // guaranteed-invalid and the seat loses the band. The equal-rectangle e2e + // would catch it only on an engine that draws the bar, so the sheet + // contract states it here. + const defined = new Set( + scrollbarRules + .flatMap(rule => rule.declarations) + .filter(([property]) => property.startsWith(INDIRECTION_PREFIX)) + .map(([property]) => property), + ) + expect(defined).toContain(WIDTH_VARIABLE) + const readers: string[] = [] + for (const file of packageStylesheets()) { + if (file === fileURLToPath(new URL('scrollbar.css', STYLES))) continue + for (const rule of parseRules(readFileSync(file, 'utf8'))) { + for (const [property, value] of rule.declarations) { + for (const name of varReferences(value)) { + if (name === WIDTH_VARIABLE) readers.push(`${file} ${rule.selectors.join(', ')}: ${property}`) + } + } + } + } + expect(readers.length, 'compensation consumer').toBeGreaterThan(0) + }) +}) + describe('scrollbar.css selectors', () => { const scrollbarColorSelectors = scrollbarRules .filter(rule => rule.declarations.some(([property]) => property === 'scrollbar-color')) diff --git a/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx b/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx index ebd0e11a33..f98950ed70 100644 --- a/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx +++ b/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx @@ -4,10 +4,10 @@ * workspace), search, the grouped tree or flat list, and the workspace * dialogs. Wide state renders the full browser; rail state renders the two * region icons (search / add workspace) as 36px controls on the shell's shared - * rail entry path, each requesting expansion through the owner share. Adding is the header - * button's one action, so it raises the directory flow with no menu in - * between; the flow and its error dialog live in WorkspacePicker (same - * package — direct composition, no slot between them). + * rail entry path, each requesting expansion through the owner share. Adding + * is the header button's one action, so it raises the directory flow with no + * menu in between; the flow and its error dialog live in WorkspacePicker + * (same package — direct composition, no slot between them). */ import { useEffect, useMemo, useRef, useState } from 'react' import clsx from 'clsx' From 7e4b8b1676588e4415e4490a44cab03cc76e501a Mon Sep 17 00:00:00 2001 From: j-xiang Date: Thu, 13 Aug 2026 00:12:44 +0800 Subject: [PATCH 010/119] docs(i18n): human-polish key Chinese READMEs --- README.i18n.yaml | 2 +- README.zh.md | 3 +- apps/cli/README.i18n.yaml | 2 +- apps/cli/README.zh.md | 24 ++++++------ apps/cli/reference/README.i18n.yaml | 2 +- apps/cli/reference/README.zh.md | 25 ++++++------ packages/core/tools/README.i18n.yaml | 2 +- packages/core/tools/README.zh.md | 38 +++++++++---------- python/sdk/README.i18n.yaml | 2 +- python/sdk/README.zh.md | 16 ++++---- .../request-response.expected.json | 2 +- 11 files changed, 58 insertions(+), 60 deletions(-) diff --git a/README.i18n.yaml b/README.i18n.yaml index 816b9ec26c..6a566a9abb 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write README.md README.md: 05eee108fe57f75671a74cec5f3858b1abdfcef2 -README.zh.md: 1a25075e63f13a1f92c678b88b45e2ecca4c4c77 +README.zh.md: a27d995c2c68525c10d287fb74474bbef17a3f57 diff --git a/README.zh.md b/README.zh.md index 1a25075e63..a27d995c2c 100644 --- a/README.zh.md +++ b/README.zh.md @@ -10,8 +10,7 @@ DeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 DeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。 -Session Log 默认留在本地。设置 `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 可仅在提交反馈时共享 Session Log,设置 `DSH_TELEMETRY_MODE=FULL` 可持续上传;`FULL` 同时会启用 dsh-sdk 命令遥测,上报匿名 ID、命令结果以及脱敏后的项目配置。请通过内部企业微信群反馈问题和建议。 - +会话日志默认保存在本地。设置 `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 后,仅在提交反馈时共享对应的会话日志;设置 `DSH_TELEMETRY_MODE=FULL` 后,会持续上传会话日志。`FULL` 还会启用 dsh-sdk 命令遥测,上报匿名 ID、命令执行结果和脱敏后的项目配置。请通过内部企业微信群反馈问题和建议。 ## 运行 diff --git a/apps/cli/README.i18n.yaml b/apps/cli/README.i18n.yaml index fa2fcf2bd4..ac025294ca 100644 --- a/apps/cli/README.i18n.yaml +++ b/apps/cli/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/README.md README.md: d36183c545475020207bbf23e58d3c98b07ac6a6 -README.zh.md: 4693339e836d81c0b069704fb43451fb22accfdb +README.zh.md: 9e5507ad767640b413d00cf16f1becc0f540be2b diff --git a/apps/cli/README.zh.md b/apps/cli/README.zh.md index 4693339e83..9e5507ad76 100644 --- a/apps/cli/README.zh.md +++ b/apps/cli/README.zh.md @@ -2,22 +2,22 @@ [English](README.md) | 中文 -`dsh` 命令是 profile 的产品启动器:profile 是按序叠放的插件组合包 patch 层,之上再叠加用户自己的覆盖层。[`src/args.ts`](src/args.ts) 负责命令语法,[`src/bin.ts`](src/bin.ts) 只加载选中的运行器。无效命令、来自其他模式的选项、配置错误和启动失败都会以非零状态退出。 +`dsh` 是 DeepSeek Harness 中用于启动 profile 的命令;profile 由多个插件组合包 patch 层按顺序叠加而成,其上再应用用户自己的覆盖配置。[`src/args.ts`](src/args.ts) 负责命令语法,[`src/bin.ts`](src/bin.ts) 只加载选中的运行器。无效命令、来自其他模式的选项、配置错误和启动失败都会以非零状态退出。 ## 入口模式 | 命令 | 用途 | |---|---| | `dsh --profile ` | 启动位于 `$DSH_HOME/profiles/` 的指定 profile。 | -| `dsh --profile headless "task"` | 运行一个新的持久化会话,打印最终答案并退出。 | +| `dsh --profile headless "task"` | 运行一个全新的持久化会话,打印最终答案并退出。 | | `dsh web` | `--profile web` 的别名。 | | `dsh plugin --profile ` | 通过在 profile 目录中转发给 pnpm 来管理该 profile 的插件。 | -调用目录是默认 workspace 根目录。`web` 和 `headless` profile 在首次使用时会从随附模板自动初始化;其他任何 profile 都必须通过 `dsh plugin` 创建。 +运行命令时所在的目录将作为默认 workspace 根目录。`web` 和 `headless` profile 在首次使用时会从随附模板自动初始化;其他任何 profile 都必须通过 `dsh plugin` 创建。 ## 应用参数 -启动器只解析属于自己的 flag,并把其后的一切交给启动起来的 profile,任何注入它的应用插件都可以解析这份共享的不可变快照([`dsh-cmdline`](../../packages/boot/cmdline/README.md))。因此启动器的 flag 必须写在前面,而启动器不认识的第一个 token 就是应用参数的起点: +启动器只解析自身的 flag,并将其后的所有内容交给已启动的 profile;注入该 profile 的任意应用插件都可以解析这份共享的不可变快照([`dsh-cmdline`](../../packages/boot/cmdline/README.md))。因此,启动器的 flag 必须写在最前面;启动器无法识别的第一个 token 标志着应用参数的开始: ```sh dsh --profile web --port 8080 # --port belongs to the web app @@ -29,19 +29,19 @@ dsh --help # the launcher's own help ## Profile -profile 目录包含一个 `package.json`(树外插件依赖,加上 profile manifest(元数据清单)`dsh.profile` 及其有序的 `bundles` 列表)和一个 `cordis.patch.yml`(用户自己的 patch 层)。 +profile 目录包含一个 `package.json`,其中记录另行安装的插件依赖,以及 profile manifest(元数据清单)`dsh.profile` 和其中按顺序排列的 `bundles` 列表;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。 -配置树在空根之上组合: -- 先按 `dsh.profile.bundles` 顺序应用各组合包的 patch -- 然后是 profile 的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml` -- 最后是 `--patch` overlay +配置树以空根为起点,依次叠加以下配置层: +- `dsh.profile.bundles` 中各组合包的 patch +- profile 自身的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml` +- `--patch` 指定的覆盖层 -`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自己的 `node_modules` 解析;pnpm 把树外插件安装在后者。 +`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自身的 `node_modules` 解析;pnpm 会将另行安装的插件放入该目录。 使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。 -[CLI(命令行界面)行为参考](reference/README.md)负责确切的层优先级、flag、关闭行为、部署默认值和源码执行。 +层的确切优先级、flag、关闭行为、部署默认值和源码执行方式,以 [CLI(命令行界面)行为参考](reference/README.md)为准。 ## 开发 -生产运行需要已构建的包与前端产物。请在仓库根目录单独运行 `pnpm run build`,然后使用 `pnpm dsh ` 运行 TypeScript 入口并转发所有参数;模块解析约定由[源码执行参考](reference/README.md#source-execution)负责。 +生产运行需要已构建的包与前端产物。请在仓库根目录单独运行 `pnpm run build`,然后使用 `pnpm dsh ` 运行 TypeScript 入口并转发所有参数;模块解析约定以[源码执行参考](reference/README.md#source-execution)为准。 diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index c23be209db..ebbede237a 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/reference/README.md README.md: 17d63fe73ea2b3bda74e9c4da91555b34c21f4ab -README.zh.md: 452d024c1411c4c239b5fc54ec2586e1891e74f3 +README.zh.md: 83945a09073057b8c220da9fc1a6b2b84b98f599 diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index 452d024c14..83945a0907 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -2,25 +2,25 @@ [English](README.md) | 中文 -本参考定义 profile、web 别名、插件管理和配置 dump 命令模式。参数由 [`src/args.ts`](../src/args.ts) 统一解析,[`src/bin.ts`](../src/bin.ts) 只动态导入选中的运行器。 +本参考定义 profile 启动、web 别名、插件管理和配置 dump 等命令模式。argv 由 [`src/args.ts`](../src/args.ts) 统一解析一次,[`src/bin.ts`](../src/bin.ts) 只会动态导入选中的运行器。 ## Profile 启动 -`dsh --profile ` 启动位于 `$DSH_HOME/profiles/` 的 profile。生效配置树在空根节点之上按以下顺序逐层组合:profile manifest(元数据清单)的 `dsh.profile.bundles` 列表所列的各个组合包 patch、profile 自身的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml`(各 profile 共享的机器本地偏好,因此优先级高于逐 profile 的层)、以及按 argv 顺序的各个 `--patch ` overlay。后应用的层按行胜出;patch 替换目标行完整的 `config` 值,而不是深度合并各键,并且可以插入新行。配置解析、schema 校验、模块解析或插件启动失败会得到报告并以非零状态退出。收到 SIGINT 或 SIGTERM 时,挂载的根节点会先 dispose(资源释放)再退出。 +`dsh --profile ` 启动位于 `$DSH_HOME/profiles/` 的 profile。生效配置树以空根节点为起点,依次叠加 profile manifest(元数据清单)的 `dsh.profile.bundles` 列表中指定的各组合包 patch、profile 自身的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml`(这是各 profile 共享的机器本地偏好,因此优先于逐 profile 配置层),以及按 argv 顺序指定的各个 `--patch ` 覆盖层。对同一配置行,后应用的层优先。patch 会替换目标行的整个 `config` 值,而不是深度合并其中的键;patch 也可以插入新行。配置解析、schema 校验、模块解析或插件启动失败时,系统会报告错误并以非零状态退出。收到 SIGINT 或 SIGTERM 时,挂载的根节点会先 dispose(资源释放)再退出。 -组合包名称先从 dsh 安装解析,再从 profile 目录解析。因此内置组合包(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`)总是来自与正在运行的 `dsh` 相同的安装;树外组合包来自 profile 由 pnpm 管理的 `node_modules`。任何 patch 行中的裸插件 `name` 通过 profile 目录的 Node 父目录逐级查找解析,该查找可达到持续维护的安装后备目录 `$DSH_HOME/profiles/node_modules`(安装的应用和组合包所依赖的每个包对应一个符号链接,每次启动时修复)。 +组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`)始终来自当前运行的 `dsh` 所属的安装;另行安装的组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。该目录为 dsh 安装中的应用和组合包所依赖的每个包各维护一个符号链接,并在每次启动时修复这些链接。 `web` 和 `headless` profile 首次使用时会从随附模板自动初始化(`web`:base + web-app;`headless`:base + headless)。其他缺失的 profile 会显式报错,并提示运行 `dsh plugin --profile add `。 ### 应用参数 -启动器自己的 flag 写在最前面,并在它不认识的第一个 token 处结束;从那里开始的一切都通过 `ctx.cmdlineArgs` 原样交给启动起来的 profile,任何注入它的应用插件都可以解析([`dsh-cmdline`](../../../packages/boot/cmdline/README.md))。因此 `dsh --profile web --port 8080` 到达的是 web 应用的 `--port`,`dsh --profile web --help` 打印的是该应用的 help 且什么也不启动,而 `dsh --help`(没有可以交付的 profile)打印的是启动器自己的 help。`-V`/`--version` 写在应用参数边界之前时会打印启动器的版本。 +启动器自身的 flag 必须写在最前面,并在遇到第一个无法识别的 token 时结束;从该 token 开始的所有内容都会通过 `ctx.cmdlineArgs` 原样交给已启动的 profile,注入该 profile 的任意应用插件都可以解析这些内容([`dsh-cmdline`](../../../packages/boot/cmdline/README.md))。因此,`dsh --profile web --port 8080` 会将 `--port` 交给 web 应用;`dsh --profile web --help` 只打印该应用的帮助信息,不启动应用;`dsh --help` 没有可供交付参数的 profile,因此会打印启动器自身的帮助信息。`-V`/`--version` 位于应用参数边界之前时,会打印启动器的版本。 -一套组合只挂载一次。普通插件注入 `cmdlineArgs`、解析本应用参数,并把结果作为服务提供出去;由 flag 配置的每一行都会注入该服务,Loader 会等服务激活后再求值该行配置(`port: !!js ctx.webStartup.port ?? 3080`),因此 flag 胜过写在它旁边的值。该优先级要求配置行保留这一表达式;若用户 patch 用字面量替换整份 `config`,运行时读取也会随之消失。help 和被拒绝的参数会请求退出——拒绝时以非零状态,help 时以 0——且不会激活依赖提供方服务的行。在线编辑 `cordis.patch.yml` 会针对仍然在线的服务重新求值表达式,因此不会重置已在服务的端口。 +每套组合只会挂载一次。普通插件注入 `cmdlineArgs`,解析所属应用的参数,并将解析结果作为服务提供。每个从 flag 取值的配置行都会注入该服务;Loader 会等到服务激活后,再对该行的配置求值(`port: !!js ctx.webStartup.port ?? 3080`),因此 flag 的优先级高于配置行中写明的值。要维持这一优先级,配置行必须保留该表达式;如果用户 patch 用字面量替换整个 `config`,也会随之移除运行时读取。帮助参数和被拒绝的参数都会请求退出:参数被拒绝时以非零状态退出,显示帮助时以 0 退出;依赖该提供方服务的配置行不会激活。在线编辑 `cordis.patch.yml` 时,系统会根据仍在运行的服务重新计算表达式,因此不会重置当前正在使用的端口。 启动器的 flag 必须写在应用参数之前,且启动器的解析器会消耗掉一个 `--`:必须以字面量 `--` 送达应用的参数需要写成 `-- --`。如果应用的第一个参数恰好等于 `web` 或 `plugin`,会选择对应的子命令。`ctx.cmdlineArgs.get()` 是共享的不可变读取:多个插件可以解析同一份快照,没有读取方的 profile 则会忽略自己的应用参数。 -随附的各应用持有这些命令行: +随附的应用接受以下命令行参数: | Profile | 参数 | |---|---| @@ -36,11 +36,11 @@ dsh --profile web --dump-default-config dsh --profile web --patch ./extra.yml --dump-config ``` -`--dump-default-config` 只打印组合包各层;`--dump-config` 额外加上 profile 的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml` 和 `--patch` overlay。两者都会打印注释,标明每行由哪个文件提供,以及哪些 overlay 修改过它;`!!js` 表达式保持未求值,找不到目标的 patch 会报告到 stderr。dump 从不运行应用命令行提供方,因此它展示的是任何应用参数被解析之前的组合配置树,并拒绝携带应用参数的调用。 +`--dump-default-config` 只打印组合包各层;`--dump-config` 额外加上 profile 的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml` 和 `--patch` overlay。两者都会打印注释,标明每行由哪个文件提供,以及哪些 overlay 修改过它;`!!js` 表达式保持未求值,找不到目标的 patch 会报告到 stderr。dump 操作不会运行应用的命令行参数提供方,因此展示的是解析任何应用参数之前的组合配置树;如果调用中包含应用参数,dump 会拒绝该调用。 ## 插件管理 -`dsh plugin --profile ` 在 profile 缺失时先初始化它(有随附模板的用模板,其他名称只装 `@deepseek-ai/dsh-base`),然后以 profile 目录为工作目录,把 `` 转发给 `pnpm`:`add`、`remove`、`why`、`update` 及其他所有 pnpm 子命令都照常可用;pnpm 必须在 PATH 上。相对路径 spec(`.`、`../plugin` 及其 `file:`/`link:` 形式)会先锚定到调用目录,因此在插件 checkout 中执行 `add .` 安装的是该 checkout,而不是 profile。每次成功运行后,`dsh.profile.bundles` 都会与已安装状态对齐:每个解析到 manifest 中声明了 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 的包的依赖加入层栈(因此让包获得该声明的 `update` 会将其激活),没有组合包声明的依赖保持为普通依赖并给出一次性警告,已移除的依赖则退出层栈。 +`dsh plugin --profile ` 在 profile 缺失时先初始化它(有随附模板的用模板,其他名称只装 `@deepseek-ai/dsh-base`),然后以 profile 目录为工作目录,把 `` 转发给 `pnpm`:`add`、`remove`、`why`、`update` 及其他所有 pnpm 子命令都照常可用;pnpm 必须在 PATH 上。相对路径 spec(`.`、`../plugin` 及其 `file:`/`link:` 形式)会先锚定到调用目录,因此在插件 checkout 中执行 `add .` 安装的是该 checkout,而不是 profile。每次成功运行后,系统都会根据当前安装状态更新 `dsh.profile.bundles`:如果某项依赖解析到的包在 manifest(元数据清单)中声明了 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`,该依赖就会加入配置层栈;如果某项依赖在 `update` 后获得该声明,也会随即激活。没有组合包声明的依赖仍作为普通依赖保留,并显示一次性警告;已移除的依赖则从配置层栈中删除。 ```sh dsh plugin --profile tui add github:deepseek-harness/turtle-ui @@ -48,7 +48,7 @@ dsh plugin --profile tui remove turtle-ui dsh --profile tui ``` -Git 托管、随附源码的插件在安装期间通过其 `prepare` 脚本构建,而 pnpm ≥10 在消费方允许之前会阻止该脚本:首次 `add` 会失败并给出 pnpm 的 `allowBuilds` 提示(以及 dsh 指向该 profile 的 `pnpm-workspace.yaml` 的指引);把打印出的键复制到那里并重新运行即可。安装已构建的 tarball 或本地 checkout 不需要任何允许。 +随源码发布的 Git 托管插件会在安装期间通过 `prepare` 脚本构建,而 pnpm ≥10 默认会阻止该脚本,直到使用方明确允许。首次运行 `add` 会失败,并显示 pnpm 的 `allowBuilds` 提示;dsh 还会提示应修改该 profile 的 `pnpm-workspace.yaml`。将输出的键复制到该文件后,重新运行命令即可。安装已经构建好的 tarball 或本地 checkout 时,无需加入 `allowBuilds`。 ## Web 别名 @@ -63,9 +63,9 @@ dsh web --help 生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`。绑定所有网络接口时,还会信任机器自动发现的 LAN IP 字面量;`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。 -进程关闭时会给插件树最多 5 秒完成 dispose。第一次 `SIGINT`/`SIGTERM` 启动该优雅排空——`SIGTERM` 是监督进程的普通停止请求,在所有 surface 上以 0 退出,`SIGINT` 报告 130;第二次信号强制立即退出。如果一次性运行正常结束时已经卡在 dispose 中,第一次 `Ctrl+C` 就会升格并立即退出,而不会被吞掉。 +进程关闭时,插件树最多有 5 秒完成 dispose。首次收到 `SIGINT` 或 `SIGTERM` 时会开始优雅排空:`SIGTERM` 是监督进程发出的常规停止请求,在所有运行模式下都以 0 退出;`SIGINT` 则报告 130。第二次收到信号时会立即强制退出。如果一次性运行在正常结束时已经卡在 dispose 阶段,第一次按下 `Ctrl+C` 就会直接升级为强制退出,而不会被忽略。 -所有模式都将调用目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。每次 profile 启动都监视两个 `cordis.patch.yml` 层(profile 与 home)的有效编辑并以事务方式重新应用;一次性 surface 经由有界关闭退出,关闭会先 dispose 监视器。 +所有模式都将运行命令时所在的目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。每次启动 profile 时,系统都会监视 profile 与 home 两个 `cordis.patch.yml` 配置层的有效变更,并以事务方式重新应用;一次性运行模式通过有界关闭流程退出,该流程会先 dispose 监视器。 新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取、网络访问和进程可见性不受限制。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。 @@ -75,8 +75,7 @@ dsh web --help 基础组合包挂载原生 DeepSeek 适配器、settings 与凭据提供方、稳定的 `web_search` 和已禁用的会话遥测。提供方凭据依次从继承环境、`$DSH_HOME/.credentials.yaml`、调用目录的 `.env` 和 `$DSH_HOME/.env` 解析;受管文档从不物化进 `process.env`,而两个 `.env` 文件都是普通启动环境层。搜索使用 `DEEPSEEK_API_KEY` 并接受 `DEEPSEEK_SEARCH_BASE_URL`;只有 patch 层插入提供方并启用 `web_fetch` 后,该工具才可用。 -会话遥测默认留在本地。`DSH_TELEMETRY_MODE=FULL` 将每条已投影会话事件作为 OTLP/HTTP 日志流式发送,`DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 则仅在记录反馈时上传会话日志后缀。`DSH_TELEMETRY_OTLP_URL` 选择其他 collector,任何非空 `DSH_TELEMETRY_DISABLED` 仍是具有最高优先级的硬性退出开关。随附基础配置没有遥测脱敏规则,因此显式启用的导出可能包含消息文本、工具参数与结果以及 workspace 路径;该部署决策由[默认关闭 Agent Note](../../../.agents/notes/implemented/feature/2026-08-10-telemetry-default-off.md)负责。 - +会话遥测默认留在本地。`DSH_TELEMETRY_MODE=FULL` 将每条已投影会话事件作为 OTLP/HTTP 日志流式发送,`DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 则仅在记录反馈时上传会话日志后缀。`DSH_TELEMETRY_OTLP_URL` 选择其他 collector。任何非空的 `DSH_TELEMETRY_DISABLED` 都是具有最终效力的遥测强制关闭开关。随附基础配置没有遥测脱敏规则,因此显式启用的导出可能包含消息文本、工具参数和结果,以及 workspace 路径;相关部署决策见[默认关闭 Agent Note](../../../.agents/notes/implemented/feature/2026-08-10-telemetry-default-off.md)。 通过 `dsh plugin --profile add ` 安装外部插件组合包。安装的包拥有其依赖,并贡献其声明的 `cordis.patch.yml` 层。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。 diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index 7011884239..9cda431253 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/tools/README.md README.md: 44eb25b79436a75f08406102fc1e3734e59b1001 -README.zh.md: a47e7c54b0cd3fc3c5146a9a8be4d1406f9ef8aa +README.zh.md: d77b4ee5248b19418c27674faf7afd52957c6382 diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index a47e7c54b0..d77b4ee524 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -工具注册表与执行流水线。工具插件注册各自的 schema 和执行器;agent loop(智能体循环)依次让每次调用经过 `tools/pre-execute`(可扩展的允许/拒绝门禁)→ 已注册的单调守卫 → `tools/execute`(供超时/重试/指标插件使用的环绕分发包装层)→ `tools/post-execute`(检查/替换结果、附加上下文)→ 定义自身的 `finalizeContent` 终结步骤 → 仅观测的 `tools/result` 通知。注册表还负责决定如何向模型呈现其工具:`mode` 配置可以选择原生 Function Calling(函数调用)、[Code Mode](#code-mode),或同时选择两者;单个 agent 可用 `presentAs` 为自己遮蔽该默认值。 +工具注册表与执行流水线。工具插件注册各自的 schema 和执行器;agent loop(智能体循环)依次让每次调用经过 `tools/pre-execute`(可扩展的允许/拒绝门禁)→ 已注册的单调守卫 → `tools/execute`(供超时/重试/指标插件使用的环绕分发包装层)→ `tools/post-execute`(检查/替换结果、附加上下文)→ 由工具定义持有的 `finalizeContent` 边界 → 仅观测的 `tools/result` 通知。注册表还决定以何种方式向模型呈现工具:`mode` 配置可以选择原生 Function Calling(函数调用)、[Code Mode](#code-mode),或同时选择两者;单个 agent 可用 `presentAs` 为自己遮蔽该默认值。 ## 服务:`ToolRegistry`(ctx 键:`tools`) @@ -13,17 +13,17 @@ tools: mode: native # native (default) | code | both ``` -`native` 以函数定义的形式贡献可见工具。`code` 贡献保留的 `run_code` 传输、生成的 `tools:sdk` 段,以及声明「只有 `run_code` 可被直接调用」的 `tools:code-only` 规则——执行器随后强制该规则,模型直呼其他任何工具名都会在策略运行之前解析为 `UNKNOWN_TOOL`;`both` 同时贡献两种形式,且不声明该规则,因为它的原生调用确实会执行。这是「未作声明的 agent」的默认值——agent preset 用 [`dsh-agent-tool-mode`](../agent-tool-mode/README.md) 为自己选择。不能注册、遮蔽、限制或移除该保留传输,且无论配置何种模式,该名称都是保留的,因为任何 agent 都可能选择 code 模式。非原生模式要求所加载 `ctx.codeRuntime` 的 `language` 有已注册的 SDK 渲染器——TypeScript 经 [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md) 交付;Python 渲染器内置,驱动任何报告 `language: 'python'` 的运行时(第一方 `dsh-code-runtime-python` 后端另行交付)。没有渲染器的运行时语言会导致提示词组装明确失败;如果 `systemPrompt.toolOrder` 条目指向当前模式未贡献的工具,系统会拒绝组装提示词。`system-prompt/assemble` 监听器可以替换注册表贡献;它返回的组装结果具有权威性,因此该监听器负责保留可用的 Code Mode 协议。 +`native` 以函数定义的形式贡献可见工具。`code` 会提供预留的 `run_code` 传输、生成的 `tools:sdk` 段,以及声明「只有 `run_code` 可被直接调用」的 `tools:code-only` 规则。执行器随后强制执行该规则:模型直接调用其他任何工具时,会在策略运行前将该调用解析为 `UNKNOWN_TOOL`;`both` 同时提供两种形式,且不声明该规则,因为其中的原生调用确实可以执行。没有单独声明呈现模式的 agent 默认采用此配置;agent preset 可通过 [`dsh-agent-tool-mode`](../agent-tool-mode/README.md) 自行选择呈现模式。不能注册、遮蔽、限制或移除该保留传输,且无论配置何种模式,该名称都是保留的,因为任何 agent 都可能选择 code 模式。非原生模式要求所加载 `ctx.codeRuntime` 的 `language` 有已注册的 SDK 渲染器——TypeScript 经 [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md) 交付;Python 渲染器内置,驱动任何报告 `language: 'python'` 的运行时(第一方 `dsh-code-runtime-python` 后端另行交付)。没有渲染器的运行时语言会导致提示词组装明确失败;如果 `systemPrompt.toolOrder` 条目指向当前模式未贡献的工具,系统会拒绝组装提示词。`system-prompt/assemble` 监听器可以替换注册表贡献;它返回的组装结果具有权威性,因此该监听器负责保留可用的 Code Mode 协议。 ### 公开 API -- `ctx.tools.register(definition: ToolDefinition): () => void`:注册一个受信任、带类型的同进程定义,其中必须包含规范的 `output` 声明。所在层由调用上下文的作用域决定:普通插件上下文会全局注册;agent 的 `agent.ctx` 只为该 agent 注册,并在此处遮蔽同名全局工具。同一层内名称重复会抛出;非原生模式还会拒绝保留的 `run_code` 传输名称。缺失或不受支持的输出声明,以及非正数或非有限的 `timeoutMs`,都会使注册失败。可选的同步 `finalizeContent` 回调会在调用开始时创建快照;在所有流水线结果(包括实体化其他结果字段时发现的错误)规范化之后,它只能替换最终面向模型的内容。随调用 fiber dispose(资源释放)。 -- `ctx.tools.presentAs(mode: ToolPresentationMode): () => void`:为本 agent 选择面向模型的呈现方式,仅对该 agent 遮蔽 `mode` 配置;从普通上下文调用会抛出(进程级呈现方式是那个配置字段),同一 scope 内第二次声明也会抛出。code 类模式还会为该 agent 注册它自己的 `tools:sdk` 段。清单本身不变——`schemas(agent)` 报告的仍是该 agent 的能力,仅组装结果中的工具会被折叠。随调用方 fiber dispose。 +- `ctx.tools.register(definition: ToolDefinition): () => void`:注册一个受信任、带类型的同进程定义,其中必须包含规范的 `output` 声明。所在层由调用上下文的作用域决定:普通插件上下文会全局注册;agent 的 `agent.ctx` 只为该 agent 注册,并在此处遮蔽同名全局工具。同一层内名称重复会抛出;非原生模式还会拒绝保留的 `run_code` 传输名称。缺失或不受支持的输出声明,以及非正数或非有限的 `timeoutMs`,都会使注册失败。可选的同步 `finalizeContent` 回调会在调用开始时纳入快照;在所有流水线结果(包括实体化其他结果字段时发现的错误)规范化之后,它只能替换最终面向模型的内容。该注册会随调用方 fiber 一同 dispose(资源释放)。 +- `ctx.tools.presentAs(mode: ToolPresentationMode): () => void`:为本 agent 选择面向模型的呈现方式,仅对该 agent 遮蔽 `mode` 配置;从普通上下文调用会抛出(进程级呈现方式是那个配置字段),同一 scope 内第二次声明也会抛出。code 类模式还会为该 agent 注册它自己的 `tools:sdk` 段。工具目录保持不变:`schemas(agent)` 仍会报告该 agent 的能力;只有组装结果中的工具列表会按所选呈现方式收束。随调用方 fiber dispose。 - `ctx.tools.restrict(filter)`:对全局工具应用 agent 作用域的允许/拒绝掩码;从普通上下文调用会抛出。筛选器在注册时创建快照;多个掩码取交集,随后再合并作用域本地工具。拒绝掩码会接纳后来出现且未点名的全局工具,而允许掩码会排除后来出现的名称。未知、本地或保留名称以及空筛选器都会被拒绝。这是实时可见性组合,不是权限边界;参见[作用域安全非目标](../../../.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-non-goals)。 -- `ctx.tools.get(name: string, scope?: ScopeKey): ToolDefinition | undefined`:按某个作用域所见的结果解析(应用遮蔽;被限制掉的全局工具视为不存在)。呈现器会传入发起调用的 agent,使卡片与实际执行内容一致。 +- `ctx.tools.get(name: string, scope?: ScopeKey): ToolDefinition | undefined`:返回指定作用域可见的解析结果,其中已应用名称遮蔽;被作用域限制排除的全局工具会被视为不存在。呈现器会传入发起调用的 agent,使卡片与实际执行内容一致。 - `ctx.tools.schemas(scope?: ScopeKey): ToolSchema[]`:返回该作用域可见的所有 schema(不含 `execute` 函数)。已交付工具的 schema 收录在 [docs/tool-catalog.md](../../../docs/tool-catalog.md) 中;该目录通过启动每个工具插件并采集此方法的结果生成(参见[工具 schema 目录 Agent Note](../../../.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md))。 - `ctx.tools.guard(guard: ToolGuard): () => void`:在 `tools/pre-execute` 之后注册单调同步执行守卫:返回理由会拒绝调用,返回 `undefined` 则保持原决定。普通上下文守卫全局生效;`agent.ctx` 守卫只对该 agent 生效。后续 waterfall(瀑布式事件)监听器无法将守卫的拒绝重新变为允许。随调用 fiber dispose。 -- `ctx.tools.execute(exec)`:以无损方式快照并冻结参数,分配不透明 token,运行完整的策略/分发/结果流水线,然后在最终观测前独立快照权威结果。无效参数会进入同一结果路径,但不会到达策略或工具主体。环绕包装层只能替换 `signal`;注册表会在调用主体前立即重新融合调用方的原始信号。 +- `ctx.tools.execute(exec)`:以无损方式快照并冻结参数,分配不透明 token,运行完整的策略/分发/结果流水线,然后在最终观测前独立快照权威结果。无效参数会进入同一结果路径,但不会到达策略或工具主体。环绕包装层只能替换 `signal`;注册表会在进入工具主体之前,立即将调用方的原始信号重新合并到当前信号中。 - `ctx.tools.executionMode(exec)`:返回 `parallel` 的唯一条件是可见定义的 `isConcurrencySafe(exec.arguments)` 分类器恰好返回 `true`;未知、隐藏、未声明、无效或抛出异常的分类结果均为独占。 ### 注入的服务 @@ -32,11 +32,11 @@ tools: ### 取消 -取消采用协作方式,并等待完全停稳。每次类型化调用都提供由调用方拥有的 `AbortSignal`;工具主体通过必填的只读 `exec.signal` 接收它,只有 `tools/execute` 包装层可以临时替换这个必填信号。注册表会在替换期间保留调用方取消,并且绝不会在已启动的同进程 Promise 尚未结算时提前返回。工具主体调用前发生的取消为 `ABORTED_BEFORE_DISPATCH`;调用主体后的取消只能把成功结果替换为 `ABORTED`。拒绝、包装层失败、工具失败、后置策略失败或由超时机制产生的 `TOOL_TIMEOUT` 仍保留更具体的结果。入口处已中止的调用会实体化并冻结参数,随后跳过所有策略和分发阶段,只发布一个结果。每个异步工具都必须观测或转发该信号,并且只能在自身拥有的工作停止后结算。[工具取消 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md) 规定完整约定和强制终止边界。 +取消采用协作方式,并等待完全停稳。每次类型化调用都提供由调用方拥有的 `AbortSignal`;工具主体通过必填的只读 `exec.signal` 接收它,只有 `tools/execute` 包装层可以临时替换这个必填信号。注册表会在替换期间保留调用方取消,并且绝不会在已启动的同进程 Promise 尚未结算时提前返回。工具主体调用前发生的取消为 `ABORTED_BEFORE_DISPATCH`;工具主体被调用后发生的取消,只能将成功结果替换为 `ABORTED`。拒绝、包装层失败、工具失败、后置策略失败或由超时机制产生的 `TOOL_TIMEOUT` 仍保留更具体的结果。入口处已中止的调用会实体化并冻结参数,随后跳过所有策略和分发阶段,只发布一个结果。每个异步工具都必须观测或转发该信号,并且只能在其负责的工作停止后结算。[工具取消 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md) 规定完整约定和强制终止边界。 ### 实时事件 -实时注册表流水线先经过 3 个可变换的 waterfall,再经过由定义拥有的内容终结器,最后发布仅供观测的 `tools/result` 事件;注册表变更有意作为不过滤的共享状态通知。确切签名、分发 mode、作用域筛选和失败隔离约定位于 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块,完整顺序则在生成的[工具执行流水线](../../../docs/tool-execution-pipeline.md)中可视化。`tools/result` 是实时事件;名称相近的 `tool/result` 是 agent loop 随后追加的持久会话事件。 +实时注册表流水线先经过 3 道可转换的 waterfall,再经过由工具定义持有的内容终结器,最后发布仅供观测的 `tools/result` 事件;注册表变更通知有意不作过滤,并作为共享状态通知发布。确切签名、分发 mode、作用域筛选和失败隔离约定位于 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块,完整顺序则在生成的[工具执行流水线](../../../docs/tool-execution-pipeline.md)中可视化。`tools/result` 是实时事件;名称相近的 `tool/result` 是 agent loop 随后追加的持久会话事件。 ### 关键类型 @@ -102,16 +102,16 @@ ctx.tools.register(defineTool({ ### 强制执行的原始 JSON Schema 子集 -`JsonSchemaNode` 是工具输出、Code Mode 生成、subagent 和工作流共享的原始对应类型。它允许任意 JSON 根、一个仅含 annotation 的无约束 JSON 节点,以及恰好匹配一个分支的 `oneOf`;annotation 必须保持为无损 JSON。`assertSupportedJsonSchema()` 拒绝不受支持的构造,而 `validateJsonSchemaValue()` 返回带路径的违规信息。subagent 和工作流通过 `assertObjectJsonSchema()` 与 `ObjectJsonSchema` 保留调用方定义的对象根要求,而不是依赖共享词汇的限制。 +`JsonSchemaNode` 是工具输出、Code Mode 生成、subagent 和工作流共享的原始 JSON Schema 对应类型。它允许任意 JSON 根、仅含注解且不施加约束的 JSON 节点,以及恰好匹配一个分支的 `oneOf`;注解必须保持为无损 JSON。`assertSupportedJsonSchema()` 拒绝不受支持的构造,而 `validateJsonSchemaValue()` 返回带路径的违规信息。subagent 和工作流通过 `assertObjectJsonSchema()` 与 `ObjectJsonSchema` 保留调用方定义的对象根要求,而不是依赖共享词汇的限制。 -### 工具拥有的 UI 呈现 +### 由工具定义的 UI 呈现 -工具可以选择拥有纯 `presentCall()` 和 `presentResult()` 呈现意图,使 UI 无需特殊处理工具名称: +工具可以选择通过纯函数 `presentCall()` 和 `presentResult()` 定义呈现意图,使 UI 无需针对工具名称编写特殊逻辑: - 调用视图为 `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`、`{ card: 'terminal', title, description?, cwd? }` 或 `{ card: 'diff', title, diffs, locations? }`。 -- 结果视图为 `{ card: 'generic', title?, content? }`、`{ card: 'terminal', title?, output?, exitCode?, signal? }`、`{ card: 'diff', title?, diffs }`、`{ card: 'search', shape, title?, truncated, total, … }`(已完成的发现型搜索——`shape: 'matches'`(grep)为按文件分组的匹配,`shape: 'paths'`(glob)为扁平路径列表,配 `truncated`/`total` 使 UI 永不把被截断的结果当作完整结果呈现;该视图不携带结果文本,且搜索没有 `card: 'search'` 的调用时对应视图)、`{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }`(已完成的文件读取→带行号、可选语法高亮的代码视图;`offset` 是窗口请求的 1-based 起始行,即使 `lines` 为空也保留;`lines` 是 `{ number, text }[]`,保留每一行的文件行号,`content` 是无读取能力的 UI 回退时使用的去信封文本)或 `{ card: 'web', kind: 'search' | 'fetch', title?, … }`(已完成的 web 检索;`kind` 各分支携带结构化的搜索来源或抓取摘要,不具备 `web` 能力的 UI 回退到原始结果内容)。 +- 结果视图为 `{ card: 'generic', title?, content? }`、`{ card: 'terminal', title?, output?, exitCode?, signal? }`、`{ card: 'diff', title?, diffs }`、`{ card: 'search', shape, title?, truncated, total, … }`(已完成的发现型搜索——`shape: 'matches'`(grep)为按文件分组的匹配,`shape: 'paths'`(glob)为扁平路径列表,配 `truncated`/`total` 使 UI 永不把被截断的结果当作完整结果呈现;该视图不携带结果文本,且搜索没有 `card: 'search'` 的调用时对应视图)、`{ card: 'read', title?, path, offset, lines, totalLines, lang?, content? }`(已完成的文件读取→带行号、可选语法高亮的代码视图;`offset` 是窗口请求的 1-based 起始行,即使 `lines` 为空也保留;`lines` 是 `{ number, text }[]`,保留每一行的文件行号,`content` 是去除读取结果外层封装后的正文,供不支持读取视图的 UI 回退显示)或 `{ card: 'web', kind: 'search' | 'fetch', title?, … }`(已完成的 web 检索;`kind` 各分支携带结构化的搜索来源或抓取摘要,不具备 `web` 能力的 UI 回退到原始结果内容)。 -返回 `undefined` 会选择通用回退。呈现器只依赖其参数和持久结果,因为 UI 会在实时流式输出和日志回放期间调用它们。`output.presentationMeta(args, value)` 为直接接口调用派生 JSON 元数据;该元数据随 `tool/result` 持久化并传回 `presentResult`,而规范值本身仍只存在于执行局部,绝不会回放。嵌套 Code 分发不会计算元数据。`defineTool` 会软验证较旧的日志参数并回退,而不会使回放崩溃。`dsh-tool-bash` 与 `dsh-tool-fs` 是参考实现;[规范输出 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md) 规定值/呈现拆分,[呈现意图 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md) 规定卡片词汇。 +返回 `undefined` 会选择通用回退。呈现器只依赖其参数和持久结果,因为 UI 会在实时流式输出和日志回放期间调用它们。`output.presentationMeta(args, value)` 为直接的顶层调用派生 JSON 元数据;该元数据随 `tool/result` 持久化并传回 `presentResult`,而规范值本身仍只存在于执行局部,绝不会回放。嵌套 Code 分发不会计算元数据。`defineTool` 会软验证较旧的日志参数并回退,而不会使回放崩溃。`dsh-tool-bash` 与 `dsh-tool-fs` 是参考实现;[规范输出 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md) 规定值/呈现拆分,[呈现意图 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md) 规定卡片词汇。 ### Code Mode @@ -119,10 +119,10 @@ ctx.tools.register(defineTool({ 在 `code`(而非 `both`)下,该传输同时也是模型唯一可用的入口:模型直呼其他任何可见工具名,都会在创建执行时、早于 `tools/pre-execute`、审批 `ask` 和 guards 解析为 `UNKNOWN_TOOL`,因此没有任何一方会观察或批准一个注定失败的调用。拒绝信息会给出正确路径(`only \`run_code\` is callable directly — call \`\` from inside a \`run_code\` program instead`),因为同一份提示词刚刚声明过那个工具,只说 `unknown tool` 会被读成部署损坏。SDK 子分发携带外层执行的 `parent` token,不受此限制,因此程序保留 SDK 声明的全部绑定。参见[执行器塌缩 note](../../../.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md)、[Code Mode 基础](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)、[类型化返回约定](../../../.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md)和[代码运行时 seam](../../code-runtime/README.md)。可以运行 `pnpm run demo:code-mode` 试用。 -- **SDK 段**(`tools:sdk`,顺序 150):一个惰性提示词段,每次组装时都会重新生成与所加载运行时语言相符的 SDK 文本。TypeScript 形态发出 `JsonValue`、精确的 `ToolArgsMap` / `ToolOutputMap`、`ToolName`、`ToolCallError` 声明、面向调用作用域可见最终能力的映射 `tools` 命名空间(特殊名称使用带引号的键),以及固定用法说明;Python 形态(`ctx.codeRuntime.language === 'python'`)发出等价的具名 `TypedDict` 与一个带相同用法说明的 `tools` 对象。其输出具有确定性:工具按字典序排列;工具集合不变时,文本逐字节相同(有利于前缀 cache)。两个代码生成器都已导出,且绝不会在提示词组装期间抛出:`jsonSchemaToTs` 处理统一 schema 的每种构造并将不受支持的原始构造降级为 `unknown`;`jsonSchemaToPy` 同理,降级为 `Any`(当某字段名不是合法的 `TypedDict` 属性时,或在 SDK 渲染之外被调用时——`TypedDict` 声明所需的命名上下文由该渲染提供——整个对象降级为 `dict[str, Any]`)。 -- **分发桥接层**(`run_code` 的 execute):每个绑定调用都会在分发前快照为无损 JSON(`undefined`、`BigInt`、循环、稀疏数组、`-0` 和特殊对象会使该次调用被拒绝),经由每次运行独有、复用原生并发约定的池调度——调用严格按提交顺序启动,连续的 `isConcurrencySafe` 调用最多可重叠经校验的 `maxParallelSubCalls` 配置个(默认 10;设为 `1` 即恢复串行分发),被分类为独占的调用先排空池、单独运行并阻挡其后的调用——以外层执行的不透明 token 作为 `parent`,并经过完整的 pre-execute → guards → execute → post-execute → result 流水线。成功会返回策略处理后的最终规范值;失败以一条消息到达 worker,并成为 `ToolCallError(toolName, message)`。每个已启动的子调用在进入流水线时记录一条 `tool/code-dispatch-start` 事件(确定性 id `:code:`,按提交顺序编号),并以一条携带完整模型可见 `content`/`isError` 结果的 `tool/code-dispatch` 事件完结(采用 `tool/result` 词汇,因此 UI 会沿原生路径呈现子调用——这对事件的 `time` 字段承载每个子调用的计时);因 run 结算而被放弃的排队调用两者都不记录。`deriveMessages()` 既不公开这两个事件,也不持久化规范值。token 关联让以提交为语义的观察器能够把内部成功延迟到最终 `run_code` 结果,而无需公开实时外层执行;普通工具副作用不会回滚。每个子调用的 `additionalContexts` 条目都会按分发顺序通过外层 `ToolRunContext` 延迟;循环只在父级 `run_code` 结果之后追加这些上下文,从而保持相邻关系,并且即使程序后来失败,也会保留各自的来源/元数据。 +- **SDK 段**(`tools:sdk`,顺序 150):一个在组装时求值的提示词段,每次组装都会重新生成与所加载运行时语言相符的 SDK 文本。TypeScript 形态会生成 `JsonValue`、精确的 `ToolArgsMap` / `ToolOutputMap`、`ToolName`、`ToolCallError` 声明,以及映射调用作用域最终可见工具的 `tools` 命名空间(特殊名称使用带引号的键),并附带固定的使用说明;Python 形态(`ctx.codeRuntime.language === 'python'`)发出等价的具名 `TypedDict` 与一个带相同用法说明的 `tools` 对象。其输出具有确定性:工具按字典序排列;工具集合不变时,文本逐字节相同(有利于前缀 cache)。两个代码生成器都已导出,且绝不会在提示词组装期间抛出:`jsonSchemaToTs` 处理统一 schema 的每种构造并将不受支持的原始构造降级为 `unknown`;`jsonSchemaToPy` 同理,降级为 `Any`(当某字段名不是合法的 `TypedDict` 属性时,或在 SDK 渲染之外被调用时——`TypedDict` 声明所需的命名上下文由该渲染提供——整个对象降级为 `dict[str, Any]`)。 +- **分发桥接层**(`run_code` 的 execute):每个绑定调用都会在分发前快照为无损 JSON(`undefined`、`BigInt`、循环、稀疏数组、`-0` 和特殊对象会使该次调用被拒绝),经由每次运行独有、复用原生并发约定的池调度——调用严格按提交顺序启动,连续的 `isConcurrencySafe` 调用最多可重叠经校验的 `maxParallelSubCalls` 配置个(默认 10;设为 `1` 即恢复串行分发),被分类为独占的调用先排空池、单独运行并阻挡其后的调用——以外层执行的不透明 token 作为 `parent`,并经过完整的 pre-execute → guards → execute → post-execute → result 流水线。成功会返回策略处理后的最终规范值;失败以一条消息到达 worker,并成为 `ToolCallError(toolName, message)`。每个已启动的子调用在进入流水线时记录一条 `tool/code-dispatch-start` 事件(确定性 id `:code:`,按提交顺序编号),并以一条携带完整模型可见 `content`/`isError` 结果的 `tool/code-dispatch` 事件完结(采用 `tool/result` 词汇,因此 UI 会沿原生路径呈现子调用——这对事件的 `time` 字段承载每个子调用的计时);因 run 结算而被放弃的排队调用两者都不记录。`deriveMessages()` 既不公开这两个事件,也不持久化规范值。token 关联使按提交语义工作的观察器可以延后提交内部调用的成功结果,直到最终 `run_code` 结果确定,而无需接收实时可变的外层执行对象;普通工具副作用不会回滚。每个子调用的 `additionalContexts` 条目都会按分发顺序通过外层 `ToolRunContext` 延迟;循环只在父级 `run_code` 结果之后追加这些上下文,从而保持相邻关系,并且即使程序后来失败,也会保留各自的来源/元数据。 - **结算纪律**:桥接层拥有一个运行作用域的中止机制;该中止会跟随传入的外层信号,并在运行因任何原因结算时触发,因此预算耗尽会中止正在运行的子工具,而不会将其遗留。桥接层随后会在返回之前排空队列,使每个 `tool/code-dispatch` 都落在仍打开的轮次内。失败的运行会抛出 `CodeRunFailedError`(`code: 'CODE_RUN_FAILED'`,message = 失败类型 + 已捕获日志),流水线会将其转换为模型可据以自我修正的结构化 `isError`。 -- **结果大小**:中间绑定值会完整传入 worker 进程,且没有逐绑定字节上限。`run_code` 返回规范的 `{ logs: string[], result?: JsonValue }`;字符串原样呈现,其他所有存在的 JSON 根都通过栈安全的美化 JSON 遍历呈现,总缩进最多为 10 个字符(更深的子树保持紧凑),`null` 保持显式,而缺少 `result` 表示程序返回 `undefined`。worker 可配置的 `maxOutputBytes`(默认 64 MiB)只应用于组合序列化后的外层日志数组、完成值或失败消息载荷;固定的结果 envelope 语法和呈现空白不计入该上限。无效和超限的完成会明确失败,只有此外层结果可以使用普通 spill。 +- **结果大小**:中间绑定值会完整传入 worker 进程,且没有逐绑定字节上限。`run_code` 返回规范的 `{ logs: string[], result?: JsonValue }`;字符串原样呈现,其他所有存在的 JSON 根都通过栈安全的美化 JSON 遍历呈现,总缩进最多为 10 个字符(更深的子树保持紧凑),`null` 保持显式,而缺少 `result` 表示程序返回 `undefined`。worker 可配置的 `maxOutputBytes`(默认 64 MiB)只应用于组合序列化后的外层日志数组、完成值或失败消息载荷;固定的结果封装语法和呈现空白不计入该上限。无效和超限的完成会明确失败,只有这个外层结果可以按常规 spill 机制处理。 ### 并行执行 @@ -177,7 +177,7 @@ The available tools: #### 模型看到的内容 -循环会保留模型发出的参数和注册表的最终内容。任何抛出或被拒绝的调用都会恰好变为 `Error: `。Code Mode 只返回外层程序打印的行和呈现后的返回值;两者都为空时返回 `(run_code completed with no output)`;失败时返回 `Error: code run failed (): `,并根据是否存在已捕获内容,在其后附加 `Captured output:` 与捕获的行。内部分发事件只保留在日志中;后置执行监听器可以在结果之后追加带来源归属的上下文。 +循环会保留模型发出的参数和注册表的最终内容。任何抛出异常或遭到拒绝的调用,都会转换为确切的 `Error: `。Code Mode 只返回外层程序打印的行和呈现后的返回值;两者都为空时返回 `(run_code completed with no output)`;失败时返回 `Error: code run failed (): `,并根据是否存在已捕获内容,在其后附加 `Captured output:` 与捕获的行。内部分发事件只保留在日志中;后置执行监听器可以在结果之后追加带来源归属的上下文。 #### Token 影响 @@ -192,7 +192,7 @@ The available tools: - **并发策略不是事件门禁**:`executionMode()` 直接读取已解析的工具定义;插件只能在自身拥有的定义上声明分类器。 - **`tools/pre-execute` 有意不允许改写 `exec.arguments`**:否则日志记录和呈现的参数会与实际运行内容失去同步;改写设计记录在[拟议的 Agent Note](../../../.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)中。 - **调用方定义的 subagent 与工作流结构化输出仍要求对象根**:这是消费方层面的守卫;共享 schema 词汇和工具输出支持任意 JSON 根。 -- **定义上的 `timeoutMs` 仅为声明**:注册表绝不会强制执行截止时间;要强制执行,必须使用 `@deepseek-ai/dsh-timeout-policy` 包装层。 -- **Code Mode 的 SDK 语言跟随已加载的那个运行时,且呈现方式按 agent 而非按工具**:`mode: code`/`both` 会拒绝组装提示词,除非 `ctx.codeRuntime.language` 有已注册的 SDK 渲染器(TypeScript 或 Python);作用域限制/遮蔽与 `presentAs` 会选择每个 agent 的可见绑定及其形态,但在同一个 agent 内不能让一个工具仅使用 Native,而另一个仅使用 Code。 +- **定义中的 `timeoutMs` 仅作声明之用**:注册表绝不会强制执行截止时间;要强制执行,必须使用 `@deepseek-ai/dsh-timeout-policy` 包装层。 +- **Code Mode 的 SDK 语言由当前加载的运行时决定,且呈现方式按 agent 而非按工具**:`mode: code`/`both` 会拒绝组装提示词,除非 `ctx.codeRuntime.language` 有已注册的 SDK 渲染器(TypeScript 或 Python);作用域限制/遮蔽与 `presentAs` 会选择每个 agent 的可见绑定及其形态,但在同一个 agent 内不能让一个工具仅使用 Native,而另一个仅使用 Code。 - **Code Mode 中间值只存在于执行局部,且没有字节上限**:这些规范的类型化值无法从会话回放重建,并可能耗尽进程或 worker 内存;只有外层 `run_code` 输出受 worker 可配置的硬上限约束。每个子调用的持久日志副本则确实有上限:`tools/code-dispatch-log` waterfall 允许 spill 策略把过大的 `tool/code-dispatch` 内容替换为预览加定位符([原理](../../../.agents/notes/implemented/feature/2026-07-26-code-dispatch-log-spill.md))。 - **每次运行都会获得全新的 `run_code` 状态**:MVP 不采用持久 REPL 风格内核(跨调用状态不会出现在日志中);参见 [Code Mode Agent Note](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)。 diff --git a/python/sdk/README.i18n.yaml b/python/sdk/README.i18n.yaml index 42aa01ec6e..fa73fe380f 100644 --- a/python/sdk/README.i18n.yaml +++ b/python/sdk/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write python/sdk/README.md README.md: 70b9d6391644d10ee7d5c29ce122632786e3bbcc -README.zh.md: 1d1a23576cc8029dacbb2df0e3d1d9fc2ce27426 +README.zh.md: 63824ed2a4427835cbbd5ef3491bc7cb0fb94f21 diff --git a/python/sdk/README.zh.md b/python/sdk/README.zh.md index 1d1a23576c..63824ed2a4 100644 --- a/python/sdk/README.zh.md +++ b/python/sdk/README.zh.md @@ -19,9 +19,9 @@ with DeepSeekHarness() as harness: result = harness.run("Say hi.") ``` -`DeepSeekHarness` 会保留延迟启动的运行时子进程,以供多次调用复用。请像上例一样将其用作上下文管理器,或在用完后显式调用 `close()`。 +`DeepSeekHarness` 会保留其按需启动的运行时子进程,以便在多次调用之间复用。请像上例一样将其用作上下文管理器,或在使用完毕后显式调用 `close()`。 -默认情况下,SDK 启动 `deepseek-harness-runtime-bin` 包内置的单文件 `dsh-jsonrpc-agent` 可执行程序,并通过 `DSH_CORDIS_CONFIG` 注入该包的默认配置(stdio JSON-RPC 服务器、`agent-core`、预载的 DeepSeek 适配器、配有显式组合语义检查点策略的 JSONL 会话持久化、本地 bash)。要运行自己的插件组合,请在配置里保留 `@deepseek-ai/dsh-jsonrpc` 条目,并传入 Cordis 配置路径。 +默认情况下,SDK 会启动 `deepseek-harness-runtime-bin` 包内置的单文件可执行程序 `dsh-jsonrpc-agent`,并通过 `DSH_CORDIS_CONFIG` 注入该包的默认配置,其中包括 stdio JSON-RPC 服务器、agent core(智能体核心)、预载的 DeepSeek 适配器、采用显式组合语义检查点策略的 JSONL 会话持久化,以及本地 bash。要运行自己的插件组合,请在配置中保留 `@deepseek-ai/dsh-jsonrpc` 配置项,并传入 Cordis 配置文件路径。 ```py from deepseek_harness import DeepSeekHarness @@ -35,14 +35,14 @@ with DeepSeekHarness( result = harness.run("Make the requested code change.") ``` -`provider` 用于选择当前 Cordis 组合已注册的提供方路由;`model` 是该适配器解析的模型 ID。`max_tokens` 是可选的正整数,用于限制根 agent(智能体)及其进程内后代每次请求的输出 token;省略时由提供方默认值控制。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek-official`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方的凭据与端点,再选择 pi-ai 已安装目录中的任意提供方/模型组合。 +`provider` 选择指定 Cordis 组合所注册的提供方路由;`model` 是该适配器解析出的模型 ID。`max_tokens` 是一个可选的正值,用于限制根 agent(智能体)及其进程内后代在每次请求中输出的 token 数量;省略该参数时,由提供方的默认行为决定输出上限。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek-official`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方专属的凭据和端点,并选择 pi-ai 已安装 catalog 中存在的任意提供方/模型组合。 -[Python SDK 教程](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/python-sdk.md)提供不使用 Web UI 的顺序安装与首次运行路径。[`jsonrpc-agent` 示例](https://github.com/deepseek-ai/deepseek-harness/blob/master/examples/jsonrpc-agent/README.md)归属该教程使用的完整独立 Cordis 文件。 +[Python SDK 教程](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/python-sdk.md)提供一套无需使用 Web UI、按步骤完成安装和首次运行的流程。该教程所用的完整独立 Cordis 配置文件位于 [`jsonrpc-agent` 示例](https://github.com/deepseek-ai/deepseek-harness/blob/master/examples/jsonrpc-agent/README.md)中。 -`Session.run()` 拥有一个从提示词进入持久 inbox 时开始、到整个 agent 下一次进入空闲状态为止的活动区间,并返回 `RunResult(session_id, final_response, finish_reason, events, notifications, session_root)`。`final_response` 是该区间内根会话最后提交的助手文本。`finish_reason` 是该区间内根会话最后一个 `turn/end` 的 `kind`,例如 `completed`、`max-tokens` 或 `error`;没有轮次结束时为 `None`。缺少字符串 `data.reason.kind` 的 `turn/end` 违反运行时协议,并会抛出 `SdkProtocolError`。两个结果字段描述的都是自有活动区间,而不是因果上归属于该提示词的输出或结束原因。steering(中途引导)、注入的上下文和其他排队工作都可能在进入空闲状态前参与其中。 +`Session.run()` 的活动区间从其提示词被持久 inbox 接收时开始,到整个 agent 下一次进入空闲状态时结束,并返回 `RunResult(session_id, final_response, finish_reason, events, notifications, session_root)`。`final_response` 是该区间内根会话最后提交的助手文本。`finish_reason` 是该区间内根会话最后一个 `turn/end` 的 `kind`,例如 `completed`、`max-tokens` 或 `error`;没有轮次结束时为 `None`。缺少字符串 `data.reason.kind` 的 `turn/end` 违反运行时协议,并会抛出 `SdkProtocolError`。这两个结果字段描述的是 `Session.run()` 所界定的活动区间,并不表示某项输出或结束原因在因果上归属于该提示词。steering(中途引导)、注入的上下文和其他排队工作,也可能在 agent 进入空闲状态前参与这段活动。 -`HarnessClient` 会在运行时进程的生命周期内保留已发现的 subagent(子 agent)祖先关系。每次执行 `Session.run()` 时,`RunResult.notifications` 与 `on_notification` 会按协议传输顺序收到根会话及所有已知后代的通知,其中包括嵌套 subagent 的生命周期事件与会话事件。`RunResult.events` 只包含根会话事件,因此后代消息不会覆盖根会话回复。底层 `session_prompt()` 会立即返回已排队消息的 `MessageId`;绕过 `Session.run()` 的调用方必须自行负责后续的活动边界。 +`HarnessClient` 会在运行时进程的整个生命周期内保留已发现的 subagent 谱系。每次执行 `Session.run()` 时,`RunResult.notifications` 与 `on_notification` 会按协议传输顺序收到根会话及所有已知后代的通知,其中包括嵌套 subagent 的生命周期事件与会话事件。`RunResult.events` 只包含根会话事件,因此后代消息不会覆盖根会话回复。底层 `session_prompt()` 会立即返回已排队消息的 `MessageId`;绕过 `Session.run()` 的调用方必须自行负责后续的活动边界。 -同样的行为也可以通过 `DSH_CORDIS_CONFIG` 为运行时子进程选定。注入逻辑位于 `HarnessClient.start()`,因此底层客户端的默认启动也具有此行为:当启动解析到内置运行时,且 `cordis` 与非空的 `DSH_CORDIS_CONFIG` 均未设置时(运行时把空值视为缺省,注入检查与之一致),使用内置的默认配置;显式给出 `runtime_bin`、`bridge_bin` 或 `launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](https://github.com/deepseek-ai/deepseek-harness/blob/master/python/sdk-runtime/README.md)。 +也可以通过 `DSH_CORDIS_CONFIG` 为运行时子进程指定配置。注入逻辑位于 `HarnessClient.start()`,因此底层客户端按默认方式启动时也具有该行为:如果启动方式最终解析为内置运行时,且既没有设置 `cordis`,也没有设置非空的 `DSH_CORDIS_CONFIG`(运行时将空值视为未设置,注入检查也是如此),系统就会使用内置默认配置;显式指定 `runtime_bin`、`bridge_bin` 或 `launch_args_override` 时,则会完全禁用该注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](https://github.com/deepseek-ai/deepseek-harness/blob/master/python/sdk-runtime/README.md)。 -`cwd` 与 `runtime_cwd` 会在启动子进程、注入环境变量和协议握手前解析为绝对路径。公开 API 只暴露真正生效的选项:部署的角色设定与持久化配置归 `cordis.yml` 管理,而 `session_root` 继续作为设置 `DSH_SESSION_ROOT` 的高层便捷选项。 +`cwd` 与 `runtime_cwd` 会在启动子进程、注入环境变量和协议握手前解析为绝对路径。公开 API 只暴露由 SDK 直接应用的选项:部署 persona 和持久化配置应在 `cordis.yml` 中定义;`session_root` 则保留为设置 `DSH_SESSION_ROOT` 的高层便捷参数。 diff --git a/scripts/snapshots/translation-prompt-v4/request-response.expected.json b/scripts/snapshots/translation-prompt-v4/request-response.expected.json index 5027739b50..497fd95a86 100644 --- a/scripts/snapshots/translation-prompt-v4/request-response.expected.json +++ b/scripts/snapshots/translation-prompt-v4/request-response.expected.json @@ -12,7 +12,7 @@ }, { "role": "assistant", - "content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 coding agent(编程智能体)。\n\n它采用了**一切皆插件**的架构。\n\n## 内测说明\n\nDeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。\n\nSession Log 默认留在本地。设置 `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 可仅在提交反馈时共享 Session Log,设置 `DSH_TELEMETRY_MODE=FULL` 可持续上传;`FULL` 同时会启用 dsh-sdk 命令遥测,上报匿名 ID、命令结果以及脱敏后的项目配置。请通过内部企业微信群反馈问题和建议。\n\n\n## 运行\n\n请先安装 Node.js(版本要求:`^22.19` 或 `>=24`)和 pnpm 11,然后运行已发布的包:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\n该命令会初始化 Web profile 并打印 Web UI 地址,默认地址为 `http://127.0.0.1:3080`。打开该地址,在**设置 → 模型**中添加 DeepSeek API 密钥,然后启动一个会话。运行命令时所在的目录将作为默认工作区;你可以尝试输入 `Summarize this repository and identify its main packages.`。\n\n下一步请阅读 [Web UI 指南](docs/user/guide/index.md)。\n\n### 从源码运行\n\n如需从仓库源码运行:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` 会准备仓库产物。`pnpm dsh web` 不会重新构建,而是直接启动同一个 Web UI。\n\n## Profile 与插件\n\nprofile 由一组按顺序排列的插件组合包构成。随附的 `web` profile 用于运行 `dsh web`。使用 `dsh plugin --profile ` 管理 profile;该命令会在对应 profile 目录中将剩余参数转发给 pnpm:\n\n```sh\nnpx -p @deepseek-ai/dsh dsh plugin --profile web add \nnpx -p @deepseek-ai/dsh dsh plugin --profile web remove \n```\n\n`add`、`remove`、`update`、`why` 等 pnpm 命令均可直接使用。该命令会先初始化不存在的 profile,再修改其中的包,并根据声明了 `dsh.bundle` 的已安装包更新 bundle 列表。具体行为见 [CLI 参考](apps/cli/reference/README.md#plugin-management)。\n\n[CLI(命令行界面)参考](apps/cli/README.md)介绍 headless 执行与自定义 profile。[Python SDK](python/README.md) 和[示例](examples/README.md)介绍程序化组合与自定义组合。\n\n## 社区\n\n扫描二维码,或打开 DeepSeek Harness 微信社区申请页面 申请加入。\n\n

\n \"DeepSeek\n

\n\n## 开发\n\n请先阅读[开发指南](docs/development.md);修改包之前,请阅读[架构文档](docs/architecture.md)。\n\n面向 agent:遵循 [AGENTS.md](AGENTS.md)。\n\n## 许可证\n\n[BSD 3-Clause](LICENSE)\n\n第三方依赖及其许可证见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。\n\n## 参与贡献\n\n向本仓库贡献前,请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。\n" + "content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 coding agent(编程智能体)。\n\n它采用了**一切皆插件**的架构。\n\n## 内测说明\n\nDeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。\n\n会话日志默认保存在本地。设置 `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 后,仅在提交反馈时共享对应的会话日志;设置 `DSH_TELEMETRY_MODE=FULL` 后,会持续上传会话日志。`FULL` 还会启用 dsh-sdk 命令遥测,上报匿名 ID、命令执行结果和脱敏后的项目配置。请通过内部企业微信群反馈问题和建议。\n\n## 运行\n\n请先安装 Node.js(版本要求:`^22.19` 或 `>=24`)和 pnpm 11,然后运行已发布的包:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\n该命令会初始化 Web profile 并打印 Web UI 地址,默认地址为 `http://127.0.0.1:3080`。打开该地址,在**设置 → 模型**中添加 DeepSeek API 密钥,然后启动一个会话。运行命令时所在的目录将作为默认工作区;你可以尝试输入 `Summarize this repository and identify its main packages.`。\n\n下一步请阅读 [Web UI 指南](docs/user/guide/index.md)。\n\n### 从源码运行\n\n如需从仓库源码运行:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` 会准备仓库产物。`pnpm dsh web` 不会重新构建,而是直接启动同一个 Web UI。\n\n## Profile 与插件\n\nprofile 由一组按顺序排列的插件组合包构成。随附的 `web` profile 用于运行 `dsh web`。使用 `dsh plugin --profile ` 管理 profile;该命令会在对应 profile 目录中将剩余参数转发给 pnpm:\n\n```sh\nnpx -p @deepseek-ai/dsh dsh plugin --profile web add \nnpx -p @deepseek-ai/dsh dsh plugin --profile web remove \n```\n\n`add`、`remove`、`update`、`why` 等 pnpm 命令均可直接使用。该命令会先初始化不存在的 profile,再修改其中的包,并根据声明了 `dsh.bundle` 的已安装包更新 bundle 列表。具体行为见 [CLI 参考](apps/cli/reference/README.md#plugin-management)。\n\n[CLI(命令行界面)参考](apps/cli/README.md)介绍 headless 执行与自定义 profile。[Python SDK](python/README.md) 和[示例](examples/README.md)介绍程序化组合与自定义组合。\n\n## 社区\n\n扫描二维码,或打开 DeepSeek Harness 微信社区申请页面 申请加入。\n\n

\n \"DeepSeek\n

\n\n## 开发\n\n请先阅读[开发指南](docs/development.md);修改包之前,请阅读[架构文档](docs/architecture.md)。\n\n面向 agent:遵循 [AGENTS.md](AGENTS.md)。\n\n## 许可证\n\n[BSD 3-Clause](LICENSE)\n\n第三方依赖及其许可证见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。\n\n## 参与贡献\n\n向本仓库贡献前,请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。\n" }, { "role": "user", From 486c87238130e87cb1e0cc5905ed02268eef50e3 Mon Sep 17 00:00:00 2001 From: j-xiang Date: Thu, 13 Aug 2026 00:42:11 +0800 Subject: [PATCH 011/119] docs(i18n): address focused README review --- apps/cli/README.i18n.yaml | 2 +- apps/cli/README.zh.md | 4 ++-- apps/cli/reference/README.i18n.yaml | 2 +- apps/cli/reference/README.zh.md | 4 ++-- packages/core/tools/README.i18n.yaml | 2 +- packages/core/tools/README.zh.md | 4 ++-- python/sdk/README.i18n.yaml | 2 +- python/sdk/README.zh.md | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/cli/README.i18n.yaml b/apps/cli/README.i18n.yaml index ac025294ca..269cfe0c8c 100644 --- a/apps/cli/README.i18n.yaml +++ b/apps/cli/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/README.md README.md: d36183c545475020207bbf23e58d3c98b07ac6a6 -README.zh.md: 9e5507ad767640b413d00cf16f1becc0f540be2b +README.zh.md: 3a87f6750ee4c1a3c94e1438b29911adef615d2c diff --git a/apps/cli/README.zh.md b/apps/cli/README.zh.md index 9e5507ad76..3a87f6750e 100644 --- a/apps/cli/README.zh.md +++ b/apps/cli/README.zh.md @@ -29,14 +29,14 @@ dsh --help # the launcher's own help ## Profile -profile 目录包含一个 `package.json`,其中记录另行安装的插件依赖,以及 profile manifest(元数据清单)`dsh.profile` 和其中按顺序排列的 `bundles` 列表;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。 +profile 目录包含一个 `package.json`,其中记录树外插件依赖,以及 profile manifest(元数据清单)`dsh.profile` 和其中按顺序排列的 `bundles` 列表;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。 配置树以空根为起点,依次叠加以下配置层: - `dsh.profile.bundles` 中各组合包的 patch - profile 自身的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml` - `--patch` 指定的覆盖层 -`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自身的 `node_modules` 解析;pnpm 会将另行安装的插件放入该目录。 +`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自身的 `node_modules` 解析;pnpm 会将树外插件安装到该目录。 使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。 diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index ebbede237a..cbc7fe54f9 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/reference/README.md README.md: 17d63fe73ea2b3bda74e9c4da91555b34c21f4ab -README.zh.md: 83945a09073057b8c220da9fc1a6b2b84b98f599 +README.zh.md: 31e0143a0ab9007cedcb0c49788c26cc61caa8a3 diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index 83945a0907..31e0143a0a 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -8,7 +8,7 @@ `dsh --profile ` 启动位于 `$DSH_HOME/profiles/` 的 profile。生效配置树以空根节点为起点,依次叠加 profile manifest(元数据清单)的 `dsh.profile.bundles` 列表中指定的各组合包 patch、profile 自身的 `cordis.patch.yml`、home 级的 `$DSH_HOME/cordis.patch.yml`(这是各 profile 共享的机器本地偏好,因此优先于逐 profile 配置层),以及按 argv 顺序指定的各个 `--patch ` 覆盖层。对同一配置行,后应用的层优先。patch 会替换目标行的整个 `config` 值,而不是深度合并其中的键;patch 也可以插入新行。配置解析、schema 校验、模块解析或插件启动失败时,系统会报告错误并以非零状态退出。收到 SIGINT 或 SIGTERM 时,挂载的根节点会先 dispose(资源释放)再退出。 -组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`)始终来自当前运行的 `dsh` 所属的安装;另行安装的组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。该目录为 dsh 安装中的应用和组合包所依赖的每个包各维护一个符号链接,并在每次启动时修复这些链接。 +组合包名称先从 dsh 安装目录解析,再从 profile 目录解析。因此,内置组合包(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`)始终来自当前运行的 `dsh` 所属的安装;树外组合包则来自 profile 中由 pnpm 管理的 `node_modules`。patch 行中的裸插件 `name` 会从 profile 目录开始,按照 Node 的模块解析规则逐级向父目录查找,直至由 dsh 维护的安装后备目录 `$DSH_HOME/profiles/node_modules`。该目录为 dsh 安装中的应用和组合包所依赖的每个包各维护一个符号链接,并在每次启动时修复这些链接。 `web` 和 `headless` profile 首次使用时会从随附模板自动初始化(`web`:base + web-app;`headless`:base + headless)。其他缺失的 profile 会显式报错,并提示运行 `dsh plugin --profile add `。 @@ -40,7 +40,7 @@ dsh --profile web --patch ./extra.yml --dump-config ## 插件管理 -`dsh plugin --profile ` 在 profile 缺失时先初始化它(有随附模板的用模板,其他名称只装 `@deepseek-ai/dsh-base`),然后以 profile 目录为工作目录,把 `` 转发给 `pnpm`:`add`、`remove`、`why`、`update` 及其他所有 pnpm 子命令都照常可用;pnpm 必须在 PATH 上。相对路径 spec(`.`、`../plugin` 及其 `file:`/`link:` 形式)会先锚定到调用目录,因此在插件 checkout 中执行 `add .` 安装的是该 checkout,而不是 profile。每次成功运行后,系统都会根据当前安装状态更新 `dsh.profile.bundles`:如果某项依赖解析到的包在 manifest(元数据清单)中声明了 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`,该依赖就会加入配置层栈;如果某项依赖在 `update` 后获得该声明,也会随即激活。没有组合包声明的依赖仍作为普通依赖保留,并显示一次性警告;已移除的依赖则从配置层栈中删除。 +`dsh plugin --profile ` 在 profile 缺失时先初始化它(有随附模板的用模板,其他名称只装 `@deepseek-ai/dsh-base`),然后以 profile 目录为工作目录,把 `` 转发给 `pnpm`:`add`、`remove`、`why`、`update` 及其他所有 pnpm 子命令都照常可用;pnpm 必须在 PATH 上。相对路径 spec(`.`、`../plugin` 及其 `file:`/`link:` 形式)会先锚定到调用目录,因此在插件 checkout 中执行 `add .` 安装的是该 checkout,而不是 profile。每次成功运行后,系统都会根据当前安装状态更新 `dsh.profile.bundles`:如果某项依赖解析到的包在 manifest 中声明了 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`,该依赖就会加入配置层栈;如果某项依赖在 `update` 后获得该声明,也会随即激活。没有组合包声明的依赖仍作为普通依赖保留,并显示一次性警告;已移除的依赖则从配置层栈中删除。 ```sh dsh plugin --profile tui add github:deepseek-harness/turtle-ui diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index 9cda431253..3e3802d74d 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/tools/README.md README.md: 44eb25b79436a75f08406102fc1e3734e59b1001 -README.zh.md: d77b4ee5248b19418c27674faf7afd52957c6382 +README.zh.md: a395f453bab5ade3530219f4883575b55c1068ff diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index d77b4ee524..a395f453ba 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -13,7 +13,7 @@ tools: mode: native # native (default) | code | both ``` -`native` 以函数定义的形式贡献可见工具。`code` 会提供预留的 `run_code` 传输、生成的 `tools:sdk` 段,以及声明「只有 `run_code` 可被直接调用」的 `tools:code-only` 规则。执行器随后强制执行该规则:模型直接调用其他任何工具时,会在策略运行前将该调用解析为 `UNKNOWN_TOOL`;`both` 同时提供两种形式,且不声明该规则,因为其中的原生调用确实可以执行。没有单独声明呈现模式的 agent 默认采用此配置;agent preset 可通过 [`dsh-agent-tool-mode`](../agent-tool-mode/README.md) 自行选择呈现模式。不能注册、遮蔽、限制或移除该保留传输,且无论配置何种模式,该名称都是保留的,因为任何 agent 都可能选择 code 模式。非原生模式要求所加载 `ctx.codeRuntime` 的 `language` 有已注册的 SDK 渲染器——TypeScript 经 [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md) 交付;Python 渲染器内置,驱动任何报告 `language: 'python'` 的运行时(第一方 `dsh-code-runtime-python` 后端另行交付)。没有渲染器的运行时语言会导致提示词组装明确失败;如果 `systemPrompt.toolOrder` 条目指向当前模式未贡献的工具,系统会拒绝组装提示词。`system-prompt/assemble` 监听器可以替换注册表贡献;它返回的组装结果具有权威性,因此该监听器负责保留可用的 Code Mode 协议。 +`native` 以函数定义的形式贡献可见工具。`code` 会提供保留的 `run_code` 传输、生成的 `tools:sdk` 段,以及声明「只有 `run_code` 可被直接调用」的 `tools:code-only` 规则。执行器随后强制执行该规则:模型直接调用其他任何工具时,会在策略运行前将该调用解析为 `UNKNOWN_TOOL`;`both` 同时提供两种形式,且不声明该规则,因为其中的原生调用确实可以执行。没有单独声明呈现模式的 agent 默认采用此配置;agent preset 可通过 [`dsh-agent-tool-mode`](../agent-tool-mode/README.md) 自行选择呈现模式。不能注册、遮蔽、限制或移除该保留传输,且无论配置何种模式,该名称都是保留的,因为任何 agent 都可能选择 code 模式。非原生模式要求所加载 `ctx.codeRuntime` 的 `language` 有已注册的 SDK 渲染器——TypeScript 经 [`dsh-code-runtime-worker`](../../code-runtime/code-runtime-worker/README.md) 交付;Python 渲染器内置,驱动任何报告 `language: 'python'` 的运行时(第一方 `dsh-code-runtime-python` 后端另行交付)。没有渲染器的运行时语言会导致提示词组装明确失败;如果 `systemPrompt.toolOrder` 条目指向当前模式未贡献的工具,系统会拒绝组装提示词。`system-prompt/assemble` 监听器可以替换注册表贡献;它返回的组装结果具有权威性,因此该监听器负责保留可用的 Code Mode 协议。 ### 公开 API @@ -120,7 +120,7 @@ ctx.tools.register(defineTool({ 在 `code`(而非 `both`)下,该传输同时也是模型唯一可用的入口:模型直呼其他任何可见工具名,都会在创建执行时、早于 `tools/pre-execute`、审批 `ask` 和 guards 解析为 `UNKNOWN_TOOL`,因此没有任何一方会观察或批准一个注定失败的调用。拒绝信息会给出正确路径(`only \`run_code\` is callable directly — call \`\` from inside a \`run_code\` program instead`),因为同一份提示词刚刚声明过那个工具,只说 `unknown tool` 会被读成部署损坏。SDK 子分发携带外层执行的 `parent` token,不受此限制,因此程序保留 SDK 声明的全部绑定。参见[执行器塌缩 note](../../../.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md)、[Code Mode 基础](../../../.agents/notes/implemented/feature/2026-06-15-code-mode.md)、[类型化返回约定](../../../.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md)和[代码运行时 seam](../../code-runtime/README.md)。可以运行 `pnpm run demo:code-mode` 试用。 - **SDK 段**(`tools:sdk`,顺序 150):一个在组装时求值的提示词段,每次组装都会重新生成与所加载运行时语言相符的 SDK 文本。TypeScript 形态会生成 `JsonValue`、精确的 `ToolArgsMap` / `ToolOutputMap`、`ToolName`、`ToolCallError` 声明,以及映射调用作用域最终可见工具的 `tools` 命名空间(特殊名称使用带引号的键),并附带固定的使用说明;Python 形态(`ctx.codeRuntime.language === 'python'`)发出等价的具名 `TypedDict` 与一个带相同用法说明的 `tools` 对象。其输出具有确定性:工具按字典序排列;工具集合不变时,文本逐字节相同(有利于前缀 cache)。两个代码生成器都已导出,且绝不会在提示词组装期间抛出:`jsonSchemaToTs` 处理统一 schema 的每种构造并将不受支持的原始构造降级为 `unknown`;`jsonSchemaToPy` 同理,降级为 `Any`(当某字段名不是合法的 `TypedDict` 属性时,或在 SDK 渲染之外被调用时——`TypedDict` 声明所需的命名上下文由该渲染提供——整个对象降级为 `dict[str, Any]`)。 -- **分发桥接层**(`run_code` 的 execute):每个绑定调用都会在分发前快照为无损 JSON(`undefined`、`BigInt`、循环、稀疏数组、`-0` 和特殊对象会使该次调用被拒绝),经由每次运行独有、复用原生并发约定的池调度——调用严格按提交顺序启动,连续的 `isConcurrencySafe` 调用最多可重叠经校验的 `maxParallelSubCalls` 配置个(默认 10;设为 `1` 即恢复串行分发),被分类为独占的调用先排空池、单独运行并阻挡其后的调用——以外层执行的不透明 token 作为 `parent`,并经过完整的 pre-execute → guards → execute → post-execute → result 流水线。成功会返回策略处理后的最终规范值;失败以一条消息到达 worker,并成为 `ToolCallError(toolName, message)`。每个已启动的子调用在进入流水线时记录一条 `tool/code-dispatch-start` 事件(确定性 id `:code:`,按提交顺序编号),并以一条携带完整模型可见 `content`/`isError` 结果的 `tool/code-dispatch` 事件完结(采用 `tool/result` 词汇,因此 UI 会沿原生路径呈现子调用——这对事件的 `time` 字段承载每个子调用的计时);因 run 结算而被放弃的排队调用两者都不记录。`deriveMessages()` 既不公开这两个事件,也不持久化规范值。token 关联使按提交语义工作的观察器可以延后提交内部调用的成功结果,直到最终 `run_code` 结果确定,而无需接收实时可变的外层执行对象;普通工具副作用不会回滚。每个子调用的 `additionalContexts` 条目都会按分发顺序通过外层 `ToolRunContext` 延迟;循环只在父级 `run_code` 结果之后追加这些上下文,从而保持相邻关系,并且即使程序后来失败,也会保留各自的来源/元数据。 +- **分发桥接层**(`run_code` 的 execute):每个绑定调用都会在分发前快照为无损 JSON(`undefined`、`BigInt`、循环、稀疏数组、`-0` 和特殊对象会使该次调用被拒绝),经由每次运行独有、复用原生并发约定的池调度——调用严格按提交顺序启动,连续的 `isConcurrencySafe` 调用最多可重叠经校验的 `maxParallelSubCalls` 配置个(默认 10;设为 `1` 即恢复串行分发),被分类为独占的调用先排空池、单独运行并阻挡其后的调用——以外层执行的不透明 token 作为 `parent`,并经过完整的 pre-execute → guards → execute → post-execute → result 流水线。成功会返回策略处理后的最终规范值;失败以一条消息到达 worker,并成为 `ToolCallError(toolName, message)`。每个已启动的子调用在进入流水线时记录一条 `tool/code-dispatch-start` 事件(确定性 id `:code:`,按提交顺序编号),并以一条携带完整模型可见 `content`/`isError` 结果的 `tool/code-dispatch` 事件完结(采用 `tool/result` 词汇,因此 UI 会沿原生路径呈现子调用——这对事件的 `time` 字段承载每个子调用的计时);因 run 结算而被放弃的排队调用两者都不记录。`deriveMessages()` 既不公开这两个事件,也不持久化规范值。token 关联使按提交语义工作的观察器可以延后提交内部调用的成功结果,直到最终 `run_code` 结果确定,而无需暴露进行中的外层执行;普通工具副作用不会回滚。每个子调用的 `additionalContexts` 条目都会按分发顺序通过外层 `ToolRunContext` 延迟;循环只在父级 `run_code` 结果之后追加这些上下文,从而保持相邻关系,并且即使程序后来失败,也会保留各自的来源/元数据。 - **结算纪律**:桥接层拥有一个运行作用域的中止机制;该中止会跟随传入的外层信号,并在运行因任何原因结算时触发,因此预算耗尽会中止正在运行的子工具,而不会将其遗留。桥接层随后会在返回之前排空队列,使每个 `tool/code-dispatch` 都落在仍打开的轮次内。失败的运行会抛出 `CodeRunFailedError`(`code: 'CODE_RUN_FAILED'`,message = 失败类型 + 已捕获日志),流水线会将其转换为模型可据以自我修正的结构化 `isError`。 - **结果大小**:中间绑定值会完整传入 worker 进程,且没有逐绑定字节上限。`run_code` 返回规范的 `{ logs: string[], result?: JsonValue }`;字符串原样呈现,其他所有存在的 JSON 根都通过栈安全的美化 JSON 遍历呈现,总缩进最多为 10 个字符(更深的子树保持紧凑),`null` 保持显式,而缺少 `result` 表示程序返回 `undefined`。worker 可配置的 `maxOutputBytes`(默认 64 MiB)只应用于组合序列化后的外层日志数组、完成值或失败消息载荷;固定的结果封装语法和呈现空白不计入该上限。无效和超限的完成会明确失败,只有这个外层结果可以按常规 spill 机制处理。 diff --git a/python/sdk/README.i18n.yaml b/python/sdk/README.i18n.yaml index fa73fe380f..50e4ae9591 100644 --- a/python/sdk/README.i18n.yaml +++ b/python/sdk/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write python/sdk/README.md README.md: 70b9d6391644d10ee7d5c29ce122632786e3bbcc -README.zh.md: 63824ed2a4427835cbbd5ef3491bc7cb0fb94f21 +README.zh.md: 02217d62b8f24dc6806356c93f63574f2fb9f129 diff --git a/python/sdk/README.zh.md b/python/sdk/README.zh.md index 63824ed2a4..02217d62b8 100644 --- a/python/sdk/README.zh.md +++ b/python/sdk/README.zh.md @@ -35,7 +35,7 @@ with DeepSeekHarness( result = harness.run("Make the requested code change.") ``` -`provider` 选择指定 Cordis 组合所注册的提供方路由;`model` 是该适配器解析出的模型 ID。`max_tokens` 是一个可选的正值,用于限制根 agent(智能体)及其进程内后代在每次请求中输出的 token 数量;省略该参数时,由提供方的默认行为决定输出上限。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek-official`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方专属的凭据和端点,并选择 pi-ai 已安装 catalog 中存在的任意提供方/模型组合。 +`provider` 选择指定 Cordis 组合所注册的提供方路由;`model` 是该适配器解析出的模型 ID。`max_tokens` 是一个可选的正整数,用于限制根 agent 及其进程内后代在每次请求中输出的 token 数量;省略该参数时,由提供方的默认行为决定输出上限。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek-official`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方专属的凭据和端点,并选择 pi-ai 已安装 catalog 中存在的任意提供方/模型组合。 [Python SDK 教程](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/python-sdk.md)提供一套无需使用 Web UI、按步骤完成安装和首次运行的流程。该教程所用的完整独立 Cordis 配置文件位于 [`jsonrpc-agent` 示例](https://github.com/deepseek-ai/deepseek-harness/blob/master/examples/jsonrpc-agent/README.md)中。 From a2d0f7f41121ee81911dd1badbf248edd3f2ab70 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 13 Aug 2026 00:36:22 +0800 Subject: [PATCH 012/119] refactor: apply repository naming contract Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change. --- ...026-06-11-event-sourced-sessions.i18n.yaml | 4 +- .../2026-06-11-event-sourced-sessions.md | 2 +- .../2026-06-11-event-sourced-sessions.zh.md | 2 +- .../2026-06-13-capability-seams.i18n.yaml | 4 +- .../2026-06-13-capability-seams.md | 8 +- .../2026-06-13-capability-seams.zh.md | 6 +- .../2026-06-14-session-persistence.i18n.yaml | 4 +- .../2026-06-14-session-persistence.md | 2 +- .../2026-06-14-session-persistence.zh.md | 2 +- ...06-17-filesystem-capability-seam.i18n.yaml | 4 +- .../2026-06-17-filesystem-capability-seam.md | 32 +- ...026-06-17-filesystem-capability-seam.zh.md | 32 +- ...ifecycle-and-ownership-contracts.i18n.yaml | 4 +- ...agent-lifecycle-and-ownership-contracts.md | 8 +- ...nt-lifecycle-and-ownership-contracts.zh.md | 4 +- .../2026-06-18-session-surface.i18n.yaml | 4 +- .../2026-06-18-session-surface.md | 2 +- .../2026-06-18-session-surface.zh.md | 2 +- .../2026-06-20-branded-ids.i18n.yaml | 4 +- .../architecture/2026-06-20-branded-ids.md | 16 +- .../architecture/2026-06-20-branded-ids.zh.md | 16 +- ...eneric-long-running-tool-runtime.i18n.yaml | 4 +- ...06-20-generic-long-running-tool-runtime.md | 64 +- ...20-generic-long-running-tool-runtime.zh.md | 62 +- ...-21-bounded-llm-request-recovery.i18n.yaml | 4 +- ...2026-06-21-bounded-llm-request-recovery.md | 4 +- ...6-06-21-bounded-llm-request-recovery.zh.md | 4 +- ...andatory-app-attribution-headers.i18n.yaml | 4 +- ...06-21-mandatory-app-attribution-headers.md | 2 +- ...21-mandatory-app-attribution-headers.zh.md | 2 +- .../2026-06-24-web-capability-seam.i18n.yaml | 4 +- .../2026-06-24-web-capability-seam.md | 32 +- .../2026-06-24-web-capability-seam.zh.md | 32 +- ...06-26-file-context-as-event-gate.i18n.yaml | 4 +- .../2026-06-26-file-context-as-event-gate.md | 58 +- ...026-06-26-file-context-as-event-gate.zh.md | 58 +- ...ash-stdin-env-trusted-plugin-api.i18n.yaml | 4 +- ...06-30-bash-stdin-env-trusted-plugin-api.md | 6 +- ...30-bash-stdin-env-trusted-plugin-api.zh.md | 6 +- ...6-07-02-tool-render-intent-union.i18n.yaml | 4 +- .../2026-07-02-tool-render-intent-union.md | 4 +- .../2026-07-02-tool-render-intent-union.zh.md | 2 +- ...bles-and-tool-guidance-ownership.i18n.yaml | 4 +- ...t-variables-and-tool-guidance-ownership.md | 4 +- ...ariables-and-tool-guidance-ownership.zh.md | 4 +- ...6-07-05-reconstructable-requests.i18n.yaml | 4 +- .../2026-07-05-reconstructable-requests.md | 4 +- .../2026-07-05-reconstructable-requests.zh.md | 4 +- ...6-07-06-timeout-deadline-library.i18n.yaml | 4 +- .../2026-07-06-timeout-deadline-library.md | 10 +- .../2026-07-06-timeout-deadline-library.zh.md | 10 +- ...06-tool-result-retention-library.i18n.yaml | 4 +- ...026-07-06-tool-result-retention-library.md | 8 +- ...-07-06-tool-result-retention-library.zh.md | 6 +- ...6-07-07-tool-call-timeout-policy.i18n.yaml | 4 +- .../2026-07-07-tool-call-timeout-policy.md | 20 +- .../2026-07-07-tool-call-timeout-policy.zh.md | 20 +- ...26-07-08-tool-output-spill-files.i18n.yaml | 4 +- .../2026-07-08-tool-output-spill-files.md | 12 +- .../2026-07-08-tool-output-spill-files.zh.md | 12 +- ...n-pressure-and-overflow-recovery.i18n.yaml | 4 +- ...mpaction-pressure-and-overflow-recovery.md | 16 +- ...ction-pressure-and-overflow-recovery.zh.md | 16 +- ...cutable-sdk-runtime-distribution.i18n.yaml | 4 +- ...ile-executable-sdk-runtime-distribution.md | 16 +- ...-executable-sdk-runtime-distribution.zh.md | 16 +- ...07-12-agent-scope-runtime-design.i18n.yaml | 4 +- .../2026-07-12-agent-scope-runtime-design.md | 10 +- ...026-07-12-agent-scope-runtime-design.zh.md | 10 +- .../2026-07-12-scoped-layers-store.i18n.yaml | 4 +- .../2026-07-12-scoped-layers-store.md | 10 +- .../2026-07-12-scoped-layers-store.zh.md | 10 +- ...-14-provider-routed-llm-adapters.i18n.yaml | 4 +- ...2026-07-14-provider-routed-llm-adapters.md | 8 +- ...6-07-14-provider-routed-llm-adapters.zh.md | 8 +- ...2026-07-15-agent-initiator-scope.i18n.yaml | 2 +- .../2026-07-15-agent-initiator-scope.md | 2 +- ...-model-catalog-and-acp-selection.i18n.yaml | 4 +- ...-15-llm-model-catalog-and-acp-selection.md | 8 +- ...-llm-model-catalog-and-acp-selection.zh.md | 8 +- .../2026-07-15-lsp-capability-seam.i18n.yaml | 4 +- .../2026-07-15-lsp-capability-seam.md | 14 +- .../2026-07-15-lsp-capability-seam.zh.md | 14 +- ...07-15-replay-token-meter-service.i18n.yaml | 4 +- .../2026-07-15-replay-token-meter-service.md | 16 +- ...026-07-15-replay-token-meter-service.zh.md | 18 +- ...19-gui-layering-and-rpc-protocol.i18n.yaml | 4 +- ...026-07-19-gui-layering-and-rpc-protocol.md | 8 +- ...-07-19-gui-layering-and-rpc-protocol.zh.md | 8 +- ...kage-invariant-runtime-contracts.i18n.yaml | 4 +- ...-19-package-invariant-runtime-contracts.md | 10 +- ...-package-invariant-runtime-contracts.zh.md | 10 +- ...0-canonical-tool-output-contract.i18n.yaml | 4 +- ...26-07-20-canonical-tool-output-contract.md | 10 +- ...07-20-canonical-tool-output-contract.zh.md | 6 +- ...el-context-and-compaction-policy.i18n.yaml | 4 +- ...ted-model-context-and-compaction-policy.md | 12 +- ...-model-context-and-compaction-policy.zh.md | 12 +- ...2-slot-type-chain-implementation.i18n.yaml | 4 +- ...26-07-22-slot-type-chain-implementation.md | 2 +- ...07-22-slot-type-chain-implementation.zh.md | 2 +- ...7-23-client-plugin-loading-model.i18n.yaml | 4 +- .../2026-07-23-client-plugin-loading-model.md | 8 +- ...26-07-23-client-plugin-loading-model.zh.md | 8 +- ...ed-reasoning-effort-capabilities.i18n.yaml | 4 +- ...ter-owned-reasoning-effort-capabilities.md | 4 +- ...-owned-reasoning-effort-capabilities.zh.md | 4 +- ...-24-single-harness-home-resolver.i18n.yaml | 4 +- ...2026-07-24-single-harness-home-resolver.md | 10 +- ...6-07-24-single-harness-home-resolver.zh.md | 10 +- ...tree-boot-and-transport-layering.i18n.yaml | 4 +- ...config-tree-boot-and-transport-layering.md | 2 +- ...fig-tree-boot-and-transport-layering.zh.md | 2 +- ...ession-scope-and-provide-channel.i18n.yaml | 4 +- ...lient-session-scope-and-provide-channel.md | 4 +- ...nt-session-scope-and-provide-channel.zh.md | 4 +- ...eb-command-surfaces-and-assembly.i18n.yaml | 4 +- ...07-25-web-command-surfaces-and-assembly.md | 10 +- ...25-web-command-surfaces-and-assembly.zh.md | 10 +- ...input-machine-and-slash-pipeline.i18n.yaml | 4 +- ...25-web-input-machine-and-slash-pipeline.md | 18 +- ...web-input-machine-and-slash-pipeline.zh.md | 18 +- ...=> 2026-07-26-job-registry-seam.i18n.yaml} | 6 +- .../2026-07-26-job-registry-seam.md | 35 + .../2026-07-26-job-registry-seam.zh.md | 35 + .../2026-07-26-subprocess-seam.i18n.yaml | 4 +- .../2026-07-26-subprocess-seam.md | 18 +- .../2026-07-26-subprocess-seam.zh.md | 16 +- .../2026-07-26-task-registry-seam.md | 35 - .../2026-07-26-task-registry-seam.zh.md | 35 - ...ompiler-independent-typert-model.i18n.yaml | 4 +- ...07-27-compiler-independent-typert-model.md | 2 +- ...27-compiler-independent-typert-model.zh.md | 2 +- ...rtable-execution-world-consumers.i18n.yaml | 4 +- ...7-28-portable-execution-world-consumers.md | 8 +- ...8-portable-execution-world-consumers.zh.md | 8 +- .../2026-07-28-user-settings-seam.i18n.yaml | 4 +- .../2026-07-28-user-settings-seam.md | 2 +- .../2026-07-28-user-settings-seam.zh.md | 2 +- ...-07-29-dsh-source-launch-tsx-esm.i18n.yaml | 4 +- .../2026-07-29-dsh-source-launch-tsx-esm.md | 2 +- ...2026-07-29-dsh-source-launch-tsx-esm.zh.md | 2 +- .../2026-07-29-package-regrouping.i18n.yaml | 4 +- .../2026-07-29-package-regrouping.md | 33 +- .../2026-07-29-package-regrouping.zh.md | 33 +- ...-token-usage-and-request-context.i18n.yaml | 4 +- ...ojected-token-usage-and-request-context.md | 2 +- ...cted-token-usage-and-request-context.zh.md | 2 +- ...est-level-llm-config-credentials.i18n.yaml | 4 +- ...29-request-level-llm-config-credentials.md | 2 +- ...request-level-llm-config-credentials.zh.md | 2 +- ...-29-terminal-llm-stream-failures.i18n.yaml | 4 +- ...2026-07-29-terminal-llm-stream-failures.md | 8 +- ...6-07-29-terminal-llm-stream-failures.zh.md | 8 +- ...adapter-owned-max-token-defaults.i18n.yaml | 4 +- ...-07-30-adapter-owned-max-token-defaults.md | 4 +- ...-30-adapter-owned-max-token-defaults.zh.md | 4 +- ...26-07-30-config-plane-boundaries.i18n.yaml | 4 +- .../2026-07-30-config-plane-boundaries.md | 2 +- .../2026-07-30-config-plane-boundaries.zh.md | 2 +- ...undaries-and-atomic-registration.i18n.yaml | 4 +- ...tial-boundaries-and-atomic-registration.md | 4 +- ...l-boundaries-and-atomic-registration.zh.md | 4 +- ...30-session-end-seed-log-boundary.i18n.yaml | 4 +- ...026-07-30-session-end-seed-log-boundary.md | 10 +- ...-07-30-session-end-seed-log-boundary.zh.md | 10 +- ...30-settings-write-path-integrity.i18n.yaml | 4 +- ...026-07-30-settings-write-path-integrity.md | 2 +- ...-07-30-settings-write-path-integrity.zh.md | 2 +- .../2026-07-30-web-config-plane.i18n.yaml | 4 +- .../2026-07-30-web-config-plane.md | 4 +- .../2026-07-30-web-config-plane.zh.md | 4 +- ...claimed-pre-step-inbox-lifecycle.i18n.yaml | 4 +- ...-07-31-claimed-pre-step-inbox-lifecycle.md | 2 +- ...-31-claimed-pre-step-inbox-lifecycle.zh.md | 2 +- ...runtime-portable-identifier-seam.i18n.yaml | 4 +- ...1-code-runtime-portable-identifier-seam.md | 2 +- ...ode-runtime-portable-identifier-seam.zh.md | 2 +- ...-07-31-goal-owned-durable-events.i18n.yaml | 4 +- .../2026-07-31-goal-owned-durable-events.md | 4 +- ...2026-07-31-goal-owned-durable-events.zh.md | 4 +- ...26-08-01-packaged-ripgrep-search.i18n.yaml | 4 +- .../2026-08-01-packaged-ripgrep-search.md | 2 +- .../2026-08-01-packaged-ripgrep-search.zh.md | 2 +- ...08-02-typert-remote-method-calls.i18n.yaml | 4 +- .../2026-08-02-typert-remote-method-calls.md | 116 +- ...026-08-02-typert-remote-method-calls.zh.md | 116 +- ...4-configuration-source-ownership.i18n.yaml | 4 +- ...26-08-04-configuration-source-ownership.md | 4 +- ...08-04-configuration-source-ownership.zh.md | 4 +- ...-a-provider-from-the-models-page.i18n.yaml | 4 +- ...claring-a-provider-from-the-models-page.md | 2 +- ...ring-a-provider-from-the-models-page.zh.md | 2 +- ...026-08-05-profile-plugin-bundles.i18n.yaml | 4 +- .../2026-08-05-profile-plugin-bundles.md | 2 +- .../2026-08-05-profile-plugin-bundles.zh.md | 2 +- ...08-05-slot-declaration-injection.i18n.yaml | 4 +- .../2026-08-05-slot-declaration-injection.md | 4 +- ...026-08-05-slot-declaration-injection.zh.md | 4 +- ...ubagent-list-identity-projection.i18n.yaml | 4 +- ...08-06-subagent-list-identity-projection.md | 2 +- ...06-subagent-list-identity-projection.zh.md | 2 +- ...hared-feedback-telemetry-user-id.i18n.yaml | 4 +- ...08-07-shared-feedback-telemetry-user-id.md | 4 +- ...07-shared-feedback-telemetry-user-id.zh.md | 4 +- ...08-08-per-preset-standing-mounts.i18n.yaml | 4 +- .../2026-08-08-per-preset-standing-mounts.md | 8 +- ...026-08-08-per-preset-standing-mounts.zh.md | 8 +- ...lient-conversation-node-assembly.i18n.yaml | 4 +- ...08-09-client-conversation-node-assembly.md | 2 +- ...09-client-conversation-node-assembly.zh.md | 2 +- ...headless-direct-core-entry-point.i18n.yaml | 4 +- ...-08-09-headless-direct-core-entry-point.md | 2 +- ...-09-headless-direct-core-entry-point.zh.md | 2 +- ...026-08-09-layered-skill-registry.i18n.yaml | 4 +- .../2026-08-09-layered-skill-registry.md | 4 +- .../2026-08-09-layered-skill-registry.zh.md | 4 +- ...8-10-fork-children-stay-one-shot.i18n.yaml | 4 +- .../2026-08-10-fork-children-stay-one-shot.md | 6 +- ...26-08-10-fork-children-stay-one-shot.zh.md | 6 +- ...st-plane-ownership-after-presets.i18n.yaml | 4 +- ...8-10-host-plane-ownership-after-presets.md | 4 +- ...0-host-plane-ownership-after-presets.zh.md | 4 +- ...6-08-10-message-feedback-sidecar.i18n.yaml | 4 +- .../2026-08-10-message-feedback-sidecar.md | 2 +- .../2026-08-10-message-feedback-sidecar.zh.md | 2 +- ...2026-08-10-remote-event-delivery.i18n.yaml | 4 +- .../2026-08-10-remote-event-delivery.md | 36 +- .../2026-08-10-remote-event-delivery.zh.md | 36 +- .../2026-08-11-plugin-settings-tabs.i18n.yaml | 4 +- .../2026-08-11-plugin-settings-tabs.md | 10 +- .../2026-08-11-plugin-settings-tabs.zh.md | 10 +- ...20-error-cause-chain-diagnostics.i18n.yaml | 4 +- ...026-07-20-error-cause-chain-diagnostics.md | 2 +- ...-07-20-error-cause-chain-diagnostics.zh.md | 2 +- ...ction-summary-prefix-cache-reuse.i18n.yaml | 4 +- ...1-compaction-summary-prefix-cache-reuse.md | 12 +- ...ompaction-summary-prefix-cache-reuse.zh.md | 12 +- ...mpty-model-response-is-retryable.i18n.yaml | 4 +- ...07-24-empty-model-response-is-retryable.md | 2 +- ...24-empty-model-response-is-retryable.zh.md | 2 +- ...2026-07-28-web-gui-feedback-loop.i18n.yaml | 2 +- .../2026-07-28-web-gui-feedback-loop.md | 2 +- ...9-human-transcript-append-origin.i18n.yaml | 4 +- ...26-07-29-human-transcript-append-origin.md | 12 +- ...07-29-human-transcript-append-origin.zh.md | 12 +- ...select-custom-answer-composition.i18n.yaml | 2 +- ...-multi-select-custom-answer-composition.md | 2 +- ...ranscript-log-ordered-projection.i18n.yaml | 4 +- ...0-web-transcript-log-ordered-projection.md | 16 +- ...eb-transcript-log-ordered-projection.zh.md | 16 +- ...-fork-anchor-floors-to-event-seq.i18n.yaml | 4 +- ...6-07-31-fork-anchor-floors-to-event-seq.md | 2 +- ...7-31-fork-anchor-floors-to-event-seq.zh.md | 2 +- ...same-basename-workspace-adoption.i18n.yaml | 4 +- ...-07-31-same-basename-workspace-adoption.md | 2 +- ...-31-same-basename-workspace-adoption.zh.md | 2 +- ...-ask-user-delegated-caller-guard.i18n.yaml | 4 +- ...6-08-01-ask-user-delegated-caller-guard.md | 6 +- ...8-01-ask-user-delegated-caller-guard.zh.md | 6 +- ...3-cli-signal-shutdown-escalation.i18n.yaml | 2 +- ...26-08-03-cli-signal-shutdown-escalation.md | 2 +- ...3-hmr-initial-scan-boot-deadlock.i18n.yaml | 2 +- ...26-08-03-hmr-initial-scan-boot-deadlock.md | 2 +- ...ontext-meter-blind-to-compaction.i18n.yaml | 4 +- ...08-05-context-meter-blind-to-compaction.md | 4 +- ...05-context-meter-blind-to-compaction.zh.md | 4 +- ...-08-06-api-key-format-validation.i18n.yaml | 4 +- .../2026-08-06-api-key-format-validation.md | 8 +- ...2026-08-06-api-key-format-validation.zh.md | 8 +- ...8-06-host-backed-web-preferences.i18n.yaml | 4 +- .../2026-08-06-host-backed-web-preferences.md | 2 +- ...26-08-06-host-backed-web-preferences.zh.md | 2 +- ...rding-step-owned-takeover-chrome.i18n.yaml | 4 +- ...6-onboarding-step-owned-takeover-chrome.md | 2 +- ...nboarding-step-owned-takeover-chrome.zh.md | 2 +- ...e-unpriced-replace-compatibility.i18n.yaml | 4 +- ...-surface-unpriced-replace-compatibility.md | 2 +- ...rface-unpriced-replace-compatibility.zh.md | 2 +- ...07-cancel-convergence-wake-latch.i18n.yaml | 2 +- ...026-08-07-cancel-convergence-wake-latch.md | 2 +- ...8-07-code-mode-executor-collapse.i18n.yaml | 4 +- .../2026-08-07-code-mode-executor-collapse.md | 4 +- ...26-08-07-code-mode-executor-collapse.zh.md | 4 +- ...9-filesystem-absence-observation.i18n.yaml | 4 +- ...26-08-09-filesystem-absence-observation.md | 4 +- ...08-09-filesystem-absence-observation.zh.md | 4 +- ...-agents-join-their-parent-preset.i18n.yaml | 4 +- ...0-child-agents-join-their-parent-preset.md | 6 +- ...hild-agents-join-their-parent-preset.zh.md | 6 +- ...08-10-pre-plugin-theme-bootstrap.i18n.yaml | 4 +- .../2026-08-10-pre-plugin-theme-bootstrap.md | 10 +- ...026-08-10-pre-plugin-theme-bootstrap.zh.md | 10 +- ...sh-catalog-follows-preset-switch.i18n.yaml | 4 +- ...-10-slash-catalog-follows-preset-switch.md | 10 +- ...-slash-catalog-follows-preset-switch.zh.md | 10 +- ...ounded-background-job-admission.i18n.yaml} | 6 +- ...08-11-bounded-background-job-admission.md} | 24 +- ...11-bounded-background-job-admission.zh.md} | 20 +- ...nt-validates-its-own-composition.i18n.yaml | 4 +- ...ing-agent-validates-its-own-composition.md | 8 +- ...-agent-validates-its-own-composition.zh.md | 8 +- ...chronous-subprocess-exit-cleanup.i18n.yaml | 4 +- ...-11-synchronous-subprocess-exit-cleanup.md | 2 +- ...-synchronous-subprocess-exit-cleanup.zh.md | 2 +- .../2026-06-14-acp-multi-session.i18n.yaml | 4 +- .../feature/2026-06-14-acp-multi-session.md | 10 +- .../2026-06-14-acp-multi-session.zh.md | 4 +- .../feature/2026-06-15-code-mode.i18n.yaml | 4 +- .../feature/2026-06-15-code-mode.md | 10 +- .../feature/2026-06-15-code-mode.zh.md | 10 +- ...26-06-17-filesystem-tool-schemas.i18n.yaml | 4 +- .../2026-06-17-filesystem-tool-schemas.md | 12 +- .../2026-06-17-filesystem-tool-schemas.zh.md | 12 +- ...06-18-compaction-capability-seam.i18n.yaml | 4 +- .../2026-06-18-compaction-capability-seam.md | 58 +- ...026-06-18-compaction-capability-seam.zh.md | 60 +- ...6-06-21-subagent-capability-seam.i18n.yaml | 4 +- .../2026-06-21-subagent-capability-seam.md | 16 +- .../2026-06-21-subagent-capability-seam.zh.md | 16 +- .../2026-06-24-workspace-context.i18n.yaml | 4 +- .../feature/2026-06-24-workspace-context.md | 10 +- .../2026-06-24-workspace-context.zh.md | 10 +- .../feature/2026-06-30-hook-bridges.i18n.yaml | 4 +- .../feature/2026-06-30-hook-bridges.md | 6 +- .../feature/2026-06-30-hook-bridges.zh.md | 6 +- .../2026-06-30-hook-protocol-lib.i18n.yaml | 4 +- .../feature/2026-06-30-hook-protocol-lib.md | 6 +- .../2026-06-30-hook-protocol-lib.zh.md | 6 +- ...30-interception-extension-points.i18n.yaml | 4 +- ...026-06-30-interception-extension-points.md | 4 +- ...-06-30-interception-extension-points.zh.md | 4 +- ...026-06-30-session-store-fork-api.i18n.yaml | 4 +- .../2026-06-30-session-store-fork-api.md | 4 +- .../2026-06-30-session-store-fork-api.zh.md | 4 +- .../2026-07-05-dynamic-workflows.i18n.yaml | 4 +- .../feature/2026-07-05-dynamic-workflows.md | 10 +- .../2026-07-05-dynamic-workflows.zh.md | 8 +- .../feature/2026-07-05-skill-system.i18n.yaml | 4 +- .../feature/2026-07-05-skill-system.md | 4 +- .../feature/2026-07-05-skill-system.zh.md | 4 +- .../2026-07-06-approval-seam.i18n.yaml | 4 +- .../feature/2026-07-06-approval-seam.md | 4 +- .../feature/2026-07-06-approval-seam.zh.md | 4 +- .../2026-07-06-explicit-tool-order.i18n.yaml | 4 +- .../feature/2026-07-06-explicit-tool-order.md | 4 +- .../2026-07-06-explicit-tool-order.zh.md | 4 +- .../feature/2026-07-06-sandbox.i18n.yaml | 4 +- .../implemented/feature/2026-07-06-sandbox.md | 26 +- .../feature/2026-07-06-sandbox.zh.md | 26 +- .../2026-07-07-mcp-client-plugin.i18n.yaml | 4 +- .../feature/2026-07-07-mcp-client-plugin.md | 6 +- .../2026-07-07-mcp-client-plugin.zh.md | 6 +- ...-07-08-background-subagent-tasks.i18n.yaml | 4 +- .../2026-07-08-background-subagent-tasks.md | 14 +- ...2026-07-08-background-subagent-tasks.zh.md | 8 +- ...-self-referential-cordis-toolset.i18n.yaml | 4 +- ...6-07-08-self-referential-cordis-toolset.md | 10 +- ...7-08-self-referential-cordis-toolset.zh.md | 10 +- ...ession-identity-and-log-location.i18n.yaml | 4 +- ...agent-session-identity-and-log-location.md | 8 +- ...nt-session-identity-and-log-location.zh.md | 8 +- ...-10-parallel-tool-call-execution.i18n.yaml | 4 +- ...2026-07-10-parallel-tool-call-execution.md | 2 +- ...6-07-10-parallel-tool-call-execution.zh.md | 2 +- ...nt-persona-tool-filter-and-depth.i18n.yaml | 4 +- ...-subagent-persona-tool-filter-and-depth.md | 6 +- ...bagent-persona-tool-filter-and-depth.zh.md | 6 +- ...26-07-14-cross-family-fs-sandbox.i18n.yaml | 4 +- .../2026-07-14-cross-family-fs-sandbox.md | 20 +- .../2026-07-14-cross-family-fs-sandbox.zh.md | 20 +- .../2026-07-16-harness-level-loop.i18n.yaml | 4 +- .../feature/2026-07-16-harness-level-loop.md | 10 +- .../2026-07-16-harness-level-loop.zh.md | 4 +- ...26-07-16-persistent-pty-sessions.i18n.yaml | 4 +- .../2026-07-16-persistent-pty-sessions.md | 42 +- .../2026-07-16-persistent-pty-sessions.zh.md | 42 +- ...-fresh-agent-ralph-workflow-tool.i18n.yaml | 4 +- ...6-07-19-fresh-agent-ralph-workflow-tool.md | 2 +- ...7-19-fresh-agent-ralph-workflow-tool.zh.md | 2 +- ...9-same-session-goal-round-driver.i18n.yaml | 4 +- ...26-07-19-same-session-goal-round-driver.md | 2 +- ...07-19-same-session-goal-round-driver.zh.md | 2 +- ...-20-code-mode-typed-tool-returns.i18n.yaml | 4 +- ...2026-07-20-code-mode-typed-tool-returns.md | 8 +- ...6-07-20-code-mode-typed-tool-returns.zh.md | 8 +- ...26-07-20-dsh-cli-personal-config.i18n.yaml | 4 +- .../2026-07-20-dsh-cli-personal-config.md | 4 +- .../2026-07-20-dsh-cli-personal-config.zh.md | 4 +- ...continuable-background-subagents.i18n.yaml | 4 +- ...-07-21-continuable-background-subagents.md | 46 +- ...-21-continuable-background-subagents.zh.md | 36 +- ...6-07-21-cross-session-references.i18n.yaml | 4 +- .../2026-07-21-cross-session-references.md | 6 +- .../2026-07-21-cross-session-references.zh.md | 6 +- ...7-21-follow-instruction-symlinks.i18n.yaml | 4 +- .../2026-07-21-follow-instruction-symlinks.md | 6 +- ...26-07-21-follow-instruction-symlinks.zh.md | 6 +- ...07-21-instruction-load-all-dedup.i18n.yaml | 4 +- .../2026-07-21-instruction-load-all-dedup.md | 2 +- ...026-07-21-instruction-load-all-dedup.zh.md | 2 +- ...-07-21-local-instruction-overlay.i18n.yaml | 4 +- .../2026-07-21-local-instruction-overlay.md | 2 +- ...2026-07-21-local-instruction-overlay.zh.md | 2 +- ...-07-21-log-backed-session-titles.i18n.yaml | 2 +- .../2026-07-21-log-backed-session-titles.md | 10 +- ...subagent-catalog-and-list-agents.i18n.yaml | 4 +- ...urable-subagent-catalog-and-list-agents.md | 14 +- ...ble-subagent-catalog-and-list-agents.zh.md | 14 +- ...ge-input-and-durable-attachments.i18n.yaml | 4 +- ...dal-image-input-and-durable-attachments.md | 8 +- ...-image-input-and-durable-attachments.zh.md | 8 +- ...3-session-telemetry-otel-revival.i18n.yaml | 4 +- ...26-07-23-session-telemetry-otel-revival.md | 6 +- ...07-23-session-telemetry-otel-revival.zh.md | 6 +- ...7-23-web-permission-and-approval.i18n.yaml | 4 +- .../2026-07-23-web-permission-and-approval.md | 10 +- ...26-07-23-web-permission-and-approval.zh.md | 10 +- .../2026-07-23-web-todo-display.i18n.yaml | 4 +- .../feature/2026-07-23-web-todo-display.md | 2 +- .../feature/2026-07-23-web-todo-display.zh.md | 2 +- ...07-24-web-session-model-selector.i18n.yaml | 4 +- .../2026-07-24-web-session-model-selector.md | 4 +- ...026-07-24-web-session-model-selector.zh.md | 4 +- ...-07-25-workspace-ui-product-flow.i18n.yaml | 4 +- .../2026-07-25-workspace-ui-product-flow.md | 2 +- ...2026-07-25-workspace-ui-product-flow.zh.md | 2 +- ...7-26-code-dispatch-ui-foundation.i18n.yaml | 2 +- .../2026-07-26-code-dispatch-ui-foundation.md | 2 +- ...ative-workspace-directory-picker.i18n.yaml | 4 +- ...07-27-native-workspace-directory-picker.md | 2 +- ...27-native-workspace-directory-picker.zh.md | 2 +- ...-07-27-skill-catalog-hot-refresh.i18n.yaml | 4 +- .../2026-07-27-skill-catalog-hot-refresh.md | 2 +- ...2026-07-27-skill-catalog-hot-refresh.zh.md | 2 +- ...2026-07-27-tmux-location-context.i18n.yaml | 4 +- .../2026-07-27-tmux-location-context.md | 6 +- .../2026-07-27-tmux-location-context.zh.md | 6 +- ...ipt-sdk-and-sdk-subagent-backend.i18n.yaml | 4 +- ...typescript-sdk-and-sdk-subagent-backend.md | 8 +- ...escript-sdk-and-sdk-subagent-backend.zh.md | 8 +- .../2026-07-27-web-session-search.i18n.yaml | 4 +- .../feature/2026-07-27-web-session-search.md | 2 +- .../2026-07-27-web-session-search.zh.md | 2 +- ...07-27-web-subagent-conversations.i18n.yaml | 4 +- .../2026-07-27-web-subagent-conversations.md | 4 +- ...026-07-27-web-subagent-conversations.zh.md | 4 +- ...-workspace-registration-deletion.i18n.yaml | 4 +- ...6-07-27-workspace-registration-deletion.md | 2 +- ...7-27-workspace-registration-deletion.zh.md | 2 +- ...ntinuable-subagent-conversations.i18n.yaml | 4 +- ...7-28-continuable-subagent-conversations.md | 10 +- ...8-continuable-subagent-conversations.zh.md | 4 +- .../2026-07-28-feedback-command.i18n.yaml | 4 +- .../feature/2026-07-28-feedback-command.md | 4 +- .../feature/2026-07-28-feedback-command.zh.md | 4 +- ...2026-07-28-sdk-max-output-tokens.i18n.yaml | 4 +- .../2026-07-28-sdk-max-output-tokens.md | 4 +- .../2026-07-28-sdk-max-output-tokens.zh.md | 4 +- ...-07-28-tool-call-file-open-in-os.i18n.yaml | 4 +- .../2026-07-28-tool-call-file-open-in-os.md | 2 +- ...2026-07-28-tool-call-file-open-in-os.zh.md | 2 +- ...29-ask-question-web-presentation.i18n.yaml | 4 +- ...026-07-29-ask-question-web-presentation.md | 4 +- ...-07-29-ask-question-web-presentation.zh.md | 4 +- ...rsistent-bash-str-replace-editor.i18n.yaml | 4 +- ...7-29-persistent-bash-str-replace-editor.md | 2 +- ...9-persistent-bash-str-replace-editor.zh.md | 2 +- ...continuable-subagent-report-tool.i18n.yaml | 2 +- ...-07-30-continuable-subagent-report-tool.md | 2 +- ...seek-onboarding-credential-setup.i18n.yaml | 4 +- ...30-deepseek-onboarding-credential-setup.md | 4 +- ...deepseek-onboarding-credential-setup.zh.md | 4 +- ...-plan-review-presentation-intent.i18n.yaml | 4 +- ...6-07-30-plan-review-presentation-intent.md | 12 +- ...7-30-plan-review-presentation-intent.zh.md | 12 +- ...6-07-30-queued-manual-compaction.i18n.yaml | 4 +- .../2026-07-30-queued-manual-compaction.md | 28 +- .../2026-07-30-queued-manual-compaction.zh.md | 28 +- ...versioned-gui-welcome-onboarding.i18n.yaml | 4 +- ...-07-30-versioned-gui-welcome-onboarding.md | 2 +- ...-30-versioned-gui-welcome-onboarding.zh.md | 2 +- .../2026-07-30-web-read-card.i18n.yaml | 4 +- .../feature/2026-07-30-web-read-card.md | 2 +- .../feature/2026-07-30-web-read-card.zh.md | 2 +- ...1-browser-derived-initial-locale.i18n.yaml | 4 +- ...26-07-31-browser-derived-initial-locale.md | 4 +- ...07-31-browser-derived-initial-locale.zh.md | 4 +- ...7-31-code-mode-language-dispatch.i18n.yaml | 4 +- .../2026-07-31-code-mode-language-dispatch.md | 6 +- ...26-07-31-code-mode-language-dispatch.zh.md | 6 +- ...31-even-out-shipped-tool-rosters.i18n.yaml | 4 +- ...026-07-31-even-out-shipped-tool-rosters.md | 10 +- ...-07-31-even-out-shipped-tool-rosters.zh.md | 10 +- ...-31-gui-full-access-confirmation.i18n.yaml | 4 +- ...2026-07-31-gui-full-access-confirmation.md | 2 +- ...6-07-31-gui-full-access-confirmation.zh.md | 2 +- ...mission-default-for-new-sessions.i18n.yaml | 4 +- ...-31-permission-default-for-new-sessions.md | 4 +- ...-permission-default-for-new-sessions.zh.md | 4 +- ...07-31-session-archive-global-set.i18n.yaml | 4 +- .../2026-07-31-session-archive-global-set.md | 2 +- ...026-07-31-session-archive-global-set.zh.md | 2 +- ...7-31-telemetry-anonymous-user-id.i18n.yaml | 4 +- .../2026-07-31-telemetry-anonymous-user-id.md | 18 +- ...26-07-31-telemetry-anonymous-user-id.zh.md | 16 +- .../2026-07-31-web-default-search.i18n.yaml | 4 +- .../feature/2026-07-31-web-default-search.md | 6 +- .../2026-07-31-web-default-search.zh.md | 4 +- ...7-31-web-telemetry-default-mount.i18n.yaml | 4 +- .../2026-07-31-web-telemetry-default-mount.md | 4 +- ...26-07-31-web-telemetry-default-mount.zh.md | 4 +- ...-workspace-write-surface-default.i18n.yaml | 4 +- ...6-07-31-workspace-write-surface-default.md | 2 +- ...7-31-workspace-write-surface-default.zh.md | 2 +- ...026-08-01-pwsh-tool-and-executor.i18n.yaml | 4 +- .../2026-08-01-pwsh-tool-and-executor.md | 10 +- .../2026-08-01-pwsh-tool-and-executor.zh.md | 8 +- .../2026-08-01-windows-pwsh-default.i18n.yaml | 4 +- .../2026-08-01-windows-pwsh-default.md | 6 +- .../2026-08-01-windows-pwsh-default.zh.md | 6 +- ...2026-08-02-pwsh-tool-bash-parity.i18n.yaml | 4 +- .../2026-08-02-pwsh-tool-bash-parity.md | 12 +- .../2026-08-02-pwsh-tool-bash-parity.zh.md | 12 +- .../2026-08-03-fs-tool-error-remedy.i18n.yaml | 4 +- .../2026-08-03-fs-tool-error-remedy.md | 2 +- .../2026-08-03-fs-tool-error-remedy.zh.md | 2 +- ...b-context-source-and-steer-marks.i18n.yaml | 4 +- ...8-04-web-context-source-and-steer-marks.md | 2 +- ...4-web-context-source-and-steer-marks.zh.md | 2 +- ...26-08-05-context-form-vocabulary.i18n.yaml | 4 +- .../2026-08-05-context-form-vocabulary.md | 6 +- .../2026-08-05-context-form-vocabulary.zh.md | 6 +- .../2026-08-05-durable-web-schedule.i18n.yaml | 4 +- .../2026-08-05-durable-web-schedule.md | 4 +- .../2026-08-05-durable-web-schedule.zh.md | 4 +- ...feedback-gated-session-telemetry.i18n.yaml | 4 +- ...-08-05-feedback-gated-session-telemetry.md | 2 +- ...-05-feedback-gated-session-telemetry.zh.md | 2 +- ...8-05-per-agent-tool-presentation.i18n.yaml | 4 +- .../2026-08-05-per-agent-tool-presentation.md | 6 +- ...26-08-05-per-agent-tool-presentation.zh.md | 6 +- .../2026-08-05-pwsh-ui-bash-parity.i18n.yaml | 4 +- .../feature/2026-08-05-pwsh-ui-bash-parity.md | 10 +- .../2026-08-05-pwsh-ui-bash-parity.zh.md | 8 +- ...26-08-06-bundled-dsh-badge-skill.i18n.yaml | 4 +- .../2026-08-06-bundled-dsh-badge-skill.md | 2 +- .../2026-08-06-bundled-dsh-badge-skill.zh.md | 2 +- ...6-continuable-subagent-interrupt.i18n.yaml | 4 +- ...26-08-06-continuable-subagent-interrupt.md | 4 +- ...08-06-continuable-subagent-interrupt.zh.md | 4 +- ...ned-subagent-settlement-delivery.i18n.yaml | 4 +- ...ager-owned-subagent-settlement-delivery.md | 4 +- ...r-owned-subagent-settlement-delivery.zh.md | 4 +- .../2026-08-06-web-install-manifest.i18n.yaml | 4 +- .../2026-08-06-web-install-manifest.md | 4 +- .../2026-08-06-web-install-manifest.zh.md | 4 +- ...default-model-follows-the-picker.i18n.yaml | 4 +- ...-08-07-default-model-follows-the-picker.md | 4 +- ...-07-default-model-follows-the-picker.zh.md | 4 +- ...knowledgement-sharing-disclosure.i18n.yaml | 4 +- ...back-acknowledgement-sharing-disclosure.md | 4 +- ...k-acknowledgement-sharing-disclosure.zh.md | 4 +- ...8-user-explicit-skill-invocation.i18n.yaml | 4 +- ...26-08-08-user-explicit-skill-invocation.md | 2 +- ...08-08-user-explicit-skill-invocation.zh.md | 2 +- ...8-08-web-background-job-display.i18n.yaml} | 6 +- .../2026-08-08-web-background-job-display.md | 136 ++ ...026-08-08-web-background-job-display.zh.md | 136 ++ .../2026-08-08-web-background-task-display.md | 136 -- ...26-08-08-web-background-task-display.zh.md | 136 -- ...ows-acl-restricted-token-sandbox.i18n.yaml | 4 +- ...08-windows-acl-restricted-token-sandbox.md | 2 +- ...windows-acl-restricted-token-sandbox.zh.md | 2 +- ...09-parallel-subagent-delegations.i18n.yaml | 4 +- ...026-08-09-parallel-subagent-delegations.md | 4 +- ...-08-09-parallel-subagent-delegations.zh.md | 4 +- ...able-subagent-policy-inheritance.i18n.yaml | 4 +- ...continuable-subagent-policy-inheritance.md | 2 +- ...tinuable-subagent-policy-inheritance.zh.md | 2 +- ...2026-08-10-telemetry-default-off.i18n.yaml | 2 +- .../2026-08-10-telemetry-default-off.md | 2 +- ...6-08-10-web-plugin-configuration.i18n.yaml | 4 +- .../2026-08-10-web-plugin-configuration.md | 4 +- .../2026-08-10-web-plugin-configuration.zh.md | 4 +- ...und-first-continuable-delegation.i18n.yaml | 2 +- ...background-first-continuable-delegation.md | 2 +- ...-completion-wakes-an-idle-owner.i18n.yaml} | 6 +- ...und-job-completion-wakes-an-idle-owner.md} | 30 +- ...-job-completion-wakes-an-idle-owner.zh.md} | 24 +- ...-deepseek-request-user-id-header.i18n.yaml | 4 +- ...6-08-11-deepseek-request-user-id-header.md | 10 +- ...8-11-deepseek-request-user-id-header.zh.md | 8 +- ...-11-message-feedback-web-surface.i18n.yaml | 4 +- ...2026-08-11-message-feedback-web-surface.md | 4 +- ...6-08-11-message-feedback-web-surface.zh.md | 4 +- ...l-profiles-bare-two-tool-runtime.i18n.yaml | 4 +- ...-minimal-profiles-bare-two-tool-runtime.md | 4 +- ...nimal-profiles-bare-two-tool-runtime.zh.md | 4 +- ...11-web-export-command-and-dialog.i18n.yaml | 4 +- ...026-08-11-web-export-command-and-dialog.md | 2 +- ...-08-11-web-export-command-and-dialog.zh.md | 2 +- .../2026-06-17-ts-build-config.i18n.yaml | 4 +- .../process/2026-06-17-ts-build-config.md | 4 +- .../process/2026-06-17-ts-build-config.zh.md | 4 +- ...-20-core-data-structures-catalog.i18n.yaml | 4 +- ...2026-06-20-core-data-structures-catalog.md | 6 +- ...6-06-20-core-data-structures-catalog.zh.md | 6 +- .../2026-07-02-tool-schema-catalog.i18n.yaml | 4 +- .../process/2026-07-02-tool-schema-catalog.md | 4 +- .../2026-07-02-tool-schema-catalog.zh.md | 4 +- ...rial-cross-platform-ci-reference.i18n.yaml | 4 +- ...7-21-serial-cross-platform-ci-reference.md | 4 +- ...1-serial-cross-platform-ci-reference.zh.md | 4 +- .../2026-07-22-fast-local-git-hooks.i18n.yaml | 4 +- .../2026-07-22-fast-local-git-hooks.md | 2 +- .../2026-07-22-fast-local-git-hooks.zh.md | 2 +- ...subsystem-cordis-surface-regions.i18n.yaml | 4 +- ...28-per-subsystem-cordis-surface-regions.md | 6 +- ...per-subsystem-cordis-surface-regions.zh.md | 6 +- ...30-generated-third-party-notices.i18n.yaml | 4 +- ...026-07-30-generated-third-party-notices.md | 2 +- ...-07-30-generated-third-party-notices.zh.md | 2 +- ...remotes-generated-contract-build.i18n.yaml | 4 +- ...08-api-remotes-generated-contract-build.md | 12 +- ...api-remotes-generated-contract-build.zh.md | 12 +- ...08-unified-github-label-taxonomy.i18n.yaml | 2 +- ...026-08-08-unified-github-label-taxonomy.md | 2 +- ...2026-08-10-npm-release-sequences.i18n.yaml | 4 +- .../2026-08-10-npm-release-sequences.md | 4 +- .../2026-08-10-npm-release-sequences.zh.md | 4 +- .../2026-06-26-fsspec-style-fs-seam.i18n.yaml | 4 +- .../2026-06-26-fsspec-style-fs-seam.md | 22 +- .../2026-06-26-fsspec-style-fs-seam.zh.md | 22 +- ...4-tighten-hook-protocol-contract.i18n.yaml | 4 +- ...26-07-04-tighten-hook-protocol-contract.md | 4 +- ...07-04-tighten-hook-protocol-contract.zh.md | 4 +- ...-20-remove-stdio-and-echo-agents.i18n.yaml | 4 +- ...2026-07-20-remove-stdio-and-echo-agents.md | 2 +- ...6-07-20-remove-stdio-and-echo-agents.zh.md | 2 +- ...nwrap-injected-content-envelopes.i18n.yaml | 4 +- ...07-20-unwrap-injected-content-envelopes.md | 8 +- ...20-unwrap-injected-content-envelopes.zh.md | 8 +- ...lan-specific-collaboration-state.i18n.yaml | 2 +- ...07-22-plan-specific-collaboration-state.md | 6 +- ...-23-acp-automation-only-protocol.i18n.yaml | 2 +- ...2026-07-23-acp-automation-only-protocol.md | 4 +- ...6-merge-subagent-control-service.i18n.yaml | 4 +- ...26-07-26-merge-subagent-control-service.md | 10 +- ...07-26-merge-subagent-control-service.zh.md | 4 +- ...subagent-continuation-operations.i18n.yaml | 4 +- ...-named-subagent-continuation-operations.md | 4 +- ...med-subagent-continuation-operations.zh.md | 4 +- ...07-27-request-error-retry-action.i18n.yaml | 4 +- .../2026-07-27-request-error-retry-action.md | 2 +- ...026-07-27-request-error-retry-action.zh.md | 2 +- ...-remove-synthetic-log-only-turns.i18n.yaml | 4 +- ...6-07-28-remove-synthetic-log-only-turns.md | 2 +- ...7-28-remove-synthetic-log-only-turns.zh.md | 2 +- ...7-29-shared-base-config-overlays.i18n.yaml | 4 +- .../2026-07-29-shared-base-config-overlays.md | 4 +- ...26-07-29-shared-base-config-overlays.zh.md | 4 +- ...-31-one-route-to-add-a-workspace.i18n.yaml | 4 +- ...2026-07-31-one-route-to-add-a-workspace.md | 2 +- ...6-07-31-one-route-to-add-a-workspace.zh.md | 2 +- .../2026-08-04-remove-tui-package.i18n.yaml | 4 +- .../2026-08-04-remove-tui-package.md | 4 +- .../2026-08-04-remove-tui-package.zh.md | 2 +- ...6-buffer-free-feedback-telemetry.i18n.yaml | 4 +- ...26-08-06-buffer-free-feedback-telemetry.md | 2 +- ...08-06-buffer-free-feedback-telemetry.zh.md | 2 +- ...conversational-schedule-delivery.i18n.yaml | 4 +- ...-08-09-conversational-schedule-delivery.md | 2 +- ...-09-conversational-schedule-delivery.zh.md | 2 +- ...6-08-09-remove-repository-plugin.i18n.yaml | 4 +- .../2026-08-09-remove-repository-plugin.md | 4 +- .../2026-08-09-remove-repository-plugin.zh.md | 4 +- ...-11-remove-sdk-project-toolchain.i18n.yaml | 4 +- ...2026-08-11-remove-sdk-project-toolchain.md | 4 +- ...6-08-11-remove-sdk-project-toolchain.zh.md | 4 +- ...eparate-source-launch-from-build.i18n.yaml | 4 +- ...08-12-separate-source-launch-from-build.md | 2 +- ...12-separate-source-launch-from-build.zh.md | 2 +- .../2026-06-19-acp-snapshot-tests.i18n.yaml | 4 +- .../testing/2026-06-19-acp-snapshot-tests.md | 2 +- .../2026-06-19-acp-snapshot-tests.zh.md | 2 +- ...-fork-child-replay-seed-boundary.i18n.yaml | 4 +- ...6-06-22-fork-child-replay-seed-boundary.md | 4 +- ...6-22-fork-child-replay-seed-boundary.zh.md | 4 +- ...6-06-22-subagent-snapshot-replay.i18n.yaml | 4 +- .../2026-06-22-subagent-snapshot-replay.md | 4 +- .../2026-06-22-subagent-snapshot-replay.zh.md | 4 +- ...6-07-24-web-gui-browser-e2e-lane.i18n.yaml | 4 +- .../2026-07-24-web-gui-browser-e2e-lane.md | 10 +- .../2026-07-24-web-gui-browser-e2e-lane.zh.md | 10 +- .../2026-06-16-typed-event-schemas.i18n.yaml | 4 +- .../2026-06-16-typed-event-schemas.md | 2 +- .../2026-06-16-typed-event-schemas.zh.md | 2 +- ...on-through-tool-capability-seams.i18n.yaml | 2 +- ...cellation-through-tool-capability-seams.md | 2 +- ...-domain-kv-storage-and-workspace.i18n.yaml | 4 +- ...6-07-24-domain-kv-storage-and-workspace.md | 12 +- ...7-24-domain-kv-storage-and-workspace.zh.md | 12 +- ...-25-client-settings-locale-theme.i18n.yaml | 4 +- ...2026-07-25-client-settings-locale-theme.md | 12 +- ...6-07-25-client-settings-locale-theme.zh.md | 12 +- ...ssion-projection-and-command-log.i18n.yaml | 4 +- ...7-27-session-projection-and-command-log.md | 2 +- ...7-session-projection-and-command-log.zh.md | 2 +- ...root-and-derived-medium-recovery.i18n.yaml | 4 +- ...torage-root-and-derived-medium-recovery.md | 2 +- ...age-root-and-derived-medium-recovery.zh.md | 2 +- ...-unary-apiproxy-remote-migration.i18n.yaml | 4 +- ...6-08-10-unary-apiproxy-remote-migration.md | 26 +- ...8-10-unary-apiproxy-remote-migration.zh.md | 26 +- ...aming-contract-and-rename-ledger.i18n.yaml | 4 +- ...itory-naming-contract-and-rename-ledger.md | 34 +- ...ry-naming-contract-and-rename-ledger.zh.md | 34 +- ...2026-07-06-recallable-compaction.i18n.yaml | 4 +- .../2026-07-06-recallable-compaction.md | 18 +- .../2026-07-06-recallable-compaction.zh.md | 18 +- .../feature/2026-08-04-task-surface.i18n.yaml | 2 +- .../feature/2026-08-04-task-surface.md | 2 +- ...-07-04-prune-dead-core-spine-api.i18n.yaml | 4 +- .../2026-07-04-prune-dead-core-spine-api.md | 12 +- ...2026-07-04-prune-dead-core-spine-api.zh.md | 12 +- ...6-07-19-make-jsonrpc-directional.i18n.yaml | 4 +- .../2026-07-19-make-jsonrpc-directional.md | 2 +- .../2026-07-19-make-jsonrpc-directional.zh.md | 2 +- ...-20-drop-bash-output-spill-files.i18n.yaml | 2 +- ...2026-06-20-drop-bash-output-spill-files.md | 4 +- ...-prune-unused-skill-registry-api.i18n.yaml | 4 +- ...6-07-12-prune-unused-skill-registry-api.md | 2 +- ...7-12-prune-unused-skill-registry-api.zh.md | 2 +- ...19-fold-compaction-package-split.i18n.yaml | 4 +- ...026-07-19-fold-compaction-package-split.md | 12 +- ...-07-19-fold-compaction-package-split.zh.md | 12 +- ...-promises-for-hand-rolled-sleeps.i18n.yaml | 4 +- ...n-timer-promises-for-hand-rolled-sleeps.md | 12 +- ...imer-promises-for-hand-rolled-sleeps.zh.md | 12 +- ...ency-swaps-rejected-by-nih-audit.i18n.yaml | 4 +- ...-dependency-swaps-rejected-by-nih-audit.md | 10 +- ...pendency-swaps-rejected-by-nih-audit.zh.md | 10 +- .../skills/dsh-find-simplifications/SKILL.md | 4 +- .../dsh-prose-standard/references/examples.md | 2 +- .github/workflows/ci.yml | 8 +- .github/workflows/sandbox.yml | 2 +- AGENTS.md | 13 +- README.i18n.yaml | 4 +- README.md | 2 +- README.zh.md | 2 +- apps/cli/README.i18n.yaml | 4 +- apps/cli/README.md | 2 +- apps/cli/README.zh.md | 2 +- apps/cli/composition.md | 106 +- .../agent-presets/code/agent.cordis.yml | 48 +- .../agent-presets/cordis/agent.cordis.yml | 40 +- .../editing-cordis-compositions/SKILL.md | 6 +- .../agent-presets/minimal/agent.cordis.yml | 8 +- .../agent-presets/standard/agent.cordis.yml | 42 +- apps/cli/package.json | 30 +- apps/cli/reference/README.i18n.yaml | 4 +- apps/cli/reference/README.md | 2 +- apps/cli/reference/README.zh.md | 2 +- apps/cli/src/profile-boot.ts | 10 +- apps/cli/tests/fixtures/dsh-badge/cordis.yml | 4 +- .../fixtures/dsh-badge/default.cordis.yml | 4 +- apps/cli/tests/fixtures/dsh-badge/snapshot.ts | 2 +- apps/cli/tests/memory-mcp-configs.spec.ts | 4 +- apps/cli/tests/telemetry-switch.spec.ts | 2 +- apps/cli/tests/web-agent-presets.e2e.ts | 19 +- apps/cli/tsconfig.json | 10 +- apps/web/package.json | 2 +- apps/web/tests/agent-preset-selection.e2e.ts | 2 +- apps/web/tests/assembled-boot.ts | 2 +- ...list.e2e.ts => background-job-list.e2e.ts} | 40 +- apps/web/tests/chat-scroll-contract.e2e.ts | 2 +- apps/web/tests/goal-multi-turn-actions.e2e.ts | 10 +- apps/web/tests/hmr-live.e2e.ts | 4 +- apps/web/tests/lifecycle-chrome.e2e.ts | 2 +- apps/web/tests/minimal-preset.snapshot.ts | 2 +- apps/web/tests/pin-browse-picker.overlay.yml | 4 +- apps/web/tests/pwsh-terminal.overlay.yml | 2 +- apps/web/tests/question-composer.e2e.ts | 2 +- apps/web/tests/scaffold.ts | 30 +- apps/web/tests/schedule-after.e2e.ts | 8 +- apps/web/tests/seeded-history.e2e.ts | 20 +- apps/web/tests/settings-chrome.e2e.ts | 2 +- apps/web/tests/shipped-composition.e2e.ts | 24 +- .../tests/sidebar-subagent-activity.e2e.ts | 6 +- apps/web/tests/smoke-real.e2e.ts | 4 +- .../running.expected.md | 2 +- .../settled.expected.md | 2 +- .../feedback-command/ack.expected.md | 4 +- .../system-prompt.expected.md | 4 +- .../replay.override.json | 4 +- .../goal-multi-turn-actions/session.jsonl | 12 +- .../goal-multi-turn-actions/ui.expected.md | 4 +- .../permission-policy-context/session.jsonl | 2 +- .../seeded-history/feedback-row.expected.md | 6 +- .../owner-running.expected.md | 2 +- .../web-surface-prompt.expected.md | 2 +- apps/web/tests/startup-auto-selection.e2e.ts | 2 +- apps/web/tests/workspace-management.e2e.ts | 32 +- apps/web/tsconfig.json | 2 +- docs/agent-lifecycle.i18n.yaml | 4 +- docs/agent-lifecycle.md | 2 +- docs/agent-lifecycle.zh.md | 2 +- docs/api-gateway.i18n.yaml | 4 +- docs/api-gateway.md | 34 +- docs/api-gateway.zh.md | 34 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 6 +- docs/architecture.zh.md | 6 +- docs/capability-seams.i18n.yaml | 4 +- docs/capability-seams.md | 264 +-- docs/capability-seams.zh.md | 284 +-- docs/config-catalog.i18n.yaml | 4 +- docs/config-catalog.md | 553 +++--- docs/config-catalog.zh.md | 529 +++--- docs/cookbook/adding-a-package.i18n.yaml | 4 +- docs/cookbook/adding-a-package.md | 30 +- docs/cookbook/adding-a-package.zh.md | 30 +- docs/cookbook/adding-a-tool.i18n.yaml | 4 +- docs/cookbook/adding-a-tool.md | 6 +- docs/cookbook/adding-a-tool.zh.md | 6 +- docs/cookbook/adding-an-llm-adapter.i18n.yaml | 4 +- docs/cookbook/adding-an-llm-adapter.md | 2 +- docs/cookbook/adding-an-llm-adapter.zh.md | 2 +- docs/cookbook/extension-cookbook.i18n.yaml | 4 +- docs/cookbook/extension-cookbook.md | 14 +- docs/cookbook/extension-cookbook.zh.md | 12 +- docs/cordis-primer.i18n.yaml | 4 +- docs/cordis-primer.md | 2 +- docs/cordis-primer.zh.md | 2 +- docs/cordis-tutorial/index.i18n.yaml | 4 +- docs/cordis-tutorial/index.md | 2 +- docs/cordis-tutorial/index.zh.md | 2 +- docs/defensive-patterns.i18n.yaml | 4 +- docs/defensive-patterns.md | 4 +- docs/defensive-patterns.zh.md | 2 +- docs/development.i18n.yaml | 4 +- docs/development.md | 8 +- docs/development.zh.md | 8 +- docs/event-producer-consumer.i18n.yaml | 4 +- docs/event-producer-consumer.md | 54 +- docs/event-producer-consumer.zh.md | 54 +- docs/glossary.i18n.yaml | 4 +- docs/glossary.md | 6 +- docs/glossary.zh.md | 6 +- docs/i18n/style-samples.md | 2 +- docs/i18n/terminology.md | 20 +- docs/module-graph.i18n.yaml | 4 +- docs/module-graph.md | 1390 +++++++------- docs/module-graph.zh.md | 1390 +++++++------- docs/persistence-catalog.i18n.yaml | 4 +- docs/persistence-catalog.md | 44 +- docs/persistence-catalog.zh.md | 44 +- ...0003-web-agent-gui-feedback-loop.i18n.yaml | 2 +- .../0003-web-agent-gui-feedback-loop.md | 2 +- ...ice-misclassified-child-failures.i18n.yaml | 4 +- ...ial-notice-misclassified-child-failures.md | 4 +- ...-notice-misclassified-child-failures.zh.md | 4 +- docs/subsystems/README.i18n.yaml | 4 +- docs/subsystems/README.md | 18 +- docs/subsystems/README.zh.md | 18 +- docs/subsystems/approval.i18n.yaml | 4 +- docs/subsystems/approval.md | 2 +- docs/subsystems/approval.zh.md | 2 +- docs/subsystems/client-modules.i18n.yaml | 4 +- docs/subsystems/client-modules.md | 8 +- docs/subsystems/client-modules.zh.md | 8 +- docs/subsystems/code-runtime.i18n.yaml | 4 +- docs/subsystems/code-runtime.md | 2 +- docs/subsystems/code-runtime.zh.md | 2 +- docs/subsystems/commands.i18n.yaml | 4 +- docs/subsystems/commands.md | 4 +- docs/subsystems/commands.zh.md | 4 +- docs/subsystems/compaction.i18n.yaml | 4 +- docs/subsystems/compaction.md | 52 +- docs/subsystems/compaction.zh.md | 52 +- docs/subsystems/core.i18n.yaml | 4 +- docs/subsystems/core.md | 6 +- docs/subsystems/core.zh.md | 6 +- docs/subsystems/credentials.i18n.yaml | 4 +- docs/subsystems/credentials.md | 8 +- docs/subsystems/credentials.zh.md | 8 +- docs/subsystems/feedback.i18n.yaml | 4 +- docs/subsystems/feedback.md | 6 +- docs/subsystems/feedback.zh.md | 6 +- docs/subsystems/filesystem.i18n.yaml | 4 +- docs/subsystems/filesystem.md | 20 +- docs/subsystems/filesystem.zh.md | 20 +- docs/subsystems/invariants.i18n.yaml | 4 +- docs/subsystems/invariants.md | 14 +- docs/subsystems/invariants.zh.md | 14 +- .../{pty.i18n.yaml => jobs.i18n.yaml} | 6 +- docs/subsystems/{tasks.md => jobs.md} | 146 +- docs/subsystems/{tasks.zh.md => jobs.zh.md} | 146 +- docs/subsystems/llm-streaming.i18n.yaml | 4 +- docs/subsystems/llm-streaming.md | 22 +- docs/subsystems/llm-streaming.zh.md | 22 +- docs/subsystems/lsp.i18n.yaml | 4 +- docs/subsystems/lsp.md | 2 +- docs/subsystems/lsp.zh.md | 2 +- docs/subsystems/permission-presets.i18n.yaml | 6 + .../{permission.md => permission-presets.md} | 20 +- ...mission.zh.md => permission-presets.zh.md} | 20 +- docs/subsystems/plan.i18n.yaml | 4 +- docs/subsystems/plan.md | 10 +- docs/subsystems/plan.zh.md | 10 +- docs/subsystems/sandbox.i18n.yaml | 4 +- docs/subsystems/sandbox.md | 4 +- docs/subsystems/sandbox.zh.md | 4 +- docs/subsystems/schedule.i18n.yaml | 4 +- docs/subsystems/schedule.md | 4 +- docs/subsystems/schedule.zh.md | 4 +- docs/subsystems/session-query.i18n.yaml | 4 +- docs/subsystems/session-query.md | 4 +- docs/subsystems/session-query.zh.md | 4 +- docs/subsystems/session-reference.i18n.yaml | 4 +- docs/subsystems/session-reference.md | 4 +- docs/subsystems/session-reference.zh.md | 4 +- docs/subsystems/session-telemetry.i18n.yaml | 6 + .../{telemetry.md => session-telemetry.md} | 60 +- ...elemetry.zh.md => session-telemetry.zh.md} | 58 +- docs/subsystems/session-title.i18n.yaml | 4 +- docs/subsystems/session-title.md | 2 +- docs/subsystems/session-title.zh.md | 2 +- docs/subsystems/session.i18n.yaml | 4 +- docs/subsystems/session.md | 14 +- docs/subsystems/session.zh.md | 14 +- docs/subsystems/settings.i18n.yaml | 4 +- docs/subsystems/settings.md | 8 +- docs/subsystems/settings.zh.md | 8 +- .../{bash.i18n.yaml => shell.i18n.yaml} | 6 +- docs/subsystems/{bash.md => shell.md} | 90 +- docs/subsystems/{bash.zh.md => shell.zh.md} | 86 +- docs/subsystems/skills.i18n.yaml | 4 +- docs/subsystems/skills.md | 8 +- docs/subsystems/skills.zh.md | 8 +- docs/subsystems/spill.i18n.yaml | 4 +- docs/subsystems/spill.md | 2 +- docs/subsystems/spill.zh.md | 2 +- docs/subsystems/subagent.i18n.yaml | 4 +- docs/subsystems/subagent.md | 30 +- docs/subsystems/subagent.zh.md | 30 +- docs/subsystems/subprocess.i18n.yaml | 4 +- docs/subsystems/subprocess.md | 16 +- docs/subsystems/subprocess.zh.md | 16 +- docs/subsystems/telemetry.i18n.yaml | 6 - .../{tasks.i18n.yaml => terminal.i18n.yaml} | 6 +- docs/subsystems/{pty.md => terminal.md} | 70 +- docs/subsystems/{pty.zh.md => terminal.zh.md} | 70 +- docs/subsystems/token-meter.i18n.yaml | 4 +- docs/subsystems/token-meter.md | 4 +- docs/subsystems/token-meter.zh.md | 4 +- docs/subsystems/tools.i18n.yaml | 4 +- docs/subsystems/tools.md | 22 +- docs/subsystems/tools.zh.md | 22 +- docs/subsystems/typert.i18n.yaml | 4 +- docs/subsystems/typert.md | 52 +- docs/subsystems/typert.zh.md | 52 +- docs/subsystems/user-interaction.i18n.yaml | 6 - ...ver.i18n.yaml => user-questions.i18n.yaml} | 6 +- ...{user-interaction.md => user-questions.md} | 30 +- ...interaction.zh.md => user-questions.zh.md} | 30 +- ...mission.i18n.yaml => web-server.i18n.yaml} | 6 +- .../{http-server.md => web-server.md} | 12 +- .../{http-server.zh.md => web-server.zh.md} | 12 +- docs/subsystems/web.i18n.yaml | 4 +- docs/subsystems/web.md | 10 +- docs/subsystems/web.zh.md | 10 +- docs/subsystems/workflow.i18n.yaml | 4 +- docs/subsystems/workflow.md | 8 +- docs/subsystems/workflow.zh.md | 8 +- docs/subsystems/workspace.i18n.yaml | 4 +- docs/subsystems/workspace.md | 12 +- docs/subsystems/workspace.zh.md | 12 +- docs/testing.i18n.yaml | 4 +- docs/testing.md | 6 +- docs/testing.zh.md | 6 +- docs/tool-catalog.i18n.yaml | 4 +- docs/tool-catalog.md | 130 +- docs/tool-catalog.zh.md | 120 +- docs/user/develop/framework/events.i18n.yaml | 4 +- docs/user/develop/framework/events.md | 2 +- docs/user/develop/framework/events.zh.md | 2 +- docs/user/develop/framework/service.i18n.yaml | 4 +- docs/user/develop/framework/service.md | 2 +- docs/user/develop/framework/service.zh.md | 2 +- docs/user/develop/practice/index.i18n.yaml | 4 +- docs/user/develop/practice/index.md | 4 +- docs/user/develop/practice/index.zh.md | 4 +- .../acp-agent/advanced.cordis.snapshot.yml | 2 +- examples/acp-agent/advanced.cordis.yml | 2 +- ...=> agent-instructions.cordis.snapshot.yml} | 2 +- ...rdis.yml => agent-instructions.cordis.yml} | 0 ...kground-job-admission.cordis.snapshot.yml} | 4 +- ...ml => background-job-admission.cordis.yml} | 2 +- .../acp-agent/both-mode.cordis.snapshot.yml | 2 +- examples/acp-agent/both-mode.cordis.yml | 2 +- .../child-question.cordis.snapshot.yml | 4 +- examples/acp-agent/child-question.cordis.yml | 4 +- ...mode-workspace-context.cordis.snapshot.yml | 2 +- .../code-mode-workspace-context.cordis.yml | 4 +- .../acp-agent/code-mode.cordis.snapshot.yml | 2 +- examples/acp-agent/code-mode.cordis.yml | 2 +- examples/acp-agent/composition.md | 42 +- examples/acp-agent/cordis.yml | 34 +- examples/acp-agent/pty-snapshot-backend.mjs | 4 +- examples/acp-agent/pty.cordis.snapshot.yml | 6 +- examples/acp-agent/pty.cordis.yml | 10 +- .../session-query.cordis.snapshot.yml | 2 +- examples/acp-agent/session-query.cordis.yml | 2 +- .../session-title.cordis.snapshot.yml | 2 +- examples/acp-agent/session-title.cordis.yml | 4 +- examples/acp-agent/tests/acp.snapshot.ts | 20 +- .../tests/fixtures/child-question-tripwire.ts | 6 +- .../{bash => shell}/tool-pwsh/cordis.yml | 10 +- .../{bash => shell}/tool-pwsh/driver.ts | 6 +- .../fixtures/workspace-context-compaction.ts | 4 +- .../tests/fs-search.cordis.snapshot.yml | 2 +- examples/acp-agent/tests/fs-search.cordis.yml | 2 +- .../input.json | 0 .../replay.override.json | 4 +- .../session.expected.jsonl | 24 +- .../session.jsonl | 0 .../stdout.expected.jsonl | 0 examples/acp-agent/tests/goal.snapshot.ts | 4 +- .../acp-agent/tests/lsp.cordis.snapshot.yml | 6 +- examples/acp-agent/tests/lsp.cordis.yml | 6 +- .../acp-agent/tests/pwsh.cordis.snapshot.yml | 6 +- examples/acp-agent/tests/pwsh.cordis.yml | 6 +- .../advanced-toolchain/session.1.jsonl | 2 +- .../advanced-toolchain/session.2.jsonl | 2 +- .../system-prompt.expected.md | 122 +- .../tool-schemas.expected.json | 110 +- .../input.json | 0 .../replay.override.json | 0 .../session.jsonl | 10 +- .../stdout.expected.jsonl | 0 .../system-prompt.expected.md | 8 +- .../workspace/.dsh-project | 0 .../workspace/AGENTS.canonical.md | 0 .../workspace/AGENTS.md | 0 .../workspace/nested/AGENTS.canonical.md | 0 .../workspace/nested/AGENTS.md | 0 .../workspace/nested/task.txt | 0 .../input.json | 2 +- .../replay.override.json | 12 +- .../session.jsonl | 36 +- .../stdout.expected.jsonl | 0 .../both-mode-turn/system-prompt.expected.md | 120 +- .../both-mode-turn/tool-schemas.expected.json | 108 +- .../code-mode-turn/system-prompt.expected.md | 120 +- .../code-mode-workspace-context/session.jsonl | 6 +- .../cordis-inspect-jsdoc/session.jsonl | 2 +- .../system-prompt.expected.md | 2 +- .../tool-schemas.expected.json | 4 +- .../hook-cc-posttool-block/session.jsonl | 8 +- .../hook-cc-posttool-context/session.jsonl | 8 +- .../hook-cc-pretool-ask/session.jsonl | 4 +- .../hook-cc-pretool-deny/session.jsonl | 4 +- .../session.jsonl | 6 +- .../hook-cc-stop-continue/session.jsonl | 12 +- .../lsp-definition/system-prompt.expected.md | 8 +- .../lsp-definition/tool-schemas.expected.json | 108 +- .../missing-sandbox-runner/input.json | 2 +- .../missing-sandbox-runner/session.jsonl | 18 +- .../snapshots/packed-chunks/session.jsonl | 4 +- .../tool-schemas.expected.json | 108 +- .../system-prompt.expected.md | 8 +- .../tool-schemas.expected.json | 108 +- .../pty-tools/system-prompt.expected.md | 10 +- .../pty-tools/tool-schemas.expected.json | 112 +- .../pwsh-tool-turn/system-prompt.expected.md | 4 +- .../pwsh-tool-turn/tool-schemas.expected.json | 28 +- .../read-image/tool-schemas.expected.json | 108 +- .../input.json | 0 .../session.jsonl | 8 +- .../stdout.expected.jsonl | 0 .../system-prompt.expected.md | 8 +- .../tool-schemas.expected.json | 108 +- .../session-title-after-turn/session.jsonl | 4 +- .../session.1.jsonl | 2 +- .../tool-schemas.expected.json | 108 +- .../system-prompt.1.expected.md | 8 +- .../tool-schemas.1.expected.json | 108 +- .../system-prompt.1.expected.md | 8 +- .../tool-schemas.1.expected.json | 108 +- .../session.1.jsonl | 2 +- .../session.2.jsonl | 2 +- .../input.json | 0 .../session.1.jsonl | 0 .../session.jsonl | 0 .../stdout.expected.jsonl | 0 .../system-prompt.1.expected.md | 8 +- .../tool-schemas.1.expected.json | 108 +- .../snapshots/subagent-mixed/session.1.jsonl | 2 +- .../snapshots/subagent-mixed/session.2.jsonl | 2 +- .../snapshots/subagent-multi/session.1.jsonl | 2 +- .../snapshots/subagent-multi/session.2.jsonl | 2 +- .../subagent-parallel/session.1.jsonl | 8 +- .../subagent-parallel/session.2.jsonl | 8 +- .../system-prompt.1.expected.md | 8 +- .../tool-schemas.1.expected.json | 108 +- .../input.json | 0 .../session.1.jsonl | 0 .../session.jsonl | 0 .../stdout.expected.jsonl | 0 .../text-turn/system-prompt.expected.md | 8 +- .../text-turn/tool-schemas.expected.json | 108 +- .../web-fetch/system-prompt.expected.md | 8 +- .../web-fetch/tool-schemas.expected.json | 108 +- .../acp-agent/web-fetch-fixture-server.mjs | 2 +- examples/acp-agent/web.cordis.snapshot.yml | 4 +- examples/acp-agent/web.cordis.yml | 4 +- .../advanced.cordis.snapshot.yml | 2 +- examples/headless-agent/advanced.cordis.yml | 2 +- .../compaction.cordis.snapshot.yml | 4 +- examples/headless-agent/composition.md | 34 +- examples/headless-agent/cordis.yml | 22 +- examples/headless-agent/e2b.cordis.yml | 14 +- .../headless-agent/pty.cordis.snapshot.yml | 6 +- .../semantic-checkpoint.cordis.snapshot.yml | 2 +- .../subagent-diagnostic.cordis.snapshot.yml | 4 +- .../subagent-inheritance.cordis.snapshot.yml | 10 +- .../headless-agent/tests/code-mode.e2e.ts | 80 +- .../headless-agent/tests/compaction.e2e.ts | 8 +- .../tests/fixtures/cli.cordis.yml | 2 +- .../tests/fixtures/e2b/e2b/bin.ts | 26 +- .../tests/fixtures/e2b/e2b/cordis.yml | 10 +- ...er.ts => session-telemetry-otel-driver.ts} | 4 +- ....yml => session-telemetry-otel.cordis.yml} | 4 +- .../tests/fixtures/telemetry-redact-rule.ts | 4 +- .../workspace-context-resume-agent.ts | 2 +- examples/headless-agent/tests/harness.ts | 28 +- .../headless-agent/tests/headless.snapshot.ts | 14 +- .../tests/semantic-checkpoint.snapshot.ts | 4 +- .../tests/session-format-guard.snapshot.ts | 4 +- .../advanced-toolchain/session.1.jsonl | 10 +- .../advanced-toolchain/session.2.jsonl | 10 +- .../advanced-toolchain/session.jsonl | 28 +- .../snapshots/compaction-recovery/input.json | 2 +- .../compaction-recovery/session.jsonl | 20 +- .../stream-json.expected.jsonl | 10 +- .../headless-profile/session.expected.jsonl | 2 +- .../tests/snapshots/pty-tools/session.jsonl | 34 +- .../stream-json.expected.jsonl | 4 +- .../parent.expected.jsonl | 4 +- .../tests/subagent-diagnostic.snapshot.ts | 6 +- .../tests/subagent-inheritance.snapshot.ts | 4 +- .../offline-edit/session.expected.jsonl | 4 +- .../precedence-change/session.expected.jsonl | 4 +- .../workspace-context-resume.snapshot.ts | 20 +- ...rkspace-context-resume.cordis.snapshot.yml | 2 +- examples/jsonrpc-agent/README.i18n.yaml | 2 +- examples/jsonrpc-agent/README.md | 2 +- examples/jsonrpc-agent/cordis.yml | 18 +- examples/jsonrpc-agent/minimal.cordis.yml | 12 +- .../subagent-dsh-sdk/child.cordis.yml | 6 +- .../jsonrpc-agent/tests/keyless-smoke.e2e.ts | 2 +- examples/jsonrpc-agent/tests/sdk.snapshot.ts | 2 +- .../notifications.expected.jsonl | 0 .../result.expected.json | 0 .../session.1.jsonl | 0 .../session.jsonl | 0 examples/package.json | 56 +- examples/web-cordis/README.i18n.yaml | 4 +- examples/web-cordis/README.md | 4 +- examples/web-cordis/README.zh.md | 4 +- examples/web-schedule/cordis.yml | 4 +- knip.json | 34 +- package.json | 4 +- packages/AGENTS.md | 4 +- packages/README.i18n.yaml | 4 +- packages/README.md | 15 +- packages/README.zh.md | 15 +- packages/acp/acp/tsconfig.json | 2 +- packages/api/README.i18n.yaml | 4 +- packages/api/README.md | 6 +- packages/api/README.zh.md | 6 +- packages/api/gateway/README.i18n.yaml | 4 +- packages/api/gateway/README.md | 10 +- packages/api/gateway/README.zh.md | 10 +- packages/api/gateway/package.json | 4 +- packages/api/gateway/src/client/index.ts | 52 +- packages/api/gateway/src/index.ts | 42 +- packages/api/gateway/src/invariant.ts | 2 +- packages/api/gateway/src/types.ts | 4 +- .../api/gateway/tests/gateway.client.spec.ts | 30 +- .../api/gateway/tests/gateway.host.spec.ts | 72 +- packages/api/gateway/tsconfig.client.json | 2 +- packages/api/gateway/tsconfig.host.json | 4 +- packages/api/remotes/README.i18n.yaml | 4 +- packages/api/remotes/README.md | 8 +- packages/api/remotes/README.zh.md | 8 +- packages/api/remotes/package.json | 2 +- packages/api/remotes/src/agent-lookup.ts | 8 +- packages/api/remotes/src/client/index.ts | 10 +- packages/api/remotes/src/index.ts | 4 +- packages/api/remotes/src/invariant.ts | 2 +- packages/api/remotes/src/types.ts | 4 +- .../api/remotes/tests/agent-lookup.spec.ts | 4 +- packages/api/remotes/tests/built-lib.e2e.ts | 2 +- packages/api/remotes/tsconfig.client.json | 2 +- packages/api/remotes/tsconfig.host.json | 4 +- .../attachment/attachment-local/package.json | 4 +- .../attachment/attachment-local/src/index.ts | 2 +- .../attachment/attachment-local/tsconfig.json | 4 +- packages/attachment/attachment/tsconfig.json | 2 +- packages/bash/bash-env/README.md | 51 - packages/bash/bash-env/README.zh.md | 51 - packages/bash/bash/README.zh.md | 53 - packages/bash/pwsh-local/README.i18n.yaml | 6 - packages/bash/pwsh-sandbox/README.i18n.yaml | 6 - packages/bash/tool-bash/README.i18n.yaml | 6 - packages/bash/tool-pwsh/README.i18n.yaml | 6 - packages/boot/app-boot/README.i18n.yaml | 4 +- packages/boot/app-boot/README.md | 2 +- packages/boot/app-boot/README.zh.md | 2 +- packages/boot/app-boot/package.json | 8 +- packages/boot/app-boot/src/index.ts | 10 +- packages/boot/app-boot/src/profile.ts | 6 +- packages/boot/app-boot/tests/app-boot.spec.ts | 2 +- packages/boot/app-boot/tsconfig.json | 6 +- packages/boot/cmdline/tsconfig.json | 2 +- packages/bundle/base/cordis.patch.yml | 70 +- packages/bundle/base/package.json | 36 +- packages/bundle/base/tests/base.spec.ts | 2 +- packages/bundle/base/tsconfig.json | 2 +- packages/bundle/headless/cordis.patch.yml | 2 +- packages/bundle/headless/package.json | 2 +- .../bundle/headless/tests/headless.spec.ts | 4 +- packages/bundle/headless/tsconfig.json | 2 +- packages/bundle/web-app/README.i18n.yaml | 4 +- packages/bundle/web-app/README.md | 2 +- packages/bundle/web-app/README.zh.md | 2 +- packages/bundle/web-app/cordis.patch.yml | 80 +- packages/bundle/web-app/package.json | 34 +- packages/bundle/web-app/src/index.ts | 26 +- packages/bundle/web-app/tests/web-app.spec.ts | 32 +- packages/bundle/web-app/tsconfig.json | 4 +- packages/client/AGENTS.md | 4 +- packages/client/README.i18n.yaml | 4 +- packages/client/README.md | 20 +- packages/client/README.zh.md | 20 +- packages/client/connection/README.i18n.yaml | 4 +- packages/client/connection/README.md | 2 +- packages/client/connection/README.zh.md | 2 +- packages/client/connection/src/client/api.ts | 2 +- .../client/connection/src/client/fixture.ts | 4 +- .../client/connection/src/client/index.ts | 2 +- packages/client/connection/src/index.ts | 8 +- packages/client/connection/src/rpc-host.ts | 2 +- .../connection/tests/node-half.host.spec.ts | 18 +- .../client/connection/tsconfig.client.json | 2 +- packages/client/connection/tsconfig.host.json | 2 +- packages/client/hmr/src/index.ts | 20 +- .../client/hmr/tests/node-half.client.spec.ts | 18 +- packages/client/hmr/tsconfig.json | 2 +- packages/client/locale/README.i18n.yaml | 4 +- packages/client/locale/README.md | 2 +- packages/client/locale/README.zh.md | 2 +- packages/client/locale/src/client/index.ts | 6 +- .../client/locale/tests/apply.client.spec.ts | 22 +- .../client/locale/tests/host.client.spec.ts | 4 +- .../locale/tests/invariant.client.spec.ts | 16 +- .../locale/tests/language-row.client.spec.tsx | 2 +- .../client/locale/tests/locale.client.spec.ts | 8 +- packages/client/locale/tsconfig.json | 2 +- packages/client/modules/src/index.ts | 18 +- packages/client/modules/src/invariant.ts | 2 +- .../modules/tests/node-half.client.spec.ts | 14 +- packages/client/modules/tsconfig.json | 2 +- packages/client/runtime/README.i18n.yaml | 4 +- packages/client/runtime/README.md | 16 +- packages/client/runtime/README.zh.md | 16 +- packages/client/runtime/package.json | 6 +- .../client/runtime/src/client/agents/scope.ts | 4 +- .../runtime/src/client/contract/session.ts | 2 +- .../src/client/contract/sessions-port.ts | 2 +- packages/client/runtime/src/client/index.ts | 32 +- .../src/client/sessions/context-provenance.ts | 2 +- .../src/client/sessions/conversation.ts | 4 +- .../runtime/src/client/sessions/manager.ts | 26 +- .../runtime/src/client/sessions/provide.ts | 4 +- .../runtime/src/client/sessions/service.ts | 30 +- .../runtime/src/client/sessions/session.ts | 6 +- packages/client/runtime/src/client/slots.ts | 8 +- .../runtime/src/client/workspaces/service.ts | 4 +- .../runtime/tests/client-apply.client.spec.ts | 16 +- .../tests/context-provenance.client.spec.ts | Bin 4572 -> 4540 bytes .../conversation-assembler.client.spec.ts | 4 +- .../conversation-registry.client.spec.ts | 4 +- .../runtime/tests/event-script.client.ts | 4 +- .../runtime/tests/invariant.client.spec.ts | 10 +- .../runtime/tests/manager.client.spec.ts | 18 +- .../tests/sessions-service.client.spec.ts | 8 +- .../tests/slots-service.client.spec.ts | 8 +- .../tests/workspaces-service.client.spec.ts | 58 +- packages/client/runtime/tsconfig.json | 6 +- .../tests/invariant.client.spec.ts | 4 +- packages/client/schema-form/tsconfig.json | 2 +- .../tests/apply.client.spec.ts | 14 +- .../tests/invariant.client.spec.ts | 4 +- packages/client/ui-agent-preset/tsconfig.json | 4 +- .../tests/invariant.client.spec.ts | 4 +- packages/client/ui-attachment/tsconfig.json | 2 +- packages/client/ui-command/tsdown.config.ts | 3 - .../README.i18n.yaml | 6 +- .../{ui-command => ui-commands}/README.md | 8 +- .../{ui-command => ui-commands}/README.zh.md | 8 +- .../{ui-command => ui-commands}/package.json | 10 +- .../src/client/PopupSelectView.module.css | 0 .../src/client/PopupSelectView.tsx | 0 .../src/client/contract.ts | 10 +- .../src/client/directory.ts | 0 .../src/client/index.ts | 22 +- .../src/client/locales.ts | 0 .../src/client/popup.ts | 8 +- .../src/client/service.ts | 38 +- .../src/css-modules.d.ts | 0 .../{ui-command => ui-commands}/src/index.ts | 2 +- .../src/invariant.ts | 8 +- .../tests/browser-plugin.client.spec.ts | 38 +- .../tests/directory.client.spec.ts | 0 .../tests/popup-view.client.spec.tsx | 0 .../tests/popup.client.spec.ts | 0 .../tests/service.client.spec.ts | 20 +- .../{ui-command => ui-commands}/tsconfig.json | 4 +- .../tsdown.config.ts | 2 +- .../client/ui-conversation/README.i18n.yaml | 4 +- packages/client/ui-conversation/README.md | 12 +- packages/client/ui-conversation/README.zh.md | 10 +- packages/client/ui-conversation/package.json | 10 +- .../ui-conversation/src/client/apply.ts | 22 +- .../src/client/chat/ChatView.tsx | 2 +- .../src/client/chat/CompactionItem.tsx | 2 +- .../src/client/contract/slots.ts | 10 +- .../src/client/conversation-nodes/command.ts | 20 +- .../client/conversation-nodes/compaction.ts | 12 +- .../ui-conversation/src/client/index.ts | 2 +- .../src/client/input/blocks.ts | 2 +- .../src/client/input/contract.ts | 8 +- .../src/client/input/facade.ts | 38 +- .../ui-conversation/src/client/input/hub.ts | 28 +- .../src/client/input/machine.ts | 2 +- .../ui-conversation/src/client/service.ts | 16 +- .../src/client/skeleton/ConversationRoot.tsx | 2 +- .../src/client/skeleton/PermissionSelect.tsx | 2 +- .../tests/apply-inject.client.spec.tsx | 4 +- .../tests/assembly-surfaces.client.spec.tsx | 10 +- .../tests/chat-apply.client.spec.tsx | 4 +- .../tests/chat-branch-tails.client.spec.tsx | 28 +- .../tests/chat-view.client.spec.tsx | 2 +- ...nversation-node-definitions.client.spec.ts | 30 +- .../tests/enter-behavior-row.client.spec.tsx | 2 +- .../tests/gate-branch-tails.client.spec.tsx | 4 +- .../ui-conversation/tests/host.client.spec.ts | 4 +- .../tests/input-bar.client.spec.tsx | 6 +- .../tests/input-machine.client.spec.ts | 2 +- .../tests/input-matrix.client.spec.tsx | 4 +- .../tests/input-scenarios.client.spec.tsx | 44 +- .../tests/selection-survival.client.spec.tsx | 4 +- .../service-orchestration.client.spec.ts | 20 +- .../tests/skeleton.client.spec.tsx | 2 +- .../tests/views-type-chain.client.spec.tsx | 6 +- packages/client/ui-conversation/tsconfig.json | 6 +- .../tests/produced-files.client.spec.tsx | 6 +- packages/client/ui-deliverables/tsconfig.json | 2 +- .../README.i18n.yaml | 6 + .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 4 +- .../src/client/DirectoryBrowser.module.css | 0 .../src/client/DirectoryBrowser.tsx | 0 .../src/client/flow.ts | 0 .../src/client/index.ts | 0 .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../tests/client-flow.client.spec.tsx | 12 +- .../tests/directory-browser.client.spec.tsx | 0 .../tsconfig.json | 2 +- .../tsdown.config.ts | 2 +- .../tests/client-flow.client.spec.tsx | 6 +- .../ui-directory-picker-native/tsconfig.json | 2 +- packages/client/ui-feedback/tsdown.config.ts | 3 - packages/client/ui-goal/src/client/slots.ts | 2 +- .../tests/browser-plugin.client.spec.tsx | 8 +- packages/client/ui-goal/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- packages/client/ui-input-trigger/README.md | 25 + packages/client/ui-input-trigger/README.zh.md | 25 + .../package.json | 4 +- .../src/client/MenuView.module.css | 0 .../src/client/MenuView.tsx | 2 +- .../src/client/contract.ts | 14 +- .../src/client/controller.ts | 34 +- .../src/client/index.ts | 30 +- .../src/client/locales.ts | 0 .../src/client/service.ts | 30 +- .../src/client/slots.ts | 4 +- .../src/core/contract.ts | 8 +- .../src/core/detect.ts | 0 .../src/core/menu.ts | 4 +- .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../src/types.ts | 16 +- .../tests/apply.client.spec.ts | 30 +- .../tests/core-detect.client.spec.ts | 0 .../tests/core-menu.client.spec.ts | 0 .../tests/menu-view.client.spec.tsx | 4 +- .../tests/service.client.spec.ts | 126 +- .../tsconfig.json | 2 +- .../client/ui-input-trigger/tsdown.config.ts | 3 + .../{ui-task => ui-jobs}/README.i18n.yaml | 6 +- packages/client/ui-jobs/README.md | 24 + .../client/{ui-task => ui-jobs}/README.zh.md | 8 +- .../client/{ui-task => ui-jobs}/package.json | 6 +- .../src/client/JobListAction.module.css} | 2 +- .../src/client/JobListAction.tsx} | 76 +- .../{ui-task => ui-jobs}/src/client/index.ts | 22 +- .../src/client/locales.ts | 20 +- .../src/css-modules.d.ts | 0 .../client/{ui-task => ui-jobs}/src/index.ts | 2 +- .../{ui-task => ui-jobs}/src/invariant.ts | 10 +- .../tests/browser-plugin.client.spec.ts | 30 +- .../tests/job-list-action.client.spec.tsx} | 118 +- .../client/{ui-task => ui-jobs}/tsconfig.json | 2 +- .../{ui-task => ui-jobs}/tsdown.config.ts | 2 +- packages/client/ui-layout/README.i18n.yaml | 4 +- packages/client/ui-layout/README.md | 2 +- packages/client/ui-layout/README.zh.md | 2 +- packages/client/ui-layout/src/client/index.ts | 6 +- .../client/ui-layout/src/client/service.ts | 4 +- .../ui-layout/tests/apply.client.spec.ts | 20 +- .../ui-layout/tests/service.client.spec.ts | 12 +- packages/client/ui-layout/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 6 +- .../README.zh.md | 6 +- .../package.json | 8 +- .../client/MessageFeedbackActions.module.css} | 0 .../src/client/MessageFeedbackActions.tsx} | 8 +- .../src/client/controller.ts | 60 +- .../src/client/index.ts | 28 +- .../src/client/locales.ts | 6 +- .../src/client/slots.ts | 22 +- .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 6 +- .../tests/browser-plugin.client.spec.tsx | 16 +- .../tests/controller.client.spec.ts | 82 +- .../message-feedback-actions.client.spec.tsx} | 40 +- .../tsconfig.json | 4 +- .../ui-message-feedback/tsdown.config.ts | 3 + .../README.i18n.yaml | 6 +- .../README.md | 8 +- .../README.zh.md | 8 +- .../package.json | 14 +- .../src/client/ModelSelect.module.css | 0 .../src/client/ModelSelect.tsx | 0 .../src/client/directory.ts | 0 .../src/client/index.ts | 28 +- .../src/client/locales.ts | 0 .../src/client/service.ts | 24 +- .../src/client/slots.ts | 0 .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../tests/browser-plugin.client.spec.ts | 20 +- .../tests/model-select.client.spec.tsx | 0 .../tsconfig.json | 6 +- .../ui-model-selection/tsdown.config.ts | 3 + packages/client/ui-model/tsdown.config.ts | 3 - packages/client/ui-models/tsdown.config.ts | 3 - .../ui-permission-presets/README.i18n.yaml | 6 + .../README.md | 4 +- .../README.zh.md | 4 +- .../package.json | 18 +- .../src/client/PermissionRow.module.css | 0 .../src/client/PermissionRow.tsx | 0 .../src/client/index.ts | 14 +- .../src/client/locales.ts | 0 .../src/client/presentation.ts | 0 .../src/client/settings-store.ts | 4 +- .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../tests/browser-plugin.client.spec.ts | 14 +- .../permission-presets-row.client.spec.tsx} | 14 +- .../tests/settings-store.client.spec.ts | 22 +- .../tsconfig.json | 8 +- .../tsdown.config.ts | 2 +- .../client/ui-permission/README.i18n.yaml | 6 - .../tests/browser-plugin.client.spec.ts | 16 +- packages/client/ui-plan/tsconfig.json | 2 +- .../client/ui-plugin-config/README.i18n.yaml | 6 - .../client/ui-plugin-config/tsdown.config.ts | 3 - packages/client/ui-plugins/README.i18n.yaml | 6 - packages/client/ui-plugins/tsdown.config.ts | 3 - .../tests/code-block.client.spec.tsx | 2 +- .../tests/invariant.client.spec.ts | 4 +- .../tests/markdown-plain-text.client.spec.ts | 6 +- packages/client/ui-primitives/tsconfig.json | 2 +- packages/client/ui-question/README.i18n.yaml | 6 - .../tests/apply.client.spec.ts | 14 +- .../tests/host.client.spec.ts | 4 +- .../tests/invariant.client.spec.ts | 4 +- .../tests/shell.client.spec.ts | 10 +- .../client/ui-settings-general/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 4 +- .../src/client/CustomProviderCard.tsx | 0 .../src/client/DeepSeekModelsEditor.tsx | 0 .../DeepSeekOnboardingDialog.module.css | 0 .../src/client/DeepSeekOnboardingDialog.tsx | 0 .../src/client/EditorFooter.tsx | 2 +- .../src/client/ModelListEditor.tsx | 0 .../src/client/ModelsSection.module.css | 0 .../src/client/ModelsSection.tsx | 0 .../src/client/ProviderEditor.tsx | 0 .../src/client/apiKey.ts | 2 +- .../src/client/index.ts | 4 +- .../src/client/locales.ts | 0 .../src/client/store.ts | 0 .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../tests/apply.client.spec.ts | 16 +- .../tests/components.client.spec.tsx | 0 .../tests/invariant.client.spec.ts | 8 +- .../tests/onboarding-dialog.client.spec.tsx | 0 .../tests/provider-form.client.spec.tsx | 0 .../tests/readiness.client.spec.ts | 0 .../tests/store.client.spec.ts | 0 .../tests/styles.client.spec.ts | 0 .../tsconfig.json | 2 +- .../ui-settings-models/tsdown.config.ts | 3 + .../README.i18n.yaml | 6 + .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 4 +- .../PluginInventorySettingsTab.module.css} | 0 .../client/PluginInventorySettingsTab.tsx} | 18 +- .../src/client/index.ts | 20 +- .../src/client/locales.ts | 4 +- .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 6 +- .../tests/browser-plugin.client.spec.tsx | 24 +- .../tests/components.client.spec.tsx | 36 +- .../tests/invariant.client.spec.ts | 6 +- .../tsconfig.json | 2 +- .../tsdown.config.ts | 3 + .../ui-settings-plugins/README.i18n.yaml | 6 + .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 4 +- .../src/client/AgentLoopCard.tsx | 4 +- .../src/client/BashCard.tsx | 4 +- .../src/client/ConfigurablePluginsTab.tsx | 4 +- .../src/client/PluginCard.module.css | 0 .../src/client/PluginCard.tsx | 10 +- .../client/PluginsSettingsSection.module.css} | 0 .../src/client/PluginsSettingsSection.tsx} | 22 +- .../src/client/WebSearchCard.tsx | 4 +- .../src/client/agent-loop-card-controller.ts} | 2 +- .../src/client/bash-card-controller.ts} | 4 +- .../src/client/card-form.ts} | 0 .../src/client/fields.module.css | 0 .../src/client/fields.tsx | 0 .../src/client/index.ts | 34 +- .../src/client/locales.ts | 6 +- .../src/client/slot-contract.ts | 0 .../src/client/web-search-card-controller.ts} | 2 +- .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../tests/apply.client.spec.ts | 26 +- .../tests/fields.client.spec.tsx | 0 .../tests/invariant.client.spec.ts | 8 +- .../tests/section.client.spec.tsx | 22 +- .../tests/stores.client.spec.ts | 8 +- .../tsconfig.json | 4 +- .../ui-settings-plugins/tsdown.config.ts | 3 + .../client/ui-settings/src/client/index.ts | 6 +- .../ui-settings/src/client/settings-scope.ts | 4 +- .../tests/invariant.client.spec.ts | 4 +- .../ui-settings/tests/plugin.client.spec.ts | 4 +- .../tests/settings-scope.client.spec.ts | 8 +- packages/client/ui-settings/tsconfig.json | 2 +- .../ui-sidebar/tests/apply.client.spec.tsx | 10 +- .../ui-sidebar/tests/invariant.client.spec.ts | 4 +- .../tests/sidebar-snapshot.client.spec.tsx | 4 +- packages/client/ui-sidebar/tsconfig.json | 2 +- packages/client/ui-skill/README.i18n.yaml | 4 +- packages/client/ui-skill/README.md | 2 +- packages/client/ui-skill/README.zh.md | 2 +- packages/client/ui-skill/package.json | 6 +- packages/client/ui-skill/src/client/index.ts | 12 +- .../tests/browser-plugin.client.spec.ts | 30 +- packages/client/ui-skill/tsconfig.json | 4 +- packages/client/ui-slash/README.i18n.yaml | 6 - packages/client/ui-slash/README.md | 25 - packages/client/ui-slash/README.zh.md | 25 - packages/client/ui-slash/tsdown.config.ts | 3 - packages/client/ui-slots/src/invariant.ts | 2 +- packages/client/ui-slots/src/renderer.ts | 4 +- .../ui-slots/tests/invariant.client.spec.ts | 4 +- packages/client/ui-slots/tsconfig.json | 2 +- packages/client/ui-subagent/README.i18n.yaml | 4 +- packages/client/ui-subagent/README.md | 4 +- packages/client/ui-subagent/README.zh.md | 4 +- packages/client/ui-subagent/package.json | 6 +- .../client/ui-subagent/src/client/index.ts | 10 +- .../tests/browser-plugin.client.spec.ts | 26 +- .../tests/conversation-ui.client.spec.tsx | 2 +- packages/client/ui-subagent/tsconfig.json | 4 +- packages/client/ui-task/README.md | 24 - packages/client/ui-theme/README.i18n.yaml | 4 +- packages/client/ui-theme/README.md | 4 +- packages/client/ui-theme/README.zh.md | 4 +- packages/client/ui-theme/package.json | 2 +- packages/client/ui-theme/src/client/index.ts | 6 +- packages/client/ui-theme/src/index.ts | 4 +- .../tests/appearance-row.client.spec.tsx | 2 +- .../ui-theme/tests/apply.client.spec.ts | 30 +- .../client/ui-theme/tests/host.client.spec.ts | 14 +- .../ui-theme/tests/invariant.client.spec.ts | 12 +- .../ui-theme/tests/theme.client.spec.ts | 8 +- packages/client/ui-theme/tsconfig.json | 2 +- packages/client/ui-tool/README.i18n.yaml | 4 +- packages/client/ui-tool/README.md | 2 +- packages/client/ui-tool/README.zh.md | 2 +- .../tool/toolviews/ask-question-row.tsx | 2 +- .../tests/ask-question-row.client.spec.tsx | 4 +- .../tests/assembly-surfaces.client.spec.tsx | 4 +- .../tests/chat-code-subcalls.client.spec.tsx | 16 +- .../tests/coverage-tails.client.spec.tsx | 2 +- .../ui-tool/tests/diff-card.client.spec.tsx | 6 +- .../ui-tool/tests/read-card.client.spec.tsx | 6 +- .../ui-tool/tests/search-card.client.spec.tsx | 2 +- .../tests/terminal-card.client.spec.tsx | 8 +- .../tests/toolview-slot.client.spec.tsx | 8 +- .../tests/toolview-type-chain.client.spec.tsx | 4 +- .../ui-tool/tests/web-card.client.spec.tsx | 2 +- packages/client/ui-tool/tsconfig.json | 2 +- packages/client/ui-trajectory/package.json | 4 +- .../trajectory-compaction-definition.ts | 28 +- .../tests/client-bundle.client.spec.ts | 6 +- .../conversation-definitions.client.spec.ts | 8 +- .../ui-trajectory/tests/table.client.spec.tsx | 6 +- .../ui-trajectory/tests/views.client.spec.tsx | 16 +- packages/client/ui-trajectory/tsconfig.json | 4 +- .../README.i18n.yaml | 6 +- .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 6 +- .../src/client/PlanReviewPanel.module.css | 0 .../src/client/PlanReviewPanel.tsx | 0 .../src/client/QuestionComposer.module.css | 0 .../src/client/QuestionComposer.tsx | 0 .../src/client/contract/slots.ts | 0 .../src/client/index.ts | 2 +- .../src/client/locales.ts | 0 .../src/css-modules.d.ts | 0 .../src/index.ts | 0 .../src/invariant.ts | 8 +- .../tests/browser-plugin.client.spec.ts | 16 +- .../tests/node-plugin.client.spec.ts | 10 +- .../tests/plan-review-panel.client.spec.tsx | 0 .../user-questions-composer.client.spec.tsx} | 0 .../tsconfig.json | 2 +- .../client/ui-user-questions/tsdown.config.ts | 3 + .../client/ui-workflow-run/README.i18n.yaml | 4 +- packages/client/ui-workflow-run/README.md | 2 +- packages/client/ui-workflow-run/README.zh.md | 2 +- .../src/client/WorkflowRunPanel.tsx | 2 +- .../tests/workflow-run.client.spec.tsx | 6 +- packages/client/ui-workflow-run/tsconfig.json | 2 +- .../src/client/WorkspaceBrowser.tsx | 130 +- .../client/ui-workspace/src/client/stores.ts | 64 +- .../client/ui-workspace/src/client/tree.ts | 10 +- .../ui-workspace/tests/apply.client.spec.ts | 12 +- .../tests/invariant.client.spec.ts | 4 +- .../tests/rename-assembly.client.spec.tsx | 4 +- .../ui-workspace/tests/tree.client.spec.ts | 52 +- .../tests/workspace-browser.client.spec.tsx | 50 +- .../tests/workspace-picker.client.spec.tsx | 2 +- packages/client/ui-workspace/tsconfig.json | 2 +- packages/client/web-react/README.i18n.yaml | 4 +- packages/client/web-react/README.md | 2 +- packages/client/web-react/README.zh.md | 2 +- .../client/web-react/src/scoped-slots.tsx | 2 +- .../tests/scoped-slots.client.spec.tsx | 2 +- packages/client/web-react/tsconfig.json | 2 +- .../web/tests/app-shell.client.spec.tsx | 8 +- packages/client/web/tsconfig.json | 2 +- packages/code-runtime/README.i18n.yaml | 4 +- packages/code-runtime/README.md | 2 +- packages/code-runtime/README.zh.md | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 8 +- .../README.zh.md | 8 +- .../package.json | 4 +- .../src/bootstrap.ts | 2 +- .../src/index.ts | 28 +- .../src/invariant.ts | 8 +- .../src/output-json.ts | 2 +- .../src/protocol.ts | 2 +- .../src/worker-json.ts | 5 +- .../src/worker.ts | 4 +- .../tests/bootstrap.spec.ts | 0 .../tests/built-lib.e2e.ts | 4 +- .../tests/output-json.spec.ts | 0 .../tests/runtime.spec.ts | 34 +- .../tests/source-worker.compat.spec.ts | 0 .../tests/worker-json.spec.ts | 0 .../tsconfig.json | 2 +- .../tsdown.config.ts | 0 .../code-runtime/code-runtime/tsconfig.json | 2 +- packages/compact/README.md | 16 - .../{compact => compaction}/README.i18n.yaml | 6 +- packages/compaction/README.md | 16 + packages/{compact => compaction}/README.zh.md | 10 +- .../command-compact/README.i18n.yaml | 6 +- .../command-compact/README.md | 14 +- .../command-compact/README.zh.md | 14 +- .../command-compact/package.json | 6 +- .../command-compact/src/index.ts | 6 +- .../command-compact/src/invariant.ts | 0 .../tests/command-compact.spec.ts | 32 +- .../command-compact/tests/invariant.spec.ts | 0 .../tests/loader-composition.spec.ts | 30 +- .../command-compact/tsconfig.json | 4 +- .../compaction-basic}/README.i18n.yaml | 6 +- .../compaction-basic}/README.md | 40 +- .../compaction-basic}/README.zh.md | 40 +- .../compaction-basic}/package.json | 14 +- .../compaction-basic}/src/config.ts | 36 +- .../compaction-basic}/src/index.ts | 32 +- .../compaction-basic}/src/invariant.ts | 8 +- .../compaction-basic}/src/region.ts | 38 +- .../compaction-basic}/src/summarizer.ts | 8 +- .../compaction-basic}/src/types.ts | 8 +- .../tests/compaction-basic.spec.ts} | 194 +- .../tests/compaction-loop-repro.spec.ts} | 44 +- .../tests/loader-composition.spec.ts | 52 +- .../tests/manual-compaction.spec.ts} | 142 +- .../compaction-basic}/tsconfig.json | 6 +- .../README.i18n.yaml | 6 +- .../compaction-tool-result-pruner}/README.md | 12 +- .../README.zh.md | 12 +- .../package.json | 8 +- .../src/config.ts | 0 .../src/index.ts | 18 +- .../src/invariant.ts | 8 +- .../src/types.ts | 0 .../tests/loader-composition.spec.ts | 20 +- .../tests/tool-result-pruner.spec.ts} | 30 +- .../tsconfig.json | 4 +- .../compaction}/README.i18n.yaml | 6 +- .../compaction}/README.md | 34 +- .../compaction}/README.zh.md | 34 +- .../compaction}/package.json | 6 +- .../compaction}/src/brand.ts | 0 .../compaction}/src/checkpoint.ts | 8 +- .../compaction}/src/index.ts | 32 +- .../compaction}/src/invariant.ts | 72 +- .../compaction}/src/tool-pairing.ts | 2 +- .../compaction}/src/types.ts | 26 +- .../compaction/tests/compaction.spec.ts} | 62 +- .../compaction}/tests/invariant.spec.ts | 190 +- .../compaction}/tests/tool-pairing.spec.ts | 2 +- .../compaction}/tsconfig.json | 2 +- .../compaction}/tsdown.config.ts | 0 packages/context/README.i18n.yaml | 4 +- packages/context/README.md | 8 +- packages/context/README.zh.md | 8 +- .../README.i18n.yaml | 6 +- .../README.md | 4 +- .../README.zh.md | 4 +- .../package.json | 8 +- .../src/config.ts | 4 +- .../src/digest.ts | 2 +- .../src/files.ts | 4 +- .../src/index.ts | 28 +- .../src/invariant.ts | 6 +- .../src/render.ts | 8 +- .../src/state.ts | 46 +- .../tests/agent-instructions.e2e.ts} | 12 +- .../tests/agent-instructions.spec.ts} | 144 +- .../tsconfig.json | 4 +- .../session-reference/README.i18n.yaml | 4 +- packages/context/session-reference/README.md | 6 +- .../context/session-reference/README.zh.md | 6 +- .../context/session-reference/package.json | 10 +- .../context/session-reference/src/index.ts | 8 +- .../session-reference/src/projection.ts | 4 +- .../tests/session-reference.spec.ts | 86 +- .../context/session-reference/tsconfig.json | 6 +- .../time-context/tests/invariant.spec.ts | 8 +- .../time-context/tests/time-context.spec.ts | 2 +- packages/context/time-context/tsconfig.json | 4 +- .../context/tmux-context/README.i18n.yaml | 4 +- packages/context/tmux-context/README.md | 4 +- packages/context/tmux-context/README.zh.md | 4 +- packages/context/tmux-context/package.json | 4 +- packages/context/tmux-context/src/index.ts | 12 +- .../tmux-context/tests/tmux-context.spec.ts | 22 +- packages/context/tmux-context/tsconfig.json | 4 +- .../core/agent-default-model/README.i18n.yaml | 4 +- packages/core/agent-default-model/README.md | 2 +- .../core/agent-default-model/README.zh.md | 2 +- .../core/agent-default-model/src/index.ts | 6 +- .../tests/agent-default-model.spec.ts | 14 +- .../core/agent-default-model/tsconfig.json | 2 +- packages/core/agent-loop/README.i18n.yaml | 4 +- packages/core/agent-loop/README.md | 2 +- packages/core/agent-loop/README.zh.md | 2 +- packages/core/agent-loop/src/agent.ts | 4 +- packages/core/agent-loop/src/index.ts | 16 +- packages/core/agent-loop/src/tool-calls.ts | 10 +- .../agent-loop/tests/agent-initiator.spec.ts | 16 +- packages/core/agent-loop/tests/agent.spec.ts | 8 +- packages/core/agent-loop/tests/cancel.spec.ts | 12 +- .../tests/config-session-id.spec.ts | 60 +- .../tests/contract-regressions.spec.ts | 42 +- .../agent-loop/tests/coverage-edges.spec.ts | 8 +- .../agent-loop/tests/interception.spec.ts | 8 +- .../core/agent-loop/tests/invariant.spec.ts | 6 +- packages/core/agent-loop/tests/loop.spec.ts | 28 +- .../core/agent-loop/tests/properties.spec.ts | 8 +- .../agent-loop/tests/request-cache.e2e.ts | 8 +- .../agent-loop/tests/request-error.spec.ts | 8 +- .../tests/request-reconstruction.spec.ts | 16 +- packages/core/agent-loop/tests/resume.spec.ts | 76 +- .../agent-loop/tests/scope-lifecycle.spec.ts | 10 +- .../core/agent-loop/tests/settings.spec.ts | 12 +- .../core/agent-loop/tests/tool-calls.spec.ts | 22 +- .../core/agent-loop/tests/tool-order.spec.ts | 8 +- packages/core/agent-loop/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 4 +- .../README.zh.md | 4 +- .../package.json | 4 +- .../src/index.ts | 6 +- .../src/invariant.ts | 8 +- .../tests/agent-tool-presentation.spec.ts} | 8 +- .../tsconfig.json | 2 +- packages/core/agent/package.json | 4 +- packages/core/agent/src/index.ts | 12 +- packages/core/agent/tests/agent.spec.ts | 2 +- packages/core/agent/tests/invariant.spec.ts | 4 +- packages/core/agent/tsconfig.json | 4 +- packages/core/scope/tests/invariant.spec.ts | 4 +- packages/core/scope/tsconfig.json | 2 +- packages/core/session/README.i18n.yaml | 4 +- packages/core/session/README.md | 4 +- packages/core/session/README.zh.md | 4 +- packages/core/session/package.json | 4 +- packages/core/session/src/index.ts | 10 +- .../core/session/src/known-event-types.ts | 8 +- packages/core/session/src/surface.ts | 2 +- packages/core/session/src/types.ts | 4 +- packages/core/session/tests/fork.spec.ts | 2 +- packages/core/session/tests/invariant.spec.ts | 6 +- packages/core/session/tests/session.spec.ts | 4 +- packages/core/session/tests/typert.spec.ts | 2 +- packages/core/session/tsconfig.json | 4 +- packages/core/system-prompt/README.i18n.yaml | 4 +- packages/core/system-prompt/README.md | 8 +- packages/core/system-prompt/README.zh.md | 8 +- packages/core/system-prompt/src/index.ts | 2 +- .../system-prompt/tests/invariant.spec.ts | 4 +- .../system-prompt/tests/system-prompt.spec.ts | 12 +- packages/core/system-prompt/tsconfig.json | 2 +- packages/core/tools/README.i18n.yaml | 4 +- packages/core/tools/README.md | 8 +- packages/core/tools/README.zh.md | 8 +- packages/core/tools/src/code-mode.ts | 8 +- packages/core/tools/src/index.ts | 38 +- packages/core/tools/src/presentation.ts | 2 +- packages/core/tools/tests/code-mode.spec.ts | 20 +- .../core/tools/tests/execution-mode.spec.ts | 6 +- .../core/tools/tests/gen-tool-catalog.spec.ts | 4 +- packages/core/tools/tests/invariant.spec.ts | 8 +- packages/core/tools/tests/scoped.spec.ts | 4 +- packages/core/tools/tests/tools.spec.ts | 8 +- packages/core/tools/tsconfig.json | 2 +- .../credentials-local/README.i18n.yaml | 4 +- .../credentials/credentials-local/README.md | 2 +- .../credentials-local/README.zh.md | 2 +- .../credentials-local/package.json | 8 +- .../credentials-local/src/index.ts | 26 +- .../credentials-local/tests/drain.spec.ts | 4 +- .../credentials-local/tests/local.spec.ts | 30 +- .../tests/review-fixes.spec.ts | 6 +- .../credentials-local/tests/watcher.spec.ts | 8 +- .../credentials-local/tsconfig.json | 6 +- packages/credentials/credentials/src/index.ts | 10 +- .../credentials/tests/invariant.spec.ts | 8 +- .../credentials/credentials/tests/memory.ts | 4 +- .../credentials/credentials/tsconfig.json | 2 +- packages/e2b/README.i18n.yaml | 4 +- packages/e2b/README.md | 2 +- packages/e2b/README.zh.md | 2 +- packages/e2b/e2b/src/index.ts | 6 +- packages/e2b/e2b/tests/composition.e2e.ts | 14 +- packages/e2b/e2b/tests/e2b.spec.ts | 28 +- packages/e2b/e2b/tsconfig.json | 2 +- packages/e2b/fs-e2b/tests/filesystem.spec.ts | 8 +- packages/e2b/fs-e2b/tsconfig.json | 2 +- packages/e2b/subprocess-e2b/README.i18n.yaml | 4 +- packages/e2b/subprocess-e2b/README.md | 2 +- packages/e2b/subprocess-e2b/README.zh.md | 2 +- packages/e2b/subprocess-e2b/src/index.ts | 6 +- packages/e2b/subprocess-e2b/src/process.ts | 4 +- packages/e2b/subprocess-e2b/src/terminal.ts | 4 +- .../subprocess-e2b/tests/subprocess.spec.ts | 20 +- .../e2b/subprocess-e2b/tests/terminal.spec.ts | 14 +- packages/e2b/subprocess-e2b/tsconfig.json | 2 +- packages/examples/README.i18n.yaml | 4 +- packages/examples/README.md | 2 +- packages/examples/README.zh.md | 2 +- packages/examples/acp-demo/README.i18n.yaml | 4 +- packages/examples/acp-demo/README.md | 4 +- packages/examples/acp-demo/README.zh.md | 4 +- packages/examples/acp-demo/package.json | 4 +- packages/examples/acp-demo/src/index.ts | 26 +- .../examples/acp-demo/tests/acp-agent.spec.ts | 26 +- .../examples/acp-demo/tests/built-bin.e2e.ts | 6 +- .../examples/acp-demo/tests/load-path.e2e.ts | 2 +- packages/examples/acp-demo/tsconfig.json | 4 +- .../agent-spine-demo/README.i18n.yaml | 4 +- packages/examples/agent-spine-demo/README.md | 20 +- .../examples/agent-spine-demo/README.zh.md | 18 +- .../examples/agent-spine-demo/package.json | 32 +- .../examples/agent-spine-demo/src/index.ts | 84 +- .../agent-spine-demo/tests/agent-core.spec.ts | 60 +- .../tests/multi-project-sandbox.e2e.ts | 8 +- .../examples/agent-spine-demo/tsconfig.json | 20 +- .../examples/jsonrpc-demo/README.i18n.yaml | 4 +- packages/examples/jsonrpc-demo/README.md | 6 +- packages/examples/jsonrpc-demo/README.zh.md | 6 +- packages/examples/jsonrpc-demo/package.json | 2 +- packages/examples/jsonrpc-demo/src/bin.ts | 2 +- packages/examples/jsonrpc-demo/src/index.ts | 4 +- .../examples/jsonrpc-demo/src/invariant.ts | 8 +- .../examples/jsonrpc-demo/src/packaged-bin.ts | 2 +- packages/examples/jsonrpc-demo/src/runner.ts | 2 +- packages/examples/jsonrpc-demo/tsconfig.json | 2 +- .../README.i18n.yaml | 2 +- .../README.md | 0 .../README.zh.md | 0 .../extensions/tool-cordis/README.i18n.yaml | 6 + .../tool-cordis/README.md | 4 +- .../tool-cordis/README.zh.md | 2 +- .../tool-cordis/package.json | 2 +- .../tool-cordis/src/api-catalog.ts | 682 ++++--- .../tool-cordis/src/fiber-state.ts | 0 .../tool-cordis/src/guard.ts | 2 +- .../tool-cordis/src/index.ts | 10 +- .../tool-cordis/src/inspect.ts | 0 .../tool-cordis/src/invariant.ts | 0 .../tool-cordis/src/mount.ts | 0 .../tool-cordis/src/present.ts | 0 .../tool-cordis/src/sandbox.ts | 2 +- .../tests/cordis-lifecycle.spec.ts | 0 .../tool-cordis/tests/cross-mount.spec.ts | 0 .../tool-cordis/tests/helpers.ts | 8 +- .../tool-cordis/tests/inspect.spec.ts | 10 +- .../tool-cordis/tests/integration.spec.ts | 0 .../tool-cordis/tests/mount.spec.ts | 0 .../tool-cordis/tests/present.spec.ts | 0 .../tool-cordis/tests/sandbox-context.spec.ts | 2 +- .../tool-cordis/tests/tool-cordis.spec.ts | 0 .../tool-cordis/tests/unmount-hmr.spec.ts | 4 +- .../tool-cordis/tsconfig.json | 2 +- .../command-feedback/README.i18n.yaml | 4 +- packages/feedback/command-feedback/README.md | 6 +- .../feedback/command-feedback/README.zh.md | 6 +- .../feedback/command-feedback/package.json | 4 +- .../feedback/command-feedback/src/index.ts | 12 +- .../tests/command-feedback.spec.ts | 28 +- .../tests/loader-composition.spec.ts | 8 +- .../feedback/command-feedback/tsconfig.json | 4 +- .../message-feedback/README.i18n.yaml | 4 +- packages/feedback/message-feedback/README.md | 2 +- .../feedback/message-feedback/README.zh.md | 2 +- .../feedback/message-feedback/package.json | 4 +- .../feedback/message-feedback/src/index.ts | 4 +- .../message-feedback/tests/invariant.spec.ts | 4 +- .../tests/loader-composition.spec.ts | 8 +- .../tests/message-feedback.spec.ts | 4 +- .../feedback/message-feedback/tsconfig.json | 4 +- packages/fs/README.i18n.yaml | 4 +- packages/fs/README.md | 4 +- packages/fs/README.zh.md | 4 +- packages/fs/fs-local/README.i18n.yaml | 4 +- packages/fs/fs-local/README.md | 2 +- packages/fs/fs-local/README.zh.md | 2 +- packages/fs/fs-local/tests/filesystem.spec.ts | 2 +- packages/fs/fs-local/tests/fsio.spec.ts | 2 +- packages/fs/fs-local/tsconfig.json | 2 +- .../fs/fs-observation-policy/README.i18n.yaml | 6 + .../README.md | 8 +- .../README.zh.md | 8 +- .../package.json | 4 +- .../src/index.ts | 14 +- .../src/invariant.ts | 8 +- .../src/types.ts | 8 +- .../tests/policy.spec.ts | 12 +- .../tsconfig.json | 2 +- packages/fs/fs-sandbox/README.i18n.yaml | 4 +- packages/fs/fs-sandbox/README.md | 4 +- packages/fs/fs-sandbox/README.zh.md | 4 +- packages/fs/fs-sandbox/src/index.ts | 2 +- packages/fs/fs-sandbox/tsconfig.json | 2 +- packages/fs/fs/README.i18n.yaml | 4 +- packages/fs/fs/README.md | 6 +- packages/fs/fs/README.zh.md | 6 +- packages/fs/fs/src/index.ts | 2 +- packages/fs/fs/tests/invariant.spec.ts | 4 +- packages/fs/fs/tests/service.spec.ts | 2 +- packages/fs/fs/tsconfig.json | 2 +- packages/fs/tool-fs-search/README.i18n.yaml | 4 +- packages/fs/tool-fs-search/README.md | 6 +- packages/fs/tool-fs-search/README.zh.md | 4 +- packages/fs/tool-fs-search/package.json | 4 +- packages/fs/tool-fs-search/src/grep.ts | 2 +- packages/fs/tool-fs-search/src/index.ts | 7 +- .../fs/tool-fs-search/src/presentation.ts | 2 +- packages/fs/tool-fs-search/src/search-core.ts | 10 +- .../tool-fs-search/tests/integration.spec.ts | 8 +- .../fs/tool-fs-search/tests/load-path.spec.ts | 8 +- .../fs/tool-fs-search/tests/tools.spec.ts | 18 +- packages/fs/tool-fs-search/tsconfig.json | 6 +- packages/fs/tool-fs/README.i18n.yaml | 4 +- packages/fs/tool-fs/README.md | 14 +- packages/fs/tool-fs/README.zh.md | 14 +- packages/fs/tool-fs/package.json | 2 +- packages/fs/tool-fs/src/edit.ts | 2 +- packages/fs/tool-fs/src/sandbox.ts | 2 +- packages/fs/tool-fs/src/write.ts | 2 +- packages/fs/tool-fs/tests/harness.ts | 2 +- packages/fs/tool-fs/tests/integration.spec.ts | 16 +- packages/fs/tool-fs/tests/read-image.spec.ts | 12 +- packages/fs/tool-fs/tests/tools.spec.ts | 18 +- packages/fs/tool-fs/tsconfig.json | 4 +- .../fs/tool-str-replace-editor/package.json | 2 +- .../tests/tools.spec.ts | 10 +- .../fs/tool-str-replace-editor/tsconfig.json | 2 +- packages/goal/README.i18n.yaml | 4 +- packages/goal/README.md | 2 +- packages/goal/README.zh.md | 2 +- .../command-goal/tests/command-goal.spec.ts | 6 +- packages/goal/command-goal/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 6 +- .../README.zh.md | 6 +- .../package.json | 4 +- .../src/index.ts | 24 +- .../src/invariant.ts | 8 +- .../src/prompt.ts | 0 .../tests/goal-round-driver.spec.ts} | 6 +- .../tests/invariant.spec.ts | 24 +- .../tsconfig.json | 2 +- .../tsdown.config.ts | 0 packages/goal/goal/package.json | 4 +- packages/goal/goal/src/index.ts | 4 +- packages/goal/goal/tests/invariant.spec.ts | 6 +- packages/goal/goal/tsconfig.json | 4 +- .../goal/tool-goal/tests/tool-goal.spec.ts | 8 +- packages/goal/tool-goal/tsconfig.json | 2 +- packages/guard/README.i18n.yaml | 4 +- packages/guard/README.md | 2 +- packages/guard/README.zh.md | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 10 +- .../README.zh.md | 10 +- .../package.json | 4 +- .../src/index.ts | 18 +- .../src/invariant.ts | 8 +- .../tests/repeat-tool-reminder.spec.ts} | 6 +- .../tsconfig.json | 2 +- .../guard/timeout-policy/README.i18n.yaml | 4 +- packages/guard/timeout-policy/README.md | 4 +- packages/guard/timeout-policy/README.zh.md | 4 +- packages/guard/timeout-policy/package.json | 2 +- packages/guard/timeout-policy/src/index.ts | 2 +- .../guard/timeout-policy/src/invariant.ts | 6 +- .../tests/timeout-policy.spec.ts | 16 +- packages/guard/timeout-policy/tsconfig.json | 2 +- packages/hooks/README.i18n.yaml | 4 +- packages/hooks/README.md | 2 +- packages/hooks/README.zh.md | 2 +- packages/hooks/hook-protocol/README.i18n.yaml | 4 +- packages/hooks/hook-protocol/README.md | 12 +- packages/hooks/hook-protocol/README.zh.md | 12 +- packages/hooks/hook-protocol/package.json | 4 +- packages/hooks/hook-protocol/src/invariant.ts | 2 +- packages/hooks/hook-protocol/src/matcher.ts | 4 +- packages/hooks/hook-protocol/src/runner.ts | 8 +- packages/hooks/hook-protocol/src/types.ts | 8 +- .../hooks/hook-protocol/tests/events.spec.ts | 6 +- .../hook-protocol/tests/invariant.spec.ts | 10 +- .../hooks/hook-protocol/tests/matcher.spec.ts | 34 +- .../hooks/hook-protocol/tests/runner.spec.ts | 20 +- packages/hooks/hook-protocol/tsconfig.json | 4 +- .../README.i18n.yaml | 6 +- .../README.md | 10 +- .../README.zh.md | 10 +- .../package.json | 6 +- .../src/config.ts | 12 +- .../src/index.ts | 38 +- .../src/invariant.ts | 8 +- .../tests/bridge.spec.ts | 42 +- .../tests/config.spec.ts | 36 +- .../tests/coverage-cases.ts | 54 +- .../tests/coverage-config.spec.ts | 0 .../tests/coverage-context.spec.ts | 0 .../tests/coverage-edge-paths.spec.ts | 0 .../tests/coverage-stop.spec.ts | 0 .../tsconfig.json | 4 +- packages/hooks/hooks-codex/package.json | 2 +- packages/hooks/hooks-codex/src/index.ts | 4 +- .../hooks/hooks-codex/tests/bridge.spec.ts | 12 +- .../hooks/hooks-codex/tests/coverage-cases.ts | 14 +- packages/hooks/hooks-codex/tsconfig.json | 4 +- packages/host/README.i18n.yaml | 4 +- packages/host/README.md | 6 +- packages/host/README.zh.md | 6 +- packages/host/apiproxy/README.i18n.yaml | 4 +- packages/host/apiproxy/README.md | 6 +- packages/host/apiproxy/README.zh.md | 6 +- packages/host/apiproxy/package.json | 6 +- packages/host/apiproxy/src/api-proxy.ts | 106 +- .../host/apiproxy/src/api/events.schema.ts | 10 +- packages/host/apiproxy/src/api/events.ts | 8 +- packages/host/apiproxy/src/api/index.ts | 2 +- .../api/{tasks.schema.ts => jobs.schema.ts} | 14 +- .../apiproxy/src/api/{tasks.ts => jobs.ts} | 10 +- .../host/apiproxy/src/api/questions.schema.ts | 4 +- packages/host/apiproxy/src/api/questions.ts | 4 +- packages/host/apiproxy/src/api/sessions.ts | 2 +- packages/host/apiproxy/src/index.ts | 2 +- packages/host/apiproxy/src/session-export.ts | 6 +- .../tests/api-proxy-agent-preset.spec.ts | 6 +- .../apiproxy/tests/api-proxy-approval.spec.ts | 8 +- .../apiproxy/tests/api-proxy-blank.spec.ts | 6 +- .../apiproxy/tests/api-proxy-cold.spec.ts | 36 +- .../apiproxy/tests/api-proxy-config.spec.ts | 34 +- .../apiproxy/tests/api-proxy-fork.spec.ts | 6 +- ...y-tasks.spec.ts => api-proxy-jobs.spec.ts} | 90 +- .../apiproxy/tests/api-proxy-models.spec.ts | 8 +- .../tests/api-proxy-projections.spec.ts | 4 +- .../apiproxy/tests/api-proxy-question.spec.ts | 8 +- .../apiproxy/tests/api-proxy-rename.spec.ts | 4 +- .../apiproxy/tests/api-proxy-search.spec.ts | 4 +- .../tests/api-proxy-subagents.spec.ts | 2 +- .../apiproxy/tests/api-proxy-view.spec.ts | 10 +- .../tests/api-proxy-workspace.spec.ts | 10 +- .../host/apiproxy/tests/rpc-schemas.spec.ts | 16 +- .../apiproxy/tests/session-export.spec.ts | 4 +- packages/host/apiproxy/tsconfig.json | 6 +- .../directory-picker-auto/README.i18n.yaml | 4 +- packages/host/directory-picker-auto/README.md | 2 +- .../host/directory-picker-auto/README.zh.md | 2 +- .../host/directory-picker-auto/package.json | 4 +- .../host/directory-picker-auto/src/index.ts | 12 +- .../tests/loader-composition.spec.ts | 2 +- .../host/directory-picker-auto/tsconfig.json | 2 +- .../directory-picker-browse/tsconfig.json | 2 +- .../tests/built-worker.e2e.ts | 2 +- .../directory-picker-native/tsconfig.json | 2 +- .../directory-picker-native/tsdown.config.ts | 2 +- packages/host/directory-picker/tsconfig.json | 2 +- .../host/frontend-static/README.i18n.yaml | 4 +- packages/host/frontend-static/README.md | 2 +- packages/host/frontend-static/README.zh.md | 2 +- packages/host/frontend-static/package.json | 2 +- packages/host/frontend-static/src/index.ts | 12 +- .../host/frontend-static/src/invariant.ts | 8 +- .../tests/frontend-static.spec.ts | 6 +- packages/host/frontend-static/tsconfig.json | 2 +- .../host/plugin-inventory/README.i18n.yaml | 4 +- packages/host/plugin-inventory/README.md | 4 +- packages/host/plugin-inventory/README.zh.md | 4 +- packages/host/plugin-inventory/package.json | 4 +- packages/host/plugin-inventory/src/index.ts | 8 +- .../plugin-inventory/tests/invariant.spec.ts | 4 +- .../plugin-inventory/tests/inventory.spec.ts | 14 +- packages/host/plugin-inventory/tsconfig.json | 4 +- packages/host/webserver/README.i18n.yaml | 4 +- packages/host/webserver/README.md | 2 +- packages/host/webserver/README.zh.md | 2 +- packages/host/webserver/src/index.ts | 12 +- packages/host/webserver/src/invariant.ts | 4 +- .../host/webserver/tests/webserver.spec.ts | 4 +- packages/host/webserver/tsconfig.json | 2 +- packages/{pty => identity}/README.i18n.yaml | 6 +- packages/identity/README.md | 9 + packages/identity/README.zh.md | 9 + .../anonymous-user-id/README.i18n.yaml | 6 + .../anonymous-user-id}/README.md | 6 +- .../anonymous-user-id}/README.zh.md | 6 +- .../anonymous-user-id}/package.json | 8 +- .../anonymous-user-id}/src/index.ts | 10 +- .../anonymous-user-id}/src/invariant.ts | 8 +- .../tests/anonymous-user-id.spec.ts} | 20 +- .../tests/invariant.spec.ts | 6 +- .../anonymous-user-id}/tsconfig.json | 4 +- packages/interaction/README.i18n.yaml | 4 +- packages/interaction/README.md | 6 +- packages/interaction/README.zh.md | 6 +- packages/interaction/commands/package.json | 4 +- packages/interaction/commands/src/index.ts | 8 +- .../commands/tests/commands.spec.ts | 6 +- .../commands/tests/invariant.spec.ts | 4 +- packages/interaction/commands/tsconfig.json | 4 +- .../README.i18n.yaml | 6 +- .../interaction/permission-presets/README.md | 28 + .../permission-presets/README.zh.md | 28 + .../package.json | 10 +- .../src/client.ts | 2 +- .../src/index.ts | 28 +- .../src/invariant.ts | 10 +- .../src/types.ts | 2 +- .../tests/invariant.spec.ts | 10 +- .../tests/permission-presets.spec.ts} | 88 +- .../tests/projection.spec.ts | 18 +- .../tsconfig.json | 4 +- packages/interaction/permission/README.md | 28 - packages/interaction/permission/README.zh.md | 28 - .../tool-ask-user/README.i18n.yaml | 4 +- packages/interaction/tool-ask-user/README.md | 6 +- .../interaction/tool-ask-user/README.zh.md | 4 +- .../interaction/tool-ask-user/package.json | 6 +- .../interaction/tool-ask-user/src/index.ts | 8 +- .../tool-ask-user/tests/tool-ask-user.spec.ts | 38 +- .../interaction/tool-ask-user/tsconfig.json | 4 +- .../user-approval/tests/invariant.spec.ts | 8 +- .../interaction/user-approval/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 16 +- .../README.zh.md | 16 +- .../package.json | 6 +- .../src/index.ts | 46 +- .../src/invariant.ts | 8 +- .../src/types.ts | 4 +- .../tests/user-questions.spec.ts} | 104 +- .../tsconfig.json | 2 +- packages/{bash => jobs}/README.i18n.yaml | 6 +- packages/jobs/README.md | 15 + packages/jobs/README.zh.md | 15 + .../jobs-local}/README.i18n.yaml | 6 +- packages/jobs/jobs-local/README.md | 34 + .../jobs-local}/README.zh.md | 12 +- .../jobs-local}/package.json | 10 +- .../jobs-local}/src/index.ts | 316 +-- .../jobs-local}/src/invariant.ts | 12 +- .../jobs-local/tests/jobs.spec.ts} | 642 +++---- .../tests/loader-composition.spec.ts | 22 +- .../jobs-local}/tsconfig.json | 4 +- .../{bash/bash => jobs/jobs}/README.i18n.yaml | 6 +- packages/jobs/jobs/README.md | 40 + .../{tasks/tasks => jobs/jobs}/README.zh.md | 16 +- .../{tasks/tasks => jobs/jobs}/package.json | 6 +- .../{tasks/tasks => jobs/jobs}/src/brand.ts | 16 +- .../{tasks/tasks => jobs/jobs}/src/index.ts | 112 +- .../tasks => jobs/jobs}/src/invariant.ts | 30 +- .../{tasks/tasks => jobs/jobs}/src/types.ts | 92 +- .../jobs}/tests/invariant.spec.ts | 52 +- packages/jobs/jobs/tests/service.spec.ts | 96 + .../{tasks/tasks => jobs/jobs}/tsconfig.json | 2 +- .../tool-jobs}/README.i18n.yaml | 6 +- packages/jobs/tool-jobs/README.md | 96 + .../tool-jobs}/README.zh.md | 24 +- .../tool-jobs}/package.json | 16 +- .../tool-jobs}/src/index.ts | 156 +- .../tool-jobs}/src/invariant.ts | 8 +- .../tool-jobs/tests/tool-jobs.spec.ts} | 346 ++-- .../tool-jobs}/tsconfig.json | 6 +- packages/llm/llm-deepseek/README.i18n.yaml | 4 +- packages/llm/llm-deepseek/README.md | 6 +- packages/llm/llm-deepseek/README.zh.md | 6 +- packages/llm/llm-deepseek/package.json | 8 +- packages/llm/llm-deepseek/src/adapter.ts | 2 +- packages/llm/llm-deepseek/src/index.ts | 10 +- .../llm/llm-deepseek/tests/adapter.e2e.ts | 10 +- .../llm/llm-deepseek/tests/adapter.spec.ts | 58 +- .../llm-deepseek/tests/dynamic-config.spec.ts | 18 +- .../tests/loader-composition.spec.ts | 20 +- packages/llm/llm-deepseek/tsconfig.json | 6 +- packages/llm/llm-pi-ai/README.i18n.yaml | 4 +- packages/llm/llm-pi-ai/README.md | 2 +- packages/llm/llm-pi-ai/README.zh.md | 2 +- packages/llm/llm-pi-ai/package.json | 4 +- packages/llm/llm-pi-ai/src/index.ts | 4 +- packages/llm/llm-pi-ai/tests/adapter.e2e.ts | 6 +- packages/llm/llm-pi-ai/tests/adapter.spec.ts | 42 +- packages/llm/llm-pi-ai/tests/catalog.spec.ts | 10 +- .../llm/llm-pi-ai/tests/discovery.spec.ts | 10 +- .../llm-pi-ai/tests/dynamic-config.spec.ts | 12 +- .../tests/loader-composition.spec.ts | 18 +- .../llm/llm-pi-ai/tests/provider-apis.e2e.ts | 4 +- packages/llm/llm-pi-ai/tsconfig.json | 4 +- .../llm/llm-retry/tests/invariant.spec.ts | 10 +- .../tests/loader-composition.spec.ts | 8 +- .../llm/llm-retry/tests/persistence.spec.ts | 8 +- packages/llm/llm-retry/tests/retry.spec.ts | 8 +- packages/llm/llm-retry/tsconfig.json | 2 +- packages/llm/llm/README.i18n.yaml | 4 +- packages/llm/llm/README.md | 8 +- packages/llm/llm/README.zh.md | 8 +- packages/llm/llm/src/index.ts | 20 +- packages/llm/llm/src/message.ts | 2 +- packages/llm/llm/src/types.ts | 2 +- packages/llm/llm/tests/invariant.spec.ts | 10 +- packages/llm/llm/tests/service.spec.ts | 94 +- packages/llm/llm/tests/topology.spec.ts | 4 +- packages/llm/llm/tsconfig.json | 2 +- packages/llm/token-meter/README.i18n.yaml | 4 +- packages/llm/token-meter/README.md | 8 +- packages/llm/token-meter/README.zh.md | 8 +- packages/llm/token-meter/package.json | 4 +- packages/llm/token-meter/src/index.ts | 6 +- .../llm/token-meter/src/surface-projection.ts | 8 +- .../context-breakdown-projection.spec.ts | 16 +- .../llm/token-meter/tests/token-meter.spec.ts | 22 +- .../tests/token-usage-projection.spec.ts | 14 +- packages/llm/token-meter/tsconfig.json | 4 +- packages/lsp/README.i18n.yaml | 4 +- packages/lsp/README.md | 2 +- packages/lsp/README.zh.md | 2 +- .../{lsp-local => lsp-stdio}/README.i18n.yaml | 6 +- .../lsp/{lsp-local => lsp-stdio}/README.md | 2 +- .../lsp/{lsp-local => lsp-stdio}/README.zh.md | 2 +- .../lsp/{lsp-local => lsp-stdio}/package.json | 4 +- .../lsp/{lsp-local => lsp-stdio}/src/abort.ts | 2 +- .../src/connection.ts | 4 +- .../{lsp-local => lsp-stdio}/src/framing.ts | 2 +- .../lsp/{lsp-local => lsp-stdio}/src/host.ts | 0 .../lsp/{lsp-local => lsp-stdio}/src/index.ts | 24 +- .../{lsp-local => lsp-stdio}/src/instance.ts | 2 +- .../{lsp-local => lsp-stdio}/src/invariant.ts | 8 +- .../{lsp-local => lsp-stdio}/src/protocol.ts | 2 +- .../{lsp-local => lsp-stdio}/src/translate.ts | 2 +- .../tests/built-lib.e2e.ts | 8 +- .../tests/connection.spec.ts | 4 +- .../tests/fixture-server.ts | 2 +- .../tests/framing.spec.ts | 2 +- .../tests/host.spec.ts | 2 +- .../tests/instance.spec.ts | 8 +- .../tests/lifecycle.spec.ts | 24 +- .../tests/provider.spec.ts | 38 +- .../tests/translate.spec.ts | 4 +- .../tests/typescript-server.e2e.ts | 8 +- .../{lsp-local => lsp-stdio}/tsconfig.json | 2 +- packages/lsp/lsp/README.i18n.yaml | 4 +- packages/lsp/lsp/README.md | 2 +- packages/lsp/lsp/README.zh.md | 2 +- packages/lsp/lsp/tsconfig.json | 2 +- packages/lsp/tool-lsp/README.i18n.yaml | 4 +- packages/lsp/tool-lsp/README.md | 2 +- packages/lsp/tool-lsp/README.zh.md | 2 +- packages/lsp/tool-lsp/package.json | 4 +- packages/lsp/tool-lsp/src/index.ts | 2 +- .../lsp/tool-lsp/tests/integration.spec.ts | 12 +- packages/lsp/tool-lsp/tests/tool-lsp.spec.ts | 4 +- packages/lsp/tool-lsp/tsconfig.json | 2 +- packages/mcp/mcp-client/src/tools.ts | 10 +- packages/mcp/mcp-client/tests/apply.spec.ts | 8 +- .../mcp/mcp-client/tests/mcp-client.e2e.ts | 4 +- .../mcp/mcp-client/tests/mcp-client.spec.ts | 4 +- .../mcp/mcp-client/tests/reconnect.spec.ts | 4 +- packages/mcp/mcp-client/tsconfig.json | 2 +- packages/plan/plan-mode/README.i18n.yaml | 4 +- packages/plan/plan-mode/README.md | 4 +- packages/plan/plan-mode/README.zh.md | 4 +- packages/plan/plan-mode/package.json | 4 +- packages/plan/plan-mode/src/index.ts | 14 +- .../plan/plan-mode/tests/integration.spec.ts | 12 +- .../plan/plan-mode/tests/invariant.spec.ts | 10 +- .../plan/plan-mode/tests/plan-mode.spec.ts | 102 +- .../plan/plan-mode/tests/projection.spec.ts | 14 +- packages/plan/plan-mode/tsconfig.json | 4 +- .../preset/agent-presets/README.i18n.yaml | 4 +- packages/preset/agent-presets/README.md | 4 +- packages/preset/agent-presets/README.zh.md | 4 +- packages/preset/agent-presets/package.json | 6 +- .../preset/agent-presets/src/authoring.ts | 2 +- .../preset/agent-presets/src/discovery.ts | 4 +- packages/preset/agent-presets/src/index.ts | 6 +- .../agent-presets/tests/invariant.spec.ts | 12 +- .../preset/agent-presets/tests/mount.spec.ts | 16 +- .../agent-presets/tests/settings.spec.ts | 12 +- .../agent-presets/tests/user-root.spec.ts | 2 +- packages/preset/agent-presets/tsconfig.json | 4 +- packages/preset/persona/tsconfig.json | 2 +- packages/pty/README.md | 15 - packages/pty/README.zh.md | 15 - packages/pty/pty-local/README.i18n.yaml | 6 - .../pty/tool-bash-persistent/README.i18n.yaml | 6 - packages/pty/tool-pty/README.i18n.yaml | 6 - packages/pty/tool-pty/README.md | 71 - packages/pty/tool-pty/README.zh.md | 71 - .../invariants/README.i18n.yaml | 6 + .../invariants/README.md | 14 +- .../invariants/README.zh.md | 14 +- .../invariants/package.json | 2 +- .../invariants/src/index.ts | 6 +- .../invariants/src/invariant.ts | 0 .../invariants/tests/service.spec.ts | 14 +- .../invariants/tsconfig.json | 0 .../sandbox/sandbox-local/README.i18n.yaml | 4 +- packages/sandbox/sandbox-local/README.md | 4 +- packages/sandbox/sandbox-local/README.zh.md | 4 +- .../sandbox-local/tests/packed-install.e2e.ts | 4 +- packages/sandbox/sandbox-local/tsconfig.json | 2 +- .../sandbox-policy/tests/invariant.spec.ts | 6 +- packages/sandbox/sandbox-policy/tsconfig.json | 2 +- .../sandbox-windows-acl/README.i18n.yaml | 4 +- .../sandbox/sandbox-windows-acl/README.md | 2 +- .../sandbox/sandbox-windows-acl/README.zh.md | 2 +- .../sandbox/sandbox-windows-acl/tsconfig.json | 2 +- packages/sandbox/sandbox/README.i18n.yaml | 4 +- packages/sandbox/sandbox/README.md | 6 +- packages/sandbox/sandbox/README.zh.md | 6 +- packages/sandbox/sandbox/tsconfig.json | 2 +- packages/schedule/README.i18n.yaml | 4 +- packages/schedule/README.md | 2 +- packages/schedule/README.zh.md | 2 +- .../README.i18n.yaml | 6 +- .../{tool-schedule => schedule}/README.md | 4 +- .../{tool-schedule => schedule}/README.zh.md | 4 +- .../{tool-schedule => schedule}/package.json | 4 +- .../{tool-schedule => schedule}/src/domain.ts | 2 +- .../{tool-schedule => schedule}/src/index.ts | 32 +- .../src/invariant.ts | 4 +- .../src/persistence.ts | 0 .../src/runtime.ts | 34 +- .../{tool-schedule => schedule}/src/tools.ts | 6 +- .../src/transaction.ts | 0 .../{tool-schedule => schedule}/src/types.ts | 2 +- .../tests/domain.spec.ts | 0 .../tests/invariant.spec.ts | 8 +- .../tests/jsonl-restart.spec.ts | 6 +- .../tests/plugin.spec.ts | 2 +- .../tests/recurrence.spec.ts | 0 .../tests/runtime.spec.ts | 258 +-- .../tests/tools.spec.ts | 4 +- .../{tool-schedule => schedule}/tsconfig.json | 2 +- .../tsdown.config.ts | 0 packages/sdk/client/tsconfig.json | 2 +- packages/sdk/protocol/README.i18n.yaml | 4 +- packages/sdk/protocol/README.md | 8 +- packages/sdk/protocol/README.zh.md | 8 +- packages/sdk/protocol/src/index.ts | 2 +- packages/sdk/protocol/src/types.ts | 4 +- packages/sdk/protocol/tsconfig.json | 2 +- packages/sdk/server/README.i18n.yaml | 4 +- packages/sdk/server/README.md | 4 +- packages/sdk/server/README.zh.md | 4 +- packages/sdk/server/package.json | 2 +- packages/sdk/server/src/index.ts | 12 +- packages/sdk/server/src/invariant.ts | 8 +- packages/sdk/server/src/server.ts | 16 +- .../server/tests/built-scope-carrier.e2e.ts | 16 +- .../sdk/server/tests/plugin-apply.spec.ts | 6 +- .../sdk/server/tests/plugin-shape.spec.ts | 4 +- packages/sdk/server/tests/server.spec.ts | 54 +- packages/sdk/server/tsconfig.json | 2 +- .../tool-cordis/README.i18n.yaml | 6 - packages/session-query/README.i18n.yaml | 4 +- packages/session-query/README.md | 2 +- packages/session-query/README.zh.md | 2 +- .../session-export/tsdown.config.ts | 3 - .../README.i18n.yaml | 6 +- .../README.md | 8 +- .../README.zh.md | 8 +- .../package.json | 10 +- .../src/client/Dialog.tsx | 18 +- .../src/client/HeaderAction.module.css | 0 .../src/client/HeaderAction.tsx | 10 +- .../src/client/controller.ts | 18 +- .../src/client/index.ts | 34 +- .../src/client/locales.ts | 4 +- .../src/css-modules.d.ts | 0 .../src/index.ts | 4 +- .../src/invariant.ts | 4 +- .../tests/client-apply.client.spec.tsx | 34 +- .../tests/command.client.spec.ts | 4 +- .../tests/controller.client.spec.ts | 22 +- .../tests/dialog.client.spec.tsx | 18 +- .../tests/header-action.client.spec.tsx | 30 +- .../tests/invariant.client.spec.ts | 4 +- .../tests/loader-composition.client.spec.ts | 12 +- .../tsconfig.json | 4 +- .../session-log-download/tsdown.config.ts | 3 + .../session-query-sqlite/README.i18n.yaml | 4 +- .../session-query-sqlite/README.md | 2 +- .../session-query-sqlite/README.zh.md | 2 +- .../session-query-sqlite/src/index.ts | 6 +- .../tests/load-path.e2e.ts | 8 +- .../session-query-sqlite/tests/sqlite.spec.ts | 94 +- .../session-query-sqlite/tsconfig.json | 2 +- .../session-query/README.i18n.yaml | 4 +- .../session-query/session-query/README.md | 4 +- .../session-query/session-query/README.zh.md | 4 +- .../session-query/session-query/src/index.ts | 6 +- .../tests/search-helpers.spec.ts | 6 +- .../session-query/tests/session-query.spec.ts | 18 +- .../session-query/tests/test-service.ts | 4 +- .../session-query/tests/tracing.spec.ts | 4 +- .../session-query/session-query/tsconfig.json | 2 +- .../tool-session-query/package.json | 2 +- .../tests/sqlite-integration.spec.ts | 18 +- .../tests/tool-session-query.spec.ts | 10 +- .../tool-session-query/tsconfig.json | 2 +- packages/session/README.i18n.yaml | 4 +- packages/session/README.md | 8 +- packages/session/README.zh.md | 6 +- .../tests/crash-recovery.e2e.ts | 4 +- .../tests/fixtures/crash-child.ts | 4 +- .../tests/session-checkpoint-policy.spec.ts | 12 +- .../session-checkpoint-policy/tsconfig.json | 2 +- .../session-persistence-jsonl/src/index.ts | 4 +- .../tests/jsonl.spec.ts | 62 +- .../tests/zstd.spec.ts | 18 +- .../session-persistence-jsonl/tsconfig.json | 2 +- .../session-persistence-sqlite/src/index.ts | 4 +- .../tests/sqlite.spec.ts | 44 +- .../session-persistence-sqlite/tsconfig.json | 2 +- .../session-persistence/README.i18n.yaml | 4 +- .../session/session-persistence/README.md | 2 +- .../session/session-persistence/README.zh.md | 2 +- .../session/session-persistence/tsconfig.json | 2 +- .../session-projection-cache/tsconfig.json | 2 +- .../session/session-projection/tsconfig.json | 2 +- packages/session/session-stats/tsconfig.json | 2 +- .../session-telemetry-otel/README.i18n.yaml | 4 +- .../session/session-telemetry-otel/README.md | 16 +- .../session-telemetry-otel/README.zh.md | 16 +- .../session-telemetry-otel/package.json | 4 +- .../session-telemetry-otel/src/index.ts | 66 +- .../tests/loader-composition.e2e.ts | 4 +- .../session-telemetry-otel/tests/otel.spec.ts | 114 +- .../session-telemetry-otel/tsconfig.json | 4 +- .../session-telemetry/README.i18n.yaml | 4 +- packages/session/session-telemetry/README.md | 12 +- .../session/session-telemetry/README.zh.md | 12 +- .../session/session-telemetry/package.json | 2 +- .../session-telemetry/src/coordinator.ts | 24 +- .../session/session-telemetry/src/index.ts | 48 +- .../session-telemetry/tests/redact.spec.ts | 32 +- .../session-telemetry/tests/telemetry.spec.ts | 56 +- .../session/session-telemetry/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 4 +- .../README.zh.md | 4 +- .../package.json | 4 +- .../src/index.ts | 8 +- .../src/invariant.ts | 8 +- .../tests/provider.spec.ts | 6 +- .../tsconfig.json | 2 +- .../README.i18n.yaml | 6 - .../README.i18n.yaml | 6 +- .../README.md | 4 +- .../README.zh.md | 4 +- .../package.json | 4 +- .../src/index.ts | 8 +- .../src/invariant.ts | 8 +- .../tests/loader-composition.spec.ts | 12 +- .../tests/provider.e2e.ts | 10 +- .../tests/provider.spec.ts | 10 +- .../tsconfig.json | 2 +- .../session-title-llm/tests/llm.spec.ts | 12 +- .../session/session-title-llm/tsconfig.json | 2 +- .../session/session-title/README.i18n.yaml | 4 +- packages/session/session-title/README.md | 4 +- packages/session/session-title/README.zh.md | 2 +- packages/session/session-title/src/index.ts | 6 +- .../session/session-title/src/normalize.ts | 2 +- .../session-title/tests/invariant.spec.ts | 4 +- .../session-title/tests/persistence.spec.ts | 12 +- .../session-title/tests/provider.spec.ts | 30 +- .../session-title/tests/rename.spec.ts | 4 +- .../tests/service-contracts.spec.ts | 22 +- packages/session/session-title/tsconfig.json | 2 +- packages/settings/README.i18n.yaml | 4 +- packages/settings/README.md | 2 +- packages/settings/README.zh.md | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 8 +- .../src/index.ts | 24 +- .../src/invariant.ts | 8 +- .../tests/concurrency.spec.ts | 6 +- .../tests/loader-composition.spec.ts | 8 +- .../tests/local.spec.ts | 6 +- .../tests/lock-race.spec.ts | 8 +- .../tests/watcher.spec.ts | 8 +- .../tsconfig.json | 4 +- packages/settings/settings/src/index.ts | 8 +- .../settings/settings/tests/invariant.spec.ts | 4 +- packages/settings/settings/tests/memory.ts | 6 +- .../settings/settings/tests/settings.spec.ts | 6 +- packages/settings/settings/tsconfig.json | 2 +- packages/{tasks => shell}/README.i18n.yaml | 6 +- packages/{bash => shell}/README.md | 16 +- packages/{bash => shell}/README.zh.md | 14 +- packages/shell/bash-local/README.i18n.yaml | 6 + packages/{bash => shell}/bash-local/README.md | 8 +- .../{bash => shell}/bash-local/README.zh.md | 8 +- .../{bash => shell}/bash-local/package.json | 6 +- .../{bash => shell}/bash-local/src/index.ts | 28 +- .../bash-local/src/invariant.ts | 0 .../bash-local/tests/executor.spec.ts | 26 +- .../bash-local/tests/settings.spec.ts | 30 +- .../{bash => shell}/bash-local/tsconfig.json | 4 +- packages/shell/bash-sandbox/README.i18n.yaml | 6 + .../{bash => shell}/bash-sandbox/README.md | 10 +- .../{bash => shell}/bash-sandbox/README.zh.md | 10 +- .../{bash => shell}/bash-sandbox/package.json | 6 +- .../bash-sandbox/src/helpers.ts | 4 +- .../{bash => shell}/bash-sandbox/src/index.ts | 20 +- .../bash-sandbox/src/invariant.ts | 0 .../bash-sandbox/tests/bwrap.e2e.ts | 8 +- .../bash-sandbox/tests/landlock.e2e.ts | 8 +- .../tests/partial-landlock.spec.ts | 10 +- .../bash-sandbox/tests/sandbox.spec.ts | 24 +- .../bash-sandbox/tests/seatbelt.e2e.ts | 8 +- .../bash-sandbox/tsconfig.json | 6 +- packages/shell/pwsh-local/README.i18n.yaml | 6 + packages/{bash => shell}/pwsh-local/README.md | 10 +- .../{bash => shell}/pwsh-local/README.zh.md | 8 +- .../{bash => shell}/pwsh-local/package.json | 6 +- .../{bash => shell}/pwsh-local/src/index.ts | 28 +- .../pwsh-local/src/invariant.ts | 0 .../{bash => shell}/pwsh-local/src/resolve.ts | 0 .../pwsh-local/tests/executor.spec.ts | 34 +- .../pwsh-local/tests/settings.spec.ts | 26 +- .../{bash => shell}/pwsh-local/tsconfig.json | 4 +- packages/shell/pwsh-sandbox/README.i18n.yaml | 6 + .../{bash => shell}/pwsh-sandbox/README.md | 2 +- .../{bash => shell}/pwsh-sandbox/README.zh.md | 2 +- .../{bash => shell}/pwsh-sandbox/package.json | 6 +- .../pwsh-sandbox/src/helpers.ts | 4 +- .../{bash => shell}/pwsh-sandbox/src/index.ts | 20 +- .../pwsh-sandbox/src/invariant.ts | 0 .../pwsh-sandbox/tests/acl.e2e.ts | 6 +- .../pwsh-sandbox/tests/sandbox.spec.ts | 18 +- .../pwsh-sandbox/tsconfig.json | 6 +- packages/shell/shell-env/README.i18n.yaml | 6 + packages/shell/shell-env/README.md | 51 + packages/shell/shell-env/README.zh.md | 51 + .../bash-env => shell/shell-env}/package.json | 12 +- .../bash-env => shell/shell-env}/src/index.ts | 32 +- .../shell-env}/src/invariant.ts | 8 +- .../shell-env/tests/shell-env.spec.ts} | 42 +- .../shell-env}/tsconfig.json | 6 +- packages/shell/shell/README.i18n.yaml | 6 + packages/{bash/bash => shell/shell}/README.md | 28 +- packages/shell/shell/README.zh.md | 53 + .../{bash/bash => shell/shell}/package.json | 6 +- .../{bash/bash => shell/shell}/src/index.ts | 48 +- .../bash => shell/shell}/src/invariant.ts | 6 +- .../{bash/bash => shell/shell}/src/render.ts | 4 +- .../{bash/bash => shell/shell}/src/types.ts | 44 +- .../bash => shell/shell}/tests/render.spec.ts | 0 .../shell}/tests/service.spec.ts | 34 +- .../{bash/bash => shell/shell}/tsconfig.json | 2 +- .../tool-bash-persistent/README.i18n.yaml | 6 + .../tool-bash-persistent/README.md | 2 +- .../tool-bash-persistent/README.zh.md | 2 +- .../tool-bash-persistent/package.json | 8 +- .../tool-bash-persistent/src/index.ts | 38 +- .../tool-bash-persistent/src/invariant.ts | 0 .../tests/loader-composition.spec.ts | 20 +- .../tool-bash-persistent/tests/tools.spec.ts | 56 +- .../tool-bash-persistent/tsconfig.json | 4 +- packages/shell/tool-bash/README.i18n.yaml | 6 + packages/{bash => shell}/tool-bash/README.md | 30 +- .../{bash => shell}/tool-bash/README.zh.md | 28 +- .../{bash => shell}/tool-bash/package.json | 20 +- .../tool-bash/src/background.ts | 8 +- .../{bash => shell}/tool-bash/src/index.ts | 48 +- .../tool-bash/src/invariant.ts | 0 .../{bash => shell}/tool-bash/src/render.ts | 16 +- .../tool-bash/tests/integration.spec.ts | 34 +- .../tool-bash/tests/tools.spec.ts | 176 +- .../{bash => shell}/tool-bash/tsconfig.json | 8 +- packages/shell/tool-pwsh/README.i18n.yaml | 6 + packages/{bash => shell}/tool-pwsh/README.md | 26 +- .../{bash => shell}/tool-pwsh/README.zh.md | 22 +- .../{bash => shell}/tool-pwsh/package.json | 18 +- .../tool-pwsh/src/background.ts | 8 +- .../{bash => shell}/tool-pwsh/src/index.ts | 56 +- .../tool-pwsh/src/invariant.ts | 0 .../{bash => shell}/tool-pwsh/src/render.ts | 10 +- .../tool-pwsh/tests/integration.spec.ts | 26 +- .../tool-pwsh/tests/loader.spec.ts | 4 +- .../tool-pwsh/tests/tools.spec.ts | 138 +- .../{bash => shell}/tool-pwsh/tsconfig.json | 10 +- packages/skill/README.i18n.yaml | 4 +- packages/skill/README.md | 2 +- packages/skill/README.zh.md | 2 +- .../skill-badge/tests/skill-badge.spec.ts | 4 +- packages/skill/skill-badge/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 6 +- .../README.zh.md | 6 +- .../package.json | 8 +- .../src/index.ts | 28 +- .../src/invariant.ts | 8 +- .../tests/skill-filesystem-watcher.spec.ts} | 56 +- .../tests/skill-filesystem.spec.ts} | 84 +- .../tsconfig.json | 4 +- packages/skill/skill/README.i18n.yaml | 4 +- packages/skill/skill/README.md | 4 +- packages/skill/skill/README.zh.md | 4 +- packages/skill/skill/src/index.ts | 8 +- packages/skill/skill/tests/skill.spec.ts | 86 +- packages/skill/skill/tsconfig.json | 2 +- packages/skill/tool-skill/package.json | 2 +- .../skill/tool-skill/tests/tool-skill.spec.ts | 26 +- packages/skill/tool-skill/tsconfig.json | 2 +- packages/spill/spill-local/tsconfig.json | 2 +- packages/spill/spill-policy/README.i18n.yaml | 4 +- packages/spill/spill-policy/README.md | 4 +- packages/spill/spill-policy/README.zh.md | 4 +- packages/spill/spill-policy/package.json | 6 +- packages/spill/spill-policy/src/index.ts | 6 +- .../spill-policy/tests/spill-policy.spec.ts | 26 +- packages/spill/spill-policy/tsconfig.json | 4 +- packages/spill/spill/README.i18n.yaml | 4 +- packages/spill/spill/README.md | 4 +- packages/spill/spill/README.zh.md | 4 +- packages/spill/spill/src/index.ts | 2 +- packages/spill/spill/tsconfig.json | 2 +- .../storage-domain/tests/invariant.spec.ts | 4 +- packages/storage/storage-domain/tsconfig.json | 2 +- .../storage-json/tests/json-backend.spec.ts | 4 +- packages/storage/storage-json/tsconfig.json | 2 +- .../storage-sqlite/tests/invariant.spec.ts | 4 +- packages/storage/storage-sqlite/tsconfig.json | 2 +- packages/storage/storage/tsconfig.json | 2 +- packages/subagent/README.i18n.yaml | 4 +- packages/subagent/README.md | 6 +- packages/subagent/README.zh.md | 6 +- .../subagent-acp/tests/subagent-acp.e2e.ts | 12 +- .../subagent-acp/tests/subagent-acp.spec.ts | 52 +- packages/subagent/subagent-acp/tsconfig.json | 4 +- .../tests/real-deepseek.e2e.ts | 8 +- .../tests/real-product.spec.ts | 8 +- .../tests/subagent-claude-code.spec.ts | 12 +- .../subagent-claude-code/tsconfig.json | 2 +- .../subagent-codex/tests/real-deepseek.e2e.ts | 8 +- .../subagent-codex/tests/real-product.spec.ts | 8 +- .../tests/subagent-codex.spec.ts | 16 +- .../subagent/subagent-codex/tsconfig.json | 2 +- .../subagent/subagent-dsh-sdk/src/index.ts | 4 +- .../tests/subagent-dsh-sdk.spec.ts | 14 +- .../subagent/subagent-dsh-sdk/tsconfig.json | 4 +- .../README.i18n.yaml | 6 +- .../README.md | 6 +- .../README.zh.md | 6 +- .../package.json | 10 +- .../src/index.ts | 12 +- .../src/invariant.ts | 8 +- .../tests/multi-subagent.spec.ts | 10 +- .../tests/subagent-fork-in-process.spec.ts} | 18 +- .../tsconfig.json | 4 +- .../README.i18n.yaml | 6 +- .../README.md | 2 +- .../README.zh.md | 2 +- .../package.json | 4 +- .../src/index.ts | 2 +- .../src/invariant.ts | 8 +- .../src/structured.ts | 4 +- .../tests/fixtures/plugins/preset-tool.js | 0 .../fixtures/presets/coding/agent.cordis.yml | 0 .../presets/reviewing/agent.cordis.yml | 0 .../tests/inheritance.spec.ts | 0 .../tests/preset-inheritance.spec.ts | 0 .../tests/structured.spec.ts | 8 +- .../tests/subagent-in-process-driver.spec.ts} | 8 +- .../tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 4 +- .../README.zh.md | 4 +- .../package.json | 8 +- .../src/index.ts | 10 +- .../src/invariant.ts | 8 +- .../tests/harness.ts | 10 +- .../tests/spawn-in-process.e2e.ts} | 0 .../tests/subagent-spawn-in-process.spec.ts} | 24 +- .../tsconfig.json | 4 +- packages/subagent/subagent/README.i18n.yaml | 4 +- packages/subagent/subagent/README.md | 6 +- packages/subagent/subagent/README.zh.md | 4 +- packages/subagent/subagent/package.json | 6 +- .../subagent/subagent/src/continuation.ts | 2 +- packages/subagent/subagent/src/index.ts | 16 +- .../subagent/subagent/src/list-children.ts | 4 +- .../subagent/subagent/src/run-settlement.ts | 12 +- packages/subagent/subagent/src/types.ts | 2 +- .../tests/continuation-inheritance.spec.ts | 8 +- .../subagent/tests/continuation.spec.ts | 22 +- .../subagent/subagent/tests/invariant.spec.ts | 8 +- .../subagent/tests/list-children.spec.ts | 18 +- .../subagent/subagent/tests/service.spec.ts | 10 +- .../subagent/tests/timing-projection.spec.ts | 4 +- packages/subagent/subagent/tsconfig.json | 4 +- .../tool-subagent-control/package.json | 2 +- .../tests/list-agents.spec.ts | 8 +- .../tests/tool-subagent-control.spec.ts | 12 +- .../tool-subagent-control/tsconfig.json | 2 +- .../tool-subagent-report/package.json | 2 +- .../tests/tool-subagent-report.spec.ts | 10 +- .../tool-subagent-report/tsconfig.json | 2 +- .../subagent/tool-subagent/README.i18n.yaml | 4 +- packages/subagent/tool-subagent/README.md | 8 +- packages/subagent/tool-subagent/README.zh.md | 8 +- packages/subagent/tool-subagent/package.json | 10 +- packages/subagent/tool-subagent/src/index.ts | 24 +- .../tests/scripted-provider.spec.ts | 6 +- .../tool-subagent/tests/tool-subagent.spec.ts | 170 +- packages/subagent/tool-subagent/tsconfig.json | 4 +- packages/subprocess/README.i18n.yaml | 4 +- packages/subprocess/README.md | 2 +- packages/subprocess/README.zh.md | 2 +- .../subprocess-local/README.i18n.yaml | 4 +- .../subprocess/subprocess-local/README.md | 2 +- .../subprocess/subprocess-local/README.zh.md | 2 +- .../subprocess/subprocess-local/src/index.ts | 6 +- .../tests/fixtures/process-exit-host.ts | 4 +- .../subprocess-local/tests/local.spec.ts | 52 +- .../subprocess-local/tests/spawn.spec.ts | 6 +- .../subprocess/subprocess-local/tsconfig.json | 2 +- .../subprocess/subprocess/README.i18n.yaml | 4 +- packages/subprocess/subprocess/README.md | 4 +- packages/subprocess/subprocess/README.zh.md | 4 +- packages/subprocess/subprocess/src/index.ts | 6 +- packages/subprocess/subprocess/src/types.ts | 4 +- .../subprocess/tests/service.spec.ts | 12 +- packages/subprocess/subprocess/tsconfig.json | 2 +- packages/support/README.i18n.yaml | 6 - packages/support/invariants/README.i18n.yaml | 6 - packages/support/llm-replay/README.i18n.yaml | 6 - packages/tasks/README.md | 15 - packages/tasks/README.zh.md | 15 - packages/tasks/tasks-local/README.i18n.yaml | 6 - packages/tasks/tasks-local/README.md | 34 - packages/tasks/tasks/README.i18n.yaml | 6 - packages/tasks/tasks/README.md | 40 - packages/tasks/tasks/tests/service.spec.ts | 96 - packages/tasks/tool-tasks/README.i18n.yaml | 6 - packages/tasks/tool-tasks/README.md | 96 - .../{pty/pty => terminal}/README.i18n.yaml | 6 +- packages/terminal/README.md | 15 + packages/terminal/README.zh.md | 15 + .../terminal/terminal-bash/README.i18n.yaml | 6 + .../terminal-bash}/README.md | 10 +- .../terminal-bash}/README.zh.md | 10 +- .../terminal-bash}/package.json | 8 +- .../terminal-bash}/src/config.ts | 10 +- .../terminal-bash}/src/index.ts | 32 +- .../terminal-bash}/src/invariant.ts | 8 +- .../terminal-bash}/src/sanitize.ts | 0 .../terminal-bash}/src/session.ts | 58 +- .../terminal-bash}/tests/config.spec.ts | 6 +- .../terminal-bash}/tests/index.spec.ts | 90 +- .../terminal-bash}/tests/local.spec.ts | 70 +- .../terminal-bash}/tests/sanitize.spec.ts | 2 +- .../terminal-bash}/tests/session.spec.ts | 60 +- .../terminal-bash}/tsconfig.json | 4 +- .../terminal}/README.i18n.yaml | 6 +- .../{pty/pty => terminal/terminal}/README.md | 14 +- .../pty => terminal/terminal}/README.zh.md | 12 +- .../pty => terminal/terminal}/package.json | 4 +- .../pty => terminal/terminal}/src/index.ts | 158 +- .../terminal}/src/invariant.ts | 8 +- .../pty => terminal/terminal}/src/types.ts | 74 +- .../terminal}/tests/service.spec.ts | 246 +-- .../pty => terminal/terminal}/tsconfig.json | 2 +- .../terminal/tool-terminal/README.i18n.yaml | 6 + packages/terminal/tool-terminal/README.md | 71 + packages/terminal/tool-terminal/README.zh.md | 71 + .../tool-terminal}/package.json | 24 +- .../tool-terminal}/src/index.ts | 64 +- .../tool-terminal}/src/invariant.ts | 10 +- .../tool-terminal}/src/render.ts | 4 +- .../tests/loader-composition.spec.ts | 30 +- .../tool-terminal}/tests/render.spec.ts | 22 +- .../tool-terminal}/tests/tools.spec.ts | 78 +- .../tool-terminal}/tsconfig.json | 8 +- .../README.i18n.yaml | 6 +- packages/{support => test-support}/README.md | 6 +- .../{support => test-support}/README.zh.md | 6 +- .../acp-snapshot/README.i18n.yaml | 2 +- .../acp-snapshot/README.md | 0 .../acp-snapshot/README.zh.md | 0 .../acp-snapshot/package.json | 2 +- .../acp-snapshot/src/harness.ts | 0 .../acp-snapshot/src/index.ts | 0 .../acp-snapshot/src/invariant.ts | 0 .../acp-snapshot/src/launcher.ts | 0 .../acp-snapshot/src/normalize.ts | 0 .../acp-snapshot/src/suite.ts | 0 .../tests/fixtures/fake-acp-agent.ts | 0 .../record-suite/rec-child/behavior.json | 0 .../record-suite/rec-child/input.json | 0 .../record-suite/rec-child/session.1.jsonl | 0 .../record-suite/rec-child/session.jsonl | 0 .../rec-child/stdout.expected.jsonl | 0 .../record-suite/rec-pin/behavior.json | 0 .../fixtures/record-suite/rec-pin/input.json | 0 .../record-suite/rec-pin/session.1.jsonl | 0 .../record-suite/rec-pin/session.jsonl | 0 .../rec-pin/stdout.expected.jsonl | 0 .../rec-pin/system-prompt.expected.md | 0 .../rec-pin/tool-schemas.expected.json | 0 .../record-suite/rec-skip/behavior.json | 0 .../fixtures/record-suite/rec-skip/input.json | 0 .../rec-skip/replay.override.json | 0 .../record-suite/rec-skip/session.jsonl | 0 .../rec-skip/stdout.expected.jsonl | 0 .../suite/authored-error/behavior.json | 0 .../fixtures/suite/authored-error/input.json | 0 .../suite/authored-error/replay.override.json | 0 .../suite/authored-error/session.jsonl | 0 .../authored-error/stdout.expected.jsonl | 0 .../fixtures/suite/blocked-log/behavior.json | 0 .../fixtures/suite/blocked-log/input.json | 0 .../fixtures/suite/blocked-log/session.jsonl | 0 .../suite/blocked-log/stdout.expected.jsonl | 0 .../fixtures/suite/no-model/behavior.json | 0 .../tests/fixtures/suite/no-model/input.json | 0 .../fixtures/suite/no-model/session.jsonl | 0 .../suite/no-model/stdout.expected.jsonl | 0 .../fixtures/suite/pin-turn/behavior.json | 0 .../tests/fixtures/suite/pin-turn/input.json | 0 .../fixtures/suite/pin-turn/session.jsonl | 0 .../suite/pin-turn/stdout.expected.jsonl | 0 .../suite/pin-turn/system-prompt.expected.md | 0 .../suite/pin-turn/tool-schemas.expected.json | 0 .../fixtures/suite/plain-turn/behavior.json | 0 .../fixtures/suite/plain-turn/input.json | 0 .../fixtures/suite/plain-turn/session.1.jsonl | 0 .../fixtures/suite/plain-turn/session.jsonl | 0 .../suite/plain-turn/stdout.expected.jsonl | 0 .../plain-turn/system-prompt.1.expected.md | 0 .../plain-turn/tool-schemas.1.expected.json | 0 .../suite/plain-turn/workspace/seed.txt | 0 .../fixtures/suite/shared-pin/behavior.json | 0 .../fixtures/suite/shared-pin/input.json | 0 .../fixtures/suite/shared-pin/session.jsonl | 0 .../suite/shared-pin/stdout.expected.jsonl | 0 .../acp-snapshot/tests/harness.spec.ts | 0 .../acp-snapshot/tests/normalize.spec.ts | 0 .../acp-snapshot/tests/suite.spec.ts | 0 .../acp-snapshot/tsconfig.json | 2 +- .../agent-loop-testkit/README.i18n.yaml | 2 +- .../agent-loop-testkit/README.md | 0 .../agent-loop-testkit/README.zh.md | 0 .../agent-loop-testkit/package.json | 2 +- .../agent-loop-testkit/src/index.ts | 12 +- .../agent-loop-testkit/src/invariant.ts | 0 .../tests/agent-loop-testkit.spec.ts | 0 .../agent-loop-testkit/tsconfig.json | 2 +- .../client-runtime/README.i18n.yaml | 6 + .../client-runtime}/README.md | 4 +- .../client-runtime}/README.zh.md | 4 +- .../client-runtime}/package.json | 4 +- .../client-runtime}/src/fixtures.ts | 2 +- .../client-runtime}/src/index.ts | 22 +- .../client-runtime}/src/invariant.ts | 2 +- .../client-runtime}/src/locale-env.ts | 2 +- .../client-runtime}/src/remote.ts | 0 .../client-runtime}/src/sessions.ts | 10 +- .../client-runtime}/src/settings-scope.ts | 0 .../client-runtime}/src/snapshot.ts | 0 .../client-runtime}/src/translate.ts | 2 +- .../client-runtime}/src/workspaces.ts | 0 .../runtime.client.spec.tsx.snap | 0 .../tests/invariant.client.spec.ts | 4 +- .../tests/remote.client.spec.ts | 0 .../tests/runtime.client.spec.tsx | 2 +- .../client-runtime}/tsconfig.json | 8 +- .../client-runtime}/tsdown.config.ts | 2 +- .../llm-mock-server/README.i18n.yaml | 2 +- .../llm-mock-server/README.md | 0 .../llm-mock-server/README.zh.md | 0 .../llm-mock-server/package.json | 2 +- .../llm-mock-server/src/bin.ts | 0 .../llm-mock-server/src/cli.ts | 0 .../llm-mock-server/src/index.ts | 0 .../llm-mock-server/src/invariant.ts | 0 .../llm-mock-server/tests/cli.spec.ts | 0 .../llm-mock-server/tests/invariant.spec.ts | 4 +- .../llm-mock-server/tests/server.spec.ts | 0 .../llm-mock-server}/tsconfig.json | 2 +- .../llm-mock-server/tsdown.config.ts | 0 .../test-support/llm-replay/README.i18n.yaml | 6 + .../llm-replay/README.md | 4 +- .../llm-replay/README.zh.md | 4 +- .../llm-replay/package.json | 6 +- .../llm-replay/src/index.ts | 16 +- .../llm-replay/src/invariant.ts | 0 .../llm-replay/tests/llm-replay.spec.ts | 100 +- .../llm-replay/tsconfig.json | 4 +- .../loader-smoke/README.i18n.yaml | 2 +- .../loader-smoke/README.md | 0 .../loader-smoke/README.zh.md | 0 .../loader-smoke/package.json | 2 +- .../loader-smoke/src/agent-turn.ts | 0 .../loader-smoke/src/index.ts | 0 .../loader-smoke/src/invariant.ts | 0 .../loader-smoke/tests/agent-turn.spec.ts | 0 .../loader-smoke/tests/example-launch.spec.ts | 0 .../loader-smoke/tests/fixtures/fail.ts | 0 .../loader-smoke/tests/fixtures/hang.ts | 0 .../loader-smoke/tests/fixtures/success.ts | 0 .../loader-smoke/tests/loader-smoke.spec.ts | 0 .../loader-smoke/tsconfig.json | 2 +- packages/todo/tool-todo/package.json | 2 +- .../todo/tool-todo/tests/invariant.spec.ts | 12 +- .../tests/loader-composition.spec.ts | 4 +- .../todo/tool-todo/tests/projection.spec.ts | 8 +- .../todo/tool-todo/tests/tool-todo.spec.ts | 8 +- packages/todo/tool-todo/tsconfig.json | 2 +- packages/typert/generator/README.i18n.yaml | 4 +- packages/typert/generator/README.md | 4 +- packages/typert/generator/README.zh.md | 4 +- packages/typert/generator/src/analyzer.ts | 82 +- .../typert/generator/src/cordis-catalog.ts | 4 +- packages/typert/generator/src/emitter.ts | 18 +- packages/typert/generator/src/model.ts | 2 +- .../tests/cordis-catalog-contract.spec.ts | 4 +- .../generator/tests/cordis-catalog.spec.ts | 2 +- .../remote-model/packages/domain/src/index.ts | 12 +- .../remote-model/packages/remote/src/index.ts | 4 +- .../fixtures/remote-model/tsconfig.base.json | 2 +- .../{type-meta.d.ts => typert-protocol.d.ts} | 34 +- .../generator/tests/remote-model.spec.ts | 72 +- .../typert/generator/tests/type-model.spec.ts | 2 +- packages/typert/generator/tsconfig.json | 2 +- packages/typert/loader/tests/loader.spec.ts | 2 +- packages/typert/loader/tsconfig.json | 2 +- .../{type-meta => protocol}/README.i18n.yaml | 6 +- .../typert/{type-meta => protocol}/README.md | 20 +- .../{type-meta => protocol}/README.zh.md | 20 +- .../{type-meta => protocol}/package.json | 6 +- .../{type-meta => protocol}/src/index.ts | 120 +- .../{type-meta => protocol}/src/invariant.ts | 8 +- .../{type-meta => protocol}/src/types.ts | 180 +- .../tests/fixtures/source-launch.ts | 6 +- .../tests/protocol.spec.ts} | 56 +- .../{type-meta => protocol}/tsconfig.json | 2 +- packages/typert/registry/package.json | 2 +- packages/typert/registry/src/client/index.ts | 2 +- packages/typert/registry/src/index.ts | 10 +- packages/typert/registry/src/service.ts | 136 +- packages/typert/registry/src/types.ts | 2 +- packages/typert/registry/tests/typert.spec.ts | 22 +- packages/typert/registry/tsconfig.json | 4 +- packages/util/README.i18n.yaml | 4 +- packages/util/README.md | 4 +- packages/util/README.zh.md | 4 +- packages/util/atomic-write/README.i18n.yaml | 4 +- packages/util/atomic-write/README.md | 2 +- packages/util/atomic-write/README.zh.md | 2 +- .../util/atomic-write/tests/invariant.spec.ts | 4 +- packages/util/atomic-write/tsconfig.json | 2 +- packages/util/brand/README.i18n.yaml | 4 +- packages/util/brand/README.md | 4 +- packages/util/brand/README.zh.md | 4 +- packages/util/brand/src/index.ts | 2 +- packages/util/brand/tsconfig.json | 2 +- packages/util/environment/README.i18n.yaml | 6 - packages/util/home-paths/README.i18n.yaml | 6 + packages/util/{paths => home-paths}/README.md | 2 +- .../util/{paths => home-paths}/README.zh.md | 2 +- .../util/{paths => home-paths}/package.json | 4 +- .../util/{paths => home-paths}/src/index.ts | 2 +- .../src/invariant.ts | 8 +- .../tests/home-paths.spec.ts} | 2 +- .../{retention => home-paths}/tsconfig.json | 2 +- .../util/launch-environment/README.i18n.yaml | 6 + .../README.md | 8 +- .../README.zh.md | 8 +- .../package.json | 4 +- .../src/index.ts | 40 +- .../src/invariant.ts | 8 +- .../tests/launch-environment.spec.ts} | 20 +- .../tsconfig.json | 2 +- packages/util/native-command/README.i18n.yaml | 4 +- packages/util/native-command/README.md | 2 +- packages/util/native-command/README.zh.md | 2 +- packages/util/native-command/tsconfig.json | 2 +- .../util/output-retention/README.i18n.yaml | 6 + .../{retention => output-retention}/README.md | 8 +- .../README.zh.md | 6 +- .../package.json | 4 +- .../src/index.ts | 2 +- .../src/invariant.ts | 8 +- .../tests/output-retention.spec.ts} | 2 +- .../output-retention}/tsconfig.json | 2 +- packages/util/paths/README.i18n.yaml | 6 - packages/util/retention/README.i18n.yaml | 6 - packages/util/timeout/README.i18n.yaml | 4 +- packages/util/timeout/README.md | 2 +- packages/util/timeout/README.zh.md | 2 +- packages/util/timeout/tsconfig.json | 2 +- packages/web/README.i18n.yaml | 4 +- packages/web/README.md | 2 +- packages/web/README.zh.md | 2 +- packages/web/tool-web/README.i18n.yaml | 4 +- packages/web/tool-web/README.md | 8 +- packages/web/tool-web/README.zh.md | 8 +- packages/web/tool-web/package.json | 4 +- packages/web/tool-web/src/fetch.ts | 4 +- packages/web/tool-web/src/index.ts | 2 +- packages/web/tool-web/src/search.ts | 2 +- .../web/tool-web/tests/integration.spec.ts | 24 +- packages/web/tool-web/tests/load-path.spec.ts | 8 +- packages/web/tool-web/tests/spill.spec.ts | 10 +- packages/web/tool-web/tests/tool-web.spec.ts | 24 +- packages/web/tool-web/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 6 +- .../README.zh.md | 6 +- .../package.json | 4 +- .../src/index.ts | 24 +- .../src/invariant.ts | 8 +- .../src/policy.ts | 2 +- .../src/provider.ts | 10 +- .../tests/fetch-http.spec.ts} | 40 +- .../tsconfig.json | 2 +- packages/web/web-search-deepseek/package.json | 4 +- packages/web/web-search-deepseek/src/index.ts | 6 +- .../web-search-deepseek/tests/deepseek.e2e.ts | 2 +- .../tests/deepseek.spec.ts | 22 +- .../tests/settings.spec.ts | 8 +- .../web/web-search-deepseek/tsconfig.json | 4 +- packages/web/web-search-exa/package.json | 4 +- packages/web/web-search-exa/src/index.ts | 4 +- packages/web/web-search-exa/tests/exa.e2e.ts | 2 +- packages/web/web-search-exa/tests/exa.spec.ts | 10 +- packages/web/web-search-exa/tsconfig.json | 4 +- .../web/web-search-perplexity/package.json | 4 +- .../web/web-search-perplexity/src/index.ts | 4 +- .../tests/perplexity.e2e.ts | 2 +- .../tests/perplexity.spec.ts | 10 +- .../web/web-search-perplexity/tsconfig.json | 4 +- packages/web/web/README.i18n.yaml | 4 +- packages/web/web/README.md | 6 +- packages/web/web/README.zh.md | 6 +- packages/web/web/src/index.ts | 12 +- packages/web/web/tests/web.spec.ts | 20 +- packages/web/web/tsconfig.json | 2 +- packages/workflow/README.i18n.yaml | 4 +- packages/workflow/README.md | 4 +- packages/workflow/README.zh.md | 4 +- packages/workflow/tool-ralph/README.i18n.yaml | 4 +- packages/workflow/tool-ralph/README.md | 6 +- packages/workflow/tool-ralph/README.zh.md | 6 +- packages/workflow/tool-ralph/package.json | 6 +- packages/workflow/tool-ralph/src/index.ts | 4 +- .../tool-ralph/tests/integration.spec.ts | 16 +- .../tool-ralph/tests/tool-ralph.spec.ts | 16 +- packages/workflow/tool-ralph/tsconfig.json | 2 +- .../workflow/tool-workflow/README.i18n.yaml | 4 +- packages/workflow/tool-workflow/README.md | 2 +- packages/workflow/tool-workflow/README.zh.md | 2 +- packages/workflow/tool-workflow/package.json | 4 +- packages/workflow/tool-workflow/src/index.ts | 6 +- .../tool-workflow/tests/invariant.spec.ts | 8 +- .../tool-workflow/tests/tool-workflow.spec.ts | 26 +- packages/workflow/tool-workflow/tsconfig.json | 2 +- .../README.i18n.yaml | 6 +- .../README.md | 6 +- .../README.zh.md | 6 +- .../package.json | 6 +- .../src/host.ts | 14 +- .../src/index.ts | 12 +- .../src/invariant.ts | 8 +- .../src/meta.ts | 2 +- .../src/protocol.ts | 2 +- .../src/realm.ts | 2 +- .../src/runtime.ts | 2 +- .../src/session.ts | 2 +- .../src/types.ts | 2 +- .../src/worker.ts | 2 +- .../tests/built-worker.e2e.ts | 10 +- .../tests/integration.spec.ts | 22 +- .../tests/meta.spec.ts | 0 .../tests/realm.spec.ts | 0 .../tests/session.spec.ts | 0 .../tests/source-worker.compat.spec.ts | 10 +- .../tests/workflow-worker-thread.e2e.ts} | 20 +- .../tests/workflow-worker-thread.spec.ts} | 150 +- .../tsconfig.json | 2 +- .../tsdown.config.ts | 0 packages/workflow/workflow/README.i18n.yaml | 4 +- packages/workflow/workflow/README.md | 6 +- packages/workflow/workflow/README.zh.md | 6 +- packages/workflow/workflow/package.json | 2 +- packages/workflow/workflow/src/index.ts | 10 +- .../workflow/workflow/tests/invariant.spec.ts | 4 +- .../workflow/workflow/tests/workflow.spec.ts | 24 +- packages/workflow/workflow/tsconfig.json | 2 +- packages/workspace/README.i18n.yaml | 4 +- packages/workspace/README.md | 2 +- packages/workspace/README.zh.md | 2 +- packages/workspace/workspace/README.i18n.yaml | 4 +- packages/workspace/workspace/README.md | 14 +- packages/workspace/workspace/README.zh.md | 14 +- packages/workspace/workspace/package.json | 2 +- packages/workspace/workspace/src/index.ts | 6 +- packages/workspace/workspace/src/invariant.ts | 8 +- .../workspace/tests/invariant.spec.ts | 6 +- .../workspace/tests/workspace.spec.ts | 8 +- packages/workspace/workspace/tsconfig.json | 2 +- pnpm-lock.yaml | 1688 ++++++++--------- python/sdk-runtime/README.i18n.yaml | 4 +- python/sdk-runtime/README.md | 4 +- python/sdk-runtime/README.zh.md | 4 +- python/sdk-runtime/package.json | 64 +- .../src/deepseek_harness_runtime/__init__.py | 4 +- .../runtime/cordis.yml | 4 +- python/sdk/README.i18n.yaml | 4 +- python/sdk/README.md | 2 +- python/sdk/README.zh.md | 2 +- python/sdk/tests/test_bundled_runtime.py | 4 +- scripts/build-exe-for-python-sdk.ts | 2 +- scripts/check-workspace-constraints.ts | 10 +- scripts/ci-workflow.spec.ts | 6 +- scripts/doc-typecheck-paths.spec.ts | 6 +- scripts/gen-cordis-catalog.ts | 140 +- scripts/gen-doc-graphs.ts | 102 +- scripts/gen-third-party-notices.spec.ts | 4 +- scripts/gen-third-party-notices.ts | 4 +- scripts/gen-tool-catalog.ts | 134 +- scripts/oxlint-contract.spec.ts | 4 +- scripts/package-invariants.spec.ts | 4 +- scripts/package-invariants.ts | 4 +- scripts/publication-payload.spec.ts | 6 +- scripts/publication-payload.ts | 2 +- scripts/release/families.spec.ts | 2 +- scripts/rescope-vendor.ts | 4 +- scripts/run-gates.spec.ts | 2 +- scripts/run-gates.ts | 10 +- scripts/smoke-python-runtime.py | 12 +- .../advanced/result.json | 48 +- .../advanced/session.1.jsonl | 2 +- .../advanced/session.2.jsonl | 2 +- .../advanced/session.jsonl | 6 +- .../request-response.expected.json | 10 +- scripts/test-invariants.spec.ts | 8 +- scripts/test-invariants.ts | 6 +- scripts/type-equiv.manifest.json | 262 +-- scripts/verify-cordis-config.ts | 4 +- .../verify-package-readme-model-experience.ts | 76 +- tsconfig.base.json | 72 +- tsconfig.client.json | 32 +- tsconfig.host.json | 104 +- tsdown.config.ts | 2 +- vitest.config.ts | 56 +- website/docs.ts | 16 +- 3281 files changed, 21730 insertions(+), 21592 deletions(-) rename .agents/notes/implemented/architecture/{2026-07-26-task-registry-seam.i18n.yaml => 2026-07-26-job-registry-seam.i18n.yaml} (59%) create mode 100644 .agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md create mode 100644 .agents/notes/implemented/architecture/2026-07-26-job-registry-seam.zh.md delete mode 100644 .agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md delete mode 100644 .agents/notes/implemented/architecture/2026-07-26-task-registry-seam.zh.md rename .agents/notes/implemented/bug-fix/{2026-08-11-bounded-background-task-admission.i18n.yaml => 2026-08-11-bounded-background-job-admission.i18n.yaml} (55%) rename .agents/notes/implemented/bug-fix/{2026-08-11-bounded-background-task-admission.md => 2026-08-11-bounded-background-job-admission.md} (64%) rename .agents/notes/implemented/bug-fix/{2026-08-11-bounded-background-task-admission.zh.md => 2026-08-11-bounded-background-job-admission.zh.md} (69%) rename .agents/notes/implemented/feature/{2026-08-08-web-background-task-display.i18n.yaml => 2026-08-08-web-background-job-display.i18n.yaml} (57%) create mode 100644 .agents/notes/implemented/feature/2026-08-08-web-background-job-display.md create mode 100644 .agents/notes/implemented/feature/2026-08-08-web-background-job-display.zh.md delete mode 100644 .agents/notes/implemented/feature/2026-08-08-web-background-task-display.md delete mode 100644 .agents/notes/implemented/feature/2026-08-08-web-background-task-display.zh.md rename .agents/notes/implemented/feature/{2026-08-11-background-task-completion-wakes-an-idle-owner.i18n.yaml => 2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml} (51%) rename .agents/notes/implemented/feature/{2026-08-11-background-task-completion-wakes-an-idle-owner.md => 2026-08-11-background-job-completion-wakes-an-idle-owner.md} (65%) rename .agents/notes/implemented/feature/{2026-08-11-background-task-completion-wakes-an-idle-owner.zh.md => 2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md} (73%) rename apps/web/tests/{background-task-list.e2e.ts => background-job-list.e2e.ts} (83%) rename apps/web/tests/snapshots/{background-task-list => background-job-list}/running.expected.md (64%) rename apps/web/tests/snapshots/{background-task-list => background-job-list}/settled.expected.md (68%) rename docs/subsystems/{pty.i18n.yaml => jobs.i18n.yaml} (73%) rename docs/subsystems/{tasks.md => jobs.md} (56%) rename docs/subsystems/{tasks.zh.md => jobs.zh.md} (56%) create mode 100644 docs/subsystems/permission-presets.i18n.yaml rename docs/subsystems/{permission.md => permission-presets.md} (75%) rename docs/subsystems/{permission.zh.md => permission-presets.zh.md} (75%) create mode 100644 docs/subsystems/session-telemetry.i18n.yaml rename docs/subsystems/{telemetry.md => session-telemetry.md} (70%) rename docs/subsystems/{telemetry.zh.md => session-telemetry.zh.md} (69%) rename docs/subsystems/{bash.i18n.yaml => shell.i18n.yaml} (72%) rename docs/subsystems/{bash.md => shell.md} (69%) rename docs/subsystems/{bash.zh.md => shell.zh.md} (71%) delete mode 100644 docs/subsystems/telemetry.i18n.yaml rename docs/subsystems/{tasks.i18n.yaml => terminal.i18n.yaml} (71%) rename docs/subsystems/{pty.md => terminal.md} (58%) rename docs/subsystems/{pty.zh.md => terminal.zh.md} (59%) delete mode 100644 docs/subsystems/user-interaction.i18n.yaml rename docs/subsystems/{http-server.i18n.yaml => user-questions.i18n.yaml} (68%) rename docs/subsystems/{user-interaction.md => user-questions.md} (82%) rename docs/subsystems/{user-interaction.zh.md => user-questions.zh.md} (82%) rename docs/subsystems/{permission.i18n.yaml => web-server.i18n.yaml} (70%) rename docs/subsystems/{http-server.md => web-server.md} (74%) rename docs/subsystems/{http-server.zh.md => web-server.zh.md} (73%) rename examples/acp-agent/{workspace-context.cordis.snapshot.yml => agent-instructions.cordis.snapshot.yml} (93%) rename examples/acp-agent/{workspace-context.cordis.yml => agent-instructions.cordis.yml} (100%) rename examples/acp-agent/{background-task-admission.cordis.snapshot.yml => background-job-admission.cordis.snapshot.yml} (91%) rename examples/acp-agent/{background-task-admission.cordis.yml => background-job-admission.cordis.yml} (96%) rename examples/acp-agent/tests/fixtures/{bash => shell}/tool-pwsh/cordis.yml (76%) rename examples/acp-agent/tests/fixtures/{bash => shell}/tool-pwsh/driver.ts (95%) rename examples/acp-agent/tests/goal-snapshots/{goal-session => goal-round-driver}/input.json (100%) rename examples/acp-agent/tests/goal-snapshots/{goal-session => goal-round-driver}/replay.override.json (91%) rename examples/acp-agent/tests/goal-snapshots/{goal-session => goal-round-driver}/session.expected.jsonl (69%) rename examples/acp-agent/tests/goal-snapshots/{goal-session => goal-round-driver}/session.jsonl (100%) rename examples/acp-agent/tests/goal-snapshots/{goal-session => goal-round-driver}/stdout.expected.jsonl (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/input.json (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/replay.override.json (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/session.jsonl (82%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/stdout.expected.jsonl (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/system-prompt.expected.md (80%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/workspace/.dsh-project (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/workspace/AGENTS.canonical.md (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/workspace/AGENTS.md (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/workspace/nested/AGENTS.canonical.md (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/workspace/nested/AGENTS.md (100%) rename examples/acp-agent/tests/snapshots/{workspace-context => agent-instructions}/workspace/nested/task.txt (100%) rename examples/acp-agent/tests/snapshots/{background-task-admission => background-job-admission}/input.json (58%) rename examples/acp-agent/tests/snapshots/{background-task-admission => background-job-admission}/replay.override.json (84%) rename examples/acp-agent/tests/snapshots/{background-task-admission => background-job-admission}/session.jsonl (78%) rename examples/acp-agent/tests/snapshots/{background-task-admission => background-job-admission}/stdout.expected.jsonl (100%) rename examples/acp-agent/tests/snapshots/{repeat-tool-guard => repeat-tool-reminder}/input.json (100%) rename examples/acp-agent/tests/snapshots/{repeat-tool-guard => repeat-tool-reminder}/session.jsonl (96%) rename examples/acp-agent/tests/snapshots/{repeat-tool-guard => repeat-tool-reminder}/stdout.expected.jsonl (100%) rename examples/acp-agent/tests/snapshots/{subagent-fork => subagent-fork-in-process}/input.json (100%) rename examples/acp-agent/tests/snapshots/{subagent-fork => subagent-fork-in-process}/session.1.jsonl (100%) rename examples/acp-agent/tests/snapshots/{subagent-fork => subagent-fork-in-process}/session.jsonl (100%) rename examples/acp-agent/tests/snapshots/{subagent-fork => subagent-fork-in-process}/stdout.expected.jsonl (100%) rename examples/acp-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/input.json (100%) rename examples/acp-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/session.1.jsonl (100%) rename examples/acp-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/session.jsonl (100%) rename examples/acp-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/stdout.expected.jsonl (100%) rename examples/headless-agent/tests/fixtures/{telemetry-otel-driver.ts => session-telemetry-otel-driver.ts} (91%) rename examples/headless-agent/tests/fixtures/{telemetry-otel.cordis.yml => session-telemetry-otel.cordis.yml} (92%) rename examples/jsonrpc-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/notifications.expected.jsonl (100%) rename examples/jsonrpc-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/result.expected.json (100%) rename examples/jsonrpc-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/session.1.jsonl (100%) rename examples/jsonrpc-agent/tests/snapshots/{subagent-spawn => subagent-spawn-in-process}/session.jsonl (100%) delete mode 100644 packages/bash/bash-env/README.md delete mode 100644 packages/bash/bash-env/README.zh.md delete mode 100644 packages/bash/bash/README.zh.md delete mode 100644 packages/bash/pwsh-local/README.i18n.yaml delete mode 100644 packages/bash/pwsh-sandbox/README.i18n.yaml delete mode 100644 packages/bash/tool-bash/README.i18n.yaml delete mode 100644 packages/bash/tool-pwsh/README.i18n.yaml delete mode 100644 packages/client/ui-command/tsdown.config.ts rename packages/client/{ui-models => ui-commands}/README.i18n.yaml (70%) rename packages/client/{ui-command => ui-commands}/README.md (73%) rename packages/client/{ui-command => ui-commands}/README.zh.md (73%) rename packages/client/{ui-command => ui-commands}/package.json (90%) rename packages/client/{ui-command => ui-commands}/src/client/PopupSelectView.module.css (100%) rename packages/client/{ui-command => ui-commands}/src/client/PopupSelectView.tsx (100%) rename packages/client/{ui-command => ui-commands}/src/client/contract.ts (92%) rename packages/client/{ui-command => ui-commands}/src/client/directory.ts (100%) rename packages/client/{ui-command => ui-commands}/src/client/index.ts (78%) rename packages/client/{ui-command => ui-commands}/src/client/locales.ts (100%) rename packages/client/{ui-command => ui-commands}/src/client/popup.ts (97%) rename packages/client/{ui-command => ui-commands}/src/client/service.ts (93%) rename packages/client/{ui-command => ui-commands}/src/css-modules.d.ts (100%) rename packages/client/{ui-command => ui-commands}/src/index.ts (92%) rename packages/client/{ui-command => ui-commands}/src/invariant.ts (84%) rename packages/client/{ui-command => ui-commands}/tests/browser-plugin.client.spec.ts (71%) rename packages/client/{ui-command => ui-commands}/tests/directory.client.spec.ts (100%) rename packages/client/{ui-command => ui-commands}/tests/popup-view.client.spec.tsx (100%) rename packages/client/{ui-command => ui-commands}/tests/popup.client.spec.ts (100%) rename packages/client/{ui-command => ui-commands}/tests/service.client.spec.ts (97%) rename packages/client/{ui-command => ui-commands}/tsconfig.json (88%) rename packages/client/{ui-question => ui-commands}/tsdown.config.ts (62%) create mode 100644 packages/client/ui-directory-picker-browse/README.i18n.yaml rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/README.md (98%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/README.zh.md (98%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/package.json (94%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/client/DirectoryBrowser.module.css (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/client/DirectoryBrowser.tsx (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/client/flow.ts (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/client/index.ts (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/css-modules.d.ts (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/index.ts (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/src/invariant.ts (85%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/tests/client-flow.client.spec.tsx (96%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/tests/directory-browser.client.spec.tsx (100%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/tsconfig.json (88%) rename packages/client/{ui-directory-picker => ui-directory-picker-browse}/tsdown.config.ts (63%) delete mode 100644 packages/client/ui-feedback/tsdown.config.ts rename packages/client/{test-runtime => ui-input-trigger}/README.i18n.yaml (69%) create mode 100644 packages/client/ui-input-trigger/README.md create mode 100644 packages/client/ui-input-trigger/README.zh.md rename packages/client/{ui-slash => ui-input-trigger}/package.json (95%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/MenuView.module.css (100%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/MenuView.tsx (98%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/contract.ts (52%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/controller.ts (92%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/index.ts (71%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/locales.ts (100%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/service.ts (76%) rename packages/client/{ui-slash => ui-input-trigger}/src/client/slots.ts (91%) rename packages/client/{ui-slash => ui-input-trigger}/src/core/contract.ts (88%) rename packages/client/{ui-slash => ui-input-trigger}/src/core/detect.ts (100%) rename packages/client/{ui-slash => ui-input-trigger}/src/core/menu.ts (97%) rename packages/client/{ui-feedback => ui-input-trigger}/src/css-modules.d.ts (100%) rename packages/client/{ui-slash => ui-input-trigger}/src/index.ts (100%) rename packages/client/{ui-slash => ui-input-trigger}/src/invariant.ts (83%) rename packages/client/{ui-slash => ui-input-trigger}/src/types.ts (96%) rename packages/client/{ui-slash => ui-input-trigger}/tests/apply.client.spec.ts (79%) rename packages/client/{ui-slash => ui-input-trigger}/tests/core-detect.client.spec.ts (100%) rename packages/client/{ui-slash => ui-input-trigger}/tests/core-menu.client.spec.ts (100%) rename packages/client/{ui-slash => ui-input-trigger}/tests/menu-view.client.spec.tsx (98%) rename packages/client/{ui-slash => ui-input-trigger}/tests/service.client.spec.ts (88%) rename packages/client/{ui-slash => ui-input-trigger}/tsconfig.json (88%) create mode 100644 packages/client/ui-input-trigger/tsdown.config.ts rename packages/client/{ui-task => ui-jobs}/README.i18n.yaml (71%) create mode 100644 packages/client/ui-jobs/README.md rename packages/client/{ui-task => ui-jobs}/README.zh.md (82%) rename packages/client/{ui-task => ui-jobs}/package.json (91%) rename packages/client/{ui-task/src/client/TaskListAction.module.css => ui-jobs/src/client/JobListAction.module.css} (96%) rename packages/client/{ui-task/src/client/TaskListAction.tsx => ui-jobs/src/client/JobListAction.tsx} (69%) rename packages/client/{ui-task => ui-jobs}/src/client/index.ts (59%) rename packages/client/{ui-task => ui-jobs}/src/client/locales.ts (73%) rename packages/client/{ui-model => ui-jobs}/src/css-modules.d.ts (100%) rename packages/client/{ui-task => ui-jobs}/src/index.ts (79%) rename packages/client/{ui-task => ui-jobs}/src/invariant.ts (80%) rename packages/client/{ui-task => ui-jobs}/tests/browser-plugin.client.spec.ts (79%) rename packages/client/{ui-task/tests/task-list-action.client.spec.tsx => ui-jobs/tests/job-list-action.client.spec.tsx} (55%) rename packages/client/{ui-task => ui-jobs}/tsconfig.json (89%) rename packages/client/{ui-task => ui-jobs}/tsdown.config.ts (61%) rename packages/client/{ui-directory-picker => ui-message-feedback}/README.i18n.yaml (69%) rename packages/client/{ui-feedback => ui-message-feedback}/README.md (83%) rename packages/client/{ui-feedback => ui-message-feedback}/README.zh.md (80%) rename packages/client/{ui-feedback => ui-message-feedback}/package.json (92%) rename packages/client/{ui-feedback/src/client/FeedbackActions.module.css => ui-message-feedback/src/client/MessageFeedbackActions.module.css} (100%) rename packages/client/{ui-feedback/src/client/FeedbackActions.tsx => ui-message-feedback/src/client/MessageFeedbackActions.tsx} (94%) rename packages/client/{ui-feedback => ui-message-feedback}/src/client/controller.ts (88%) rename packages/client/{ui-feedback => ui-message-feedback}/src/client/index.ts (72%) rename packages/client/{ui-feedback => ui-message-feedback}/src/client/locales.ts (91%) rename packages/client/{ui-feedback => ui-message-feedback}/src/client/slots.ts (77%) rename packages/client/{ui-models => ui-message-feedback}/src/css-modules.d.ts (100%) rename packages/client/{ui-feedback => ui-message-feedback}/src/index.ts (100%) rename packages/client/{ui-feedback => ui-message-feedback}/src/invariant.ts (88%) rename packages/client/{ui-feedback => ui-message-feedback}/tests/browser-plugin.client.spec.tsx (94%) rename packages/client/{ui-feedback => ui-message-feedback}/tests/controller.client.spec.ts (89%) rename packages/client/{ui-feedback/tests/feedback-actions.client.spec.tsx => ui-message-feedback/tests/message-feedback-actions.client.spec.tsx} (86%) rename packages/client/{ui-feedback => ui-message-feedback}/tsconfig.json (86%) create mode 100644 packages/client/ui-message-feedback/tsdown.config.ts rename packages/client/{ui-model => ui-model-selection}/README.i18n.yaml (69%) rename packages/client/{ui-model => ui-model-selection}/README.md (72%) rename packages/client/{ui-model => ui-model-selection}/README.zh.md (72%) rename packages/client/{ui-model => ui-model-selection}/package.json (85%) rename packages/client/{ui-model => ui-model-selection}/src/client/ModelSelect.module.css (100%) rename packages/client/{ui-model => ui-model-selection}/src/client/ModelSelect.tsx (100%) rename packages/client/{ui-model => ui-model-selection}/src/client/directory.ts (100%) rename packages/client/{ui-model => ui-model-selection}/src/client/index.ts (88%) rename packages/client/{ui-model => ui-model-selection}/src/client/locales.ts (100%) rename packages/client/{ui-model => ui-model-selection}/src/client/service.ts (83%) rename packages/client/{ui-model => ui-model-selection}/src/client/slots.ts (100%) rename packages/client/{ui-plugins => ui-model-selection}/src/css-modules.d.ts (100%) rename packages/client/{ui-model => ui-model-selection}/src/index.ts (100%) rename packages/client/{ui-model => ui-model-selection}/src/invariant.ts (81%) rename packages/client/{ui-model => ui-model-selection}/tests/browser-plugin.client.spec.ts (95%) rename packages/client/{ui-model => ui-model-selection}/tests/model-select.client.spec.tsx (100%) rename packages/client/{ui-model => ui-model-selection}/tsconfig.json (83%) create mode 100644 packages/client/ui-model-selection/tsdown.config.ts delete mode 100644 packages/client/ui-model/tsdown.config.ts delete mode 100644 packages/client/ui-models/tsdown.config.ts create mode 100644 packages/client/ui-permission-presets/README.i18n.yaml rename packages/client/{ui-permission => ui-permission-presets}/README.md (59%) rename packages/client/{ui-permission => ui-permission-presets}/README.zh.md (61%) rename packages/client/{ui-permission => ui-permission-presets}/package.json (83%) rename packages/client/{ui-permission => ui-permission-presets}/src/client/PermissionRow.module.css (100%) rename packages/client/{ui-permission => ui-permission-presets}/src/client/PermissionRow.tsx (100%) rename packages/client/{ui-permission => ui-permission-presets}/src/client/index.ts (93%) rename packages/client/{ui-permission => ui-permission-presets}/src/client/locales.ts (100%) rename packages/client/{ui-permission => ui-permission-presets}/src/client/presentation.ts (100%) rename packages/client/{ui-permission => ui-permission-presets}/src/client/settings-store.ts (98%) rename packages/client/{ui-permission => ui-permission-presets}/src/css-modules.d.ts (100%) rename packages/client/{ui-permission => ui-permission-presets}/src/index.ts (100%) rename packages/client/{ui-permission => ui-permission-presets}/src/invariant.ts (81%) rename packages/client/{ui-permission => ui-permission-presets}/tests/browser-plugin.client.spec.ts (96%) rename packages/client/{ui-permission/tests/permission-row.client.spec.tsx => ui-permission-presets/tests/permission-presets-row.client.spec.tsx} (92%) rename packages/client/{ui-permission => ui-permission-presets}/tests/settings-store.client.spec.ts (92%) rename packages/client/{ui-permission => ui-permission-presets}/tsconfig.json (78%) rename packages/client/{ui-permission => ui-permission-presets}/tsdown.config.ts (64%) delete mode 100644 packages/client/ui-permission/README.i18n.yaml delete mode 100644 packages/client/ui-plugin-config/README.i18n.yaml delete mode 100644 packages/client/ui-plugin-config/tsdown.config.ts delete mode 100644 packages/client/ui-plugins/README.i18n.yaml delete mode 100644 packages/client/ui-plugins/tsdown.config.ts delete mode 100644 packages/client/ui-question/README.i18n.yaml rename packages/client/{ui-feedback => ui-settings-models}/README.i18n.yaml (69%) rename packages/client/{ui-models => ui-settings-models}/README.md (99%) rename packages/client/{ui-models => ui-settings-models}/README.zh.md (99%) rename packages/client/{ui-models => ui-settings-models}/package.json (95%) rename packages/client/{ui-models => ui-settings-models}/src/client/CustomProviderCard.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/DeepSeekModelsEditor.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/DeepSeekOnboardingDialog.module.css (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/DeepSeekOnboardingDialog.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/EditorFooter.tsx (97%) rename packages/client/{ui-models => ui-settings-models}/src/client/ModelListEditor.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/ModelsSection.module.css (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/ModelsSection.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/ProviderEditor.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/apiKey.ts (97%) rename packages/client/{ui-models => ui-settings-models}/src/client/index.ts (96%) rename packages/client/{ui-models => ui-settings-models}/src/client/locales.ts (100%) rename packages/client/{ui-models => ui-settings-models}/src/client/store.ts (100%) rename packages/client/{ui-slash => ui-settings-models}/src/css-modules.d.ts (100%) rename packages/client/{ui-models => ui-settings-models}/src/index.ts (100%) rename packages/client/{ui-models => ui-settings-models}/src/invariant.ts (81%) rename packages/client/{ui-models => ui-settings-models}/tests/apply.client.spec.ts (95%) rename packages/client/{ui-models => ui-settings-models}/tests/components.client.spec.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/tests/invariant.client.spec.ts (70%) rename packages/client/{ui-models => ui-settings-models}/tests/onboarding-dialog.client.spec.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/tests/provider-form.client.spec.tsx (100%) rename packages/client/{ui-models => ui-settings-models}/tests/readiness.client.spec.ts (100%) rename packages/client/{ui-models => ui-settings-models}/tests/store.client.spec.ts (100%) rename packages/client/{ui-models => ui-settings-models}/tests/styles.client.spec.ts (100%) rename packages/client/{ui-models => ui-settings-models}/tsconfig.json (92%) create mode 100644 packages/client/ui-settings-models/tsdown.config.ts create mode 100644 packages/client/ui-settings-plugin-inventory/README.i18n.yaml rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/README.md (97%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/README.zh.md (97%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/package.json (94%) rename packages/client/{ui-plugins/src/client/PluginSettingsSection.module.css => ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css} (100%) rename packages/client/{ui-plugins/src/client/PluginSettingsSection.tsx => ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.tsx} (93%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/src/client/index.ts (61%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/src/client/locales.ts (92%) rename packages/client/{ui-task => ui-settings-plugin-inventory}/src/css-modules.d.ts (100%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/src/index.ts (100%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/src/invariant.ts (78%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/tests/browser-plugin.client.spec.tsx (80%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/tests/components.client.spec.tsx (79%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/tests/invariant.client.spec.ts (70%) rename packages/client/{ui-plugins => ui-settings-plugin-inventory}/tsconfig.json (90%) create mode 100644 packages/client/ui-settings-plugin-inventory/tsdown.config.ts create mode 100644 packages/client/ui-settings-plugins/README.i18n.yaml rename packages/client/{ui-plugin-config => ui-settings-plugins}/README.md (99%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/README.zh.md (99%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/package.json (95%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/AgentLoopCard.tsx (93%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/BashCard.tsx (95%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/ConfigurablePluginsTab.tsx (91%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/PluginCard.module.css (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/PluginCard.tsx (93%) rename packages/client/{ui-plugin-config/src/client/PluginConfigSection.module.css => ui-settings-plugins/src/client/PluginsSettingsSection.module.css} (100%) rename packages/client/{ui-plugin-config/src/client/PluginConfigSection.tsx => ui-settings-plugins/src/client/PluginsSettingsSection.tsx} (86%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/WebSearchCard.tsx (96%) rename packages/client/{ui-plugin-config/src/client/agent-loop-store.ts => ui-settings-plugins/src/client/agent-loop-card-controller.ts} (97%) rename packages/client/{ui-plugin-config/src/client/bash-store.ts => ui-settings-plugins/src/client/bash-card-controller.ts} (96%) rename packages/client/{ui-plugin-config/src/client/card-store.ts => ui-settings-plugins/src/client/card-form.ts} (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/fields.module.css (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/fields.tsx (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/index.ts (86%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/locales.ts (96%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/client/slot-contract.ts (100%) rename packages/client/{ui-plugin-config/src/client/web-search-store.ts => ui-settings-plugins/src/client/web-search-card-controller.ts} (99%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/css-modules.d.ts (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/index.ts (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/src/invariant.ts (82%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/tests/apply.client.spec.ts (89%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/tests/fields.client.spec.tsx (100%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/tests/invariant.client.spec.ts (76%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/tests/section.client.spec.tsx (95%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/tests/stores.client.spec.ts (99%) rename packages/client/{ui-plugin-config => ui-settings-plugins}/tsconfig.json (84%) create mode 100644 packages/client/ui-settings-plugins/tsdown.config.ts delete mode 100644 packages/client/ui-slash/README.i18n.yaml delete mode 100644 packages/client/ui-slash/README.md delete mode 100644 packages/client/ui-slash/README.zh.md delete mode 100644 packages/client/ui-slash/tsdown.config.ts delete mode 100644 packages/client/ui-task/README.md rename packages/client/{ui-command => ui-user-questions}/README.i18n.yaml (69%) rename packages/client/{ui-question => ui-user-questions}/README.md (98%) rename packages/client/{ui-question => ui-user-questions}/README.zh.md (98%) rename packages/client/{ui-question => ui-user-questions}/package.json (92%) rename packages/client/{ui-question => ui-user-questions}/src/client/PlanReviewPanel.module.css (100%) rename packages/client/{ui-question => ui-user-questions}/src/client/PlanReviewPanel.tsx (100%) rename packages/client/{ui-question => ui-user-questions}/src/client/QuestionComposer.module.css (100%) rename packages/client/{ui-question => ui-user-questions}/src/client/QuestionComposer.tsx (100%) rename packages/client/{ui-question => ui-user-questions}/src/client/contract/slots.ts (100%) rename packages/client/{ui-question => ui-user-questions}/src/client/index.ts (96%) rename packages/client/{ui-question => ui-user-questions}/src/client/locales.ts (100%) rename packages/client/{ui-question => ui-user-questions}/src/css-modules.d.ts (100%) rename packages/client/{ui-question => ui-user-questions}/src/index.ts (100%) rename packages/client/{ui-question => ui-user-questions}/src/invariant.ts (82%) rename packages/client/{ui-question => ui-user-questions}/tests/browser-plugin.client.spec.ts (87%) rename packages/client/{ui-question => ui-user-questions}/tests/node-plugin.client.spec.ts (78%) rename packages/client/{ui-question => ui-user-questions}/tests/plan-review-panel.client.spec.tsx (100%) rename packages/client/{ui-question/tests/question-composer.client.spec.tsx => ui-user-questions/tests/user-questions-composer.client.spec.tsx} (100%) rename packages/client/{ui-question => ui-user-questions}/tsconfig.json (90%) create mode 100644 packages/client/ui-user-questions/tsdown.config.ts rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/README.i18n.yaml (67%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/README.md (90%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/README.zh.md (90%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/package.json (92%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/bootstrap.ts (99%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/index.ts (93%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/invariant.ts (81%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/output-json.ts (99%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/protocol.ts (97%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/worker-json.ts (99%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/src/worker.ts (74%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tests/bootstrap.spec.ts (100%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tests/built-lib.e2e.ts (94%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tests/output-json.spec.ts (100%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tests/runtime.spec.ts (96%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tests/source-worker.compat.spec.ts (100%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tests/worker-json.spec.ts (100%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tsconfig.json (90%) rename packages/code-runtime/{code-runtime-worker => code-runtime-worker-thread}/tsdown.config.ts (100%) delete mode 100644 packages/compact/README.md rename packages/{compact => compaction}/README.i18n.yaml (71%) create mode 100644 packages/compaction/README.md rename packages/{compact => compaction}/README.zh.md (50%) rename packages/{compact => compaction}/command-compact/README.i18n.yaml (69%) rename packages/{compact => compaction}/command-compact/README.md (78%) rename packages/{compact => compaction}/command-compact/README.zh.md (77%) rename packages/{compact => compaction}/command-compact/package.json (89%) rename packages/{compact => compaction}/command-compact/src/index.ts (94%) rename packages/{compact => compaction}/command-compact/src/invariant.ts (100%) rename packages/{compact => compaction}/command-compact/tests/command-compact.spec.ts (92%) rename packages/{compact => compaction}/command-compact/tests/invariant.spec.ts (100%) rename packages/{compact => compaction}/command-compact/tests/loader-composition.spec.ts (88%) rename packages/{compact => compaction}/command-compact/tsconfig.json (81%) rename packages/{compact/compact => compaction/compaction-basic}/README.i18n.yaml (69%) rename packages/{compact/compact-basic => compaction/compaction-basic}/README.md (73%) rename packages/{compact/compact-basic => compaction/compaction-basic}/README.zh.md (73%) rename packages/{compact/compact-basic => compaction/compaction-basic}/package.json (83%) rename packages/{compact/compact-basic => compaction/compaction-basic}/src/config.ts (91%) rename packages/{compact/compact-basic => compaction/compaction-basic}/src/index.ts (94%) rename packages/{compact/compact-basic => compaction/compaction-basic}/src/invariant.ts (83%) rename packages/{compact/compact-basic => compaction/compaction-basic}/src/region.ts (94%) rename packages/{compact/compact-basic => compaction/compaction-basic}/src/summarizer.ts (97%) rename packages/{compact/compact-basic => compaction/compaction-basic}/src/types.ts (92%) rename packages/{compact/compact-basic/tests/compact-basic.spec.ts => compaction/compaction-basic/tests/compaction-basic.spec.ts} (92%) rename packages/{compact/compact-basic/tests/compact-loop-repro.spec.ts => compaction/compaction-basic/tests/compaction-loop-repro.spec.ts} (94%) rename packages/{compact/compact-basic => compaction/compaction-basic}/tests/loader-composition.spec.ts (69%) rename packages/{compact/compact-basic/tests/manual-compact.spec.ts => compaction/compaction-basic/tests/manual-compaction.spec.ts} (89%) rename packages/{compact/compact-basic => compaction/compaction-basic}/tsconfig.json (82%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/README.i18n.yaml (67%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/README.md (80%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/README.zh.md (80%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/package.json (86%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/src/config.ts (100%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/src/index.ts (93%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/src/invariant.ts (79%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/src/types.ts (100%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/tests/loader-composition.spec.ts (76%) rename packages/{compact/compact-tool-result-prune/tests/tool-result-prune.spec.ts => compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts} (93%) rename packages/{compact/compact-tool-result-prune => compaction/compaction-tool-result-pruner}/tsconfig.json (82%) rename packages/{compact/compact-basic => compaction/compaction}/README.i18n.yaml (70%) rename packages/{compact/compact => compaction/compaction}/README.md (60%) rename packages/{compact/compact => compaction/compaction}/README.zh.md (60%) rename packages/{compact/compact => compaction/compaction}/package.json (89%) rename packages/{compact/compact => compaction/compaction}/src/brand.ts (100%) rename packages/{compact/compact => compaction/compaction}/src/checkpoint.ts (88%) rename packages/{compact/compact => compaction/compaction}/src/index.ts (88%) rename packages/{compact/compact => compaction/compaction}/src/invariant.ts (79%) rename packages/{compact/compact => compaction/compaction}/src/tool-pairing.ts (99%) rename packages/{compact/compact => compaction/compaction}/src/types.ts (85%) rename packages/{compact/compact/tests/compact.spec.ts => compaction/compaction/tests/compaction.spec.ts} (76%) rename packages/{compact/compact => compaction/compaction}/tests/invariant.spec.ts (62%) rename packages/{compact/compact => compaction/compaction}/tests/tool-pairing.spec.ts (99%) rename packages/{compact/compact => compaction/compaction}/tsconfig.json (90%) rename packages/{compact/compact => compaction/compaction}/tsdown.config.ts (100%) rename packages/context/{workspace-context => agent-instructions}/README.i18n.yaml (69%) rename packages/context/{workspace-context => agent-instructions}/README.md (92%) rename packages/context/{workspace-context => agent-instructions}/README.zh.md (92%) rename packages/context/{workspace-context => agent-instructions}/package.json (89%) rename packages/context/{workspace-context => agent-instructions}/src/config.ts (97%) rename packages/context/{workspace-context => agent-instructions}/src/digest.ts (94%) rename packages/context/{workspace-context => agent-instructions}/src/files.ts (99%) rename packages/context/{workspace-context => agent-instructions}/src/index.ts (94%) rename packages/context/{workspace-context => agent-instructions}/src/invariant.ts (82%) rename packages/context/{workspace-context => agent-instructions}/src/render.ts (98%) rename packages/context/{workspace-context => agent-instructions}/src/state.ts (92%) rename packages/context/{workspace-context/tests/workspace-context.e2e.ts => agent-instructions/tests/agent-instructions.e2e.ts} (95%) rename packages/context/{workspace-context/tests/workspace-context.spec.ts => agent-instructions/tests/agent-instructions.spec.ts} (97%) rename packages/context/{workspace-context => agent-instructions}/tsconfig.json (85%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/README.i18n.yaml (68%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/README.md (85%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/README.zh.md (86%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/package.json (92%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/src/index.ts (94%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/src/invariant.ts (84%) rename packages/core/{agent-tool-mode/tests/agent-tool-mode.spec.ts => agent-tool-presentation/tests/agent-tool-presentation.spec.ts} (96%) rename packages/core/{agent-tool-mode => agent-tool-presentation}/tsconfig.json (88%) rename packages/{self-modification => extensions}/README.i18n.yaml (80%) rename packages/{self-modification => extensions}/README.md (100%) rename packages/{self-modification => extensions}/README.zh.md (100%) create mode 100644 packages/extensions/tool-cordis/README.i18n.yaml rename packages/{self-modification => extensions}/tool-cordis/README.md (89%) rename packages/{self-modification => extensions}/tool-cordis/README.zh.md (90%) rename packages/{self-modification => extensions}/tool-cordis/package.json (96%) rename packages/{self-modification => extensions}/tool-cordis/src/api-catalog.ts (93%) rename packages/{self-modification => extensions}/tool-cordis/src/fiber-state.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/src/guard.ts (99%) rename packages/{self-modification => extensions}/tool-cordis/src/index.ts (97%) rename packages/{self-modification => extensions}/tool-cordis/src/inspect.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/src/invariant.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/src/mount.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/src/present.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/src/sandbox.ts (98%) rename packages/{self-modification => extensions}/tool-cordis/tests/cordis-lifecycle.spec.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/tests/cross-mount.spec.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/tests/helpers.ts (93%) rename packages/{self-modification => extensions}/tool-cordis/tests/inspect.spec.ts (96%) rename packages/{self-modification => extensions}/tool-cordis/tests/integration.spec.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/tests/mount.spec.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/tests/present.spec.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/tests/sandbox-context.spec.ts (99%) rename packages/{self-modification => extensions}/tool-cordis/tests/tool-cordis.spec.ts (100%) rename packages/{self-modification => extensions}/tool-cordis/tests/unmount-hmr.spec.ts (97%) rename packages/{self-modification => extensions}/tool-cordis/tsconfig.json (90%) create mode 100644 packages/fs/fs-observation-policy/README.i18n.yaml rename packages/fs/{fs-policy => fs-observation-policy}/README.md (87%) rename packages/fs/{fs-policy => fs-observation-policy}/README.zh.md (87%) rename packages/fs/{fs-policy => fs-observation-policy}/package.json (92%) rename packages/fs/{fs-policy => fs-observation-policy}/src/index.ts (92%) rename packages/fs/{fs-policy => fs-observation-policy}/src/invariant.ts (76%) rename packages/fs/{fs-policy => fs-observation-policy}/src/types.ts (79%) rename packages/fs/{fs-policy => fs-observation-policy}/tests/policy.spec.ts (94%) rename packages/fs/{fs-policy => fs-observation-policy}/tsconfig.json (87%) rename packages/goal/{goal-session => goal-round-driver}/README.i18n.yaml (69%) rename packages/goal/{goal-session => goal-round-driver}/README.md (98%) rename packages/goal/{goal-session => goal-round-driver}/README.zh.md (98%) rename packages/goal/{goal-session => goal-round-driver}/package.json (93%) rename packages/goal/{goal-session => goal-round-driver}/src/index.ts (93%) rename packages/goal/{goal-session => goal-round-driver}/src/invariant.ts (94%) rename packages/goal/{goal-session => goal-round-driver}/src/prompt.ts (100%) rename packages/goal/{goal-session/tests/goal-session.spec.ts => goal-round-driver/tests/goal-round-driver.spec.ts} (99%) rename packages/goal/{goal-session => goal-round-driver}/tests/invariant.spec.ts (83%) rename packages/goal/{goal-session => goal-round-driver}/tsconfig.json (89%) rename packages/goal/{goal-session => goal-round-driver}/tsdown.config.ts (100%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/README.i18n.yaml (69%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/README.md (87%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/README.zh.md (87%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/package.json (93%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/src/index.ts (93%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/src/invariant.ts (83%) rename packages/guard/{repeat-tool-guard/tests/repeat-tool-guard.spec.ts => repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts} (99%) rename packages/guard/{repeat-tool-guard => repeat-tool-reminder}/tsconfig.json (90%) rename packages/hooks/{hooks-claude => hooks-claude-code}/README.i18n.yaml (69%) rename packages/hooks/{hooks-claude => hooks-claude-code}/README.md (96%) rename packages/hooks/{hooks-claude => hooks-claude-code}/README.zh.md (96%) rename packages/hooks/{hooks-claude => hooks-claude-code}/package.json (93%) rename packages/hooks/{hooks-claude => hooks-claude-code}/src/config.ts (92%) rename packages/hooks/{hooks-claude => hooks-claude-code}/src/index.ts (92%) rename packages/hooks/{hooks-claude => hooks-claude-code}/src/invariant.ts (83%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/bridge.spec.ts (95%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/config.spec.ts (72%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/coverage-cases.ts (95%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/coverage-config.spec.ts (100%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/coverage-context.spec.ts (100%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/coverage-edge-paths.spec.ts (100%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tests/coverage-stop.spec.ts (100%) rename packages/hooks/{hooks-claude => hooks-claude-code}/tsconfig.json (89%) rename packages/host/apiproxy/src/api/{tasks.schema.ts => jobs.schema.ts} (69%) rename packages/host/apiproxy/src/api/{tasks.ts => jobs.ts} (84%) rename packages/host/apiproxy/tests/{api-proxy-tasks.spec.ts => api-proxy-jobs.spec.ts} (74%) rename packages/{pty => identity}/README.i18n.yaml (57%) create mode 100644 packages/identity/README.md create mode 100644 packages/identity/README.zh.md create mode 100644 packages/identity/anonymous-user-id/README.i18n.yaml rename packages/{session/user-id => identity/anonymous-user-id}/README.md (78%) rename packages/{session/user-id => identity/anonymous-user-id}/README.zh.md (79%) rename packages/{session/user-id => identity/anonymous-user-id}/package.json (84%) rename packages/{session/user-id => identity/anonymous-user-id}/src/index.ts (93%) rename packages/{session/user-id => identity/anonymous-user-id}/src/invariant.ts (79%) rename packages/{session/user-id/tests/user-id.spec.ts => identity/anonymous-user-id/tests/anonymous-user-id.spec.ts} (81%) rename packages/{session/user-id => identity/anonymous-user-id}/tests/invariant.spec.ts (62%) rename packages/{session/user-id => identity/anonymous-user-id}/tsconfig.json (73%) rename packages/interaction/{user-interaction => permission-presets}/README.i18n.yaml (68%) create mode 100644 packages/interaction/permission-presets/README.md create mode 100644 packages/interaction/permission-presets/README.zh.md rename packages/interaction/{permission => permission-presets}/package.json (84%) rename packages/interaction/{permission => permission-presets}/src/client.ts (86%) rename packages/interaction/{permission => permission-presets}/src/index.ts (95%) rename packages/interaction/{permission => permission-presets}/src/invariant.ts (83%) rename packages/interaction/{permission => permission-presets}/src/types.ts (96%) rename packages/interaction/{permission => permission-presets}/tests/invariant.spec.ts (88%) rename packages/interaction/{permission/tests/permission.spec.ts => permission-presets/tests/permission-presets.spec.ts} (77%) rename packages/interaction/{permission => permission-presets}/tests/projection.spec.ts (92%) rename packages/interaction/{permission => permission-presets}/tsconfig.json (89%) delete mode 100644 packages/interaction/permission/README.md delete mode 100644 packages/interaction/permission/README.zh.md rename packages/interaction/{permission => user-questions}/README.i18n.yaml (69%) rename packages/interaction/{user-interaction => user-questions}/README.md (78%) rename packages/interaction/{user-interaction => user-questions}/README.zh.md (78%) rename packages/interaction/{user-interaction => user-questions}/package.json (85%) rename packages/interaction/{user-interaction => user-questions}/src/index.ts (74%) rename packages/interaction/{user-interaction => user-questions}/src/invariant.ts (84%) rename packages/interaction/{user-interaction => user-questions}/src/types.ts (95%) rename packages/interaction/{user-interaction/tests/user-interaction.spec.ts => user-questions/tests/user-questions.spec.ts} (66%) rename packages/interaction/{user-interaction => user-questions}/tsconfig.json (87%) rename packages/{bash => jobs}/README.i18n.yaml (59%) create mode 100644 packages/jobs/README.md create mode 100644 packages/jobs/README.zh.md rename packages/{bash/bash-local => jobs/jobs-local}/README.i18n.yaml (56%) create mode 100644 packages/jobs/jobs-local/README.md rename packages/{tasks/tasks-local => jobs/jobs-local}/README.zh.md (69%) rename packages/{tasks/tasks-local => jobs/jobs-local}/package.json (87%) rename packages/{tasks/tasks-local => jobs/jobs-local}/src/index.ts (61%) rename packages/{tasks/tasks-local => jobs/jobs-local}/src/invariant.ts (72%) rename packages/{tasks/tasks-local/tests/tasks.spec.ts => jobs/jobs-local/tests/jobs.spec.ts} (58%) rename packages/{tasks/tasks-local => jobs/jobs-local}/tests/loader-composition.spec.ts (75%) rename packages/{tasks/tasks-local => jobs/jobs-local}/tsconfig.json (86%) rename packages/{bash/bash => jobs/jobs}/README.i18n.yaml (57%) create mode 100644 packages/jobs/jobs/README.md rename packages/{tasks/tasks => jobs/jobs}/README.zh.md (52%) rename packages/{tasks/tasks => jobs/jobs}/package.json (83%) rename packages/{tasks/tasks => jobs/jobs}/src/brand.ts (57%) rename packages/{tasks/tasks => jobs/jobs}/src/index.ts (62%) rename packages/{tasks/tasks => jobs/jobs}/src/invariant.ts (57%) rename packages/{tasks/tasks => jobs/jobs}/src/types.ts (62%) rename packages/{tasks/tasks => jobs/jobs}/tests/invariant.spec.ts (57%) create mode 100644 packages/jobs/jobs/tests/service.spec.ts rename packages/{tasks/tasks => jobs/jobs}/tsconfig.json (89%) rename packages/{bash/bash-env => jobs/tool-jobs}/README.i18n.yaml (56%) create mode 100644 packages/jobs/tool-jobs/README.md rename packages/{tasks/tool-tasks => jobs/tool-jobs}/README.zh.md (57%) rename packages/{tasks/tool-tasks => jobs/tool-jobs}/package.json (75%) rename packages/{tasks/tool-tasks => jobs/tool-jobs}/src/index.ts (68%) rename packages/{tasks/tool-tasks => jobs/tool-jobs}/src/invariant.ts (85%) rename packages/{tasks/tool-tasks/tests/tool-tasks.spec.ts => jobs/tool-jobs/tests/tool-jobs.spec.ts} (71%) rename packages/{tasks/tool-tasks => jobs/tool-jobs}/tsconfig.json (81%) rename packages/lsp/{lsp-local => lsp-stdio}/README.i18n.yaml (71%) rename packages/lsp/{lsp-local => lsp-stdio}/README.md (99%) rename packages/lsp/{lsp-local => lsp-stdio}/README.zh.md (99%) rename packages/lsp/{lsp-local => lsp-stdio}/package.json (96%) rename packages/lsp/{lsp-local => lsp-stdio}/src/abort.ts (97%) rename packages/lsp/{lsp-local => lsp-stdio}/src/connection.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/src/framing.ts (98%) rename packages/lsp/{lsp-local => lsp-stdio}/src/host.ts (100%) rename packages/lsp/{lsp-local => lsp-stdio}/src/index.ts (95%) rename packages/lsp/{lsp-local => lsp-stdio}/src/instance.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/src/invariant.ts (80%) rename packages/lsp/{lsp-local => lsp-stdio}/src/protocol.ts (98%) rename packages/lsp/{lsp-local => lsp-stdio}/src/translate.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/built-lib.e2e.ts (93%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/connection.spec.ts (98%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/fixture-server.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/framing.spec.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/host.spec.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/instance.spec.ts (98%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/lifecycle.spec.ts (97%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/provider.spec.ts (92%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/translate.spec.ts (99%) rename packages/lsp/{lsp-local => lsp-stdio}/tests/typescript-server.e2e.ts (95%) rename packages/lsp/{lsp-local => lsp-stdio}/tsconfig.json (92%) delete mode 100644 packages/pty/README.md delete mode 100644 packages/pty/README.zh.md delete mode 100644 packages/pty/pty-local/README.i18n.yaml delete mode 100644 packages/pty/tool-bash-persistent/README.i18n.yaml delete mode 100644 packages/pty/tool-pty/README.i18n.yaml delete mode 100644 packages/pty/tool-pty/README.md delete mode 100644 packages/pty/tool-pty/README.zh.md create mode 100644 packages/runtime-diagnostics/invariants/README.i18n.yaml rename packages/{support => runtime-diagnostics}/invariants/README.md (90%) rename packages/{support => runtime-diagnostics}/invariants/README.zh.md (90%) rename packages/{support => runtime-diagnostics}/invariants/package.json (94%) rename packages/{support => runtime-diagnostics}/invariants/src/index.ts (98%) rename packages/{support => runtime-diagnostics}/invariants/src/invariant.ts (100%) rename packages/{support => runtime-diagnostics}/invariants/tests/service.spec.ts (97%) rename packages/{support => runtime-diagnostics}/invariants/tsconfig.json (100%) rename packages/schedule/{tool-schedule => schedule}/README.i18n.yaml (69%) rename packages/schedule/{tool-schedule => schedule}/README.md (96%) rename packages/schedule/{tool-schedule => schedule}/README.zh.md (96%) rename packages/schedule/{tool-schedule => schedule}/package.json (95%) rename packages/schedule/{tool-schedule => schedule}/src/domain.ts (99%) rename packages/schedule/{tool-schedule => schedule}/src/index.ts (73%) rename packages/schedule/{tool-schedule => schedule}/src/invariant.ts (95%) rename packages/schedule/{tool-schedule => schedule}/src/persistence.ts (100%) rename packages/schedule/{tool-schedule => schedule}/src/runtime.ts (87%) rename packages/schedule/{tool-schedule => schedule}/src/tools.ts (98%) rename packages/schedule/{tool-schedule => schedule}/src/transaction.ts (100%) rename packages/schedule/{tool-schedule => schedule}/src/types.ts (99%) rename packages/schedule/{tool-schedule => schedule}/tests/domain.spec.ts (100%) rename packages/schedule/{tool-schedule => schedule}/tests/invariant.spec.ts (95%) rename packages/schedule/{tool-schedule => schedule}/tests/jsonl-restart.spec.ts (96%) rename packages/schedule/{tool-schedule => schedule}/tests/plugin.spec.ts (98%) rename packages/schedule/{tool-schedule => schedule}/tests/recurrence.spec.ts (100%) rename packages/schedule/{tool-schedule => schedule}/tests/runtime.spec.ts (84%) rename packages/schedule/{tool-schedule => schedule}/tests/tools.spec.ts (99%) rename packages/schedule/{tool-schedule => schedule}/tsconfig.json (92%) rename packages/schedule/{tool-schedule => schedule}/tsdown.config.ts (100%) delete mode 100644 packages/self-modification/tool-cordis/README.i18n.yaml delete mode 100644 packages/session-query/session-export/tsdown.config.ts rename packages/session-query/{session-export => session-log-download}/README.i18n.yaml (67%) rename packages/session-query/{session-export => session-log-download}/README.md (87%) rename packages/session-query/{session-export => session-log-download}/README.zh.md (86%) rename packages/session-query/{session-export => session-log-download}/package.json (88%) rename packages/session-query/{session-export => session-log-download}/src/client/Dialog.tsx (75%) rename packages/session-query/{session-export => session-log-download}/src/client/HeaderAction.module.css (100%) rename packages/session-query/{session-export => session-log-download}/src/client/HeaderAction.tsx (66%) rename packages/session-query/{session-export => session-log-download}/src/client/controller.ts (89%) rename packages/session-query/{session-export => session-log-download}/src/client/index.ts (57%) rename packages/session-query/{session-export => session-log-download}/src/client/locales.ts (92%) rename packages/session-query/{session-export => session-log-download}/src/css-modules.d.ts (100%) rename packages/session-query/{session-export => session-log-download}/src/index.ts (91%) rename packages/session-query/{session-export => session-log-download}/src/invariant.ts (83%) rename packages/session-query/{session-export => session-log-download}/tests/client-apply.client.spec.tsx (70%) rename packages/session-query/{session-export => session-log-download}/tests/command.client.spec.ts (91%) rename packages/session-query/{session-export => session-log-download}/tests/controller.client.spec.ts (86%) rename packages/session-query/{session-export => session-log-download}/tests/dialog.client.spec.tsx (79%) rename packages/session-query/{session-export => session-log-download}/tests/header-action.client.spec.tsx (69%) rename packages/session-query/{session-export => session-log-download}/tests/invariant.client.spec.ts (83%) rename packages/session-query/{session-export => session-log-download}/tests/loader-composition.client.spec.ts (87%) rename packages/session-query/{session-export => session-log-download}/tsconfig.json (83%) create mode 100644 packages/session-query/session-log-download/tsdown.config.ts rename packages/session/{user-id => session-title-all-prompts-llm}/README.i18n.yaml (67%) rename packages/session/{session-title-all-messages-llm => session-title-all-prompts-llm}/README.md (80%) rename packages/session/{session-title-all-messages-llm => session-title-all-prompts-llm}/README.zh.md (81%) rename packages/session/{session-title-all-messages-llm => session-title-all-prompts-llm}/package.json (91%) rename packages/session/{session-title-all-messages-llm => session-title-all-prompts-llm}/src/index.ts (83%) rename packages/session/{session-title-first-message-llm => session-title-all-prompts-llm}/src/invariant.ts (79%) rename packages/session/{session-title-all-messages-llm => session-title-all-prompts-llm}/tests/provider.spec.ts (96%) rename packages/session/{session-title-first-message-llm => session-title-all-prompts-llm}/tsconfig.json (87%) delete mode 100644 packages/session/session-title-first-message-llm/README.i18n.yaml rename packages/session/{session-title-all-messages-llm => session-title-first-prompt-llm}/README.i18n.yaml (67%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/README.md (82%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/README.zh.md (80%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/package.json (92%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/src/index.ts (83%) rename packages/session/{session-title-all-messages-llm => session-title-first-prompt-llm}/src/invariant.ts (80%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/tests/loader-composition.spec.ts (92%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/tests/provider.e2e.ts (87%) rename packages/session/{session-title-first-message-llm => session-title-first-prompt-llm}/tests/provider.spec.ts (94%) rename packages/session/{session-title-all-messages-llm => session-title-first-prompt-llm}/tsconfig.json (87%) rename packages/settings/{settings-local => settings-file}/README.i18n.yaml (69%) rename packages/settings/{settings-local => settings-file}/README.md (99%) rename packages/settings/{settings-local => settings-file}/README.zh.md (99%) rename packages/settings/{settings-local => settings-file}/package.json (87%) rename packages/settings/{settings-local => settings-file}/src/index.ts (94%) rename packages/settings/{settings-local => settings-file}/src/invariant.ts (85%) rename packages/settings/{settings-local => settings-file}/tests/concurrency.spec.ts (95%) rename packages/settings/{settings-local => settings-file}/tests/loader-composition.spec.ts (95%) rename packages/settings/{settings-local => settings-file}/tests/local.spec.ts (98%) rename packages/settings/{settings-local => settings-file}/tests/lock-race.spec.ts (94%) rename packages/settings/{settings-local => settings-file}/tests/watcher.spec.ts (96%) rename packages/settings/{settings-local => settings-file}/tsconfig.json (83%) rename packages/{tasks => shell}/README.i18n.yaml (59%) rename packages/{bash => shell}/README.md (71%) rename packages/{bash => shell}/README.zh.md (69%) create mode 100644 packages/shell/bash-local/README.i18n.yaml rename packages/{bash => shell}/bash-local/README.md (68%) rename packages/{bash => shell}/bash-local/README.zh.md (68%) rename packages/{bash => shell}/bash-local/package.json (91%) rename packages/{bash => shell}/bash-local/src/index.ts (93%) rename packages/{bash => shell}/bash-local/src/invariant.ts (100%) rename packages/{bash => shell}/bash-local/tests/executor.spec.ts (94%) rename packages/{bash => shell}/bash-local/tests/settings.spec.ts (75%) rename packages/{bash => shell}/bash-local/tsconfig.json (86%) create mode 100644 packages/shell/bash-sandbox/README.i18n.yaml rename packages/{bash => shell}/bash-sandbox/README.md (82%) rename packages/{bash => shell}/bash-sandbox/README.zh.md (82%) rename packages/{bash => shell}/bash-sandbox/package.json (92%) rename packages/{bash => shell}/bash-sandbox/src/helpers.ts (96%) rename packages/{bash => shell}/bash-sandbox/src/index.ts (91%) rename packages/{bash => shell}/bash-sandbox/src/invariant.ts (100%) rename packages/{bash => shell}/bash-sandbox/tests/bwrap.e2e.ts (96%) rename packages/{bash => shell}/bash-sandbox/tests/landlock.e2e.ts (96%) rename packages/{bash => shell}/bash-sandbox/tests/partial-landlock.spec.ts (98%) rename packages/{bash => shell}/bash-sandbox/tests/sandbox.spec.ts (97%) rename packages/{bash => shell}/bash-sandbox/tests/seatbelt.e2e.ts (96%) rename packages/{bash => shell}/bash-sandbox/tsconfig.json (82%) create mode 100644 packages/shell/pwsh-local/README.i18n.yaml rename packages/{bash => shell}/pwsh-local/README.md (75%) rename packages/{bash => shell}/pwsh-local/README.zh.md (77%) rename packages/{bash => shell}/pwsh-local/package.json (91%) rename packages/{bash => shell}/pwsh-local/src/index.ts (93%) rename packages/{bash => shell}/pwsh-local/src/invariant.ts (100%) rename packages/{bash => shell}/pwsh-local/src/resolve.ts (100%) rename packages/{bash => shell}/pwsh-local/tests/executor.spec.ts (95%) rename packages/{bash => shell}/pwsh-local/tests/settings.spec.ts (78%) rename packages/{bash => shell}/pwsh-local/tsconfig.json (86%) create mode 100644 packages/shell/pwsh-sandbox/README.i18n.yaml rename packages/{bash => shell}/pwsh-sandbox/README.md (75%) rename packages/{bash => shell}/pwsh-sandbox/README.zh.md (74%) rename packages/{bash => shell}/pwsh-sandbox/package.json (91%) rename packages/{bash => shell}/pwsh-sandbox/src/helpers.ts (97%) rename packages/{bash => shell}/pwsh-sandbox/src/index.ts (91%) rename packages/{bash => shell}/pwsh-sandbox/src/invariant.ts (100%) rename packages/{bash => shell}/pwsh-sandbox/tests/acl.e2e.ts (97%) rename packages/{bash => shell}/pwsh-sandbox/tests/sandbox.spec.ts (97%) rename packages/{bash => shell}/pwsh-sandbox/tsconfig.json (80%) create mode 100644 packages/shell/shell-env/README.i18n.yaml create mode 100644 packages/shell/shell-env/README.md create mode 100644 packages/shell/shell-env/README.zh.md rename packages/{bash/bash-env => shell/shell-env}/package.json (83%) rename packages/{bash/bash-env => shell/shell-env}/src/index.ts (90%) rename packages/{bash/bash-env => shell/shell-env}/src/invariant.ts (81%) rename packages/{bash/bash-env/tests/bash-env.spec.ts => shell/shell-env/tests/shell-env.spec.ts} (84%) rename packages/{bash/bash-env => shell/shell-env}/tsconfig.json (79%) create mode 100644 packages/shell/shell/README.i18n.yaml rename packages/{bash/bash => shell/shell}/README.md (50%) create mode 100644 packages/shell/shell/README.zh.md rename packages/{bash/bash => shell/shell}/package.json (88%) rename packages/{bash/bash => shell/shell}/src/index.ts (70%) rename packages/{bash/bash => shell/shell}/src/invariant.ts (87%) rename packages/{bash/bash => shell/shell}/src/render.ts (93%) rename packages/{bash/bash => shell/shell}/src/types.ts (85%) rename packages/{bash/bash => shell/shell}/tests/render.spec.ts (100%) rename packages/{bash/bash => shell/shell}/tests/service.spec.ts (66%) rename packages/{bash/bash => shell/shell}/tsconfig.json (89%) create mode 100644 packages/shell/tool-bash-persistent/README.i18n.yaml rename packages/{pty => shell}/tool-bash-persistent/README.md (93%) rename packages/{pty => shell}/tool-bash-persistent/README.zh.md (95%) rename packages/{pty => shell}/tool-bash-persistent/package.json (89%) rename packages/{pty => shell}/tool-bash-persistent/src/index.ts (91%) rename packages/{pty => shell}/tool-bash-persistent/src/invariant.ts (100%) rename packages/{pty => shell}/tool-bash-persistent/tests/loader-composition.spec.ts (91%) rename packages/{pty => shell}/tool-bash-persistent/tests/tools.spec.ts (94%) rename packages/{pty => shell}/tool-bash-persistent/tsconfig.json (76%) create mode 100644 packages/shell/tool-bash/README.i18n.yaml rename packages/{bash => shell}/tool-bash/README.md (60%) rename packages/{bash => shell}/tool-bash/README.zh.md (61%) rename packages/{bash => shell}/tool-bash/package.json (81%) rename packages/{bash => shell}/tool-bash/src/background.ts (75%) rename packages/{bash => shell}/tool-bash/src/index.ts (91%) rename packages/{bash => shell}/tool-bash/src/invariant.ts (100%) rename packages/{bash => shell}/tool-bash/src/render.ts (90%) rename packages/{bash => shell}/tool-bash/tests/integration.spec.ts (91%) rename packages/{bash => shell}/tool-bash/tests/tools.spec.ts (90%) rename packages/{bash => shell}/tool-bash/tsconfig.json (82%) create mode 100644 packages/shell/tool-pwsh/README.i18n.yaml rename packages/{bash => shell}/tool-pwsh/README.md (66%) rename packages/{bash => shell}/tool-pwsh/README.zh.md (69%) rename packages/{bash => shell}/tool-pwsh/package.json (82%) rename packages/{bash => shell}/tool-pwsh/src/background.ts (79%) rename packages/{bash => shell}/tool-pwsh/src/index.ts (92%) rename packages/{bash => shell}/tool-pwsh/src/invariant.ts (100%) rename packages/{bash => shell}/tool-pwsh/src/render.ts (94%) rename packages/{bash => shell}/tool-pwsh/tests/integration.spec.ts (88%) rename packages/{bash => shell}/tool-pwsh/tests/loader.spec.ts (94%) rename packages/{bash => shell}/tool-pwsh/tests/tools.spec.ts (90%) rename packages/{bash => shell}/tool-pwsh/tsconfig.json (79%) rename packages/skill/{skill-local => skill-filesystem}/README.i18n.yaml (69%) rename packages/skill/{skill-local => skill-filesystem}/README.md (97%) rename packages/skill/{skill-local => skill-filesystem}/README.zh.md (96%) rename packages/skill/{skill-local => skill-filesystem}/package.json (86%) rename packages/skill/{skill-local => skill-filesystem}/src/index.ts (97%) rename packages/skill/{skill-local => skill-filesystem}/src/invariant.ts (83%) rename packages/skill/{skill-local/tests/skill-local-watcher.spec.ts => skill-filesystem/tests/skill-filesystem-watcher.spec.ts} (90%) rename packages/skill/{skill-local/tests/skill-local.spec.ts => skill-filesystem/tests/skill-filesystem.spec.ts} (93%) rename packages/skill/{skill-local => skill-filesystem}/tsconfig.json (78%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/README.i18n.yaml (68%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/README.md (91%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/README.zh.md (92%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/package.json (83%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/src/index.ts (92%) rename packages/subagent/{subagent-inprocess => subagent-fork-in-process}/src/invariant.ts (81%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/tests/multi-subagent.spec.ts (95%) rename packages/subagent/{subagent-fork/tests/subagent-fork.spec.ts => subagent-fork-in-process/tests/subagent-fork-in-process.spec.ts} (96%) rename packages/subagent/{subagent-fork => subagent-fork-in-process}/tsconfig.json (83%) rename packages/subagent/{subagent-spawn => subagent-in-process-driver}/README.i18n.yaml (67%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/README.md (99%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/README.zh.md (99%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/package.json (94%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/src/index.ts (99%) rename packages/subagent/{subagent-fork => subagent-in-process-driver}/src/invariant.ts (80%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/src/structured.ts (98%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tests/fixtures/plugins/preset-tool.js (100%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tests/fixtures/presets/coding/agent.cordis.yml (100%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tests/fixtures/presets/reviewing/agent.cordis.yml (100%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tests/inheritance.spec.ts (100%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tests/preset-inheritance.spec.ts (100%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tests/structured.spec.ts (99%) rename packages/subagent/{subagent-inprocess/tests/subagent-inprocess.spec.ts => subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts} (98%) rename packages/subagent/{subagent-inprocess => subagent-in-process-driver}/tsconfig.json (91%) rename packages/subagent/{subagent-inprocess => subagent-spawn-in-process}/README.i18n.yaml (67%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/README.md (90%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/README.zh.md (90%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/package.json (87%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/src/index.ts (87%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/src/invariant.ts (81%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/tests/harness.ts (88%) rename packages/subagent/{subagent-spawn/tests/spawn.e2e.ts => subagent-spawn-in-process/tests/spawn-in-process.e2e.ts} (100%) rename packages/subagent/{subagent-spawn/tests/subagent-spawn.spec.ts => subagent-spawn-in-process/tests/subagent-spawn-in-process.spec.ts} (97%) rename packages/subagent/{subagent-spawn => subagent-spawn-in-process}/tsconfig.json (80%) delete mode 100644 packages/support/README.i18n.yaml delete mode 100644 packages/support/invariants/README.i18n.yaml delete mode 100644 packages/support/llm-replay/README.i18n.yaml delete mode 100644 packages/tasks/README.md delete mode 100644 packages/tasks/README.zh.md delete mode 100644 packages/tasks/tasks-local/README.i18n.yaml delete mode 100644 packages/tasks/tasks-local/README.md delete mode 100644 packages/tasks/tasks/README.i18n.yaml delete mode 100644 packages/tasks/tasks/README.md delete mode 100644 packages/tasks/tasks/tests/service.spec.ts delete mode 100644 packages/tasks/tool-tasks/README.i18n.yaml delete mode 100644 packages/tasks/tool-tasks/README.md rename packages/{pty/pty => terminal}/README.i18n.yaml (57%) create mode 100644 packages/terminal/README.md create mode 100644 packages/terminal/README.zh.md create mode 100644 packages/terminal/terminal-bash/README.i18n.yaml rename packages/{pty/pty-local => terminal/terminal-bash}/README.md (78%) rename packages/{pty/pty-local => terminal/terminal-bash}/README.zh.md (79%) rename packages/{pty/pty-local => terminal/terminal-bash}/package.json (89%) rename packages/{pty/pty-local => terminal/terminal-bash}/src/config.ts (84%) rename packages/{pty/pty-local => terminal/terminal-bash}/src/index.ts (82%) rename packages/{pty/pty-local => terminal/terminal-bash}/src/invariant.ts (79%) rename packages/{pty/pty-local => terminal/terminal-bash}/src/sanitize.ts (100%) rename packages/{pty/pty-local => terminal/terminal-bash}/src/session.ts (93%) rename packages/{pty/pty-local => terminal/terminal-bash}/tests/config.spec.ts (87%) rename packages/{pty/pty-local => terminal/terminal-bash}/tests/index.spec.ts (86%) rename packages/{pty/pty-local => terminal/terminal-bash}/tests/local.spec.ts (75%) rename packages/{pty/pty-local => terminal/terminal-bash}/tests/sanitize.spec.ts (98%) rename packages/{pty/pty-local => terminal/terminal-bash}/tests/session.spec.ts (96%) rename packages/{pty/pty-local => terminal/terminal-bash}/tsconfig.json (88%) rename packages/{bash/bash-sandbox => terminal/terminal}/README.i18n.yaml (55%) rename packages/{pty/pty => terminal/terminal}/README.md (71%) rename packages/{pty/pty => terminal/terminal}/README.zh.md (70%) rename packages/{pty/pty => terminal/terminal}/package.json (93%) rename packages/{pty/pty => terminal/terminal}/src/index.ts (75%) rename packages/{pty/pty => terminal/terminal}/src/invariant.ts (81%) rename packages/{pty/pty => terminal/terminal}/src/types.ts (71%) rename packages/{pty/pty => terminal/terminal}/tests/service.spec.ts (67%) rename packages/{pty/pty => terminal/terminal}/tsconfig.json (87%) create mode 100644 packages/terminal/tool-terminal/README.i18n.yaml create mode 100644 packages/terminal/tool-terminal/README.md create mode 100644 packages/terminal/tool-terminal/README.zh.md rename packages/{pty/tool-pty => terminal/tool-terminal}/package.json (74%) rename packages/{pty/tool-pty => terminal/tool-terminal}/src/index.ts (85%) rename packages/{pty/tool-pty => terminal/tool-terminal}/src/invariant.ts (77%) rename packages/{pty/tool-pty => terminal/tool-terminal}/src/render.ts (97%) rename packages/{pty/tool-pty => terminal/tool-terminal}/tests/loader-composition.spec.ts (84%) rename packages/{pty/tool-pty => terminal/tool-terminal}/tests/render.spec.ts (74%) rename packages/{pty/tool-pty => terminal/tool-terminal}/tests/tools.spec.ts (89%) rename packages/{pty/tool-pty => terminal/tool-terminal}/tsconfig.json (76%) rename packages/{fs/fs-policy => test-support}/README.i18n.yaml (56%) rename packages/{support => test-support}/README.md (75%) rename packages/{support => test-support}/README.zh.md (83%) rename packages/{support => test-support}/acp-snapshot/README.i18n.yaml (79%) rename packages/{support => test-support}/acp-snapshot/README.md (100%) rename packages/{support => test-support}/acp-snapshot/README.zh.md (100%) rename packages/{support => test-support}/acp-snapshot/package.json (96%) rename packages/{support => test-support}/acp-snapshot/src/harness.ts (100%) rename packages/{support => test-support}/acp-snapshot/src/index.ts (100%) rename packages/{support => test-support}/acp-snapshot/src/invariant.ts (100%) rename packages/{support => test-support}/acp-snapshot/src/launcher.ts (100%) rename packages/{support => test-support}/acp-snapshot/src/normalize.ts (100%) rename packages/{support => test-support}/acp-snapshot/src/suite.ts (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/fake-acp-agent.ts (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-child/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-child/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-child/session.1.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-child/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-child/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/session.1.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/system-prompt.expected.md (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-pin/tool-schemas.expected.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-skip/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-skip/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-skip/replay.override.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-skip/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/record-suite/rec-skip/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/authored-error/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/authored-error/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/authored-error/replay.override.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/authored-error/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/authored-error/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/blocked-log/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/blocked-log/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/blocked-log/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/blocked-log/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/no-model/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/no-model/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/no-model/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/no-model/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/pin-turn/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/pin-turn/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/pin-turn/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/pin-turn/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/pin-turn/system-prompt.expected.md (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/pin-turn/tool-schemas.expected.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/session.1.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/system-prompt.1.expected.md (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/tool-schemas.1.expected.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/plain-turn/workspace/seed.txt (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/shared-pin/behavior.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/shared-pin/input.json (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/shared-pin/session.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/fixtures/suite/shared-pin/stdout.expected.jsonl (100%) rename packages/{support => test-support}/acp-snapshot/tests/harness.spec.ts (100%) rename packages/{support => test-support}/acp-snapshot/tests/normalize.spec.ts (100%) rename packages/{support => test-support}/acp-snapshot/tests/suite.spec.ts (100%) rename packages/{support => test-support}/acp-snapshot/tsconfig.json (84%) rename packages/{support => test-support}/agent-loop-testkit/README.i18n.yaml (78%) rename packages/{support => test-support}/agent-loop-testkit/README.md (100%) rename packages/{support => test-support}/agent-loop-testkit/README.zh.md (100%) rename packages/{support => test-support}/agent-loop-testkit/package.json (96%) rename packages/{support => test-support}/agent-loop-testkit/src/index.ts (85%) rename packages/{support => test-support}/agent-loop-testkit/src/invariant.ts (100%) rename packages/{support => test-support}/agent-loop-testkit/tests/agent-loop-testkit.spec.ts (100%) rename packages/{support => test-support}/agent-loop-testkit/tsconfig.json (90%) create mode 100644 packages/test-support/client-runtime/README.i18n.yaml rename packages/{client/test-runtime => test-support/client-runtime}/README.md (87%) rename packages/{client/test-runtime => test-support/client-runtime}/README.zh.md (84%) rename packages/{client/test-runtime => test-support/client-runtime}/package.json (93%) rename packages/{client/test-runtime => test-support/client-runtime}/src/fixtures.ts (99%) rename packages/{client/test-runtime => test-support/client-runtime}/src/index.ts (96%) rename packages/{client/test-runtime => test-support/client-runtime}/src/invariant.ts (93%) rename packages/{client/test-runtime => test-support/client-runtime}/src/locale-env.ts (95%) rename packages/{client/test-runtime => test-support/client-runtime}/src/remote.ts (100%) rename packages/{client/test-runtime => test-support/client-runtime}/src/sessions.ts (98%) rename packages/{client/test-runtime => test-support/client-runtime}/src/settings-scope.ts (100%) rename packages/{client/test-runtime => test-support/client-runtime}/src/snapshot.ts (100%) rename packages/{client/test-runtime => test-support/client-runtime}/src/translate.ts (94%) rename packages/{client/test-runtime => test-support/client-runtime}/src/workspaces.ts (100%) rename packages/{client/test-runtime => test-support/client-runtime}/tests/__snapshots__/runtime.client.spec.tsx.snap (100%) rename packages/{client/test-runtime => test-support/client-runtime}/tests/invariant.client.spec.ts (77%) rename packages/{client/test-runtime => test-support/client-runtime}/tests/remote.client.spec.ts (100%) rename packages/{client/test-runtime => test-support/client-runtime}/tests/runtime.client.spec.tsx (99%) rename packages/{client/test-runtime => test-support/client-runtime}/tsconfig.json (66%) rename packages/{client/test-runtime => test-support/client-runtime}/tsdown.config.ts (67%) rename packages/{support => test-support}/llm-mock-server/README.i18n.yaml (78%) rename packages/{support => test-support}/llm-mock-server/README.md (100%) rename packages/{support => test-support}/llm-mock-server/README.zh.md (100%) rename packages/{support => test-support}/llm-mock-server/package.json (94%) rename packages/{support => test-support}/llm-mock-server/src/bin.ts (100%) rename packages/{support => test-support}/llm-mock-server/src/cli.ts (100%) rename packages/{support => test-support}/llm-mock-server/src/index.ts (100%) rename packages/{support => test-support}/llm-mock-server/src/invariant.ts (100%) rename packages/{support => test-support}/llm-mock-server/tests/cli.spec.ts (100%) rename packages/{support => test-support}/llm-mock-server/tests/invariant.spec.ts (84%) rename packages/{support => test-support}/llm-mock-server/tests/server.spec.ts (100%) rename packages/{util/paths => test-support/llm-mock-server}/tsconfig.json (78%) rename packages/{support => test-support}/llm-mock-server/tsdown.config.ts (100%) create mode 100644 packages/test-support/llm-replay/README.i18n.yaml rename packages/{support => test-support}/llm-replay/README.md (93%) rename packages/{support => test-support}/llm-replay/README.zh.md (93%) rename packages/{support => test-support}/llm-replay/package.json (89%) rename packages/{support => test-support}/llm-replay/src/index.ts (98%) rename packages/{support => test-support}/llm-replay/src/invariant.ts (100%) rename packages/{support => test-support}/llm-replay/tests/llm-replay.spec.ts (96%) rename packages/{support => test-support}/llm-replay/tsconfig.json (80%) rename packages/{support => test-support}/loader-smoke/README.i18n.yaml (79%) rename packages/{support => test-support}/loader-smoke/README.md (100%) rename packages/{support => test-support}/loader-smoke/README.zh.md (100%) rename packages/{support => test-support}/loader-smoke/package.json (96%) rename packages/{support => test-support}/loader-smoke/src/agent-turn.ts (100%) rename packages/{support => test-support}/loader-smoke/src/index.ts (100%) rename packages/{support => test-support}/loader-smoke/src/invariant.ts (100%) rename packages/{support => test-support}/loader-smoke/tests/agent-turn.spec.ts (100%) rename packages/{support => test-support}/loader-smoke/tests/example-launch.spec.ts (100%) rename packages/{support => test-support}/loader-smoke/tests/fixtures/fail.ts (100%) rename packages/{support => test-support}/loader-smoke/tests/fixtures/hang.ts (100%) rename packages/{support => test-support}/loader-smoke/tests/fixtures/success.ts (100%) rename packages/{support => test-support}/loader-smoke/tests/loader-smoke.spec.ts (100%) rename packages/{support => test-support}/loader-smoke/tsconfig.json (85%) rename packages/typert/generator/tests/fixtures/remote-model/{type-meta.d.ts => typert-protocol.d.ts} (66%) rename packages/typert/{type-meta => protocol}/README.i18n.yaml (70%) rename packages/typert/{type-meta => protocol}/README.md (72%) rename packages/typert/{type-meta => protocol}/README.zh.md (71%) rename packages/typert/{type-meta => protocol}/package.json (85%) rename packages/typert/{type-meta => protocol}/src/index.ts (76%) rename packages/typert/{type-meta => protocol}/src/invariant.ts (78%) rename packages/typert/{type-meta => protocol}/src/types.ts (76%) rename packages/typert/{type-meta => protocol}/tests/fixtures/source-launch.ts (86%) rename packages/typert/{type-meta/tests/type-meta.spec.ts => protocol/tests/protocol.spec.ts} (83%) rename packages/typert/{type-meta => protocol}/tsconfig.json (84%) delete mode 100644 packages/util/environment/README.i18n.yaml create mode 100644 packages/util/home-paths/README.i18n.yaml rename packages/util/{paths => home-paths}/README.md (99%) rename packages/util/{paths => home-paths}/README.zh.md (99%) rename packages/util/{paths => home-paths}/package.json (92%) rename packages/util/{paths => home-paths}/src/index.ts (99%) rename packages/util/{retention => home-paths}/src/invariant.ts (79%) rename packages/util/{paths/tests/paths.spec.ts => home-paths/tests/home-paths.spec.ts} (98%) rename packages/util/{retention => home-paths}/tsconfig.json (78%) create mode 100644 packages/util/launch-environment/README.i18n.yaml rename packages/util/{environment => launch-environment}/README.md (83%) rename packages/util/{environment => launch-environment}/README.zh.md (82%) rename packages/util/{environment => launch-environment}/package.json (91%) rename packages/util/{environment => launch-environment}/src/index.ts (71%) rename packages/util/{environment => launch-environment}/src/invariant.ts (78%) rename packages/util/{environment/tests/environment.spec.ts => launch-environment/tests/launch-environment.spec.ts} (80%) rename packages/util/{environment => launch-environment}/tsconfig.json (78%) create mode 100644 packages/util/output-retention/README.i18n.yaml rename packages/util/{retention => output-retention}/README.md (98%) rename packages/util/{retention => output-retention}/README.zh.md (98%) rename packages/util/{retention => output-retention}/package.json (91%) rename packages/util/{retention => output-retention}/src/index.ts (99%) rename packages/util/{paths => output-retention}/src/invariant.ts (77%) rename packages/util/{retention/tests/retention.spec.ts => output-retention/tests/output-retention.spec.ts} (99%) rename packages/{support/llm-mock-server => util/output-retention}/tsconfig.json (78%) delete mode 100644 packages/util/paths/README.i18n.yaml delete mode 100644 packages/util/retention/README.i18n.yaml rename packages/web/{web-fetch-local => web-fetch-http}/README.i18n.yaml (70%) rename packages/web/{web-fetch-local => web-fetch-http}/README.md (93%) rename packages/web/{web-fetch-local => web-fetch-http}/README.zh.md (92%) rename packages/web/{web-fetch-local => web-fetch-http}/package.json (93%) rename packages/web/{web-fetch-local => web-fetch-http}/src/index.ts (82%) rename packages/web/{web-fetch-local => web-fetch-http}/src/invariant.ts (84%) rename packages/web/{web-fetch-local => web-fetch-http}/src/policy.ts (98%) rename packages/web/{web-fetch-local => web-fetch-http}/src/provider.ts (97%) rename packages/web/{web-fetch-local/tests/fetch-local.spec.ts => web-fetch-http/tests/fetch-http.spec.ts} (94%) rename packages/web/{web-fetch-local => web-fetch-http}/tsconfig.json (89%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/README.i18n.yaml (68%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/README.md (96%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/README.zh.md (96%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/package.json (92%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/host.ts (98%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/index.ts (96%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/invariant.ts (82%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/meta.ts (98%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/protocol.ts (98%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/realm.ts (99%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/runtime.ts (99%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/session.ts (99%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/types.ts (98%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/src/worker.ts (91%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tests/built-worker.e2e.ts (89%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tests/integration.spec.ts (88%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tests/meta.spec.ts (100%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tests/realm.spec.ts (100%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tests/session.spec.ts (100%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tests/source-worker.compat.spec.ts (85%) rename packages/workflow/{workflow-workerthread/tests/workflow-workerthread.e2e.ts => workflow-worker-thread/tests/workflow-worker-thread.e2e.ts} (87%) rename packages/workflow/{workflow-workerthread/tests/workflow-workerthread.spec.ts => workflow-worker-thread/tests/workflow-worker-thread.spec.ts} (92%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tsconfig.json (92%) rename packages/workflow/{workflow-workerthread => workflow-worker-thread}/tsdown.config.ts (100%) diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml index 3d34ff3934..760c3fd099 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md -2026-06-11-event-sourced-sessions.md: 1303ee843cd5f9e02eae7a1f9bc51594be4bb407 -2026-06-11-event-sourced-sessions.zh.md: 4308251ddc4564fc0547230d10671b6595389a1a +2026-06-11-event-sourced-sessions.md: b6d17d2db1d9b489f3d4224683b61e62be485014 +2026-06-11-event-sourced-sessions.zh.md: a78349ea2385b3c9d32870757bb607f15996196d diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md index 1303ee843c..b6d17d2db1 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md @@ -25,4 +25,4 @@ Ordering contract: the loop claims inbox messages before `agent/pre-step`, opens - Replay, trace, and telemetry are structurally guaranteed, not bolted on. - Persistence stays a plugin concern; the in-memory store ships in dsh-session. - The event vocabulary is merge-extensible (plugins add e.g. compaction events); [session persistence](2026-06-14-session-persistence.md) froze its shape once the log became durable. -- Derivation cost grows with log length — compaction (dsh-compact) is the intended mitigation, not log mutation. +- Derivation cost grows with log length — compaction (dsh-compaction) is the intended mitigation, not log mutation. diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md index 4308251ddc..a78349ea23 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md @@ -25,4 +25,4 @@ MVP 要求严格的基于事件的追踪,以及完全可回放的会话(严 - 回放、追踪与遥测在结构上得到保证,而非事后附加。 - 持久化仍是插件关注点;内存存储随 dsh-session 一起提供。 - 事件词汇可通过合并扩展(插件可添加如压缩(compaction)事件);[会话持久化](2026-06-14-session-persistence.md)在日志具备持久性后固定了其结构。 -- 派生成本随日志长度增长,压缩(dsh-compact)是预期的缓解手段,而不是改写日志。 +- 派生成本随日志长度增长,压缩(dsh-compaction)是预期的缓解手段,而不是改写日志。 diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml index cc21a54254..f07afccac7 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-13-capability-seams.md -2026-06-13-capability-seams.md: b2dc124f7bf0b56598466e6521e0764af06075ab -2026-06-13-capability-seams.zh.md: 79befde57392888dd6a07236379b0155b68d2f01 +2026-06-13-capability-seams.md: efb000631c6dfe91ab648b102a14a27c30d790b4 +2026-06-13-capability-seams.zh.md: bf6571dfa5c3a90f856480bc22c6560ed65b3e77 diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md index b2dc124f7b..efb000631c 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md @@ -8,15 +8,15 @@ English | [中文](2026-06-13-capability-seams.zh.md) The harness has swappable capabilities — bash execution today, sandboxed/remote executors and alternative model providers tomorrow. A capability has three concerns that change at different rates and for different reasons: the *contract* (what the capability is), the *implementation* (how it runs), and the *consumer API* (what the model and other plugins program against). Bundling them in one package couples those rates of change — swapping a local executor for a sandboxed one would churn the tool schemas the model sees, even though the model-facing contract never changed. -This is distinct from "who provides vs. needs a capability at runtime", which Cordis already answers with services + `inject` (a provider registers `ctx.bash`; a consumer declares `inject: ['bash']` and its fiber pends until the service exists). That mechanism is necessary but doesn't dictate package boundaries; this Agent Note does. +This is distinct from "who provides vs. needs a capability at runtime", which Cordis already answers with services + `inject` (a provider registers `ctx.shell`; a consumer declares `inject: ['bash']` and its fiber pends until the service exists). That mechanism is necessary but doesn't dictate package boundaries; this Agent Note does. ## Decision A swappable capability has **three roles**: -1. **Service Definition** — the Cordis `Service` and vocabulary types owning `ctx.` and depending only on the vocabulary the contract needs (e.g. `dsh-bash`: `BashExecutor`, `BashRunResult`, `BashProcess`). A definition may be an abstract class or a concrete registry service; it is never a TypeScript `interface`. +1. **Service Definition** — the Cordis `Service` and vocabulary types owning `ctx.` and depending only on the vocabulary the contract needs (e.g. `dsh-shell`: `ShellExecutor`, `ShellRunResult`, `ShellProcess`). A definition may be an abstract class or a concrete registry service; it is never a TypeScript `interface`. 2. **Service provider** — a plugin that supplies or registers an implementation (e.g. `dsh-bash-local`: subprocesses, process-group kills, spill-file truncation). Sandboxed and remote providers are sibling packages implementing or registering against the same Service Definition. -3. **Consumer** — what the model and plugins program against (e.g. `dsh-tool-bash`: the `bash` schema, with background handles registered into the generic task runtime). Consumers inject the service key and never import provider-specific types. +3. **Consumer** — what the model and plugins program against (e.g. `dsh-tool-bash`: the `bash` schema, with background handles registered into the generic job runtime). Consumers inject the service key and never import provider-specific types. Service providers and Consumers then evolve independently: a sandboxed executor replaces `dsh-bash-local` without touching a tool schema. @@ -24,7 +24,7 @@ Roles normally use separate packages when they evolve independently, but the spl ## Terminology: "seam" names the trio, not the interface -A **seam** is the whole capability — the three roles together: a **Service Definition** (the Cordis `Service` that owns `ctx.` and the vocabulary), one or more **Service providers**, and one or more **Consumers**. `packages/bash` is the canonical example — `dsh-bash` / `dsh-bash-local`+`dsh-bash-sandbox` / `dsh-tool-bash`. A package may own multiple roles, but one role alone is not the seam. The term "seam" is reserved for this complete capability; name a constituent by its role, class, service, contract, or extension point. The [glossary](../../../../docs/glossary.md#capability-seam) is the canonical entry. +A **seam** is the whole capability — the three roles together: a **Service Definition** (the Cordis `Service` that owns `ctx.` and the vocabulary), one or more **Service providers**, and one or more **Consumers**. `packages/shell` is the canonical example — `dsh-shell` / `dsh-bash-local`+`dsh-bash-sandbox` / `dsh-tool-bash`. A package may own multiple roles, but one role alone is not the seam. The term "seam" is reserved for this complete capability; name a constituent by its role, class, service, contract, or extension point. The [glossary](../../../../docs/glossary.md#capability-seam) is the canonical entry. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md index 79befde573..bf6571dfa5 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md @@ -8,13 +8,13 @@ Status: implemented harness 具有可替换的能力:当前是 bash 执行,未来会有沙箱化/远程执行器和替代模型提供方。一项能力涉及三个关注点,它们以不同速率、因不同原因变化:*约定*(这项能力是什么)、*实现*(它如何运行)、*消费方 API*(模型和其他插件面向什么编程)。将三者捆绑在一个包中会耦合这些变化速率——把本地执行器换成沙箱化执行器时,模型看到的工具 schema 也会被搅动,尽管面向模型的约定从未改变。 -这与「谁在运行时提供、谁需要一项能力」是不同的问题,后者 Cordis 已通过服务 + `inject` 解决(提供方注册 `ctx.bash`;消费方声明 `inject: ['bash']`,其 fiber 挂起直到服务存在)。该机制是必要的,但不决定包的边界;本 Agent Note 决定的是包的边界。 +这与「谁在运行时提供、谁需要一项能力」是不同的问题,后者 Cordis 已通过服务 + `inject` 解决(提供方注册 `ctx.shell`;消费方声明 `inject: ['bash']`,其 fiber 挂起直到服务存在)。该机制是必要的,但不决定包的边界;本 Agent Note 决定的是包的边界。 ## 决策 一项可替换的能力包含**三个角色**: -1. **Service Definition**——拥有 `ctx.` 的 Cordis `Service` 和词汇类型,仅依赖约定所需的词汇(例如 `dsh-bash`:`BashExecutor`、`BashRunResult`、`BashProcess`)。Service Definition 可以是抽象类,也可以是具体的注册表服务;绝不是 TypeScript `interface`。 +1. **Service Definition**——拥有 `ctx.` 的 Cordis `Service` 和词汇类型,仅依赖约定所需的词汇(例如 `dsh-shell`:`ShellExecutor`、`ShellRunResult`、`ShellProcess`)。Service Definition 可以是抽象类,也可以是具体的注册表服务;绝不是 TypeScript `interface`。 2. **Service provider**——提供或注册实现的插件(例如 `dsh-bash-local`:子进程、进程组 kill、spill 文件截断)。沙箱化和远程 Service provider 是依据同一 Service Definition 实现或注册的兄弟包。 3. **Consumer**——模型和插件编程所面向的内容(例如 `dsh-tool-bash`:`bash` schema,后台句柄注册到通用任务运行时)。Consumer 注入服务键,从不导入 Service provider 特有的类型。 @@ -24,7 +24,7 @@ Service provider 与 Consumer 由此独立演进:沙箱化执行器替换 `dsh ## 术语:seam 指三者组合,而非接口 -一个 **seam** 是完整的能力——三个角色合在一起:**Service Definition**(拥有 `ctx.` 和词汇的 Cordis `Service`)、一个或多个 **Service provider**,以及一个或多个 **Consumer**。`packages/bash` 是规范范例——`dsh-bash` / `dsh-bash-local`+`dsh-bash-sandbox` / `dsh-tool-bash`。一个包可以承担多个角色,但单个角色本身不是 seam。「seam」一词严格保留给这种完整能力;命名其中一个组成部分时,应使用其角色、类、服务、约定或扩展点。[术语表](../../../../docs/glossary.md#capability-seam)是规范条目。 +一个 **seam** 是完整的能力——三个角色合在一起:**Service Definition**(拥有 `ctx.` 和词汇的 Cordis `Service`)、一个或多个 **Service provider**,以及一个或多个 **Consumer**。`packages/shell` 是规范范例——`dsh-shell` / `dsh-bash-local`+`dsh-bash-sandbox` / `dsh-tool-bash`。一个包可以承担多个角色,但单个角色本身不是 seam。「seam」一词严格保留给这种完整能力;命名其中一个组成部分时,应使用其角色、类、服务、约定或扩展点。[术语表](../../../../docs/glossary.md#capability-seam)是规范条目。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml index 880cda00e7..61f9754431 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-14-session-persistence.md -2026-06-14-session-persistence.md: 6961b8f3f4274587c2920fe2b2602aff5efe4afc -2026-06-14-session-persistence.zh.md: a99b236167d8f3229de083c917684d517b50ab9c +2026-06-14-session-persistence.md: 62228bd2f5b25b13880a563818d08f3a2d52d956 +2026-06-14-session-persistence.zh.md: b10ceebd95d4d05ae3f7ea620183ad8fffd074ee diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md index 6961b8f3f4..62228bd2f5 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md +++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md @@ -12,7 +12,7 @@ The [event-sourced model](2026-06-11-event-sourced-sessions.md) makes the append ## Decision -Persistence is a **capability seam** with an abstract Service Definition ([capability seams](2026-06-13-capability-seams.md), the `dsh-bash` template), not loop or core logic: +Persistence is a **capability seam** with an abstract Service Definition ([capability seams](2026-06-13-capability-seams.md), the `dsh-shell` template), not loop or core logic: 1. **Interface** (`dsh-session-persistence`, `ctx.sessionPersistence`) — an abstract `SessionPersistence` service: `locate`/`create`/`append`/`prepare`/`load`/`inspect`/`readFrom`/`list`/`listSnapshots`. Its persisted unit IS the existing `SessionEvent` (`{ type, seq, time, data }`), reused verbatim — no conversion type. 2. **Implementation** (`dsh-session-persistence-jsonl`) — an append-only logical JSONL log per session: a `SessionHeader` line followed by storage records that losslessly represent the contiguous `SessionEvent` stream. Eligible `assistant/chunk` delta runs use packed rows by default; [checksummed Zstandard frames](2026-07-19-zstandard-jsonl-session-logs.md) are the default physical encoding, with raw lines configurable. diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md index a99b236167..b10ceebd95 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -持久化是一个具有抽象 Service Definition 的**能力 seam**([能力 seam](2026-06-13-capability-seams.md),`dsh-bash` 模板),而非循环或核心逻辑: +持久化是一个具有抽象 Service Definition 的**能力 seam**([能力 seam](2026-06-13-capability-seams.md),`dsh-shell` 模板),而非循环或核心逻辑: 1. **接口**(`dsh-session-persistence`,`ctx.sessionPersistence`):一个抽象的 `SessionPersistence` 服务,提供 `locate`/`create`/`append`/`prepare`/`load`/`inspect`/`readFrom`/`list`/`listSnapshots`。其持久化单元就是现有的 `SessionEvent`(`{ type, seq, time, data }`),原样复用,无转换类型。 2. **实现**(`dsh-session-persistence-jsonl`):每个会话一个仅追加的逻辑 JSONL 日志:先是一行 `SessionHeader`,随后是无损表示连续 `SessionEvent` 流的存储记录。符合条件的 `assistant/chunk` 增量连续段默认使用打包行;[带校验和的 Zstandard 帧](2026-07-19-zstandard-jsonl-session-logs.md)是默认物理编码,也可通过配置使用原始行。 diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml index 3ae4c57d3d..fc070d7c3a 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md -2026-06-17-filesystem-capability-seam.md: 63628d592c17f000ae49f9558597bf8059f04942 -2026-06-17-filesystem-capability-seam.zh.md: d986869bf10f58c8e01873b1d77105accb7bea76 +2026-06-17-filesystem-capability-seam.md: 953ef0eaafe8f654bf55c3e5c4208560243c3d0b +2026-06-17-filesystem-capability-seam.zh.md: 1073226594acbe8c1c596bb4b2dcc959b20c0769 diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md index 63628d592c..953ef0eaaf 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md @@ -6,7 +6,7 @@ English | [中文](2026-06-17-filesystem-capability-seam.zh.md) ## Problem -The harness has a concrete `bash` capability seam (`dsh-bash` / `dsh-bash-local` / `dsh-tool-bash`), but filesystem operations were about to land as model-facing tools without an equivalent seam. If `read`, `write`, and `edit` directly used `node:fs`, the model-facing tool package would own filesystem execution policy, local path resolution, atomic write behavior, text decoding, symlink behavior, and edit semantics all at once. +The harness has a concrete `bash` capability seam (`dsh-shell` / `dsh-bash-local` / `dsh-tool-bash`), but filesystem operations were about to land as model-facing tools without an equivalent seam. If `read`, `write`, and `edit` directly used `node:fs`, the model-facing tool package would own filesystem execution policy, local path resolution, atomic write behavior, text decoding, symlink behavior, and edit semantics all at once. That couples three concerns that change independently: @@ -28,7 +28,7 @@ Filesystem access is a first-class capability seam following [the capability-sea The Consumer package depends only on the Service Definition package, never on `dsh-fs-local`. A deployment that wants a different backend loads a different provider for `ctx.fs` without changing the tool schemas or model-facing prompt guidance. -The read-before-write/edit and observed-state policy is a fourth package, `@deepseek-ai/dsh-fs-policy` (`packages/fs/fs-policy`), contributed through the `fs/*` event gate rather than living on `ctx.fs`; a deployment loading `dsh-tool-fs` also loads `dsh-fs-policy` to get read-before-write/edit. This decision established the three-package boundary; the split of policy off the provider base class is decided by [the split-fs-seam Agent Note](../simplification/2026-06-26-fsspec-style-fs-seam.md), and its realization as an event-gate plugin (not a method service) by [the event-gate Agent Note](2026-06-26-file-context-as-event-gate.md). +The read-before-write/edit and observed-state policy is a fourth package, `@deepseek-ai/dsh-fs-observation-policy` (`packages/fs/fs-observation-policy`), contributed through the `fs/*` event gate rather than living on `ctx.fs`; a deployment loading `dsh-tool-fs` also loads `dsh-fs-observation-policy` to get read-before-write/edit. This decision established the three-package boundary; the split of policy off the provider base class is decided by [the split-fs-seam Agent Note](../simplification/2026-06-26-fsspec-style-fs-seam.md), and its realization as an event-gate plugin (not a method service) by [the event-gate Agent Note](2026-06-26-file-context-as-event-gate.md). The first backend is deliberately local-only: `dsh-fs-local` implements `ctx.fs` against the host filesystem. Future sibling backends can provide sandboxed, remote, virtual, or project-scoped filesystems behind the same interface. @@ -36,7 +36,7 @@ The first consumer is deliberately text-file-only: `dsh-tool-fs` exposes model-f Filesystem permissions and sandboxing are not implied by this split. The local backend resolves relative paths from its configured base directory, but containment policy is a separate decision: either a stricter `ctx.fs` implementation enforces it, or a permission/sandbox plugin wraps `tools/execute` and vetoes calls before they reach the consumer. -Read-before-write/edit and observed state belong to `dsh-fs-policy`, not `ctx.fs`. Through the `fs/*` event gate, the policy records versions per opaque actor and supplies optional mutation expectations; the provider enforces freshness atomically. `dsh-tool-fs` emits the events without depending on the policy. See the [split-seam](../simplification/2026-06-26-fsspec-style-fs-seam.md) and [event-gate](2026-06-26-file-context-as-event-gate.md) Agent Notes. +Read-before-write/edit and observed state belong to `dsh-fs-observation-policy`, not `ctx.fs`. Through the `fs/*` event gate, the policy records versions per opaque actor and supplies optional mutation expectations; the provider enforces freshness atomically. `dsh-tool-fs` emits the events without depending on the policy. See the [split-seam](../simplification/2026-06-26-fsspec-style-fs-seam.md) and [event-gate](2026-06-26-file-context-as-event-gate.md) Agent Notes. ## Package topology @@ -47,7 +47,7 @@ The filesystem seam uses the same dependency direction as the bash trio: consumer interface implementation ``` -`@deepseek-ai/dsh-fs` depends only on `cordis` plus the repo-wide `HarnessError` base from `@deepseek-ai/dsh-llm`. It declares the `ctx.fs` key, the abstract `FileSystem` service, the vocabulary types shared by backends and consumers, the filesystem error vocabulary, and the `fs/*` policy event vocabulary. It carries no observed-state store and no owner-derivation shape; the events pass an opaque `object` actor that the provider never reads, and the `dsh-fs-policy` plugin owns the owner-derivation shape and the observed-state store on top of those events. +`@deepseek-ai/dsh-fs` depends only on `cordis` plus the repo-wide `HarnessError` base from `@deepseek-ai/dsh-llm`. It declares the `ctx.fs` key, the abstract `FileSystem` service, the vocabulary types shared by backends and consumers, the filesystem error vocabulary, and the `fs/*` policy event vocabulary. It carries no observed-state store and no owner-derivation shape; the events pass an opaque `object` actor that the provider never reads, and the `dsh-fs-observation-policy` plugin owns the owner-derivation shape and the observed-state store on top of those events. `@deepseek-ai/dsh-fs-local` depends on `@deepseek-ai/dsh-fs` and `cordis`. It subclasses `FileSystem`, registers itself as `ctx.fs`, owns local-backend configuration such as the base directory, and contains all direct `node:fs` / `node:path` access. It holds no observed-state store — freshness is a version token the backend mints and the policy plugin records. @@ -68,13 +68,13 @@ The interface covers these semantic operations: - Create or replace a UTF-8 text file. - Edit an existing UTF-8 text file by literal replacement. -The provider contract also carries the freshness hooks that policy builds on — but the observed-state store and owner derivation live in the `dsh-fs-policy` plugin, not on `ctx.fs`: +The provider contract also carries the freshness hooks that policy builds on — but the observed-state store and owner derivation live in the `dsh-fs-observation-policy` plugin, not on `ctx.fs`: - The backend mints an opaque `version` token per target (in `stat` and in every read/mutation outcome). - `writeText`/`editText` take an OPTIONAL version expectation: omit it for an unconditional bare-provider mutation, or supply it to guard the mutation inside the backend's atomic critical section. -- The `dsh-fs-policy` plugin decides that expectation on `fs/write-intent`/`fs/edit-intent` and records observed versions on `fs/observed`, keyed by an owner it derives from the opaque event actor (normally `exec.agent.session`). +- The `dsh-fs-observation-policy` plugin decides that expectation on `fs/write-intent`/`fs/edit-intent` and records observed versions on `fs/observed`, keyed by an owner it derives from the opaque event actor (normally `exec.agent.session`). -Authorization is version freshness, not a full/partial view distinction: any read records the target's version, and a later write/edit is authorized as long as the file is still at that version — so a windowed read of lines 100-150 authorizes an edit of line 120. The observed-state store is a `WeakMap>` inside `dsh-fs-policy`; `dsh-fs` holds none of it and treats the actor as opaque. (This decision first modeled a `FileState` cache with `full`/`partial` views on `ctx.fs`; the split-fs-seam and event-gate notes replaced that with the freshness-based policy plugin described here.) +Authorization is version freshness, not a full/partial view distinction: any read records the target's version, and a later write/edit is authorized as long as the file is still at that version — so a windowed read of lines 100-150 authorizes an edit of line 120. The observed-state store is a `WeakMap>` inside `dsh-fs-observation-policy`; `dsh-fs` holds none of it and treats the actor as opaque. (This decision first modeled a `FileState` cache with `full`/`partial` views on `ctx.fs`; the split-fs-seam and event-gate notes replaced that with the freshness-based policy plugin described here.) Path resolution is explicit and allowed to be async. Local resolution may only normalize a path, but sandboxed/remote/project-scoped backends may need I/O to resolve a user-supplied path into a stable target identity. @@ -86,11 +86,11 @@ Resolved targets must expose at least three concepts: `targetKey` remains opaque even when another capability shares the provider's execution world. Such consumers ask the provider for `processPath(target)`, `fileUrl(target)`, or `contains(parent, child)`; the [portable execution-world decision](2026-07-28-portable-execution-world-consumers.md) owns why these facts sit on the filesystem seam. -Read and mutation results must include an opaque file `version`. The local backend derives its token from bigint stat metadata (`dev`, `ino`, `size`, `mtimeNs`, and `ctimeNs`) so same-size rewrites and inode replacement invalidate consumers reliably; a remote backend can use a revision id or hash-like token. The `dsh-fs-policy` plugin records versions for stale checks; consumers may display related metadata but must not interpret the version token. +Read and mutation results must include an opaque file `version`. The local backend derives its token from bigint stat metadata (`dev`, `ino`, `size`, `mtimeNs`, and `ctimeNs`) so same-size rewrites and inode replacement invalidate consumers reliably; a remote backend can use a revision id or hash-like token. The `dsh-fs-observation-policy` plugin records versions for stale checks; consumers may display related metadata but must not interpret the version token. -The provider hands back decoded text: `readText` returns a whole regular text file and `streamText` streams the same text semantics for large files or consumer-owned retention limits. Line windowing, byte ceilings, numbered-line rendering, and total-line accounting live in consumers such as `dsh-tool-fs` and `dsh-lsp-local`. The provider owns regular-file checks, UTF-8 decoding, and binary/NUL rejection; it does not know about line windows, protocol limits, or views. +The provider hands back decoded text: `readText` returns a whole regular text file and `streamText` streams the same text semantics for large files or consumer-owned retention limits. Line windowing, byte ceilings, numbered-line rendering, and total-line accounting live in consumers such as `dsh-tool-fs` and `dsh-lsp-stdio`. The provider owns regular-file checks, UTF-8 decoding, and binary/NUL rejection; it does not know about line windows, protocol limits, or views. -Observed-state recording is not on `ctx.fs`: after a successful read the executor emits `fs/observed`, and the `dsh-fs-policy` plugin records `{ version }` for the deriving owner. There is no `full`/`partial` view — a read at any window records the version, and freshness (not view completeness) authorizes a later write/edit. +Observed-state recording is not on `ctx.fs`: after a successful read the executor emits `fs/observed`, and the `dsh-fs-observation-policy` plugin records `{ version }` for the deriving owner. There is no `full`/`partial` view — a read at any window records the version, and freshness (not view completeness) authorizes a later write/edit. Full-file writes create or replace UTF-8 text files. Backends may create parent directories when that behavior is supported and documented. Existing non-regular targets are rejected. `writeText` takes an optional expectation: `createIfAbsent` creates a missing target and rejects an existing one with `FS_NOT_OBSERVED` (the path the policy uses for an unobserved owner); `replaceIfVersion` replaces only when the target exists at the observed version, else `FS_STALE_VERSION`; omitting the expectation is the unconditional bare-provider create-or-overwrite. The policy plugin chooses which expectation to supply from the owner's observed state. @@ -115,19 +115,19 @@ Each tool follows the same execution shape: 1. Validate and normalize model arguments. 2. Call the appropriate `ctx.fs` operation. 3. Format the result as `ContentBlock[]` for the model. -4. Let thrown backend/tool errors flow through `ToolRegistry.execute()`, which converts them into `isError` tool results. +4. Let thrown backend/tool errors flow through `ToolRuntime.execute()`, which converts them into `isError` tool results. -The package registers prompt guidance through `ctx.systemPrompt.section(...)` and registers schemas through `ctx.tools.register(...)`. Tool schemas still flow into the normal prompt assembly path via `SystemPrompt.assemble()` and `ToolRegistry.schemas()`; no agent-loop changes are required. +The package registers prompt guidance through `ctx.systemPrompt.section(...)` and registers schemas through `ctx.tools.register(...)`. Tool schemas still flow into the normal prompt assembly path via `SystemPrompt.assemble()` and `ToolRuntime.schemas()`; no agent-loop changes are required. The tool package keeps model-facing contracts stable when backends change: a local backend and a remote backend may resolve paths differently internally, but the `read` / `write` / `edit` schemas do not change solely because the backend changes. -The default deployment requires a prior `read` before updating an existing file with `write` or `edit`. `tool-fs` does not implement this by checking whether a tool named `read` ran: it dispatches the `fs/write-intent`/`fs/edit-intent` events (passing the execution context as the opaque actor), and the `dsh-fs-policy` plugin derives the owner, gates on prior observation, and supplies the version expectation. Any windowed read authorizes a later write/edit as long as the file is unchanged. Creating a new file with `write` does not require prior observation. +The default deployment requires a prior `read` before updating an existing file with `write` or `edit`. `tool-fs` does not implement this by checking whether a tool named `read` ran: it dispatches the `fs/write-intent`/`fs/edit-intent` events (passing the execution context as the opaque actor), and the `dsh-fs-observation-policy` plugin derives the owner, gates on prior observation, and supplies the version expectation. Any windowed read authorizes a later write/edit as long as the file is unchanged. Creating a new file with `write` does not require prior observation. The root plugin registers the full suite by composing the per-tool registration helpers. It injects `fs`, `tools`, and `systemPrompt`. ## Testing -Tests follow the package boundary, not only the user-visible tools: the service contract in `dsh-fs`; real filesystem behavior through the `ctx.fs` interface in `dsh-fs-local` (resolution, symlinks, streaming, binary/UTF-8 rejection, unconditional and version-guarded writes, literal-edit semantics, line-ending preservation, structured `FsError` codes); the consumer surface in `dsh-tool-fs` against the real local provider (mock only the model/clock, never the collaborator); and integration through `ctx.tools.execute()` with and without `dsh-fs-policy`, world-verified by reading files back from disk rather than trusting either the canonical value or rendered content. The observed-state/owner-derivation policy is tested in `dsh-fs-policy`, not here. +Tests follow the package boundary, not only the user-visible tools: the service contract in `dsh-fs`; real filesystem behavior through the `ctx.fs` interface in `dsh-fs-local` (resolution, symlinks, streaming, binary/UTF-8 rejection, unconditional and version-guarded writes, literal-edit semantics, line-ending preservation, structured `FsError` codes); the consumer surface in `dsh-tool-fs` against the real local provider (mock only the model/clock, never the collaborator); and integration through `ctx.tools.execute()` with and without `dsh-fs-observation-policy`, world-verified by reading files back from disk rather than trusting either the canonical value or rendered content. The observed-state/owner-derivation policy is tested in `dsh-fs-observation-policy`, not here. The defensive-pattern classes this repo has been bitten by are pinned directly: @@ -152,11 +152,11 @@ The defensive-pattern classes this repo has been bitten by are pinned directly: **Edit semantics are race-prone by nature.** Literal edit is a read-modify-write operation; the guard is the backend's atomic mutation critical section plus the optional version expectation, so concurrent edits settle deterministically — one wins, the other gets `FS_STALE_VERSION`. -**Observed state does not belong on `ctx.fs`.** Recording what an execution context has seen is workflow policy, not raw filesystem I/O. This decision first placed it inside the filesystem seam; the split-fs-seam note then established that a sandboxed/remote backend should not inherit model-facing observation policy, and moved it into the `dsh-fs-policy` plugin. The provider contract keeps only what write/edit safety genuinely needs at the storage layer — a backend-minted version token and an optional version-guarded mutation — while the policy plugin owns owner derivation, observed-state, and read-before-edit gating over the `fs/*` events. +**Observed state does not belong on `ctx.fs`.** Recording what an execution context has seen is workflow policy, not raw filesystem I/O. This decision first placed it inside the filesystem seam; the split-fs-seam note then established that a sandboxed/remote backend should not inherit model-facing observation policy, and moved it into the `dsh-fs-observation-policy` plugin. The provider contract keeps only what write/edit safety genuinely needs at the storage layer — a backend-minted version token and an optional version-guarded mutation — while the policy plugin owns owner derivation, observed-state, and read-before-edit gating over the `fs/*` events. **The `resolve`-then-operate shape costs an extra round-trip per call.** Each tool may resolve a path to an `FsTarget` and then issue the read/write/edit as a separate `ctx.fs` call. For the local backend this is negligible (resolution is in-memory path normalization), but a remote/sandboxed backend may turn each step into its own request, so a single `read` can become two network round-trips. Backends where the round-trip matters can cache or fold resolution internally while preserving the observable contract. -**Observed-state persistence is deferred.** Observed state lives in memory (the `WeakMap` inside `dsh-fs-policy`), so a resumed session conservatively requires files to be read again before write/edit until a future session-event or persistence mechanism makes observation replayable. +**Observed-state persistence is deferred.** Observed state lives in memory (the `WeakMap` inside `dsh-fs-observation-policy`), so a resumed session conservatively requires files to be read again before write/edit until a future session-event or persistence mechanism makes observation replayable. **Error codes become part of the seam.** `FsError` codes make stale-version and observation failures machine-routable through the existing structured error taxonomy. The cost is that `dsh-fs` imports the shared `HarnessError` base from `dsh-llm`; that dependency is intentional and stays limited to the error vocabulary. diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md index d986869bf1..1073226594 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` / `dsh-tool-bash`),但文件系统操作当时即将作为面向模型的工具落地,却没有等价的 seam。如果 `read`、`write` 和 `edit` 直接使用 `node:fs`,面向模型的工具包就会同时承担文件系统执行策略、本地路径解析、原子写入行为、文本解码、符号链接行为和编辑语义。 +harness 已有一个具体的 `bash` 能力 seam(`dsh-shell` / `dsh-bash-local` / `dsh-tool-bash`),但文件系统操作当时即将作为面向模型的工具落地,却没有等价的 seam。如果 `read`、`write` 和 `edit` 直接使用 `node:fs`,面向模型的工具包就会同时承担文件系统执行策略、本地路径解析、原子写入行为、文本解码、符号链接行为和编辑语义。 这把三个独立变化的关注点耦合在了一起: @@ -28,7 +28,7 @@ harness 已有一个具体的 `bash` 能力 seam(`dsh-bash` / `dsh-bash-local` Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需要不同后端的部署只需为 `ctx.fs` 加载不同的提供方,无需改动工具 schema 或面向模型的提示词引导。 -读后写/编辑与观测状态策略是第四个包 `@deepseek-ai/dsh-fs-policy`(`packages/fs/fs-policy`),通过 `fs/*` 事件门控贡献,而非挂在 `ctx.fs` 上;加载 `dsh-tool-fs` 的部署同时加载 `dsh-fs-policy` 以获得读后写/编辑能力。本决策确立了由三个包构成的边界;策略从提供方基类拆出的决策由 [拆分文件系统 seam Agent Note](../simplification/2026-06-26-fsspec-style-fs-seam.md) 做出,其以事件门控插件(而非方法服务)实现的方式由 [事件门控 Agent Note](2026-06-26-file-context-as-event-gate.md) 做出。 +读后写/编辑与观测状态策略是第四个包 `@deepseek-ai/dsh-fs-observation-policy`(`packages/fs/fs-observation-policy`),通过 `fs/*` 事件门控贡献,而非挂在 `ctx.fs` 上;加载 `dsh-tool-fs` 的部署同时加载 `dsh-fs-observation-policy` 以获得读后写/编辑能力。本决策确立了由三个包构成的边界;策略从提供方基类拆出的决策由 [拆分文件系统 seam Agent Note](../simplification/2026-06-26-fsspec-style-fs-seam.md) 做出,其以事件门控插件(而非方法服务)实现的方式由 [事件门控 Agent Note](2026-06-26-file-context-as-event-gate.md) 做出。 第一个后端有意仅限本地:`dsh-fs-local` 基于宿主文件系统实现 `ctx.fs`。未来的兄弟后端可在同一接口之后提供沙箱、远程、虚拟或项目作用域的文件系统。 @@ -36,7 +36,7 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 文件系统权限和沙箱并非此拆分所隐含。本地后端从其配置的基目录解析相对路径,但路径包含约束策略是独立的决策:要么由更严格的 `ctx.fs` 实现强制执行,要么由权限/沙箱插件包装 `tools/execute` 并在调用到达消费方之前否决。 -读后写/编辑与观测状态属于 `dsh-fs-policy`,而非 `ctx.fs`。通过 `fs/*` 事件门控,策略按不透明 actor 记录版本,并提供可选的变更期望;提供方原子性地强制新鲜度。`dsh-tool-fs` 发出事件但不依赖策略。见[拆分文件系统 seam](../simplification/2026-06-26-fsspec-style-fs-seam.md)和[事件门控插件](2026-06-26-file-context-as-event-gate.md) Agent Note。 +读后写/编辑与观测状态属于 `dsh-fs-observation-policy`,而非 `ctx.fs`。通过 `fs/*` 事件门控,策略按不透明 actor 记录版本,并提供可选的变更期望;提供方原子性地强制新鲜度。`dsh-tool-fs` 发出事件但不依赖策略。见[拆分文件系统 seam](../simplification/2026-06-26-fsspec-style-fs-seam.md)和[事件门控插件](2026-06-26-file-context-as-event-gate.md) Agent Note。 ## 包拓扑 @@ -47,7 +47,7 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 consumer interface implementation ``` -`@deepseek-ai/dsh-fs` 仅依赖 `cordis` 加上来自 `@deepseek-ai/dsh-llm` 的仓库级 `HarnessError` 基类。它声明 `ctx.fs` 键、抽象 `FileSystem` 服务、后端和消费方共享的词汇类型、文件系统错误词汇,以及 `fs/*` 策略事件词汇。它不持有观测状态存储,也不持有 owner 推导形态;事件传递一个不透明的 `object` actor,提供方从不读取它,`dsh-fs-policy` 插件在这些事件之上拥有 owner 推导形态和观测状态存储。 +`@deepseek-ai/dsh-fs` 仅依赖 `cordis` 加上来自 `@deepseek-ai/dsh-llm` 的仓库级 `HarnessError` 基类。它声明 `ctx.fs` 键、抽象 `FileSystem` 服务、后端和消费方共享的词汇类型、文件系统错误词汇,以及 `fs/*` 策略事件词汇。它不持有观测状态存储,也不持有 owner 推导形态;事件传递一个不透明的 `object` actor,提供方从不读取它,`dsh-fs-observation-policy` 插件在这些事件之上拥有 owner 推导形态和观测状态存储。 `@deepseek-ai/dsh-fs-local` 依赖 `@deepseek-ai/dsh-fs` 和 `cordis`。它继承 `FileSystem`,将自身注册为 `ctx.fs`,拥有本地后端配置(如基目录),并包含所有直接的 `node:fs` / `node:path` 访问。它不持有观测状态存储——新鲜度是后端铸造、策略插件记录的版本令牌。 @@ -68,13 +68,13 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 - 创建或替换一个 UTF-8 文本文件。 - 通过字面替换编辑一个已有的 UTF-8 文本文件。 -提供方约定还携带策略所依赖的新鲜度钩子——但观测状态存储和 owner 推导位于 `dsh-fs-policy` 插件中,而非 `ctx.fs` 上: +提供方约定还携带策略所依赖的新鲜度钩子——但观测状态存储和 owner 推导位于 `dsh-fs-observation-policy` 插件中,而非 `ctx.fs` 上: - 后端为每个目标铸造一个不透明的 `version` 令牌(在 `stat` 以及每次读取/变更结果中)。 - `writeText`/`editText` 接受一个可选的版本期望:省略它表示无条件的裸提供方变更;提供它则在后端的原子临界区内守护变更。 -- `dsh-fs-policy` 插件在 `fs/write-intent`/`fs/edit-intent` 上决定该期望,并在 `fs/observed` 上记录观测版本,以它从不透明事件 actor 推导出的 owner 为键(通常是 `exec.agent.session`)。 +- `dsh-fs-observation-policy` 插件在 `fs/write-intent`/`fs/edit-intent` 上决定该期望,并在 `fs/observed` 上记录观测版本,以它从不透明事件 actor 推导出的 owner 为键(通常是 `exec.agent.session`)。 -授权基于版本新鲜度,而非完整/部分视图的区分:任何读取都会记录目标的版本,后续的写入/编辑只要文件仍处于该版本就被授权——因此对第 100-150 行的窗口化读取可以授权对第 120 行的编辑。观测状态存储是 `dsh-fs-policy` 内部的 `WeakMap>`;`dsh-fs` 不持有任何此类数据,并将 actor 视为不透明。(本决策最初建模了一个带 `full`/`partial` 视图的 `FileState` 缓存放在 `ctx.fs` 上;拆分文件系统 seam 与事件门控两份笔记将其替换为此处描述的基于新鲜度的策略插件。) +授权基于版本新鲜度,而非完整/部分视图的区分:任何读取都会记录目标的版本,后续的写入/编辑只要文件仍处于该版本就被授权——因此对第 100-150 行的窗口化读取可以授权对第 120 行的编辑。观测状态存储是 `dsh-fs-observation-policy` 内部的 `WeakMap>`;`dsh-fs` 不持有任何此类数据,并将 actor 视为不透明。(本决策最初建模了一个带 `full`/`partial` 视图的 `FileState` 缓存放在 `ctx.fs` 上;拆分文件系统 seam 与事件门控两份笔记将其替换为此处描述的基于新鲜度的策略插件。) 路径解析是显式的,允许异步。本地解析可能只做路径规范化,但沙箱/远程/项目作用域的后端可能需要 I/O 才能将用户提供的路径解析为稳定的目标标识。 @@ -86,11 +86,11 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 即使另一项能力共享提供方的执行环境,`targetKey` 仍保持不透明。这类消费方通过提供方的 `processPath(target)`、`fileUrl(target)` 或 `contains(parent, child)` 获取所需事实;[可移植执行环境决策](2026-07-28-portable-execution-world-consumers.md)说明这些事实为何属于文件系统 seam。 -读取和变更结果必须包含不透明的文件 `version`。本地后端从 bigint stat 元数据(`dev`、`ino`、`size`、`mtimeNs` 和 `ctimeNs`)派生令牌,因此同大小重写和 inode 替换都会可靠地使消费方失效;远程后端可以使用 revision id 或类似 hash 的令牌。`dsh-fs-policy` 插件记录版本用于陈旧检查;消费方可以展示相关元数据但禁止解释版本令牌。 +读取和变更结果必须包含不透明的文件 `version`。本地后端从 bigint stat 元数据(`dev`、`ino`、`size`、`mtimeNs` 和 `ctimeNs`)派生令牌,因此同大小重写和 inode 替换都会可靠地使消费方失效;远程后端可以使用 revision id 或类似 hash 的令牌。`dsh-fs-observation-policy` 插件记录版本用于陈旧检查;消费方可以展示相关元数据但禁止解释版本令牌。 -提供方返回已解码的文本:`readText` 返回整个普通文本文件,`streamText` 为大文件或消费方自有的保留上限流式传输相同的文本语义。行窗口化、字节上限、带行号渲染和总行数统计归 `dsh-tool-fs`、`dsh-lsp-local` 等消费方所有。提供方负责普通文件检查、UTF-8 解码和二进制/NUL 拒绝;它不知道行窗口、协议上限或视图。 +提供方返回已解码的文本:`readText` 返回整个普通文本文件,`streamText` 为大文件或消费方自有的保留上限流式传输相同的文本语义。行窗口化、字节上限、带行号渲染和总行数统计归 `dsh-tool-fs`、`dsh-lsp-stdio` 等消费方所有。提供方负责普通文件检查、UTF-8 解码和二进制/NUL 拒绝;它不知道行窗口、协议上限或视图。 -观测状态记录不在 `ctx.fs` 上:成功读取后,执行器发出 `fs/observed`,`dsh-fs-policy` 插件为推导出的 owner 记录 `{ version }`。没有 `full`/`partial` 视图——任何窗口的读取都记录版本,新鲜度(而非视图完整性)授权后续的写入/编辑。 +观测状态记录不在 `ctx.fs` 上:成功读取后,执行器发出 `fs/observed`,`dsh-fs-observation-policy` 插件为推导出的 owner 记录 `{ version }`。没有 `full`/`partial` 视图——任何窗口的读取都记录版本,新鲜度(而非视图完整性)授权后续的写入/编辑。 全文件写入创建或替换 UTF-8 文本文件。后端在支持且有文档说明时可以创建父目录。已有的非常规目标被拒绝。`writeText` 接受一个可选期望:`createIfAbsent` 创建缺失的目标并拒绝已存在的(报 `FS_NOT_OBSERVED`,这是策略为未观测 owner 使用的路径);`replaceIfVersion` 仅在目标处于观测版本时替换,否则报 `FS_STALE_VERSION`;省略期望则为无条件的裸提供方创建或覆盖。策略插件根据 owner 的观测状态选择提供哪个期望。 @@ -115,19 +115,19 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 1. 校验并规范化模型参数。 2. 调用相应的 `ctx.fs` 操作。 3. 将结果格式化为面向模型的 `ContentBlock[]`。 -4. 让抛出的后端/工具错误流经 `ToolRegistry.execute()`,由其转换为 `isError` 工具结果。 +4. 让抛出的后端/工具错误流经 `ToolRuntime.execute()`,由其转换为 `isError` 工具结果。 -该包通过 `ctx.systemPrompt.section(...)` 注册提示词引导,通过 `ctx.tools.register(...)` 注册 schema。工具 schema 仍通过 `SystemPrompt.assemble()` 和 `ToolRegistry.schemas()` 流入正常的提示词组装路径;无需改动 agent loop(智能体循环)。 +该包通过 `ctx.systemPrompt.section(...)` 注册提示词引导,通过 `ctx.tools.register(...)` 注册 schema。工具 schema 仍通过 `SystemPrompt.assemble()` 和 `ToolRuntime.schemas()` 流入正常的提示词组装路径;无需改动 agent loop(智能体循环)。 工具包在后端变化时保持面向模型的约定稳定:本地后端和远程后端内部可能以不同方式解析路径,但 `read` / `write` / `edit` schema 不会仅因后端变化而改变。 -默认部署要求在用 `write` 或 `edit` 更新已有文件之前先 `read`。`tool-fs` 不通过检查是否运行过名为 `read` 的工具来实现这一点:它分发 `fs/write-intent`/`fs/edit-intent` 事件(将执行上下文作为不透明 actor 传递),`dsh-fs-policy` 插件推导 owner、对先前观测进行门控并提供版本期望。任何窗口化读取都能授权后续的写入/编辑,只要文件未变。用 `write` 创建新文件不要求先前观测。 +默认部署要求在用 `write` 或 `edit` 更新已有文件之前先 `read`。`tool-fs` 不通过检查是否运行过名为 `read` 的工具来实现这一点:它分发 `fs/write-intent`/`fs/edit-intent` 事件(将执行上下文作为不透明 actor 传递),`dsh-fs-observation-policy` 插件推导 owner、对先前观测进行门控并提供版本期望。任何窗口化读取都能授权后续的写入/编辑,只要文件未变。用 `write` 创建新文件不要求先前观测。 根插件通过组合各工具的注册辅助函数来注册完整套件。它注入 `fs`、`tools` 和 `systemPrompt`。 ## 测试 -测试遵循包边界,而不仅是用户可见的工具:`dsh-fs` 中的服务约定;`dsh-fs-local` 中通过 `ctx.fs` 接口测试的真实文件系统行为(解析、符号链接、流式传输、二进制/UTF-8 拒绝、无条件和版本守护的写入、字面编辑语义、行尾保留、结构化 `FsError` 错误码);`dsh-tool-fs` 中基于真实本地提供方的消费方接口(只 mock 模型/时钟,从不 mock 协作者);以及通过 `ctx.tools.execute()` 在有和没有 `dsh-fs-policy` 的情况下进行集成测试,通过从磁盘回读文件来验证世界状态,既不信任规范值,也不信任渲染内容。观测状态/owner 推导策略在 `dsh-fs-policy` 中测试,不在此处。 +测试遵循包边界,而不仅是用户可见的工具:`dsh-fs` 中的服务约定;`dsh-fs-local` 中通过 `ctx.fs` 接口测试的真实文件系统行为(解析、符号链接、流式传输、二进制/UTF-8 拒绝、无条件和版本守护的写入、字面编辑语义、行尾保留、结构化 `FsError` 错误码);`dsh-tool-fs` 中基于真实本地提供方的消费方接口(只 mock 模型/时钟,从不 mock 协作者);以及通过 `ctx.tools.execute()` 在有和没有 `dsh-fs-observation-policy` 的情况下进行集成测试,通过从磁盘回读文件来验证世界状态,既不信任规范值,也不信任渲染内容。观测状态/owner 推导策略在 `dsh-fs-observation-policy` 中测试,不在此处。 本仓库曾踩过的防御性模式类别被直接固定: @@ -152,11 +152,11 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 **编辑语义天然易受竞争影响。** 字面编辑是读-改-写操作;守护手段是后端的原子变更临界区加上可选的版本期望,因此并发编辑确定性地收敛——一个赢,另一个得到 `FS_STALE_VERSION`。 -**观测状态不属于 `ctx.fs`。** 记录执行上下文看到了什么是工作流策略,而非原始文件系统 I/O。本决策最初将其放在文件系统 seam 内部;拆分文件系统 seam 笔记随后确立了沙箱/远程后端不应继承面向模型的观测策略,并将其移入 `dsh-fs-policy` 插件。提供方约定只保留写入/编辑安全在存储层真正需要的东西——后端铸造的版本令牌和可选的版本守护变更——而策略插件拥有 owner 推导、观测状态和基于 `fs/*` 事件的读后编辑门控。 +**观测状态不属于 `ctx.fs`。** 记录执行上下文看到了什么是工作流策略,而非原始文件系统 I/O。本决策最初将其放在文件系统 seam 内部;拆分文件系统 seam 笔记随后确立了沙箱/远程后端不应继承面向模型的观测策略,并将其移入 `dsh-fs-observation-policy` 插件。提供方约定只保留写入/编辑安全在存储层真正需要的东西——后端铸造的版本令牌和可选的版本守护变更——而策略插件拥有 owner 推导、观测状态和基于 `fs/*` 事件的读后编辑门控。 **`resolve` 然后操作的形态每次调用多一次往返。** 每个工具可能先将路径解析为 `FsTarget`,再以单独的 `ctx.fs` 调用发起读取/写入/编辑。对本地后端来说这可以忽略(解析是内存中的路径规范化),但远程/沙箱后端可能将每步变成独立请求,使单次 `read` 变为两次网络往返。往返开销重要的后端可以在内部缓存或折叠解析,同时保持可观测约定不变。 -**观测状态持久化被推迟。** 观测状态存在于内存中(`dsh-fs-policy` 内部的 `WeakMap`),因此恢复的会话保守地要求文件在写入/编辑前重新读取,直到未来的会话事件或持久化机制使观测可回放。 +**观测状态持久化被推迟。** 观测状态存在于内存中(`dsh-fs-observation-policy` 内部的 `WeakMap`),因此恢复的会话保守地要求文件在写入/编辑前重新读取,直到未来的会话事件或持久化机制使观测可回放。 **错误码成为 seam 的一部分。** `FsError` 错误码使陈旧版本和观测失败可通过既有的结构化错误分类体系进行机器路由。代价是 `dsh-fs` 从 `dsh-llm` 导入共享的 `HarnessError` 基类;该依赖是有意为之且限于错误词汇。 diff --git a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.i18n.yaml index ac99292150..d3c3a774bd 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md -2026-06-18-agent-lifecycle-and-ownership-contracts.md: c3522d18dad2703664f12364e4d70cc057a3a945 -2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md: 16781c8a5d1a1c67d1fe9296b0f34f2a00b11caf +2026-06-18-agent-lifecycle-and-ownership-contracts.md: f0f9f90b15dee9155cfb1e8c772503642ff34be3 +2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md: a5f6ca10dbe7c71281cccdaf83fff95139ad2d79 diff --git a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md index c3522d18da..f0f9f90b15 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md +++ b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.md @@ -6,7 +6,7 @@ English | [中文](2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md) ## Problem -Several ACP and tool-bash limitations were symptoms of the same missing ownership contract: plugins could create or resume agents through `ctx.agents`, but they could not own and dispose one agent independently, and long-running bash tasks carried no stable owner in the executor itself. ACP aborted and awaited agents on disconnect but could not unregister just that session's agent; `session/cancel` could not cancel queued-but-not-yet-started work; and `tool-bash` kept task ownership in a plugin-local `Map`, so an HMR reload could make an old task look unowned. +Several ACP and tool-bash limitations were symptoms of the same missing ownership contract: plugins could create or resume agents through `ctx.agents`, but they could not own and dispose one agent independently, and long-running bash tasks carried no stable owner in the executor itself. ACP aborted and awaited agents on disconnect but could not unregister just that session's agent; `session/cancel` could not cancel queued-but-not-yet-started work; and `tool-bash` kept job ownership in a plugin-local `Map`, so an HMR reload could make an old task look unowned. ## Decision @@ -24,7 +24,7 @@ A new `cancel()` verb on the `Agent` interface — the single public stop primit ### 3. Bash owner token in the Service Definition -Background-task ownership moved from a `tool-bash` plugin-local `Map` into the executor. `BashExecRequest` gains an optional `owner?: string`; the resolved `BashExecSpec` carries it as required-but-nullable `owner: string | undefined` (a forgotten owner is a visible `undefined`, never a silently-absent property). The executor stores the token on its task and exposes it via a new `BashExecutor.ownerOf(id): string | undefined` method (NOT on the public `BashTask` — one read path, no redundant API). `tool-bash` deletes its `Map` entirely: it stamps `exec.agent?.id` (the shared registry/session id) as the owner at `start`, and `bash_output`/`bash_kill` compare `ctx.bash.ownerOf(id)` to the caller's token with `!== undefined` semantics (an empty-string token is still a real owner). The completion notice finds the live agent by scanning `ctx.get('agents')?.list()` for `agent.id === ownerToken` (read via `ctx.get` — `onTaskDone` runs on the bash fiber, a foreign fiber, where the `ctx.agents` proxy would throw). Because ownership now lives on the task in the executor (disposed with the `dsh-bash` fiber), it SURVIVES a `tool-bash` HMR reload — closing the old `XXX(tool-bash-owner-hmr)` gap. (The `onTaskDone` listener is still effect-scoped to `tool-bash`'s `apply`, so a completion landing during the reload gap still drops its one notice — the pre-existing reload-gap drop — but the ownership fence itself is HMR-proof.) +Background-job ownership moved from a `tool-bash` plugin-local `Map` into the executor. `ShellExecRequest` gains an optional `owner?: string`; the resolved `ShellExecSpec` carries it as required-but-nullable `owner: string | undefined` (a forgotten owner is a visible `undefined`, never a silently-absent property). The executor stores the token on its task and exposes it via a new `ShellExecutor.ownerOf(id): string | undefined` method (NOT on the public `BashTask` — one read path, no redundant API). `tool-bash` deletes its `Map` entirely: it stamps `exec.agent?.id` (the shared registry/session id) as the owner at `start`, and `bash_output`/`bash_kill` compare `ctx.shell.ownerOf(id)` to the caller's token with `!== undefined` semantics (an empty-string token is still a real owner). The completion notice finds the live agent by scanning `ctx.get('agents')?.list()` for `agent.id === ownerToken` (read via `ctx.get` — `onJobDone` runs on the bash fiber, a foreign fiber, where the `ctx.agents` proxy would throw). Because ownership now lives on the task in the executor (disposed with the `dsh-shell` fiber), it SURVIVES a `tool-bash` HMR reload — closing the old `XXX(tool-bash-owner-hmr)` gap. (The `onJobDone` listener is still effect-scoped to `tool-bash`'s `apply`, so a completion landing during the reload gap still drops its one notice — the pre-existing reload-gap drop — but the ownership fence itself is HMR-proof.) ## Verification @@ -32,7 +32,7 @@ These invariants hold and are pinned by tests: - ACP disconnect or plugin teardown leaves no registered agent and no session-store entry for any bridge-owned session, including a create racing connection closure. - `session/cancel` before a queued prompt starts prevents that prompt from running; a later accepted prompt remains an independent queued turn. -- A `tool-bash` HMR reload does NOT make an existing background task readable or killable by a different session (ownership survives on the executor). +- A `tool-bash` HMR reload does NOT make an existing background job readable or killable by a different session (ownership survives on the executor). - Existing non-ACP demos still work without managing handles explicitly; config-created agents remain owned by the `AgentLoop` plugin fiber. ## Session owner tokens are unique among live agents @@ -41,7 +41,7 @@ The bash owner-token comparison relies on the shared `Agent.id`/`SessionId` bein ## Alternatives considered -- **A public `BashTask.owner` field** instead of the `BashExecutor.ownerOf(id)` Service Definition method — rejected: one read path, no redundant API. +- **A public `BashTask.owner` field** instead of the `ShellExecutor.ownerOf(id)` Service Definition method — rejected: one read path, no redundant API. - **Sibling cordis effects for the agent's session lifecycle** — rejected: a fiber unload disposes sibling effects concurrently (`Promise.all`), racing removal of the store-owned append publication hooks against the loop's closing `session/flush`; the single composite effect's ordered LIFO chain is what captures the closing `turn/end` on both disposal paths. - **A separate step-only `abort()` beside `cancel()`** — shipped originally, then removed as unused; `cancel()` is the single public stop primitive ([the public-stop-API Agent Note](../simplification/2026-06-20-public-agent-stop-api.md)). diff --git a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md index 16781c8a5d..a5f6ca10db 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-contracts.zh.md @@ -24,7 +24,7 @@ ACP(Agent Client Protocol)与 tool-bash 的若干限制是同一个所有权 ### 3. Service Definition 中的 Bash 所有者令牌 -后台任务所有权从 `tool-bash` 插件本地的 `Map` 移入执行器。`BashExecRequest` 新增可选的 `owner?: string`;解析后的 `BashExecSpec` 将其作为必需但可空的 `owner: string | undefined` 携带(被遗忘的 owner 是可见的 `undefined`,而非静默缺失的属性)。执行器把 token 存在任务上,并通过新的 `BashExecutor.ownerOf(id): string | undefined` 方法暴露它(不放在公开的 `BashTask` 上——只有一条读取路径,没有冗余 API)。`tool-bash` 完全删除其 `Map`:它在 `start` 时将 `exec.agent?.id`(共享的注册表/会话 id)盖章为 owner,`bash_output`/`bash_kill` 则以 `!== undefined` 语义把 `ctx.bash.ownerOf(id)` 与调用方 token 比较(空字符串 token 仍是真实 owner)。完成通知通过扫描 `ctx.get('agents')?.list()` 查找 `agent.id === ownerToken` 的存活 agent(经 `ctx.get` 读取——`onTaskDone` 运行在 bash fiber 这一外部 fiber 上,直接使用 `ctx.agents` proxy 会抛异常)。由于所有权现在保存在执行器的任务上(随 `dsh-bash` fiber dispose),它能跨越 `tool-bash` HMR 重载,关闭旧的 `XXX(tool-bash-owner-hmr)` 缺口。(`onTaskDone` 监听器仍受 `tool-bash` 的 `apply` effect 约束,因此落在重载间隙的完成仍会丢失一条通知——既有的重载间隙丢失——但所有权隔离本身已经不受 HMR 影响。) +后台任务所有权从 `tool-bash` 插件本地的 `Map` 移入执行器。`ShellExecRequest` 新增可选的 `owner?: string`;解析后的 `ShellExecSpec` 将其作为必需但可空的 `owner: string | undefined` 携带(被遗忘的 owner 是可见的 `undefined`,而非静默缺失的属性)。执行器把 token 存在任务上,并通过新的 `ShellExecutor.ownerOf(id): string | undefined` 方法暴露它(不放在公开的 `BashTask` 上——只有一条读取路径,没有冗余 API)。`tool-bash` 完全删除其 `Map`:它在 `start` 时将 `exec.agent?.id`(共享的注册表/会话 id)盖章为 owner,`bash_output`/`bash_kill` 则以 `!== undefined` 语义把 `ctx.shell.ownerOf(id)` 与调用方 token 比较(空字符串 token 仍是真实 owner)。完成通知通过扫描 `ctx.get('agents')?.list()` 查找 `agent.id === ownerToken` 的存活 agent(经 `ctx.get` 读取——`onJobDone` 运行在 bash fiber 这一外部 fiber 上,直接使用 `ctx.agents` proxy 会抛异常)。由于所有权现在保存在执行器的任务上(随 `dsh-shell` fiber dispose),它能跨越 `tool-bash` HMR 重载,关闭旧的 `XXX(tool-bash-owner-hmr)` 缺口。(`onJobDone` 监听器仍受 `tool-bash` 的 `apply` effect 约束,因此落在重载间隙的完成仍会丢失一条通知——既有的重载间隙丢失——但所有权隔离本身已经不受 HMR 影响。) ## 验证 @@ -41,7 +41,7 @@ bash 所有者 token 比较依赖共享的 `Agent.id`/`SessionId` 在存活 agen ## 曾考虑的替代方案 -- **公开的 `BashTask.owner` 字段**而非 `BashExecutor.ownerOf(id)` Service Definition 方法:否决。一条读取路径即可,无需冗余 API。 +- **公开的 `BashTask.owner` 字段**而非 `ShellExecutor.ownerOf(id)` Service Definition 方法:否决。一条读取路径即可,无需冗余 API。 - **为 agent 的会话生命周期使用兄弟 Cordis effect**:否决。fiber 卸载时并发释放兄弟 effect(`Promise.all`),store 拥有的 append 发布钩子的移除与循环的关闭 `session/flush` 产生竞争;单一复合 effect 的有序 LIFO 链才能在两条释放路径上都捕获关闭的 `turn/end`。 - **在 `cancel()` 之外另设一个仅中止步骤的 `abort()`**:最初发布过,后因无人使用而移除;`cancel()` 是唯一的公开停止原语(见[公开停止接口 Agent Note](../simplification/2026-06-20-public-agent-stop-api.md))。 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml index 8282967ba2..936e601b48 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-18-session-surface.md -2026-06-18-session-surface.md: fd48a4ee4e177b84a235b044b054e4ac266df8ba -2026-06-18-session-surface.zh.md: 496624016cd68e2e8b0fe65ed0246c5d469c4585 +2026-06-18-session-surface.md: 3682ae7b8b58b9e5d40695732c3a1531d0651d5e +2026-06-18-session-surface.zh.md: 8cba9645dc6d0c8a4d1ee096668fc0bc38aaa725 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md index fd48a4ee4e..3682ae7b8b 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md @@ -68,6 +68,6 @@ Every surface-eligible event must carry `surfaceOp` or it would disappear from d - **`packages/session/session-persistence-jsonl`**: No changes required. - **`packages/session/session-persistence`**: Abstract interface unchanged. -The surface is the foundation history manipulation ships on — dsh-compact's compaction rides it. A compaction or tool-result-prune plugin appends one of the existing message-producing event types (a `user/message` carrying the summary, say) with `surfaceOp: { op: 'replace', start, end }` and `sourceEventSeqs` covering the shadowed entries — the new event takes the range's place on the surface while the plugin's own trace events (e.g. `compaction/start`, `compaction/end`) stay off it. Replay preserves the decision deterministically. +The surface is the foundation history manipulation ships on — dsh-compaction's compaction rides it. A compaction or tool-result-pruner plugin appends one of the existing message-producing event types (a `user/message` carrying the summary, say) with `surfaceOp: { op: 'replace', start, end }` and `sourceEventSeqs` covering the shadowed entries — the new event takes the range's place on the surface while the plugin's own trace events (e.g. `compaction/start`, `compaction/end`) stay off it. Replay preserves the decision deterministically. A `tool/result` replacement may rewrite exactly one current `tool/result` and must preserve every data field except `content`. Session acceptance enforces this rule together with positional range and cited source-event validation, independent of optional diagnostic plugins. diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md index 496624016c..8cba9645dc 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md @@ -68,6 +68,6 @@ export type SurfaceOp = - **`packages/session/session-persistence-jsonl`**:无需改动。 - **`packages/session/session-persistence`**:抽象接口不变。 -surface 是历史操纵赖以落地的基础——dsh-compact 的压缩就搭载于其上。压缩或 tool-result-prune 插件追加一个既有的消息产出事件类型(例如一条携带摘要的 `user/message`),附带 `surfaceOp: { op: 'replace', start, end }` 和覆盖被遮蔽条目的 `sourceEventSeqs`——新事件在 surface 上取代该范围的位置,而插件自身的 trace 事件(如 `compaction/start`、`compaction/end`)不进入 surface。回放以确定性方式保留该决策。 +surface 是历史操纵赖以落地的基础——dsh-compaction 的压缩就搭载于其上。压缩或 tool-result-pruner 插件追加一个既有的消息产出事件类型(例如一条携带摘要的 `user/message`),附带 `surfaceOp: { op: 'replace', start, end }` 和覆盖被遮蔽条目的 `sourceEventSeqs`——新事件在 surface 上取代该范围的位置,而插件自身的 trace 事件(如 `compaction/start`、`compaction/end`)不进入 surface。回放以确定性方式保留该决策。 一次 `tool/result` 替换只能改写当前的一个 `tool/result`,并且必须保留除 `content` 以外的每个数据字段。Session 接纳会与位置范围和引用的源事件校验一起强制这条规则,不依赖可选的诊断插件。 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml index e6f65b6879..877a53e0ac 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-20-branded-ids.md -2026-06-20-branded-ids.md: ded48409bcb3deb19e35029fa795b5ea28c9f6d7 -2026-06-20-branded-ids.zh.md: 824d802b4aaec9f4a6ae530659387126a30fbc2d +2026-06-20-branded-ids.md: 6bf68c9b6b6dea8317992bcb6f694a8ee141f048 +2026-06-20-branded-ids.zh.md: 11a78df609888fdf917246940c12463373ff2de3 diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md index ded48409bc..6bf68c9b6b 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.md @@ -8,9 +8,9 @@ English | [中文](2026-06-20-branded-ids.zh.md) The harness brands `CallId` (`packages/llm/llm/src/brand.ts`) and the shared agent/session `SessionId` (`packages/core/session/src/types.ts`) using the `Branded = string & { readonly [BRAND]: B }` machinery (owned by the type-only `@deepseek-ai/dsh-brand` package at `packages/util/brand/` — see its [README](../../../../packages/util/brand/README.md)) and a zero-cost cast factory per type. `dsh-brand` also states the governing policy: *"Branding is for ids that cross package boundaries and could plausibly be confused; not every string needs a brand."* That policy is right; the problem is that it is only half-applied. Two gaps let a structurally-identical-but-semantically-wrong string slip through the type checker today. -**Gap 1 — unbranded cross-boundary IDs in the bash seam.** The background-task id is a plain `string`: `BashTask.id: string` (`packages/bash/bash/src/types.ts`), carried as `string` through the whole executor seam (`BashExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)` in `packages/bash/bash/src/index.ts`) and validated/passed as `string` by the model-facing tools (`validateTaskId`, `assertTaskAccess`, the `task_id` schema arg in `packages/bash/tool-bash/src/index.ts`). It is generated by a per-executor counter — `` `bash-${this.nextTaskId++}` `` in `packages/bash/bash-local/src/index.ts` — which gives it **exactly the same `name-N` shape as `SessionId`'s default** (`` `session-${++counter}` `` in `packages/core/session/src/index.ts`). A bash task id and a session id are trivially swappable at a call site and the compiler says nothing. It is a model-facing id (the model passes `task_id` back to `bash_output`/`bash_kill`), so a confusion here is reachable from untrusted input. +**Gap 1 — unbranded cross-boundary IDs in the bash seam.** The background-job id is a plain `string`: `BashTask.id: string` (`packages/shell/shell/src/types.ts`), carried as `string` through the whole executor seam (`ShellExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)` in `packages/shell/shell/src/index.ts`) and validated/passed as `string` by the model-facing tools (`validateJobId`, `assertTaskAccess`, the `job_id` schema arg in `packages/shell/tool-bash/src/index.ts`). It is generated by a per-executor counter — `` `bash-${this.nextTaskId++}` `` in `packages/shell/bash-local/src/index.ts` — which gives it **exactly the same `name-N` shape as `SessionId`'s default** (`` `session-${++counter}` `` in `packages/core/session/src/index.ts`). A bash job id and a session id are trivially swappable at a call site and the compiler says nothing. It is a model-facing id (the model passes `job_id` back to `bash_output`/`bash_kill`), so a confusion here is reachable from untrusted input. -The bash **owner token** is the related sub-case: `BashExecRequest.owner?: string` and `BashExecSpec.owner: string | undefined` (`packages/bash/bash/src/types.ts`) are documented as a deliberately *opaque* isolation key, but in every live caller the value IS the owning agent's shared `Agent.id`/`SessionId` (`callerToken = (exec) => exec.agent?.id` in `packages/bash/tool-bash/src/index.ts`) wearing a different seam-local name. It is compared for access control (`owner !== callerToken(exec)`), so a mismatched-but-well-typed string here is a cross-session isolation bug the type system currently cannot catch. This is the shared id alias covered by the [unified agent/session identity decision](../simplification/2026-06-20-unify-agent-and-session-id.md). +The bash **owner token** is the related sub-case: `ShellExecRequest.owner?: string` and `ShellExecSpec.owner: string | undefined` (`packages/shell/shell/src/types.ts`) are documented as a deliberately *opaque* isolation key, but in every live caller the value IS the owning agent's shared `Agent.id`/`SessionId` (`callerToken = (exec) => exec.agent?.id` in `packages/shell/tool-bash/src/index.ts`) wearing a different seam-local name. It is compared for access control (`owner !== callerToken(exec)`), so a mismatched-but-well-typed string here is a cross-session isolation bug the type system currently cannot catch. This is the shared id alias covered by the [unified agent/session identity decision](../simplification/2026-06-20-unify-agent-and-session-id.md). **Gap 2 — brand erosion at the boundaries of the *already-branded* IDs.** Even `CallId` and `SessionId` decay back to bare `string` at exactly the places confusion is most likely: registry/store key types and public method params. Representative sites include the session store, the agent registry (both keyed by the shared `SessionId`), tool-presentation call-id maps, ACP's session records, and the persistence coordinator. A brand that is dropped at a collection key buys nothing on lookups — the value of the existing brands is partly unrealized. @@ -18,9 +18,9 @@ The bash **owner token** is the related sub-case: `BashExecRequest.owner?: strin A type-only change. Brands are zero-cost casts; nothing about runtime behavior, serialization, comparison, or the wire format changes. The decision has three parts, all honoring the existing "not every string" policy. -- **Brand the bash task id.** Add `BashTaskId = Branded<'BashTaskId'>` plus its same-named factory in `packages/bash/bash/src/types.ts` (the package that *owns* the id), importing `Branded` from `@deepseek-ai/dsh-brand` exactly as `SessionId` does. The brand primitive lives in the dependency-free `dsh-brand` utility package precisely so `dsh-bash` can brand its ids by depending on it alone — it never pulls in `dsh-llm` (or `dsh-session`) just to reach `Branded`. Thread it through `BashTask.id`, the `BashExecutor` Service Definition methods (`get`/`ownerOf`/`readOutput`/`kill`), the generation site in `dsh-bash-local` (brand the counter output once, at creation), and the `dsh-tool-bash` validate/access surface (`validateTaskId` returns a `BashTaskId`; `task_id` is branded at the tool boundary where the model's string arrives). +- **Brand the bash job id.** Add `BashTaskId = Branded<'BashTaskId'>` plus its same-named factory in `packages/shell/shell/src/types.ts` (the package that *owns* the id), importing `Branded` from `@deepseek-ai/dsh-brand` exactly as `SessionId` does. The brand primitive lives in the dependency-free `dsh-brand` utility package precisely so `dsh-shell` can brand its ids by depending on it alone — it never pulls in `dsh-llm` (or `dsh-session`) just to reach `Branded`. Thread it through `BashTask.id`, the `ShellExecutor` Service Definition methods (`get`/`ownerOf`/`readOutput`/`kill`), the generation site in `dsh-bash-local` (brand the counter output once, at creation), and the `dsh-tool-bash` validate/access surface (`validateJobId` returns a `BashTaskId`; `job_id` is branded at the tool boundary where the model's string arrives). -- **Mint a distinct `OwnerToken` brand.** Add `OwnerToken = Branded<'OwnerToken'>` in `packages/bash/bash/src/types.ts`; type `BashExecRequest.owner` / `BashExecSpec.owner` / `BashExecutor.ownerOf` as `OwnerToken | undefined`. The `dsh-tool-bash` consumer casts the agent's shared `id` (`SessionId`) into an `OwnerToken` at the boundary — the one place the two vocabularies meet. The bash Service Definition never imports `dsh-session`. (Rationale in the next section.) +- **Mint a distinct `OwnerToken` brand.** Add `OwnerToken = Branded<'OwnerToken'>` in `packages/shell/shell/src/types.ts`; type `ShellExecRequest.owner` / `ShellExecSpec.owner` / `ShellExecutor.ownerOf` as `OwnerToken | undefined`. The `dsh-tool-bash` consumer casts the agent's shared `id` (`SessionId`) into an `OwnerToken` at the boundary — the one place the two vocabularies meet. The bash Service Definition never imports `dsh-session`. (Rationale in the next section.) - **Stop the brand erosion.** Propagate the existing brands to the `Map` key types and public method params listed under Gap 2 — `Map`, `Map`, `get(id: SessionId)`, `Map`, ACP's `SessionId` surface, and the coordinator's `Map`. This is the larger mechanical share of the change and the part that makes the *existing* brands actually load-bearing on lookups, not just on struct fields. @@ -46,21 +46,21 @@ export function OwnerToken(id: string): OwnerToken { ### Why not typing `owner` as `SessionId`? -The obvious shortcut is to type `owner` as `SessionId` directly — it always *is* one. We reject that. The bash executor seam is a capability seam (Service Definition `dsh-bash`, Service provider `dsh-bash-local`, Consumer `dsh-tool-bash`) and its owner token is *documented as deliberately opaque*: the executor "never interprets it (no access policy lives in the seam — that is the consumer's job)" (`packages/bash/bash/src/types.ts`). Typing the Service Definition's field as `SessionId` would import `dsh-session`'s vocabulary into a package that must not know what an owner token *means* — it would couple a generic execution backend to the session model and contradict the opaque-token design. A sandboxed or remote executor that replaces `dsh-bash-local` should not inherit a session dependency. The distinct `OwnerToken` brand keeps the seam decoupled: `dsh-bash` knows only "an owner is some opaque branded token," and the `dsh-tool-bash` consumer — which already decides the access policy — is the single boundary that casts its `SessionId` into an `OwnerToken`. The brand still delivers the safety win (you cannot pass a `BashTaskId` or a raw string where an owner is expected) without the coupling. +The obvious shortcut is to type `owner` as `SessionId` directly — it always *is* one. We reject that. The bash executor seam is a capability seam (Service Definition `dsh-shell`, Service provider `dsh-bash-local`, Consumer `dsh-tool-bash`) and its owner token is *documented as deliberately opaque*: the executor "never interprets it (no access policy lives in the seam — that is the consumer's job)" (`packages/shell/shell/src/types.ts`). Typing the Service Definition's field as `SessionId` would import `dsh-session`'s vocabulary into a package that must not know what an owner token *means* — it would couple a generic execution backend to the session model and contradict the opaque-token design. A sandboxed or remote executor that replaces `dsh-bash-local` should not inherit a session dependency. The distinct `OwnerToken` brand keeps the seam decoupled: `dsh-shell` knows only "an owner is some opaque branded token," and the `dsh-tool-bash` consumer — which already decides the access policy — is the single boundary that casts its `SessionId` into an `OwnerToken`. The brand still delivers the safety win (you cannot pass a `BashTaskId` or a raw string where an owner is expected) without the coupling. ## Out of scope / possible extensions Kept deliberately narrow per the "not every string needs a brand" policy. Each of these is a plausible future brand, deferred with a reason, not a commitment: -- **`ModelId`** (`GenerateOptions.model`, the `LlmService` adapter-registry key) — a real cross-package lookup key (config → agent → llm → adapter); a reasonable next brand, left out only to keep this decision's blast radius focused. -- **`ToolName`** (the `ToolRegistry` key) — author-defined, human-readable, and rarely confused with another id; the weakest candidate, likely not worth a brand. +- **`ModelId`** (`GenerateOptions.model`, the `LlmRuntime` adapter-registry key) — a real cross-package lookup key (config → agent → llm → adapter); a reasonable next brand, left out only to keep this decision's blast radius focused. +- **`ToolName`** (the `ToolRuntime` key) — author-defined, human-readable, and rarely confused with another id; the weakest candidate, likely not worth a brand. - **`ErrorCode`** (`HarnessError.code`) — a closed vocabulary (`ABORTED`, `NO_ADAPTER`, …), not a per-instance id; better served by a string-literal union than a brand, if anything. - **Numeric ordinals** — turn number, step number, and the event `seq` are `number`, not `string`, so `Branded` does not apply; a parallel `number & { readonly [BRAND]: B }` variant could brand them, but they are positional ordinals rarely passed across boundaries, so the payoff is low. - **Validated construction** — the brand factories are pure casts with no runtime check, and every boundary (ACP `sessionId`, provider-issued `call.id`, the empty-string fallback in `dsh-llm-deepseek`) trusts the raw string today. A `SessionId.parse()` / `isValid()` companion that throws on malformed input at boundaries is a genuine gap, but it is a *runtime-behavior* change with its own design (what is "malformed"? what happens on failure?) and belongs in its own decision, not bundled into this type-only change. ## Verification -The landed invariants: `BashTaskId` and `OwnerToken` are defined in `dsh-bash` and threaded end-to-end (Service Definition, the `dsh-bash-local` generation site, the `dsh-tool-bash` model-facing tool) with no `dsh-bash` dependency on `dsh-session`; no collection keyed by an in-scope branded id (`CallId`/`SessionId`/`BashTaskId`) is keyed by bare `string`; public method params and exported signatures keep the brand; and brands are constructed via the cast factory at each boundary where a raw string enters (provider call id, ACP session id, model-supplied `task_id`), never as scattered `as` casts. +The landed invariants: `BashTaskId` and `OwnerToken` are defined in `dsh-shell` and threaded end-to-end (Service Definition, the `dsh-bash-local` generation site, the `dsh-tool-bash` model-facing tool) with no `dsh-shell` dependency on `dsh-session`; no collection keyed by an in-scope branded id (`CallId`/`SessionId`/`BashTaskId`) is keyed by bare `string`; public method params and exported signatures keep the brand; and brands are constructed via the cast factory at each boundary where a raw string enters (provider call id, ACP session id, model-supplied `job_id`), never as scattered `as` casts. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md index 824d802b4a..11a78df609 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-branded-ids.zh.md @@ -8,9 +8,9 @@ Status: implemented harness 使用 `Branded = string & { readonly [BRAND]: B }` 机制,为 `CallId`(`packages/llm/llm/src/brand.ts`)和 agent(智能体)/会话共享的 `SessionId`(`packages/core/session/src/types.ts`)做 brand 处理;该机制由纯类型包 `@deepseek-ai/dsh-brand` 拥有,位于 `packages/util/brand/`,见其 [README](../../../../packages/util/brand/README.md),并为每个类型提供零开销的 cast 工厂。`dsh-brand` 还声明了治理策略:*「Branding 用于跨包边界且可能被混淆的 id;不是每个 string 都需要 brand。」* 这条策略是正确的;问题在于它只落实了一半。两处缺口使得结构相同但语义错误的 string 今天仍能通过类型检查器。 -**缺口 1:bash seam 中未 brand 的跨边界 ID。** 后台 task id 是普通 `string`:`BashTask.id: string`(`packages/bash/bash/src/types.ts`),作为 `string` 贯穿整个执行器 seam(`packages/bash/bash/src/index.ts` 中的 `BashExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)`),再由面向模型的工具以 `string` 校验并传递(`validateTaskId`、`assertTaskAccess`、`packages/bash/tool-bash/src/index.ts` 中 `task_id` 的 schema 参数)。它由每执行器计数器生成——`packages/bash/bash-local/src/index.ts` 中的 `` `bash-${this.nextTaskId++}` ``——其形状与 `SessionId` 的默认值**完全相同,都是 `name-N`**(`packages/core/session/src/index.ts` 中的 `` `session-${++counter}` ``)。bash task id 和会话 id 在调用点轻易就能互换,而编译器毫无反应。它是面向模型的 id(模型会把 `task_id` 传回 `bash_output`/`bash_kill`),所以该混淆可由不受信任的输入触达。 +**缺口 1:bash seam 中未 brand 的跨边界 ID。** 后台 job id 是普通 `string`:`BashTask.id: string`(`packages/shell/shell/src/types.ts`),作为 `string` 贯穿整个执行器 seam(`packages/shell/shell/src/index.ts` 中的 `ShellExecutor.get`/`ownerOf`/`readOutput`/`kill(id: string)`),再由面向模型的工具以 `string` 校验并传递(`validateJobId`、`assertTaskAccess`、`packages/shell/tool-bash/src/index.ts` 中 `job_id` 的 schema 参数)。它由每执行器计数器生成——`packages/shell/bash-local/src/index.ts` 中的 `` `bash-${this.nextTaskId++}` ``——其形状与 `SessionId` 的默认值**完全相同,都是 `name-N`**(`packages/core/session/src/index.ts` 中的 `` `session-${++counter}` ``)。bash job id 和会话 id 在调用点轻易就能互换,而编译器毫无反应。它是面向模型的 id(模型会把 `job_id` 传回 `bash_output`/`bash_kill`),所以该混淆可由不受信任的输入触达。 -bash **owner token** 是相关的子情形:`BashExecRequest.owner?: string` 和 `BashExecSpec.owner: string | undefined`(`packages/bash/bash/src/types.ts`)被文档描述为刻意*不透明*的隔离键,但在所有实际调用方中,该值就是所属 agent 共享的 `Agent.id`/`SessionId`(`callerToken = (exec) => exec.agent?.id`,位于 `packages/bash/tool-bash/src/index.ts`),只是披着另一个 seam 本地名称。它被用于访问控制比较(`owner !== callerToken(exec)`),因此一个不匹配但类型正确的 string 在此处就是跨会话隔离 bug,而当前类型系统无法捕获。这正是[统一 agent/session 标识决策](../simplification/2026-06-20-unify-agent-and-session-id.md)覆盖的共享 id 别名。 +bash **owner token** 是相关的子情形:`ShellExecRequest.owner?: string` 和 `ShellExecSpec.owner: string | undefined`(`packages/shell/shell/src/types.ts`)被文档描述为刻意*不透明*的隔离键,但在所有实际调用方中,该值就是所属 agent 共享的 `Agent.id`/`SessionId`(`callerToken = (exec) => exec.agent?.id`,位于 `packages/shell/tool-bash/src/index.ts`),只是披着另一个 seam 本地名称。它被用于访问控制比较(`owner !== callerToken(exec)`),因此一个不匹配但类型正确的 string 在此处就是跨会话隔离 bug,而当前类型系统无法捕获。这正是[统一 agent/session 标识决策](../simplification/2026-06-20-unify-agent-and-session-id.md)覆盖的共享 id 别名。 **缺口 2:*已经 brand* 的 ID 在边界处被侵蚀。** 就连 `CallId` 和 `SessionId` 也恰好在最容易混淆的地方退化为裸 `string`:注册表/store 键类型和公开方法参数。代表性位置包括会话存储、agent 注册表(二者都以共享的 `SessionId` 为键)、工具展示层的 call-id map、ACP(Agent Client Protocol)的会话记录,以及持久化协调器。在集合键处丢弃 brand,会让既有 brand 在查找时毫无价值;它们的价值只实现了一部分。 @@ -18,9 +18,9 @@ bash **owner token** 是相关的子情形:`BashExecRequest.owner?: string` 纯类型变更。Brand 是零开销 cast;运行时行为、序列化、比较和协议格式(wire format)均不变。该决策分三部分,全部遵循既有的「不是每个 string 都需要」策略。 -- **为 bash task id 加 brand。** 在 `packages/bash/bash/src/types.ts`(*拥有*该 id 的包)中添加 `BashTaskId = Branded<'BashTaskId'>` 及其同名工厂,从 `@deepseek-ai/dsh-brand` 导入 `Branded`,方式与 `SessionId` 完全一致。brand 原语位于无依赖的 `dsh-brand` 工具包中,正是为了让 `dsh-bash` 仅依赖它就能为自己的 id 加 brand,而无需引入 `dsh-llm`(或 `dsh-session`)来获取 `Branded`。将其贯穿 `BashTask.id`、`BashExecutor` Service Definition 方法(`get`/`ownerOf`/`readOutput`/`kill`)、`dsh-bash-local` 中的生成点(在创建时对计数器输出做一次 brand),以及 `dsh-tool-bash` 的校验/访问面(`validateTaskId` 返回 `BashTaskId`;`task_id` 在模型 string 到达的工具边界处被 brand)。 +- **为 bash job id 加 brand。** 在 `packages/shell/shell/src/types.ts`(*拥有*该 id 的包)中添加 `BashTaskId = Branded<'BashTaskId'>` 及其同名工厂,从 `@deepseek-ai/dsh-brand` 导入 `Branded`,方式与 `SessionId` 完全一致。brand 原语位于无依赖的 `dsh-brand` 工具包中,正是为了让 `dsh-shell` 仅依赖它就能为自己的 id 加 brand,而无需引入 `dsh-llm`(或 `dsh-session`)来获取 `Branded`。将其贯穿 `BashTask.id`、`ShellExecutor` Service Definition 方法(`get`/`ownerOf`/`readOutput`/`kill`)、`dsh-bash-local` 中的生成点(在创建时对计数器输出做一次 brand),以及 `dsh-tool-bash` 的校验/访问面(`validateJobId` 返回 `BashTaskId`;`job_id` 在模型 string 到达的工具边界处被 brand)。 -- **铸造独立的 `OwnerToken` brand。** 在 `packages/bash/bash/src/types.ts` 中添加 `OwnerToken = Branded<'OwnerToken'>`;将 `BashExecRequest.owner` / `BashExecSpec.owner` / `BashExecutor.ownerOf` 的类型标注为 `OwnerToken | undefined`。`dsh-tool-bash` 消费方在边界处将 agent 共享的 `id`(`SessionId`)cast 为 `OwnerToken`——这是两套词汇唯一交汇的地方。bash Service Definition 从不导入 `dsh-session`。(理由见下一节。) +- **铸造独立的 `OwnerToken` brand。** 在 `packages/shell/shell/src/types.ts` 中添加 `OwnerToken = Branded<'OwnerToken'>`;将 `ShellExecRequest.owner` / `ShellExecSpec.owner` / `ShellExecutor.ownerOf` 的类型标注为 `OwnerToken | undefined`。`dsh-tool-bash` 消费方在边界处将 agent 共享的 `id`(`SessionId`)cast 为 `OwnerToken`——这是两套词汇唯一交汇的地方。bash Service Definition 从不导入 `dsh-session`。(理由见下一节。) - **阻止 brand 侵蚀。** 将既有 brand 传播到缺口 2 列出的 `Map` 键类型和公开方法参数中:`Map`、`Map`、`get(id: SessionId)`、`Map`、ACP 的 `SessionId` surface、协调器的 `Map`。这是变更中机械量最大的部分,也是让*既有* brand 在查找处真正发挥作用(而不仅仅标注在结构体字段上)的关键。 @@ -46,21 +46,21 @@ export function OwnerToken(id: string): OwnerToken { ### 为什么不把 `owner` 类型标注为 `SessionId`? -显而易见的捷径是直接把 `owner` 类型标注为 `SessionId`——它确实*总是*一个会话 id。我们否决这个方案。bash 执行器 seam 是能力 seam(Service Definition `dsh-bash`、Service provider `dsh-bash-local`、Consumer `dsh-tool-bash`),其 owner token 被*明确记录为刻意不透明*:执行器「从不解释它(seam 中没有访问策略——那是消费方的职责)」(`packages/bash/bash/src/types.ts`)。把 Service Definition 的字段类型标注为 `SessionId`,会把 `dsh-session` 的词汇引入一个不应知道 owner token *含义*的包——这会让通用执行后端耦合会话模型,并违背不透明 token 的设计。取代 `dsh-bash-local` 的沙箱化执行器或远程执行器不应继承会话依赖。独立的 `OwnerToken` brand 使 seam 保持解耦:`dsh-bash` 只知道「owner 是某种带 brand 的不透明 token」,而已经决定访问策略的 `dsh-tool-bash` 消费方,是把其 `SessionId` cast 为 `OwnerToken` 的唯一边界。该 brand 仍带来安全收益(不能把 `BashTaskId` 或裸 string 传到 owner 位置),且不引入耦合。 +显而易见的捷径是直接把 `owner` 类型标注为 `SessionId`——它确实*总是*一个会话 id。我们否决这个方案。bash 执行器 seam 是能力 seam(Service Definition `dsh-shell`、Service provider `dsh-bash-local`、Consumer `dsh-tool-bash`),其 owner token 被*明确记录为刻意不透明*:执行器「从不解释它(seam 中没有访问策略——那是消费方的职责)」(`packages/shell/shell/src/types.ts`)。把 Service Definition 的字段类型标注为 `SessionId`,会把 `dsh-session` 的词汇引入一个不应知道 owner token *含义*的包——这会让通用执行后端耦合会话模型,并违背不透明 token 的设计。取代 `dsh-bash-local` 的沙箱化执行器或远程执行器不应继承会话依赖。独立的 `OwnerToken` brand 使 seam 保持解耦:`dsh-shell` 只知道「owner 是某种带 brand 的不透明 token」,而已经决定访问策略的 `dsh-tool-bash` 消费方,是把其 `SessionId` cast 为 `OwnerToken` 的唯一边界。该 brand 仍带来安全收益(不能把 `BashTaskId` 或裸 string 传到 owner 位置),且不引入耦合。 ## 不在范围内 / 可能的扩展 遵循「不是每个 string 都需要 brand」的策略,刻意保持窄范围。以下每项都是合理的未来 brand 候选,附带推迟理由而非承诺: -- **`ModelId`**(`GenerateOptions.model`,`LlmService` 适配器注册表的键):一个真正的跨包查找键(config → agent → llm → 适配器);合理的下一个 brand,仅为控制本决策的影响范围而暂不纳入。 -- **`ToolName`**(`ToolRegistry` 的键):由作者定义、人类可读,且很少与其他 id 混淆;最弱的候选,可能不值得加 brand。 +- **`ModelId`**(`GenerateOptions.model`,`LlmRuntime` 适配器注册表的键):一个真正的跨包查找键(config → agent → llm → 适配器);合理的下一个 brand,仅为控制本决策的影响范围而暂不纳入。 +- **`ToolName`**(`ToolRuntime` 的键):由作者定义、人类可读,且很少与其他 id 混淆;最弱的候选,可能不值得加 brand。 - **`ErrorCode`**(`HarnessError.code`):一个封闭词汇(`ABORTED`、`NO_ADAPTER`……),不是逐实例的 id;如果要做,string 字面量联合类型比 brand 更合适。 - **数值序号**:轮次号、步骤号和事件 `seq` 是 `number` 而非 `string`,`Branded` 不适用;可以用并行的 `number & { readonly [BRAND]: B }` 变体来 brand 它们,但它们是位置序号、很少跨边界传递,收益较低。 - **带校验的构造**:brand 工厂是纯 cast,无运行时检查,且每个边界(ACP `sessionId`、提供方签发的 `call.id`、`dsh-llm-deepseek` 中的空字符串回退)今天都信任裸 string。一个在边界处对格式错误的输入抛异常的 `SessionId.parse()` / `isValid()` 配套工具确实是缺口,但它是*运行时行为*变更,有自己的设计问题(什么算「格式错误」?失败时会怎样?),应在独立决策中处理,不应捆绑进这次纯类型变更。 ## 验证 -已落地的不变式如下:`BashTaskId` 和 `OwnerToken` 定义在 `dsh-bash` 中,并端到端贯穿 Service Definition、`dsh-bash-local` 生成点与 `dsh-tool-bash` 面向模型的工具,且 `dsh-bash` 未添加对 `dsh-session` 的依赖;没有任何以范围内 brand id(`CallId`/`SessionId`/`BashTaskId`)为键的集合使用裸 `string`;公开方法参数和导出签名保留 brand;每个原始 string 进入的边界(提供方 call id、ACP 会话 id、模型提供的 `task_id`)都通过 cast 工厂构造 brand,而不是散落的 `as` cast。 +已落地的不变式如下:`BashTaskId` 和 `OwnerToken` 定义在 `dsh-shell` 中,并端到端贯穿 Service Definition、`dsh-bash-local` 生成点与 `dsh-tool-bash` 面向模型的工具,且 `dsh-shell` 未添加对 `dsh-session` 的依赖;没有任何以范围内 brand id(`CallId`/`SessionId`/`BashTaskId`)为键的集合使用裸 `string`;公开方法参数和导出签名保留 brand;每个原始 string 进入的边界(提供方 call id、ACP 会话 id、模型提供的 `job_id`)都通过 cast 工厂构造 brand,而不是散落的 `as` cast。 ## 后果 diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml index 088891c738..3480f0b231 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md -2026-06-20-generic-long-running-tool-runtime.md: 1edc3422c253e06178a5c8ebf68dfd4ef1289e31 -2026-06-20-generic-long-running-tool-runtime.zh.md: 1f5349b2aeb02e8db21150012300a6d0a0493ae1 +2026-06-20-generic-long-running-tool-runtime.md: 7db43323dd83f8e99698a7163412c9b33a607dfb +2026-06-20-generic-long-running-tool-runtime.zh.md: fe12ef264d294224fcfaf503ba958fa90c782347 diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md index 1edc3422c2..7db43323dd 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md @@ -1,4 +1,4 @@ -# Agent Note: The background task runtime (`ctx.tasks`) and generic task control tools +# Agent Note: The background job runtime (`ctx.jobs`) and generic task control tools Status: implemented @@ -6,38 +6,38 @@ English | [中文](2026-06-20-generic-long-running-tool-runtime.zh.md) ## Problem -Background bash originally combined two responsibilities: the bash executor ran processes and also managed task ids, ownership, incremental reads, cancellation, completion listeners, and model-facing control tools. Adding background subagents required the same lifecycle and interaction contract. Implementing that contract independently for every long-running capability would duplicate isolation, cleanup, notification, and prompt behavior while teaching the model a different collect-and-stop protocol for each producer. +Background bash originally combined two responsibilities: the bash executor ran processes and also managed job ids, ownership, incremental reads, cancellation, completion listeners, and model-facing control tools. Adding background subagents required the same lifecycle and interaction contract. Implementing that contract independently for every long-running capability would duplicate isolation, cleanup, notification, and prompt behavior while teaching the model a different collect-and-stop protocol for each producer. -The task registry, control tools, and completion notices form one harness capability. Bash and subagents should supply execution-specific hooks without owning generic task behavior. +The job registry, control tools, and completion notices form one harness capability. Bash and subagents should supply execution-specific hooks without owning generic task behavior. ## Decision -The `tasks/` package group owns background-task semantics: +The `jobs/` package group owns background-job semantics: -- `@deepseek-ai/dsh-tasks` registers running work as `ctx.tasks` and owns task ids, authorization, snapshots, reads, cancellation, waiting, completion listeners, and cleanup. -- `@deepseek-ai/dsh-tool-tasks` exposes `task_output`, `task_list`, and `task_kill`, injects completion notices, and supplies the background-task system-prompt guidance. +- `@deepseek-ai/dsh-jobs` registers running work as `ctx.jobs` and owns job ids, authorization, snapshots, reads, cancellation, waiting, completion listeners, and cleanup. +- `@deepseek-ai/dsh-tool-jobs` exposes `job_output`, `job_list`, and `job_kill`, injects completion notices, and supplies the background-job system-prompt guidance. -Long-running tools are producers. `dsh-tool-bash` adapts a `BashProcess` into incremental output and process cancellation; `dsh-tool-subagent` adapts a child run into final output and child disposal. The bash and subagent capability seams remain independent of sessions and the task registry. +Long-running tools are producers. `dsh-tool-bash` adapts a `ShellProcess` into incremental output and process cancellation; `dsh-tool-subagent` adapts a child run into final output and child disposal. The bash and subagent capability seams remain independent of sessions and the job registry. -`TaskService` is the Service Definition in `@deepseek-ai/dsh-tasks`; the process-local provider is `LocalTaskService` in `@deepseek-ai/dsh-tasks-local` (the [task-registry contract Agent Note](2026-07-26-task-registry-seam.md) records that split). +`JobRegistry` is the Service Definition in `@deepseek-ai/dsh-jobs`; the process-local provider is `LocalJobRegistry` in `@deepseek-ai/dsh-jobs-local` (the [task-registry contract Agent Note](2026-07-26-job-registry-seam.md) records that split). ## Runtime contract -The literal types live on the [tasks subsystem page](../../../../docs/subsystems/tasks.md). A producer calls `ctx.tasks.start()` with a kind, label, optional owning `Agent`, optional positive `outputLimitBytes`, and a `run()` function. The runtime completes all failable preflight work before calling `run()` and invokes it once. After `run()` returns hooks, registration commits without another failable step; a producer cannot start work that lacks a collectable task id. +The literal types live on the [tasks subsystem page](../../../../docs/subsystems/jobs.md). A producer calls `ctx.jobs.start()` with a kind, label, optional owning `Agent`, optional positive `outputLimitBytes`, and a `run()` function. The runtime completes all failable preflight work before calling `run()` and invokes it once. After `run()` returns hooks, registration commits without another failable step; a producer cannot start work that lacks a collectable job id. -The process-local provider also owns bounded admission, whose rationale is recorded in the [bounded background task admission decision](../bug-fix/2026-08-11-bounded-background-task-admission.md). Its positive-safe-integer `maxConcurrentTasksPerOwner` config defaults to `10`; `start()` derives each exact `Agent` object's active count from `running` and `stopping` records, while every unowned task shares one service bucket. Capacity rejection occurs before `run()` and id allocation, and producer `done` settlement is the only event that releases a stopping task's place. The provider does not queue, preempt, or retain a second mutable count. +The process-local provider also owns bounded admission, whose rationale is recorded in the [bounded background job admission decision](../bug-fix/2026-08-11-bounded-background-job-admission.md). Its positive-safe-integer `maxConcurrentJobsPerOwner` config defaults to `10`; `start()` derives each exact `Agent` object's active count from `running` and `stopping` records, while every unowned task shares one service bucket. Capacity rejection occurs before `run()` and id allocation, and producer `done` settlement is the only event that releases a stopping task's place. The provider does not queue, preempt, or retain a second mutable count. -`outputLimitBytes` is producer-owned presentation policy, not a registry buffer. The registry validates and projects it unchanged into `TaskSnapshot`; generic control APIs apply the cap to complete model-facing output after adding their own status or notice metadata. Omitting it preserves the existing controller behavior, so the runtime does not impose a hidden default on unrelated producer families. +`outputLimitBytes` is producer-owned presentation policy, not a registry buffer. The registry validates and projects it unchanged into `JobSnapshot`; generic control APIs apply the cap to complete model-facing output after adding their own status or notice metadata. Omitting it preserves the existing controller behavior, so the runtime does not impose a hidden default on unrelated producer families. -A model-facing producer exposes that committed id in its canonical success value, normally `{ kind: 'background', taskId }`; Native rendering may keep human-readable prose. A pre-aborted background call fails rather than returning a no-op because no task exists to satisfy the promised handle. Once registration publishes the id, cancellation belongs to the task's own controller and the task runtime: later cancellation of the producing tool call must not kill the published task. `task_kill`, owner disposal, and service teardown request cancellation; foreground execution remains coupled to the call's `exec.signal`. +A model-facing producer exposes that committed id in its canonical success value, normally `{ kind: 'background', jobId }`; Native rendering may keep human-readable prose. A pre-aborted background call fails rather than returning a no-op because no task exists to satisfy the promised handle. Once registration publishes the id, cancellation belongs to the task's own controller and the job runtime: later cancellation of the producing tool call must not kill the published task. `job_kill`, owner disposal, and service teardown request cancellation; foreground execution remains coupled to the call's `exec.signal`. The producer hooks define three responsibilities: - `cancel(reason?)` synchronously requests termination, is idempotent, and must cause `done` to settle. - `done` never rejects and settles only after the producer has released the task's resources. -- Optional `readOutput()` returns the next consuming output delta. Omitting it declares a final-output task whose terminal result comes from `TaskOutcome.output`. +- Optional `readOutput()` returns the next consuming output delta. Omitting it declares a final-output task whose terminal result comes from `JobOutcome.output`. -Statuses are `running`, `stopping`, `completed`, `killed`, and `failed`. Producer-specific information such as an exit code or stop reason belongs in `detail`; the registry does not interpret it. Task kinds form a merge-extensible string union, and task ids are branded and generated as `-N`, with a counter per kind. +Statuses are `running`, `stopping`, `completed`, `killed`, and `failed`. Producer-specific information such as an exit code or stop reason belongs in `detail`; the registry does not interpret it. Task kinds form a merge-extensible string union, and job ids are branded and generated as `-N`, with a counter per kind. The runtime attaches one continuation to `done`, records the first terminal outcome, resolves waiters, and invokes completion listeners with per-listener error containment. First-wins settlement matters during teardown: if `cancel` throws, the runtime force-fails the record and warns that work may be orphaned rather than waiting forever for a promise that may never settle. A later producer outcome cannot overwrite that diagnosis or notify twice. A `cancel` that returns without eventually settling `done` still blocks teardown because the runtime cannot distinguish it from a slow, valid stop. @@ -45,7 +45,7 @@ Task registrations are not effects of the producer tool fiber. Reloading a tool ## Authorization and owner lifecycle -Task ids are runtime-global and predictable, so every access is authorized by the registry. `get`, `read`, `wait`, and `kill` accept the calling `Agent`; `list` returns only tasks visible to that caller. An owned task is accessible only to the exact owning session. Unowned tasks are open to non-agent callers and die with the task service. +Job ids are runtime-global and predictable, so every access is authorized by the registry. `get`, `read`, `wait`, and `kill` accept the calling `Agent`; `list` returns only tasks visible to that caller. An owned task is accessible only to the exact owning session. Unowned tasks are open to non-agent callers and die with the task service. The snapshot stores the owner's branded `SessionId` for authorization, while lifecycle operations retain the exact live `Agent` instance. These identities serve different purposes: session equality grants access, but exact object identity selects cleanup and completion delivery. Reusing an agent or session id cannot redirect an old scope's cleanup or notices to a replacement. @@ -55,45 +55,45 @@ For contract-compliant producers, `AgentHandle.dispose()` resolves only after ow ## Service API -`TaskService` provides: +`JobRegistry` provides: - `start(spec)` for preflighted, provider-admitted, atomic registration. - `get(id, caller?)` and `list(caller?)` for non-consuming snapshots. - `read(id, caller?)` for a consuming stream delta or an idempotent final result. - `kill(id, caller?, reason?)` for cancellation. - `wait(id, timeoutMs, caller?, signal?)` for bounded terminal waiting. -- `onTaskDone(listener)` for effect-scoped observation with exact-owner delivery and listener containment. +- `onJobDone(listener)` for effect-scoped observation with exact-owner delivery and listener containment. - `attachController(name)` for the task-controller availability fence. `wait` returns the terminal snapshot when the task settles or the live snapshot when its timeout expires. Aborting a wait cancels only that wait. If settlement has already assigned terminal delivery to the waiter, the terminal snapshot still wins. Waiters unregister synchronously on abort so a same-tick settlement cannot suppress a completion notice on behalf of a reader that receives nothing. -A producer loaded without any controller would let callers start work they cannot collect or stop. `dsh-tool-tasks` therefore calls `attachController()` for its lifetime, and `start()` fails before producer execution when no controller is attached. This check occurs at start rather than plugin load because sibling plugins may activate concurrently. Custom non-model controllers can attach themselves without teaching the registry tool names. +A producer loaded without any controller would let callers start work they cannot collect or stop. `dsh-tool-jobs` therefore calls `attachController()` for its lifetime, and `start()` fails before producer execution when no controller is attached. This check occurs at start rather than plugin load because sibling plugins may activate concurrently. Custom non-model controllers can attach themselves without teaching the registry tool names. ## Model-facing control API -`dsh-tool-tasks` registers three kind-independent tools with generic UI cards: +`dsh-tool-jobs` registers three kind-independent tools with generic UI cards: -- `task_output(task_id, wait?, timeout_ms?)` reads output and always appends `[status: ...]`. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Reads are non-blocking unless `wait: true`, whose timeout is defaulted and capped by plugin config. A wait timeout reports the still-running status and does not stop the task. -- `task_list()` returns caller-visible tasks as ` []