@myagentroam/agent 0.10.27 → 0.10.28

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,13 +1,15 @@
1
1
  import type { MarAgentReasoningEffort } from '../model/configuration.js';
2
- import type { SubagentController, SubagentTaskSnapshot, SubagentHandledResult } from '../subagent/contracts.js';
2
+ import type { SubagentController, SubagentTaskSnapshot, SubagentManyOutput, SubagentHandledResult } from '../subagent/contracts.js';
3
3
  import type { CoreOperationContext } from './operation.js';
4
4
  import type { ExtensionResourceScope } from './resources.js';
5
5
  import type { ExtensionHooks } from './hooks.js';
6
6
  import type { CoreDomainOperations, CoreDomainFacts, CoreCapabilitySnapshot } from './contracts.js';
7
7
  export type CoreAgentSnapshot = Readonly<SubagentTaskSnapshot>;
8
+ export type CoreAgentManySnapshot = Readonly<SubagentManyOutput>;
9
+ export type CoreAgentWaitResult = CoreAgentSnapshot | CoreAgentManySnapshot;
8
10
  export type CoreAgentHandledResult = SubagentHandledResult;
9
11
  export type CoreAgentSpawnResult = CoreAgentSnapshot | CoreAgentHandledResult;
10
- export declare function isHandledAgentResult(value: CoreAgentSpawnResult): value is CoreAgentHandledResult;
12
+ export declare function isHandledAgentResult(value: CoreAgentSpawnResult | CoreAgentManySnapshot): value is CoreAgentHandledResult;
11
13
  export interface CoreAgentSpawnInput {
12
14
  readonly turnSignal?: AbortSignal;
13
15
  readonly waitFor?: 'start' | 'completion';
@@ -26,6 +28,8 @@ export interface CoreAgentTarget {
26
28
  export interface CoreAgentWaitInput {
27
29
  readonly untilSettled?: true;
28
30
  readonly agentId?: string;
31
+ readonly agentIds?: readonly string[];
32
+ readonly mode?: 'any' | 'all';
29
33
  readonly signal?: AbortSignal;
30
34
  readonly waitMs?: number;
31
35
  readonly waitFor?: 'completion' | 'message';
@@ -39,7 +43,7 @@ export interface CoreAgentsBackend {
39
43
  spawn(input: CoreAgentSpawnInput, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
40
44
  list(call: CoreOperationContext): Promise<readonly CoreAgentSnapshot[]>;
41
45
  inspect(input: CoreAgentTarget, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
42
- wait(input: CoreAgentWaitInput, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
46
+ wait(input: CoreAgentWaitInput, call: CoreOperationContext): Promise<CoreAgentWaitResult>;
43
47
  message(input: CoreAgentMessageInput, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
44
48
  cancel(input: CoreAgentTarget, call: CoreOperationContext): Promise<CoreAgentSnapshot>;
45
49
  }
@@ -49,7 +53,13 @@ export interface CoreAgentsApi {
49
53
  readonly signal?: AbortSignal;
50
54
  }): Promise<readonly CoreAgentSnapshot[]>;
51
55
  inspect(input: CoreAgentTarget): Promise<CoreAgentSnapshot>;
52
- wait(input: CoreAgentWaitInput): Promise<CoreAgentSnapshot>;
56
+ wait(input: CoreAgentWaitInput & {
57
+ readonly agentIds: readonly string[];
58
+ }): Promise<CoreAgentManySnapshot>;
59
+ wait(input: CoreAgentWaitInput & {
60
+ readonly agentIds?: undefined;
61
+ }): Promise<CoreAgentSnapshot>;
62
+ wait(input: CoreAgentWaitInput): Promise<CoreAgentWaitResult>;
53
63
  message(input: CoreAgentMessageInput): Promise<CoreAgentSnapshot>;
54
64
  cancel(input: CoreAgentTarget): Promise<CoreAgentSnapshot>;
55
65
  }
@@ -1,6 +1,7 @@
1
1
  import { MarAgentError } from '../error.js';
2
+ import { parseAgentWaitTargets } from '../subagent/wait.js';
2
3
  export function isHandledAgentResult(value) {
3
- return value.outcome === 'handled' && !Object.hasOwn(value, 'agentId');
4
+ return 'outcome' in value && value.outcome === 'handled' && !Object.hasOwn(value, 'agentId');
4
5
  }
5
6
  /** All callers share one interception and authoritative leaf-completion boundary. */
6
7
  export function invokeCoreAgentOperation(hooks, key, input, call, leaf) {
@@ -8,12 +9,16 @@ export function invokeCoreAgentOperation(hooks, key, input, call, leaf) {
8
9
  const result = structuredClone(await leaf(input, effective));
9
10
  if (isHandledAgentResult(result))
10
11
  return result;
11
- await hooks.emit('agent.completed', {
12
- operation: key.slice('agent.'.length),
13
- agentId: result.agentId,
14
- status: result.status,
15
- ...(result.deliveryStatus === undefined ? {} : { deliveryStatus: result.deliveryStatus })
16
- }, effective);
12
+ const agents = Array.isArray(result.agents)
13
+ ? result.agents
14
+ : [result];
15
+ for (const agent of agents)
16
+ await hooks.emit('agent.completed', {
17
+ operation: key.slice('agent.'.length),
18
+ agentId: agent.agentId,
19
+ status: agent.status,
20
+ ...(agent.deliveryStatus === undefined ? {} : { deliveryStatus: agent.deliveryStatus })
21
+ }, effective);
17
22
  return result;
18
23
  });
19
24
  }
@@ -46,9 +51,18 @@ function resolveAgentSpawn(input) {
46
51
  };
47
52
  }
48
53
  export function bindAgentController(controller, action) {
49
- const wait = (input) => action('agent.wait', input, (target, call) => target.agentId === undefined
50
- ? controller.outputAny({ ...target, signal: call.signal })
51
- : controller.output(target.agentId, { ...target, signal: call.signal }));
54
+ const wait = (input) => action('agent.wait', input, (target, call) => {
55
+ const selection = parseAgentWaitTargets(target);
56
+ return selection.agentIds !== undefined
57
+ ? controller.outputMany(selection.agentIds, {
58
+ ...target,
59
+ mode: selection.mode ?? 'any',
60
+ signal: call.signal
61
+ })
62
+ : target.agentId === undefined
63
+ ? controller.outputAny({ ...target, signal: call.signal })
64
+ : controller.output(target.agentId, { ...target, signal: call.signal });
65
+ });
52
66
  return {
53
67
  run: (input, options) => {
54
68
  return action('agent.spawn', {
@@ -83,6 +97,7 @@ export function bindAgentController(controller, action) {
83
97
  },
84
98
  output: (id, options) => wait({ ...options, agentId: id }),
85
99
  outputAny: (options) => wait({ ...options }),
100
+ outputMany: (ids, options) => wait({ ...options, agentIds: ids }),
86
101
  message: (id, message, options) => action('agent.message', { ...options, agentId: id, message }, (target, call) => controller.message(target.agentId, target.message, {
87
102
  ...(target.delivery === undefined ? {} : { delivery: target.delivery }),
88
103
  signal: call.signal
@@ -146,11 +161,12 @@ export function bindCoreAgents(backend, scope, run, hooks) {
146
161
  const copied = target(input);
147
162
  return run(async (call) => structuredClone(await backend.inspect(copied, call)), copied.signal);
148
163
  },
149
- wait: async (input) => {
164
+ wait: (async (input) => {
150
165
  if (!input || typeof input !== 'object' || Array.isArray(input))
151
166
  throw invalid('Agent wait input is invalid.');
152
- return invoke('agent.wait', input.agentId === undefined ? { ...input } : target(input));
153
- },
167
+ const selection = parseAgentWaitTargets(input);
168
+ return invoke('agent.wait', input.agentId === undefined ? { ...input, ...selection } : target(input));
169
+ }),
154
170
  message: async (input) => invoke('agent.message', target(input)),
155
171
  cancel: async (input) => invoke('agent.cancel', target(input))
156
172
  };
@@ -4,7 +4,7 @@ import type { CorePromptInput, CorePromptResult } from './prompt-input.js';
4
4
  import type { CoreModelEvent } from './model-stream.js';
5
5
  import type { CorePromptComposition, CorePromptSectionInput } from './prompt-composition.js';
6
6
  import type { CoreModelsApi } from './models.js';
7
- import type { CoreAgentsApi, CoreAgentSpawnInput, CoreAgentWaitInput, CoreAgentMessageInput, CoreAgentTarget, CoreAgentSnapshot, CoreAgentSpawnResult, CoreAgentCompletion } from './agents.js';
7
+ import type { CoreAgentsApi, CoreAgentSpawnInput, CoreAgentWaitInput, CoreAgentWaitResult, CoreAgentMessageInput, CoreAgentTarget, CoreAgentSnapshot, CoreAgentSpawnResult, CoreAgentCompletion } from './agents.js';
8
8
  import type { CoreDataApi } from './data.js';
9
9
  import type { CoreMcpApi, CoreMcpConnectInput, CoreMcpConnectionInput, CoreMcpConnectionRef, CoreMcpCatalog, CoreMcpCompletion } from './mcp.js';
10
10
  import type { CoreFilesApi, CoreFileOperations, CoreFileCompletion } from './files.js';
@@ -38,7 +38,7 @@ export interface CoreDomainOperations extends CoreFileOperations {
38
38
  };
39
39
  'agent.wait': {
40
40
  input: CoreAgentWaitInput;
41
- output: CoreAgentSnapshot;
41
+ output: CoreAgentWaitResult;
42
42
  };
43
43
  'agent.message': {
44
44
  input: CoreAgentMessageInput;
@@ -10,6 +10,7 @@ import { fetchModelResponse, modelRetryMaxAttempts, isRetryableModelRequestError
10
10
  import { anthropicThinkingBlock, assertAnthropicThinkingBudget } from './anthropic-thinking.js';
11
11
  import { resolveModelTextBlocks } from './text-blocks.js';
12
12
  import { reportModelAttempt } from './attempt-diagnostics.js';
13
+ import { productUserAgent } from './product-user-agent.js';
13
14
  export class AnthropicMessagesAdapter {
14
15
  configuration;
15
16
  constructor(configuration) {
@@ -108,6 +109,7 @@ export class AnthropicMessagesAdapter {
108
109
  'x-api-key': apiKey.reveal(),
109
110
  'anthropic-version': '2023-06-01',
110
111
  'content-type': 'application/json',
112
+ 'user-agent': productUserAgent(),
111
113
  accept: 'text/event-stream'
112
114
  },
113
115
  body: JSON.stringify({
@@ -12,6 +12,7 @@ import { acquireModelCredential, appendModelCredentialHeaders, modelCredentialSe
12
12
  import { resolveModelTextBlocks } from './text-blocks.js';
13
13
  import { jsonToolInput } from './tool-input.js';
14
14
  import { reportModelAttempt } from './attempt-diagnostics.js';
15
+ import { productUserAgent } from './product-user-agent.js';
15
16
  const WEBSOCKET_IDLE_TIMEOUT_MS = 300_000;
16
17
  const WEBSOCKET_SEND_FRAME_BYTES = 1024 * 1024;
17
18
  export const OPENAI_RESPONSES_EARLY_EOF_REASON = 'OPENAI_RESPONSES_EARLY_EOF';
@@ -1554,6 +1555,7 @@ function responsesHeaders(credential, promptCacheKey, turnState) {
1554
1555
  authorization: `Bearer ${credential.bearer.reveal()}`,
1555
1556
  'content-type': 'application/json',
1556
1557
  accept: 'text/event-stream',
1558
+ 'user-agent': productUserAgent(),
1557
1559
  'x-client-request-id': randomUUID(),
1558
1560
  ...(promptCacheKey
1559
1561
  ? {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Product identity sent to upstream model endpoints. Runtime credential headers may
3
+ * override this value per request.
4
+ */
5
+ export declare function productUserAgent(): string;
@@ -0,0 +1,22 @@
1
+ import { readFileSync } from 'node:fs';
2
+ let cachedUserAgent;
3
+ /**
4
+ * Product identity sent to upstream model endpoints. Runtime credential headers may
5
+ * override this value per request.
6
+ */
7
+ export function productUserAgent() {
8
+ cachedUserAgent ??= resolveProductUserAgent();
9
+ return cachedUserAgent;
10
+ }
11
+ function resolveProductUserAgent() {
12
+ try {
13
+ const raw = readFileSync(new URL('../../../package.json', import.meta.url), 'utf8');
14
+ const version = JSON.parse(raw).version;
15
+ if (typeof version === 'string' && version.length > 0 && version.length <= 64)
16
+ return 'MyAgentRoam/' + version + ' (' + process.platform + '; ' + process.arch + ')';
17
+ }
18
+ catch {
19
+ // Fall through to the identity-preserving fallback below.
20
+ }
21
+ return 'MyAgentRoam/unknown (' + process.platform + '; ' + process.arch + ')';
22
+ }
@@ -83,7 +83,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
83
83
  summary: string;
84
84
  truncated?: boolean;
85
85
  artifacts?: readonly MarAgentToolArtifact[];
86
- data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../subagent/contracts.js').SubagentTaskSnapshot;
86
+ data?: CodeCellEventData | import('../tools/contracts.js').ExecResult | Omit<import('../tools/contracts.js').ExecResult, 'stdout' | 'stderr'> | import('../tools/contracts.js').ApplyPatchResult | import('../tools/contracts.js').ReadEventData | import('../tools/contracts.js').ViewImageResult | import('../subagent/contracts.js').SubagentTaskSnapshot | import('../subagent/contracts.js').SubagentManyOutput;
87
87
  } | {
88
88
  type: 'tool.failed';
89
89
  resultExtensions?: readonly import('../tools/result-extensions.js').ToolResultExtension[];
@@ -1,4 +1,5 @@
1
1
  import { MarAgentError } from '../error.js';
2
+ import { parseAgentWaitTargets } from '../subagent/wait.js';
2
3
  /** Uses the Session's sole task controller, including its durable children and queue. */
3
4
  export function createExtensionAgentsBackend(input) {
4
5
  const withController = async (call, run) => {
@@ -30,12 +31,18 @@ export function createExtensionAgentsBackend(input) {
30
31
  list: (call) => withController(call, async (controller) => controller.inspectAll()),
31
32
  inspect: (target, call) => withController(call, (controller) => controller.inspect(target.agentId)),
32
33
  wait: (target, call) => withController(call, (controller) => {
34
+ const selection = parseAgentWaitTargets(target);
33
35
  const options = {
34
36
  waitMs: target.waitMs ?? 30_000,
35
37
  ...(target.untilSettled === undefined ? {} : { untilSettled: target.untilSettled }),
36
38
  ...(target.waitFor === undefined ? {} : { waitFor: target.waitFor }),
37
39
  signal: call.signal
38
40
  };
41
+ if (selection.agentIds !== undefined)
42
+ return controller.outputMany(selection.agentIds, {
43
+ ...options,
44
+ mode: selection.mode ?? 'any'
45
+ });
39
46
  if (target.untilSettled && target.agentId === undefined)
40
47
  throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'An unbounded wait requires an agent id.');
41
48
  return target.agentId === undefined
@@ -267,6 +267,7 @@ export function createSessionOpener(services) {
267
267
  let sequence = 0;
268
268
  let manualCompactionStarted = false;
269
269
  let emitTail = Promise.resolve();
270
+ let releaseSession;
270
271
  let exhaustedModelAttempt;
271
272
  // Serialize durable events before either the local stream or Host observes them.
272
273
  const emit = (event, assertLive) => {
@@ -283,7 +284,7 @@ export function createSessionOpener(services) {
283
284
  const operation = emitTail.then(async () => {
284
285
  assertLive?.();
285
286
  const persistent = eventForPersistence(full);
286
- if (persistent !== undefined)
287
+ if (persistent !== undefined && releaseSession !== undefined)
287
288
  await store.append(sessionId, {
288
289
  type: full.type,
289
290
  payload: persistent,
@@ -337,7 +338,6 @@ export function createSessionOpener(services) {
337
338
  let finalAnswer = '';
338
339
  let executionLoop;
339
340
  let manualCompaction;
340
- let releaseSession;
341
341
  let executionToolSources;
342
342
  let selectedTurn;
343
343
  const contextGc = new ContextGcRuntime();
@@ -81,6 +81,17 @@ export interface SubagentOutputOptions {
81
81
  waitFor?: SubagentWaitFor;
82
82
  signal?: AbortSignal;
83
83
  }
84
+ export type SubagentWaitMode = 'any' | 'all';
85
+ export interface SubagentManyOutputOptions extends Omit<SubagentOutputOptions, 'untilSettled'> {
86
+ mode?: SubagentWaitMode;
87
+ }
88
+ export interface SubagentManyOutput {
89
+ mode: SubagentWaitMode;
90
+ waitFor: SubagentWaitFor;
91
+ waitOutcome: SubagentWaitOutcome;
92
+ agents: SubagentTaskSnapshot[];
93
+ pendingAgentIds: string[];
94
+ }
84
95
  export interface SubagentHandledResult {
85
96
  readonly outcome: 'handled';
86
97
  readonly value?: unknown;
@@ -91,6 +102,7 @@ export interface SubagentController {
91
102
  run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentRunResult>;
92
103
  output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
93
104
  outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
105
+ outputMany(agentIds: readonly string[], options?: SubagentManyOutputOptions): Promise<SubagentManyOutput>;
94
106
  message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
95
107
  cancel(agentId: string): Promise<SubagentTaskSnapshot>;
96
108
  listSessionResources(): readonly {
@@ -1,5 +1,6 @@
1
+ import type { SubagentManyOutput, SubagentManyOutputOptions } from './contracts.js';
1
2
  import type { SubagentController, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskResult, SubagentTaskSnapshot } from './contracts.js';
2
- export type { SubagentController, SubagentLatestActivity, SubagentLatestMessage, SubagentMessageOptions, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskResult, SubagentTaskSnapshot, SubagentWaitFor, SubagentWaitOutcome } from './contracts.js';
3
+ export type { SubagentController, SubagentLatestActivity, SubagentLatestMessage, SubagentMessageOptions, SubagentManyOutput, SubagentManyOutputOptions, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskResult, SubagentTaskSnapshot, SubagentWaitFor, SubagentWaitOutcome } from './contracts.js';
3
4
  export declare class SubagentScheduler implements SubagentController {
4
5
  #private;
5
6
  readonly limits: {
@@ -118,6 +119,7 @@ export declare class SubagentScheduler implements SubagentController {
118
119
  turnId?: string;
119
120
  deliveryStatus?: "accepted" | "applied" | "cancelled" | "failed";
120
121
  }>;
122
+ outputMany(agentIds: readonly string[], options?: SubagentManyOutputOptions): Promise<SubagentManyOutput>;
121
123
  cancel(agentId: string): Promise<SubagentTaskSnapshot>;
122
124
  message(_agentId: string, _message: string): Promise<SubagentTaskSnapshot>;
123
125
  listSessionResources(): {
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { describeMarAgentError, MarAgentError } from '../error.js';
3
3
  import { marAgentReasoningEffortSchema } from '../model/configuration.js';
4
4
  import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
5
+ import { parseAgentWaitTargets, waitForAgentCondition } from './wait.js';
5
6
  export class SubagentScheduler {
6
7
  limits;
7
8
  #tasks = new Map();
@@ -111,6 +112,49 @@ export class SubagentScheduler {
111
112
  waitOutcome: completed ? 'completion' : 'timeout'
112
113
  };
113
114
  }
115
+ async outputMany(agentIds, options = {}) {
116
+ options.signal?.throwIfAborted();
117
+ const { agentIds: ids, mode = 'any' } = parseAgentWaitTargets({ agentIds, mode: options.mode });
118
+ validateOutputOptions(options);
119
+ rejectUnsupportedMessageWait(options);
120
+ if (!ids || 'untilSettled' in options)
121
+ throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Group waits require explicit targets and a bounded deadline.');
122
+ const tasks = ids.map((id) => {
123
+ const task = this.#tasks.get(id);
124
+ if (!task)
125
+ throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent task was not found.');
126
+ return task;
127
+ });
128
+ const terminal = (task) => task.status !== 'queued' && task.status !== 'running';
129
+ const snapshot = options.waitMs === undefined || options.waitMs === 0;
130
+ const satisfied = !snapshot &&
131
+ (await waitForAgentCondition({
132
+ ready: () => (mode === 'all' ? tasks.every(terminal) : tasks.some(terminal)),
133
+ subscribe: (notify) => {
134
+ this.#completionWaiters.add(notify);
135
+ return () => this.#completionWaiters.delete(notify);
136
+ },
137
+ waitMs: Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs),
138
+ signal: options.signal
139
+ }));
140
+ options.signal?.throwIfAborted();
141
+ return {
142
+ mode,
143
+ waitFor: 'completion',
144
+ waitOutcome: snapshot ? 'snapshot' : satisfied ? 'completion' : 'timeout',
145
+ pendingAgentIds: tasks.filter((task) => !terminal(task)).map((task) => task.agentId),
146
+ agents: tasks.map((task) => ({
147
+ ...this.#public(task),
148
+ waitOutcome: snapshot
149
+ ? 'snapshot'
150
+ : terminal(task)
151
+ ? 'completion'
152
+ : satisfied
153
+ ? 'snapshot'
154
+ : 'timeout'
155
+ }))
156
+ };
157
+ }
114
158
  async cancel(agentId) {
115
159
  const task = this.#tasks.get(agentId);
116
160
  if (!task)
@@ -3,6 +3,7 @@ import type { MarAgentReasoningEffort } from '../model/configuration.js';
3
3
  import type { ExecutionResult } from '../runtime/contracts.js';
4
4
  import type { MarAgentEvent } from '../runtime/events.js';
5
5
  import type { SessionRecord, SessionSource } from '../session/jsonl-store.js';
6
+ import type { SubagentManyOutput, SubagentManyOutputOptions } from './contracts.js';
6
7
  import type { SubagentController, SubagentMessageOptions, SubagentOutputOptions, SubagentRunOptions, SubagentTaskInput, SubagentTaskSnapshot } from './scheduler.js';
7
8
  export interface SubagentModelOption {
8
9
  readonly id: string;
@@ -112,6 +113,7 @@ export declare class SubagentSessionController implements SubagentController {
112
113
  run(input: SubagentTaskInput, options?: SubagentRunOptions): Promise<SubagentTaskSnapshot>;
113
114
  output(agentId: string, options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
114
115
  outputAny(options?: SubagentOutputOptions): Promise<SubagentTaskSnapshot>;
116
+ outputMany(agentIds: readonly string[], options?: SubagentManyOutputOptions): Promise<SubagentManyOutput>;
115
117
  message(agentId: string, message: string, options?: SubagentMessageOptions): Promise<SubagentTaskSnapshot>;
116
118
  cancel(agentId: string): Promise<SubagentTaskSnapshot>;
117
119
  listSessionResources(): {
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { MarAgentError, describeMarAgentError } from '../error.js';
4
4
  import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
5
+ import { parseAgentWaitTargets, waitForAgentCondition, waitDeadline } from './wait.js';
5
6
  export class SubagentSessionController {
6
7
  parentSessionId;
7
8
  rootSessionId;
@@ -52,6 +53,7 @@ export class SubagentSessionController {
52
53
  message: (id, message, options) => enter(() => this.message(id, message, options)),
53
54
  output: (id, options) => this.output(id, options),
54
55
  outputAny: (options) => this.outputAny(options),
56
+ outputMany: (ids, options) => this.outputMany(ids, options),
55
57
  cancel: (id) => this.cancel(id),
56
58
  listSessionResources: () => this.listSessionResources(),
57
59
  dispose: () => this.dispose()
@@ -166,7 +168,7 @@ export class SubagentSessionController {
166
168
  verification: [],
167
169
  evidence: [],
168
170
  terminalPending: false,
169
- terminalObserved: false,
171
+ observedTerminalRevision: -1,
170
172
  resourceRevision: ++this.#resourceRevision
171
173
  };
172
174
  if (!native)
@@ -356,6 +358,109 @@ export class SubagentSessionController {
356
358
  waitOutcome: completed ? 'completion' : 'timeout'
357
359
  };
358
360
  }
361
+ async outputMany(agentIds, options = {}) {
362
+ options.signal?.throwIfAborted();
363
+ const { agentIds: ids, mode = 'any' } = parseAgentWaitTargets({ agentIds, mode: options.mode }, this.#retainedExecution.getStore()?.native
364
+ ? Infinity
365
+ : TOOL_EXECUTION_LIMITS.agentWaitMaxAgents);
366
+ validateOutputOptions(options, this.#retainedExecution.getStore()?.native === true);
367
+ if (!ids || 'untilSettled' in options)
368
+ throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Group waits require a bounded deadline.');
369
+ const states = await Promise.all(ids.map((id) => this.#find(id)));
370
+ options.signal?.throwIfAborted();
371
+ const waitFor = options.waitFor ?? 'completion';
372
+ const targets = states.map((state) => ({
373
+ state,
374
+ turn: state.turn,
375
+ view: copyWaitState(state),
376
+ observation: this.#nativeObservation(state)
377
+ }));
378
+ const refresh = () => {
379
+ for (const target of targets) {
380
+ if (!isTerminalState(target.view) &&
381
+ (target.state.turn === target.turn ||
382
+ (target.state.turn === undefined && isTerminalState(target.state))))
383
+ target.view = copyWaitState(target.state);
384
+ }
385
+ };
386
+ const unread = (target) => target.view.messageRevision >
387
+ (target.observation?.messageRevision ?? target.state.observedMessageRevision);
388
+ const ready = (target) => isTerminalState(target.view) || (waitFor === 'message' && unread(target));
389
+ const snapshot = options.waitMs === undefined || options.waitMs === 0;
390
+ let satisfied = false;
391
+ if (!snapshot) {
392
+ const waitMs = this.#retainedExecution.getStore()?.native
393
+ ? options.waitMs
394
+ : Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
395
+ satisfied = await waitForAgentCondition({
396
+ waitMs,
397
+ signal: options.signal,
398
+ ready: () => {
399
+ refresh();
400
+ return mode === 'all' ? targets.every(ready) : targets.some(ready);
401
+ },
402
+ subscribe: (notify) => {
403
+ const listener = () => notify();
404
+ this.#activityWaiters.add(listener);
405
+ return () => this.#activityWaiters.delete(listener);
406
+ }
407
+ });
408
+ options.signal?.throwIfAborted();
409
+ }
410
+ refresh();
411
+ const pendingAgentIds = targets
412
+ .filter((target) => !ready(target))
413
+ .map((target) => target.state.agentId);
414
+ const agents = targets.map((target) => {
415
+ const { state, view, observation } = target;
416
+ const terminal = isTerminalState(view);
417
+ const outcome = snapshot
418
+ ? 'snapshot'
419
+ : terminal
420
+ ? 'completion'
421
+ : ready(target)
422
+ ? 'message'
423
+ : satisfied
424
+ ? 'snapshot'
425
+ : 'timeout';
426
+ const includeLatestMessage = outcome !== 'snapshot' && unread(target);
427
+ const includeTerminalDetails = outcome === 'completion' &&
428
+ (observation
429
+ ? observation.terminalRevision < view.resourceRevision
430
+ : state.observedTerminalRevision < view.resourceRevision);
431
+ const result = {
432
+ ...this.#snapshot(view, { includeLatestMessage, includeTerminalDetails }),
433
+ ...(target.turn === undefined ? {} : { turnId: target.turn.turnId }),
434
+ waitOutcome: outcome
435
+ };
436
+ if (includeLatestMessage) {
437
+ if (observation)
438
+ observation.messageRevision = Math.max(observation.messageRevision, view.messageRevision);
439
+ else
440
+ state.observedMessageRevision = Math.max(state.observedMessageRevision, view.messageRevision);
441
+ }
442
+ if (includeTerminalDetails) {
443
+ if (observation)
444
+ observation.terminalRevision = Math.max(observation.terminalRevision, view.resourceRevision);
445
+ else
446
+ state.observedTerminalRevision = Math.max(state.observedTerminalRevision, view.resourceRevision);
447
+ }
448
+ return result;
449
+ });
450
+ return {
451
+ mode,
452
+ waitFor,
453
+ agents,
454
+ pendingAgentIds,
455
+ waitOutcome: snapshot
456
+ ? 'snapshot'
457
+ : !satisfied
458
+ ? 'timeout'
459
+ : agents.some((agent) => agent.waitOutcome === 'message')
460
+ ? 'message'
461
+ : 'completion'
462
+ };
463
+ }
359
464
  async message(agentId, message, options = {}) {
360
465
  const execution = this.#requireExecution();
361
466
  if (typeof message !== 'string' ||
@@ -529,7 +634,6 @@ export class SubagentSessionController {
529
634
  state.verification = [];
530
635
  state.evidence = [];
531
636
  state.terminalPending = false;
532
- state.terminalObserved = false;
533
637
  state.turn = {
534
638
  turnId,
535
639
  ...(native?.ownerInstanceId === undefined ? {} : { ownerInstanceId: native.ownerInstanceId }),
@@ -730,9 +834,9 @@ export class SubagentSessionController {
730
834
  native.terminalRevision = state.resourceRevision;
731
835
  return true;
732
836
  }
733
- if (!isTerminalState(state) || state.terminalObserved)
837
+ if (!isTerminalState(state) || state.observedTerminalRevision >= state.resourceRevision)
734
838
  return false;
735
- state.terminalObserved = true;
839
+ state.observedTerminalRevision = state.resourceRevision;
736
840
  return true;
737
841
  }
738
842
  #createDelivery(state, execution, receipt, signal) {
@@ -1022,7 +1126,7 @@ function restoreAgentState(session) {
1022
1126
  verification: [],
1023
1127
  evidence: [],
1024
1128
  terminalPending: false,
1025
- terminalObserved: false,
1129
+ observedTerminalRevision: -1,
1026
1130
  resourceRevision: 0
1027
1131
  };
1028
1132
  if (terminalType === 'execution.completed' && terminal) {
@@ -1268,20 +1372,12 @@ function waitForPromise(promise, signal) {
1268
1372
  abort();
1269
1373
  });
1270
1374
  }
1271
- /** Explicit waits retain their deadline without overflowing the platform timer. */
1272
- function waitDeadline(waitMs, finish) {
1273
- const deadline = performance.now() + waitMs;
1274
- let timer;
1275
- const tick = () => {
1276
- const remaining = deadline - performance.now();
1277
- if (remaining <= 0)
1278
- finish();
1279
- else
1280
- arm(remaining);
1281
- };
1282
- const arm = (remaining) => {
1283
- timer = setTimeout(tick, Math.min(remaining, 2_147_483_647));
1375
+ /** Retain the waited Turn's evidence when the live Session moves on to another Turn. */
1376
+ function copyWaitState(state) {
1377
+ return {
1378
+ ...state,
1379
+ changedFiles: [...state.changedFiles],
1380
+ verification: [...state.verification],
1381
+ evidence: state.evidence.map((item) => ({ ...item }))
1284
1382
  };
1285
- arm(waitMs);
1286
- return () => clearTimeout(timer);
1287
1383
  }
@@ -0,0 +1,19 @@
1
+ import type { SubagentWaitMode } from './contracts.js';
2
+ /** Validate the entire target set before any observation can be consumed. */
3
+ export declare function parseAgentWaitTargets(input: {
4
+ agentId?: unknown;
5
+ agentIds?: unknown;
6
+ mode?: unknown;
7
+ }, maxAgents?: number): {
8
+ agentIds?: string[];
9
+ mode?: SubagentWaitMode;
10
+ };
11
+ /** A group owns one listener and one deadline; readiness never consumes observations. */
12
+ export declare function waitForAgentCondition(input: {
13
+ ready(): boolean;
14
+ subscribe(notify: () => void): () => void;
15
+ waitMs: number;
16
+ signal?: AbortSignal | undefined;
17
+ }): Promise<boolean>;
18
+ /** Explicit waits retain their deadline without overflowing the platform timer. */
19
+ export declare function waitDeadline(waitMs: number, finish: () => void): () => void;
@@ -0,0 +1,77 @@
1
+ import { MarAgentError } from '../error.js';
2
+ /** Validate the entire target set before any observation can be consumed. */
3
+ export function parseAgentWaitTargets(input, maxAgents = Infinity) {
4
+ if (input.agentIds === undefined || input.agentIds === null) {
5
+ if (input.mode !== undefined && input.mode !== null)
6
+ invalid();
7
+ return {};
8
+ }
9
+ if ((input.agentId !== undefined && input.agentId !== null) ||
10
+ !Array.isArray(input.agentIds) ||
11
+ input.agentIds.length === 0 ||
12
+ input.agentIds.length > maxAgents ||
13
+ input.agentIds.some((id) => typeof id !== 'string' || !id.trim()) ||
14
+ (input.mode !== undefined &&
15
+ input.mode !== null &&
16
+ input.mode !== 'any' &&
17
+ input.mode !== 'all'))
18
+ invalid();
19
+ const agentIds = input.agentIds.map((id) => id.trim());
20
+ if (new Set(agentIds).size !== agentIds.length)
21
+ invalid();
22
+ return { agentIds, mode: input.mode === 'all' ? 'all' : 'any' };
23
+ }
24
+ function invalid() {
25
+ throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Agent wait targets are invalid.');
26
+ }
27
+ /** A group owns one listener and one deadline; readiness never consumes observations. */
28
+ export function waitForAgentCondition(input) {
29
+ input.signal?.throwIfAborted();
30
+ return new Promise((resolve, reject) => {
31
+ let settled = false;
32
+ let unsubscribe = () => { };
33
+ let cancelTimer = () => { };
34
+ const finish = (ready, error) => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ unsubscribe();
39
+ cancelTimer();
40
+ input.signal?.removeEventListener('abort', abort);
41
+ if (error === undefined)
42
+ resolve(ready);
43
+ else
44
+ reject(error);
45
+ };
46
+ const notify = () => {
47
+ if (input.ready())
48
+ finish(true);
49
+ };
50
+ const abort = () => finish(false, input.signal?.reason ??
51
+ new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
52
+ unsubscribe = input.subscribe(notify);
53
+ cancelTimer = waitDeadline(input.waitMs, () => finish(input.ready()));
54
+ input.signal?.addEventListener('abort', abort, { once: true });
55
+ if (input.signal?.aborted)
56
+ abort();
57
+ else
58
+ notify();
59
+ });
60
+ }
61
+ /** Explicit waits retain their deadline without overflowing the platform timer. */
62
+ export function waitDeadline(waitMs, finish) {
63
+ const deadline = performance.now() + waitMs;
64
+ let timer;
65
+ const tick = () => {
66
+ const remaining = deadline - performance.now();
67
+ if (remaining <= 0)
68
+ finish();
69
+ else
70
+ arm(remaining);
71
+ };
72
+ const arm = (remaining) => {
73
+ timer = setTimeout(tick, Math.min(remaining, 2_147_483_647));
74
+ };
75
+ arm(waitMs);
76
+ return () => clearTimeout(timer);
77
+ }
@@ -3,5 +3,5 @@ import type { ToolRuntimeContext } from '../runtime-context.js';
3
3
  export declare const agentWaitToolDefinition: ClientToolDefinition;
4
4
  export declare function executeAgentWait(arguments_: unknown, context: ToolRuntimeContext): Promise<{
5
5
  content: string;
6
- data: import("../../subagent/contracts.js").SubagentTaskSnapshot;
6
+ data: import("../../subagent/contracts.js").SubagentTaskSnapshot | import("../../subagent/contracts.js").SubagentManyOutput;
7
7
  }>;
@@ -1,14 +1,29 @@
1
1
  import { MarAgentError } from '../../error.js';
2
2
  import { TOOL_EXECUTION_LIMITS } from '../execution-limits.js';
3
+ import { parseAgentWaitTargets } from '../../subagent/wait.js';
3
4
  export const agentWaitToolDefinition = {
4
5
  name: 'agent_wait',
5
6
  allowedCallers: ['direct'],
6
7
  parallelSafety: 'serial',
7
- description: 'Read or wait a bounded time for a child agent result. Wait only when the next local step depends on the child result; otherwise continue useful non-overlapping work. 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_wait instead (tools.exec_wait in Code Mode). 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.',
8
+ description: 'Read or wait a bounded time for a child agent result. Wait only when the next local step depends on the child result; otherwise continue useful non-overlapping work. 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_wait instead (tools.exec_wait in Code Mode). 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. Use agentIds with mode any (default) or all to wait for an explicit non-empty set of up to 8 distinct child IDs; never combine agentId and agentIds or use mode without agentIds. mode and waitFor are independent. A group uses one deadline and returns mode, waitFor, waitOutcome, agents in input order, and pendingAgentIds. Failed, cancelled and interrupted turns also satisfy completion; terminal state satisfies message waits too. Group waitMs=0 returns only snapshots without consuming messages. Single-target and untargeted results include 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.',
8
9
  inputSchema: {
9
10
  type: 'object',
10
11
  properties: {
11
12
  agentId: { type: 'string' },
13
+ agentIds: {
14
+ type: 'array',
15
+ items: { type: 'string', minLength: 1 },
16
+ minItems: 1,
17
+ maxItems: TOOL_EXECUTION_LIMITS.agentWaitMaxAgents,
18
+ uniqueItems: true,
19
+ description: 'Explicit child-agent set. Mutually exclusive with agentId.'
20
+ },
21
+ mode: {
22
+ type: 'string',
23
+ enum: ['any', 'all'],
24
+ default: 'any',
25
+ description: 'For agentIds only: return when any or all selected children satisfy waitFor.'
26
+ },
12
27
  waitFor: {
13
28
  type: 'string',
14
29
  enum: ['completion', 'message'],
@@ -36,16 +51,18 @@ export async function executeAgentWait(arguments_, context) {
36
51
  waitFor: value.waitFor ?? 'completion',
37
52
  signal: context.signal
38
53
  };
39
- const task = value.agentId === undefined
40
- ? await subagents.outputAny(options)
41
- : await subagents.output(value.agentId, options);
54
+ const task = value.agentIds !== undefined
55
+ ? await subagents.outputMany(value.agentIds, { ...options, mode: value.mode ?? 'any' })
56
+ : value.agentId === undefined
57
+ ? await subagents.outputAny(options)
58
+ : await subagents.output(value.agentId, options);
42
59
  return { content: JSON.stringify(task), data: task };
43
60
  }
44
61
  function parseAgentWaitInput(value) {
45
62
  if (!value || typeof value !== 'object' || Array.isArray(value))
46
63
  return invalidAgentWait();
47
64
  const input = value;
48
- if (Object.keys(input).some((key) => !['agentId', 'waitFor', 'waitMs'].includes(key)) ||
65
+ if (Object.keys(input).some((key) => !['agentId', 'agentIds', 'mode', 'waitFor', 'waitMs'].includes(key)) ||
49
66
  (input.agentId !== undefined &&
50
67
  input.agentId !== null &&
51
68
  (typeof input.agentId !== 'string' || input.agentId.trim().length === 0)) ||
@@ -60,6 +77,7 @@ function parseAgentWaitInput(value) {
60
77
  input.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
61
78
  return invalidAgentWait();
62
79
  return {
80
+ ...parseAgentWaitTargets(input, TOOL_EXECUTION_LIMITS.agentWaitMaxAgents),
63
81
  ...(typeof input.agentId === 'string' ? { agentId: input.agentId.trim() } : {}),
64
82
  ...(input.waitFor === 'completion' || input.waitFor === 'message'
65
83
  ? { waitFor: input.waitFor }
@@ -638,9 +638,8 @@ function streamText(value, emit) {
638
638
  return;
639
639
  }
640
640
  const ancestors = new Set();
641
- const write = (item, key, path) => {
642
- if (item && typeof item === 'object' && typeof item.toJSON === 'function')
643
- item = item.toJSON(key);
641
+ const toJSONValue = (item, key) => item && typeof item === 'object' && typeof item.toJSON === 'function' ? item.toJSON(key) : item;
642
+ const write = (item, path) => {
644
643
  if (['bigint', 'function', 'symbol', 'undefined'].includes(typeof item))
645
644
  throw Object.assign(new TypeError(`Only JSON values may be emitted; ${path} has type ${typeof item}. Omit optional fields or use null explicitly.`), {
646
645
  code: 'MAR_AGENT_TOOL_INPUT_INVALID'
@@ -670,7 +669,7 @@ function streamText(value, emit) {
670
669
  for (let index = 0; index < length; index++) {
671
670
  if (index)
672
671
  emit(',');
673
- write(item[index], String(index), `${path}[${index}]`.slice(0, 256));
672
+ write(toJSONValue(item[index], String(index)) ?? null, `${path}[${index}]`.slice(0, 256));
674
673
  }
675
674
  emit(']');
676
675
  }
@@ -683,23 +682,25 @@ function streamText(value, emit) {
683
682
  typeof item.mimeType === 'string' &&
684
683
  item.mimeType.startsWith('image/');
685
684
  for (const name of names) {
685
+ const field = toJSONValue(item[name], name);
686
+ if (field === undefined)
687
+ continue;
686
688
  if (!first)
687
689
  emit(',');
688
690
  first = false;
689
691
  chunks(name, true);
690
692
  emit(':');
691
- const field = item[name];
692
693
  write(imagePayload &&
693
694
  (name === 'dataBase64' || (name === 'data' && item.type === 'image')) &&
694
695
  typeof field === 'string'
695
696
  ? '[image payload omitted; use observe.image]'
696
- : field, name, `${path}[${JSON.stringify(name.slice(0, 64))}]`.slice(0, 256));
697
+ : field, `${path}[${JSON.stringify(name.slice(0, 64))}]`.slice(0, 256));
697
698
  }
698
699
  emit('}');
699
700
  }
700
701
  ancestors.delete(item);
701
702
  };
702
- write(value, '', '$');
703
+ write(toJSONValue(value, ''), '$');
703
704
  }
704
705
  function compilerSourceLocation(error, source) {
705
706
  if (error?.name !== 'SyntaxError' ||
@@ -46,6 +46,7 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
46
46
  agentWaitWaitFloorMs: 10000;
47
47
  agentWaitDefaultWaitMs: 30000;
48
48
  agentWaitMaxWaitMs: 300000;
49
+ agentWaitMaxAgents: 8;
49
50
  subagentDescriptionMaxCharacters: 80;
50
51
  subagentPromptMaxBytes: 131072;
51
52
  subagentMessageMaxBytes: 131072;
@@ -46,6 +46,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
46
46
  agentWaitWaitFloorMs: 10_000,
47
47
  agentWaitDefaultWaitMs: 30_000,
48
48
  agentWaitMaxWaitMs: 300_000,
49
+ agentWaitMaxAgents: 8,
49
50
  subagentDescriptionMaxCharacters: 80,
50
51
  subagentPromptMaxBytes: 131_072,
51
52
  subagentMessageMaxBytes: 131_072,
@@ -1,4 +1,4 @@
1
- import type { SubagentTaskSnapshot } from '../subagent/contracts.js';
1
+ import type { SubagentTaskSnapshot, SubagentManyOutput } from '../subagent/contracts.js';
2
2
  import type { ApplyPatchResult, ApplyPatchPartialResult, CodeCellEventData, ExecResult, MarAgentToolArtifact, ToolExecutionResult, ReadEventData, ViewImageResult } from './contracts.js';
3
3
  import type { ToolRuntimeImage } from './runtime-context.js';
4
4
  export declare function currentExecutionImages(modelInputImages: readonly {
@@ -10,7 +10,7 @@ export declare function boundedModelToolOutput(toolName: string, value: string,
10
10
  truncated: boolean;
11
11
  };
12
12
  /** Project only known, validated tool result shapes into public structured events. */
13
- export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SubagentTaskSnapshot | undefined;
13
+ export declare function structuredToolEventData(toolName: string, value: unknown): CodeCellEventData | ExecResult | ApplyPatchResult | ReadEventData | ViewImageResult | SubagentTaskSnapshot | SubagentManyOutput | undefined;
14
14
  /** Partial patch facts are allowlisted; arbitrary error details never become public event data. */
15
15
  export declare function structuredToolFailureData(toolName: string, error: unknown): ApplyPatchPartialResult | undefined;
16
16
  export declare function structuredToolArtifacts(value: readonly MarAgentToolArtifact[] | undefined): readonly MarAgentToolArtifact[] | undefined;
@@ -35,6 +35,18 @@ export function boundedModelToolOutput(toolName, value, data) {
35
35
  export function structuredToolEventData(toolName, value) {
36
36
  if (!isRecord(value))
37
37
  return undefined;
38
+ if (toolName === 'agent_wait' &&
39
+ (value.mode === 'any' || value.mode === 'all') &&
40
+ (value.waitFor === 'completion' || value.waitFor === 'message') &&
41
+ ['snapshot', 'message', 'completion', 'timeout'].includes(String(value.waitOutcome)) &&
42
+ Array.isArray(value.agents) &&
43
+ value.agents.length > 0 &&
44
+ value.agents.length <= TOOL_EXECUTION_LIMITS.agentWaitMaxAgents &&
45
+ value.agents.every((agent) => isRecord(agent) && isSubagentTaskSnapshot(agent)) &&
46
+ Array.isArray(value.pendingAgentIds) &&
47
+ value.pendingAgentIds.every((id) => typeof id === 'string' &&
48
+ value.agents.some((agent) => agent.agentId === id)))
49
+ return value;
38
50
  if ((toolName === 'code_exec' || toolName === 'code_exec_wait') &&
39
51
  typeof value.cellId === 'string' &&
40
52
  ['running', 'completed', 'failed', 'cancelled'].includes(String(value.status)))
@@ -1,10 +1,11 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  import { constants } from 'node:fs';
3
- import { lstat, open, readFile } from 'node:fs/promises';
3
+ import { lstat, open } from 'node:fs/promises';
4
4
  import { promisify } from 'node:util';
5
5
  import { z } from 'zod';
6
6
  import { MarAgentError } from '../agent-core/error.js';
7
7
  import { SecretValue } from '../agent-core/model/configuration.js';
8
+ import { productUserAgent } from '../agent-core/model/product-user-agent.js';
8
9
  const MAX_AUTH_FILE_BYTES = 1024 * 1024;
9
10
  const TOKEN_MAX_BYTES = 131_072;
10
11
  const AUTH_FALLBACK_LIFETIME_MS = 8 * 24 * 60 * 60 * 1000;
@@ -52,7 +53,7 @@ export class CodexAuthFileCredentialProvider {
52
53
  headers: {
53
54
  'ChatGPT-Account-ID': new SecretValue(parsed.accountId),
54
55
  originator: 'myagentroam',
55
- 'user-agent': await productUserAgent()
56
+ 'user-agent': productUserAgent()
56
57
  }
57
58
  };
58
59
  this.#cached = { fingerprint: file.fingerprint, credential };
@@ -263,18 +264,6 @@ function record(value) {
263
264
  ? value
264
265
  : {};
265
266
  }
266
- let productUserAgentPromise;
267
- async function productUserAgent() {
268
- productUserAgentPromise ??= readFile(new URL('../../package.json', import.meta.url), 'utf8')
269
- .then((content) => {
270
- const value = JSON.parse(content);
271
- if (typeof value.version !== 'string')
272
- throw new Error('package version unavailable');
273
- return `MyAgentRoam/${value.version} (${process.platform}; ${process.arch})`;
274
- })
275
- .catch(() => `MyAgentRoam/unknown (${process.platform}; ${process.arch})`);
276
- return await productUserAgentPromise;
277
- }
278
267
  function unsafeFile(cause) {
279
268
  return new MarAgentError('MAR_AGENT_CREDENTIAL_FILE_UNSAFE', 'Codex auth file permissions or file type are unsafe.', cause === undefined ? {} : { cause });
280
269
  }
package/dist/index.d.ts CHANGED
@@ -29,7 +29,7 @@ export type { CoreFilesApi, CoreFilesBackend, CoreFilePath, CoreFileWrite, CoreF
29
29
  export type { CoreHostExtensionPorts, CoreHostToolSource, CoreHostToolsBackend } from './agent-core/host/contracts.js';
30
30
  export type { CoreEnvironmentApi, CoreEnvironmentBackend, CoreEnvironmentKey, CoreEnvironmentSet, CoreEnvironmentChange, CoreEnvironmentCompletion } from './agent-core/extensions/environment.js';
31
31
  export type { CoreMcpApi, CoreMcpBackend, CoreMcpConnectionBackend, CoreMcpToolCall, CoreMcpServerDefinition, CoreMcpServerRef, CoreMcpServerRegistration, CoreMcpConnectionRef, CoreMcpConnectInput, CoreMcpConnectionInput, CoreMcpCallInput, CoreMcpCatalog, CoreMcpCompletion } from './agent-core/extensions/mcp.js';
32
- export type { CoreAgentsApi, CoreAgentsBackend, CoreAgentSpawnInput, CoreAgentTarget, CoreAgentWaitInput, CoreAgentMessageInput, CoreAgentSnapshot, CoreAgentHandledResult, CoreAgentSpawnResult, CoreAgentCompletion } from './agent-core/extensions/agents.js';
32
+ export type { CoreAgentsApi, CoreAgentsBackend, CoreAgentSpawnInput, CoreAgentTarget, CoreAgentWaitInput, CoreAgentWaitResult, CoreAgentManySnapshot, CoreAgentMessageInput, CoreAgentSnapshot, CoreAgentHandledResult, CoreAgentSpawnResult, CoreAgentCompletion } from './agent-core/extensions/agents.js';
33
33
  export type { CoreSettingsApi, CoreSettingsBackend, CoreSettingsReadInput, CoreSettingsUpdate, CoreSettingsDescription, CoreSettingDefinition, CoreSettingsSnapshot, CoreSettingsResult, CoreSettingsCompletion } from './agent-core/extensions/settings.js';
34
34
  export type { CoreNetworkApi, CoreNetworkBackend, CoreHttpRequest, CoreHttpResponse, CoreHttpCompletion } from './agent-core/extensions/network.js';
35
35
  export type { CoreSessionApi, CoreSessionReceipt, CoreSessionInspection, CoreSessionDescription, CoreSessionMessages, CoreSessionMessagesInput, CoreSessionMessage, CoreSessionOperationStatus, CoreAppendInput, CoreSubmitInput, CoreInputSource, CoreCompactInput, CoreSessionActionResult } from './agent-core/extensions/session-input.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.10.27",
3
+ "version": "0.10.28",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",