@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/README.md +14 -3
- package/docs/orchestration-model.md +17 -12
- package/package.json +5 -1
- package/skills/pi-orchestra/SKILL.md +92 -0
- package/src/adapters/in-memory-store.ts +45 -20
- package/src/adapters/index.ts +1 -0
- package/src/adapters/pi-runtime.ts +114 -38
- package/src/adapters/sqlite-store.ts +276 -0
- package/src/adapters/store-subscriptions.ts +20 -0
- package/src/core/bus-format.ts +13 -4
- package/src/core/bus.ts +66 -0
- package/src/core/orchestra.ts +21 -26
- package/src/core/store.ts +12 -3
- package/src/core/subagent.ts +5 -3
- package/src/core/workflow.ts +5 -4
- package/src/core/workgroup.ts +3 -3
- package/src/extension/index.ts +13 -5
- package/src/extension/orchestra-events.ts +107 -24
- package/src/extension/workflow-monitor.ts +16 -14
- package/src/profiles/code-reviewer.ts +20 -0
- package/src/profiles/evidence-synthesizer.ts +20 -0
- package/src/profiles/external-researcher.ts +20 -0
- package/src/profiles/index.ts +5 -1
- package/src/profiles/profile.ts +31 -0
- package/src/profiles/source-code-qa.ts +19 -0
- package/src/tools/bus.ts +93 -35
- package/src/tools/subagent.ts +29 -8
- package/src/tools/workflow.ts +47 -50
- package/src/tools/workgroup.ts +22 -14
- package/src/utils.ts +12 -2
- package/src/profiles/stage-leader.ts +0 -20
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { isBusMessageDelivered, markBusMessagesDelivered, type BusMessage } from "../core/bus.ts";
|
|
1
2
|
import type { AgentRun, AgentRunResult, AgentState } from "../core/subagent.ts";
|
|
2
3
|
import type { AgentStore } from "../core/store.ts";
|
|
3
4
|
import type { WorkflowRun } from "../core/workflow.ts";
|
|
4
5
|
import type { WorkgroupStrategy } from "../core/workgroup.ts";
|
|
5
|
-
import { formatNamedEntityLabel, isTerminalAgentState, toAgentRunResult } from "../utils.ts";
|
|
6
|
+
import { formatNamedEntityLabel, isAgentRunActive, isTerminalAgentState, toAgentRunResult } from "../utils.ts";
|
|
6
7
|
|
|
7
8
|
export const ORCHESTRA_EVENT_CUSTOM_TYPE = "pi-orchestra.event";
|
|
8
9
|
|
|
@@ -22,6 +23,11 @@ export type OrchestraMainEvent =
|
|
|
22
23
|
| {
|
|
23
24
|
type: "workflow.finished";
|
|
24
25
|
workflow: WorkflowRun;
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
type: "bus.message";
|
|
29
|
+
busId: string;
|
|
30
|
+
message: BusMessage;
|
|
25
31
|
};
|
|
26
32
|
|
|
27
33
|
export interface WorkgroupRegistration {
|
|
@@ -33,8 +39,8 @@ export interface WorkgroupRegistration {
|
|
|
33
39
|
export interface OrchestraEventControllerOptions {
|
|
34
40
|
store: AgentStore;
|
|
35
41
|
sendEvents: (events: OrchestraMainEvent[], content: string) => void;
|
|
36
|
-
/** Defaults to 50 ms. Use 0 in tests for immediate delivery. */
|
|
37
|
-
flushDelayMs
|
|
42
|
+
/** Defaults to 50 ms when undefined. Use 0 in tests for immediate delivery. */
|
|
43
|
+
flushDelayMs: number | undefined;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
interface RegisteredWorkgroup {
|
|
@@ -47,16 +53,27 @@ interface LaunchingWorkgroup {
|
|
|
47
53
|
finishedRunIds: Set<string>;
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
interface BusMessageDelivery {
|
|
57
|
+
subscriptionId: string;
|
|
58
|
+
message: BusMessage;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface FormatOrchestraEventsOptions {
|
|
62
|
+
formatBusMessageFrom: ((from: string) => string) | undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
export class OrchestraEventController {
|
|
51
66
|
private readonly store: AgentStore;
|
|
52
67
|
private readonly sendEvents: OrchestraEventControllerOptions["sendEvents"];
|
|
53
68
|
private readonly flushDelayMs: number;
|
|
54
|
-
private readonly
|
|
69
|
+
private readonly runFinished = new Map<string, boolean>();
|
|
55
70
|
private readonly workflowStates = new Map<string, AgentState>();
|
|
56
71
|
private readonly mainWorkgroupsByBusId = new Map<string, RegisteredWorkgroup>();
|
|
57
72
|
private readonly launchingWorkgroupsByBusId = new Map<string, LaunchingWorkgroup>();
|
|
58
73
|
private readonly queuedEvents: OrchestraMainEvent[] = [];
|
|
74
|
+
private readonly queuedBusMessageDeliveries: BusMessageDelivery[] = [];
|
|
59
75
|
private readonly unsubscribeRuns: () => void;
|
|
76
|
+
private readonly unsubscribeBusMessages: () => void;
|
|
60
77
|
private readonly unsubscribeWorkflows: () => void;
|
|
61
78
|
private flushTimer: ReturnType<typeof setTimeout> | undefined;
|
|
62
79
|
|
|
@@ -65,11 +82,18 @@ export class OrchestraEventController {
|
|
|
65
82
|
this.sendEvents = options.sendEvents;
|
|
66
83
|
this.flushDelayMs = options.flushDelayMs ?? 50;
|
|
67
84
|
|
|
68
|
-
for (const run of this.store.listRuns()) this.
|
|
85
|
+
for (const run of this.store.listRuns()) this.runFinished.set(run.id, isAgentFinishRun(run));
|
|
69
86
|
for (const workflow of this.store.listWorkflows()) this.workflowStates.set(workflow.id, workflow.state);
|
|
70
87
|
|
|
71
|
-
this.unsubscribeRuns = this.store.subscribeRuns((run) => this.handleRunSaved(run));
|
|
72
|
-
this.
|
|
88
|
+
this.unsubscribeRuns = this.store.subscribeRuns((run) => this.handleRunSaved(run), undefined);
|
|
89
|
+
this.unsubscribeBusMessages = this.store.subscribeBusMessages(
|
|
90
|
+
(event) => this.handleBusMessageSaved(event),
|
|
91
|
+
undefined,
|
|
92
|
+
);
|
|
93
|
+
this.unsubscribeWorkflows = this.store.subscribeWorkflows(
|
|
94
|
+
(workflow) => this.handleWorkflowSaved(workflow),
|
|
95
|
+
undefined,
|
|
96
|
+
);
|
|
73
97
|
}
|
|
74
98
|
|
|
75
99
|
beginWorkgroup(busId: string, strategy: WorkgroupStrategy): void {
|
|
@@ -98,7 +122,9 @@ export class OrchestraEventController {
|
|
|
98
122
|
if (this.flushTimer) clearTimeout(this.flushTimer);
|
|
99
123
|
this.flushTimer = undefined;
|
|
100
124
|
this.queuedEvents.length = 0;
|
|
125
|
+
this.queuedBusMessageDeliveries.length = 0;
|
|
101
126
|
this.unsubscribeRuns();
|
|
127
|
+
this.unsubscribeBusMessages();
|
|
102
128
|
this.unsubscribeWorkflows();
|
|
103
129
|
}
|
|
104
130
|
|
|
@@ -107,15 +133,23 @@ export class OrchestraEventController {
|
|
|
107
133
|
this.flushTimer = undefined;
|
|
108
134
|
if (this.queuedEvents.length === 0) return;
|
|
109
135
|
|
|
110
|
-
const events = this.queuedEvents
|
|
111
|
-
this.
|
|
136
|
+
const events = [...this.queuedEvents];
|
|
137
|
+
const busMessageDeliveries = [...this.queuedBusMessageDeliveries];
|
|
138
|
+
this.sendEvents(
|
|
139
|
+
events,
|
|
140
|
+
formatOrchestraEvents(events, { formatBusMessageFrom: (from) => this.formatBusMessageFrom(from) }),
|
|
141
|
+
);
|
|
142
|
+
this.queuedEvents.splice(0, events.length);
|
|
143
|
+
this.queuedBusMessageDeliveries.splice(0, busMessageDeliveries.length);
|
|
144
|
+
for (const delivery of busMessageDeliveries) this.markBusMessageDelivered(delivery);
|
|
112
145
|
}
|
|
113
146
|
|
|
114
147
|
private handleRunSaved(run: AgentRun): void {
|
|
115
|
-
const
|
|
116
|
-
|
|
148
|
+
const wasFinished = this.runFinished.get(run.id) ?? false;
|
|
149
|
+
const isFinished = isAgentFinishRun(run);
|
|
150
|
+
this.runFinished.set(run.id, isFinished);
|
|
117
151
|
|
|
118
|
-
if (!
|
|
152
|
+
if (!isFinished || wasFinished) return;
|
|
119
153
|
if (this.isWorkflowBus(run.busId)) return;
|
|
120
154
|
|
|
121
155
|
const registeredWorkgroup = this.mainWorkgroupsByBusId.get(run.busId);
|
|
@@ -146,11 +180,29 @@ export class OrchestraEventController {
|
|
|
146
180
|
this.queueEvent({ type: "subagent.finished", busId: run.busId, run: toAgentRunResult(run) });
|
|
147
181
|
}
|
|
148
182
|
|
|
183
|
+
private handleBusMessageSaved(event: { busId: string; message: BusMessage }): void {
|
|
184
|
+
if (event.message.from === "main") return;
|
|
185
|
+
|
|
186
|
+
const subscriptions = this.store.listBusSubscriptions({
|
|
187
|
+
busId: event.busId,
|
|
188
|
+
subscriberId: "main",
|
|
189
|
+
subscriberKind: "main",
|
|
190
|
+
});
|
|
191
|
+
for (const subscription of subscriptions) {
|
|
192
|
+
if (isBusMessageDelivered(subscription, event.message.id)) continue;
|
|
193
|
+
if (this.hasQueuedBusMessageDelivery(subscription.id, event.message.id)) continue;
|
|
194
|
+
|
|
195
|
+
this.queuedBusMessageDeliveries.push({ subscriptionId: subscription.id, message: event.message });
|
|
196
|
+
this.queueEvent({ type: "bus.message", busId: event.busId, message: event.message });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
149
200
|
private handleWorkflowSaved(workflow: WorkflowRun): void {
|
|
150
201
|
const previousState = this.workflowStates.get(workflow.id);
|
|
151
202
|
this.workflowStates.set(workflow.id, workflow.state);
|
|
152
203
|
|
|
153
|
-
if (!isTerminalAgentState(workflow.state) ||
|
|
204
|
+
if (!isTerminalAgentState(workflow.state) || (previousState !== undefined && isTerminalAgentState(previousState)))
|
|
205
|
+
return;
|
|
154
206
|
this.queueEvent({ type: "workflow.finished", workflow });
|
|
155
207
|
}
|
|
156
208
|
|
|
@@ -161,17 +213,35 @@ export class OrchestraEventController {
|
|
|
161
213
|
private getPendingWorkgroupRunIds(busId: string, runIds: Set<string>): string[] {
|
|
162
214
|
return this.store
|
|
163
215
|
.listRuns()
|
|
164
|
-
.filter((run) => run.busId === busId && runIds.has(run.id) && run
|
|
216
|
+
.filter((run) => run.busId === busId && runIds.has(run.id) && isAgentRunActive(run))
|
|
165
217
|
.map((run) => run.id);
|
|
166
218
|
}
|
|
167
219
|
|
|
168
220
|
private getPendingBusRunIds(busId: string): string[] {
|
|
169
221
|
return this.store
|
|
170
222
|
.listRuns()
|
|
171
|
-
.filter((run) => run.busId === busId && run
|
|
223
|
+
.filter((run) => run.busId === busId && isAgentRunActive(run))
|
|
172
224
|
.map((run) => run.id);
|
|
173
225
|
}
|
|
174
226
|
|
|
227
|
+
private hasQueuedBusMessageDelivery(subscriptionId: string, messageId: string): boolean {
|
|
228
|
+
return this.queuedBusMessageDeliveries.some(
|
|
229
|
+
(delivery) => delivery.subscriptionId === subscriptionId && delivery.message.id === messageId,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private formatBusMessageFrom(from: string): string {
|
|
234
|
+
if (from === "main") return from;
|
|
235
|
+
return this.store.getRun(from)?.name ?? from;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private markBusMessageDelivered(delivery: BusMessageDelivery): void {
|
|
239
|
+
const subscription = this.store.getBusSubscription(delivery.subscriptionId);
|
|
240
|
+
if (!subscription) return;
|
|
241
|
+
|
|
242
|
+
this.store.saveBusSubscription(markBusMessagesDelivered(subscription, delivery.message));
|
|
243
|
+
}
|
|
244
|
+
|
|
175
245
|
private queueEvent(event: OrchestraMainEvent): void {
|
|
176
246
|
this.queuedEvents.push(event);
|
|
177
247
|
if (this.flushDelayMs === 0) {
|
|
@@ -183,28 +253,37 @@ export class OrchestraEventController {
|
|
|
183
253
|
}
|
|
184
254
|
}
|
|
185
255
|
|
|
186
|
-
export function formatOrchestraEvents(events: OrchestraMainEvent[]): string {
|
|
256
|
+
export function formatOrchestraEvents(events: OrchestraMainEvent[], options?: FormatOrchestraEventsOptions): string {
|
|
187
257
|
const headline = events.length === 1 ? "Pi-orchestra event:" : `Pi-orchestra events (${events.length}):`;
|
|
188
|
-
return [headline, ...events.flatMap((event) => ["", formatOrchestraEvent(event)])].join("\n");
|
|
258
|
+
return [headline, ...events.flatMap((event) => ["", formatOrchestraEvent(event, options)])].join("\n");
|
|
189
259
|
}
|
|
190
260
|
|
|
191
|
-
function formatOrchestraEvent(event: OrchestraMainEvent): string {
|
|
261
|
+
function formatOrchestraEvent(event: OrchestraMainEvent, options: FormatOrchestraEventsOptions | undefined): string {
|
|
192
262
|
if (event.type === "workflow.finished") return formatWorkflowFinishedEvent(event.workflow);
|
|
263
|
+
if (event.type === "bus.message") return formatBusMessageEvent(event, options);
|
|
193
264
|
|
|
194
265
|
const runLabel = event.run.name === event.run.runId ? event.run.runId : `${event.run.name} (${event.run.runId})`;
|
|
195
266
|
const lines =
|
|
196
267
|
event.type === "workgroup.member_finished"
|
|
197
268
|
? [
|
|
198
|
-
`- Workgroup member finished on bus ${event.busId}: ${runLabel}
|
|
269
|
+
`- Workgroup member finished on bus ${event.busId}: ${runLabel} ${formatRunFinishedState(event.run)}.`,
|
|
199
270
|
` Strategy: ${event.strategy}`,
|
|
200
271
|
` Pending workgroup run ids: ${event.pendingRunIds.length > 0 ? event.pendingRunIds.join(", ") : "none"}`,
|
|
201
272
|
]
|
|
202
|
-
: [`- Subagent finished on bus ${event.busId}: ${runLabel}
|
|
273
|
+
: [`- Subagent finished on bus ${event.busId}: ${runLabel} ${formatRunFinishedState(event.run)}.`];
|
|
203
274
|
|
|
204
275
|
lines.push(...formatRunResultLines(event.run));
|
|
205
276
|
return lines.join("\n");
|
|
206
277
|
}
|
|
207
278
|
|
|
279
|
+
function formatBusMessageEvent(
|
|
280
|
+
event: Extract<OrchestraMainEvent, { type: "bus.message" }>,
|
|
281
|
+
options: FormatOrchestraEventsOptions | undefined,
|
|
282
|
+
): string {
|
|
283
|
+
const from = options?.formatBusMessageFrom?.(event.message.from) ?? event.message.from;
|
|
284
|
+
return [`- Bus message on ${event.busId} from ${from}:`, event.message.message].join("\n");
|
|
285
|
+
}
|
|
286
|
+
|
|
208
287
|
function formatWorkflowFinishedEvent(workflow: WorkflowRun): string {
|
|
209
288
|
const lines = [`- Workflow finished: ${formatNamedEntityLabel(workflow)} is ${workflow.state}.`];
|
|
210
289
|
if (workflow.result) {
|
|
@@ -222,10 +301,14 @@ function formatRunResultLines(run: AgentRunResult): string[] {
|
|
|
222
301
|
return lines;
|
|
223
302
|
}
|
|
224
303
|
|
|
225
|
-
function
|
|
226
|
-
return
|
|
304
|
+
function formatRunFinishedState(run: AgentRunResult): string {
|
|
305
|
+
return run.result ? `finished with ${run.result.status}; state=${run.state}` : `is ${run.state}`;
|
|
227
306
|
}
|
|
228
307
|
|
|
229
|
-
function
|
|
230
|
-
return
|
|
308
|
+
function isAgentFinishRun(run: AgentRun | AgentRunResult): boolean {
|
|
309
|
+
return run.result !== undefined || isAgentResultState(run.state);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isAgentResultState(state: AgentState | undefined): boolean {
|
|
313
|
+
return state === "success" || state === "blocked" || state === "failed";
|
|
231
314
|
}
|
|
@@ -2,7 +2,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { AgentRun } from "../core/subagent.ts";
|
|
3
3
|
import type { AgentStore } from "../core/store.ts";
|
|
4
4
|
import type { WorkflowRun, WorkflowStageRun } from "../core/workflow.ts";
|
|
5
|
-
import { isTerminalAgentState } from "../utils.ts";
|
|
5
|
+
import { isAgentRunFinished, isTerminalAgentState } from "../utils.ts";
|
|
6
6
|
|
|
7
7
|
const WIDGET_KEY = "pi-orchestra.workflow-monitor";
|
|
8
8
|
const MAX_MONITORED_WORKFLOWS = 2;
|
|
@@ -10,9 +10,9 @@ const MAX_WIDGET_LINES = 10;
|
|
|
10
10
|
const DEFAULT_TICK_MS = 1_000;
|
|
11
11
|
|
|
12
12
|
export interface WorkflowMonitorControllerOptions {
|
|
13
|
-
now
|
|
14
|
-
/** Defaults to 1000 ms. Use 0 to disable uptime ticks in tests. */
|
|
15
|
-
tickMs
|
|
13
|
+
now: (() => number) | undefined;
|
|
14
|
+
/** Defaults to 1000 ms when undefined. Use 0 to disable uptime ticks in tests. */
|
|
15
|
+
tickMs: number | undefined;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export class WorkflowMonitorController {
|
|
@@ -24,7 +24,7 @@ export class WorkflowMonitorController {
|
|
|
24
24
|
|
|
25
25
|
constructor(
|
|
26
26
|
private readonly store: AgentStore,
|
|
27
|
-
options: WorkflowMonitorControllerOptions = {},
|
|
27
|
+
options: WorkflowMonitorControllerOptions = { now: undefined, tickMs: undefined },
|
|
28
28
|
) {
|
|
29
29
|
this.now = options.now ?? Date.now;
|
|
30
30
|
this.tickMs = options.tickMs ?? DEFAULT_TICK_MS;
|
|
@@ -39,8 +39,8 @@ export class WorkflowMonitorController {
|
|
|
39
39
|
|
|
40
40
|
this.ctx = ctx;
|
|
41
41
|
if (!this.unsubscribe) {
|
|
42
|
-
const unsubscribeRuns = this.store.subscribeRuns(() => this.render());
|
|
43
|
-
const unsubscribeWorkflows = this.store.subscribeWorkflows(() => this.render());
|
|
42
|
+
const unsubscribeRuns = this.store.subscribeRuns(() => this.render(), undefined);
|
|
43
|
+
const unsubscribeWorkflows = this.store.subscribeWorkflows(() => this.render(), undefined);
|
|
44
44
|
this.unsubscribe = () => {
|
|
45
45
|
unsubscribeRuns();
|
|
46
46
|
unsubscribeWorkflows();
|
|
@@ -112,10 +112,11 @@ function appendWorkflowLines(lines: string[], store: AgentStore, workflow: Workf
|
|
|
112
112
|
if (lines.length >= MAX_WIDGET_LINES) return;
|
|
113
113
|
|
|
114
114
|
const stage = getCurrentStage(workflow);
|
|
115
|
+
const workflowLabel = `${workflow.name} [${formatUptimeSince(workflow.startedAtMs, nowMs)}]`;
|
|
115
116
|
const stageLabel = stage
|
|
116
|
-
? formatStageLabel(store, workflow, stage)
|
|
117
|
+
? formatStageLabel(store, workflow, stage, nowMs)
|
|
117
118
|
: `none (0/${workflow.stages.length}) | agents (0/0)`;
|
|
118
|
-
lines.push(`${
|
|
119
|
+
lines.push(`${workflowLabel} | ${stageLabel}`);
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
function listActiveWorkflows(store: AgentStore): WorkflowRun[] {
|
|
@@ -128,10 +129,11 @@ function getCurrentStage(workflow: WorkflowRun): WorkflowStageRun | undefined {
|
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function formatStageLabel(store: AgentStore, workflow: WorkflowRun, stage: WorkflowStageRun): string {
|
|
132
|
+
function formatStageLabel(store: AgentStore, workflow: WorkflowRun, stage: WorkflowStageRun, nowMs: number): string {
|
|
132
133
|
const stageIndex = workflow.stages.indexOf(stage);
|
|
133
134
|
const stagePosition = stageIndex >= 0 ? `${stageIndex + 1}/${workflow.stages.length}` : `?/${workflow.stages.length}`;
|
|
134
|
-
|
|
135
|
+
const stageUptime = formatUptimeSince(stage.startedAtMs, nowMs);
|
|
136
|
+
return `${stage.name} [${stageUptime}] (${stagePosition}) | agents (${formatStageProgress(store, stage)})`;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
function formatStageProgress(store: AgentStore, stage: WorkflowStageRun): string {
|
|
@@ -139,8 +141,8 @@ function formatStageProgress(store: AgentStore, stage: WorkflowStageRun): string
|
|
|
139
141
|
return `${progress.completed}/${progress.total}`;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
function
|
|
143
|
-
const elapsedSeconds = Math.max(0, Math.floor((nowMs -
|
|
144
|
+
function formatUptimeSince(startedAtMs: number, nowMs: number): string {
|
|
145
|
+
const elapsedSeconds = Math.max(0, Math.floor((nowMs - startedAtMs) / 1_000));
|
|
144
146
|
const seconds = elapsedSeconds % 60;
|
|
145
147
|
const totalMinutes = Math.floor(elapsedSeconds / 60);
|
|
146
148
|
const minutes = totalMinutes % 60;
|
|
@@ -157,7 +159,7 @@ function pad2(value: number): string {
|
|
|
157
159
|
|
|
158
160
|
function calculateStageProgress(store: AgentStore, stage: WorkflowStageRun): { completed: number; total: number } {
|
|
159
161
|
const runs = collectStageRuns(store, stage);
|
|
160
|
-
const completed = runs.filter(
|
|
162
|
+
const completed = runs.filter(isAgentRunFinished).length;
|
|
161
163
|
const workerRunCount = runs.filter((run) => run.id !== stage.leaderRunId).length;
|
|
162
164
|
const workerTotal = Math.max(stage.members.length, stage.workerRunIds.length, workerRunCount);
|
|
163
165
|
const leaderTotal = stage.phase === "leader" || stage.leaderRunId !== undefined ? 1 : 0;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentProfile } from "../core/subagent.ts";
|
|
2
|
+
import { defineAgentProfile, type ToolProfileOptions } from "./profile.ts";
|
|
3
|
+
|
|
4
|
+
export interface CodeReviewerProfileOptions extends ToolProfileOptions {}
|
|
5
|
+
|
|
6
|
+
export function createCodeReviewerProfile(options: CodeReviewerProfileOptions): AgentProfile {
|
|
7
|
+
return defineAgentProfile({
|
|
8
|
+
defaultName: "code-reviewer",
|
|
9
|
+
systemPrompt: [
|
|
10
|
+
"You are a code reviewer: identify bugs, behavioral regressions, security risks, and missing tests in the assigned local code or change.",
|
|
11
|
+
"Prioritize findings over summary. For each finding, include severity, affected file/symbol or line reference, impact, and a concrete remediation direction.",
|
|
12
|
+
"Use local repository evidence and supplied task context; treat bus context as supplemental and note conflicts.",
|
|
13
|
+
"Do not modify files or perform unrelated implementation work, even if editing tools are present.",
|
|
14
|
+
"If no material issues are found, say so clearly and note residual test gaps or review limits.",
|
|
15
|
+
"Use status=success when review findings or a no-issues conclusion are supported, blocked when the target/diff/context/tools are insufficient, and failed when review execution fails.",
|
|
16
|
+
],
|
|
17
|
+
tools: options.tools,
|
|
18
|
+
options,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentProfile } from "../core/subagent.ts";
|
|
2
|
+
import { defineAgentProfile, type ToolProfileOptions } from "./profile.ts";
|
|
3
|
+
|
|
4
|
+
export interface EvidenceSynthesizerProfileOptions extends ToolProfileOptions {}
|
|
5
|
+
|
|
6
|
+
export function createEvidenceSynthesizerProfile(options: EvidenceSynthesizerProfileOptions): AgentProfile {
|
|
7
|
+
return defineAgentProfile({
|
|
8
|
+
defaultName: "evidence-synthesizer",
|
|
9
|
+
systemPrompt: [
|
|
10
|
+
"You are an evidence synthesizer: produce concise canonical output from supplied worker results and shared context.",
|
|
11
|
+
"Treat supplied context (workflow/stage goals, previous outputs, worker results, bus context) as primary evidence.",
|
|
12
|
+
"Use allowed tools only to verify evidence or resolve concrete gaps needed for synthesis; do not broaden scope or perform unrelated work.",
|
|
13
|
+
"Do not modify files or external state; if necessary tools/context are unavailable, report blocked instead of guessing.",
|
|
14
|
+
"Deduplicate and reconcile evidence; note conflicts/gaps; prefer finish results over bus context.",
|
|
15
|
+
"Prefer status=success if useful output exists; use blocked if context is insufficient, failed if synthesis fails.",
|
|
16
|
+
],
|
|
17
|
+
tools: options.tools,
|
|
18
|
+
options,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentProfile } from "../core/subagent.ts";
|
|
2
|
+
import { defineAgentProfile, type ToolProfileOptions } from "./profile.ts";
|
|
3
|
+
|
|
4
|
+
export interface ExternalResearcherProfileOptions extends ToolProfileOptions {}
|
|
5
|
+
|
|
6
|
+
export function createExternalResearcherProfile(options: ExternalResearcherProfileOptions): AgentProfile {
|
|
7
|
+
return defineAgentProfile({
|
|
8
|
+
defaultName: "external-researcher",
|
|
9
|
+
systemPrompt: [
|
|
10
|
+
"You are an external research agent: gather, verify, and synthesize information from outside the target repository.",
|
|
11
|
+
"Use the supplied task and bus context to define scope; prefer official and primary sources, then clearly label secondary sources.",
|
|
12
|
+
"Record source names, links, publication or version dates when available, and distinguish current facts from inference.",
|
|
13
|
+
"Compare similar projects only when the assignment asks; otherwise stay focused on the named target.",
|
|
14
|
+
"Do not modify local files. If required search/fetch/browse tools or credible sources are unavailable, finish with status=blocked instead of guessing.",
|
|
15
|
+
"Finish with concise findings, cited source list, conflicts/gaps, confidence, and useful structured data when comparison or source tables help.",
|
|
16
|
+
],
|
|
17
|
+
tools: options.tools,
|
|
18
|
+
options,
|
|
19
|
+
});
|
|
20
|
+
}
|
package/src/profiles/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentProfile } from "../core/subagent.ts";
|
|
2
|
+
|
|
3
|
+
export interface BaseProfileOptions {
|
|
4
|
+
name: string | undefined;
|
|
5
|
+
model: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ToolProfileOptions extends BaseProfileOptions {
|
|
9
|
+
tools: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface DefineAgentProfileOptions {
|
|
13
|
+
defaultName: string;
|
|
14
|
+
systemPrompt: readonly string[];
|
|
15
|
+
tools: readonly string[];
|
|
16
|
+
options: BaseProfileOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defineAgentProfile({
|
|
20
|
+
defaultName,
|
|
21
|
+
systemPrompt,
|
|
22
|
+
tools,
|
|
23
|
+
options,
|
|
24
|
+
}: DefineAgentProfileOptions): AgentProfile {
|
|
25
|
+
return {
|
|
26
|
+
name: options.name ?? defaultName,
|
|
27
|
+
systemPrompt: systemPrompt.join("\n"),
|
|
28
|
+
tools: [...tools],
|
|
29
|
+
model: options.model,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AgentProfile } from "../core/subagent.ts";
|
|
2
|
+
import { defineAgentProfile, type ToolProfileOptions } from "./profile.ts";
|
|
3
|
+
|
|
4
|
+
export interface SourceCodeQaProfileOptions extends ToolProfileOptions {}
|
|
5
|
+
|
|
6
|
+
export function createSourceCodeQaProfile(options: SourceCodeQaProfileOptions): AgentProfile {
|
|
7
|
+
return defineAgentProfile({
|
|
8
|
+
defaultName: "source-code-qa",
|
|
9
|
+
systemPrompt: [
|
|
10
|
+
"You are a source-code QA agent: answer questions using the target repository's source code, tests, and local docs.",
|
|
11
|
+
"Treat repository files and explicit task context as authoritative; treat bus context as supplemental unless the task says otherwise.",
|
|
12
|
+
"Inspect only the local project and supplied context; do not perform external research or modify files.",
|
|
13
|
+
"Finish with a concise answer, evidence paths/symbols or line references when useful, assumptions, and unresolved gaps.",
|
|
14
|
+
"Use status=success when the answer is supported, blocked when required code/context/tools are unavailable, and failed when inspection fails.",
|
|
15
|
+
],
|
|
16
|
+
tools: options.tools,
|
|
17
|
+
options,
|
|
18
|
+
});
|
|
19
|
+
}
|