@g3un/pi-orchestra 0.2.1 → 0.9.2

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/src/tools/bus.ts CHANGED
@@ -1,28 +1,43 @@
1
1
  import { defineTool, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
- import type { Bus, BusMessage } from "../core/bus.ts";
3
+ import {
4
+ createBusSubscription,
5
+ createBusSubscriptionId,
6
+ type Bus,
7
+ type BusMessage,
8
+ type BusSubscription,
9
+ } from "../core/bus.ts";
4
10
  import type { OrchestraApi } from "../core/orchestra.ts";
5
- import { formatNamedEntityLabel } from "../utils.ts";
11
+ import type { AgentStore } from "../core/store.ts";
6
12
 
7
13
  export type BusInput =
8
14
  | {
9
15
  action: "create";
10
- name?: string;
16
+ name: string | undefined;
11
17
  }
12
18
  | {
13
19
  action: "status";
14
- id: string;
20
+ name: string;
15
21
  }
16
22
  | {
17
23
  action: "publish";
18
- id: string;
24
+ name: string;
19
25
  message: string;
20
- from?: string;
26
+ from: string;
27
+ }
28
+ | {
29
+ action: "subscribe";
30
+ name: string;
31
+ }
32
+ | {
33
+ action: "unsubscribe";
34
+ name: string;
21
35
  };
22
36
 
23
37
  export interface BusOutput {
24
38
  bus?: Bus;
25
39
  busMessage?: BusMessage;
40
+ subscription?: BusSubscription;
26
41
  message: string;
27
42
  }
28
43
 
@@ -33,11 +48,13 @@ export interface BusTool {
33
48
 
34
49
  export interface BusToolDeps {
35
50
  orchestra: OrchestraApi;
51
+ store: AgentStore;
36
52
  }
37
53
 
38
54
  const BusActionParams = Type.String({
39
- enum: ["create", "status", "publish"],
40
- description: "create/status/publish shared context buses; completion is delivered through pi-orchestra events.",
55
+ enum: ["create", "status", "publish", "subscribe", "unsubscribe"],
56
+ description:
57
+ "create/status/publish/subscribe/unsubscribe shared context buses; completion is delivered through pi-orchestra events.",
41
58
  });
42
59
 
43
60
  const BusToolParams = Type.Object(
@@ -45,45 +62,52 @@ const BusToolParams = Type.Object(
45
62
  action: BusActionParams,
46
63
  name: Type.Optional(
47
64
  Type.String({
48
- description: "Optional short bus name for action=create.",
49
- }),
50
- ),
51
- id: Type.Optional(
52
- Type.String({
53
- description: "Required except create. Bus id/name returned by action=create.",
65
+ description:
66
+ "Optional for action=create; required for action=status/publish. Use the short bus name, not a bus id.",
54
67
  }),
55
68
  ),
56
69
  message: Type.Optional(
57
70
  Type.String({
58
- description: "Required for action=publish. Shared context for attached agents.",
71
+ description: "Required for action=publish. Shared context for subscribed agents.",
59
72
  }),
60
73
  ),
61
74
  },
62
75
  { additionalProperties: false },
63
76
  );
64
77
 
65
- export function createBusTool({ orchestra }: BusToolDeps): BusTool {
78
+ export function createBusTool({ orchestra, store }: BusToolDeps): BusTool {
66
79
  return {
67
80
  name: "bus",
68
81
 
69
82
  async execute(input) {
70
83
  if (input.action === "create") {
71
84
  const bus = orchestra.createBus({ name: input.name });
72
- return { bus, message: formatBusStatus(bus, `Created bus ${formatNamedEntityLabel(bus)}.`) };
85
+ return { bus, message: formatBusStatus(bus, store, `Created bus ${formatBusLabel(bus)}.`) };
73
86
  }
74
87
 
75
- const bus = orchestra.getBus(input.id);
76
- if (!bus) return { message: formatBusNotFound(input.id) };
88
+ const bus = orchestra.getBus(input.name);
89
+ if (!bus) return { message: formatBusNotFound(input.name) };
77
90
 
78
91
  if (input.action === "status") {
79
- return { bus, message: formatBusStatus(bus) };
92
+ return { bus, message: formatBusStatus(bus, store) };
93
+ }
94
+
95
+ if (input.action === "subscribe") {
96
+ const subscription = subscribeMainToBus(store, bus);
97
+ return { bus, subscription, message: `Subscribed main to bus ${formatBusLabel(bus)} for new messages.` };
98
+ }
99
+
100
+ if (input.action === "unsubscribe") {
101
+ const subscriptionId = createBusSubscriptionId(bus.id, "main", "main");
102
+ store.deleteBusSubscription(subscriptionId);
103
+ return { bus, message: `Unsubscribed main from bus ${formatBusLabel(bus)}.` };
80
104
  }
81
105
 
82
- const published = await orchestra.publishBus(input.id, input.message, input.from ?? "main");
106
+ const published = await orchestra.publishBus(input.name, input.message, input.from ?? "main");
83
107
  return {
84
108
  bus: published.bus,
85
109
  busMessage: published.busMessage,
86
- message: formatBusStatus(published.bus, `Published message to bus ${formatNamedEntityLabel(published.bus)}.`),
110
+ message: formatBusStatus(published.bus, store, `Published message to bus ${formatBusLabel(published.bus)}.`),
87
111
  };
88
112
  },
89
113
  };
@@ -97,7 +121,8 @@ export function defineBusPiTool(resolveTool: (ctx: ExtensionContext) => BusTool)
97
121
  promptSnippet: "Use one bus per delegated work item; spawn related subagents or workgroups on it.",
98
122
  promptGuidelines: [
99
123
  "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.",
124
+ "Use bus publish with the bus name, not a bus id, to send shared context to subscribed agents; bus status shows published messages.",
125
+ "Use bus subscribe when main needs live bus messages; it starts from new messages after subscription. Unsubscribe when tracking is no longer useful.",
101
126
  "Do not wait on buses; pi-orchestra delivers subagent and workgroup finish events automatically.",
102
127
  ],
103
128
  parameters: BusToolParams,
@@ -117,35 +142,68 @@ function toBusInput(params: RawBusParams): BusInput {
117
142
  if (params.action === "create") return { action: "create", name: params.name };
118
143
 
119
144
  if (params.action === "status") {
120
- if (!params.id) throw new Error("bus action=status requires id.");
121
- return { action: "status", id: params.id };
145
+ if (!params.name) throw new Error("bus action=status requires name.");
146
+ return { action: "status", name: params.name };
122
147
  }
123
148
 
124
- if (!params.id) throw new Error("bus action=publish requires id.");
149
+ if (params.action === "subscribe") {
150
+ if (!params.name) throw new Error("bus action=subscribe requires name.");
151
+ return { action: "subscribe", name: params.name };
152
+ }
153
+
154
+ if (params.action === "unsubscribe") {
155
+ if (!params.name) throw new Error("bus action=unsubscribe requires name.");
156
+ return { action: "unsubscribe", name: params.name };
157
+ }
158
+
159
+ if (!params.name) throw new Error("bus action=publish requires name.");
125
160
  if (!params.message) throw new Error("bus action=publish requires message.");
126
- return { action: "publish", id: params.id, message: params.message };
161
+ return { action: "publish", name: params.name, message: params.message, from: "main" };
162
+ }
163
+
164
+ function subscribeMainToBus(store: AgentStore, bus: Bus): BusSubscription {
165
+ const id = createBusSubscriptionId(bus.id, "main", "main");
166
+ const subscription =
167
+ store.getBusSubscription(id) ??
168
+ createBusSubscription({
169
+ busId: bus.id,
170
+ subscriberId: "main",
171
+ subscriberKind: "main",
172
+ deliveredMessageIds: bus.messages.map((message) => message.id),
173
+ });
174
+ store.saveBusSubscription(subscription);
175
+ return subscription;
127
176
  }
128
177
 
129
- function formatBusNotFound(id: string): string {
130
- return `Bus ${id} not found.`;
178
+ function formatBusNotFound(name: string): string {
179
+ return `Bus ${name} not found.`;
131
180
  }
132
181
 
133
182
  function formatBusStatus(
134
183
  bus: Bus,
135
- headline = `Bus ${formatNamedEntityLabel(bus)} has ${bus.messages.length} message(s).`,
184
+ store: AgentStore,
185
+ headline = `Bus ${formatBusLabel(bus)} has ${bus.messages.length} message(s).`,
136
186
  ): string {
137
187
  if (bus.messages.length === 0) return headline;
138
188
 
139
- return [headline, "", "Messages:", ...bus.messages.map(formatBusMessage)].join("\n");
189
+ return [headline, "", "Messages:", ...bus.messages.map((message) => formatBusMessage(message, store))].join("\n");
190
+ }
191
+
192
+ function formatBusMessage(message: BusMessage, store: AgentStore): string {
193
+ return [`- ${message.id} from ${formatBusMessageFrom(message.from, store)}:`, message.message].join("\n");
194
+ }
195
+
196
+ function formatBusMessageFrom(from: string, store: AgentStore): string {
197
+ if (from === "main") return from;
198
+ return store.getRun(from)?.name ?? from;
140
199
  }
141
200
 
142
- function formatBusMessage(message: BusMessage): string {
143
- return [`- ${message.id} from ${message.from}:`, message.message].join("\n");
201
+ function formatBusLabel(bus: Bus): string {
202
+ return bus.name;
144
203
  }
145
204
 
146
205
  type RawBusParams = {
147
- action: "create" | "status" | "publish";
206
+ action: "create" | "status" | "publish" | "subscribe" | "unsubscribe";
148
207
  name?: string;
149
- id?: string;
150
208
  message?: string;
151
209
  };
@@ -9,7 +9,7 @@ export interface SubagentSpawnInput {
9
9
  profile: AgentProfile;
10
10
  task: string;
11
11
  busId: string;
12
- name?: string;
12
+ name: string | undefined;
13
13
  }
14
14
 
15
15
  export type SubagentInput =
@@ -17,18 +17,18 @@ export type SubagentInput =
17
17
  | {
18
18
  action: "status";
19
19
  id: string;
20
- busId?: string;
20
+ busId: string | undefined;
21
21
  }
22
22
  | {
23
23
  action: "message";
24
24
  id: string;
25
25
  message: string;
26
- busId?: string;
26
+ busId: string | undefined;
27
27
  }
28
28
  | {
29
29
  action: "close";
30
30
  id: string;
31
- busId?: string;
31
+ busId: string | undefined;
32
32
  };
33
33
 
34
34
  export interface SubagentOutput {
@@ -53,7 +53,10 @@ export const AgentProfileParams = Type.Object(
53
53
  {
54
54
  name: Type.String({ description: "Short role/name for the subagent." }),
55
55
  systemPrompt: Type.String({ description: "System prompt for the subagent." }),
56
- tools: Type.Optional(Type.Array(Type.String(), { description: "Optional tool allowlist for the subagent." })),
56
+ tools: Type.Array(Type.String(), {
57
+ description:
58
+ "Explicit tool allowlist for the subagent. Use [] for no work tools; finish and publish_bus are added automatically.",
59
+ }),
57
60
  model: Type.Optional(
58
61
  Type.String({
59
62
  description: "Optional provider/model id.",
@@ -143,7 +146,7 @@ export function defineSubagentPiTool(resolveTool: (ctx: ExtensionContext) => Sub
143
146
  description: "Create and manage isolated subagents.",
144
147
  promptSnippet: "Spawn a subagent on an existing bus, then status/message/close it later.",
145
148
  promptGuidelines: [
146
- "Create a bus first; spawn attaches the subagent via busId.",
149
+ "Create a bus first; spawn subscribes the subagent to busId.",
147
150
  "Attach cooperating subagents to the same bus.",
148
151
  "Use returned run id/name for status, message, or close.",
149
152
  ],
@@ -166,7 +169,13 @@ function toSubagentInput(params: RawSubagentParams): SubagentInput {
166
169
  if (!params.profile) throw new Error("subagent action=spawn requires profile.");
167
170
  if (!params.task) throw new Error("subagent action=spawn requires task.");
168
171
  if (!params.busId) throw new Error("subagent action=spawn requires busId.");
169
- return { action: "spawn", profile: params.profile, task: params.task, busId: params.busId, name: params.name };
172
+ return {
173
+ action: "spawn",
174
+ profile: toAgentProfile(params.profile),
175
+ task: params.task,
176
+ busId: params.busId,
177
+ name: params.name,
178
+ };
170
179
  }
171
180
 
172
181
  if (params.action === "status") {
@@ -192,6 +201,16 @@ function withDefaultModel(input: SubagentInput, ctx: ExtensionContext): Subagent
192
201
  };
193
202
  }
194
203
 
204
+ export function toAgentProfile(profile: RawAgentProfileParams): AgentProfile {
205
+ if (!Array.isArray(profile.tools)) throw new Error(`Profile "${profile.name}" requires tools.`);
206
+ return {
207
+ name: profile.name,
208
+ systemPrompt: profile.systemPrompt,
209
+ tools: profile.tools,
210
+ model: profile.model,
211
+ };
212
+ }
213
+
195
214
  export function withDefaultProfileModel(profile: AgentProfile, ctx: ExtensionContext): AgentProfile {
196
215
  if (profile.model || !ctx.model) return profile;
197
216
  return {
@@ -232,7 +251,7 @@ function formatModelId(model: AgentProfileModel): string {
232
251
 
233
252
  type RawSubagentParams = {
234
253
  action: "spawn" | "status" | "message" | "close";
235
- profile?: AgentProfile;
254
+ profile?: RawAgentProfileParams;
236
255
  task?: string;
237
256
  busId?: string;
238
257
  name?: string;
@@ -240,4 +259,6 @@ type RawSubagentParams = {
240
259
  message?: string;
241
260
  };
242
261
 
262
+ export type RawAgentProfileParams = Omit<AgentProfile, "model"> & Partial<Pick<AgentProfile, "model">>;
263
+
243
264
  type AgentProfileModel = NonNullable<ExtensionContext["model"]>;
@@ -5,13 +5,13 @@ 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";
8
- import { createStageLeaderProfile } from "../profiles/stage-leader.ts";
9
8
  import {
10
9
  closeAgentRuns,
11
10
  createEntityIdentity,
12
11
  formatError,
13
12
  findWorkflow,
14
13
  formatNamedEntityLabel,
14
+ isAgentRunFinished,
15
15
  isTerminalAgentState,
16
16
  normalizeEntityName,
17
17
  requireWorkflow,
@@ -29,7 +29,7 @@ import {
29
29
  export type WorkflowInput =
30
30
  | {
31
31
  action: "start";
32
- name?: string;
32
+ name: string | undefined;
33
33
  goal: string;
34
34
  stages: WorkflowStageSpec[];
35
35
  }
@@ -58,8 +58,8 @@ export interface WorkflowToolDeps {
58
58
  }
59
59
 
60
60
  export interface WorkflowPiToolOptions {
61
- onWorkflowInput?: (ctx: ExtensionContext, input: WorkflowInput) => void;
62
- onWorkflowOutput?: (ctx: ExtensionContext, output: WorkflowOutput) => void;
61
+ onWorkflowInput: ((ctx: ExtensionContext, input: WorkflowInput) => void) | undefined;
62
+ onWorkflowOutput: ((ctx: ExtensionContext, output: WorkflowOutput) => void) | undefined;
63
63
  }
64
64
 
65
65
  const WorkflowStageParams = Type.Object(
@@ -78,7 +78,7 @@ const WorkflowStageParams = Type.Object(
78
78
  description: "Worker subagents for this stage.",
79
79
  minItems: 1,
80
80
  }),
81
- leader: Type.Optional(WorkgroupMemberParams),
81
+ leader: WorkgroupMemberParams,
82
82
  },
83
83
  { additionalProperties: false },
84
84
  );
@@ -108,7 +108,8 @@ const WorkflowToolParams = Type.Object(
108
108
  ),
109
109
  stages: Type.Optional(
110
110
  Type.Array(WorkflowStageParams, {
111
- description: "Required for action=start. Linear stages executed in order with automatic stage leaders.",
111
+ description:
112
+ "Required for action=start. Linear stages executed in order; each stage specifies an explicit leader.",
112
113
  minItems: 1,
113
114
  }),
114
115
  ),
@@ -117,7 +118,12 @@ const WorkflowToolParams = Type.Object(
117
118
  );
118
119
 
119
120
  export function createWorkflowTool({ orchestra, store }: WorkflowToolDeps): WorkflowTool {
120
- const workgroupTool = createWorkgroupTool({ orchestra });
121
+ const workgroupTool = createWorkgroupTool({
122
+ orchestra,
123
+ onWorkgroupLaunching: undefined,
124
+ onWorkgroupLaunched: undefined,
125
+ onWorkgroupLaunchFailed: undefined,
126
+ });
121
127
  const runnerTasks = new Map<string, Promise<void>>();
122
128
 
123
129
  const startRunner = (workflowId: string) => {
@@ -152,16 +158,17 @@ export function createWorkflowTool({ orchestra, store }: WorkflowToolDeps): Work
152
158
 
153
159
  export function defineWorkflowPiTool(
154
160
  resolveTool: (ctx: ExtensionContext) => WorkflowTool,
155
- options: WorkflowPiToolOptions = {},
161
+ options: WorkflowPiToolOptions = { onWorkflowInput: undefined, onWorkflowOutput: undefined },
156
162
  ) {
157
163
  return defineTool({
158
164
  name: "workflow",
159
165
  label: "Workflow",
160
- description: "Run linear workgroup stages with automatic restricted stage leaders.",
166
+ description: "Run linear workgroup stages, each with an explicit leader that synthesizes its workers' output.",
161
167
  promptSnippet: "Launch a multi-stage workflow; completion is delivered automatically as a pi-orchestra event.",
162
168
  promptGuidelines: [
163
169
  "Use workflow for ordered multi-stage work; not branching/DAG plans.",
164
- "Each stage gets its own bus and automatic leader; previous outputs feed the next stage.",
170
+ "Each stage gets its own bus and requires an explicit leader that synthesizes its workers' output; previous stage outputs feed the next stage.",
171
+ "Prefer the evidence-synthesizer profile for stage leaders unless the stage needs a specialized synthesizer; give the leader an explicit tool allowlist (often the union of its workers' tools).",
165
172
  "Use compete when one worker success is enough; use synthesize when findings must be combined.",
166
173
  "Use workflow status for progress; workflow.finished events deliver terminal success/blocked/failed/closed results.",
167
174
  ],
@@ -195,7 +202,7 @@ async function runWorkflow(workflowId: string, deps: WorkflowRunnerDeps): Promis
195
202
 
196
203
  const stageIndex = workflow.stages.findIndex((stage) => stage.state === "idle" && !stage.phase);
197
204
  if (stageIndex < 0) {
198
- if (workflow.state === "idle")
205
+ if (workflow.state === "running")
199
206
  deps.store.saveWorkflow({
200
207
  ...workflow,
201
208
  state: "success",
@@ -216,7 +223,12 @@ async function runStage(workflowId: string, stageIndex: number, deps: WorkflowRu
216
223
  const workflow = requireWorkflow(deps.store, workflowId);
217
224
  const stage = workflow.stages[stageIndex];
218
225
  const bus = deps.orchestra.createBus({ name: `${workflow.name}-${stage.name}` });
219
- updateStage(deps.store, workflow, stageIndex, { state: "idle", phase: "workers", busId: bus.id });
226
+ updateStage(deps.store, workflow, stageIndex, {
227
+ state: "running",
228
+ phase: "workers",
229
+ startedAtMs: Date.now(),
230
+ busId: bus.id,
231
+ });
220
232
 
221
233
  const workgroupOutput = await deps.workgroupTool.execute({
222
234
  busId: bus.id,
@@ -251,10 +263,10 @@ async function runStage(workflowId: string, stageIndex: number, deps: WorkflowRu
251
263
  return;
252
264
  }
253
265
 
254
- await runStageLeader(workflowId, stageIndex, bus.id, settledWorkgroup.workerResults, deps);
266
+ await runEvidenceSynthesizer(workflowId, stageIndex, bus.id, settledWorkgroup.workerResults, deps);
255
267
  }
256
268
 
257
- async function runStageLeader(
269
+ async function runEvidenceSynthesizer(
258
270
  workflowId: string,
259
271
  stageIndex: number,
260
272
  busId: string,
@@ -270,12 +282,12 @@ async function runStageLeader(
270
282
  { name: stage.leader.name },
271
283
  );
272
284
  if (isWorkflowClosed(deps.store, workflowId)) {
273
- await deps.orchestra.closeAgent(leaderRun.id);
285
+ await deps.orchestra.closeAgent(leaderRun.id, { busId: undefined });
274
286
  return;
275
287
  }
276
288
 
277
289
  updateStage(deps.store, requireWorkflow(deps.store, workflowId), stageIndex, {
278
- state: "idle",
290
+ state: "running",
279
291
  phase: "leader",
280
292
  leaderRunId: leaderRun.id,
281
293
  });
@@ -309,21 +321,20 @@ function createWorkflowRun(
309
321
  ): WorkflowRun {
310
322
  validateStages(input.stages);
311
323
  const identity = createEntityIdentity(input.name, "workflow", existingWorkflows, "Workflow");
324
+ const startedAtMs = Date.now();
312
325
  return {
313
326
  ...identity,
314
327
  goal: input.goal,
315
- startedAtMs: Date.now(),
316
- state: "idle",
328
+ startedAtMs,
329
+ state: "running",
317
330
  currentStageIndex: 0,
318
- stages: input.stages.map((stage) => {
319
- const stageRun = {
320
- ...stage,
321
- name: normalizeEntityName(stage.name, "Workflow stage"),
322
- state: "idle" as const,
323
- workerRunIds: [],
324
- };
325
- return { ...stageRun, leader: resolveStageLeader(stage, identity.name) };
326
- }),
331
+ stages: input.stages.map((stage) => ({
332
+ ...stage,
333
+ name: normalizeEntityName(stage.name, "Workflow stage"),
334
+ state: "idle" as const,
335
+ startedAtMs,
336
+ workerRunIds: [],
337
+ })),
327
338
  };
328
339
  }
329
340
 
@@ -339,20 +350,6 @@ function validateStages(stages: WorkflowStageSpec[]): void {
339
350
  }
340
351
  }
341
352
 
342
- function resolveStageLeader(stage: WorkflowStageSpec, workflowName: string): WorkgroupMember {
343
- const stageName = normalizeEntityName(stage.name, "Workflow stage");
344
- const leader = stage.leader ?? {
345
- profile: createStageLeaderProfile({ name: `${workflowName}-${stageName}-leader` }),
346
- };
347
- return {
348
- ...leader,
349
- profile: {
350
- ...leader.profile,
351
- tools: [],
352
- },
353
- };
354
- }
355
-
356
353
  function toWorkflowInput(params: RawWorkflowParams): WorkflowInput {
357
354
  if (params.action === "start") {
358
355
  if (!params.goal) throw new Error("workflow action=start requires goal.");
@@ -369,15 +366,15 @@ function toWorkflowStageSpec(stage: RawWorkflowStageParams): WorkflowStageSpec {
369
366
  if (!stage.goal) throw new Error(`workflow stage ${stage.name} requires goal.`);
370
367
  if (!stage.strategy) throw new Error(`workflow stage ${stage.name} requires strategy.`);
371
368
  if (!stage.members || stage.members.length === 0) throw new Error(`workflow stage ${stage.name} requires members.`);
369
+ if (!stage.leader) throw new Error(`workflow stage ${stage.name} requires a leader.`);
372
370
 
373
- const spec: WorkflowStageSpec = {
371
+ return {
374
372
  name: stage.name,
375
373
  goal: stage.goal,
376
374
  strategy: stage.strategy,
377
375
  members: stage.members.map((member, index) => toWorkgroupMember(member, `workflow member ${index + 1}`)),
376
+ leader: toWorkgroupMember(stage.leader, "workflow leader"),
378
377
  };
379
- if (stage.leader) spec.leader = toWorkgroupMember(stage.leader, "workflow leader");
380
- return spec;
381
378
  }
382
379
 
383
380
  function withDefaultModels(input: WorkflowInput, ctx: ExtensionContext): WorkflowInput {
@@ -387,7 +384,7 @@ function withDefaultModels(input: WorkflowInput, ctx: ExtensionContext): Workflo
387
384
  stages: input.stages.map((stage) => ({
388
385
  ...stage,
389
386
  members: withDefaultModelsForWorkgroupMembers(stage.members, ctx),
390
- leader: stage.leader ? withDefaultModelForWorkgroupMember(stage.leader, ctx) : undefined,
387
+ leader: withDefaultModelForWorkgroupMember(stage.leader, ctx),
391
388
  })),
392
389
  };
393
390
  }
@@ -478,7 +475,7 @@ function buildLeaderTask(workflow: WorkflowRun, stageIndex: number, workerResult
478
475
  return [
479
476
  "You are the leader for this workflow stage.",
480
477
  ...strategyInstructions,
481
- "Use supplied context only; prefer finish results over bus context.",
478
+ "Treat supplied context as primary; prefer finish results over bus context.",
482
479
  "",
483
480
  "Workflow goal:",
484
481
  workflow.goal,
@@ -572,7 +569,7 @@ function buildStageOutput(
572
569
  if (!leaderRun.result) {
573
570
  return {
574
571
  status: "failed",
575
- summary: `Stage leader ${leaderRunId} reached ${leaderRun.state} without a result payload.`,
572
+ summary: `Evidence synthesizer ${leaderRunId} reached ${leaderRun.state} without a result payload.`,
576
573
  leaderRunId,
577
574
  workerResults: workerResults.map(toWorkflowRunResult),
578
575
  };
@@ -606,13 +603,13 @@ function formatStageOutput(output: WorkflowStageOutput): string {
606
603
 
607
604
  function terminalRunEvent(store: AgentStore, runId: string): Promise<AgentRun> {
608
605
  const initialRun = store.getRun(runId);
609
- if (initialRun && isTerminalAgentState(initialRun.state)) return Promise.resolve(initialRun);
606
+ if (initialRun && isAgentRunFinished(initialRun)) return Promise.resolve(initialRun);
610
607
 
611
608
  return new Promise((resolve) => {
612
609
  let unsubscribe: () => void = () => undefined;
613
610
  unsubscribe = store.subscribeRuns(
614
611
  (run) => {
615
- if (!isTerminalAgentState(run.state)) return;
612
+ if (!isAgentRunFinished(run)) return;
616
613
 
617
614
  unsubscribe();
618
615
  resolve(run);
@@ -632,7 +629,7 @@ function formatWorkflowMessage(
632
629
  ): string {
633
630
  const parts = [headline, "", `Goal: ${workflow.goal}`, "", "Stages:"];
634
631
  for (const [index, stage] of workflow.stages.entries()) {
635
- const current = index === workflow.currentStageIndex && workflow.state === "idle" ? " current" : "";
632
+ const current = index === workflow.currentStageIndex && workflow.state === "running" ? " current" : "";
636
633
  parts.push(`- ${stage.name}: ${stage.state}${current}${stage.busId ? ` bus=${stage.busId}` : ""}`);
637
634
  }
638
635
  if (workflow.result) parts.push("", "Result:", formatStageOutput(workflow.result));