@myagentroam/agent 0.11.28 → 0.11.29

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.
@@ -1,5 +1,5 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import { permissionStopFromError } from '../permissions.js';
2
+ import { RuntimeResourceHandles } from '../runtime-resource-handles.js';
3
3
  import { MarAgentError, describeMarAgentError } from '../../error.js';
4
4
  import { truncateModelText } from '../../runtime/token-budget.js';
5
5
  import { TOOL_EXECUTION_LIMITS as limits } from '../execution-limits.js';
@@ -39,6 +39,7 @@ function options(value, wait) {
39
39
  /** Session-owned execution, separate from the cancellable model-tool wait. */
40
40
  export class CodeCells {
41
41
  #cells = new Map();
42
+ #handles = new RuntimeResourceHandles('cell', (id) => this.#cells.has(id));
42
43
  #closed = false;
43
44
  #disposing = new Set();
44
45
  async start(value, context, run) {
@@ -75,7 +76,7 @@ export class CodeCells {
75
76
  throw new MarAgentError('MAR_AGENT_PROCESS_LIMIT', `The Local Host shared running-cell limit (${limits.codeMaxConcurrentCells}) was reached.`);
76
77
  const controller = new AbortController();
77
78
  const cell = {
78
- cellId: randomUUID(),
79
+ cellId: this.#handles.create(),
79
80
  sessionId: context.sessionId,
80
81
  executionId: context.executionId,
81
82
  controller,
@@ -160,6 +161,7 @@ export class CodeCells {
160
161
  async wait(value, context) {
161
162
  const input = options(value, true);
162
163
  context.signal.throwIfAborted();
164
+ this.#handles.assertType(input.cellId);
163
165
  const cell = this.#cells.get(input.cellId);
164
166
  if (!cell || cell.sessionId !== context.sessionId)
165
167
  throw new MarAgentError('MAR_AGENT_CODE_CELL_NOT_FOUND', 'Code Exec cell is unavailable in this Session.');
@@ -45,7 +45,7 @@ Built-in namespaces:
45
45
  - catalog.tools() returns a read-only array of the tools callable in this cell, including name, description and inputSchema. It does not discover or authorize additional tools.
46
46
  - await cell.sleep(ms) performs a cancellable delay up to ${limits.codeSleepMaxMs} ms.
47
47
  - await cell.yield_control() returns accumulated output to the model immediately while this cell continues running.
48
- - Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}}. 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.
48
+ - Optionally start source with // @options: {"yieldMs": ${limits.codeDefaultYieldMs}}. 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 (c_ plus 12 lowercase hex characters); use direct code_exec_wait for new output, completion or termination.
49
49
  - waitOutcome separates snapshot, completion, timeout, and yield from the cell status. A foreground steer returns interrupted with reason=steer without stopping the cell or its internal tool waits.
50
50
  - 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.
51
51
  - tools, observe, state, catalog and cell are frozen bindings. Do not shadow their names.
@@ -84,7 +84,10 @@ export const codeExecWaitToolDefinition = {
84
84
  inputSchema: {
85
85
  type: 'object',
86
86
  properties: {
87
- cellId: { type: 'string' },
87
+ cellId: {
88
+ type: 'string',
89
+ description: 'Copy the complete cellId returned by code_exec (c_ followed by 12 lowercase hex characters). Never use a processId (p_); use exec_wait for processes.'
90
+ },
88
91
  yieldMs: {
89
92
  type: 'integer',
90
93
  minimum: 0,
@@ -1,4 +1,4 @@
1
- import { randomUUID } from 'node:crypto';
1
+ import { RuntimeResourceHandles } from '../runtime-resource-handles.js';
2
2
  import { StringDecoder } from 'node:string_decoder';
3
3
  import { MarAgentError } from '../../error.js';
4
4
  import { directLocalProcessExecutor, signalProcessTree } from '../../host/local-process-executor.js';
@@ -30,7 +30,7 @@ export const execToolDefinition = {
30
30
  name: 'exec',
31
31
  allowedCallers: ['direct', 'programmatic'],
32
32
  parallelSafety: 'safe',
33
- 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, call exec_wait directly with processId, not exec. Truncation does not stop the process. Cancelling the initial wait does not terminate the process. waitOutcome distinguishes snapshot, completion, timeout, and yield. A foreground wait may return interrupted with reason=steer; the process keeps running.',
33
+ description: 'Start a platform-native command, script, or program in workspace/default cwd. Returns a stable processId (p_ plus 12 lowercase hex characters), 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, call exec_wait directly with processId, not exec. Truncation does not stop the process. Cancelling the initial wait does not terminate the process. waitOutcome distinguishes snapshot, completion, timeout, and yield. A foreground wait may return interrupted with reason=steer; the process keeps running.',
34
34
  outputSchema: {
35
35
  type: 'object',
36
36
  required: ['content', 'data'],
@@ -112,7 +112,7 @@ export const execWaitToolDefinition = {
112
112
  processId: {
113
113
  type: 'string',
114
114
  minLength: 1,
115
- description: 'Stable processId returned by exec.'
115
+ description: 'Copy the complete processId returned by exec (p_ followed by 12 lowercase hex characters). Never use a cellId (c_); use code_exec_wait for cells.'
116
116
  },
117
117
  action: { enum: ['poll', 'write', 'cancel'], default: 'poll' },
118
118
  yieldMs: {
@@ -154,6 +154,7 @@ export class ExecTool {
154
154
  processExecutor;
155
155
  #processes = new Map();
156
156
  #completed = new Map();
157
+ #handles = new RuntimeResourceHandles('process', (id) => this.#processes.has(id) || this.#completed.has(id));
157
158
  #startingProcesses = new Set();
158
159
  constructor(paths, maxOutputBytes = TOOL_EXECUTION_LIMITS.execDefaultMaxOutputBytes, maxProcesses = TOOL_EXECUTION_LIMITS.maxProcesses, environment = process.env, shell, processExecutor = directLocalProcessExecutor) {
159
160
  this.paths = paths;
@@ -168,6 +169,7 @@ export class ExecTool {
168
169
  throw executionAbortReason(signal);
169
170
  const input = parseExecInput(arguments_);
170
171
  if ('processId' in input) {
172
+ this.#handles.assertType(input.processId);
171
173
  const process = this.#processes.get(input.processId);
172
174
  const completed = this.#completed.get(input.processId);
173
175
  if (completed) {
@@ -294,7 +296,7 @@ export class ExecTool {
294
296
  details: { reasonCode: error?.code ?? 'UNKNOWN' }
295
297
  });
296
298
  }
297
- const id = randomUUID();
299
+ const id = this.#handles.create();
298
300
  let resolveExited;
299
301
  let resolveSettled;
300
302
  const managed = {
@@ -0,0 +1,21 @@
1
+ declare const types: {
2
+ readonly cell: {
3
+ readonly prefix: "c_";
4
+ readonly field: "cellId";
5
+ readonly tool: "code_exec_wait";
6
+ };
7
+ readonly process: {
8
+ readonly prefix: "p_";
9
+ readonly field: "processId";
10
+ readonly tool: "exec_wait";
11
+ };
12
+ };
13
+ /** Typed runtime handles; resource owners retain registration and lifecycle authority. */
14
+ export declare class RuntimeResourceHandles {
15
+ private readonly kind;
16
+ private readonly isReserved;
17
+ constructor(kind: keyof typeof types, isReserved: (id: string) => boolean);
18
+ create(): string;
19
+ assertType(id: string): void;
20
+ }
21
+ export {};
@@ -0,0 +1,28 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { MarAgentError } from '../error.js';
3
+ const types = {
4
+ cell: { prefix: 'c_', field: 'cellId', tool: 'code_exec_wait' },
5
+ process: { prefix: 'p_', field: 'processId', tool: 'exec_wait' }
6
+ };
7
+ /** Typed runtime handles; resource owners retain registration and lifecycle authority. */
8
+ export class RuntimeResourceHandles {
9
+ kind;
10
+ isReserved;
11
+ constructor(kind, isReserved) {
12
+ this.kind = kind;
13
+ this.isReserved = isReserved;
14
+ }
15
+ create() {
16
+ let id;
17
+ do {
18
+ id = `${types[this.kind].prefix}${randomBytes(6).toString('hex')}`;
19
+ } while (this.isReserved(id));
20
+ return id;
21
+ }
22
+ assertType(id) {
23
+ const expected = types[this.kind];
24
+ const actual = Object.values(types).find((type) => id.startsWith(type.prefix));
25
+ if (actual && actual !== expected)
26
+ throw new MarAgentError('MAR_AGENT_RESOURCE_HANDLE_TYPE_MISMATCH', `Expected ${expected.field} (${expected.prefix}...). This looks like ${actual.field}; use ${actual.tool} for that resource.`, { details: { expected: expected.field, received: actual.field, tool: actual.tool } });
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.11.28",
3
+ "version": "0.11.29",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",