@g3un/pi-orchestra 0.1.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/LICENSE +21 -0
- package/README.md +46 -0
- package/docs/orchestration-model.md +69 -0
- package/package.json +56 -0
- package/src/adapters/in-memory-store.ts +85 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/pi-runtime.ts +348 -0
- package/src/core/bus-format.ts +14 -0
- package/src/core/bus.ts +11 -0
- package/src/core/index.ts +8 -0
- package/src/core/orchestra.ts +322 -0
- package/src/core/runtime.ts +14 -0
- package/src/core/store.ts +21 -0
- package/src/core/subagent.ts +27 -0
- package/src/core/workflow.ts +49 -0
- package/src/core/workgroup.ts +12 -0
- package/src/extension/index.ts +58 -0
- package/src/index.ts +4 -0
- package/src/profiles/index.ts +1 -0
- package/src/profiles/stage-leader.ts +20 -0
- package/src/tools/bus.ts +275 -0
- package/src/tools/index.ts +4 -0
- package/src/tools/subagent.ts +243 -0
- package/src/tools/workflow.ts +712 -0
- package/src/tools/workgroup.ts +422 -0
- package/src/utils.ts +101 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import type { AgentProfile, AgentRun } from "./subagent.ts";
|
|
2
|
+
import type { Bus, BusMessage } from "./bus.ts";
|
|
3
|
+
import type { AgentRuntime } from "./runtime.ts";
|
|
4
|
+
import { createEntityIdentity, isTerminalAgentState, resolveWaitTimeoutMs, toWaitRunResult } from "../utils.ts";
|
|
5
|
+
import type { AgentStore } from "./store.ts";
|
|
6
|
+
|
|
7
|
+
export interface OrchestraApi {
|
|
8
|
+
createBus(options?: CreateBusOptions): Bus;
|
|
9
|
+
getBus(id: string): Bus | undefined;
|
|
10
|
+
publishBus(id: string, message: string, from?: string): Promise<PublishedBusMessage>;
|
|
11
|
+
|
|
12
|
+
spawnAgent(profile: AgentProfile, task: string, busId: string, options?: SpawnAgentOptions): Promise<AgentRun>;
|
|
13
|
+
getRun(id: string, options?: RunLookupOptions): AgentRun | undefined;
|
|
14
|
+
listRuns(options?: ListRunsOptions): AgentRun[];
|
|
15
|
+
messageAgent(id: string, message: string, options?: RunLookupOptions): Promise<AgentRun>;
|
|
16
|
+
closeAgent(id: string, options?: RunLookupOptions): Promise<AgentRun | undefined>;
|
|
17
|
+
waitBusSettled(busId: string, options?: WaitBusSettledOptions): Promise<WaitBusSettledResult>;
|
|
18
|
+
waitNextRun(busId: string, options?: WaitNextRunOptions): Promise<WaitNextRunResult>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CreateBusOptions {
|
|
22
|
+
name?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SpawnAgentOptions {
|
|
26
|
+
name?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PublishedBusMessage {
|
|
30
|
+
bus: Bus;
|
|
31
|
+
busMessage: BusMessage;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WaitBusSettledResult {
|
|
35
|
+
bus: Bus;
|
|
36
|
+
runs: AgentRun[];
|
|
37
|
+
runResults: WaitRunResult[];
|
|
38
|
+
timedOut: boolean;
|
|
39
|
+
pendingRunIds: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WaitNextRunResult {
|
|
43
|
+
bus: Bus;
|
|
44
|
+
run?: AgentRun;
|
|
45
|
+
runResult?: WaitRunResult;
|
|
46
|
+
runs: AgentRun[];
|
|
47
|
+
runResults: WaitRunResult[];
|
|
48
|
+
timedOut: boolean;
|
|
49
|
+
pendingRunIds: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WaitRunResult {
|
|
53
|
+
runId: string;
|
|
54
|
+
name: string;
|
|
55
|
+
profile: string;
|
|
56
|
+
state: AgentRun["state"];
|
|
57
|
+
result?: AgentRun["result"];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface WaitBusSettledOptions {
|
|
61
|
+
/** Defaults to 10 minutes. Use null to wait indefinitely. */
|
|
62
|
+
timeoutMs?: number | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface WaitNextRunOptions {
|
|
66
|
+
/** Defaults to 10 minutes. Use null to wait indefinitely. */
|
|
67
|
+
timeoutMs?: number | null;
|
|
68
|
+
/** Run ids or names that have already been handled by the leader. */
|
|
69
|
+
excludeRunIds?: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ListRunsOptions {
|
|
73
|
+
busId?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RunLookupOptions {
|
|
77
|
+
/** Optional bus id or name for narrowing run lookup. */
|
|
78
|
+
busId?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface OrchestraDeps {
|
|
82
|
+
runtime: AgentRuntime;
|
|
83
|
+
store: AgentStore;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class Orchestra implements OrchestraApi {
|
|
87
|
+
private readonly runtime: AgentRuntime;
|
|
88
|
+
private readonly store: AgentStore;
|
|
89
|
+
|
|
90
|
+
constructor({ runtime, store }: OrchestraDeps) {
|
|
91
|
+
this.runtime = runtime;
|
|
92
|
+
this.store = store;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
createBus(options: CreateBusOptions = {}): Bus {
|
|
96
|
+
const identity = this.createBusIdentity(options.name);
|
|
97
|
+
const bus: Bus = { ...identity, messages: [] };
|
|
98
|
+
this.store.saveBus(bus);
|
|
99
|
+
return bus;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getBus(id: string): Bus | undefined {
|
|
103
|
+
return this.findBus(id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async publishBus(id: string, message: string, from = "main"): Promise<PublishedBusMessage> {
|
|
107
|
+
const bus = this.requireBus(id);
|
|
108
|
+
const busMessage = await this.runtime.publishBus(bus.id, message, from);
|
|
109
|
+
return { bus: this.requireBus(bus.id), busMessage };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async spawnAgent(
|
|
113
|
+
profile: AgentProfile,
|
|
114
|
+
task: string,
|
|
115
|
+
busId: string,
|
|
116
|
+
options: SpawnAgentOptions = {},
|
|
117
|
+
): Promise<AgentRun> {
|
|
118
|
+
const bus = this.requireBus(busId);
|
|
119
|
+
return await this.runtime.spawn(profile, task, bus.id, this.createRunIdentity(profile, options.name));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getRun(id: string, options: RunLookupOptions = {}): AgentRun | undefined {
|
|
123
|
+
return this.findRun(id, options);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
listRuns(options: ListRunsOptions = {}): AgentRun[] {
|
|
127
|
+
const runs = this.store.listRuns();
|
|
128
|
+
if (!options.busId) return runs;
|
|
129
|
+
const bus = this.requireBus(options.busId);
|
|
130
|
+
return runs.filter((run) => run.busId === bus.id);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async messageAgent(id: string, message: string, options: RunLookupOptions = {}): Promise<AgentRun> {
|
|
134
|
+
const run = this.requireRun(id, options);
|
|
135
|
+
return await this.runtime.message(run.id, message);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async closeAgent(id: string, options: RunLookupOptions = {}): Promise<AgentRun | undefined> {
|
|
139
|
+
const run = this.getRun(id, options);
|
|
140
|
+
return await this.runtime.close(run?.id ?? id);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
waitBusSettled(busId: string, options: WaitBusSettledOptions = {}): Promise<WaitBusSettledResult> {
|
|
144
|
+
const timeoutMs = resolveWaitTimeoutMs("waitBusSettled", options.timeoutMs);
|
|
145
|
+
const bus = this.requireBus(busId);
|
|
146
|
+
const initialRuns = this.listRuns({ busId: bus.id });
|
|
147
|
+
if (initialRuns.every((run) => isTerminalAgentState(run.state))) {
|
|
148
|
+
return Promise.resolve(buildWaitBusSettledResult(bus, initialRuns, false));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
const latestRuns = new Map(initialRuns.map((run) => [run.id, run]));
|
|
153
|
+
const unsubscribeAll: Array<() => void> = [];
|
|
154
|
+
let settled = false;
|
|
155
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
156
|
+
|
|
157
|
+
const cleanup = () => {
|
|
158
|
+
if (timeout) clearTimeout(timeout);
|
|
159
|
+
for (const unsubscribe of unsubscribeAll.splice(0)) unsubscribe();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const getLatestRuns = () => initialRuns.map((run) => latestRuns.get(run.id) ?? run);
|
|
163
|
+
|
|
164
|
+
const resolveIfDone = () => {
|
|
165
|
+
const runs = getLatestRuns();
|
|
166
|
+
if (runs.some((run) => !isTerminalAgentState(run.state))) return;
|
|
167
|
+
|
|
168
|
+
settled = true;
|
|
169
|
+
cleanup();
|
|
170
|
+
resolve(buildWaitBusSettledResult(bus, runs, false));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (timeoutMs !== null) {
|
|
174
|
+
timeout = setTimeout(() => {
|
|
175
|
+
if (settled) return;
|
|
176
|
+
|
|
177
|
+
settled = true;
|
|
178
|
+
cleanup();
|
|
179
|
+
resolve(buildWaitBusSettledResult(bus, getLatestRuns(), true));
|
|
180
|
+
}, timeoutMs);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const run of initialRuns) {
|
|
184
|
+
if (isTerminalAgentState(run.state)) continue;
|
|
185
|
+
unsubscribeAll.push(
|
|
186
|
+
this.store.subscribeRun(run.id, (updatedRun) => {
|
|
187
|
+
if (settled) return;
|
|
188
|
+
latestRuns.set(updatedRun.id, updatedRun);
|
|
189
|
+
resolveIfDone();
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
resolveIfDone();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
waitNextRun(busId: string, options: WaitNextRunOptions = {}): Promise<WaitNextRunResult> {
|
|
199
|
+
const timeoutMs = resolveWaitTimeoutMs("waitNextRun", options.timeoutMs);
|
|
200
|
+
const bus = this.requireBus(busId);
|
|
201
|
+
const initialRuns = this.listRuns({ busId: bus.id });
|
|
202
|
+
const excludedRunIds = this.resolveExcludedRunIds(initialRuns, options.excludeRunIds ?? []);
|
|
203
|
+
const candidateRuns = initialRuns.filter((run) => !excludedRunIds.has(run.id));
|
|
204
|
+
const alreadyDone = candidateRuns.find((run) => isTerminalAgentState(run.state));
|
|
205
|
+
if (alreadyDone || candidateRuns.length === 0) {
|
|
206
|
+
return Promise.resolve(buildWaitNextRunResult(bus, initialRuns, alreadyDone, false, excludedRunIds));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return new Promise((resolve) => {
|
|
210
|
+
const latestRuns = new Map(initialRuns.map((run) => [run.id, run]));
|
|
211
|
+
const unsubscribeAll: Array<() => void> = [];
|
|
212
|
+
let settled = false;
|
|
213
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
214
|
+
|
|
215
|
+
const cleanup = () => {
|
|
216
|
+
if (timeout) clearTimeout(timeout);
|
|
217
|
+
for (const unsubscribe of unsubscribeAll.splice(0)) unsubscribe();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const getLatestRuns = () => initialRuns.map((run) => latestRuns.get(run.id) ?? run);
|
|
221
|
+
|
|
222
|
+
const resolveWithRun = (run: AgentRun) => {
|
|
223
|
+
settled = true;
|
|
224
|
+
cleanup();
|
|
225
|
+
resolve(buildWaitNextRunResult(bus, getLatestRuns(), run, false, excludedRunIds));
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
if (timeoutMs !== null) {
|
|
229
|
+
timeout = setTimeout(() => {
|
|
230
|
+
if (settled) return;
|
|
231
|
+
|
|
232
|
+
settled = true;
|
|
233
|
+
cleanup();
|
|
234
|
+
resolve(buildWaitNextRunResult(bus, getLatestRuns(), undefined, true, excludedRunIds));
|
|
235
|
+
}, timeoutMs);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const run of initialRuns) {
|
|
239
|
+
if (isTerminalAgentState(run.state)) continue;
|
|
240
|
+
unsubscribeAll.push(
|
|
241
|
+
this.store.subscribeRun(run.id, (updatedRun) => {
|
|
242
|
+
if (settled) return;
|
|
243
|
+
latestRuns.set(updatedRun.id, updatedRun);
|
|
244
|
+
if (!excludedRunIds.has(updatedRun.id) && isTerminalAgentState(updatedRun.state))
|
|
245
|
+
resolveWithRun(updatedRun);
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private requireBus(id: string): Bus {
|
|
253
|
+
const bus = this.findBus(id);
|
|
254
|
+
if (!bus) throw new Error(`Bus ${id} not found.`);
|
|
255
|
+
return bus;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private findBus(id: string): Bus | undefined {
|
|
259
|
+
return this.store.getBus(id) ?? this.store.listBuses().find((bus) => bus.name === id);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private requireRun(id: string, options: RunLookupOptions = {}): AgentRun {
|
|
263
|
+
const run = this.findRun(id, options);
|
|
264
|
+
if (!run) throw new Error(`Agent ${id} not found.`);
|
|
265
|
+
return run;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private findRun(id: string, options: RunLookupOptions = {}): AgentRun | undefined {
|
|
269
|
+
const bus = options.busId ? this.requireBus(options.busId) : undefined;
|
|
270
|
+
const runById = this.store.getRun(id);
|
|
271
|
+
if (runById && (!bus || runById.busId === bus.id)) return runById;
|
|
272
|
+
|
|
273
|
+
return this.store.listRuns().find((run) => run.name === id && (!bus || run.busId === bus.id));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private resolveExcludedRunIds(busRuns: AgentRun[], excludedRunIds: string[]): Set<string> {
|
|
277
|
+
const resolvedIds = new Set<string>();
|
|
278
|
+
for (const id of excludedRunIds) {
|
|
279
|
+
const run = busRuns.find((current) => current.id === id || current.name === id);
|
|
280
|
+
resolvedIds.add(run?.id ?? id);
|
|
281
|
+
}
|
|
282
|
+
return resolvedIds;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private createBusIdentity(name: string | undefined) {
|
|
286
|
+
return createEntityIdentity(name, "bus", this.store.listBuses(), "Bus");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private createRunIdentity(profile: AgentProfile, name: string | undefined) {
|
|
290
|
+
return createEntityIdentity(name, profile.name, this.store.listRuns(), "Agent");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function buildWaitBusSettledResult(bus: Bus, runs: AgentRun[], timedOut: boolean): WaitBusSettledResult {
|
|
295
|
+
return {
|
|
296
|
+
bus,
|
|
297
|
+
runs,
|
|
298
|
+
runResults: runs.map(toWaitRunResult),
|
|
299
|
+
timedOut,
|
|
300
|
+
pendingRunIds: runs.filter((run) => !isTerminalAgentState(run.state)).map((run) => run.id),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function buildWaitNextRunResult(
|
|
305
|
+
bus: Bus,
|
|
306
|
+
runs: AgentRun[],
|
|
307
|
+
run: AgentRun | undefined,
|
|
308
|
+
timedOut: boolean,
|
|
309
|
+
excludedRunIds: Set<string>,
|
|
310
|
+
): WaitNextRunResult {
|
|
311
|
+
return {
|
|
312
|
+
bus,
|
|
313
|
+
run,
|
|
314
|
+
runResult: run ? toWaitRunResult(run) : undefined,
|
|
315
|
+
runs,
|
|
316
|
+
runResults: runs.map(toWaitRunResult),
|
|
317
|
+
timedOut,
|
|
318
|
+
pendingRunIds: runs
|
|
319
|
+
.filter((current) => !excludedRunIds.has(current.id) && !isTerminalAgentState(current.state))
|
|
320
|
+
.map((current) => current.id),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentProfile, AgentRun } from "./subagent.ts";
|
|
2
|
+
import type { BusMessage } from "./bus.ts";
|
|
3
|
+
|
|
4
|
+
export interface SpawnAgentRuntimeOptions {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AgentRuntime {
|
|
10
|
+
spawn(profile: AgentProfile, task: string, busId: string, options: SpawnAgentRuntimeOptions): Promise<AgentRun>;
|
|
11
|
+
message(id: string, message: string): Promise<AgentRun>;
|
|
12
|
+
publishBus(busId: string, message: string, from: string): Promise<BusMessage>;
|
|
13
|
+
close(id: string): Promise<AgentRun | undefined>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentRun } from "./subagent.ts";
|
|
2
|
+
import type { Bus, BusMessage } from "./bus.ts";
|
|
3
|
+
import type { WorkflowRun } from "./workflow.ts";
|
|
4
|
+
|
|
5
|
+
export interface AgentStore {
|
|
6
|
+
saveRun(run: AgentRun): void;
|
|
7
|
+
getRun(id: string): AgentRun | undefined;
|
|
8
|
+
listRuns(): AgentRun[];
|
|
9
|
+
subscribeRun(id: string, listener: (run: AgentRun) => void): () => void;
|
|
10
|
+
|
|
11
|
+
saveBus(bus: Bus): void;
|
|
12
|
+
getBus(id: string): Bus | undefined;
|
|
13
|
+
listBuses(): Bus[];
|
|
14
|
+
/** Add or replace a bus message by id. */
|
|
15
|
+
addBusMessage(busId: string, message: BusMessage): void;
|
|
16
|
+
|
|
17
|
+
saveWorkflow(workflow: WorkflowRun): void;
|
|
18
|
+
getWorkflow(id: string): WorkflowRun | undefined;
|
|
19
|
+
listWorkflows(): WorkflowRun[];
|
|
20
|
+
subscribeWorkflow(id: string, listener: (workflow: WorkflowRun) => void): () => void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const AGENT_RESULT_STATUS_VALUES = ["success", "blocked", "failed"] as const;
|
|
2
|
+
export type AgentResultStatus = (typeof AGENT_RESULT_STATUS_VALUES)[number];
|
|
3
|
+
|
|
4
|
+
export type AgentState = "idle" | "closed" | AgentResultStatus;
|
|
5
|
+
|
|
6
|
+
export interface AgentProfile {
|
|
7
|
+
name: string;
|
|
8
|
+
systemPrompt: string;
|
|
9
|
+
tools?: string[];
|
|
10
|
+
model?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentResult {
|
|
14
|
+
status: AgentResultStatus;
|
|
15
|
+
summary: string;
|
|
16
|
+
data?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AgentRun {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
profile: string;
|
|
23
|
+
task: string;
|
|
24
|
+
busId: string;
|
|
25
|
+
state: AgentState;
|
|
26
|
+
result?: AgentResult;
|
|
27
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AgentResult, AgentResultStatus, AgentState } from "./subagent.ts";
|
|
2
|
+
import type { WorkgroupMember, WorkgroupStrategy } from "./workgroup.ts";
|
|
3
|
+
|
|
4
|
+
export interface WorkflowStageSpec {
|
|
5
|
+
name: string;
|
|
6
|
+
goal: string;
|
|
7
|
+
strategy: WorkgroupStrategy;
|
|
8
|
+
members: WorkgroupMember[];
|
|
9
|
+
/** If omitted, a restricted default stage leader is used. */
|
|
10
|
+
leader?: WorkgroupMember;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface WorkflowStageOutput {
|
|
14
|
+
status: AgentResultStatus;
|
|
15
|
+
summary: string;
|
|
16
|
+
data?: unknown;
|
|
17
|
+
leaderRunId?: string;
|
|
18
|
+
workerResults: WorkflowRunResult[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WorkflowRunResult {
|
|
22
|
+
runId: string;
|
|
23
|
+
name: string;
|
|
24
|
+
profile: string;
|
|
25
|
+
state: string;
|
|
26
|
+
result?: AgentResult;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkflowStageRun extends Omit<WorkflowStageSpec, "leader"> {
|
|
30
|
+
leader: WorkgroupMember;
|
|
31
|
+
state: AgentState;
|
|
32
|
+
phase?: "workers" | "leader";
|
|
33
|
+
busId?: string;
|
|
34
|
+
workerRunIds: string[];
|
|
35
|
+
leaderRunId?: string;
|
|
36
|
+
output?: WorkflowStageOutput;
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WorkflowRun {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
goal: string;
|
|
44
|
+
state: AgentState;
|
|
45
|
+
currentStageIndex: number;
|
|
46
|
+
stages: WorkflowStageRun[];
|
|
47
|
+
result?: WorkflowStageOutput;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentProfile } from "./subagent.ts";
|
|
2
|
+
|
|
3
|
+
export const WORKGROUP_STRATEGY_VALUES = ["compete", "synthesize"] as const;
|
|
4
|
+
export type WorkgroupStrategy = (typeof WORKGROUP_STRATEGY_VALUES)[number];
|
|
5
|
+
|
|
6
|
+
export interface WorkgroupMember {
|
|
7
|
+
profile: AgentProfile;
|
|
8
|
+
/** Optional globally unique short run name. If omitted, one is generated from the profile name. */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** Member-specific assignment or focus within the workgroup goal. */
|
|
11
|
+
assignment?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { InMemoryAgentStore } from "../adapters/in-memory-store.ts";
|
|
3
|
+
import { PiAgentRuntime } from "../adapters/pi-runtime.ts";
|
|
4
|
+
import { Orchestra } from "../core/orchestra.ts";
|
|
5
|
+
import { createBusTool, defineBusPiTool, type BusTool } from "../tools/bus.ts";
|
|
6
|
+
import { createSubagentTool, defineSubagentPiTool, type SubagentTool } from "../tools/subagent.ts";
|
|
7
|
+
import { createWorkflowTool, defineWorkflowPiTool, type WorkflowTool } from "../tools/workflow.ts";
|
|
8
|
+
import { createWorkgroupTool, defineWorkgroupPiTool, type WorkgroupTool } from "../tools/workgroup.ts";
|
|
9
|
+
|
|
10
|
+
interface ToolBundle {
|
|
11
|
+
busTool: BusTool;
|
|
12
|
+
subagentTool: SubagentTool;
|
|
13
|
+
workgroupTool: WorkgroupTool;
|
|
14
|
+
workflowTool: WorkflowTool;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function piOrchestraExtension(pi: ExtensionAPI): void {
|
|
18
|
+
const bundles = new Map<string, ToolBundle>();
|
|
19
|
+
const getToolBundle = (ctx: ExtensionContext) => getBundle(bundles, ctx);
|
|
20
|
+
|
|
21
|
+
pi.registerTool(defineBusPiTool((ctx) => getToolBundle(ctx).busTool));
|
|
22
|
+
pi.registerTool(defineSubagentPiTool((ctx) => getToolBundle(ctx).subagentTool));
|
|
23
|
+
pi.registerTool(defineWorkgroupPiTool((ctx) => getToolBundle(ctx).workgroupTool));
|
|
24
|
+
pi.registerTool(defineWorkflowPiTool((ctx) => getToolBundle(ctx).workflowTool));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getBundle(bundles: Map<string, ToolBundle>, ctx: ExtensionContext): ToolBundle {
|
|
28
|
+
const existing = bundles.get(ctx.cwd);
|
|
29
|
+
if (existing) return existing;
|
|
30
|
+
|
|
31
|
+
const store = new InMemoryAgentStore();
|
|
32
|
+
const runtime = new PiAgentRuntime({
|
|
33
|
+
store,
|
|
34
|
+
cwd: ctx.cwd,
|
|
35
|
+
resolveModel: (model) => resolveModel(ctx, model),
|
|
36
|
+
});
|
|
37
|
+
const orchestra = new Orchestra({ runtime, store });
|
|
38
|
+
const bundle = {
|
|
39
|
+
busTool: createBusTool({ orchestra }),
|
|
40
|
+
subagentTool: createSubagentTool({ orchestra }),
|
|
41
|
+
workgroupTool: createWorkgroupTool({ orchestra }),
|
|
42
|
+
workflowTool: createWorkflowTool({ orchestra, store }),
|
|
43
|
+
};
|
|
44
|
+
bundles.set(ctx.cwd, bundle);
|
|
45
|
+
return bundle;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveModel(ctx: ExtensionContext, model: string): ReturnType<ExtensionContext["modelRegistry"]["find"]> {
|
|
49
|
+
const slashIndex = model.indexOf("/");
|
|
50
|
+
if (slashIndex < 0) {
|
|
51
|
+
const currentProvider = ctx.model?.provider;
|
|
52
|
+
return currentProvider ? ctx.modelRegistry.find(currentProvider, model) : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const provider = model.slice(0, slashIndex);
|
|
56
|
+
const modelId = model.slice(slashIndex + 1);
|
|
57
|
+
return provider && modelId ? ctx.modelRegistry.find(provider, modelId) : undefined;
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./stage-leader.ts";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentProfile } from "../core/subagent.ts";
|
|
2
|
+
|
|
3
|
+
export interface StageLeaderProfileOptions {
|
|
4
|
+
name?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createStageLeaderProfile(options: StageLeaderProfileOptions = {}): AgentProfile {
|
|
9
|
+
return {
|
|
10
|
+
name: options.name ?? "stage-leader",
|
|
11
|
+
systemPrompt: [
|
|
12
|
+
"You are a workflow stage leader: produce concise canonical output for the next stage.",
|
|
13
|
+
"Use only supplied context (workflow/stage goals, previous outputs, worker results, bus context); do not research, inspect files, run commands, or request external info.",
|
|
14
|
+
"Deduplicate and reconcile findings; 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
|
+
].join("\n"),
|
|
17
|
+
tools: [],
|
|
18
|
+
model: options.model,
|
|
19
|
+
};
|
|
20
|
+
}
|