Merge origin/master into codex/truncated-design

This commit is contained in:
Dudu-0223
2026-07-17 18:21:54 +08:00
1057 changed files with 42961 additions and 18526 deletions

View File

@@ -82,10 +82,11 @@ function registerEcho(ctx: Context, name = 'echo'): unknown[] {
}
/** A structural fake of the owning agent: captures session appends. */
function fakeAgent(): { agent: Agent; events: { type: string; data: unknown }[] } {
function fakeAgent(options: { cwd?: string } = { cwd: '/workspace' }): { agent: Agent; events: { type: string; data: unknown }[] } {
const events: { type: string; data: unknown }[] = []
const agent = {
session: {
header: options.cwd === undefined ? {} : { cwd: options.cwd },
append: (type: string, data: unknown) => { events.push({ type, data }) },
},
} as unknown as Agent
@@ -328,7 +329,7 @@ describe('the run_code dispatch bridge', () => {
const tools = request.bindings[0]!.functions
const first = await tools.echo!({ value: 'one' })
const second = await tools.echo!({ value: 'two' })
return { logs: [{ source: 'console', level: 'log', text: `saw ${String(first)}` }], value: second }
return { logs: [`saw ${String(first)}`], value: second }
}
const result = await runCode(ctx, 'const …: string = …', { agent })
expect(result.isError).toBe(false)
@@ -339,7 +340,7 @@ describe('the run_code dispatch bridge', () => {
{ parentCallId: 'call-1', subCallId: 'call-1:code:1', name: 'echo', arguments: { value: 'one' }, isError: false, resultSummary: 'echo:one' },
{ parentCallId: 'call-1', subCallId: 'call-1:code:2', name: 'echo', arguments: { value: 'two' }, isError: false, resultSummary: 'echo:two' },
])
expect(result.meta).toEqual({ logs: [{ source: 'console', level: 'log', text: 'saw echo:one' }], dispatches: 2 })
expect(result.meta).toEqual({ logs: ['saw echo:one'] })
})
it('exposes only an opaque parent token to nested result observers', async () => {
@@ -350,10 +351,8 @@ describe('the run_code dispatch bridge', () => {
return { logs: [], value: 'done' }
}
// Model a timeout-style outer wrapper: it temporarily installs a signal,
// delegates, then restores the exact prior shape. A nested result observer
// is observe-only and must not receive the live outer execution object;
// freezing the correlation value it sees therefore cannot break restore.
// Freeze the nested observer's parent correlation. If that were the live
// outer execution object, the timeout-style wrapper could not restore it.
ctx.on('tools/execute', async (exec, next) => {
if (exec.name !== RUN_CODE_NAME) return next()
const previous = exec.signal
@@ -479,33 +478,77 @@ describe('the run_code dispatch bridge', () => {
expect(dispatch.arguments).toEqual({ value: 'x', when: '1970-01-01T00:00:00.000Z' })
})
it('suppresses sub-call additionalContext (deliberately; pinned)', async () => {
it('defers sub-call additionalContexts onto the outer run_code result', async () => {
const { ctx, runtime } = await setup({ mode: 'code' })
registerEcho(ctx)
ctx.on('tools/post-execute', (exec, _result, next): Promise<PostToolDecision> => {
if (exec.name === 'echo') {
return Promise.resolve({
kind: 'accept' as const,
additionalContext: { content: [{ type: 'text' as const, text: 'context for the next request' }], source: { kind: 'plugin' as const, plugin: 'test' } },
additionalContexts: [{
content: [{ type: 'text' as const, text: `context for ${exec.callId}` }],
source: { kind: 'plugin' as const, plugin: 'test' },
envelope: 'raw' as const,
meta: { callId: exec.callId },
}],
})
}
return next()
})
runtime.behavior = async (request) => {
await request.bindings[0]!.functions.echo!({ value: 'x' })
await request.bindings[0]!.functions.echo!({ value: 'y' })
return { logs: [], value: 'done' }
}
const result = await runCode(ctx, 'program')
expect(result.isError).toBe(false)
// The sub-call's context has no safe outlet mid-run; the parent result
// must not carry it either.
expect(result.additionalContext).toBeUndefined()
expect(result.additionalContexts).toEqual([
{
content: [{ type: 'text', text: 'context for call-1:code:1' }],
source: { kind: 'plugin', plugin: 'test' },
envelope: 'raw',
meta: { callId: 'call-1:code:1' },
},
{
content: [{ type: 'text', text: 'context for call-1:code:2' }],
source: { kind: 'plugin', plugin: 'test' },
envelope: 'raw',
meta: { callId: 'call-1:code:2' },
},
])
})
it('keeps sub-call contexts when run_code fails after the nested dispatch', async () => {
const { ctx, runtime } = await setup({ mode: 'both' })
registerEcho(ctx)
ctx.on('tools/post-execute', (exec, _result, next): Promise<PostToolDecision> => {
if (exec.name !== 'echo') return next()
return Promise.resolve({
kind: 'accept',
additionalContexts: [{
content: [{ type: 'text', text: 'nested context' }],
source: { kind: 'plugin', plugin: 'test' },
}],
})
})
runtime.behavior = async (request) => {
await request.bindings[0]!.functions.echo!({ value: 'x' })
return { logs: [], error: { kind: 'exception', message: 'program failed later' } }
}
const result = await runCode(ctx, 'program')
expect(result.isError).toBe(true)
expect(result.additionalContexts).toEqual([{
content: [{ type: 'text', text: 'nested context' }],
source: { kind: 'plugin', plugin: 'test' },
}])
})
it('converts a failed run into a structured isError result carrying kind, message, and captured logs', async () => {
const { ctx, runtime } = await setup({ mode: 'code' })
runtime.behavior = () => Promise.resolve({
logs: [{ source: 'console', level: 'log', text: 'got this far' }],
logs: ['got this far'],
error: { kind: 'timeout', message: 'compute budget exhausted (300ms busy)' },
})
const result = await runCode(ctx, 'program')
@@ -577,11 +620,8 @@ describe('the run_code dispatch bridge', () => {
},
}))
runtime.behavior = async (request) => {
// Start a sub-dispatch, keep its rejection held, and fail the run once
// the tool is genuinely in flight — a seam error AFTER work has begun.
// The bridge's settlement still owes quiescence: without the finally,
// run_code would return now and the slow tool would finish (and log)
// afterwards.
// Start a sub-dispatch, keep its rejection held, and fail the run once the tool is
// genuinely in flight — a seam error after work has begun.
request.bindings[0]!.functions.slow!({ id: 'orphan' }).catch(() => 'held')
await inFlight
throw new Error('backend exploded')
@@ -632,7 +672,7 @@ describe('the run_code dispatch bridge', () => {
const view = tool.presentResult?.({ code: 'return 1' }, {
content: [{ type: 'text', text: 'model-facing' }],
isError: false,
meta: { logs: [{ source: 'console', level: 'log', text: 'printed' }], dispatches: 1 },
meta: { logs: ['printed'] },
})
// The result omits the title — an update replaces only provided fields,
// so the pending card's program title persists through completion.
@@ -641,9 +681,10 @@ describe('the run_code dispatch bridge', () => {
content: [{ type: 'text', text: 'printed' }],
})
// No captured output → no content either; everything pending persists.
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [], dispatches: 2 } }))
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [] } }))
.toEqual({ card: 'generic' })
// Replay with an unrecognizable meta falls back to the generic rendering.
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [{ text: 'legacy' }], dispatches: 1 } })).toBeUndefined()
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { other: true } })).toBeUndefined()
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false })).toBeUndefined()
})
@@ -675,6 +716,52 @@ describe('the run_code dispatch bridge', () => {
expect(dispatch.resultSummary.endsWith('…')).toBe(true)
})
it('normalizes the session workspace root before bounding durable result summaries', async () => {
const { ctx, runtime } = await setup({ mode: 'code' })
ctx.tools.register(defineTool({
name: 'workspace_path',
description: 'Return a path beneath the session workspace.',
parameters: {},
execute(_args, exec) {
const cwd = exec.agent?.session.header.cwd ?? ''
return Promise.resolve([{ type: 'text' as const, text: `<path>${cwd}/nested/task.txt</path>\n${'x'.repeat(240)}` }])
},
}))
runtime.behavior = async request => ({
logs: [],
value: await request.bindings[0]!.functions.workspace_path!({}),
})
const short = fakeAgent({ cwd: '/tmp/workspace' })
const long = fakeAgent({ cwd: `/tmp/${'long-segment/'.repeat(30)}workspace` })
const shortResult = await runCode(ctx, 'program', { agent: short.agent })
const longResult = await runCode(ctx, 'program', { agent: long.agent })
const shortDispatch = short.events[0]!.data as SessionEventMap['tool/code-dispatch']
const longDispatch = long.events[0]!.data as SessionEventMap['tool/code-dispatch']
expect(shortResult.content).not.toEqual(longResult.content)
expect(shortDispatch.resultSummary).toBe(longDispatch.resultSummary)
expect(shortDispatch.resultSummary).toHaveLength(201)
expect(shortDispatch.resultSummary).toMatch(/^<path>\.\/nested\/task\.txt<\/path>\n.+…$/)
})
it('leaves result summaries unchanged when a session cwd is absent or is the filesystem root', async () => {
const { ctx, runtime } = await setup({ mode: 'code' })
registerEcho(ctx)
runtime.behavior = async request => ({
logs: [],
value: await request.bindings[0]!.functions.echo!({ value: '/workspace/value' }),
})
const absent = fakeAgent({})
const root = fakeAgent({ cwd: '/' })
await runCode(ctx, 'program', { agent: absent.agent })
await runCode(ctx, 'program', { agent: root.agent })
expect((absent.events[0]!.data as SessionEventMap['tool/code-dispatch']).resultSummary).toBe('echo:/workspace/value')
expect((root.events[0]!.data as SessionEventMap['tool/code-dispatch']).resultSummary).toBe('echo:/workspace/value')
})
it('rejects undefined, JSON-throwing, and JSON-unrepresentable binding arguments BEFORE dispatch', async () => {
const { ctx, runtime } = await setup({ mode: 'code' })
const calls = registerEcho(ctx)

View File

@@ -1,17 +1,5 @@
/**
* Guarantee tests for the tool-schema catalog generator
* (`scripts/gen-tool-catalog.ts`).
*
* The generated catalog is frozen by a regenerate-and-diff freshness gate, so
* the freshness half is exercised by `pnpm run verify-tool-catalog` in CI. What
* a freshness diff CANNOT prove is (a) that BOOTING the tool plugins yields the
* shipped schema — the whole reason this generator boots instead of parsing
* source (a runtime-spread enum resolves to its literal members) — and (b) that
* the completeness guard REJECTS a tool package missing from the boot manifest,
* the property that replaces the AST pass's "nothing silently omitted". These
* tests drive the exported `collectToolCatalog` / `assertManifestComplete` /
* `render` directly, mirroring the negative-path style of the cordis-catalog
* generator tests.
* Guarantee tests for the tool-schema catalog generator (`scripts/gen-tool-catalog.ts`).
*/
import { describe, expect, it } from 'vitest'
@@ -35,7 +23,7 @@ describe('gen-tool-catalog collectToolCatalog', () => {
it('boots every shipped tool package and harvests its model-facing schemas', async () => {
const catalog = await collectToolCatalog()
const names = catalog.flatMap(entry => entry.schemas.map(s => s.name)).sort()
expect(names).toEqual(['ask_user_question', 'bash', 'bash_kill', 'bash_output', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'edit', 'glob', 'grep', 'read', 'run_code', 'skill', 'subagent', 'todo_write', 'web_fetch', 'web_search', 'workflow', 'write'])
expect(names).toEqual(['ask_user_question', 'bash', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'edit', 'glob', 'grep', 'read', 'run_code', 'skill', 'subagent', 'task_kill', 'task_list', 'task_output', 'todo_write', 'web_fetch', 'web_search', 'workflow', 'write'])
// Every tool carries a JSON-Schema `parameters` object (what the model sees).
for (const entry of catalog) {
for (const schema of entry.schemas) {
@@ -62,11 +50,8 @@ describe('gen-tool-catalog collectToolCatalog', () => {
})
it('records the shipped `subagent_fork` alias in a note (config-driven tool name)', async () => {
// `tool-subagent`'s registered name is the load-time `toolName` config, so
// the shipped agents surface this one package as both `subagent` and
// `subagent_fork`. Booting yields only the default name; the note is how a
// reader learns the fork alias the model also sees. Without it the catalog
// would silently under-report the shipped tool surface.
// `tool-subagent`'s registered name is the load-time `toolName` config, so the shipped
// agents surface this one package as both `subagent` and `subagent_fork`.
const catalog = await collectToolCatalog()
const subagent = catalog.find(entry => entry.pkg === '@deepseek-ai/dsh-tool-subagent')
expect(subagent?.schemas.map(s => s.name)).toEqual(['subagent'])

View File

@@ -548,7 +548,6 @@ describe('scoped execution dispatch', () => {
expect(reads).toBe(1)
expect(result).toEqual({
callId: CallId('unstable-arguments'),
content: [{ type: 'text', text: 'ran:t' }],
isError: false,
})
@@ -564,10 +563,9 @@ describe('scoped execution dispatch', () => {
ctx.on('internal/dispatch', (mode, name) => {
if (name === 'tools/result') dispatchModes.push(mode)
})
ctx.on('tools/execute', async (exec, next) => {
ctx.on('tools/execute', async (_exec, next) => {
await next()
return {
callId: exec.callId,
content: [{ type: 'text', text: 'outer failure' }],
isError: true,
}

View File

@@ -80,7 +80,7 @@ describe('ToolRegistry', () => {
const ctx = await setup()
ctx.tools.register(echoTool)
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result).toEqual({ callId: CallId('c1'), content: [{ type: 'text', text: 'hi' }], isError: false })
expect(result).toEqual({ content: [{ type: 'text', text: 'hi' }], isError: false })
})
it('threads a tool-attached meta (object return form) onto the result', async () => {
@@ -94,7 +94,6 @@ describe('ToolRegistry', () => {
})
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'meta-tool', arguments: {} })
expect(result).toEqual({
callId: CallId('c1'),
content: [{ type: 'text', text: 'ok' }],
isError: false,
meta: { diffs: [{ path: 'a', oldText: null, newText: 'x' }] },
@@ -111,7 +110,7 @@ describe('ToolRegistry', () => {
},
})
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'no-meta-tool', arguments: {} })
expect(result).toEqual({ callId: CallId('c1'), content: [{ type: 'text', text: 'ok' }], isError: false })
expect(result).toEqual({ content: [{ type: 'text', text: 'ok' }], isError: false })
expect('meta' in result).toBe(false)
})
@@ -178,13 +177,12 @@ describe('ToolRegistry', () => {
})
})
it('ToolNotFoundError carries the tool name and a stable code', async () => {
it('ToolNotFoundError carries a stable message and code', async () => {
const { HarnessError } = await import('@deepseek-ai/dsh-llm')
const err = new ToolNotFoundError('ghost')
expect(err).toBeInstanceOf(HarnessError)
expect(err.name).toBe('ToolNotFoundError')
expect(err.code).toBe('UNKNOWN_TOOL')
expect(err.toolName).toBe('ghost')
expect(err.message).toBe('unknown tool "ghost"')
})
@@ -350,7 +348,7 @@ describe('ToolRegistry', () => {
expect(result.content[0]).toMatchObject({ text: 'output rejected: try again' })
})
it('a block decision can ALSO attach additionalContext', async () => {
it('a block decision can ALSO attach additionalContexts', async () => {
const ctx = await setup()
ctx.tools.register(echoTool)
@@ -358,24 +356,95 @@ describe('ToolRegistry', () => {
({
kind: 'block',
feedback: [{ type: 'text', text: 'rejected' }],
additionalContext: { content: [{ type: 'text', text: 'why it was rejected' }], source: { kind: 'plugin', plugin: 'test' } },
additionalContexts: [{ content: [{ type: 'text', text: 'why it was rejected' }], source: { kind: 'plugin', plugin: 'test' } }],
}))
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result.isError).toBe(true)
expect(result.content[0]).toMatchObject({ text: 'rejected' })
expect(result.additionalContext).toMatchObject({ content: [{ text: 'why it was rejected' }], source: { kind: 'plugin', plugin: 'test' } })
expect(result.additionalContexts).toMatchObject([{ content: [{ text: 'why it was rejected' }], source: { kind: 'plugin', plugin: 'test' } }])
})
it('a post-execute additionalContext rides on the result for the loop to buffer', async () => {
it('post-execute additionalContexts ride on the result for the loop to buffer', async () => {
const ctx = await setup()
ctx.tools.register(echoTool)
ctx.on('tools/post-execute', async (_exec, _result, _next): Promise<PostToolDecision> =>
({ kind: 'accept', additionalContext: { content: [{ type: 'text', text: 'fyi' }], source: { kind: 'plugin', plugin: 'test' } } }))
({ kind: 'accept', additionalContexts: [{ content: [{ type: 'text', text: 'fyi' }], source: { kind: 'plugin', plugin: 'test' } }] }))
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result.additionalContext).toMatchObject({ content: [{ text: 'fyi' }], source: { kind: 'plugin', plugin: 'test' } })
expect(result.additionalContexts).toMatchObject([{ content: [{ text: 'fyi' }], source: { kind: 'plugin', plugin: 'test' } }])
})
it('preserves tool-deferred, execute-wrapper, and post-execute contexts in order', async () => {
const ctx = await setup()
ctx.tools.register(defineTool({
name: 'composite',
description: 'composite',
parameters: {},
async execute(_args, exec) {
exec.deferContext({ content: [{ type: 'text', text: 'nested-1' }], source: { kind: 'plugin', plugin: 'nested-1' }, meta: { n: 1 } })
exec.deferContext({ content: [{ type: 'text', text: 'nested-2' }], source: { kind: 'plugin', plugin: 'nested-2' }, envelope: 'raw' })
return [{ type: 'text', text: 'done' }]
},
}))
ctx.on('tools/execute', async (_exec, next) => {
const result = await next()
return {
...result,
additionalContexts: [
...result.additionalContexts ?? [],
{ content: [{ type: 'text', text: 'wrapper' }], source: { kind: 'plugin', plugin: 'wrapper' } },
],
}
})
ctx.on('tools/post-execute', async (_exec, _result, next): Promise<PostToolDecision> => {
const downstream = await next()
return {
...downstream,
additionalContexts: [
{ content: [{ type: 'text', text: 'post' }], source: { kind: 'plugin', plugin: 'post' } },
...downstream.additionalContexts ?? [],
],
}
})
const result = await ctx.tools.execute({ callId: CallId('composite'), name: 'composite', arguments: {} })
expect(result.additionalContexts?.map(context => context.source)).toEqual([
{ kind: 'plugin', plugin: 'nested-1' },
{ kind: 'plugin', plugin: 'nested-2' },
{ kind: 'plugin', plugin: 'wrapper' },
{ kind: 'plugin', plugin: 'post' },
])
expect(result.additionalContexts?.[0]?.meta).toEqual({ n: 1 })
expect(result.additionalContexts?.[1]?.envelope).toBe('raw')
})
it('keeps deferred contexts when a composite tool throws, but drops them when the outer call is blocked', async () => {
const ctx = await setup()
ctx.tools.register(defineTool({
name: 'failing-composite',
description: 'failing composite',
parameters: {},
async execute(_args, exec) {
exec.deferContext({ content: [{ type: 'text', text: 'nested' }], source: { kind: 'plugin', plugin: 'nested' } })
throw new Error('outer failure')
},
}))
const failed = await ctx.tools.execute({ callId: CallId('failed'), name: 'failing-composite', arguments: {} })
expect(failed.isError).toBe(true)
expect(failed.additionalContexts?.map(context => context.source)).toEqual([{ kind: 'plugin', plugin: 'nested' }])
ctx.on('tools/post-execute', async (): Promise<PostToolDecision> => ({
kind: 'block',
feedback: [{ type: 'text', text: 'blocked' }],
additionalContexts: [{ content: [{ type: 'text', text: 'block-only' }], source: { kind: 'plugin', plugin: 'blocker' } }],
}))
const blocked = await ctx.tools.execute({ callId: CallId('blocked'), name: 'failing-composite', arguments: {} })
expect(blocked.isError).toBe(true)
expect(blocked.additionalContexts?.map(context => context.source)).toEqual([{ kind: 'plugin', plugin: 'blocker' }])
})
it('composes pre + post waterfalls around dispatch (sandbox-wrap pattern)', async () => {
@@ -425,7 +494,7 @@ describe('ToolRegistry', () => {
ctx.on('tools/post-execute', async (_exec, _result, next) => { order.push('post'); return next() })
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'traced', arguments: { text: 'hi' } })
expect(result).toEqual({ callId: CallId('c1'), content: [{ type: 'text', text: 'hi' }], isError: false })
expect(result).toEqual({ content: [{ type: 'text', text: 'hi' }], isError: false })
// The around seam wraps dispatch; pre gates before it, post runs over its result.
expect(order).toEqual(['pre', 'execute:before', 'dispatch', 'execute:after', 'post'])
})
@@ -526,48 +595,33 @@ describe('ToolRegistry', () => {
async execute() { dispatched = true; return [] },
})
ctx.on('tools/execute', async (exec: ToolExecution, _next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult> =>
({ callId: exec.callId, content: [{ type: 'text', text: 'short-circuited' }], isError: false }))
ctx.on('tools/execute', async (_exec: ToolExecution, _next: () => Promise<ToolExecutionResult>): Promise<ToolExecutionResult> =>
({ content: [{ type: 'text', text: 'short-circuited' }], isError: false }))
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'never-runs', arguments: {} })
expect(dispatched).toBe(false) // returning without next() skips core dispatch
expect(result.content[0]).toMatchObject({ text: 'short-circuited' })
})
it('preserves additionalContext supplied by an around-dispatch result', async () => {
it('preserves additionalContexts supplied by an around-dispatch result', async () => {
const ctx = await setup()
ctx.tools.register(echoTool)
ctx.on('tools/execute', async exec => ({
callId: exec.callId,
ctx.on('tools/execute', async () => ({
content: [{ type: 'text', text: 'short-circuited with context' }],
isError: false,
additionalContext: {
additionalContexts: [{
content: [{ type: 'text', text: 'from around dispatch' }],
source: { kind: 'plugin', plugin: 'test' },
},
}],
}))
const result = await ctx.tools.execute({
callId: CallId('around-context'), name: 'echo', arguments: {},
})
expect(result.additionalContext).toEqual({
expect(result.additionalContexts).toEqual([{
content: [{ type: 'text', text: 'from around dispatch' }],
source: { kind: 'plugin', plugin: 'test' },
})
})
it('normalizes a tools/execute result with the wrong call id', async () => {
const ctx = await setup()
ctx.tools.register(echoTool)
ctx.on('tools/execute', async () => ({ callId: CallId('other'), content: [], isError: false }))
const result = await ctx.tools.execute({
callId: CallId('malformed-shape'), name: 'echo', arguments: {},
})
expect(result.isError).toBe(true)
expect(result.content[0]).toMatchObject({
text: 'Error: tools/execute returned callId "other" for authoritative call "malformed-shape"',
})
}])
})
it('returns an isError result when a tools/execute listener throws', async () => {
@@ -577,7 +631,6 @@ describe('ToolRegistry', () => {
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result).toEqual({
callId: CallId('c1'),
content: [{ type: 'text', text: 'Error: wrapper broke' }],
isError: true,
})
@@ -593,7 +646,6 @@ describe('ToolRegistry', () => {
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result).toEqual({
callId: CallId('c1'),
content: [{ type: 'text', text: 'Error: permission hook broke' }],
isError: true,
})
@@ -609,7 +661,6 @@ describe('ToolRegistry', () => {
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result).toEqual({
callId: CallId('c1'),
content: [{ type: 'text', text: 'Error: post hook broke' }],
isError: true,
})
@@ -625,7 +676,6 @@ describe('ToolRegistry', () => {
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
expect(result).toMatchObject({
callId: CallId('c1'),
isError: true,
error: { name: 'HarnessError', code: 'DENIED' },
})
@@ -703,16 +753,9 @@ describe('ToolRegistry', () => {
})
it('register() returns the EXACT effect disposer: a composite yield nests the teardown in order', async () => {
// The registry-disposer convention (set by agents.register): the returned
// function IS the cordis effect disposer, so a composite (generator)
// effect that yields it has the unregistration run at that yield's LIFO
// position on owner unload. A wrapper would leave the inner effect
// disposing as a CONCURRENT SIBLING of the composite; the async probe
// below (disposed first, LIFO) yields the event loop exactly like the
// agent factory's stop-and-drain link, and a sibling unregistration fires
// in that window — the probe would observe the tool already gone. Pins
// the convention for the whole register-method family (system-prompt
// registrars, registerProvider, setFactory share the same return).
// Registry methods return the exact Cordis effect disposer so a composite yield places
// unregistration at its LIFO position. A wrapper would create a concurrent sibling; this async
// probe yields during earlier teardown and would then observe the tool already removed.
const ctx = await setup()
const order: string[] = []
const fiber = await ctx.plugin(Object.assign((inner: Context) => {
@@ -999,20 +1042,18 @@ describe('schema DSL edge cases', () => {
port: { type: 'number' },
},
})
// no 'required' key in the nested object because nothing is required
const config = jsonSchema.properties['config'] as Record<string, unknown>
expect('required' in config).toBe(false)
})
})
describe('schema DSL regressions (Codex review round 2)', () => {
describe('schema DSL optional and nested contracts', () => {
it('InferArgs makes non-required keys genuinely optional (omittable)', () => {
type Args = InferArgs<{
path: { type: 'string'; required: true }
limit: { type: 'number' }
}>
expectTypeOf<Args>().toEqualTypeOf<{ path: string; limit?: number }>()
// omitting the optional key is assignable — the actual regression
const omitted: Args = { path: '/tmp' }
expect(omitted.limit).toBeUndefined()
})
@@ -1263,7 +1304,7 @@ describe('defineTool validation (the runtime-validation RFC, part 1)', () => {
},
}))
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'reader', arguments: { path: '/x' } })
expect(result).toEqual({ callId: CallId('c1'), content: [{ type: 'text', text: 'read /x' }], isError: false })
expect(result).toEqual({ content: [{ type: 'text', text: 'read /x' }], isError: false })
})
it('ToolArgsError carries a stable code and the violation list', () => {