Compare commits

..

35 Commits

Author SHA1 Message Date
ed152416d5 feat: add searxng web-search provider, openrouter cost balance UI, offline scripts; update source-launch and docs
Some checks failed
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / windows node 24 / wine blocking (push) Has been skipped
CI / wine apt cache (push) Successful in 58s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 2m46s
Deploy documentation / deploy (push) Has been skipped
E2E (real DeepSeek API) / e2e (push) Failing after 1m18s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m18s
Landlock Run / Matrix (push) Successful in 13s
Release (vendor) / Pack npm tarballs (push) Failing after 3m43s
Release (dsh) / Pack npm tarballs (push) Failing after 1m53s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m51s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
2026-08-20 13:01:40 +07:00
imccyu
99f6f02fec Merge pull request #2620 from deepseek-harness/release/dsh-0.1.0-rc.7
release: dsh@0.1.0-rc.7
2026-08-17 19:03:17 +08:00
imccyu
bb4ca698d6 release(dsh): 0.1.0-rc.7 2026-08-17 18:47:02 +08:00
Tianyi Cui
8822d6744f Merge pull request #2252 from deepseek-harness/worktree/fix-1463-rich-content-bridge
fix: bridge durable image content across MCP and ACP
2026-08-17 18:38:01 +08:00
_Kerman
cc68bbf382 Merge pull request #2517 from deepseek-harness/xtr/node-pty-1.2-beta
chore(subprocess-local): bump node-pty beta
2026-08-17 18:12:16 +08:00
Yichen Jiang
a9ce05a04c Merge pull request #2462 from deepseek-harness/worktree/python-sdk-model-visible-assertions
test(python): 固定极简组合的模型可见面
2026-08-17 17:46:49 +08:00
Yichen Jiang
47d6760052 Merge branch 'master' into worktree/python-sdk-model-visible-assertions 2026-08-17 17:01:52 +08:00
Yichen Jiang
8692a1b76b test(python): pin the minimal composition's model-visible surface
The Python lane never compared what the minimal composition shows the
model: the mock model only asserted system-role messages, and the
advanced snapshot tokenizes the assembled system prompt and tool
schemas.

The sdk-minimal scenario now records model-visible.json — every model
request's advertised tool schemas verbatim and its message list, with
system and user text kept and assistant/tool payloads reduced to call
identity so the expected output replays on macOS and Linux. It excludes
the dynamic runtime-context snapshot, which the same composition emits
on macOS and not on Linux (#2488).

AGENTS.md and the testing policy name both SDKs as independent
projections of the agent loop, session lifecycle, and SessionEventMap.
2026-08-17 16:49:14 +08:00
Tianyi Cui
5139329d29 Merge remote-tracking branch 'origin/master' into worktree/fix-1463-rich-content-bridge 2026-08-17 13:56:17 +08:00
Tianyi Cui
e0bb7cafc0 Merge pull request #2308 from deepseek-harness/feat/web-collapsible-question-composer
feat(web): make the ask-user question card collapsible
2026-08-17 13:54:50 +08:00
Tianyi Cui
c15f2f4d25 Merge remote-tracking branch 'origin/master' into HEAD 2026-08-17 11:39:33 +08:00
Tianyi Cui
b2f23f88ad Merge commit '70396085b141370ce32de1be4e225b4384eaf46d' into HEAD
# Conflicts:
#	.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md
#	.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md
#	.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml
#	docs/config-catalog.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	docs/tool-catalog.i18n.yaml
#	docs/tool-catalog.md
#	docs/tool-catalog.zh.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/code-mode-turn/tool-schemas.expected.json
#	packages/core/tools/README.i18n.yaml
#	packages/core/tools/README.zh.md
#	packages/core/tools/src/code-mode.ts
#	packages/host/apiproxy/tests/api-proxy-models.spec.ts
#	packages/host/plugin-inventory/tests/inventory.spec.ts
#	packages/mcp/mcp-client/tests/mcp-client.e2e.ts
#	packages/mcp/mcp-client/tests/mcp-client.spec.ts
#	packages/self-modification/tool-cordis/src/api-catalog.ts
#	packages/test-support/acp-snapshot/README.i18n.yaml
#	pnpm-lock.yaml
2026-08-17 11:31:59 +08:00
GeeeekExplorer
db959a0879 test(web): cover collapsing and expanding the question card
Two new QuestionComposer cases: the collapse toggle hides the option list
and leaves the header strip, and collapsing between answering preserves
both the picked draft and the current question position through a full
submit; re-expanding does not steal focus back into the textarea. Refresh
the question-composer and steering web e2e goldens for the new header
button.
2026-08-17 01:18:44 +08:00
GeeeekExplorer
4b25b0e76d feat(web): make the ask-user question card collapsible
The ask-user takeover rendered the pending question set as a bottom card
capped at min(60vh, 520px) with no way to reduce it, which buried the
conversation above while the user decided. Add a minimize toggle next to
the dismiss action: collapsed, the card becomes a header strip (title plus
the two icon buttons) and the option body and footer unmount; expanding
restores the full card. Drafts and the current question index live in
QuestionFlow local state, so collapse/expand never loses them. The
free-form textarea autofocuses only on first presentation, so re-expanding
does not steal focus from the toggle.

Agent Note: .agents/notes/implemented/feature/2026-08-11-collapsible-ask-user-question-card.{md,zh.md,i18n.yaml}
2026-08-17 01:18:44 +08:00
_Kerman
b1030d6480 Merge branch 'master' into xtr/node-pty-1.2-beta 2026-08-13 18:39:17 +08:00
_Kerman
b11b5359f9 ci: use pnpm node-gyp for manylinux rebuild 2026-08-13 18:13:03 +08:00
_Kerman
a785eb80f7 fix(python-runtime): fall back to node-pty prebuild 2026-08-13 18:12:36 +08:00
_Kerman
1106b0b03d ci: rebuild node-pty for manylinux 2026-08-13 18:06:36 +08:00
_Kerman
348a49b62c docs: update node-pty patch notice 2026-08-13 18:02:39 +08:00
_Kerman
078dd2b6df chore(subprocess-local): bump node-pty beta 2026-08-13 17:58:13 +08:00
Tianyi Cui
b3e2de873d Merge master into worktree/fix-1463-rich-content-bridge
# Conflicts:
#	.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md
#	.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md
#	.agents/notes/implemented/feature/2026-08-10-telemetry-default-off.i18n.yaml
#	.agents/notes/implemented/feature/2026-08-10-telemetry-default-off.md
#	.agents/notes/implemented/feature/2026-08-10-telemetry-default-off.zh.md
#	.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md
#	apps/web/tests/scaffold.ts
2026-08-12 21:25:26 +08:00
Tianyi Cui
d322206246 fix(ci): stabilize latest-master acceptance gates 2026-08-12 19:41:06 +08:00
Tianyi Cui
238e7f456a fix(docs): repair latest-master pairing drift 2026-08-12 17:48:17 +08:00
Tianyi Cui
6e64f77030 test(plugin-inventory): avoid randomized id order 2026-08-12 17:48:17 +08:00
Tianyi Cui
f3bfcf33bb fix(ci): budget native Windows coverage timing 2026-08-12 17:48:17 +08:00
Tianyi Cui
de17206051 fix(attachment): type failure codes 2026-08-12 17:48:17 +08:00
Tianyi Cui
fdd1050510 test(snapshot): refresh code-mode image prompt 2026-08-12 17:48:17 +08:00
Tianyi Cui
adf4878b4a fix(acp): isolate prompt admission from agent work 2026-08-12 17:48:17 +08:00
Tianyi Cui
57fc6bc539 fix(attachment): distinguish admission from storage failures 2026-08-12 17:48:17 +08:00
Tianyi Cui
32c584561a ci: refresh pull request merge ref 2026-08-12 17:48:17 +08:00
Tianyi Cui
ee5111841a docs: refresh module graph for rich content bridges 2026-08-12 17:48:17 +08:00
Tianyi Cui
4f87c1fe6d feat(acp): bridge durable image prompts and replies 2026-08-12 17:48:17 +08:00
Tianyi Cui
49426cae02 fix(mcp): project image results through durable attachments 2026-08-12 17:48:17 +08:00
Tianyi Cui
e00146be73 fix(tools): forward nested image results in code mode 2026-08-12 17:48:17 +08:00
Tianyi Cui
219d2a1fb9 feat(attachment): add ordered image batch admission 2026-08-12 17:48:17 +08:00
452 changed files with 9518 additions and 926 deletions

View File

@@ -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/architecture/2026-08-19-searxng-web-search-provider.md
2026-08-19-searxng-web-search-provider.md: 377dba2af133677b4fd1fd591cb3929ac2acda33
2026-08-19-searxng-web-search-provider.zh.md: 29d406831ea947c383263dd5cdba840fa9e448a8

View File

@@ -0,0 +1,38 @@
# Agent Note: SearXNG web search provider
Status: implemented
English | [中文](2026-08-19-searxng-web-search-provider.zh.md)
## Problem
The harness [web capability seam](2026-06-24-web-capability-seam.md) ships three search providers — Exa, Perplexity, and native DeepSeek web search — all of which are hosted API services. None fits a deployment that wants to self-host its own metasearch. SearXNG is a self-hostable metasearch engine with a JSON API, so it is the natural backend for such deployments. It differs from the hosted providers in two ways that shape the adapter: there is no single canonical SearXNG endpoint (each instance is its own server, so `baseURL` cannot have a sensible public default), and an engine does not expose a per-request result-count control (page size is instance configuration).
## Decision
Add `@deepseek-ai/dsh-web-search-searxng` (`packages/web/web-search-searxng`), a `WebSearchProvider` that registers under the stable id `searxng` and calls a SearXNG instance's JSON API (`GET {base}/search?q=...&format=json`).
- `baseURL` has **no default**; the schema keeps it optional so an omitted or unparseable value surfaces as an unavailable provider (`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`) rather than a boot error. The product requires evidence before an unsupported default endpoint, and no single public SearXNG instance is authoritative.
- Config flows through the `web-search-searxng` Settings namespace (layered over the cordis entry), and the provider projects the resolved section **per search**, so a settings edit applies to the next call without re-registration — the same projection topology as `dsh-web-search-deepseek`.
- The provider carries **no API key and no `Authorization` header**: it targets open instances, so no credential can leak to a redirect target. HTTP redirects are still rejected with `redirect: 'error'`, mapping to `WEB_PROVIDER_ERROR`, matching the seam's redirect hygiene.
- `language` (SearXNG `language`, default `auto`) and `timeRange` (`day`|`week`|`month`|`year`) are exposed; a request carries no result count because the API cannot bound one, so the seam's `maxResults` truncation is the sole bound. `content` is omitted (no generated answer).
- Results map to `WebSearchSource`: `url` ← `url`, `title` ← `title`, `snippet` ← `content`. Unlike Exa, a result **without a snippet is kept** — URL and title are still useful — so sources are not dropped for missing snippets.
The package follows the provider topology of `dsh-web-search-exa`: a function/namespace plugin (`inject: ['web']`) that registers into `ctx.web`, `inject`-less in the sense that it registers a backend rather than owning the `ctx.web` key, with package-owned mapping and error translation and an empty package invariant companion.
## Alternatives considered
### Give `baseURL` a public-instance default
Rejected. A default like `https://searx.be` would pick an arbitrary, possibly unreachable or rate-limited public instance and silently send traffic to a third party without the operator choosing it. Requiring an explicit `baseURL` makes the provider unavailable until configured, which is the honest misconfiguration signal the seam already names (`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`).
### Route SearXNG through an OpenAI-compatible gateway instead of a dedicated provider
Rejected. The harness search seam consumes `WebSearchProvider` backends, not arbitrary OpenAI-compatible endpoints; the LLM provider configuration surface does not feed `ctx.web`. A dedicated SearXNG `WebSearchProvider` is the only path that reaches the web search tools without a bespoke glue service.
## Consequences
- A deployment gains a self-hosted search backend whose query goes to the operator's own instance, with optional language and recency filters but no per-query result-count control — single-page results are truncated by the seam.
- The provider set grows to four search backends; `searchProvider` (or `$DSH_WEB_SEARCH_PROVIDER`) names `searxng` to select it, or auto-selection picks it when it is the only usable provider.
- Redirect rejection means a misconfigured `baseURL` that answers 3xx surfaces as `WEB_PROVIDER_ERROR` rather than silently following; with no credentials on the request this is hygiene, not secret protection.
- Snippet-less results are retained, so `tool-web` renders fallback URL/hostname labels for them rather than dropping the source.

View File

@@ -0,0 +1,38 @@
# Agent Note: SearXNG web search provider
Status: implemented
[English](2026-08-19-searxng-web-search-provider.md) | 中文
## 问题
harness [web 能力 seam](2026-06-24-web-capability-seam.md) 自带了三个搜索提供方——Exa、Perplexity 与 DeepSeek 原生 web 搜索——它们都是托管 API 服务。它们都不适合希望自托管元搜索的部署场景。SearXNG 是具备 JSON API 的可自托管元搜索引擎,因此是这类部署的自然后端。它在这两个方面与托管提供方不同,进而塑造了适配器:不存在统一的 SearXNG 端点(每个实例都是独立服务器,因此 `baseURL` 无法有合理的公共默认值),且引擎不提供按请求控制结果数量的方式(页面大小属于实例配置)。
## 决策
新增 `@deepseek-ai/dsh-web-search-searxng`(`packages/web/web-search-searxng`),一个以稳定 id `searxng` 注册、调用 SearXNG 实例 JSON API(`GET {base}/search?q=...&format=json`)的 `WebSearchProvider`。
- `baseURL` **无默认值**;schema 保持其为可选,因此省略或无法解析的值呈现为不可用提供方(`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`),而非启动错误。产品要求先有证据再采用未经验证的默认端点,且不存在权威的公共 SearXNG 实例。
- 配置经由 `web-search-searxng` Settings 命名空间流入(叠加在 cordis 条目之上),提供方**按搜索**投影解析后的分节,因此设置变更在下次调用即生效,无需重新注册——与 `dsh-web-search-deepseek` 的投影拓扑一致。
- 提供方**不携带 API 密钥或 `Authorization` 头**:它面向开放实例,因此不会有凭据泄露给重定向目标。HTTP 重定向仍以 `redirect: 'error'` 拒绝,映射为 `WEB_PROVIDER_ERROR`,与 seam 的重定向卫生一致。
- 公开 `language`(SearXNG `language`,默认 `auto`)与 `timeRange`(`day`|`week`|`month`|`year`);请求不携带结果数量,因为 API 无法限制数量,因此 seam 的 `maxResults` 截断是唯一上限。省略 `content`(无生成答案)。
- 结果映射为 `WebSearchSource`:`url` ← `url`、`title` ← `title`、`snippet` ← `content`。与 Exa 不同,**没有 snippet 的结果会被保留**——URL 与标题仍然有用——因此不会因缺少 snippet 而丢弃来源。
该包遵循 `dsh-web-search-exa` 的提供方拓扑:函数/命名空间插件(`inject: ['web']`)向 `ctx.web` 注册后端而非拥有 `ctx.web` 键,拥有包级映射与错误转换,并带一个空的包级 invariant 伴生插件。
## 备选方案
### 为 `baseURL` 提供公共实例默认值
拒绝。类似 `https://searx.be` 的默认值会挑选任意、可能不可达或被限速的公共实例,并在未由操作员选择的情况下把流量悄悄发给第三方。要求显式 `baseURL` 会使提供方在配置前不可用,这正是 seam 已命名的诚实误配置信号(`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)。
### 通过 OpenAI 兼容网关路由 SearXNG,而非专用提供方
拒绝。harness 搜索 seam 消费的是 `WebSearchProvider` 后端,而非任意 OpenAI 兼容端点;LLM 提供方配置面并不供给 `ctx.web`。专用 SearXNG `WebSearchProvider` 是不借助自制胶水服务而触达 web 搜索工具的唯一路径。
## 后果
- 部署获得自托管搜索后端,其查询发往操作员自己的实例,支持可选的语言与时效过滤器,但没有按查询的结果数量控制——单页结果由 seam 截断。
- 提供方集合增至四个搜索后端;`searchProvider`(或 `$DSH_WEB_SEARCH_PROVIDER`)指定 `searxng` 以选择它,或当其是唯一可用提供方时自动选择。
- 拒绝重定向意味着返回 3xx 的误配置 `baseURL` 呈现为 `WEB_PROVIDER_ERROR` 而非静默跟随;由于请求无凭据,这是卫生而非密钥保护。
- 无 snippet 的结果被保留,因此 `tool-web` 为它们渲染回退的 URL/主机名标签,而非丢弃来源。

View File

@@ -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-07-mcp-client-plugin.md
2026-07-07-mcp-client-plugin.md: 84fbda6f7832d82854144d8e3687bfe23fec6fa4
2026-07-07-mcp-client-plugin.zh.md: 3270803f7cb2cea08e170fdeb84c253c97e16a8e
2026-07-07-mcp-client-plugin.md: f9d997fd06dbf14f86ec344a2d5f16c7412119d0
2026-07-07-mcp-client-plugin.zh.md: a4b89a948c14d564d2fc37bee6f91fb923e9ed49

View File

@@ -141,11 +141,11 @@ Tools are never silently skipped; which tools are available never depends on plu
A unified `execute` handler for all tools from one MCP server:
1. Resolve `rawName` (the executor closes over it) and call `client.callTool({ name: rawName, arguments }, { signal: exec.signal })` with the configured timeout — the public name is never sent to the server.
2. Map the result:
- Multiple `text` content blocks → join with `'\n'` into a single `TextBlock` (required: `flattenText` uses `join('')` without separator, so multiple blocks would lose inter-block boundaries).
- `image` content blocks → discard with a `ctx.logger.warn` (the harness has no image content block type; [drop-image Agent Note](../simplification/2026-07-04-drop-image-content-block.md)).
- `isError: true` → map to the harness `isError` result path (`{ content: [...], isError: true }`).
3. Cancellation: `exec.signal` (from the agent loop's cancel) is passed through to the MCP SDK's `callTool`, which sends `$/cancelRequest` to the server.
2. Preserve canonical success as `{ content: JsonValue[], structuredContent? }`; complete MCP JSON blocks remain the programmatic/Code Mode value. `isError: true` throws before any image persistence so the registry owns the failure path.
3. Prepare a separate ordered Native projection. Text runs join with `'\n'`; resource links preserve name and URI as text; audio, embedded resources, malformed blocks, and unknown types become explicit diagnostics. If any image exists, the bridge strictly decodes the complete batch, resolves the calling agent's latest exact route, requires an attachment store plus explicit model image input, and delegates all-member validation and ordered persistence to `AttachmentStore.saveImages()`. Any decode, capability, or storage refusal renders every image as diagnostic text and returns no partial references.
4. Keep `output.render` synchronous and pure. The executor stages its richer projection in a generation-local `WeakMap` keyed by the exact execution; `finalizeContent` installs it only when the registry's post-execute result still has the original canonical value and fallback content. A policy block, value replacement, or content replacement remains authoritative, and a re-sync cannot let an older generation consume new execution state.
5. Code Mode receives the untouched canonical value. Its generic dispatch bridge defers a successful final content sequence containing an image through the outer `run_code` result, so MCP requires no private parent-token special case.
6. Cancellation: `exec.signal` (from the agent loop's cancel) is passed through to the MCP SDK's `callTool`, exact-model lookup, and the pre-storage gate.
### Subprocess environment (stdio transport)
@@ -189,13 +189,25 @@ Rejected. The remote name is untrusted, non-unique across deployments, and chang
Rejected. `flattenText()` in the DeepSeek serializer uses `join('')` (no separator) when flattening `ContentBlock[]` to wire format. Multiple text blocks would silently lose inter-block boundaries — a correctness bug. All existing tools return a single TextBlock; the MCP bridge follows suit.
### Replace the canonical MCP result with core `ContentBlock[]`
Rejected. Programmatic callers need protocol-complete MCP blocks and `structuredContent`, while Native consumers need durable core images rather than base64. One canonical protocol value plus a separate projection preserves both contracts.
### Add a generic RichContent service or perform I/O in `output.render`
Rejected. Core already owns the role-neutral content vocabulary, and a second service would duplicate its logging and ordering contracts. `output.render` is pure, synchronous, and replayable, so attachment I/O belongs in async execution with an exact finalization handoff.
### Let each image-returning tool special-case Code Mode parents
Rejected. That couples leaf tools to composite-tool internals and misses future rich tools. The generic Code Mode bridge observes the final post-policy content and forwards image-bearing results uniformly.
## Testing
Coverage is named per tier; each behavior lives at the cheapest tier that can express it.
- **Unit** (`tests/mcp-client.spec.ts`, `tests/apply.spec.ts`, mocked MCP SDK): the `publicToolName` algorithm (clean, normalize, truncate-and-hash, determinism, distinct-identity separation), raw-vs-public wire discipline, cross-server and native-tool coexistence, duplicate-`serverName` load failure and reservation release, invalid-tool-list rejection, generation swap/rollback, failed-re-sync retention, result mapping, cancellation, config schema validation. 100% per-file coverage gates the package.
- **E2E** (`tests/mcp-client.e2e.ts`, keyless): the real MCP protocol against the in-repo fixture server, `@modelcontextprotocol/server-everything`, and `@modelcontextprotocol/server-filesystem` over stdio, and against an in-process `StreamableHTTPServerTransport` server over Streamable HTTP — discovery under the namespace, dotted-name normalization end to end, execution round-trips, duplicate-`serverName` rejection, disposal.
- **Snapshot**: deliberately none. MCP tools introduce no new presentation shape — they register as raw `ToolDefinition`s and UI consumers use the generic-card fallback already pinned by their presentation suites. Adding an MCP server to a runnable snapshot composition would mutate its pinned system-prompt fixture and make every replay depend on spawning an external MCP server process for no new behavior. If a later change gives MCP tools their own render intent, that change names its snapshot coverage then.
- **Unit** (`tests/mcp-client.spec.ts`, `tests/apply.spec.ts`, mocked MCP SDK): the `publicToolName` algorithm (clean, normalize, truncate-and-hash, determinism, distinct-identity separation), raw-vs-public wire discipline, cross-server and native-tool coexistence, duplicate-`serverName` load failure and reservation release, invalid-tool-list rejection, generation swap/rollback, failed-re-sync retention, lossless canonical results, mixed rich ordering, atomic malformed batches, exact capability/store refusal, explicit non-image diagnostics, post-execute policy precedence, cancellation, and config schema validation. 100% per-file coverage gates the package.
- **E2E** (`tests/mcp-client.e2e.ts`, keyless): the real MCP protocol against the in-repo fixture server, `@modelcontextprotocol/server-everything`, and `@modelcontextprotocol/server-filesystem` over stdio, and against an in-process `StreamableHTTPServerTransport` server over Streamable HTTP — discovery under the namespace, dotted-name normalization end to end, execution round-trips, durable image save/read with base64 retained only in the canonical value, explicit refusal without an image route, duplicate-`serverName` rejection, and disposal.
- **Snapshot**: the assembled ACP example owns the transport-visible inline-image transcript and the Code Mode image-forwarding transcript; package E2E owns the real MCP wire because the runnable snapshot must stay keyless and deterministic rather than spawning third-party server packages. MCP tool cards still use the generic-card fallback and require no package-specific UI snapshot.
## Consequences
@@ -206,3 +218,4 @@ Coverage is named per tier; each behavior lives at the cheapest tier that can ex
- **Tool schema quality**: MCP servers may expose poorly-described tools (vague descriptions, incomplete JSON schemas). The harness passes them through as-is — garbage-in-garbage-out; that is the server author's responsibility, not the bridge's.
- **Stdio process management**: a misbehaving MCP server that ignores signals could wedge dispose. The Cordis fiber disposal has bounded quiescence; a stuck transport eventually times out at the framework level.
- Crash recovery is automatic within the [reconnect budget](2026-08-06-mcp-client-auto-reconnect.md); manual reload remains the path after exhaustion or with `reconnect.enabled: false`.
- Image payloads can enter model context only through the shared durable attachment store and an exact positive route capability. Audio and embedded-resource payloads remain execution-local with explicit diagnostics.

View File

@@ -141,11 +141,11 @@ MCP 仅保证工具名在[单个服务器内](https://modelcontextprotocol.io/sp
为来自同一个 MCP 服务器的所有工具提供统一的 `execute` 处理器:
1. 解析 `rawName`(执行器闭包持有它),以配置的超时时间调用 `client.callTool({ name: rawName, arguments }, { signal: exec.signal })`——公开名称永远不发送给服务器。
2. 映射结果:
- 多个 `text` 内容块 → 以 `'\n'` 连接为单个 `TextBlock`(之所以必须这样做,是因为 `flattenText` 使用无分隔符的 `join('')`,多个内容块会丢失块间边界)。
- `image` 内容块 → 丢弃并 `ctx.logger.warn`(harness 没有图片内容块类型;[删除图片 Agent Note](../simplification/2026-07-04-drop-image-content-block.md))。
- `isError: true` → 映射到 harness 的 `isError` 结果路径(`{ content: [...], isError: true }`)。
3. 取消:`exec.signal`(来自 agent loop(智能体循环)的取消)透传给 MCP SDK 的 `callTool`,后者向服务器发送 `$/cancelRequest`。
2. 把规范成功值保留为 `{ content: JsonValue[], structuredContent? }`;完整 MCP JSON 块仍是程序化调用/Code Mode 值。`isError: true` 会在持久化任何图片前抛出,使失败路径归注册表所有。
3. 另行准备有序 Native 投影。连续文本块以 `'\n'` 连接;资源链接以文本保留名称和 URI;音频、嵌入资源、格式错误的块和未知类型成为明确诊断。只要存在图片,桥接层就严格解码完整批次,解析调用 agent 的最新确切路由,要求附件存储以及模型明确支持图片输入,再把全成员校验和有序持久化委托给 `AttachmentStore.saveImages()`。任何解码、能力或存储拒绝都会把全部图片渲染为诊断文本,且不返回部分引用。
4. 保持 `output.render` 同步且纯净。执行器把更丰富的投影暂存在按同步世代创建、以确切执行为键的 `WeakMap` 中;只有注册表的 post-execute 结果仍保留原规范值和兜底内容时,`finalizeContent` 才安装该投影。策略阻止、值替换或内容替换仍具有权威性,重新同步也无法让旧世代消费新执行状态。
5. Code Mode 接收未改动的规范值。其通用分发桥接层会把包含图片的成功最终内容序列经外层 `run_code` 结果延后,因此 MCP 无需私有父 token 特例。
6. 取消:`exec.signal`(来自 agent loop 的取消)透传给 MCP SDK 的 `callTool`、确切模型查询和存储前门禁。
### 子进程环境(stdio 传输)
@@ -189,13 +189,25 @@ v1 否决。它能防止跨服务器冲突,但无法将 MCP 注册与原生 ha
否决。DeepSeek 序列化器中的 `flattenText()` 在将 `ContentBlock[]` 扁平化为协议格式(wire format)时使用 `join('')`(无分隔符)。多个 text 块会静默丢失块间边界——这是正确性缺陷。所有现有工具返回单个 TextBlock;MCP 桥接遵循同一做法。
### 用核心 `ContentBlock[]` 替换规范 MCP 结果
不予采用。程序化调用方需要协议完整的 MCP 块和 `structuredContent`,Native 消费方则需要持久核心图片而不是 base64。一份规范协议值加一份独立投影可以同时保留两项契约。
### 添加通用 RichContent 服务,或在 `output.render` 中执行 I/O
不予采用。核心已经拥有角色无关的内容词汇,第二套服务会重复其日志与顺序契约。`output.render` 必须纯净、同步且可回放,因此附件 I/O 属于异步执行,再经确切的最终化交接安装结果。
### 让每个返回图片的工具分别特殊处理 Code Mode 父调用
不予采用。这会把叶子工具与组合工具内部机制耦合,并漏掉未来丰富工具。通用 Code Mode 桥接层观察最终 post-policy 内容,统一转发含图片结果。
## 测试
覆盖范围按层级列出;每项行为都放在能够表达它的最低成本层级。
- **单元测试**(`tests/mcp-client.spec.ts`、`tests/apply.spec.ts`,mock MCP SDK):`publicToolName` 算法(干净名称、规范化、截断加 hash、确定性、不同标识的分离)、raw 与 public 的协议纪律、跨服务器与原生工具共存、重复 `serverName` 加载失败与预留释放、无效工具列表拒绝、注册代切换/回滚、重新同步失败时保留上一代注册、结果映射、取消、配置 schema 校验。100% 逐文件覆盖率门禁约束该包。
- **E2E**(`tests/mcp-client.e2e.ts`,无需密钥):使用真实 MCP 协议对接仓库内的 fixture(测试前置数据)服务器、`@modelcontextprotocol/server-everything` 和 `@modelcontextprotocol/server-filesystem`(stdio 传输),以及进程内 `StreamableHTTPServerTransport` 服务器(Streamable HTTP 传输)——命名空间下的发现、带点号名称的端到端规范化、执行往返、重复 `serverName` 拒绝、dispose。
- **快照**:刻意不做。MCP 工具不引入新的展示形态——它们以原始 `ToolDefinition` 注册,UI 消费方使用各自展示测试套件已固定的通用卡片兜底。将 MCP 服务器添加到某个可运行的快照组合会改变其已固定的系统提示词 fixture,且使每次回放依赖于 spawn 外部 MCP 服务器进程,而新增行为为零。如果后续变更为 MCP 工具引入专属渲染意图,该变更届时自行声明快照覆盖。
- **单元测试**(`tests/mcp-client.spec.ts`、`tests/apply.spec.ts`,mock MCP SDK):`publicToolName` 算法(干净名称、规范化、截断加 hash、确定性、不同标识的分离)、raw 与 public 的协议纪律、跨服务器与原生工具共存、重复 `serverName` 加载失败与预留释放、无效工具列表拒绝、注册代切换/回滚、重新同步失败时保留上一代注册、无损规范结果、丰富内容混合顺序、格式错误批次原子性、确切能力/存储拒绝、明确的非图片诊断、post-execute 策略优先级、取消,以及配置 schema 校验。100% 逐文件覆盖率门禁约束该包。
- **E2E**(`tests/mcp-client.e2e.ts`,无需密钥):使用真实 MCP 协议对接仓库内的 fixture(测试前置数据)服务器、`@modelcontextprotocol/server-everything` 和 `@modelcontextprotocol/server-filesystem`(stdio 传输),以及进程内 `StreamableHTTPServerTransport` 服务器(Streamable HTTP 传输)——命名空间下的发现、带点号名称的端到端规范化、执行往返、持久图片保存/读取且 base64 只保留在规范值中、缺少图片路由时明确拒绝、重复 `serverName` 拒绝,以及 dispose。
- **快照**:组装后的 ACP 示例负责传输可见的内联图片 transcript 与 Code Mode 图片转发 transcript;包 E2E 负责真实 MCP 协议,因为可运行快照必须保持无密钥且确定,而不是 spawn 第三方服务器包。MCP 工具卡片仍使用通用卡片兜底,无需包专属 UI 快照。
## 后果
@@ -206,3 +218,4 @@ v1 否决。它能防止跨服务器冲突,但无法将 MCP 注册与原生 ha
- **工具 schema 质量**:MCP 服务器可能暴露描述不佳的工具(模糊的描述、不完整的 JSON Schema)。harness 原样透传——垃圾进垃圾出;这是服务器作者的责任,不是桥接的。
- **Stdio 进程管理**:行为异常的 MCP 服务器如果忽略信号,可能卡住 dispose。Cordis fiber 的 dispose 具有有界的完全停稳过程;卡住的传输层最终会在框架层面超时。
- 崩溃恢复在[重连预算](2026-08-06-mcp-client-auto-reconnect.md)内自动进行;耗尽后或配置 `reconnect.enabled: false` 时回退为手动重新加载。
- 图片载荷只有通过共享持久附件存储和确切正向路由能力,才能进入模型上下文。音频与嵌入资源载荷仍只存在于执行局部,并附带明确诊断。

View File

@@ -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-20-code-mode-typed-tool-returns.md
2026-07-20-code-mode-typed-tool-returns.md: 747b33384238957190932e07a5f41d01cf1b65b6
2026-07-20-code-mode-typed-tool-returns.zh.md: 9ad41285ca76427c39a63ae54f80fe34e62f2520
2026-07-20-code-mode-typed-tool-returns.md: a8a251f5f0d39f4deedc42e08eb45c2b5fa11807
2026-07-20-code-mode-typed-tool-returns.zh.md: 3d7ae4f98c569d3908f14fc918aebe7190e7ce61

View File

@@ -14,7 +14,7 @@ The [canonical tool-output contract](../architecture/2026-07-20-canonical-tool-o
## Decision
Code Mode is a typed projection of the visible tool registry. Each successful binding resolves to the final canonical `JsonValue` after post-execute policy, while a failed binding rejects with a real `ToolCallError`. Intermediate values remain inside the run and cross the worker boundary whole. Only the outer `run_code` logs, completion value, or failure diagnostic enter the configurable output ledger and the model-facing spill pipeline.
Code Mode is a typed projection of the visible tool registry. Each successful binding resolves to the final canonical `JsonValue` after post-execute policy, while a failed binding rejects with a real `ToolCallError`. Intermediate values remain inside the run and cross the worker boundary whole. The outer `run_code` logs, completion value, or failure diagnostic enter the configurable output ledger and model-facing spill pipeline; a successfully settled sub-call whose final Native content contains an image additionally defers that complete ordered content through the parent result as logged, source-attributed context.
This note owns the return and failure contract layered on the original [Code Mode foundation](2026-06-15-code-mode.md). The unified schema vocabulary is owned by the [JSON-value schema DSL note](../architecture/2026-07-20-unified-json-value-schema-dsl.md), and Native rendering and policy projection remain owned by the canonical-output note.
@@ -49,7 +49,7 @@ declare const tools: {
### Binding values and failures
Before dispatch the bridge snapshots binding arguments as lossless JSON and snapshots the detached value again for an independent durable summary event. Host-side detachment, immutable execution, and output-schema projection all use iterative traversals rather than nested structured clone or recursive freezing. `undefined`, non-finite numbers, `-0`, sparse arrays, cycles, functions, and exotic objects reject that call before the tool runs. Successful dispatch returns `ToolExecutionResult.value`; Native `content`, metadata, and internal error information do not cross to the program.
Before dispatch the bridge snapshots binding arguments as lossless JSON and snapshots the detached value again for an independent durable summary event. Host-side detachment, immutable execution, and output-schema projection all use iterative traversals rather than nested structured clone or recursive freezing. `undefined`, non-finite numbers, `-0`, sparse arrays, cycles, functions, and exotic objects reject that call before the tool runs. Successful dispatch returns `ToolExecutionResult.value`; Native `content`, metadata, and internal error information do not cross to the program. Image-bearing final content is not a second binding value: the bridge ferries it after the outer result so the next model request can see the durable image, while post-execute block/content replacement remains authoritative and text-only results are not duplicated.
Code Mode declares its rejection capability on the runtime request as `{ name: "ToolCallError", memberNameProperty: "toolName" }`. The runtime Service Definition treats those names as data: the worker materializes and injects the actual constructor used for `tools` binding failures, so `error instanceof ToolCallError` works without making a generic runtime know about tools. The worker constructs failures and defines their public fields through module-captured error and property-definition intrinsics plus null-prototype descriptors, so model mutations cannot replace the promised rejection with a worker failure. The error has the standard `Error` message plus the exact `toolName`; it deliberately omits `ToolFailure.info`, error codes, and Native content. This is an exception contract for control flow, not a failure union for programmatic classification.
@@ -73,13 +73,13 @@ Temporary Cordis Plugins follow the same rule: `cordis_mount` returns `{ id, plu
### Persistence, metadata, and spill
Nested dispatch logs the sub-call's full rendered `content`/`isError` on `tool/code-dispatch` but does not persist canonical values. `tool/result` continues to persist only rendered content, error, and optional metadata. `SESSION_FORMAT_VERSION` remains unchanged (pre-release shape churn does not bump it) and replay cannot recreate intermediate canonical program values.
Nested dispatch logs the sub-call's full rendered `content`/`isError` on `tool/code-dispatch` but does not persist canonical values. `tool/result` continues to persist only rendered content, error, and optional metadata. A successful final content sequence containing an image is also wrapped in a source-attributed user message and deferred through the outer result; the normal session event makes that model-visible input reconstructable. `SESSION_FORMAT_VERSION` remains unchanged (pre-release shape churn does not bump it) and replay cannot recreate intermediate canonical program values.
The opaque `exec.parent` token marks nested calls. Presentation metadata and generic or tool-owned spill projections skip those calls because they have no direct result card and their canonical values never enter context. The outer `run_code` call alone produces one card and may spill its final post-policy presentation; `run_code` intentionally declares neither a result presenter nor presentation metadata, so UI adapters complete the card through their generic raw-content fallback using durable `tool/result.content`.
## Testing
Compile-time and snapshot tests pin exact `ToolArgsMap`, `ToolOutputMap`, `ToolName`, schema-to-TypeScript coverage, and exotic names. Registry and real-worker tests cover scalar, array, object, and null values; raw string rendering; absent `undefined`; consumer-declared real rejection classes, including `ToolCallError`; invalid arguments and completions, including intrinsic-looking forged prototypes; model-mutated JSON-boundary globals, prototype methods, constructor slots, and inherited descriptor fields; typed binding failures after those mutations; large uncapped intermediate bindings; nested spill suppression; exact and over-limit 64 MiB accounting; combined logs/value/diagnostic accounting; giant thrown stacks; bounded failure spill; hostile forged traffic; and built-package execution.
Compile-time and snapshot tests pin exact `ToolArgsMap`, `ToolOutputMap`, `ToolName`, schema-to-TypeScript coverage, exotic names, and assembled Code Mode image forwarding. Registry and real-worker tests cover scalar, array, object, and null values; raw string rendering; absent `undefined`; consumer-declared real rejection classes, including `ToolCallError`; invalid arguments and completions, including intrinsic-looking forged prototypes; model-mutated JSON-boundary globals, prototype methods, constructor slots, and inherited descriptor fields; typed binding failures after those mutations; large uncapped intermediate bindings; nested spill suppression; generic image-bearing context deferral plus post-execute replacement/block precedence; exact and over-limit 64 MiB accounting; combined logs/value/diagnostic accounting; giant thrown stacks; bounded failure spill; hostile forged traffic; and built-package execution.
Keyless real-worker integration tests pin the two handle workflows that prose results could not safely support. A background bash call returns its job id, the outer run settles, and a later run polls that id to completion; separate cases prove pre-abort creates no task, post-publication call abort preserves the task, foreground execution stays signal-coupled, and `job_kill` owns cancellation. A Cordis program reads an active or pending mount's id and `waitingFor` fields directly, unmounts by that id, and confirms removal without parsing rendered text.
@@ -93,6 +93,10 @@ Keyless real-worker integration tests pin the two handle workflows that prose re
**Silently inspect or truncate an oversized completion.** Rejected because changing a JSON value into a string is lossy and type-incorrect. The explicit `output-limit` failure lets the model choose a smaller result, while the retained logs and diagnostic can still use normal outer spill.
**Require each rich leaf tool to inspect `exec.parent` and defer itself.** Rejected because it couples leaf tools to Code Mode internals, duplicates policy handling, and misses future rich tools. The dispatch bridge owns generic forwarding from the already settled final result.
**Expose Native rich content as part of every binding's canonical value.** Rejected because a canonical value is lossless JSON and tool-specific; attachment blocks are a model projection with durable lifecycle semantics. Keeping the value and projection separate preserves typed programs without dropping images from later model context.
## Consequences
Code programs can compose tools through stable values instead of reverse-engineering Native prose. Native and Both Mode retain their existing text and UI presentation, while Code Mode receives output-schema types and exact runtime JSON. Tool authors must treat the canonical value as their programmatic API and put display-only formatting in the renderer.
@@ -107,6 +111,6 @@ The worker performs bounded-depth flat-wire transport and lossless validation bu
- Intermediate values have no byte cap and can exhaust process or worker memory through retention, flat-wire copies, or structured-clone cost.
- The 64 MiB hard cap applies only to the outer variable payloads, excluding fixed result-envelope syntax and presentation whitespace; spill cannot recover bytes rejected beyond that cap.
- Provider or executor acquisition limits may already have discarded source data before a canonical value reaches Code Mode.
- Unsupported MCP output schemas fall back to `JsonValue`; richer Native multimedia projection is deferred.
- Unsupported MCP output schemas fall back to `JsonValue`; admitted MCP images use the generic deferred projection, while audio and embedded-resource payloads remain diagnostic-only.
- There is one result card per outer `run_code`, never per nested call.
- Code failures expose `ToolCallError` message and tool name only, without a programmatic error-code union.

View File

@@ -14,7 +14,7 @@ Code Mode 过去会把每个嵌套工具的结果从 `ContentBlock[]` 重新投
## 决策
Code Mode 是可见工具注册表的类型化投影。每个成功的绑定调用都会解析为 post-execute 策略处理后的最终规范 `JsonValue`,失败的绑定调用则会以真正的 `ToolCallError` 拒绝 Promise。中间值只存在于本次运行中,并完整跨越 worker 边界。只有外层 `run_code` 的日志、完成值或失败诊断会进入可配置的输出账本以及面向模型的 spill 流水线。
Code Mode 是可见工具注册表的类型化投影。每个成功的绑定调用都会解析为 post-execute 策略处理后的最终规范 `JsonValue`,失败的绑定调用则会以真正的 `ToolCallError` 拒绝 Promise。中间值只存在于本次运行中,并完整跨越 worker 边界。外层 `run_code` 的日志、完成值或失败诊断会进入可配置的输出账本以及面向模型的输出落盘流水线;如果成功结算的子调用最终 Native 内容包含图片,其完整有序内容还会经父结果延后为写入日志且带来源归属的上下文。
本文档定义叠加在原始 [Code Mode 基础](2026-06-15-code-mode.md)之上的返回值与失败约定。统一 schema 词汇由 [JSON 值 schema DSL Agent Note](../architecture/2026-07-20-unified-json-value-schema-dsl.md)负责定义;Native 渲染与策略投影仍由规范输出 Agent Note 负责定义。
@@ -49,7 +49,7 @@ declare const tools: {
### 绑定值与失败
分发前,桥接层会把绑定参数快照为无损 JSON,再对分离后的值生成一次快照,供独立的持久摘要事件使用。宿主侧的值分离、执行数据的不可变处理与输出 schema 投影均采用迭代遍历,而不使用嵌套结构化克隆或递归冻结。`undefined`、非有限数、`-0`、稀疏数组、循环引用、函数和非普通对象都会使该调用在工具运行前被拒绝。成功分发会返回 `ToolExecutionResult.value`;Native `content`、元数据和内部错误信息不会传入程序。
分发前,桥接层会把绑定参数快照为无损 JSON,再对分离后的值生成一次快照,供独立的持久摘要事件使用。宿主侧的值分离、执行数据的不可变处理与输出 schema 投影均采用迭代遍历,而不使用嵌套结构化克隆或递归冻结。`undefined`、非有限数、`-0`、稀疏数组、循环引用、函数和非普通对象都会使该调用在工具运行前被拒绝。成功分发会返回 `ToolExecutionResult.value`;Native `content`、元数据和内部错误信息不会传入程序。含图片的最终内容不是第二份绑定值:桥接层会在外层结果之后转运它,使下一次模型请求可以看到持久图片;post-execute 阻止/内容替换仍具有权威性,纯文本结果不会重复。
Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProperty: "toolName" }` 声明其以异常拒绝 Promise 的能力。运行时 Service Definition 只把这些名称视为数据:worker 会动态生成并注入真正用于 `tools` 绑定失败的构造函数,因此无需让通用运行时了解工具,`error instanceof ToolCallError` 也能成立。worker 使用模块初始化时捕获的 Error 构造函数与属性定义内建方法,配合原型为 null 的属性描述符,构造失败对象并定义其公开字段,因此模型代码的修改不会把约定承诺的 reject 变成 worker 失败。该错误包含标准的 `Error` 消息和确切的 `toolName`,并有意省略 `ToolFailure.info`、错误代码与 Native 内容。这是一项用于控制流的异常约定,而不是供程序分类的失败联合。
@@ -73,13 +73,13 @@ Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProper
### 持久化、元数据与 spill
嵌套分发在 `tool/code-dispatch` 上记录子调用完整渲染后的 `content`/`isError`,但不会持久化规范值。`tool/result` 继续只持久化渲染后的内容、错误和可选元数据。`SESSION_FORMAT_VERSION` 保持不变(预发布阶段的形状变动不递增版本号),回放也无法重建程序的规范中间值。
嵌套分发在 `tool/code-dispatch` 上记录子调用完整渲染后的 `content`/`isError`,但不会持久化规范值。`tool/result` 继续只持久化渲染后的内容、错误和可选元数据。包含图片的成功最终内容序列还会包装成带来源归属的用户消息,并经外层结果延后;普通会话事件使该模型可见输入可以重建。`SESSION_FORMAT_VERSION` 保持不变(预发布阶段的形状变动不递增版本号),回放也无法重建程序的规范中间值。
不透明的 `exec.parent` token 用于标识嵌套调用。由于这些调用没有直接对应的结果卡片,而且其规范值永远不会进入上下文,展示元数据以及通用或工具自有的 spill 投影都会跳过它们。只有外层 `run_code` 调用会生成一张卡片,并且可能对 post-policy 处理后的最终展示执行 spill;`run_code` 有意既不声明结果展示器,也不声明展示元数据,因此 UI 适配器会通过通用的原始内容回退机制,使用持久化的 `tool/result.content` 补全该卡片。
## 测试
编译期测试与快照测试锁定了精确的 `ToolArgsMap`、`ToolOutputMap`、`ToolName`、schema 到 TypeScript 的覆盖范围以及特殊名称。注册表与真实 worker 测试覆盖标量、数组、对象和 null 值;字符串原文渲染;缺席的 `undefined`;消费方声明、实际用于拒绝 Promise 的异常类,包括 `ToolCallError`;无效参数与完成值,包括伪装为内建原型的伪造原型;模型代码修改过的 JSON 边界全局对象、原型方法、构造函数槽位,以及继承而来的属性描述符字段;上述修改后的类型化绑定失败;不设上限的大型中间绑定值;嵌套 spill 抑制;64 MiB 上限内外的精确计量;日志、值与诊断的组合计量;抛出的超大堆栈;有界失败的 spill;不可信对端伪造的流量;以及构建后包的执行。
编译期测试与快照测试锁定了精确的 `ToolArgsMap`、`ToolOutputMap`、`ToolName`、schema 到 TypeScript 的覆盖范围、特殊名称,以及组装后的 Code Mode 图片转发。注册表与真实 worker 测试覆盖标量、数组、对象和 null 值;字符串原文渲染;缺席的 `undefined`;消费方声明、实际用于拒绝 Promise 的异常类,包括 `ToolCallError`;无效参数与完成值,包括伪装为内建原型的伪造原型;模型代码修改过的 JSON 边界全局对象、原型方法、构造函数槽位,以及继承而来的属性描述符字段;上述修改后的类型化绑定失败;不设上限的大型中间绑定值;嵌套输出落盘抑制;通用含图片上下文延后以及 post-execute 替换/阻止优先级;64 MiB 上限内外的精确计量;日志、值与诊断的组合计量;抛出的超大堆栈;有界失败的输出落盘;不可信对端伪造的流量;以及构建后包的执行。
无密钥的真实 worker 集成测试锁定了自然语言结果无法安全支持的两种句柄工作流。后台 bash 调用返回 job id,外层运行结束,之后的运行再根据该 id 轮询直至任务完成;其他用例分别证明,预先中止不会创建任务、发布后的调用取消会保留任务、前台执行仍与信号耦合,并且由 `job_kill` 负责取消。Cordis 程序会直接读取 active 或 pending 挂载的 id 和 `waitingFor` 字段,按该 id 卸载,并在不解析渲染文本的情况下确认挂载已移除。
@@ -93,6 +93,10 @@ Code Mode 通过运行时请求中的 `{ name: "ToolCallError", memberNameProper
**静默检查格式化或截断过大的完成值:**不予采纳。把 JSON 值改成字符串既有损又违反类型。显式的 `output-limit` 失败让模型可以选择返回更小的结果,而保留的日志和诊断仍可使用普通的外层 spill 机制。
**要求每个丰富叶子工具检查 `exec.parent` 并自行延后。** 不予采用,因为这会把叶子工具与 Code Mode 内部机制耦合、重复策略处理,并遗漏未来丰富工具。分发桥接层负责从已经结算的最终结果通用转发。
**把 Native 丰富内容暴露为每个绑定规范值的一部分。** 不予采用,因为规范值是无损 JSON 且由工具定义;附件块是具有持久生命周期语义的模型投影。保持值与投影分离,既能保留类型化程序,也不会从后续模型上下文中丢弃图片。
## 后果
Code Mode 程序可以通过稳定值组合工具,无需逆向解析 Native 自然语言。Native 和 Both Mode 保留现有文本与 UI 展示,Code Mode 则获得输出 schema 类型和精确的运行时 JSON。工具作者必须把规范值视为程序化 API,并将仅用于展示的格式化放入渲染器。
@@ -107,6 +111,6 @@ worker 会以嵌套深度有界的扁平协议格式传输数据并执行无损
- 中间值没有字节上限,可能因值的保留、扁平协议格式副本或结构化克隆开销而耗尽进程或 worker 内存。
- 64 MiB 硬上限只适用于外层可变负载,不计固定的结果封装语法与展示空白;spill 无法恢复超出该上限后被拒绝的字节。
- 提供方或执行器的采集上限可能在规范值到达 Code Mode 前就已丢弃部分源数据。
- 不支持的 MCP 输出 schema 会回退为 `JsonValue`;更丰富的 Native 多媒体投影留待后续实现。
- 不支持的 MCP 输出 schema 会回退为 `JsonValue`;已准入的 MCP 图片使用通用延后投影,而音频和嵌入资源载荷仍只提供诊断。
- 每个外层 `run_code` 只有一张结果卡片,嵌套调用不会各自生成卡片。
- Code Mode 失败只暴露 `ToolCallError` 的消息与工具名,不提供程序可用的错误代码联合。

View File

@@ -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-22-web-multimodal-image-input-and-durable-attachments.md
2026-07-22-web-multimodal-image-input-and-durable-attachments.md: fcbe301f705f8e01095eac9a02329904626af843
2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: 338f27c6db934092b62ed6107f8706fe451b6a4b
2026-07-22-web-multimodal-image-input-and-durable-attachments.md: 5162e0d6f63715ffd83cff1a622e07c110a7938e
2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: d3a5cf18a7d308753d5e46741cea2b2993536f70

View File

@@ -16,7 +16,7 @@ Peer products converge on an attachment rail above the editor, but their storage
## Decision
Pasted or dropped raster images are the Web composer's first consumer of a durable attachment capability. Unsent files remain temporary client-owned draft state. The host validates and durably commits every accepted user image before appending its message event. A provider adapter that produces structured image output must durably commit the output before appending its assistant block. Canonical user and assistant content contains only role-neutral `ImageBlock` references.
Pasted or dropped raster images are the Web composer's first consumer of a durable attachment capability. Unsent files remain temporary client-owned draft state. Every rich-content intake adapter decodes its wire blocks, proves route capability, and delegates the complete image batch to the attachment service before appending its message event. A provider adapter that produces structured image output must durably commit the output before appending its assistant block. Canonical user and assistant content contains only role-neutral `ImageBlock` references.
Version one supports PNG, JPEG, WebP, and GIF paste and drag-and-drop, image-only or mixed prompts, historical user and assistant image rendering, and original-image preview on a single click (display and interaction specifics superseded in part by the [attachment-display alignment note](2026-08-11-web-attachment-display-alignment.md)). File picking, generic files, PDF, audio, video, image copying, and a custom context menu remain separate follow-ups.
@@ -114,7 +114,7 @@ type PromptInputPart =
}
```
Base64 crosses JSON-RPC once and is discarded after persistence. The host validates canonical base64, image count, aggregate bytes, individual bytes, the declared MIME against a fully decoded raster, intrinsic dimensions, and decoded-pixel count. It awaits the seam's storage-free `validateImage` for every batch member before saving any member, so one malformed image cannot strand the batch's valid members as unreferenced objects. Storage commits then run in submission order to bound full-raster decoder memory. If a later storage I/O operation fails, the host appends no user event, but an earlier immutable content-addressed object may remain unreferenced; version one leaves cleanup to future reference-aware garbage collection instead of adding destructive rollback to the deduplicated store. Only after every image succeeds does it call the agent with normalized text and durable image blocks in the submitted order. A failure exposes no attachment path or raw bytes.
Base64 crosses a wire boundary once and is discarded after persistence. Each front door validates canonical base64 and declared MIME shape, then calls `AttachmentStore.saveImages()` with the whole decoded batch. The service owns image count, aggregate bytes, individual bytes, fully decoded raster/MIME agreement, intrinsic dimensions, and decoded-pixel count; it validates every batch member before saving any member, so one malformed image cannot strand the batch's valid members as unreferenced objects. Storage commits then run in submission order to bound full-raster decoder memory. If a later storage I/O operation fails, the caller appends no model-visible event and receives no partial references, but an earlier immutable content-addressed object may remain unreferenced; version one leaves cleanup to future reference-aware garbage collection instead of adding destructive rollback to the deduplicated store. Only after every image succeeds does the front door call the agent with normalized text and durable image blocks in wire order. A failure exposes no attachment path or raw bytes.
`session.attachment` is a read-only, session-scoped endpoint. The host serves bytes only when a durable event in that session references the requested attachment identifier. The client deduplicates loads by session and attachment identifier while that session is rendered, revokes resolved URLs on rendered-session disposal, and rejects invalidated late loads before allocating an object URL so an unmounted session or disposed service cannot repopulate the cache.
@@ -128,7 +128,7 @@ The Pi-AI adapter is the first visual-input route: it resolves `ctx.attachments`
Core supports structured assistant image blocks, but no current production provider route is certified for image output. Any future output-capable adapter must retrieve provider bytes under bounded size and time policy, validate them through the same attachment service, persist them, and only then publish the atomic `ImageBlock`. A URL in assistant Markdown remains text and is never downloaded automatically.
Provider-neutral token estimation does not guess visual pricing from image dimensions; provider-reported usage remains authoritative. ACP renders an explicit image marker until that protocol API gains native image support rather than silently omitting the block.
Provider-neutral token estimation does not guess visual pricing from image dimensions; provider-reported usage remains authoritative. ACP advertises image prompts only when its configured exact route and attachment deployment can accept them, persists inline input before publishing the user event, and re-reads committed assistant image references for native ACP image updates. MCP keeps canonical raw blocks for programmatic callers while projecting admitted images to durable core blocks; Code Mode carries any settled image-bearing sub-result through the outer result as logged source-attributed context.
Compaction replays the selected conversation prefix, including image references, into the configured summarization route. A visual-capable route resolves those references through its adapter; a text-only route fails explicitly instead of silently dropping the visual context. The synthesized checkpoint remains text-only, and `compaction-basic` rejects image summary output with `UNSUPPORTED_CONTENT`.
@@ -148,22 +148,24 @@ Malformed base64, unsupported or mismatched media, truncated image payloads, exc
| Surface | Responsibility |
| --- | --- |
| `packages/attachment/attachment` | Opaque attachment identifier, image reference, limits, failures, and `ctx.attachments` service. |
| `packages/attachment/attachment` | Opaque attachment identifier, image reference, limits, failures, and single/batch admission through `ctx.attachments`. |
| `packages/attachment/attachment-local` | Private content-addressed storage, complete raster decoding, integrity verification, and configuration. |
| `packages/llm/llm` | Role-neutral `ImageBlock` and input-modality metadata. |
| `packages/llm/llm-pi-ai` | Resolve durable supported image input into native provider content. |
| `packages/llm/llm-deepseek` | Reject image content explicitly. |
| `packages/compaction/compaction-basic` | Preserve images in summary input and reject non-text checkpoint output explicitly. |
| `packages/host/apiproxy` and `packages/bundle/base` | Narrow upload wire, persist-before-event ordering, session-authorized reads, limits and model preflight, plus default profile composition. |
| `packages/host/apiproxy` and `packages/bundle/base` | Narrow upload wire, shared batch admission, limits and routed-model preflight, persist-before-event ordering, session-authorized reads, and default profile composition. |
| `packages/client/connection` and `packages/client/runtime` | Bounded request buffering, wire types, fixture images, prompt uploads, attachment reads, and durable-reference folding. |
| `packages/client/ui-conversation` | Per-session draft images, attachment rail, user and assistant image controls, and original preview. |
| `packages/acp/acp` | Explicit fallback rendering for image blocks. |
| `packages/acp/acp` | Conditional native image capability, atomic inline-image admission, and verified assistant-image delivery. |
| `packages/mcp/mcp-client` | Lossless canonical MCP results plus capability-gated durable image projection and explicit diagnostics for unsupported rich blocks. |
| `packages/core/tools` | Generic Code Mode forwarding of settled image-bearing sub-results after the outer result. |
The attachment packages form the interface/implementation side of one capability seam. Composer behavior stays in the conversation object layer, provider conversion stays in adapters, and no change is required in `agent-loop`.
### Implementation
The implemented slice includes the attachment seam, role-neutral image block, Pi-AI input conversion, DeepSeek rejection, durable host ordering, Web upload/read protocol, current image-limit enforcement, bounded Web request bodies, in-memory draft images, paste/drop rail, user and assistant history rendering, single-click preview, compaction handling, and keyless assembled Web coverage.
The implemented slice includes the attachment seam and shared batch admission, role-neutral image block, Pi-AI input conversion, DeepSeek rejection, durable Web/ACP/MCP ordering, Web upload/read protocol, conditional ACP image wire support, lossless MCP canonical results with durable image projection, generic Code Mode rich-result forwarding, current image-limit enforcement, bounded Web request bodies, in-memory draft images, paste/drop rail, user and assistant history rendering, single-click preview, compaction handling, and keyless assembled Web and ACP coverage.
No compatibility shim is required for the pre-release prompt wire; all call sites and fixtures change with the introducing slice.
@@ -193,12 +195,25 @@ Composer presentation can use a generic attachment rail, but provider semantics
UI state can be stale and does not protect direct SDK, ACP, replay, or uncatalogued model paths. Silent filtering changes user intent. Provider enforcement remains mandatory, while UI checks are optional earlier feedback.
### Add a generic RichContent service above the core content vocabulary
Rejected because the core already has the role-neutral `ContentBlock` vocabulary and attachment references. A second generic service would duplicate ordering, capability, logging, and lifetime semantics while still requiring each wire adapter to parse its own protocol. Narrow image adapters around the existing core preserve ownership and leave audio/resources to earn their own lifecycle contracts.
### Normalize MCP results into core content as the canonical tool value
Rejected because Code Mode and programmatic callers need the complete MCP JSON blocks and optional `structuredContent`; replacing that value with a Native projection would make the bridge lossy. MCP retains the protocol value and prepares a separate model projection, with final post-execute policy remaining authoritative.
### Perform attachment reads and writes inside synchronous output renderers
Rejected because tool renderers are pure, synchronous, and replayable. MCP prepares image projection during async execution and installs it only at the registry's finalization boundary; ACP performs async admission and output conversion in its transport lifecycle. Code Mode forwarding observes the already settled final content instead of giving individual image tools private parent-token behavior.
## Testing
- Storage tests cover content-addressed deduplication, private permissions, admission failures, corruption/missing-object failures, and reading history after deployment limits are lowered.
- Host and protocol tests cover persist-before-event ordering, absence of base64 in logs, session-scoped authorization, capability rejection, upload limits, bounded HTTP request bodies, image-admission/model-selection races (queued and steering placements), pending publication, idle release without publication, text-only queue edits, and selection against current derived history after compaction.
- Client unit tests cover paste and drop, mixed clipboard text, image-only send, draft restoration, ordering, draft/session-scope/application object-URL cleanup, and a deferred historical read that completes after disposal; the keyless assembled built-client lane (`apps/web/tests/image-display.snapshot.ts`, `DSH_EXAMPLE_MODE=lib pnpm run test:snapshot`) covers the historical user and assistant galleries over the authorized attachment route, the original-size lightbox, and the composer paste rail.
- Adapter and compaction tests cover native Pi-AI image conversion, late attachment-service composition, text-only rejection, recursively nested tool-result images, preserved summary input, and explicit image-output rejection.
- Attachment, MCP, ACP, and Code Mode tests cover all-member validation before writes, mixed text/image ordering, no inline base64 in durable events, exact route-capability gates, explicit unsupported-content diagnostics, post-execute replacement/block precedence, cancellation during admission, verified assistant-image delivery, and generic nested-image forwarding. A keyless assembled ACP snapshot sends a real inline PNG and pins only its durable reference in the session log.
- A credentialed real-API test sends a PNG through the Anthropic `claude-opus-4-8` route and requires the model to identify its QR code.
- The current production adapter set has no certified image-output route; output-provider certification remains outside version one.

View File

@@ -16,7 +16,7 @@ Status: implemented
## 决策
粘贴或拖放的光栅图片是 Web 输入区对持久附件能力的首个应用场景。未发送文件仍是由客户端持有的临时草稿状态。宿主在追加相应消息事件前,校验并持久提交每张已接受的用户图片。生成结构化图片输出的提供方适配器在追加相应助手块前,也必须持久提交输出。规范用户内容与助手内容只包含角色无关的 `ImageBlock` 引用。
粘贴或拖放的光栅图片是 Web 输入区对持久附件能力的首个应用场景。未发送文件仍是由客户端持有的临时草稿状态。每个丰富内容接入适配器都会解码自身协议块、证明路由能力,并在追加消息事件前把完整图片批次委托给附件服务。生成结构化图片输出的提供方适配器在追加相应助手块前,也必须持久提交输出。规范用户内容与助手内容只包含角色无关的 `ImageBlock` 引用。
第一版支持粘贴和拖放 PNG、JPEG、WebP 与 GIF,支持仅图片或混合提示词,支持渲染历史用户图片与助手图片,并支持单击预览原图(展示与交互细节部分由[附件展示对齐 Note](2026-08-11-web-attachment-display-alignment.md)取代)。文件选择、通用文件、PDF、音频、视频、图片复制和自定义上下文菜单仍分别作为后续工作。
@@ -114,7 +114,7 @@ type PromptInputPart =
}
```
Base64 只跨越一次 JSON-RPC,并在持久化后丢弃。宿主会校验规范 base64、图片数量、总字节数、单张图片字节数、声明的 MIME 与完整解码后的光栅图片是否一致、固有尺寸和解码像素数。它会在保存任何成员之前,等待seam 上不触碰存储的 `validateImage` 完成对每个批次成员的校验,因此一张畸形图片不会把批次中的有效成员留成无引用对象。随后按提交顺序执行存储提交,以限制完整光栅解码器的内存占用。如果后续存储 I/O 操作失败,宿主不会追加用户事件,但先前的不可变内容寻址对象可能保持无引用状态;第一版将清理留给未来按引用感知的垃圾回收,而不向去重存储添加破坏性回滚。只有每张图片都成功后,宿主才会用规范化文本和按提交顺序排列的持久图片块调用 agent。失败时不公开任何附件路径或原始字节。
Base64 只跨越一次协议边界,并在持久化后丢弃。每个入口都会校验规范 base64 与声明的 MIME 形状,再用完整解码批次调用 `AttachmentStore.saveImages()`。服务负责图片数量、总字节数、单张图片字节数、声明 MIME 与完整解码后的光栅图片是否一致、固有尺寸和解码像素数;它会在保存任何成员之前校验每个批次成员,因此一张畸形图片不会把批次中的有效成员留成无引用对象。随后按提交顺序执行存储提交,以限制完整光栅解码器的内存占用。如果后续存储 I/O 操作失败,调用方不会追加模型可见事件,也不会收到部分引用,但先前的不可变内容寻址对象可能保持无引用状态;第一版将清理留给未来按引用感知的垃圾回收,而不向去重存储添加破坏性回滚。只有每张图片都成功后,入口才会用规范化文本和按协议顺序排列的持久图片块调用 agent。失败时不公开任何附件路径或原始字节。
`session.attachment` 是只读且限定于会话作用域的端点。只有该会话中的持久事件引用了所请求的附件标识符,宿主才提供字节。会话处于渲染状态时,客户端会按会话和附件标识符对加载操作去重;已渲染会话释放时会撤销已解析的 URL,并在分配对象 URL 前拒绝已失效的延迟加载,以免已卸载的会话或已释放的服务重新写入缓存。
@@ -128,7 +128,7 @@ Pi-AI 适配器是首条视觉输入路径:它在请求时解析 `ctx.attachme
核心层支持结构化助手图片块,但当前没有任何生产提供方路径通过图片输出认证。未来任何支持输出的适配器都必须在有界的大小和时间策略下获取提供方字节,通过同一个附件服务校验并持久化字节,之后才能以原子方式发布 `ImageBlock`。助手 Markdown 中的 URL 仍是文本,绝不自动下载。
提供方无关的 token 估算不会根据图片尺寸猜测视觉定价;提供方返回的用量仍是权威值。在 ACP(Agent Client Protocol)接口原生支持图片前,ACP 会渲染明确的图片标记,而不是静默省略该块。
提供方无关的 token 估算不会根据图片尺寸猜测视觉定价;提供方返回的用量仍是权威值。只有配置的确切路由与附件部署可以接受图片时,ACP(Agent Client Protocol)才公布图片提示词能力;它会在发布用户事件前持久化内联输入,并重新读取已提交的助手图片引用来发送原生 ACP 图片更新。MCP 为程序化调用方保留规范原始块,同时把已准入图片投影为持久核心块;Code Mode 会把任何已经结算且含图片的子结果经外层结果转运为带来源归属且写入日志的上下文。
压缩会把选定的会话前缀(包含图片引用)回放到已配置的摘要生成路径中。支持视觉的路径会通过适配器解析这些引用;仅文本路径会明确失败,而不是静默丢弃视觉上下文。合成的检查点仍仅包含文本,`compaction-basic` 会以 `UNSUPPORTED_CONTENT` 拒绝包含图片的摘要输出。
@@ -148,22 +148,24 @@ Pi-AI 适配器是首条视觉输入路径:它在请求时解析 `ctx.attachme
| 接口 | 职责 |
| --- | --- |
| `packages/attachment/attachment` | 不透明附件标识符、图片引用、限制、错误和 `ctx.attachments` 服务。 |
| `packages/attachment/attachment` | 不透明附件标识符、图片引用、限制、错误,以及通过 `ctx.attachments` 提供的单张/批量准入。 |
| `packages/attachment/attachment-local` | 私有内容寻址存储、完整光栅解码、完整性校验和配置。 |
| `packages/llm/llm` | 角色无关的 `ImageBlock` 和输入模态元数据。 |
| `packages/llm/llm-pi-ai` | 将持久且受支持的图片输入解析为提供方原生内容。 |
| `packages/llm/llm-deepseek` | 明确拒绝图片内容。 |
| `packages/compaction/compaction-basic` | 在摘要输入中保留图片,并明确拒绝非文本检查点输出。 |
| `packages/host/apiproxy` 和 `packages/bundle/base` | 范围狭窄的上传协议、先持久化再追加事件的顺序、会话授权读取、限制和模型前置检查,以及默认 profile 组合。 |
| `packages/host/apiproxy` 和 `packages/bundle/base` | 范围狭窄的上传协议、共享批量准入、限制和路由模型前置检查、先持久化再追加事件的顺序、会话授权读取,以及默认 profile 组合。 |
| `packages/client/connection` 和 `packages/client/runtime` | 有界请求缓冲、协议类型、fixture(测试前置数据)图片、提示词上传、附件读取和持久引用折叠。 |
| `packages/client/ui-conversation` | 每个会话的草稿图片、附件栏、用户与助手图片控件和原图预览。 |
| `packages/acp/acp` | 图片块的明确兜底渲染。 |
| `packages/acp/acp` | 条件式原生图片能力、原子内联图片准入,以及经过校验的助手图片交付。 |
| `packages/mcp/mcp-client` | 无损规范 MCP 结果、经能力门禁的持久图片投影,以及针对不受支持丰富块的明确诊断。 |
| `packages/core/tools` | 在外层结果之后通用转发已经结算且含图片的 Code Mode 子结果。 |
附件包构成一个能力 seam 的接口与实现侧。输入区行为留在会话对象层,提供方转换留在适配器中,无需修改 `agent-loop`。
### 实现
已实现的范围包括附件 seam、角色无关的图片块、Pi-AI 输入转换、DeepSeek 拒绝、宿主持久化顺序、Web 上传与读取协议、当前图片限制执行、大小受限的 Web 请求体、内存草稿图片、粘贴与拖放附件栏、用户与助手历史图片渲染、单击预览、压缩处理,以及组装后无需密钥的 Web 覆盖。
已实现的范围包括附件服务边界与共享批量准入、角色无关的图片块、Pi-AI 输入转换、DeepSeek 拒绝、Web/ACP/MCP 的持久化顺序、Web 上传与读取协议、条件式 ACP 图片协议支持、无损 MCP 规范结果与持久图片投影、通用 Code Mode 丰富结果转发、当前图片限制执行、大小受限的 Web 请求体、内存草稿图片、粘贴与拖放附件栏、用户与助手历史图片渲染、单击预览、压缩处理,以及组装后无需密钥的 Web 与 ACP 覆盖。
预发布提示词协议不需要兼容包装层;引入相应切片时会同时修改所有调用点和 fixture。
@@ -193,12 +195,25 @@ Pi-AI 适配器是首条视觉输入路径:它在请求时解析 `ctx.attachme
UI 状态可能陈旧,也无法保护直接 SDK、ACP、回放或未收录模型的路径。静默过滤会改变用户意图。提供方强制检查仍是必需项,UI 检查则是可选的提前反馈。
### 在核心内容词汇之上添加通用 RichContent 服务
不予采用,因为核心已经拥有角色无关的 `ContentBlock` 词汇与附件引用。第二套通用服务会重复顺序、能力、日志和生命周期语义,同时每个协议适配器仍需解析自身协议。围绕现有核心构建范围狭窄的图片适配器,可以保持归属清晰,并让音频/资源在确有需要时建立自己的生命周期契约。
### 把 MCP 结果规范化为核心内容,并将其作为规范工具值
不予采用,因为 Code Mode 和程序化调用方需要完整 MCP JSON 块及可选 `structuredContent`;用 Native 投影替换该值会让桥接有损。MCP 保留协议值,并另行准备模型投影;最终 post-execute 策略仍具有权威性。
### 在同步输出渲染器中执行附件读写
不予采用,因为工具渲染器必须纯净、同步且可回放。MCP 在异步执行期间准备图片投影,只在注册表最终化边界安装;ACP 在自己的传输生命周期中执行异步准入和输出转换。Code Mode 转发观察已经结算的最终内容,而不是让各图片工具各自处理私有父 token 行为。
## 测试
- 存储测试覆盖内容寻址去重、私有权限、准入失败、对象损坏或缺失时的失败,以及收紧部署限制后读取历史数据。
- 宿主与协议测试覆盖先持久化再追加事件的顺序、日志中不含 base64、会话作用域授权、能力拒绝、上传限制、大小受限的 HTTP 请求体、图片准入与模型选择的竞态(排队与 steering 两种放置)、待发布状态、未发布即空闲时的门槛释放、仅文本的队列编辑,以及压缩后依据当前派生历史进行的选择。
- 客户端单元测试覆盖粘贴与拖放、混合剪贴板文本、仅图片发送、草稿恢复、顺序、草稿、会话作用域和应用层级的对象 URL 清理,以及一项在释放后才完成的延迟历史读取;keyless 的组装后构建产物通道(`apps/web/tests/image-display.snapshot.ts`,`DSH_EXAMPLE_MODE=lib pnpm run test:snapshot`)覆盖经授权附件路由渲染的历史用户与助手图片画廊、原图 lightbox,以及 composer 粘贴缩略图条。
- 适配器与压缩测试覆盖 Pi-AI 原生图片转换、后置附件服务组合、仅文本拒绝、递归嵌套在工具结果中的图片、保留摘要输入,以及明确拒绝图片输出。
- 附件、MCP、ACP 与 Code Mode 测试覆盖写入前校验全部成员、图文混合顺序、持久事件不含内联 base64、确切路由能力门禁、明确的不支持内容诊断、post-execute 替换/阻止优先级、准入期间取消、经过校验的助手图片交付,以及通用嵌套图片转发。组装后的无密钥 ACP 快照发送真实内联 PNG,并在会话日志中只固定其持久引用。
- 需要凭据的实际 API 测试会通过 Anthropic `claude-opus-4-8` 路径发送一张 PNG,并要求模型识别其中的二维码。
- 当前生产适配器集合没有经过认证的图片输出路由;输出提供方认证仍不在第一版范围内。

View File

@@ -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/feature/2026-08-11-collapsible-ask-user-question-card.md
2026-08-11-collapsible-ask-user-question-card.md: 5c7e62749e63a6042285b79751c400ba09038b49
2026-08-11-collapsible-ask-user-question-card.zh.md: 5f4b5851e4b595e634841bf87827db70fe928afb

View File

@@ -0,0 +1,32 @@
# Agent Note: Collapsible Ask-User Question Card
Status: implemented
English | [中文](2026-08-11-collapsible-ask-user-question-card.zh.md)
## Problem
`dsh`'s ask-user takeover renders the pending question set as a bottom card capped at `min(60vh, 520px)`, so a long batch or a user who wants to re-read the conversation above before deciding has no way to reduce the card — the conversation above becomes hard to read because only a few lines peek out at the top.
## Decision
Add a minimize/maximize toggle to the question card header, next to the existing dismiss action. Collapsing hides the option body and the footer actions, leaving a header strip (eyebrow, title, both icon buttons) so the user still sees that a question is pending; expanding restores the full card.
- State lives in `QuestionFlow` local state (`minimized`), so drafts and the current question index survive collapse/expand — nothing is re-derived or reset, and the answers already picked remain submit-ready.
- The toggle is a plain `IconChevronDownOutline14` / `IconChevronUpOutline14` pair on the existing 24px icon-button grid; `aria-expanded` reflects the card state and the label flips between `nav.minimize` / `nav.maximize` (the collapsed button reads "expand" for screen readers).
- While minimized the option body and footer are unmounted (`{!minimized && ...}`), so no hidden interactive surface remains in the a11y tree.
- The collapse button is disabled while a submit/cancel is in flight (`busy !== null`), matching the dismiss button's existing guard.
- CSS: `.cardMinimized` drops the `max-height` cap and hides `.body` / `.footer`; `.header` gains bottom padding so the strip is not cramped.
- Scope: only the generic question flow (`QuestionFlow`) gets the toggle. The plan-review card (`PlanReviewPanel`) is a different shape (one decision over one plan) and keeps its current layout.
## Consequences
- Users can shrink the question card to read the conversation, then expand to answer — drafts and position are preserved because the state lives in the flow component, not in the DOM.
- The minimize action is visually adjacent to dismiss; both share the icon button style, so the header stays balanced.
- Product copy additions are confined to the `question` locale namespace (`nav.minimize` / `nav.maximize`), paired zh/en per the dictionary contract.
## Alternatives considered
- **Auto-collapse on scroll**: collapsing the card when the user scrolls the conversation would reclaim space without a button, but it fights the user mid-interaction and hides the pending-question signal unexpectedly; an explicit toggle keeps the decision with the user.
- **Resizable card**: a drag handle would let users size the card freely, but it is more machinery than the ask needs and does not address "I want the card out of the way entirely".
- **Persisting the collapsed state per session**: nice-to-have, but the ask is per-interaction; persisting adds storage and sync complexity without a clear win for this surface.

View File

@@ -0,0 +1,32 @@
# Agent Note: 可收起的提问卡片
Status: implemented
[English](2026-08-11-collapsible-ask-user-question-card.md) | 中文
## Problem
`dsh` 的 ask-user 接管界面把待回答的问题组渲染为底部卡片,高度上限为 `min(60vh, 520px)`;当问题批次较长、或用户想先阅读上方的会话记录再决定时,卡片会占满大部分视口且无法缩小——上方会话几乎被遮住,只能看到顶部几行。
## Decision
在提问卡片头部(现有的"放弃整组问题"按钮旁)增加收起/展开切换按钮。收起时隐藏选项主体和底部操作区,只保留一条头部(eyebrow、标题、两个图标按钮),用户仍能看到"有未答问题"的信号;展开后恢复完整卡片。
- 状态存放在 `QuestionFlow` 的本地 state(`minimized`),因此收起/展开不会丢失草稿和当前题目索引——已选答案仍可直接提交。
- 切换按钮使用 `IconChevronDownOutline14` / `IconChevronUpOutline14`,复用现有 24px 图标按钮网格;`aria-expanded` 反映卡片状态,文案在 `nav.minimize` / `nav.maximize` 之间切换(收起后按钮对读屏器显示为"展开")。
- 收起时选项主体和底部通过 `{!minimized && ...}` 卸载,a11y 树中不残留隐藏的可交互面。
- 提交/取消进行中(`busy !== null`)时收起按钮禁用,与现有放弃按钮的守卫一致。
- CSS:`.cardMinimized` 去掉 `max-height` 上限并隐藏 `.body` / `.footer`;`.header` 增加底部 padding,避免折叠后过于局促。
- 范围:只有通用提问流(`QuestionFlow`)获得该切换。计划评审卡片(`PlanReviewPanel`)是另一种形态(对一个计划做一次决策),保持现有布局。
## Consequences
- 用户可以缩小提问卡片以阅读会话,再展开作答——草稿和位置因状态存放在流程组件中而得以保留。
- 收起动作紧邻放弃按钮,二者共用图标按钮样式,头部保持平衡。
- 新增产品文案仅落在 `question` locale 命名空间(`nav.minimize` / `nav.maximize`),按字典契约中英成对。
## Alternatives considered
- **滚动时自动收起**:用户滚动会话时自动折叠卡片可以省空间,但会在交互中途与用户对抗,并意外隐藏"待答问题"信号;显式切换把决定权交给用户。
- **可拖拽调整大小**:拖拽手柄让用户自由调整卡片大小,但比需求所需的机制更复杂,也没有解决"让卡片完全让开"的诉求。
- **按会话持久化折叠状态**:锦上添花,但提问是单次交互;持久化引入存储与同步复杂度,对这个界面没有明确收益。

View File

@@ -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/feature/2026-08-20-openrouter-cost-balance-readouts.md
2026-08-20-openrouter-cost-balance-readouts.md: 339acc4140eb6c34d1c3910a93babbeda9a28cac
2026-08-20-openrouter-cost-balance-readouts.zh.md: 16606ae58b346a1eeb10f703eae748665f64ea83

View File

@@ -0,0 +1,90 @@
# Agent Note: OpenRouter cost and balance readouts in the web GUI
Status: implemented
English | [中文](2026-08-20-openrouter-cost-balance-readouts.zh.md)
> Scope: two new packages — `@deepseek-ai/dsh-openrouter-usage` (host gateway +
> projection) and `@deepseek-ai/dsh-client-ui-openrouter-usage` (web readouts) —
> composed into the web profile. The gateway resolves the same
> `OPENROUTER_API_KEY` credential that OpenRouter LLM routing uses.
## Problem
A user running the web GUI with LLM calls routed through OpenRouter has no
visibility into what those calls cost or how much account credit remains.
Token counts exist in the session log (`assistant/message.usage`, `assistant/chunk`
usage chunks) and the token-meter projection, but there is no monetary figure
anywhere in the repository, and no durable record of OpenRouter's per-model
pricing or account balance. The harness deliberately discards the provider's
own cost metadata (the pi-ai adapter zeroes `usage.cost`), so cost had to be
reconstructed from token counts against a pricing table fetched from
OpenRouter's public API.
## Decision
**Cost is a client-facing read-model projection, never a logged or model-visible
fact.** The gateway fetches OpenRouter's model pricing table (`GET /models`,
`pricing.prompt`/`completion` USD per token, flat `request` fee, disclosed
cache rates) and folds each session's logged usage against it in the
`openRouterCost` `SessionProjectionMap` entry. Balance comes from OpenRouter's
canonical balance endpoint `GET /credits` (available credits = `total_credits`
minus the spent `total_usage`, the figure the dashboard surfaces), merged with
`GET /auth/key` for the key label and the monthly token budget
(`usage`/`limit`); a standard `sk-or-v1` key returns **no** `credits` field on
`/auth/key`, so that endpoint alone would always read a null balance. Both
fetches go through plain `globalThis.fetch` with `redirect: 'error'`, the host
standard for credential-bearing outbound calls.
**The web surface is two slot entries.** A per-session cost readout registers
into `conversation.composer.dock` (the shipped stats line's dock), reading the
`openRouterCost` projection through the `useProjection` framework seat; a live
balance badge registers into `sidebar.footer.action`, polling the balance
Remote on a 60s client-side interval. Both hide themselves when their data is
absent: cost hides before any priced step, balance renders an empty dashes
marker when the snapshot has no value yet.
**The API key is a credential reference, seeded identically to routing.**
`apiKeyEnv` (default `OPENROUTER_API_KEY`) resolves through the credentials
seam with an environment fallback exactly like `dsh-web-search-deepseek`,
captured per refresh in a thunk so a credentials or settings change applies on
the next tick without a rebuild.
**No key → no fetch, and the plugin stays inert.** No key produces an empty
pricing table (every step stays unpriced), an all-`null` balance, and the
client reads null-ish values it renders as absent — the same dormant posture
as the LLM adapters, not a load-time failure.
## Alternatives considered
- **Persisting per-request cost as a session event** — cost is derived from
events already logged (`assistant/message` usage, `assistant/chunk` usage);
an event would duplicate accounting, be priced at the wrong time (pricing
changes with `pricingRefreshMs`), and leak a client-facing figure into the
durable log. The projection stays the single read-model home.
- **Balance via `/auth/key` alone** — the plan's original shape; the real
endpoint response for a standard `sk-or-v1` key carries no `credits` field,
verified against OpenRouter's live API, so the snapshot would always read
null. `/credits` is the authoritative balance endpoint and `/auth/key` only
supplements label + monthly budget.
- **Reading `usage.cost` from pi-ai** — the harness deliberately discards
provider cost metadata (the adapter pins `NO_COST`); reconstructing from
tokens × table is the only available path and is documented as an estimate.
- **A settings card and a dedicated credentials UI** — deferred: the readouts
work with the existing credential the routing already uses; a card adds
surface without changing behavior.
## Consequences
`test:gui` stays green (3800 tests) and both packages carry unit tests plus a
real-composition loader test that boots the shipped YAML shape through the
vendored Loader, resolves the key from a credentials document, and asserts
the mocked `/models` and `/credits` fetches populate the projection and the
snapshot. The numbers are an estimate, not itemized billing: the projection
prices `tokens × pricing` per step, pricing is applied as of fold time
(refreshing pricing affects only cells folded afterward), cache rates fall
back to the prompt rate when undisclosed, and free-tier / promotional pricing
may differ from the model table — all documented under Known Limitations.
Nothing is model-visible: the agent loop, session log format, and SDKs are
untouched. Deferred: a settings card, and any per-generation billing fidelity
that would require logging OpenRouter generation ids.

View File

@@ -0,0 +1,32 @@
# Agent Note: OpenRouter cost and balance readouts in the web GUI
Status: implemented
[English](2026-08-20-openrouter-cost-balance-readouts.md) | 中文
> 范围:两个新包 —— `@deepseek-ai/dsh-openrouter-usage`(主机端网关 + 投影)与 `@deepseek-ai/dsh-client-ui-openrouter-usage`(Web 读数)—— 组合进 web profile。网关解析与 OpenRouter LLM 路由相同的 `OPENROUTER_API_KEY` 凭证。
## Problem
使用 Web GUI 并通过 OpenRouter 路由 LLM 调用的用户,无法看到这些调用花费了多少、账户还剩多少额度。token 计数存在于会话日志(`assistant/message.usage`、`assistant/chunk` 用量块)和 token-meter 投影中,但仓库里没有任何货币数字,也没有 OpenRouter 按模型定价或账户余额的持久记录。harness 有意丢弃 provider 自身的成本元数据(pi-ai 适配器将 `usage.cost` 清零),因此成本必须根据 token 计数与从 OpenRouter 公共 API 拉取的定价表重建。
## Decision
**费用是面向客户端的只读模型投影,绝不是被记录或模型可见的事实。** 网关拉取 OpenRouter 的模型定价表(`GET /models`,`pricing.prompt`/`completion` USD 每 token、flat 的 `request` 费用、披露的缓存费率),并在 `openRouterCost` 的 `SessionProjectionMap` 条目中按各会话已记录的用量折叠它。余额来自 OpenRouter 权威余额端点 `GET /credits`(可用余额 = `total_credits` 减去已花费的 `total_usage`,即仪表盘展示的数字),并与 `GET /auth/key` 合并以获取 key 标签与月度 token 预算(`usage`/`limit`);标准 `sk-or-v1` key 在 `/auth/key` 上**不**返回 `credits` 字段,因此仅靠该端点总会读到空余额。两个请求都使用 `redirect: 'error'` 的 `global.fetch` —— 携带凭证的出站调用的主机标准。
**Web 表面是两个 slot 条目。**每会话费用读数注册进 `conversation.composer.dock`(随附统计行的 dock),通过 `useProjection` 框架座位读取 `openRouterCost` 投影;实时余额徽章注册进 `sidebar.footer.action`,以客户端 60s 间隔轮询余额 Remote。两者在数据缺失时自我隐藏:费用在任何已计价步骤前隐藏,余额在快照尚无值时渲染空的破折号标记。
**API key 是凭证引用,与路由的初始化方式一致。**`apiKeyEnv`(默认 `OPENROUTER_API_KEY`)与 `dsh-web-search-deepseek` 完全一样,通过凭证接缝加环境回退解析,按刷新在 thunk 中捕获,使凭证或设置变更无需重建即可在下一周期生效。
**无 key → 不发请求,插件保持惰性。**无 key 产生空定价表(每个 step 都保持未计价)、全 `null` 余额,客户端把 null 渲染为缺失 —— 与 LLM 适配器相同的休眠姿态,而非加载期失败。
## Alternatives considered
- **将会话事件持久化每次请求费用** —— 费用派生自已记录的事件(`assistant/message` 用量、`assistant/chunk` 用量);事件会重复记账、在错误时间计价(`pricingRefreshMs` 变更定价),并使客户端数字泄漏进持久日志。投影保持唯一的读数模型归属。
- **仅用 `/auth/key` 读取余额** —— 计划的初始形态;真实端点响应中标准 `sk-or-v1` key 不携带 `credits` 字段(已对照 OpenRouter 线上 API 验证),因此快照总会是 null。`/credits` 是权威余额端点,`/auth/key` 只是补充标签与月度预算。
- **读 pi-ai 的 `usage.cost`** —— harness 刻意丢弃 provider 成本元数据(适配器钉定 `NO_COST`);从 token × 定价表重建是唯一可行路径,并记录为估算。
- **提供设置卡片与专用凭证 UI** —— 推迟:读数可以用路由已使用的既有凭证工作;卡片只是界面,不改变行为。
## Consequences
`test:gui` 保持全绿(3800 个测试),两个包都带单元测试加真实组合 loader 测试:把带变形的 YAML 形状(session + 投影注册表 + 凭证 + openrouter-usage)跑进 vendored Loader,从凭证文档解析 key,并断言被 mock 的 `/models` 与 `/credits` 请求填充投影与快照。数字是估算而非逐项账单:投影按 step 以 `tokens × pricing` 计价,计价格次折叠时生效(刷新定价只影响其后折叠的单元格),未披露时缓存费率回退到 prompt 费率,免费档位/促销价格可能与模型表不同 —— 全部记录在 Known Limitations。没有任何内容对模型可见:agent 循环、会话日志格式与 SDK 均未改动。推迟:设置卡片、以及需要记录 OpenRouter generation id 的逐代账单保真度。

View File

@@ -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/process/2026-08-08-native-windows-pull-request-ci.md
2026-08-08-native-windows-pull-request-ci.md: d7db73b2049ae2d08f8996bcfd5b54fa15901478
2026-08-08-native-windows-pull-request-ci.zh.md: 474b7f71aca8fbb5e0082fd2e4faf8e462bf0c0f
2026-08-08-native-windows-pull-request-ci.md: 31a1a1893b0c6248a30ac6e12b409282f608a689
2026-08-08-native-windows-pull-request-ci.zh.md: ba2520c2514580e5af367df86dd3195485a0a34c

View File

@@ -18,7 +18,7 @@ Every pull request also starts an ordinary independent `windows-native` job name
The native job is deliberately absent from `all-checks-passed.needs` and does not use `continue-on-error`: the aggregate neither waits for it nor changes conclusion because of it, while the job retains its own unmasked result. Workspace build, production-site, and 100%-per-file coverage failures make the native job fail. The broader static, documentation, package, and built-artifact portability inventory remains observational. Linux remains the owner of duplicate lint and snapshot enforcement, while native Windows independently enforces supported-source coverage.
The 16-core lane gives coverage a two-worker budget, split into one instrumented worker and one exempt-heavy worker, runs two top-level gates concurrently, and allows eight publint workers. Every Vitest project uses forked workers because Node 24's CJS lexer fatal reproduced in shared worker threads on Windows and POSIX; the two-gate schedule prevents the exempt-heavy Oxlint probe from racing the workspace build over its temporary contract files. Asynchronous fixtures whose real process, Git, SQLite, watcher, or lazy grammar startup can exceed Vitest's default polling window use explicit bounded waits without changing their asserted outcomes. The LSP sources and the ACL-sandbox sources remain in the Windows denominator: stub-based failure-path suites carry every in-process ACL-sandbox file to 100%, and only the runner entry stays excluded — it executes exclusively as a spawned child outside the instrumented run, its behavior pinned end-to-end by the runner suite. Narrow annotated V8 ignores cover only unreachable branches (peer-platform arms and lifecycle-unreachable guards), with their behavior tests retained on the owning platform.
The 16-core lane gives coverage a two-worker budget, split into one instrumented worker and one exempt-heavy worker, runs two top-level gates concurrently, and allows eight publint workers. Every Vitest project uses forked workers because Node 24's CJS lexer fatal reproduced in shared worker threads on Windows and POSIX; the two-gate schedule prevents the exempt-heavy Oxlint probe from racing the workspace build over its temporary contract files. Both coverage gates set Vitest's default per-test and polling budgets to 30 seconds because unrelated process, Git, SQLite, watcher, grammar, and static-gate fixtures can exceed 15 seconds only under the complete lane's concurrent Windows instrumentation. The script-only translation-pairing merge suite runs in the exempt-heavy gate because it imports only `scripts/` sources and child processes; V8 instrumentation contributes no threshold coverage there but magnifies Git-process latency. Lefthook concurrency fixtures retain their outcomes with 30-second case budgets and a 10-second process-ready probe, while the installer allows five seconds for a preempted lock owner to publish its record after exclusive creation. Workspace-context composition fixtures use a test-owned signal without an unrelated one-second deadline. These lane-scoped budgets preserve asserted outcomes, while the 60-minute job deadline still bounds a stuck run. The LSP sources and the ACL-sandbox sources remain in the Windows denominator: stub-based failure-path suites carry every in-process ACL-sandbox file to 100%, and only the runner entry stays excluded — it executes exclusively as a spawned child outside the instrumented run, its behavior pinned end-to-end by the runner suite. Narrow annotated V8 ignores cover only unreachable branches (peer-platform arms and lifecycle-unreachable guards), with their behavior tests retained on the owning platform.
The 16-core allocation is the measured capacity point for this inventory. Relative to the previous two-core serial job, six coverage workers produced complete passes in 6 minutes 27 seconds and 7 minutes 50 seconds, but later exact-head repeats exposed unreliable fixtures and worker exits under four, three, and two concurrent instrumented workers. The selected budget therefore reduces that fan-out to one while retaining the exempt-heavy suite as a second concurrent coverage worker and preserving two-way top-level overlap. A 32-core comparison reduced aggregate gate time by only 1.47 seconds and still triggered the CJS-lexer fatal inside a fork worker, so additional cores did not provide a reliable wall-clock improvement.

View File

@@ -18,7 +18,7 @@ Status: implemented
原生作业被刻意排除在 `all-checks-passed.needs` 之外,且不使用 `continue-on-error`:聚合流程既不等待它,也不会因它改变结论;该作业则保留自身未被掩盖的结果。工作区构建、生产网站和逐文件 100% 覆盖率检查失败会使原生作业失败。更广泛的静态检查、文档、包和构建产物可移植性清单仍作为观测项报告。重复的 lint 与快照强制检查仍由 Linux 负责,原生 Windows 则独立强制执行受支持源码覆盖率。
16 核通道为覆盖率分配 2 个工作线程,其中 1 个用于插桩套件,1 个用于免覆盖率项较多的套件;同时运行 2 项顶层门禁,并允许 8 个 publint 工作线程。每个 Vitest 项目都使用 fork 工作线程,因为 Node 24 的 CJS lexer 致命故障可在 Windows 与 POSIX 的共享工作线程中复现;双门禁调度可避免免覆盖率项较多的 Oxlint 探测与工作区构建在临时约定文件上发生竞态。对于真实进程、Git、SQLite、watcher 或延迟语法启动可能超过 Vitest 的默认轮询窗口的异步 fixture,系统会使用显式的有界等待,而不改变其断言结果。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。
16 核通道为覆盖率分配 2 个工作线程,其中 1 个用于插桩套件,1 个用于免覆盖率项较多的套件;同时运行 2 项顶层门禁,并允许 8 个 publint 工作线程。每个 Vitest 项目都使用 fork 工作线程,因为 Node 24 的 CJS lexer 致命故障可在 Windows 与 POSIX 的共享工作线程中复现;双门禁调度可避免免覆盖率项较多的 Oxlint 探测与工作区构建在临时约定文件上发生竞态。两项覆盖率门禁都将 Vitest 默认的单测试和轮询时间预算设为 30 秒,因为在完整通道并发的 Windows 插桩下,多个互不相关的进程、Git、SQLite、watcher、语法和静态门禁 fixture 可能超过 15 秒。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入免覆盖率项较多的门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。这些只属于该通道的预算保留了原有断言结果,60 分钟的作业截止时间仍会约束卡死的运行。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。
16 核配置是这项清单经实测选定的容量规格。与此前的双核串行作业相比,6 个覆盖率工作线程曾分别以 6 分 27 秒和 7 分 50 秒跑出完整通过结果,但后续的分支头精确复跑先后在 4 个、3 个和 2 个插桩工作线程并发时暴露出不稳定的 fixture 与工作线程退出。因此,所选预算将这一扇出降至 1,同时保留免覆盖率项较多的套件作为第二个并发覆盖率工作线程,并继续让两项顶层门禁重叠执行。32 核对比仅将聚合门禁时间缩短 1.47 秒,且仍在 fork 工作线程内触发 CJS lexer 致命故障,因此增加核心数没有带来可靠的墙钟时间改善。

View File

@@ -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/process/2026-08-19-offline-pnpm-prefetch-scripts.md
2026-08-19-offline-pnpm-prefetch-scripts.md: c00eda84455b582f1ee006617d3031473b14f9e4
2026-08-19-offline-pnpm-prefetch-scripts.zh.md: 6314726a7a30cfa79b188d2a98eefa3f2525437a

View File

@@ -0,0 +1,37 @@
# Agent Note: Offline pnpm prefetch scripts
Status: implemented
English | [中文](2026-08-19-offline-pnpm-prefetch-scripts.zh.md)
## Problem
A source checkout cannot boot `dsh web` until a supported Node runtime, `pnpm install`, and `pnpm run build` have materialized `node_modules`, package `lib/` output, and `apps/web/dist`. Install reaches the npm registry (and Corepack reaches it for `pnpm@11.7.0`); the Node distro comes from `nodejs.org/dist`. Node's fetch client cannot use `socks5h://` proxies, so a machine whose only ambient proxy is SOCKS fails `pnpm install` even while curl through the same proxy succeeds. An air-gapped follow-up install therefore needs a same-OS prefetch of the official Node archive, the lockfile store, and a Corepack home that does not call the registry.
## Decision
[`scripts/offline/download-deps`](../../../../scripts/offline/download-deps.ps1) (POSIX twin [`.sh`](../../../../scripts/offline/download-deps.sh)) runs while `nodejs.org` and the registry are reachable. It downloads the official Node zip/tar for the current OS and CPU from `DSH_NODE_DIST_BASE` (default `https://nodejs.org/dist`), verifies `SHASUMS256.txt`, unpacks into ignored [`.offline-cache/node/runtime`](../../../../.gitignore), and writes `.offline-cache/node/runtime.json`. The Node version is `DSH_NODE_VERSION` when set, otherwise the running Node when it satisfies `engines.node` (`^22.19.0 || >=24.0.0`), otherwise the newest `v24.x` from `index.json` that publishes this platform's archive. It then sets `COREPACK_HOME` to `.offline-cache/corepack`, `corepack prepare`s the root `package.json` `packageManager` pin using that Node, and `pnpm fetch --frozen-lockfile --store-dir .offline-store`. [`scripts/offline/install-deps`](../../../../scripts/offline/install-deps.ps1) (POSIX twin [`.sh`](../../../../scripts/offline/install-deps.sh)) unpacks the cached Node if needed, prepends it to `PATH`, sets `COREPACK_ENABLE_NETWORK=0`, and runs `pnpm install --offline --frozen-lockfile` against that store. Optional `-Build` / `--build` runs `npm run build:lib` then `pnpm --dir apps/web run build`, avoiding the nested `pnpm --filter` invocation inside `npm run build:web` that can pick a different Corepack pnpm than `packageManager`.
`DSH_NPM_HTTP_PROXY`, when set, is an HTTP overlay applied only around `corepack prepare` and `pnpm fetch`, then the process proxy variables are restored so a caller SOCKS session is unchanged. curl uses the inherited `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` (SOCKS included) and falls back to `DSH_NPM_HTTP_PROXY` only when none of those are set. A `socks5` or `socks5h` value for Node/pnpm (from `DSH_NPM_HTTP_PROXY`, or from the inherited proxies when no HTTP overlay is set) fails before Corepack runs, because that scheme is not a supported pnpm transport.
The scripts prefetch Node and the lockfile for the host OS and CPU they run on. They do not download LLM weights, Playwright browsers, or a local inference server. Copy the checkout including `.offline-store` and `.offline-cache` to the offline machine.
## Alternatives considered
**Tell contributors to copy a finished `node_modules` tree and skip a second install.** A copied isolated pnpm layout is host-specific and breaks when the store path or package content-address links do not match; `pnpm install --offline` against a fetched store is the documented pnpm air-gap path.
**Check `supportedArchitectures` into the download script so one store serves win32 and linux.** Rejected for the default: optional native packages (esbuild, koffi, node-pty) must match the install host, and a mixed store still needs a same-OS install to unpack the right binaries. Cross-OS prefetch remains an explicit local `pnpm fetch` config, not the script default.
**Add root `package.json` scripts that call these files.** Nested `pnpm` under `npm run` is what already mismatches Corepack versions on `build:web`; the wrappers stay invoked as files so they control `COREPACK_HOME` and `--store-dir`.
**Overwrite the caller's `HTTP_PROXY` for the whole download script.** Rejected: `.\download-deps.ps1` runs in the current PowerShell session, so a session-wide overwrite would replace a working SOCKS proxy for later commands. curl can use SOCKS; only Node/pnpm need the HTTP overlay, and only for those child processes.
**Ship the Node MSI or pkg installer.** Rejected: those installers need administrator rights and a global install; the official zip/tar is relocatable, and install only unpacks it and prepends `PATH`.
**Hardcode one Node version in the script.** Rejected: a pinned patch goes stale against `engines.node` and CI (`22.19`, `24`, `26`). Matching the running Node keeps optional native packages aligned with the prefetch host; `index.json` is the fallback when Node is not on `PATH`.
**Vendor the pnpm store in git.** The store is hundreds of megabytes of registry tarballs; `.gitignore` keeps `.offline-store/` and `.offline-cache/` untracked.
## Consequences
Air-gap preparation is a two-command, same-OS pair and does not change the online `pnpm install` path. The offline machine does not need a preinstalled Node: install unpacks the cached official distro. `dsh plugin … add` of a registry or git spec still needs a network on the machine that runs it. Local chat still needs a separately installed OpenAI-compatible server; these scripts do not substitute for that.

View File

@@ -0,0 +1,37 @@
# Agent Note: Offline pnpm prefetch scripts
Status: implemented
[English](2026-08-19-offline-pnpm-prefetch-scripts.md) | 中文
## Problem
源码检出在受支持的 Node 运行时、`pnpm install` 与 `pnpm run build` 物化 `node_modules`、各包 `lib/` 以及 `apps/web/dist` 之前无法启动 `dsh web`。安装会访问 npm registry(Corepack 为 `pnpm@11.7.0` 也会访问);Node 发行包来自 `nodejs.org/dist`。Node 的 fetch 客户端不能使用 `socks5h://` 代理,因此环境里只有 SOCKS 代理的机器即使 curl 经同一代理成功,`pnpm install` 仍会失败。后续在隔离网络中安装,需要按相同 OS 预取官方 Node 归档、lockfile store,以及一个不再访问 registry 的 Corepack home。
## Decision
[`scripts/offline/download-deps`](../../../../scripts/offline/download-deps.ps1)(POSIX 对侧 [`.sh`](../../../../scripts/offline/download-deps.sh))在能访问 `nodejs.org` 与 registry 时运行。它从 `DSH_NODE_DIST_BASE`(默认 `https://nodejs.org/dist`)下载当前 OS 与 CPU 的官方 Node zip/tar,校验 `SHASUMS256.txt`,解压到已忽略的 [`.offline-cache/node/runtime`](../../../../.gitignore),并写入 `.offline-cache/node/runtime.json`。Node 版本在设置了 `DSH_NODE_VERSION` 时用它,否则在运行中的 Node 满足 `engines.node`(`^22.19.0 || >=24.0.0`)时用该版本,否则用 `index.json` 中发布了当前平台归档的最新 `v24.x`。随后把 `COREPACK_HOME` 设为 `.offline-cache/corepack`,用该 Node 按根目录 `package.json` 的 `packageManager` 引脚执行 `corepack prepare`,并运行 `pnpm fetch --frozen-lockfile --store-dir .offline-store`。[`scripts/offline/install-deps`](../../../../scripts/offline/install-deps.ps1)(POSIX 对侧 [`.sh`](../../../../scripts/offline/install-deps.sh))在需要时解压缓存的 Node,把它前置到 `PATH`,设置 `COREPACK_ENABLE_NETWORK=0`,并对该 store 执行 `pnpm install --offline --frozen-lockfile`。可选的 `-Build` / `--build` 先运行 `npm run build:lib`,再运行 `pnpm --dir apps/web run build`,从而避开 `npm run build:web` 内部嵌套的 `pnpm --filter`——它可能选出与 `packageManager` 不同的 Corepack pnpm。
若设置了 `DSH_NPM_HTTP_PROXY`,它只作为 HTTP 覆盖层套在 `corepack prepare` 与 `pnpm fetch` 周围,随后恢复进程内的代理变量,因此调用方会话里的 SOCKS 代理保持不变。curl 使用继承的 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`(包括 SOCKS),仅在这些变量都未设置时才回退到 `DSH_NPM_HTTP_PROXY`。供 Node/pnpm 使用的值(来自 `DSH_NPM_HTTP_PROXY`,或在未设置 HTTP 覆盖层时来自继承的代理变量)若为 `socks5` 或 `socks5h`,会在 Corepack 运行前失败,因为该 scheme 不是 pnpm 支持的传输。
脚本只为运行时所在主机的 OS 与 CPU 预取 Node 与 lockfile。它们不下载 LLM 权重、Playwright 浏览器或本地推理服务器。把包含 `.offline-store` 与 `.offline-cache` 的检出复制到离线机器。
## Alternatives considered
**让贡献者复制已完成的 `node_modules` 树并跳过第二次安装。** 复制出的 isolated pnpm 布局与主机相关,在 store 路径或包 content-address 链接不匹配时会损坏;针对已 fetch 的 store 运行 `pnpm install --offline` 才是 pnpm 文档中的隔离网络路径。
**把 `supportedArchitectures` 写进 download 脚本,使一份 store 同时服务 win32 与 linux。** 拒绝作为默认:可选原生包(esbuild、koffi、node-pty)必须匹配安装主机,混合 store 仍需在相同 OS 上安装才能解出正确二进制。跨 OS 预取仍是显式的本地 `pnpm fetch` 配置,不是脚本默认。
**在根 `package.json` 增加调用这些文件的 scripts。** `npm run` 下嵌套 `pnpm` 正是 `build:web` 上 Corepack 版本错配的原因;包装器保持按文件调用,以便自行控制 `COREPACK_HOME` 与 `--store-dir`。
**在整个 download 脚本期间覆盖调用方的 `HTTP_PROXY`。** 拒绝:`.\download-deps.ps1` 在当前 PowerShell 会话中运行,会话级覆盖会把后续命令仍可用的 SOCKS 代理换掉。curl 可以使用 SOCKS;只有 Node/pnpm 需要 HTTP 覆盖层,且仅针对这些子进程。
**随脚本分发 Node 的 MSI 或 pkg 安装包。** 拒绝:那些安装器需要管理员权限和全局安装;官方 zip/tar 可重定位,install 只需解压并前置 `PATH`。
**在脚本中写死某一个 Node 版本。** 拒绝:钉死的 patch 会相对 `engines.node` 与 CI(`22.19`、`24`、`26`)过时。与正在运行的 Node 对齐,可使可选原生包与预取主机一致;`PATH` 上没有 Node 时回退到 `index.json`。
**把 pnpm store 纳入 git。** store 是数百 MB 的 registry tarball;`.gitignore` 使 `.offline-store/` 与 `.offline-cache/` 保持未跟踪。
## Consequences
隔离网络准备是一对相同 OS 上的两条命令,不改变在线 `pnpm install` 路径。离线机器不需要预先安装 Node:install 会解压缓存的官方发行包。在运行 `dsh plugin … add` 安装 registry 或 git spec 的机器上仍然需要网络。本地对话仍需要另行安装的 OpenAI-compatible 服务器;这些脚本不能替代它。

View File

@@ -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/process/2026-08-19-source-dsh-http-proxy-overlay.md
2026-08-19-source-dsh-http-proxy-overlay.md: 814633aeda22d1afb306e9f42c26a0701e62b746
2026-08-19-source-dsh-http-proxy-overlay.zh.md: 878a6024027bbc6081cc08b5891d7da6dc243895

View File

@@ -0,0 +1,35 @@
# Agent Note: Source dsh HTTP proxy overlay
Status: implemented
English | [中文](2026-08-19-source-dsh-http-proxy-overlay.zh.md)
## Problem
Machines whose ambient proxy is SOCKS can reach OpenRouter with curl and still fail `pnpm dsh` HTTPS fetches: Node, undici, and pi-ai accept `http://` / `https://` proxy URLs and reject `socks5` / `socks5h`. Setting `HTTPS_PROXY` in the calling PowerShell or bash session also routes later shell commands through that HTTP proxy. Product `.env` files cannot supply `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, or `NO_PROXY` because those names are bootstrap-only. Assigning those names on `process.env` after Node has started does not rebind `fetch`: undici reads `NODE_USE_ENV_PROXY` and `HTTPS_PROXY` at process start. Node `--env-file-if-exists` also leaves inherited names in place, so a user-level `HTTPS_PROXY=socks5h://…` still wins.
## Decision
The root `dsh` script runs [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts) as `node --import tsx/esm scripts/run-source-dsh.ts`. [`applySourceDshHttpProxy`](../../../../scripts/apply-source-dsh-http-proxy.ts) writes HTTP proxy variables onto a copy of `process.env`. When gitignored [`.dsh-http-proxy.env`](../../../../.gitignore) or `DSH_HTTP_PROXY` supplies an overlay and `DSH_SOURCE_HTTP_PROXY_APPLIED` is unset, the wrapper respawns the same Node argv with that environment so `NODE_USE_ENV_PROXY=1` and the HTTP proxy URLs exist before `fetch` initializes. The marker prevents a second respawn. The calling shell is unchanged.
`DSH_HTTP_PROXY`, when set to a non-empty HTTP(S) URL, overwrites `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` (both cases) and sets `NODE_USE_ENV_PROXY=1`. When `NO_PROXY` and `no_proxy` are both unset, it sets them to `localhost,127.0.0.1,::1`. Otherwise the overlay file's parsed assignments are applied, then any remaining SOCKS value on those six names is replaced by the file's HTTP URL so an omitted `ALL_PROXY` cannot keep `socks5h://`. A `socks5` or `socks5h` winning overlay fails before the CLI boots. Absent both sources, the inherited environment is unchanged and there is no respawn. The wrapper prints the applied HTTP URL once to stderr before respawn.
The installed `apps/cli/lib/bin.js` path is unchanged: it has no checkout overlay file.
## Alternatives considered
**Tell the user to export `HTTPS_PROXY` in the shell before `pnpm dsh`.** That reaches OpenRouter, but the same variables remain on later commands in that session, including tools that should keep a SOCKS proxy.
**Put proxy names in the invoking `.env` or `$DSH_HOME/.env`.** Rejected by the [configuration-source-ownership decision](../architecture/2026-08-04-configuration-source-ownership.md): bootstrap network variables may come only from the inherited process environment.
**Assign `NODE_USE_ENV_PROXY` only inside the wrapper after Node has started.** OpenRouter traffic uses `fetch`; Node binds the env-proxy dispatcher at process start, so a later `process.env` write does not change it.
**Use Node `--env-file-if-exists` without respawning.** That file does not replace names the parent already set, so an inherited `socks5h://` `HTTPS_PROXY` still reaches `fetch`.
## Consequences
Source `pnpm dsh` can use an HTTP overlay without mutating the calling shell. An inherited SOCKS `HTTPS_PROXY` is replaced in the respawned Node process, not in the shell. Contributors without `DSH_HTTP_PROXY` or `.dsh-http-proxy.env` see the previous inheritance behavior and no extra process. A SOCKS-only overlay fails loud instead of timing out against OpenRouter. The published bin still requires the caller to pass HTTP proxy variables when the host needs a proxy. `--use-env-proxy` is not on the command: Node 22.19 in CI rejects the flag.
## Testing
`scripts/apply-source-dsh-http-proxy.spec.ts` covers no-overlay, `DSH_HTTP_PROXY` overwrite of SOCKS, file parse, file-versus-`DSH_HTTP_PROXY` precedence, SOCKS leftover on omitted names, SOCKS rejection, and the one-shot respawn marker. `apps/cli/tests/source-launch.compat.spec.ts` pins the root `dsh` command to `scripts/run-source-dsh.ts` and smokes that vector.

View File

@@ -0,0 +1,35 @@
# Agent Note: Source dsh HTTP proxy overlay
Status: implemented
[English](2026-08-19-source-dsh-http-proxy-overlay.md) | 中文
## 问题
环境中只有 SOCKS 代理的机器可以用 curl 访问 OpenRouter,但 `pnpm dsh` 的 HTTPS fetch 仍会失败:Node、undici 和 pi-ai 接受 `http://` / `https://` 代理 URL,并拒绝 `socks5` / `socks5h`。在调用方的 PowerShell 或 bash 会话里设置 `HTTPS_PROXY` 也会让该会话中后续命令走这个 HTTP 代理。产品 `.env` 文件不能提供 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY` 或 `NO_PROXY`,因为这些名称只能来自进程启动环境。在 Node 启动之后再往 `process.env` 写入这些名称不会重新绑定 `fetch`:undici 在进程启动时读取 `NODE_USE_ENV_PROXY` 和 `HTTPS_PROXY`。Node `--env-file-if-exists` 同样不会替换已继承的名称,因此用户级的 `HTTPS_PROXY=socks5h://…` 仍然生效。
## 决策
根目录的 `dsh` 脚本以 `node --import tsx/esm scripts/run-source-dsh.ts` 运行 [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts)。[`applySourceDshHttpProxy`](../../../../scripts/apply-source-dsh-http-proxy.ts) 把 HTTP 代理变量写入 `process.env` 的副本。当被 gitignore 的 [`.dsh-http-proxy.env`](../../../../.gitignore) 或 `DSH_HTTP_PROXY` 提供覆盖层且未设置 `DSH_SOURCE_HTTP_PROXY_APPLIED` 时,包装层用该环境按相同的 Node argv 再拉起一次进程,以便在 `fetch` 初始化之前就存在 `NODE_USE_ENV_PROXY=1` 和 HTTP 代理 URL。该标记防止第二次再拉起。调用方 shell 不变。
当 `DSH_HTTP_PROXY` 设为非空的 HTTP(S) URL 时,它会覆盖 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`(两种大小写)并设置 `NODE_USE_ENV_PROXY=1`。当 `NO_PROXY` 与 `no_proxy` 都未设置时,它将它们设为 `localhost,127.0.0.1,::1`。否则先应用覆盖文件中解析出的赋值,再把这六个名称上残留的 SOCKS 值换成文件中的 HTTP URL,以免漏写的 `ALL_PROXY` 仍指向 `socks5h://`。最终生效的覆盖层若为 `socks5` 或 `socks5h`,会在 CLI 启动前失败。两种来源都不存在时,继承的环境保持不变,也不会再拉起进程。包装层在再拉起之前会把生效的 HTTP URL 打印到 stderr 一次。
已安装的 `apps/cli/lib/bin.js` 路径不变:它没有检出级覆盖文件。
## 考虑过的备选方案
**让用户在运行 `pnpm dsh` 之前于 shell 中导出 `HTTPS_PROXY`。**这样可以访问 OpenRouter,但这些变量会留在该会话的后续命令上,包括本应继续使用 SOCKS 代理的工具。
**把代理名称放进调用目录的 `.env` 或 `$DSH_HOME/.env`。**被[配置来源归属决策](../architecture/2026-08-04-configuration-source-ownership.md)拒绝:网络启动变量只能来自继承的进程环境。
**仅在包装层内、Node 启动之后赋值 `NODE_USE_ENV_PROXY`。** OpenRouter 流量走 `fetch`;Node 在进程启动时绑定 env-proxy dispatcher,随后再写 `process.env` 不会改变它。
**使用 Node `--env-file-if-exists` 且不再拉起进程。**该文件不会替换父进程已经设置的名称,因此继承来的 `socks5h://` `HTTPS_PROXY` 仍会到达 `fetch`。
## 影响
源码路径的 `pnpm dsh` 可以使用 HTTP 覆盖层,而不修改调用方 shell。继承来的 SOCKS `HTTPS_PROXY` 会在再拉起的 Node 进程中被替换,而不是在 shell 中。没有 `DSH_HTTP_PROXY` 或 `.dsh-http-proxy.env` 的贡献者仍看到原来的继承行为,也不会多一个进程。仅有 SOCKS 的覆盖层会立即失败,而不是在访问 OpenRouter 时超时。已发布的 bin 在宿主需要代理时,仍要求调用方传入 HTTP 代理变量。命令行不加 `--use-env-proxy`:CI 中的 Node 22.19 会拒绝该 flag。
## 测试
`scripts/apply-source-dsh-http-proxy.spec.ts` 覆盖无覆盖层、`DSH_HTTP_PROXY` 覆盖 SOCKS、文件解析、文件与 `DSH_HTTP_PROXY` 的优先级、文件未写出的名称上残留的 SOCKS、SOCKS 拒绝,以及一次性再拉起标记。`apps/cli/tests/source-launch.compat.spec.ts` 将根目录 `dsh` 命令钉在 `scripts/run-source-dsh.ts` 上,并对该启动向量做冒烟测试。

View File

@@ -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/simplification/2026-07-23-acp-automation-only-protocol.md
2026-07-23-acp-automation-only-protocol.md: e7a6670a44db9d4fd2bba5f013083e35d60fc313
2026-07-23-acp-automation-only-protocol.zh.md: cdefe80c3e868a41541c7ebea42e3811c7fb9249
2026-07-23-acp-automation-only-protocol.md: deeba55ebb48468af80a6c74a704b18e07f33477
2026-07-23-acp-automation-only-protocol.zh.md: ab23280ca7d2f39b33b80f1d9fefe977345b8676

View File

@@ -8,15 +8,17 @@ English | [中文](2026-07-23-acp-automation-only-protocol.zh.md)
The ACP bridge had become a second interactive product UI. It translated durable events into editor cards, terminal metadata, diffs, plans, titles, reasoning, commands, modes, model and permission pickers, session navigation, and human elicitation. Those responsibilities duplicated the TUI and the Web client while coupling an automation transport to UI services, persistence queries, presentation policy, and editor-specific conventions.
ACP still has one useful role: another agent or automated controller can start a harness process, create an isolated session, send text, receive the committed answer, cancel work, and answer a permission request. The out-of-process ACP subagent backend depends on that standard protocol boundary.
ACP still has one useful role: another agent or automated controller can start a harness process, create an isolated session, send text or a narrowly supported inline image, receive the committed text/image answer, cancel work, and answer a permission request. The out-of-process ACP subagent backend depends on that standard protocol boundary.
The snapshot suite complicates removal. Most ACP scenarios exercise the assembled agent backend rather than ACP presentation, so deleting the suite with the editor bridge would discard broad keyless behavioral coverage.
## Decision
`@deepseek-ai/dsh-acp` is an automation transport under [`packages/acp/acp`](../../../../packages/acp/acp/README.md), outside the `ui` package group. Its public protocol is intentionally small: version negotiation, fresh text sessions with one in-flight prompt each, committed assistant text updates, per-session cancellation, concurrent sessions, and connection-owned teardown. Prompts carry the spec-required baseline only — text plus resource links flattened to bracketed textual references; the bridge rejects additional directories, MCP servers, beyond-baseline prompt content (image, audio, embedded resources), empty prompts, unknown sessions, and overlapping prompts.
`@deepseek-ai/dsh-acp` is an automation transport under [`packages/acp/acp`](../../../../packages/acp/acp/README.md), outside the `ui` package group. Its public protocol is intentionally small: version negotiation, fresh sessions with one in-flight prompt each, committed assistant text/image updates, per-session cancellation, concurrent sessions, and connection-owned teardown. Prompts preserve text and supported raster images in wire order, while resource links flatten to bracketed textual references; the bridge rejects additional directories, MCP servers, audio, embedded resources, malformed or empty prompts, unknown sessions, and overlapping prompts.
The bridge emits only committed `assistant/message` text. Reasoning, raw chunks, tool activity, todos, plans, titles, retry markers, terminal metadata, diffs, locations, and resource links remain in the durable session log or in UI-specific transports. It does not provide session load/list/delete, commands, modes, configuration selectors, model switching, plan review, or human elicitation.
Image capability is truthful rather than structural: `initialize` advertises it only when a durable attachment store exists and the configured exact provider/model resolves with explicit image input. Each image prompt rechecks the session's latest exact route, strictly decodes every block, and delegates the complete batch to `AttachmentStore.saveImages()` before publishing the user event. Cancellation reserves and aborts the admission slot before any asynchronous work, waits for already-started writes to quiesce before the prompt settles, and never publishes a late message; before the prompt enters the Agent inbox it neither cancels nor waits for unrelated Agent work. A completed content-addressed write may remain unreachable because destructive rollback is not valid for a deduplicated store. Caller-correctable image-policy failures map to invalid parameters, while route lookup, storage corruption, and persistence failures remain internal faults.
The bridge emits only committed `assistant/message` text and images. A per-session promise chain preserves block and message order while assistant image references are asynchronously re-read and integrity-verified for ACP base64 delivery; a missing or corrupt object fails prompt delivery instead of becoming a placeholder. Reasoning, raw chunks, tool activity, todos, plans, titles, retry markers, terminal metadata, diffs, locations, and resource links remain in the durable session log or in UI-specific transports. It does not provide session load/list/delete, commands, modes, configuration selectors, model switching, plan review, or human elicitation.
One-shot `session/request_permission` remains. It is a machine policy channel for bridge-owned agents, not a human approval UI: the answerer accepts only an exact agent object in the bridge's live session map, delegates foreign or call-less requests, and maps failed RPCs to the fail-closed unavailable outcome. The client chooses allow once, reject once, or cancel, and the bridge never turns that response into a durable grant. Asking policy stays in the approval seam and its producers; [`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.md) uses this channel programmatically.
@@ -24,13 +26,13 @@ The app composition contains the agent spine, persistence, checkpoint policy, an
The transport programs interface-level agent, session, and approval services rather than the concrete agent loop. Tool execution stays inside the harness; ACP never delegates shell execution to an editor. stdout carries framed JSON-RPC only, so the app mounts no stdout logger and the bridge does not monkey-patch process output.
Disconnect and plugin disposal share one memoized quiescence boundary. Both successful and failed transport closure settle pending prompts as cancelled, dispose every bridge-owned agent, and await loop and session cleanup. A create that loses the close race disposes its unpublished handle.
Disconnect and plugin disposal share one memoized quiescence boundary. Both successful and failed transport closure cancel prompt admission and agents, drain ordered output, settle pending prompts as cancelled, dispose every bridge-owned agent, and await loop and session cleanup. A create that loses the close race disposes its unpublished handle.
## Snapshot boundary
The ACP snapshot suite still boots the assembled ACP example and retains scenarios that pin backend behavior. Only scenarios driven through deleted UI methods leave the suite; semantic-checkpoint recovery runs through the headless `stream-json` example because ACP no longer loads sessions.
Protocol and lifecycle tests pin stop-reason and prompt codecs, version negotiation, fresh-session creation, text and resource-link flattening, rejection of empty or unsupported prompts, exact-agent permission ownership, multi-session isolation, prompt settlement, per-session cancellation, failed transport closure, ACP-only reload cleanup, and teardown quiescence. Built and real-stdio smokes reject stray stdout. The `session/new` branch that loses a real stdio close race remains coverage-exempt because the in-memory transport cannot reproduce that ordering; it disposes the unpublished handle, while the surrounding disposal tests pin the no-orphan invariant.
Protocol and lifecycle tests pin stop-reason codecs, version negotiation, truthful image capability, fresh-session creation, ordered text/image admission, resource-link flattening, all-member validation before writes, absence of inline base64 in durable events, rejection of empty or unsupported prompts, exact-agent permission ownership, multi-session isolation, prompt settlement after ordered output, verified assistant-image delivery, cancellation during admission without a late followup or cancellation of unrelated Agent work, exclusion of unrelated pre-inbox failures, failed transport closure, ACP-only reload cleanup, and teardown quiescence. An assembled keyless snapshot sends a real inline PNG through the runnable ACP example and pins only its durable reference in the session log. Built and real-stdio smokes reject stray stdout. The `session/new` branch that loses a real stdio close race remains coverage-exempt because the in-memory transport cannot reproduce that ordering; it disposes the unpublished handle, while the surrounding disposal tests pin the no-orphan invariant.
## Alternatives considered
@@ -44,10 +46,16 @@ Protocol and lifecycle tests pin stop-reason and prompt codecs, version negotiat
**Delete the ACP snapshot suite or migrate every scenario in this change.** Rejected because most scenarios test the backend and remain valuable, while a full harness migration is an independent testing change. Only scenarios whose driver was a deleted UI method leave this suite.
**Advertise image support whenever the ACP SDK has an image block.** Rejected because protocol vocabulary does not prove this deployment can persist bytes or that the configured exact route accepts visual input. Unknown capability is false at initialization; prompt admission rechecks the live route.
**Flatten inline and assistant images to markers or persist ACP base64 in session events.** Rejected because markers silently lose model/user intent and base64 makes durable logs the binary store. ACP translates between its wire block and the existing durable `ImageBlock` reference at the transport boundary.
**Create a generic RichContent service for ACP, MCP, and Web.** Rejected because core `ContentBlock` plus the attachment seam already own the shared contract. Each front door keeps only protocol parsing, capability proof, and lifecycle orchestration; shared batch limits and image validation stay in `AttachmentStore.saveImages()`.
## Consequences
ACP has a narrow contract suitable for agents and automation, while TUI and Web own human interaction and presentation. The package has fewer injected services, dependencies, protocol branches, and lifecycle states, and it no longer claims compatibility as a general editor entry point.
Automation clients receive complete committed text rather than token deltas or structured tool UI. They inspect durable logs or another API when they need reasoning, tool traces, titles, or richer state. Fresh-session-only operation also means callers that need durable browsing or resume use a host API rather than ACP.
Automation clients receive complete committed text/images rather than token deltas or structured tool UI. They inspect durable logs or another API when they need reasoning, tool traces, titles, or richer state. Fresh-session-only operation also means callers that need durable browsing or resume use a host API rather than ACP.
Backend snapshot coverage therefore remains transport-coupled to ACP even though that transport is incidental to the behavior under test.

View File

@@ -8,15 +8,17 @@ Status: implemented
ACP(Agent Client Protocol)桥接层已经变成第二套交互式产品 UI。它将持久事件转换为编辑器卡片、终端元数据、diff、计划、标题、推理(reasoning)、命令、模式、模型和权限选择器、会话导航以及面向人类的询问。这些职责与 TUI 和 Web 客户端重复,同时将自动化传输层与 UI 服务、持久化查询、展示策略和编辑器特定约定耦合在一起。
ACP 仍有一个有用的职责:另一个 agent(智能体)或自动化控制器可以启动 harness 进程、创建隔离会话、发送文本、接收已提交的回答、取消工作并回答权限请求。跨进程 ACP subagent 后端依赖这个标准协议边界。
ACP 仍有一个有用的职责:另一个 agent(智能体)或自动化控制器可以启动 harness 进程、创建隔离会话、发送文本或范围狭窄的受支持内联图片、接收已提交的文本/图片回答、取消工作并回答权限请求。跨进程 ACP subagent 后端依赖这个标准协议边界。
快照套件使移除工作更复杂。大多数 ACP 场景测试的是组装后的 agent 后端,而不是 ACP 展示层;如果随编辑器桥接层一起删除整个套件,就会丢失大量无密钥行为测试。
## 决策
`@deepseek-ai/dsh-acp` 是位于 [`packages/acp/acp`](../../../../packages/acp/acp/README.md) 下、独立于 `ui` 包组的自动化传输层。其公开协议特意保持精简:版本协商、全新文本会话(每个会话最多允许一个进行中的提示词)、已提交的助手文本更新、按会话取消、并发会话,以及由连接负责的资源清理。提示词只承载规范要求的基线内容——文本,加上被展平为方括号文本引用的资源链接;桥接层会拒绝附加目录、MCP 服务器、超出基线的提示词内容(图片、音频、内嵌资源)、空提示词、未知会话和重叠提示词。
`@deepseek-ai/dsh-acp` 是位于 [`packages/acp/acp`](../../../../packages/acp/acp/README.md) 下、独立于 `ui` 包组的自动化传输层。其公开协议特意保持精简:版本协商、全新会话(每个会话最多允许一个进行中的提示词)、已提交的助手文本/图片更新、按会话取消、并发会话,以及由连接负责的资源清理。提示词按协议顺序保留文本与受支持光栅图片,资源链接则展平为方括号文本引用;桥接层会拒绝附加目录、MCP 服务器、音频、嵌入资源、格式错误或空提示词、未知会话和重叠提示词。
桥接层只发出已提交的 `assistant/message` 文本。推理、原始分片、工具活动、待办事项、计划、标题、重试标记、终端元数据、diff、位置和资源链接仍保留在持久会话日志或 UI 专用传输层中。它不提供会话加载、列出与删除、命令、模式、配置选择器、模型切换、plan 评审或面向人类的询问。
图片能力必须真实,而不能只看结构:只有持久附件存储存在,且配置的确切提供方/模型解析后明确支持图片输入时,`initialize` 才会公布该能力。每个图片提示词都会重新检查会话的最新确切路由、严格解码全部块,并在发布用户事件前把完整批次委托给 `AttachmentStore.saveImages()`。取消会在任何异步工作前预留并中止准入槽位,使提示词在已经启动的写入停稳后才结算,而且绝不发布迟到消息;提示词进入 Agent inbox 前既不会取消,也不会等待无关的 Agent 工作。已经完成的内容寻址写入可能保持不可达,因为对去重存储执行破坏性回滚并不正确。可由调用方修正的图片策略失败会映射为无效参数,路由查询、存储损坏和持久化失败则仍属于内部故障。
桥接层只发出已提交的 `assistant/message` 文本与图片。每个会话使用一条 Promise 链,在异步重新读取并校验助手图片引用、将其转换为 ACP base64 交付时保持块与消息顺序;对象缺失或损坏会使提示词交付失败,而不是变成占位符。推理、原始分片、工具活动、待办事项、计划、标题、重试标记、终端元数据、diff、位置和资源链接仍保留在持久会话日志或 UI 专用传输层中。它不提供会话加载、列出与删除、命令、模式、配置选择器、模型切换、plan 评审或面向人类的询问。
保留一次性 `session/request_permission`。它是为桥接层拥有的 agent 提供的机器策略通道,而不是面向人类的审批 UI:应答者只接受桥接层当前会话映射中登记的同一 agent 对象;不属于桥接层当前 agent 的请求或未关联具体调用的请求会继续委派;RPC 失败则映射为故障时默认拒绝的 `unavailable` 结果。客户端可选择允许一次、拒绝一次或取消,桥接层绝不会将该响应转换为持久授权。询问策略仍归审批 seam 及其生产者所有;[`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.md) 会以程序化方式使用该通道。
@@ -24,13 +26,13 @@ ACP 仍有一个有用的职责:另一个 agent(智能体)或自动化控
传输层调用 agent、会话和审批的接口服务,而不依赖具体的 agent loop(智能体循环)。工具执行仍留在 harness 内;ACP 绝不会把 shell 执行委派给编辑器。stdout 只承载分帧 JSON-RPC,因此 app 不挂载 stdout logger,桥接层也不会 monkey-patch 进程输出。
断开连接与插件 dispose(资源释放)共享同一个经记忆化的完全停稳边界。传输关闭无论成功还是失败,都会将待处理提示词以已取消状态结算,dispose 每个由桥接层拥有的 agent,并等待 agent loop 和会话清理完成。创建流程如果在与关闭的竞态中落败,就会 dispose 其尚未发布的 handle。
断开连接与插件 dispose(资源释放)共享同一个经记忆化的完全停稳边界。传输关闭无论成功还是失败,都会取消提示词准入和 agent、排空有序输出、将待处理提示词以已取消状态结算、dispose 每个由桥接层拥有的 agent,并等待循环和会话清理完成。创建流程如果在与关闭的竞态中落败,就会 dispose 其尚未发布的 handle。
## 快照边界
ACP 快照套件仍会启动组装后的 ACP 示例,并保留用于锁定后端行为的场景。从该套件移出的只有通过已删除的 UI 方法驱动的场景;由于 ACP 不再加载会话,语义检查点恢复通过 headless `stream-json` 示例执行。
协议与生命周期测试会锁定停止原因编解码器和提示词编解码器、版本协商、新会话创建、文本与资源链接展平、拒绝空提示词或不受支持的提示词、基于同一 agent 对象的权限归属、多会话隔离、提示词结算、按会话取消、传输关闭失败、ACP 专属重载清理,以及拆卸完全停稳。构建产物冒烟测试与真实 stdio 冒烟测试会拒绝混入 stdout 的额外输出。`session/new` 中在真实 stdio 关闭竞态中落败的分支仍豁免覆盖率要求,因为内存传输层无法复现这一顺序;该分支会 dispose 尚未发布的 handle,而周边 dispose 测试会锁定无遗留资源不变式。
协议与生命周期测试会锁定停止原因编解码器、版本协商、真实图片能力、新会话创建、有序文本/图片准入、资源链接展平、写入前校验全部成员、持久事件中不含内联 base64、拒绝空提示词或不受支持的提示词、基于同一 agent 对象的权限归属、多会话隔离、在有序输出后结算提示词、经过校验的助手图片交付、准入期间取消且不产生迟到 followup 或取消无关 Agent 工作、排除进入 inbox 前的无关失败、传输关闭失败、ACP 专属重载清理,以及拆卸完全停稳。组装后的无密钥快照通过可运行 ACP 示例发送一张真实内联 PNG,并在会话日志中只固定其持久引用。构建产物冒烟测试与真实 stdio 冒烟测试会拒绝混入 stdout 的额外输出。`session/new` 中在真实 stdio 关闭竞态中落败的分支仍豁免覆盖率要求,因为内存传输层无法复现这一顺序;该分支会 dispose 尚未发布的 handle,而周边 dispose 测试会锁定无遗留资源不变式。
## 考虑过的替代方案
@@ -44,10 +46,16 @@ ACP 快照套件仍会启动组装后的 ACP 示例,并保留用于锁定后
**删除 ACP 快照套件,或在本次变更中迁移每个场景。** 不予采用,因为大多数场景测试后端且仍有价值,而完整的 harness 迁移是一项独立的测试变更。只有通过已删除的 UI 方法驱动的场景才离开该套件。
**只要 ACP SDK 具有图片块就公布图片支持。** 不予采用,因为协议词汇不能证明当前部署可以持久化字节,也不能证明配置的确切路由接受视觉输入。初始化时能力未知即为 false;提示词准入会重新检查实时路由。
**把内联图片和助手图片展平为标记,或把 ACP base64 持久化进会话事件。** 不予采用,因为标记会静默丢失模型/用户意图,base64 则会让持久日志变成二进制存储。ACP 在传输边界把自身协议块与现有持久 `ImageBlock` 引用相互转换。
**为 ACP、MCP 和 Web 创建通用 RichContent 服务。** 不予采用,因为核心 `ContentBlock` 与附件 seam 已经拥有共享契约。每个入口只保留协议解析、能力证明与生命周期编排;共享批次限制和图片校验留在 `AttachmentStore.saveImages()` 中。
## 结果
ACP 具有适合 agent 与自动化的精简约定,而 TUI 和 Web 拥有面向人类的交互与展示。该包注入的服务、依赖、协议分支和生命周期状态更少,也不再将自身定位为通用编辑器入口。
自动化客户端收到完整的已提交文本,而不是 token 增量或结构化工具 UI。当它们需要推理、工具跟踪信息、标题或更丰富的状态时,需要查看持久日志或其他 API。只支持全新会话也意味着,需要浏览持久会话或恢复会话的调用方必须使用 host API,而不是 ACP。
自动化客户端收到完整的已提交文本/图片,而不是 token 增量或结构化工具 UI。当它们需要推理、工具跟踪信息、标题或更丰富的状态时,需要查看持久日志或其他 API。只支持全新会话也意味着,需要浏览持久会话或恢复会话的调用方必须使用 host API,而不是 ACP。
因此,后端快照测试仍与 ACP 传输层耦合,尽管对于受测行为而言,该传输层只是附带因素。

View File

@@ -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/simplification/2026-08-10-source-run-without-managed-installer.md
2026-08-10-source-run-without-managed-installer.md: ce618c88327fc11ad0cad9042e171800a5492663
2026-08-10-source-run-without-managed-installer.zh.md: e54ec93b8bd13a36b7e8b6813899d5a1e27b788c
2026-08-10-source-run-without-managed-installer.md: 3deb66367cc869bdef04527ca13300f0558d40d6
2026-08-10-source-run-without-managed-installer.zh.md: 70b9d3fb604dc472841e44977317dc46d8e352d4

View File

@@ -12,7 +12,7 @@ That lifecycle is not required to run or develop DeepSeek Harness from a source
## Decision
The repository supports source execution through its root `pnpm` scripts. The `dsh` entry in `package.json` launches `apps/cli/src/bin.ts` directly through `node --import tsx/esm`; artifact generation is the separate `pnpm run build` operation defined by the [source-launch/build separation decision](2026-08-12-separate-source-launch-from-build.md). The package script forwards arguments and inherits the caller's environment, including `NODE_USE_ENV_PROXY=1` when a supporting Node version must honor `HTTP_PROXY` and `HTTPS_PROXY`. Users select Web with `pnpm dsh web` and headless execution with `pnpm dsh --profile headless "task"`. The independent ACP example remains available through `pnpm run demo:acp`.
The repository supports source execution through its root `pnpm` scripts. The `dsh` entry in `package.json` launches [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts) through `node --import tsx/esm`; that wrapper overlays HTTP proxy variables for this Node process ([source HTTP proxy overlay](../process/2026-08-19-source-dsh-http-proxy-overlay.md)) and then dispatches `apps/cli/src/bin.ts`. Artifact generation is the separate `pnpm run build` operation defined by the [source-launch/build separation decision](2026-08-12-separate-source-launch-from-build.md). The package script forwards arguments and inherits the caller's environment; the overlay can replace inherited SOCKS proxy variables so Node fetch can reach providers such as OpenRouter. Users select Web with `pnpm dsh web` and headless execution with `pnpm dsh --profile headless "task"`. The independent ACP example remains available through `pnpm run demo:acp`.
The repository does not distribute a source installer, an installer test suite, or skills that assume a managed `current` symlink and timestamped staging worktrees. Users own source checkout placement, Git updates, and any launcher they create outside the repository.

View File

@@ -12,7 +12,7 @@ Status: implemented
## 决策
仓库通过根目录的 `pnpm` 脚本支持从源码运行。`package.json` 中的 `dsh` 项通过 `node --import tsx/esm` 直接启动 `apps/cli/src/bin.ts`;产物生成是独立的 `pnpm run build` 操作,由[源码启动与构建分离决策](2026-08-12-separate-source-launch-from-build.md)规定。该包脚本会转发参数并继承调用方环境;当支持环境代理的 Node 版本必须遵循 `HTTP_PROXY` 和 `HTTPS_PROXY` 时,调用方可设置 `NODE_USE_ENV_PROXY=1`。用户使用 `pnpm dsh web` 选择 Web,使用 `pnpm dsh --profile headless "task"` 选择无头执行。独立的 ACP(Agent Client Protocol)示例仍可通过 `pnpm run demo:acp` 运行。
仓库通过根目录的 `pnpm` 脚本支持从源码运行。`package.json` 中的 `dsh` 项通过 `node --import tsx/esm` 启动 [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts);该包装层仅为本 Node 进程套用 HTTP 代理变量([源码 HTTP 代理覆盖层](../process/2026-08-19-source-dsh-http-proxy-overlay.md)),然后分派 `apps/cli/src/bin.ts`。产物生成是独立的 `pnpm run build` 操作,由[源码启动与构建分离决策](2026-08-12-separate-source-launch-from-build.md)规定。该包脚本会转发参数并继承调用方环境;该覆盖层可以替换继承来的 SOCKS 代理变量,以便 Node fetch 能够访问 OpenRouter 等提供方。用户使用 `pnpm dsh web` 选择 Web,使用 `pnpm dsh --profile headless "task"` 选择无头执行。独立的 ACP(Agent Client Protocol)示例仍可通过 `pnpm run demo:acp` 运行。
仓库不分发源码安装器、安装器测试套件,也不分发依赖受管理的 `current` 符号链接和带时间戳 staging worktree 的 skill。源码检出的存放位置、Git 更新,以及用户在仓库外创建的任何启动器均由用户负责。

View File

@@ -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/simplification/2026-08-12-separate-source-launch-from-build.md
2026-08-12-separate-source-launch-from-build.md: 639e9de0ce13a571e7f83929d9c76eac2664daab
2026-08-12-separate-source-launch-from-build.zh.md: bbb55123860f2bba0209e95e803fa7f49328bdef
2026-08-12-separate-source-launch-from-build.md: 0fba024bee8b3099be625a5727c6d1a53248162e
2026-08-12-separate-source-launch-from-build.zh.md: 6ce95330bb2a4c9f63bb0e3d969b07eb43941036

View File

@@ -12,7 +12,7 @@ Source modules reached through tsx and browser modules reached through built bun
## Decision
The root `dsh` script only runs `node --import tsx/esm apps/cli/src/bin.ts`. `pnpm run build` remains the separate operation that generates package and frontend artifacts. Source users run the build before the first production-like launch and whenever frontend or client-plugin artifacts need refreshing.
The root `dsh` script runs `node --import tsx/esm scripts/run-source-dsh.ts`, which overlays HTTP proxy variables for that Node process and then dispatches `apps/cli/src/bin.ts`. `pnpm run build` remains the separate operation that generates package and frontend artifacts. Source users run the build before the first production-like launch and whenever frontend or client-plugin artifacts need refreshing.
Missing Typert host artifacts fail profile boot through module-resolution errors without a build instruction. Once those host artifacts exist, missing frontend and client-plugin artifacts fail at startup with diagnostics that direct the user to `pnpm run build`. The launcher does not validate artifact freshness: existing stale frontend or client-plugin bundles are accepted and can run older browser code until the next build. After package Node halves have been built once, `pnpm run dev:web` rebuilds only packages that declare `dsh.client`; it keeps client-plugin bundles current and activates their hot-reload path, but does not rebuild the frontend shell.
@@ -30,7 +30,7 @@ This decision owns build scheduling only. The [tsx ESM source-launch decision](.
- Repeated source launches do not wait for a complete repository build, and build output is not mixed with CLI output.
- Source users own artifact freshness. Missing artifacts stop startup, but only frontend and client-plugin failures direct users to `pnpm run build`; existing stale frontend and client-plugin bundles can silently serve older browser code.
- TUI, Web, and headless selection, argument forwarding, environment inheritance, and the tsx ESM launch vector remain unchanged.
- TUI, Web, and headless selection, argument forwarding, and the tsx ESM launch vector remain unchanged. HTTP proxy overlay for the source Node process is owned by the [source HTTP proxy overlay decision](../process/2026-08-19-source-dsh-http-proxy-overlay.md).
- The root onboarding and CLI reference show build and launch as separate commands and document the stale-artifact behavior.
## Verification

View File

@@ -12,7 +12,7 @@ TypeScript 源码启动器无需在每次调用前完成整个仓库的构建。
## 决策
根目录的 `dsh` 脚本只运行 `node --import tsx/esm apps/cli/src/bin.ts`。`pnpm run build` 仍是生成包与前端产物的独立操作。源码用户在首次进行类生产启动前运行构建,并在前端或 Client plugin 产物需要刷新时再次运行。
根目录的 `dsh` 脚本运行 `node --import tsx/esm scripts/run-source-dsh.ts`,该包装层仅为本 Node 进程套用 HTTP 代理变量,然后分派 `apps/cli/src/bin.ts`。`pnpm run build` 仍是生成包与前端产物的独立操作。源码用户在首次进行类生产启动前运行构建,并在前端或 Client plugin 产物需要刷新时再次运行。
Typert Host 产物缺失时,profile 启动会因不含构建指引的模块解析错误而失败。这些 Host 产物存在后,如果前端或 Client plugin 产物缺失,启动会失败,诊断信息会指示用户运行 `pnpm run build`。启动器不会验证产物是否为最新:已有的陈旧前端或 Client plugin 组合包仍会被接受,并可能继续运行旧版浏览器代码,直至下次构建。各包的 Node 半侧至少构建过一次后,`pnpm run dev:web` 只重建声明了 `dsh.client` 的包;它会保持 Client plugin 组合包为最新状态并启用其热重载路径,但不会重建前端 shell。
@@ -30,7 +30,7 @@ Typert Host 产物缺失时,profile 启动会因不含构建指引的模块解
- 重复的源码启动无需等待完整的仓库构建,构建输出也不会与 CLI 输出混在一起。
- 源码用户负责产物新鲜度。产物缺失会阻止启动,但只有前端与 Client plugin 产物缺失的错误会指示用户运行 `pnpm run build`;已有的过期前端与 Client plugin 组合包可能静默提供旧版浏览器代码。
- TUI、Web 与无头模式选择、参数转发、环境继承,以及 tsx ESM 启动方式保持不变。
- TUI、Web 与无头模式选择、参数转发,以及 tsx ESM 启动方式保持不变。源码 Node 进程的 HTTP 代理覆盖层由[源码 HTTP 代理覆盖层决策](../process/2026-08-19-source-dsh-http-proxy-overlay.md)规定。
- 根目录上手指南与 CLI 参考将构建和启动列为独立命令,并说明过期产物行为。
## 验证

View File

@@ -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/testing/2026-08-12-required-python-runtime-pull-request-ci.md
2026-08-12-required-python-runtime-pull-request-ci.md: 2f5dcac17262bd885049221620a4d9708b083faa
2026-08-12-required-python-runtime-pull-request-ci.zh.md: 66c81f70d1bf25355425030883ccc4307efbb1ce
2026-08-12-required-python-runtime-pull-request-ci.md: 61b1e832be6d29eafe5cb304d2bca3f0a59e3d84
2026-08-12-required-python-runtime-pull-request-ci.zh.md: 92bf80688d8d152b26fdd893fe0f0b96553868e9

View File

@@ -10,11 +10,11 @@ Ordinary pull-request CI runs the complete Python SDK pytest suite against fake
## Decision
Every pull request has a required `python-runtime` job in [CI](../../../../.github/workflows/ci.yml). It calls the shared [single-executable builder](../../../../.github/workflows/build-exe-for-python-sdk.yml) for `node24-linux-x64` without a path filter and participates in `all checks passed`. The called workflow builds the real executable, runs all keyless Python full-turn and direct-binary scenarios including the committed executable snapshot, builds the SDK and runtime wheels, installs them into a clean virtual environment, checks the executable and native addon's GLIBC requirements, and runs the installed wheels in a manylinux 2.28 container.
Every pull request has a required `python-runtime` job in [CI](../../../../.github/workflows/ci.yml). It calls the shared [single-executable builder](../../../../.github/workflows/build-exe-for-python-sdk.yml) for `node24-linux-x64` without a path filter and participates in `all checks passed`. The called workflow builds the real executable, runs all keyless Python full-turn and direct-binary scenarios including both committed snapshots, builds the SDK and runtime wheels, installs them into a clean virtual environment, checks the executable and native addon's GLIBC requirements, and runs the installed wheels in a manylinux 2.28 container.
The required job and the [Python publication workflow](../process/2026-08-11-python-publication-workflow.md) use the same builder. Its concurrency key includes the caller workflow, so required CI and an explicit full release validation for the same ref do not cancel each other. The complete linux-x64, linux-arm64, and macos-arm64 matrix remains a release validation because platform-independent runtime, SDK, and snapshot behavior needs one merge-blocking native carrier, while architecture-specific executable, addon, wheel-tag, and deployment-target behavior still needs all release targets before publication.
The executable snapshot normalizes opaque session, message, subagent, and workflow-run identifiers before comparison. A newly persisted workflow event therefore changes the reviewed expected output without making a random run identifier part of that output.
The advanced executable snapshot normalizes opaque session, message, subagent, and workflow-run identifiers before comparison. A newly persisted workflow event therefore changes the reviewed expected output without making a random run identifier part of that output. The minimal scenario's [model-visible snapshot](2026-08-13-python-minimal-model-visible-snapshot.md) covers the assembled system prompt, tool schemas, and message list that this one tokenizes.
## Alternatives considered

View File

@@ -10,11 +10,11 @@ Status: implemented
## 决策
每个拉取请求都在 [CI](../../../../.github/workflows/ci.yml) 中运行必需的 `python-runtime` 作业。该作业不使用路径过滤,调用共享的[单文件可执行程序构建器](../../../../.github/workflows/build-exe-for-python-sdk.yml)构建 `node24-linux-x64`,并参与 `all checks passed`。被调用的工作流会构建真实可执行文件,运行全部无密钥 Python 完整轮次和直接二进制场景(包括检入的 exe 快照),构建 SDK 与运行时 wheel 包,将二者安装进干净的虚拟环境,检查可执行文件与原生 addon 的 GLIBC 依赖,并在 manylinux 2.28 容器中运行已安装的 wheel 包。
每个拉取请求都在 [CI](../../../../.github/workflows/ci.yml) 中运行必需的 `python-runtime` 作业。该作业不使用路径过滤,调用共享的[单文件可执行程序构建器](../../../../.github/workflows/build-exe-for-python-sdk.yml)构建 `node24-linux-x64`,并参与 `all checks passed`。被调用的工作流会构建真实可执行文件,运行全部无密钥 Python 完整轮次和直接二进制场景(包括两份检入的快照),构建 SDK 与运行时 wheel 包,将二者安装进干净的虚拟环境,检查可执行文件与原生 addon 的 GLIBC 依赖,并在 manylinux 2.28 容器中运行已安装的 wheel 包。
必需作业与 [Python 发布工作流](../process/2026-08-11-python-publication-workflow.md)共用同一构建器。其并发键包含调用方工作流,因此同一 ref 上的必需 CI 与显式完整发布验证不会互相取消。完整的 linux-x64、linux-arm64 和 macos-arm64 矩阵仍属于发布验证:平台无关的运行时、SDK 与快照行为只需要一个阻断合并的原生载体,而架构相关的可执行文件、addon、wheel 包标签与部署目标行为在发布前仍需要全部发布目标验证。
exe 快照会在比较前规范化不透明的会话、消息、subagent 和工作流运行标识符。因此,新增的持久化工作流事件会改变经过审阅的预期输出,但不会把随机运行标识符写入其中。
进阶 exe 快照会在比较前规范化不透明的会话、消息、subagent 和工作流运行标识符。因此,新增的持久化工作流事件会改变经过审阅的预期输出,但不会把随机运行标识符写入其中。极简场景的[模型可见快照](2026-08-13-python-minimal-model-visible-snapshot.md)覆盖了这份快照所占位化的已组装系统提示词、工具 schema 与消息列表。
## 曾考虑的替代方案

View File

@@ -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/testing/2026-08-13-python-minimal-model-visible-snapshot.md
2026-08-13-python-minimal-model-visible-snapshot.md: 66cfa1ed667d9a60579b0d27ddca2667614d7e1c
2026-08-13-python-minimal-model-visible-snapshot.zh.md: 40f596208f07532e68e382013b36e0d7ec46de3d

View File

@@ -0,0 +1,33 @@
# Agent Note: Python minimal-composition model-visible snapshot
Status: implemented
English | [中文](2026-08-13-python-minimal-model-visible-snapshot.zh.md)
## Problem
The Python lane never compared what the minimal composition actually shows the model. Dynamic runtime context reaches history as a user message, so the mock model's assertion that system-role messages equal the deployment persona could not see it, and the advanced executable snapshot replaces each request header's assembled system prompt with a token and each tool schema with its name. The sandbox-policy runtime-context message therefore rode along in the checked-in [minimal composition](../../../../examples/jsonrpc-agent/minimal.cordis.yml) while `python-runtime` stayed green, and any plugin that adds a system section, a tool, or another context message could do the same.
## Decision
The `sdk-minimal` scenario in [the packaged-runtime smoke](../../../../scripts/smoke-python-runtime.py) records `scripts/snapshots/python-sdk-single-exe/minimal/model-visible.json`: for every model request of the turn, the advertised tool schemas verbatim and the message list. System and user messages keep their full text with the scenario's temporary directory tokenized; assistant and tool messages keep only call identity, because their PTY and filesystem text differs across the platforms the expected output replays on.
One model-visible message is excluded: the agent loop's dynamic runtime-context snapshot. The same composition emits it on macOS and not on Linux, which the required lane runs, so no single expected output can carry it. That difference is a defect in its own right ([#2488](https://github.com/deepseek-harness/deepseek-harness/issues/2488)) — this expected output covers every other model-visible message rather than waiting for it.
The mock model no longer asserts the minimal scenario's tools and system prompts — the snapshot owns that surface and reports a complete diff instead of the first mismatch. Snapshot comparison takes its directory and file set as arguments, so the `minimal` and `advanced` expected outputs use one implementation, and `--update-snapshots` accepts `sdk-minimal`.
## Alternatives considered
**Snapshot the minimal session log, like the advanced scenario.** The minimal turn drives a real PTY and editor, so persisted tool results carry platform-dependent text. The expected output would go red for reasons unrelated to model-visible assembly, and normalizing that text away leaves the log carrying little the model-visible file does not.
**Extend the mock model's inline assertions.** Every new model-visible contribution would need another hand-written expectation, and a failure names one mismatch rather than the whole surface. Tool descriptions would also be duplicated from the composition into the script.
**Rely on the TypeScript SDK snapshot.** Its `persistent-tools` scenario pins the same composition's system prompt, tool schemas, and runtime context, but through replayed model responses and a source or `lib` runtime, in a different required job. It cannot show what the deployed executable's closure assembles for a Python caller.
## Consequences
A change to the minimal composition's model-visible surface — a system section, a tool, a tool description, or an added user message — now fails `python-runtime` with the exact diff, and landing it means rerunning `--scenario sdk-minimal --update-snapshots` and reviewing that diff. The minimal composition's tool descriptions become reviewed expected output.
Assistant and tool message text is no longer compared, and the runtime-context snapshot is not compared at all. The scenario's own assertions continue to own persistent-shell state, editor output, and the final response; [#2488](https://github.com/deepseek-harness/deepseek-harness/issues/2488) owns the excluded message until its platform difference is resolved.
[AGENTS.md](../../../../AGENTS.md) and [the testing policy](../../../../docs/testing.md) now name both SDKs as independent projections of the agent loop, session lifecycle, and `SessionEventMap`, so a change to any of those carries updating both expected outputs rather than only the one a contributor happens to run.

View File

@@ -0,0 +1,33 @@
# Agent Note:Python 极简组合的模型可见快照
Status: implemented
[English](2026-08-13-python-minimal-model-visible-snapshot.md) | 中文
## 问题
Python 通道从未比对极简组合实际展示给模型的内容。动态运行时上下文以 user 消息进入历史,因此 mock 模型"system 角色消息等于部署 persona"的断言看不见它;而进阶可执行文件快照会把每个请求头中已组装的系统提示词换成占位符、把每个工具 schema 换成其名称。于是 sandbox-policy 的运行时上下文消息一直搭车留在签入的[极简组合](../../../../examples/jsonrpc-agent/minimal.cordis.yml)里,而 `python-runtime` 始终是绿的;任何新增系统分段、工具或其他上下文消息的插件都能照此蒙混过关。
## 决策
[打包运行时冒烟测试](../../../../scripts/smoke-python-runtime.py)的 `sdk-minimal` 场景会录制 `scripts/snapshots/python-sdk-single-exe/minimal/model-visible.json`:对该回合的每个模型请求,逐字记录对外公布的工具 schema 与消息列表。system 与 user 消息保留全文,仅将场景的临时目录替换为占位符;assistant 与 tool 消息只保留调用标识,因为它们的 PTY 与文件系统文本在期望输出需要重放的各平台上并不相同。
有一条模型可见消息被排除在外:agent loop 的动态运行时上下文快照。同一组合在 macOS 上会发出它,在必需车道所用的 Linux 上不会,因此任何单一期望输出都无法承载它。该差异本身就是缺陷([#2488](https://github.com/deepseek-harness/deepseek-harness/issues/2488))——这份期望输出覆盖其余全部模型可见消息,而不是等它先被修复。
mock 模型不再断言极简场景的工具与系统提示词——该面由快照拥有,并给出完整差异而非首个不匹配项。快照比对以目录与文件集合为参数,因此 `minimal` 与 `advanced` 两份期望输出共用一套实现,且 `--update-snapshots` 接受 `sdk-minimal`。
## 曾考虑的替代方案
**像进阶场景那样对极简会话日志做快照。** 极简回合驱动真实 PTY 与编辑器,持久化的工具结果带有平台相关文本。期望输出会因与模型可见组装无关的原因变红;而把这些文本归一化掉之后,日志所承载的内容也就所剩无几。
**扩展 mock 模型中的内联断言。** 每新增一项模型可见贡献都要再手写一条期望,且失败只会指出一处不匹配而非整个面。工具描述还会从组合复制进脚本,形成重复。
**依赖 TypeScript SDK 快照。** 其 `persistent-tools` 场景固定了同一组合的系统提示词、工具 schema 与运行时上下文,但走的是重放的模型响应与 source 或 `lib` 运行时,且位于另一个必需任务中。它无法体现已部署可执行文件的闭包为 Python 调用方组装出什么。
## 后果
极简组合模型可见面的改动——系统分段、工具、工具描述或新增的 user 消息——现在会让 `python-runtime` 带着精确差异失败;要让它落地,就必须重新运行 `--scenario sdk-minimal --update-snapshots` 并审阅该差异。极简组合的工具描述由此成为经过审阅的期望输出。
assistant 与 tool 消息文本不再参与比对,运行时上下文快照则完全不参与比对。持久 shell 状态、编辑器输出与最终响应仍由该场景自身的断言拥有;被排除的那条消息由 [#2488](https://github.com/deepseek-harness/deepseek-harness/issues/2488) 负责,直到其平台差异得到解决。
[AGENTS.md](../../../../AGENTS.md) 与[测试政策](../../../../docs/testing.md)现已点明两个 SDK 都是 agent loop、会话生命周期与 `SessionEventMap` 的独立投影,因此改动其中任何一项都要连带更新两侧的期望输出,而不只是贡献者恰好会运行的那一侧。

View File

@@ -194,6 +194,7 @@ jobs:
*) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
esac
addon_dir="$(realpath packages/subprocess/subprocess-local/node_modules/node-pty)"
(cd "$addon_dir" && npm_config_build_from_source=true pnpm run install)
addon="$addon_dir/build/Release/pty.node"
[ -f "$addon_dir/build/Makefile" ] || {
echo "::error::node-pty install did not generate $addon_dir/build/Makefile"

View File

@@ -455,6 +455,9 @@ jobs:
timeout-minutes: 120
env:
DSH_COVERAGE_MAX_WORKERS: '2'
# Instrumented process and polling fixtures can exceed Vitest's defaults
# under the complete lane's concurrent gate load.
DSH_COVERAGE_TEST_TIMEOUT_MS: '30000'
DSH_GATE_CONCURRENCY: '2'
DSH_PUBLINT_CONCURRENCY: '8'
steps:

3
.gitignore vendored
View File

@@ -1,10 +1,13 @@
CLAUDE.local.md
.env
.dsh-http-proxy.env
node_modules/
lib/
*.tsbuildinfo
pnpm-debug.log
.pnpm-store/
.offline-store/
.offline-cache/
.cache/
examples/*/*.jsonl
.storages/

View File

@@ -123,6 +123,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`,
- **Testing policy** — [docs/testing.md](docs/testing.md). Every non-trivial model- or product-user-visible behavior change adds or updates a keyless snapshot through a real runnable example in the same PR; package tests, e2e-only assertions, and mock-only fixtures do not substitute for the assembled application transcript. Fixtures must replay on macOS/Linux; fix fixtures, not normalizers.
- **A tool's UI render intent is part of its design**, decided up front (`generic`/`terminal`/`diff`, `locations`); presentation methods are pure functions of `args` ([cookbook](docs/cookbook/adding-a-tool.md)).
- **Plan unit, e2e, and snapshot coverage** for capability seams, lifecycle paths, and transcript output; include missing snapshot-harness support in the same change.
- **Both SDKs project the loop.** Agent-loop, session-lifecycle, and `SessionEventMap` changes update the TypeScript and Python SDK expected outputs in the same PR; `pnpm run test` covers neither ([surfaces](docs/testing.md#when-a-snapshot-test-is-required)).
- **Choose PR history deliberately.** Split independent changes; fix the introducing PR before propagation. Standalone PRs and official stacks may merge-forward or rebase after review. Rewrites use `--force-with-lease`, abort on remote movement, never raw `--force`; an in-progress merge-forward preserves its checkpoint before taking a newer base ([rationale](.agents/notes/implemented/process/2026-08-02-native-github-stacks-and-optional-rebases.md)).
- **Labels:** one PR `kind/*`, all material `area/*`, and native Issue Type ([taxonomy](.agents/notes/implemented/process/2026-08-08-unified-github-label-taxonomy.md)).
- TODO markers: `FIXME`/`TODO`/`XXX` by urgency ([semantics](docs/development.md)).

View File

@@ -93,7 +93,7 @@ External packages that a workspace package resolves at runtime. The tier covers
pnpm applies local patches to the following packages at install time, so shipped artifacts carry modified copies; each patch file is the complete record of the modification:
- `node-pty@1.1.0` — [`patches/node-pty@1.1.0.patch`](patches/node-pty@1.1.0.patch)
- `node-pty@1.2.0-beta.15` — [`patches/node-pty@1.2.0-beta.15.patch`](patches/node-pty@1.2.0-beta.15.patch)
## Official Claude Code platform payloads

55
_probe2.py Normal file
View File

@@ -0,0 +1,55 @@
import urllib.request, ssl, json, urllib.parse, socket
ctx = ssl.create_default_context()
def get(url, headers=None, timeout=10):
h = headers or {'user-agent': 'Mozilla/5.0 (compatible; deepseek-harness/0.0.1)'}
req = urllib.request.Request(url, headers=h)
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as r:
return r.status, r.read().decode('utf-8', 'replace')
# Independent recent instance lists
lists = [
'https://searx.space/data/instances.json',
'https://raw.githubusercontent.com/searxng/searx-instances/master/instances.json',
]
seen = {}
for src in lists:
try:
st, body = get(src)
data = json.loads(body)
inst = data.get('instances', data) if isinstance(data, dict) else {}
if isinstance(inst, dict):
for host, meta in inst.items():
if isinstance(meta, dict):
urlv = meta.get('url') or (f'https://{host}' if not host.startswith('http') else host)
jf = meta.get('json', False)
seen.setdefault(urlv.rstrip('/'), {'json': jf, 'meta': meta})
print(f'[{src}] status={st} counted={len(seen)}')
except Exception as e:
print(f'[{src}] FAIL {type(e).__name__}: {e}')
print('total candidates:', len(seen))
q = urllib.parse.quote('погода Норильск')
found = []
# Probe json-capable first, then all https ones briefly
pref = [u for u, m in seen.items() if m.get('json')]
rest = [u for u, m in seen.items() if not m.get('json')]
for u in pref + rest:
if not u.startswith('http'):
continue
base = u.rstrip('/')
url = f'{base}/search?q={q}&format=json'
try:
st, body = get(url, {'accept': 'application/json', 'user-agent': 'deepseek-harness/0.0.1'}, timeout=8)
if '"results"' in body and ('"url"' in body or 'results' in body):
found.append((base, st, body[:600]))
print(f'!!! CANDIDATE {base} HTTP {st}')
print(body[:600])
break
except Exception as e:
pass
if not found:
print('NO working json instance among', len(seen), 'candidates')

View File

@@ -1,7 +1,7 @@
{
"name": "@deepseek-ai/dsh",
"description": "dsh CLI: profile boot, plugin management, and the browser UI alias",
"version": "0.1.0-rc.6",
"version": "0.1.0-rc.7",
"publishConfig": {
"access": "public"
},

View File

@@ -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 apps/cli/reference/README.md
README.md: 0be64fdfc0ad4e81d23f25a26881fa89f37565b0
README.zh.md: 649bf15df814abf4875fed794c2e76c494bfcc25
README.md: 4d0fca9e7749da722aa3210898bdc8566b706191
README.zh.md: b254e891b304173d145be0969d6661a7a1ac3f66

View File

@@ -81,4 +81,4 @@ Install external plugin bundles through `dsh plugin --profile <name> add <packag
## Source execution
From the repository root, run `pnpm run build` separately after a fresh checkout and whenever artifacts need updating, then use `pnpm dsh <args...>`. The `package.json` script launches `apps/cli/src/bin.ts` with `node --import tsx/esm` without building and forwards every argument. Missing Typert host artifacts fail profile boot through module-resolution errors without a build instruction. Once those host artifacts exist, missing frontend or client-plugin bundles fail at startup with an instruction to run `pnpm run build`. The launcher does not check freshness, so existing stale bundles can run older browser code until rebuilt. The process inherits the launch environment; set `NODE_USE_ENV_PROXY=1` when a supporting Node version must honor `HTTP_PROXY` and `HTTPS_PROXY`. The installed form launches the built `apps/cli/lib/bin.js` without rebuilding the repository.
From the repository root, run `pnpm run build` separately after a fresh checkout and whenever artifacts need updating, then use `pnpm dsh <args...>`. The `package.json` script launches [`scripts/run-source-dsh.ts`](../../../scripts/run-source-dsh.ts) with `node --import tsx/esm` without building. When `.dsh-http-proxy.env` or `DSH_HTTP_PROXY` is set, that wrapper respawns Node with HTTP proxy variables and `NODE_USE_ENV_PROXY=1` so `fetch` binds them at start, then dispatches `apps/cli/src/bin.ts` and forwards every argument. Missing Typert host artifacts fail profile boot through module-resolution errors without a build instruction. Once those host artifacts exist, missing frontend or client-plugin bundles fail at startup with an instruction to run `pnpm run build`. The launcher does not check freshness, so existing stale bundles can run older browser code until rebuilt. The process inherits the launch environment; the respawned process can replace an inherited SOCKS proxy because Node cannot fetch through SOCKS, and `.env` files cannot set `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`. The installed form launches the built `apps/cli/lib/bin.js` without rebuilding the repository.

View File

@@ -81,4 +81,4 @@ dsh web --help
## 源码执行
请在仓库根目录中,于全新 checkout 之后及产物需要更新时单独运行 `pnpm run build`,然后使用 `pnpm dsh <args...>`。`package.json` 中的脚本不会构建,而是通过 `node --import tsx/esm` 启动 `apps/cli/src/bin.ts`,并转发所有参数。Typert Host 产物缺失时,profile 启动会因不含构建指引的模块解析错误而失败。这些 Host 产物存在后,如果前端或 Client plugin 组合包缺失,启动会失败并提示运行 `pnpm run build`。启动器不会检查产物是否为最新,因此已有的陈旧组合包可能继续运行旧版浏览器代码,直至重新构建。该进程会继承启动环境;当支持环境代理的 Node 版本必须遵循 `HTTP_PROXY` 和 `HTTPS_PROXY` 时,请设置 `NODE_USE_ENV_PROXY=1`。安装形式会直接启动构建后的 `apps/cli/lib/bin.js`,不会重新构建仓库。
请在仓库根目录中,于全新 checkout 之后及产物需要更新时单独运行 `pnpm run build`,然后使用 `pnpm dsh <args...>`。`package.json` 中的脚本不会构建,而是通过 `node --import tsx/esm` 启动 [`scripts/run-source-dsh.ts`](../../../scripts/run-source-dsh.ts)。当设置了 `.dsh-http-proxy.env` 或 `DSH_HTTP_PROXY` 时,该包装层会用 HTTP 代理变量和 `NODE_USE_ENV_PROXY=1` 再拉起 Node,以便 `fetch` 在启动时绑定它们,随后分派 `apps/cli/src/bin.ts` 并转发所有参数。Typert Host 产物缺失时,profile 启动会因不含构建指引的模块解析错误而失败。这些 Host 产物存在后,如果前端或 Client plugin 组合包缺失,启动会失败并提示运行 `pnpm run build`。启动器不会检查产物是否为最新,因此已有的陈旧组合包可能继续运行旧版浏览器代码,直至重新构建。该进程会继承启动环境;再拉起的进程可以替换继承来的 SOCKS 代理,因为 Node 无法通过 SOCKS 发起 fetch,且 `.env` 文件不能设置 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`。安装形式会直接启动构建后的 `apps/cli/lib/bin.js`,不会重新构建仓库。

View File

@@ -4,9 +4,9 @@ import { execa } from 'execa'
import { describe, expect, it } from 'vitest'
/**
* Keyless smoke for SOURCE `dsh` execution: run `apps/cli/src/bin.ts`
* Keyless smoke for SOURCE `dsh` execution: run `scripts/run-source-dsh.ts`
* with the exact production runtime vector (`node --import tsx/esm`, the
* vector the root `dsh` script invokes directly) and assert the
* vector the root `dsh` script invokes) and assert the
* required-config diagnostic. The Node compatibility matrix runs this
* WHOLE file, so a Node release changing module hooks or TypeScript handling
* breaks this gate instead of every developer's `pnpm dsh`; the built-bin
@@ -14,14 +14,14 @@ import { describe, expect, it } from 'vitest'
*/
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
const dshSourceBin = 'apps/cli/src/bin.ts'
const dshSourceBin = 'scripts/run-source-dsh.ts'
describe('dsh SOURCE launcher (node --import tsx/esm)', () => {
it('launches the source CLI without building', async () => {
const rootPackage = JSON.parse(await readFile(new URL('../../../package.json', import.meta.url), 'utf8')) as {
readonly scripts?: Record<string, string>
}
expect(rootPackage.scripts?.dsh).toBe('node --import tsx/esm apps/cli/src/bin.ts')
expect(rootPackage.scripts?.dsh).toBe('node --import tsx/esm scripts/run-source-dsh.ts')
})
it('boots the source entry and requires a profile', async () => {

View File

@@ -1,7 +1,7 @@
{
"name": "@deepseek-ai/dsh-web-frontend",
"description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web",
"version": "0.1.0-rc.6",
"version": "0.1.0-rc.7",
"publishConfig": {
"access": "public"
},

View File

@@ -1,6 +1,8 @@
- region "Which color do you prefer?":
- text: Pick one
- heading "Which color do you prefer?" [level=2]
- button "Collapse the question card" [expanded]:
- img
- button "Dismiss all questions":
- img
- group:

View File

@@ -1,6 +1,8 @@
- region "Which color do you prefer?":
- text: Pick one
- heading "Which color do you prefer?" [level=2]
- button "Collapse the question card" [expanded]:
- img
- button "Dismiss all questions":
- img
- group:

View File

@@ -32,6 +32,8 @@
- region "Ready to continue?":
- text: Checkpoint
- heading "Ready to continue?" [level=2]
- button "Collapse the question card" [expanded]:
- img
- button "Dismiss all questions":
- img
- radiogroup:

View File

@@ -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/capability-seams.md
capability-seams.md: a990a9dd4d92d10e37b82e6a63caa4a5a469c441
capability-seams.zh.md: 441d9222835e67c4a9f657d2335860005934094b
capability-seams.md: d6022d8889075b8bf839a24bb916c71ec73f5d4a
capability-seams.zh.md: 99cc8ee117994af574a27b52a1e2c9ccf4270be9

View File

@@ -164,6 +164,7 @@ flowchart LR
pkg_web["web"]
svc_web["ctx.web<br/>Web access provider registry"]
pkg_web_search_exa["web-search-exa"]
pkg_web_search_searxng["web-search-searxng"]
pkg_web_search_perplexity["web-search-perplexity"]
pkg_web_search_deepseek["web-search-deepseek"]
pkg_web_fetch_http["web-fetch-http"]
@@ -190,6 +191,9 @@ flowchart LR
pkg_lsp_local["lsp-local"]
pkg_tool_lsp["tool-lsp"]
svc_apiProxy["ctx.apiProxy<br/>Host API dispatch"]
pkg_openrouter_usage["openrouter-usage"]
svc_openRouterUsage["ctx.openRouterUsage<br/>OpenRouter usage and balance gateway"]
pkg_ui_openrouter_usage["ui-openrouter-usage"]
pkg_cordis_host_runner["cordis-host-runner"]
svc_dynamicCordisRunner["ctx.dynamicCordisRunner<br/>Dynamic Cordis package host runner"]
svc_cordisInspect["ctx.cordisInspect<br/>Dynamic Cordis inspect registry"]
@@ -235,6 +239,7 @@ flowchart LR
pkg_lsp_local --> svc_lsp
pkg_message_feedback --> svc_messageFeedback
pkg_modules --> svc_clientModules
pkg_openrouter_usage --> svc_openRouterUsage
pkg_permission_presets --> svc_permissionPresets
pkg_plan_mode --> svc_planMode
pkg_pwsh_local --> svc_shell
@@ -290,6 +295,7 @@ flowchart LR
pkg_web_search_deepseek --> svc_web
pkg_web_search_exa --> svc_web
pkg_web_search_perplexity --> svc_web
pkg_web_search_searxng --> svc_web
pkg_webserver --> svc_webServer
pkg_workflow --> svc_workflowEngine
pkg_workflow_worker_thread --> svc_workflowEngine
@@ -328,6 +334,7 @@ flowchart LR
svc_llm --> pkg_agent_loop
svc_llm --> pkg_compaction_basic
svc_lsp --> pkg_tool_lsp
svc_openRouterUsage --> pkg_ui_openrouter_usage
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_terminal_bash
svc_sandboxPolicy --> pkg_bash_sandbox
@@ -457,7 +464,7 @@ flowchart LR
| `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. |
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. |
| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | - | Producers (background bash, PTY sends, and subagent delegations) register running work; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. |
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. |
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-searxng`](../packages/web/web-search-searxng), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. |
| `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill. |
| `ctx.directoryPicker` | `seam` | `directory-picker` | `directory-picker-native`, `directory-picker-browse` | `apiproxy` | - | Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement). |
| `ctx.webServer` | `core` | `webserver` | - | `connection`, `modules`, `hmr` | - | Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes. |
@@ -465,6 +472,7 @@ flowchart LR
| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | `lsp-local` | [`tool-lsp`](../packages/lsp/tool-lsp) | - | Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result. |
| `ctx.apiProxy` | `core` | `apiproxy` | - | `connection` | - | The transport-agnostic host gateway face: it dispatches browser API calls, and each open host stream subscribes to the events it forwards rather than being pushed to through a broadcast verb. |
| `ctx.openRouterUsage` | `core` | [`openrouter-usage`](../packages/llm/openrouter-usage) | - | `ui-openrouter-usage` | - | The host gateway folds token-usage projections and a live balance snapshot; the web UI client reaches the same service over the wire through its remote namespace. |
| `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace. |
| `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Registers host inspect providers, mirrors the client provider manifest, and routes client queries through the dynamic Cordis transport. |

View File

@@ -166,6 +166,7 @@ flowchart LR
pkg_web["web"]
svc_web["ctx.web<br/>Web access provider registry"]
pkg_web_search_exa["web-search-exa"]
pkg_web_search_searxng["web-search-searxng"]
pkg_web_search_perplexity["web-search-perplexity"]
pkg_web_search_deepseek["web-search-deepseek"]
pkg_web_fetch_http["web-fetch-http"]
@@ -192,6 +193,9 @@ flowchart LR
pkg_lsp_local["lsp-local"]
pkg_tool_lsp["tool-lsp"]
svc_apiProxy["ctx.apiProxy<br/>Host API dispatch"]
pkg_openrouter_usage["openrouter-usage"]
svc_openRouterUsage["ctx.openRouterUsage<br/>OpenRouter usage and balance gateway"]
pkg_ui_openrouter_usage["ui-openrouter-usage"]
pkg_cordis_host_runner["cordis-host-runner"]
svc_dynamicCordisRunner["ctx.dynamicCordisRunner<br/>Dynamic Cordis package host runner"]
svc_cordisInspect["ctx.cordisInspect<br/>Dynamic Cordis inspect registry"]
@@ -237,6 +241,7 @@ flowchart LR
pkg_lsp_local --> svc_lsp
pkg_message_feedback --> svc_messageFeedback
pkg_modules --> svc_clientModules
pkg_openrouter_usage --> svc_openRouterUsage
pkg_permission_presets --> svc_permissionPresets
pkg_plan_mode --> svc_planMode
pkg_pwsh_local --> svc_shell
@@ -292,6 +297,7 @@ flowchart LR
pkg_web_search_deepseek --> svc_web
pkg_web_search_exa --> svc_web
pkg_web_search_perplexity --> svc_web
pkg_web_search_searxng --> svc_web
pkg_webserver --> svc_webServer
pkg_workflow --> svc_workflowEngine
pkg_workflow_worker_thread --> svc_workflowEngine
@@ -330,6 +336,7 @@ flowchart LR
svc_llm --> pkg_agent_loop
svc_llm --> pkg_compaction_basic
svc_lsp --> pkg_tool_lsp
svc_openRouterUsage --> pkg_ui_openrouter_usage
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_terminal_bash
svc_sandboxPolicy --> pkg_bash_sandbox
@@ -459,7 +466,7 @@ flowchart LR
| `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 |
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 |
| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | - | 生产方(后台 bash、PTY 发送和 subagent 委派)登记正在运行的工作;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 |
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | 搜索和抓取提供方注册到同一个 ctx.web seam;tool-web 负责稳定的面向模型名称。 |
| `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-searxng`](../packages/web/web-search-searxng), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | 搜索和抓取提供方注册到同一个 ctx.web seam;tool-web 负责稳定的面向模型名称。 |
| `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | 后端保存过大的工具文本,并返回面向模型的定位信息和取回提示;spill-policy 是 tools/post-execute 消费方,负责决定何时 spill。 |
| `ctx.directoryPicker` | `seam` | `directory-picker` | `directory-picker-native`, `directory-picker-browse` | `apiproxy` | - | 带判别标记的交互能力:原生后端在 Host 显示设备上打开一个操作系统选择器,浏览后端为应用内浏览器提供列表与创建原语;双端后端通过其浏览器侧填充 ui-workspace 目录流程的 slot(不通过协议发布)。 |
| `ctx.webServer` | `core` | `webserver` | - | `connection`, `modules`, `hmr` | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 |
@@ -467,6 +474,7 @@ flowchart LR
| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 |
| `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | `lsp-local` | [`tool-lsp`](../packages/lsp/tool-lsp) | - | 提供方注册与选择,加上恰好四种操作的标准化查询执行;该 seam 不提供协议逃生口,后端必须转换为标准化请求和结果。 |
| `ctx.apiProxy` | `core` | `apiproxy` | - | `connection` | - | 与传输无关的 Host 网关接口:它分派浏览器 API 调用,每条打开的 Host 流自行订阅转发事件,而不是由广播方法向其推送。 |
| `ctx.openRouterUsage` | `core` | [`openrouter-usage`](../packages/llm/openrouter-usage) | - | `ui-openrouter-usage` | - | Host 网关折叠 token 用量投影与实时余额快照;Web UI 客户端通过其 remote 命名空间经线路访问同一服务。 |
| `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 拥有内存定义注册表、Host 半的 vm 沙箱和 request-run 往返流程;浏览器页面通过其 Remote 命名空间在线访问同一服务。 |
| `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 注册 Host inspect 提供方、镜像 Client 提供方 manifest,并通过动态 Cordis 传输路由 Client 查询。 |

View File

@@ -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: 4f22ed3da7de81f94d6fc5ee55a305d117c126e7
config-catalog.zh.md: 7054ec8b52a8c46bc1ace97112f086119a61cfec
config-catalog.md: 040deb80fe232dc61bba1b2041a541d33516973f
config-catalog.zh.md: e0166072b66d4a940c5242f23974a602a5658f1a

View File

@@ -29,7 +29,7 @@ export interface AcpConfig {
Depends on: `Stream` (`@agentclientprotocol/sdk`)
Source: [`packages/acp/acp/src/index.ts:70`](../packages/acp/acp/src/index.ts)
Source: [`packages/acp/acp/src/index.ts:71`](../packages/acp/acp/src/index.ts)
<a id="deepseek-aidsh-acp-demo"></a>
@@ -1293,6 +1293,28 @@ export interface Config {
Source: [`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedback/message-feedback/src/index.ts)
<a id="deepseek-aidsh-openrouter-usage"></a>
## `@deepseek-ai/dsh-openrouter-usage`
```ts config-catalog
/** Plugin config (all optional — the service fills env-var and constant defaults). */
export interface Config {
/** Credential reference resolved per refresh; defaults to `OPENROUTER_API_KEY`. */
apiKeyEnv?: string
/** OpenRouter API root; `/models` and `/auth/key` are appended. */
baseURL?: string
/** Whether periodic pricing/balance sync runs. Defaults to true. */
syncEnabled?: boolean
/** Pricing-table refresh interval, ms. Defaults to 6h. */
pricingRefreshMs?: number
/** Balance refresh interval, ms. Defaults to 60s. */
balanceRefreshMs?: number
}
```
Source: [`packages/llm/openrouter-usage/src/index.ts:48`](../packages/llm/openrouter-usage/src/index.ts)
<a id="deepseek-aidsh-permission-presets"></a>
## `@deepseek-ai/dsh-permission-presets`
@@ -2991,6 +3013,28 @@ export interface Config {
Source: [`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts)
<a id="deepseek-aidsh-web-search-searxng"></a>
## `@deepseek-ai/dsh-web-search-searxng`
Requires: `web`
```ts config-catalog
/** Plugin config. `baseURL` is optional in the schema so an omitted value
* surfaces as an unavailable provider (the seam's `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)
* rather than failing boot; `apply` fills constant defaults. */
export interface Config {
/** SearXNG instance base; `/search` is appended. Empty/unparseable makes the provider unavailable. */
baseURL?: string
/** SearXNG `language` to request. Defaults to `auto`. */
language?: string
/** SearXNG `time_range` recency filter. Omitted = no filter. */
timeRange?: 'day' | 'week' | 'month' | 'year'
}
```
Source: [`packages/web/web-search-searxng/src/index.ts:40`](../packages/web/web-search-searxng/src/index.ts)
<a id="deepseek-aidsh-workflow-worker-thread"></a>
## `@deepseek-ai/dsh-workflow-worker-thread`
@@ -3044,6 +3088,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-client-ui-layout` ([`packages/client/ui-layout/src/index.ts`](../packages/client/ui-layout/src/index.ts))
- `@deepseek-ai/dsh-client-ui-message-feedback` ([`packages/client/ui-message-feedback/src/index.ts`](../packages/client/ui-message-feedback/src/index.ts))
- `@deepseek-ai/dsh-client-ui-model-selection` ([`packages/client/ui-model-selection/src/index.ts`](../packages/client/ui-model-selection/src/index.ts))
- `@deepseek-ai/dsh-client-ui-openrouter-usage` ([`packages/client/ui-openrouter-usage/src/index.ts`](../packages/client/ui-openrouter-usage/src/index.ts))
- `@deepseek-ai/dsh-client-ui-permission-presets` ([`packages/client/ui-permission-presets/src/index.ts`](../packages/client/ui-permission-presets/src/index.ts))
- `@deepseek-ai/dsh-client-ui-plan` ([`packages/client/ui-plan/src/index.ts`](../packages/client/ui-plan/src/index.ts))
- `@deepseek-ai/dsh-client-ui-settings` ([`packages/client/ui-settings/src/index.ts`](../packages/client/ui-settings/src/index.ts))

View File

@@ -31,7 +31,7 @@ export interface AcpConfig {
依赖:`Stream`(`@agentclientprotocol/sdk`)
来源:[`packages/acp/acp/src/index.ts:70`](../packages/acp/acp/src/index.ts)
来源:[`packages/acp/acp/src/index.ts:71`](../packages/acp/acp/src/index.ts)
<a id="deepseek-aidsh-acp-demo"></a>
@@ -1295,6 +1295,28 @@ export interface Config {
来源:[`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedback/message-feedback/src/index.ts)
<a id="deepseek-aidsh-openrouter-usage"></a>
## `@deepseek-ai/dsh-openrouter-usage`
```ts config-catalog
/** Plugin config (all optional — the service fills env-var and constant defaults). */
export interface Config {
/** Credential reference resolved per refresh; defaults to `OPENROUTER_API_KEY`. */
apiKeyEnv?: string
/** OpenRouter API root; `/models` and `/auth/key` are appended. */
baseURL?: string
/** Whether periodic pricing/balance sync runs. Defaults to true. */
syncEnabled?: boolean
/** Pricing-table refresh interval, ms. Defaults to 6h. */
pricingRefreshMs?: number
/** Balance refresh interval, ms. Defaults to 60s. */
balanceRefreshMs?: number
}
```
来源:[`packages/llm/openrouter-usage/src/index.ts:48`](../packages/llm/openrouter-usage/src/index.ts)
<a id="deepseek-aidsh-permission-presets"></a>
## `@deepseek-ai/dsh-permission-presets`
@@ -2993,6 +3015,28 @@ export interface Config {
来源:[`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts)
<a id="deepseek-aidsh-web-search-searxng"></a>
## `@deepseek-ai/dsh-web-search-searxng`
需要:`web`
```ts config-catalog
/** Plugin config. `baseURL` is optional in the schema so an omitted value
* surfaces as an unavailable provider (the seam's `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)
* rather than failing boot; `apply` fills constant defaults. */
export interface Config {
/** SearXNG instance base; `/search` is appended. Empty/unparseable makes the provider unavailable. */
baseURL?: string
/** SearXNG `language` to request. Defaults to `auto`. */
language?: string
/** SearXNG `time_range` recency filter. Omitted = no filter. */
timeRange?: 'day' | 'week' | 'month' | 'year'
}
```
来源:[`packages/web/web-search-searxng/src/index.ts:40`](../packages/web/web-search-searxng/src/index.ts)
<a id="deepseek-aidsh-workflow-worker-thread"></a>
## `@deepseek-ai/dsh-workflow-worker-thread`
@@ -3046,6 +3090,7 @@ export interface Config {
- `@deepseek-ai/dsh-client-ui-layout`([`packages/client/ui-layout/src/index.ts`](../packages/client/ui-layout/src/index.ts))
- `@deepseek-ai/dsh-client-ui-message-feedback`([`packages/client/ui-message-feedback/src/index.ts`](../packages/client/ui-message-feedback/src/index.ts))
- `@deepseek-ai/dsh-client-ui-model-selection`([`packages/client/ui-model-selection/src/index.ts`](../packages/client/ui-model-selection/src/index.ts))
- `@deepseek-ai/dsh-client-ui-openrouter-usage`([`packages/client/ui-openrouter-usage/src/index.ts`](../packages/client/ui-openrouter-usage/src/index.ts))
- `@deepseek-ai/dsh-client-ui-permission-presets`([`packages/client/ui-permission-presets/src/index.ts`](../packages/client/ui-permission-presets/src/index.ts))
- `@deepseek-ai/dsh-client-ui-plan`([`packages/client/ui-plan/src/index.ts`](../packages/client/ui-plan/src/index.ts))
- `@deepseek-ai/dsh-client-ui-settings`([`packages/client/ui-settings/src/index.ts`](../packages/client/ui-settings/src/index.ts))

View File

@@ -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/event-producer-consumer.md
event-producer-consumer.md: 6a79e6f7ce5addc64b10efa8da7a886dcfb36dc2
event-producer-consumer.zh.md: f7576a8e28e4f1db2c65c324595c05c98b8fe488
event-producer-consumer.md: 6adc181c811b34433801c337b36b4ebf086a4fae
event-producer-consumer.zh.md: a0e1f993eaf342b555450086f42ca151d1e0d6d5

View File

@@ -29,7 +29,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:397`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:367`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:373`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:29`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:29`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials), [`openrouter-usage`](../packages/llm/openrouter-usage) |
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) |
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:76`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem) |

View File

@@ -31,7 +31,7 @@
| `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:397`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:367`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:373`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `apiproxy` |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:29`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/types.ts:29`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials), [`openrouter-usage`](../packages/llm/openrouter-usage) |
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) |
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:76`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem) |

View File

@@ -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: 24ee9a4815b4236336ae37f6c926dba4718dafb9
module-graph.zh.md: d87feb946a43a2f9d391e7894b4a143af4e18406
module-graph.md: 1a09c6c39dd0710b490ec23ae76b928cd6edf8d6
module-graph.zh.md: 811ee0899068ce475df8b6b168513f60a492d56f

View File

@@ -21,6 +21,7 @@ flowchart TD
pkg_llm_deepseek["llm-deepseek"]
pkg_llm_pi_ai["llm-pi-ai"]
pkg_llm_retry["llm-retry"]
pkg_openrouter_usage["openrouter-usage"]
pkg_token_meter["token-meter"]
end
subgraph group_core["packages/core"]
@@ -74,6 +75,7 @@ flowchart TD
pkg_web_search_deepseek["web-search-deepseek"]
pkg_web_search_exa["web-search-exa"]
pkg_web_search_perplexity["web-search-perplexity"]
pkg_web_search_searxng["web-search-searxng"]
end
subgraph group_spill["packages/spill"]
pkg_spill["spill"]
@@ -137,6 +139,7 @@ flowchart TD
pkg_client_ui_layout["client-ui-layout"]
pkg_client_ui_message_feedback["client-ui-message-feedback"]
pkg_client_ui_model_selection["client-ui-model-selection"]
pkg_client_ui_openrouter_usage["client-ui-openrouter-usage"]
pkg_client_ui_permission_presets["client-ui-permission-presets"]
pkg_client_ui_plan["client-ui-plan"]
pkg_client_ui_primitives["client-ui-primitives"]
@@ -455,6 +458,9 @@ flowchart TD
pkg_web_search_perplexity --> pkg_invariants
pkg_web_search_perplexity --> pkg_launch_environment
pkg_web_search_perplexity --> pkg_web
pkg_web_search_searxng --> pkg_invariants
pkg_web_search_searxng --> pkg_settings
pkg_web_search_searxng --> pkg_web
pkg_spill --> pkg_brand
pkg_spill --> pkg_invariants
pkg_spill --> pkg_llm
@@ -486,6 +492,14 @@ flowchart TD
pkg_llm_retry --> pkg_llm
pkg_llm_retry --> pkg_session
pkg_llm_retry --> pkg_timeout
pkg_openrouter_usage --> pkg_credentials
pkg_openrouter_usage --> pkg_invariants
pkg_openrouter_usage --> pkg_launch_environment
pkg_openrouter_usage --> pkg_llm
pkg_openrouter_usage --> pkg_session
pkg_openrouter_usage --> pkg_session_projection
pkg_openrouter_usage --> pkg_settings
pkg_openrouter_usage --> pkg_typert_protocol
pkg_agent_default_model --> pkg_agent
pkg_agent_default_model --> pkg_invariants
pkg_agent_default_model --> pkg_llm
@@ -637,7 +651,9 @@ flowchart TD
pkg_session_query --> pkg_session_persistence
pkg_session_query --> pkg_session_title
pkg_acp --> pkg_agent
pkg_acp --> pkg_attachment
pkg_acp --> pkg_invariants
pkg_acp --> pkg_llm
pkg_acp --> pkg_session
pkg_acp --> pkg_user_approval
pkg_headless --> pkg_agent
@@ -869,6 +885,7 @@ flowchart TD
pkg_tool_lsp --> pkg_system_prompt
pkg_tool_lsp --> pkg_timeout
pkg_tool_lsp --> pkg_tools
pkg_mcp_client --> pkg_attachment
pkg_mcp_client --> pkg_invariants
pkg_mcp_client --> pkg_llm
pkg_mcp_client --> pkg_subprocess
@@ -1004,6 +1021,7 @@ flowchart TD
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_llm
pkg_api_remotes --> pkg_message_feedback
pkg_api_remotes --> pkg_openrouter_usage
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_settings
@@ -1324,6 +1342,15 @@ flowchart TD
pkg_client_ui_message_feedback --> pkg_invariants
pkg_client_ui_message_feedback --> pkg_message_feedback
pkg_client_ui_message_feedback --> pkg_typert_protocol
pkg_client_ui_openrouter_usage --> pkg_api_remotes
pkg_client_ui_openrouter_usage --> pkg_client_locale
pkg_client_ui_openrouter_usage --> pkg_client_runtime
pkg_client_ui_openrouter_usage --> pkg_client_ui_conversation
pkg_client_ui_openrouter_usage --> pkg_client_ui_primitives
pkg_client_ui_openrouter_usage --> pkg_client_ui_sidebar
pkg_client_ui_openrouter_usage --> pkg_client_ui_slots
pkg_client_ui_openrouter_usage --> pkg_invariants
pkg_client_ui_openrouter_usage --> pkg_openrouter_usage
pkg_client_ui_plan --> pkg_api_remotes
pkg_client_ui_plan --> pkg_client_locale
pkg_client_ui_plan --> pkg_client_runtime
@@ -1480,6 +1507,7 @@ flowchart TD
| [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`web-search-searxng`](../packages/web/web-search-searxng) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) |
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) |
| [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
@@ -1489,6 +1517,7 @@ flowchart TD
| [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
| [`acp-snapshot`](../packages/test-support/acp-snapshot) | `test-support` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`openrouter-usage`](../packages/llm/openrouter-usage) | `llm` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) |
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
@@ -1522,7 +1551,7 @@ flowchart TD
| [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-title`](../packages/session/session-title) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) |
| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
@@ -1561,7 +1590,7 @@ flowchart TD
| [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
| [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
@@ -1582,7 +1611,7 @@ flowchart TD
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`openrouter-usage`](../packages/llm/openrouter-usage), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
@@ -1626,6 +1655,7 @@ flowchart TD
| [`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/runtime-diagnostics/invariants) |
| [`client-ui-jobs`](../packages/client/ui-jobs) | `client` | [`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/runtime-diagnostics/invariants) |
| [`client-ui-message-feedback`](../packages/client/ui-message-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/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback), [`typert-protocol`](../packages/typert/protocol) |
| [`client-ui-openrouter-usage`](../packages/client/ui-openrouter-usage) | `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-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`openrouter-usage`](../packages/llm/openrouter-usage) |
| [`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/runtime-diagnostics/invariants), [`plan-mode`](../packages/plan/plan-mode) |
| [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) |
| [`client-ui-tool`](../packages/client/ui-tool) | `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/runtime-diagnostics/invariants) |

View File

@@ -23,6 +23,7 @@ flowchart TD
pkg_llm_deepseek["llm-deepseek"]
pkg_llm_pi_ai["llm-pi-ai"]
pkg_llm_retry["llm-retry"]
pkg_openrouter_usage["openrouter-usage"]
pkg_token_meter["token-meter"]
end
subgraph group_core["packages/core"]
@@ -76,6 +77,7 @@ flowchart TD
pkg_web_search_deepseek["web-search-deepseek"]
pkg_web_search_exa["web-search-exa"]
pkg_web_search_perplexity["web-search-perplexity"]
pkg_web_search_searxng["web-search-searxng"]
end
subgraph group_spill["packages/spill"]
pkg_spill["spill"]
@@ -139,6 +141,7 @@ flowchart TD
pkg_client_ui_layout["client-ui-layout"]
pkg_client_ui_message_feedback["client-ui-message-feedback"]
pkg_client_ui_model_selection["client-ui-model-selection"]
pkg_client_ui_openrouter_usage["client-ui-openrouter-usage"]
pkg_client_ui_permission_presets["client-ui-permission-presets"]
pkg_client_ui_plan["client-ui-plan"]
pkg_client_ui_primitives["client-ui-primitives"]
@@ -457,6 +460,9 @@ flowchart TD
pkg_web_search_perplexity --> pkg_invariants
pkg_web_search_perplexity --> pkg_launch_environment
pkg_web_search_perplexity --> pkg_web
pkg_web_search_searxng --> pkg_invariants
pkg_web_search_searxng --> pkg_settings
pkg_web_search_searxng --> pkg_web
pkg_spill --> pkg_brand
pkg_spill --> pkg_invariants
pkg_spill --> pkg_llm
@@ -488,6 +494,14 @@ flowchart TD
pkg_llm_retry --> pkg_llm
pkg_llm_retry --> pkg_session
pkg_llm_retry --> pkg_timeout
pkg_openrouter_usage --> pkg_credentials
pkg_openrouter_usage --> pkg_invariants
pkg_openrouter_usage --> pkg_launch_environment
pkg_openrouter_usage --> pkg_llm
pkg_openrouter_usage --> pkg_session
pkg_openrouter_usage --> pkg_session_projection
pkg_openrouter_usage --> pkg_settings
pkg_openrouter_usage --> pkg_typert_protocol
pkg_agent_default_model --> pkg_agent
pkg_agent_default_model --> pkg_invariants
pkg_agent_default_model --> pkg_llm
@@ -639,7 +653,9 @@ flowchart TD
pkg_session_query --> pkg_session_persistence
pkg_session_query --> pkg_session_title
pkg_acp --> pkg_agent
pkg_acp --> pkg_attachment
pkg_acp --> pkg_invariants
pkg_acp --> pkg_llm
pkg_acp --> pkg_session
pkg_acp --> pkg_user_approval
pkg_headless --> pkg_agent
@@ -871,6 +887,7 @@ flowchart TD
pkg_tool_lsp --> pkg_system_prompt
pkg_tool_lsp --> pkg_timeout
pkg_tool_lsp --> pkg_tools
pkg_mcp_client --> pkg_attachment
pkg_mcp_client --> pkg_invariants
pkg_mcp_client --> pkg_llm
pkg_mcp_client --> pkg_subprocess
@@ -1006,6 +1023,7 @@ flowchart TD
pkg_api_remotes --> pkg_invariants
pkg_api_remotes --> pkg_llm
pkg_api_remotes --> pkg_message_feedback
pkg_api_remotes --> pkg_openrouter_usage
pkg_api_remotes --> pkg_session
pkg_api_remotes --> pkg_session_persistence
pkg_api_remotes --> pkg_settings
@@ -1326,6 +1344,15 @@ flowchart TD
pkg_client_ui_message_feedback --> pkg_invariants
pkg_client_ui_message_feedback --> pkg_message_feedback
pkg_client_ui_message_feedback --> pkg_typert_protocol
pkg_client_ui_openrouter_usage --> pkg_api_remotes
pkg_client_ui_openrouter_usage --> pkg_client_locale
pkg_client_ui_openrouter_usage --> pkg_client_runtime
pkg_client_ui_openrouter_usage --> pkg_client_ui_conversation
pkg_client_ui_openrouter_usage --> pkg_client_ui_primitives
pkg_client_ui_openrouter_usage --> pkg_client_ui_sidebar
pkg_client_ui_openrouter_usage --> pkg_client_ui_slots
pkg_client_ui_openrouter_usage --> pkg_invariants
pkg_client_ui_openrouter_usage --> pkg_openrouter_usage
pkg_client_ui_plan --> pkg_api_remotes
pkg_client_ui_plan --> pkg_client_locale
pkg_client_ui_plan --> pkg_client_runtime
@@ -1482,6 +1509,7 @@ flowchart TD
| [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
| [`web-search-searxng`](../packages/web/web-search-searxng) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) |
| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) |
| [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
@@ -1491,6 +1519,7 @@ flowchart TD
| [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
| [`acp-snapshot`](../packages/test-support/acp-snapshot) | `test-support` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`openrouter-usage`](../packages/llm/openrouter-usage) | `llm` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) |
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
@@ -1524,7 +1553,7 @@ flowchart TD
| [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-title`](../packages/session/session-title) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) |
| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
@@ -1563,7 +1592,7 @@ flowchart TD
| [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
| [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
@@ -1584,7 +1613,7 @@ flowchart TD
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`commands`](../packages/interaction/commands), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`credentials`](../packages/credentials/credentials), [`goal`](../packages/goal/goal), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`openrouter-usage`](../packages/llm/openrouter-usage), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`typert-registry`](../packages/typert/registry) |
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
@@ -1628,6 +1657,7 @@ flowchart TD
| [`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/runtime-diagnostics/invariants) |
| [`client-ui-jobs`](../packages/client/ui-jobs) | `client` | [`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/runtime-diagnostics/invariants) |
| [`client-ui-message-feedback`](../packages/client/ui-message-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/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback), [`typert-protocol`](../packages/typert/protocol) |
| [`client-ui-openrouter-usage`](../packages/client/ui-openrouter-usage) | `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-sidebar`](../packages/client/ui-sidebar), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`openrouter-usage`](../packages/llm/openrouter-usage) |
| [`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/runtime-diagnostics/invariants), [`plan-mode`](../packages/plan/plan-mode) |
| [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-input-trigger`](../packages/client/ui-input-trigger), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/runtime-diagnostics/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) |
| [`client-ui-tool`](../packages/client/ui-tool) | `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/runtime-diagnostics/invariants) |

View File

@@ -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/attachment.md
attachment.md: ff7f14ceae8d4f8055d5cfd4367373729dc5ecbc
attachment.zh.md: 63769e621b8e088cda9bd49fbf23d08db8058669
attachment.md: ff5a802b23b0111dff4481394772438f5d68feab
attachment.zh.md: e3db8cd58e9bf2eebad66dacb78353cad98d2fd5

View File

@@ -94,6 +94,16 @@ Immutable binary attachment service. Implementations validate bytes before publi
*/
abstract validateImage(input: SaveImageAttachment): Promise<void>
/**
* Validate one ordered image batch before committing any member.
* Validation failures start no writes; storage failures return no partial
* references, although already published content-addressed objects may stay
* unreachable until a future retention policy collects them.
* @param inputs - encoded images in their owning message order.
* @returns durable references in the exact input order.
*/
async saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]>
/**
* Validate and durably commit one image before its owning session event is appended.
* @param input - encoded bytes, declared media type, and optional display name.
@@ -111,5 +121,5 @@ abstract saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef>
abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>
```
Source: [`packages/attachment/attachment/src/index.ts:29`](../../packages/attachment/attachment/src/index.ts)
Source: [`packages/attachment/attachment/src/index.ts:31`](../../packages/attachment/attachment/src/index.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -94,6 +94,16 @@ Immutable binary attachment service. Implementations validate bytes before publi
*/
abstract validateImage(input: SaveImageAttachment): Promise<void>
/**
* Validate one ordered image batch before committing any member.
* Validation failures start no writes; storage failures return no partial
* references, although already published content-addressed objects may stay
* unreachable until a future retention policy collects them.
* @param inputs - encoded images in their owning message order.
* @returns durable references in the exact input order.
*/
async saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]>
/**
* Validate and durably commit one image before its owning session event is appended.
* @param input - encoded bytes, declared media type, and optional display name.
@@ -111,5 +121,5 @@ abstract saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef>
abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>
```
Source: [`packages/attachment/attachment/src/index.ts:29`](../../packages/attachment/attachment/src/index.ts)
Source: [`packages/attachment/attachment/src/index.ts:31`](../../packages/attachment/attachment/src/index.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -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/web.md
web.md: 3bcd3ac24927c8c51baeabd770e2bd91c5ad1b77
web.zh.md: 3348be2b808dc286364f5a795b236cb299acd6a6
web.md: 1d5f9bb5fa4dd12673a379f1d495b0f578183ef8
web.zh.md: 38b9d5da9a4685adeb5db83e7d0ee895ae2ce013

View File

@@ -2,7 +2,7 @@
English | [中文](web.zh.md)
The web access seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md) that spans **two operations** (search and fetch) on one `ctx.web` service, split across packages: Service Definition ([dsh-web](../../packages/web/web), `ctx.web` + the provider registries), Service Providers ([dsh-web-search-exa](../../packages/web/web-search-exa), [dsh-web-search-perplexity](../../packages/web/web-search-perplexity), [dsh-web-search-deepseek](../../packages/web/web-search-deepseek), [dsh-web-fetch-http](../../packages/web/web-fetch-http)), and Consumer ([dsh-tool-web](../../packages/web/tool-web), the `web_search`/`web_fetch` tool schemas). Web is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A search-provider swap does not change how the model asks for a query, and a fetch-provider swap does not change how the model asks for a URL.
The web access seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md) that spans **two operations** (search and fetch) on one `ctx.web` service, split across packages: Service Definition ([dsh-web](../../packages/web/web), `ctx.web` + the provider registries), Service Providers ([dsh-web-search-exa](../../packages/web/web-search-exa), [dsh-web-search-searxng](../../packages/web/web-search-searxng), [dsh-web-search-perplexity](../../packages/web/web-search-perplexity), [dsh-web-search-deepseek](../../packages/web/web-search-deepseek), [dsh-web-fetch-http](../../packages/web/web-fetch-http)), and Consumer ([dsh-tool-web](../../packages/web/tool-web), the `web_search`/`web_fetch` tool schemas). Web is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A search-provider swap does not change how the model asks for a query, and a fetch-provider swap does not change how the model asks for a URL.
Source: [`packages/web/web/src/types.ts`](../../packages/web/web/src/types.ts)

View File

@@ -2,7 +2,7 @@
[English](web.md) | 中文
Web 访问 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md),在同一个 `ctx.web` 服务上横跨**两项操作**(search 与 fetch),并拆分到多个包:Service Definition([dsh-web](../../packages/web/web),`ctx.web` + 提供方注册表)、Service Provider([dsh-web-search-exa](../../packages/web/web-search-exa)、[dsh-web-search-perplexity](../../packages/web/web-search-perplexity)、[dsh-web-search-deepseek](../../packages/web/web-search-deepseek)、[dsh-web-fetch-http](../../packages/web/web-fetch-http))与 Consumer([dsh-tool-web](../../packages/web/tool-web),即 `web_search`/`web_fetch` 工具 schema)。Web 是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.md) 中。更换 search 提供方不会改变模型提交查询的方式,更换 fetch 提供方也不会改变模型请求 URL 的方式。
Web 访问 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md),在同一个 `ctx.web` 服务上横跨**两项操作**(search 与 fetch),并拆分到多个包:Service Definition([dsh-web](../../packages/web/web),`ctx.web` + 提供方注册表)、Service Provider([dsh-web-search-exa](../../packages/web/web-search-exa)、[dsh-web-search-searxng](../../packages/web/web-search-searxng)、[dsh-web-search-perplexity](../../packages/web/web-search-perplexity)、[dsh-web-search-deepseek](../../packages/web/web-search-deepseek)、[dsh-web-fetch-http](../../packages/web/web-fetch-http))与 Consumer([dsh-tool-web](../../packages/web/tool-web),即 `web_search`/`web_fetch` 工具 schema)。Web 是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.md) 中。更换 search 提供方不会改变模型提交查询的方式,更换 fetch 提供方也不会改变模型请求 URL 的方式。
源码:[`packages/web/web/src/types.ts`](../../packages/web/web/src/types.ts)

View File

@@ -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/testing.md
testing.md: 8ed81412a954bcd1d4b5d84eb4133ed081092764
testing.zh.md: 5662db51847ba2f4e297d0bd3d4af0d024fa29fb
testing.md: bef73983e48365f6655e4d4242f4f73223d971ed
testing.zh.md: 7a5935165a335b4f3885092ef7b7b29f08f53c51

View File

@@ -46,4 +46,4 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword
## When a snapshot test is required
Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through a runnable example's owning snapshot suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.
Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless scenario in the same PR through a runnable example's owning snapshot suite. Package tests, e2e assertions, mock/test-only compositions, and PR rationale do not replace the assembled transcript; extend the harness when needed. ACP automation scenarios use `examples/<name>/tests/snapshots/`, a scenario table over the [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) suite factory (`examples/acp-agent` is primary); `examples/headless-agent` owns the internal canonical-event JSONL snapshots and replay fixtures. The `pwsh-tool-turn` ACP scenario boots real `pwsh` and skips where it is absent. Completed interactive-terminal journeys use JSONL-driven scenarios under `apps/cli/tests/snapshots/`; transient presentation uses the package-local semantic matrix, with a PTY case when input, Loader selection, or terminal teardown changes. Browser-rendered web GUI journeys use `apps/web/tests/snapshots/`. The two SDKs project the agent loop, session lifecycle, and `SessionEventMap` independently, so changing any of those updates both: `examples/jsonrpc-agent/tests/snapshots/` owns the TypeScript client; `scripts/snapshots/python-sdk-single-exe/` owns the Python client, which only the required `python-runtime` CI job runs. New capability seams, lifecycle variants, or transcript surfaces name every coverage tier at plan time and verify the harness can express it before implementation.

View File

@@ -46,4 +46,4 @@ e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身
## 何时需要快照测试
每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过可运行示例所属的快照套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景使用 `examples/<name>/tests/snapshots/`,即基于 [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) 套件工厂的场景表(`examples/acp-agent` 为主套件);`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。
每项非平凡的模型可见、协议可见或人类可见变更,都必须在同一 PR 中,通过可运行示例所属的快照套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 自动化场景使用 `examples/<name>/tests/snapshots/`,即基于 [`dsh-acp-snapshot`](../packages/test-support/acp-snapshot/README.md) 套件工厂的场景表(`examples/acp-agent` 为主套件);`examples/headless-agent` 拥有内部规范事件 JSONL 快照与回放 fixture。`pwsh-tool-turn` ACP 场景启动真实 `pwsh`,在无 `pwsh` 的主机上跳过。已完成的交互式终端旅程使用 `apps/cli/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。浏览器渲染的 Web GUI 旅程使用上述 Web 应用快照套件。两个 SDK 各自独立地投影 agent loop、会话生命周期与 `SessionEventMap`,因此改动其中任何一项都要同时更新两者:`examples/jsonrpc-agent/tests/snapshots/` 拥有 TypeScript 客户端;`scripts/snapshots/python-sdk-single-exe/` 拥有 Python 客户端,且只有必需的 `python-runtime` CI 作业会运行它。新的能力 seam、生命周期变体或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。

View File

@@ -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: 50563c97c6cd5496871ea7fa52c4823a56b088fd
tool-catalog.zh.md: ed0c7e3f70cffbecd3d20a1556bcb0cd4204df00
tool-catalog.md: 02501d0721768d38c1e51bdfb87dcc79696bfd04
tool-catalog.zh.md: 3583662dbaf72ea78bddb6a2b7d6b18360f3f3e1

View File

@@ -120,7 +120,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. 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.
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 is program output — curate it. Image-bearing subtool results are attached after the run.
```json
{

View File

@@ -122,7 +122,7 @@ ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类
### `run_code`
针对可用工具执行 TypeScript 程序。接受两个必填参数:`code`,即异步函数的**函数体**(仅使用可擦除语法;支持顶层 `await` 和 `return`);以及 `description`,简要说明该程序做什么。请根据系统提示词中的声明,以 `await tools.name(args)` 形式调用工具。只有打印或返回的内容会传回,请谨慎筛选。
针对可用工具执行 TypeScript 程序。接受两个必填参数:`code`,即异步函数的**函数体**(仅使用可擦除语法;支持顶层 `await` 和 `return`);以及 `description`,简要说明该程序做什么。请根据系统提示词中的声明,以 `await tools.name(args)` 形式调用工具。只有打印或返回的内容属于程序输出,请谨慎筛选。含图片的子工具结果会在运行结束后附加。
```json
{

View File

@@ -0,0 +1,42 @@
# Keyless replay combines Code Mode with the durable image store and an exact
# image-capable replay route. The scenario generates its tiny PNG inside the
# run_code program, then exercises read_image as a nested dispatch.
- id: base
name: '@deepseek-ai/cordis-plugin-include'
config:
path: ./cordis.yml
patches:
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
disabled: true
- id: acp-agent
name: '@deepseek-ai/dsh-acp-demo'
config:
provider: deepseek-official
model: deepseek-v4-flash
persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
persistenceCompression: none
workspaceContext:
maxBytes: 65536
tools:
mode: code
persona: |
You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
Verify your work by running the code or tests. Keep answers brief and factual.
- insert:
- id: attachment-local
name: '@deepseek-ai/dsh-attachment-local'
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'
- id: llm-replay
name: '@deepseek-ai/dsh-llm-replay'
config:
providers:
- id: deepseek-official
name: DeepSeek
models:
- id: deepseek-v4-flash
inputModalities: [text, image]
- id: deepseek-v4-pro
inputModalities: [text]

View File

@@ -0,0 +1,29 @@
# Code Mode image overlay: mounts the worker runtime and durable attachment
# store so a nested read_image result can cross the generic rich-result bridge.
# The authored snapshot is replay-only; the live config retains the ordinary
# exact provider route for manual use.
- id: base
name: '@deepseek-ai/cordis-plugin-include'
config:
path: ./cordis.yml
patches:
- id: acp-agent
name: '@deepseek-ai/dsh-acp-demo'
config:
provider: deepseek-official
model: deepseek-v4-pro
persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
workspaceContext:
maxBytes: 65536
tools:
mode: code
persona: |
You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
Verify your work by running the code or tests. Keep answers brief and factual.
- insert:
- id: attachment-local
name: '@deepseek-ai/dsh-attachment-local'
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker-thread'

View File

@@ -32,6 +32,7 @@ const AGENT = {
// The Code Mode overlay configs (include-patched variants of cordis.yml; the
// replay swap resolves each one's sibling `*cordis.snapshot.yml`).
const CODE_MODE_CONFIG = fileURLToPath(new URL('../code-mode.cordis.yml', import.meta.url))
const CODE_MODE_IMAGE_CONFIG = fileURLToPath(new URL('../code-mode-image.cordis.yml', import.meta.url))
const CODE_MODE_WORKSPACE_CONTEXT_CONFIG = fileURLToPath(new URL('../code-mode-workspace-context.cordis.yml', import.meta.url))
const BOTH_MODE_CONFIG = fileURLToPath(new URL('../both-mode.cordis.yml', import.meta.url))
const WORKSPACE_CONTEXT_CONFIG = fileURLToPath(new URL('../agent-instructions.cordis.yml', import.meta.url))
@@ -212,6 +213,13 @@ const SCENARIOS: Scenario[] = [
headerClass: 'image',
configPath: IMAGE_TEXT_ROUTE_CONFIG,
},
{
name: 'inline-image-prompt',
hasModelTurn: true,
recorded: false,
headerClass: 'image',
configPath: IMAGE_CONFIG,
},
{
name: 'pty-tools',
hasModelTurn: true,
@@ -547,6 +555,16 @@ const SCENARIOS: Scenario[] = [
// tools:sdk section rides in the prompt, and the program's tool calls land as
// tool/code-dispatch events. Each overlay composes and pins its own header class.
{ name: 'code-mode-turn', hasModelTurn: true, recorded: true, pinsHeader: true, headerClass: 'code', configPath: CODE_MODE_CONFIG },
{
name: 'code-mode-read-image',
hasModelTurn: true,
recorded: false,
pinsHeader: true,
headerClass: 'code-image',
toolSchemasSource: 'code-mode-turn',
configPath: CODE_MODE_IMAGE_CONFIG,
posixOnly: true,
},
// A nested fs dispatch inside run_code discovers workspace instructions. The
// projection enters the inbox after the outer result and becomes model-visible
// on the following step, retaining workspace provenance end to end.

View File

@@ -136,7 +136,7 @@ Use subagent in the background by default. Start independent delegations togethe
- 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.
- 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`.
- 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.
- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
The available tools:

View File

@@ -443,7 +443,7 @@
},
{
"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.",
"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 is program output — curate it. Image-bearing subtool results are attached after the run.",
"parameters": {
"type": "object",
"properties": {

View File

@@ -30,7 +30,7 @@ Use subagent in the background by default. Start independent delegations togethe
- 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.
- 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`.
- 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.
- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
The available tools:

View File

@@ -246,7 +246,7 @@
},
{
"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.",
"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 is program output — curate it. Image-bearing subtool results are attached after the run.",
"parameters": {
"type": "object",
"properties": {

View File

@@ -0,0 +1,14 @@
{
"steps": [
{
"op": "initialize"
},
{
"op": "newSession"
},
{
"op": "prompt",
"text": "Using ONE run_code program, create a one-pixel PNG with Node.js, call read_image on it, then reply with exactly the single word DONE."
}
]
}

View File

@@ -0,0 +1,33 @@
{"type":"session","version":0,"id":"44444444-4444-4444-8444-444444444444","createdAt":1783952000000,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"agent/inbox/spliced","seq":0,"time":1786431644501,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Using ONE run_code program, create a one-pixel PNG with Node.js, call read_image on it, then reply with exactly the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"08e67dbb-9432-4fe4-b7da-4483998c0a31"}]}}
{"type":"turn/start","seq":1,"time":1786431644502,"data":{"turn":1}}
{"type":"agent/inbox/spliced","seq":2,"time":1786431644502,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":3,"time":1786431644557,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":4,"time":1786431644558,"data":{"content":[{"type":"text","text":"Using ONE run_code program, create a one-pixel PNG with Node.js, call read_image on it, then reply with exactly the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"08e67dbb-9432-4fe4-b7da-4483998c0a31"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1786431644558,"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.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"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."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"99b9db8d-e4ec-4ea9-b5e2-1e4c0ff6354b"},"surfaceOp":"append"}
{"type":"session/title","seq":6,"time":1786431644558,"data":{"title":"Using ONE run_code program, create","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":7,"time":1786431644559,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":8,"time":1786431644560,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":9,"time":1783952000009,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":10,"time":1786431644571,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"code-image-call","name":"run_code","arguments":"{\"code\":\"const bytes = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130];\\nawait tools.bash({ command: \\\"node -e \\\\\\\"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\\\\\\\"\\\", description: \\\"Create a one pixel PNG\\\" });\\nconst image = await tools.read_image({ file_path: \\\"red.png\\\" });\\nreturn image.path;\",\"description\":\"Create and inspect one image\"}"}}}}
{"type":"assistant/chunk","seq":11,"time":1786431644572,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":12,"time":1786431644572,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":13,"time":1786431644572,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"code-image-call","name":"run_code","arguments":"{\"code\":\"const bytes = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130];\\nawait tools.bash({ command: \\\"node -e \\\\\\\"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\\\\\\\"\\\", description: \\\"Create a one pixel PNG\\\" });\\nconst image = await tools.read_image({ file_path: \\\"red.png\\\" });\\nreturn image.path;\",\"description\":\"Create and inspect one image\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"644382c5-5a05-4bda-b8dc-b9195d6a7d8b"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12],"surfaceOp":"append"}
{"type":"tool/call","seq":14,"time":1786431644573,"data":{"turn":1,"step":1,"callId":"code-image-call","name":"run_code","arguments":"{\"code\":\"const bytes = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130];\\nawait tools.bash({ command: \\\"node -e \\\\\\\"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\\\\\\\"\\\", description: \\\"Create a one pixel PNG\\\" });\\nconst image = await tools.read_image({ file_path: \\\"red.png\\\" });\\nreturn image.path;\",\"description\":\"Create and inspect one image\"}"}}
{"type":"tool/code-dispatch-start","seq":15,"time":1786431644697,"data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:code:1","name":"bash","arguments":{"command":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","description":"Create a one pixel PNG"}}}
{"type":"tool/code-dispatch","seq":16,"time":1786431644828,"data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:code:1","name":"bash","arguments":{"command":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","description":"Create a one pixel PNG"},"isError":false,"content":[{"type":"text","text":"(no output)"}]}}
{"type":"tool/code-dispatch-start","seq":17,"time":1786431644829,"data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:code:2","name":"read_image","arguments":{"file_path":"red.png"}}}
{"type":"tool/code-dispatch","seq":18,"time":1786431644871,"data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:code:2","name":"read_image","arguments":{"file_path":"red.png"},"isError":false,"content":[{"type":"text","text":"<path>{{cwd}}/red.png</path>\n<type>image</type>\n<content>\nimage/png image, 1x1 px, 69 bytes\n</content>"},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","bytes":69,"width":1,"height":1,"name":"red.png"}}]}}
{"type":"tool/result","seq":19,"time":1786431644874,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"code-image-call"},"content":[{"type":"tool-result","toolCallId":"code-image-call","content":[{"type":"text","text":"{{cwd}}/red.png"}],"isError":false}],"role":"user","id":"73e999fa-4aab-4609-970d-4c675e3557f1"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"agent/inbox/spliced","seq":20,"time":1786431644874,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"<path>{{cwd}}/red.png</path>\n<type>image</type>\n<content>\nimage/png image, 1x1 px, 69 bytes\n</content>"},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","bytes":69,"width":1,"height":1,"name":"red.png"}}],"source":{"kind":"plugin","plugin":"tools-code-mode"},"role":"user","id":"99bca54a-c323-4df8-8695-7ef17d02dd65"}]}}
{"type":"step/end","seq":21,"time":1786431644874,"data":{"turn":1,"step":1}}
{"type":"agent/inbox/spliced","seq":22,"time":1786431644874,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":23,"time":1786431644884,"data":{"turn":1,"step":2}}
{"type":"user/message","seq":24,"time":1786431644885,"data":{"content":[{"type":"text","text":"<path>{{cwd}}/red.png</path>\n<type>image</type>\n<content>\nimage/png image, 1x1 px, 69 bytes\n</content>"},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","bytes":69,"width":1,"height":1,"name":"red.png"}}],"source":{"kind":"plugin","plugin":"tools-code-mode"},"role":"user","id":"99bca54a-c323-4df8-8695-7ef17d02dd65"},"surfaceOp":"append"}
{"type":"assistant/chunk","seq":25,"time":1786431644889,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":26,"time":1786431644889,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
{"type":"assistant/chunk","seq":27,"time":1786431644890,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":28,"time":1786431644890,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":29,"time":1786431644890,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"a6da60ea-d420-432b-ba00-9b99af045110"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[25,26,27,28],"surfaceOp":"append"}
{"type":"step/end","seq":30,"time":1786431644890,"data":{"turn":1,"step":2}}
{"type":"turn/end","seq":31,"time":1786431644890,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

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

View File

@@ -0,0 +1,459 @@
You are an AI agent powered by DeepSeek Harness.
You are a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.
Verify your work by running the code or tests. Keep answers brief and factual.
`run_code` is the only tool you can call directly — a tool call naming any other tool fails. Reach every tool the SDK declares below from inside the program.
Use 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.
Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-observation-policy requires it) and prefer edit for targeted changes.
Use 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-observation-policy requires it), unless you just created or edited it in this session.
Check the [exit code: N] marker on every bash result; investigate failures before moving on.
Track every background job id you start. You are notified in-session when a job finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running job's work. Before giving a final answer, collect every still-relevant job with job_output (set wait: true only when you are genuinely blocked on it), and job_kill jobs that stopped mattering.
Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked.
Use 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.
Use 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.
Use 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.
## Writing code for run_code
`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.
- 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`.
- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
The available tools:
```ts
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
interface ToolArgsMap {
/** 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> 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 job id immediately; read its output with `job_output` and stop it with `job_kill`. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later. */
bash: {
/** The bash command to execute. */
command: string;
/** 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". */
description: string;
/** Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry. */
timeoutMs?: number;
/** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */
workdir?: string;
/** Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies. */
run_in_background?: boolean;
/** The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval. */
sandbox_permissions?: "workspace-write" | "danger-full-access";
/** Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access. */
justification?: string;
} & Record<string, JsonValue>;
/** Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say "create a goal". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority. */
create_goal: {
/** The concrete completion objective inferred from the direct human request. */
objective: string;
/** Optional positive safe-integer limit on automatic continuation rounds. */
max_goal_rounds?: number;
} & Record<string, JsonValue>;
/** Edit an existing UTF-8 text file by replacing literal text. */
edit: {
/** Path to edit, resolved by the filesystem backend. */
file_path: string;
/** Literal text to replace. Must match exactly. */
old_string: string;
/** Literal replacement text. Use an empty string to delete the match. */
new_string: string;
/** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */
replace_all?: boolean;
/** The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval. */
sandbox_permissions?: "workspace-write" | "danger-full-access";
/** Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. */
justification?: string;
} & Record<string, JsonValue>;
/** Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal. */
get_goal: Record<string, JsonValue>;
/** 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. */
interrupt_agent: {
/** The agent id of the running agent to interrupt. */
agent_id: string;
} & Record<string, JsonValue>;
/** Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops. */
job_kill: {
/** Job id returned by the tool that started the background work. */
job_id: string;
/** Optional short reason, recorded in the log and forwarded to the job. */
reason?: string;
} & Record<string, JsonValue>;
/** List your background jobs (running and finished) with their ids, kinds, and statuses. */
job_list: Record<string, JsonValue>;
/** Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap. */
job_output: {
/** Job id returned by the tool that started the background work. */
job_id: string;
/** Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive. */
wait?: boolean;
/** Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum. */
timeout_ms?: number;
} & Record<string, JsonValue>;
/** List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only. */
list_agents: {
/** children (default) lists direct children only; descendants walks the complete tree below you. */
scope?: "children" | "descendants";
} & Record<string, JsonValue>;
/** 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. */
ralph: {
/** The immutable completion objective for every fresh Ralph round. */
objective: string;
/** Optional positive safe-integer round cap, bounded by the deployment ceiling. */
maxRounds?: number;
} & Record<string, JsonValue>;
/** Read a UTF-8 text file and return line-numbered content. */
read: {
/** Path to read, resolved by the filesystem backend. */
file_path: string;
/** 1-based first line to return. Defaults to 1. */
offset?: number;
/** Maximum number of lines to return. Defaults to 2000. */
limit?: number;
} & Record<string, JsonValue>;
/** Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input. */
read_image: {
/** Path to the image file, resolved by the filesystem backend. */
file_path: string;
} & Record<string, JsonValue>;
/** 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. */
send_message: {
/** The subagent id returned when the background subagent was started. */
subagent_id: string;
/** The message to deliver to the subagent. */
message: string;
} & Record<string, JsonValue>;
/** 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. */
skill: {
/** The exact skill name from the available skills list. */
name: string;
} & Record<string, JsonValue>;
/** 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. */
subagent: {
/** A short (3-5 word) description of the delegated task, for display. */
description: string;
/** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */
prompt: string;
/** 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. */
run_in_background?: boolean;
} & Record<string, JsonValue>;
/** 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. */
subagent_fork: {
/** A short (3-5 word) description of the delegated task, for display. */
description: string;
/** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */
prompt: string;
} & Record<string, JsonValue>;
/** 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). */
todo_write: {
/** The COMPLETE task list, replacing any previous list. */
todos: ({
/** What the task is — a short imperative line. */
content: string;
/** pending (not started) | in_progress (now) | completed (done). */
status: "pending" | "in_progress" | "completed";
})[];
} & Record<string, JsonValue>;
/** Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason. */
update_goal: {
/** Exact id returned by get_goal. */
goal_id: string;
/** Exact positive revision returned by get_goal. */
revision: number;
/** edit | pause | resume | complete | blocked */
action: "edit" | "pause" | "resume" | "complete" | "blocked";
/** Replacement objective; valid only with action edit. */
objective?: string;
/** Replacement cap; valid only with action edit. */
max_goal_rounds?: number;
/** Concrete blocking condition; required only with action blocked. */
blocked_reason?: string;
} & Record<string, JsonValue>;
/** 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 <value>` — the value must be JSON-serializable and is this tool's result. Script-body hooks: - `agent(prompt, opts?): Promise<any>` — 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<any[]>` — 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<any[]>` — 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. */
workflow: {
/** The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return <json-value>`). */
script: string;
/** The workflow identity block (plain JSON — never code). */
meta: {
/** Short kebab-case workflow name. */
name: string;
/** One-line description of what the workflow does. */
description: string;
/** Optional guidance on when this workflow applies. */
whenToUse?: string;
/** Optional phase declarations matched by phase() calls. */
phases?: ({
/** The phase title phase() calls match by exact string. */
title: string;
/** Optional one-line description of the phase. */
detail?: string;
/** Optional provider override this phase is expected to use. */
provider?: string;
/** Optional model override this phase is expected to use. */
model?: string;
} & Record<string, JsonValue>)[];
} & Record<string, JsonValue>;
/** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {"files": [...]}). */
args?: Record<string, JsonValue>;
} & Record<string, JsonValue>;
/** Create or fully replace a UTF-8 text file. */
write: {
/** Path to write, resolved by the filesystem backend. */
file_path: string;
/** Full UTF-8 text content to write. */
content: string;
/** The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval. */
sandbox_permissions?: "workspace-write" | "danger-full-access";
/** Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. */
justification?: string;
} & Record<string, JsonValue>;
}
interface ToolOutputMap {
bash: {
kind: "background";
jobId: string;
} | {
kind: "foreground";
exitCode: number | null;
signal: string | null;
timedOut: boolean;
aborted: boolean;
timeoutMs: number;
stdout: {
text: string;
truncated: boolean;
spillPath?: string;
};
stderr: {
text: string;
truncated: boolean;
spillPath?: string;
};
sandbox?: {
mode: string;
denied: boolean;
enforcement?: string;
runnerFailed?: boolean;
};
};
create_goal: {
goal: null;
} | {
goal: {
id: string;
revision: number;
objective: string;
phase: "active" | "paused" | "blocked" | "complete";
roundsStarted: number;
maxGoalRounds: number;
blockedReason?: {
code: string;
message: string;
};
};
activation: "armed" | "disarmed";
};
edit: {
path: string;
before: string;
after: string;
};
get_goal: {
goal: null;
} | {
goal: {
id: string;
revision: number;
objective: string;
phase: "active" | "paused" | "blocked" | "complete";
roundsStarted: number;
maxGoalRounds: number;
blockedReason?: {
code: string;
message: string;
};
};
activation: "armed" | "disarmed";
};
interrupt_agent: {
accepted: boolean;
};
job_kill: {
outcome: "cancellation-requested" | "already-finished";
job: {
id: string;
kind: string;
label: string;
status: "running" | "stopping" | "completed" | "killed" | "failed";
detail?: string;
startedAt: number;
finishedAt?: number;
};
};
job_list: ({
id: string;
kind: string;
label: string;
status: "running" | "stopping" | "completed" | "killed" | "failed";
detail?: string;
startedAt: number;
finishedAt?: number;
})[];
job_output: {
text: string;
job: {
id: string;
kind: string;
label: string;
status: "running" | "stopping" | "completed" | "killed" | "failed";
detail?: string;
startedAt: number;
finishedAt?: number;
};
};
list_agents: ({
kind: "child";
id: string;
label: string;
status: "running" | "idle" | "ready";
parent?: string;
depth?: number;
} | {
kind: "diagnostic";
id: string;
reason: "corrupt" | "unsupported" | "unavailable";
parent?: string;
depth?: number;
})[];
ralph: {
runId: string;
agentsStarted: number;
result: JsonValue;
};
read: {
path: string;
offset: number;
lines: {
number: number;
text: string;
}[];
totalLines: number;
};
read_image: {
path: string;
image: {
attachmentId: string;
mediaType: "image/png" | "image/jpeg" | "image/webp" | "image/gif";
bytes: number;
width: number;
height: number;
name?: string;
};
};
send_message: {
messageId: string;
};
skill: {
name: string;
provider: string;
resourceBase?: {
kind: "directory";
path: string;
} | {
kind: "url";
url: string;
} | {
kind: "opaque";
description: string;
};
content: string;
};
subagent: {
kind: "background";
jobId: string;
} | {
kind: "continuable";
subagentId: string;
} | {
kind: "foreground";
runId: string;
output: JsonValue[];
};
subagent_fork: {
kind: "background";
jobId: string;
} | {
kind: "continuable";
subagentId: string;
} | {
kind: "foreground";
runId: string;
output: JsonValue[];
};
todo_write: {
todos: ({
content: string;
status: "pending" | "in_progress" | "completed";
})[];
counts: {
pending: number;
inProgress: number;
completed: number;
};
};
update_goal: {
goal: null;
} | {
goal: {
id: string;
revision: number;
objective: string;
phase: "active" | "paused" | "blocked" | "complete";
roundsStarted: number;
maxGoalRounds: number;
blockedReason?: {
code: string;
message: string;
};
};
activation: "armed" | "disarmed";
};
workflow: {
runId: string;
agentsStarted: number;
result: JsonValue;
};
write: {
path: string;
operation: "create" | "update";
before: string | null;
after: string;
};
}
type ToolName = keyof ToolOutputMap
declare class ToolCallError extends Error {
readonly name: "ToolCallError";
readonly toolName: ToolName;
}
declare const tools: {
[K in ToolName]: (args: ToolArgsMap[K]) => Promise<ToolOutputMap[K]>;
}
```

View File

@@ -32,7 +32,7 @@ Use subagent in the background by default. Start independent delegations togethe
- 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.
- 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`.
- 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.
- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
The available tools:

View File

@@ -2,7 +2,7 @@
"initial": [
{
"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.",
"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 is program output — curate it. Image-bearing subtool results are attached after the run.",
"parameters": {
"type": "object",
"properties": {

View File

@@ -0,0 +1,28 @@
{
"steps": [
{
"op": "initialize"
},
{
"op": "newSession"
},
{
"op": "promptContent",
"content": [
{
"type": "text",
"text": "Inspect this image, then reply with exactly "
},
{
"type": "image",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC",
"mimeType": "image/png"
},
{
"type": "text",
"text": "the single word DONE."
}
]
}
]
}

View File

@@ -0,0 +1,17 @@
{"type":"session","version":0,"id":"44444444-4444-4444-8444-444444444444","createdAt":1783952000000,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"agent/inbox/spliced","seq":0,"time":1783952000001,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Inspect this image, then reply with exactly "},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","width":1,"height":1,"bytes":69}},{"type":"text","text":"the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"0c0c0c0c-0000-4000-8000-000000000001"}]}}
{"type":"turn/start","seq":1,"time":1783952000002,"data":{"turn":1}}
{"type":"agent/inbox/spliced","seq":2,"time":1783952000002,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","seq":3,"time":1783952000003,"data":{"turn":1,"step":1}}
{"type":"user/message","seq":4,"time":1783952000003,"data":{"content":[{"type":"text","text":"Inspect this image, then reply with exactly "},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","width":1,"height":1,"bytes":69}},{"type":"text","text":"the single word DONE."}],"source":{"kind":"user"},"role":"user","id":"0c0c0c0c-0000-4000-8000-000000000001"},"surfaceOp":"append"}
{"type":"user/message","seq":5,"time":1783952000004,"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.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"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."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"0c0c0c0c-0000-4000-8000-000000000002"},"surfaceOp":"append"}
{"type":"session/title","seq":6,"time":1783952000004,"data":{"title":"Inspect this image, then reply","messageSeqs":[4],"source":{"kind":"fallback"}}}
{"type":"request/header","seq":7,"time":1783952000005,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":8,"time":1783952000005,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":9,"time":1783952000006,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":10,"time":1783952000007,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}}}
{"type":"assistant/chunk","seq":11,"time":1783952000008,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":12,"time":1783952000009,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":13,"time":1783952000009,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"0c0c0c0c-0000-4000-8000-000000000003"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12],"surfaceOp":"append"}
{"type":"step/end","seq":14,"time":1783952000010,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":15,"time":1783952000010,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

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

View File

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

View File

@@ -530,6 +530,16 @@
"tests/**/*.ts"
]
},
"packages/web/web-search-searxng": {
"entry": [
"tests/**/*.spec.ts",
"tests/**/*.e2e.ts"
],
"project": [
"src/**/*.ts",
"tests/**/*.ts"
]
},
"packages/web/web-search-perplexity": {
"entry": [
"tests/**/*.spec.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@deepseek-ai/dsh-root",
"version": "0.1.0-rc.6",
"version": "0.1.0-rc.7",
"license": "MIT",
"private": true,
"type": "module",
@@ -134,7 +134,7 @@
"release:pack": "tsx scripts/release/pack.ts",
"release:verify-packed-install": "tsx scripts/release/verify-packed-install.ts",
"release:publish": "tsx scripts/release/publish.ts",
"dsh": "node --import tsx/esm apps/cli/src/bin.ts",
"dsh": "node --import tsx/esm scripts/run-source-dsh.ts",
"demo:code-mode": "node scripts/demo-code-mode.mjs",
"demo:cordis": "node scripts/demo-cordis.mjs",
"demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml",

Some files were not shown because too many files have changed in this diff Show More