@myagentroam/agent 0.9.139 → 0.9.140
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/host/local-agent-host.d.ts +3 -1
- package/dist/host/local-agent-host.js +29 -3
- package/dist/prompts/resources.js +4 -2
- package/dist/prompts/workflow.js +2 -0
- package/dist/runtime/context-gc.js +25 -8
- package/dist/sdk/agent.js +40 -38
- package/dist/session/context-gc-checkpoint.d.ts +1 -1
- package/dist/session/context-gc-checkpoint.js +8 -5
- package/dist/session/context-gc-replacements.d.ts +1 -1
- package/dist/session/context-gc-replacements.js +5 -3
- package/dist/tools/agent-wait.js +1 -1
- package/dist/tools/apply-patch.js +1 -1
- package/dist/tools/callers.js +1 -1
- package/dist/tools/code-mode/cells.d.ts +7 -1
- package/dist/tools/code-mode/cells.js +32 -8
- package/dist/tools/code-mode/engine.d.ts +5 -2
- package/dist/tools/code-mode/quickjs-engine.js +106 -18
- package/dist/tools/code-mode/text-output.d.ts +21 -0
- package/dist/tools/code-mode/text-output.js +75 -0
- package/dist/tools/code-mode/worker.js +1 -1
- package/dist/tools/code-mode.js +36 -18
- package/dist/tools/exec.d.ts +3 -0
- package/dist/tools/exec.js +56 -15
- package/dist/tools/execution-limits.d.ts +4 -1
- package/dist/tools/execution-limits.js +4 -1
- package/dist/tools/local-registry.js +5 -5
- package/dist/tools/registry.js +2 -1
- package/dist/tools/skill.js +1 -1
- package/dist/tools/tool-search.js +5 -3
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ToolCall, ToolExecutionResult } from '../../sdk/types.js';
|
|
2
2
|
import type { CodeModeStateEntry, CodeModeStateMutation } from './state.js';
|
|
3
|
+
export type ProgrammaticToolResult = Omit<ToolExecutionResult, 'content'> & {
|
|
4
|
+
content?: string;
|
|
5
|
+
};
|
|
3
6
|
export interface CodeModeCatalogEntry {
|
|
4
7
|
readonly name: string;
|
|
5
8
|
readonly description: string;
|
|
@@ -26,8 +29,8 @@ export interface CodeModeEngine {
|
|
|
26
29
|
stateTotalBytes: number;
|
|
27
30
|
sleepMs: number;
|
|
28
31
|
};
|
|
29
|
-
onToolCall(call: ToolCall): Promise<
|
|
30
|
-
onText(value: string): void;
|
|
32
|
+
onToolCall(call: ToolCall): Promise<ProgrammaticToolResult>;
|
|
33
|
+
onText(value: string, originalBytes?: number): void;
|
|
31
34
|
onImage(value: unknown): void;
|
|
32
35
|
onYield(): void;
|
|
33
36
|
}): Promise<{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BoundedTextOutput } from './text-output.js';
|
|
1
2
|
// This trusted module runs only in the Host-managed worker, never in the model's JS context.
|
|
2
3
|
/** @returns {Promise<import('./engine.js').CodeModeEngine>} */
|
|
3
4
|
export async function createCodeModeEngine(options) {
|
|
@@ -27,6 +28,7 @@ export async function createCodeModeEngine(options) {
|
|
|
27
28
|
const state = new Map(input.state.map((entry) => [entry.key, entry.value]));
|
|
28
29
|
const stateMutations = [];
|
|
29
30
|
let outputBytes = 0;
|
|
31
|
+
let textCalls = 0;
|
|
30
32
|
const notify = () => {
|
|
31
33
|
wake?.();
|
|
32
34
|
wake = undefined;
|
|
@@ -69,16 +71,7 @@ export async function createCodeModeEngine(options) {
|
|
|
69
71
|
return item;
|
|
70
72
|
});
|
|
71
73
|
};
|
|
72
|
-
globalThis.__text =
|
|
73
|
-
if (value === null || ['string', 'number', 'boolean', 'bigint', 'undefined'].includes(typeof value))
|
|
74
|
-
return String(value);
|
|
75
|
-
return JSON.stringify(value, (_key, item) => {
|
|
76
|
-
if (['bigint', 'function', 'symbol', 'undefined'].includes(typeof item))
|
|
77
|
-
throw Object.assign(new TypeError('Only JSON values may be emitted.'),
|
|
78
|
-
{ code: 'MAR_AGENT_TOOL_INPUT_INVALID' });
|
|
79
|
-
return item;
|
|
80
|
-
});
|
|
81
|
-
};
|
|
74
|
+
globalThis.__text = ${streamText.toString()};
|
|
82
75
|
globalThis.__deepFreeze = function deepFreeze(value) {
|
|
83
76
|
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
84
77
|
for (const item of Object.values(value)) deepFreeze(item);
|
|
@@ -207,17 +200,32 @@ export async function createCodeModeEngine(options) {
|
|
|
207
200
|
consumeToolCall.dispose();
|
|
208
201
|
const out = vm.newObject();
|
|
209
202
|
method(out, 'text', (handle) => {
|
|
210
|
-
const
|
|
203
|
+
const separatorBytes = textCalls ? 1 : 0;
|
|
204
|
+
const capture = new BoundedTextOutput(Math.max(0, input.limits.outputBytes - outputBytes - separatorBytes), '');
|
|
205
|
+
const emit = vm.newFunction('textChunk', (chunk) => {
|
|
206
|
+
if (!chunk || vm.typeof(chunk) !== 'string')
|
|
207
|
+
return {
|
|
208
|
+
error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'Text output chunks must be strings.')
|
|
209
|
+
};
|
|
210
|
+
const length = vm.getProp(chunk, 'length');
|
|
211
|
+
const oversized = vm.getNumber(length) > 4096 * 6;
|
|
212
|
+
length.dispose();
|
|
213
|
+
if (oversized)
|
|
214
|
+
return {
|
|
215
|
+
error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Text serialization chunk limit exceeded.')
|
|
216
|
+
};
|
|
217
|
+
capture.append(vm.getString(chunk));
|
|
218
|
+
return vm.undefined;
|
|
219
|
+
});
|
|
220
|
+
const result = vm.callFunction(formatText, vm.undefined, handle ?? vm.undefined, emit);
|
|
221
|
+
emit.dispose();
|
|
211
222
|
if (result.error)
|
|
212
223
|
return { error: result.error };
|
|
213
224
|
try {
|
|
214
|
-
const
|
|
215
|
-
outputBytes +=
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Text output limit exceeded.')
|
|
219
|
-
};
|
|
220
|
-
input.onText(value);
|
|
225
|
+
const captured = capture.snapshot();
|
|
226
|
+
outputBytes += captured.retainedBytes + separatorBytes;
|
|
227
|
+
textCalls++;
|
|
228
|
+
input.onText(captured.text, captured.originalBytes);
|
|
221
229
|
}
|
|
222
230
|
finally {
|
|
223
231
|
result.value.dispose();
|
|
@@ -453,6 +461,86 @@ export async function createCodeModeEngine(options) {
|
|
|
453
461
|
}
|
|
454
462
|
};
|
|
455
463
|
}
|
|
464
|
+
// Compiled inside the guest: stream bounded chunks, never build a complete serialized object.
|
|
465
|
+
function streamText(value, emit) {
|
|
466
|
+
'use strict';
|
|
467
|
+
const chunks = (text, quoted = false) => {
|
|
468
|
+
if (quoted)
|
|
469
|
+
emit('"');
|
|
470
|
+
for (let start = 0; start < text.length;) {
|
|
471
|
+
let end = Math.min(text.length, start + 4096);
|
|
472
|
+
if (end < text.length &&
|
|
473
|
+
text.charCodeAt(end - 1) >= 0xd800 &&
|
|
474
|
+
text.charCodeAt(end - 1) <= 0xdbff &&
|
|
475
|
+
text.charCodeAt(end) >= 0xdc00 &&
|
|
476
|
+
text.charCodeAt(end) <= 0xdfff)
|
|
477
|
+
end--;
|
|
478
|
+
const part = text.slice(start, end);
|
|
479
|
+
emit(quoted ? JSON.stringify(part).slice(1, -1) : part);
|
|
480
|
+
start = end;
|
|
481
|
+
}
|
|
482
|
+
if (quoted)
|
|
483
|
+
emit('"');
|
|
484
|
+
};
|
|
485
|
+
if (value === null ||
|
|
486
|
+
['string', 'number', 'boolean', 'bigint', 'undefined'].includes(typeof value)) {
|
|
487
|
+
chunks(String(value));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const ancestors = new Set();
|
|
491
|
+
const write = (item, key) => {
|
|
492
|
+
if (item && typeof item === 'object' && typeof item.toJSON === 'function')
|
|
493
|
+
item = item.toJSON(key);
|
|
494
|
+
if (['bigint', 'function', 'symbol', 'undefined'].includes(typeof item))
|
|
495
|
+
throw Object.assign(new TypeError('Only JSON values may be emitted.'), {
|
|
496
|
+
code: 'MAR_AGENT_TOOL_INPUT_INVALID'
|
|
497
|
+
});
|
|
498
|
+
if (item instanceof Number || item instanceof String || item instanceof Boolean)
|
|
499
|
+
item = item.valueOf();
|
|
500
|
+
if (item instanceof BigInt)
|
|
501
|
+
throw new TypeError('BigInt objects cannot be emitted.');
|
|
502
|
+
if (item === null) {
|
|
503
|
+
emit('null');
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (typeof item === 'string') {
|
|
507
|
+
chunks(item, true);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (typeof item === 'number' || typeof item === 'boolean') {
|
|
511
|
+
emit(JSON.stringify(item));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (ancestors.has(item))
|
|
515
|
+
throw new TypeError('Cyclic values cannot be emitted.');
|
|
516
|
+
ancestors.add(item);
|
|
517
|
+
if (Array.isArray(item)) {
|
|
518
|
+
emit('[');
|
|
519
|
+
const length = item.length;
|
|
520
|
+
for (let index = 0; index < length; index++) {
|
|
521
|
+
if (index)
|
|
522
|
+
emit(',');
|
|
523
|
+
write(item[index], String(index));
|
|
524
|
+
}
|
|
525
|
+
emit(']');
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
emit('{');
|
|
529
|
+
let first = true;
|
|
530
|
+
for (const name of Object.keys(item)) {
|
|
531
|
+
if (!first)
|
|
532
|
+
emit(',');
|
|
533
|
+
first = false;
|
|
534
|
+
chunks(name, true);
|
|
535
|
+
emit(':');
|
|
536
|
+
write(item[name], name);
|
|
537
|
+
}
|
|
538
|
+
emit('}');
|
|
539
|
+
}
|
|
540
|
+
ancestors.delete(item);
|
|
541
|
+
};
|
|
542
|
+
write(value, '');
|
|
543
|
+
}
|
|
456
544
|
function diagnostic(code, message) {
|
|
457
545
|
return Object.assign(new Error(message), { code });
|
|
458
546
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function renderCapturedText(snapshot: any, imageCount?: number): string;
|
|
2
|
+
/** Bounded UTF-8 capture shared by trusted engines, the bridge and retained cells. */
|
|
3
|
+
export class BoundedTextOutput {
|
|
4
|
+
constructor(maximumBytes: any, separator?: string);
|
|
5
|
+
maximumBytes: any;
|
|
6
|
+
separator: string;
|
|
7
|
+
append(value: any, originalBytes?: number): void;
|
|
8
|
+
snapshot(): {
|
|
9
|
+
text: string;
|
|
10
|
+
originalBytes: number;
|
|
11
|
+
retainedBytes: number;
|
|
12
|
+
droppedBytes: number;
|
|
13
|
+
};
|
|
14
|
+
drain(): {
|
|
15
|
+
text: string;
|
|
16
|
+
originalBytes: number;
|
|
17
|
+
retainedBytes: number;
|
|
18
|
+
droppedBytes: number;
|
|
19
|
+
};
|
|
20
|
+
#private;
|
|
21
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Bounded UTF-8 capture shared by trusted engines, the bridge and retained cells. */
|
|
2
|
+
export class BoundedTextOutput {
|
|
3
|
+
#chunks = [];
|
|
4
|
+
#used = 0;
|
|
5
|
+
#original = 0;
|
|
6
|
+
#retained = 0;
|
|
7
|
+
#hasValue = false;
|
|
8
|
+
#sealed = false;
|
|
9
|
+
constructor(maximumBytes, separator = '\n') {
|
|
10
|
+
this.maximumBytes = maximumBytes;
|
|
11
|
+
this.separator = separator;
|
|
12
|
+
}
|
|
13
|
+
append(value, originalBytes = Buffer.byteLength(value)) {
|
|
14
|
+
const separator = this.#hasValue ? this.separator : '';
|
|
15
|
+
this.#hasValue = true;
|
|
16
|
+
this.#original += Buffer.byteLength(separator) + originalBytes;
|
|
17
|
+
this.#capture(separator);
|
|
18
|
+
this.#capture(value);
|
|
19
|
+
if (originalBytes > Buffer.byteLength(value))
|
|
20
|
+
this.#sealed = true;
|
|
21
|
+
}
|
|
22
|
+
#capture(value) {
|
|
23
|
+
const remaining = Math.max(0, this.maximumBytes - this.#used);
|
|
24
|
+
if (this.#sealed || !value.length)
|
|
25
|
+
return;
|
|
26
|
+
if (!remaining) {
|
|
27
|
+
this.#sealed = true;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Slice UTF-16 first so even a large injected-engine string never creates an unbounded Buffer.
|
|
31
|
+
let end = Math.min(value.length, remaining);
|
|
32
|
+
if (end < value.length &&
|
|
33
|
+
end > 0 &&
|
|
34
|
+
value.charCodeAt(end - 1) >= 0xd800 &&
|
|
35
|
+
value.charCodeAt(end - 1) <= 0xdbff &&
|
|
36
|
+
value.charCodeAt(end) >= 0xdc00 &&
|
|
37
|
+
value.charCodeAt(end) <= 0xdfff)
|
|
38
|
+
end--;
|
|
39
|
+
const bytes = Buffer.from(value.slice(0, end));
|
|
40
|
+
let length = Math.min(bytes.length, remaining);
|
|
41
|
+
while (length > 0 && length < bytes.length && (bytes[length] & 0xc0) === 0x80)
|
|
42
|
+
length--;
|
|
43
|
+
if (length < Buffer.byteLength(value))
|
|
44
|
+
this.#sealed = true;
|
|
45
|
+
if (!length)
|
|
46
|
+
return;
|
|
47
|
+
this.#chunks.push(bytes.toString('utf8', 0, length));
|
|
48
|
+
this.#used += length;
|
|
49
|
+
this.#retained += length;
|
|
50
|
+
}
|
|
51
|
+
snapshot() {
|
|
52
|
+
return {
|
|
53
|
+
text: this.#chunks.join(''),
|
|
54
|
+
originalBytes: this.#original,
|
|
55
|
+
retainedBytes: this.#retained,
|
|
56
|
+
droppedBytes: this.#original - this.#retained
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
drain() {
|
|
60
|
+
const snapshot = this.snapshot();
|
|
61
|
+
this.#chunks = [];
|
|
62
|
+
this.#original = 0;
|
|
63
|
+
this.#retained = 0;
|
|
64
|
+
this.#hasValue = false;
|
|
65
|
+
// Consuming output never replenishes the cell's capture budget.
|
|
66
|
+
return snapshot;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function renderCapturedText(snapshot, imageCount = 0) {
|
|
70
|
+
const text = snapshot.droppedBytes > 0
|
|
71
|
+
? `${snapshot.text}\n...[${snapshot.droppedBytes} text bytes omitted; original output: ${snapshot.originalBytes} bytes]...`
|
|
72
|
+
: snapshot.text;
|
|
73
|
+
const imageSummary = imageCount > 0 ? `Code Exec emitted ${imageCount} image${imageCount === 1 ? '' : 's'}.` : '';
|
|
74
|
+
return [text, imageSummary].filter(Boolean).join('\n');
|
|
75
|
+
}
|
|
@@ -31,7 +31,7 @@ lines.on('line', async (line) => {
|
|
|
31
31
|
state: frame.state,
|
|
32
32
|
limits: frame.limits,
|
|
33
33
|
signal: controller.signal,
|
|
34
|
-
onText: (value) => send({ kind: 'text', value }),
|
|
34
|
+
onText: (value, originalBytes) => send({ kind: 'text', value, originalBytes }),
|
|
35
35
|
onImage: (value) => send({ kind: 'image', value }),
|
|
36
36
|
onYield: () => send({ kind: 'yield' }),
|
|
37
37
|
onToolCall: (call) => new Promise((resolve, reject) => {
|
package/dist/tools/code-mode.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createToolCallScheduler } from './scheduler.js';
|
|
|
12
12
|
import { detectImageMime } from './view-image.js';
|
|
13
13
|
import { allowsDirectTool, allowsToolCaller } from './callers.js';
|
|
14
14
|
import { CodeCells } from './code-mode/cells.js';
|
|
15
|
+
import { BoundedTextOutput, renderCapturedText } from './code-mode/text-output.js';
|
|
15
16
|
import { parseCodeModeStateMutations } from './code-mode/state.js';
|
|
16
17
|
export const codeModeToolDefinition = {
|
|
17
18
|
name: 'code_exec',
|
|
@@ -20,25 +21,25 @@ export const codeModeToolDefinition = {
|
|
|
20
21
|
- Use bounded combinations, parallel independent calls, and filtering of large intermediate results to reduce model round trips.
|
|
21
22
|
- 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.
|
|
22
23
|
- 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.
|
|
23
|
-
- 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
|
|
24
|
+
- 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 to start managed processes when permitted; use tools.exec_wait afterward to wait, write stdin or cancel.
|
|
24
25
|
|
|
25
26
|
Tool calls:
|
|
26
27
|
- 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.
|
|
27
|
-
- Results use the MAR envelope: { content
|
|
28
|
-
- 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.
|
|
29
|
-
- tool_search, question, code_exec, code_exec_wait 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
|
+
- Results use the MAR envelope: { content?: string, data?: unknown, images?: Array<{mimeType:string,dataBase64:string}>, truncated?: boolean }. Redundant JSON content is omitted when it exactly renders data; independent text remains in content. Prefer data for programmatic results: result.data.results[0].text for read source text and result.data.exitCode/result.data.stdout for exec. content, when present, is rendered text, not raw file bytes. Use only fields documented by that tool; do not invent success or ok flags.
|
|
29
|
+
- Tool errors reject with code/message and may be caught. A failed cell preserves already emitted output alongside its status and error; inspect it before retrying completed work. 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.
|
|
30
|
+
- skill, tool_search, question, code_exec, code_exec_wait and direct-only tools cannot be called here. Load Skills directly and read their instructions before the next tool call. Use direct tool_search before a later code cell for deferred tools; the current cell's tools do not change.
|
|
30
31
|
|
|
31
32
|
Built-in namespaces:
|
|
32
|
-
- 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.
|
|
33
|
+
- out.text(value): void emits text. Strings and scalar values are stringified; JSON-compatible objects/arrays are serialized as compact JSON. Excess ordinary text is truncated with omission metadata, without stopping later code or tools. Cyclic or unsupported object values fail. Only explicitly emitted output reaches the model.
|
|
33
34
|
- 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.
|
|
34
35
|
- 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.
|
|
35
36
|
- 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.
|
|
36
37
|
- await cell.sleep(ms) performs a cancellable Host-side delay up to 30000 ms and remains subject to the cell deadline.
|
|
37
38
|
- await cell.yield_control() returns accumulated output to the model immediately while this cell continues running.
|
|
38
|
-
- Optionally start source with // @options: {"yieldMs":
|
|
39
|
+
- Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}, "maxOutputTokens": 10000}. yieldMs defaults to ${limits.codeDefaultYieldMs} ms and is a wait window (0..${limits.codeMaxYieldMs}), not a cell timeout. Explicit values are preserved and do not change nested tools' wait windows. A running result includes cellId; use direct code_exec_wait for new output, completion or termination.
|
|
39
40
|
- Registered cells are Session warm resources: ending or cancelling the model Execution cancels its wait, not the cell. Reuse retained cellId; do not start duplicate work. Host cleanup or explicit termination stops the cell.
|
|
40
41
|
- tools, out, state, catalog and cell are frozen bindings. Do not shadow their names.
|
|
41
|
-
- Input
|
|
42
|
+
- Input max 1 MiB; cumulative text capture max 1 MiB with truncation; 128 calls, 120 seconds. Tool-specific limits apply before explicit text capture. Protocol, image, memory and time limits remain hard failures. Failures do not roll back completed tool work; do not replay successful operations blindly.
|
|
42
43
|
|
|
43
44
|
Examples (copy the JavaScript source, not the fences; use only tools available in the current directory):
|
|
44
45
|
Independent reads, emitting only source text:
|
|
@@ -74,7 +75,13 @@ export const codeExecWaitToolDefinition = {
|
|
|
74
75
|
type: 'object',
|
|
75
76
|
properties: {
|
|
76
77
|
cellId: { type: 'string' },
|
|
77
|
-
yieldMs: {
|
|
78
|
+
yieldMs: {
|
|
79
|
+
type: 'integer',
|
|
80
|
+
minimum: 0,
|
|
81
|
+
maximum: limits.codeMaxWaitYieldMs,
|
|
82
|
+
default: limits.codeDefaultWaitYieldMs,
|
|
83
|
+
description: `Wait up to this many milliseconds. Defaults to ${limits.codeDefaultWaitYieldMs}; maximum ${limits.codeMaxWaitYieldMs}. Zero returns immediately without stopping the cell. This does not extend the cell execution deadline.`
|
|
84
|
+
},
|
|
78
85
|
maxOutputTokens: { type: 'integer', minimum: 1, maximum: limits.codeMaxOutputTokens },
|
|
79
86
|
action: {
|
|
80
87
|
type: 'string',
|
|
@@ -259,9 +266,8 @@ export class CodeModeTool {
|
|
|
259
266
|
owner: { sessionId: context.sessionId, executionId: context.executionId },
|
|
260
267
|
signal: context.signal
|
|
261
268
|
});
|
|
262
|
-
const text =
|
|
269
|
+
const text = new BoundedTextOutput(limits.codeMaxOutputBytes);
|
|
263
270
|
const images = [];
|
|
264
|
-
let outputBytes = 0;
|
|
265
271
|
let transportBytes = 0;
|
|
266
272
|
let calls = 0;
|
|
267
273
|
let complete = false;
|
|
@@ -312,11 +318,12 @@ export class CodeModeTool {
|
|
|
312
318
|
if (complete)
|
|
313
319
|
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent data after completion.');
|
|
314
320
|
if (frame.kind === 'text' && typeof frame.value === 'string') {
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
321
|
+
const originalBytes = frame.originalBytes ?? Buffer.byteLength(frame.value);
|
|
322
|
+
if (!Number.isSafeInteger(originalBytes) ||
|
|
323
|
+
originalBytes < Buffer.byteLength(frame.value))
|
|
324
|
+
throw new MarAgentError('MAR_AGENT_CODE_PROTOCOL_ERROR', 'Code Mode sent invalid text byte counts.');
|
|
325
|
+
text.append(frame.value, originalBytes);
|
|
326
|
+
output?.text(frame.value, originalBytes);
|
|
320
327
|
}
|
|
321
328
|
else if (frame.kind === 'image') {
|
|
322
329
|
if (!context.codeMode?.supportsImages)
|
|
@@ -346,7 +353,11 @@ export class CodeModeTool {
|
|
|
346
353
|
arguments: frame.arguments,
|
|
347
354
|
callId: randomUUID()
|
|
348
355
|
});
|
|
349
|
-
|
|
356
|
+
const { content, ...structured } = output;
|
|
357
|
+
const projected = output.data !== undefined && content === JSON.stringify(output.data)
|
|
358
|
+
? structured
|
|
359
|
+
: output;
|
|
360
|
+
await write({ id, ok: true, output: projected });
|
|
350
361
|
}
|
|
351
362
|
catch (error) {
|
|
352
363
|
if (context.signal.aborted)
|
|
@@ -458,9 +469,16 @@ export class CodeModeTool {
|
|
|
458
469
|
throw new MarAgentError('MAR_AGENT_CODE_INCOMPLETE', 'Code Mode exited without a completion message.');
|
|
459
470
|
if (stateMutations.length)
|
|
460
471
|
await context.codeMode.commitState?.(stateMutations);
|
|
472
|
+
const captured = text.snapshot();
|
|
461
473
|
return {
|
|
462
|
-
content:
|
|
463
|
-
|
|
474
|
+
content: renderCapturedText(captured, images.length) ||
|
|
475
|
+
'Code Mode completed without emitted text.',
|
|
476
|
+
data: {
|
|
477
|
+
toolCalls: calls,
|
|
478
|
+
outputBytes: captured.originalBytes,
|
|
479
|
+
outputDroppedBytes: captured.droppedBytes
|
|
480
|
+
},
|
|
481
|
+
truncated: captured.droppedBytes > 0,
|
|
464
482
|
...(images.length ? { images } : {})
|
|
465
483
|
};
|
|
466
484
|
}
|
package/dist/tools/exec.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ export interface ExecResult {
|
|
|
34
34
|
cached?: boolean;
|
|
35
35
|
}
|
|
36
36
|
export declare const execToolDefinition: ClientToolDefinition;
|
|
37
|
+
export declare const execWaitToolDefinition: ClientToolDefinition;
|
|
38
|
+
/** Validate the public entry points before delegating to the single process manager. */
|
|
39
|
+
export declare function execToolInput(name: 'exec' | 'exec_wait', value: unknown): ExecInput;
|
|
37
40
|
export declare class ExecTool {
|
|
38
41
|
#private;
|
|
39
42
|
private readonly paths;
|
package/dist/tools/exec.js
CHANGED
|
@@ -7,7 +7,7 @@ export const execToolDefinition = {
|
|
|
7
7
|
name: 'exec',
|
|
8
8
|
allowedCallers: ['direct', 'programmatic'],
|
|
9
9
|
parallelSafety: 'safe',
|
|
10
|
-
description: '
|
|
10
|
+
description: 'Start a platform-native command, script, or program in workspace/default cwd. Returns a stable processId, status, bounded stdout/stderr, cumulative byte counts, dropped-byte counts, exitCode and truncated. A completed status does not imply exitCode 0; inspect stderr/exitCode. For subsequent waiting, stdin writes or cancellation, use exec_wait with processId, not exec. Truncation does not stop the process. Cancelling the initial wait does not terminate the process.',
|
|
11
11
|
outputSchema: {
|
|
12
12
|
type: 'object',
|
|
13
13
|
required: ['content', 'data'],
|
|
@@ -44,13 +44,9 @@ export const execToolDefinition = {
|
|
|
44
44
|
inputSchema: {
|
|
45
45
|
type: 'object',
|
|
46
46
|
properties: {
|
|
47
|
-
action: {
|
|
48
|
-
enum: ['start', 'poll', 'write', 'cancel'],
|
|
49
|
-
description: 'Lifecycle operation. Omit when providing command to start a process.'
|
|
50
|
-
},
|
|
51
47
|
command: {
|
|
52
48
|
type: 'string',
|
|
53
|
-
description: '
|
|
49
|
+
description: 'Platform-native command to start.'
|
|
54
50
|
},
|
|
55
51
|
cwd: {
|
|
56
52
|
type: 'string',
|
|
@@ -62,15 +58,58 @@ export const execToolDefinition = {
|
|
|
62
58
|
minimum: 0,
|
|
63
59
|
maximum: TOOL_EXECUTION_LIMITS.execMaxYieldMs,
|
|
64
60
|
default: TOOL_EXECUTION_LIMITS.execDefaultYieldMs,
|
|
65
|
-
description: `Wait before yielding output. Defaults to ${TOOL_EXECUTION_LIMITS.execDefaultYieldMs} ms
|
|
61
|
+
description: `Wait before yielding output. Defaults to ${TOOL_EXECUTION_LIMITS.execDefaultYieldMs} ms. Maximum ${TOOL_EXECUTION_LIMITS.execMaxYieldMs} ms. Zero returns immediately. This does not extend the process timeout.`
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ['command'],
|
|
65
|
+
additionalProperties: false
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
export const execWaitToolDefinition = {
|
|
69
|
+
name: 'exec_wait',
|
|
70
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
71
|
+
parallelSafety: 'safe',
|
|
72
|
+
description: 'Wait for or control a managed process by processId. action defaults to poll: wait for new output or termination and return incremental stdout/stderr. write sends data to stdin; cancel terminates the process or removes its cached result. Terminal polls return a bounded snapshot marked cached for up to five minutes. Cancelling a wait does not terminate the process. Uses the same Session-owned processes as exec; cannot start a process or extend its timeout. Inspect stderr/exitCode; completed does not imply success. Supports direct and programmatic calls; in Code Mode use tools.exec_wait. The cell execution deadline still bounds waits made inside a cell.',
|
|
73
|
+
outputSchema: execToolDefinition.outputSchema,
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
processId: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
minLength: 1,
|
|
80
|
+
description: 'Stable processId returned by exec.'
|
|
66
81
|
},
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
action: { enum: ['poll', 'write', 'cancel'], default: 'poll' },
|
|
83
|
+
yieldMs: {
|
|
84
|
+
type: 'integer',
|
|
85
|
+
minimum: 0,
|
|
86
|
+
maximum: TOOL_EXECUTION_LIMITS.execMaxPollYieldMs,
|
|
87
|
+
default: TOOL_EXECUTION_LIMITS.execDefaultYieldMs,
|
|
88
|
+
description: `Poll wait window in milliseconds; default ${TOOL_EXECUTION_LIMITS.execDefaultYieldMs}, maximum ${TOOL_EXECUTION_LIMITS.execMaxPollYieldMs}. Zero returns immediately. Not a process timeout.`
|
|
89
|
+
},
|
|
90
|
+
data: { type: 'string', description: 'Required stdin data for write.' }
|
|
69
91
|
},
|
|
70
|
-
required: [],
|
|
92
|
+
required: ['processId'],
|
|
71
93
|
additionalProperties: false
|
|
72
94
|
}
|
|
73
95
|
};
|
|
96
|
+
/** Validate the public entry points before delegating to the single process manager. */
|
|
97
|
+
export function execToolInput(name, value) {
|
|
98
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
99
|
+
return invalidExecInput();
|
|
100
|
+
const input = value;
|
|
101
|
+
const allowed = name === 'exec'
|
|
102
|
+
? ['command', 'cwd', 'timeoutMs', 'yieldMs']
|
|
103
|
+
: ['processId', 'action', 'yieldMs', 'data'];
|
|
104
|
+
if (Object.keys(input).some((key) => !allowed.includes(key)))
|
|
105
|
+
return invalidExecInput();
|
|
106
|
+
if (name === 'exec')
|
|
107
|
+
return parseExecInput(input);
|
|
108
|
+
const action = input.action ?? 'poll';
|
|
109
|
+
if (!['poll', 'write', 'cancel'].includes(String(action)))
|
|
110
|
+
return invalidExecInput();
|
|
111
|
+
return parseExecInput({ ...input, action });
|
|
112
|
+
}
|
|
74
113
|
export class ExecTool {
|
|
75
114
|
paths;
|
|
76
115
|
maxOutputBytes;
|
|
@@ -108,7 +147,7 @@ export class ExecTool {
|
|
|
108
147
|
if (!process || (owner !== undefined && process.ownerSessionId !== owner.sessionId))
|
|
109
148
|
throw new MarAgentError('MAR_AGENT_PROCESS_NOT_FOUND', 'Managed process was not found.');
|
|
110
149
|
if (input.action === 'poll' && input.yieldMs)
|
|
111
|
-
await waitForProcessChange(process, Math.min(Math.max(input.yieldMs, 0), TOOL_EXECUTION_LIMITS.
|
|
150
|
+
await waitForProcessChange(process, Math.min(Math.max(input.yieldMs, 0), TOOL_EXECUTION_LIMITS.execMaxPollYieldMs), signal);
|
|
112
151
|
if (input.action === 'write')
|
|
113
152
|
process.child.stdin.write(input.data);
|
|
114
153
|
if (input.action === 'cancel')
|
|
@@ -403,11 +442,10 @@ function appendOutput(output, chunk, maximumBytes, receivedBytes = chunk.length)
|
|
|
403
442
|
const content = span.subarray(output.windowOffset, output.windowOffset + output.bytes);
|
|
404
443
|
output.chunks = [];
|
|
405
444
|
output.bytes = 0;
|
|
406
|
-
output.droppedBytes += overflow;
|
|
407
445
|
const pushSlice = (start, end) => {
|
|
408
446
|
while (start < end && (content[start] & 0xc0) === 0x80)
|
|
409
447
|
start++;
|
|
410
|
-
while (end > start && (content[end
|
|
448
|
+
while (end > start && end < content.length && (content[end] & 0xc0) === 0x80)
|
|
411
449
|
end--;
|
|
412
450
|
if (start >= end)
|
|
413
451
|
return;
|
|
@@ -417,6 +455,7 @@ function appendOutput(output, chunk, maximumBytes, receivedBytes = chunk.length)
|
|
|
417
455
|
};
|
|
418
456
|
pushSlice(0, Math.min(head, content.length));
|
|
419
457
|
pushSlice(Math.min(head + overflow, content.length), content.length);
|
|
458
|
+
output.droppedBytes += content.length - output.bytes;
|
|
420
459
|
output.window = span;
|
|
421
460
|
}
|
|
422
461
|
function consumeOutput(output) {
|
|
@@ -436,7 +475,9 @@ function parseExecInput(value) {
|
|
|
436
475
|
const action = input.action == null && typeof input.command === 'string' ? 'start' : input.action;
|
|
437
476
|
if (!['start', 'poll', 'write', 'cancel'].includes(String(action)))
|
|
438
477
|
return invalidExecInput();
|
|
439
|
-
const yieldMs = optionalBoundedInteger(input.yieldMs, 0,
|
|
478
|
+
const yieldMs = optionalBoundedInteger(input.yieldMs, 0, action === 'poll'
|
|
479
|
+
? TOOL_EXECUTION_LIMITS.execMaxPollYieldMs
|
|
480
|
+
: TOOL_EXECUTION_LIMITS.execMaxYieldMs);
|
|
440
481
|
if (action === 'start') {
|
|
441
482
|
if (typeof input.command !== 'string' || input.command.trim() === '')
|
|
442
483
|
return invalidExecInput();
|
|
@@ -457,7 +498,7 @@ function parseExecInput(value) {
|
|
|
457
498
|
return {
|
|
458
499
|
action,
|
|
459
500
|
processId: input.processId,
|
|
460
|
-
|
|
501
|
+
yieldMs: yieldMs ?? TOOL_EXECUTION_LIMITS.execDefaultYieldMs
|
|
461
502
|
};
|
|
462
503
|
if (action === 'write') {
|
|
463
504
|
if (typeof input.data !== 'string')
|
|
@@ -10,6 +10,7 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
|
|
|
10
10
|
execMaxTimeoutMs: 3600000;
|
|
11
11
|
execDefaultYieldMs: 10000;
|
|
12
12
|
execMaxYieldMs: 30000;
|
|
13
|
+
execMaxPollYieldMs: 300000;
|
|
13
14
|
execTerminationGraceMs: 1000;
|
|
14
15
|
execDefaultMaxOutputBytes: 1000000;
|
|
15
16
|
maxProcesses: 8;
|
|
@@ -21,8 +22,10 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
|
|
|
21
22
|
codeMaxTransportBytes: 67108864;
|
|
22
23
|
codeMaxCalls: 128;
|
|
23
24
|
codeMaxConcurrentCells: 4;
|
|
24
|
-
codeDefaultYieldMs:
|
|
25
|
+
codeDefaultYieldMs: 30000;
|
|
26
|
+
codeDefaultWaitYieldMs: 10000;
|
|
25
27
|
codeMaxYieldMs: 30000;
|
|
28
|
+
codeMaxWaitYieldMs: 300000;
|
|
26
29
|
codeMaxOutputTokens: 10000;
|
|
27
30
|
codeCompletedRetentionMs: 300000;
|
|
28
31
|
codeMaxCompletedResults: 16;
|
|
@@ -10,6 +10,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
|
|
|
10
10
|
execMaxTimeoutMs: 3_600_000,
|
|
11
11
|
execDefaultYieldMs: 10_000,
|
|
12
12
|
execMaxYieldMs: 30_000,
|
|
13
|
+
execMaxPollYieldMs: 300_000,
|
|
13
14
|
execTerminationGraceMs: 1_000,
|
|
14
15
|
execDefaultMaxOutputBytes: 1_000_000,
|
|
15
16
|
maxProcesses: 8,
|
|
@@ -21,8 +22,10 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
|
|
|
21
22
|
codeMaxTransportBytes: 67_108_864,
|
|
22
23
|
codeMaxCalls: 128,
|
|
23
24
|
codeMaxConcurrentCells: 4,
|
|
24
|
-
codeDefaultYieldMs:
|
|
25
|
+
codeDefaultYieldMs: 30_000,
|
|
26
|
+
codeDefaultWaitYieldMs: 10_000,
|
|
25
27
|
codeMaxYieldMs: 30_000,
|
|
28
|
+
codeMaxWaitYieldMs: 300_000,
|
|
26
29
|
codeMaxOutputTokens: 10_000,
|
|
27
30
|
codeCompletedRetentionMs: 300_000,
|
|
28
31
|
codeMaxCompletedResults: 16,
|
|
@@ -2,7 +2,7 @@ import { MarAgentError } from '../error.js';
|
|
|
2
2
|
import { AGENT_EXECUTION_POLICY } from '../runtime/execution-policy.js';
|
|
3
3
|
import { modelTokenBudgetBytes } from '../runtime/token-budget.js';
|
|
4
4
|
import { ApplyPatchTool } from './apply-patch.js';
|
|
5
|
-
import { ExecTool } from './exec.js';
|
|
5
|
+
import { ExecTool, execToolInput } from './exec.js';
|
|
6
6
|
import { CodeModeTool } from './code-mode.js';
|
|
7
7
|
import { ReadTool } from './read.js';
|
|
8
8
|
import { WorkspacePathResolver } from './shared/path-resolver.js';
|
|
@@ -55,16 +55,16 @@ export class LocalToolRegistry {
|
|
|
55
55
|
return { content: JSON.stringify(result), data: result };
|
|
56
56
|
}
|
|
57
57
|
],
|
|
58
|
-
[
|
|
59
|
-
|
|
58
|
+
...['exec', 'exec_wait'].map((name) => [
|
|
59
|
+
name,
|
|
60
60
|
async (arguments_, context) => {
|
|
61
|
-
const result = await this.#exec.execute(arguments_, context.signal, {
|
|
61
|
+
const result = await this.#exec.execute(execToolInput(name, arguments_), context.signal, {
|
|
62
62
|
sessionId: context.sessionId,
|
|
63
63
|
executionId: context.executionId
|
|
64
64
|
});
|
|
65
65
|
return { content: JSON.stringify(result), data: result, truncated: result.truncated };
|
|
66
66
|
}
|
|
67
|
-
],
|
|
67
|
+
]),
|
|
68
68
|
...(web.webSearch
|
|
69
69
|
? [
|
|
70
70
|
[
|
package/dist/tools/registry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyPatchToolDefinition } from './apply-patch.js';
|
|
2
|
-
import { execToolDefinition } from './exec.js';
|
|
2
|
+
import { execToolDefinition, execWaitToolDefinition } from './exec.js';
|
|
3
3
|
import { codeModeToolDefinition, codeExecWaitToolDefinition } from './code-mode.js';
|
|
4
4
|
import { executeQuestion, questionToolDefinition } from './question.js';
|
|
5
5
|
import { readToolDefinition } from './read.js';
|
|
@@ -20,6 +20,7 @@ export const BUILTIN_TOOL_DEFINITIONS = [
|
|
|
20
20
|
readToolDefinition,
|
|
21
21
|
viewImageToolDefinition,
|
|
22
22
|
execToolDefinition,
|
|
23
|
+
execWaitToolDefinition,
|
|
23
24
|
codeModeToolDefinition,
|
|
24
25
|
codeExecWaitToolDefinition,
|
|
25
26
|
applyPatchToolDefinition,
|