blob: 92567ca1ee51ba9574f822030a653a9c3c36d1ae [file]
import { createTestToolRuntime } from './execution-boundary-test-helpers.js';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, test } from 'node:test';
import type {
LlmConnection,
SessionHeader,
Task,
TaskAgentOutcome,
TaskLedgerStore,
TaskOwner,
} from '@maka/core';
import type { SessionEvent } from '@maka/core';
import { zodSchema } from 'ai';
import { buildBuiltinTools } from '../builtin-tools.js';
import {
AGENT_CONTEXT_ISOLATED,
AGENT_INVOCATION_FOREGROUND,
AGENT_WORKSPACE_SAME_WORKSPACE,
AGENT_WORKSPACE_WORKTREE,
AGENT_WRITE_BACK_PATCH,
AGENT_WRITE_BACK_SUMMARY,
IMPLEMENTATION_AGENT_ID,
IMPLEMENTATION_AGENT_DEFINITION,
IMPLEMENTATION_AGENT_PROFILE,
LOCAL_READ_AGENT_ID,
LOCAL_READ_AGENT_DEFINITION,
LOCAL_READ_AGENT_PROFILE,
WEB_RESEARCH_AGENT_ID,
WEB_RESEARCH_AGENT_DEFINITION,
WEB_RESEARCH_AGENT_PROFILE,
assertAgentDefinitionRunnable,
evaluateAgentDefinitionAvailability,
evaluateAgentDefinitionToolAccess,
listBuiltinAgentDefinitions,
requireBuiltinAgentDefinitionByProfile,
} from '../agent-catalog.js';
import {
AGENT_LIST_TOOL_NAME,
AGENT_OUTPUT_TOOL_NAME,
AGENT_SPAWN_TOOL_NAME,
CHILD_AGENT_TOOL_NAMES,
buildChildAgentTools,
buildParentAgentTools,
buildSubagentListTool,
buildSubagentOutputTool,
buildSubagentSpawnTool,
} from '../subagent-tools.js';
import { ToolRuntime, type MakaTool } from '../tool-runtime.js';
import { expect } from '../test-helpers.js';
describe('subagent tools', () => {
test('parent-facing agent tools declare permission hints and names', () => {
const spawnTool = buildSubagentSpawnTool();
expect(spawnTool.categoryHint).toBe('subagent');
expect(buildParentAgentTools().map((tool) => tool.name)).toEqual([
AGENT_SPAWN_TOOL_NAME,
AGENT_LIST_TOOL_NAME,
AGENT_OUTPUT_TOOL_NAME,
]);
});
test('parent tools advertise only definitions runnable in their composition', () => {
const tools = buildParentAgentTools({ definitions: [LOCAL_READ_AGENT_DEFINITION] });
const spawn = tools.find((tool) => tool.name === AGENT_SPAWN_TOOL_NAME);
expect(spawn).toBeDefined();
const spawnSchema = spawn!.parameters as {
safeParse(input: unknown): { success: boolean };
};
expect(
spawnSchema.safeParse({ profile: LOCAL_READ_AGENT_PROFILE, task: 'Inspect the repo.' })
.success,
).toBe(true);
expect(
spawnSchema.safeParse({ profile: WEB_RESEARCH_AGENT_PROFILE, task: 'Search the web.' })
.success,
).toBe(false);
expect(
spawnSchema.safeParse({ profile: IMPLEMENTATION_AGENT_PROFILE, task: 'Change a file.' })
.success,
).toBe(false);
expect(buildParentAgentTools({ definitions: [] }).map((tool) => tool.name)).toEqual([
AGENT_LIST_TOOL_NAME,
AGENT_OUTPUT_TOOL_NAME,
]);
});
test('agent_spawn advertises task_id only when task binding is available', async () => {
const advertisedProperties = async (tool: MakaTool) => {
const schema = (await zodSchema(tool.parameters as never).jsonSchema) as {
properties?: Record<string, unknown>;
};
return schema.properties ?? {};
};
expect(Object.keys(await advertisedProperties(buildSubagentSpawnTool()))).toEqual([
'profile',
'subagent_id',
'task',
'write_back',
'isolation',
]);
expect(
Object.keys(
await advertisedProperties(
buildSubagentSpawnTool({ taskLedger: taskLedgerStub(undefined, []) }),
),
),
).toEqual(['profile', 'subagent_id', 'task', 'write_back', 'isolation', 'task_id']);
});
test('agent_spawn strips task_id when task binding is unavailable', () => {
const schema = buildSubagentSpawnTool().parameters as {
safeParse(input: unknown): { success: boolean; data?: Record<string, unknown> };
};
const parsed = schema.safeParse({
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the repo.',
task_id: 'T1',
});
expect(parsed.success).toBe(true);
expect(parsed.data).toEqual({
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the repo.',
});
const presetParsed = schema.safeParse({
profile: 'not-a-real-profile',
subagent_id: 'fast-reader',
task: 'Inspect the repo.',
task_id: { malformed: true },
ignored: true,
});
expect(presetParsed.success).toBe(true);
expect(presetParsed.data).toEqual({
subagent_id: 'fast-reader',
task: 'Inspect the repo.',
});
});
test('agent_spawn names both recovery routes when no child selector is provided', () => {
const schema = buildSubagentSpawnTool({
definitions: [LOCAL_READ_AGENT_DEFINITION, WEB_RESEARCH_AGENT_DEFINITION],
}).parameters as {
safeParse(input: unknown): {
success: boolean;
error?: { issues: Array<{ message: string }> };
};
};
const parsed = schema.safeParse({ task: 'Inspect the repo.' });
expect(parsed.success).toBe(false);
expect(parsed.error?.issues.map((issue) => issue.message)).toContain(
'No child selector was provided. Call agent_list and pass a returned subagent_id to agent_spawn, or pass one legacy profile: local_read, web_research.',
);
});
test('built-in catalog exposes local-read without shell, web, nested, or write tools', () => {
expect(LOCAL_READ_AGENT_DEFINITION.id).toBe(LOCAL_READ_AGENT_ID);
expect(LOCAL_READ_AGENT_DEFINITION.profile).toBe(LOCAL_READ_AGENT_PROFILE);
expect(LOCAL_READ_AGENT_DEFINITION.contract).toEqual({
capability: 'local_read',
invocation: AGENT_INVOCATION_FOREGROUND,
context: AGENT_CONTEXT_ISOLATED,
workspace: AGENT_WORKSPACE_SAME_WORKSPACE,
defaultWriteBack: AGENT_WRITE_BACK_SUMMARY,
supportedWriteBack: [AGENT_WRITE_BACK_SUMMARY],
});
expect(LOCAL_READ_AGENT_DEFINITION.permissionMode).toBe('explore');
expect([...LOCAL_READ_AGENT_DEFINITION.tools]).toEqual(['Read', 'Glob', 'Grep']);
expect(LOCAL_READ_AGENT_DEFINITION.tools.includes('Bash')).toBe(false);
expect(LOCAL_READ_AGENT_DEFINITION.tools.includes('WebSearch')).toBe(false);
expect(LOCAL_READ_AGENT_DEFINITION.tools.includes('WebFetch')).toBe(false);
expect(LOCAL_READ_AGENT_DEFINITION.tools.includes('ExploreAgent')).toBe(false);
const definitions = listBuiltinAgentDefinitions({
tools: [
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
testCatalogTool('WebSearch', 'web_read'),
],
});
expect(definitions.find((definition) => definition.id === LOCAL_READ_AGENT_ID)).toEqual({
id: LOCAL_READ_AGENT_ID,
profile: LOCAL_READ_AGENT_PROFILE,
name: 'Local Read',
description: 'Read-only repository exploration with file and text search tools only.',
permissionMode: 'explore',
tools: ['Read', 'Glob', 'Grep'],
contract: LOCAL_READ_AGENT_DEFINITION.contract,
availability: { status: 'available' },
});
});
test('built-in catalog exposes web-research with only WebSearch and no local or write tools', () => {
expect(WEB_RESEARCH_AGENT_DEFINITION.id).toBe(WEB_RESEARCH_AGENT_ID);
expect(WEB_RESEARCH_AGENT_DEFINITION.profile).toBe(WEB_RESEARCH_AGENT_PROFILE);
expect(WEB_RESEARCH_AGENT_DEFINITION.contract).toEqual({
capability: 'web_research',
invocation: AGENT_INVOCATION_FOREGROUND,
context: AGENT_CONTEXT_ISOLATED,
workspace: AGENT_WORKSPACE_SAME_WORKSPACE,
defaultWriteBack: AGENT_WRITE_BACK_SUMMARY,
supportedWriteBack: [AGENT_WRITE_BACK_SUMMARY],
});
expect(WEB_RESEARCH_AGENT_DEFINITION.permissionMode).toBe('execute');
expect([...WEB_RESEARCH_AGENT_DEFINITION.tools]).toEqual(['WebSearch']);
expect(WEB_RESEARCH_AGENT_DEFINITION.tools.includes('Read')).toBe(false);
expect(WEB_RESEARCH_AGENT_DEFINITION.tools.includes('Bash')).toBe(false);
expect(WEB_RESEARCH_AGENT_DEFINITION.tools.includes('Write')).toBe(false);
expect(WEB_RESEARCH_AGENT_DEFINITION.tools.includes('ExploreAgent')).toBe(false);
const withWebSearch = listBuiltinAgentDefinitions({
tools: [
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
testCatalogTool('WebSearch', undefined),
],
});
expect(withWebSearch.map((definition) => definition.profile)).toEqual([
LOCAL_READ_AGENT_PROFILE,
WEB_RESEARCH_AGENT_PROFILE,
IMPLEMENTATION_AGENT_PROFILE,
]);
expect(withWebSearch.find((definition) => definition.id === WEB_RESEARCH_AGENT_ID)).toEqual({
id: WEB_RESEARCH_AGENT_ID,
profile: WEB_RESEARCH_AGENT_PROFILE,
name: 'Web Research',
description: 'Network-backed web research with WebSearch only.',
permissionMode: 'execute',
tools: ['WebSearch'],
contract: WEB_RESEARCH_AGENT_DEFINITION.contract,
availability: { status: 'available' },
});
expect(
listBuiltinAgentDefinitions({
tools: [
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
],
}).find((definition) => definition.id === WEB_RESEARCH_AGENT_ID)?.availability,
).toEqual({
status: 'unavailable',
reason: 'missing_tools',
missingTools: ['WebSearch'],
});
expect(
listBuiltinAgentDefinitions({
tools: [
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
testCatalogTool('WebSearch', 'web_read'),
],
}).find((definition) => definition.id === WEB_RESEARCH_AGENT_ID)?.availability,
).toEqual({ status: 'available' });
});
test('built-in catalog exposes implementation only when a worktree executor is available', async () => {
expect(IMPLEMENTATION_AGENT_DEFINITION.id).toBe(IMPLEMENTATION_AGENT_ID);
expect(IMPLEMENTATION_AGENT_DEFINITION.profile).toBe(IMPLEMENTATION_AGENT_PROFILE);
expect(IMPLEMENTATION_AGENT_DEFINITION.contract).toEqual({
capability: 'implementation',
invocation: AGENT_INVOCATION_FOREGROUND,
context: AGENT_CONTEXT_ISOLATED,
workspace: AGENT_WORKSPACE_WORKTREE,
defaultWriteBack: AGENT_WRITE_BACK_PATCH,
supportedWriteBack: [AGENT_WRITE_BACK_PATCH],
});
expect(IMPLEMENTATION_AGENT_DEFINITION.permissionMode).toBe('execute');
expect([...IMPLEMENTATION_AGENT_DEFINITION.tools]).toEqual([
'Read',
'Glob',
'Grep',
'Write',
'Edit',
'Bash',
'WriteStdin',
'StopBackgroundTask',
]);
expect(IMPLEMENTATION_AGENT_DEFINITION.tools.includes('WebSearch')).toBe(false);
expect(IMPLEMENTATION_AGENT_DEFINITION.tools.includes('ExploreAgent')).toBe(false);
const availability = listBuiltinAgentDefinitions({
tools: implementationCatalogTools(),
}).find((definition) => definition.id === IMPLEMENTATION_AGENT_ID)?.availability;
expect(availability).toEqual({
status: 'unavailable',
reason: 'workspace_isolation_unavailable',
workspace: AGENT_WORKSPACE_WORKTREE,
requiredRuntime: 'worktree_child_executor',
});
await expectRejects(
Promise.resolve().then(() =>
assertAgentDefinitionRunnable({
definition: IMPLEMENTATION_AGENT_DEFINITION,
tools: implementationCatalogTools(),
}),
),
/worktree child executor/,
);
const runnableAvailability = listBuiltinAgentDefinitions({
worktreeChildExecutorAvailable: true,
tools: implementationCatalogTools(),
}).find((definition) => definition.id === IMPLEMENTATION_AGENT_ID)?.availability;
expect(runnableAvailability).toEqual({ status: 'available' });
assertAgentDefinitionRunnable({
worktreeChildExecutorAvailable: true,
definition: IMPLEMENTATION_AGENT_DEFINITION,
tools: implementationCatalogTools(),
});
});
test('agent definition availability depends on exposed tools, not legacy parent modes', () => {
expect(
evaluateAgentDefinitionAvailability({
definition: LOCAL_READ_AGENT_DEFINITION,
tools: [testCatalogTool('Read', 'read')],
}),
).toEqual({
status: 'unavailable',
reason: 'missing_tools',
missingTools: ['Glob', 'Grep'],
});
expect(
evaluateAgentDefinitionAvailability({
definition: {
...LOCAL_READ_AGENT_DEFINITION,
id: 'writer',
permissionMode: 'execute',
},
tools: [
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
],
}),
).toEqual({ status: 'available' });
});
test('agent definition policy uses the explicit tool allowlist', () => {
expect(
evaluateAgentDefinitionToolAccess(
LOCAL_READ_AGENT_DEFINITION,
testCatalogTool('Read', 'read'),
),
).toEqual({
category: 'read',
decision: 'allow',
});
expect(
evaluateAgentDefinitionToolAccess(
LOCAL_READ_AGENT_DEFINITION,
testCatalogTool('Write', 'file_write'),
),
).toEqual({
category: 'file_write',
decision: 'block',
});
expect(
evaluateAgentDefinitionToolAccess(
{
...LOCAL_READ_AGENT_DEFINITION,
id: 'web-review',
tools: ['WebSearch'],
},
testCatalogTool('WebSearch', 'web_read'),
),
).toEqual({
category: 'web_read',
decision: 'allow',
});
});
test('legacy parent mode does not override the authoritative child boundary and tool surface', () => {
assertAgentDefinitionRunnable({
definition: {
...LOCAL_READ_AGENT_DEFINITION,
id: 'writer',
permissionMode: 'execute',
},
tools: [
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
],
});
});
test('child agent toolset keeps only built-in profile allowlisted tools', () => {
const tools = buildChildAgentTools([
...buildBuiltinTools(),
testCatalogTool('WriteStdin', 'shell_unsafe'),
testCatalogTool('StopBackgroundTask', 'shell_unsafe'),
{
name: AGENT_SPAWN_TOOL_NAME,
description: 'spawn',
parameters: {},
categoryHint: 'subagent',
impl: async () => ({}),
},
{
name: 'WebSearch',
description: 'web',
parameters: {},
categoryHint: 'web_read',
impl: async () => ({}),
},
{
name: 'ExploreAgent',
description: 'deterministic exploration',
parameters: {},
categoryHint: 'subagent',
impl: async () => ({}),
},
]);
expect(tools.map((tool) => tool.name)).toEqual([
'Read',
'Glob',
'Grep',
'WebSearch',
'Write',
'Edit',
'Bash',
'WriteStdin',
'StopBackgroundTask',
]);
expect([...CHILD_AGENT_TOOL_NAMES]).toEqual([
'Read',
'Glob',
'Grep',
'WebSearch',
'Write',
'Edit',
'Bash',
'WriteStdin',
'StopBackgroundTask',
]);
});
test('does not smuggle ArchiveRead through the child allowlist', () => {
// The pool-level pass-through this replaces never worked: every child
// surface is re-narrowed against `definition.tools`, and no definition
// lists ArchiveRead. The decoder now reaches a child from its own backend's
// archive capability, so the allowlist stays exactly what it says it is.
const tools = buildChildAgentTools([
testCatalogTool('Read', 'read'),
testCatalogTool('Glob', 'read'),
testCatalogTool('Grep', 'read'),
testCatalogTool('WebSearch', 'web_read'),
testCatalogTool('ArchiveRead', 'read'),
]);
expect(tools.find((tool) => tool.name === 'ArchiveRead')).toBeUndefined();
});
test('child agent toolset enforces explore-mode read-only behavior without prompting', async () => {
const cwd = await mkdtemp(join(tmpdir(), 'maka-child-tools-'));
try {
await writeFile(join(cwd, 'notes.txt'), 'SUBAGENT_CHILD_TOOL_MARKER\n', 'utf8');
const events: SessionEvent[] = [];
const runtime = makeChildToolRuntime(cwd);
const tools = new Map(
buildChildAgentTools(buildBuiltinTools()).map((tool) => [tool.name, tool]),
);
await runTool(runtime, tools, 'Read', { path: 'notes.txt' }, events);
await runTool(runtime, tools, 'Glob', { pattern: '*.txt' }, events);
await runTool(runtime, tools, 'Grep', { pattern: 'SUBAGENT_CHILD_TOOL_MARKER' }, events);
expect(events.some((event) => event.type === 'permission_request')).toBe(false);
expect(tools.has('Bash')).toBe(true);
} finally {
await rm(cwd, { recursive: true, force: true });
}
});
test('agent_spawn delegates an explicit profile and task through the narrow context capability', async () => {
const tool = buildSubagentSpawnTool();
const abortController = new AbortController();
const calls: unknown[] = [];
const output: Array<{ stream: string; chunk: string }> = [];
const result = await tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the runtime tests.',
task_id: 'ignored-without-task-binding',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-1',
abortSignal: abortController.signal,
emitOutput: (stream, chunk) => output.push({ stream, chunk }),
spawnChildSession: async (input) => {
calls.push(input);
input.onEvent?.({
type: 'tool_start',
id: 'child-start',
turnId: 'child-turn',
ts: 1,
toolUseId: 'child-tool',
toolName: 'Read',
displayName: 'Read file',
args: { path: 'secret.txt' },
});
input.onEvent?.({
type: 'tool_result',
id: 'child-result',
turnId: 'child-turn',
ts: 2,
toolUseId: 'child-tool',
isError: false,
content: { kind: 'text', text: 'secret body' },
});
return {
profile: input.agentProfile,
childSessionId: 'child-session',
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
turnId: 'child-turn',
runId: 'child-run',
status: 'completed',
permissionMode: 'explore',
summary: 'done',
artifactIds: [],
internalField: 'must not cross the tool result boundary',
};
},
},
);
expect(tool.name).toBe(AGENT_SPAWN_TOOL_NAME);
expect(tool.categoryHint).toBe('subagent');
expect(calls).toHaveLength(1);
const call = calls[0] as {
agentProfile: string;
prompt: string;
onEvent?: (event: SessionEvent) => void;
};
expect(call.agentProfile).toBe(LOCAL_READ_AGENT_PROFILE);
expect(call.prompt).toBe('Inspect the runtime tests.');
expect(typeof call.onEvent).toBe('function');
expect(output).toEqual([
{ stream: 'stdout', chunk: 'Starting child agent: Local Read\n' },
{ stream: 'stdout', chunk: 'Child tool started: Read file\n' },
{ stream: 'stdout', chunk: 'Child tool finished: Read file\n' },
{ stream: 'stdout', chunk: 'Child agent Local Read: completed\n' },
]);
expect(JSON.stringify(output)).not.toContain('secret.txt');
expect(JSON.stringify(output)).not.toContain('secret body');
expect(result).toEqual({
kind: 'subagent',
childSessionId: 'child-session',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Local Read',
turnId: 'child-turn',
runId: 'child-run',
status: 'completed',
permissionMode: 'explore',
summary: 'done',
artifactIds: [],
});
});
test('agent_spawn prefers a configured subagent_id over a redundant legacy profile', async () => {
const tool = buildSubagentSpawnTool();
const calls: Array<{ agentProfile: string; subagentId?: string; prompt?: string }> = [];
await tool.impl(
{
profile: IMPLEMENTATION_AGENT_PROFILE,
subagent_id: 'fast-reader',
task: 'Inspect the runtime tests.',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
listChildAgents: async () => ({
presets: [
{
id: 'fast-reader',
profile: LOCAL_READ_AGENT_PROFILE,
availability: { status: 'available' },
},
],
}),
spawnChildSession: async (input) => {
calls.push(input);
return {
profile: input.agentProfile,
childSessionId: 'child-session',
agentId: LOCAL_READ_AGENT_ID,
agentName: 'Local Read',
turnId: 'child-turn',
runId: 'child-run',
status: 'completed',
permissionMode: 'explore',
summary: 'done',
artifactIds: [],
};
},
},
);
expect(calls).toHaveLength(1);
expect(calls[0]?.agentProfile).toBe(LOCAL_READ_AGENT_PROFILE);
expect(calls[0]?.subagentId).toBe('fast-reader');
expect(calls[0]?.prompt).toBe('Inspect the runtime tests.');
});
test('agent_spawn bounds projected child tool activity', async () => {
const tool = buildSubagentSpawnTool();
const output: string[] = [];
await tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect many files.',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: (_stream, chunk) => output.push(chunk),
spawnChildSession: async (input) => {
for (let index = 0; index < 100; index += 1) {
input.onEvent?.({
type: 'tool_start',
id: `start-${index}`,
turnId: 'child-turn',
ts: index,
toolUseId: `child-tool-${index}`,
toolName: 'Read',
args: { path: `${index}.txt` },
});
}
return {
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
turnId: 'child-turn',
status: 'completed',
permissionMode: 'explore',
summary: 'done',
artifactIds: [],
};
},
},
);
expect(output).toHaveLength(66);
expect(output[0]).toBe('Starting child agent: Local Read\n');
expect(output.at(-1)).toBe('Child agent Local Read: completed\n');
});
test('agent_spawn bounds projected startup failures', async () => {
const tool = buildSubagentSpawnTool();
const output: string[] = [];
await expectRejects(
Promise.resolve(
tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Fail.',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: (_stream, chunk) => output.push(chunk),
spawnChildSession: async () => {
throw new Error('x'.repeat(10_000));
},
},
),
),
/^x+$/,
);
expect(output).toHaveLength(2);
expect((output[1]?.length ?? Number.POSITIVE_INFINITY) < 1_100).toBe(true);
});
test('agent_spawn delegates web_research through the catalog definition', async () => {
const tool = buildSubagentSpawnTool();
const calls: unknown[] = [];
const result = await tool.impl(
{
profile: WEB_RESEARCH_AGENT_PROFILE,
task: 'Find current sources.',
write_back: AGENT_WRITE_BACK_SUMMARY,
isolation: AGENT_WORKSPACE_SAME_WORKSPACE,
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async (input) => {
calls.push(input);
return {
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
turnId: 'child-turn',
status: 'completed',
permissionMode: 'execute',
summary: 'done',
artifactIds: [],
};
},
},
);
expect(calls).toHaveLength(1);
const call = calls[0] as {
agentProfile: string;
prompt: string;
onEvent?: (event: SessionEvent) => void;
};
expect(call.agentProfile).toBe(WEB_RESEARCH_AGENT_PROFILE);
expect(call.prompt).toBe('Find current sources.');
expect(typeof call.onEvent).toBe('function');
expect(result).toMatchObject({
kind: 'subagent',
agentId: WEB_RESEARCH_AGENT_ID,
agentName: 'Web Research',
permissionMode: 'execute',
});
});
test('agent_spawn binds a current-session task and records real child refs without auto-completing', async () => {
const task: Task = {
id: 'task-uuid',
key: 'T1',
subject: 'inspect runtime',
status: 'pending',
createdAt: 1,
updatedAt: 1,
};
const calls: string[] = [];
const ledger = taskLedgerStub(task, calls);
const tool = buildSubagentSpawnTool({ taskLedger: ledger });
const result = await tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the runtime tests.',
task_id: 'T1',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async (input) => {
await input.onReady?.({
childSessionId: 'child-session',
runId: 'child-run',
turnId: 'child-turn',
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
permissionMode: 'explore',
});
return {
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
runId: 'child-run',
turnId: 'child-turn',
status: 'completed',
permissionMode: 'explore',
summary: 'inspection complete',
artifactIds: [],
};
},
},
);
expect(calls).toEqual(['get:session-1:T1', 'claim:child-turn', 'settle:completed:child-run']);
expect(task.status).toBe('in_progress');
expect(task.owner).toEqual({
actor: 'child_agent',
sessionId: 'child-session',
agentId: LOCAL_READ_AGENT_ID,
runId: 'child-run',
turnId: 'child-turn',
});
expect(result).toMatchObject({ kind: 'subagent', runId: 'child-run', status: 'completed' });
});
test('agent_spawn rejects a forged task reference before starting a child', async () => {
let spawned = false;
const ledger = taskLedgerStub(undefined, []);
const tool = buildSubagentSpawnTool({ taskLedger: ledger });
await expectRejects(
Promise.resolve(
tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect.',
task_id: 'T99',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async () => {
spawned = true;
return {};
},
},
),
),
/No such task in this session/,
);
expect(spawned).toBe(false);
});
test('agent_spawn records failed and cancelled child outcomes with real refs', async () => {
for (const status of ['failed', 'cancelled'] as const) {
const task: Task = {
id: `task-${status}`,
key: 'T1',
subject: status,
status: 'pending',
createdAt: 1,
updatedAt: 1,
};
const calls: string[] = [];
const tool = buildSubagentSpawnTool({ taskLedger: taskLedgerStub(task, calls) });
const result = await tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: `Run child that becomes ${status}.`,
task_id: task.key,
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async (input) => {
await input.onReady?.({
childSessionId: 'child-session',
runId: 'child-run',
turnId: `child-${status}`,
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
permissionMode: 'explore',
});
return {
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
runId: `run-${status}`,
turnId: `child-${status}`,
status,
permissionMode: 'explore',
summary: `${status} summary`,
artifactIds: [],
};
},
},
);
expect(calls).toEqual([
'get:session-1:T1',
`claim:child-${status}`,
`settle:${status}:run-${status}`,
]);
expect(task.status).toBe(status);
expect(task.owner).toEqual({
actor: 'child_agent',
sessionId: 'child-session',
agentId: LOCAL_READ_AGENT_ID,
runId: `run-${status}`,
turnId: `child-${status}`,
});
expect(result).toMatchObject({ kind: 'subagent', status, runId: `run-${status}` });
}
});
test('agent_spawn marks a claimed task failed when child startup throws', async () => {
const task: Task = {
id: 'task-startup-failure',
key: 'T1',
subject: 'startup',
status: 'pending',
createdAt: 1,
updatedAt: 1,
};
const calls: string[] = [];
const tool = buildSubagentSpawnTool({ taskLedger: taskLedgerStub(task, calls) });
await expectRejects(
Promise.resolve(
tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Fail after allocating the child turn.',
task_id: task.key,
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async (input) => {
await input.onReady?.({
childSessionId: 'child-session',
runId: 'child-run',
turnId: 'child-turn',
agentId: requireBuiltinAgentDefinitionByProfile(input.agentProfile).id,
agentName: requireBuiltinAgentDefinitionByProfile(input.agentProfile).name,
permissionMode: 'explore',
});
throw new Error('child startup failed');
},
},
),
),
/child startup failed/,
);
expect(calls).toEqual(['get:session-1:T1', 'claim:child-turn', 'settle:failed:undefined']);
expect(task.status).toBe('failed');
});
test('agent_spawn rejects a task reference that only exists in another session', async () => {
const task: Task = {
id: 'other-task',
key: 'T1',
subject: 'other',
status: 'pending',
createdAt: 1,
updatedAt: 1,
};
const ledger = taskLedgerStub(task, []);
ledger.get = async (sessionId) => (sessionId === 'session-2' ? task : undefined);
let spawned = false;
const tool = buildSubagentSpawnTool({ taskLedger: ledger });
await expectRejects(
Promise.resolve(
tool.impl(
{
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect.',
task_id: task.key,
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async () => {
spawned = true;
return {};
},
},
),
),
/No such task in this session/,
);
expect(spawned).toBe(false);
});
test('agent_spawn validates profile contracts and delegates worktree availability to runtime', async () => {
const tool = buildSubagentSpawnTool();
const schema = tool.parameters as {
safeParse(input: unknown): { success: boolean; data?: unknown };
};
expect(
schema.safeParse({ profile: LOCAL_READ_AGENT_PROFILE, task: 'Inspect the repo.' }).success,
).toBe(true);
expect(
schema.safeParse({ profile: WEB_RESEARCH_AGENT_PROFILE, task: 'Find current sources.' })
.success,
).toBe(true);
expect(
schema.safeParse({
profile: IMPLEMENTATION_AGENT_PROFILE,
task: 'Edit the repo.',
write_back: AGENT_WRITE_BACK_PATCH,
isolation: AGENT_WORKSPACE_WORKTREE,
}),
).toEqual({
success: true,
data: {
profile: IMPLEMENTATION_AGENT_PROFILE,
task: 'Edit the repo.',
write_back: AGENT_WRITE_BACK_PATCH,
isolation: AGENT_WORKSPACE_WORKTREE,
},
});
expect(
schema.safeParse({
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the repo.',
write_back: AGENT_WRITE_BACK_SUMMARY,
isolation: AGENT_WORKSPACE_SAME_WORKSPACE,
}),
).toEqual({
success: true,
data: {
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the repo.',
write_back: AGENT_WRITE_BACK_SUMMARY,
isolation: AGENT_WORKSPACE_SAME_WORKSPACE,
},
});
expect(
schema.safeParse({
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the repo.',
write_back: 'patch',
}).success,
).toBe(false);
expect(
schema.safeParse({
profile: LOCAL_READ_AGENT_PROFILE,
task: 'Inspect the repo.',
isolation: 'worktree',
}).success,
).toBe(false);
expect(
schema.safeParse({
profile: IMPLEMENTATION_AGENT_PROFILE,
task: 'Edit the repo.',
write_back: AGENT_WRITE_BACK_SUMMARY,
isolation: AGENT_WORKSPACE_WORKTREE,
}).success,
).toBe(false);
expect(
schema.safeParse({
profile: IMPLEMENTATION_AGENT_PROFILE,
task: 'Edit the repo.',
write_back: AGENT_WRITE_BACK_PATCH,
isolation: AGENT_WORKSPACE_SAME_WORKSPACE,
}).success,
).toBe(false);
expect(
schema.safeParse({ agent: LOCAL_READ_AGENT_ID, task: 'Inspect the repo.' }).success,
).toBe(false);
expect(
schema.safeParse({ profile: LOCAL_READ_AGENT_ID, task: 'Inspect the repo.' }).success,
).toBe(false);
expect(
schema.safeParse({ profile: WEB_RESEARCH_AGENT_ID, task: 'Find current sources.' }).success,
).toBe(false);
expect(
schema.safeParse({ agent_name: 'Researcher', instructions: 'Read only.', prompt: 'Inspect.' })
.success,
).toBe(false);
const calls: unknown[] = [];
await tool.impl(
{
profile: IMPLEMENTATION_AGENT_PROFILE,
task: 'Edit files.',
write_back: AGENT_WRITE_BACK_PATCH,
isolation: AGENT_WORKSPACE_WORKTREE,
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-1',
abortSignal: new AbortController().signal,
emitOutput: () => {},
spawnChildSession: async (input) => {
calls.push(input);
return {
childSessionId: 'child-session',
agentId: IMPLEMENTATION_AGENT_ID,
agentName: 'Implementation',
turnId: 'child-turn',
runId: 'child-run',
status: 'completed',
permissionMode: 'execute',
summary: 'done',
artifactIds: [],
};
},
},
);
expect(calls).toHaveLength(1);
expect(calls[0]).toMatchObject({
agentProfile: IMPLEMENTATION_AGENT_PROFILE,
prompt: 'Edit files.',
});
});
test('agent projection tools delegate through read-only context capabilities', async () => {
const listTool = buildSubagentListTool();
const outputTool = buildSubagentOutputTool();
const list = await listTool.impl(
{},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-list',
abortSignal: new AbortController().signal,
emitOutput: () => {},
listChildAgents: async () => ({
definitions: [
{
id: LOCAL_READ_AGENT_ID,
profile: LOCAL_READ_AGENT_PROFILE,
name: 'Local Read',
description: 'Read-only repository exploration.',
contract: { workspace: 'same_workspace', defaultWriteBack: 'summary' },
availability: { status: 'available' },
},
],
presets: [
{
id: 'fast-reader',
name: 'Fast reader',
description: 'Cheap repository inspection.',
profile: LOCAL_READ_AGENT_PROFILE,
model: 'deepseek-v4-flash',
thinkingLevel: 'low',
availability: { status: 'available' },
},
],
executions: [{ execution: { kind: 'legacy_child_run', runId: 'child-run' } }],
runs: [{ runId: 'child-run', turnId: 'child-turn' }],
}),
},
);
const output = await outputTool.impl(
{ run_id: 'child-run' },
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-output',
abortSignal: new AbortController().signal,
emitOutput: () => {},
readChildAgentOutput: async (input) => ({ requested: input }),
},
);
const childSessionOutput = await outputTool.impl(
{ child_session_id: 'child-session', run_id: 'child-session-run' },
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-output-child-session',
abortSignal: new AbortController().signal,
emitOutput: () => {},
readChildAgentOutput: async (input) => ({ requested: input }),
},
);
expect(listTool.name).toBe(AGENT_LIST_TOOL_NAME);
expect(outputTool.name).toBe(AGENT_OUTPUT_TOOL_NAME);
expect(list).toEqual({
presets: [
{
subagent_id: 'fast-reader',
name: 'Fast reader',
description: 'Cheap repository inspection.',
profile: LOCAL_READ_AGENT_PROFILE,
model: 'deepseek-v4-flash',
thinking_level: 'low',
status: 'available',
},
],
legacy_profiles: [
{
agent_id: LOCAL_READ_AGENT_ID,
profile: LOCAL_READ_AGENT_PROFILE,
name: 'Local Read',
description: 'Read-only repository exploration.',
workspace: 'same_workspace',
write_back: 'summary',
status: 'available',
},
],
page: { returned: 1, total: 1 },
view: 'selection',
});
expect(output).toEqual({
requested: {
execution: {
kind: 'legacy_child_run',
sessionId: 'session-1',
runId: 'child-run',
},
},
});
expect(childSessionOutput).toEqual({
requested: {
execution: {
kind: 'child_session',
sessionId: 'child-session',
currentRunId: 'child-session-run',
},
},
});
});
test('agent_list keeps discovery compact, paginated, and free of execution history', async () => {
const listTool = buildSubagentListTool();
const schema = listTool.parameters as {
safeParse(input: unknown): {
success: boolean;
data?: { view?: string; cursor?: string };
};
};
expect(schema.safeParse({ ignored: true }).data).toEqual({ view: 'selection' });
expect(schema.safeParse({ cursor: 'not-a-cursor' }).success).toBe(false);
const catalog = {
definitions: [
{
id: LOCAL_READ_AGENT_ID,
profile: LOCAL_READ_AGENT_PROFILE,
name: 'Local Read',
description: 'Read-only repository exploration.',
contract: { workspace: 'same_workspace', defaultWriteBack: 'summary' },
availability: { status: 'available' },
},
],
presets: Array.from({ length: 11 }, (_, index) => ({
id: `reader-${index}`,
name: `Reader ${index}`,
description: 'x'.repeat(1_000),
profile: LOCAL_READ_AGENT_PROFILE,
model: 'deepseek-v4-flash',
availability:
index === 10
? { status: 'unavailable', reason: 'connection_disabled' }
: { status: 'available' },
})),
executions: Array.from({ length: 100 }, (_, index) => ({ runId: `run-${index}` })),
runs: Array.from({ length: 100 }, (_, index) => ({ runId: `legacy-run-${index}` })),
};
const call = async (
input: { view?: 'selection' | 'catalog'; cursor?: string },
source = catalog,
) =>
(await listTool.impl(input, {
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-list-compact',
abortSignal: new AbortController().signal,
emitOutput: () => {},
listChildAgents: async () => source,
})) as Record<string, unknown>;
const first = await call({});
expect((first.presets as unknown[]).length).toBe(8);
expect(first.page).toEqual({ returned: 8, total: 10, next_cursor: '8' });
expect('definitions' in first).toBe(false);
expect('executions' in first).toBe(false);
expect('runs' in first).toBe(false);
expect(JSON.stringify(first).length < 8_192).toBe(true);
const second = await call({ cursor: '8' });
expect((second.presets as unknown[]).length).toBe(2);
expect(second.page).toEqual({ returned: 2, total: 10 });
const diagnosticTail = await call({ view: 'catalog', cursor: '8' });
expect(diagnosticTail.page).toEqual({ returned: 3, total: 11 });
expect((diagnosticTail.presets as Array<Record<string, unknown>>)[2]).toMatchObject({
subagent_id: 'reader-10',
status: 'unavailable',
reason: 'connection_disabled',
});
const worstCase = await call(
{},
{
...catalog,
definitions: catalog.definitions.map((definition) => ({
...definition,
name: 'n'.repeat(128),
description: 'd'.repeat(1_000),
})),
presets: catalog.presets.map((preset, index) => ({
...preset,
id: `reader-${index}`.padEnd(128, 'x'),
name: 'n'.repeat(128),
model: 'm'.repeat(500),
})),
},
);
expect(JSON.stringify(worstCase).length <= 7_000).toBe(true);
});
test('agent_output accepts linked child-session and legacy run locators', () => {
const outputTool = buildSubagentOutputTool();
const schema = outputTool.parameters as { safeParse(input: unknown): { success: boolean } };
expect(schema.safeParse({ run_id: 'child-run' }).success).toBe(true);
expect(schema.safeParse({ run_id: 'child-run', view: 'result' }).success).toBe(true);
expect(schema.safeParse({ turn_id: 'child-turn' }).success).toBe(true);
expect(schema.safeParse({ child_session_id: 'child-session' }).success).toBe(true);
expect(
schema.safeParse({
child_session_id: 'child-session',
run_id: 'child-run',
}).success,
).toBe(true);
expect(schema.safeParse({}).success).toBe(false);
expect(schema.safeParse({ run_id: 'child-run', turn_id: 'child-turn' }).success).toBe(false);
expect(
schema.safeParse({
child_session_id: 'child-session',
turn_id: 'child-turn',
}).success,
).toBe(false);
expect(schema.safeParse({ child_session_id: '' }).success).toBe(false);
expect(schema.safeParse({ run_id: '' }).success).toBe(false);
});
test('agent_output uses an explicit locator when a provider fills unrelated fields', async () => {
const outputTool = buildSubagentOutputTool();
const parsed = (
outputTool.parameters as {
safeParse(input: unknown): { success: boolean; data?: Record<string, unknown> };
}
).safeParse({
locator: 'child_session_run',
child_session_id: 'child-session',
run_id: 'child-run',
turn_id: { malformed: true },
ignored: true,
});
expect(parsed.success).toBe(true);
expect(parsed.data).toEqual({
locator: 'child_session_run',
child_session_id: 'child-session',
run_id: 'child-run',
});
const output = await outputTool.impl(
{
locator: 'child_session_run',
child_session_id: 'child-session',
run_id: 'child-run',
turn_id: 'provider-placeholder',
max_events: 100,
max_bytes: 32_768,
view: 'runtime_events',
},
{
sessionId: 'session-1',
turnId: 'parent-turn',
cwd: '/tmp/cwd',
toolCallId: 'tool-output-provider-filled',
abortSignal: new AbortController().signal,
emitOutput: () => {},
readChildAgentOutput: async (input) => ({ requested: input }),
},
);
expect(output).toEqual({
requested: {
execution: {
kind: 'child_session',
sessionId: 'child-session',
currentRunId: 'child-run',
},
maxEvents: 100,
maxBytes: 32_768,
view: 'runtime_events',
},
});
});
});
function makeChildToolRuntime(cwd: string): ToolRuntime {
return createTestToolRuntime({
sessionId: 'session-1',
header: childHeader(cwd),
connection: testConnection(),
modelId: 'mock-model',
appendMessage: async () => {},
newId: nextId(),
now: () => 1,
getPermissionPauseTarget: () => null,
});
}
async function runTool(
runtime: ToolRuntime,
tools: Map<string, MakaTool>,
name: string,
args: unknown,
events: SessionEvent[],
): Promise<unknown> {
const tool = tools.get(name);
if (!tool) throw new Error(`Missing child tool ${name}`);
return (
await runtime.settleToolCall({
tool,
turnId: 'child-turn',
toolCallId: `tool-${name}-${typeof args === 'object' && args && 'command' in args ? (args as { command: string }).command : 'read'}`,
input: args,
abortSignal: new AbortController().signal,
eventSink: {
push: (event) => events.push(event),
pushAndWaitUntilConsumed: async (event) => {
events.push(event);
},
},
})
).result;
}
function testCatalogTool(name: string, categoryHint: MakaTool['categoryHint']): MakaTool {
return {
name,
description: name,
parameters: {},
categoryHint,
impl: async () => ({}),
};
}
function implementationCatalogTools(): MakaTool[] {
return IMPLEMENTATION_AGENT_DEFINITION.tools.map((name) => testCatalogTool(name, undefined));
}
async function expectRejects(promise: Promise<unknown>, pattern: RegExp): Promise<void> {
try {
await promise;
} catch (error) {
expect(error instanceof Error ? error.message : String(error)).toMatch(pattern);
return;
}
throw new Error('Expected promise to reject');
}
async function waitFor(predicate: () => boolean, timeoutMs = 1_000): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (!predicate()) {
if (Date.now() >= deadline) throw new Error('Timed out waiting for condition');
await new Promise<void>((resolve) => setImmediate(resolve));
}
}
function childHeader(cwd: string): SessionHeader {
return {
id: 'session-1',
workspaceRoot: cwd,
cwd,
createdAt: 1,
lastUsedAt: 1,
name: 'Test',
titleIsManual: true,
isFlagged: false,
labels: [],
isArchived: false,
status: 'active',
statusUpdatedAt: 1,
hasUnread: false,
backend: 'ai-sdk',
llmConnectionSlug: 'anthropic-main',
connectionLocked: true,
model: 'mock-model',
permissionMode: 'explore',
schemaVersion: 1,
};
}
function testConnection(): LlmConnection {
return {
slug: 'anthropic-main',
name: 'Anthropic',
providerType: 'anthropic',
defaultModel: 'mock-model',
enabled: true,
createdAt: 1,
updatedAt: 1,
};
}
function nextId(): () => string {
let id = 0;
return () => `id-${++id}`;
}
function taskLedgerStub(task: Task | undefined, calls: string[]): TaskLedgerStore {
return {
list: async () => (task ? [task] : []),
get: async (sessionId, id) => {
calls.push(`get:${sessionId}:${id}`);
return task && (task.id === id || task.key === id) ? task : undefined;
},
create: async () => ({ created: [], total: task ? 1 : 0 }),
update: async () => {
if (!task) throw new Error('No such task');
return { updated: task, total: 1 };
},
claim: async (_sessionId, _id, owner: TaskOwner) => {
if (!task) throw new Error('No such task');
calls.push(`claim:${owner.turnId}`);
task.status = 'in_progress';
task.owner = owner;
return { updated: task, total: 1 };
},
claimAvailable: async (_sessionId, _id, owner: TaskOwner) => {
if (!task) throw new Error('No such task');
calls.push(`claimAvailable:${owner.turnId}`);
task.status = 'in_progress';
task.owner = owner;
return { updated: task, total: 1 };
},
settleAgentOutcome: async (_sessionId, _id, outcome: TaskAgentOutcome) => {
if (!task) throw new Error('No such task');
calls.push(`settle:${outcome.status}:${outcome.owner.runId}`);
task.owner = outcome.owner;
if (outcome.status === 'failed') task.status = 'failed';
if (outcome.status === 'cancelled') task.status = 'cancelled';
return { updated: task, total: 1 };
},
subscribe: () => () => {},
};
}