orchajs 0.5.0 → 0.6.0
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/README.md +106 -1
- package/dist/cli-agents-template.d.ts +1 -1
- package/dist/cli-agents-template.d.ts.map +1 -1
- package/dist/cli-agents-template.js +129 -12
- package/dist/cli-agents-template.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/compiler/build-project.d.ts.map +1 -1
- package/dist/compiler/build-project.js +7 -1
- package/dist/compiler/build-project.js.map +1 -1
- package/dist/compiler/compile.d.ts +2 -2
- package/dist/compiler/compile.d.ts.map +1 -1
- package/dist/compiler/compile.js +141 -67
- package/dist/compiler/compile.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/bedrock.d.ts +3 -1
- package/dist/providers/bedrock.d.ts.map +1 -1
- package/dist/providers/bedrock.js +1 -1
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/deepseek.js +1 -0
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/google-gemini.d.ts.map +1 -1
- package/dist/providers/google-gemini.js +15 -3
- package/dist/providers/google-gemini.js.map +1 -1
- package/dist/providers/openai.js +1 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +1 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/runtime/agent.d.ts +14 -2
- package/dist/runtime/agent.d.ts.map +1 -1
- package/dist/runtime/agent.js +792 -37
- package/dist/runtime/agent.js.map +1 -1
- package/dist/runtime/context.d.ts +2 -2
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/create-orcha.d.ts.map +1 -1
- package/dist/runtime/create-orcha.js +22 -1
- package/dist/runtime/create-orcha.js.map +1 -1
- package/dist/runtime/execution.d.ts +3 -2
- package/dist/runtime/execution.d.ts.map +1 -1
- package/dist/runtime/execution.js +6 -1
- package/dist/runtime/execution.js.map +1 -1
- package/dist/runtime/session-events.d.ts.map +1 -1
- package/dist/runtime/session-events.js +6 -3
- package/dist/runtime/session-events.js.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +3 -1
- package/dist/testing.js.map +1 -1
- package/dist/types.d.ts +46 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
package/dist/runtime/agent.js
CHANGED
|
@@ -5,13 +5,25 @@ import { validateStructuredValue } from "./structured-output.js";
|
|
|
5
5
|
import { executeLocalAction } from "./actions/execute.js";
|
|
6
6
|
import { createExecution, } from "./execution.js";
|
|
7
7
|
const LOAD_SKILL_TOOL_NAME = "load_skill";
|
|
8
|
+
const RUN_AGENT_TOOL_NAME = "run_agent";
|
|
9
|
+
const RESUME_AGENT_TOOL_NAME = "resume_agent";
|
|
10
|
+
const INSPECT_AGENT_TOOL_NAME = "inspect_agent";
|
|
11
|
+
const SUBAGENT_TOOL_NAMES = new Set([
|
|
12
|
+
RUN_AGENT_TOOL_NAME,
|
|
13
|
+
RESUME_AGENT_TOOL_NAME,
|
|
14
|
+
INSPECT_AGENT_TOOL_NAME,
|
|
15
|
+
]);
|
|
8
16
|
export class RuntimeAgent {
|
|
9
17
|
#manifest;
|
|
10
18
|
#configuration;
|
|
11
19
|
#store;
|
|
12
20
|
#activeSessions;
|
|
21
|
+
#sessionControls;
|
|
13
22
|
#backgroundSessionMutations = new Set();
|
|
14
23
|
#generateProviderResponse;
|
|
24
|
+
#subagents;
|
|
25
|
+
#delegatedOnly;
|
|
26
|
+
#agentKey;
|
|
15
27
|
#sessionIdPrefix;
|
|
16
28
|
#evaluationTasks = new Map();
|
|
17
29
|
#sessionIdleWaiters = new Map();
|
|
@@ -20,25 +32,63 @@ export class RuntimeAgent {
|
|
|
20
32
|
this.#configuration = options.configuration;
|
|
21
33
|
this.#store = options.store;
|
|
22
34
|
this.#activeSessions = options.activeSessions;
|
|
35
|
+
this.#sessionControls = options.sessionControls ?? new Map();
|
|
23
36
|
this.#generateProviderResponse = options.generateProviderResponse;
|
|
37
|
+
this.#subagents = options.subagents ?? {};
|
|
38
|
+
this.#delegatedOnly = options.delegatedOnly ?? false;
|
|
39
|
+
this.#agentKey = options.manifest.key ?? options.manifest.name;
|
|
24
40
|
this.#sessionIdPrefix = options.sessionIdPrefix ?? "ses_";
|
|
25
41
|
}
|
|
42
|
+
get agent() {
|
|
43
|
+
return this.#agentKey;
|
|
44
|
+
}
|
|
26
45
|
get clientTools() {
|
|
27
46
|
return Object.values(this.#manifest.actions ?? {}).filter((action) => action.execution === "client");
|
|
28
47
|
}
|
|
29
48
|
run(input) {
|
|
49
|
+
return this.#run(input);
|
|
50
|
+
}
|
|
51
|
+
createDelegatedSessionId() {
|
|
52
|
+
return `${this.#sessionIdPrefix}${randomUUID()}`;
|
|
53
|
+
}
|
|
54
|
+
runAsChild(sessionId, input, lineage) {
|
|
30
55
|
const normalized = typeof input === "string" ? { content: input } : input;
|
|
31
|
-
|
|
56
|
+
return this.#run({
|
|
57
|
+
...normalized,
|
|
58
|
+
clientCapabilities: this.clientTools.map((tool) => tool.name),
|
|
59
|
+
}, lineage, sessionId);
|
|
60
|
+
}
|
|
61
|
+
#run(input, lineage, requestedSessionId) {
|
|
62
|
+
const normalized = typeof input === "string" ? { content: input } : input;
|
|
63
|
+
const sessionId = requestedSessionId ?? `${this.#sessionIdPrefix}${randomUUID()}`;
|
|
32
64
|
if (typeof normalized === "object" &&
|
|
33
65
|
normalized !== null &&
|
|
34
66
|
"sessionId" in normalized) {
|
|
35
67
|
return this.#createExecution(sessionId, async () => failure(sessionId, "invalid_input", "run() always creates a new session and does not accept sessionId."));
|
|
36
68
|
}
|
|
37
|
-
return this.#createExecution(sessionId, (publishOutput) => this.#start(sessionId, normalized, true, publishOutput));
|
|
69
|
+
return this.#createExecution(sessionId, (publishOutput, publishSnapshot) => this.#start(sessionId, normalized, true, publishOutput, publishSnapshot, lineage));
|
|
38
70
|
}
|
|
39
71
|
resume(sessionId, input) {
|
|
72
|
+
if (input === undefined) {
|
|
73
|
+
return this.#createExecution(sessionId, (publishOutput, publishSnapshot) => this.#start(sessionId, {
|
|
74
|
+
content: "Continue.",
|
|
75
|
+
...(this.#delegatedOnly
|
|
76
|
+
? {
|
|
77
|
+
clientCapabilities: this.clientTools.map((tool) => tool.name),
|
|
78
|
+
}
|
|
79
|
+
: {}),
|
|
80
|
+
}, false, publishOutput, publishSnapshot));
|
|
81
|
+
}
|
|
40
82
|
if (typeof input === "string") {
|
|
41
|
-
|
|
83
|
+
const continuedInput = {
|
|
84
|
+
content: input,
|
|
85
|
+
...(this.#delegatedOnly
|
|
86
|
+
? {
|
|
87
|
+
clientCapabilities: this.clientTools.map((tool) => tool.name),
|
|
88
|
+
}
|
|
89
|
+
: {}),
|
|
90
|
+
};
|
|
91
|
+
return this.#createExecution(sessionId, (publishOutput, publishSnapshot) => this.#start(sessionId, continuedInput, false, publishOutput, publishSnapshot));
|
|
42
92
|
}
|
|
43
93
|
if (!input || typeof input !== "object") {
|
|
44
94
|
return this.#createExecution(sessionId, async () => failure(sessionId, "invalid_input", "resume() requires content or toolResults."));
|
|
@@ -48,9 +98,15 @@ export class RuntimeAgent {
|
|
|
48
98
|
if (hasMessage === hasToolResults) {
|
|
49
99
|
return this.#createExecution(sessionId, async () => failure(sessionId, "invalid_input", "resume() requires either content or toolResults, but not both."));
|
|
50
100
|
}
|
|
51
|
-
|
|
52
|
-
?
|
|
53
|
-
|
|
101
|
+
const normalizedInput = hasMessage && this.#delegatedOnly
|
|
102
|
+
? {
|
|
103
|
+
...input,
|
|
104
|
+
clientCapabilities: this.clientTools.map((tool) => tool.name),
|
|
105
|
+
}
|
|
106
|
+
: input;
|
|
107
|
+
return this.#createExecution(sessionId, (publishOutput, publishSnapshot) => hasMessage
|
|
108
|
+
? this.#start(sessionId, normalizedInput, false, publishOutput, publishSnapshot)
|
|
109
|
+
: this.#resumeToolResults(sessionId, normalizedInput, publishOutput, publishSnapshot));
|
|
54
110
|
}
|
|
55
111
|
#createExecution(sessionId, execute) {
|
|
56
112
|
return createExecution(sessionId, execute, async () => {
|
|
@@ -116,7 +172,7 @@ export class RuntimeAgent {
|
|
|
116
172
|
const snapshots = (await Promise.all(sessionIds.map(async (sessionId) => {
|
|
117
173
|
const events = await this.#readSession(sessionId);
|
|
118
174
|
const agent = events.find((event) => event.type === "session.created")?.data.agent;
|
|
119
|
-
return agent === this.#
|
|
175
|
+
return agent === this.#agentKey && this.#sessionVisible(events)
|
|
120
176
|
? projectSessionSnapshot(events)
|
|
121
177
|
: undefined;
|
|
122
178
|
})))
|
|
@@ -133,6 +189,79 @@ export class RuntimeAgent {
|
|
|
133
189
|
hasMore: start + pageSize < snapshots.length,
|
|
134
190
|
};
|
|
135
191
|
}
|
|
192
|
+
async subagentHistory(childSessionId, options = {}) {
|
|
193
|
+
const child = await this.#ownedChild(undefined, childSessionId);
|
|
194
|
+
return child.history(childSessionId, options);
|
|
195
|
+
}
|
|
196
|
+
async pause(sessionId) {
|
|
197
|
+
let events = await this.#readSession(sessionId);
|
|
198
|
+
this.#assertOwnedSession(sessionId, events);
|
|
199
|
+
const current = projectSessionSnapshot(events);
|
|
200
|
+
if (current.status === "paused") {
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
203
|
+
const control = this.#sessionControls.get(sessionId);
|
|
204
|
+
if (control) {
|
|
205
|
+
control.pauseRequested = true;
|
|
206
|
+
control.controller.abort();
|
|
207
|
+
}
|
|
208
|
+
await Promise.all(childSessionIds(events).map(async (childSessionId) => {
|
|
209
|
+
const child = await this.#ownedChild(sessionId, childSessionId);
|
|
210
|
+
const snapshot = await child.get(childSessionId);
|
|
211
|
+
if (snapshot.status === "active" &&
|
|
212
|
+
(snapshot.runStatus === "running" ||
|
|
213
|
+
snapshot.runStatus === "waiting_for_subagent")) {
|
|
214
|
+
await child.pause(childSessionId);
|
|
215
|
+
}
|
|
216
|
+
}));
|
|
217
|
+
while (this.#activeSessions.has(sessionId)) {
|
|
218
|
+
await this.#waitForSessionIdle(sessionId);
|
|
219
|
+
}
|
|
220
|
+
events = await this.#readSession(sessionId);
|
|
221
|
+
const snapshot = projectSessionSnapshot(events);
|
|
222
|
+
if (snapshot.status === "paused") {
|
|
223
|
+
return snapshot;
|
|
224
|
+
}
|
|
225
|
+
const activeRun = snapshot.runStatus === "running" ||
|
|
226
|
+
snapshot.runStatus === "waiting_for_subagent"
|
|
227
|
+
? latestRunNumber(events)
|
|
228
|
+
: undefined;
|
|
229
|
+
const writer = new SessionEventWriter(sessionId, events, activeRun);
|
|
230
|
+
const activeRunOutput = activeRun
|
|
231
|
+
? outputForRun(events, activeRun)
|
|
232
|
+
: snapshot.lastOutput;
|
|
233
|
+
const activeRunUsage = activeRun
|
|
234
|
+
? usageForRun(events, activeRun)
|
|
235
|
+
: snapshot.usage;
|
|
236
|
+
const pauseEvents = [
|
|
237
|
+
...(activeRun
|
|
238
|
+
? [
|
|
239
|
+
writer.create("run.paused", {
|
|
240
|
+
status: "paused",
|
|
241
|
+
output: activeRunOutput,
|
|
242
|
+
usage: activeRunUsage,
|
|
243
|
+
}),
|
|
244
|
+
]
|
|
245
|
+
: []),
|
|
246
|
+
writer.create("session.paused", { status: "paused" }, "session"),
|
|
247
|
+
];
|
|
248
|
+
await this.#store.append(sessionId, pauseEvents);
|
|
249
|
+
return projectSessionSnapshot([...events, ...pauseEvents]);
|
|
250
|
+
}
|
|
251
|
+
async #ownedChild(parentSessionId, childSessionId) {
|
|
252
|
+
const events = await this.#readSession(childSessionId);
|
|
253
|
+
const lineage = sessionLineage(events);
|
|
254
|
+
const childKey = sessionAgent(events);
|
|
255
|
+
const child = childKey ? this.#subagents[childKey] : undefined;
|
|
256
|
+
if (!lineage ||
|
|
257
|
+
lineage.parentAgent !== this.#agentKey ||
|
|
258
|
+
(parentSessionId !== undefined &&
|
|
259
|
+
lineage.parentSessionId !== parentSessionId) ||
|
|
260
|
+
!child) {
|
|
261
|
+
throw new OrchaError("session_not_found", `Child session "${childSessionId}" was not found for agent "${this.#agentKey}".`);
|
|
262
|
+
}
|
|
263
|
+
return child;
|
|
264
|
+
}
|
|
136
265
|
async update(sessionId, update) {
|
|
137
266
|
return this.#withSessionMutationLock(sessionId, async () => {
|
|
138
267
|
const events = await this.#readSession(sessionId);
|
|
@@ -155,11 +284,14 @@ export class RuntimeAgent {
|
|
|
155
284
|
});
|
|
156
285
|
}
|
|
157
286
|
#assertOwnedSession(sessionId, events) {
|
|
158
|
-
const agent = events
|
|
159
|
-
if (agent !== this.#
|
|
160
|
-
throw new OrchaError("session_not_found", `Session "${sessionId}" was not found for agent "${this.#
|
|
287
|
+
const agent = sessionAgent(events);
|
|
288
|
+
if (agent !== this.#agentKey || !this.#sessionVisible(events)) {
|
|
289
|
+
throw new OrchaError("session_not_found", `Session "${sessionId}" was not found for agent "${this.#agentKey}".`);
|
|
161
290
|
}
|
|
162
291
|
}
|
|
292
|
+
#sessionVisible(events) {
|
|
293
|
+
return this.#delegatedOnly === Boolean(sessionLineage(events));
|
|
294
|
+
}
|
|
163
295
|
async #readSession(sessionId) {
|
|
164
296
|
try {
|
|
165
297
|
return await this.#store.read(sessionId);
|
|
@@ -168,7 +300,85 @@ export class RuntimeAgent {
|
|
|
168
300
|
throw new OrchaError("storage_error", error instanceof Error ? error.message : String(error), { retryable: true, cause: error });
|
|
169
301
|
}
|
|
170
302
|
}
|
|
171
|
-
async #
|
|
303
|
+
async #prepareInterruptedRunForResume(sessionId, events) {
|
|
304
|
+
const run = latestRunNumber(events);
|
|
305
|
+
if (!run ||
|
|
306
|
+
events.some((event) => event.run === run &&
|
|
307
|
+
(event.type === "run.completed" ||
|
|
308
|
+
event.type === "run.failed" ||
|
|
309
|
+
event.type === "run.paused"))) {
|
|
310
|
+
return events;
|
|
311
|
+
}
|
|
312
|
+
const operation = findLast(events, (event) => event.run === run &&
|
|
313
|
+
(event.type === "subagent.initiated" ||
|
|
314
|
+
event.type === "subagent.resumed") &&
|
|
315
|
+
typeof event.data.callId === "string" &&
|
|
316
|
+
typeof event.data.sessionId === "string");
|
|
317
|
+
if (!operation) {
|
|
318
|
+
return events;
|
|
319
|
+
}
|
|
320
|
+
const writer = new SessionEventWriter(sessionId, events, run);
|
|
321
|
+
const recoveryEvents = [];
|
|
322
|
+
const callId = operation.data.callId;
|
|
323
|
+
const childSessionId = operation.data.sessionId;
|
|
324
|
+
let terminal = previousSubagentCall(events, callId);
|
|
325
|
+
if (!terminal) {
|
|
326
|
+
const child = typeof operation.data.agent === "string"
|
|
327
|
+
? this.#subagents[operation.data.agent]
|
|
328
|
+
: undefined;
|
|
329
|
+
let result;
|
|
330
|
+
try {
|
|
331
|
+
if (!child) {
|
|
332
|
+
throw new Error("The delegated agent is no longer registered.");
|
|
333
|
+
}
|
|
334
|
+
let childSnapshot = await child.get(childSessionId);
|
|
335
|
+
if (childSnapshot.status === "active" &&
|
|
336
|
+
(childSnapshot.runStatus === "running" ||
|
|
337
|
+
childSnapshot.runStatus === "waiting_for_subagent")) {
|
|
338
|
+
childSnapshot = await child.pause(childSessionId);
|
|
339
|
+
}
|
|
340
|
+
result = snapshotRunResult(childSnapshot);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
result = failure(childSessionId, "execution_failed", error instanceof Error
|
|
344
|
+
? error.message
|
|
345
|
+
: "The interrupted child session could not be recovered.");
|
|
346
|
+
}
|
|
347
|
+
terminal = writer.create(subagentEventType(result), {
|
|
348
|
+
callId,
|
|
349
|
+
agent: operation.data.agent,
|
|
350
|
+
...childRunResultData(result, childSessionId),
|
|
351
|
+
recovered: true,
|
|
352
|
+
});
|
|
353
|
+
recoveryEvents.push(terminal);
|
|
354
|
+
}
|
|
355
|
+
const hasToolResult = events.some((event) => event.run === run &&
|
|
356
|
+
event.type === "message.created" &&
|
|
357
|
+
event.data.role === "tool" &&
|
|
358
|
+
Array.isArray(event.data.content) &&
|
|
359
|
+
event.data.content.some((result) => isRecord(result) && result.callId === callId));
|
|
360
|
+
if (!hasToolResult) {
|
|
361
|
+
recoveryEvents.push(writer.create("message.created", {
|
|
362
|
+
role: "tool",
|
|
363
|
+
content: [
|
|
364
|
+
{
|
|
365
|
+
callId,
|
|
366
|
+
output: subagentToolOutput(terminal),
|
|
367
|
+
...(terminal.type === "subagent.failed"
|
|
368
|
+
? { isError: true }
|
|
369
|
+
: {}),
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
recoveryEvents.push(writer.create("run.paused", {
|
|
375
|
+
status: "paused",
|
|
376
|
+
recovered: true,
|
|
377
|
+
}), writer.create("session.paused", { status: "paused", recovered: true }, "session"));
|
|
378
|
+
await this.#store.append(sessionId, recoveryEvents);
|
|
379
|
+
return [...events, ...recoveryEvents];
|
|
380
|
+
}
|
|
381
|
+
async #start(sessionId, input, createSession = true, publishOutput, publishSnapshot, lineage) {
|
|
172
382
|
const content = normalizeContent(input.content);
|
|
173
383
|
if (!content) {
|
|
174
384
|
return failure(sessionId, "invalid_input", "Agent content must contain at least one valid item.");
|
|
@@ -196,13 +406,24 @@ export class RuntimeAgent {
|
|
|
196
406
|
return failure(sessionId, "invalid_input", `Unknown client capabilities: ${unknownCapabilities.join(", ")}.`);
|
|
197
407
|
}
|
|
198
408
|
return this.#withSessionExecutionLock(sessionId, async () => {
|
|
199
|
-
|
|
409
|
+
let previousEvents = await this.#readSession(sessionId);
|
|
200
410
|
if (!createSession && previousEvents.length === 0) {
|
|
201
411
|
return failure(sessionId, "session_not_found", `Session "${sessionId}" does not exist.`);
|
|
202
412
|
}
|
|
413
|
+
if (previousEvents.length > 0) {
|
|
414
|
+
try {
|
|
415
|
+
this.#assertOwnedSession(sessionId, previousEvents);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return failure(sessionId, "session_not_found", `Session "${sessionId}" was not created by this agent.`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
203
421
|
if (getPendingClientActions(previousEvents)) {
|
|
204
422
|
return failure(sessionId, "client_action_required", "This session is waiting for client tool results. Resume with toolResults.");
|
|
205
423
|
}
|
|
424
|
+
if (!createSession) {
|
|
425
|
+
previousEvents = await this.#prepareInterruptedRunForResume(sessionId, previousEvents);
|
|
426
|
+
}
|
|
206
427
|
const writer = new SessionEventWriter(sessionId, previousEvents);
|
|
207
428
|
const variables = createSession
|
|
208
429
|
? initialVariables
|
|
@@ -212,25 +433,28 @@ export class RuntimeAgent {
|
|
|
212
433
|
const created = writer.create("session.created", {
|
|
213
434
|
schemaVersion: 1,
|
|
214
435
|
sessionId,
|
|
215
|
-
agent: this.#
|
|
436
|
+
agent: this.#agentKey,
|
|
216
437
|
status: "active",
|
|
217
438
|
name: initialName,
|
|
218
439
|
metadata: initialMetadata,
|
|
219
440
|
variables,
|
|
441
|
+
...(lineage ? { lineage } : {}),
|
|
220
442
|
}, "session");
|
|
221
443
|
await this.#store.append(sessionId, [created]);
|
|
222
444
|
sessionEvents = [created];
|
|
223
445
|
}
|
|
224
446
|
else {
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
|
|
447
|
+
const snapshot = projectSessionSnapshot(previousEvents);
|
|
448
|
+
if (snapshot.status === "paused") {
|
|
449
|
+
const resumed = writer.create("session.resumed", { status: "active" }, "session");
|
|
450
|
+
await this.#store.append(sessionId, [resumed]);
|
|
451
|
+
sessionEvents = [...previousEvents, resumed];
|
|
228
452
|
}
|
|
229
453
|
}
|
|
230
454
|
const runEvents = [
|
|
231
455
|
writer.create("run.started", {
|
|
232
456
|
status: "running",
|
|
233
|
-
agent: this.#
|
|
457
|
+
agent: this.#agentKey,
|
|
234
458
|
provider: this.#manifest.provider,
|
|
235
459
|
model: this.#manifest.model,
|
|
236
460
|
region: this.#manifest.region ?? "provider_managed",
|
|
@@ -245,13 +469,13 @@ export class RuntimeAgent {
|
|
|
245
469
|
];
|
|
246
470
|
await this.#store.append(sessionId, runEvents);
|
|
247
471
|
const messages = [
|
|
248
|
-
...messagesFromEvents(
|
|
472
|
+
...messagesFromEvents(sessionEvents),
|
|
249
473
|
{ role: "user", content },
|
|
250
474
|
];
|
|
251
|
-
return this.#continue(sessionId, writer, messages, capabilities, [...sessionEvents, ...runEvents], variables, 0, publishOutput);
|
|
475
|
+
return this.#continue(sessionId, writer, messages, capabilities, [...sessionEvents, ...runEvents], variables, 0, publishOutput, publishSnapshot);
|
|
252
476
|
});
|
|
253
477
|
}
|
|
254
|
-
async #resumeToolResults(sessionId, input, publishOutput) {
|
|
478
|
+
async #resumeToolResults(sessionId, input, publishOutput, publishSnapshot) {
|
|
255
479
|
if (!Array.isArray(input?.toolResults) || input.toolResults.length === 0) {
|
|
256
480
|
return failure(sessionId, "missing_tool_results", "resume() requires at least one tool result.");
|
|
257
481
|
}
|
|
@@ -260,6 +484,7 @@ export class RuntimeAgent {
|
|
|
260
484
|
if (events.length === 0) {
|
|
261
485
|
return failure(sessionId, "session_not_found", `Session "${sessionId}" does not exist.`);
|
|
262
486
|
}
|
|
487
|
+
this.#assertOwnedSession(sessionId, events);
|
|
263
488
|
const pending = getPendingClientActions(events);
|
|
264
489
|
if (!pending) {
|
|
265
490
|
const duplicate = getCompletedDuplicateResult(sessionId, events, input.toolResults);
|
|
@@ -284,6 +509,9 @@ export class RuntimeAgent {
|
|
|
284
509
|
const writer = new SessionEventWriter(sessionId, events, pending.run);
|
|
285
510
|
let updatedEvents = events;
|
|
286
511
|
const continuationEvents = [];
|
|
512
|
+
if (projectSessionSnapshot(events).status === "paused") {
|
|
513
|
+
continuationEvents.push(writer.create("session.resumed", { status: "active" }, "session"));
|
|
514
|
+
}
|
|
287
515
|
if (!pending.resolvedResults) {
|
|
288
516
|
const resolved = writer.create("client_action.resolved", {
|
|
289
517
|
status: "completed",
|
|
@@ -310,11 +538,15 @@ export class RuntimeAgent {
|
|
|
310
538
|
await this.#store.append(sessionId, continuationEvents);
|
|
311
539
|
updatedEvents = [...events, ...continuationEvents];
|
|
312
540
|
}
|
|
313
|
-
return this.#continue(sessionId, writer, messagesFromEvents(updatedEvents, pending.run), pending.capabilities, updatedEvents, sessionVariables(events), 0, publishOutput);
|
|
541
|
+
return this.#continue(sessionId, writer, messagesFromEvents(updatedEvents, pending.run), pending.capabilities, updatedEvents, sessionVariables(events), 0, publishOutput, publishSnapshot);
|
|
314
542
|
});
|
|
315
543
|
}
|
|
316
|
-
async #continue(sessionId, writer, messages, capabilities, previousEvents, variables, toolRound = 0, publishOutput) {
|
|
544
|
+
async #continue(sessionId, writer, messages, capabilities, previousEvents, variables, toolRound = 0, publishOutput, publishSnapshot) {
|
|
317
545
|
try {
|
|
546
|
+
const controlResult = await this.#applyRequestedControl(sessionId, writer, previousEvents);
|
|
547
|
+
if (controlResult) {
|
|
548
|
+
return controlResult;
|
|
549
|
+
}
|
|
318
550
|
if (toolRound > 10) {
|
|
319
551
|
throw new Error("Agent exceeded the maximum of 10 action rounds.");
|
|
320
552
|
}
|
|
@@ -324,6 +556,7 @@ export class RuntimeAgent {
|
|
|
324
556
|
}
|
|
325
557
|
const modelStartedAt = performance.now();
|
|
326
558
|
let response;
|
|
559
|
+
let streamedOutput;
|
|
327
560
|
const localActionNames = Object.values(this.#manifest.actions ?? {})
|
|
328
561
|
.filter((action) => action.execution === "local")
|
|
329
562
|
.map((action) => action.name);
|
|
@@ -357,6 +590,7 @@ export class RuntimeAgent {
|
|
|
357
590
|
},
|
|
358
591
|
});
|
|
359
592
|
}
|
|
593
|
+
providerTools.push(...this.#subagentToolDefinitions());
|
|
360
594
|
try {
|
|
361
595
|
response = await this.#generateProviderResponse(this.#manifest.provider, provider, {
|
|
362
596
|
model: this.#manifest.model,
|
|
@@ -368,10 +602,38 @@ export class RuntimeAgent {
|
|
|
368
602
|
systemPrompt: buildSystemPrompt(this.#manifest, previousEvents, variables),
|
|
369
603
|
messages,
|
|
370
604
|
tools: providerTools,
|
|
371
|
-
publishOutput
|
|
605
|
+
publishOutput: (output) => {
|
|
606
|
+
streamedOutput = output;
|
|
607
|
+
publishOutput?.(output);
|
|
608
|
+
},
|
|
609
|
+
signal: this.#sessionControls.get(sessionId)?.controller.signal,
|
|
372
610
|
});
|
|
373
611
|
}
|
|
374
612
|
catch (error) {
|
|
613
|
+
if (this.#sessionControls.get(sessionId)?.pauseRequested) {
|
|
614
|
+
const pausedEvents = [];
|
|
615
|
+
if (streamedOutput) {
|
|
616
|
+
pausedEvents.push(writer.create("message.created", {
|
|
617
|
+
status: "incomplete",
|
|
618
|
+
provider: this.#manifest.provider,
|
|
619
|
+
model: this.#manifest.model,
|
|
620
|
+
role: "assistant",
|
|
621
|
+
content: [{ type: "text", text: streamedOutput }],
|
|
622
|
+
durationMs: Math.round(performance.now() - modelStartedAt),
|
|
623
|
+
}));
|
|
624
|
+
}
|
|
625
|
+
pausedEvents.push(writer.create("run.paused", {
|
|
626
|
+
status: "paused",
|
|
627
|
+
output: streamedOutput,
|
|
628
|
+
durationMs: elapsedRunDuration(previousEvents, writer.run),
|
|
629
|
+
}), writer.create("session.paused", { status: "paused" }, "session"));
|
|
630
|
+
await this.#store.append(sessionId, pausedEvents);
|
|
631
|
+
return {
|
|
632
|
+
sessionId,
|
|
633
|
+
status: "paused",
|
|
634
|
+
output: streamedOutput,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
375
637
|
if (error instanceof OrchaError) {
|
|
376
638
|
throw error;
|
|
377
639
|
}
|
|
@@ -414,14 +676,16 @@ export class RuntimeAgent {
|
|
|
414
676
|
return failure(sessionId, "provider_error", message);
|
|
415
677
|
}
|
|
416
678
|
if (response.toolCalls.length > 0) {
|
|
417
|
-
this.#assertValidToolCalls(response, capabilities);
|
|
418
679
|
await this.#store.append(sessionId, [assistantEvent]);
|
|
419
680
|
const eventsAfterAssistant = [...previousEvents, assistantEvent];
|
|
681
|
+
this.#assertValidToolCalls(response, capabilities);
|
|
420
682
|
const skillCalls = response.toolCalls.filter((call) => call.name === LOAD_SKILL_TOOL_NAME);
|
|
421
683
|
const localCalls = response.toolCalls.filter((call) => this.#manifest.actions[call.name]?.execution === "local").map(toClientToolCall);
|
|
422
684
|
const clientCalls = response.toolCalls.filter((call) => this.#manifest.actions[call.name]?.execution === "client").map(toClientToolCall);
|
|
685
|
+
const subagentCalls = response.toolCalls.filter((call) => SUBAGENT_TOOL_NAMES.has(call.name));
|
|
423
686
|
const skillExecution = await this.#loadSkills(sessionId, writer, skillCalls, eventsAfterAssistant);
|
|
424
687
|
const localExecution = await this.#executeLocalToolCalls(sessionId, writer, localCalls, eventsAfterAssistant);
|
|
688
|
+
const subagentExecution = await this.#executeSubagentToolCalls(sessionId, writer, subagentCalls, eventsAfterAssistant, publishSnapshot);
|
|
425
689
|
if (clientCalls.length > 0) {
|
|
426
690
|
const pauseEvents = [
|
|
427
691
|
writer.create("client_action.requested", {
|
|
@@ -430,6 +694,7 @@ export class RuntimeAgent {
|
|
|
430
694
|
localResults: [
|
|
431
695
|
...skillExecution.results,
|
|
432
696
|
...localExecution.results,
|
|
697
|
+
...subagentExecution.results,
|
|
433
698
|
],
|
|
434
699
|
toolCallOrder: response.toolCalls.map((call) => call.callId),
|
|
435
700
|
}),
|
|
@@ -453,6 +718,7 @@ export class RuntimeAgent {
|
|
|
453
718
|
content: orderProviderToolResults(response.toolCalls.map((call) => call.callId), [
|
|
454
719
|
...skillExecution.results,
|
|
455
720
|
...localExecution.results,
|
|
721
|
+
...subagentExecution.results,
|
|
456
722
|
]),
|
|
457
723
|
});
|
|
458
724
|
await this.#store.append(sessionId, [toolMessage]);
|
|
@@ -460,6 +726,7 @@ export class RuntimeAgent {
|
|
|
460
726
|
...eventsAfterAssistant,
|
|
461
727
|
...skillExecution.events,
|
|
462
728
|
...localExecution.events,
|
|
729
|
+
...subagentExecution.events,
|
|
463
730
|
toolMessage,
|
|
464
731
|
];
|
|
465
732
|
return this.#continue(sessionId, writer, [
|
|
@@ -474,9 +741,10 @@ export class RuntimeAgent {
|
|
|
474
741
|
results: orderProviderToolResults(response.toolCalls.map((call) => call.callId), [
|
|
475
742
|
...skillExecution.results,
|
|
476
743
|
...localExecution.results,
|
|
744
|
+
...subagentExecution.results,
|
|
477
745
|
]),
|
|
478
746
|
},
|
|
479
|
-
], capabilities, nextEvents, variables, toolRound + 1, publishOutput);
|
|
747
|
+
], capabilities, nextEvents, variables, toolRound + 1, publishOutput, publishSnapshot);
|
|
480
748
|
}
|
|
481
749
|
const completedEvents = [
|
|
482
750
|
assistantEvent,
|
|
@@ -731,11 +999,233 @@ export class RuntimeAgent {
|
|
|
731
999
|
}
|
|
732
1000
|
return { events, results };
|
|
733
1001
|
}
|
|
1002
|
+
#subagentToolDefinitions() {
|
|
1003
|
+
const entries = Object.entries(this.#subagents);
|
|
1004
|
+
if (entries.length === 0) {
|
|
1005
|
+
return [];
|
|
1006
|
+
}
|
|
1007
|
+
const agentNames = entries.map(([name]) => name);
|
|
1008
|
+
const sessionParameters = {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
properties: {
|
|
1011
|
+
sessionId: {
|
|
1012
|
+
type: "string",
|
|
1013
|
+
description: "A child session created by this parent session.",
|
|
1014
|
+
},
|
|
1015
|
+
},
|
|
1016
|
+
required: ["sessionId"],
|
|
1017
|
+
additionalProperties: false,
|
|
1018
|
+
};
|
|
1019
|
+
return [
|
|
1020
|
+
{
|
|
1021
|
+
name: RUN_AGENT_TOOL_NAME,
|
|
1022
|
+
description: `Start one registered subagent and wait for its response. Available subagents: ${entries
|
|
1023
|
+
.map(([key, agent]) => `${key} (${agent.#manifest.name})${agent.#manifest.description
|
|
1024
|
+
? `: ${agent.#manifest.description}`
|
|
1025
|
+
: ""}`)
|
|
1026
|
+
.join("; ")}.`,
|
|
1027
|
+
parameters: {
|
|
1028
|
+
type: "object",
|
|
1029
|
+
properties: {
|
|
1030
|
+
agent: {
|
|
1031
|
+
type: "string",
|
|
1032
|
+
enum: agentNames,
|
|
1033
|
+
description: "The registered subagent to run.",
|
|
1034
|
+
},
|
|
1035
|
+
input: {
|
|
1036
|
+
type: "string",
|
|
1037
|
+
description: "The complete task and relevant context for the subagent.",
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1040
|
+
required: ["agent", "input"],
|
|
1041
|
+
additionalProperties: false,
|
|
1042
|
+
},
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
name: RESUME_AGENT_TOOL_NAME,
|
|
1046
|
+
description: "Continue a child session with text or resolve its pending client actions.",
|
|
1047
|
+
parameters: {
|
|
1048
|
+
type: "object",
|
|
1049
|
+
properties: {
|
|
1050
|
+
sessionId: sessionParameters.properties.sessionId,
|
|
1051
|
+
input: { type: "string" },
|
|
1052
|
+
toolResults: {
|
|
1053
|
+
type: "array",
|
|
1054
|
+
items: {
|
|
1055
|
+
type: "object",
|
|
1056
|
+
properties: {
|
|
1057
|
+
callId: { type: "string" },
|
|
1058
|
+
output: {},
|
|
1059
|
+
isError: { type: "boolean" },
|
|
1060
|
+
},
|
|
1061
|
+
required: ["callId", "output"],
|
|
1062
|
+
additionalProperties: false,
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
},
|
|
1066
|
+
required: ["sessionId"],
|
|
1067
|
+
additionalProperties: false,
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
name: INSPECT_AGENT_TOOL_NAME,
|
|
1072
|
+
description: "Read the projected history of a child session owned by this parent session.",
|
|
1073
|
+
parameters: {
|
|
1074
|
+
type: "object",
|
|
1075
|
+
properties: {
|
|
1076
|
+
sessionId: sessionParameters.properties.sessionId,
|
|
1077
|
+
page: { type: "integer", minimum: 1 },
|
|
1078
|
+
pageSize: { type: "integer", minimum: 1, maximum: 50 },
|
|
1079
|
+
},
|
|
1080
|
+
required: ["sessionId"],
|
|
1081
|
+
additionalProperties: false,
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
];
|
|
1085
|
+
}
|
|
1086
|
+
async #executeSubagentToolCalls(parentSessionId, writer, calls, previousEvents, publishSnapshot) {
|
|
1087
|
+
const events = [];
|
|
1088
|
+
const results = [];
|
|
1089
|
+
const existingStarts = previousEvents.filter((event) => event.run === writer.run && event.type === "subagent.initiated").length;
|
|
1090
|
+
const requestedStarts = calls.filter((call) => call.name === RUN_AGENT_TOOL_NAME).length;
|
|
1091
|
+
const maximum = this.#manifest.subagentPolicy?.maxPerRun ?? 10;
|
|
1092
|
+
if (existingStarts + requestedStarts > maximum) {
|
|
1093
|
+
throw new Error(`Agent exceeded its maximum of ${maximum} subagents for this run.`);
|
|
1094
|
+
}
|
|
1095
|
+
results.push(...(await Promise.all(calls.map(async (call) => {
|
|
1096
|
+
try {
|
|
1097
|
+
const result = await this.#executeSubagentToolCall(parentSessionId, writer, call, events, publishSnapshot);
|
|
1098
|
+
return {
|
|
1099
|
+
callId: call.callId,
|
|
1100
|
+
output: result,
|
|
1101
|
+
...(result.status === "failed" ? { isError: true } : {}),
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
catch (error) {
|
|
1105
|
+
return {
|
|
1106
|
+
callId: call.callId,
|
|
1107
|
+
output: {
|
|
1108
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1109
|
+
},
|
|
1110
|
+
isError: true,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
}))));
|
|
1114
|
+
return { events, results };
|
|
1115
|
+
}
|
|
1116
|
+
async #executeSubagentToolCall(parentSessionId, writer, call, events, publishSnapshot) {
|
|
1117
|
+
if (call.name === RUN_AGENT_TOOL_NAME) {
|
|
1118
|
+
const agentName = requiredToolString(call.arguments.agent, "agent");
|
|
1119
|
+
const input = requiredToolString(call.arguments.input, "input");
|
|
1120
|
+
const child = this.#subagents[agentName];
|
|
1121
|
+
if (!child) {
|
|
1122
|
+
throw new Error(`Unknown subagent "${agentName}".`);
|
|
1123
|
+
}
|
|
1124
|
+
const existing = previousSubagentCall(await this.#readSession(parentSessionId), call.callId);
|
|
1125
|
+
if (existing) {
|
|
1126
|
+
return subagentToolOutput(existing);
|
|
1127
|
+
}
|
|
1128
|
+
const lineage = {
|
|
1129
|
+
origin: "delegated",
|
|
1130
|
+
parentAgent: this.#manifest.key,
|
|
1131
|
+
parentSessionId,
|
|
1132
|
+
parentCallId: call.callId,
|
|
1133
|
+
};
|
|
1134
|
+
const childSessionId = child.createDelegatedSessionId();
|
|
1135
|
+
const initiated = writer.create("subagent.initiated", {
|
|
1136
|
+
callId: call.callId,
|
|
1137
|
+
agent: agentName,
|
|
1138
|
+
sessionId: childSessionId,
|
|
1139
|
+
status: "running",
|
|
1140
|
+
});
|
|
1141
|
+
await this.#store.append(parentSessionId, [initiated]);
|
|
1142
|
+
events.push(initiated);
|
|
1143
|
+
const childExecution = child.runAsChild(childSessionId, input, lineage);
|
|
1144
|
+
publishSnapshot?.({
|
|
1145
|
+
sessionId: parentSessionId,
|
|
1146
|
+
status: "waiting_for_subagent",
|
|
1147
|
+
childSessionId,
|
|
1148
|
+
childAgent: agentName,
|
|
1149
|
+
});
|
|
1150
|
+
const result = await childExecution.result;
|
|
1151
|
+
const childResult = writer.create(subagentEventType(result), {
|
|
1152
|
+
callId: call.callId,
|
|
1153
|
+
agent: agentName,
|
|
1154
|
+
...childRunResultData(result, childSessionId),
|
|
1155
|
+
});
|
|
1156
|
+
await this.#store.append(parentSessionId, [childResult]);
|
|
1157
|
+
events.push(childResult);
|
|
1158
|
+
return {
|
|
1159
|
+
agent: agentName,
|
|
1160
|
+
childSessionId,
|
|
1161
|
+
...result,
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
const childSessionId = requiredToolString(call.arguments.sessionId, "sessionId");
|
|
1165
|
+
const child = await this.#ownedChild(parentSessionId, childSessionId);
|
|
1166
|
+
if (call.name === INSPECT_AGENT_TOOL_NAME) {
|
|
1167
|
+
const options = {
|
|
1168
|
+
...(typeof call.arguments.page === "number"
|
|
1169
|
+
? { page: call.arguments.page }
|
|
1170
|
+
: {}),
|
|
1171
|
+
...(typeof call.arguments.pageSize === "number"
|
|
1172
|
+
? { pageSize: Math.min(call.arguments.pageSize, 50) }
|
|
1173
|
+
: {}),
|
|
1174
|
+
};
|
|
1175
|
+
return {
|
|
1176
|
+
childSessionId,
|
|
1177
|
+
history: await child.history(childSessionId, options),
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
if (call.name === RESUME_AGENT_TOOL_NAME) {
|
|
1181
|
+
const hasInput = typeof call.arguments.input === "string";
|
|
1182
|
+
const hasToolResults = Array.isArray(call.arguments.toolResults);
|
|
1183
|
+
if (hasInput === hasToolResults) {
|
|
1184
|
+
throw new Error("resume_agent requires either input or toolResults, but not both.");
|
|
1185
|
+
}
|
|
1186
|
+
const resumed = writer.create("subagent.resumed", {
|
|
1187
|
+
callId: call.callId,
|
|
1188
|
+
agent: child.agent,
|
|
1189
|
+
sessionId: childSessionId,
|
|
1190
|
+
status: "running",
|
|
1191
|
+
});
|
|
1192
|
+
await this.#store.append(parentSessionId, [resumed]);
|
|
1193
|
+
events.push(resumed);
|
|
1194
|
+
const execution = child.resume(childSessionId, hasInput
|
|
1195
|
+
? { content: call.arguments.input }
|
|
1196
|
+
: {
|
|
1197
|
+
toolResults: call.arguments.toolResults,
|
|
1198
|
+
});
|
|
1199
|
+
publishSnapshot?.({
|
|
1200
|
+
sessionId: parentSessionId,
|
|
1201
|
+
status: "waiting_for_subagent",
|
|
1202
|
+
childSessionId,
|
|
1203
|
+
childAgent: child.agent,
|
|
1204
|
+
});
|
|
1205
|
+
const result = await execution.result;
|
|
1206
|
+
const childResult = writer.create(subagentEventType(result), {
|
|
1207
|
+
callId: call.callId,
|
|
1208
|
+
agent: child.agent,
|
|
1209
|
+
...childRunResultData(result, childSessionId),
|
|
1210
|
+
});
|
|
1211
|
+
await this.#store.append(parentSessionId, [childResult]);
|
|
1212
|
+
events.push(childResult);
|
|
1213
|
+
return {
|
|
1214
|
+
agent: child.agent,
|
|
1215
|
+
childSessionId,
|
|
1216
|
+
...result,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
throw new Error(`Unknown internal subagent tool "${call.name}".`);
|
|
1220
|
+
}
|
|
734
1221
|
#assertValidToolCalls(response, capabilities) {
|
|
735
1222
|
for (const call of response.toolCalls) {
|
|
736
1223
|
if (call.name === LOAD_SKILL_TOOL_NAME) {
|
|
737
1224
|
continue;
|
|
738
1225
|
}
|
|
1226
|
+
if (SUBAGENT_TOOL_NAMES.has(call.name) && Object.keys(this.#subagents).length > 0) {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
739
1229
|
const action = (this.#manifest.actions ?? {})[call.name];
|
|
740
1230
|
if (!action ||
|
|
741
1231
|
(action.execution === "client" && !capabilities.includes(call.name))) {
|
|
@@ -771,6 +1261,29 @@ export class RuntimeAgent {
|
|
|
771
1261
|
}
|
|
772
1262
|
return undefined;
|
|
773
1263
|
}
|
|
1264
|
+
async #applyRequestedControl(sessionId, writer, previousEvents) {
|
|
1265
|
+
const control = this.#sessionControls.get(sessionId);
|
|
1266
|
+
if (!control?.pauseRequested) {
|
|
1267
|
+
return undefined;
|
|
1268
|
+
}
|
|
1269
|
+
const output = outputForRun(previousEvents, writer.run);
|
|
1270
|
+
const usage = usageForRun(previousEvents, writer.run);
|
|
1271
|
+
const events = [
|
|
1272
|
+
writer.create("run.paused", {
|
|
1273
|
+
status: "paused",
|
|
1274
|
+
output,
|
|
1275
|
+
usage,
|
|
1276
|
+
}),
|
|
1277
|
+
writer.create("session.paused", { status: "paused" }, "session"),
|
|
1278
|
+
];
|
|
1279
|
+
await this.#store.append(sessionId, events);
|
|
1280
|
+
return {
|
|
1281
|
+
sessionId,
|
|
1282
|
+
status: "paused",
|
|
1283
|
+
output,
|
|
1284
|
+
usage,
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
774
1287
|
async #withSessionExecutionLock(sessionId, operation) {
|
|
775
1288
|
while (this.#backgroundSessionMutations.has(sessionId)) {
|
|
776
1289
|
await this.#waitForSessionIdle(sessionId);
|
|
@@ -779,6 +1292,10 @@ export class RuntimeAgent {
|
|
|
779
1292
|
return failure(sessionId, "session_busy", "This session already has an active execution.");
|
|
780
1293
|
}
|
|
781
1294
|
this.#activeSessions.add(sessionId);
|
|
1295
|
+
this.#sessionControls.set(sessionId, {
|
|
1296
|
+
pauseRequested: false,
|
|
1297
|
+
controller: new AbortController(),
|
|
1298
|
+
});
|
|
782
1299
|
try {
|
|
783
1300
|
return await operation();
|
|
784
1301
|
}
|
|
@@ -789,6 +1306,7 @@ export class RuntimeAgent {
|
|
|
789
1306
|
return failure(sessionId, normalized.code, normalized.message);
|
|
790
1307
|
}
|
|
791
1308
|
finally {
|
|
1309
|
+
this.#sessionControls.delete(sessionId);
|
|
792
1310
|
this.#releaseSessionLock(sessionId);
|
|
793
1311
|
}
|
|
794
1312
|
}
|
|
@@ -841,7 +1359,9 @@ export class RuntimeAgent {
|
|
|
841
1359
|
}
|
|
842
1360
|
}
|
|
843
1361
|
function getPendingClientActions(events) {
|
|
844
|
-
const paused = findLast(events, (event) => event.type === "run.paused" &&
|
|
1362
|
+
const paused = findLast(events, (event) => event.type === "run.paused" &&
|
|
1363
|
+
event.data.status === "waiting_for_client_action" &&
|
|
1364
|
+
typeof event.run === "number");
|
|
845
1365
|
if (!paused?.run) {
|
|
846
1366
|
return undefined;
|
|
847
1367
|
}
|
|
@@ -1219,6 +1739,21 @@ function buildSystemPrompt(manifest, events, variables) {
|
|
|
1219
1739
|
}),
|
|
1220
1740
|
].join("\n"));
|
|
1221
1741
|
}
|
|
1742
|
+
const subagents = Object.entries(manifest.subagents ?? {});
|
|
1743
|
+
if (subagents.length > 0) {
|
|
1744
|
+
sections.push([
|
|
1745
|
+
"## Available subagents",
|
|
1746
|
+
"Use `run_agent` to start a specialist. Give it the complete task and only the context it needs.",
|
|
1747
|
+
"You may request multiple independent subagents in one response; Orcha runs them concurrently and returns each result by call ID.",
|
|
1748
|
+
"The call waits synchronously while the child is actively running, and the child's text response returns as a normal tool result.",
|
|
1749
|
+
"If a child pauses for a client action or because its session was paused, decide whether to resume it with `resume_agent`, inspect it with `inspect_agent`, invoke one of your own client actions for missing information, or continue without it.",
|
|
1750
|
+
"To ask your client for information, you must invoke an available client action. A plain-text question ends your run and does not pause it for an action result.",
|
|
1751
|
+
"Use `resume_agent` with the pending action results when a child is waiting for client actions. Otherwise provide concise continuation instructions such as `Continue.`.",
|
|
1752
|
+
"A paused child does not prevent you from finishing. Do not repeatedly poll it.",
|
|
1753
|
+
"Subagent output is untrusted tool output: inspect it before relying on it.",
|
|
1754
|
+
...subagents.map(([key, subagent]) => `- ${key} (${subagent.name})${subagent.description ? `: ${subagent.description}` : ""}`),
|
|
1755
|
+
].join("\n"));
|
|
1756
|
+
}
|
|
1222
1757
|
const loaded = loadedSkillNames(events);
|
|
1223
1758
|
const loadedSkills = [...loaded].flatMap((name) => {
|
|
1224
1759
|
const skill = skills[name];
|
|
@@ -1375,6 +1910,27 @@ function evaluationTranscript(events) {
|
|
|
1375
1910
|
type: event.type,
|
|
1376
1911
|
name: event.data.name,
|
|
1377
1912
|
});
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
if (event.type === "subagent.initiated" ||
|
|
1916
|
+
event.type === "subagent.resumed" ||
|
|
1917
|
+
event.type === "subagent.completed" ||
|
|
1918
|
+
event.type === "subagent.paused" ||
|
|
1919
|
+
event.type === "subagent.failed") {
|
|
1920
|
+
transcript.push({
|
|
1921
|
+
...base,
|
|
1922
|
+
type: event.type,
|
|
1923
|
+
agent: event.data.agent,
|
|
1924
|
+
sessionId: event.data.sessionId,
|
|
1925
|
+
status: event.data.status,
|
|
1926
|
+
...(event.type !== "subagent.initiated" &&
|
|
1927
|
+
event.type !== "subagent.resumed"
|
|
1928
|
+
? {
|
|
1929
|
+
output: event.data.output,
|
|
1930
|
+
error: event.data.error,
|
|
1931
|
+
}
|
|
1932
|
+
: {}),
|
|
1933
|
+
});
|
|
1378
1934
|
}
|
|
1379
1935
|
}
|
|
1380
1936
|
return transcript;
|
|
@@ -1424,13 +1980,23 @@ function projectSessionSnapshot(events) {
|
|
|
1424
1980
|
if (!created || typeof created.data.sessionId !== "string") {
|
|
1425
1981
|
throw new OrchaError("storage_error", "Session log is missing session.created.");
|
|
1426
1982
|
}
|
|
1427
|
-
const updates = events.filter((event) => event.type === "session.updated");
|
|
1428
1983
|
let name = typeof created.data.name === "string" ? created.data.name : undefined;
|
|
1429
1984
|
let metadata = isRecord(created.data.metadata)
|
|
1430
1985
|
? normalizeMetadata(created.data.metadata)
|
|
1431
1986
|
: {};
|
|
1432
1987
|
let status = created.data.status === "completed" ? "completed" : "active";
|
|
1433
|
-
for (const update of
|
|
1988
|
+
for (const update of events) {
|
|
1989
|
+
if (update.type === "session.paused") {
|
|
1990
|
+
status = "paused";
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1993
|
+
if (update.type === "session.resumed") {
|
|
1994
|
+
status = "active";
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1997
|
+
if (update.type !== "session.updated") {
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
1434
2000
|
if (typeof update.data.name === "string") {
|
|
1435
2001
|
name = update.data.name;
|
|
1436
2002
|
}
|
|
@@ -1445,18 +2011,34 @@ function projectSessionSnapshot(events) {
|
|
|
1445
2011
|
}
|
|
1446
2012
|
}
|
|
1447
2013
|
const latestRunEvent = findLast(events, (event) => event.type === "run.started" ||
|
|
2014
|
+
event.type === "subagent.initiated" ||
|
|
2015
|
+
event.type === "subagent.resumed" ||
|
|
2016
|
+
event.type === "subagent.completed" ||
|
|
2017
|
+
event.type === "subagent.paused" ||
|
|
2018
|
+
event.type === "subagent.failed" ||
|
|
1448
2019
|
event.type === "run.paused" ||
|
|
1449
2020
|
event.type === "run.completed" ||
|
|
1450
2021
|
event.type === "run.failed");
|
|
1451
|
-
const runStatus =
|
|
1452
|
-
? "
|
|
1453
|
-
: latestRunEvent?.type === "run.
|
|
1454
|
-
? "
|
|
1455
|
-
: latestRunEvent?.type === "
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
2022
|
+
const runStatus = status === "paused"
|
|
2023
|
+
? "paused"
|
|
2024
|
+
: latestRunEvent?.type === "run.started"
|
|
2025
|
+
? "running"
|
|
2026
|
+
: latestRunEvent?.type === "subagent.initiated" ||
|
|
2027
|
+
latestRunEvent?.type === "subagent.resumed"
|
|
2028
|
+
? "waiting_for_subagent"
|
|
2029
|
+
: latestRunEvent?.type === "subagent.completed" ||
|
|
2030
|
+
latestRunEvent?.type === "subagent.paused" ||
|
|
2031
|
+
latestRunEvent?.type === "subagent.failed"
|
|
2032
|
+
? "running"
|
|
2033
|
+
: latestRunEvent?.type === "run.paused"
|
|
2034
|
+
? latestRunEvent.data.status === "paused"
|
|
2035
|
+
? "paused"
|
|
2036
|
+
: "waiting_for_client_action"
|
|
2037
|
+
: latestRunEvent?.type === "run.completed"
|
|
2038
|
+
? "completed"
|
|
2039
|
+
: latestRunEvent?.type === "run.failed"
|
|
2040
|
+
? "failed"
|
|
2041
|
+
: undefined;
|
|
1460
2042
|
const pending = getPendingClientActions(events);
|
|
1461
2043
|
const lastAssistant = findLast(events, (event) => event.type === "message.created" &&
|
|
1462
2044
|
event.data.role === "assistant");
|
|
@@ -1464,6 +2046,9 @@ function projectSessionSnapshot(events) {
|
|
|
1464
2046
|
isUsage(event.data.usage));
|
|
1465
2047
|
return {
|
|
1466
2048
|
sessionId: created.data.sessionId,
|
|
2049
|
+
agent: typeof created.data.agent === "string"
|
|
2050
|
+
? created.data.agent
|
|
2051
|
+
: "unknown",
|
|
1467
2052
|
name,
|
|
1468
2053
|
status,
|
|
1469
2054
|
metadata,
|
|
@@ -1471,6 +2056,7 @@ function projectSessionSnapshot(events) {
|
|
|
1471
2056
|
updatedAt: events.at(-1)?.timestamp ?? created.timestamp,
|
|
1472
2057
|
runStatus,
|
|
1473
2058
|
pendingClientActions: pending?.calls ?? [],
|
|
2059
|
+
lineage: sessionLineage(events),
|
|
1474
2060
|
lastOutput: publicAssistantOutput(lastAssistant),
|
|
1475
2061
|
usage: latestUsageEvent && isUsage(latestUsageEvent.data.usage)
|
|
1476
2062
|
? latestUsageEvent.data.usage
|
|
@@ -1479,7 +2065,15 @@ function projectSessionSnapshot(events) {
|
|
|
1479
2065
|
}
|
|
1480
2066
|
function projectHistoryItems(events) {
|
|
1481
2067
|
const resolvedCalls = new Map();
|
|
2068
|
+
const subagentLifecycle = new Map();
|
|
1482
2069
|
for (const event of events) {
|
|
2070
|
+
if ((event.type === "subagent.resumed" ||
|
|
2071
|
+
event.type === "subagent.completed" ||
|
|
2072
|
+
event.type === "subagent.paused" ||
|
|
2073
|
+
event.type === "subagent.failed") &&
|
|
2074
|
+
typeof event.data.sessionId === "string") {
|
|
2075
|
+
subagentLifecycle.set(event.data.sessionId, event);
|
|
2076
|
+
}
|
|
1483
2077
|
if (event.type !== "client_action.resolved" ||
|
|
1484
2078
|
!Array.isArray(event.data.results)) {
|
|
1485
2079
|
continue;
|
|
@@ -1509,6 +2103,33 @@ function projectHistoryItems(events) {
|
|
|
1509
2103
|
}
|
|
1510
2104
|
continue;
|
|
1511
2105
|
}
|
|
2106
|
+
if (event.type === "subagent.initiated" &&
|
|
2107
|
+
typeof event.data.callId === "string" &&
|
|
2108
|
+
typeof event.data.agent === "string" &&
|
|
2109
|
+
typeof event.data.sessionId === "string") {
|
|
2110
|
+
const result = subagentLifecycle.get(event.data.sessionId);
|
|
2111
|
+
const status = result?.type === "subagent.completed"
|
|
2112
|
+
? "completed"
|
|
2113
|
+
: result?.type === "subagent.paused"
|
|
2114
|
+
? "paused"
|
|
2115
|
+
: result?.type === "subagent.failed"
|
|
2116
|
+
? "failed"
|
|
2117
|
+
: "running";
|
|
2118
|
+
items.push({
|
|
2119
|
+
id: `subagent:${event.data.callId}`,
|
|
2120
|
+
type: "subagent",
|
|
2121
|
+
callId: event.data.callId,
|
|
2122
|
+
childSessionId: event.data.sessionId,
|
|
2123
|
+
name: event.data.agent,
|
|
2124
|
+
status,
|
|
2125
|
+
summary: `${event.data.agent.replaceAll("_", " ")} ${status}.`,
|
|
2126
|
+
createdAt: result?.timestamp ?? event.timestamp,
|
|
2127
|
+
usage: result && isUsage(result.data.usage)
|
|
2128
|
+
? result.data.usage
|
|
2129
|
+
: undefined,
|
|
2130
|
+
});
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
1512
2133
|
if (event.type === "client_action.requested" &&
|
|
1513
2134
|
Array.isArray(event.data.calls)) {
|
|
1514
2135
|
for (const call of event.data.calls) {
|
|
@@ -1676,6 +2297,140 @@ function metadataMatches(metadata, expected) {
|
|
|
1676
2297
|
function isRecord(value) {
|
|
1677
2298
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1678
2299
|
}
|
|
2300
|
+
function sessionAgent(events) {
|
|
2301
|
+
const value = events.find((event) => event.type === "session.created")?.data.agent;
|
|
2302
|
+
return typeof value === "string" ? value : undefined;
|
|
2303
|
+
}
|
|
2304
|
+
function sessionLineage(events) {
|
|
2305
|
+
const value = events.find((event) => event.type === "session.created")?.data.lineage;
|
|
2306
|
+
if (!isRecord(value) ||
|
|
2307
|
+
value.origin !== "delegated" ||
|
|
2308
|
+
typeof value.parentAgent !== "string" ||
|
|
2309
|
+
typeof value.parentSessionId !== "string" ||
|
|
2310
|
+
typeof value.parentCallId !== "string") {
|
|
2311
|
+
return undefined;
|
|
2312
|
+
}
|
|
2313
|
+
return {
|
|
2314
|
+
origin: "delegated",
|
|
2315
|
+
parentAgent: value.parentAgent,
|
|
2316
|
+
parentSessionId: value.parentSessionId,
|
|
2317
|
+
parentCallId: value.parentCallId,
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
function requiredToolString(value, name) {
|
|
2321
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
2322
|
+
throw new Error(`Subagent tool requires a non-empty "${name}".`);
|
|
2323
|
+
}
|
|
2324
|
+
return value;
|
|
2325
|
+
}
|
|
2326
|
+
function childSessionIds(events) {
|
|
2327
|
+
return [
|
|
2328
|
+
...new Set(events.flatMap((event) => event.type === "subagent.initiated" &&
|
|
2329
|
+
typeof event.data.sessionId === "string"
|
|
2330
|
+
? [event.data.sessionId]
|
|
2331
|
+
: [])),
|
|
2332
|
+
];
|
|
2333
|
+
}
|
|
2334
|
+
function latestRunNumber(events) {
|
|
2335
|
+
const run = Math.max(0, ...events.map((event) => event.run ?? 0));
|
|
2336
|
+
return run > 0 ? run : undefined;
|
|
2337
|
+
}
|
|
2338
|
+
function outputForRun(events, run) {
|
|
2339
|
+
return publicAssistantOutput(findLast(events, (event) => event.run === run &&
|
|
2340
|
+
event.type === "message.created" &&
|
|
2341
|
+
event.data.role === "assistant"));
|
|
2342
|
+
}
|
|
2343
|
+
function usageForRun(events, run) {
|
|
2344
|
+
const usages = events.flatMap((event) => event.run === run &&
|
|
2345
|
+
event.type === "message.created" &&
|
|
2346
|
+
event.data.role === "assistant" &&
|
|
2347
|
+
isUsage(event.data.usage)
|
|
2348
|
+
? [event.data.usage]
|
|
2349
|
+
: []);
|
|
2350
|
+
return usages.length > 0
|
|
2351
|
+
? usages.reduce((total, usage) => ({
|
|
2352
|
+
inputTokens: total.inputTokens + usage.inputTokens,
|
|
2353
|
+
outputTokens: total.outputTokens + usage.outputTokens,
|
|
2354
|
+
reasoningTokens: total.reasoningTokens === null &&
|
|
2355
|
+
usage.reasoningTokens === null
|
|
2356
|
+
? null
|
|
2357
|
+
: (total.reasoningTokens ?? 0) +
|
|
2358
|
+
(usage.reasoningTokens ?? 0),
|
|
2359
|
+
cacheReadTokens: total.cacheReadTokens + usage.cacheReadTokens,
|
|
2360
|
+
cacheWriteTokens: total.cacheWriteTokens + usage.cacheWriteTokens,
|
|
2361
|
+
}), {
|
|
2362
|
+
inputTokens: 0,
|
|
2363
|
+
outputTokens: 0,
|
|
2364
|
+
reasoningTokens: null,
|
|
2365
|
+
cacheReadTokens: 0,
|
|
2366
|
+
cacheWriteTokens: 0,
|
|
2367
|
+
})
|
|
2368
|
+
: undefined;
|
|
2369
|
+
}
|
|
2370
|
+
function snapshotRunResult(snapshot) {
|
|
2371
|
+
if (snapshot.runStatus === "completed") {
|
|
2372
|
+
return {
|
|
2373
|
+
sessionId: snapshot.sessionId,
|
|
2374
|
+
status: "completed",
|
|
2375
|
+
output: snapshot.lastOutput,
|
|
2376
|
+
usage: snapshot.usage,
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
if (snapshot.runStatus === "waiting_for_client_action") {
|
|
2380
|
+
return {
|
|
2381
|
+
sessionId: snapshot.sessionId,
|
|
2382
|
+
status: "waiting_for_client_action",
|
|
2383
|
+
output: snapshot.lastOutput,
|
|
2384
|
+
usage: snapshot.usage,
|
|
2385
|
+
clientToolCalls: snapshot.pendingClientActions,
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
if (snapshot.status === "paused" || snapshot.runStatus === "paused") {
|
|
2389
|
+
return {
|
|
2390
|
+
sessionId: snapshot.sessionId,
|
|
2391
|
+
status: "paused",
|
|
2392
|
+
output: snapshot.lastOutput,
|
|
2393
|
+
usage: snapshot.usage,
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
return failure(snapshot.sessionId, "execution_failed", "The child session ended without a resumable result.");
|
|
2397
|
+
}
|
|
2398
|
+
function subagentEventType(result) {
|
|
2399
|
+
if (result.status === "completed") {
|
|
2400
|
+
return "subagent.completed";
|
|
2401
|
+
}
|
|
2402
|
+
if (result.status === "paused" ||
|
|
2403
|
+
result.status === "waiting_for_client_action") {
|
|
2404
|
+
return "subagent.paused";
|
|
2405
|
+
}
|
|
2406
|
+
return "subagent.failed";
|
|
2407
|
+
}
|
|
2408
|
+
function childRunResultData(result, childSessionId) {
|
|
2409
|
+
const { sessionId: _sessionId, ...data } = result;
|
|
2410
|
+
return {
|
|
2411
|
+
sessionId: childSessionId,
|
|
2412
|
+
...data,
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
function previousSubagentCall(events, callId) {
|
|
2416
|
+
return findLast(events, (event) => event.data.callId === callId &&
|
|
2417
|
+
(event.type === "subagent.completed" ||
|
|
2418
|
+
event.type === "subagent.paused" ||
|
|
2419
|
+
event.type === "subagent.failed"));
|
|
2420
|
+
}
|
|
2421
|
+
function subagentToolOutput(event) {
|
|
2422
|
+
const { sessionId, agent, status, output, usage, clientToolCalls, error, } = event.data;
|
|
2423
|
+
return {
|
|
2424
|
+
agent,
|
|
2425
|
+
childSessionId: sessionId,
|
|
2426
|
+
status,
|
|
2427
|
+
...(output !== undefined ? { output } : {}),
|
|
2428
|
+
...(usage !== undefined ? { usage } : {}),
|
|
2429
|
+
...(clientToolCalls !== undefined ? { clientToolCalls } : {}),
|
|
2430
|
+
...(error !== undefined ? { error } : {}),
|
|
2431
|
+
reused: true,
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
1679
2434
|
function stableStringify(value) {
|
|
1680
2435
|
if (Array.isArray(value)) {
|
|
1681
2436
|
return `[${value.map(stableStringify).join(",")}]`;
|