@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
|
@@ -125,7 +125,7 @@ export declare class SubagentScheduler implements SubagentController {
|
|
|
125
125
|
turnId?: string;
|
|
126
126
|
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
127
127
|
} | {
|
|
128
|
-
waitOutcome: "
|
|
128
|
+
waitOutcome: "completion" | "timeout";
|
|
129
129
|
agentId: string;
|
|
130
130
|
description: string;
|
|
131
131
|
status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
@@ -172,7 +172,7 @@ export declare class SubagentScheduler implements SubagentController {
|
|
|
172
172
|
turnId?: string;
|
|
173
173
|
deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
|
|
174
174
|
} | {
|
|
175
|
-
waitOutcome: "
|
|
175
|
+
waitOutcome: "completion" | "timeout";
|
|
176
176
|
agentId: string;
|
|
177
177
|
description: string;
|
|
178
178
|
status: "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
|
|
@@ -199,7 +199,7 @@ export declare class SubagentScheduler implements SubagentController {
|
|
|
199
199
|
message(_agentId: string, _message: string): Promise<SubagentTaskSnapshot>;
|
|
200
200
|
listSessionResources(): {
|
|
201
201
|
agentId: string;
|
|
202
|
-
status: "completed" | "cancelled" | "running" | "
|
|
202
|
+
status: "completed" | "cancelled" | "running" | "queued" | "failed";
|
|
203
203
|
description: string;
|
|
204
204
|
}[];
|
|
205
205
|
dispose(): Promise<void>;
|
|
@@ -91,7 +91,7 @@ export declare class SubagentSessionController implements SubagentController {
|
|
|
91
91
|
cancel(agentId: string): Promise<SubagentTaskSnapshot>;
|
|
92
92
|
listSessionResources(): {
|
|
93
93
|
agentId: string;
|
|
94
|
-
status: "completed" | "cancelled" | "running" | "
|
|
94
|
+
status: "completed" | "cancelled" | "running" | "queued" | "failed" | "interrupted";
|
|
95
95
|
description: string;
|
|
96
96
|
}[];
|
|
97
97
|
dispose(): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MarAgentError } from '../error.js';
|
|
2
2
|
export const agentCancelToolDefinition = {
|
|
3
3
|
name: 'agent_cancel',
|
|
4
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
4
5
|
parallelSafety: 'serial',
|
|
5
6
|
description: 'Idempotently cancel a child agent using the agentId returned by agent_start. For an exec processId, use exec with action:"cancel" instead. Waits for a running task to reach cancelled and returns its agentId and description.',
|
|
6
7
|
inputSchema: {
|
|
@@ -2,6 +2,7 @@ import { MarAgentError } from '../error.js';
|
|
|
2
2
|
import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
3
3
|
export const agentMessageToolDefinition = {
|
|
4
4
|
name: 'agent_message',
|
|
5
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
5
6
|
parallelSafety: 'serial',
|
|
6
7
|
description: 'Send a real user message to an existing child agent. Default delivery=append inserts guidance into a running child turn at the next safe boundary after the current model response or complete tool batch. Explicit delivery=replace immediately requests cancellation, waits for the old turn to close, then starts a new turn in the same Session. When idle, either mode starts a new turn using persisted history. Returns messageId, turnId, deliveryStatus, current status, and model metadata without repeating the child’s previous latestMessage, latestActivity, summary, or verification evidence. accepted means queued in memory; applied means persisted in model context, not that the guidance has been executed. Use agent_wait for subsequent progress or completion. Does not wait for the new task to complete or roll back existing side effects.',
|
|
7
8
|
inputSchema: {
|
|
@@ -3,6 +3,7 @@ import { marAgentReasoningEffortSchema } from '../model/configuration.js';
|
|
|
3
3
|
import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
4
4
|
export const agentStartToolDefinition = {
|
|
5
5
|
name: 'agent_start',
|
|
6
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
6
7
|
parallelSafety: 'serial',
|
|
7
8
|
description: 'Start one bounded, non-recursive child agent with self-contained context. Use when an independent investigation would consume substantial main-context space or when safe parallel work has real value; keep small or tightly coupled work in the main context. All agents share the filesystem, so avoid conflicting writes. The optional reasoningEffort overrides the parent effort and must be supported by the selected model; omit it to inherit. Returns agentId for agent_wait, agent_message, or agent_cancel, description, queued/running/completed/failed/cancelled status, bounded summary, changedFiles, verification, and read evidence; background returns immediately while foreground waits for a terminal result.',
|
|
8
9
|
inputSchema: {
|
package/dist/tools/agent-wait.js
CHANGED
|
@@ -2,6 +2,7 @@ import { MarAgentError } from '../error.js';
|
|
|
2
2
|
import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
3
3
|
export const agentWaitToolDefinition = {
|
|
4
4
|
name: 'agent_wait',
|
|
5
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
5
6
|
parallelSafety: 'serial',
|
|
6
7
|
description: 'Read or wait a bounded time for a child agent result. waitFor defaults to "completion"; use "message" only when a fresh public commentary/final message would change the next decision. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. When blocked on a live child result, prefer one 300000 ms completion wait rather than repeated short waits. Every result includes waitOutcome as snapshot, message, completion, or timeout plus the real agentId and description. A message, timeout with unread progress, or first terminal completion result includes the newly observed latest public commentary/final message; snapshots and repeated observations omit previously observed message text. latestActivity can still identify a public message or bounded tool started/completed/failed status. A queued or running result is not completion. A message wait consumes only the latest unread public message in the current Session runtime; tool activity does not wake it, and parent cancellation preserves unread progress for a later Execution. Do not use message waits as heartbeat checks or request progress merely to confirm that a child is still running. First terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims.',
|
|
7
8
|
inputSchema: {
|
|
@@ -6,8 +6,9 @@ import { MarAgentError } from '../error.js';
|
|
|
6
6
|
import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
7
7
|
export const applyPatchToolDefinition = {
|
|
8
8
|
name: 'apply_patch',
|
|
9
|
+
allowedCallers: ['direct', 'programmatic'],
|
|
9
10
|
parallelSafety: 'serial',
|
|
10
|
-
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>; context-only hunks may also be used as ordered anchors before a later changing hunk. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
|
|
11
|
+
description: `Preferred for precise, reviewable local edits. Do not batch all task files merely to reduce tool calls. Group only a few tightly coupled edits whose combined patch is quick to generate and review; split broad or independent work into successive bounded patches before composing them. Submit a raw *** Begin Patch / *** End Patch patch. Supports Add File, Update File, Delete File, and Move to. Add File may replace an existing regular file, and Move to may replace an existing regular destination; duplicate operations on the same resolved path remain invalid. Update hunks accept @@ or @@ <change context>; context-only hunks may also be used as ordered anchors before a later changing hunk. Context is searched forward with Codex-style exact, whitespace-tolerant, then common Unicode punctuation-tolerant matching. Syntax and resolved paths are validated before writing, then file units are committed in order. Each file unit is atomic, and a Move keeps its source and destination atomic. On MAR_AGENT_PATCH_PARTIAL, earlier files remain applied; the error lists applied, failed, and pending paths, so retry only failed and pending paths. Existing file permissions, line endings, and final-newline state are preserved. Returns changed file paths and operations with per-file and total line counts when all units succeed. In Code Mode, the Promise resolves on success and rejects with code/message on failure; the returned envelope has data.files and data.totals, not an ok or success flag. Oversized patches are rejected without advertising defensive limits as recommended patch sizes. A first-unit MAR_AGENT_PATCH_CONFLICT applies nothing; hunk match conflicts identify the affected path and hunk so you can reread only that file before retrying.`,
|
|
11
12
|
inputMode: 'freeform',
|
|
12
13
|
outputSchema: {
|
|
13
14
|
type: 'object',
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ClientToolDefinition, ToolCaller } from '../model/contracts.js';
|
|
2
|
+
/** Normalize trusted Host policy independently of discovery, Agent scope and parallel safety. */
|
|
3
|
+
export declare function toolCallers(tool: ClientToolDefinition): readonly ToolCaller[];
|
|
4
|
+
export declare function allowsToolCaller(tool: ClientToolDefinition, caller: ToolCaller): boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MarAgentError } from '../error.js';
|
|
2
|
+
/** Normalize trusted Host policy independently of discovery, Agent scope and parallel safety. */
|
|
3
|
+
export function toolCallers(tool) {
|
|
4
|
+
const value = tool.allowedCallers;
|
|
5
|
+
if (value === undefined)
|
|
6
|
+
return tool.name.startsWith('mcp__') ? ['direct', 'programmatic'] : ['direct'];
|
|
7
|
+
if (!Array.isArray(value) ||
|
|
8
|
+
value.length === 0 ||
|
|
9
|
+
value.some((caller) => caller !== 'direct' && caller !== 'programmatic'))
|
|
10
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Tool caller policy is invalid.');
|
|
11
|
+
return ['direct', 'programmatic'].filter((caller) => value.includes(caller));
|
|
12
|
+
}
|
|
13
|
+
export function allowsToolCaller(tool, caller) {
|
|
14
|
+
// Control tools cannot be widened by a Host override or historical definition.
|
|
15
|
+
if (caller === 'programmatic' && ['code_mode', 'tool_search', 'question'].includes(tool.name))
|
|
16
|
+
return false;
|
|
17
|
+
return toolCallers(tool).includes(caller);
|
|
18
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ToolCall, ToolExecutionResult } from '../../sdk/types.js';
|
|
2
|
+
import type { CodeModeStateEntry, CodeModeStateMutation } from './state.js';
|
|
3
|
+
export interface CodeModeCatalogEntry {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly description: string;
|
|
6
|
+
readonly inputSchema: unknown;
|
|
7
|
+
readonly inputMode: 'json' | 'freeform';
|
|
8
|
+
}
|
|
9
|
+
/** A trusted, replaceable worker-side engine; no engine handles cross this interface. */
|
|
10
|
+
export interface CodeModeEngine {
|
|
11
|
+
execute(input: {
|
|
12
|
+
code: string;
|
|
13
|
+
tools: readonly string[];
|
|
14
|
+
catalog: readonly CodeModeCatalogEntry[];
|
|
15
|
+
state: readonly CodeModeStateEntry[];
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
limits: {
|
|
18
|
+
memoryBytes: number;
|
|
19
|
+
stackBytes: number;
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
outputBytes: number;
|
|
22
|
+
stateEntries: number;
|
|
23
|
+
stateMutations: number;
|
|
24
|
+
stateKeyBytes: number;
|
|
25
|
+
stateValueBytes: number;
|
|
26
|
+
stateTotalBytes: number;
|
|
27
|
+
sleepMs: number;
|
|
28
|
+
};
|
|
29
|
+
onToolCall(call: ToolCall): Promise<ToolExecutionResult>;
|
|
30
|
+
onText(value: string): void;
|
|
31
|
+
onImage(value: unknown): void;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
readonly stateMutations: readonly CodeModeStateMutation[];
|
|
34
|
+
}>;
|
|
35
|
+
dispose(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/** Only the Local Host may supply this descriptor; it is never part of model tool input. */
|
|
38
|
+
export interface CodeModeEngineFactory {
|
|
39
|
+
readonly moduleUrl: string;
|
|
40
|
+
readonly runtimeDirectories: readonly string[];
|
|
41
|
+
readonly options?: Readonly<Record<string, unknown>>;
|
|
42
|
+
}
|
|
43
|
+
export type CreateCodeModeEngine = (options: Readonly<Record<string, unknown>>) => Promise<CodeModeEngine>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
// This trusted module runs only in the Host-managed worker, never in the model's JS context.
|
|
2
|
+
/** @returns {Promise<import('./engine.js').CodeModeEngine>} */
|
|
3
|
+
export async function createCodeModeEngine(options) {
|
|
4
|
+
const { newQuickJSWASMModuleFromVariant } = await import(options.coreModuleUrl);
|
|
5
|
+
const variant = (await import(options.variantModuleUrl)).default;
|
|
6
|
+
const quickjs = await newQuickJSWASMModuleFromVariant(variant);
|
|
7
|
+
let runtime;
|
|
8
|
+
let context;
|
|
9
|
+
let disposed = false;
|
|
10
|
+
return {
|
|
11
|
+
async execute(input) {
|
|
12
|
+
if (disposed || runtime)
|
|
13
|
+
throw diagnostic('MAR_AGENT_CODE_FAILED', 'Engine is not reusable.');
|
|
14
|
+
runtime = quickjs.newRuntime();
|
|
15
|
+
runtime.setMemoryLimit(input.limits.memoryBytes);
|
|
16
|
+
runtime.setMaxStackSize(input.limits.stackBytes);
|
|
17
|
+
const deadline = Date.now() + input.limits.timeoutMs;
|
|
18
|
+
runtime.setInterruptHandler(() => input.signal.aborted || Date.now() >= deadline);
|
|
19
|
+
context = runtime.newContext();
|
|
20
|
+
const vm = context;
|
|
21
|
+
let wake;
|
|
22
|
+
let bridgeFailure;
|
|
23
|
+
const pending = new Set();
|
|
24
|
+
const deferreds = new Set();
|
|
25
|
+
const unconsumedToolCalls = new Set();
|
|
26
|
+
const sleepTimers = new Set();
|
|
27
|
+
const state = new Map(input.state.map((entry) => [entry.key, entry.value]));
|
|
28
|
+
const stateMutations = [];
|
|
29
|
+
let outputBytes = 0;
|
|
30
|
+
const notify = () => {
|
|
31
|
+
wake?.();
|
|
32
|
+
wake = undefined;
|
|
33
|
+
};
|
|
34
|
+
const fail = (handle) => {
|
|
35
|
+
const error = vm.dump(handle);
|
|
36
|
+
handle.dispose();
|
|
37
|
+
if (input.signal.aborted)
|
|
38
|
+
throw input.signal.reason;
|
|
39
|
+
if (Date.now() >= deadline)
|
|
40
|
+
throw diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its deadline.');
|
|
41
|
+
const message = String(error?.message ?? error).slice(0, 4096);
|
|
42
|
+
const code = message.includes('out of memory')
|
|
43
|
+
? 'MAR_AGENT_CODE_MEMORY_LIMIT'
|
|
44
|
+
: typeof error?.code === 'string'
|
|
45
|
+
? error.code
|
|
46
|
+
: 'MAR_AGENT_CODE_FAILED';
|
|
47
|
+
throw diagnostic(code, message);
|
|
48
|
+
};
|
|
49
|
+
const guestError = (code, message) => {
|
|
50
|
+
const handle = vm.newError({ name: 'Error', message });
|
|
51
|
+
const value = vm.newString(code);
|
|
52
|
+
vm.setProp(handle, 'code', value);
|
|
53
|
+
value.dispose();
|
|
54
|
+
return handle;
|
|
55
|
+
};
|
|
56
|
+
runtime.setModuleLoader(() => ({
|
|
57
|
+
error: guestError('MAR_AGENT_CODE_IMPORT_DENIED', 'Module imports are unavailable.')
|
|
58
|
+
}));
|
|
59
|
+
const setup = vm.evalCode(`
|
|
60
|
+
for (const name of ['console', 'process', 'require', 'module', 'Buffer', 'fetch',
|
|
61
|
+
'WebSocket', 'WebAssembly', 'SharedArrayBuffer', 'Atomics', 'text', 'image'])
|
|
62
|
+
delete globalThis[name];
|
|
63
|
+
globalThis.__parseJson = JSON.parse;
|
|
64
|
+
globalThis.__serialize = value => {
|
|
65
|
+
return JSON.stringify(value, (_key, item) => {
|
|
66
|
+
if (['bigint', 'function', 'symbol', 'undefined'].includes(typeof item))
|
|
67
|
+
throw Object.assign(new TypeError('Only JSON values may cross the tool bridge.'),
|
|
68
|
+
{ code: 'MAR_AGENT_TOOL_INPUT_INVALID' });
|
|
69
|
+
return item;
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
globalThis.__text = value => {
|
|
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
|
+
};
|
|
82
|
+
globalThis.__deepFreeze = function deepFreeze(value) {
|
|
83
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
84
|
+
for (const item of Object.values(value)) deepFreeze(item);
|
|
85
|
+
Object.freeze(value);
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
};
|
|
89
|
+
`);
|
|
90
|
+
if (setup.error)
|
|
91
|
+
fail(setup.error);
|
|
92
|
+
setup.value.dispose();
|
|
93
|
+
const parseJson = vm.getProp(vm.global, '__parseJson');
|
|
94
|
+
const serialize = vm.getProp(vm.global, '__serialize');
|
|
95
|
+
const formatText = vm.getProp(vm.global, '__text');
|
|
96
|
+
const deepFreeze = vm.getProp(vm.global, '__deepFreeze');
|
|
97
|
+
const parse = (value) => {
|
|
98
|
+
const handle = vm.newString(JSON.stringify(value));
|
|
99
|
+
const parsed = vm.callFunction(parseJson, vm.undefined, handle);
|
|
100
|
+
handle.dispose();
|
|
101
|
+
if (parsed.error)
|
|
102
|
+
fail(parsed.error);
|
|
103
|
+
return parsed.value;
|
|
104
|
+
};
|
|
105
|
+
const jsonValue = (handle) => {
|
|
106
|
+
const result = vm.callFunction(serialize, vm.undefined, handle);
|
|
107
|
+
if (result.error)
|
|
108
|
+
return { error: result.error };
|
|
109
|
+
try {
|
|
110
|
+
return { value: JSON.parse(vm.getString(result.value)) };
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
result.value.dispose();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const method = (object, name, callback) => {
|
|
117
|
+
const handle = vm.newFunction(name, callback);
|
|
118
|
+
vm.setProp(object, name, handle);
|
|
119
|
+
handle.dispose();
|
|
120
|
+
};
|
|
121
|
+
const key = (handle) => {
|
|
122
|
+
if (!handle || vm.typeof(handle) !== 'string')
|
|
123
|
+
return {
|
|
124
|
+
error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'State key must be a string.')
|
|
125
|
+
};
|
|
126
|
+
const value = vm.getString(handle);
|
|
127
|
+
if (!value || Buffer.byteLength(value) > input.limits.stateKeyBytes)
|
|
128
|
+
return {
|
|
129
|
+
error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'State key is invalid or too large.')
|
|
130
|
+
};
|
|
131
|
+
return { value };
|
|
132
|
+
};
|
|
133
|
+
const validateState = () => {
|
|
134
|
+
if (state.size > input.limits.stateEntries)
|
|
135
|
+
return guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode state has too many entries.');
|
|
136
|
+
const entries = [...state].map(([entryKey, value]) => ({ key: entryKey, value }));
|
|
137
|
+
if (Buffer.byteLength(JSON.stringify(entries)) > input.limits.stateTotalBytes)
|
|
138
|
+
return guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode state is too large.');
|
|
139
|
+
};
|
|
140
|
+
const tools = vm.newObject();
|
|
141
|
+
let toolCallSequence = 0;
|
|
142
|
+
for (const name of input.tools) {
|
|
143
|
+
method(tools, name, (args = vm.undefined) => {
|
|
144
|
+
const parsed = jsonValue(args);
|
|
145
|
+
if (parsed.error)
|
|
146
|
+
return parsed;
|
|
147
|
+
const operationId = ++toolCallSequence;
|
|
148
|
+
unconsumedToolCalls.add(operationId);
|
|
149
|
+
const deferred = vm.newPromise();
|
|
150
|
+
deferreds.add(deferred);
|
|
151
|
+
const job = Promise.resolve()
|
|
152
|
+
.then(() => input.onToolCall({ name, arguments: parsed.value }))
|
|
153
|
+
.then((output) => {
|
|
154
|
+
if (disposed || input.signal.aborted)
|
|
155
|
+
return;
|
|
156
|
+
const handle = parse(output);
|
|
157
|
+
try {
|
|
158
|
+
deferred.resolve(handle);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
handle.dispose();
|
|
162
|
+
}
|
|
163
|
+
}, (error) => {
|
|
164
|
+
if (disposed || input.signal.aborted)
|
|
165
|
+
return;
|
|
166
|
+
const handle = vm.newError({
|
|
167
|
+
name: 'Error',
|
|
168
|
+
message: String(error?.message ?? error).slice(0, 4096)
|
|
169
|
+
});
|
|
170
|
+
const code = vm.newString(String(error?.code ?? 'MAR_AGENT_HOST_OPERATION_FAILED'));
|
|
171
|
+
vm.setProp(handle, 'code', code);
|
|
172
|
+
code.dispose();
|
|
173
|
+
try {
|
|
174
|
+
deferred.reject(handle);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
handle.dispose();
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
.catch((error) => {
|
|
181
|
+
bridgeFailure = diagnostic('MAR_AGENT_CODE_ENGINE_FAILED', String(error?.message ?? error).slice(0, 4096));
|
|
182
|
+
})
|
|
183
|
+
.finally(() => {
|
|
184
|
+
pending.delete(job);
|
|
185
|
+
deferreds.delete(deferred);
|
|
186
|
+
if (deferred.alive)
|
|
187
|
+
deferred.dispose();
|
|
188
|
+
notify();
|
|
189
|
+
});
|
|
190
|
+
pending.add(job);
|
|
191
|
+
const operation = vm.newObject();
|
|
192
|
+
const id = vm.newNumber(operationId);
|
|
193
|
+
vm.setProp(operation, 'id', id);
|
|
194
|
+
vm.setProp(operation, 'promise', deferred.handle);
|
|
195
|
+
id.dispose();
|
|
196
|
+
return operation;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
vm.setProp(vm.global, 'tools', tools);
|
|
200
|
+
tools.dispose();
|
|
201
|
+
const consumeToolCall = vm.newFunction('__consumeToolCall', (idHandle) => {
|
|
202
|
+
if (idHandle && vm.typeof(idHandle) === 'number')
|
|
203
|
+
unconsumedToolCalls.delete(vm.getNumber(idHandle));
|
|
204
|
+
return vm.undefined;
|
|
205
|
+
});
|
|
206
|
+
vm.setProp(vm.global, '__consumeToolCall', consumeToolCall);
|
|
207
|
+
consumeToolCall.dispose();
|
|
208
|
+
const out = vm.newObject();
|
|
209
|
+
method(out, 'text', (handle) => {
|
|
210
|
+
const result = vm.callFunction(formatText, vm.undefined, handle ?? vm.undefined);
|
|
211
|
+
if (result.error)
|
|
212
|
+
return { error: result.error };
|
|
213
|
+
try {
|
|
214
|
+
const value = vm.getString(result.value);
|
|
215
|
+
outputBytes += Buffer.byteLength(value) + 1;
|
|
216
|
+
if (outputBytes > input.limits.outputBytes)
|
|
217
|
+
return {
|
|
218
|
+
error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Text output limit exceeded.')
|
|
219
|
+
};
|
|
220
|
+
input.onText(value);
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
result.value.dispose();
|
|
224
|
+
}
|
|
225
|
+
return vm.undefined;
|
|
226
|
+
});
|
|
227
|
+
method(out, 'image', (handle) => {
|
|
228
|
+
const parsed = jsonValue(handle ?? vm.undefined);
|
|
229
|
+
if (parsed.error)
|
|
230
|
+
return parsed;
|
|
231
|
+
input.onImage(parsed.value);
|
|
232
|
+
return vm.undefined;
|
|
233
|
+
});
|
|
234
|
+
vm.setProp(vm.global, 'out', out);
|
|
235
|
+
out.dispose();
|
|
236
|
+
const stateObject = vm.newObject();
|
|
237
|
+
method(stateObject, 'get', (keyHandle) => {
|
|
238
|
+
const parsed = key(keyHandle);
|
|
239
|
+
if (parsed.error)
|
|
240
|
+
return parsed;
|
|
241
|
+
return state.has(parsed.value) ? parse(state.get(parsed.value)) : vm.undefined;
|
|
242
|
+
});
|
|
243
|
+
method(stateObject, 'has', (keyHandle) => {
|
|
244
|
+
const parsed = key(keyHandle);
|
|
245
|
+
if (parsed.error)
|
|
246
|
+
return parsed;
|
|
247
|
+
return state.has(parsed.value) ? vm.true : vm.false;
|
|
248
|
+
});
|
|
249
|
+
method(stateObject, 'set', (keyHandle, valueHandle = vm.undefined) => {
|
|
250
|
+
const parsedKey = key(keyHandle);
|
|
251
|
+
if (parsedKey.error)
|
|
252
|
+
return parsedKey;
|
|
253
|
+
const parsedValue = jsonValue(valueHandle);
|
|
254
|
+
if (parsedValue.error)
|
|
255
|
+
return parsedValue;
|
|
256
|
+
if (Buffer.byteLength(JSON.stringify(parsedValue.value)) > input.limits.stateValueBytes)
|
|
257
|
+
return { error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'State value is too large.') };
|
|
258
|
+
const previous = state.has(parsedKey.value)
|
|
259
|
+
? { exists: true, value: state.get(parsedKey.value) }
|
|
260
|
+
: { exists: false };
|
|
261
|
+
state.set(parsedKey.value, parsedValue.value);
|
|
262
|
+
const invalid = validateState();
|
|
263
|
+
if (invalid) {
|
|
264
|
+
if (previous.exists)
|
|
265
|
+
state.set(parsedKey.value, previous.value);
|
|
266
|
+
else
|
|
267
|
+
state.delete(parsedKey.value);
|
|
268
|
+
return { error: invalid };
|
|
269
|
+
}
|
|
270
|
+
if (stateMutations.length >= input.limits.stateMutations) {
|
|
271
|
+
if (previous.exists)
|
|
272
|
+
state.set(parsedKey.value, previous.value);
|
|
273
|
+
else
|
|
274
|
+
state.delete(parsedKey.value);
|
|
275
|
+
return {
|
|
276
|
+
error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Too many state mutations.')
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
stateMutations.push({ operation: 'set', key: parsedKey.value, value: parsedValue.value });
|
|
280
|
+
return vm.undefined;
|
|
281
|
+
});
|
|
282
|
+
method(stateObject, 'delete', (keyHandle) => {
|
|
283
|
+
const parsed = key(keyHandle);
|
|
284
|
+
if (parsed.error)
|
|
285
|
+
return parsed;
|
|
286
|
+
if (!state.has(parsed.value))
|
|
287
|
+
return vm.false;
|
|
288
|
+
if (stateMutations.length >= input.limits.stateMutations)
|
|
289
|
+
return {
|
|
290
|
+
error: guestError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Too many state mutations.')
|
|
291
|
+
};
|
|
292
|
+
state.delete(parsed.value);
|
|
293
|
+
stateMutations.push({ operation: 'delete', key: parsed.value });
|
|
294
|
+
return vm.true;
|
|
295
|
+
});
|
|
296
|
+
vm.setProp(vm.global, 'state', stateObject);
|
|
297
|
+
stateObject.dispose();
|
|
298
|
+
const catalog = vm.newObject();
|
|
299
|
+
method(catalog, 'tools', () => {
|
|
300
|
+
const value = parse(input.catalog);
|
|
301
|
+
const frozen = vm.callFunction(deepFreeze, vm.undefined, value);
|
|
302
|
+
value.dispose();
|
|
303
|
+
if (frozen.error)
|
|
304
|
+
return { error: frozen.error };
|
|
305
|
+
return frozen.value;
|
|
306
|
+
});
|
|
307
|
+
vm.setProp(vm.global, 'catalog', catalog);
|
|
308
|
+
catalog.dispose();
|
|
309
|
+
const cell = vm.newObject();
|
|
310
|
+
method(cell, 'sleep', (durationHandle) => {
|
|
311
|
+
if (!durationHandle || vm.typeof(durationHandle) !== 'number')
|
|
312
|
+
return {
|
|
313
|
+
error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'Sleep duration must be an integer.')
|
|
314
|
+
};
|
|
315
|
+
const duration = vm.getNumber(durationHandle);
|
|
316
|
+
if (!Number.isSafeInteger(duration) || duration < 0 || duration > input.limits.sleepMs)
|
|
317
|
+
return {
|
|
318
|
+
error: guestError('MAR_AGENT_TOOL_INPUT_INVALID', 'Sleep duration is out of range.')
|
|
319
|
+
};
|
|
320
|
+
const deferred = vm.newPromise();
|
|
321
|
+
deferreds.add(deferred);
|
|
322
|
+
let timer;
|
|
323
|
+
const job = new Promise((resolve) => {
|
|
324
|
+
timer = setTimeout(resolve, duration);
|
|
325
|
+
sleepTimers.add(timer);
|
|
326
|
+
})
|
|
327
|
+
.then(() => {
|
|
328
|
+
sleepTimers.delete(timer);
|
|
329
|
+
if (!disposed && !input.signal.aborted)
|
|
330
|
+
deferred.resolve(vm.undefined);
|
|
331
|
+
})
|
|
332
|
+
.catch((error) => {
|
|
333
|
+
bridgeFailure = diagnostic('MAR_AGENT_CODE_ENGINE_FAILED', String(error?.message ?? error).slice(0, 4096));
|
|
334
|
+
})
|
|
335
|
+
.finally(() => {
|
|
336
|
+
pending.delete(job);
|
|
337
|
+
deferreds.delete(deferred);
|
|
338
|
+
if (deferred.alive)
|
|
339
|
+
deferred.dispose();
|
|
340
|
+
notify();
|
|
341
|
+
});
|
|
342
|
+
pending.add(job);
|
|
343
|
+
return deferred.handle;
|
|
344
|
+
});
|
|
345
|
+
vm.setProp(vm.global, 'cell', cell);
|
|
346
|
+
cell.dispose();
|
|
347
|
+
const lock = vm.evalCode(`
|
|
348
|
+
const rawTools = globalThis.tools;
|
|
349
|
+
const consumeToolCall = globalThis.__consumeToolCall;
|
|
350
|
+
const wrappedTools = Object.fromEntries(Object.keys(rawTools).map(name => [name, (...args) => {
|
|
351
|
+
const operation = rawTools[name](...args);
|
|
352
|
+
const consume = () => consumeToolCall(operation.id);
|
|
353
|
+
return Object.freeze({
|
|
354
|
+
then(onFulfilled, onRejected) {
|
|
355
|
+
consume();
|
|
356
|
+
return operation.promise.then(onFulfilled, onRejected);
|
|
357
|
+
},
|
|
358
|
+
catch(onRejected) {
|
|
359
|
+
consume();
|
|
360
|
+
return operation.promise.catch(onRejected);
|
|
361
|
+
},
|
|
362
|
+
finally(onFinally) {
|
|
363
|
+
consume();
|
|
364
|
+
return operation.promise.finally(onFinally);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}]));
|
|
368
|
+
globalThis.tools = new Proxy(wrappedTools, { get(target, name) {
|
|
369
|
+
if (name === 'then') return undefined;
|
|
370
|
+
if (!Object.prototype.hasOwnProperty.call(target, name))
|
|
371
|
+
return () => { throw Object.assign(new Error('The requested nested tool is unavailable.'),
|
|
372
|
+
{ code: 'MAR_AGENT_TOOL_NOT_AVAILABLE' }); };
|
|
373
|
+
return target[name];
|
|
374
|
+
}});
|
|
375
|
+
for (const name of ['tools', 'out', 'state', 'catalog', 'cell']) {
|
|
376
|
+
const value = globalThis[name];
|
|
377
|
+
Object.freeze(value);
|
|
378
|
+
Object.defineProperty(globalThis, name, {
|
|
379
|
+
value, writable: false, configurable: false, enumerable: true
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
delete globalThis.__parseJson;
|
|
383
|
+
delete globalThis.__serialize;
|
|
384
|
+
delete globalThis.__text;
|
|
385
|
+
delete globalThis.__deepFreeze;
|
|
386
|
+
delete globalThis.__consumeToolCall;
|
|
387
|
+
`);
|
|
388
|
+
if (lock.error)
|
|
389
|
+
fail(lock.error);
|
|
390
|
+
lock.value.dispose();
|
|
391
|
+
input.signal.addEventListener('abort', notify, { once: true });
|
|
392
|
+
let main;
|
|
393
|
+
try {
|
|
394
|
+
const evaluated = vm.evalCode(`(async () => {\n${input.code}\n})()`, 'code-mode.js');
|
|
395
|
+
if (evaluated.error)
|
|
396
|
+
fail(evaluated.error);
|
|
397
|
+
main = evaluated.value;
|
|
398
|
+
for (;;) {
|
|
399
|
+
input.signal.throwIfAborted();
|
|
400
|
+
if (bridgeFailure)
|
|
401
|
+
throw bridgeFailure;
|
|
402
|
+
const jobs = runtime.executePendingJobs(100);
|
|
403
|
+
if (jobs.error)
|
|
404
|
+
fail(jobs.error);
|
|
405
|
+
jobs.dispose();
|
|
406
|
+
const promiseState = vm.getPromiseState(main);
|
|
407
|
+
if (promiseState.type === 'rejected')
|
|
408
|
+
fail(promiseState.error);
|
|
409
|
+
if (promiseState.type === 'fulfilled') {
|
|
410
|
+
promiseState.value.dispose();
|
|
411
|
+
if (pending.size)
|
|
412
|
+
throw diagnostic('MAR_AGENT_CODE_INCOMPLETE', 'Unawaited asynchronous operations remain; completed tool operations are not rolled back.');
|
|
413
|
+
if (unconsumedToolCalls.size)
|
|
414
|
+
throw diagnostic('MAR_AGENT_CODE_INCOMPLETE', 'Tool calls must be awaited; completed tool operations are not rolled back.');
|
|
415
|
+
return { stateMutations };
|
|
416
|
+
}
|
|
417
|
+
if (runtime.hasPendingJob())
|
|
418
|
+
continue;
|
|
419
|
+
if (Date.now() >= deadline)
|
|
420
|
+
throw diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its deadline.');
|
|
421
|
+
if (!pending.size)
|
|
422
|
+
throw diagnostic('MAR_AGENT_CODE_INCOMPLETE', 'JavaScript returned an unresolved promise without active operations.');
|
|
423
|
+
await new Promise((resolve) => {
|
|
424
|
+
wake = resolve;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
input.signal.removeEventListener('abort', notify);
|
|
430
|
+
disposed = true;
|
|
431
|
+
for (const timer of sleepTimers)
|
|
432
|
+
clearTimeout(timer);
|
|
433
|
+
main?.dispose();
|
|
434
|
+
for (const deferred of deferreds)
|
|
435
|
+
if (deferred.alive)
|
|
436
|
+
deferred.dispose();
|
|
437
|
+
parseJson.dispose();
|
|
438
|
+
serialize.dispose();
|
|
439
|
+
formatText.dispose();
|
|
440
|
+
deepFreeze.dispose();
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
async dispose() {
|
|
444
|
+
disposed = true;
|
|
445
|
+
if (context?.alive)
|
|
446
|
+
context.dispose();
|
|
447
|
+
if (runtime?.alive)
|
|
448
|
+
runtime.dispose();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function diagnostic(code, message) {
|
|
453
|
+
return Object.assign(new Error(message), { code });
|
|
454
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CodeModeStateEntry {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly value: unknown;
|
|
4
|
+
}
|
|
5
|
+
export type CodeModeStateMutation = {
|
|
6
|
+
readonly operation: 'set';
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly value: unknown;
|
|
9
|
+
} | {
|
|
10
|
+
readonly operation: 'delete';
|
|
11
|
+
readonly key: string;
|
|
12
|
+
};
|
|
13
|
+
type StateRecord = {
|
|
14
|
+
readonly type: string;
|
|
15
|
+
readonly payload: unknown;
|
|
16
|
+
};
|
|
17
|
+
export declare function parseCodeModeStateMutations(value: unknown): CodeModeStateMutation[];
|
|
18
|
+
export declare function restoreCodeModeState(records: readonly StateRecord[]): CodeModeStateEntry[];
|
|
19
|
+
export declare function applyCodeModeStateMutations(entries: readonly CodeModeStateEntry[], mutations: readonly CodeModeStateMutation[]): CodeModeStateEntry[];
|
|
20
|
+
export {};
|