@myagentroam/agent 0.9.148 → 0.9.150

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.
@@ -20,6 +20,7 @@ export interface CodeModeEngine {
20
20
  limits: {
21
21
  memoryBytes: number;
22
22
  stackBytes: number;
23
+ /** Cumulative synchronous guest execution budget; asynchronous waiting is excluded. */
23
24
  timeoutMs: number;
24
25
  outputBytes: number;
25
26
  stateEntries: number;
@@ -29,20 +29,19 @@ export const codeModeToolDefinition = {
29
29
  Tool calls:
30
30
  - 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.
31
31
  - 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.
32
- - 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.
32
+ - Tool errors reject with code/message and may be caught. A failed cell preserves already emitted output alongside its status and error; completed tool effects are not rolled back. 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.
33
33
  - 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.
34
34
 
35
35
  Built-in namespaces:
36
36
  - observe.result(result): void registers an unmodified complete tools.NAME result or caught tool error as a trusted tool_result, using the original text and all images without repeating program data. Non-original, copied or modified values are rejected. This selects evidence for the next model response, not a log and not a new model call. Prefer observe.result for inspecting tool results.
37
- - observe.text(value): void registers bounded text/JSON values. observe.image(value): void registers one image value using the existing capability, MIME and byte limits. Both accept original tool results for compatibility and use the same complete tool_result observation path; they do not select part of an original result. Extracted single images, copies and computed summaries are value observations, never trusted tool results. Observations are returned in registration order. Only explicitly observed evidence reaches the model; registration does not prove inspection. Sources and types are runtime-assigned. No out or callable observe(value) alias exists.
37
+ - observe.text(value): void registers text/JSON values. observe.image(value): void registers one image value when image input is supported. Both accept original tool results for compatibility and use the same complete tool_result observation path; they do not select part of an original result. Extracted single images, copies and computed summaries are value observations, never trusted tool results. Observations are returned in registration order. Only explicitly observed evidence reaches the model; registration does not prove inspection. Sources and types are runtime-assigned. No out or callable observe(value) alias exists.
38
38
  - 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.
39
39
  - 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.
40
- - await cell.sleep(ms) performs a cancellable Host-side delay up to 30000 ms and remains subject to the cell deadline.
40
+ - await cell.sleep(ms) performs a cancellable delay up to ${limits.codeSleepMaxMs} ms.
41
41
  - await cell.yield_control() returns accumulated output to the model immediately while this cell continues running.
42
- - 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.
42
+ - Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}, "maxOutputTokens": 10000}. yieldMs defaults to ${limits.codeDefaultYieldMs} ms and controls this call's wait window (0..${limits.codeMaxYieldMs}), not nested tool timeouts or wait windows. A running result includes cellId; use direct code_exec_wait for new output, completion or termination.
43
43
  - 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.
44
44
  - tools, observe, state, catalog and cell are frozen bindings. Do not shadow their names.
45
- - 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.
46
45
 
47
46
  Examples (copy the JavaScript source, not the fences; use only tools available in the current directory):
48
47
  Independent reads, observing the original tool results:
@@ -84,7 +83,7 @@ export const codeExecWaitToolDefinition = {
84
83
  minimum: 0,
85
84
  maximum: limits.codeMaxWaitYieldMs,
86
85
  default: limits.codeDefaultWaitYieldMs,
87
- 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.`
86
+ description: `Wait up to this many milliseconds. Defaults to ${limits.codeDefaultWaitYieldMs}; maximum ${limits.codeMaxWaitYieldMs}. Zero returns immediately without stopping the cell. Waiting does not restart the cell or extend nested tool timeouts.`
88
87
  },
89
88
  maxOutputTokens: { type: 'integer', minimum: 1, maximum: limits.codeMaxOutputTokens },
90
89
  action: {
@@ -229,12 +228,10 @@ export class CodeModeTool {
229
228
  release = resolve;
230
229
  });
231
230
  this.#active.set(controller, { sessionId: context.sessionId, done });
232
- const timer = setTimeout(() => controller.abort(new MarAgentError('MAR_AGENT_CODE_TIMEOUT', 'Code Mode exceeded its execution time limit.')), limits.codeTimeoutMs);
233
231
  try {
234
232
  return await this.#run(code, { ...context, signal }, controller, output);
235
233
  }
236
234
  finally {
237
- clearTimeout(timer);
238
235
  this.#active.delete(controller);
239
236
  release();
240
237
  }
@@ -15,8 +15,30 @@ export async function createCodeModeEngine(options) {
15
15
  runtime = quickjs.newRuntime();
16
16
  runtime.setMemoryLimit(input.limits.memoryBytes);
17
17
  runtime.setMaxStackSize(input.limits.stackBytes);
18
- const deadline = Date.now() + input.limits.timeoutMs;
19
- runtime.setInterruptHandler(() => input.signal.aborted || Date.now() >= deadline);
18
+ let executionMs = 0;
19
+ let executionStarted;
20
+ const budgetExceeded = () => executionMs + (executionStarted === undefined ? 0 : performance.now() - executionStarted) >=
21
+ input.limits.timeoutMs;
22
+ const executionTimeout = () => diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its execution budget.');
23
+ // Charge only synchronous guest work, including reentrant bridge calls, not async waiting.
24
+ const runGuest = (run) => {
25
+ input.signal.throwIfAborted();
26
+ if (budgetExceeded())
27
+ throw executionTimeout();
28
+ const outermost = executionStarted === undefined;
29
+ if (outermost)
30
+ executionStarted = performance.now();
31
+ try {
32
+ return run();
33
+ }
34
+ finally {
35
+ if (outermost) {
36
+ executionMs += performance.now() - executionStarted;
37
+ executionStarted = undefined;
38
+ }
39
+ }
40
+ };
41
+ runtime.setInterruptHandler(() => input.signal.aborted || budgetExceeded());
20
42
  context = runtime.newContext();
21
43
  const vm = context;
22
44
  let wake;
@@ -35,12 +57,15 @@ export async function createCodeModeEngine(options) {
35
57
  wake = undefined;
36
58
  };
37
59
  const fail = (handle, locateSource = false) => {
38
- const error = vm.dump(handle);
39
- handle.dispose();
40
- if (input.signal.aborted)
41
- throw input.signal.reason;
42
- if (Date.now() >= deadline)
43
- throw diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its deadline.');
60
+ let error;
61
+ try {
62
+ error = runGuest(() => vm.dump(handle));
63
+ }
64
+ finally {
65
+ handle.dispose();
66
+ }
67
+ if (budgetExceeded())
68
+ throw executionTimeout();
44
69
  let message = String(error?.message ?? error).slice(0, 4096);
45
70
  const syntaxError = locateSource && error?.name === 'SyntaxError';
46
71
  if (syntaxError)
@@ -64,10 +89,12 @@ export async function createCodeModeEngine(options) {
64
89
  value.dispose();
65
90
  return handle;
66
91
  };
92
+ const evalGuest = (...args) => runGuest(() => vm.evalCode(...args));
93
+ const callGuest = (...args) => runGuest(() => vm.callFunction(...args));
67
94
  runtime.setModuleLoader(() => ({
68
95
  error: guestError('MAR_AGENT_CODE_IMPORT_DENIED', 'Module imports are unavailable.')
69
96
  }));
70
- const setup = vm.evalCode(`
97
+ const setup = evalGuest(`
71
98
  for (const name of ['console', 'process', 'require', 'module', 'Buffer', 'fetch',
72
99
  'WebSocket', 'WebAssembly', 'SharedArrayBuffer', 'Atomics', 'text', 'image'])
73
100
  delete globalThis[name];
@@ -119,7 +146,7 @@ export async function createCodeModeEngine(options) {
119
146
  const formatText = vm.getProp(vm.global, '__text');
120
147
  const snapshotFunction = vm.getProp(vm.global, '__snapshot');
121
148
  const snapshot = (handle, errorSnapshot = false) => {
122
- const value = vm.callFunction(snapshotFunction, vm.undefined, handle, errorSnapshot ? vm.true : vm.false);
149
+ const value = callGuest(snapshotFunction, vm.undefined, handle, errorSnapshot ? vm.true : vm.false);
123
150
  if (value.error) {
124
151
  value.error.dispose();
125
152
  return '';
@@ -134,14 +161,18 @@ export async function createCodeModeEngine(options) {
134
161
  const deepFreeze = vm.getProp(vm.global, '__deepFreeze');
135
162
  const parse = (value) => {
136
163
  const handle = vm.newString(JSON.stringify(value));
137
- const parsed = vm.callFunction(parseJson, vm.undefined, handle);
138
- handle.dispose();
139
- if (parsed.error)
140
- fail(parsed.error);
141
- return parsed.value;
164
+ try {
165
+ const parsed = callGuest(parseJson, vm.undefined, handle);
166
+ if (parsed.error)
167
+ fail(parsed.error);
168
+ return parsed.value;
169
+ }
170
+ finally {
171
+ handle.dispose();
172
+ }
142
173
  };
143
174
  const jsonValue = (handle) => {
144
- const result = vm.callFunction(serialize, vm.undefined, handle);
175
+ const result = callGuest(serialize, vm.undefined, handle);
145
176
  if (result.error)
146
177
  return { error: result.error };
147
178
  try {
@@ -191,44 +222,57 @@ export async function createCodeModeEngine(options) {
191
222
  .then((output) => {
192
223
  if (disposed || input.signal.aborted)
193
224
  return;
194
- const handle = parse(output);
195
- origins.push({
196
- handle: handle.dup(),
197
- snapshot: snapshot(handle),
198
- errorSnapshot: false,
199
- operationId: String(operationId)
225
+ runGuest(() => {
226
+ const handle = parse(output);
227
+ try {
228
+ const fingerprint = snapshot(handle);
229
+ origins.push({
230
+ handle: handle.dup(),
231
+ snapshot: fingerprint,
232
+ errorSnapshot: false,
233
+ operationId: String(operationId)
234
+ });
235
+ deferred.resolve(handle);
236
+ }
237
+ finally {
238
+ handle.dispose();
239
+ }
200
240
  });
201
- try {
202
- deferred.resolve(handle);
203
- }
204
- finally {
205
- handle.dispose();
206
- }
207
241
  }, (error) => {
208
242
  if (disposed || input.signal.aborted)
209
243
  return;
210
- const handle = vm.newError({
211
- name: 'Error',
212
- message: String(error?.message ?? error).slice(0, 4096)
213
- });
214
- const code = vm.newString(String(error?.code ?? 'MAR_AGENT_HOST_OPERATION_FAILED'));
215
- vm.setProp(handle, 'code', code);
216
- code.dispose();
217
- origins.push({
218
- handle: handle.dup(),
219
- snapshot: snapshot(handle, true),
220
- errorSnapshot: true,
221
- operationId: String(operationId)
244
+ runGuest(() => {
245
+ const handle = vm.newError({
246
+ name: 'Error',
247
+ message: String(error?.message ?? error).slice(0, 4096)
248
+ });
249
+ try {
250
+ const code = vm.newString(String(error?.code ?? 'MAR_AGENT_HOST_OPERATION_FAILED'));
251
+ try {
252
+ vm.setProp(handle, 'code', code);
253
+ }
254
+ finally {
255
+ code.dispose();
256
+ }
257
+ const fingerprint = snapshot(handle, true);
258
+ origins.push({
259
+ handle: handle.dup(),
260
+ snapshot: fingerprint,
261
+ errorSnapshot: true,
262
+ operationId: String(operationId)
263
+ });
264
+ deferred.reject(handle);
265
+ }
266
+ finally {
267
+ handle.dispose();
268
+ }
222
269
  });
223
- try {
224
- deferred.reject(handle);
225
- }
226
- finally {
227
- handle.dispose();
228
- }
229
270
  })
230
271
  .catch((error) => {
231
- bridgeFailure = diagnostic('MAR_AGENT_CODE_ENGINE_FAILED', String(error?.message ?? error).slice(0, 4096));
272
+ bridgeFailure =
273
+ error?.code === 'MAR_AGENT_CODE_TIMEOUT'
274
+ ? error
275
+ : diagnostic('MAR_AGENT_CODE_ENGINE_FAILED', String(error?.message ?? error).slice(0, 4096));
232
276
  })
233
277
  .finally(() => {
234
278
  pending.delete(job);
@@ -294,7 +338,7 @@ export async function createCodeModeEngine(options) {
294
338
  capture.append(vm.getString(chunk));
295
339
  return vm.undefined;
296
340
  });
297
- const result = vm.callFunction(formatText, vm.undefined, handle ?? vm.undefined, emit);
341
+ const result = callGuest(formatText, vm.undefined, handle ?? vm.undefined, emit);
298
342
  emit.dispose();
299
343
  if (result.error)
300
344
  return { error: result.error };
@@ -384,7 +428,7 @@ export async function createCodeModeEngine(options) {
384
428
  const catalog = vm.newObject();
385
429
  method(catalog, 'tools', () => {
386
430
  const value = parse(input.catalog);
387
- const frozen = vm.callFunction(deepFreeze, vm.undefined, value);
431
+ const frozen = callGuest(deepFreeze, vm.undefined, value);
388
432
  value.dispose();
389
433
  if (frozen.error)
390
434
  return { error: frozen.error };
@@ -434,7 +478,7 @@ export async function createCodeModeEngine(options) {
434
478
  });
435
479
  vm.setProp(vm.global, 'cell', cell);
436
480
  cell.dispose();
437
- const lock = vm.evalCode(`
481
+ const lock = evalGuest(`
438
482
  const rawTools = globalThis.tools;
439
483
  const consumeToolCall = globalThis.__consumeToolCall;
440
484
  const wrappedTools = Object.fromEntries(Object.keys(rawTools).map(name => [name, (...args) => {
@@ -482,7 +526,7 @@ export async function createCodeModeEngine(options) {
482
526
  input.signal.addEventListener('abort', notify, { once: true });
483
527
  let main;
484
528
  try {
485
- const evaluated = vm.evalCode(`(async () => {\n${input.code}\n})()`, 'code-mode.js');
529
+ const evaluated = evalGuest(`(async () => {\n${input.code}\n})()`, 'code-mode.js');
486
530
  if (evaluated.error)
487
531
  fail(evaluated.error, true);
488
532
  main = evaluated.value;
@@ -490,11 +534,13 @@ export async function createCodeModeEngine(options) {
490
534
  input.signal.throwIfAborted();
491
535
  if (bridgeFailure)
492
536
  throw bridgeFailure;
493
- const jobs = runtime.executePendingJobs(100);
537
+ const jobs = runGuest(() => runtime.executePendingJobs(100));
494
538
  if (jobs.error)
495
539
  fail(jobs.error);
496
540
  jobs.dispose();
497
- const promiseState = vm.getPromiseState(main);
541
+ if (budgetExceeded())
542
+ throw executionTimeout();
543
+ const promiseState = runGuest(() => vm.getPromiseState(main));
498
544
  if (promiseState.type === 'rejected')
499
545
  fail(promiseState.error);
500
546
  if (promiseState.type === 'fulfilled') {
@@ -507,8 +553,6 @@ export async function createCodeModeEngine(options) {
507
553
  }
508
554
  if (runtime.hasPendingJob())
509
555
  continue;
510
- if (Date.now() >= deadline)
511
- throw diagnostic('MAR_AGENT_CODE_TIMEOUT', 'JavaScript execution exceeded its deadline.');
512
556
  if (!pending.size)
513
557
  throw diagnostic('MAR_AGENT_CODE_INCOMPLETE', 'JavaScript returned an unresolved promise without active operations.');
514
558
  await new Promise((resolve) => {
@@ -38,7 +38,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
38
38
  codeHeapMegabytes: 128,
39
39
  codeMemoryBytes: 33_554_432,
40
40
  codeStackBytes: 524_288,
41
- codeTimeoutMs: 120_000,
41
+ codeTimeoutMs: 120_000, // Cumulative synchronous guest execution, not cell wall time.
42
42
  patchMaxBytes: 4_194_304,
43
43
  patchMaxFiles: 100,
44
44
  patchMaxFileBytes: 8_388_608,
@@ -69,7 +69,7 @@ export const execWaitToolDefinition = {
69
69
  name: 'exec_wait',
70
70
  allowedCallers: ['direct', 'programmatic'],
71
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.',
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.',
73
73
  outputSchema: execToolDefinition.outputSchema,
74
74
  inputSchema: {
75
75
  type: 'object',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.148",
3
+ "version": "0.9.150",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",