@myagentroam/agent 0.9.135 → 0.9.137

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 (72) hide show
  1. package/dist/cli/main.js +1 -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 +2 -0
  8. package/dist/model/configuration.d.ts +5 -0
  9. package/dist/model/configuration.js +8 -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 +85 -20
  13. package/dist/prompts/index.d.ts +2 -1
  14. package/dist/prompts/index.js +2 -2
  15. package/dist/prompts/workflow.d.ts +1 -1
  16. package/dist/prompts/workflow.js +9 -2
  17. package/dist/runtime/context-gc-runtime.d.ts +1 -0
  18. package/dist/runtime/context-gc-runtime.js +3 -0
  19. package/dist/runtime/context-gc-tokens.d.ts +1 -0
  20. package/dist/runtime/context-gc-tokens.js +1 -1
  21. package/dist/runtime/context-gc.d.ts +1 -0
  22. package/dist/runtime/context-gc.js +85 -8
  23. package/dist/runtime/context-projection.js +19 -2
  24. package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +29 -2
  25. package/dist/sdk/agent.js +249 -26
  26. package/dist/sdk/mcp-catalog.d.ts +13 -0
  27. package/dist/sdk/mcp-catalog.js +219 -0
  28. package/dist/sdk/types.d.ts +53 -1
  29. package/dist/session/context-gc-checkpoint.d.ts +7 -1
  30. package/dist/session/context-gc-checkpoint.js +28 -2
  31. package/dist/session/context-gc-replacements.d.ts +7 -0
  32. package/dist/session/context-gc-replacements.js +26 -0
  33. package/dist/session/jsonl-store.d.ts +6 -0
  34. package/dist/session/jsonl-store.js +92 -54
  35. package/dist/subagent/scheduler.d.ts +3 -3
  36. package/dist/subagent/session-controller.d.ts +1 -1
  37. package/dist/tools/agent-cancel.js +1 -0
  38. package/dist/tools/agent-message.js +1 -0
  39. package/dist/tools/agent-start.js +1 -0
  40. package/dist/tools/agent-wait.js +1 -0
  41. package/dist/tools/apply-patch.js +2 -1
  42. package/dist/tools/callers.d.ts +6 -0
  43. package/dist/tools/callers.js +23 -0
  44. package/dist/tools/code-mode/engine.d.ts +43 -0
  45. package/dist/tools/code-mode/engine.js +1 -0
  46. package/dist/tools/code-mode/quickjs-engine.d.ts +2 -0
  47. package/dist/tools/code-mode/quickjs-engine.js +454 -0
  48. package/dist/tools/code-mode/state.d.ts +20 -0
  49. package/dist/tools/code-mode/state.js +82 -0
  50. package/dist/tools/code-mode/worker.d.ts +1 -0
  51. package/dist/tools/code-mode/worker.js +65 -0
  52. package/dist/tools/code-mode.d.ts +8 -4
  53. package/dist/tools/code-mode.js +154 -120
  54. package/dist/tools/exec.js +1 -0
  55. package/dist/tools/execution-limits.d.ts +8 -0
  56. package/dist/tools/execution-limits.js +8 -0
  57. package/dist/tools/local-registry.d.ts +2 -0
  58. package/dist/tools/local-registry.js +1 -1
  59. package/dist/tools/question.js +1 -0
  60. package/dist/tools/read.js +1 -0
  61. package/dist/tools/registry.js +3 -0
  62. package/dist/tools/runtime-context.d.ts +2 -0
  63. package/dist/tools/runtime.js +3 -1
  64. package/dist/tools/skill.js +1 -0
  65. package/dist/tools/todo-read.js +1 -0
  66. package/dist/tools/todo-write.js +1 -0
  67. package/dist/tools/tool-search.d.ts +43 -0
  68. package/dist/tools/tool-search.js +284 -0
  69. package/dist/tools/view-image.js +1 -0
  70. package/dist/tools/web-fetch.js +1 -0
  71. package/dist/tools/web-search.js +1 -0
  72. package/package.json +3 -1
@@ -1,13 +1,58 @@
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 { allowsDirectTool, 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
+ - Code Mode Only: call the programmatic tools listed below through tools.NAME inside this tool, including single-tool operations. They are omitted from the top-level tool list. Direct-only tools remain separate model tools and cannot be called here.
19
+ - Use bounded combinations, parallel independent calls, and filtering of large intermediate results to reduce model round trips.
20
+ - 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.
21
+ - 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.
22
+ - 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.
23
+
24
+ Tool calls:
25
+ - 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.
26
+ - 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.
27
+ - 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.
28
+ - 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.
29
+
30
+ Built-in namespaces:
31
+ - 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.
32
+ - 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.
33
+ - 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.
34
+ - 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.
35
+ - await cell.sleep(ms) performs a cancellable Host-side delay up to 30000 ms and remains subject to the cell deadline.
36
+ - tools, out, state, catalog and cell are frozen bindings. Do not shadow their names.
37
+ - Input/text output max 1 MiB, 128 calls, 120 seconds. Failures do not roll back completed tool work; do not replay successful operations blindly.
38
+
39
+ Examples (copy the JavaScript source, not the fences; use only tools available in the current directory):
40
+ Independent reads, emitting only source text:
41
+ \`\`\`js
42
+ const [a, b] = await Promise.all([
43
+ tools.read({ reads: [{ path: "a.txt" }] }),
44
+ tools.read({ reads: [{ path: "b.txt" }] })
45
+ ]);
46
+ out.text({ a: a.data.results[0].text, b: b.data.results[0].text });
47
+ \`\`\`
48
+ Inspect before a dependent edit; successful await needs no invented success flag:
49
+ \`\`\`js
50
+ const inspected = await tools.read({ reads: [{ path: "source.txt" }] });
51
+ const sourceText = inspected.data.results[0].text;
52
+ if (!sourceText?.includes("original=1")) throw new Error("Unexpected source");
53
+ const patched = await tools.apply_patch("*** Begin Patch\\n*** Update File: source.txt\\n@@\\n original=1\\n+updated=2\\n*** End Patch");
54
+ out.text({ files: patched.data.files.length });
55
+ \`\`\``,
11
56
  inputMode: 'freeform',
12
57
  inputSchema: {
13
58
  type: 'object',
@@ -15,71 +60,79 @@ export const codeModeToolDefinition = {
15
60
  required: ['code'],
16
61
  additionalProperties: false
17
62
  },
63
+ allowedCallers: ['direct'],
18
64
  parallelSafety: 'serial'
19
65
  };
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));
66
+ /** Deferred definitions travel in search results, not in the stable Code Mode description. */
67
+ export function projectCodeModeTools(tools, residentTools = tools) {
68
+ const codeModeEnabled = tools.some((tool) => tool.name === 'code_mode' && allowsToolCaller(tool, 'direct'));
69
+ const direct = tools.filter((tool) => allowsDirectTool(tool, codeModeEnabled));
70
+ if (!codeModeEnabled)
71
+ return direct;
72
+ const programmatic = residentTools.filter((tool) => allowsToolCaller(tool, 'programmatic'));
73
+ const directory = JSON.stringify({
74
+ callableNames: programmatic.map((tool) => tool.name),
75
+ programmaticOnly: programmatic.map((tool) => ({
76
+ name: tool.name,
77
+ description: tool.description,
78
+ inputSchema: tool.inputSchema,
79
+ inputMode: tool.inputMode ?? 'json'
80
+ }))
81
+ });
82
+ if (Buffer.byteLength(directory) > 32 * 1024)
83
+ throw new MarAgentError('MAR_AGENT_TOOL_EXPOSURE_LIMIT', 'Programmatic tool directory is too large.');
84
+ return direct.map((tool) => tool.name === 'code_mode'
85
+ ? {
86
+ ...tool,
87
+ description: `${codeModeToolDefinition.description}\nDeferred MCP definitions are returned by direct tool_search and become callable from the next model response, not later in the same response; they are not appended to this description.\nProgrammatic tools (call only inside code_mode): ${directory}`
88
+ }
89
+ : tool);
46
90
  }
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';
91
+ /** Preserve native search definitions while providing the same discovery evidence to other protocols. */
92
+ export function projectCodeModeSearchResult(message, codeModeEnabled) {
93
+ if (!message.toolSearchTools)
94
+ return message;
95
+ if (!codeModeEnabled)
96
+ return {
97
+ ...message,
98
+ toolSearchTools: message.toolSearchTools.filter((tool) => allowsToolCaller(tool, 'direct'))
99
+ };
100
+ const tools = message.toolSearchTools.map((tool) => ({
101
+ name: tool.name,
102
+ description: allowsToolCaller(tool, 'programmatic')
103
+ ? `Call only as tools.${tool.name} inside code_mode, never as a direct model tool.\n${tool.description}`
104
+ : tool.description,
105
+ inputSchema: tool.inputSchema,
106
+ inputMode: tool.inputMode ?? 'json',
107
+ caller: allowsToolCaller(tool, 'programmatic') ? 'programmatic' : 'direct'
108
+ }));
109
+ return {
110
+ ...message,
111
+ content: JSON.stringify({ tools }),
112
+ toolSearchTools: tools.map(({ caller: _caller, ...tool }) => tool)
113
+ };
114
+ }
115
+ function defaultEngineFactory() {
116
+ const core = import.meta.resolve('quickjs-emscripten-core');
117
+ const variant = import.meta.resolve('@jitl/quickjs-wasmfile-release-sync');
118
+ const ffi = pathToFileURL(createRequire(core).resolve('@jitl/quickjs-ffi-types')).href;
119
+ const dependencyRoot = (url) => {
120
+ const root = dirname(dirname(fileURLToPath(url)));
121
+ // pnpm's sibling dependencies are symlinks; keep the package-local node_modules tree visible.
122
+ return root.includes(`${sep}.pnpm${sep}`) ? dirname(dirname(root)) : root;
123
+ };
124
+ return {
125
+ moduleUrl: new URL('./code-mode/quickjs-engine.js', import.meta.url).href,
126
+ runtimeDirectories: [
127
+ ...new Set([
128
+ fileURLToPath(new URL('./code-mode/', import.meta.url)),
129
+ dependencyRoot(core),
130
+ dependencyRoot(variant),
131
+ dependencyRoot(ffi)
132
+ ])
133
+ ],
134
+ options: { coreModuleUrl: core, variantModuleUrl: variant }
135
+ };
83
136
  }
84
137
  const environmentKeys = [
85
138
  'PATH',
@@ -96,11 +149,13 @@ export class CodeModeTool {
96
149
  paths;
97
150
  environment;
98
151
  executor;
152
+ engineFactory;
99
153
  #active = new Map();
100
- constructor(paths, environment = {}, executor = directLocalProcessExecutor) {
154
+ constructor(paths, environment = {}, executor = directLocalProcessExecutor, engineFactory = defaultEngineFactory()) {
101
155
  this.paths = paths;
102
156
  this.environment = environment;
103
157
  this.executor = executor;
158
+ this.engineFactory = engineFactory;
104
159
  }
105
160
  async execute(value, context) {
106
161
  const code = typeof value === 'string'
@@ -157,12 +212,13 @@ export class CodeModeTool {
157
212
  `--max-old-space-size=${limits.codeHeapMegabytes}`,
158
213
  '--input-type=module',
159
214
  '--eval',
160
- nodeCell
215
+ await readFile(new URL('./code-mode/worker.js', import.meta.url), 'utf8')
161
216
  ],
162
217
  cwd,
163
218
  shell: false,
164
219
  environment,
165
220
  environmentKeys: Object.keys(environment),
221
+ runtimeDirectories: this.engineFactory.runtimeDirectories,
166
222
  detached: process.platform !== 'win32',
167
223
  owner: { sessionId: context.sessionId, executionId: context.executionId },
168
224
  signal: context.signal
@@ -173,6 +229,7 @@ export class CodeModeTool {
173
229
  let transportBytes = 0;
174
230
  let calls = 0;
175
231
  let complete = false;
232
+ let stateMutations = [];
176
233
  let failure;
177
234
  let stderr = '';
178
235
  let buffered = '';
@@ -212,7 +269,9 @@ export class CodeModeTool {
212
269
  context.signal.throwIfAborted();
213
270
  return context.codeMode.invoke(call, context.signal);
214
271
  });
215
- const available = new Set(context.codeMode.tools.map((tool) => tool.name));
272
+ const available = new Set(context
273
+ .codeMode.tools.filter((tool) => allowsToolCaller(tool, 'programmatic'))
274
+ .map((tool) => tool.name));
216
275
  const receive = (frame) => {
217
276
  if (complete)
218
277
  throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent data after completion.');
@@ -266,6 +325,7 @@ export class CodeModeTool {
266
325
  // Child completion cannot undo Host tool effects from calls it did not await.
267
326
  if (pending.size)
268
327
  throw new MarAgentError('MAR_AGENT_CODE_INCOMPLETE', 'Code Mode returned with unawaited tool calls; completed operations are not rolled back.');
328
+ stateMutations = parseCodeModeStateMutations(frame.stateMutations);
269
329
  complete = true;
270
330
  void signalProcessTree(child, 'SIGKILL').catch(fail);
271
331
  }
@@ -324,12 +384,39 @@ export class CodeModeTool {
324
384
  if (context.signal.aborted)
325
385
  abort();
326
386
  else
327
- await write({ code });
387
+ await write({
388
+ code,
389
+ tools: [...available],
390
+ catalog: context
391
+ .codeMode.tools.filter((tool) => available.has(tool.name))
392
+ .map((tool) => ({
393
+ name: tool.name,
394
+ description: tool.description,
395
+ inputSchema: tool.inputSchema,
396
+ inputMode: tool.inputMode ?? 'json'
397
+ })),
398
+ state: context.codeMode.state ?? [],
399
+ engine: this.engineFactory,
400
+ limits: {
401
+ memoryBytes: limits.codeMemoryBytes,
402
+ stackBytes: limits.codeStackBytes,
403
+ timeoutMs: limits.codeTimeoutMs,
404
+ outputBytes: limits.codeMaxOutputBytes,
405
+ stateEntries: limits.codeStateMaxEntries,
406
+ stateMutations: limits.codeStateMaxMutations,
407
+ stateKeyBytes: limits.codeStateMaxKeyBytes,
408
+ stateValueBytes: limits.codeStateMaxValueBytes,
409
+ stateTotalBytes: limits.codeStateMaxTotalBytes,
410
+ sleepMs: limits.codeSleepMaxMs
411
+ }
412
+ });
328
413
  await exit;
329
414
  if (failure !== undefined)
330
415
  throw failure;
331
416
  if (!complete)
332
417
  throw new MarAgentError('MAR_AGENT_CODE_INCOMPLETE', 'Code Mode exited without a completion message.');
418
+ if (stateMutations.length)
419
+ await context.codeMode.commitState?.(stateMutations);
333
420
  return {
334
421
  content: text.join('\n') || 'Code Mode completed without emitted text.',
335
422
  data: { toolCalls: calls },
@@ -367,56 +454,3 @@ function parseImage(value) {
367
454
  throw invalid();
368
455
  return { mimeType: image.mimeType, dataBase64: image.dataBase64 };
369
456
  }
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: {
@@ -1,5 +1,6 @@
1
1
  export const todoReadToolDefinition = {
2
2
  name: 'todo_read',
3
+ allowedCallers: ['direct', 'programmatic'],
3
4
  parallelSafety: 'safe',
4
5
  description: 'Read this session’s current Todo revision and items. Use before updating when the revision may have changed. Do not read an empty or new Todo merely to decide whether straightforward bounded work needs one.',
5
6
  inputSchema: { type: 'object', properties: {}, additionalProperties: false }
@@ -2,6 +2,7 @@ import { MarAgentError } from '../error.js';
2
2
  import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
3
3
  export const todoWriteToolDefinition = {
4
4
  name: 'todo_write',
5
+ allowedCallers: ['direct', 'programmatic'],
5
6
  parallelSafety: 'serial',
6
7
  description: 'Replace this session’s entire Todo list using optimistic expectedRevision. Preserve stable IDs and statuses only when extending the same objective. For a materially different objective, include only the new objective’s items. Do not retain completed items from an earlier objective; when all current items are completed, default a substantive new request to replacement unless the user explicitly asks to continue, extend, verify, or follow up. Use only after Todo has been chosen for coordination-heavy work; straightforward bounded work does not need it. Keep at most one in_progress item and update statuses after meaningful stage changes. When another substantive tool call is already ready and does not depend on the Todo result, include both in the same model response instead of spending a response only for Todo bookkeeping.',
7
8
  inputSchema: {
@@ -0,0 +1,43 @@
1
+ import type { ClientToolDefinition } from '../model/contracts.js';
2
+ import type { McpCatalogTool, McpToolCatalog, ToolExecutionResult } from '../sdk/types.js';
3
+ import type { ToolRuntimeContext } from './runtime-context.js';
4
+ export declare const TOOL_SEARCH_LIMITS: Readonly<{
5
+ defaultLimit: 5;
6
+ maximumLimit: 20;
7
+ queryBytes: 4096;
8
+ catalogBytes: number;
9
+ maximumDefinitionTokens: 16384;
10
+ definitionWindowFraction: 0.25;
11
+ minimumContinuationTokens: 1024;
12
+ }>;
13
+ export declare const toolSearchToolDefinition: ClientToolDefinition;
14
+ export declare function parseToolSearchInput(input: unknown): {
15
+ query: string;
16
+ limit: number;
17
+ };
18
+ /** Deferred discovery state reconstructed from the current post-GC model history. */
19
+ export declare class DeferredMcpRegistry {
20
+ #private;
21
+ constructor(discoveredNames: readonly string[]);
22
+ restore(discoveredNames: readonly string[]): void;
23
+ update(catalog: McpToolCatalog, eligible: (tool: McpCatalogTool) => boolean): void;
24
+ get hasDeferred(): boolean;
25
+ snapshot(): readonly string[];
26
+ expectedTool(name: string): {
27
+ sourceIdentity: string;
28
+ definitionRevision: string;
29
+ } | undefined;
30
+ restoredDefinitions(names: readonly string[]): readonly ClientToolDefinition[];
31
+ projectDirect(base: readonly ClientToolDefinition[]): ClientToolDefinition[];
32
+ project(base: readonly ClientToolDefinition[], selection?: readonly string[], executionSelection?: Set<string>): ClientToolDefinition[];
33
+ search(query: string, limit: number): McpCatalogTool[];
34
+ expose(input: unknown, context: {
35
+ signal: AbortSignal;
36
+ base: readonly ClientToolDefinition[];
37
+ window: number;
38
+ system: string;
39
+ }): Promise<ToolExecutionResult>;
40
+ assertBudget(base: readonly ClientToolDefinition[], window: number, system: string, selection?: readonly string[], executionSelection?: ReadonlySet<string>): void;
41
+ navigation(): string | undefined;
42
+ }
43
+ export declare function executeToolSearch(input: unknown, context: ToolRuntimeContext): Promise<ToolExecutionResult>;