@myagentroam/agent 0.9.135 → 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.
- package/dist/cli/main.js +1 -1
- package/dist/host/local-agent-host.d.ts +9 -7
- package/dist/host/local-agent-host.js +77 -5
- package/dist/host/local-process-executor.d.ts +2 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/model/catalog-file.d.ts +2 -0
- package/dist/model/configuration.d.ts +5 -0
- package/dist/model/configuration.js +8 -0
- package/dist/model/contracts.d.ts +7 -0
- package/dist/model/openai-responses.d.ts +1 -1
- package/dist/model/openai-responses.js +85 -20
- package/dist/prompts/workflow.js +2 -0
- package/dist/runtime/context-gc-runtime.d.ts +1 -0
- package/dist/runtime/context-gc-runtime.js +3 -0
- package/dist/runtime/context-gc-tokens.d.ts +1 -0
- package/dist/runtime/context-gc-tokens.js +1 -1
- package/dist/runtime/context-gc.d.ts +1 -0
- package/dist/runtime/context-gc.js +85 -8
- package/dist/runtime/context-projection.js +19 -2
- package/dist/sandbox/linux/bubblewrap-sandbox-executor.js +29 -2
- package/dist/sdk/agent.js +208 -25
- package/dist/sdk/mcp-catalog.d.ts +13 -0
- package/dist/sdk/mcp-catalog.js +219 -0
- package/dist/sdk/types.d.ts +50 -1
- package/dist/session/context-gc-checkpoint.d.ts +7 -1
- package/dist/session/context-gc-checkpoint.js +28 -2
- package/dist/session/context-gc-replacements.d.ts +7 -0
- package/dist/session/context-gc-replacements.js +26 -0
- package/dist/session/jsonl-store.d.ts +6 -0
- package/dist/session/jsonl-store.js +92 -54
- package/dist/subagent/scheduler.d.ts +3 -3
- package/dist/subagent/session-controller.d.ts +1 -1
- package/dist/tools/agent-cancel.js +1 -0
- package/dist/tools/agent-message.js +1 -0
- package/dist/tools/agent-start.js +1 -0
- package/dist/tools/agent-wait.js +1 -0
- package/dist/tools/apply-patch.js +2 -1
- package/dist/tools/callers.d.ts +4 -0
- package/dist/tools/callers.js +18 -0
- package/dist/tools/code-mode/engine.d.ts +43 -0
- package/dist/tools/code-mode/engine.js +1 -0
- package/dist/tools/code-mode/quickjs-engine.d.ts +2 -0
- package/dist/tools/code-mode/quickjs-engine.js +454 -0
- package/dist/tools/code-mode/state.d.ts +20 -0
- package/dist/tools/code-mode/state.js +82 -0
- package/dist/tools/code-mode/worker.d.ts +1 -0
- package/dist/tools/code-mode/worker.js +65 -0
- package/dist/tools/code-mode.d.ts +5 -3
- package/dist/tools/code-mode.js +129 -120
- package/dist/tools/exec.js +1 -0
- package/dist/tools/execution-limits.d.ts +8 -0
- package/dist/tools/execution-limits.js +8 -0
- package/dist/tools/local-registry.d.ts +2 -0
- package/dist/tools/local-registry.js +1 -1
- package/dist/tools/question.js +1 -0
- package/dist/tools/read.js +1 -0
- package/dist/tools/registry.js +3 -0
- package/dist/tools/runtime-context.d.ts +2 -0
- package/dist/tools/runtime.js +3 -1
- package/dist/tools/skill.js +1 -0
- package/dist/tools/todo-read.js +1 -0
- package/dist/tools/todo-write.js +1 -0
- package/dist/tools/tool-search.d.ts +43 -0
- package/dist/tools/tool-search.js +278 -0
- package/dist/tools/view-image.js +1 -0
- package/dist/tools/web-fetch.js +1 -0
- package/dist/tools/web-search.js +1 -0
- 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
|
-
/**
|
|
7
|
-
export declare function
|
|
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
|
-
|
|
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
|
}
|
package/dist/tools/code-mode.js
CHANGED
|
@@ -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:
|
|
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
|
-
/**
|
|
21
|
-
export function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
|
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
|
-
`;
|
package/dist/tools/exec.js
CHANGED
|
@@ -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
|
[
|
package/dist/tools/question.js
CHANGED
|
@@ -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: {
|
package/dist/tools/read.js
CHANGED
|
@@ -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',
|
package/dist/tools/registry.js
CHANGED
|
@@ -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
|
}
|
package/dist/tools/runtime.js
CHANGED
|
@@ -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, {
|
package/dist/tools/skill.js
CHANGED
|
@@ -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: {
|