@myagentroam/agent 0.9.134 → 0.9.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cli/main.js +10 -1
  2. package/dist/host/local-agent-host.d.ts +9 -7
  3. package/dist/host/local-agent-host.js +77 -5
  4. package/dist/host/local-process-executor.d.ts +2 -0
  5. package/dist/index.d.ts +4 -2
  6. package/dist/index.js +1 -0
  7. package/dist/model/catalog-file.d.ts +4 -0
  8. package/dist/model/configuration.d.ts +13 -0
  9. package/dist/model/configuration.js +11 -0
  10. package/dist/model/contracts.d.ts +7 -0
  11. package/dist/model/openai-responses.d.ts +1 -1
  12. package/dist/model/openai-responses.js +90 -21
  13. package/dist/prompts/workflow.js +2 -0
  14. package/dist/runtime/context-gc-runtime.d.ts +1 -0
  15. package/dist/runtime/context-gc-runtime.js +3 -0
  16. package/dist/runtime/context-gc-tokens.d.ts +1 -0
  17. package/dist/runtime/context-gc-tokens.js +1 -1
  18. package/dist/runtime/context-gc.d.ts +1 -0
  19. package/dist/runtime/context-gc.js +85 -8
  20. package/dist/runtime/context-projection.js +19 -2
  21. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +29 -2
  22. package/dist/sdk/agent.js +212 -25
  23. package/dist/sdk/mcp-catalog.d.ts +13 -0
  24. package/dist/sdk/mcp-catalog.js +219 -0
  25. package/dist/sdk/types.d.ts +50 -1
  26. package/dist/session/context-gc-checkpoint.d.ts +7 -1
  27. package/dist/session/context-gc-checkpoint.js +28 -2
  28. package/dist/session/context-gc-replacements.d.ts +7 -0
  29. package/dist/session/context-gc-replacements.js +26 -0
  30. package/dist/session/jsonl-store.d.ts +6 -0
  31. package/dist/session/jsonl-store.js +92 -54
  32. package/dist/subagent/scheduler.d.ts +3 -3
  33. package/dist/subagent/session-controller.d.ts +1 -1
  34. package/dist/tools/agent-cancel.js +1 -0
  35. package/dist/tools/agent-message.js +1 -0
  36. package/dist/tools/agent-start.js +1 -0
  37. package/dist/tools/agent-wait.js +1 -0
  38. package/dist/tools/apply-patch.js +2 -1
  39. package/dist/tools/callers.d.ts +4 -0
  40. package/dist/tools/callers.js +18 -0
  41. package/dist/tools/code-mode/engine.d.ts +43 -0
  42. package/dist/tools/code-mode/engine.js +1 -0
  43. package/dist/tools/code-mode/quickjs-engine.d.ts +2 -0
  44. package/dist/tools/code-mode/quickjs-engine.js +454 -0
  45. package/dist/tools/code-mode/state.d.ts +20 -0
  46. package/dist/tools/code-mode/state.js +82 -0
  47. package/dist/tools/code-mode/worker.d.ts +1 -0
  48. package/dist/tools/code-mode/worker.js +65 -0
  49. package/dist/tools/code-mode.d.ts +5 -3
  50. package/dist/tools/code-mode.js +129 -120
  51. package/dist/tools/exec.js +1 -0
  52. package/dist/tools/execution-limits.d.ts +8 -0
  53. package/dist/tools/execution-limits.js +8 -0
  54. package/dist/tools/local-registry.d.ts +2 -0
  55. package/dist/tools/local-registry.js +1 -1
  56. package/dist/tools/question.js +1 -0
  57. package/dist/tools/read.js +1 -0
  58. package/dist/tools/registry.js +3 -0
  59. package/dist/tools/runtime-context.d.ts +2 -0
  60. package/dist/tools/runtime.js +3 -1
  61. package/dist/tools/skill.js +1 -0
  62. package/dist/tools/todo-read.js +1 -0
  63. package/dist/tools/todo-write.js +1 -0
  64. package/dist/tools/tool-search.d.ts +43 -0
  65. package/dist/tools/tool-search.js +278 -0
  66. package/dist/tools/view-image.js +1 -0
  67. package/dist/tools/web-fetch.js +1 -0
  68. package/dist/tools/web-search.js +1 -0
  69. package/package.json +3 -1
@@ -0,0 +1,82 @@
1
+ import { MarAgentError } from '../../error.js';
2
+ import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
3
+ export function parseCodeModeStateMutations(value) {
4
+ if (!Array.isArray(value) || value.length > limits.codeStateMaxMutations)
5
+ throw invalidState();
6
+ return value.map((item) => {
7
+ if (!isObject(item) || !validKey(item.key))
8
+ throw invalidState();
9
+ if (item.operation === 'delete' &&
10
+ Object.keys(item).every((key) => ['operation', 'key'].includes(key)))
11
+ return { operation: 'delete', key: item.key };
12
+ if (item.operation === 'set' &&
13
+ Object.keys(item).every((key) => ['operation', 'key', 'value'].includes(key)) &&
14
+ Object.prototype.hasOwnProperty.call(item, 'value'))
15
+ return { operation: 'set', key: item.key, value: cloneStateValue(item.value) };
16
+ throw invalidState();
17
+ });
18
+ }
19
+ export function restoreCodeModeState(records) {
20
+ let state = [];
21
+ for (const record of records) {
22
+ if (record.type !== 'code.state.updated')
23
+ continue;
24
+ if (!isObject(record.payload) ||
25
+ !Object.keys(record.payload).every((key) => key === 'mutations'))
26
+ throw corruptState();
27
+ let mutations;
28
+ try {
29
+ mutations = parseCodeModeStateMutations(record.payload.mutations);
30
+ state = applyCodeModeStateMutations(state, mutations);
31
+ }
32
+ catch (error) {
33
+ if (error instanceof MarAgentError)
34
+ throw new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Code Mode state is corrupt.', {
35
+ cause: error
36
+ });
37
+ throw error;
38
+ }
39
+ }
40
+ return state;
41
+ }
42
+ export function applyCodeModeStateMutations(entries, mutations) {
43
+ const state = new Map(entries.map((entry) => [entry.key, cloneStateValue(entry.value)]));
44
+ for (const mutation of mutations) {
45
+ if (mutation.operation === 'delete')
46
+ state.delete(mutation.key);
47
+ else
48
+ state.set(mutation.key, cloneStateValue(mutation.value));
49
+ }
50
+ if (state.size > limits.codeStateMaxEntries)
51
+ throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode state has too many entries.');
52
+ const result = [...state].map(([key, value]) => ({ key, value }));
53
+ if (Buffer.byteLength(JSON.stringify(result)) > limits.codeStateMaxTotalBytes)
54
+ throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode state is too large.');
55
+ return result;
56
+ }
57
+ function validKey(value) {
58
+ return (typeof value === 'string' &&
59
+ value.length > 0 &&
60
+ Buffer.byteLength(value) <= limits.codeStateMaxKeyBytes);
61
+ }
62
+ function cloneStateValue(value) {
63
+ let serialized;
64
+ try {
65
+ serialized = JSON.stringify(value);
66
+ }
67
+ catch {
68
+ throw invalidState();
69
+ }
70
+ if (serialized === undefined || Buffer.byteLength(serialized) > limits.codeStateMaxValueBytes)
71
+ throw invalidState();
72
+ return JSON.parse(serialized);
73
+ }
74
+ function invalidState() {
75
+ return new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Code Mode state mutation is invalid.');
76
+ }
77
+ function corruptState() {
78
+ return new MarAgentError('MAR_AGENT_SESSION_CORRUPT', 'Code Mode state is corrupt.');
79
+ }
80
+ function isObject(value) {
81
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
82
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import { createInterface } from 'node:readline';
2
+ // The worker evaluates only a trusted engine module. Model source is passed as data to that engine.
3
+ const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
4
+ const send = (message) => process.stdout.write(JSON.stringify(message) + '\n');
5
+ const pending = new Map();
6
+ const controller = new AbortController();
7
+ let sequence = 0;
8
+ let started = false;
9
+ lines.on('line', async (line) => {
10
+ let engine;
11
+ try {
12
+ const frame = JSON.parse(line);
13
+ if (started) {
14
+ const deferred = pending.get(frame.id);
15
+ if (!deferred)
16
+ return;
17
+ pending.delete(frame.id);
18
+ if (frame.ok)
19
+ deferred.resolve(frame.output);
20
+ else
21
+ deferred.reject(Object.assign(new Error(frame.error.message), { code: frame.error.code }));
22
+ return;
23
+ }
24
+ started = true;
25
+ const { createCodeModeEngine } = await import(frame.engine.moduleUrl);
26
+ engine = await createCodeModeEngine(frame.engine.options ?? {});
27
+ const result = await engine.execute({
28
+ code: frame.code,
29
+ tools: frame.tools,
30
+ catalog: frame.catalog,
31
+ state: frame.state,
32
+ limits: frame.limits,
33
+ signal: controller.signal,
34
+ onText: (value) => send({ kind: 'text', value }),
35
+ onImage: (value) => send({ kind: 'image', value }),
36
+ onToolCall: (call) => new Promise((resolve, reject) => {
37
+ const id = ++sequence;
38
+ pending.set(id, { resolve, reject });
39
+ send({ kind: 'call', id, name: call.name, arguments: call.arguments });
40
+ })
41
+ });
42
+ if (pending.size)
43
+ throw Object.assign(new Error('Unawaited tool calls remain.'), {
44
+ code: 'MAR_AGENT_CODE_INCOMPLETE'
45
+ });
46
+ await engine.dispose();
47
+ engine = undefined;
48
+ await new Promise((resolve) => process.stdout.write(JSON.stringify({ kind: 'done', stateMutations: result.stateMutations }) + '\n', resolve));
49
+ }
50
+ catch (error) {
51
+ controller.abort(error);
52
+ for (const deferred of pending.values())
53
+ deferred.reject(error);
54
+ pending.clear();
55
+ try {
56
+ await engine?.dispose();
57
+ }
58
+ catch { }
59
+ await new Promise((resolve) => process.stdout.write(JSON.stringify({
60
+ kind: 'error',
61
+ code: typeof error?.code === 'string' ? error.code : 'MAR_AGENT_CODE_ENGINE_FAILED',
62
+ message: String(error?.message ?? error).slice(0, 4096)
63
+ }) + '\n', resolve));
64
+ }
65
+ });
@@ -2,15 +2,17 @@ import { type LocalProcessExecutor } from '../host/local-process-executor.js';
2
2
  import type { ClientToolDefinition } from '../model/contracts.js';
3
3
  import type { ToolExecutionContext, ToolExecutionResult } from '../sdk/types.js';
4
4
  import { WorkspacePathResolver } from './shared/path-resolver.js';
5
+ import type { CodeModeEngineFactory } from './code-mode/engine.js';
5
6
  export declare const codeModeToolDefinition: ClientToolDefinition;
6
- /** Render tool-owned result contracts without changing provider schemas or tool execution. */
7
- export declare function withCodeModeResultTypes(tools: readonly ClientToolDefinition[]): ClientToolDefinition[];
7
+ /** Direct model tools and bounded input-only declarations for the programmatic surface. */
8
+ export declare function projectCodeModeTools(tools: readonly ClientToolDefinition[]): ClientToolDefinition[];
8
9
  export declare class CodeModeTool {
9
10
  #private;
10
11
  private readonly paths;
11
12
  private readonly environment;
12
13
  private readonly executor;
13
- constructor(paths: WorkspacePathResolver, environment?: NodeJS.ProcessEnv, executor?: LocalProcessExecutor);
14
+ private readonly engineFactory;
15
+ constructor(paths: WorkspacePathResolver, environment?: NodeJS.ProcessEnv, executor?: LocalProcessExecutor, engineFactory?: CodeModeEngineFactory);
14
16
  execute(value: unknown, context: ToolExecutionContext): Promise<ToolExecutionResult>;
15
17
  dispose(sessionId?: string): Promise<void>;
16
18
  }
@@ -1,13 +1,57 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { createRequire } from 'node:module';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { readFile } from 'node:fs/promises';
5
+ import { dirname, sep } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
2
7
  import { StringDecoder } from 'node:string_decoder';
3
8
  import { MarAgentError, describeMarAgentError } from '../error.js';
4
9
  import { directLocalProcessExecutor, signalProcessTree } from '../host/local-process-executor.js';
5
10
  import { TOOL_EXECUTION_LIMITS as limits } from './execution-limits.js';
6
11
  import { createToolCallScheduler } from './scheduler.js';
7
12
  import { detectImageMime } from './view-image.js';
13
+ import { allowsToolCaller } from './callers.js';
14
+ import { parseCodeModeStateMutations } from './code-mode/state.js';
8
15
  export const codeModeToolDefinition = {
9
16
  name: 'code_mode',
10
- description: 'Run raw JavaScript in a fresh Node.js process to compose tool calls and process results in one model turn. Top-level await and dynamic Node module imports via await import(...) are supported. Call tools.NAME(input) using the available tool schemas; freeform tools accept their raw string. Results have content and optional data/images/truncated. Use Promise.all for independent calls and await dependencies. Runtime enforces safe/serial tool barriers. text(value) or console.log(value) emits selected output; image(value) emits one returned {mimeType,dataBase64} image. Tool errors reject with code/message and may be caught. Await every tool call; no JS state survives a cell. Whole-cell execution has the same Host policy hooks and OS permissions as exec, not a restricted JavaScript sandbox. Do not access or disclose secrets. Use tools for managed processes and tasks that must survive this cell; cell cleanup follows the Host process-tree mechanism and is not containment for detached descendants. Do not call code_mode recursively. Input max 1 MiB, emitted text max 1 MiB, max 128 nested calls, wall time 120 seconds. Output-limit and runtime failures do not roll back completed work.',
17
+ description: `Run JavaScript to orchestrate or compose tool calls.
18
+ - Use for bounded combinations of tools or filtering large intermediate results. A single direct tool call is preferable when that tool allows direct calls and composition adds no benefit.
19
+ - Evaluates source in a fresh restricted ECMAScript runtime with top-level await and Promise.all. It is not Node.js or a browser. JavaScript globals do not survive between cells; bounded JSON state is available through state.
20
+ - Accepts raw JavaScript source text, not JSON, quoted strings, or markdown code fences. Responses uses the source directly; Anthropic uses the code field of its tool input object. In either case, do not quote or fence the source again.
21
+ - Use only standard ECMAScript built-ins and the explicitly documented tools, out, state, catalog and cell namespaces. Do not use Node.js, browser or Web APIs. Module imports, filesystem and network APIs are unavailable. Obtain external capabilities only through available tools. Use tools.exec for managed background processes when permitted.
22
+
23
+ Tool calls:
24
+ - tools.NAME(input) returns a Promise. Use the existing tool input schema; freeform tools such as apply_patch accept a raw string, not a JSON wrapper.
25
+ - Results use the MAR envelope: { content: string, data?: unknown, images?: Array<{mimeType:string,dataBase64:string}>, truncated?: boolean }. content is rendered text, not raw file bytes. Use result.data.results[0].text for read source text and result.data.exitCode/result.data.stdout for exec. Use only fields documented by that tool; do not invent success or ok flags.
26
+ - Tool errors reject with code/message and may be caught. Await every call; unawaited calls fail the cell. Use Promise.all for independent calls and sequential await for dependencies. Runtime preserves safe/serial barriers and Host checks.
27
+ - tool_search, question, code_mode and direct-only tools cannot be called here. Use direct tool_search before a later code cell for deferred tools; the current cell's tools do not change.
28
+
29
+ Built-in namespaces:
30
+ - out.text(value): void emits text. Strings and scalar values are stringified; JSON-compatible objects/arrays are serialized as compact JSON. Cyclic or unsupported object values fail. Only explicitly emitted output reaches the model.
31
+ - out.image(value: {mimeType:string,dataBase64:string}): void emits one returned image; for example out.image(result.images[0]) when that image exists and the model supports images. It does not accept a path, URL, or result.content.
32
+ - state.get(key), state.has(key), state.set(key, value), state.delete(key) access bounded session JSON state. Mutations commit atomically only when the cell succeeds; failed or cancelled cells discard them.
33
+ - catalog.tools() returns a read-only snapshot of the tools callable in this cell, including name, description, inputSchema and inputMode. It does not discover or authorize additional tools.
34
+ - await cell.sleep(ms) performs a cancellable Host-side delay up to 30000 ms and remains subject to the cell deadline.
35
+ - tools, out, state, catalog and cell are frozen bindings. Do not shadow their names.
36
+ - Input/text output max 1 MiB, 128 calls, 120 seconds. Failures do not roll back completed tool work; do not replay successful operations blindly.
37
+
38
+ Examples (copy the JavaScript source, not the fences; use only tools available in the current directory):
39
+ Independent reads, emitting only source text:
40
+ \`\`\`js
41
+ const [a, b] = await Promise.all([
42
+ tools.read({ reads: [{ path: "a.txt" }] }),
43
+ tools.read({ reads: [{ path: "b.txt" }] })
44
+ ]);
45
+ out.text({ a: a.data.results[0].text, b: b.data.results[0].text });
46
+ \`\`\`
47
+ Inspect before a dependent edit; successful await needs no invented success flag:
48
+ \`\`\`js
49
+ const inspected = await tools.read({ reads: [{ path: "source.txt" }] });
50
+ const sourceText = inspected.data.results[0].text;
51
+ if (!sourceText?.includes("original=1")) throw new Error("Unexpected source");
52
+ const patched = await tools.apply_patch("*** Begin Patch\\n*** Update File: source.txt\\n@@\\n original=1\\n+updated=2\\n*** End Patch");
53
+ out.text({ files: patched.data.files.length });
54
+ \`\`\``,
11
55
  inputMode: 'freeform',
12
56
  inputSchema: {
13
57
  type: 'object',
@@ -15,71 +59,55 @@ export const codeModeToolDefinition = {
15
59
  required: ['code'],
16
60
  additionalProperties: false
17
61
  },
62
+ allowedCallers: ['direct'],
18
63
  parallelSafety: 'serial'
19
64
  };
20
- /** Render tool-owned result contracts without changing provider schemas or tool execution. */
21
- export function withCodeModeResultTypes(tools) {
22
- if (!tools.some((tool) => tool.name === 'code_mode'))
23
- return [...tools];
24
- const declarations = [];
25
- let bytes = 0;
26
- for (const tool of tools) {
27
- if (tool.name === 'code_mode' || !tool.outputSchema)
28
- continue;
29
- const declaration = `${JSON.stringify(tool.name)}: ${schemaType(tool.outputSchema)};`;
30
- const size = Buffer.byteLength(declaration);
31
- if (bytes + size > 16 * 1024)
32
- continue;
33
- declarations.push(declaration);
34
- bytes += size;
35
- }
36
- const description = `${codeModeToolDefinition.description}
37
-
38
- tools.NAME(input) returns a result envelope, not its data fields directly. content is rendered tool text, not raw file bytes; read content includes line numbers and metadata. Tool-specific fields are in data (for example, an exec result uses result.data.exitCode and result.data.stdout). Names not listed below return { content: string; data?: unknown; images?: Array<{ mimeType: string; dataBase64: string }>; truncated?: boolean }.
39
- Return types for available tools; use their existing input schemas for arguments:
40
- \`\`\`ts
41
- type ToolResults = {
42
- ${declarations.join('\n')}
43
- };
44
- \`\`\``;
45
- return tools.map((tool) => (tool.name === 'code_mode' ? { ...tool, description } : tool));
65
+ /** Direct model tools and bounded input-only declarations for the programmatic surface. */
66
+ export function projectCodeModeTools(tools) {
67
+ const direct = tools.filter((tool) => allowsToolCaller(tool, 'direct'));
68
+ if (!direct.some((tool) => tool.name === 'code_mode'))
69
+ return direct;
70
+ const programmatic = tools.filter((tool) => allowsToolCaller(tool, 'programmatic'));
71
+ const hidden = programmatic.filter((tool) => !allowsToolCaller(tool, 'direct'));
72
+ const directory = JSON.stringify({
73
+ callableNames: programmatic.map((tool) => tool.name),
74
+ programmaticOnly: hidden.map((tool) => ({
75
+ name: tool.name,
76
+ description: tool.description,
77
+ inputSchema: tool.inputSchema,
78
+ inputMode: tool.inputMode ?? 'json'
79
+ }))
80
+ });
81
+ if (Buffer.byteLength(directory) > 32 * 1024)
82
+ throw new MarAgentError('MAR_AGENT_TOOL_EXPOSURE_LIMIT', 'Programmatic tool directory is too large.');
83
+ return direct.map((tool) => tool.name === 'code_mode'
84
+ ? {
85
+ ...tool,
86
+ description: `${codeModeToolDefinition.description}\nProgrammatic tools (other schemas are provided by their direct tool definitions): ${directory}`
87
+ }
88
+ : tool);
46
89
  }
47
- function schemaType(value, depth = 0) {
48
- if (!value || typeof value !== 'object' || Array.isArray(value) || depth > 8)
49
- return 'unknown';
50
- const schema = value;
51
- if (Array.isArray(schema.enum))
52
- return (schema.enum
53
- .slice(0, 32)
54
- .map((item) => JSON.stringify(item))
55
- .join(' | ') || 'unknown');
56
- if (Array.isArray(schema.anyOf))
57
- return (schema.anyOf
58
- .slice(0, 8)
59
- .map((item) => schemaType(item, depth + 1))
60
- .join(' | ') || 'unknown');
61
- if (Array.isArray(schema.type))
62
- return schema.type
63
- .slice(0, 8)
64
- .map((type) => schemaType({ ...schema, type }, depth + 1))
65
- .join(' | ');
66
- if (schema.type === 'array')
67
- return `Array<${schemaType(schema.items, depth + 1)}>`;
68
- if (schema.type === 'object') {
69
- const properties = schema.properties;
70
- if (!properties || typeof properties !== 'object' || Array.isArray(properties))
71
- return 'Record<string, unknown>';
72
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
73
- return `{ ${Object.entries(properties)
74
- .slice(0, 32)
75
- .map(([name, property]) => `${JSON.stringify(name)}${required.has(name) ? '' : '?'}: ${schemaType(property, depth + 1)};`)
76
- .join(' ')} }`;
77
- }
78
- if (schema.type === 'integer' || schema.type === 'number')
79
- return 'number';
80
- return ['string', 'boolean', 'null'].includes(String(schema.type))
81
- ? String(schema.type)
82
- : 'unknown';
90
+ function defaultEngineFactory() {
91
+ const core = import.meta.resolve('quickjs-emscripten-core');
92
+ const variant = import.meta.resolve('@jitl/quickjs-wasmfile-release-sync');
93
+ const ffi = pathToFileURL(createRequire(core).resolve('@jitl/quickjs-ffi-types')).href;
94
+ const dependencyRoot = (url) => {
95
+ const root = dirname(dirname(fileURLToPath(url)));
96
+ // pnpm's sibling dependencies are symlinks; keep the package-local node_modules tree visible.
97
+ return root.includes(`${sep}.pnpm${sep}`) ? dirname(dirname(root)) : root;
98
+ };
99
+ return {
100
+ moduleUrl: new URL('./code-mode/quickjs-engine.js', import.meta.url).href,
101
+ runtimeDirectories: [
102
+ ...new Set([
103
+ fileURLToPath(new URL('./code-mode/', import.meta.url)),
104
+ dependencyRoot(core),
105
+ dependencyRoot(variant),
106
+ dependencyRoot(ffi)
107
+ ])
108
+ ],
109
+ options: { coreModuleUrl: core, variantModuleUrl: variant }
110
+ };
83
111
  }
84
112
  const environmentKeys = [
85
113
  'PATH',
@@ -96,11 +124,13 @@ export class CodeModeTool {
96
124
  paths;
97
125
  environment;
98
126
  executor;
127
+ engineFactory;
99
128
  #active = new Map();
100
- constructor(paths, environment = {}, executor = directLocalProcessExecutor) {
129
+ constructor(paths, environment = {}, executor = directLocalProcessExecutor, engineFactory = defaultEngineFactory()) {
101
130
  this.paths = paths;
102
131
  this.environment = environment;
103
132
  this.executor = executor;
133
+ this.engineFactory = engineFactory;
104
134
  }
105
135
  async execute(value, context) {
106
136
  const code = typeof value === 'string'
@@ -157,12 +187,13 @@ export class CodeModeTool {
157
187
  `--max-old-space-size=${limits.codeHeapMegabytes}`,
158
188
  '--input-type=module',
159
189
  '--eval',
160
- nodeCell
190
+ await readFile(new URL('./code-mode/worker.js', import.meta.url), 'utf8')
161
191
  ],
162
192
  cwd,
163
193
  shell: false,
164
194
  environment,
165
195
  environmentKeys: Object.keys(environment),
196
+ runtimeDirectories: this.engineFactory.runtimeDirectories,
166
197
  detached: process.platform !== 'win32',
167
198
  owner: { sessionId: context.sessionId, executionId: context.executionId },
168
199
  signal: context.signal
@@ -173,6 +204,7 @@ export class CodeModeTool {
173
204
  let transportBytes = 0;
174
205
  let calls = 0;
175
206
  let complete = false;
207
+ let stateMutations = [];
176
208
  let failure;
177
209
  let stderr = '';
178
210
  let buffered = '';
@@ -212,7 +244,9 @@ export class CodeModeTool {
212
244
  context.signal.throwIfAborted();
213
245
  return context.codeMode.invoke(call, context.signal);
214
246
  });
215
- const available = new Set(context.codeMode.tools.map((tool) => tool.name));
247
+ const available = new Set(context
248
+ .codeMode.tools.filter((tool) => allowsToolCaller(tool, 'programmatic'))
249
+ .map((tool) => tool.name));
216
250
  const receive = (frame) => {
217
251
  if (complete)
218
252
  throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent data after completion.');
@@ -266,6 +300,7 @@ export class CodeModeTool {
266
300
  // Child completion cannot undo Host tool effects from calls it did not await.
267
301
  if (pending.size)
268
302
  throw new MarAgentError('MAR_AGENT_CODE_INCOMPLETE', 'Code Mode returned with unawaited tool calls; completed operations are not rolled back.');
303
+ stateMutations = parseCodeModeStateMutations(frame.stateMutations);
269
304
  complete = true;
270
305
  void signalProcessTree(child, 'SIGKILL').catch(fail);
271
306
  }
@@ -324,12 +359,39 @@ export class CodeModeTool {
324
359
  if (context.signal.aborted)
325
360
  abort();
326
361
  else
327
- await write({ code });
362
+ await write({
363
+ code,
364
+ tools: [...available],
365
+ catalog: context
366
+ .codeMode.tools.filter((tool) => available.has(tool.name))
367
+ .map((tool) => ({
368
+ name: tool.name,
369
+ description: tool.description,
370
+ inputSchema: tool.inputSchema,
371
+ inputMode: tool.inputMode ?? 'json'
372
+ })),
373
+ state: context.codeMode.state ?? [],
374
+ engine: this.engineFactory,
375
+ limits: {
376
+ memoryBytes: limits.codeMemoryBytes,
377
+ stackBytes: limits.codeStackBytes,
378
+ timeoutMs: limits.codeTimeoutMs,
379
+ outputBytes: limits.codeMaxOutputBytes,
380
+ stateEntries: limits.codeStateMaxEntries,
381
+ stateMutations: limits.codeStateMaxMutations,
382
+ stateKeyBytes: limits.codeStateMaxKeyBytes,
383
+ stateValueBytes: limits.codeStateMaxValueBytes,
384
+ stateTotalBytes: limits.codeStateMaxTotalBytes,
385
+ sleepMs: limits.codeSleepMaxMs
386
+ }
387
+ });
328
388
  await exit;
329
389
  if (failure !== undefined)
330
390
  throw failure;
331
391
  if (!complete)
332
392
  throw new MarAgentError('MAR_AGENT_CODE_INCOMPLETE', 'Code Mode exited without a completion message.');
393
+ if (stateMutations.length)
394
+ await context.codeMode.commitState?.(stateMutations);
333
395
  return {
334
396
  content: text.join('\n') || 'Code Mode completed without emitted text.',
335
397
  data: { toolCalls: calls },
@@ -367,56 +429,3 @@ function parseImage(value) {
367
429
  throw invalid();
368
430
  return { mimeType: image.mimeType, dataBase64: image.dataBase64 };
369
431
  }
370
- // The cell runs only in a Host-managed child process, never in the credential-bearing runtime.
371
- const nodeCell = String.raw `
372
- import { createInterface } from 'node:readline';
373
- import { formatWithOptions } from 'node:util';
374
- const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
375
- const send = message => process.stdout.write(JSON.stringify(message) + '\n');
376
- const requests = new Map();
377
- let sequence = 0;
378
- const tools = new Proxy(Object.create(null), {
379
- get: (_, name) => name === 'then' ? undefined : arguments_ => new Promise((resolve, reject) => {
380
- const id = ++sequence;
381
- requests.set(id, { resolve, reject });
382
- send({ kind: 'call', id, name, arguments: arguments_ });
383
- })
384
- });
385
- const format = value => {
386
- if (typeof value === 'string') return value;
387
- try {
388
- const json = JSON.stringify(value);
389
- if (json !== undefined) return json;
390
- } catch {}
391
- return formatWithOptions({ colors: false }, value);
392
- };
393
- const text = value => send({ kind: 'text', value: format(value) });
394
- const image = value => send({ kind: 'image', value });
395
- for (const name of ['log', 'info', 'warn', 'error', 'debug'])
396
- console[name] = (...values) => text(formatWithOptions({ colors: false }, ...values));
397
- let started = false;
398
- lines.on('line', async line => {
399
- try {
400
- const frame = JSON.parse(line);
401
- if (!started) {
402
- started = true;
403
- const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
404
- try {
405
- await new AsyncFunction('tools', 'text', 'image', frame.code)(tools, text, image);
406
- await new Promise(resolve => process.stdout.write(JSON.stringify({ kind: 'done' }) + '\n', resolve));
407
- } catch (error) {
408
- await new Promise(resolve => process.stdout.write(JSON.stringify({
409
- kind: 'error', code: typeof error?.code === 'string' ? error.code : 'MAR_AGENT_CODE_FAILED',
410
- message: String(error?.message ?? error)
411
- }) + '\n', resolve));
412
- }
413
- } else {
414
- const request = requests.get(frame.id);
415
- if (!request) return;
416
- requests.delete(frame.id);
417
- if (frame.ok) request.resolve(frame.output);
418
- else request.reject(Object.assign(new Error(frame.error.message), { code: frame.error.code }));
419
- }
420
- } catch { process.exit(1); }
421
- });
422
- `;
@@ -5,6 +5,7 @@ import { MarAgentError } from '../error.js';
5
5
  import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
6
6
  export const execToolDefinition = {
7
7
  name: 'exec',
8
+ allowedCallers: ['direct', 'programmatic'],
8
9
  parallelSafety: 'safe',
9
10
  description: 'Unrestricted general system execution. Providing command starts any platform-native command, script, text/file tool, builder, generator, network client, or program in workspace/default cwd; action may be omitted for start. poll waits up to yieldMs for new output or termination and returns incremental stdout/stderr while running; after termination, poll returns a bounded cached result with earlier output for up to five minutes while the Session remains hosted. write sends stdin to running processes; cancel terminates a running process or removes a completed result. Returns status, processId, stdout/stderr, cumulative stdoutBytes/stderrBytes, per-result dropped bytes, exitCode, and truncated; a completed status does not imply exitCode 0. Always inspect stderr/exitCode and consume terminal output or cancel.',
10
11
  outputSchema: {
@@ -21,7 +21,15 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
21
21
  codeMaxTransportBytes: 67108864;
22
22
  codeMaxCalls: 128;
23
23
  codeMaxConcurrentCells: 4;
24
+ codeStateMaxEntries: 128;
25
+ codeStateMaxMutations: 128;
26
+ codeStateMaxKeyBytes: 256;
27
+ codeStateMaxValueBytes: 65536;
28
+ codeStateMaxTotalBytes: 1048576;
29
+ codeSleepMaxMs: 30000;
24
30
  codeHeapMegabytes: 128;
31
+ codeMemoryBytes: 33554432;
32
+ codeStackBytes: 524288;
25
33
  codeTimeoutMs: 120000;
26
34
  patchMaxBytes: 4194304;
27
35
  patchMaxFiles: 100;
@@ -21,7 +21,15 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
21
21
  codeMaxTransportBytes: 67_108_864,
22
22
  codeMaxCalls: 128,
23
23
  codeMaxConcurrentCells: 4,
24
+ codeStateMaxEntries: 128,
25
+ codeStateMaxMutations: 128,
26
+ codeStateMaxKeyBytes: 256,
27
+ codeStateMaxValueBytes: 65_536,
28
+ codeStateMaxTotalBytes: 1_048_576,
29
+ codeSleepMaxMs: 30_000,
24
30
  codeHeapMegabytes: 128,
31
+ codeMemoryBytes: 33_554_432,
32
+ codeStackBytes: 524_288,
25
33
  codeTimeoutMs: 120_000,
26
34
  patchMaxBytes: 4_194_304,
27
35
  patchMaxFiles: 100,
@@ -1,3 +1,4 @@
1
+ import type { CodeModeEngineFactory } from './code-mode/engine.js';
1
2
  import type { LocalProcessExecutor } from '../host/local-process-executor.js';
2
3
  import type { ToolCall, ToolExecutionContext, ToolExecutionResult } from '../sdk/types.js';
3
4
  import { type WorkspacePathMount } from './shared/path-resolver.js';
@@ -11,6 +12,7 @@ export declare class LocalToolRegistry {
11
12
  webFetch?: boolean;
12
13
  allowUntrustedTls?: boolean;
13
14
  processExecutor?: LocalProcessExecutor;
15
+ codeModeEngineFactory?: CodeModeEngineFactory;
14
16
  restrictPaths?: boolean;
15
17
  workspaceWritable?: boolean;
16
18
  pathMounts?: readonly WorkspacePathMount[];
@@ -23,7 +23,7 @@ export class LocalToolRegistry {
23
23
  const viewImage = new ViewImageTool(paths);
24
24
  const patch = new ApplyPatchTool(paths);
25
25
  this.#exec = new ExecTool(paths, undefined, undefined, environment, shell, web.processExecutor);
26
- this.#code = new CodeModeTool(paths, environment, web.processExecutor);
26
+ this.#code = new CodeModeTool(paths, environment, web.processExecutor, web.codeModeEngineFactory);
27
27
  this.#handlers = new Map([
28
28
  ['code_mode', (arguments_, context) => this.#code.execute(arguments_, context)],
29
29
  [
@@ -3,6 +3,7 @@ import { MarAgentError } from '../error.js';
3
3
  import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
4
4
  export const questionToolDefinition = {
5
5
  name: 'question',
6
+ allowedCallers: ['direct'],
6
7
  parallelSafety: 'serial',
7
8
  description: 'Pause for one to three tightly related user-owned decisions that materially change the result and cannot be inferred from the request, conversation, or relevant safe discovery. Prefer asking over guessing in Plan mode; do not avoid clarification by inventing configuration, branches, abstractions, or support for multiple outcomes. Provide 2-3 mutually exclusive options with consequences, put the recommended option first and identify it in its label or description, and use another question round if key ambiguity remains. This is not a permission/approval tool and should not interrupt routine in-scope work.',
8
9
  inputSchema: {
@@ -5,6 +5,7 @@ import { MarAgentError } from '../error.js';
5
5
  import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
6
6
  export const readToolDefinition = {
7
7
  name: 'read',
8
+ allowedCallers: ['direct', 'programmatic'],
8
9
  parallelSafety: 'safe',
9
10
  outputSchema: {
10
11
  type: 'object',
@@ -13,8 +13,10 @@ import { executeTodoWrite, todoWriteToolDefinition } from './todo-write.js';
13
13
  import { webFetchToolDefinition } from './web-fetch.js';
14
14
  import { webSearchToolDefinition } from './web-search.js';
15
15
  import { executeSkill, skillToolDefinition } from './skill.js';
16
+ import { executeToolSearch, toolSearchToolDefinition } from './tool-search.js';
16
17
  export const BUILTIN_TOOL_DEFINITIONS = [
17
18
  skillToolDefinition,
19
+ toolSearchToolDefinition,
18
20
  readToolDefinition,
19
21
  viewImageToolDefinition,
20
22
  execToolDefinition,
@@ -32,6 +34,7 @@ export const BUILTIN_TOOL_DEFINITIONS = [
32
34
  ];
33
35
  export const AGENT_TOOL_HANDLERS = new Map([
34
36
  ['skill', executeSkill],
37
+ ['tool_search', executeToolSearch],
35
38
  ['question', executeQuestion],
36
39
  ['todo_read', (_arguments, context) => executeTodoRead(context)],
37
40
  ['todo_write', executeTodoWrite],
@@ -47,5 +47,7 @@ export interface ToolRuntimeContext {
47
47
  codeMode?: import('../sdk/types.js').ToolExecutionContext['codeMode'];
48
48
  skills: ReadonlyMap<string, AgentSkill>;
49
49
  recentImages?: readonly ToolRuntimeImage[];
50
+ searchMcpTools?: (input: unknown, signal: AbortSignal) => Promise<import('../sdk/types.js').ToolExecutionResult>;
51
+ expectedMcpTool?: import('../sdk/types.js').McpToolCallContext['expectedTool'];
50
52
  emit(event: ToolRuntimeEvent): Promise<void>;
51
53
  }
@@ -10,7 +10,9 @@ export async function executeRegisteredTool(name, arguments_, callId, context) {
10
10
  throw new MarAgentError('MAR_AGENT_MCP_FAILED', 'MCP execution is unavailable.');
11
11
  return context.host.callMcpTool(call, context.signal, {
12
12
  sessionId: context.sessionId,
13
- executionId: context.executionId
13
+ executionId: context.executionId,
14
+ mode: context.mode,
15
+ ...(context.expectedMcpTool ? { expectedTool: context.expectedMcpTool } : {})
14
16
  });
15
17
  }
16
18
  return context.host.executeTool(call, {
@@ -2,6 +2,7 @@ import { MarAgentError } from '../error.js';
2
2
  import { agentSkillSource, loadAgentSkill } from '../runtime/instructions.js';
3
3
  export const skillToolDefinition = {
4
4
  name: 'skill',
5
+ allowedCallers: ['direct', 'programmatic'],
5
6
  parallelSafety: 'safe',
6
7
  description: 'Load a specialized Skill when the task matches one listed in the system prompt. Returns its instructions and source; file-based Skills also include a base directory and resource-path sample. Host-provided skills have no filesystem base. The name must exactly match the advertised catalog.',
7
8
  inputSchema: {