@g3un/pi-orchestra 0.1.1 → 0.2.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 +8 -6
- package/docs/orchestration-model.md +8 -4
- package/package.json +12 -1
- package/src/core/orchestra.ts +1 -195
- package/src/core/subagent.ts +8 -0
- package/src/extension/index.ts +33 -3
- package/src/extension/orchestra-events.ts +231 -0
- package/src/tools/bus.ts +9 -133
- package/src/tools/workflow.ts +36 -105
- package/src/tools/workgroup.ts +186 -71
- package/src/utils.ts +4 -14
package/src/tools/bus.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { defineTool, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import type { AgentRun } from "../core/subagent.ts";
|
|
4
3
|
import type { Bus, BusMessage } from "../core/bus.ts";
|
|
5
|
-
import type { OrchestraApi
|
|
4
|
+
import type { OrchestraApi } from "../core/orchestra.ts";
|
|
6
5
|
import { formatNamedEntityLabel } from "../utils.ts";
|
|
7
6
|
|
|
8
7
|
export type BusInput =
|
|
@@ -19,31 +18,11 @@ export type BusInput =
|
|
|
19
18
|
id: string;
|
|
20
19
|
message: string;
|
|
21
20
|
from?: string;
|
|
22
|
-
}
|
|
23
|
-
| {
|
|
24
|
-
action: "wait_settled";
|
|
25
|
-
id: string;
|
|
26
|
-
/** Defaults to 10 minutes. Use null to wait indefinitely. */
|
|
27
|
-
timeoutMs?: number | null;
|
|
28
|
-
}
|
|
29
|
-
| {
|
|
30
|
-
action: "wait_next";
|
|
31
|
-
id: string;
|
|
32
|
-
/** Run ids or names to ignore. */
|
|
33
|
-
excludeRunIds?: string[];
|
|
34
|
-
/** Defaults to 10 minutes. Use null to wait indefinitely. */
|
|
35
|
-
timeoutMs?: number | null;
|
|
36
21
|
};
|
|
37
22
|
|
|
38
23
|
export interface BusOutput {
|
|
39
24
|
bus?: Bus;
|
|
40
25
|
busMessage?: BusMessage;
|
|
41
|
-
run?: AgentRun;
|
|
42
|
-
runResult?: WaitRunResult;
|
|
43
|
-
runs?: AgentRun[];
|
|
44
|
-
runResults?: WaitRunResult[];
|
|
45
|
-
timedOut?: boolean;
|
|
46
|
-
pendingRunIds?: string[];
|
|
47
26
|
message: string;
|
|
48
27
|
}
|
|
49
28
|
|
|
@@ -57,8 +36,8 @@ export interface BusToolDeps {
|
|
|
57
36
|
}
|
|
58
37
|
|
|
59
38
|
const BusActionParams = Type.String({
|
|
60
|
-
enum: ["create", "status", "publish"
|
|
61
|
-
description: "create/status/publish
|
|
39
|
+
enum: ["create", "status", "publish"],
|
|
40
|
+
description: "create/status/publish shared context buses; completion is delivered through pi-orchestra events.",
|
|
62
41
|
});
|
|
63
42
|
|
|
64
43
|
const BusToolParams = Type.Object(
|
|
@@ -79,24 +58,6 @@ const BusToolParams = Type.Object(
|
|
|
79
58
|
description: "Required for action=publish. Shared context for attached agents.",
|
|
80
59
|
}),
|
|
81
60
|
),
|
|
82
|
-
excludeRunIds: Type.Optional(
|
|
83
|
-
Type.Array(Type.String(), {
|
|
84
|
-
description: "Optional for action=wait_next. Already handled run ids/names.",
|
|
85
|
-
}),
|
|
86
|
-
),
|
|
87
|
-
timeoutMs: Type.Optional(
|
|
88
|
-
Type.Union(
|
|
89
|
-
[
|
|
90
|
-
Type.Number({
|
|
91
|
-
exclusiveMinimum: 0,
|
|
92
|
-
}),
|
|
93
|
-
Type.Null(),
|
|
94
|
-
],
|
|
95
|
-
{
|
|
96
|
-
description: "Optional for wait actions. Positive ms; default 10 min; null waits indefinitely.",
|
|
97
|
-
},
|
|
98
|
-
),
|
|
99
|
-
),
|
|
100
61
|
},
|
|
101
62
|
{ additionalProperties: false },
|
|
102
63
|
);
|
|
@@ -118,35 +79,6 @@ export function createBusTool({ orchestra }: BusToolDeps): BusTool {
|
|
|
118
79
|
return { bus, message: formatBusStatus(bus) };
|
|
119
80
|
}
|
|
120
81
|
|
|
121
|
-
if (input.action === "wait_settled") {
|
|
122
|
-
const output = await orchestra.waitBusSettled(bus.id, { timeoutMs: input.timeoutMs });
|
|
123
|
-
return {
|
|
124
|
-
bus: output.bus,
|
|
125
|
-
runs: output.runs,
|
|
126
|
-
runResults: output.runResults,
|
|
127
|
-
timedOut: output.timedOut,
|
|
128
|
-
pendingRunIds: output.pendingRunIds,
|
|
129
|
-
message: formatWaitBusSettledMessage(output),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (input.action === "wait_next") {
|
|
134
|
-
const output = await orchestra.waitNextRun(bus.id, {
|
|
135
|
-
excludeRunIds: input.excludeRunIds,
|
|
136
|
-
timeoutMs: input.timeoutMs,
|
|
137
|
-
});
|
|
138
|
-
return {
|
|
139
|
-
bus: output.bus,
|
|
140
|
-
run: output.run,
|
|
141
|
-
runResult: output.runResult,
|
|
142
|
-
runs: output.runs,
|
|
143
|
-
runResults: output.runResults,
|
|
144
|
-
timedOut: output.timedOut,
|
|
145
|
-
pendingRunIds: output.pendingRunIds,
|
|
146
|
-
message: formatWaitNextRunMessage(output),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
82
|
const published = await orchestra.publishBus(input.id, input.message, input.from ?? "main");
|
|
151
83
|
return {
|
|
152
84
|
bus: published.bus,
|
|
@@ -161,13 +93,12 @@ export function defineBusPiTool(resolveTool: (ctx: ExtensionContext) => BusTool)
|
|
|
161
93
|
return defineTool({
|
|
162
94
|
name: "bus",
|
|
163
95
|
label: "Bus",
|
|
164
|
-
description: "Create, inspect, publish to
|
|
165
|
-
promptSnippet:
|
|
166
|
-
"Use one bus per delegated work item; spawn related subagents on it and collect results with wait actions.",
|
|
96
|
+
description: "Create, inspect, and publish to work buses.",
|
|
97
|
+
promptSnippet: "Use one bus per delegated work item; spawn related subagents or workgroups on it.",
|
|
167
98
|
promptGuidelines: [
|
|
168
|
-
"
|
|
169
|
-
"publish
|
|
170
|
-
"
|
|
99
|
+
"Use bus create before spawning related subagents or workgroups; reuse it for that work item.",
|
|
100
|
+
"Use bus publish to send shared context to attached agents; bus status shows published messages.",
|
|
101
|
+
"Do not wait on buses; pi-orchestra delivers subagent and workgroup finish events automatically.",
|
|
171
102
|
],
|
|
172
103
|
parameters: BusToolParams,
|
|
173
104
|
executionMode: "sequential",
|
|
@@ -190,21 +121,6 @@ function toBusInput(params: RawBusParams): BusInput {
|
|
|
190
121
|
return { action: "status", id: params.id };
|
|
191
122
|
}
|
|
192
123
|
|
|
193
|
-
if (params.action === "wait_settled") {
|
|
194
|
-
if (!params.id) throw new Error("bus action=wait_settled requires id.");
|
|
195
|
-
return { action: "wait_settled", id: params.id, timeoutMs: params.timeoutMs };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (params.action === "wait_next") {
|
|
199
|
-
if (!params.id) throw new Error("bus action=wait_next requires id.");
|
|
200
|
-
return {
|
|
201
|
-
action: "wait_next",
|
|
202
|
-
id: params.id,
|
|
203
|
-
excludeRunIds: params.excludeRunIds,
|
|
204
|
-
timeoutMs: params.timeoutMs,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
124
|
if (!params.id) throw new Error("bus action=publish requires id.");
|
|
209
125
|
if (!params.message) throw new Error("bus action=publish requires message.");
|
|
210
126
|
return { action: "publish", id: params.id, message: params.message };
|
|
@@ -227,49 +143,9 @@ function formatBusMessage(message: BusMessage): string {
|
|
|
227
143
|
return [`- ${message.id} from ${message.from}:`, message.message].join("\n");
|
|
228
144
|
}
|
|
229
145
|
|
|
230
|
-
function formatWaitBusSettledMessage(result: WaitBusSettledResult): string {
|
|
231
|
-
const busLabel = formatNamedEntityLabel(result.bus);
|
|
232
|
-
const headline = result.timedOut
|
|
233
|
-
? `Timed out waiting for bus ${busLabel} to settle; ${result.pendingRunIds.length} run(s) still pending.`
|
|
234
|
-
: `All ${result.runs.length} run(s) attached to bus ${busLabel} reached terminal state.`;
|
|
235
|
-
if (result.runs.length === 0) return headline;
|
|
236
|
-
|
|
237
|
-
return [headline, "", "Runs:", ...result.runs.map(formatRunSummary)].join("\n");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function formatRunSummary(run: AgentRun): string {
|
|
241
|
-
const runLabel = formatNamedEntityLabel(run);
|
|
242
|
-
if (!run.result) return `- ${runLabel}: ${run.state}`;
|
|
243
|
-
return `- ${runLabel}: ${run.state} result=${run.result.status} summary=${run.result.summary}`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function formatWaitNextRunMessage(result: WaitNextRunResult): string {
|
|
247
|
-
const busLabel = formatNamedEntityLabel(result.bus);
|
|
248
|
-
if (result.run) {
|
|
249
|
-
return [
|
|
250
|
-
`Next terminal run on bus ${busLabel}: ${formatNamedEntityLabel(result.run)} is ${result.run.state}.`,
|
|
251
|
-
"",
|
|
252
|
-
formatRunResult(result.run),
|
|
253
|
-
].join("\n");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (result.timedOut) {
|
|
257
|
-
return `Timed out waiting for the next run on bus ${busLabel}; ${result.pendingRunIds.length} run(s) still pending.`;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return `No unhandled current runs remain on bus ${busLabel}.`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function formatRunResult(run: AgentRun): string {
|
|
264
|
-
if (!run.result) return "No result payload recorded.";
|
|
265
|
-
return [`Result: ${run.result.status}`, run.result.summary].join("\n");
|
|
266
|
-
}
|
|
267
|
-
|
|
268
146
|
type RawBusParams = {
|
|
269
|
-
action: "create" | "status" | "publish"
|
|
147
|
+
action: "create" | "status" | "publish";
|
|
270
148
|
name?: string;
|
|
271
149
|
id?: string;
|
|
272
150
|
message?: string;
|
|
273
|
-
excludeRunIds?: string[];
|
|
274
|
-
timeoutMs?: number | null;
|
|
275
151
|
};
|
package/src/tools/workflow.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineTool, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import type { AgentResultStatus, AgentRun } from "../core/subagent.ts";
|
|
4
|
-
import type { OrchestraApi
|
|
3
|
+
import type { AgentResultStatus, AgentRun, AgentRunResult } from "../core/subagent.ts";
|
|
4
|
+
import type { OrchestraApi } from "../core/orchestra.ts";
|
|
5
5
|
import type { AgentStore } from "../core/store.ts";
|
|
6
6
|
import type { WorkflowRun, WorkflowStageOutput, WorkflowStageRun, WorkflowStageSpec } from "../core/workflow.ts";
|
|
7
7
|
import { WORKGROUP_STRATEGY_VALUES, type WorkgroupMember, type WorkgroupStrategy } from "../core/workgroup.ts";
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
isTerminalAgentState,
|
|
16
16
|
normalizeEntityName,
|
|
17
17
|
requireWorkflow,
|
|
18
|
-
resolveWaitTimeoutMs,
|
|
19
18
|
} from "../utils.ts";
|
|
20
19
|
import {
|
|
21
20
|
createWorkgroupTool,
|
|
@@ -41,17 +40,10 @@ export type WorkflowInput =
|
|
|
41
40
|
| {
|
|
42
41
|
action: "cancel";
|
|
43
42
|
id: string;
|
|
44
|
-
}
|
|
45
|
-
| {
|
|
46
|
-
action: "wait";
|
|
47
|
-
id: string;
|
|
48
|
-
/** Defaults to 10 minutes. Use null to wait indefinitely. */
|
|
49
|
-
timeoutMs?: number | null;
|
|
50
43
|
};
|
|
51
44
|
|
|
52
45
|
export interface WorkflowOutput {
|
|
53
46
|
workflow?: WorkflowRun;
|
|
54
|
-
timedOut?: boolean;
|
|
55
47
|
message: string;
|
|
56
48
|
}
|
|
57
49
|
|
|
@@ -92,8 +84,8 @@ const WorkflowStageParams = Type.Object(
|
|
|
92
84
|
);
|
|
93
85
|
|
|
94
86
|
const WorkflowActionParams = Type.String({
|
|
95
|
-
enum: ["start", "status", "cancel"
|
|
96
|
-
description: "start launches; status inspects; cancel closes active runs
|
|
87
|
+
enum: ["start", "status", "cancel"],
|
|
88
|
+
description: "start launches; status inspects progress or results; cancel closes active runs.",
|
|
97
89
|
});
|
|
98
90
|
|
|
99
91
|
const WorkflowToolParams = Type.Object(
|
|
@@ -106,7 +98,7 @@ const WorkflowToolParams = Type.Object(
|
|
|
106
98
|
),
|
|
107
99
|
id: Type.Optional(
|
|
108
100
|
Type.String({
|
|
109
|
-
description: "Required for status/cancel
|
|
101
|
+
description: "Required for status/cancel. Workflow id/name.",
|
|
110
102
|
}),
|
|
111
103
|
),
|
|
112
104
|
goal: Type.Optional(
|
|
@@ -120,19 +112,6 @@ const WorkflowToolParams = Type.Object(
|
|
|
120
112
|
minItems: 1,
|
|
121
113
|
}),
|
|
122
114
|
),
|
|
123
|
-
timeoutMs: Type.Optional(
|
|
124
|
-
Type.Union(
|
|
125
|
-
[
|
|
126
|
-
Type.Number({
|
|
127
|
-
exclusiveMinimum: 0,
|
|
128
|
-
}),
|
|
129
|
-
Type.Null(),
|
|
130
|
-
],
|
|
131
|
-
{
|
|
132
|
-
description: "Optional for action=wait. Positive ms; default 10 min; null waits indefinitely.",
|
|
133
|
-
},
|
|
134
|
-
),
|
|
135
|
-
),
|
|
136
115
|
},
|
|
137
116
|
{ additionalProperties: false },
|
|
138
117
|
);
|
|
@@ -161,24 +140,9 @@ export function createWorkflowTool({ orchestra, store }: WorkflowToolDeps): Work
|
|
|
161
140
|
}
|
|
162
141
|
|
|
163
142
|
const workflow = findWorkflow(store, input.id);
|
|
164
|
-
if (!workflow) {
|
|
165
|
-
return input.action === "wait"
|
|
166
|
-
? { timedOut: false, message: formatWorkflowNotFound(input.id) }
|
|
167
|
-
: { message: formatWorkflowNotFound(input.id) };
|
|
168
|
-
}
|
|
143
|
+
if (!workflow) return { message: formatWorkflowNotFound(input.id) };
|
|
169
144
|
|
|
170
|
-
if (input.action === "status") {
|
|
171
|
-
return { workflow, message: formatWorkflowMessage(workflow) };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (input.action === "wait") {
|
|
175
|
-
const result = await waitWorkflow(store, workflow.id, input.timeoutMs);
|
|
176
|
-
return {
|
|
177
|
-
workflow: result.workflow,
|
|
178
|
-
timedOut: result.timedOut,
|
|
179
|
-
message: formatWaitWorkflowMessage(result.workflow, result.timedOut, input.id),
|
|
180
|
-
};
|
|
181
|
-
}
|
|
145
|
+
if (input.action === "status") return { workflow, message: formatWorkflowMessage(workflow) };
|
|
182
146
|
|
|
183
147
|
const closedWorkflow = await closeWorkflow(orchestra, store, workflow);
|
|
184
148
|
return { workflow: closedWorkflow, message: formatWorkflowMessage(closedWorkflow, "Workflow cancelled.") };
|
|
@@ -194,12 +158,12 @@ export function defineWorkflowPiTool(
|
|
|
194
158
|
name: "workflow",
|
|
195
159
|
label: "Workflow",
|
|
196
160
|
description: "Run linear workgroup stages with automatic restricted stage leaders.",
|
|
197
|
-
promptSnippet: "Launch a multi-stage workflow
|
|
161
|
+
promptSnippet: "Launch a multi-stage workflow; completion is delivered automatically as a pi-orchestra event.",
|
|
198
162
|
promptGuidelines: [
|
|
199
163
|
"Use workflow for ordered multi-stage work; not branching/DAG plans.",
|
|
200
164
|
"Each stage gets its own bus and automatic leader; previous outputs feed the next stage.",
|
|
201
165
|
"Use compete when one worker success is enough; use synthesize when findings must be combined.",
|
|
202
|
-
"Use status for progress
|
|
166
|
+
"Use workflow status for progress; workflow.finished events deliver terminal success/blocked/failed/closed results.",
|
|
203
167
|
],
|
|
204
168
|
parameters: WorkflowToolParams,
|
|
205
169
|
executionMode: "sequential",
|
|
@@ -272,7 +236,13 @@ async function runStage(workflowId: string, stageIndex: number, deps: WorkflowRu
|
|
|
272
236
|
workerRunIds: workgroupOutput.runs.map((run) => run.id),
|
|
273
237
|
});
|
|
274
238
|
|
|
275
|
-
const settledWorkgroup = await settleWorkgroupRuns(
|
|
239
|
+
const settledWorkgroup = await settleWorkgroupRuns(
|
|
240
|
+
deps.orchestra,
|
|
241
|
+
deps.store,
|
|
242
|
+
bus.id,
|
|
243
|
+
workgroupOutput.runs.map((run) => run.id),
|
|
244
|
+
stage.strategy,
|
|
245
|
+
);
|
|
276
246
|
if (isWorkflowClosed(deps.store, workflowId)) return;
|
|
277
247
|
|
|
278
248
|
if (stage.strategy === "compete" && !settledWorkgroup.winner) {
|
|
@@ -288,7 +258,7 @@ async function runStageLeader(
|
|
|
288
258
|
workflowId: string,
|
|
289
259
|
stageIndex: number,
|
|
290
260
|
busId: string,
|
|
291
|
-
workerResults:
|
|
261
|
+
workerResults: AgentRunResult[],
|
|
292
262
|
deps: WorkflowRunnerDeps,
|
|
293
263
|
): Promise<void> {
|
|
294
264
|
const workflow = requireWorkflow(deps.store, workflowId);
|
|
@@ -310,10 +280,9 @@ async function runStageLeader(
|
|
|
310
280
|
leaderRunId: leaderRun.id,
|
|
311
281
|
});
|
|
312
282
|
|
|
313
|
-
const
|
|
283
|
+
const latestLeaderRun = await terminalRunEvent(deps.store, leaderRun.id);
|
|
314
284
|
if (isWorkflowClosed(deps.store, workflowId)) return;
|
|
315
285
|
|
|
316
|
-
const latestLeaderRun = leaderSettled.runs.find((run) => run.id === leaderRun.id) ?? leaderRun;
|
|
317
286
|
const output = buildStageOutput(latestLeaderRun, leaderRun.id, workerResults);
|
|
318
287
|
finishStage(deps.store, requireWorkflow(deps.store, workflowId), stageIndex, output);
|
|
319
288
|
}
|
|
@@ -391,7 +360,6 @@ function toWorkflowInput(params: RawWorkflowParams): WorkflowInput {
|
|
|
391
360
|
}
|
|
392
361
|
|
|
393
362
|
if (!params.id) throw new Error(`workflow action=${params.action} requires id.`);
|
|
394
|
-
if (params.action === "wait") return { action: "wait", id: params.id, timeoutMs: params.timeoutMs };
|
|
395
363
|
return { action: params.action, id: params.id };
|
|
396
364
|
}
|
|
397
365
|
|
|
@@ -499,7 +467,7 @@ function buildStageWorkerGoal(workflow: WorkflowRun, stageIndex: number): string
|
|
|
499
467
|
return parts.join("\n");
|
|
500
468
|
}
|
|
501
469
|
|
|
502
|
-
function buildLeaderTask(workflow: WorkflowRun, stageIndex: number, workerResults:
|
|
470
|
+
function buildLeaderTask(workflow: WorkflowRun, stageIndex: number, workerResults: AgentRunResult[]): string {
|
|
503
471
|
const stage = workflow.stages[stageIndex];
|
|
504
472
|
const strategyInstructions =
|
|
505
473
|
stage.strategy === "compete"
|
|
@@ -546,12 +514,12 @@ function formatPreviousStageOutput(stageName: string, output: WorkflowStageOutpu
|
|
|
546
514
|
return [`<stage_output name="${stageName}">`, formatStageOutputForPrompt(output), "</stage_output>"].join("\n");
|
|
547
515
|
}
|
|
548
516
|
|
|
549
|
-
function formatWorkerResults(workerResults:
|
|
517
|
+
function formatWorkerResults(workerResults: AgentRunResult[]): string {
|
|
550
518
|
if (workerResults.length === 0) return "None.";
|
|
551
519
|
return workerResults.map(formatWorkerResult).join("\n\n");
|
|
552
520
|
}
|
|
553
521
|
|
|
554
|
-
function formatWorkerResult(result:
|
|
522
|
+
function formatWorkerResult(result: AgentRunResult): string {
|
|
555
523
|
const lines = [
|
|
556
524
|
`<worker_result run_id="${result.runId}" name="${result.name}" profile="${result.profile}" state="${result.state}">`,
|
|
557
525
|
];
|
|
@@ -575,7 +543,7 @@ function formatJsonData(data: unknown): string {
|
|
|
575
543
|
return JSON.stringify(data, null, 2) ?? String(data);
|
|
576
544
|
}
|
|
577
545
|
|
|
578
|
-
function buildCompeteNoWinnerOutput(workerResults:
|
|
546
|
+
function buildCompeteNoWinnerOutput(workerResults: AgentRunResult[]): WorkflowStageOutput {
|
|
579
547
|
const status = workerResults.some((worker) => worker.result?.status === "blocked") ? "blocked" : "failed";
|
|
580
548
|
const counts = countWorkerResultStatuses(workerResults);
|
|
581
549
|
const workflowRunResults = workerResults.map(toWorkflowRunResult);
|
|
@@ -587,7 +555,7 @@ function buildCompeteNoWinnerOutput(workerResults: WaitRunResult[]): WorkflowSta
|
|
|
587
555
|
};
|
|
588
556
|
}
|
|
589
557
|
|
|
590
|
-
function countWorkerResultStatuses(workerResults:
|
|
558
|
+
function countWorkerResultStatuses(workerResults: AgentRunResult[]): Record<AgentResultStatus, number> {
|
|
591
559
|
const counts: Record<AgentResultStatus, number> = { success: 0, blocked: 0, failed: 0 };
|
|
592
560
|
for (const worker of workerResults) {
|
|
593
561
|
if (worker.result) counts[worker.result.status]++;
|
|
@@ -598,7 +566,7 @@ function countWorkerResultStatuses(workerResults: WaitRunResult[]): Record<Agent
|
|
|
598
566
|
function buildStageOutput(
|
|
599
567
|
leaderRun: AgentRun,
|
|
600
568
|
leaderRunId: string,
|
|
601
|
-
workerResults:
|
|
569
|
+
workerResults: AgentRunResult[],
|
|
602
570
|
): WorkflowStageOutput {
|
|
603
571
|
if (!leaderRun.result) {
|
|
604
572
|
return {
|
|
@@ -619,7 +587,7 @@ function buildStageOutput(
|
|
|
619
587
|
return output;
|
|
620
588
|
}
|
|
621
589
|
|
|
622
|
-
function toWorkflowRunResult(result:
|
|
590
|
+
function toWorkflowRunResult(result: AgentRunResult) {
|
|
623
591
|
const output = {
|
|
624
592
|
runId: result.runId,
|
|
625
593
|
name: result.name,
|
|
@@ -635,49 +603,21 @@ function formatStageOutput(output: WorkflowStageOutput): string {
|
|
|
635
603
|
return parts.join("\n");
|
|
636
604
|
}
|
|
637
605
|
|
|
638
|
-
function
|
|
639
|
-
store
|
|
640
|
-
|
|
641
|
-
timeoutMs: number | null | undefined,
|
|
642
|
-
): Promise<{ workflow: WorkflowRun; timedOut: boolean }> {
|
|
643
|
-
const resolvedTimeoutMs = resolveWaitTimeoutMs("workflow wait", timeoutMs);
|
|
644
|
-
const initialWorkflow = requireWorkflow(store, workflowId);
|
|
645
|
-
if (isTerminalAgentState(initialWorkflow.state)) {
|
|
646
|
-
return Promise.resolve({ workflow: initialWorkflow, timedOut: false });
|
|
647
|
-
}
|
|
606
|
+
function terminalRunEvent(store: AgentStore, runId: string): Promise<AgentRun> {
|
|
607
|
+
const initialRun = store.getRun(runId);
|
|
608
|
+
if (initialRun && isTerminalAgentState(initialRun.state)) return Promise.resolve(initialRun);
|
|
648
609
|
|
|
649
610
|
return new Promise((resolve) => {
|
|
650
|
-
let
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const cleanup = () => {
|
|
655
|
-
if (timeout) clearTimeout(timeout);
|
|
656
|
-
unsubscribe();
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
const unsubscribe = store.subscribeWorkflows(
|
|
660
|
-
(workflow) => {
|
|
661
|
-
if (settled) return;
|
|
662
|
-
latestWorkflow = workflow;
|
|
663
|
-
if (!isTerminalAgentState(workflow.state)) return;
|
|
611
|
+
let unsubscribe: () => void = () => undefined;
|
|
612
|
+
unsubscribe = store.subscribeRuns(
|
|
613
|
+
(run) => {
|
|
614
|
+
if (!isTerminalAgentState(run.state)) return;
|
|
664
615
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
resolve({ workflow, timedOut: false });
|
|
616
|
+
unsubscribe();
|
|
617
|
+
resolve(run);
|
|
668
618
|
},
|
|
669
|
-
(
|
|
619
|
+
(run) => run.id === runId,
|
|
670
620
|
);
|
|
671
|
-
|
|
672
|
-
if (resolvedTimeoutMs !== null) {
|
|
673
|
-
timeout = setTimeout(() => {
|
|
674
|
-
if (settled) return;
|
|
675
|
-
|
|
676
|
-
settled = true;
|
|
677
|
-
cleanup();
|
|
678
|
-
resolve({ workflow: latestWorkflow, timedOut: true });
|
|
679
|
-
}, resolvedTimeoutMs);
|
|
680
|
-
}
|
|
681
621
|
});
|
|
682
622
|
}
|
|
683
623
|
|
|
@@ -685,14 +625,6 @@ function formatWorkflowNotFound(id: string): string {
|
|
|
685
625
|
return `Workflow ${id} not found.`;
|
|
686
626
|
}
|
|
687
627
|
|
|
688
|
-
function formatWaitWorkflowMessage(workflow: WorkflowRun | undefined, timedOut: boolean, requestedId?: string): string {
|
|
689
|
-
if (!workflow) return formatWorkflowNotFound(requestedId ?? "");
|
|
690
|
-
const label = requestedId === workflow.id ? workflow.id : formatNamedEntityLabel(workflow);
|
|
691
|
-
const prefix = timedOut ? "Timed out waiting for" : "Workflow reached terminal state:";
|
|
692
|
-
const result = workflow.result ? ` result=${workflow.result.status}` : "";
|
|
693
|
-
return `${prefix} ${label}; state=${workflow.state}${result}.`;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
628
|
function formatWorkflowMessage(
|
|
697
629
|
workflow: WorkflowRun,
|
|
698
630
|
headline = `Workflow ${formatNamedEntityLabel(workflow)} is ${workflow.state}.`,
|
|
@@ -708,12 +640,11 @@ function formatWorkflowMessage(
|
|
|
708
640
|
}
|
|
709
641
|
|
|
710
642
|
type RawWorkflowParams = {
|
|
711
|
-
action: "start" | "status" | "cancel"
|
|
643
|
+
action: "start" | "status" | "cancel";
|
|
712
644
|
name?: string;
|
|
713
645
|
id?: string;
|
|
714
646
|
goal?: string;
|
|
715
647
|
stages?: RawWorkflowStageParams[];
|
|
716
|
-
timeoutMs?: number | null;
|
|
717
648
|
};
|
|
718
649
|
|
|
719
650
|
type RawWorkflowStageParams = {
|