@zachwill/pi-orchestrate 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 +94 -0
- package/examples/workers/investigator.md +41 -0
- package/examples/workers/scout.md +36 -0
- package/examples/workers/worker.md +44 -0
- package/extension/catalog.ts +372 -0
- package/extension/contract.ts +70 -0
- package/extension/delivery.ts +196 -0
- package/extension/domain.ts +335 -0
- package/extension/host.ts +107 -0
- package/extension/index.ts +176 -0
- package/extension/presentation.ts +629 -0
- package/extension/runtime.ts +1193 -0
- package/extension/scheduler.ts +66 -0
- package/extension/tools.ts +559 -0
- package/extension/worker-session.ts +526 -0
- package/package.json +46 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
ExtensionFactory,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import {
|
|
7
|
+
discoverWorkerCatalog,
|
|
8
|
+
type DiscoverWorkerCatalogOptions,
|
|
9
|
+
} from "./catalog.js";
|
|
10
|
+
import { appendOrchestratorContract } from "./contract.js";
|
|
11
|
+
import type { WorkerCatalog } from "./domain.js";
|
|
12
|
+
import {
|
|
13
|
+
attachProcessHost,
|
|
14
|
+
createProcessHost,
|
|
15
|
+
destroyProcessHost,
|
|
16
|
+
detachProcessHost,
|
|
17
|
+
type ProcessHost,
|
|
18
|
+
type ProcessHostAttachment,
|
|
19
|
+
} from "./host.js";
|
|
20
|
+
import {
|
|
21
|
+
createStatusController,
|
|
22
|
+
registerOrchestrationPresentation,
|
|
23
|
+
type StatusController,
|
|
24
|
+
} from "./presentation.js";
|
|
25
|
+
import { registerOrchestrationTools } from "./tools.js";
|
|
26
|
+
|
|
27
|
+
const DISPATCH_TOOL_NAMES: ReadonlySet<string> = new Set([
|
|
28
|
+
"orchestrate",
|
|
29
|
+
"worker_send",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
type DispatchMode = "async" | "inline";
|
|
33
|
+
|
|
34
|
+
interface OwnerBinding {
|
|
35
|
+
readonly ownerSessionId: string;
|
|
36
|
+
readonly generation: symbol;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OrchestrationExtensionDependencies {
|
|
40
|
+
getHost?(): ProcessHost;
|
|
41
|
+
destroyHost?(host: ProcessHost): Promise<void>;
|
|
42
|
+
discoverCatalog?(options: DiscoverWorkerCatalogOptions): WorkerCatalog;
|
|
43
|
+
createStatusController?(runtime: ProcessHost["runtime"]): StatusController;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createOrchestrationExtension(
|
|
47
|
+
dependencies: OrchestrationExtensionDependencies = {},
|
|
48
|
+
): ExtensionFactory {
|
|
49
|
+
return (pi) => {
|
|
50
|
+
const host = dependencies.getHost?.() ?? createProcessHost();
|
|
51
|
+
const discoverCatalog = dependencies.discoverCatalog ?? discoverWorkerCatalog;
|
|
52
|
+
const statusController =
|
|
53
|
+
dependencies.createStatusController?.(host.runtime) ??
|
|
54
|
+
createStatusController(host.runtime);
|
|
55
|
+
const dispatchModes = new Map<string, DispatchMode>();
|
|
56
|
+
let hostAttachment: ProcessHostAttachment | undefined;
|
|
57
|
+
let activeBinding: OwnerBinding | undefined;
|
|
58
|
+
let cachedCatalog: WorkerCatalog | undefined;
|
|
59
|
+
|
|
60
|
+
const catalogFor = (ctx: ExtensionContext): WorkerCatalog => {
|
|
61
|
+
if (cachedCatalog) return cachedCatalog;
|
|
62
|
+
cachedCatalog = discoverCatalog({
|
|
63
|
+
cwd: ctx.cwd,
|
|
64
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
65
|
+
});
|
|
66
|
+
return cachedCatalog;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
registerOrchestrationTools(pi, {
|
|
70
|
+
runtime: host.runtime,
|
|
71
|
+
getCatalog: catalogFor,
|
|
72
|
+
getDispatchMode: (toolCallId) => dispatchModes.get(toolCallId) ?? "inline",
|
|
73
|
+
});
|
|
74
|
+
registerOrchestrationPresentation(pi);
|
|
75
|
+
|
|
76
|
+
pi.on("session_start", (_event, ctx) => {
|
|
77
|
+
hostAttachment ??= attachProcessHost(host);
|
|
78
|
+
if (activeBinding) {
|
|
79
|
+
host.delivery.unbind(
|
|
80
|
+
activeBinding.ownerSessionId,
|
|
81
|
+
activeBinding.generation,
|
|
82
|
+
);
|
|
83
|
+
statusController.unbind(activeBinding.ownerSessionId);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
dispatchModes.clear();
|
|
87
|
+
cachedCatalog = undefined;
|
|
88
|
+
const binding: OwnerBinding = {
|
|
89
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
90
|
+
generation: Symbol("pi-orchestrate-parent-binding"),
|
|
91
|
+
};
|
|
92
|
+
activeBinding = binding;
|
|
93
|
+
host.delivery.bind({
|
|
94
|
+
ownerSessionId: binding.ownerSessionId,
|
|
95
|
+
generation: binding.generation,
|
|
96
|
+
isIdle: ctx.isIdle,
|
|
97
|
+
sendMessage: pi.sendMessage,
|
|
98
|
+
});
|
|
99
|
+
statusController.bind(binding.ownerSessionId, ctx);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
103
|
+
cachedCatalog = discoverCatalog({
|
|
104
|
+
cwd: ctx.cwd,
|
|
105
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
systemPrompt: appendOrchestratorContract(
|
|
109
|
+
event.systemPrompt,
|
|
110
|
+
cachedCatalog,
|
|
111
|
+
),
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
pi.on("message_end", (event) => {
|
|
116
|
+
if (event.message.role !== "assistant") return;
|
|
117
|
+
const toolCalls = event.message.content.filter(
|
|
118
|
+
(part) => part.type === "toolCall",
|
|
119
|
+
);
|
|
120
|
+
const mode: DispatchMode = toolCalls.length === 1 ? "async" : "inline";
|
|
121
|
+
|
|
122
|
+
for (const toolCall of toolCalls) {
|
|
123
|
+
if (DISPATCH_TOOL_NAMES.has(toolCall.name)) {
|
|
124
|
+
dispatchModes.set(toolCall.id, mode);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
pi.on("tool_execution_end", (event) => {
|
|
130
|
+
dispatchModes.delete(event.toolCallId);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
pi.on("agent_start", () => {
|
|
134
|
+
const binding = activeBinding;
|
|
135
|
+
if (!binding) return;
|
|
136
|
+
host.delivery.markAgentStarted(
|
|
137
|
+
binding.ownerSessionId,
|
|
138
|
+
binding.generation,
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
pi.on("agent_settled", () => {
|
|
143
|
+
const binding = activeBinding;
|
|
144
|
+
if (!binding) return;
|
|
145
|
+
host.delivery.markAgentSettled(
|
|
146
|
+
binding.ownerSessionId,
|
|
147
|
+
binding.generation,
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
pi.on("session_shutdown", async (event) => {
|
|
152
|
+
const binding = activeBinding;
|
|
153
|
+
activeBinding = undefined;
|
|
154
|
+
cachedCatalog = undefined;
|
|
155
|
+
dispatchModes.clear();
|
|
156
|
+
|
|
157
|
+
if (binding) {
|
|
158
|
+
host.delivery.unbind(binding.ownerSessionId, binding.generation);
|
|
159
|
+
}
|
|
160
|
+
statusController.dispose();
|
|
161
|
+
|
|
162
|
+
const attachment = hostAttachment;
|
|
163
|
+
hostAttachment = undefined;
|
|
164
|
+
const wasLastAttachment = attachment
|
|
165
|
+
? detachProcessHost(host, attachment)
|
|
166
|
+
: false;
|
|
167
|
+
if (event.reason === "quit" && wasLastAttachment) {
|
|
168
|
+
await (dependencies.destroyHost ?? destroyProcessHost)(host);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default function piOrchestrateExtension(pi: ExtensionAPI): void {
|
|
175
|
+
createOrchestrationExtension()(pi);
|
|
176
|
+
}
|