@myagentroam/agent 0.11.38 → 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.
- package/dist/cli/catalog-file.d.ts +2 -0
- package/dist/cli/main.js +11 -4
- package/dist/cli/subagent/control.d.ts +56 -0
- package/dist/cli/subagent/control.js +219 -0
- package/dist/cli/subagent/history.d.ts +17 -0
- package/dist/cli/subagent/history.js +85 -0
- package/dist/cli/subagent/index.d.ts +10 -0
- package/dist/cli/subagent/index.js +94 -0
- package/dist/cli/subagent/native.d.ts +91 -0
- package/dist/cli/subagent/native.js +359 -0
- package/dist/cli/subagent/observations.d.ts +57 -0
- package/dist/cli/subagent/observations.js +351 -0
- package/dist/cli/subagent/query.d.ts +4 -0
- package/dist/cli/subagent/query.js +114 -0
- package/dist/cli/subagent/resources.d.ts +3 -0
- package/dist/cli/subagent/resources.js +112 -0
- package/dist/cli/subagent/snapshot.d.ts +77 -0
- package/dist/cli/subagent/snapshot.js +127 -0
- package/dist/cli/subagent/start.d.ts +24 -0
- package/dist/cli/subagent/start.js +201 -0
- package/dist/cli/subagent/wait-output.d.ts +19 -0
- package/dist/cli/subagent/wait-output.js +56 -0
- package/dist/cli/subagent/wait.d.ts +175 -0
- package/dist/cli/subagent/wait.js +453 -0
- package/dist/extensions/contracts.d.ts +2 -5
- package/dist/extensions/manager.d.ts +0 -2
- package/dist/extensions/manager.js +1 -2
- package/dist/extensions/models.d.ts +3 -0
- package/dist/extensions/operation.d.ts +8 -0
- package/dist/extensions/operation.js +3 -0
- package/dist/extensions/services.d.ts +9 -1
- package/dist/extensions/services.js +26 -3
- package/dist/extensions/session-context.d.ts +18 -0
- package/dist/extensions/session-context.js +4 -0
- package/dist/extensions/session-input.d.ts +2 -2
- package/dist/extensions/session-input.js +3 -4
- package/dist/extensions/session-messages.d.ts +1 -1
- package/dist/extensions/session-tools.js +20 -3
- package/dist/extensions/session.d.ts +1 -4
- package/dist/extensions/session.js +2 -16
- package/dist/extensions/tools.js +21 -3
- package/dist/extensions.d.ts +5 -2
- package/dist/extensions.js +2 -1
- package/dist/host/local-agent-host.d.ts +2 -2
- package/dist/host/local-exec-tool.d.ts +1 -1
- package/dist/host/local-exec-tool.js +28 -3
- package/dist/host/local-tool-registry.d.ts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/model/configuration.d.ts +3 -1
- package/dist/model/configuration.js +1 -0
- package/dist/model/openai-responses.js +5 -0
- package/dist/model/responses-request.js +9 -0
- package/dist/prompts/index.js +1 -3
- package/dist/prompts/resources.d.ts +1 -5
- package/dist/prompts/resources.js +3 -12
- package/dist/prompts/subagent.d.ts +1 -1
- package/dist/prompts/subagent.js +1 -1
- package/dist/runtime/context/gc-candidates-web.d.ts +6 -0
- package/dist/runtime/context/gc-candidates-web.js +53 -1
- package/dist/runtime/context/gc-candidates.js +1 -9
- package/dist/runtime/context/gc.js +19 -11
- package/dist/runtime/context/projection.js +1 -0
- package/dist/runtime/events.d.ts +4 -3
- package/dist/runtime/execution-handle.d.ts +2 -0
- package/dist/runtime/wait.js +12 -2
- package/dist/sdk/agent.js +94 -5
- package/dist/sdk/execution-context-coordinator.d.ts +2 -3
- package/dist/sdk/execution-context-coordinator.js +3 -3
- package/dist/sdk/execution-input.d.ts +3 -4
- package/dist/sdk/execution-input.js +31 -25
- package/dist/sdk/execution-round-preparation.d.ts +1 -1
- package/dist/sdk/execution-round-preparation.js +4 -4
- package/dist/sdk/execution-tool-catalog.js +2 -10
- package/dist/sdk/execution-tool-scope.d.ts +1 -3
- package/dist/sdk/execution-tool-scope.js +10 -12
- package/dist/sdk/extension-models.js +3 -0
- package/dist/sdk/extension-session-launch.d.ts +3 -2
- package/dist/sdk/extension-session-launch.js +2 -3
- package/dist/sdk/extension-tools.js +0 -2
- package/dist/sdk/native-steer.d.ts +2 -2
- package/dist/sdk/native-steer.js +2 -2
- package/dist/sdk/session-composition.d.ts +2 -17
- package/dist/sdk/session-execution-factory.d.ts +2 -3
- package/dist/sdk/session-execution-factory.js +20 -114
- package/dist/sdk/session-execution.d.ts +13 -7
- package/dist/sdk/session-execution.js +11 -3
- package/dist/sdk/session-extension-messages.d.ts +1 -3
- package/dist/sdk/session-extension-messages.js +14 -9
- package/dist/sdk/session-extension-prompt.js +1 -3
- package/dist/sdk/session-maintenance.d.ts +1 -0
- package/dist/sdk/session-maintenance.js +16 -7
- package/dist/sdk/session-open.d.ts +2 -3
- package/dist/sdk/session-open.js +5 -24
- package/dist/sdk/session-runtime.d.ts +19 -8
- package/dist/sdk/session-runtime.js +36 -30
- package/dist/sdk/session.d.ts +3 -2
- package/dist/sdk/session.js +22 -89
- package/dist/sdk/types.d.ts +36 -0
- package/dist/session/catalog.d.ts +22 -1
- package/dist/session/catalog.js +102 -0
- package/dist/session/context-gc-checkpoint.js +30 -1
- package/dist/session/context-gc-contract.d.ts +7 -0
- package/dist/session/context-gc-replacements.d.ts +3 -0
- package/dist/session/context-gc-replacements.js +26 -0
- package/dist/session/store/contracts.d.ts +2 -2
- package/dist/session/store/jsonl/directory-index.d.ts +1 -1
- package/dist/session/store/jsonl/directory-index.js +18 -17
- package/dist/session/store/jsonl/history.js +1 -1
- package/dist/session/store/jsonl/index-transaction.js +2 -8
- package/dist/session/store/jsonl/jsonl-store.d.ts +1 -1
- package/dist/session/store/jsonl/jsonl-store.js +24 -9
- package/dist/session/types.d.ts +1 -0
- package/dist/tools/agent/sleep.js +1 -1
- package/dist/tools/callers.js +1 -12
- package/dist/tools/code-mode/dispatch.js +5 -2
- package/dist/tools/code-mode/index.d.ts +1 -1
- package/dist/tools/code-mode/index.js +2 -0
- package/dist/tools/contracts.d.ts +20 -0
- package/dist/tools/execution-limits.d.ts +5 -20
- package/dist/tools/execution-limits.js +0 -2
- package/dist/tools/execution.js +16 -4
- package/dist/tools/local/exec.js +8 -2
- package/dist/tools/output-projection.d.ts +5 -2
- package/dist/tools/output-projection.js +133 -92
- package/dist/tools/registry.js +1 -13
- package/dist/tools/runtime-context.d.ts +1 -21
- package/package.json +2 -2
- package/dist/extensions/agents.d.ts +0 -100
- package/dist/extensions/agents.js +0 -121
- package/dist/runtime/context/gc-candidates-subagent.d.ts +0 -6
- package/dist/runtime/context/gc-candidates-subagent.js +0 -100
- package/dist/sdk/extension-agent-controller.d.ts +0 -3
- package/dist/sdk/extension-agent-controller.js +0 -69
- package/dist/sdk/extension-agents.d.ts +0 -9
- package/dist/sdk/extension-agents.js +0 -58
- package/dist/sdk/subagent-backend.d.ts +0 -3
- package/dist/sdk/subagent-backend.js +0 -49
- package/dist/subagent/contracts.d.ts +0 -107
- package/dist/subagent/contracts.js +0 -1
- package/dist/subagent/limits.d.ts +0 -17
- package/dist/subagent/limits.js +0 -17
- package/dist/subagent/model-update.d.ts +0 -2
- package/dist/subagent/model-update.js +0 -81
- package/dist/subagent/session-controller.d.ts +0 -87
- package/dist/subagent/session-controller.js +0 -1343
- package/dist/subagent/task.d.ts +0 -134
- package/dist/subagent/task.js +0 -436
- package/dist/subagent/turn-contracts.d.ts +0 -64
- package/dist/subagent/turn-contracts.js +0 -1
- package/dist/subagent/wait-targets.d.ts +0 -10
- package/dist/subagent/wait-targets.js +0 -26
- package/dist/subagent/wait.d.ts +0 -26
- package/dist/subagent/wait.js +0 -73
- package/dist/tools/agent/agent-cancel.d.ts +0 -7
- package/dist/tools/agent/agent-cancel.js +0 -29
- package/dist/tools/agent/agent-message.d.ts +0 -7
- package/dist/tools/agent/agent-message.js +0 -57
- package/dist/tools/agent/agent-start.d.ts +0 -12
- package/dist/tools/agent/agent-start.js +0 -106
- package/dist/tools/agent/agent-wait.d.ts +0 -7
- 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
|
-
}
|