@myagentroam/agent 0.11.39 → 0.11.40

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 (161) hide show
  1. package/dist/cli/catalog-file.d.ts +2 -0
  2. package/dist/cli/main.js +11 -4
  3. package/dist/cli/subagent/control.d.ts +56 -0
  4. package/dist/cli/subagent/control.js +219 -0
  5. package/dist/cli/subagent/history.d.ts +17 -0
  6. package/dist/cli/subagent/history.js +85 -0
  7. package/dist/cli/subagent/index.d.ts +10 -0
  8. package/dist/cli/subagent/index.js +94 -0
  9. package/dist/cli/subagent/native.d.ts +91 -0
  10. package/dist/cli/subagent/native.js +359 -0
  11. package/dist/cli/subagent/observations.d.ts +57 -0
  12. package/dist/cli/subagent/observations.js +351 -0
  13. package/dist/cli/subagent/query.d.ts +4 -0
  14. package/dist/cli/subagent/query.js +114 -0
  15. package/dist/cli/subagent/resources.d.ts +3 -0
  16. package/dist/cli/subagent/resources.js +112 -0
  17. package/dist/cli/subagent/snapshot.d.ts +77 -0
  18. package/dist/cli/subagent/snapshot.js +127 -0
  19. package/dist/cli/subagent/start.d.ts +24 -0
  20. package/dist/cli/subagent/start.js +201 -0
  21. package/dist/cli/subagent/wait-output.d.ts +19 -0
  22. package/dist/cli/subagent/wait-output.js +56 -0
  23. package/dist/cli/subagent/wait.d.ts +175 -0
  24. package/dist/cli/subagent/wait.js +453 -0
  25. package/dist/extensions/contracts.d.ts +2 -5
  26. package/dist/extensions/manager.d.ts +0 -2
  27. package/dist/extensions/manager.js +1 -2
  28. package/dist/extensions/models.d.ts +3 -0
  29. package/dist/extensions/operation.d.ts +8 -0
  30. package/dist/extensions/operation.js +3 -0
  31. package/dist/extensions/services.d.ts +9 -1
  32. package/dist/extensions/services.js +26 -3
  33. package/dist/extensions/session-context.d.ts +18 -0
  34. package/dist/extensions/session-context.js +4 -0
  35. package/dist/extensions/session-input.d.ts +2 -2
  36. package/dist/extensions/session-input.js +3 -4
  37. package/dist/extensions/session-messages.d.ts +1 -1
  38. package/dist/extensions/session-tools.js +20 -3
  39. package/dist/extensions/session.d.ts +1 -4
  40. package/dist/extensions/session.js +2 -16
  41. package/dist/extensions/tools.js +21 -3
  42. package/dist/extensions.d.ts +5 -2
  43. package/dist/extensions.js +2 -1
  44. package/dist/host/local-agent-host.d.ts +2 -2
  45. package/dist/host/local-exec-tool.d.ts +1 -1
  46. package/dist/host/local-exec-tool.js +28 -3
  47. package/dist/host/local-tool-registry.d.ts +2 -2
  48. package/dist/index.d.ts +2 -2
  49. package/dist/model/configuration.d.ts +3 -1
  50. package/dist/model/configuration.js +1 -0
  51. package/dist/model/openai-responses.js +5 -0
  52. package/dist/model/responses-request.js +9 -0
  53. package/dist/prompts/index.js +1 -3
  54. package/dist/prompts/resources.d.ts +1 -5
  55. package/dist/prompts/resources.js +3 -12
  56. package/dist/prompts/subagent.d.ts +1 -1
  57. package/dist/prompts/subagent.js +1 -1
  58. package/dist/runtime/context/gc-candidates-web.d.ts +6 -0
  59. package/dist/runtime/context/gc-candidates-web.js +53 -1
  60. package/dist/runtime/context/gc-candidates.js +1 -9
  61. package/dist/runtime/context/gc.js +19 -11
  62. package/dist/runtime/context/projection.js +1 -0
  63. package/dist/runtime/events.d.ts +4 -3
  64. package/dist/runtime/execution-handle.d.ts +2 -0
  65. package/dist/runtime/wait.js +12 -2
  66. package/dist/sdk/agent.js +94 -5
  67. package/dist/sdk/execution-context-coordinator.d.ts +2 -3
  68. package/dist/sdk/execution-context-coordinator.js +3 -3
  69. package/dist/sdk/execution-input.d.ts +3 -4
  70. package/dist/sdk/execution-input.js +31 -25
  71. package/dist/sdk/execution-round-preparation.d.ts +1 -1
  72. package/dist/sdk/execution-round-preparation.js +4 -4
  73. package/dist/sdk/execution-tool-catalog.js +2 -10
  74. package/dist/sdk/execution-tool-scope.d.ts +1 -3
  75. package/dist/sdk/execution-tool-scope.js +10 -12
  76. package/dist/sdk/extension-models.js +3 -0
  77. package/dist/sdk/extension-session-launch.d.ts +3 -2
  78. package/dist/sdk/extension-session-launch.js +2 -3
  79. package/dist/sdk/extension-tools.js +0 -2
  80. package/dist/sdk/native-steer.d.ts +2 -2
  81. package/dist/sdk/native-steer.js +2 -2
  82. package/dist/sdk/session-composition.d.ts +2 -17
  83. package/dist/sdk/session-execution-factory.d.ts +2 -3
  84. package/dist/sdk/session-execution-factory.js +20 -114
  85. package/dist/sdk/session-execution.d.ts +13 -7
  86. package/dist/sdk/session-execution.js +11 -3
  87. package/dist/sdk/session-extension-messages.d.ts +1 -3
  88. package/dist/sdk/session-extension-messages.js +14 -9
  89. package/dist/sdk/session-extension-prompt.js +1 -3
  90. package/dist/sdk/session-maintenance.d.ts +1 -0
  91. package/dist/sdk/session-maintenance.js +16 -7
  92. package/dist/sdk/session-open.d.ts +2 -3
  93. package/dist/sdk/session-open.js +5 -24
  94. package/dist/sdk/session-runtime.d.ts +19 -8
  95. package/dist/sdk/session-runtime.js +36 -30
  96. package/dist/sdk/session.d.ts +3 -2
  97. package/dist/sdk/session.js +22 -89
  98. package/dist/sdk/types.d.ts +36 -0
  99. package/dist/session/catalog.d.ts +22 -1
  100. package/dist/session/catalog.js +102 -0
  101. package/dist/session/context-gc-checkpoint.js +30 -1
  102. package/dist/session/context-gc-contract.d.ts +7 -0
  103. package/dist/session/context-gc-replacements.d.ts +3 -0
  104. package/dist/session/context-gc-replacements.js +26 -0
  105. package/dist/session/store/contracts.d.ts +2 -2
  106. package/dist/session/store/jsonl/directory-index.d.ts +1 -1
  107. package/dist/session/store/jsonl/directory-index.js +18 -17
  108. package/dist/session/store/jsonl/history.js +1 -1
  109. package/dist/session/store/jsonl/index-transaction.js +2 -8
  110. package/dist/session/store/jsonl/jsonl-store.d.ts +1 -1
  111. package/dist/session/store/jsonl/jsonl-store.js +24 -9
  112. package/dist/session/types.d.ts +1 -0
  113. package/dist/tools/agent/sleep.js +1 -1
  114. package/dist/tools/callers.js +1 -12
  115. package/dist/tools/code-mode/dispatch.js +5 -2
  116. package/dist/tools/code-mode/index.d.ts +1 -1
  117. package/dist/tools/code-mode/index.js +2 -0
  118. package/dist/tools/contracts.d.ts +20 -0
  119. package/dist/tools/execution-limits.d.ts +5 -20
  120. package/dist/tools/execution-limits.js +0 -2
  121. package/dist/tools/execution.js +16 -4
  122. package/dist/tools/local/exec.js +8 -2
  123. package/dist/tools/output-projection.d.ts +5 -2
  124. package/dist/tools/output-projection.js +133 -92
  125. package/dist/tools/registry.js +1 -13
  126. package/dist/tools/runtime-context.d.ts +1 -21
  127. package/package.json +2 -2
  128. package/dist/extensions/agents.d.ts +0 -100
  129. package/dist/extensions/agents.js +0 -121
  130. package/dist/runtime/context/gc-candidates-subagent.d.ts +0 -6
  131. package/dist/runtime/context/gc-candidates-subagent.js +0 -100
  132. package/dist/sdk/extension-agent-controller.d.ts +0 -3
  133. package/dist/sdk/extension-agent-controller.js +0 -69
  134. package/dist/sdk/extension-agents.d.ts +0 -9
  135. package/dist/sdk/extension-agents.js +0 -58
  136. package/dist/sdk/subagent-backend.d.ts +0 -3
  137. package/dist/sdk/subagent-backend.js +0 -49
  138. package/dist/subagent/contracts.d.ts +0 -107
  139. package/dist/subagent/contracts.js +0 -1
  140. package/dist/subagent/limits.d.ts +0 -17
  141. package/dist/subagent/limits.js +0 -17
  142. package/dist/subagent/model-update.d.ts +0 -2
  143. package/dist/subagent/model-update.js +0 -81
  144. package/dist/subagent/session-controller.d.ts +0 -87
  145. package/dist/subagent/session-controller.js +0 -1343
  146. package/dist/subagent/task.d.ts +0 -134
  147. package/dist/subagent/task.js +0 -436
  148. package/dist/subagent/turn-contracts.d.ts +0 -64
  149. package/dist/subagent/turn-contracts.js +0 -1
  150. package/dist/subagent/wait-targets.d.ts +0 -10
  151. package/dist/subagent/wait-targets.js +0 -26
  152. package/dist/subagent/wait.d.ts +0 -26
  153. package/dist/subagent/wait.js +0 -73
  154. package/dist/tools/agent/agent-cancel.d.ts +0 -7
  155. package/dist/tools/agent/agent-cancel.js +0 -29
  156. package/dist/tools/agent/agent-message.d.ts +0 -7
  157. package/dist/tools/agent/agent-message.js +0 -57
  158. package/dist/tools/agent/agent-start.d.ts +0 -12
  159. package/dist/tools/agent/agent-start.js +0 -106
  160. package/dist/tools/agent/agent-wait.d.ts +0 -7
  161. package/dist/tools/agent/agent-wait.js +0 -91
@@ -1,1343 +0,0 @@
1
- import { waitMetadata } from '../runtime/wait.js';
2
- import { AsyncLocalStorage } from 'node:async_hooks';
3
- import { randomUUID } from 'node:crypto';
4
- import { AgentError, describeAgentError } from '../error.js';
5
- import { SUBAGENT_LIMITS } from './limits.js';
6
- import { parseAgentWaitTargets, SubagentWaitCoordinator, waitForAgentGroup } from './wait.js';
7
- import { SubagentTask, observeAgentEvent } from './task.js';
8
- const RECENT_TERMINAL_RESOURCE_LIMIT = 16;
9
- export class SubagentSessionController {
10
- parentSessionId;
11
- rootSessionId;
12
- backend;
13
- limits;
14
- #agents = new Map();
15
- // Discovery order is independent of the order in which history is requested.
16
- #discovered = new Map();
17
- #loading = new Map();
18
- #readers = new Map();
19
- #allReaders = 0;
20
- #queue = [];
21
- #waiters = new SubagentWaitCoordinator();
22
- modelMessages = {
23
- pending: () => [...this.#agents.values()].some((state) => state.messageRevision > state.observedMessageRevision),
24
- subscribe: (listener) => {
25
- const notify = (activity) => {
26
- if (activity.kind === 'message')
27
- listener();
28
- };
29
- return this.#waiters.subscribeActivity(notify);
30
- }
31
- };
32
- #nativeObservations = new Map();
33
- #activityRevision = 0;
34
- #resourceRevision = 0;
35
- #running = 0;
36
- #disposed = false;
37
- #creationBudget = { count: 0 };
38
- #retainedExecution = new AsyncLocalStorage();
39
- #execution;
40
- #pendingDeliveries = new Set();
41
- constructor(parentSessionId, rootSessionId, backend, limits = { maxTasks: 16, maxConcurrent: 16 }) {
42
- this.parentSessionId = parentSessionId;
43
- this.rootSessionId = rootSessionId;
44
- this.backend = backend;
45
- this.limits = limits;
46
- }
47
- static async open(input) {
48
- const controller = new SubagentSessionController(input.parentSessionId, input.rootSessionId, input.backend, input.limits);
49
- const sessions = await input.backend.listChildren(input.parentSessionId);
50
- // Map insertion order is creation order; values independently rank activity.
51
- for (const session of sessions)
52
- controller.#discovered.set(session.id, 0);
53
- for (const session of [...sessions].sort((a, b) => a.lastActivityAt.localeCompare(b.lastActivityAt))) {
54
- controller.#discovered.set(session.id, ++controller.#resourceRevision);
55
- }
56
- return controller;
57
- }
58
- async beginParentExecution(execution) {
59
- if (this.#disposed)
60
- throw new AgentError('MAR_AGENT_SUBAGENT_LIMIT', 'Subagent controller is disposed.');
61
- this.#execution = execution;
62
- this.#creationBudget = { count: 0 };
63
- for (const state of this.#agents.values())
64
- await this.#publishTerminal(state);
65
- this.#releaseOldDetails();
66
- }
67
- /** A retained cell must not borrow a later model Execution's identity or task budget. */
68
- captureExecution(executionId) {
69
- const enter = this.captureParentFrame(executionId);
70
- return {
71
- run: (input, options) => enter(() => this.run(input, options)),
72
- message: (id, message, options) => enter(() => this.message(id, message, options)),
73
- output: (id, options) => this.output(id, options),
74
- outputAny: (options) => this.outputAny(options),
75
- outputMany: (ids, options) => this.outputMany(ids, options),
76
- cancel: (id) => this.cancel(id),
77
- listSessionResources: () => this.listSessionResources(),
78
- dispose: () => this.dispose()
79
- };
80
- }
81
- /** SDK physical tool ingress shares the same captured parent as its model controls. */
82
- captureParentFrame(executionId) {
83
- const execution = executionId === undefined ? this.#requireExecution() : this.#execution;
84
- if (!execution || (executionId !== undefined && execution.executionId !== executionId))
85
- throw new AgentError('MAR_AGENT_SUBAGENT_FAILED', 'Parent Execution source is unavailable.');
86
- const context = (executionId === undefined
87
- ? this.#retainedExecution.getStore()
88
- : undefined) ?? {
89
- execution,
90
- budget: this.#creationBudget
91
- };
92
- return (run) => this.#retainedExecution.run(context, run);
93
- }
94
- withOperation(operation, run) {
95
- operation.signal.throwIfAborted();
96
- if (!operation.operationId.trim())
97
- throw new AgentError('MAR_AGENT_SUBAGENT_FAILED', 'Parent operation identity is invalid.');
98
- return this.#retainedExecution.run({ execution: operation, budget: { count: 0 }, native: true }, run);
99
- }
100
- async inspect(agentId) {
101
- return this.#using([agentId], async () => {
102
- return this.#snapshot(await this.#find(agentId), { full: true });
103
- });
104
- }
105
- inspectAll() {
106
- if (this.#disposed)
107
- throw new AgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
108
- return this.#orderedStates().map((state) => this.#snapshot(state, { full: true }));
109
- }
110
- async list(signal) {
111
- return this.#using(undefined, async () => {
112
- await this.prepareAll(signal);
113
- return this.inspectAll();
114
- });
115
- }
116
- /** Full listing is explicit; ordinary opens and executions never require it. */
117
- async prepareAll(signal) {
118
- for (const id of this.#discovered.keys()) {
119
- signal?.throwIfAborted();
120
- await this.#restoreDiscovered(id);
121
- }
122
- signal?.throwIfAborted();
123
- }
124
- async prepareSessionResources(signal) {
125
- const candidates = [...this.#discovered]
126
- .filter(([id]) => {
127
- const state = this.#agents.get(id);
128
- return !state || isTerminalState(state);
129
- })
130
- .sort(([a, ar], [b, br]) => (this.#agents.get(b)?.resourceRevision ?? br) -
131
- (this.#agents.get(a)?.resourceRevision ?? ar));
132
- let prepared = 0;
133
- for (const [id] of candidates) {
134
- signal?.throwIfAborted();
135
- const state = await this.#restoreDiscovered(id);
136
- if (state && isTerminalState(state) && ++prepared === RECENT_TERMINAL_RESOURCE_LIMIT)
137
- break;
138
- }
139
- signal?.throwIfAborted();
140
- this.#releaseOldDetails();
141
- }
142
- async #restoreDiscovered(agentId) {
143
- try {
144
- return await this.#find(agentId);
145
- }
146
- catch (error) {
147
- if (!(error instanceof AgentError) || error.code !== 'MAR_AGENT_SUBAGENT_NOT_FOUND')
148
- throw error;
149
- this.#discovered.delete(agentId);
150
- return undefined;
151
- }
152
- }
153
- #orderedStates() {
154
- return [...this.#discovered.keys()].flatMap((id) => {
155
- const state = this.#agents.get(id);
156
- return state ? [state] : [];
157
- });
158
- }
159
- collectModelUpdates() {
160
- return [...this.#agents.values()]
161
- .filter((state) => state.messageRevision > state.observedMessageRevision ||
162
- (isTerminalState(state) &&
163
- state.terminalRevision !== undefined &&
164
- state.terminalRevision > state.observedTerminalRevision))
165
- .sort((left, right) => left.resourceRevision - right.resourceRevision)
166
- .slice(0, SUBAGENT_LIMITS.agentWaitMaxAgents)
167
- .map((state) => {
168
- const messageRevision = state.messageRevision;
169
- const terminalRevision = isTerminalState(state) ? state.terminalRevision : undefined;
170
- const includeTerminalDetails = terminalRevision !== undefined && terminalRevision > state.observedTerminalRevision;
171
- const snapshot = this.#snapshot(state, {
172
- includeLatestMessage: messageRevision > state.observedMessageRevision,
173
- includeTerminalDetails,
174
- full: false
175
- });
176
- return {
177
- snapshot,
178
- truncated: snapshot.latestMessage?.truncated === true ||
179
- (includeTerminalDetails &&
180
- (snapshot.summary !== state.summary ||
181
- (snapshot.changedFiles?.length ?? 0) < state.changedFiles.length)),
182
- acknowledge: () => {
183
- state.acknowledgeMessage(messageRevision);
184
- if (includeTerminalDetails)
185
- state.acknowledgeTerminal(terminalRevision);
186
- }
187
- };
188
- });
189
- }
190
- async cancelOwned(ownerInstanceId) {
191
- for (const pending of this.#pendingDeliveries)
192
- if (pending.ownerInstanceId === ownerInstanceId)
193
- pending.delivery.settled?.('cancelled');
194
- const results = await Promise.allSettled([...this.#agents.values()]
195
- .filter((state) => state.turn?.ownerInstanceId === ownerInstanceId)
196
- .map((state) => this.#control(state, async () => {
197
- if (state.turn?.ownerInstanceId === ownerInstanceId)
198
- await this.#cancelState(state);
199
- })));
200
- const failure = results.find((result) => result.status === 'rejected');
201
- this.#nativeObservations.delete(ownerInstanceId);
202
- if (failure?.status === 'rejected')
203
- throw failure.reason;
204
- }
205
- endParentExecution(executionId) {
206
- if (this.#execution?.executionId === executionId)
207
- this.#execution = undefined;
208
- }
209
- parentExecution(executionId) {
210
- const execution = this.#retainedExecution.getStore()?.execution ?? this.#execution;
211
- return execution?.executionId === executionId ? { ...execution, executionId } : undefined;
212
- }
213
- async run(input, options = {}) {
214
- options.signal?.throwIfAborted();
215
- options.turnSignal?.throwIfAborted();
216
- if (options.waitFor !== undefined &&
217
- options.waitFor !== 'start' &&
218
- options.waitFor !== 'completion')
219
- throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Task start wait is invalid.');
220
- const execution = this.#requireExecution();
221
- const native = this.#retainedExecution.getStore()?.native === true;
222
- validateTaskInput(input, native);
223
- if (!native &&
224
- (this.#retainedExecution.getStore()?.budget ?? this.#creationBudget).count >=
225
- this.limits.maxTasks)
226
- throw new AgentError('MAR_AGENT_SUBAGENT_LIMIT', 'Subagent task limit was reached.');
227
- const source = {
228
- type: 'subagent',
229
- parentSessionId: this.parentSessionId,
230
- rootSessionId: this.rootSessionId,
231
- ...(execution.executionId === undefined ? {} : { parentExecutionId: execution.executionId }),
232
- ...(native && 'operationId' in execution
233
- ? {
234
- parentOperationId: execution.operationId,
235
- ...(execution.ownerInstanceId === undefined
236
- ? {}
237
- : { ownerInstanceId: execution.ownerInstanceId }),
238
- ...(execution.extensionId === undefined ? {} : { extensionId: execution.extensionId })
239
- }
240
- : {}),
241
- role: input.role,
242
- description: input.description.trim()
243
- };
244
- const created = await this.backend.createSession({
245
- title: input.description.trim(),
246
- source
247
- });
248
- try {
249
- if (this.#disposed)
250
- throw new AgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
251
- this.#requireExecution();
252
- options.signal?.throwIfAborted();
253
- options.turnSignal?.throwIfAborted();
254
- }
255
- catch (error) {
256
- await this.#deleteUnstartedSession(created.id, error);
257
- }
258
- return this.#using([created.id], async () => {
259
- const state = new SubagentTask({
260
- agentId: created.id,
261
- description: source.description,
262
- role: input.role,
263
- ...(source.ownerInstanceId === undefined
264
- ? {}
265
- : { ownerInstanceId: source.ownerInstanceId }),
266
- ...(source.extensionId === undefined ? {} : { extensionId: source.extensionId }),
267
- status: 'queued',
268
- messageRevision: 0,
269
- observedMessageRevision: 0,
270
- latestMessageActivityRevision: 0,
271
- changedFiles: [],
272
- verification: [],
273
- evidence: [],
274
- terminalPending: false,
275
- observedTerminalRevision: -1,
276
- resourceRevision: ++this.#resourceRevision
277
- });
278
- if (!native)
279
- (this.#retainedExecution.getStore()?.budget ?? this.#creationBudget).count++;
280
- this.#agents.set(state.agentId, state);
281
- this.#discovered.set(state.agentId, state.resourceRevision);
282
- const knownModel = execution.models.get(input.modelId ?? execution.modelId);
283
- if (knownModel) {
284
- state.selectModel(knownModel);
285
- }
286
- try {
287
- const selected = resolveModel(execution, input);
288
- state.selectModel(selected);
289
- }
290
- catch (error) {
291
- this.#setFailure(state, error);
292
- }
293
- try {
294
- await options.onCreated?.(this.#receiptSnapshot(state));
295
- if (this.#disposed)
296
- throw new AgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
297
- this.#requireExecution();
298
- options.signal?.throwIfAborted();
299
- }
300
- catch (error) {
301
- await this.#deleteUnstartedSession(state.agentId, error);
302
- }
303
- if (state.status === 'failed') {
304
- await this.#publishTerminal(state);
305
- this.#observeTerminal(state);
306
- return this.#snapshot(state);
307
- }
308
- this.#queueTurn(state, input.prompt, execution, [], undefined, undefined, options.signal, options.turnSignal);
309
- if (options.waitFor === 'start' || (options.waitFor === undefined && input.background))
310
- return this.#receiptSnapshot(state);
311
- await waitForPromise(state.turn.completion, options.signal);
312
- const includeLatestMessage = this.#hasUnreadMessage(state);
313
- const includeTerminalDetails = this.#observeTerminal(state);
314
- this.#observeLatestMessage(state);
315
- return this.#snapshot(state, { includeLatestMessage, includeTerminalDetails });
316
- });
317
- }
318
- async output(agentId, options = {}) {
319
- return this.#using([agentId], async () => {
320
- options.signal?.throwIfAborted();
321
- const state = await this.#find(agentId);
322
- options.signal?.throwIfAborted();
323
- validateOutputOptions(options, this.#retainedExecution.getStore()?.native === true);
324
- if (options.untilSettled) {
325
- if (state.turn)
326
- await waitForPromise(state.turn.completion, options.signal);
327
- options.signal?.throwIfAborted();
328
- const includeLatestMessage = this.#hasUnreadMessage(state);
329
- const includeTerminalDetails = this.#observeTerminal(state);
330
- this.#observeLatestMessage(state);
331
- return {
332
- ...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
333
- waitOutcome: 'completion'
334
- };
335
- }
336
- if (options.waitMs === undefined || options.waitMs === 0)
337
- return {
338
- ...this.#snapshot(state, {
339
- includeLatestMessage: false,
340
- includeTerminalDetails: false
341
- }),
342
- waitOutcome: 'snapshot'
343
- };
344
- if (isTerminalState(state)) {
345
- const includeLatestMessage = this.#hasUnreadMessage(state);
346
- const includeTerminalDetails = this.#observeTerminal(state);
347
- this.#observeLatestMessage(state);
348
- return {
349
- ...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
350
- waitOutcome: 'completion'
351
- };
352
- }
353
- const waitFor = options.waitFor ?? 'completion';
354
- if (waitFor === 'message' && this.#hasUnreadMessage(state)) {
355
- this.#observeLatestMessage(state);
356
- return { ...this.#snapshot(state), waitOutcome: 'message' };
357
- }
358
- const waitMs = this.#retainedExecution.getStore()?.native
359
- ? options.waitMs
360
- : Math.max(options.waitMs, SUBAGENT_LIMITS.agentWaitWaitFloorMs);
361
- if (waitFor === 'completion') {
362
- const completed = await this.#waitForCompletion(state, waitMs, options.signal, options.waitInterrupt);
363
- options.signal?.throwIfAborted();
364
- const includeLatestMessage = this.#hasUnreadMessage(state);
365
- const includeTerminalDetails = completed ? this.#observeTerminal(state) : false;
366
- if (includeLatestMessage)
367
- this.#observeLatestMessage(state);
368
- return {
369
- ...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
370
- ...waitMetadata(completed ? 'completion' : options.waitInterrupt?.pending() ? 'interrupted' : 'timeout')
371
- };
372
- }
373
- const activity = await this.#waitForMessageOrCompletion([state], waitMs, options.signal, options.waitInterrupt);
374
- options.signal?.throwIfAborted();
375
- const includeLatestMessage = activity?.kind === 'message' ||
376
- (activity?.kind === 'completion' && this.#hasUnreadMessage(activity.state));
377
- const includeTerminalDetails = activity?.kind === 'completion' ? this.#observeTerminal(activity.state) : false;
378
- if (activity)
379
- this.#observeLatestMessage(activity.state);
380
- return {
381
- ...this.#snapshot(activity?.state ?? state, {
382
- includeLatestMessage,
383
- includeTerminalDetails
384
- }),
385
- ...waitMetadata(activity?.kind ?? (options.waitInterrupt?.pending() ? 'interrupted' : 'timeout'))
386
- };
387
- });
388
- }
389
- async outputAny(options = {}) {
390
- return this.#using(undefined, async () => {
391
- options.signal?.throwIfAborted();
392
- if (options.untilSettled)
393
- throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'An unbounded wait requires an agent id.');
394
- validateOutputOptions(options, this.#retainedExecution.getStore()?.native === true);
395
- const hasLive = [...this.#agents.values()].some((state) => !isTerminalState(state));
396
- if (!hasLive) {
397
- for (const id of [...this.#discovered.keys()].reverse()) {
398
- options.signal?.throwIfAborted();
399
- if (await this.#restoreDiscovered(id))
400
- break;
401
- }
402
- }
403
- options.signal?.throwIfAborted();
404
- const states = this.#orderedStates();
405
- if (states.length === 0)
406
- throw new AgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
407
- const live = states.filter((state) => state.status === 'queued' || state.status === 'running');
408
- if (options.waitMs === undefined || options.waitMs === 0)
409
- return {
410
- ...this.#snapshot(live.at(-1) ?? states.at(-1), {
411
- includeLatestMessage: false,
412
- includeTerminalDetails: false
413
- }),
414
- waitOutcome: 'snapshot'
415
- };
416
- if (live.length === 0) {
417
- const state = states.at(-1);
418
- const includeLatestMessage = this.#hasUnreadMessage(state);
419
- const includeTerminalDetails = this.#observeTerminal(state);
420
- this.#observeLatestMessage(state);
421
- return {
422
- ...this.#snapshot(state, { includeLatestMessage, includeTerminalDetails }),
423
- waitOutcome: 'completion'
424
- };
425
- }
426
- const waitFor = options.waitFor ?? 'completion';
427
- if (waitFor === 'message') {
428
- const pending = this.#oldestUnreadMessage(live);
429
- if (pending) {
430
- this.#observeLatestMessage(pending);
431
- return { ...this.#snapshot(pending), waitOutcome: 'message' };
432
- }
433
- }
434
- const waitMs = this.#retainedExecution.getStore()?.native
435
- ? options.waitMs
436
- : Math.max(options.waitMs, SUBAGENT_LIMITS.agentWaitWaitFloorMs);
437
- if (waitFor === 'message') {
438
- const activity = await this.#waitForMessageOrCompletion(live, waitMs, options.signal, options.waitInterrupt);
439
- options.signal?.throwIfAborted();
440
- const includeLatestMessage = activity?.kind === 'message' ||
441
- (activity?.kind === 'completion' && this.#hasUnreadMessage(activity.state));
442
- const includeTerminalDetails = activity?.kind === 'completion' ? this.#observeTerminal(activity.state) : false;
443
- if (activity)
444
- this.#observeLatestMessage(activity.state);
445
- return {
446
- ...this.#snapshot(activity?.state ?? live.at(-1), {
447
- includeLatestMessage,
448
- includeTerminalDetails
449
- }),
450
- ...waitMetadata(activity?.kind ?? (options.waitInterrupt?.pending() ? 'interrupted' : 'timeout'))
451
- };
452
- }
453
- const completed = await this.#waitForAnyCompletion(live, waitMs, options.signal, options.waitInterrupt);
454
- options.signal?.throwIfAborted();
455
- const state = completed ?? this.#oldestUnreadMessage(live) ?? live.at(-1);
456
- const includeLatestMessage = this.#hasUnreadMessage(state);
457
- const includeTerminalDetails = completed ? this.#observeTerminal(state) : false;
458
- if (includeLatestMessage)
459
- this.#observeLatestMessage(state);
460
- return {
461
- ...this.#snapshot(state, {
462
- includeLatestMessage,
463
- includeTerminalDetails
464
- }),
465
- ...waitMetadata(completed ? 'completion' : options.waitInterrupt?.pending() ? 'interrupted' : 'timeout')
466
- };
467
- });
468
- }
469
- async outputMany(agentIds, options = {}) {
470
- options.signal?.throwIfAborted();
471
- const { agentIds: ids, mode = 'any' } = parseAgentWaitTargets({ agentIds, mode: options.mode }, this.#retainedExecution.getStore()?.native ? Infinity : SUBAGENT_LIMITS.agentWaitMaxAgents);
472
- validateOutputOptions(options, this.#retainedExecution.getStore()?.native === true);
473
- if (!ids || 'untilSettled' in options)
474
- throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Group waits require a bounded deadline.');
475
- return this.#using(ids, async () => {
476
- const states = await Promise.all(ids.map((id) => this.#find(id)));
477
- options.signal?.throwIfAborted();
478
- const waitFor = options.waitFor ?? 'completion';
479
- const targets = states.map((state) => ({
480
- state,
481
- turn: state.turn,
482
- view: copyWaitState(state),
483
- observation: this.#nativeObservation(state)
484
- }));
485
- const refresh = () => {
486
- for (const target of targets) {
487
- if (!isTerminalState(target.view) &&
488
- (target.state.turn === target.turn ||
489
- (target.state.turn === undefined && isTerminalState(target.state))))
490
- target.view = copyWaitState(target.state);
491
- }
492
- };
493
- const unread = (target) => target.view.messageRevision >
494
- (target.observation?.messageRevision ?? target.state.observedMessageRevision);
495
- const ready = (target) => isTerminalState(target.view) || (waitFor === 'message' && unread(target));
496
- const { snapshot, satisfied } = await waitForAgentGroup({
497
- targets,
498
- mode,
499
- refresh,
500
- ready,
501
- waitMs: options.waitMs === undefined ||
502
- options.waitMs === 0 ||
503
- this.#retainedExecution.getStore()?.native
504
- ? options.waitMs
505
- : Math.max(options.waitMs, SUBAGENT_LIMITS.agentWaitWaitFloorMs),
506
- signal: options.signal,
507
- interrupt: options.waitInterrupt,
508
- subscribe: (notify) => {
509
- const listener = () => notify();
510
- return this.#waiters.subscribeActivity(listener);
511
- }
512
- });
513
- const pendingAgentIds = targets
514
- .filter((target) => !ready(target))
515
- .map((target) => target.state.agentId);
516
- const agents = targets.map((target) => {
517
- const { state, view, observation } = target;
518
- const terminal = isTerminalState(view);
519
- const outcome = snapshot
520
- ? 'snapshot'
521
- : terminal
522
- ? 'completion'
523
- : ready(target)
524
- ? 'message'
525
- : satisfied
526
- ? 'snapshot'
527
- : options.waitInterrupt?.pending()
528
- ? 'interrupted'
529
- : 'timeout';
530
- const includeLatestMessage = outcome !== 'snapshot' && unread(target);
531
- const includeTerminalDetails = outcome === 'completion' &&
532
- (observation
533
- ? observation.terminalRevision < view.resourceRevision
534
- : state.observedTerminalRevision < view.resourceRevision);
535
- const result = {
536
- ...this.#snapshot(view, { includeLatestMessage, includeTerminalDetails }),
537
- ...(target.turn === undefined ? {} : { turnId: target.turn.turnId }),
538
- ...waitMetadata(outcome)
539
- };
540
- if (includeLatestMessage) {
541
- if (observation)
542
- observation.messageRevision = Math.max(observation.messageRevision, view.messageRevision);
543
- else
544
- state.acknowledgeMessage(view.messageRevision);
545
- }
546
- if (includeTerminalDetails) {
547
- if (observation)
548
- observation.terminalRevision = Math.max(observation.terminalRevision, view.resourceRevision);
549
- else
550
- state.acknowledgeTerminal(view.resourceRevision);
551
- }
552
- return result;
553
- });
554
- return {
555
- mode,
556
- waitFor,
557
- agents,
558
- pendingAgentIds,
559
- ...waitMetadata(snapshot
560
- ? 'snapshot'
561
- : !satisfied
562
- ? options.waitInterrupt?.pending()
563
- ? 'interrupted'
564
- : 'timeout'
565
- : agents.some((agent) => agent.waitOutcome === 'message')
566
- ? 'message'
567
- : 'completion')
568
- };
569
- });
570
- }
571
- async message(agentId, message, options = {}) {
572
- return this.#using([agentId], async () => {
573
- const execution = this.#requireExecution();
574
- if (typeof message !== 'string' ||
575
- message.trim().length === 0 ||
576
- (this.#retainedExecution.getStore()?.native !== true &&
577
- Buffer.byteLength(message) > SUBAGENT_LIMITS.subagentMessageMaxBytes) ||
578
- (options.delivery !== undefined &&
579
- options.delivery !== 'append' &&
580
- options.delivery !== 'replace'))
581
- throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Agent message input is invalid.');
582
- const state = await this.#find(agentId);
583
- return this.#control(state, async () => {
584
- this.#requireExecution();
585
- options.signal?.throwIfAborted();
586
- // Resolve before cancelling so an unavailable model does not stop valid work.
587
- const selected = resolveExistingModel(execution, state);
588
- const turnId = state.turn?.turnId ?? randomUUID();
589
- const receipt = { messageId: randomUUID(), turnId, deliveryStatus: 'accepted' };
590
- const mutableReceipt = receipt;
591
- const { delivery, waitSignal } = this.#createDelivery(state, execution, mutableReceipt, options.signal);
592
- try {
593
- const sendSignal = options.signal === undefined
594
- ? waitSignal
595
- : AbortSignal.any([waitSignal, options.signal]);
596
- sendSignal.throwIfAborted();
597
- if (options.delivery === 'replace')
598
- await waitForPromise(this.#cancelState(state), sendSignal);
599
- this.#requireExecution();
600
- sendSignal.throwIfAborted();
601
- if (state.turn && state.status !== 'running' && state.status !== 'queued') {
602
- await waitForPromise(state.turn.completion, sendSignal);
603
- }
604
- if (state.status === 'running' && state.turn) {
605
- const turn = state.turn;
606
- if (!turn.handle)
607
- await waitForPromise(turn.handleReady, sendSignal);
608
- this.#requireExecution();
609
- options.signal?.throwIfAborted();
610
- delivery.signal?.throwIfAborted();
611
- if (state.status === 'running' &&
612
- state.turn === turn &&
613
- turn.handle?.enqueueMessage(message, delivery)) {
614
- delivery.accepted();
615
- state.setReceipt(mutableReceipt);
616
- return this.#messageReceiptSnapshot(state);
617
- }
618
- await waitForPromise(turn.completion, sendSignal);
619
- }
620
- else if (state.status === 'queued' && state.turn) {
621
- delivery.accepted();
622
- state.appendMessage({ text: message, delivery });
623
- state.setReceipt(mutableReceipt);
624
- return this.#messageReceiptSnapshot(state);
625
- }
626
- this.#requireExecution();
627
- options.signal?.throwIfAborted();
628
- delivery.signal?.throwIfAborted();
629
- state.selectModel(selected);
630
- // A closing turn may have rejected append; reserve a fresh identity for its successor.
631
- mutableReceipt.turnId = randomUUID();
632
- delivery.accepted();
633
- state.setReceipt(mutableReceipt);
634
- this.#queueTurn(state, message, execution, [], mutableReceipt.turnId, delivery, options.signal);
635
- return this.#messageReceiptSnapshot(state);
636
- }
637
- catch (error) {
638
- delivery.settled?.(options.signal?.aborted || ('signal' in execution && execution.signal.aborted)
639
- ? 'cancelled'
640
- : 'failed');
641
- throw error;
642
- }
643
- });
644
- });
645
- }
646
- async cancel(agentId) {
647
- return this.#using([agentId], async () => {
648
- const state = await this.#find(agentId);
649
- await this.#control(state, () => this.#cancelState(state));
650
- const includeLatestMessage = this.#hasUnreadMessage(state);
651
- const includeTerminalDetails = this.#observeTerminal(state);
652
- this.#observeLatestMessage(state);
653
- return this.#snapshot(state, { includeLatestMessage, includeTerminalDetails });
654
- });
655
- }
656
- #control(state, operation) {
657
- return state.control(operation);
658
- }
659
- async #cancelState(state) {
660
- if (state.status === 'queued' && state.turn) {
661
- const turn = state.turn;
662
- const index = this.#queue.indexOf(state);
663
- if (index >= 0)
664
- this.#queue.splice(index, 1);
665
- state.cancelQueued(true);
666
- this.#touchResource(state, true);
667
- this.#closeReceipt(state);
668
- await this.#publishTerminal(state);
669
- state.settleQueuedTurn(turn);
670
- this.#notifyCompletion(state);
671
- state.releaseTurn(turn);
672
- this.#drain();
673
- }
674
- else if (state.status === 'running' && state.turn) {
675
- const turn = state.turn;
676
- const needsHandle = !turn.handle;
677
- state.requestCancel(turn);
678
- if (needsHandle) {
679
- await turn.handleReady;
680
- }
681
- await turn.completion;
682
- }
683
- }
684
- listSessionResources() {
685
- const states = [...this.#agents.values()];
686
- const active = states
687
- .filter((state) => state.status === 'queued' || state.status === 'running')
688
- .sort((left, right) => right.resourceRevision - left.resourceRevision);
689
- const terminal = states
690
- .filter((state) => state.status !== 'queued' && state.status !== 'running')
691
- .sort((left, right) => right.resourceRevision - left.resourceRevision)
692
- .slice(0, RECENT_TERMINAL_RESOURCE_LIMIT);
693
- return [...active, ...terminal].map((state) => ({
694
- agentId: state.agentId,
695
- status: state.status,
696
- description: state.description
697
- }));
698
- }
699
- async dispose() {
700
- if (this.#disposed)
701
- return;
702
- this.#disposed = true;
703
- for (const pending of this.#pendingDeliveries)
704
- pending.delivery.settled?.('cancelled');
705
- for (const state of this.#queue.splice(0)) {
706
- const turn = state.turn;
707
- state.cancelQueued(false);
708
- this.#closeReceipt(state);
709
- if (turn) {
710
- state.settleQueuedTurn(turn);
711
- }
712
- this.#notifyCompletion(state);
713
- if (turn)
714
- state.releaseTurn(turn);
715
- }
716
- for (const state of this.#agents.values()) {
717
- if (state.status !== 'running' || !state.turn)
718
- continue;
719
- state.requestCancel(state.turn);
720
- }
721
- await Promise.allSettled([...this.#agents.values()].flatMap((state) => state.turn === undefined ? [] : [state.turn.completion]));
722
- this.#execution = undefined;
723
- this.#nativeObservations.clear();
724
- }
725
- #queueTurn(state, prompt, execution, pendingMessages, turnId = randomUUID(), delivery, signal, turnSignal) {
726
- const native = this.#retainedExecution.getStore()?.native === true && 'operationId' in execution
727
- ? execution
728
- : undefined;
729
- const sourceSignal = turnSignal ??
730
- (native === undefined
731
- ? undefined
732
- : signal === undefined
733
- ? native.signal
734
- : AbortSignal.any([native.signal, signal]));
735
- this.#touchResource(state);
736
- let turn;
737
- const abort = () => {
738
- void this.#control(state, async () => {
739
- if (state.turn === turn)
740
- await this.#cancelState(state);
741
- }).catch(() => undefined);
742
- };
743
- turn = state.queueTurn({
744
- turnId,
745
- ...(native?.ownerInstanceId === undefined
746
- ? {}
747
- : { ownerInstanceId: native.ownerInstanceId }),
748
- ...(native?.origin === undefined
749
- ? {}
750
- : { origin: Object.freeze({ ...native.origin, signal: sourceSignal }) }),
751
- ...(delivery === undefined ? {} : { delivery }),
752
- prompt,
753
- start: execution.startTurn,
754
- pendingMessages
755
- }, () => sourceSignal?.removeEventListener('abort', abort));
756
- sourceSignal?.addEventListener('abort', abort, { once: true });
757
- this.#queue.push(state);
758
- this.#drain();
759
- if (sourceSignal?.aborted)
760
- abort();
761
- }
762
- #drain() {
763
- while (!this.#disposed && this.#running < this.limits.maxConcurrent && this.#queue.length > 0) {
764
- const state = this.#queue.shift();
765
- const turn = state.turn;
766
- if (!turn || !state.markRunning(turn))
767
- continue;
768
- this.#touchResource(state);
769
- this.#running++;
770
- void this.#startAndConsumeTurn(state, turn);
771
- }
772
- }
773
- async #startAndConsumeTurn(state, turn) {
774
- const outcome = await state.runTurn(turn, () => this.#activityRevision + 1, () => {
775
- this.#activityRevision++;
776
- this.#touchResource(state);
777
- this.#notifyActivity({ state, kind: 'message' });
778
- }, () => {
779
- this.#touchResource(state, true);
780
- this.#closeReceipt(state);
781
- });
782
- if (outcome === 'opening-failed') {
783
- this.#running--;
784
- state.settleTurn(turn);
785
- state.releaseTurn(turn);
786
- this.#notifyCompletion(state);
787
- await this.#publishTerminal(state);
788
- this.#drain();
789
- return;
790
- }
791
- try {
792
- await this.#publishTerminal(state);
793
- }
794
- finally {
795
- this.#closeReceipt(state);
796
- this.#running--;
797
- state.settleTurn(turn);
798
- this.#notifyCompletion(state);
799
- state.releaseTurn(turn);
800
- this.#drain();
801
- }
802
- }
803
- #setFailure(state, error) {
804
- state.fail(error);
805
- this.#touchResource(state, true);
806
- }
807
- #touchResource(state, terminal = false) {
808
- state.touch(++this.#resourceRevision, terminal);
809
- }
810
- #waitForAnyCompletion(candidates, waitMs, signal, interrupt) {
811
- return this.#waiters.waitCompletion((state) => candidates.includes(state), () => candidates.find(isTerminalState), waitMs, signal, interrupt);
812
- }
813
- #waitForCompletion(state, waitMs, signal, interrupt) {
814
- return this.#waiters.waitCompletion((completed) => completed === state, () => (isTerminalState(state) ? state : undefined), waitMs, signal, interrupt);
815
- }
816
- #notifyCompletion(state) {
817
- this.#waiters.notifyCompletion(state);
818
- this.#notifyActivity({ state, kind: 'completion' });
819
- }
820
- #waitForMessageOrCompletion(candidates, waitMs, signal, interrupt) {
821
- return this.#waiters.waitActivity((activity) => candidates.includes(activity.state), () => {
822
- const pending = this.#oldestUnreadMessage(candidates);
823
- if (pending)
824
- return { state: pending, kind: 'message' };
825
- const terminal = candidates.find(isTerminalState);
826
- return terminal ? { state: terminal, kind: 'completion' } : undefined;
827
- }, waitMs, signal, interrupt);
828
- }
829
- #notifyActivity(activity) {
830
- this.#waiters.notifyActivity(activity);
831
- }
832
- #hasUnreadMessage(state) {
833
- return (state.messageRevision >
834
- (this.#nativeObservation(state)?.messageRevision ?? state.observedMessageRevision));
835
- }
836
- #nativeObservation(state) {
837
- const context = this.#retainedExecution.getStore();
838
- if (!context?.native || !('operationId' in context.execution))
839
- return undefined;
840
- const owner = context.execution.ownerInstanceId ?? context.execution.operationId;
841
- let observations = this.#nativeObservations.get(owner);
842
- if (!observations)
843
- this.#nativeObservations.set(owner, (observations = new Map()));
844
- let observation = observations.get(state.agentId);
845
- if (!observation)
846
- observations.set(state.agentId, (observation = { messageRevision: 0, terminalRevision: 0 }));
847
- return observation;
848
- }
849
- #oldestUnreadMessage(states) {
850
- return states
851
- .filter((state) => this.#hasUnreadMessage(state))
852
- .sort((left, right) => left.latestMessageActivityRevision - right.latestMessageActivityRevision)
853
- .at(0);
854
- }
855
- #observeLatestMessage(state) {
856
- const native = this.#nativeObservation(state);
857
- if (native)
858
- native.messageRevision = state.messageRevision;
859
- else
860
- state.acknowledgeMessage(state.messageRevision);
861
- }
862
- #observeTerminal(state) {
863
- const native = this.#nativeObservation(state);
864
- if (native) {
865
- if (!isTerminalState(state) || native.terminalRevision === state.resourceRevision)
866
- return false;
867
- native.terminalRevision = state.resourceRevision;
868
- return true;
869
- }
870
- if (!isTerminalState(state) || state.observedTerminalRevision >= state.resourceRevision)
871
- return false;
872
- state.acknowledgeTerminal(state.resourceRevision);
873
- return true;
874
- }
875
- #createDelivery(state, execution, receipt, signal) {
876
- const native = this.#retainedExecution.getStore()?.native === true && 'operationId' in execution
877
- ? execution
878
- : undefined;
879
- const sourceSignal = native === undefined
880
- ? undefined
881
- : signal === undefined
882
- ? native.signal
883
- : AbortSignal.any([native.signal, signal]);
884
- const controller = new AbortController();
885
- const release = () => {
886
- sourceSignal?.removeEventListener('abort', abort);
887
- this.#pendingDeliveries.delete(pending);
888
- };
889
- const delivery = {
890
- accepted: () => {
891
- pending.admitted = true;
892
- },
893
- messageId: receipt.messageId,
894
- ...(native?.origin === undefined ? {} : { origin: native.origin }),
895
- ...(native === undefined ? {} : { signal: controller.signal }),
896
- applied: () => {
897
- // A durable commit remains applied even if cancellation races its completion callback.
898
- state.updateDelivery(receipt, 'applied');
899
- release();
900
- },
901
- settled: (status) => {
902
- if (!state.updateDelivery(receipt, status))
903
- return;
904
- controller.abort(new AgentError('MAR_AGENT_CANCELLED', 'Agent message was cancelled.'));
905
- release();
906
- }
907
- };
908
- const pending = {
909
- state,
910
- receipt,
911
- delivery,
912
- admitted: false,
913
- ...(native?.ownerInstanceId === undefined ? {} : { ownerInstanceId: native.ownerInstanceId })
914
- };
915
- const abort = () => delivery.settled?.('cancelled');
916
- this.#pendingDeliveries.add(pending);
917
- sourceSignal?.addEventListener('abort', abort, { once: true });
918
- if (sourceSignal?.aborted)
919
- abort();
920
- return { delivery, waitSignal: controller.signal };
921
- }
922
- #closeReceipt(state) {
923
- for (const pending of this.#pendingDeliveries)
924
- if (pending.admitted &&
925
- pending.state === state &&
926
- pending.receipt.turnId === state.turn?.turnId)
927
- pending.delivery.settled?.(state.status === 'cancelled' ? 'cancelled' : 'failed');
928
- }
929
- async #deleteUnstartedSession(agentId, error) {
930
- this.#agents.delete(agentId);
931
- this.#discovered.delete(agentId);
932
- try {
933
- await this.backend.deleteSession(agentId);
934
- }
935
- catch (cleanupError) {
936
- const original = describeAgentError(error, 'MAR_AGENT_SUBAGENT_FAILED');
937
- const cleanup = describeAgentError(cleanupError, 'MAR_AGENT_SUBAGENT_FAILED');
938
- throw new AgentError(original.code, `${original.message} Cleanup also failed: ${cleanup.message}`, { cause: cleanupError });
939
- }
940
- throw error;
941
- }
942
- async #using(ids, operation) {
943
- if (ids === undefined)
944
- this.#allReaders++;
945
- else
946
- for (const id of ids)
947
- this.#readers.set(id, (this.#readers.get(id) ?? 0) + 1);
948
- try {
949
- return await operation();
950
- }
951
- finally {
952
- if (ids === undefined)
953
- this.#allReaders--;
954
- else
955
- for (const id of ids) {
956
- const count = this.#readers.get(id) - 1;
957
- if (count)
958
- this.#readers.set(id, count);
959
- else
960
- this.#readers.delete(id);
961
- }
962
- }
963
- }
964
- #releaseOldDetails() {
965
- if (this.#allReaders)
966
- return;
967
- const terminal = [...this.#agents.values()]
968
- .filter(isTerminalState)
969
- .sort((a, b) => b.resourceRevision - a.resourceRevision);
970
- for (const state of terminal.slice(RECENT_TERMINAL_RESOURCE_LIMIT)) {
971
- if (state.detailsEvicted ||
972
- !state.restorable ||
973
- state.turn ||
974
- state.controlTail ||
975
- this.#readers.has(state.agentId) ||
976
- this.#loading.has(state.agentId) ||
977
- state.terminalPending ||
978
- state.messageRevision > state.observedMessageRevision ||
979
- (state.terminalRevision !== undefined &&
980
- state.terminalRevision > state.observedTerminalRevision) ||
981
- [...this.#pendingDeliveries].some((pending) => pending.state === state))
982
- continue;
983
- // Keep identity and cursors for old acknowledgements; only the disk-backed body unloads.
984
- state.evictDetails();
985
- }
986
- }
987
- async #find(agentId) {
988
- if (this.#disposed)
989
- throw new AgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
990
- const known = this.#agents.get(agentId);
991
- if (known && !known.detailsEvicted)
992
- return known;
993
- const pending = this.#loading.get(agentId);
994
- if (pending)
995
- return pending;
996
- const load = (async () => {
997
- const persisted = await this.backend.loadChild(this.parentSessionId, agentId);
998
- if (this.#disposed)
999
- throw new AgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
1000
- if (!persisted) {
1001
- this.#agents.delete(agentId);
1002
- this.#discovered.delete(agentId);
1003
- throw new AgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
1004
- }
1005
- const existing = this.#agents.get(agentId);
1006
- if (existing && !existing.detailsEvicted)
1007
- return existing;
1008
- const restored = restoreAgentState(persisted);
1009
- if (existing) {
1010
- existing.restoreDetails(restored);
1011
- return existing;
1012
- }
1013
- let revision = this.#discovered.get(agentId);
1014
- if (revision === undefined) {
1015
- revision = ++this.#resourceRevision;
1016
- this.#discovered.set(agentId, revision);
1017
- }
1018
- restored.resourceRevision = revision;
1019
- const task = new SubagentTask(restored);
1020
- this.#agents.set(agentId, task);
1021
- return task;
1022
- })();
1023
- this.#loading.set(agentId, load);
1024
- try {
1025
- return await load;
1026
- }
1027
- finally {
1028
- if (this.#loading.get(agentId) === load)
1029
- this.#loading.delete(agentId);
1030
- }
1031
- }
1032
- #requireExecution() {
1033
- if (this.#disposed)
1034
- throw new AgentError('MAR_AGENT_DISPOSED', 'Subagent controller is disposed.');
1035
- const execution = this.#retainedExecution.getStore()?.execution ?? this.#execution;
1036
- if (!execution)
1037
- throw new AgentError('MAR_AGENT_SUBAGENT_FAILED', 'Subagent control requires an active parent execution.');
1038
- if ('signal' in execution)
1039
- execution.signal.throwIfAborted();
1040
- return execution;
1041
- }
1042
- #receiptSnapshot(state) {
1043
- return this.#snapshot(state, {
1044
- includeLatestMessage: false,
1045
- includeLatestActivity: false,
1046
- includeTerminalDetails: false
1047
- });
1048
- }
1049
- #messageReceiptSnapshot(state) {
1050
- // A follow-up starts a new observation boundary; messages from the preceding state
1051
- // must not be returned later as fresh progress for the newly requested work.
1052
- this.#observeLatestMessage(state);
1053
- return this.#receiptSnapshot(state);
1054
- }
1055
- #snapshot(state, options = {}) {
1056
- const terminal = state.status === 'completed' ||
1057
- state.status === 'failed' ||
1058
- state.status === 'cancelled' ||
1059
- state.status === 'interrupted';
1060
- const includeLatestMessage = options.includeLatestMessage ?? true;
1061
- const includeLatestActivity = options.includeLatestActivity ?? true;
1062
- const includeTerminalDetails = options.includeTerminalDetails ?? true;
1063
- const full = options.full ?? this.#retainedExecution.getStore()?.native === true;
1064
- const latestMessage = !includeLatestMessage || state.latestMessage === undefined
1065
- ? undefined
1066
- : full
1067
- ? { ...state.latestMessage }
1068
- : projectLatestMessage(state.latestMessage, terminal);
1069
- const summary = !includeTerminalDetails || state.summary === undefined
1070
- ? undefined
1071
- : full
1072
- ? state.summary
1073
- : truncateUtf8(state.summary, SUBAGENT_LIMITS.subagentSummaryMaxBytes);
1074
- const omitDuplicateMessage = !full &&
1075
- terminal &&
1076
- latestMessage?.phase === 'final_answer' &&
1077
- summary !== undefined &&
1078
- summary === state.latestMessage?.text &&
1079
- (state.receipt === undefined || state.receipt.turnId === latestMessage.turnId);
1080
- return {
1081
- agentId: state.agentId,
1082
- description: state.description,
1083
- status: state.status,
1084
- ...(full && this.#retainedExecution.getStore()?.native === true
1085
- ? {
1086
- role: state.role,
1087
- ...(state.extensionId === undefined ? {} : { extensionId: state.extensionId })
1088
- }
1089
- : {}),
1090
- ...(includeTerminalDetails && state.permissionStop
1091
- ? { permissionStop: state.permissionStop }
1092
- : {}),
1093
- ...state.receipt,
1094
- ...(omitDuplicateMessage ? { turnId: latestMessage.turnId } : {}),
1095
- ...(state.modelId === undefined ? {} : { modelId: state.modelId }),
1096
- ...(state.modelName === undefined ? {} : { modelName: state.modelName }),
1097
- ...(state.reasoningEffort === undefined ? {} : { reasoningEffort: state.reasoningEffort }),
1098
- ...(latestMessage === undefined || omitDuplicateMessage ? {} : { latestMessage }),
1099
- ...(!includeLatestActivity || state.latestActivity === undefined
1100
- ? {}
1101
- : { latestActivity: { ...state.latestActivity } }),
1102
- ...(summary === undefined ? {} : { summary }),
1103
- ...(!includeTerminalDetails || !terminal || state.changedFiles.length === 0
1104
- ? {}
1105
- : {
1106
- changedFiles: full
1107
- ? [...state.changedFiles]
1108
- : state.changedFiles.slice(0, SUBAGENT_LIMITS.subagentChangedFilesMaxItems)
1109
- }),
1110
- ...(!full || !includeTerminalDetails || !terminal || state.verification.length === 0
1111
- ? {}
1112
- : { verification: [...state.verification] }),
1113
- ...(!full || !includeTerminalDetails || !terminal || state.evidence.length === 0
1114
- ? {}
1115
- : { evidence: state.evidence.map((item) => ({ ...item })) }),
1116
- ...(state.errorCode === undefined ? {} : { errorCode: state.errorCode }),
1117
- ...(state.errorMessage === undefined ? {} : { errorMessage: state.errorMessage })
1118
- };
1119
- }
1120
- async #publishTerminal(state) {
1121
- if (!state.terminalPending || !this.#execution)
1122
- return;
1123
- const revision = state.terminalRevision;
1124
- try {
1125
- await this.#execution.emitTerminal(this.#snapshot(state, { full: false }));
1126
- state.terminalPublished(revision);
1127
- }
1128
- catch {
1129
- // Keep the terminal transition pending for a later active parent Execution.
1130
- }
1131
- }
1132
- }
1133
- function projectLatestMessage(message, terminal) {
1134
- const text = truncateCharacters(message.text, !terminal && message.phase === 'commentary'
1135
- ? SUBAGENT_LIMITS.subagentRunningLatestMessageMaxCharacters
1136
- : 10_000);
1137
- return {
1138
- ...message,
1139
- text,
1140
- truncated: message.truncated === true || text !== message.text
1141
- };
1142
- }
1143
- function validateTaskInput(input, native = false) {
1144
- if (!input ||
1145
- typeof input !== 'object' ||
1146
- typeof input.description !== 'string' ||
1147
- typeof input.prompt !== 'string' ||
1148
- (input.role !== 'general-purpose' && input.role !== 'explore') ||
1149
- (input.reasoningEffort !== undefined && !isReasoningEffort(input.reasoningEffort)) ||
1150
- input.parentSubagentId ||
1151
- !input.description.trim() ||
1152
- (!native && input.description.length > SUBAGENT_LIMITS.subagentDescriptionMaxCharacters) ||
1153
- !input.prompt.trim() ||
1154
- (!native && Buffer.byteLength(input.prompt) > SUBAGENT_LIMITS.subagentPromptMaxBytes))
1155
- throw new AgentError(input?.parentSubagentId
1156
- ? 'MAR_AGENT_SUBAGENT_RECURSION_DISABLED'
1157
- : 'MAR_AGENT_TOOL_INPUT_INVALID', input?.parentSubagentId ? 'Subagents cannot create subagents.' : 'Subagent task is invalid.');
1158
- }
1159
- function resolveModel(execution, input) {
1160
- const modelId = input.modelId ?? execution.modelId;
1161
- const model = execution.models.get(modelId);
1162
- if (!model || model.conversationModel === false)
1163
- throw new AgentError('MAR_AGENT_SUBAGENT_MODEL_UNAVAILABLE', 'Selected model is unavailable for subagents.');
1164
- if (input.reasoningEffort !== undefined &&
1165
- !model.reasoningEfforts.includes(input.reasoningEffort))
1166
- throw new AgentError('MAR_AGENT_SUBAGENT_REASONING_EFFORT_UNAVAILABLE', 'Selected reasoning effort is unavailable for the subagent model.', {
1167
- details: {
1168
- modelId,
1169
- reasoningEffort: input.reasoningEffort,
1170
- supportedReasoningEfforts: model.reasoningEfforts
1171
- }
1172
- });
1173
- const reasoningEffort = input.reasoningEffort ??
1174
- (model.reasoningEfforts.includes(execution.reasoningEffort)
1175
- ? execution.reasoningEffort
1176
- : model.defaultReasoningEffort);
1177
- return { ...model, reasoningEffort };
1178
- }
1179
- function resolveExistingModel(execution, state) {
1180
- const model = execution.models.get(state.modelId ?? execution.modelId);
1181
- if (!model)
1182
- throw new AgentError('MAR_AGENT_SUBAGENT_MODEL_UNAVAILABLE', 'The subagent model is no longer available.');
1183
- return {
1184
- ...model,
1185
- reasoningEffort: state.reasoningEffort && model.reasoningEfforts.includes(state.reasoningEffort)
1186
- ? state.reasoningEffort
1187
- : model.defaultReasoningEffort
1188
- };
1189
- }
1190
- function restoreAgentState(session) {
1191
- const delivered = session.records.findLast((record) => (record.type === 'turn.user' || record.type === 'extension.input') &&
1192
- isRecord(record.payload) &&
1193
- typeof record.payload.messageId === 'string' &&
1194
- typeof record.turnId === 'string');
1195
- const latestExecutionStart = session.records.findLastIndex((record) => eventType(record) === 'execution.started');
1196
- const currentRecords = latestExecutionStart < 0 ? session.records : session.records.slice(latestExecutionStart);
1197
- const header = session.records.findLast((record) => record.type === 'execution.header' && isRecord(record.payload))?.payload;
1198
- const terminal = currentRecords.findLast((record) => ['execution.completed', 'execution.failed', 'execution.cancelled'].includes(eventType(record) ?? ''));
1199
- const terminalType = eventType(terminal);
1200
- const state = {
1201
- restorable: true,
1202
- agentId: session.id,
1203
- description: session.source.description,
1204
- role: session.source.role,
1205
- ...(session.source.ownerInstanceId === undefined
1206
- ? {}
1207
- : { ownerInstanceId: session.source.ownerInstanceId }),
1208
- ...(session.source.extensionId === undefined
1209
- ? {}
1210
- : { extensionId: session.source.extensionId }),
1211
- status: terminalType === 'execution.completed'
1212
- ? 'completed'
1213
- : terminalType === 'execution.failed'
1214
- ? 'failed'
1215
- : terminalType === 'execution.cancelled'
1216
- ? 'cancelled'
1217
- : 'interrupted',
1218
- ...(delivered && isRecord(delivered.payload)
1219
- ? {
1220
- receipt: {
1221
- messageId: delivered.payload.messageId,
1222
- turnId: delivered.turnId,
1223
- deliveryStatus: 'applied'
1224
- }
1225
- }
1226
- : {}),
1227
- ...(typeof header?.modelId === 'string' ? { modelId: header.modelId } : {}),
1228
- ...(typeof header?.modelName === 'string' ? { modelName: header.modelName } : {}),
1229
- ...(isReasoningEffort(header?.reasoningEffort)
1230
- ? { reasoningEffort: header.reasoningEffort }
1231
- : {}),
1232
- messageRevision: 0,
1233
- observedMessageRevision: 0,
1234
- latestMessageActivityRevision: 0,
1235
- changedFiles: [],
1236
- verification: [],
1237
- evidence: [],
1238
- terminalPending: false,
1239
- observedTerminalRevision: -1,
1240
- resourceRevision: 0
1241
- };
1242
- if (terminalType === 'execution.completed' && terminal) {
1243
- const event = terminal.payload;
1244
- if (event.permissionStop)
1245
- state.permissionStop = event.permissionStop;
1246
- }
1247
- for (const record of currentRecords) {
1248
- if (!isRecord(record.payload) ||
1249
- !['message.completed', 'tool.started', 'tool.completed', 'tool.failed'].includes(eventType(record) ?? ''))
1250
- continue;
1251
- const event = record.payload;
1252
- if (observeAgentEvent(state, event))
1253
- state.messageRevision++;
1254
- }
1255
- state.observedMessageRevision = state.messageRevision;
1256
- if (state.status === 'completed' && state.latestMessage?.phase === 'final_answer')
1257
- state.summary = state.latestMessage.text;
1258
- if (state.status === 'failed' && terminal && isRecord(terminal.payload)) {
1259
- if (typeof terminal.payload.code === 'string')
1260
- state.errorCode = terminal.payload.code;
1261
- if (typeof terminal.payload.message === 'string')
1262
- state.errorMessage = terminal.payload.message;
1263
- }
1264
- return state;
1265
- }
1266
- function eventType(record) {
1267
- return record && isRecord(record.payload) && typeof record.payload.type === 'string'
1268
- ? record.payload.type
1269
- : undefined;
1270
- }
1271
- function isReasoningEffort(value) {
1272
- return (value === 'low' ||
1273
- value === 'medium' ||
1274
- value === 'high' ||
1275
- value === 'xhigh' ||
1276
- value === 'max' ||
1277
- value === 'ultra');
1278
- }
1279
- function isRecord(value) {
1280
- return value !== null && typeof value === 'object' && !Array.isArray(value);
1281
- }
1282
- function truncateUtf8(value, maximumBytes) {
1283
- const bytes = Buffer.from(value);
1284
- if (bytes.length <= maximumBytes)
1285
- return value;
1286
- let end = maximumBytes;
1287
- while (end > 0 && (bytes[end] & 0xc0) === 0x80)
1288
- end--;
1289
- return bytes.subarray(0, end).toString('utf8');
1290
- }
1291
- function truncateCharacters(value, maximumCharacters) {
1292
- const characters = [...value];
1293
- return characters.length <= maximumCharacters
1294
- ? value
1295
- : characters.slice(0, maximumCharacters).join('');
1296
- }
1297
- function validateOutputOptions(options, native = false) {
1298
- if ((options.untilSettled !== undefined && options.untilSettled !== true) ||
1299
- (options.untilSettled === true && options.waitFor === 'message') ||
1300
- (options.waitFor !== undefined &&
1301
- options.waitFor !== 'completion' &&
1302
- options.waitFor !== 'message') ||
1303
- (options.waitMs !== undefined &&
1304
- (!Number.isSafeInteger(options.waitMs) ||
1305
- options.waitMs < SUBAGENT_LIMITS.agentWaitMinWaitMs ||
1306
- (!native && options.waitMs > SUBAGENT_LIMITS.agentWaitMaxWaitMs))))
1307
- throw new AgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Task wait is invalid.');
1308
- }
1309
- function isTerminalState(state) {
1310
- return (state.status === 'completed' ||
1311
- state.status === 'failed' ||
1312
- state.status === 'cancelled' ||
1313
- state.status === 'interrupted');
1314
- }
1315
- function waitForPromise(promise, signal) {
1316
- signal?.throwIfAborted();
1317
- if (signal === undefined)
1318
- return promise;
1319
- return new Promise((resolve, reject) => {
1320
- let settled = false;
1321
- const finish = (result) => {
1322
- if (settled)
1323
- return;
1324
- settled = true;
1325
- signal.removeEventListener('abort', abort);
1326
- if ('error' in result)
1327
- reject(result.error);
1328
- else
1329
- resolve(result.value);
1330
- };
1331
- const abort = () => finish({
1332
- error: signal.reason ?? new AgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.')
1333
- });
1334
- signal.addEventListener('abort', abort, { once: true });
1335
- promise.then((value) => finish({ value }), (error) => finish({ error }));
1336
- if (signal.aborted)
1337
- abort();
1338
- });
1339
- }
1340
- /** Retain the waited Turn's evidence when the live Session moves on to another Turn. */
1341
- function copyWaitState(state) {
1342
- return state.copyWaitState();
1343
- }