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
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/fs/tool-fs/README.md
|
||||
README.md: 7e334f886747cd8dc566a572c699cd80c7cf62fe
|
||||
README.zh.md: 269992c86d46744bf11a9360202767ac1243dc91
|
||||
README.md: 71dc4134feee6bf1b5d17eef5f62e587653adf6d
|
||||
README.zh.md: dff4b838aea4c6fa50a92ce2102bfdd2a96150a5
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
The **model-facing filesystem tools** — `read`, `read_image`, `write`, `edit` — and their **executor**. This is the consumer layer of the filesystem stack: it owns tool names, JSON schemas, argument validation, prompt sections, **read windowing**, and result formatting. It reads/writes/edits through the `ctx.fs` provider contract ([`@deepseek-ai/dsh-fs`](../fs)) **directly**. The freshness/observation policy is contributed by a separate plugin ([`@deepseek-ai/dsh-fs-policy`](../fs-policy)) through the `fs/*` event gate; the tool is not method-coupled to it. Under a confining provider, the shared sandbox-policy service is required for per-session execution and the tool exposes escalation for filesystem mutations.
|
||||
The **model-facing filesystem tools** — `read`, `read_image`, `write`, `edit` — and their **executor**. This is the consumer layer of the filesystem stack: it owns tool names, JSON schemas, argument validation, prompt sections, **read windowing**, and result formatting. It reads/writes/edits through the `ctx.fs` provider contract ([`@deepseek-ai/dsh-fs`](../fs)) **directly**. The freshness/observation policy is contributed by a separate plugin ([`@deepseek-ai/dsh-fs-observation-policy`](../fs-observation-policy)) through the `fs/*` event gate; the tool is not method-coupled to it. Under a confining provider, the shared sandbox-policy service is required for per-session execution and the tool exposes escalation for filesystem mutations.
|
||||
|
||||
```ts ignore-check
|
||||
// Default deployment: a ctx.fs provider, the policy plugin, then the tools.
|
||||
await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) // @deepseek-ai/dsh-fs-local
|
||||
await ctx.plugin(FsPolicy) // @deepseek-ai/dsh-fs-policy (policy gate)
|
||||
await ctx.plugin(FsPolicy) // @deepseek-ai/dsh-fs-observation-policy (policy gate)
|
||||
await ctx.plugin(LocalAttachmentStore, { dshHome }) // optional — enables durable read_image results
|
||||
await ctx.plugin(ToolFs) // this package — read/write/edit, plus read_image with attachments
|
||||
```
|
||||
|
||||
`@deepseek-ai/dsh-fs-policy` is **optional**: omit it and the tools run against the bare provider (unconditional write/overwrite/edit, no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit.
|
||||
`@deepseek-ai/dsh-fs-observation-policy` is **optional**: omit it and the tools run against the bare provider (unconditional write/overwrite/edit, no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit.
|
||||
|
||||
`read_image` registers only while a durable `ctx.attachments` service is mounted — without one the deployment cannot commit image bytes, so the tool never appears. Execution additionally requires the exact routed model to declare `image` input (resolved through `ctx.llm.resolveModelInfo` from the session's latest request header, falling back to agent options); an unknown or text-only route gets a refusal result before any filesystem I/O, so a text route's durable history stays free of image blocks.
|
||||
|
||||
@@ -49,13 +49,13 @@ The tools do **not** inject a policy service or inspect any cache. Each tool res
|
||||
- **write** — `ctx.waterfall('fs/write-intent', target, exec, () => undefined)` for the optional guard, then `ctx.fs.writeText(target, content, intent)`, then `fs/observed`. (0 stat.)
|
||||
- **edit** — `ctx.waterfall('fs/edit-intent', target, exec, () => undefined)` for the optional guard, then `ctx.fs.editText(target, edit, intent)`, then `fs/observed`. (0 stat.)
|
||||
|
||||
The tool passes `exec` (the tool-execution context) as the opaque `actor` on every dispatch. The default thunks return `undefined` (the unconstrained bare provider). When `@deepseek-ai/dsh-fs-policy` is loaded it occupies the single decision slot — returning `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED` — and records on `fs/observed`. Backend errors (`FsError`) and a thrown `FS_NOT_OBSERVED` flow through `ToolRegistry.execute()` and become `isError` tool results with their `{ name, code }` attached.
|
||||
The tool passes `exec` (the tool-execution context) as the opaque `actor` on every dispatch. The default thunks return `undefined` (the unconstrained bare provider). When `@deepseek-ai/dsh-fs-observation-policy` is loaded it occupies the single decision slot — returning `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED` — and records on `fs/observed`. Backend errors (`FsError`) and a thrown `FS_NOT_OBSERVED` flow through `ToolRuntime.execute()` and become `isError` tool results with their `{ name, code }` attached.
|
||||
|
||||
When `ctx.fs.sandboxMode` reports confinement, write/edit advertise `sandbox_permissions` and `justification` and resolve approved retries through `ctx.approval`. The policy owner contributes capability-neutral standing policy; the tool results retain operation-specific denial and retry guidance.
|
||||
|
||||
## `fs/observed` is fire-and-forget
|
||||
|
||||
`fs/observed` fires AFTER the read/read_image/write/edit already succeeded, via a plain `ctx.emit`. A listener is contractually a synchronous, side-effect-only recorder (`@deepseek-ai/dsh-fs-policy`'s is a `WeakMap.set`); the tool does not guard the emit, so a listener that throws would surface as the tool's `isError` result — async or fallible observation does not belong on this event.
|
||||
`fs/observed` fires AFTER the read/read_image/write/edit already succeeded, via a plain `ctx.emit`. A listener is contractually a synchronous, side-effect-only recorder (`@deepseek-ai/dsh-fs-observation-policy`'s is a `WeakMap.set`); the tool does not guard the emit, so a listener that throws would surface as the tool's `isError` result — async or fallible observation does not belong on this event.
|
||||
|
||||
`read` opts into concurrent scheduling because its only mutation is the synchronous version recorder. Recorder races fail closed when a later `write` or `edit` re-checks the version under its target lock; both mutation tools remain exclusive. See the [parallel tool-call Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md).
|
||||
|
||||
@@ -78,13 +78,13 @@ Use the read tool — not shell commands like cat — to inspect text files. Res
|
||||
##### Write guidance
|
||||
|
||||
```markdown
|
||||
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-policy requires it) and prefer edit for targeted changes.
|
||||
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.
|
||||
```
|
||||
|
||||
##### Edit guidance
|
||||
|
||||
```markdown
|
||||
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-policy requires it), unless you just created or edited it in this session.
|
||||
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.
|
||||
```
|
||||
|
||||
#### Token effect
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
**面向模型的文件系统工具**(`read`、`read_image`、`write`、`edit`)及其**执行器**。这是文件系统栈的消费方层:拥有工具名称、JSON Schema、参数校验、提示词段、**读取窗口逻辑**和结果格式化。它**直接**通过 `ctx.fs` 提供方约定([`@deepseek-ai/dsh-fs`](../fs))读取/写入/编辑。新鲜度/观察策略由独立插件([`@deepseek-ai/dsh-fs-policy`](../fs-policy))通过 `fs/*` 事件门禁贡献;工具不与其方法耦合。使用施加沙箱限制的提供方时,逐会话执行需要共享沙箱策略服务,工具还会为文件系统变更提供升权路径。
|
||||
**面向模型的文件系统工具**(`read`、`read_image`、`write`、`edit`)及其**执行器**。这是文件系统栈的消费方层:拥有工具名称、JSON Schema、参数校验、提示词段、**读取窗口逻辑**和结果格式化。它**直接**通过 `ctx.fs` 提供方约定([`@deepseek-ai/dsh-fs`](../fs))读取/写入/编辑。新鲜度/观察策略由独立插件([`@deepseek-ai/dsh-fs-observation-policy`](../fs-observation-policy))通过 `fs/*` 事件门禁贡献;工具不与其方法耦合。使用施加沙箱限制的提供方时,逐会话执行需要共享沙箱策略服务,工具还会为文件系统变更提供升权路径。
|
||||
|
||||
```ts ignore-check
|
||||
// Default deployment: a ctx.fs provider, the policy plugin, then the tools.
|
||||
await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) // @deepseek-ai/dsh-fs-local
|
||||
await ctx.plugin(FsPolicy) // @deepseek-ai/dsh-fs-policy (policy gate)
|
||||
await ctx.plugin(FsPolicy) // @deepseek-ai/dsh-fs-observation-policy (policy gate)
|
||||
await ctx.plugin(LocalAttachmentStore, { dshHome }) // optional — enables durable read_image results
|
||||
await ctx.plugin(ToolFs) // this package — read/write/edit, plus read_image with attachments
|
||||
```
|
||||
|
||||
`@deepseek-ai/dsh-fs-policy` 是**可选的**:省略时,工具直接使用裸提供方(无条件写入/覆盖/编辑,无已观察状态)。加载这些工具的部署也应加载该插件,从而提供写入/编辑前读取行为。
|
||||
`@deepseek-ai/dsh-fs-observation-policy` 是**可选的**:省略时,工具直接使用裸提供方(无条件写入/覆盖/编辑,无已观察状态)。加载这些工具的部署也应加载该插件,从而提供写入/编辑前读取行为。
|
||||
|
||||
`read_image` 只在持久 `ctx.attachments` 服务已挂载时注册:没有它,部署无法持久提交图像字节,工具就不会出现。执行时还要求确切路由的模型声明 `image` 输入(通过 `ctx.llm.resolveModelInfo` 从会话最新请求 header 解析,缺失时回退到 agent 选项);未知或纯文本路由在任何文件系统 I/O 之前就得到拒绝结果,因此文本路由的持久历史不会出现图像块。
|
||||
|
||||
@@ -49,13 +49,13 @@ await ctx.plugin(ToolFs) // this package — re
|
||||
- **write**:调用 `ctx.waterfall('fs/write-intent', target, exec, () => undefined)` 取得可选防护,然后调用 `ctx.fs.writeText(target, content, intent)`,再发出 `fs/observed`。(0 次 stat。)
|
||||
- **edit**:调用 `ctx.waterfall('fs/edit-intent', target, exec, () => undefined)` 取得可选防护,然后调用 `ctx.fs.editText(target, edit, intent)`,再发出 `fs/observed`。(0 次 stat。)
|
||||
|
||||
工具在每次分派中把 `exec`(工具执行上下文)作为不透明 `actor` 传入。默认 thunk 返回 `undefined`(不受约束的裸提供方)。加载 `@deepseek-ai/dsh-fs-policy` 后,它会占用单个决策槽:返回 `createIfAbsent`/`replaceIfVersion`/`{ version }` 或抛出 `FS_NOT_OBSERVED`,并在 `fs/observed` 时记录。后端错误(`FsError`)和抛出的 `FS_NOT_OBSERVED` 会流经 `ToolRegistry.execute()`,变成 `isError` 工具结果,并附带 `{ name, code }`。
|
||||
工具在每次分派中把 `exec`(工具执行上下文)作为不透明 `actor` 传入。默认 thunk 返回 `undefined`(不受约束的裸提供方)。加载 `@deepseek-ai/dsh-fs-observation-policy` 后,它会占用单个决策槽:返回 `createIfAbsent`/`replaceIfVersion`/`{ version }` 或抛出 `FS_NOT_OBSERVED`,并在 `fs/observed` 时记录。后端错误(`FsError`)和抛出的 `FS_NOT_OBSERVED` 会流经 `ToolRuntime.execute()`,变成 `isError` 工具结果,并附带 `{ name, code }`。
|
||||
|
||||
当 `ctx.fs.sandboxMode` 表明提供方施加沙箱限制时,write/edit 会公开 `sandbox_permissions` 与 `justification`,并通过 `ctx.approval` 处理获批后的重试。策略归属方会贡献与具体能力无关的常驻策略;工具结果仍保留针对具体操作的拒绝与重试引导。
|
||||
|
||||
## `fs/observed` 发后即忘
|
||||
|
||||
`fs/observed` 在 read/read_image/write/edit 已经成功之后,通过普通 `ctx.emit` 发出。监听器的约定是同步且只有副作用的记录器(`@deepseek-ai/dsh-fs-policy` 使用 `WeakMap.set`);工具不保护这次发出,因此监听器抛出会作为工具的 `isError` 结果出现。异步或可能失败的观察不属于该事件。
|
||||
`fs/observed` 在 read/read_image/write/edit 已经成功之后,通过普通 `ctx.emit` 发出。监听器的约定是同步且只有副作用的记录器(`@deepseek-ai/dsh-fs-observation-policy` 使用 `WeakMap.set`);工具不保护这次发出,因此监听器抛出会作为工具的 `isError` 结果出现。异步或可能失败的观察不属于该事件。
|
||||
|
||||
`read` 允许并发调度,因为它唯一会改变状态的操作是同步记录版本。稍后的 `write` 或 `edit` 会在目标锁内重新检查版本,因此即使记录器发生竞态,系统也会安全地拒绝操作;两个变更工具仍保持互斥。见[并行工具调用 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md)。
|
||||
|
||||
@@ -78,13 +78,13 @@ Use the read tool — not shell commands like cat — to inspect text files. Res
|
||||
##### Write 指导
|
||||
|
||||
```markdown
|
||||
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-policy requires it) and prefer edit for targeted changes.
|
||||
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.
|
||||
```
|
||||
|
||||
##### Edit 指导
|
||||
|
||||
```markdown
|
||||
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-policy requires it), unless you just created or edited it in this session.
|
||||
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.
|
||||
```
|
||||
|
||||
#### Token 影响
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-tool-fs",
|
||||
"description": "Model-facing filesystem tools (read, write, edit) over the DeepSeek Harness filesystem seam (ctx.fs)",
|
||||
"version": "0.0.1-rc.2",
|
||||
"version": "0.1.0-rc.6",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -30,7 +30,7 @@
|
||||
"lib/invariant.js",
|
||||
"lib/types/**/*.d.ts"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"diff": "^9.0.0",
|
||||
"@deepseek-ai/schemastery": "workspace:^"
|
||||
@@ -55,7 +55,7 @@
|
||||
"@deepseek-ai/dsh-attachment": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-observation-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
|
||||
|
||||
@@ -77,7 +77,7 @@ export function applyEditTool(ctx: Context, sandbox: FsSandboxController): void
|
||||
ctx.systemPrompt.section({
|
||||
name: 'tool:edit',
|
||||
order: 102,
|
||||
text: '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-policy requires it), unless you just created or edited it in this session.',
|
||||
text: '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.',
|
||||
})
|
||||
|
||||
ctx.tools.register(defineTool({
|
||||
|
||||
@@ -111,7 +111,7 @@ export class FsSandboxController {
|
||||
* Map a thrown provider error for the model: a `FS_SANDBOX_DENIED` becomes a
|
||||
* `FsError` whose text is the shared `[sandbox: …]` denial marker plus the
|
||||
* same-turn escalation hint, so a policy denial reads identically to bash's
|
||||
* WHILE keeping the structured `FS_SANDBOX_DENIED` code — `ToolRegistry`
|
||||
* WHILE keeping the structured `FS_SANDBOX_DENIED` code — `ToolRuntime`
|
||||
* populates `result.error` only for `HarnessError` instances, so a plain
|
||||
* `Error` would strip the code retry/observers key off. Any other error
|
||||
* passes through unchanged. A `FS_SANDBOX_DENIED` only arises under a
|
||||
|
||||
@@ -63,7 +63,7 @@ export function applyWriteTool(ctx: Context, sandbox: FsSandboxController): void
|
||||
ctx.systemPrompt.section({
|
||||
name: 'tool:write',
|
||||
order: 101,
|
||||
text: '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-policy requires it) and prefer edit for targeted changes.',
|
||||
text: '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.',
|
||||
})
|
||||
|
||||
ctx.tools.register(defineTool({
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-observation-policy'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import { join } from 'node:path'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
|
||||
import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
|
||||
import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-observation-policy'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
|
||||
const testToolSignal = new AbortController().signal
|
||||
@@ -48,12 +48,12 @@ afterEach(async () => {
|
||||
// --------------------------------------------------------------------------
|
||||
// DEFAULT deployment: the policy gate plugin is loaded.
|
||||
// --------------------------------------------------------------------------
|
||||
describe('default deployment (with dsh-fs-policy)', () => {
|
||||
describe('default deployment (with dsh-fs-observation-policy)', () => {
|
||||
beforeEach(async () => {
|
||||
dir = await mkdtemp(join(tmpdir(), 'dsh-tool-fs-'))
|
||||
ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(LocalFileSystem, { cwd: dir })
|
||||
await ctx.plugin(FsPolicy)
|
||||
fiber = await ctx.plugin(ToolFs)
|
||||
@@ -310,12 +310,12 @@ describe('default deployment (with dsh-fs-policy)', () => {
|
||||
// --------------------------------------------------------------------------
|
||||
// BARE deployment: the tool suite WITHOUT the policy gate.
|
||||
// --------------------------------------------------------------------------
|
||||
describe('bare provider (no dsh-fs-policy)', () => {
|
||||
describe('bare provider (no dsh-fs-observation-policy)', () => {
|
||||
beforeEach(async () => {
|
||||
dir = await mkdtemp(join(tmpdir(), 'dsh-tool-fs-bare-'))
|
||||
ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(LocalFileSystem, { cwd: dir })
|
||||
fiber = await ctx.plugin(ToolFs)
|
||||
})
|
||||
@@ -383,7 +383,7 @@ describe('per-session cwd', () => {
|
||||
sessionDir = await mkdtemp(join(tmpdir(), 'dsh-tool-fs-session-'))
|
||||
ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(LocalFileSystem, { cwd: dir }) // config.cwd = dir, NOT sessionDir
|
||||
await ctx.plugin(FsPolicy)
|
||||
fiber = await ctx.plugin(ToolFs)
|
||||
@@ -428,7 +428,7 @@ describe('signal, concurrency, and the fs/observed contract', () => {
|
||||
dir = await mkdtemp(join(tmpdir(), 'dsh-tool-fs-'))
|
||||
ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(LocalFileSystem, { cwd: dir })
|
||||
await ctx.plugin(FsPolicy)
|
||||
fiber = await ctx.plugin(ToolFs)
|
||||
|
||||
@@ -12,13 +12,13 @@ import { join } from 'node:path'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
||||
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
|
||||
import { CallId, LlmAdapter, LlmService } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId, LlmAdapter, LlmRuntime } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, LlmModelInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
|
||||
import ToolRuntime, { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
|
||||
import type { Config as ToolConfig } from '@deepseek-ai/dsh-tools'
|
||||
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-observation-policy'
|
||||
import LocalAttachmentStore from '@deepseek-ai/dsh-attachment-local'
|
||||
import { AttachmentId, AttachmentStore } from '@deepseek-ai/dsh-attachment'
|
||||
import type { ImageAttachmentLimits, ImageAttachmentRef, SaveImageAttachment, StoredImageAttachment } from '@deepseek-ai/dsh-attachment'
|
||||
@@ -100,7 +100,7 @@ interface SetupOptions {
|
||||
async function setup(options: SetupOptions = {}) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry, { mode: options.toolMode ?? 'native' })
|
||||
await ctx.plugin(ToolRuntime, { mode: options.toolMode ?? 'native' })
|
||||
if (options.toolMode === 'code' || options.toolMode === 'both') {
|
||||
await ctx.plugin(FakeRuntime)
|
||||
}
|
||||
@@ -110,7 +110,7 @@ async function setup(options: SetupOptions = {}) {
|
||||
await ctx.plugin(LocalAttachmentStore, { dshHome: home, ...options.storeConfig })
|
||||
}
|
||||
if (options.llm !== false) {
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(LlmRuntime)
|
||||
ctx.llm.registerAdapter(['visual'], new CatalogAdapter(options.models ?? [
|
||||
{ provider: 'visual', id: 'vision-model', name: 'Vision', inputModalities: ['text', 'image'] },
|
||||
{ provider: 'visual', id: 'text-model', name: 'Text', inputModalities: ['text'] },
|
||||
@@ -444,7 +444,7 @@ describe('registration surface', () => {
|
||||
it('withdraws read_image when the tool-fs fiber or the attachment store is disposed (HMR safety)', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry, { mode: 'native' })
|
||||
await ctx.plugin(ToolRuntime, { mode: 'native' })
|
||||
await ctx.plugin(LocalFileSystem, { cwd: dir })
|
||||
await ctx.plugin(FsPolicy)
|
||||
const attachmentsFiber = await ctx.plugin(LocalAttachmentStore, { dshHome: home })
|
||||
|
||||
@@ -10,7 +10,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join, resolve, sep } from 'node:path'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { type ToolResult } from '@deepseek-ai/dsh-tools'
|
||||
import ToolRuntime, { type ToolResult } from '@deepseek-ai/dsh-tools'
|
||||
import { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'
|
||||
import type {
|
||||
FsDirEntry,
|
||||
@@ -22,7 +22,7 @@ import type {
|
||||
FsWriteIntent,
|
||||
FsWriteOutcome,
|
||||
} from '@deepseek-ai/dsh-fs'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-observation-policy'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import { STREAM_MIN_SIZE } from '../src/read.ts'
|
||||
import { formatReadOutput } from '../src/read-render.ts'
|
||||
@@ -101,7 +101,7 @@ class FakeFs extends FileSystem {
|
||||
async function setup() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(FakeFs)
|
||||
await ctx.plugin(FsPolicy)
|
||||
await ctx.plugin(ToolFs)
|
||||
@@ -177,7 +177,7 @@ describe('registration', () => {
|
||||
it('stays pending until ctx.fs exists (inject)', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(ToolFs) // no fs provider
|
||||
expect(ctx.tools.schemas()).toHaveLength(0)
|
||||
})
|
||||
@@ -185,7 +185,7 @@ describe('registration', () => {
|
||||
it('unregisters everything on fiber disposal (HMR safety)', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(FakeFs)
|
||||
await ctx.plugin(FsPolicy)
|
||||
const fiber = await ctx.plugin(ToolFs)
|
||||
@@ -692,7 +692,7 @@ describe('read caps are plugin config', () => {
|
||||
async function setupWith(config: ToolFs.Config) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(FakeFs)
|
||||
await ctx.plugin(FsPolicy)
|
||||
await ctx.plugin(ToolFs, config)
|
||||
@@ -749,7 +749,7 @@ describe('read caps are plugin config', () => {
|
||||
] as const)('rejects a non-positive or fractional %s at load', async (name, config) => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(FakeFs)
|
||||
await expect(ctx.plugin(ToolFs, config)).rejects.toThrow(new RegExp(`tool-fs: ${name} must be a positive integer`))
|
||||
})
|
||||
@@ -791,7 +791,7 @@ describe('sandbox escalation API (write/edit)', () => {
|
||||
async function setupConfining(opts: { approval?: boolean } = {}) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(SandboxPolicyService, { mode: 'workspace-write' })
|
||||
await ctx.plugin(SandboxingFakeFs)
|
||||
await ctx.plugin(FsPolicy)
|
||||
@@ -821,7 +821,7 @@ describe('sandbox escalation API (write/edit)', () => {
|
||||
it('fails load when a confining filesystem has no shared sandbox-policy resolver', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(SandboxingFakeFs)
|
||||
await expect(ctx.plugin(ToolFs)).rejects.toThrow('tool-fs: the mounted filesystem confines but ctx.sandboxPolicy is missing')
|
||||
})
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
"path": "../fs"
|
||||
},
|
||||
{
|
||||
"path": "../fs-policy"
|
||||
"path": "../fs-observation-policy"
|
||||
},
|
||||
{
|
||||
"path": "../../support/invariants"
|
||||
"path": "../../runtime-diagnostics/invariants"
|
||||
},
|
||||
{
|
||||
"path": "../../sandbox/sandbox"
|
||||
|
||||
Reference in New Issue
Block a user