From 965adbb5cf23ffb176e6375cae89f850acde2ca4 Mon Sep 17 00:00:00 2001 From: fz Date: Sat, 5 Sep 2026 22:11:20 +0800 Subject: [PATCH] feat(sdk): disable str_replace_editor by default --- ...-single-dsh-application-launcher.i18n.yaml | 4 +- ...6-08-22-single-dsh-application-launcher.md | 2 +- ...8-22-single-dsh-application-launcher.zh.md | 2 +- ...26-09-05-sdk-default-file-editor.i18n.yaml | 6 ++ .../2026-09-05-sdk-default-file-editor.md | 29 +++++++ .../2026-09-05-sdk-default-file-editor.zh.md | 29 +++++++ .../tests/profiles/sdk/keyless-smoke.e2e.ts | 27 ++++-- packages/bundle/sdk-app/README.i18n.yaml | 4 +- packages/bundle/sdk-app/README.md | 4 +- packages/bundle/sdk-app/README.zh.md | 4 +- packages/bundle/sdk-app/cordis.patch.yml | 3 + .../sdk/bash-tool/tool-schemas.expected.json | 85 ------------------- snapshots/sdk/persistent-tools/cordis.yml | 3 + .../tool-schemas.expected.json | 85 ------------------- .../tool-schemas.1.expected.json | 85 ------------------- .../tool-schemas.expected.json | 85 ------------------- .../sdk/text-turn/tool-schemas.expected.json | 85 ------------------- snapshots/session/text-turn/cordis.yml | 4 + 18 files changed, 106 insertions(+), 440 deletions(-) create mode 100644 .agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.i18n.yaml create mode 100644 .agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.md create mode 100644 .agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.zh.md diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml index 43ff35c345..1a04b28026 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md -2026-08-22-single-dsh-application-launcher.md: 5729ed770b1d26969079e6341d2d2da4b17d0b90 -2026-08-22-single-dsh-application-launcher.zh.md: 45d5b9758fd1f01057de0f34e580249b63f9877e +2026-08-22-single-dsh-application-launcher.md: 7fcb33d8d5c0a9979f3cc5724c2211bf9a1e55a1 +2026-08-22-single-dsh-application-launcher.zh.md: ac91a7b854042b5f974931aa69fffc91fd91319d diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md index 5729ed770b..7fcb33d8d5 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md @@ -34,7 +34,7 @@ Profile manifests own patch reload: Custom profiles default to `live`. A startup profile still applies its bundle, profile, home-level, and invocation `--patch` layers, but it does not watch them after boot. `dsh-base` inserts the module-HMR row disabled; a profile with a tested source-module reload lifecycle must enable it explicitly. None of the shipped profiles enable server module HMR: `patchReload: live` uses the launcher's config-only watcher while the startup profiles install no watcher. SDK and ACP cannot safely replace their server, agents, persistence, or tool registry inside one owned stdio connection. -The shipped protocol profiles reserve stdout for protocol frames, expose help without starting transport, and route stdin EOF and signals through bounded root disposal. ACP remains automation-only. The SDK JSON-RPC methods, notification fields, and `initialize.serverInfo.name` remain stable. Full-profile model-visible tool and persistence defaults come from `dsh-base`; `sdk-minimal` owns its explicit defaults. Runnable snapshots own the assembled application outputs. +The shipped protocol profiles reserve stdout for protocol frames, expose help without starting transport, and route stdin EOF and signals through bounded root disposal. ACP remains automation-only. The SDK JSON-RPC methods, notification fields, and `initialize.serverInfo.name` remain stable. Full-profile model-visible tool and persistence defaults start from `dsh-base`; the SDK application applies its [default editor selection](../simplification/2026-09-05-sdk-default-file-editor.md), while `sdk-minimal` owns its explicit defaults. Runnable snapshots own the assembled application outputs. ### TypeScript SDK customization diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md index 45d5b9758f..ac91a7b854 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md @@ -34,7 +34,7 @@ Profile manifest 负责 patch 重载: 自定义 profile 默认为 `live`。`startup` profile 仍会应用组合包、profile、home 级与调用时 `--patch` 各层,但启动后不会监视这些文件。`dsh-base` 插入的模块 HMR(热模块替换)配置项默认禁用;具有经过验证的源码模块重载生命周期的 profile 必须显式启用它。随附 profile 均不启用服务器模块 HMR:`patchReload: live` 使用启动器的仅配置 watcher,`startup` profile 则不安装 watcher。SDK 与 ACP 无法在一个自有 stdio 连接内安全替换其服务器、agent、持久化或工具注册表。 -随附协议 profile 将 stdout 保留给协议帧,显示帮助时不启动 transport,并通过有界根节点 dispose(资源释放)处理 stdin EOF 与信号。ACP 继续仅用于自动化。SDK JSON-RPC 方法、通知字段与 `initialize.serverInfo.name` 保持稳定。完整 profile 的模型可见工具与持久化默认值来自 `dsh-base`;`sdk-minimal` 拥有自己的显式默认值。可运行快照负责固定已组装的应用输出。 +随附协议 profile 将 stdout 保留给协议帧,显示帮助时不启动 transport,并通过有界根节点 dispose(资源释放)处理 stdin EOF 与信号。ACP 继续仅用于自动化。SDK JSON-RPC 方法、通知字段与 `initialize.serverInfo.name` 保持稳定。完整 profile 的模型可见工具与持久化默认值以 `dsh-base` 为基础;SDK 应用采用其[默认编辑器选择](../simplification/2026-09-05-sdk-default-file-editor.zh.md),而 `sdk-minimal` 拥有自己的显式默认值。可运行快照负责固定已组装的应用输出。 ### TypeScript SDK 自定义 diff --git a/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.i18n.yaml new file mode 100644 index 0000000000..de9afc7a68 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.md +2026-09-05-sdk-default-file-editor.md: ca1d321fb3562f0ea06fca6e4a4a146f922996d3 +2026-09-05-sdk-default-file-editor.zh.md: 9d54e839953f430826622681ab17bc56664e7dff diff --git a/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.md b/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.md new file mode 100644 index 0000000000..ca1d321fb3 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.md @@ -0,0 +1,29 @@ +# Agent Note: SDK default file editor selection + +Status: implemented + +English | [中文](2026-09-05-sdk-default-file-editor.zh.md) + +## Problem + +The standard SDK profile inherits both `read`/`write`/`edit` and `str_replace_editor` from the shared base. These offer overlapping file editing interfaces, while the Web standard preset selects the former. [Issue #3599](https://github.com/deepseek-harness/deepseek-harness/issues/3599) requests the same editor choice for the standard SDK without making all SDK and Web behavior identical. + +## Decision + +The [SDK application patch](../../../../packages/bundle/sdk-app/cordis.patch.yml) disables the inherited `tool-str-replace-editor` row. The SDK retains `read`, `write`, and `edit`; the shared base, reusable editor package, and standalone `sdk-minimal` composition retain their own defaults. A trusted profile, home, or invocation patch can explicitly enable the row. + +This narrows the SDK tool default described by [one dsh launcher](../architecture/2026-08-22-single-dsh-application-launcher.md). That decision remains active for launch ownership, shared services, and patch precedence; no active note is fully superseded. + +## Alternatives considered + +**Keep both editors enabled.** Shared base composition explains their availability, but the standard SDK does not need two default interfaces for the same file editing operations. Callers requiring this editor can select it explicitly. + +**Remove the shared registration or reuse the entire Web preset.** Either changes more than the requested SDK default. Keeping the change in the SDK bundle preserves other profiles and the existing application architecture. + +## Consequences + +Default SDK requests omit the editor schema. Callers that select `str_replace_editor` by name need an explicit patch or their existing dedicated composition. This decision makes no claim of complete SDK/Web tool parity. + +## Verification + +The [SDK profile process test](../../../../apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts) captures actual model requests for the default roster, explicit editor enablement, and standalone minimal roster. [SDK recorded sessions](../../../../snapshots/sdk/sdk.snapshot.ts) pin the resulting schemas; shared cross-profile and persistent-editor recordings explicitly retain their required editor. diff --git a/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.zh.md b/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.zh.md new file mode 100644 index 0000000000..9d54e83995 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-05-sdk-default-file-editor.zh.md @@ -0,0 +1,29 @@ +# Agent Note: SDK 默认文件编辑器选择 + +Status: implemented + +[English](2026-09-05-sdk-default-file-editor.md) | 中文 + +## Problem + +标准 SDK profile 从共享 base 同时继承 `read`/`write`/`edit` 和 `str_replace_editor`。这些工具提供重叠的文件编辑接口,而 Web standard preset 选择前者。[Issue #3599](https://github.com/deepseek-harness/deepseek-harness/issues/3599) 要求标准 SDK 采用相同的编辑器选择,不要求 SDK 与 Web 的全部行为一致。 + +## Decision + +[SDK 应用 patch](../../../../packages/bundle/sdk-app/cordis.patch.yml) 禁用继承的 `tool-str-replace-editor` 配置项。SDK 保留 `read`、`write` 和 `edit`;共享 base、可复用编辑器包及独立 `sdk-minimal` 组合保留各自的默认值。受信任的 profile、home 或逐次调用 patch 可以显式启用该配置项。 + +本决策收窄了[统一 dsh 启动器](../architecture/2026-08-22-single-dsh-application-launcher.zh.md)所述的 SDK 工具默认值。该决策对启动所有权、共享服务和 patch 优先级仍然有效;没有被完全取代的活跃 Agent Note。 + +## Alternatives considered + +**继续默认启用两个编辑器。** 共享 base 组合解释了它们为何可用,但标准 SDK 无需为相同的文件编辑操作默认提供两套接口。需要此编辑器的调用方可以显式选择它。 + +**移除共享注册或复用整个 Web preset。** 两者都会扩大 SDK 默认值调整的范围。将改动放在 SDK bundle 中可以保留其他 profile 和现有应用架构。 + +## Consequences + +默认 SDK 请求省略该编辑器 schema。按名称选择 `str_replace_editor` 的调用方需要显式 patch 或其现有专用组合。本决策不承诺 SDK/Web 的全部工具一致。 + +## Verification + +[SDK profile 进程测试](../../../../apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts) 捕获默认工具清单、显式启用编辑器和独立极简工具清单的实际模型请求。[SDK 录制会话](../../../../snapshots/sdk/sdk.snapshot.ts) 固定最终 schema;跨 profile 共享录制与持久编辑器录制显式保留其所需的编辑器。 diff --git a/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts b/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts index fd5e704b64..91c6e80dcc 100644 --- a/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts +++ b/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts @@ -1,5 +1,5 @@ import { createServer } from 'node:http' -import { mkdtemp, readFile, readdir, rm } from 'node:fs/promises' +import { mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' @@ -46,11 +46,14 @@ function waitForLine( describe('Python SDK dsh profile keyless smoke', () => { it.each([ - { label: 'reports max-token turns with the default mapping config', envValue: undefined }, - { label: 'reports max-token turns with mapping enabled through env', envValue: 'true' }, - { label: 'reports max-token turns with mapping disabled through env', envValue: 'false' }, - ])('$label', async ({ envValue }) => { + { label: 'reports max-token turns with the default mapping config', envValue: undefined, editorEnabled: false }, + { label: 'reports max-token turns with mapping enabled through env', envValue: 'true', editorEnabled: false }, + { label: 'reports max-token turns with mapping disabled through env', envValue: 'false', editorEnabled: false }, + { label: 'allows an explicit patch to enable str_replace_editor', envValue: undefined, editorEnabled: true }, + ])('$label', async ({ envValue, editorEnabled }) => { const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-runtime-smoke-')) + const editorPatch = join(root, 'editor.patch.yml') + if (editorEnabled) await writeFile(editorPatch, '- id: tool-str-replace-editor\n disabled: false\n') const modelRequests: Record[] = [] const modelServer = createServer((request, response) => { let body = '' @@ -76,6 +79,7 @@ describe('Python SDK dsh profile keyless smoke', () => { binScript, '--profile', 'sdk', + ...(editorEnabled ? ['--patch', editorPatch] : []), ], { cwd: repoRoot, env: { @@ -154,7 +158,8 @@ describe('Python SDK dsh profile keyless smoke', () => { const toolNames = tools.map(tool => tool.function?.name) expect(modelRequests[0]?.reasoning_effort).toBe('max') expect(modelRequests[0]?.max_tokens).toBe(1234) - expect(toolNames).toEqual(expect.arrayContaining(['web_fetch', 'web_search'])) + expect(toolNames).toEqual(expect.arrayContaining(['read', 'write', 'edit', 'web_fetch', 'web_search'])) + expect(toolNames.includes('str_replace_editor')).toBe(editorEnabled) expect(toolNames).not.toContain('list_subagent_models') child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`) @@ -180,9 +185,13 @@ describe('Python SDK dsh profile keyless smoke', () => { it('boots the standalone minimal profile through its generated manifest', async () => { const root = await mkdtemp(join(tmpdir(), 'dsh-python-sdk-minimal-')) + const modelRequests: Record[] = [] const modelServer = createServer((request, response) => { - request.resume() + let body = '' + request.setEncoding('utf8') + request.on('data', (chunk: string) => { body += chunk }) request.on('end', () => { + modelRequests.push(JSON.parse(body) as Record) response.writeHead(200, { 'content-type': 'text/event-stream' }) response.write('data: {"choices":[{"delta":{"role":"assistant","content":null}}]}\n\n') response.write('data: {"choices":[{"delta":{"content":"done"}}]}\n\n') @@ -249,6 +258,10 @@ describe('Python SDK dsh profile keyless smoke', () => { bundles: ['@deepseek-ai/dsh-sdk-minimal'], patchReload: 'startup', }) + const tools = modelRequests[0]?.tools as { function?: { name?: string } }[] + expect(tools.map(tool => tool.function?.name).sort()).toEqual( + [process.platform === 'win32' ? 'pwsh' : 'bash', 'str_replace_editor'].sort(), + ) child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'shutdown' })}\n`) await waitForLine(lines, value => value.id === 3, () => stderr) diff --git a/packages/bundle/sdk-app/README.i18n.yaml b/packages/bundle/sdk-app/README.i18n.yaml index cd2ccefc48..9402ab24ca 100644 --- a/packages/bundle/sdk-app/README.i18n.yaml +++ b/packages/bundle/sdk-app/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bundle/sdk-app/README.md -README.md: c83ff2178ebc3dd059c926270564306af0828b72 -README.zh.md: 9b25cbfde70f23309b2dc78a237043d0e8101528 +README.md: c00409984ca627897a54bd68d249a29a7856b91d +README.zh.md: 5d5713d8e452c0a1ee550306f62967711c5fcdcc diff --git a/packages/bundle/sdk-app/README.md b/packages/bundle/sdk-app/README.md index c83ff2178e..c00409984c 100644 --- a/packages/bundle/sdk-app/README.md +++ b/packages/bundle/sdk-app/README.md @@ -31,6 +31,8 @@ The startup provider binds stdin EOF to the launcher's bounded successful shutdo `DSH_MAX_TOKENS_AS_SUCCESS` retains the SDK deployment mapping: unset or JSON `true` reports token-limited subagent completion as accepted, while JSON `false` reports it as an error. Provider/model and workspace cwd arrive through the SDK initialization request; the base profile owns adapters, tools, persistence, policy, settings, and credentials. +A profile, home, or invocation patch can set `tool-str-replace-editor` to `disabled: false` to enable it explicitly. The standalone `sdk-minimal` profile owns its separate tool selection. + ----- @@ -40,7 +42,7 @@ The startup provider binds stdin EOF to the launcher's bounded successful shutdo #### What the model sees -The profile supplies `You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.` before the base tool and context contributions. The exact SDK initialization route and session cwd resolve the placeholders. +The profile supplies `You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.` before the base tool and context contributions. The exact SDK initialization route and session cwd resolve the placeholders. Default file tool schemas include `read`, `write`, and `edit`; they omit `str_replace_editor`. #### Token effect diff --git a/packages/bundle/sdk-app/README.zh.md b/packages/bundle/sdk-app/README.zh.md index 9b25cbfde7..5d5713d8e4 100644 --- a/packages/bundle/sdk-app/README.zh.md +++ b/packages/bundle/sdk-app/README.zh.md @@ -31,6 +31,8 @@ kind: "package-bundle" `DSH_MAX_TOKENS_AS_SUCCESS` 保留 SDK 部署映射:未设置或 JSON `true` 把 token 达限的 subagent 完成报告为已接受,JSON `false` 则报告为错误。模型提供方/模型与工作区 cwd 通过 SDK 初始化请求传入;base profile 拥有适配器、工具、持久化、策略、settings 与 credentials。 +profile、home 或逐次调用 patch 可以把 `tool-str-replace-editor` 设置为 `disabled: false`,显式启用它。独立的 `sdk-minimal` profile 自行决定其工具选择。 + ----- @@ -40,7 +42,7 @@ kind: "package-bundle" #### 模型看到什么 -profile 会在 base 工具与上下文贡献之前提供 `You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.`。确切的 SDK 初始化路由与会话 cwd 会解析其中的占位符。 +profile 会在 base 工具与上下文贡献之前提供 `You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.`。确切的 SDK 初始化路由与会话 cwd 会解析其中的占位符。默认文件工具 schema 包含 `read`、`write` 和 `edit`,不包含 `str_replace_editor`。 #### Token 影响 diff --git a/packages/bundle/sdk-app/cordis.patch.yml b/packages/bundle/sdk-app/cordis.patch.yml index 373e7aeb63..fca7fb6146 100644 --- a/packages/bundle/sdk-app/cordis.patch.yml +++ b/packages/bundle/sdk-app/cordis.patch.yml @@ -8,6 +8,9 @@ - id: session-title-llm disabled: true +- id: tool-str-replace-editor + disabled: true + - insert: - id: sdk-app-startup name: '@deepseek-ai/dsh-sdk-app' diff --git a/snapshots/sdk/bash-tool/tool-schemas.expected.json b/snapshots/sdk/bash-tool/tool-schemas.expected.json index 716b08cd1d..dd7a2da87e 100644 --- a/snapshots/sdk/bash-tool/tool-schemas.expected.json +++ b/snapshots/sdk/bash-tool/tool-schemas.expected.json @@ -357,91 +357,6 @@ ] } }, - { - "name": "str_replace_editor", - "description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with ``\n* A null placeholder for a parameter unused by the selected command is treated as omitted. Required parameters still need values; omit `str_replace.new_str` rather than setting it to null when deleting a match\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.", - "enum": [ - "view", - "create", - "str_replace", - "insert" - ] - }, - "path": { - "type": "string", - "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`." - }, - "file_text": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `create` command, with the content of the file to be created. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "insert_line": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "description": "Required integer parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "new_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Optional string parameter of `str_replace` command containing the new string (if omitted, no string will be added). Required string parameter of `insert` command containing the string to insert. A null placeholder is accepted only by commands that do not use this parameter." - }, - "old_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `str_replace` command containing the string in `path` to replace. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "view_range": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "integer" - } - }, - { - "type": "null" - } - ], - "description": "Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file." - } - }, - "required": [ - "command", - "path" - ] - } - }, { "name": "subagent", "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This call waits for the subagent and returns its result.", diff --git a/snapshots/sdk/persistent-tools/cordis.yml b/snapshots/sdk/persistent-tools/cordis.yml index c78756cb69..a8be6e8d35 100644 --- a/snapshots/sdk/persistent-tools/cordis.yml +++ b/snapshots/sdk/persistent-tools/cordis.yml @@ -2,6 +2,9 @@ # editor as model-facing tools, with process-local providers and an explicit # danger-full-access / never-ask policy. +- id: tool-str-replace-editor + disabled: false + - id: llm-deepseek name: '@deepseek-ai/dsh-llm-deepseek' config: diff --git a/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json b/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json index ee4ad0e6b6..ba63548517 100644 --- a/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json +++ b/snapshots/sdk/subagent-dsh-sdk-diagnostic/tool-schemas.expected.json @@ -357,91 +357,6 @@ ] } }, - { - "name": "str_replace_editor", - "description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with ``\n* A null placeholder for a parameter unused by the selected command is treated as omitted. Required parameters still need values; omit `str_replace.new_str` rather than setting it to null when deleting a match\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.", - "enum": [ - "view", - "create", - "str_replace", - "insert" - ] - }, - "path": { - "type": "string", - "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`." - }, - "file_text": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `create` command, with the content of the file to be created. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "insert_line": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "description": "Required integer parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "new_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Optional string parameter of `str_replace` command containing the new string (if omitted, no string will be added). Required string parameter of `insert` command containing the string to insert. A null placeholder is accepted only by commands that do not use this parameter." - }, - "old_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `str_replace` command containing the string in `path` to replace. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "view_range": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "integer" - } - }, - { - "type": "null" - } - ], - "description": "Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file." - } - }, - "required": [ - "command", - "path" - ] - } - }, { "name": "subagent", "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` steers the child's nearest step while it is running and starts a turn while it is idle. Set `run_in_background: false` only when your next action depends on receiving the result.", diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json index c21ad8a0f5..e7899283b4 100644 --- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json +++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.1.expected.json @@ -357,91 +357,6 @@ ] } }, - { - "name": "str_replace_editor", - "description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with ``\n* A null placeholder for a parameter unused by the selected command is treated as omitted. Required parameters still need values; omit `str_replace.new_str` rather than setting it to null when deleting a match\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.", - "enum": [ - "view", - "create", - "str_replace", - "insert" - ] - }, - "path": { - "type": "string", - "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`." - }, - "file_text": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `create` command, with the content of the file to be created. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "insert_line": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "description": "Required integer parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "new_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Optional string parameter of `str_replace` command containing the new string (if omitted, no string will be added). Required string parameter of `insert` command containing the string to insert. A null placeholder is accepted only by commands that do not use this parameter." - }, - "old_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `str_replace` command containing the string in `path` to replace. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "view_range": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "integer" - } - }, - { - "type": "null" - } - ], - "description": "Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file." - } - }, - "required": [ - "command", - "path" - ] - } - }, { "name": "subagent", "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` steers the child's nearest step while it is running and starts a turn while it is idle. Set `run_in_background: false` only when your next action depends on receiving the result.", diff --git a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json index 9cc82c6fb5..73c4ae509a 100644 --- a/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json +++ b/snapshots/sdk/subagent-dsh-sdk-dynamic-route/tool-schemas.expected.json @@ -374,91 +374,6 @@ ] } }, - { - "name": "str_replace_editor", - "description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with ``\n* A null placeholder for a parameter unused by the selected command is treated as omitted. Required parameters still need values; omit `str_replace.new_str` rather than setting it to null when deleting a match\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.", - "enum": [ - "view", - "create", - "str_replace", - "insert" - ] - }, - "path": { - "type": "string", - "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`." - }, - "file_text": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `create` command, with the content of the file to be created. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "insert_line": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "description": "Required integer parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "new_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Optional string parameter of `str_replace` command containing the new string (if omitted, no string will be added). Required string parameter of `insert` command containing the string to insert. A null placeholder is accepted only by commands that do not use this parameter." - }, - "old_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `str_replace` command containing the string in `path` to replace. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "view_range": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "integer" - } - }, - { - "type": "null" - } - ], - "description": "Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file." - } - }, - "required": [ - "command", - "path" - ] - } - }, { "name": "subagent", "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This call waits for the subagent and returns its result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and this provider's route defaults. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.", diff --git a/snapshots/sdk/text-turn/tool-schemas.expected.json b/snapshots/sdk/text-turn/tool-schemas.expected.json index 716b08cd1d..dd7a2da87e 100644 --- a/snapshots/sdk/text-turn/tool-schemas.expected.json +++ b/snapshots/sdk/text-turn/tool-schemas.expected.json @@ -357,91 +357,6 @@ ] } }, - { - "name": "str_replace_editor", - "description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with ``\n* A null placeholder for a parameter unused by the selected command is treated as omitted. Required parameters still need values; omit `str_replace.new_str` rather than setting it to null when deleting a match\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.", - "enum": [ - "view", - "create", - "str_replace", - "insert" - ] - }, - "path": { - "type": "string", - "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`." - }, - "file_text": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `create` command, with the content of the file to be created. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "insert_line": { - "oneOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "description": "Required integer parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "new_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Optional string parameter of `str_replace` command containing the new string (if omitted, no string will be added). Required string parameter of `insert` command containing the string to insert. A null placeholder is accepted only by commands that do not use this parameter." - }, - "old_str": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "description": "Required string parameter of `str_replace` command containing the string in `path` to replace. A null placeholder is treated as omitted by commands that do not use this parameter." - }, - "view_range": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "integer" - } - }, - { - "type": "null" - } - ], - "description": "Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file." - } - }, - "required": [ - "command", - "path" - ] - } - }, { "name": "subagent", "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This call waits for the subagent and returns its result.", diff --git a/snapshots/session/text-turn/cordis.yml b/snapshots/session/text-turn/cordis.yml index ea97cc0616..45582b5ec0 100644 --- a/snapshots/session/text-turn/cordis.yml +++ b/snapshots/session/text-turn/cordis.yml @@ -2,6 +2,10 @@ # The shipped profile owns application startup; this patch fixes only the # deterministic test composition and raw persistence needed for recording. +# Shared recordings keep their editor across headless and SDK profile defaults. +- id: tool-str-replace-editor + disabled: false + - id: llm-deepseek name: '@deepseek-ai/dsh-llm-deepseek' config: