@zachwill/pi-orchestrate 0.1.1 → 0.2.1
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 +3 -0
- package/extension/catalog.ts +176 -157
- package/extension/domain.ts +5 -1
- package/extension/host.ts +2 -2
- package/extension/presentation.ts +139 -104
- package/extension/runtime.ts +152 -64
- package/extension/scheduler.ts +44 -25
- package/extension/tools.ts +63 -23
- package/extension/worker-session.ts +488 -295
- package/package.json +3 -2
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import type { AgentMessage, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
3
5
|
import type { Api, AssistantMessage, Model } from "@earendil-works/pi-ai";
|
|
4
6
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
AgentSessionRuntime,
|
|
8
|
+
createAgentSessionFromServices,
|
|
9
|
+
createAgentSessionServices,
|
|
10
|
+
DefaultPackageManager,
|
|
7
11
|
ModelRuntime,
|
|
8
12
|
type ModelRegistry,
|
|
9
13
|
type ResourceLoader,
|
|
10
14
|
SessionManager,
|
|
11
15
|
SettingsManager,
|
|
12
16
|
type AgentSessionEvent,
|
|
13
|
-
type
|
|
17
|
+
type AgentSessionServices,
|
|
18
|
+
type CreateAgentSessionResult,
|
|
19
|
+
type DefaultResourceLoader,
|
|
14
20
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import
|
|
21
|
+
import { Effect, Exit, Schema, Scope } from "effect";
|
|
22
|
+
import type {
|
|
23
|
+
WorkerDefinition,
|
|
24
|
+
WorkerMessageDirection,
|
|
25
|
+
WorkerOutcome,
|
|
26
|
+
WorkerUsage,
|
|
27
|
+
} from "./domain.js";
|
|
16
28
|
|
|
17
29
|
const DIRECT_CHILD_BOUNDARY =
|
|
18
30
|
"You are a direct child worker session. Do not spawn, delegate to, or orchestrate other workers or child sessions. Complete the assigned task yourself and return the result directly to the parent orchestrator.";
|
|
31
|
+
const ORCHESTRATE_PACKAGE_ROOT = canonicalPath(fileURLToPath(new URL("..", import.meta.url)));
|
|
19
32
|
|
|
20
33
|
interface WorkerAgentSession {
|
|
21
34
|
readonly sessionFile: string | undefined;
|
|
@@ -23,11 +36,19 @@ interface WorkerAgentSession {
|
|
|
23
36
|
prompt(instructions: string): Promise<void>;
|
|
24
37
|
abort(): Promise<void>;
|
|
25
38
|
dispose(): void;
|
|
39
|
+
bindExtensions(bindings: { mode: "print" }): Promise<void>;
|
|
26
40
|
subscribe(listener: (event: AgentSessionEvent) => void): () => void;
|
|
27
41
|
}
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
interface OwnedWorkerRuntime {
|
|
44
|
+
readonly session: WorkerAgentSession;
|
|
45
|
+
dispose(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type ResourceLoaderOptions = Omit<
|
|
49
|
+
ConstructorParameters<typeof DefaultResourceLoader>[0],
|
|
50
|
+
"cwd" | "agentDir" | "settingsManager"
|
|
51
|
+
>;
|
|
31
52
|
|
|
32
53
|
export interface WorkerSessionFactoryOptions {
|
|
33
54
|
cwd: string;
|
|
@@ -44,18 +65,42 @@ export interface WorkerSessionHandle {
|
|
|
44
65
|
readonly sessionFile: string;
|
|
45
66
|
prompt(instructions: string): Promise<WorkerOutcome>;
|
|
46
67
|
abort(): Promise<void>;
|
|
47
|
-
dispose(): void
|
|
68
|
+
dispose(): Promise<void>;
|
|
48
69
|
subscribeUsage(listener: (usage: WorkerUsage) => void): () => void;
|
|
49
70
|
subscribeActivity(listener: (activity: string | undefined) => void): () => void;
|
|
71
|
+
subscribeMessageDirection(listener: (direction: WorkerMessageDirection) => void): () => void;
|
|
50
72
|
}
|
|
51
73
|
|
|
52
74
|
export interface WorkerSessionFactory {
|
|
53
75
|
create(options: WorkerSessionFactoryOptions): Promise<WorkerSessionHandle>;
|
|
54
76
|
}
|
|
55
77
|
|
|
56
|
-
|
|
78
|
+
export class WorkerModelAcquisitionError extends Schema.TaggedErrorClass<WorkerModelAcquisitionError>()(
|
|
79
|
+
"WorkerSession.ModelAcquisitionError",
|
|
80
|
+
{ message: Schema.String, cause: Schema.Defect() },
|
|
81
|
+
) {}
|
|
82
|
+
|
|
83
|
+
export class WorkerResourceAcquisitionError extends Schema.TaggedErrorClass<WorkerResourceAcquisitionError>()(
|
|
84
|
+
"WorkerSession.ResourceAcquisitionError",
|
|
85
|
+
{ message: Schema.String, cause: Schema.Defect() },
|
|
86
|
+
) {}
|
|
87
|
+
|
|
88
|
+
export class WorkerAgentSessionAcquisitionError extends Schema.TaggedErrorClass<WorkerAgentSessionAcquisitionError>()(
|
|
89
|
+
"WorkerSession.AgentSessionAcquisitionError",
|
|
90
|
+
{ message: Schema.String, cause: Schema.Defect() },
|
|
91
|
+
) {}
|
|
92
|
+
|
|
93
|
+
interface SettingsManagerInput {
|
|
57
94
|
cwd: string;
|
|
58
|
-
|
|
95
|
+
agentDir: string;
|
|
96
|
+
projectTrusted: boolean;
|
|
97
|
+
compaction: WorkerDefinition["compaction"];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ExtensionPathInput {
|
|
101
|
+
cwd: string;
|
|
102
|
+
agentDir: string;
|
|
103
|
+
settingsManager: SettingsManager;
|
|
59
104
|
}
|
|
60
105
|
|
|
61
106
|
interface ModelRuntimeInput {
|
|
@@ -63,38 +108,61 @@ interface ModelRuntimeInput {
|
|
|
63
108
|
modelsPath: string;
|
|
64
109
|
}
|
|
65
110
|
|
|
66
|
-
interface
|
|
111
|
+
interface ServicesInput {
|
|
67
112
|
cwd: string;
|
|
68
113
|
agentDir: string;
|
|
69
|
-
|
|
114
|
+
settingsManager: SettingsManager;
|
|
70
115
|
modelRuntime: ModelRuntime;
|
|
116
|
+
resourceLoaderOptions: ResourceLoaderOptions;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface AgentSessionInput {
|
|
120
|
+
services: AgentSessionServices;
|
|
121
|
+
sessionManager: SessionManager;
|
|
122
|
+
model: Model<Api>;
|
|
71
123
|
thinkingLevel: ThinkingLevel | undefined;
|
|
72
124
|
tools: string[];
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface RuntimeInput {
|
|
128
|
+
session: WorkerAgentSession;
|
|
129
|
+
services: AgentSessionServices;
|
|
76
130
|
}
|
|
77
131
|
|
|
78
132
|
export interface WorkerSessionDependencies {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
settings: InMemorySettings,
|
|
83
|
-
options: { projectTrusted: boolean },
|
|
84
|
-
): unknown;
|
|
133
|
+
createSettingsManager(input: SettingsManagerInput): SettingsManager;
|
|
134
|
+
resolveExtensionPaths(input: ExtensionPathInput): Promise<readonly string[]>;
|
|
135
|
+
createSessionManager(input: { cwd: string; parentSessionFile: string | undefined }): SessionManager;
|
|
85
136
|
createModelRuntime(input: ModelRuntimeInput): Promise<ModelRuntime>;
|
|
137
|
+
createServices(input: ServicesInput): Promise<AgentSessionServices>;
|
|
86
138
|
createAgentSession(input: AgentSessionInput): Promise<{ session: WorkerAgentSession }>;
|
|
139
|
+
createRuntime(input: RuntimeInput): OwnedWorkerRuntime;
|
|
87
140
|
}
|
|
88
141
|
|
|
89
142
|
const defaultDependencies: WorkerSessionDependencies = {
|
|
90
|
-
|
|
143
|
+
createSettingsManager: ({ cwd, agentDir, projectTrusted, compaction }) => {
|
|
144
|
+
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
|
145
|
+
if (compaction !== undefined) settingsManager.applyOverrides({ compaction: { ...compaction } });
|
|
146
|
+
return settingsManager;
|
|
147
|
+
},
|
|
148
|
+
resolveExtensionPaths: async ({ cwd, agentDir, settingsManager }) => {
|
|
149
|
+
const resolved = await new DefaultPackageManager({ cwd, agentDir, settingsManager }).resolve();
|
|
150
|
+
return resolved.extensions.filter((entry) => entry.enabled).map((entry) => entry.path);
|
|
151
|
+
},
|
|
91
152
|
createSessionManager: ({ cwd, parentSessionFile }) =>
|
|
92
153
|
SessionManager.create(cwd, undefined, { parentSession: parentSessionFile }),
|
|
93
|
-
createSettingsManager: (settings, options) =>
|
|
94
|
-
SettingsManager.inMemory(settings, { projectTrusted: options.projectTrusted }),
|
|
95
154
|
createModelRuntime: (input) => ModelRuntime.create(input),
|
|
155
|
+
createServices: (input) => createAgentSessionServices(input),
|
|
96
156
|
createAgentSession: (input) =>
|
|
97
|
-
|
|
157
|
+
createAgentSessionFromServices(input) as Promise<CreateAgentSessionResult & { session: WorkerAgentSession }>,
|
|
158
|
+
createRuntime: ({ session, services }) =>
|
|
159
|
+
new AgentSessionRuntime(
|
|
160
|
+
session as CreateAgentSessionResult["session"],
|
|
161
|
+
services,
|
|
162
|
+
async () => {
|
|
163
|
+
throw new Error("Worker sessions do not support runtime replacement");
|
|
164
|
+
},
|
|
165
|
+
) as unknown as OwnedWorkerRuntime,
|
|
98
166
|
};
|
|
99
167
|
|
|
100
168
|
export function resolveWorkerModel(
|
|
@@ -119,57 +187,34 @@ export function resolveWorkerModel(
|
|
|
119
187
|
return model;
|
|
120
188
|
}
|
|
121
189
|
|
|
122
|
-
|
|
123
|
-
dispose(): void;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isDisposableResource(resource: ResourceLoader): resource is ResourceLoader & DisposableResource {
|
|
127
|
-
return "dispose" in resource && typeof resource.dispose === "function";
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function disposeResourceLoader(resourceLoader: ResourceLoader): void {
|
|
131
|
-
if (!isDisposableResource(resourceLoader)) return;
|
|
190
|
+
function canonicalPath(path: string): string {
|
|
132
191
|
try {
|
|
133
|
-
|
|
192
|
+
return realpathSync.native(path);
|
|
134
193
|
} catch {
|
|
135
|
-
|
|
194
|
+
return resolve(path);
|
|
136
195
|
}
|
|
137
196
|
}
|
|
138
197
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} catch {
|
|
143
|
-
// Other owned resources still need to be released.
|
|
144
|
-
}
|
|
198
|
+
function isWithin(path: string, root: string): boolean {
|
|
199
|
+
const child = relative(root, path);
|
|
200
|
+
return child === "" || (!child.startsWith("..") && !isAbsolute(child));
|
|
145
201
|
}
|
|
146
202
|
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
if (typeof error === "string" && error !== "") return error;
|
|
150
|
-
return "Worker prompt failed";
|
|
203
|
+
export function isOrchestrationExtensionPath(path: string): boolean {
|
|
204
|
+
return isWithin(canonicalPath(path), ORCHESTRATE_PACKAGE_ROOT);
|
|
151
205
|
}
|
|
152
206
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
cost: number;
|
|
159
|
-
contextTokens: number;
|
|
160
|
-
turns: number;
|
|
207
|
+
function resourceLoaderFinalizer(resourceLoader: ResourceLoader): Effect.Effect<void> {
|
|
208
|
+
return Effect.sync(() => {
|
|
209
|
+
if (!("dispose" in resourceLoader) || typeof resourceLoader.dispose !== "function") return;
|
|
210
|
+
resourceLoader.dispose();
|
|
211
|
+
}).pipe(Effect.ignoreCause);
|
|
161
212
|
}
|
|
162
213
|
|
|
163
|
-
function
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
cacheRead: 0,
|
|
168
|
-
cacheWrite: 0,
|
|
169
|
-
cost: 0,
|
|
170
|
-
contextTokens: 0,
|
|
171
|
-
turns: 0,
|
|
172
|
-
};
|
|
214
|
+
function describeError(error: unknown, fallback: string): string {
|
|
215
|
+
if (error instanceof Error) return error.message;
|
|
216
|
+
if (typeof error === "string" && error !== "") return error;
|
|
217
|
+
return fallback;
|
|
173
218
|
}
|
|
174
219
|
|
|
175
220
|
function assistantText(message: AssistantMessage | undefined): string | undefined {
|
|
@@ -188,184 +233,157 @@ function lastAssistant(messages: AgentMessage[]): AssistantMessage | undefined {
|
|
|
188
233
|
return undefined;
|
|
189
234
|
}
|
|
190
235
|
|
|
236
|
+
interface MutableWorkerUsage {
|
|
237
|
+
input: number;
|
|
238
|
+
output: number;
|
|
239
|
+
cacheRead: number;
|
|
240
|
+
cacheWrite: number;
|
|
241
|
+
cost: number;
|
|
242
|
+
contextTokens: number;
|
|
243
|
+
turns: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function emptyUsage(): MutableWorkerUsage {
|
|
247
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 };
|
|
248
|
+
}
|
|
249
|
+
|
|
191
250
|
class DefaultWorkerSessionHandle implements WorkerSessionHandle {
|
|
192
251
|
readonly sessionFile: string;
|
|
193
|
-
|
|
194
252
|
private readonly usage = emptyUsage();
|
|
195
253
|
private readonly usageListeners = new Set<(usage: WorkerUsage) => void>();
|
|
196
|
-
private readonly activityListeners = new Set<
|
|
197
|
-
|
|
198
|
-
>();
|
|
199
|
-
private readonly unsubscribeSession: () => void;
|
|
254
|
+
private readonly activityListeners = new Set<(activity: string | undefined) => void>();
|
|
255
|
+
private readonly messageDirectionListeners = new Set<(direction: WorkerMessageDirection) => void>();
|
|
256
|
+
private readonly activeToolCalls = new Map<string, string>();
|
|
200
257
|
private disposed = false;
|
|
258
|
+
private disposePromise: Promise<void> | undefined;
|
|
201
259
|
private activity: string | undefined;
|
|
202
|
-
private
|
|
260
|
+
private messageDirection: WorkerMessageDirection | undefined;
|
|
203
261
|
private prompting = false;
|
|
204
262
|
private abortRequested = false;
|
|
205
263
|
private promptAssistant: AssistantMessage | undefined;
|
|
206
264
|
|
|
207
265
|
constructor(
|
|
208
|
-
private readonly
|
|
209
|
-
private readonly resourceLoader: ResourceLoader,
|
|
266
|
+
private readonly runtime: OwnedWorkerRuntime,
|
|
210
267
|
private readonly reusable: boolean,
|
|
268
|
+
sessionFile: string,
|
|
269
|
+
private readonly scope: Scope.Closeable,
|
|
211
270
|
) {
|
|
212
|
-
this.sessionFile =
|
|
213
|
-
this.unsubscribeSession = session.subscribe((event) => this.onSessionEvent(event));
|
|
271
|
+
this.sessionFile = sessionFile;
|
|
214
272
|
}
|
|
215
273
|
|
|
216
|
-
|
|
274
|
+
receiveSessionEvent(event: AgentSessionEvent): void {
|
|
275
|
+
if (event.type === "message_start") {
|
|
276
|
+
if (event.message.role === "assistant") this.setMessageDirection("from-model");
|
|
277
|
+
else if (event.message.role === "user" || event.message.role === "toolResult") this.setMessageDirection("to-model");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
217
280
|
if (event.type === "tool_execution_start") {
|
|
218
281
|
this.activeToolCalls.delete(event.toolCallId);
|
|
219
282
|
this.activeToolCalls.set(event.toolCallId, event.toolName);
|
|
220
283
|
this.setActivity(event.toolName);
|
|
221
284
|
return;
|
|
222
285
|
}
|
|
223
|
-
|
|
224
286
|
if (event.type === "tool_execution_end") {
|
|
225
287
|
if (this.activeToolCalls.get(event.toolCallId) !== event.toolName) return;
|
|
226
288
|
this.activeToolCalls.delete(event.toolCallId);
|
|
227
|
-
this.setActivity(this.
|
|
289
|
+
this.setActivity([...this.activeToolCalls.values()].at(-1));
|
|
228
290
|
return;
|
|
229
291
|
}
|
|
230
|
-
|
|
231
292
|
if (event.type !== "turn_end" || event.message.role !== "assistant") return;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.
|
|
235
|
-
this.usage.
|
|
236
|
-
this.usage.
|
|
237
|
-
this.usage.
|
|
238
|
-
this.usage.
|
|
239
|
-
this.usage.cost += message.usage.cost?.total ?? 0;
|
|
240
|
-
this.usage.contextTokens = message.usage.totalTokens ?? 0;
|
|
293
|
+
this.promptAssistant = event.message;
|
|
294
|
+
this.usage.input += event.message.usage.input ?? 0;
|
|
295
|
+
this.usage.output += event.message.usage.output ?? 0;
|
|
296
|
+
this.usage.cacheRead += event.message.usage.cacheRead ?? 0;
|
|
297
|
+
this.usage.cacheWrite += event.message.usage.cacheWrite ?? 0;
|
|
298
|
+
this.usage.cost += event.message.usage.cost?.total ?? 0;
|
|
299
|
+
this.usage.contextTokens = event.message.usage.totalTokens ?? 0;
|
|
241
300
|
this.usage.turns += 1;
|
|
242
|
-
|
|
243
|
-
for (const listener of [...this.usageListeners]) {
|
|
244
|
-
try {
|
|
245
|
-
listener({ ...this.usage });
|
|
246
|
-
} catch {
|
|
247
|
-
// One subscriber cannot prevent other subscribers from receiving usage.
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private mostRecentActiveTool(): string | undefined {
|
|
253
|
-
let latest: string | undefined;
|
|
254
|
-
for (const toolName of this.activeToolCalls.values()) latest = toolName;
|
|
255
|
-
return latest;
|
|
301
|
+
for (const listener of [...this.usageListeners]) safelyNotify(() => listener({ ...this.usage }));
|
|
256
302
|
}
|
|
257
303
|
|
|
258
304
|
private setActivity(activity: string | undefined): void {
|
|
259
305
|
if (this.activity === activity) return;
|
|
260
306
|
this.activity = activity;
|
|
261
|
-
for (const listener of [...this.activityListeners])
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
for (const listener of [...this.activityListeners]) safelyNotify(() => listener(activity));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private setMessageDirection(direction: WorkerMessageDirection): void {
|
|
311
|
+
if (this.messageDirection === direction) return;
|
|
312
|
+
this.messageDirection = direction;
|
|
313
|
+
for (const listener of [...this.messageDirectionListeners]) safelyNotify(() => listener(direction));
|
|
268
314
|
}
|
|
269
315
|
|
|
270
316
|
async prompt(instructions: string): Promise<WorkerOutcome> {
|
|
271
317
|
if (this.disposed) throw new Error("Worker session has been disposed");
|
|
272
318
|
if (this.prompting) throw new Error("Worker session is already processing a prompt");
|
|
273
|
-
|
|
274
319
|
this.prompting = true;
|
|
275
320
|
this.abortRequested = false;
|
|
276
321
|
this.promptAssistant = undefined;
|
|
277
|
-
|
|
278
|
-
|
|
322
|
+
this.setMessageDirection("to-model");
|
|
323
|
+
const previousAssistant = lastAssistant(this.runtime.session.messages);
|
|
324
|
+
let failureMessage: string | undefined;
|
|
279
325
|
try {
|
|
280
|
-
await this.session.prompt(instructions);
|
|
281
|
-
const latestAssistant = lastAssistant(this.session.messages);
|
|
282
|
-
const message = this.promptAssistant ?? (latestAssistant !== previousAssistant ? latestAssistant : undefined);
|
|
283
|
-
return this.outcomeFrom(message);
|
|
326
|
+
await this.runtime.session.prompt(instructions);
|
|
284
327
|
} catch (error) {
|
|
285
|
-
|
|
286
|
-
const message = this.promptAssistant ?? (latestAssistant !== previousAssistant ? latestAssistant : undefined);
|
|
287
|
-
return this.outcomeFrom(message, describePromptError(error));
|
|
328
|
+
failureMessage = describeError(error, "Worker prompt failed");
|
|
288
329
|
} finally {
|
|
289
330
|
this.prompting = false;
|
|
290
331
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
private outcomeFrom(
|
|
294
|
-
message: AssistantMessage | undefined,
|
|
295
|
-
failureMessage?: string,
|
|
296
|
-
): WorkerOutcome {
|
|
332
|
+
const latestAssistant = lastAssistant(this.runtime.session.messages);
|
|
333
|
+
const message = this.promptAssistant ?? (latestAssistant !== previousAssistant ? latestAssistant : undefined);
|
|
297
334
|
const text = assistantText(message);
|
|
298
335
|
const assistantPayload = text === undefined ? {} : { assistantText: text };
|
|
299
|
-
|
|
300
336
|
if (this.abortRequested || message?.stopReason === "aborted") {
|
|
301
337
|
const abortMessage = message?.errorMessage ?? failureMessage;
|
|
302
|
-
return {
|
|
303
|
-
status: "aborted",
|
|
304
|
-
...(abortMessage === undefined ? {} : { message: abortMessage }),
|
|
305
|
-
...assistantPayload,
|
|
306
|
-
};
|
|
338
|
+
return { status: "aborted", ...(abortMessage ? { message: abortMessage } : {}), ...assistantPayload };
|
|
307
339
|
}
|
|
308
|
-
|
|
309
340
|
if (message?.stopReason === "error") {
|
|
310
|
-
return {
|
|
311
|
-
status: "failed",
|
|
312
|
-
message: message.errorMessage ?? failureMessage ?? "Worker assistant reported a failure",
|
|
313
|
-
...assistantPayload,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (failureMessage !== undefined) {
|
|
318
|
-
return { status: "failed", message: failureMessage, ...assistantPayload };
|
|
341
|
+
return { status: "failed", message: message.errorMessage ?? failureMessage ?? "Worker assistant reported a failure", ...assistantPayload };
|
|
319
342
|
}
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
status: this.reusable ? "ready" : "completed",
|
|
323
|
-
assistantText: text ?? "",
|
|
324
|
-
};
|
|
343
|
+
if (failureMessage) return { status: "failed", message: failureMessage, ...assistantPayload };
|
|
344
|
+
return { status: this.reusable ? "ready" : "completed", assistantText: text ?? "" };
|
|
325
345
|
}
|
|
326
346
|
|
|
327
347
|
async abort(): Promise<void> {
|
|
328
348
|
if (this.prompting) this.abortRequested = true;
|
|
329
|
-
await this.session.abort();
|
|
349
|
+
await this.runtime.session.abort();
|
|
330
350
|
}
|
|
331
351
|
|
|
332
|
-
dispose(): void {
|
|
333
|
-
if (this.
|
|
334
|
-
this.disposed = true;
|
|
352
|
+
dispose(): Promise<void> {
|
|
353
|
+
if (this.disposePromise) return this.disposePromise;
|
|
335
354
|
|
|
336
|
-
|
|
337
|
-
this.unsubscribeSession();
|
|
338
|
-
} catch {
|
|
339
|
-
// Continue releasing independently owned resources.
|
|
340
|
-
}
|
|
355
|
+
this.disposed = true;
|
|
341
356
|
this.activeToolCalls.clear();
|
|
342
357
|
this.usageListeners.clear();
|
|
343
358
|
this.activityListeners.clear();
|
|
344
|
-
|
|
345
|
-
|
|
359
|
+
this.messageDirectionListeners.clear();
|
|
360
|
+
this.disposePromise = Effect.runPromise(disposeWorkerSession(this.scope));
|
|
361
|
+
return this.disposePromise;
|
|
346
362
|
}
|
|
347
363
|
|
|
348
364
|
subscribeUsage(listener: (usage: WorkerUsage) => void): () => void {
|
|
349
|
-
this.usageListeners
|
|
350
|
-
let subscribed = true;
|
|
351
|
-
return () => {
|
|
352
|
-
if (!subscribed) return;
|
|
353
|
-
subscribed = false;
|
|
354
|
-
this.usageListeners.delete(listener);
|
|
355
|
-
};
|
|
365
|
+
return subscribe(this.usageListeners, listener);
|
|
356
366
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
listener: (activity: string | undefined) => void,
|
|
360
|
-
): () => void {
|
|
361
|
-
this.activityListeners.add(listener);
|
|
362
|
-
let subscribed = true;
|
|
363
|
-
return () => {
|
|
364
|
-
if (!subscribed) return;
|
|
365
|
-
subscribed = false;
|
|
366
|
-
this.activityListeners.delete(listener);
|
|
367
|
-
};
|
|
367
|
+
subscribeActivity(listener: (activity: string | undefined) => void): () => void {
|
|
368
|
+
return subscribe(this.activityListeners, listener);
|
|
368
369
|
}
|
|
370
|
+
subscribeMessageDirection(listener: (direction: WorkerMessageDirection) => void): () => void {
|
|
371
|
+
return subscribe(this.messageDirectionListeners, listener);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function safelyNotify(callback: () => void): void {
|
|
376
|
+
try { callback(); } catch { /* One cleanup/listener cannot block another. */ }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function subscribe<T>(listeners: Set<(value: T) => void>, listener: (value: T) => void): () => void {
|
|
380
|
+
listeners.add(listener);
|
|
381
|
+
let active = true;
|
|
382
|
+
return () => {
|
|
383
|
+
if (!active) return;
|
|
384
|
+
active = false;
|
|
385
|
+
listeners.delete(listener);
|
|
386
|
+
};
|
|
369
387
|
}
|
|
370
388
|
|
|
371
389
|
function selectedModelCoordinates(
|
|
@@ -374,158 +392,333 @@ function selectedModelCoordinates(
|
|
|
374
392
|
): { provider: string; modelId: string } {
|
|
375
393
|
if (definition.model) return definition.model;
|
|
376
394
|
if (parentModel) return { provider: parentModel.provider, modelId: parentModel.id };
|
|
377
|
-
throw new Error(
|
|
378
|
-
|
|
379
|
-
|
|
395
|
+
throw new Error(`Worker "${definition.name}" has no configured model and no parent model is available`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function modelAcquisitionError(cause: unknown, fallback: string): WorkerModelAcquisitionError {
|
|
399
|
+
return new WorkerModelAcquisitionError({ message: describeError(cause, fallback), cause });
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function resourceAcquisitionError(cause: unknown): WorkerResourceAcquisitionError {
|
|
403
|
+
return new WorkerResourceAcquisitionError({
|
|
404
|
+
message: describeError(cause, "Worker resource acquisition failed"),
|
|
405
|
+
cause,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function agentSessionAcquisitionError(cause: unknown): WorkerAgentSessionAcquisitionError {
|
|
410
|
+
return new WorkerAgentSessionAcquisitionError({
|
|
411
|
+
message: describeError(cause, "Worker agent session acquisition failed"),
|
|
412
|
+
cause,
|
|
413
|
+
});
|
|
380
414
|
}
|
|
381
415
|
|
|
382
|
-
|
|
416
|
+
const refreshModelRuntime = Effect.fn("WorkerSession.refreshModelRuntime")(function* (
|
|
383
417
|
modelRuntime: ModelRuntime,
|
|
384
418
|
definition: WorkerDefinition,
|
|
385
|
-
)
|
|
386
|
-
const result =
|
|
419
|
+
) {
|
|
420
|
+
const result = yield* Effect.tryPromise({
|
|
421
|
+
try: () => modelRuntime.refresh({ allowNetwork: false }),
|
|
422
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model refresh failed"),
|
|
423
|
+
});
|
|
387
424
|
if (result.errors.size > 0) {
|
|
388
|
-
const errors = [...result.errors]
|
|
389
|
-
|
|
390
|
-
.join("; ");
|
|
391
|
-
throw new Error(
|
|
425
|
+
const errors = [...result.errors].map(([id, error]) => `${id}: ${error.message}`).join("; ");
|
|
426
|
+
const cause = new Error(
|
|
392
427
|
`Worker "${definition.name}" failed to refresh child model providers: ${errors}`,
|
|
393
428
|
);
|
|
429
|
+
return yield* Effect.fail(modelAcquisitionError(cause, "Worker model refresh failed"));
|
|
394
430
|
}
|
|
395
431
|
if (result.aborted) {
|
|
396
|
-
|
|
432
|
+
const cause = new Error(`Worker "${definition.name}" child model refresh was aborted`);
|
|
433
|
+
return yield* Effect.fail(modelAcquisitionError(cause, "Worker model refresh failed"));
|
|
397
434
|
}
|
|
398
|
-
}
|
|
435
|
+
});
|
|
399
436
|
|
|
400
|
-
|
|
437
|
+
const prepareChildModelRuntime = Effect.fn("WorkerSession.prepareChildModelRuntime")(function* (
|
|
401
438
|
options: WorkerSessionFactoryOptions,
|
|
402
439
|
dependencies: WorkerSessionDependencies,
|
|
403
|
-
)
|
|
404
|
-
const selected =
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
440
|
+
) {
|
|
441
|
+
const selected = yield* Effect.try({
|
|
442
|
+
try: () => selectedModelCoordinates(options.definition, options.parentModel),
|
|
443
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model selection failed"),
|
|
444
|
+
});
|
|
445
|
+
const modelRuntime = yield* Effect.tryPromise({
|
|
446
|
+
try: () => dependencies.createModelRuntime({
|
|
447
|
+
authPath: join(options.agentDir, "auth.json"),
|
|
448
|
+
modelsPath: join(options.agentDir, "models.json"),
|
|
449
|
+
}),
|
|
450
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model runtime creation failed"),
|
|
408
451
|
});
|
|
409
452
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
);
|
|
421
|
-
}
|
|
453
|
+
yield* Effect.try({
|
|
454
|
+
try: () => {
|
|
455
|
+
for (const providerId of options.modelRegistry.getRegisteredProviderIds()) {
|
|
456
|
+
const config = options.modelRegistry.getRegisteredProviderConfig(providerId);
|
|
457
|
+
if (config) modelRuntime.registerProvider(providerId, { ...config });
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model provider registration failed"),
|
|
461
|
+
});
|
|
462
|
+
yield* refreshModelRuntime(modelRuntime, options.definition);
|
|
422
463
|
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
464
|
+
const model = yield* Effect.try({
|
|
465
|
+
try: () => modelRuntime.getModel(selected.provider, selected.modelId),
|
|
466
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model resolution failed"),
|
|
467
|
+
});
|
|
468
|
+
if (model) {
|
|
469
|
+
const auth = yield* Effect.tryPromise({
|
|
470
|
+
try: () => options.modelRegistry.getApiKeyAndHeaders(model),
|
|
471
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model authentication failed"),
|
|
472
|
+
});
|
|
473
|
+
if (auth.ok) {
|
|
474
|
+
yield* Effect.tryPromise({
|
|
475
|
+
try: async () => {
|
|
476
|
+
if (auth.headers) {
|
|
477
|
+
modelRuntime.registerProvider(selected.provider, { headers: { ...auth.headers } });
|
|
478
|
+
}
|
|
479
|
+
if (auth.apiKey && !options.modelRegistry.isUsingOAuth(model)) {
|
|
480
|
+
await modelRuntime.setRuntimeApiKey(selected.provider, auth.apiKey);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model runtime key setup failed"),
|
|
429
484
|
});
|
|
430
485
|
}
|
|
431
|
-
// OAuth resolution returns an access token through the compatibility API, but
|
|
432
|
-
// installing that token as a runtime API key masks the complete OAuth credential
|
|
433
|
-
// that the child already reads from the shared auth.json file.
|
|
434
|
-
if (resolvedAuth.apiKey && !parentUsesOAuth) {
|
|
435
|
-
await modelRuntime.setRuntimeApiKey(selected.provider, resolvedAuth.apiKey);
|
|
436
|
-
}
|
|
437
|
-
// Pi 0.80.10 exposes resolved provider env here but has no public ModelRuntime
|
|
438
|
-
// runtime-env setter. Provider registrations, resolved headers, and non-OAuth
|
|
439
|
-
// API keys are copied.
|
|
440
486
|
}
|
|
441
487
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
488
|
+
return { selected, modelRuntime };
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
function skillLoaderOptions(definition: WorkerDefinition): Pick<ResourceLoaderOptions, "noSkills" | "skillsOverride"> {
|
|
492
|
+
if (definition.skills === undefined) return {};
|
|
493
|
+
const selectedSkills = new Set(definition.skills);
|
|
494
|
+
return {
|
|
495
|
+
noSkills: selectedSkills.size === 0,
|
|
496
|
+
skillsOverride: (base) => ({
|
|
497
|
+
skills: base.skills.filter((skill) => selectedSkills.has(skill.name)),
|
|
498
|
+
diagnostics: base.diagnostics,
|
|
499
|
+
}),
|
|
500
|
+
};
|
|
450
501
|
}
|
|
451
502
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
503
|
+
function contextLoaderOptions(
|
|
504
|
+
agentDir: string,
|
|
505
|
+
projectTrusted: boolean,
|
|
506
|
+
): Pick<ResourceLoaderOptions, "agentsFilesOverride"> {
|
|
507
|
+
if (projectTrusted) return {};
|
|
508
|
+
const globalRoot = canonicalPath(agentDir);
|
|
455
509
|
return {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
510
|
+
agentsFilesOverride: (base) => ({
|
|
511
|
+
agentsFiles: base.agentsFiles.filter((file) => isWithin(canonicalPath(file.path), globalRoot)),
|
|
512
|
+
}),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const disposeWorkerSession = Effect.fn("WorkerSession.dispose")(function* (
|
|
517
|
+
scope: Scope.Closeable,
|
|
518
|
+
) {
|
|
519
|
+
yield* Scope.close(scope, Exit.void).pipe(Effect.ignoreCause);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const acquireWorkerServices = Effect.fn("WorkerSession.acquireServices")(function* (
|
|
523
|
+
options: WorkerSessionFactoryOptions,
|
|
524
|
+
dependencies: WorkerSessionDependencies,
|
|
525
|
+
modelRuntime: ModelRuntime,
|
|
526
|
+
) {
|
|
527
|
+
const definition = options.definition;
|
|
528
|
+
const settingsManager = yield* Effect.try({
|
|
529
|
+
try: () => dependencies.createSettingsManager({
|
|
530
|
+
cwd: options.cwd,
|
|
531
|
+
agentDir: options.agentDir,
|
|
532
|
+
projectTrusted: options.projectTrusted,
|
|
533
|
+
compaction: definition.compaction,
|
|
534
|
+
}),
|
|
535
|
+
catch: resourceAcquisitionError,
|
|
536
|
+
});
|
|
537
|
+
const extensionPaths = yield* Effect.tryPromise({
|
|
538
|
+
try: () => dependencies.resolveExtensionPaths({
|
|
539
|
+
cwd: options.cwd,
|
|
540
|
+
agentDir: options.agentDir,
|
|
541
|
+
settingsManager,
|
|
542
|
+
}),
|
|
543
|
+
catch: resourceAcquisitionError,
|
|
544
|
+
}).pipe(Effect.map((paths) => paths.filter((path) => !isOrchestrationExtensionPath(path))));
|
|
545
|
+
|
|
546
|
+
return yield* Effect.acquireRelease(
|
|
547
|
+
Effect.tryPromise({
|
|
548
|
+
// Pi's supported helper constructs and reloads its loader internally. If reload rejects,
|
|
549
|
+
// it exposes no loader to dispose; callers cannot make that acquisition failure-atomic.
|
|
550
|
+
try: () => dependencies.createServices({
|
|
467
551
|
cwd: options.cwd,
|
|
468
552
|
agentDir: options.agentDir,
|
|
469
|
-
settingsManager
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
})
|
|
553
|
+
settingsManager,
|
|
554
|
+
modelRuntime,
|
|
555
|
+
resourceLoaderOptions: {
|
|
556
|
+
noExtensions: true,
|
|
557
|
+
additionalExtensionPaths: extensionPaths,
|
|
558
|
+
noPromptTemplates: true,
|
|
559
|
+
noThemes: true,
|
|
560
|
+
...skillLoaderOptions(definition),
|
|
561
|
+
...contextLoaderOptions(options.agentDir, options.projectTrusted),
|
|
562
|
+
appendSystemPrompt: [definition.systemPrompt, DIRECT_CHILD_BOUNDARY],
|
|
563
|
+
},
|
|
564
|
+
}),
|
|
565
|
+
catch: resourceAcquisitionError,
|
|
566
|
+
}),
|
|
567
|
+
(services) => resourceLoaderFinalizer(services.resourceLoader),
|
|
568
|
+
);
|
|
569
|
+
});
|
|
481
570
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
571
|
+
interface AgentSessionOwnership {
|
|
572
|
+
readonly session: WorkerAgentSession;
|
|
573
|
+
runtime: OwnedWorkerRuntime | undefined;
|
|
574
|
+
}
|
|
485
575
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
576
|
+
function rawSessionFinalizer(session: WorkerAgentSession): Effect.Effect<void> {
|
|
577
|
+
return Effect.sync(() => session.dispose()).pipe(Effect.ignoreCause);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function agentSessionFinalizer(ownership: AgentSessionOwnership): Effect.Effect<void> {
|
|
581
|
+
const runtime = ownership.runtime;
|
|
582
|
+
if (!runtime) return rawSessionFinalizer(ownership.session);
|
|
583
|
+
return Effect.tryPromise(() => runtime.dispose()).pipe(
|
|
584
|
+
Effect.catch(() => rawSessionFinalizer(ownership.session)),
|
|
585
|
+
Effect.ignoreCause,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const acquireAgentSession = Effect.fn("WorkerSession.acquireAgentSession")(function* (
|
|
590
|
+
dependencies: WorkerSessionDependencies,
|
|
591
|
+
input: AgentSessionInput,
|
|
592
|
+
) {
|
|
593
|
+
return yield* Effect.acquireRelease(
|
|
594
|
+
Effect.tryPromise({
|
|
595
|
+
try: () => dependencies.createAgentSession(input),
|
|
596
|
+
catch: agentSessionAcquisitionError,
|
|
597
|
+
}).pipe(Effect.map(({ session }) => {
|
|
598
|
+
const ownership: AgentSessionOwnership = { session, runtime: undefined };
|
|
599
|
+
return ownership;
|
|
600
|
+
})),
|
|
601
|
+
agentSessionFinalizer,
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const acquireAgentSessionRuntime = Effect.fn("WorkerSession.acquireAgentSessionRuntime")(function* (
|
|
606
|
+
dependencies: WorkerSessionDependencies,
|
|
607
|
+
ownership: AgentSessionOwnership,
|
|
608
|
+
services: AgentSessionServices,
|
|
609
|
+
) {
|
|
610
|
+
const runtime = yield* Effect.try({
|
|
611
|
+
try: () => dependencies.createRuntime({ session: ownership.session, services }),
|
|
612
|
+
catch: agentSessionAcquisitionError,
|
|
613
|
+
});
|
|
614
|
+
ownership.runtime = runtime;
|
|
615
|
+
return runtime;
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const acquireSessionSubscription = Effect.fn("WorkerSession.acquireSubscription")(function* (
|
|
619
|
+
runtime: OwnedWorkerRuntime,
|
|
620
|
+
handle: DefaultWorkerSessionHandle,
|
|
621
|
+
) {
|
|
622
|
+
return yield* Effect.acquireRelease(
|
|
623
|
+
Effect.try({
|
|
624
|
+
try: () => runtime.session.subscribe((event) => handle.receiveSessionEvent(event)),
|
|
625
|
+
catch: agentSessionAcquisitionError,
|
|
626
|
+
}),
|
|
627
|
+
(unsubscribe) => Effect.sync(unsubscribe).pipe(Effect.ignoreCause),
|
|
628
|
+
);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
632
|
+
options: WorkerSessionFactoryOptions,
|
|
633
|
+
dependencies: WorkerSessionDependencies,
|
|
634
|
+
) {
|
|
635
|
+
const scope = yield* Scope.make("sequential");
|
|
636
|
+
const acquisition = Effect.gen(function* () {
|
|
637
|
+
const definition = options.definition;
|
|
638
|
+
const { selected, modelRuntime } = yield* prepareChildModelRuntime(options, dependencies);
|
|
639
|
+
const services = yield* acquireWorkerServices(options, dependencies, modelRuntime);
|
|
640
|
+
|
|
641
|
+
yield* refreshModelRuntime(modelRuntime, definition);
|
|
642
|
+
const model = yield* Effect.try({
|
|
643
|
+
try: () => {
|
|
644
|
+
const resolvedModel = modelRuntime.getModel(selected.provider, selected.modelId);
|
|
645
|
+
if (!resolvedModel) {
|
|
646
|
+
throw new Error(
|
|
647
|
+
`Worker "${definition.name}" configured model "${selected.provider}/${selected.modelId}" was not found`,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
return resolvedModel;
|
|
651
|
+
},
|
|
652
|
+
catch: (cause) => modelAcquisitionError(cause, "Worker model resolution failed"),
|
|
653
|
+
});
|
|
654
|
+
const sessionManager = yield* Effect.try({
|
|
655
|
+
try: () => dependencies.createSessionManager({
|
|
656
|
+
cwd: options.cwd,
|
|
657
|
+
parentSessionFile: options.parentSessionFile,
|
|
658
|
+
}),
|
|
659
|
+
catch: agentSessionAcquisitionError,
|
|
660
|
+
});
|
|
661
|
+
const ownership = yield* acquireAgentSession(dependencies, {
|
|
662
|
+
services,
|
|
663
|
+
sessionManager,
|
|
664
|
+
model,
|
|
665
|
+
thinkingLevel: definition.thinking,
|
|
666
|
+
tools: [...definition.tools],
|
|
667
|
+
});
|
|
668
|
+
const runtime = yield* acquireAgentSessionRuntime(dependencies, ownership, services);
|
|
669
|
+
yield* Effect.tryPromise({
|
|
670
|
+
try: () => runtime.session.bindExtensions({ mode: "print" }),
|
|
671
|
+
catch: agentSessionAcquisitionError,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
yield* Effect.try({
|
|
675
|
+
try: () => {
|
|
676
|
+
if (definition.skills === undefined) return;
|
|
677
|
+
const loadedNames = new Set(
|
|
678
|
+
services.resourceLoader.getSkills().skills.map((skill) => skill.name),
|
|
491
679
|
);
|
|
492
|
-
|
|
680
|
+
const missing = definition.skills.filter((name) => !loadedNames.has(name));
|
|
681
|
+
if (missing.length > 0) {
|
|
493
682
|
throw new Error(
|
|
494
|
-
`Worker "${definition.name}" selected skills were not loaded: ${
|
|
683
|
+
`Worker "${definition.name}" selected skills were not loaded: ${missing.join(", ")}`,
|
|
495
684
|
);
|
|
496
685
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
(
|
|
503
|
-
cwd: options.cwd,
|
|
504
|
-
agentDir: options.agentDir,
|
|
505
|
-
model,
|
|
506
|
-
modelRuntime,
|
|
507
|
-
thinkingLevel: definition.thinking,
|
|
508
|
-
tools: [...definition.tools],
|
|
509
|
-
resourceLoader,
|
|
510
|
-
sessionManager,
|
|
511
|
-
settingsManager,
|
|
512
|
-
}));
|
|
513
|
-
if (!session.sessionFile) {
|
|
686
|
+
},
|
|
687
|
+
catch: resourceAcquisitionError,
|
|
688
|
+
});
|
|
689
|
+
const sessionFile = yield* Effect.try({
|
|
690
|
+
try: () => {
|
|
691
|
+
if (!runtime.session.sessionFile) {
|
|
514
692
|
throw new Error("Worker session was not created with durable storage");
|
|
515
693
|
}
|
|
694
|
+
return runtime.session.sessionFile;
|
|
695
|
+
},
|
|
696
|
+
catch: agentSessionAcquisitionError,
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const handle = new DefaultWorkerSessionHandle(
|
|
700
|
+
runtime,
|
|
701
|
+
definition.lifecycle === "reusable",
|
|
702
|
+
sessionFile,
|
|
703
|
+
scope,
|
|
704
|
+
);
|
|
705
|
+
yield* acquireSessionSubscription(runtime, handle);
|
|
706
|
+
return handle;
|
|
707
|
+
}).pipe(Scope.provide(scope));
|
|
708
|
+
|
|
709
|
+
return yield* acquisition.pipe(
|
|
710
|
+
Effect.catchCause((cause) =>
|
|
711
|
+
Effect.gen(function* () {
|
|
712
|
+
yield* Scope.close(scope, Exit.failCause(cause)).pipe(Effect.ignoreCause);
|
|
713
|
+
return yield* Effect.failCause(cause);
|
|
714
|
+
})
|
|
715
|
+
),
|
|
716
|
+
);
|
|
717
|
+
});
|
|
516
718
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
session = undefined;
|
|
523
|
-
return handle;
|
|
524
|
-
} catch (error) {
|
|
525
|
-
if (session) disposeSession(session);
|
|
526
|
-
disposeResourceLoader(resourceLoader);
|
|
527
|
-
throw error;
|
|
528
|
-
}
|
|
529
|
-
},
|
|
530
|
-
};
|
|
719
|
+
export function createWorkerSessionFactory(
|
|
720
|
+
overrides: Partial<WorkerSessionDependencies> = {},
|
|
721
|
+
): WorkerSessionFactory {
|
|
722
|
+
const dependencies: WorkerSessionDependencies = { ...defaultDependencies, ...overrides };
|
|
723
|
+
return { create: (options) => Effect.runPromise(createWorkerSession(options, dependencies)) };
|
|
531
724
|
}
|