From e9784c073be2c73da9963dbfea503279ce9aaf0d Mon Sep 17 00:00:00 2001 From: _Kerman Date: Mon, 19 Oct 2026 16:32:29 +0800 Subject: [PATCH] fix(tools): settle executor calls after unload and validate configuration --- .../tests/profiles/sdk/keyless-smoke.e2e.ts | 2 +- apps/cli/tests/web-agent-presets.e2e.ts | 6 +- apps/web/tests/shipped-composition.e2e.ts | 4 +- docs/config-catalog.md | 30 +----- docs/config-catalog.zh.md | 30 +----- docs/module-graph.md | 3 +- docs/module-graph.zh.md | 3 +- docs/persistence-catalog.i18n.yaml | 4 +- docs/persistence-catalog.md | 2 +- docs/persistence-catalog.zh.md | 2 +- docs/persistence-schema.json | 2 +- docs/subsystems/tools.md | 3 +- docs/subsystems/tools.zh.md | 3 +- .../tool-executor-plugins/guide.i18n.yaml | 4 +- .../tool-executor-plugins/guide.md | 2 +- .../tool-executor-plugins/guide.zh.md | 2 +- .../context/session-reference/package.json | 2 +- .../core/agent-loop/tests/tool-calls.spec.ts | 10 +- .../agent-tool-presentation/README.i18n.yaml | 8 +- .../core/agent-tool-presentation/README.md | 4 +- .../core/agent-tool-presentation/README.zh.md | 4 +- .../core/agent-tool-presentation/src/index.ts | 5 +- .../tests/agent-tool-presentation.spec.ts | 16 +--- .../tool-executor-native/README.i18n.yaml | 8 +- packages/core/tool-executor-native/README.md | 8 +- .../core/tool-executor-native/README.zh.md | 8 +- .../core/tool-executor-native/package.json | 5 - .../core/tool-executor-native/src/index.ts | 20 +--- .../core/tool-executor-ptc/README.i18n.yaml | 12 +-- packages/core/tool-executor-ptc/README.md | 9 +- packages/core/tool-executor-ptc/README.zh.md | 9 +- packages/core/tool-executor-ptc/package.json | 2 - packages/core/tool-executor-ptc/src/config.ts | 13 +++ packages/core/tool-executor-ptc/src/index.ts | 39 ++------ .../core/tool-executor-ptc/src/ptc-calls.ts | 8 +- packages/core/tool-executor-ptc/src/ptc.ts | 39 ++++---- .../core/tool-executor-ptc/src/py-types.ts | 16 ++-- .../tool-executor-ptc/tests/ptc-calls.spec.ts | 6 +- .../core/tool-executor-ptc/tests/ptc.spec.ts | 93 +++++++++++++------ packages/core/tools/README.i18n.yaml | 4 +- packages/core/tools/README.md | 2 +- packages/core/tools/README.zh.md | 2 +- packages/core/tools/src/contracts.ts | 2 +- packages/core/tools/src/index.ts | 6 ++ packages/core/tools/src/pipeline.ts | 17 ++-- .../core/tools/tests/execution-mode.spec.ts | 20 ++-- packages/core/tools/tests/tools.spec.ts | 42 +++++++++ .../examples/persistent-ptc.cordis.yml | 4 +- .../extensions/tool-cordis/src/api-catalog.ts | 4 +- packages/fs/tool-fs/tests/read-image.spec.ts | 6 +- packages/fs/tool-fs/tests/tools.spec.ts | 12 +-- .../goal/tool-goal/tests/tool-goal.spec.ts | 2 +- packages/llm/llm-deepseek/package.json | 2 +- .../tests/tool-executors.spec.ts | 15 ++- .../references/packages.md | 2 +- packages/spill/spill-policy/package.json | 1 - .../tool-subagent/tests/tool-subagent.spec.ts | 6 +- .../tests/agent-loop-testkit.spec.ts | 11 +++ .../test-support/tool-testkit/src/index.ts | 2 +- packages/web/tool-web/tests/tool-web.spec.ts | 4 +- pnpm-lock.yaml | 22 ++--- scripts/gen-cordis-catalog.ts | 1 - .../cordis-inspect-jsdoc/session.v4.jsonl | 2 +- 63 files changed, 327 insertions(+), 310 deletions(-) create mode 100644 packages/core/tool-executor-ptc/src/config.ts diff --git a/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts b/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts index 4d3bb617b8..5c51bd36ae 100644 --- a/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts +++ b/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts @@ -399,7 +399,7 @@ describe('Python SDK dsh profile keyless smoke', () => { const patch = join(root, 'failure.yml') await mkdir(home) if (blocked) await writeFile(join(home, 'logs'), 'blocked') - await writeFile(patch, '- id: tool-executor-native\n config:\n maxParallelToolCalls: 0\n') + await writeFile(patch, '- id: agent-loop\n config:\n maxParallelToolCalls: 0\n') const child = execa(launch.command, [ ...launch.args, '--profile', 'sdk', '--patch', patch, ], { diff --git a/apps/cli/tests/web-agent-presets.e2e.ts b/apps/cli/tests/web-agent-presets.e2e.ts index 5944476fb2..43620ba878 100644 --- a/apps/cli/tests/web-agent-presets.e2e.ts +++ b/apps/cli/tests/web-agent-presets.e2e.ts @@ -816,7 +816,7 @@ describe('a user preset declared from the shipped cordis rows', () => { expect(queriedText).toContain(`"name": "${toolName}"`) } - // The `Config` provider reads the booted profile tree: the executor selector declares a Config, + // The `Config` provider reads the booted profile tree: the Agent loop declares a Config, // and the bootstrap include row is a carrier. The name filter keeps each page small. type ConfigRow = { id: string; patchId: string; name: string; status: string } type ConfigPage = { data: { entries: ConfigRow[]; total: number; nextOffset: number | null } } @@ -831,9 +831,9 @@ describe('a user preset declared from the shipped cordis rows', () => { expect(page.isError, resultText(page)).toBe(false) return (JSON.parse(resultText(page)) as ConfigPage).data } - const toolsRows = await listConfigs({ name: '@deepseek-ai/dsh-tool-executor-native' }, 'copied-preset-inspect-configs') + const toolsRows = await listConfigs({ name: '@deepseek-ai/dsh-agent-loop' }, 'copied-preset-inspect-configs') const toolsRow = toolsRows.entries[0] - expect(toolsRow).toMatchObject({ patchId: 'tool-executor-native', status: 'schema' }) + expect(toolsRow).toMatchObject({ patchId: 'agent-loop', status: 'schema' }) expect(toolsRows).toMatchObject({ total: 1, nextOffset: null }) const includes = await listConfigs({ name: 'cordis:include' }, 'copied-preset-inspect-includes') expect(includes.entries.find(entry => entry.patchId === 'include')).toMatchObject({ status: 'tree' }) diff --git a/apps/web/tests/shipped-composition.e2e.ts b/apps/web/tests/shipped-composition.e2e.ts index 669a48faf1..9868bc8968 100644 --- a/apps/web/tests/shipped-composition.e2e.ts +++ b/apps/web/tests/shipped-composition.e2e.ts @@ -1,4 +1,4 @@ -import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit' +import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit' // Boots the shipped Web composition over the built dist this lane already uses // and asserts its catalog, defaults, Loader lifecycle, and one complete Auto // producer-to-tool path. Browser scenarios in this lane own visual behavior. @@ -680,7 +680,7 @@ it('lets a preset producer reach the background-job registry', async () => { // `tool-bash` is a preset row and `tasks` is a host registry; the producer // resolves it with `ctx.get`, so a registry hidden behind a preset realm // fails here — with every task control still listed in the catalog above. - const started = await executeToolFixture(ctx, { + const started = await executeModelToolFixture(ctx, { signal, callId: ToolCallId('shipped-bash-background'), name: 'bash', diff --git a/docs/config-catalog.md b/docs/config-catalog.md index ee53ca268d..33ab75b9e4 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -162,7 +162,7 @@ export interface Config { ## `@deepseek-ai/dsh-agent-tool-presentation` - `inject`: `tools` -- `source`: [`packages/core/agent-tool-presentation/src/index.ts:26`](../packages/core/agent-tool-presentation/src/index.ts) +- `source`: [`packages/core/agent-tool-presentation/src/index.ts:25`](../packages/core/agent-tool-presentation/src/index.ts) ```ts config-catalog /** @@ -3822,40 +3822,19 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-tool-executor-native` - -- `inject`: `tools` -- `refs`: `Volatile` (`@deepseek-ai/cosmokit`) -- `source`: [`packages/core/tool-executor-native/src/index.ts:14`](../packages/core/tool-executor-native/src/index.ts) - -```ts config-catalog -/** Native batch concurrency configuration. */ -export interface Config { - /** Maximum overlapping concurrency-safe calls in one batch. */ - maxParallelToolCalls: Volatile -} -``` - - ## `@deepseek-ai/dsh-tool-executor-ptc` -- `inject`: `tools` · `systemPrompt` · `ptcRuntime` -- `refs`: `Volatile` (`@deepseek-ai/cosmokit`) -- `source`: [`packages/core/tool-executor-ptc/src/index.ts:19`](../packages/core/tool-executor-ptc/src/index.ts) +- `inject`: `tools` · `systemPrompt` +- `source`: [`packages/core/tool-executor-ptc/src/config.ts:5`](../packages/core/tool-executor-ptc/src/config.ts) ```ts config-catalog /** Program execution limits. */ export interface Config { - /** Maximum overlapping concurrency-safe calls in one model batch. */ - maxParallelToolCalls: Volatile /** Maximum overlapping concurrency-safe program subcalls. */ - maxParallelSubCalls?: number + maxParallelSubCalls: number } ``` @@ -4769,6 +4748,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-terminal` | — | [`packages/terminal/terminal/src/index.ts`](../packages/terminal/terminal/src/index.ts) | | `@deepseek-ai/dsh-tool-call-timeout-policy` | `tools` | [`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts) | | `@deepseek-ai/dsh-tool-cordis` | `tools` · `cordisInspect` | [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts) | +| `@deepseek-ai/dsh-tool-executor-native` | `tools` | [`packages/core/tool-executor-native/src/index.ts`](../packages/core/tool-executor-native/src/index.ts) | | `@deepseek-ai/dsh-tool-schedule` | `tools` | [`packages/schedule/tool-schedule/src/index.ts`](../packages/schedule/tool-schedule/src/index.ts) | | `@deepseek-ai/dsh-tool-subagent-control` | `tools` · `subagents` · `agents` | [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) | | `@deepseek-ai/dsh-tool-working-directory` | `tools` · `workingDirectory` | [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 7da2fed7a5..1e2e6f7a05 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -164,7 +164,7 @@ export interface Config { ## `@deepseek-ai/dsh-agent-tool-presentation` - `inject`: `tools` -- `source`: [`packages/core/agent-tool-presentation/src/index.ts:26`](../packages/core/agent-tool-presentation/src/index.ts) +- `source`: [`packages/core/agent-tool-presentation/src/index.ts:25`](../packages/core/agent-tool-presentation/src/index.ts) ```ts config-catalog /** @@ -3824,40 +3824,19 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-tool-executor-native` - -- `inject`: `tools` -- `refs`: `Volatile` (`@deepseek-ai/cosmokit`) -- `source`: [`packages/core/tool-executor-native/src/index.ts:14`](../packages/core/tool-executor-native/src/index.ts) - -```ts config-catalog -/** Native batch concurrency configuration. */ -export interface Config { - /** Maximum overlapping concurrency-safe calls in one batch. */ - maxParallelToolCalls: Volatile -} -``` - - ## `@deepseek-ai/dsh-tool-executor-ptc` -- `inject`: `tools` · `systemPrompt` · `ptcRuntime` -- `refs`: `Volatile` (`@deepseek-ai/cosmokit`) -- `source`: [`packages/core/tool-executor-ptc/src/index.ts:19`](../packages/core/tool-executor-ptc/src/index.ts) +- `inject`: `tools` · `systemPrompt` +- `source`: [`packages/core/tool-executor-ptc/src/config.ts:5`](../packages/core/tool-executor-ptc/src/config.ts) ```ts config-catalog /** Program execution limits. */ export interface Config { - /** Maximum overlapping concurrency-safe calls in one model batch. */ - maxParallelToolCalls: Volatile /** Maximum overlapping concurrency-safe program subcalls. */ - maxParallelSubCalls?: number + maxParallelSubCalls: number } ``` @@ -4771,6 +4750,7 @@ export interface Config { | `@deepseek-ai/dsh-terminal` | — | [`packages/terminal/terminal/src/index.ts`](../packages/terminal/terminal/src/index.ts) | | `@deepseek-ai/dsh-tool-call-timeout-policy` | `tools` | [`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts) | | `@deepseek-ai/dsh-tool-cordis` | `tools` · `cordisInspect` | [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts) | +| `@deepseek-ai/dsh-tool-executor-native` | `tools` | [`packages/core/tool-executor-native/src/index.ts`](../packages/core/tool-executor-native/src/index.ts) | | `@deepseek-ai/dsh-tool-schedule` | `tools` | [`packages/schedule/tool-schedule/src/index.ts`](../packages/schedule/tool-schedule/src/index.ts) | | `@deepseek-ai/dsh-tool-subagent-control` | `tools` · `subagents` · `agents` | [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) | | `@deepseek-ai/dsh-tool-working-directory` | `tools` · `workingDirectory` | [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) | diff --git a/docs/module-graph.md b/docs/module-graph.md index c5ba1734a8..233e731000 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -1194,7 +1194,6 @@ flowchart TD pkg_spill_policy --> pkg_session pkg_spill_policy --> pkg_spill pkg_spill_policy --> pkg_token_meter - pkg_spill_policy --> pkg_tool_executor_ptc pkg_spill_policy --> pkg_tools pkg_session_query_sqlite --> pkg_session pkg_session_query_sqlite --> pkg_session_persistence @@ -1769,7 +1768,7 @@ flowchart TD | [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | | [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`fs-local`](../packages/fs/fs-local), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) | -| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tools`](../packages/core/tools) | +| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) | | [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 6a7f781d4a..ba174b22f4 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -1196,7 +1196,6 @@ flowchart TD pkg_spill_policy --> pkg_session pkg_spill_policy --> pkg_spill pkg_spill_policy --> pkg_token_meter - pkg_spill_policy --> pkg_tool_executor_ptc pkg_spill_policy --> pkg_tools pkg_session_query_sqlite --> pkg_session pkg_session_query_sqlite --> pkg_session_persistence @@ -1771,7 +1770,7 @@ flowchart TD | [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | | [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`fs-local`](../packages/fs/fs-local), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) | -| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tools`](../packages/core/tools) | +| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) | | [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 634a3a5b47..8112d1933e 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -1830,8 +1830,8 @@ en: 08be4349548406a9 zh: 8e7cf9966198ef78 /session-persistence-event-catalog/resolved-persistence-types/kind-tool-registry: - en: 4fde95c569339141 - zh: 6fa54e19c24ab7f3 + en: c59d020828d8211f + zh: 154a30cb69e79489 /session-persistence-event-catalog/resolved-persistence-types/kind-user: en: a58f339332877b15 zh: b7406af7d8197b64 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index ad03afd422..54674899b3 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -7542,7 +7542,7 @@ SHA-256: `c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49` SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543` -Sources: [`packages/core/tools/src/index.ts:17`](../packages/core/tools/src/index.ts) +Sources: [`packages/core/tools/src/index.ts:18`](../packages/core/tools/src/index.ts) | Property | Presence | Type | |---|---|---| diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index ac37597541..f4833af8b8 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -7544,7 +7544,7 @@ SHA-256: `c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49` SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543` -来源:[`packages/core/tools/src/index.ts:17`](../packages/core/tools/src/index.ts) +来源:[`packages/core/tools/src/index.ts:18`](../packages/core/tools/src/index.ts) | 属性 | 存在性 | 类型 | |---|---|---| diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index 935980f278..c1aed265be 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -24599,7 +24599,7 @@ "digest": "8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543", "names": [], "sources": [ - "packages/core/tools/src/index.ts:17" + "packages/core/tools/src/index.ts:18" ] }, { diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index a43e3f29e0..a92f41d6df 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -293,7 +293,7 @@ interface ToolExecutionInput { * mode sets this on SDK sub-dispatches so commit-style observers can wait for * the outer `run_code` outcome without receiving its live mutable execution. * The token also marks the call as a transport sub-dispatch rather than a - * model-direct call: under `mode: 'ptc'`, only calls WITH a parent may + * model-direct call: with the PTC executor selected, only calls with a parent may * execute a native tool name — a model-direct call (no parent) is denied as * `UNKNOWN_TOOL` before the policy pipeline. */ @@ -635,6 +635,7 @@ schemas(scope?: ScopeKey): ToolSchema[] /** * Provide the executor for the calling scope; an unscoped contribution is the deployment default. + * Rendering any executor-owned section requires the viewing scope's selection to remain available. * @param executor - initialized provider owned by the contributing plugin. * @returns the effect disposer. Unloading leaves the selection unavailable until it is replaced. */ diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index a0fecf88cc..187d61c3f7 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -293,7 +293,7 @@ interface ToolExecutionInput { * mode sets this on SDK sub-dispatches so commit-style observers can wait for * the outer `run_code` outcome without receiving its live mutable execution. * The token also marks the call as a transport sub-dispatch rather than a - * model-direct call: under `mode: 'ptc'`, only calls WITH a parent may + * model-direct call: with the PTC executor selected, only calls with a parent may * execute a native tool name — a model-direct call (no parent) is denied as * `UNKNOWN_TOOL` before the policy pipeline. */ @@ -635,6 +635,7 @@ schemas(scope?: ScopeKey): ToolSchema[] /** * Provide the executor for the calling scope; an unscoped contribution is the deployment default. + * Rendering any executor-owned section requires the viewing scope's selection to remain available. * @param executor - initialized provider owned by the contributing plugin. * @returns the effect disposer. Unloading leaves the selection unavailable until it is replaced. */ diff --git a/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.i18n.yaml index bf3b31aa5b..60370bde67 100644 --- a/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.i18n.yaml +++ b/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.i18n.yaml @@ -9,8 +9,8 @@ en: 73002a2963b9ea27 zh: a67ea382d7c70c61 /mount-a-tool-executor/change: - en: b47c04b407b204a9 - zh: f3f54c9b25eb1951 + en: f732d85027bf0236 + zh: d6597cde7ba31e47 /mount-a-tool-executor/migration: en: 411860d06f1013ea zh: 615e044545d89b55 diff --git a/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.md b/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.md index 9c3fc0bc4a..9ea22ca563 100644 --- a/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.md +++ b/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.md @@ -9,7 +9,7 @@ English | [中文](guide.zh.md) ## Change -`@deepseek-ai/dsh-tools` keeps tool registration and executor selection, but no longer accepts `mode` or `maxParallelSubCalls`. Native and PTC execution require `@deepseek-ai/dsh-tool-executor-native` or `@deepseek-ai/dsh-tool-executor-ptc`. Shipped profiles mount an executor; custom compositions must add one. The existing `dsh-agent-tool-presentation` selector remains supported but is deprecated. Enabled `tool-executor-native` and `tool-executor-ptc` rows are required at startup; invalid executor configuration now stops startup. +`@deepseek-ai/dsh-tools` keeps tool registration and executor selection, but rejects the removed `mode` and `maxParallelSubCalls` configuration fields at load time. Native and PTC execution require `@deepseek-ai/dsh-tool-executor-native` or `@deepseek-ai/dsh-tool-executor-ptc`. Shipped profiles mount an executor; custom compositions must add one. The existing `dsh-agent-tool-presentation` selector remains supported but is deprecated. Enabled `tool-executor-native` and `tool-executor-ptc` rows are required at startup; invalid executor configuration now stops startup. ## Migration diff --git a/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.zh.md index 0d9e484c8e..6e0f4c9a24 100644 --- a/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.zh.md +++ b/docs/upgrade-guide/v0.2.1-rc.2/tool-executor-plugins/guide.zh.md @@ -9,7 +9,7 @@ description: "工具执行模式从 dsh-tools 配置移至显式执行器插件 ## 变更 -`@deepseek-ai/dsh-tools` 保留工具注册和执行器选择,但不再接受 `mode` 或 `maxParallelSubCalls`。原生和 PTC 执行分别要求 `@deepseek-ai/dsh-tool-executor-native` 或 `@deepseek-ai/dsh-tool-executor-ptc`。内置 profile 已挂载执行器,自定义组合必须添加一个。现有 `dsh-agent-tool-presentation` 选择器仍受支持,但已弃用。 启用的 `tool-executor-native` 和 `tool-executor-ptc` 条目属于启动必需项;无效的执行器配置会终止启动。 +`@deepseek-ai/dsh-tools` 保留工具注册和执行器选择,但加载时会拒绝已移除的 `mode` 和 `maxParallelSubCalls` 配置字段。原生和 PTC 执行分别要求 `@deepseek-ai/dsh-tool-executor-native` 或 `@deepseek-ai/dsh-tool-executor-ptc`。内置 profile 已挂载执行器,自定义组合必须添加一个。现有 `dsh-agent-tool-presentation` 选择器仍受支持,但已弃用。启用的 `tool-executor-native` 和 `tool-executor-ptc` 条目属于启动必需项;无效的执行器配置会终止启动。 ## 迁移 diff --git a/packages/context/session-reference/package.json b/packages/context/session-reference/package.json index e7ffcca708..8cefe88f74 100644 --- a/packages/context/session-reference/package.json +++ b/packages/context/session-reference/package.json @@ -47,7 +47,6 @@ "license": "MIT", "dependencies": { "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-tool-executor-native": "workspace:*", "@deepseek-ai/dsh-util-values": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", "zod": "4.4.3" @@ -95,6 +94,7 @@ "@deepseek-ai/dsh-subagent": "workspace:*", "@deepseek-ai/dsh-spill-local": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", + "@deepseek-ai/dsh-tool-executor-native": "workspace:*", "@deepseek-ai/dsh-tool-fs": "workspace:*", "@deepseek-ai/dsh-tool-testkit": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", diff --git a/packages/core/agent-loop/tests/tool-calls.spec.ts b/packages/core/agent-loop/tests/tool-calls.spec.ts index a57106751d..d08e9f6bca 100644 --- a/packages/core/agent-loop/tests/tool-calls.spec.ts +++ b/packages/core/agent-loop/tests/tool-calls.spec.ts @@ -1,8 +1,8 @@ -import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' /** * Exercises scheduler ordering and cancellation with deterministic gated tools. * ACP expected outputs own transcript-facing coverage. */ +import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc' @@ -37,13 +37,13 @@ async function harness(adapter: MockAdapter, maxParallelToolCalls?: number, exec await ctx.plugin(ToolRuntime) let toolPipeline!: ToolExecutionPipeline await ctx.plugin({ - inject: ['tools'], Config: NativeToolExecutor.Config, - apply(owner: Context, config: NativeToolExecutor.Config) { - const executor = new NativeToolExecutor.NativeToolExecutor(owner, () => config.maxParallelToolCalls.get()) + inject: ['tools'], + apply(owner: Context) { + const executor = new NativeToolExecutor.NativeToolExecutor(owner, () => executorLimit ?? 10) toolPipeline = executor['pipeline'] owner.tools.setExecutor(executor) }, - }, { ...executorLimit === undefined ? {} : { maxParallelToolCalls: executorLimit } }) + }) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [], diff --git a/packages/core/agent-tool-presentation/README.i18n.yaml b/packages/core/agent-tool-presentation/README.i18n.yaml index 2a2ed58937..b39ccedaf7 100644 --- a/packages/core/agent-tool-presentation/README.i18n.yaml +++ b/packages/core/agent-tool-presentation/README.i18n.yaml @@ -21,8 +21,8 @@ en: 1550e3dcc5a3aa1e zh: 47b2d1e0e3e3cbf2 /deepseek-ai-dsh-agent-tool-presentation/use-this-package/what-ptc-mode-requires: - en: d5004b2caa31b245 - zh: 32619d7e03c7c2ac + en: 3ec33f18b3ac0859 + zh: 92bf371bfbbc511d /deepseek-ai-dsh-agent-tool-presentation/use-this-package/one-presentation-per-agent: en: 46bf80f051175567 zh: 8f5e1623da5d1520 @@ -36,8 +36,8 @@ en: 89ecdb3d5d032e7f zh: 64ae2feb4344c679 /deepseek-ai-dsh-agent-tool-presentation/understand-the-implementation/behavior-notes: - en: 7996925328f0e771 - zh: 40146455aac119d2 + en: 4a44ac4057732829 + zh: 813d3ec7627146fd /deepseek-ai-dsh-agent-tool-presentation/further-exploration: en: 6a1d0096e4da25e4 zh: 9d5de770addb69d2 diff --git a/packages/core/agent-tool-presentation/README.md b/packages/core/agent-tool-presentation/README.md index 569ee37d63..b3c22d9bfe 100644 --- a/packages/core/agent-tool-presentation/README.md +++ b/packages/core/agent-tool-presentation/README.md @@ -45,7 +45,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a ### What PTC mode requires -Selecting `ptc` needs a composed PTC runtime (`ctx.ptcRuntime`) whose language has a registered SDK renderer — the TypeScript runtime ships via [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.md), and both the TypeScript and Python SDK renderers are built into `dsh-tool-executor-ptc`. A preset that selects a PTC mode against a deployment composing no such runtime refuses to mount, naming this row, so the failure lands where the operator can act instead of at the session's first request. +Selecting `ptc` needs a composed PTC runtime (`ctx.ptcRuntime`) whose language has a registered SDK renderer — the TypeScript runtime ships via [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.md), and both the TypeScript and Python SDK renderers are built into `dsh-tool-executor-ptc`. Selection takes effect even when the runtime is missing; prompt assembly or program execution then reports the missing runtime instead of inheriting native execution. ### One presentation per agent @@ -73,7 +73,7 @@ The tool registry cannot move into a preset: its consumers are all host-plane ### Behavior notes -`native` applies immediately. A PTC mode instead waits for `ctx.ptcRuntime`, a host-plane service: a preset selecting PTC mode against a deployment composing no runtime holds this row pending, and `dsh-agent-preset-registry` refuses the mount naming this id. `setExecutor` is itself the effect, so the declaration unwinds with this row without a second wrapper owning it. +Both modes mount their executor immediately. PTC checks runtime availability when describing or executing a program. The mounted executor owns its scoped registration, which is withdrawn with this selector's plugin lifetime. diff --git a/packages/core/agent-tool-presentation/README.zh.md b/packages/core/agent-tool-presentation/README.zh.md index e30fc01cb5..f54c3799a6 100644 --- a/packages/core/agent-tool-presentation/README.zh.md +++ b/packages/core/agent-tool-presentation/README.zh.md @@ -45,7 +45,7 @@ kind: "package-reference" ### PTC 模式需要什么 -选择 `ptc` 需要已组合的 PTC 运行时(`ctx.ptcRuntime`),且其语言有已注册的 SDK 渲染器——TypeScript 运行时经 [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.zh.md) 交付,TypeScript 与 Python 的 SDK 渲染器都内置在 `dsh-tools` 中。针对未组装此类运行时的部署选择 PTC 模式的 preset 会拒绝挂载并点名这一行,使失败落在操作者可以行动的地方,而不是落在会话的第一次请求上。 +选择 `ptc` 需要已组合的 PTC 运行时(`ctx.ptcRuntime`),且其语言有已注册的 SDK 渲染器——TypeScript 运行时经 [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.zh.md) 交付,TypeScript 与 Python 的 SDK 渲染器都内置在 `dsh-tool-executor-ptc` 中。即使缺少运行时,选择也会生效;随后提示词组装或程序执行会报告缺少运行时,而不会继承原生执行方式。 ### 每个 agent 只声明一次呈现方式 @@ -73,7 +73,7 @@ kind: "package-reference" ### 行为说明 -`native` 立即生效。PTC 模式则等待 `ctx.ptcRuntime`——这是一个宿主平面服务:针对未组装运行时的部署选择 PTC mode 的 preset 会让这一行停在 pending,`dsh-agent-preset-registry` 会指名此 id 拒绝挂载。`setExecutor` 本身就是 effect,因此该声明随这一行撤销,无需第二个包装层拥有它。 +两种模式都会立即挂载各自的执行器。PTC 在描述或执行程序时检查运行时是否可用。挂载的执行器拥有作用域注册,并随此选择器的插件生命周期一同撤销。 diff --git a/packages/core/agent-tool-presentation/src/index.ts b/packages/core/agent-tool-presentation/src/index.ts index cc621472e3..ce986fce5f 100644 --- a/packages/core/agent-tool-presentation/src/index.ts +++ b/packages/core/agent-tool-presentation/src/index.ts @@ -13,9 +13,8 @@ import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc' export const name = 'tool-presentation' /** - * Required services. `ptcRuntime` is NOT listed: a `native` row must mount in - * a deployment that composes no runtime, and the mode-dependent wait is - * declared inside {@link apply} instead. + * Tool selection does not require a program runtime; the PTC executor + * reports a missing runtime when its description or execution is requested. */ export const inject = ['tools'] diff --git a/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts b/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts index 3dcbbc747c..4481f31788 100644 --- a/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts +++ b/packages/core/agent-tool-presentation/tests/agent-tool-presentation.spec.ts @@ -1,9 +1,8 @@ /** * The row an agent preset carries to pick its tool presentation. What it owes * its caller: the choice reaches THIS agent and no other, it unwinds with the - * agent, and a code mode composed against a deployment with no PTC runtime - * stops at mount — where a preset's activation audit can name it — rather - * than at the first prompt assembly. + * agent, and a missing PTC runtime rejects the selected scope's prompt + * instead of inheriting the native executor. */ import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' @@ -68,8 +67,7 @@ describe('the tool-presentation row', () => { }) it('declares the services it uses without holding a PTC runtime hostage', () => { - // A `native` row must mount where no runtime is composed, so the wait is - // conditional inside apply rather than static metadata. + // Native execution does not require a program runtime. expect(inject).toEqual(['tools']) }) @@ -95,17 +93,13 @@ describe('the tool-presentation row', () => { await expect(ctx.systemPrompt.assemble({ scope: agent })).rejects.toThrow('selected tool executor is unavailable') }) - it('waits for a PTC runtime the deployment does not compose', async () => { + it('rejects PTC assembly when the deployment does not compose its runtime', async () => { const ctx = await host({ runtime: false }) const { agent, row } = await mount(ctx, { mode: 'ptc' }) - // Pending, not applied: `dsh-agent-preset-registry` rejects a mount holding a row - // that never reached a usable state, naming this id — so the preset fails - // where the operator can act, instead of at the first request. expect(row.ctx.get('ptcRuntime')).toBeUndefined() - const assembly = await ctx.systemPrompt.assemble({ scope: agent }) - expect(assembly.tools.map(tool => tool.name)).toEqual(['echo']) + await expect(ctx.systemPrompt.assemble({ scope: agent })).rejects.toThrow('requires a PTC runtime') }) it('applies once the runtime arrives', async () => { diff --git a/packages/core/tool-executor-native/README.i18n.yaml b/packages/core/tool-executor-native/README.i18n.yaml index 118a6d84fb..8d8ed7b889 100644 --- a/packages/core/tool-executor-native/README.i18n.yaml +++ b/packages/core/tool-executor-native/README.i18n.yaml @@ -15,11 +15,11 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-tool-executor-native/use-this-package: - en: 176f69450bacc28e - zh: fc3c2d876195a4a4 + en: 0e73b1a49308a0f6 + zh: 968b3ee68284d849 /deepseek-ai-dsh-tool-executor-native/understand-the-implementation: - en: d005036097c11788 - zh: 65fcaec551dc1697 + en: 6a06c6b318f58436 + zh: 4f572feca095e3a6 /deepseek-ai-dsh-tool-executor-native/further-exploration: en: e8ae407b0ecdc5d3 zh: 022a8a714d24da4b diff --git a/packages/core/tool-executor-native/README.md b/packages/core/tool-executor-native/README.md index 5d6103c5fc..0841540ba6 100644 --- a/packages/core/tool-executor-native/README.md +++ b/packages/core/tool-executor-native/README.md @@ -29,13 +29,9 @@ Add this row to a composition with `dsh-tools` mounted. The nearest scoped execu ```yaml - name: '@deepseek-ai/dsh-tool-executor-native' - config: - maxParallelToolCalls: 10 ``` -For Agent steps, configure [agent-loop concurrency](../agent-loop/README.md); the loop always forwards its resolved limit to `execute()`. The executor's `maxParallelToolCalls` supplies the limit only when the caller omits it. This fallback is volatile, defaults to `10`, and is sampled for the next tool group. It must be a positive integer; `1` selects serial execution. - -[Configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-executor-native) +Configure [agent-loop concurrency](../agent-loop/README.md) for Agent steps; the loop reads the limit once per batch. This plugin has no configuration fields. Direct `NativeToolExecutor` callers may pass a constructor callback, defaulting to `10`, which is sampled for each tool group when the batch omits `maxParallelToolCalls`. Limits must be positive integers; `1` selects serial execution. ----- @@ -47,7 +43,7 @@ For Agent steps, configure [agent-loop concurrency](../agent-loop/README.md); th The executor receives the entire raw call batch and owns parsing, scheduling, cancellation, and optional model-step logging. It reuses the shared invocation pipeline for policy, tool bodies, and result notifications. After abort, it waits for started work and returns ordered error results for skipped calls. -- [`src/index.ts`](src/index.ts) — Validated configuration and scoped registration; tool descriptions and batch execution. +- [`src/index.ts`](src/index.ts) — Scoped executor registration; tool descriptions and batch execution. diff --git a/packages/core/tool-executor-native/README.zh.md b/packages/core/tool-executor-native/README.zh.md index 82f71691e6..4c9ea6ee19 100644 --- a/packages/core/tool-executor-native/README.zh.md +++ b/packages/core/tool-executor-native/README.zh.md @@ -29,13 +29,9 @@ kind: "package-reference" ```yaml - name: '@deepseek-ai/dsh-tool-executor-native' - config: - maxParallelToolCalls: 10 ``` -Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循环始终向 `execute()` 传入解析后的上限。只有调用方未传入上限时,才使用执行器的 `maxParallelToolCalls`。这个后备配置是 volatile 字段,默认值为 `10`,在下一个工具组开始时读取。值必须是正整数;`1` 表示串行执行。 - -[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-executor-native) +Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循环在每个批次开始时读取一次上限。本插件没有配置字段。直接构造 `NativeToolExecutor` 的调用方可以传入回调,默认返回 `10`;批次未指定 `maxParallelToolCalls` 时,每个工具组读取该回调。上限必须是正整数;`1` 表示串行执行。 ----- @@ -47,7 +43,7 @@ Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循 执行器接收完整的原始调用批次,负责解析、调度、取消和可选的模型步骤日志。它复用共享调用流水线来执行策略、工具和结果通知。中止后,它等待已开始的工作完成,并为跳过的调用返回有序错误结果。 -- [`src/index.ts`](src/index.ts) — 配置校验和作用域注册;工具描述和批次执行。 +- [`src/index.ts`](src/index.ts) — 作用域执行器注册;工具描述和批次执行。 diff --git a/packages/core/tool-executor-native/package.json b/packages/core/tool-executor-native/package.json index 3b733d5e63..e027d0e9bb 100644 --- a/packages/core/tool-executor-native/package.json +++ b/packages/core/tool-executor-native/package.json @@ -28,17 +28,12 @@ ], "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/cosmokit": "workspace:~", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*" }, - "dependencies": { - "@deepseek-ai/schemastery": "workspace:~" - }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/cosmokit": "workspace:~", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*" diff --git a/packages/core/tool-executor-native/src/index.ts b/packages/core/tool-executor-native/src/index.ts index 24685a696b..457749152c 100644 --- a/packages/core/tool-executor-native/src/index.ts +++ b/packages/core/tool-executor-native/src/index.ts @@ -3,15 +3,13 @@ import type { Context } from '@deepseek-ai/cordis' import { projectToolSchema } from '@deepseek-ai/dsh-tools' import type { ToolDefinition, ToolExecutor, ToolModelInput, ToolBatchInput, ToolBatchResult } from '@deepseek-ai/dsh-tools' import { ToolExecutionPipeline, executeToolBatch } from '@deepseek-ai/dsh-tools/pipeline' -import type { Volatile } from '@deepseek-ai/cosmokit' -import z from '@deepseek-ai/schemastery' /** Executes visible capabilities directly and owns their complete batch settlement. */ export class NativeToolExecutor implements ToolExecutor { private readonly pipeline: ToolExecutionPipeline constructor(ctx: Context, private readonly maxParallel: () => number = () => 10) { - this.pipeline = new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }) + this.pipeline = new ToolExecutionPipeline(ctx) } describe(tools: readonly ToolDefinition[]): ToolModelInput { @@ -28,22 +26,10 @@ export const name = 'native-tool-executor' /** Tool registration and scoped dispatch. */ export const inject = ['tools'] -/** Native batch concurrency configuration. */ -export interface Config { - /** Maximum overlapping concurrency-safe calls in one batch. */ - maxParallelToolCalls: Volatile -} - -/** Validated live concurrency limit; one makes each batch serial. */ -export const Config: z<{ maxParallelToolCalls?: number }, Config> = z.object({ - maxParallelToolCalls: z.number().step(1).min(1).default(10).volatile(), -}) - /** * Select native execution in the mounting scope. * @param ctx - context owning this executor's selection and lifetime. - * @param config - live batch concurrency configuration. */ -export function apply(ctx: Context, config: Config): void { - ctx.tools.setExecutor(new NativeToolExecutor(ctx, () => config.maxParallelToolCalls.get())) +export function apply(ctx: Context): void { + ctx.tools.setExecutor(new NativeToolExecutor(ctx)) } diff --git a/packages/core/tool-executor-ptc/README.i18n.yaml b/packages/core/tool-executor-ptc/README.i18n.yaml index 3a3836c1d0..e6d35ea2bc 100644 --- a/packages/core/tool-executor-ptc/README.i18n.yaml +++ b/packages/core/tool-executor-ptc/README.i18n.yaml @@ -9,17 +9,17 @@ en: e253ed9cec03a396 zh: 922fde282e2b0fd2 /deepseek-ai-dsh-tool-executor-ptc/summary: - en: 60fb16b4d443d265 - zh: 6b0df6471c370c6b + en: 66e159c308afb6c1 + zh: 29b1a646ae7f918a /deepseek-ai-dsh-tool-executor-ptc/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-tool-executor-ptc/use-this-package: - en: 5cfc28690358a419 - zh: d066d078ca649022 + en: 488f394426b3aaa0 + zh: a15185f91a1c342c /deepseek-ai-dsh-tool-executor-ptc/understand-the-implementation: - en: f054dc0faabb468f - zh: 10f13508bfa9ef84 + en: 9d4f61c351945d30 + zh: 1c575526230b0e02 /deepseek-ai-dsh-tool-executor-ptc/understand-the-implementation/ptc-mode: en: 11d32e05ba397f86 zh: 4e071ec4e6222d9d diff --git a/packages/core/tool-executor-ptc/README.md b/packages/core/tool-executor-ptc/README.md index b7b56146a5..7519301f2a 100644 --- a/packages/core/tool-executor-ptc/README.md +++ b/packages/core/tool-executor-ptc/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Use this executor when models should call tools through `run_code` programs. Mount a compatible PTC runtime and tool registry first. Scoped restrictions determine the generated SDK; only `run_code` is directly callable. Configure root-call and program-subcall concurrency independently. +Use this executor when models should call tools through `run_code` programs. Mount a compatible PTC runtime and tool registry first. Scoped restrictions determine the generated SDK; only `run_code` is directly callable. Configure concurrency for parallel-safe program subcalls. ## Table of Contents @@ -30,13 +30,12 @@ Add this row to a composition with `dsh-tools` mounted. The nearest scoped execu ```yaml - name: '@deepseek-ai/dsh-tool-executor-ptc' config: - maxParallelToolCalls: 10 maxParallelSubCalls: 10 ``` -For Agent steps, configure [agent-loop concurrency](../agent-loop/README.md); the loop always forwards its resolved limit to `execute()`. The executor's `maxParallelToolCalls` supplies the limit only when the caller omits it. This fallback is volatile, defaults to `10`, and is sampled for the next tool group. It must be a positive integer; `1` selects serial execution. +Root `run_code` calls are exclusive and execute serially. `maxParallelSubCalls` limits parallel-safe nested calls within each program and defaults to `10`; it must be a positive integer, with `1` selecting serial execution. Exclusive calls form barriers. -This package also requires `ctx.ptcRuntime` and `ctx.systemPrompt`; Agent-owned programs need `ctx.workingDirectory`. `maxParallelSubCalls` limits overlapping parallel-safe calls within each program and defaults to `10`. +This package also requires `ctx.ptcRuntime` and `ctx.systemPrompt`; Agent-owned programs need `ctx.workingDirectory`. A missing runtime rejects prompt assembly or execution; the selected PTC executor never falls back to native execution. Import durable event payloads from `@deepseek-ai/dsh-tool-executor-ptc/types`; this entry is safe for detached readers and client compilation. Host policy extensions import `PtcDispatchLog`, `tools/ptc-dispatch-log`, and the `ptc-mode` message-source declaration through a type-only import of the package entry, without mounting an executor. @@ -50,7 +49,7 @@ Import durable event payloads from `@deepseek-ai/dsh-tool-executor-ptc/types`; t
Implementation internals — click to expand -The executor receives the entire raw call batch and owns parsing, scheduling, cancellation, and optional model-step logging. It reuses the shared invocation pipeline for policy, tool bodies, and result notifications. After abort, it waits for started work and returns ordered error results for skipped calls. +The executor receives the entire raw call batch and owns parsing, scheduling, cancellation, and optional model-step logging. It reuses the shared invocation pipeline for policy, tool bodies, and result notifications. After abort, it waits for started work and returns ordered error results for skipped calls. Unloading the executor aborts active sandboxed programs and waits for their nested calls to settle. Accepted batches still settle through that executor, potentially with cancellation errors. ### PTC mode diff --git a/packages/core/tool-executor-ptc/README.zh.md b/packages/core/tool-executor-ptc/README.zh.md index f4d60631ea..8625183568 100644 --- a/packages/core/tool-executor-ptc/README.zh.md +++ b/packages/core/tool-executor-ptc/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -当模型应通过 `run_code` 程序调用工具时,使用此执行器。先挂载兼容的 PTC 运行时和工具注册表。作用域限制决定生成的 SDK;只有 `run_code` 能被直接调用。根调用和程序子调用的并发限制分别配置。 +当模型应通过 `run_code` 程序调用工具时,使用此执行器。先挂载兼容的 PTC 运行时和工具注册表。作用域限制决定生成的 SDK;只有 `run_code` 能被直接调用。可配置并行安全的程序子调用并发上限。 ## 目录 @@ -30,13 +30,12 @@ kind: "package-reference" ```yaml - name: '@deepseek-ai/dsh-tool-executor-ptc' config: - maxParallelToolCalls: 10 maxParallelSubCalls: 10 ``` -Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循环始终向 `execute()` 传入解析后的上限。只有调用方未传入上限时,才使用执行器的 `maxParallelToolCalls`。这个后备配置是 volatile 字段,默认值为 `10`,在下一个工具组开始时读取。值必须是正整数;`1` 表示串行执行。 +根 `run_code` 调用是独占调用,按串行执行。`maxParallelSubCalls` 限制每个程序中并行安全的嵌套调用数,默认值为 `10`;值必须是正整数,`1` 表示串行执行。独占调用构成屏障。 -本包还要求 `ctx.ptcRuntime` 和 `ctx.systemPrompt`;带 Agent 的程序要求 `ctx.workingDirectory`。`maxParallelSubCalls` 限制每个程序内重叠的并行安全调用,默认值为 `10`。 +本包还要求 `ctx.ptcRuntime` 和 `ctx.systemPrompt`;带 Agent 的程序要求 `ctx.workingDirectory`。缺少运行时时,提示词组装或执行会失败;选中的 PTC 执行器不会回退到原生执行。 持久化事件载荷从 `@deepseek-ai/dsh-tool-executor-ptc/types` 导入;该入口可用于独立读取者与 Client 编译。Host 策略扩展通过包主入口的仅类型导入获取 `PtcDispatchLog`、`tools/ptc-dispatch-log` 与 `ptc-mode` 消息来源声明,无需挂载执行器。 @@ -50,7 +49,7 @@ Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循
实现细节——点击展开 -执行器接收完整的原始调用批次,负责解析、调度、取消和可选的模型步骤日志。它复用共享调用流水线来执行策略、工具和结果通知。中止后,它等待已开始的工作完成,并为跳过的调用返回有序错误结果。 +执行器接收完整的原始调用批次,负责解析、调度、取消和可选的模型步骤日志。它复用共享调用流水线来执行策略、工具和结果通知。中止后,它等待已开始的工作完成,并为跳过的调用返回有序错误结果。卸载执行器会中止运行中的沙箱程序,并等待其嵌套调用结束。已接纳批次仍由该执行器结算,但可能返回取消错误。 ### PTC mode diff --git a/packages/core/tool-executor-ptc/package.json b/packages/core/tool-executor-ptc/package.json index 89c8973e84..c2dc63b7e1 100644 --- a/packages/core/tool-executor-ptc/package.json +++ b/packages/core/tool-executor-ptc/package.json @@ -33,7 +33,6 @@ ], "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/cosmokit": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-ptc-runtime": "workspace:*", @@ -52,7 +51,6 @@ }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/cosmokit": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", diff --git a/packages/core/tool-executor-ptc/src/config.ts b/packages/core/tool-executor-ptc/src/config.ts new file mode 100644 index 0000000000..b2710764e2 --- /dev/null +++ b/packages/core/tool-executor-ptc/src/config.ts @@ -0,0 +1,13 @@ +/** Configuration shared by plugin mounting and direct executor construction. @module */ +import z from '@deepseek-ai/schemastery' + +/** Program execution limits. */ +export interface Config { + /** Maximum overlapping concurrency-safe program subcalls. */ + maxParallelSubCalls: number +} + +/** Validated program subcall concurrency; one makes subcalls serial. */ +export const Config: z, Config> = z.object({ + maxParallelSubCalls: z.natural().min(1).default(10), +}) diff --git a/packages/core/tool-executor-ptc/src/index.ts b/packages/core/tool-executor-ptc/src/index.ts index 74de8722b0..fa6c00086b 100644 --- a/packages/core/tool-executor-ptc/src/index.ts +++ b/packages/core/tool-executor-ptc/src/index.ts @@ -1,14 +1,12 @@ /** PTC execution plugin for the mounting Agent, preset, or deployment scope. @module */ import type { Context } from '@deepseek-ai/cordis' -import type { ScopeKey } from '@deepseek-ai/dsh-scope' import { RUN_CODE_NAME, ToolNotFoundError, projectToolSchema } from '@deepseek-ai/dsh-tools' import type { ToolDefinition, ToolExecutor, ToolModelInput, ToolPromptSection, ToolBatchInput, ToolBatchResult } from '@deepseek-ai/dsh-tools' import { ToolExecutionPipeline, executeToolBatch } from '@deepseek-ai/dsh-tools/pipeline' import { createRunCodeTool, requirePtcRuntime, type PtcSdkLanguage } from './ptc.ts' import { renderToolsSdk, type ToolSdkSchema } from './ts-types.ts' import { renderToolsSdkPy } from './py-types.ts' -import type { Volatile } from '@deepseek-ai/cosmokit' -import z from '@deepseek-ai/schemastery' +import { Config } from './config.ts' const PTC_ONLY_INSTRUCTION = `\`${RUN_CODE_NAME}\` 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.` @@ -24,17 +22,15 @@ export class PtcExecutor implements ToolExecutor { private readonly maxParallelSubCalls: number private ptcTransport: ToolDefinition | undefined - constructor(private readonly ctx: Context, config: Pick = {}, private readonly maxParallel: () => number = () => 10) { + constructor(private readonly ctx: Context, config: Partial = {}, private readonly maxParallel: () => number = () => 10) { + this.maxParallelSubCalls = Config(config).maxParallelSubCalls + const tools = ctx.tools this.sections = [ { name: 'tools:ptc-only', order: ctx.systemPrompt.getSectionOrder('PTC_ONLY'), text: PTC_ONLY_INSTRUCTION, interpolate: false }, { name: 'tools:sdk', order: ctx.systemPrompt.getSectionOrder('TOOLS_SDK'), text: tools => this.renderSdk(tools), interpolate: false }, ] - this.maxParallelSubCalls = config.maxParallelSubCalls ?? 10 - if (!Number.isInteger(this.maxParallelSubCalls) || this.maxParallelSubCalls < 1) { - throw new Error('maxParallelSubCalls must be a positive integer') - } this.pipeline = new ToolExecutionPipeline(ctx, { - get: (name, scope) => this.get(name, scope), + get: (name, scope) => name === RUN_CODE_NAME ? this.requirePtcTransport() : tools.get(name, scope), reject: (name, nested) => !nested && name !== RUN_CODE_NAME ? new ToolNotFoundError(name, `only \`${RUN_CODE_NAME}\` is callable directly — call \`${name}\` from inside a \`${RUN_CODE_NAME}\` program instead`) : undefined, @@ -54,10 +50,6 @@ export class PtcExecutor implements ToolExecutor { })), runtime.programFormat) } - private get(name: string, scope?: ScopeKey): ToolDefinition | undefined { - return name === RUN_CODE_NAME ? this.requirePtcTransport() : this.ctx.tools.get(name, scope) - } - execute(input: ToolBatchInput): Promise { return executeToolBatch(input, this.pipeline, this.maxParallel) } @@ -69,6 +61,7 @@ export class PtcExecutor implements ToolExecutor { } } +export { Config } from './config.ts' export { CodeRunFailedError } from './errors.ts' export type { PtcDispatchLog } from './types.ts' export type { PtcDispatchStartEventData, PtcDispatchEventData } from './session-types.ts' @@ -77,22 +70,8 @@ export { jsonSchemaToPy, renderToolsSdkPy } from './py-types.ts' /** Cordis plugin name. */ export const name = 'ptc-tool-executor' -/** The program provider activates with its runtime. */ -export const inject = ['tools', 'systemPrompt', 'ptcRuntime'] - -/** Program execution limits. */ -export interface Config { - /** Maximum overlapping concurrency-safe calls in one model batch. */ - maxParallelToolCalls: Volatile - /** Maximum overlapping concurrency-safe program subcalls. */ - maxParallelSubCalls?: number -} - -/** Validated program execution configuration. */ -export const Config: z<{ maxParallelToolCalls?: number; maxParallelSubCalls?: number }, Config> = z.object({ - maxParallelToolCalls: z.number().step(1).min(1).default(10).volatile(), - maxParallelSubCalls: z.natural().min(1).default(10), -}) +/** Register the selected executor even when its runtime is unavailable. */ +export const inject = ['tools', 'systemPrompt'] /** * Select PTC execution in the mounting scope. @@ -100,5 +79,5 @@ export const Config: z<{ maxParallelToolCalls?: number; maxParallelSubCalls?: nu * @param config - program subcall concurrency limits. */ export function apply(ctx: Context, config: Config): void { - ctx.tools.setExecutor(new PtcExecutor(ctx, config, () => config.maxParallelToolCalls.get())) + ctx.tools.setExecutor(new PtcExecutor(ctx, config)) } diff --git a/packages/core/tool-executor-ptc/src/ptc-calls.ts b/packages/core/tool-executor-ptc/src/ptc-calls.ts index 16de96380c..c088ff92b4 100644 --- a/packages/core/tool-executor-ptc/src/ptc-calls.ts +++ b/packages/core/tool-executor-ptc/src/ptc-calls.ts @@ -15,8 +15,8 @@ export class PtcCalls { private disposed = false /** - * Observe policy changes for the owning tool registry. - * @param ctx - Registry lifecycle and Session events. + * Observe policy changes for the owning executor. + * @param ctx - Executor lifecycle and Session events. */ constructor(ctx: Context) { ctx.on('session/event', (session, event) => { @@ -28,7 +28,7 @@ export class PtcCalls { ctx.effect(() => async () => { this.disposed = true const calls = [...this.calls.values()].flatMap(calls => [...calls]) - for (const call of calls) call.controller.abort(new Error('PTC tool registry disposed')) + for (const call of calls) call.controller.abort(new Error('PTC tool executor disposed')) await Promise.all(calls.map(call => call.done)) }, 'PTC calls') } @@ -48,7 +48,7 @@ export class PtcCalls { await Promise.all(pending.map(call => call.done)) } signal.throwIfAborted() - if (this.disposed) throw new Error('PTC tool registry disposed') + if (this.disposed) throw new Error('PTC tool executor disposed') const controller = new AbortController() const done = Promise.withResolvers() const call = { mode: mode(), controller, done: done.promise } diff --git a/packages/core/tool-executor-ptc/src/ptc.ts b/packages/core/tool-executor-ptc/src/ptc.ts index be45a469f8..899b89dd71 100644 --- a/packages/core/tool-executor-ptc/src/ptc.ts +++ b/packages/core/tool-executor-ptc/src/ptc.ts @@ -19,7 +19,7 @@ import type {} from '@deepseek-ai/dsh-user-approval' import type {} from '@deepseek-ai/dsh-working-directory' import type {} from '@deepseek-ai/dsh-sandbox-policy' import { deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values' -import { defineTool, parameterSchemaSpecToJsonSchema } from '@deepseek-ai/dsh-tools' +import { defineTool, parameterSchemaSpecToJsonSchema, RUN_CODE_NAME } from '@deepseek-ai/dsh-tools' import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' import { toolErrorMessage } from '@deepseek-ai/dsh-tools/pipeline' import { scopeTarget } from '@deepseek-ai/dsh-scope' @@ -29,8 +29,7 @@ import type { ToolDefinition, ToolExecutionResult, ToolRunContext } from '@deeps import type {} from './session-types.ts' /** The model-facing name of the PTC mode tool. */ -export { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools' -import { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools' +export { RUN_CODE_NAME } /** Language-specific tool and code-parameter descriptions. */ interface RunCodeFlavor { @@ -264,19 +263,6 @@ function resolveSandboxPolicy(ctx: Context, exec: ToolRunContext): SandboxExecut return policy.resolve(exec.agent === undefined ? {} : { session: exec.agent.session }) } -/** A failed log-content listener preserves the settled content and cannot fail the dispatch. */ -async function shapeDispatchLog(ctx: Context, dispatch: PtcDispatchLog): Promise { - try { - return await ctx.waterfall( - scopeTarget(ctx.tools, dispatch.agent), 'tools/ptc-dispatch-log', dispatch, - () => Promise.resolve(dispatch.content), - ) - } catch (error: unknown) { - ctx.logger.warn(`tools: ptc-dispatch-log listener failed for ${dispatch.name}: ${toolErrorMessage(error)}; logging the original settled content`) - return dispatch.content - } -} - /** * Build the reserved `run_code` transport outside registered capabilities. * @param ctx - executor context owning policy listeners and active program cleanup. @@ -289,6 +275,23 @@ export function createRunCodeTool( executor: ToolExecutionPipeline, maxParallel: number, ): ToolDefinition { + // Retain services for log settlement after the executor context becomes inactive. + const tools = ctx.tools + const logger = ctx.logger + + /** A failed log-content listener preserves the settled content and cannot fail the dispatch. */ + async function shapeDispatchLog(dispatch: PtcDispatchLog): Promise { + try { + return await ctx.waterfall( + scopeTarget(tools, dispatch.agent), 'tools/ptc-dispatch-log', dispatch, + () => Promise.resolve(dispatch.content), + ) + } catch (error: unknown) { + logger.warn(`tools: ptc-dispatch-log listener failed for ${dispatch.name}: ${toolErrorMessage(error)}; logging the original settled content`) + return dispatch.content + } + } + const stateOwners = new WeakMap>() const calls = new PtcCalls(ctx) const definition = defineTool({ @@ -536,7 +539,7 @@ export function createRunCodeTool( // The listener may replace the durable copy with a preview and // locator; the program's value and model-visible result are // untouched. - const logged = await shapeDispatchLog(ctx, { + const logged = await shapeDispatchLog({ exec, agent, subCallId, name, isError: result.isError, // The registry deep-froze this projection at result // finalization; append snapshots the final copy again, so @@ -645,7 +648,7 @@ export function createRunCodeTool( // restricted globals vanish) — the same view the SDK section declared, // so a program can bind exactly what its prompt promised; sub-dispatch // re-resolves per call through the same view (exec.agent threads down). - for (const schema of ctx.tools.schemas(exec.agent)) { + for (const schema of tools.schemas(exec.agent)) { Object.defineProperty(functions, schema.name, { enumerable: true, value: binding(deepFreeze(schema)) }) } diff --git a/packages/core/tool-executor-ptc/src/py-types.ts b/packages/core/tool-executor-ptc/src/py-types.ts index 9634099bd3..2d5e9d9e5a 100644 --- a/packages/core/tool-executor-ptc/src/py-types.ts +++ b/packages/core/tool-executor-ptc/src/py-types.ts @@ -5,12 +5,12 @@ * store, keyed by the loaded {@link @deepseek-ai/dsh-ptc-runtime#PtcRuntime.language | PTC * runtime's language}. * - * Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated - * SDK is the model's ONLY source for each tool's argument names, required fields, types, + * With the PTC executor selected, native tool schemas are omitted from the request. This generated + * SDK is the model's only source for each tool's argument names, required fields, types, * descriptions, and canonical output types. Object-shaped arguments and outputs render as one * named `TypedDict` per tool (and per nested object), not an opaque `dict[str, Any]`, so the * shape survives into the program under the mode that has nothing else to carry it. - * @module @deepseek-ai/dsh-tools/src/py-types + * @module */ import { assertSupportedJsonSchema } from '@deepseek-ai/dsh-tools' @@ -31,7 +31,7 @@ const IDENTIFIER = /^[\p{XID_Start}_]\p{XID_Continue}*$/u * Python identifiers are not ASCII: `路径` is as legal a field name as `path`, * and rejecting it would degrade the whole enclosing object, dropping every * field's name, requiredness, and type — information whose only source under - * `mode: 'ptc'` is this generated text. + * PTC execution is this generated text. * * NFKC stability is a second and separate condition, because CPython * normalizes identifiers at compile time while JSON keys are compared as @@ -162,7 +162,7 @@ interface RenderState { * (`SyntaxError: source code string cannot contain null bytes`), whether it * sits in a docstring or in a comment, so one such byte anywhere in a schema * description would make the whole generated SDK unparseable — under - * `mode: 'ptc'`, the model's only declaration of the tools. The rest are + * PTC execution, the model's only declaration of the tools. The rest are * legal but invisible; escaping them with the same rule keeps the emitted text * readable and the treatment uniform. * @@ -578,7 +578,7 @@ function renderType(schema: unknown, className: string, state: RenderState): str } } // TypedDict syntax cannot express openness, so an open object states it - // in-band: the annotation is advisory either way, and `mode: 'ptc'` + // in-band: the annotation is advisory either way, and PTC execution // omits the native schemas, making this line the model's only signal // that extra keys are accepted. if (node.additionalProperties !== false) { @@ -777,8 +777,8 @@ export function renderToolsSdkPy(schemas: readonly ToolSdkSchema[]): string { // of that method's body. Emitted before the `async def` it would instead // become the `Tools` class docstring (for the first tool) or a dead // expression (for every later one), leaving every method undocumented — - // and under `mode: 'ptc'` this SDK is the model's only description of - // what a tool does. A docstring is a complete body, so the `...` stub is + // and with PTC selected, this SDK is the model's only description of what + // a tool does. A docstring is a complete body, so the `...` stub is // only for the description-less case. const doc = docLines(schema.description, 2) members.push(doc.length > 0 diff --git a/packages/core/tool-executor-ptc/tests/ptc-calls.spec.ts b/packages/core/tool-executor-ptc/tests/ptc-calls.spec.ts index bf5dce527a..5fe9fd7ec8 100644 --- a/packages/core/tool-executor-ptc/tests/ptc-calls.spec.ts +++ b/packages/core/tool-executor-ptc/tests/ptc-calls.spec.ts @@ -67,7 +67,7 @@ it('honors cancellation while waiting for a canceled predecessor', async () => { expect(next).not.toHaveBeenCalled() }) -it('unloads the registry only after calls stop and refuses late admission', async () => { +it('unloads the executor only after calls stop and refuses late admission', async () => { const { ctx, session, calls, mode, signal } = await setup() const cleanup = Promise.withResolvers() const stopped = Promise.withResolvers() @@ -79,11 +79,11 @@ it('unloads the registry only after calls stop and refuses late admission', asyn try { await stopped.promise const next = calls.run(session.id, signal, mode, async () => 'unexpected') - const rejected = expect(next).rejects.toThrow('PTC tool registry disposed') + const rejected = expect(next).rejects.toThrow('PTC tool executor disposed') cleanup.resolve(undefined) await first await rejected await disposed - await expect(calls.run(session.id, signal, mode, async () => 'unexpected')).rejects.toThrow('PTC tool registry disposed') + await expect(calls.run(session.id, signal, mode, async () => 'unexpected')).rejects.toThrow('PTC tool executor disposed') } finally { cleanup.resolve(undefined); await first; await disposed } }) diff --git a/packages/core/tool-executor-ptc/tests/ptc.spec.ts b/packages/core/tool-executor-ptc/tests/ptc.spec.ts index c42b502e2a..e3cbd422e0 100644 --- a/packages/core/tool-executor-ptc/tests/ptc.spec.ts +++ b/packages/core/tool-executor-ptc/tests/ptc.spec.ts @@ -83,7 +83,7 @@ async function setup(options: SetupOptions = {}) { runtime = ctx.ptcRuntime as FakeRuntime } return { ctx, tools: { - get: (name: string, scope?: ScopeKey) => executor instanceof PtcExecutor ? executor['get'](name, scope) : ctx.tools.get(name, scope), + get: (name: string, scope?: ScopeKey) => executor instanceof PtcExecutor && name === RUN_CODE_NAME ? executor['requirePtcTransport']() : ctx.tools.get(name, scope), execute: (input: ToolExecutionInput) => executeCall(ctx, input), }, systemPrompt: ctx.systemPrompt, runtime: runtime! } } @@ -490,7 +490,7 @@ describe('mode-aware wire contribution', () => { it.each(['typescript', 'python'])('lists code before description for a %s runtime', async (language) => { const { ctx, systemPrompt } = await setup({ mode: 'ptc', runtime: { language } }) try { - const parameters = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)!.parameters + const parameters = new PtcExecutor(ctx)['requirePtcTransport']().parameters expect(Object.keys(parameters.properties as Record).slice(0, 2)) .toEqual(['code', 'description']) expect(parameters).toHaveProperty('required', ['code', 'description']) @@ -558,17 +558,17 @@ describe('mode-aware wire contribution', () => { it.each(['ruby', 'constructor'])('rejects transport schema reads for unsupported runtime language %s', async (language) => { const { ctx } = await setup({ mode: 'ptc', runtime: { language } }) - const definition = new PtcExecutor(ctx)['get'](RUN_CODE_NAME) + const definition = new PtcExecutor(ctx)['requirePtcTransport']() const message = `no SDK renderer registered for runtime language "${language}" (known: "typescript", "python")` - expect(() => definition?.description).toThrow(message) - expect(() => definition?.parameters).toThrow(message) + expect(() => definition.description).toThrow(message) + expect(() => definition.parameters).toThrow(message) }) it('rejects transport schema reads when no runtime is mounted', async () => { const { ctx } = await setup({ mode: 'ptc', runtime: false }) - const definition = new PtcExecutor(ctx)['get'](RUN_CODE_NAME) - expect(() => definition?.description).toThrow('requires a PTC runtime') - expect(() => definition?.parameters).toThrow('requires a PTC runtime') + const definition = new PtcExecutor(ctx)['requirePtcTransport']() + expect(() => definition.description).toThrow('requires a PTC runtime') + expect(() => definition.parameters).toThrow('requires a PTC runtime') }) it("rejects the assembly when toolOrder names a native tool that mode 'ptc' no longer contributes", async () => { @@ -584,7 +584,7 @@ describe('mode-aware wire contribution', () => { await ctx.plugin(FakeRuntime, {}) const fiber = await ctx.plugin(ToolRuntime) await ctx.plugin(PtcToolExecutor) - expect(new PtcExecutor(ctx)['get'](RUN_CODE_NAME)).toBeDefined() + expect(new PtcExecutor(ctx)['requirePtcTransport']()).toBeDefined() await fiber.dispose() const assembly = await ctx.systemPrompt.assemble() expect(assembly.tools).toEqual([]) @@ -718,14 +718,8 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { const gated = registerGated(ctx, 'safe_read', true) runtime.behavior = async (request) => { const tools = request.bindings[0]!.functions - const all = Promise.all([ - tools.safe_read!({ id: 'a' }), - tools.safe_read!({ id: 'b' }), - tools.safe_read!({ id: 'c' }), - ]) + const all = Promise.all(Array.from({ length: 3 }, (_, i) => tools.safe_read!({ id: String(i) }))) await expect.poll(() => gated.pending()).toBe(2) - // The third call waits for a slot. - expect(gated.pending()).toBe(2) gated.release() await expect.poll(() => gated.pending()).toBe(2) gated.releaseAll() @@ -733,7 +727,6 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => { return { logs: [], value: 'capped' } } const result = await runCode(ctx, 'program') - if (result.isError) console.error('CAP-FAIL:', (result.content[0] as { text: string }).text) expect(result.isError).toBe(false) expect(gated.peakLive()).toBe(2) }) @@ -1659,7 +1652,7 @@ describe('the run_code dispatch bridge', () => { it('presents the model-authored description as the execute-card title over the program input', async () => { const { ctx } = await setup({ mode: 'ptc' }) - const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)! + const tool = new PtcExecutor(ctx)['requirePtcTransport']() // The description labels the card (the bash description precedent); the // program itself remains the expanded raw input. expect(tool.presentCall?.({ code: 'return 1', description: 'Return the constant one' })).toEqual({ @@ -1687,7 +1680,7 @@ describe('the run_code dispatch bridge', () => { runtime.behavior = () => Promise.resolve(output) const result = await runCode(ctx, 'return 1') - const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)! + const tool = new PtcExecutor(ctx)['requirePtcTransport']() expect(result.content).toEqual([{ type: 'text', text }]) // Presenters keep the pending program title and render this durable content @@ -1706,7 +1699,7 @@ describe('the run_code dispatch bridge', () => { }) const result = await runCode(ctx, 'return 1') - const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)! + const tool = new PtcExecutor(ctx)['requirePtcTransport']() expect(result.content).toEqual([{ type: 'text', text: preview }]) expect('presentResult' in tool).toBe(false) @@ -1720,7 +1713,7 @@ describe('the run_code dispatch bridge', () => { }) const result = await runCode(ctx, 'return 1') - const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)! + const tool = new PtcExecutor(ctx)['requirePtcTransport']() expect(result.isError).toBe(true) expect(result.content).toEqual([{ @@ -2000,21 +1993,21 @@ describe('the run_code dispatch bridge', () => { expect(derived[0]?.role).toBe('user') }) - it('direct construction rejects a non-positive parallel sub-call cap at load', async () => { + it('direct construction rejects a non-positive parallel subcall limit', async () => { const ctx = new Context() - provideWorkingDirectoryFixture(ctx) - await ctx.plugin(SystemPrompt, {}) - expect(() => new PtcExecutor(ctx, { maxParallelSubCalls: 0 })) - .toThrow('maxParallelSubCalls must be a positive integer') + try { + await ctx.plugin(SystemPrompt) + expect(() => new PtcExecutor(ctx, { maxParallelSubCalls: 0 })).toThrow('maxParallelSubCalls') + } finally { await ctx.fiber.dispose() } }) - it('direct construction in PTC mode defaults the parallel sub-call cap', async () => { + it('direct construction exposes the PTC transport without configuration', async () => { const ctx = new Context() provideWorkingDirectoryFixture(ctx) await ctx.plugin(SystemPrompt, {}) await ctx.plugin(ToolRuntime) const registry = new PtcExecutor(ctx) - expect(registry['get'](RUN_CODE_NAME)).toBeDefined() + expect(registry['requirePtcTransport']()).toBeDefined() }) it('defaults to native mode under direct construction with no config', async () => { @@ -2542,3 +2535,47 @@ describe('PTC state ownership', () => { } finally { format.mockRestore(); instructions.mockRestore(); await ctx.fiber.dispose() } }) }) + +it.each([false, true])('settles nested calls after the PTC executor fiber unloads (Agent: %s)', async (owned) => { + const ctx = new Context() + provideWorkingDirectoryFixture(ctx) + await ctx.plugin(SystemPrompt) + await ctx.plugin(ToolRuntime) + await ctx.plugin(FakeRuntime) + const provider = ctx.plugin(PtcToolExecutor) + await provider + ctx.tools.register(defineTool({ + name: 'echo', description: 'Retained capability.', parameters: {}, + output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] }, + execute: async () => 'settled', + })) + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const runtime = ctx.ptcRuntime as FakeRuntime + runtime.behavior = async (request) => { + entered.resolve(undefined) + await release.promise + return { logs: [], value: await request.bindings[0]!.functions.echo!({}) } + } + const agent = { ctx, id: SessionId('unloading-ptc'), session: Session.create(SessionId('unloading-ptc')) } as Agent + const input = { + calls: [{ type: 'tool-call' as const, id: ToolCallId('unloading-program'), name: RUN_CODE_NAME, + arguments: JSON.stringify({ code: 'return await tools.echo({})', description: 'Invoke retained tool after unload' }) }], + signal: new AbortController().signal, + ...owned ? { agent } : {}, + } + const pending = ctx.tools.execute(input) + try { + await entered.promise + await provider.dispose() + await expect(ctx.tools.execute(input)).rejects.toThrow('selected tool executor is unavailable') + release.resolve(undefined) + expect((await pending).results[0]?.result).toMatchObject({ isError: false, value: { logs: [], result: 'settled' } }) + if (owned) expect(agent.session.snapshotEvents().filter(event => event.type === 'tool/ptc-dispatch')) + .toMatchObject([{ data: { name: 'echo', isError: false, content: [{ type: 'text', text: 'settled' }] } }]) + } finally { + release.resolve(undefined) + await Promise.allSettled([pending]) + await ctx.fiber.dispose() + } +}) diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index ff0a69e461..687c39c221 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -21,8 +21,8 @@ en: 9d2e6caa39c53d62 zh: 096a777f828c7565 /deepseek-ai-dsh-tools/use-this-package/select-an-executor: - en: f3efeb3d996cb9c5 - zh: 442cf4ca6d68fea0 + en: 1ddfa584f9280cb7 + zh: b0899fcb8163569e /deepseek-ai-dsh-tools/use-this-package/restrict-tools-per-agent: en: c6f98b2283cc2e47 zh: c2cdce5d13473f15 diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index bcd25bc40f..cee98fd3f7 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -72,7 +72,7 @@ Mount one executor in the deployment, preset, or Agent scope. The nearest scope - name: '@deepseek-ai/dsh-tool-executor-native' ``` -Agent-step concurrency uses the [agent-loop configuration](../agent-loop/README.md). See the [native](../tool-executor-native/README.md) or [PTC](../tool-executor-ptc/README.md) package for executor settings. Custom providers implement only `describe(definitions)` and `execute(batch)`; the shared pipeline is optional. The registry selects the current scoped executor separately for prompt descriptions and each `ctx.tools.execute()` batch. An accepted batch finishes through its selected provider even if that provider unloads; new batches reject until a replacement is registered. +Agent-step concurrency uses the [agent-loop configuration](../agent-loop/README.md). See the [native](../tool-executor-native/README.md) or [PTC](../tool-executor-ptc/README.md) package for executor settings. Custom providers implement only `describe(definitions)` and `execute(batch)`; the shared pipeline is optional. The registry selects the current scoped executor separately for prompt descriptions and each `ctx.tools.execute()` batch. An accepted batch settles through its selected provider even if that provider unloads; settlement may include cancellation errors according to the provider’s disposal policy. New batches reject until a replacement is registered. ### Restrict tools per agent diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index c7f07ab753..fe97c14c4a 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -72,7 +72,7 @@ ctx.tools.register(defineTool({ - name: '@deepseek-ai/dsh-tool-executor-native' ``` -Agent 步骤的并发上限使用 [agent-loop 配置](../agent-loop/README.zh.md)。执行器设置参见[原生](../tool-executor-native/README.zh.md)或 [PTC](../tool-executor-ptc/README.zh.md) 包。自定义提供者只需实现 `describe(definitions)` 和 `execute(batch)`,共享流水线为可选项。注册表分别为提示词描述和每次 `ctx.tools.execute()` 批次选择当前作用域的执行器。已接纳的批次由所选提供者完成,即使该提供者已卸载;注册替代提供者之前,新批次会被拒绝。 +Agent 步骤的并发上限使用 [agent-loop 配置](../agent-loop/README.zh.md)。执行器设置参见[原生](../tool-executor-native/README.zh.md)或 [PTC](../tool-executor-ptc/README.zh.md) 包。自定义提供者只需实现 `describe(definitions)` 和 `execute(batch)`,共享流水线为可选项。注册表分别为提示词描述和每次 `ctx.tools.execute()` 批次选择当前作用域的执行器。已接纳批次仍由所选提供者结算,即使该提供者已卸载;根据提供者的卸载策略,结果可能包含取消错误。注册替代提供者之前,新批次会被拒绝。 ### 按 agent 限制工具 diff --git a/packages/core/tools/src/contracts.ts b/packages/core/tools/src/contracts.ts index abf652fd9e..8ca74bcc65 100644 --- a/packages/core/tools/src/contracts.ts +++ b/packages/core/tools/src/contracts.ts @@ -142,7 +142,7 @@ export interface ToolExecutionInput { * mode sets this on SDK sub-dispatches so commit-style observers can wait for * the outer `run_code` outcome without receiving its live mutable execution. * The token also marks the call as a transport sub-dispatch rather than a - * model-direct call: under `mode: 'ptc'`, only calls WITH a parent may + * model-direct call: with the PTC executor selected, only calls with a parent may * execute a native tool name — a model-direct call (no parent) is denied as * `UNKNOWN_TOOL` before the policy pipeline. */ diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index ab301264c0..92f0ca612a 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -1,5 +1,6 @@ /** Tool definitions and scoped dispatch to plugin-owned executors. @module */ import { Context, Service } from '@deepseek-ai/cordis' +import z from '@deepseek-ai/schemastery' import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeChainOf } from '@deepseek-ai/dsh-scope' import type { ScopeKey, ScopeLayer } from '@deepseek-ai/dsh-scope' import { ToolExecutorUnavailableError } from './executor.ts' @@ -79,6 +80,10 @@ class GuardLayer { /** Concrete tool registry and scoped executor dispatcher. */ export class ToolsService extends Service { static inject = ['systemPrompt'] + static Config = z.object({ + mode: z.never().description('Removed. Mount a native or PTC tool executor instead.'), + maxParallelSubCalls: z.never().description('Moved to the PTC tool executor configuration.'), + }) private readonly layers = new ScopedLayers(scope => new ToolLayer(scope), () => { this.ctx.emit('tools/change') }) private readonly scoped = new WeakMap() @@ -231,6 +236,7 @@ export class ToolsService extends Service { } /** * Provide the executor for the calling scope; an unscoped contribution is the deployment default. + * Rendering any executor-owned section requires the viewing scope's selection to remain available. * @param executor - initialized provider owned by the contributing plugin. * @returns the effect disposer. Unloading leaves the selection unavailable until it is replaced. */ diff --git a/packages/core/tools/src/pipeline.ts b/packages/core/tools/src/pipeline.ts index d39e49feef..db5f0321d2 100644 --- a/packages/core/tools/src/pipeline.ts +++ b/packages/core/tools/src/pipeline.ts @@ -9,12 +9,13 @@ import type {} from '@deepseek-ai/dsh-user-approval' import { assertNever, deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values' import { TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH, ToolNotFoundError, ToolOutputError } from './contracts.ts' import { validateJsonSchemaValue } from './json-schema.ts' +import type { ToolsService } from './index.ts' import type { ToolDefinition, ToolExecutionInput, ToolExecutionMode, ToolExecution, ToolRunContext, ToolExecutionResult, ToolExecutionSuccess, ToolErrorInfo, ToolExecutionToken, PreToolDecision, PostToolDecision } from './contracts.ts' /** Executor-owned lookup and restrictions applied before extensible execution policy. */ export interface ToolExecutionSource { - /** Read the live definition visible to the calling scope. */ - get(name: string, scope?: ScopeKey): ToolDefinition | undefined + /** Override registry lookup for executor-owned capabilities; omitted uses the retained registry. */ + get?(name: string, scope?: ScopeKey): ToolDefinition | undefined /** Reject an otherwise visible invocation before policy; omitted permits it. */ reject?(name: string, nested: boolean): Error | undefined } @@ -56,15 +57,19 @@ interface FusedToolSignal { * for complete calls, including a single-call batch. */ export class ToolExecutionPipeline { - private readonly tools: Context['tools'] + private readonly tools: ToolsService - constructor(private readonly ctx: Context, private readonly source: ToolExecutionSource) { + constructor(private readonly ctx: Context, private readonly source: ToolExecutionSource = {}) { // Accepted calls retain their event subject while the service is withdrawn during shutdown. this.tools = ctx.tools } + private get(name: string, scope?: ScopeKey): ToolDefinition | undefined { + return this.source.get === undefined ? this.tools.get(name, scope) : this.source.get(name, scope) + } + private resolveExecution(name: string, scope: ScopeKey | undefined, nested: boolean): ToolDefinition | undefined { - const tool = this.source.get(name, scope) + const tool = this.get(name, scope) return tool !== undefined && this.source.reject?.(name, nested) === undefined ? tool : undefined } @@ -111,7 +116,7 @@ export class ToolExecutionPipeline { const parent = exec.parent const signal = exec.signal // Executor refusals precede extensible policy; unknown tools retain dispatch-stage handling. - const visible = this.source.get(name, agent) + const visible = this.get(name, agent) const rejection = visible === undefined ? undefined : this.source.reject?.(name, parent !== undefined) const rejected = rejection !== undefined const concludingExecutions = this.concludingExecutions diff --git a/packages/core/tools/tests/execution-mode.spec.ts b/packages/core/tools/tests/execution-mode.spec.ts index f8dbf21f9f..d506ad08a8 100644 --- a/packages/core/tools/tests/execution-mode.spec.ts +++ b/packages/core/tools/tests/execution-mode.spec.ts @@ -1,5 +1,5 @@ -import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' /** Covers fail-closed per-call classification and model-schema isolation. */ +import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' import { describe, expect, expectTypeOf, it } from 'vitest' @@ -33,7 +33,7 @@ describe('ToolExecutionPipeline.executionMode', () => { isConcurrencySafe: () => true, async execute() { return [] }, })) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('safe', {}))).toEqual({ kind: 'parallel' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('safe', {}))).toEqual({ kind: 'parallel' }) }) it('defaults to exclusive for a tool with no isConcurrencySafe declaration', async () => { @@ -44,12 +44,12 @@ describe('ToolExecutionPipeline.executionMode', () => { parameters: {}, async execute() { return [] }, })) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('plain', {}))).toEqual({ kind: 'exclusive' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('plain', {}))).toEqual({ kind: 'exclusive' }) }) it('returns exclusive for an unknown tool', async () => { const ctx = await setup() - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('nonexistent', {}))).toEqual({ kind: 'exclusive' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('nonexistent', {}))).toEqual({ kind: 'exclusive' }) }) it('returns exclusive when the classifier returns false for these args', async () => { @@ -61,8 +61,8 @@ describe('ToolExecutionPipeline.executionMode', () => { isConcurrencySafe: args => args.mode === 'read', async execute() { return [] }, })) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('rw', { mode: 'read' }))).toEqual({ kind: 'parallel' }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('rw', { mode: 'write' }))).toEqual({ kind: 'exclusive' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('rw', { mode: 'read' }))).toEqual({ kind: 'parallel' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('rw', { mode: 'write' }))).toEqual({ kind: 'exclusive' }) }) it('classifies invalid defineContentToolFixture arguments as exclusive without throwing', async () => { @@ -74,7 +74,7 @@ describe('ToolExecutionPipeline.executionMode', () => { isConcurrencySafe: () => true, async execute() { return [] }, })) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('needs-mode', {}))).toEqual({ kind: 'exclusive' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('needs-mode', {}))).toEqual({ kind: 'exclusive' }) }) it('treats a throwing raw classifier as exclusive', async () => { @@ -88,7 +88,7 @@ describe('ToolExecutionPipeline.executionMode', () => { async execute() { return null }, } ctx.tools.register(raw) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('thrower', {}))).toEqual({ kind: 'exclusive' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('thrower', {}))).toEqual({ kind: 'exclusive' }) }) it('treats a truthy non-boolean raw result as exclusive', async () => { @@ -102,7 +102,7 @@ describe('ToolExecutionPipeline.executionMode', () => { async execute() { return null }, } as unknown as ToolDefinition ctx.tools.register(raw) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('truthy', {}))).toEqual({ kind: 'exclusive' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('truthy', {}))).toEqual({ kind: 'exclusive' }) }) it('passes parsed arguments directly to a raw definition', async () => { @@ -116,7 +116,7 @@ describe('ToolExecutionPipeline.executionMode', () => { isConcurrencySafe(args) { seen = args; return true }, async execute() { return null }, }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('raw-safe', { anything: 1 }))).toEqual({ kind: 'parallel' }) + expect(new ToolExecutionPipeline(ctx).executionMode(exec('raw-safe', { anything: 1 }))).toEqual({ kind: 'parallel' }) expect(seen).toEqual({ anything: 1 }) }) diff --git a/packages/core/tools/tests/tools.spec.ts b/packages/core/tools/tests/tools.spec.ts index 7b12f80037..785927d780 100644 --- a/packages/core/tools/tests/tools.spec.ts +++ b/packages/core/tools/tests/tools.spec.ts @@ -52,6 +52,48 @@ const echoTool = defineTool({ }) describe('ToolRuntime', () => { + it.each([{ mode: 'ptc' }, { maxParallelSubCalls: 4 }])('rejects removed execution configuration %j during plugin mounting', async (config) => { + const ctx = new Context() + try { + await ctx.plugin(SystemPrompt) + // @ts-expect-error Removed fields can still arrive from untyped configuration files. + await expect(ctx.plugin(ToolRuntime, config).await()).rejects.toThrow('invalid config') + } finally { await ctx.fiber.dispose() } + }) + + it('settles accepted calls after the actual native executor fiber unloads', async () => { + const ctx = new Context() + await ctx.plugin(SystemPrompt) + await ctx.plugin(ToolRuntime) + const provider = ctx.plugin(NativeToolExecutor) + await provider + ctx.tools.register(echoTool) + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + ctx.on('tools/pre-execute', async (exec, next) => { + if (exec.callId === 'first') { entered.resolve(undefined); await release.promise } + return next() + }) + const input = { + calls: ['first', 'second'].map(id => ({ type: 'tool-call' as const, id: ToolCallId(id), name: 'echo', arguments: JSON.stringify({ text: id }) })), + signal: new AbortController().signal, + } + const pending = ctx.tools.execute(input) + try { + await entered.promise + await provider.dispose() + await expect(ctx.tools.execute(input)).rejects.toThrow('selected tool executor is unavailable') + release.resolve(undefined) + expect((await pending).results.map(({ result }) => result)).toMatchObject([ + { isError: false, value: 'first' }, { isError: false, value: 'second' }, + ]) + } finally { + release.resolve(undefined) + await Promise.allSettled([pending]) + await ctx.fiber.dispose() + } + }) + it('preserves deferred loading through registry and prompt schema projection', async () => { const ctx = await setup() try { diff --git a/packages/experimental/cua-macos/examples/persistent-ptc.cordis.yml b/packages/experimental/cua-macos/examples/persistent-ptc.cordis.yml index 1efa35f829..5ab779eb29 100644 --- a/packages/experimental/cua-macos/examples/persistent-ptc.cordis.yml +++ b/packages/experimental/cua-macos/examples/persistent-ptc.cordis.yml @@ -2,4 +2,6 @@ config: executionMode: persistent-in-memory - id: tool-executor-native - name: '@deepseek-ai/dsh-tool-executor-ptc' + disabled: true +- id: tool-executor-ptc + disabled: false diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index ce795f4305..08df76b3a1 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -3489,7 +3489,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { signature: 'setExecutor(executor: ToolExecutor): () => void', - description: 'Provide the executor for the calling scope; an unscoped contribution is the deployment default.', + description: 'Provide the executor for the calling scope; an unscoped contribution is the deployment default. Rendering any executor-owned section requires the viewing scope\'s selection to remain available.', parameters: [{ name: 'executor', description: 'initialized provider owned by the contributing plugin.' }], returns: 'the effect disposer. Unloading leaves the selection unavailable until it is replaced.', }, @@ -8394,7 +8394,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [ }, { name: 'ToolsService', - declaration: 'export class ToolsService extends Service {\n static inject;\n constructor(ctx: Context);\n register(definition: ToolDefinition): () => void;\n restrict(filter: ToolRestriction): () => void;\n get(name: string, scope?: ScopeKey): ToolDefinition | undefined;\n definitions(scope?: ScopeKey): readonly ToolDefinition[];\n schemas(scope?: ScopeKey): ToolSchema[];\n setExecutor(executor: ToolExecutor): () => void;\n async execute(input: ToolBatchInput): Promise;\n guard(guard: ToolGuard): () => void;\n checkGuards(exec: ToolExecution): string | undefined;\n}', + declaration: 'export class ToolsService extends Service {\n static inject;\n static Config;\n constructor(ctx: Context);\n register(definition: ToolDefinition): () => void;\n restrict(filter: ToolRestriction): () => void;\n get(name: string, scope?: ScopeKey): ToolDefinition | undefined;\n definitions(scope?: ScopeKey): readonly ToolDefinition[];\n schemas(scope?: ScopeKey): ToolSchema[];\n setExecutor(executor: ToolExecutor): () => void;\n async execute(input: ToolBatchInput): Promise;\n guard(guard: ToolGuard): () => void;\n checkGuards(exec: ToolExecution): string | undefined;\n}', }, { name: 'ToolUpdate', diff --git a/packages/fs/tool-fs/tests/read-image.spec.ts b/packages/fs/tool-fs/tests/read-image.spec.ts index cd66e7cf6d..97087edf7e 100644 --- a/packages/fs/tool-fs/tests/read-image.spec.ts +++ b/packages/fs/tool-fs/tests/read-image.spec.ts @@ -1,5 +1,3 @@ -import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' -import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit' /** * The `read_image` tool over the REAL local filesystem and attachment store: * extension routing, extension-less content sniffing (attachment object paths @@ -7,6 +5,8 @@ import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-to * commit + image-block rendering, attachment admission failures, and the * regression that `read` keeps its text-only contract. */ +import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' +import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit' import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc' @@ -840,7 +840,7 @@ describe('registration surface', () => { it('declares read_image parallel-safe and presents a read-family card', async () => { const ctx = await setup() - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('img-parallel'), name: 'read_image', arguments: { file_path: 'a.png' }, })).toEqual({ kind: 'parallel' }) expect(ctx.tools.get('read_image')?.presentCall?.({ file_path: 'shot.png' })).toEqual({ diff --git a/packages/fs/tool-fs/tests/tools.spec.ts b/packages/fs/tool-fs/tests/tools.spec.ts index a3d3384374..abc9ff4787 100644 --- a/packages/fs/tool-fs/tests/tools.spec.ts +++ b/packages/fs/tool-fs/tests/tools.spec.ts @@ -1,9 +1,9 @@ -import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' -import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit' /** * Consumer API tests over a fake provider and the real policy collaborator: schemas, * validation, formatting, typed errors, intent dispatch, and observation-driven authorization. */ +import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline' +import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit' import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc' @@ -198,11 +198,11 @@ describe('registration', () => { it('declares read parallel-safe while write/edit remain exclusive', async () => { const { ctx } = await setup() - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } })) + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } })) .toEqual({ kind: 'parallel' }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } })) + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } })) .toEqual({ kind: 'exclusive' }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } })) + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } })) .toEqual({ kind: 'exclusive' }) }) @@ -1149,7 +1149,7 @@ describe('scope-aware PTC guidance', () => { await ctx.plugin(SystemPrompt) await ctx.plugin(GuidancePtcRuntime) await ctx.plugin(ToolRuntime) - await ctx.plugin((mode) === 'ptc' ? PtcToolExecutor : NativeToolExecutor) + await ctx.plugin(mode === 'ptc' ? PtcToolExecutor : NativeToolExecutor) await ctx.plugin(FakeFs) await ctx.plugin(ToolFs) const { key, scope } = await guidanceScope(ctx) diff --git a/packages/goal/tool-goal/tests/tool-goal.spec.ts b/packages/goal/tool-goal/tests/tool-goal.spec.ts index 5ef28599a9..40bcef44b9 100644 --- a/packages/goal/tool-goal/tests/tool-goal.spec.ts +++ b/packages/goal/tool-goal/tests/tool-goal.spec.ts @@ -157,7 +157,7 @@ describe('goal tool registration and presentation', () => { const { ctx, fiber } = await harness({ blockedAfterConsecutiveRounds: 5 }) expect(['create_goal', 'get_goal', 'update_goal'].map(name => ctx.tools.get(name)?.name)) .toEqual(['create_goal', 'get_goal', 'update_goal']) - const pipeline = new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }) + const pipeline = new ToolExecutionPipeline(ctx) for (const name of ['create_goal', 'get_goal', 'update_goal']) { expect(pipeline.executionMode({ signal: testToolSignal, callId: ToolCallId(name), name, arguments: {} })) .toEqual({ kind: 'exclusive' }) diff --git a/packages/llm/llm-deepseek/package.json b/packages/llm/llm-deepseek/package.json index 37c0cae4d3..e23cf8f784 100644 --- a/packages/llm/llm-deepseek/package.json +++ b/packages/llm/llm-deepseek/package.json @@ -42,7 +42,6 @@ "dependencies": { "@deepseek-ai/cosmokit": "workspace:~", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-tool-executor-native": "workspace:*", "@deepseek-ai/dsh-util-values": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", "eventsource-parser": "^3.1.0" @@ -72,6 +71,7 @@ "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", + "@deepseek-ai/dsh-tool-executor-native": "workspace:*", "@deepseek-ai/dsh-tool-testkit": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", "@deepseek-ai/dsh-credentials-local": "workspace:*", diff --git a/packages/preset/agent-preset-registry/tests/tool-executors.spec.ts b/packages/preset/agent-preset-registry/tests/tool-executors.spec.ts index f2d2dedef2..2c0c0b9088 100644 --- a/packages/preset/agent-preset-registry/tests/tool-executors.spec.ts +++ b/packages/preset/agent-preset-registry/tests/tool-executors.spec.ts @@ -1,5 +1,5 @@ -import { executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit' /** Executor plugins follow the Loader-owned preset revision lifetime. */ +import { executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit' import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -91,7 +91,7 @@ class ProgramFixture extends PtcRuntime { } } -it('loads the separate executor packages through cordis.yml presets and logs PTC subcalls', async () => { +it.each([true, false])('loads cordis.yml executor presets with runtime availability %s', async (hasRuntime) => { const root = await mkdtemp(join(tmpdir(), 'dsh-executor-packages-')) const ctx = await harness() onTestFinished(async () => { @@ -99,7 +99,7 @@ it('loads the separate executor packages through cordis.yml presets and logs PTC }) await ctx.plugin(FsLocal) await ctx.plugin(WorkingDirectory, { defaultDirectory: root }) - await ctx.plugin(ProgramFixture) + if (hasRuntime) await ctx.plugin(ProgramFixture) ctx.tools.register(defineTool({ name: 'echo', description: 'Shared capability.', parameters: { value: { type: 'string', required: true } }, output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] }, @@ -127,6 +127,15 @@ it('loads the separate executor packages through cordis.yml presets and logs PTC const native = await agentOn(ctx, 'native-package', 'standard') const ptc = await agentOn(ctx, 'ptc-package', 'ptc') expect((await ctx.systemPrompt.assemble({ scope: native })).tools.map(tool => tool.name)).toEqual(['echo']) + if (!hasRuntime) { + await expect(ctx.systemPrompt.assemble({ scope: ptc })).rejects.toThrow('requires a PTC runtime') + const batch = await ctx.tools.execute({ + agent: ptc, calls: [{ type: 'tool-call', id: ToolCallId('missing-runtime'), name: 'echo', arguments: '{}' }], + signal: new AbortController().signal, + }) + expect(batch.results[0]?.result).toMatchObject({ isError: true, error: { info: { code: 'UNKNOWN_TOOL' } } }) + return + } expect((await ctx.systemPrompt.assemble({ scope: ptc })).tools.map(tool => tool.name)).toEqual(['run_code']) const signal = new AbortController().signal expect(await executeModelToolFixture(ctx, { diff --git a/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md b/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md index b4ee30714b..625fcf2a51 100644 --- a/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md +++ b/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md @@ -157,7 +157,7 @@ Every package below exports a Cordis plugin that a bundle patch can name in a Lo | `@deepseek-ai/dsh-agent-tool-presentation` | yes | Deprecated tool presentation selector; use dsh-tool-executor-native or dsh-tool-executor-ptc directly | | `@deepseek-ai/dsh-session` | no | Event-sourced session store for the DeepSeek Harness | | `@deepseek-ai/dsh-system-prompt` | yes | System prompt assembly registry for the DeepSeek Harness | -| `@deepseek-ai/dsh-tool-executor-native` | yes | Native tool execution plugin | +| `@deepseek-ai/dsh-tool-executor-native` | no | Native tool execution plugin | | `@deepseek-ai/dsh-tool-executor-ptc` | yes | PTC program execution, tool bindings, and generated SDK prompts | | `@deepseek-ai/dsh-tools` | no | Tool registry and execution pipeline for the DeepSeek Harness | diff --git a/packages/spill/spill-policy/package.json b/packages/spill/spill-policy/package.json index 9dea684ef9..ee98142aff 100644 --- a/packages/spill/spill-policy/package.json +++ b/packages/spill/spill-policy/package.json @@ -38,7 +38,6 @@ "@deepseek-ai/dsh-output-retention": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-spill": "workspace:*", - "@deepseek-ai/dsh-tool-executor-ptc": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-attachment": "workspace:*", diff --git a/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts b/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts index c685976692..df6bdcf362 100644 --- a/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts +++ b/packages/subagent/tool-subagent/tests/tool-subagent.spec.ts @@ -111,13 +111,13 @@ describe('dsh-tool-subagent', () => { it('classifies delegations as concurrency-safe', async () => { const ctx = await setup({ provider: 'mock' }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('subagent-first'), name: 'subagent', arguments: { description: 'do work', prompt: 'Reply OK' }, })).toEqual({ kind: 'parallel' }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('subagent-second'), name: 'subagent', @@ -696,7 +696,7 @@ describe('dsh-tool-subagent local activation', () => { it('classifies local activations concurrency-safe', async () => { const { ctx } = await continuableSetup() - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('subagent-continuable'), name: 'subagent', diff --git a/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts b/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts index 7c172737ab..d76c97d70f 100644 --- a/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts +++ b/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts @@ -43,6 +43,17 @@ describe('dsh-agent-loop-testkit', () => { } }) + it('mounts the PTC executor with its runtime', async () => { + const ctx = new Context() + try { + await ctx.plugin(FixtureRuntime) + await mountAgentLoopTestDependencies(ctx, { tools: { mode: 'ptc' } }) + expect((await ctx.systemPrompt.assemble()).tools.map(tool => tool.name)).toEqual(['run_code']) + } finally { + await ctx.fiber.dispose() + } + }) + it('rejects mutations through an unsupported Agent stub Inbox', () => { const inbox = unsupportedInbox() diff --git a/packages/test-support/tool-testkit/src/index.ts b/packages/test-support/tool-testkit/src/index.ts index ded44c27bf..d9d7435658 100644 --- a/packages/test-support/tool-testkit/src/index.ts +++ b/packages/test-support/tool-testkit/src/index.ts @@ -60,7 +60,7 @@ export function defineContentToolFixture( * @internal */ export async function executeToolFixture(ctx: Context, input: ToolExecutionInput): Promise { - const pipeline = new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }) + const pipeline = new ToolExecutionPipeline(ctx) const prepared = await pipeline.prepare(input) const outcome = prepared.kind === 'dispatch' ? await pipeline.dispatch(prepared.exec) : prepared return pipeline.complete(prepared.exec, outcome) diff --git a/packages/web/tool-web/tests/tool-web.spec.ts b/packages/web/tool-web/tests/tool-web.spec.ts index 03ed9993bf..a036e10b51 100644 --- a/packages/web/tool-web/tests/tool-web.spec.ts +++ b/packages/web/tool-web/tests/tool-web.spec.ts @@ -457,9 +457,9 @@ describe('tool-web registration', () => { const names = ctx.tools.schemas().map(s => s.name) expect(names).toContain('web_search') expect(names).toContain('web_fetch') - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('search-safe'), name: 'web_search', arguments: { queries: ['q'] } })) + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('search-safe'), name: 'web_search', arguments: { queries: ['q'] } })) .toEqual({ kind: 'parallel' }) - expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } })) + expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } })) .toEqual({ kind: 'parallel' }) await fiber.dispose() expect(ctx.tools.schemas().map(s => s.name)).not.toContain('web_search') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b2b9da3e6..e130663bd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7035,9 +7035,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-tool-executor-native': - specifier: workspace:* - version: link:../../core/tool-executor-native '@deepseek-ai/dsh-util-values': specifier: workspace:* version: link:../../util/values @@ -7102,6 +7099,9 @@ importers: '@deepseek-ai/dsh-system-prompt': specifier: workspace:* version: link:../../core/system-prompt + '@deepseek-ai/dsh-tool-executor-native': + specifier: workspace:* + version: link:../../core/tool-executor-native '@deepseek-ai/dsh-tool-fs': specifier: workspace:* version: link:../../fs/tool-fs @@ -7411,13 +7411,6 @@ importers: version: link:../scope packages/core/tool-executor-native: - dependencies: - '@deepseek-ai/cosmokit': - specifier: link:../../../vendor/cosmokit - version: link:../../../vendor/cosmokit - '@deepseek-ai/schemastery': - specifier: link:../../../vendor/schemastery - version: link:../../../vendor/schemastery devDependencies: '@deepseek-ai/cordis': specifier: workspace:~ @@ -7434,9 +7427,6 @@ importers: packages/core/tool-executor-ptc: dependencies: - '@deepseek-ai/cosmokit': - specifier: link:../../../vendor/cosmokit - version: link:../../../vendor/cosmokit '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand @@ -11468,9 +11458,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-tool-executor-native': - specifier: workspace:* - version: link:../../core/tool-executor-native '@deepseek-ai/dsh-util-values': specifier: workspace:* version: link:../../util/values @@ -11562,6 +11549,9 @@ importers: '@deepseek-ai/dsh-timeout': specifier: workspace:* version: link:../../util/timeout + '@deepseek-ai/dsh-tool-executor-native': + specifier: workspace:* + version: link:../../core/tool-executor-native '@deepseek-ai/dsh-tool-testkit': specifier: workspace:* version: link:../../test-support/tool-testkit diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index b7043b8538..f09c20a812 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -728,7 +728,6 @@ export const LINK_MAP: Readonly> = { ToolExecutionResult: 'tools.md', ToolExecutionToken: 'tools.md', ToolGuard: 'tools.md', - ToolPresentationMode: 'tools.md', ToolRuntime: 'tools.md', ToolsService: 'tools.md', ToolExecutor: 'tools.md', diff --git a/snapshots/session/cordis-inspect-jsdoc/session.v4.jsonl b/snapshots/session/cordis-inspect-jsdoc/session.v4.jsonl index 6fb94c5c26..cbf96e8019 100644 --- a/snapshots/session/cordis-inspect-jsdoc/session.v4.jsonl +++ b/snapshots/session/cordis-inspect-jsdoc/session.v4.jsonl @@ -14,7 +14,7 @@ {"type":"session/title","data":{"title":"Inspect the exact tools service","messageSeqs":[8],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1788778145981,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788778145981,"index":0,"dt":[],"id":"inspect-tools-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"]},{"type":"chunk","time":1788778145981,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}}},{"type":"chunk","time":1788778145981,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1788778145981,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"inspect-tools-api"},"toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Concrete tool registry and scoped executor dispatcher.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"Restrict global tools for the calling agent scope. Empty filters, unknown names, scope-local names, and reserved transport names fail. Restrictions intersect; scoped registrations remain visible.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"definitions(scope?: ScopeKey): readonly ToolDefinition[]\",\n \"description\": \"Read the visible capability definitions for descriptions and program bindings.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"viewing Agent or preset; omitted selects the global view.\"\n }\n ],\n \"returns\": \"definitions after scoped shadowing and restrictions.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"setExecutor(executor: ToolExecutor): () => void\",\n \"description\": \"Provide the executor for the calling scope; an unscoped contribution is the deployment default.\",\n \"parameters\": [\n {\n \"name\": \"executor\",\n \"description\": \"initialized provider owned by the contributing plugin.\"\n }\n ],\n \"returns\": \"the effect disposer. Unloading leaves the selection unavailable until it is replaced.\"\n },\n {\n \"signature\": \"async execute(input: ToolBatchInput): Promise\",\n \"description\": \"Delegate a raw call batch to its Agent's provider or the deployment default. Select at dispatch; accepted batches settle through that provider even if it unloads.\",\n \"parameters\": [\n {\n \"name\": \"input\",\n \"description\": \"original calls, optional owning Agent, and optional logging step.\"\n }\n ],\n \"returns\": \"the selected executor's settled batch; unavailable selections reject.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic denial in the calling scope.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"evaluated after extensible pre-execute policy by the shared pipeline.\"\n }\n ],\n \"returns\": \"the effect disposer that removes this guard.\"\n },\n {\n \"signature\": \"checkGuards(exec: ToolExecution): string | undefined\",\n \"description\": \"Check global and inherited guards after pre-execute policy and approval resolve.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"invocation whose guards are checked before dispatch.\"\n }\n ],\n \"returns\": \"the first denial reason, or undefined when no guard denies.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssembleContext\",\n \"declaration\": \"export interface AssembleContext {\\n scope?: ScopeKey;\\n signal?: AbortSignal;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"PromptSection\",\n \"declaration\": \"export interface PromptSection {\\n readonly name: string;\\n readonly order: number;\\n readonly text: string | ((context: AssembleContext) => string);\\n readonly interpolate?: boolean;\\n readonly complete?: boolean;\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolBatchInput\",\n \"declaration\": \"export interface ToolBatchInput {\\n readonly calls: readonly ToolCallBlock[];\\n readonly signal: AbortSignal;\\n readonly maxParallelToolCalls?: number;\\n readonly agent?: Agent;\\n readonly log?: {\\n readonly turn: number;\\n readonly step: number;\\n };\\n}\"\n },\n {\n \"name\": \"ToolBatchResult\",\n \"declaration\": \"export interface ToolBatchResult {\\n readonly results: readonly ToolCallResult[];\\n readonly additionalContexts: readonly UserMessage[];\\n readonly concluded: boolean;\\n}\"\n },\n {\n \"name\": \"ToolCallBlock\",\n \"declaration\": \"export interface ToolCallBlock {\\n type: 'tool-call';\\n id: ToolCallId;\\n name: string;\\n arguments: string;\\n}\"\n },\n {\n \"name\": \"ToolCallId\",\n \"declaration\": \"export type ToolCallId = Branded<'ToolCallId'>;\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n projectContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n reason?: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly schema?: ToolSchema;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolExecutor\",\n \"declaration\": \"export interface ToolExecutor {\\n readonly sections?: readonly ToolPromptSection[];\\n describe(tools: readonly ToolDefinition[]): ToolModelInput;\\n execute(input: ToolBatchInput): Promise;\\n}\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolModelInput\",\n \"declaration\": \"export type ToolModelInput = Pick;\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPromptSection\",\n \"declaration\": \"export interface ToolPromptSection extends Pick {\\n readonly text: string | ((tools: readonly ToolDefinition[]) => string);\\n}\"\n },\n {\n \"name\": \"ToolProviderResult\",\n \"declaration\": \"export interface ToolProviderResult {\\n readonly schemas: readonly ToolSchema[];\\n readonly knownNames?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n deferLoading?: true;\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends MessageBase {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"inspect-tools-api"},"toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Concrete tool registry and scoped executor dispatcher.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"Restrict global tools for the calling agent scope. Empty filters, unknown names, scope-local names, and reserved transport names fail. Restrictions intersect; scoped registrations remain visible.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"definitions(scope?: ScopeKey): readonly ToolDefinition[]\",\n \"description\": \"Read the visible capability definitions for descriptions and program bindings.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"viewing Agent or preset; omitted selects the global view.\"\n }\n ],\n \"returns\": \"definitions after scoped shadowing and restrictions.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"setExecutor(executor: ToolExecutor): () => void\",\n \"description\": \"Provide the executor for the calling scope; an unscoped contribution is the deployment default. Rendering any executor-owned section requires the viewing scope's selection to remain available.\",\n \"parameters\": [\n {\n \"name\": \"executor\",\n \"description\": \"initialized provider owned by the contributing plugin.\"\n }\n ],\n \"returns\": \"the effect disposer. Unloading leaves the selection unavailable until it is replaced.\"\n },\n {\n \"signature\": \"async execute(input: ToolBatchInput): Promise\",\n \"description\": \"Delegate a raw call batch to its Agent's provider or the deployment default. Select at dispatch; accepted batches settle through that provider even if it unloads.\",\n \"parameters\": [\n {\n \"name\": \"input\",\n \"description\": \"original calls, optional owning Agent, and optional logging step.\"\n }\n ],\n \"returns\": \"the selected executor's settled batch; unavailable selections reject.\"\n },\n {\n \"signature\": \"guard(guard: ToolGuard): () => void\",\n \"description\": \"Register a monotonic denial in the calling scope.\",\n \"parameters\": [\n {\n \"name\": \"guard\",\n \"description\": \"evaluated after extensible pre-execute policy by the shared pipeline.\"\n }\n ],\n \"returns\": \"the effect disposer that removes this guard.\"\n },\n {\n \"signature\": \"checkGuards(exec: ToolExecution): string | undefined\",\n \"description\": \"Check global and inherited guards after pre-execute policy and approval resolve.\",\n \"parameters\": [\n {\n \"name\": \"exec\",\n \"description\": \"invocation whose guards are checked before dispatch.\"\n }\n ],\n \"returns\": \"the first denial reason, or undefined when no guard denies.\"\n }\n ]\n },\n \"referencedTypes\": [\n {\n \"name\": \"Agent\",\n \"declaration\": \"export interface Agent {\\n readonly id: SessionId;\\n}\"\n },\n {\n \"name\": \"AssembleContext\",\n \"declaration\": \"export interface AssembleContext {\\n scope?: ScopeKey;\\n signal?: AbortSignal;\\n}\"\n },\n {\n \"name\": \"Branded\",\n \"declaration\": \"export type Branded = string & {\\n readonly [BRAND]: B;\\n};\"\n },\n {\n \"name\": \"DiffCallView\",\n \"declaration\": \"export interface DiffCallView {\\n card: 'diff';\\n title: string;\\n diffs: FileDiff[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"DiffResultView\",\n \"declaration\": \"export interface DiffResultView {\\n card: 'diff';\\n title?: string;\\n diffs: FileDiff[];\\n}\"\n },\n {\n \"name\": \"FileDiff\",\n \"declaration\": \"export interface FileDiff {\\n path: string;\\n oldText: string | null;\\n newText: string;\\n}\"\n },\n {\n \"name\": \"FileLocation\",\n \"declaration\": \"export interface FileLocation {\\n path: string;\\n line?: number;\\n}\"\n },\n {\n \"name\": \"GenericCallView\",\n \"declaration\": \"export interface GenericCallView {\\n card: 'generic';\\n title: string;\\n kind?: ToolCallKind;\\n rawInput?: unknown;\\n content?: ContentBlock[];\\n locations?: FileLocation[];\\n}\"\n },\n {\n \"name\": \"GenericResultView\",\n \"declaration\": \"export interface GenericResultView {\\n card: 'generic';\\n title?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"JsonSchemaNode\",\n \"declaration\": \"export interface JsonSchemaNode {\\n type?: JsonSchemaType;\\n oneOf?: JsonSchemaNode[];\\n properties?: Record;\\n required?: string[];\\n additionalProperties?: boolean;\\n items?: JsonSchemaNode;\\n enum?: JsonSchemaScalar[];\\n const?: JsonSchemaScalar;\\n description?: string;\\n title?: string;\\n default?: JsonValue;\\n examples?: JsonValue;\\n}\"\n },\n {\n \"name\": \"JsonSchemaScalar\",\n \"declaration\": \"export type JsonSchemaScalar = string | number | boolean | null;\"\n },\n {\n \"name\": \"JsonSchemaType\",\n \"declaration\": \"export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';\"\n },\n {\n \"name\": \"JsonValue\",\n \"declaration\": \"export type JsonValue = null | boolean | number | string | JsonValue[] | {\\n [key: string]: JsonValue;\\n};\"\n },\n {\n \"name\": \"PromptSection\",\n \"declaration\": \"export interface PromptSection {\\n readonly name: string;\\n readonly order: number;\\n readonly text: string | ((context: AssembleContext) => string);\\n readonly interpolate?: boolean;\\n readonly complete?: boolean;\\n}\"\n },\n {\n \"name\": \"ReadFileLine\",\n \"declaration\": \"export interface ReadFileLine {\\n number: number;\\n text: string;\\n}\"\n },\n {\n \"name\": \"ReadResultView\",\n \"declaration\": \"export interface ReadResultView {\\n card: 'read';\\n title?: string;\\n path: string;\\n offset: number;\\n lines: ReadFileLine[];\\n totalLines: number;\\n lang?: string;\\n content?: ContentBlock[];\\n}\"\n },\n {\n \"name\": \"ScopeKey\",\n \"declaration\": \"export type ScopeKey = object;\"\n },\n {\n \"name\": \"SearchFileMatches\",\n \"declaration\": \"export interface SearchFileMatches {\\n path: string;\\n matches: SearchLineMatch[];\\n}\"\n },\n {\n \"name\": \"SearchLineMatch\",\n \"declaration\": \"export interface SearchLineMatch {\\n lineNumber: number;\\n line: string;\\n}\"\n },\n {\n \"name\": \"SearchMatchesResultView\",\n \"declaration\": \"export interface SearchMatchesResultView {\\n card: 'search';\\n shape: 'matches';\\n title?: string;\\n files: SearchFileMatches[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchPathsResultView\",\n \"declaration\": \"export interface SearchPathsResultView {\\n card: 'search';\\n shape: 'paths';\\n title?: string;\\n paths: string[];\\n truncated: boolean;\\n total: number;\\n}\"\n },\n {\n \"name\": \"SearchResultView\",\n \"declaration\": \"export type SearchResultView = SearchMatchesResultView | SearchPathsResultView;\"\n },\n {\n \"name\": \"SessionId\",\n \"declaration\": \"export type SessionId = Branded<'SessionId'>;\"\n },\n {\n \"name\": \"TerminalCallView\",\n \"declaration\": \"export interface TerminalCallView {\\n card: 'terminal';\\n title: string;\\n description?: string;\\n cwd?: string;\\n}\"\n },\n {\n \"name\": \"TerminalResultView\",\n \"declaration\": \"export interface TerminalResultView {\\n card: 'terminal';\\n title?: string;\\n output?: string;\\n exitCode?: number;\\n signal?: string;\\n}\"\n },\n {\n \"name\": \"ToolBatchInput\",\n \"declaration\": \"export interface ToolBatchInput {\\n readonly calls: readonly ToolCallBlock[];\\n readonly signal: AbortSignal;\\n readonly maxParallelToolCalls?: number;\\n readonly agent?: Agent;\\n readonly log?: {\\n readonly turn: number;\\n readonly step: number;\\n };\\n}\"\n },\n {\n \"name\": \"ToolBatchResult\",\n \"declaration\": \"export interface ToolBatchResult {\\n readonly results: readonly ToolCallResult[];\\n readonly additionalContexts: readonly UserMessage[];\\n readonly concluded: boolean;\\n}\"\n },\n {\n \"name\": \"ToolCallBlock\",\n \"declaration\": \"export interface ToolCallBlock {\\n type: 'tool-call';\\n id: ToolCallId;\\n name: string;\\n arguments: string;\\n}\"\n },\n {\n \"name\": \"ToolCallId\",\n \"declaration\": \"export type ToolCallId = Branded<'ToolCallId'>;\"\n },\n {\n \"name\": \"ToolCallKind\",\n \"declaration\": \"export type ToolCallKind = 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other';\"\n },\n {\n \"name\": \"ToolCallView\",\n \"declaration\": \"export type ToolCallView = GenericCallView | TerminalCallView | DiffCallView;\"\n },\n {\n \"name\": \"ToolDefinition\",\n \"declaration\": \"export interface ToolDefinition extends ToolSchema {\\n readonly output: ToolOutputDefinition;\\n execute(args: unknown, exec: ToolRunContext): Promise;\\n projectContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n finalizeContent?(exec: Readonly, result: Readonly): ContentBlock[] | undefined;\\n timeoutMs?: number;\\n isConcurrencySafe?(args: unknown): boolean;\\n presentCall?(args: unknown): ToolCallView | undefined;\\n presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined;\\n}\"\n },\n {\n \"name\": \"ToolErrorInfo\",\n \"declaration\": \"export interface ToolErrorInfo {\\n name: string;\\n code: string;\\n reason?: string;\\n}\"\n },\n {\n \"name\": \"ToolExecution\",\n \"declaration\": \"export interface ToolExecution extends ToolExecutionInput {\\n readonly rootCallId: ToolCallId;\\n readonly token: ToolExecutionToken;\\n}\"\n },\n {\n \"name\": \"ToolExecutionFailure\",\n \"declaration\": \"export interface ToolExecutionFailure {\\n readonly isError: true;\\n readonly error: ToolFailure;\\n readonly value?: never;\\n readonly content: ContentBlock[];\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: never;\\n}\"\n },\n {\n \"name\": \"ToolExecutionInput\",\n \"declaration\": \"export interface ToolExecutionInput {\\n readonly callId: ToolCallId;\\n readonly rootCallId?: ToolCallId;\\n readonly name: string;\\n readonly schema?: ToolSchema;\\n readonly arguments: unknown;\\n readonly agent?: Agent;\\n readonly parent?: ToolExecutionToken;\\n readonly signal: AbortSignal;\\n}\"\n },\n {\n \"name\": \"ToolExecutionResult\",\n \"declaration\": \"export type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure;\"\n },\n {\n \"name\": \"ToolExecutionSuccess\",\n \"declaration\": \"export interface ToolExecutionSuccess {\\n readonly isError: false;\\n readonly value: JsonValue;\\n readonly content: ContentBlock[];\\n readonly error?: never;\\n readonly meta?: JsonValue;\\n readonly additionalContexts?: UserMessage[];\\n readonly concludesTurn?: true;\\n}\"\n },\n {\n \"name\": \"ToolExecutionToken\",\n \"declaration\": \"export type ToolExecutionToken = symbol & {\\n readonly [toolExecutionTokenBrand]: true;\\n};\"\n },\n {\n \"name\": \"ToolExecutor\",\n \"declaration\": \"export interface ToolExecutor {\\n readonly sections?: readonly ToolPromptSection[];\\n describe(tools: readonly ToolDefinition[]): ToolModelInput;\\n execute(input: ToolBatchInput): Promise;\\n}\"\n },\n {\n \"name\": \"ToolFailure\",\n \"declaration\": \"export interface ToolFailure {\\n message: string;\\n info?: ToolErrorInfo;\\n}\"\n },\n {\n \"name\": \"ToolGuard\",\n \"declaration\": \"export type ToolGuard = (execution: Readonly) => string | undefined;\"\n },\n {\n \"name\": \"ToolModelInput\",\n \"declaration\": \"export type ToolModelInput = Pick;\"\n },\n {\n \"name\": \"ToolOutputDefinition\",\n \"declaration\": \"export interface ToolOutputDefinition {\\n readonly schema: JsonSchemaNode;\\n render(args: unknown, value: JsonValue): ContentBlock[];\\n presentationMeta?(args: unknown, value: JsonValue): JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolPromptSection\",\n \"declaration\": \"export interface ToolPromptSection extends Pick {\\n readonly text: string | ((tools: readonly ToolDefinition[]) => string);\\n}\"\n },\n {\n \"name\": \"ToolProviderResult\",\n \"declaration\": \"export interface ToolProviderResult {\\n readonly schemas: readonly ToolSchema[];\\n readonly knownNames?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolRestriction\",\n \"declaration\": \"export interface ToolRestriction {\\n readonly allow?: readonly string[];\\n readonly deny?: readonly string[];\\n}\"\n },\n {\n \"name\": \"ToolResult\",\n \"declaration\": \"export interface ToolResult {\\n content: ContentBlock[];\\n isError: boolean;\\n meta?: JsonValue;\\n}\"\n },\n {\n \"name\": \"ToolResultView\",\n \"declaration\": \"export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView | ReadResultView | WebResultView;\"\n },\n {\n \"name\": \"ToolRunContext\",\n \"declaration\": \"export interface ToolRunContext extends ToolExecution {\\n deferContext(context: UserMessage): void;\\n concludeTurn(): void;\\n}\"\n },\n {\n \"name\": \"ToolSchema\",\n \"declaration\": \"export interface ToolSchema {\\n deferLoading?: true;\\n name: string;\\n description: string;\\n parameters: Record;\\n}\"\n },\n {\n \"name\": \"UserMessage\",\n \"declaration\": \"export interface UserMessage extends MessageBase {\\n readonly role: 'user';\\n}\"\n },\n {\n \"name\": \"WebFetchResultView\",\n \"declaration\": \"export interface WebFetchResultView {\\n card: 'web';\\n kind: 'fetch';\\n title?: string;\\n url: string;\\n statusCode: number;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebResultView\",\n \"declaration\": \"export type WebResultView = WebSearchResultView | WebFetchResultView;\"\n },\n {\n \"name\": \"WebSearchResultView\",\n \"declaration\": \"export interface WebSearchResultView {\\n card: 'web';\\n kind: 'search';\\n title?: string;\\n sources: WebSource[];\\n answer?: string;\\n truncated: boolean;\\n}\"\n },\n {\n \"name\": \"WebSource\",\n \"declaration\": \"export interface WebSource {\\n url: string;\\n title?: string;\\n snippet?: string;\\n publishedAt?: string;\\n}\"\n }\n ]\n }\n}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1788778146017,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788778146017,"index":0,"dt":[],"id":"inspect-subprocess-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"]},{"type":"chunk","time":1788778146017,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}}},{"type":"chunk","time":1788778146017,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1788778146017,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}