@myagentroam/agent 0.9.137 → 0.9.138

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/host/local-agent-host.d.ts +5 -0
  2. package/dist/host/local-agent-host.js +2 -1
  3. package/dist/prompts/index.d.ts +1 -1
  4. package/dist/prompts/index.js +1 -1
  5. package/dist/prompts/resources.d.ts +1 -0
  6. package/dist/prompts/resources.js +8 -2
  7. package/dist/prompts/workflow.js +2 -2
  8. package/dist/runtime/context-gc.js +10 -6
  9. package/dist/runtime/context-projection.js +1 -1
  10. package/dist/sdk/agent.js +88 -53
  11. package/dist/sdk/types.d.ts +13 -1
  12. package/dist/session/context-gc-checkpoint.d.ts +1 -1
  13. package/dist/session/context-gc-checkpoint.js +11 -5
  14. package/dist/session/context-gc-replacements.d.ts +2 -0
  15. package/dist/session/context-gc-replacements.js +18 -0
  16. package/dist/session/jsonl-store.d.ts +5 -2
  17. package/dist/session/jsonl-store.js +105 -12
  18. package/dist/subagent/session-controller.d.ts +2 -0
  19. package/dist/subagent/session-controller.js +23 -6
  20. package/dist/tools/callers.js +2 -1
  21. package/dist/tools/code-mode/cells.d.ts +20 -0
  22. package/dist/tools/code-mode/cells.js +250 -0
  23. package/dist/tools/code-mode/dispatch.d.ts +21 -0
  24. package/dist/tools/code-mode/dispatch.js +53 -0
  25. package/dist/tools/code-mode/engine.d.ts +1 -0
  26. package/dist/tools/code-mode/quickjs-engine.js +4 -0
  27. package/dist/tools/code-mode/worker.js +1 -0
  28. package/dist/tools/code-mode.d.ts +10 -1
  29. package/dist/tools/code-mode.js +51 -10
  30. package/dist/tools/execution-limits.d.ts +5 -0
  31. package/dist/tools/execution-limits.js +5 -0
  32. package/dist/tools/local-registry.d.ts +5 -0
  33. package/dist/tools/local-registry.js +5 -1
  34. package/dist/tools/registry.js +2 -1
  35. package/dist/tools/scheduler.d.ts +2 -0
  36. package/dist/tools/scheduler.js +11 -0
  37. package/dist/tools/tool-search.js +2 -2
  38. package/package.json +1 -1
@@ -11,9 +11,10 @@ import { TOOL_EXECUTION_LIMITS as limits } from './execution-limits.js';
11
11
  import { createToolCallScheduler } from './scheduler.js';
12
12
  import { detectImageMime } from './view-image.js';
13
13
  import { allowsDirectTool, allowsToolCaller } from './callers.js';
14
+ import { CodeCells } from './code-mode/cells.js';
14
15
  import { parseCodeModeStateMutations } from './code-mode/state.js';
15
16
  export const codeModeToolDefinition = {
16
- name: 'code_mode',
17
+ name: 'code_exec',
17
18
  description: `Run JavaScript to orchestrate or compose tool calls.
18
19
  - Code Mode Only: call the programmatic tools listed below through tools.NAME inside this tool, including single-tool operations. They are omitted from the top-level tool list. Direct-only tools remain separate model tools and cannot be called here.
19
20
  - Use bounded combinations, parallel independent calls, and filtering of large intermediate results to reduce model round trips.
@@ -25,7 +26,7 @@ Tool calls:
25
26
  - tools.NAME(input) returns a Promise. Use the existing tool input schema; freeform tools such as apply_patch accept a raw string, not a JSON wrapper.
26
27
  - Results use the MAR envelope: { content: string, data?: unknown, images?: Array<{mimeType:string,dataBase64:string}>, truncated?: boolean }. content is rendered text, not raw file bytes. Use result.data.results[0].text for read source text and result.data.exitCode/result.data.stdout for exec. Use only fields documented by that tool; do not invent success or ok flags.
27
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.
28
- - tool_search, question, code_mode and direct-only tools cannot be called here. Use direct tool_search before a later code cell for deferred tools; the current cell's tools do not change.
29
+ - 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.
29
30
 
30
31
  Built-in namespaces:
31
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,6 +34,9 @@ Built-in namespaces:
33
34
  - state.get(key), state.has(key), state.set(key, value), state.delete(key) access bounded session JSON state. Mutations commit atomically only when the cell succeeds; failed or cancelled cells discard them.
34
35
  - catalog.tools() returns a read-only snapshot of the tools callable in this cell, including name, description, inputSchema and inputMode. It does not discover or authorize additional tools.
35
36
  - await cell.sleep(ms) performs a cancellable Host-side delay up to 30000 ms and remains subject to the cell deadline.
37
+ - await cell.yield_control() returns accumulated output to the model immediately while this cell continues running.
38
+ - Optionally start source with // @options: {"yieldMs": 10000, "maxOutputTokens": 10000}. yieldMs is a wait window (0..30000), not a cell timeout. A running result includes cellId; use direct code_exec_wait for new output, completion or termination.
39
+ - 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.
36
40
  - tools, out, state, catalog and cell are frozen bindings. Do not shadow their names.
37
41
  - Input/text output max 1 MiB, 128 calls, 120 seconds. Failures do not roll back completed tool work; do not replay successful operations blindly.
38
42
 
@@ -63,9 +67,30 @@ out.text({ files: patched.data.files.length });
63
67
  allowedCallers: ['direct'],
64
68
  parallelSafety: 'serial'
65
69
  };
70
+ export const codeExecWaitToolDefinition = {
71
+ name: 'code_exec_wait',
72
+ description: 'Wait for a retained Code Exec cell by cellId. Returns only new output, or a bounded terminal snapshot marked cached on repeat reads. yieldMs is a wait window, not a timeout. action defaults to poll (wait); action cancel stops the cell and waits for cleanup. Cancelling this wait does not cancel the cell. Cells belong to the current Session and cannot survive Host restart. Call directly, never inside code_exec.',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ cellId: { type: 'string' },
77
+ yieldMs: { type: 'integer', minimum: 0, maximum: limits.codeMaxYieldMs },
78
+ maxOutputTokens: { type: 'integer', minimum: 1, maximum: limits.codeMaxOutputTokens },
79
+ action: {
80
+ type: 'string',
81
+ enum: ['poll', 'cancel'],
82
+ description: 'Defaults to poll; cancel terminates the cell rather than just its current wait.'
83
+ }
84
+ },
85
+ required: ['cellId'],
86
+ additionalProperties: false
87
+ },
88
+ allowedCallers: ['direct'],
89
+ parallelSafety: 'safe'
90
+ };
66
91
  /** Deferred definitions travel in search results, not in the stable Code Mode description. */
67
92
  export function projectCodeModeTools(tools, residentTools = tools) {
68
- const codeModeEnabled = tools.some((tool) => tool.name === 'code_mode' && allowsToolCaller(tool, 'direct'));
93
+ const codeModeEnabled = tools.some((tool) => tool.name === 'code_exec' && allowsToolCaller(tool, 'direct'));
69
94
  const direct = tools.filter((tool) => allowsDirectTool(tool, codeModeEnabled));
70
95
  if (!codeModeEnabled)
71
96
  return direct;
@@ -81,10 +106,10 @@ export function projectCodeModeTools(tools, residentTools = tools) {
81
106
  });
82
107
  if (Buffer.byteLength(directory) > 32 * 1024)
83
108
  throw new MarAgentError('MAR_AGENT_TOOL_EXPOSURE_LIMIT', 'Programmatic tool directory is too large.');
84
- return direct.map((tool) => tool.name === 'code_mode'
109
+ return direct.map((tool) => tool.name === 'code_exec'
85
110
  ? {
86
111
  ...tool,
87
- description: `${codeModeToolDefinition.description}\nDeferred MCP definitions are returned by direct tool_search and become callable from the next model response, not later in the same response; they are not appended to this description.\nProgrammatic tools (call only inside code_mode): ${directory}`
112
+ description: `${codeModeToolDefinition.description}\nDeferred MCP definitions are returned by direct tool_search and become callable from the next model response, not later in the same response; they are not appended to this description.\nProgrammatic tools (call only inside code_exec): ${directory}`
88
113
  }
89
114
  : tool);
90
115
  }
@@ -100,7 +125,7 @@ export function projectCodeModeSearchResult(message, codeModeEnabled) {
100
125
  const tools = message.toolSearchTools.map((tool) => ({
101
126
  name: tool.name,
102
127
  description: allowsToolCaller(tool, 'programmatic')
103
- ? `Call only as tools.${tool.name} inside code_mode, never as a direct model tool.\n${tool.description}`
128
+ ? `Call only as tools.${tool.name} inside code_exec, never as a direct model tool.\n${tool.description}`
104
129
  : tool.description,
105
130
  inputSchema: tool.inputSchema,
106
131
  inputMode: tool.inputMode ?? 'json',
@@ -150,6 +175,16 @@ export class CodeModeTool {
150
175
  environment;
151
176
  executor;
152
177
  engineFactory;
178
+ #cells = new CodeCells();
179
+ start(value, context) {
180
+ return this.#cells.start(value, context, (code, detached, output) => this.execute(code, detached, output));
181
+ }
182
+ wait(value, context) {
183
+ return this.#cells.wait(value, context);
184
+ }
185
+ listSessionResources(sessionId) {
186
+ return this.#cells.snapshot(sessionId);
187
+ }
153
188
  #active = new Map();
154
189
  constructor(paths, environment = {}, executor = directLocalProcessExecutor, engineFactory = defaultEngineFactory()) {
155
190
  this.paths = paths;
@@ -157,7 +192,7 @@ export class CodeModeTool {
157
192
  this.executor = executor;
158
193
  this.engineFactory = engineFactory;
159
194
  }
160
- async execute(value, context) {
195
+ async execute(value, context, output) {
161
196
  const code = typeof value === 'string'
162
197
  ? value
163
198
  : value &&
@@ -185,7 +220,7 @@ export class CodeModeTool {
185
220
  this.#active.set(controller, { sessionId: context.sessionId, done });
186
221
  const timer = setTimeout(() => controller.abort(new MarAgentError('MAR_AGENT_CODE_TIMEOUT', 'Code Mode exceeded its execution time limit.')), limits.codeTimeoutMs);
187
222
  try {
188
- return await this.#run(code, { ...context, signal }, controller);
223
+ return await this.#run(code, { ...context, signal }, controller, output);
189
224
  }
190
225
  finally {
191
226
  clearTimeout(timer);
@@ -194,12 +229,13 @@ export class CodeModeTool {
194
229
  }
195
230
  }
196
231
  async dispose(sessionId) {
232
+ await this.#cells.dispose(sessionId);
197
233
  const active = [...this.#active].filter(([, entry]) => sessionId === undefined || entry.sessionId === sessionId);
198
234
  for (const [controller] of active)
199
235
  controller.abort(new MarAgentError('MAR_AGENT_CODE_CANCELLED', 'Code Mode resources were released.'));
200
236
  await Promise.all(active.map(([, entry]) => entry.done));
201
237
  }
202
- async #run(code, context, controller) {
238
+ async #run(code, context, controller, output) {
203
239
  const cwd = await this.paths.resolve('.');
204
240
  context.signal.throwIfAborted();
205
241
  const environment = Object.fromEntries(environmentKeys.flatMap((key) => {
@@ -280,6 +316,7 @@ export class CodeModeTool {
280
316
  if (outputBytes > limits.codeMaxOutputBytes)
281
317
  throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode emitted too much text.');
282
318
  text.push(frame.value);
319
+ output?.text(frame.value);
283
320
  }
284
321
  else if (frame.kind === 'image') {
285
322
  if (!context.codeMode?.supportsImages)
@@ -288,6 +325,10 @@ export class CodeModeTool {
288
325
  if (images.length >= 5)
289
326
  throw new MarAgentError('MAR_AGENT_TOOL_OUTPUT_LIMIT', 'Code Mode emitted too many images.');
290
327
  images.push(image);
328
+ output?.image(image);
329
+ }
330
+ else if (frame.kind === 'yield') {
331
+ output?.yield();
291
332
  }
292
333
  else if (frame.kind === 'call' &&
293
334
  Number.isSafeInteger(frame.id) &&
@@ -298,7 +339,7 @@ export class CodeModeTool {
298
339
  seen.add(id);
299
340
  const job = (async () => {
300
341
  try {
301
- if (frame.name === 'code_mode' || !available.has(frame.name))
342
+ if (frame.name === 'code_exec' || !available.has(frame.name))
302
343
  throw new MarAgentError('MAR_AGENT_TOOL_NOT_AVAILABLE', 'The requested nested tool is unavailable.');
303
344
  const output = await invoke({
304
345
  name: frame.name,
@@ -21,6 +21,11 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
21
21
  codeMaxTransportBytes: 67108864;
22
22
  codeMaxCalls: 128;
23
23
  codeMaxConcurrentCells: 4;
24
+ codeDefaultYieldMs: 10000;
25
+ codeMaxYieldMs: 30000;
26
+ codeMaxOutputTokens: 10000;
27
+ codeCompletedRetentionMs: 300000;
28
+ codeMaxCompletedResults: 16;
24
29
  codeStateMaxEntries: 128;
25
30
  codeStateMaxMutations: 128;
26
31
  codeStateMaxKeyBytes: 256;
@@ -21,6 +21,11 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
21
21
  codeMaxTransportBytes: 67_108_864,
22
22
  codeMaxCalls: 128,
23
23
  codeMaxConcurrentCells: 4,
24
+ codeDefaultYieldMs: 10_000,
25
+ codeMaxYieldMs: 30_000,
26
+ codeMaxOutputTokens: 10_000,
27
+ codeCompletedRetentionMs: 300_000,
28
+ codeMaxCompletedResults: 16,
24
29
  codeStateMaxEntries: 128,
25
30
  codeStateMaxMutations: 128,
26
31
  codeStateMaxKeyBytes: 256,
@@ -24,6 +24,11 @@ export declare class LocalToolRegistry {
24
24
  command: string;
25
25
  cwd: string;
26
26
  }[];
27
+ listCodeCells(sessionId: string): {
28
+ cellId: string;
29
+ status: "completed" | "cancelled" | "running" | "failed";
30
+ executionId: string;
31
+ }[];
27
32
  disposeSessionResources(sessionId: string): Promise<void>;
28
33
  dispose(): Promise<void>;
29
34
  }
@@ -25,7 +25,8 @@ export class LocalToolRegistry {
25
25
  this.#exec = new ExecTool(paths, undefined, undefined, environment, shell, web.processExecutor);
26
26
  this.#code = new CodeModeTool(paths, environment, web.processExecutor, web.codeModeEngineFactory);
27
27
  this.#handlers = new Map([
28
- ['code_mode', (arguments_, context) => this.#code.execute(arguments_, context)],
28
+ ['code_exec', (arguments_, context) => this.#code.start(arguments_, context)],
29
+ ['code_exec_wait', (arguments_, context) => this.#code.wait(arguments_, context)],
29
30
  [
30
31
  'read',
31
32
  async (arguments_, context) => {
@@ -124,6 +125,9 @@ export class LocalToolRegistry {
124
125
  listSessionResources(sessionId) {
125
126
  return this.#exec.listSessionResources(sessionId);
126
127
  }
128
+ listCodeCells(sessionId) {
129
+ return this.#code.listSessionResources(sessionId);
130
+ }
127
131
  async disposeSessionResources(sessionId) {
128
132
  await Promise.all([this.#exec.disposeSession(sessionId), this.#code.dispose(sessionId)]);
129
133
  }
@@ -1,6 +1,6 @@
1
1
  import { applyPatchToolDefinition } from './apply-patch.js';
2
2
  import { execToolDefinition } from './exec.js';
3
- import { codeModeToolDefinition } from './code-mode.js';
3
+ import { codeModeToolDefinition, codeExecWaitToolDefinition } from './code-mode.js';
4
4
  import { executeQuestion, questionToolDefinition } from './question.js';
5
5
  import { readToolDefinition } from './read.js';
6
6
  import { viewImageToolDefinition } from './view-image.js';
@@ -21,6 +21,7 @@ export const BUILTIN_TOOL_DEFINITIONS = [
21
21
  viewImageToolDefinition,
22
22
  execToolDefinition,
23
23
  codeModeToolDefinition,
24
+ codeExecWaitToolDefinition,
24
25
  applyPatchToolDefinition,
25
26
  questionToolDefinition,
26
27
  todoReadToolDefinition,
@@ -1,4 +1,6 @@
1
1
  import type { ClientToolDefinition } from '../model/contracts.js';
2
+ /** One barrier domain for foreground tools and all retained cells in a Session. */
3
+ export declare function createSessionToolScheduler(): <T>(definition: ClientToolDefinition | undefined, run: () => Promise<T>) => Promise<T>;
2
4
  /** Schedules an incremental stream while preserving the same safe/serial barriers as batches. */
3
5
  export declare function createToolCallScheduler<TCall extends {
4
6
  readonly name: string;
@@ -1,3 +1,14 @@
1
+ /** One barrier domain for foreground tools and all retained cells in a Session. */
2
+ export function createSessionToolScheduler() {
3
+ const schedule = createToolCallScheduler([
4
+ { name: 'safe', parallelSafety: 'safe' },
5
+ { name: 'serial', parallelSafety: 'serial' }
6
+ ], (job) => job.run());
7
+ return (definition, run) => schedule({
8
+ name: definition?.parallelSafety === 'safe' ? 'safe' : 'serial',
9
+ run
10
+ });
11
+ }
1
12
  /** Schedules an incremental stream while preserving the same safe/serial barriers as batches. */
2
13
  export function createToolCallScheduler(definitions, execute) {
3
14
  const safety = new Map(definitions.map((definition) => [definition.name, definition.parallelSafety]));
@@ -13,7 +13,7 @@ export const TOOL_SEARCH_LIMITS = Object.freeze({
13
13
  });
14
14
  export const toolSearchToolDefinition = {
15
15
  name: 'tool_search',
16
- description: 'Search deferred MCP metadata and return matching tool definitions for the next model call. Use the full callable name for exact selection, or keywords for BM25 search. Never executes the discovered tools. With Code Mode Only, programmatic tools must be called through code_mode, not directly. Already exposed tools need not be searched again. The selected definitions must fit the exposure budget or the search fails atomically.',
16
+ description: 'Search deferred MCP metadata and return matching tool definitions for the next model call. Use the full callable name for exact selection, or keywords for BM25 search. Never executes the discovered tools. With Code Mode Only, programmatic tools must be called through code_exec, not directly. Already exposed tools need not be searched again. The selected definitions must fit the exposure budget or the search fails atomically.',
17
17
  inputSchema: {
18
18
  type: 'object',
19
19
  properties: {
@@ -207,7 +207,7 @@ export class DeferredMcpRegistry {
207
207
  const direct = projectCodeModeTools(resident);
208
208
  const residentNames = new Set(resident.map((tool) => tool.name));
209
209
  const definitions = active.filter((tool) => !residentNames.has(tool.name));
210
- const searchResult = projectCodeModeSearchResult({ role: 'tool', content: '', toolSearchTools: definitions }, base.some((tool) => tool.name === 'code_mode'));
210
+ const searchResult = projectCodeModeSearchResult({ role: 'tool', content: '', toolSearchTools: definitions }, base.some((tool) => tool.name === 'code_exec'));
211
211
  // Search-result definitions still consume context even when Only mode hides direct tools.
212
212
  const estimated = countModelTokens(searchResult.content || JSON.stringify(searchResult.toolSearchTools));
213
213
  const full = Math.max(countModelTokens(JSON.stringify(tools)), countModelTokens(JSON.stringify(direct)) + estimated);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.137",
3
+ "version": "0.9.138",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",