Merge branch 'codex/tool-json-schema-dsl' into codex/canonical-tool-output

# Conflicts:
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.md
#	docs/config-catalog.md
#	docs/cookbook/adding-a-tool.i18n.yaml
#	docs/cookbook/adding-a-tool.md
#	docs/cookbook/adding-a-tool.zh.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/tools.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	packages/bash/tool-bash/src/index.ts
#	packages/core/agent-loop/src/tool-calls.ts
#	packages/core/agent-loop/tests/cancel.spec.ts
#	packages/core/agent-loop/tests/contract-regressions.spec.ts
#	packages/core/agent-loop/tests/tool-calls.spec.ts
#	packages/core/tools/README.md
#	packages/core/tools/src/index.ts
#	packages/core/tools/tests/code-mode.spec.ts
#	packages/core/tools/tests/tools.spec.ts
#	packages/fs/tool-fs-search/tests/integration.spec.ts
#	packages/fs/tool-fs-search/tests/tools.spec.ts
#	packages/fs/tool-fs/tests/integration.spec.ts
#	packages/mcp/mcp-client/src/tools.ts
#	packages/timeout/timeout-policy/tests/timeout-policy.spec.ts
#	packages/web/tool-web/tests/integration.spec.ts
#	packages/web/tool-web/tests/tool-web.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-21 23:39:03 +08:00
194 changed files with 3387 additions and 1222 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ export class FsSandboxSurface {
agent: exec.agent,
callId: exec.callId,
toolName,
...exec.signal ? { signal: exec.signal } : {},
signal: exec.signal,
},
)
}
+1 -1
View File
@@ -28,6 +28,6 @@ export function sessionResolveOptions(exec: ToolExecution): { cwd?: string; sign
const cwd = sessionCwd(exec)
return {
...cwd !== undefined ? { cwd } : {},
...exec.signal !== undefined ? { signal: exec.signal } : {},
signal: exec.signal,
}
}
+11 -8
View File
@@ -12,11 +12,13 @@ import { join } from 'node:path'
import { Context } from 'cordis'
import { CallId } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import ToolRegistry, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
const testToolSignal = new AbortController().signal
let dir: string
let ctx: Context
let fiber: Awaited<ReturnType<Context['plugin']>>
@@ -26,6 +28,7 @@ const session = { header: {} }
let callCounter = 0
function call(name: string, args: unknown) {
return ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`call-${++callCounter}`),
name,
arguments: args,
@@ -299,6 +302,7 @@ describe('per-session cwd', () => {
const callIn = (sessionObj: object, name: string, args: unknown) =>
ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`call-${++callCounter}`),
name,
arguments: args,
@@ -344,26 +348,25 @@ describe('signal, concurrency, and the fs/observed contract', () => {
const callSig = (signal: AbortSignal, name: string, args: unknown) =>
ctx.tools.execute({ callId: CallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never, signal })
const callOwned = (name: string, args: unknown) =>
ctx.tools.execute({ callId: CallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never })
ctx.tools.execute({ signal: testToolSignal, callId: CallId(`c-${++callCounter}`), name, arguments: args, agent: { session } as never })
it('a pre-aborted signal makes read/write/edit return isError FS_ABORTED', async () => {
it('a pre-aborted registry call skips read/write/edit with ABORTED_BEFORE_DISPATCH', async () => {
await writeFile(join(dir, 'a.txt'), 'hello')
const read = await callSig(AbortSignal.abort(), 'read', { file_path: 'a.txt' })
expect(read.isError).toBe(true)
expect(read.error).toMatchObject({ info: { code: 'FS_ABORTED' } })
expect(read.error).toMatchObject({ info: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } })
const write = await callSig(AbortSignal.abort(), 'write', { file_path: 'new.txt', content: 'x' })
expect(write.isError).toBe(true)
expect(write.error).toMatchObject({ info: { code: 'FS_ABORTED' } })
expect(write.error).toMatchObject({ info: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } })
await expect(readFile(join(dir, 'new.txt'), 'utf8')).rejects.toMatchObject({ code: 'ENOENT' })
// Read first (un-aborted, SAME session owner) so the edit clears the
// observation gate; then the aborted edit fails on the signal, not on
// FS_NOT_OBSERVED.
// observation gate; then the registry skips the aborted edit before its body.
expect((await callOwned('read', { file_path: 'a.txt' })).isError).toBe(false)
const edit = await callSig(AbortSignal.abort(), 'edit', { file_path: 'a.txt', old_string: 'hello', new_string: 'bye' })
expect(edit.isError).toBe(true)
expect(edit.error).toMatchObject({ info: { code: 'FS_ABORTED' } })
expect(edit.error).toMatchObject({ info: { name: 'AbortError', code: TOOL_ABORTED_BEFORE_DISPATCH } })
expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('hello') // unchanged
})
+6 -3
View File
@@ -27,6 +27,8 @@ import type { FileReadOutcome } from '../src/read-render.ts'
import ApprovalService from '@deepseek-ai/dsh-user-approval'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
const testToolSignal = new AbortController().signal
/** An in-memory fake provider; a test can arm a rejection on any primitive. */
class FakeFs extends FileSystem {
files = new Map<string, string>()
@@ -93,6 +95,7 @@ async function setup() {
let callCounter = 0
function call(ctx: Context, name: string, args: unknown, agent?: object) {
return ctx.tools.execute({
signal: testToolSignal,
callId: CallId(`call-${++callCounter}`),
name,
arguments: args,
@@ -112,11 +115,11 @@ describe('registration', () => {
it('declares read parallel-safe while write/edit remain exclusive', async () => {
const { ctx } = await setup()
expect(ctx.tools.executionMode({ callId: CallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } }))
expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } }))
.toEqual({ kind: 'parallel' })
expect(ctx.tools.executionMode({ callId: CallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } }))
expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } }))
.toEqual({ kind: 'exclusive' })
expect(ctx.tools.executionMode({ callId: CallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } }))
expect(ctx.tools.executionMode({ signal: testToolSignal, callId: CallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } }))
.toEqual({ kind: 'exclusive' })
})