@zachwill/pi-orchestrate 0.2.1 → 0.4.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/README.md +77 -48
- package/examples/workers/investigator.md +5 -31
- package/examples/workers/scout.md +5 -26
- package/examples/workers/web.md +84 -0
- package/examples/workers/worker.md +9 -35
- package/extension/catalog.ts +40 -22
- package/extension/contract.ts +4 -4
- package/extension/delivery.ts +72 -16
- package/extension/domain.ts +21 -55
- package/extension/host.ts +1 -37
- package/extension/index.ts +39 -11
- package/extension/presentation.ts +62 -158
- package/extension/runtime.ts +239 -331
- package/extension/tools.ts +206 -214
- package/extension/worker-session.ts +213 -50
- package/extension/worker-settlement.ts +106 -0
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
DefaultPackageManager,
|
|
11
11
|
ModelRuntime,
|
|
12
12
|
type ModelRegistry,
|
|
13
|
+
type PromptOptions,
|
|
13
14
|
type ResourceLoader,
|
|
14
15
|
SessionManager,
|
|
15
16
|
SettingsManager,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type CreateAgentSessionResult,
|
|
19
20
|
type DefaultResourceLoader,
|
|
20
21
|
} from "@earendil-works/pi-coding-agent";
|
|
21
|
-
import { Effect, Exit, Schema, Scope } from "effect";
|
|
22
|
+
import { Cause, Effect, Exit, Schema, Scope } from "effect";
|
|
22
23
|
import type {
|
|
23
24
|
WorkerDefinition,
|
|
24
25
|
WorkerMessageDirection,
|
|
@@ -27,13 +28,14 @@ import type {
|
|
|
27
28
|
} from "./domain.js";
|
|
28
29
|
|
|
29
30
|
const DIRECT_CHILD_BOUNDARY =
|
|
30
|
-
"You are a direct child worker session. Do not spawn, delegate to, or orchestrate
|
|
31
|
+
"You are a direct child worker session. Do not spawn, delegate to, or orchestrate descendant Pi worker sessions. Complete the assigned task yourself and return the result directly to the parent orchestrator.";
|
|
31
32
|
const ORCHESTRATE_PACKAGE_ROOT = canonicalPath(fileURLToPath(new URL("..", import.meta.url)));
|
|
32
33
|
|
|
33
34
|
interface WorkerAgentSession {
|
|
34
35
|
readonly sessionFile: string | undefined;
|
|
35
36
|
readonly messages: AgentMessage[];
|
|
36
|
-
prompt(instructions: string): Promise<void>;
|
|
37
|
+
prompt(instructions: string, options?: PromptOptions): Promise<void>;
|
|
38
|
+
abortCompaction(): void;
|
|
37
39
|
abort(): Promise<void>;
|
|
38
40
|
dispose(): void;
|
|
39
41
|
bindExtensions(bindings: { mode: "print" }): Promise<void>;
|
|
@@ -75,21 +77,66 @@ export interface WorkerSessionFactory {
|
|
|
75
77
|
create(options: WorkerSessionFactoryOptions): Promise<WorkerSessionHandle>;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
const WorkerModelAcquisitionOperation = Schema.Literals([
|
|
81
|
+
"select-model",
|
|
82
|
+
"create-model-runtime",
|
|
83
|
+
"register-providers",
|
|
84
|
+
"refresh-providers",
|
|
85
|
+
"resolve-model",
|
|
86
|
+
"authenticate-model",
|
|
87
|
+
"configure-runtime-auth",
|
|
88
|
+
]);
|
|
89
|
+
type WorkerModelAcquisitionOperation = typeof WorkerModelAcquisitionOperation.Type;
|
|
90
|
+
|
|
91
|
+
const WorkerResourceAcquisitionOperation = Schema.Literals([
|
|
92
|
+
"create-settings",
|
|
93
|
+
"resolve-extensions",
|
|
94
|
+
"create-services",
|
|
95
|
+
"validate-skills",
|
|
96
|
+
]);
|
|
97
|
+
type WorkerResourceAcquisitionOperation = typeof WorkerResourceAcquisitionOperation.Type;
|
|
98
|
+
|
|
99
|
+
const WorkerAgentSessionAcquisitionOperation = Schema.Literals([
|
|
100
|
+
"create-session-manager",
|
|
101
|
+
"create-session",
|
|
102
|
+
"create-runtime",
|
|
103
|
+
"bind-extensions",
|
|
104
|
+
"verify-durability",
|
|
105
|
+
"subscribe-events",
|
|
106
|
+
]);
|
|
107
|
+
type WorkerAgentSessionAcquisitionOperation = typeof WorkerAgentSessionAcquisitionOperation.Type;
|
|
108
|
+
|
|
78
109
|
export class WorkerModelAcquisitionError extends Schema.TaggedErrorClass<WorkerModelAcquisitionError>()(
|
|
79
110
|
"WorkerSession.ModelAcquisitionError",
|
|
80
|
-
{ message: Schema.String, cause: Schema.Defect() },
|
|
111
|
+
{ operation: WorkerModelAcquisitionOperation, message: Schema.String, cause: Schema.Defect() },
|
|
81
112
|
) {}
|
|
82
113
|
|
|
83
114
|
export class WorkerResourceAcquisitionError extends Schema.TaggedErrorClass<WorkerResourceAcquisitionError>()(
|
|
84
115
|
"WorkerSession.ResourceAcquisitionError",
|
|
85
|
-
{ message: Schema.String, cause: Schema.Defect() },
|
|
116
|
+
{ operation: WorkerResourceAcquisitionOperation, message: Schema.String, cause: Schema.Defect() },
|
|
86
117
|
) {}
|
|
87
118
|
|
|
88
119
|
export class WorkerAgentSessionAcquisitionError extends Schema.TaggedErrorClass<WorkerAgentSessionAcquisitionError>()(
|
|
89
120
|
"WorkerSession.AgentSessionAcquisitionError",
|
|
90
|
-
{ message: Schema.String, cause: Schema.Defect() },
|
|
121
|
+
{ operation: WorkerAgentSessionAcquisitionOperation, message: Schema.String, cause: Schema.Defect() },
|
|
91
122
|
) {}
|
|
92
123
|
|
|
124
|
+
const WorkerSessionCleanupOperation = Schema.Literals([
|
|
125
|
+
"unsubscribe",
|
|
126
|
+
"runtime",
|
|
127
|
+
"raw-session",
|
|
128
|
+
"resource-loader",
|
|
129
|
+
"scope-close",
|
|
130
|
+
]);
|
|
131
|
+
export type WorkerSessionCleanupOperation = typeof WorkerSessionCleanupOperation.Type;
|
|
132
|
+
|
|
133
|
+
export interface WorkerSessionCleanupFailure {
|
|
134
|
+
readonly operation: WorkerSessionCleanupOperation;
|
|
135
|
+
readonly cause: unknown;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type WorkerSessionCleanupReporter = (failure: WorkerSessionCleanupFailure) => void;
|
|
139
|
+
|
|
93
140
|
interface SettingsManagerInput {
|
|
94
141
|
cwd: string;
|
|
95
142
|
agentDir: string;
|
|
@@ -137,6 +184,7 @@ export interface WorkerSessionDependencies {
|
|
|
137
184
|
createServices(input: ServicesInput): Promise<AgentSessionServices>;
|
|
138
185
|
createAgentSession(input: AgentSessionInput): Promise<{ session: WorkerAgentSession }>;
|
|
139
186
|
createRuntime(input: RuntimeInput): OwnedWorkerRuntime;
|
|
187
|
+
reportCleanupFailure(failure: WorkerSessionCleanupFailure): void;
|
|
140
188
|
}
|
|
141
189
|
|
|
142
190
|
const defaultDependencies: WorkerSessionDependencies = {
|
|
@@ -163,6 +211,12 @@ const defaultDependencies: WorkerSessionDependencies = {
|
|
|
163
211
|
throw new Error("Worker sessions do not support runtime replacement");
|
|
164
212
|
},
|
|
165
213
|
) as unknown as OwnedWorkerRuntime,
|
|
214
|
+
reportCleanupFailure: ({ operation }) => {
|
|
215
|
+
process.emitWarning("Worker session cleanup failed", {
|
|
216
|
+
code: "PI_ORCHESTRATE_WORKER_CLEANUP",
|
|
217
|
+
detail: `Operation: ${operation}`,
|
|
218
|
+
});
|
|
219
|
+
},
|
|
166
220
|
};
|
|
167
221
|
|
|
168
222
|
export function resolveWorkerModel(
|
|
@@ -204,11 +258,41 @@ export function isOrchestrationExtensionPath(path: string): boolean {
|
|
|
204
258
|
return isWithin(canonicalPath(path), ORCHESTRATE_PACKAGE_ROOT);
|
|
205
259
|
}
|
|
206
260
|
|
|
207
|
-
function
|
|
261
|
+
function reportCleanupFailure(
|
|
262
|
+
reporter: WorkerSessionCleanupReporter,
|
|
263
|
+
operation: WorkerSessionCleanupOperation,
|
|
264
|
+
cause: unknown,
|
|
265
|
+
): Effect.Effect<void> {
|
|
208
266
|
return Effect.sync(() => {
|
|
267
|
+
try {
|
|
268
|
+
reporter({ operation, cause });
|
|
269
|
+
} catch {
|
|
270
|
+
// Reporting must never make best-effort cleanup fail.
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function bestEffortCleanup(
|
|
276
|
+
reporter: WorkerSessionCleanupReporter,
|
|
277
|
+
operation: WorkerSessionCleanupOperation,
|
|
278
|
+
cleanup: () => void | Promise<void>,
|
|
279
|
+
): Effect.Effect<void> {
|
|
280
|
+
return Effect.tryPromise({
|
|
281
|
+
try: async () => cleanup(),
|
|
282
|
+
catch: (cause) => cause,
|
|
283
|
+
}).pipe(
|
|
284
|
+
Effect.catch((cause) => reportCleanupFailure(reporter, operation, cause)),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function resourceLoaderFinalizer(
|
|
289
|
+
resourceLoader: ResourceLoader,
|
|
290
|
+
reporter: WorkerSessionCleanupReporter,
|
|
291
|
+
): Effect.Effect<void> {
|
|
292
|
+
return bestEffortCleanup(reporter, "resource-loader", () => {
|
|
209
293
|
if (!("dispose" in resourceLoader) || typeof resourceLoader.dispose !== "function") return;
|
|
210
294
|
resourceLoader.dispose();
|
|
211
|
-
})
|
|
295
|
+
});
|
|
212
296
|
}
|
|
213
297
|
|
|
214
298
|
function describeError(error: unknown, fallback: string): string {
|
|
@@ -267,6 +351,7 @@ class DefaultWorkerSessionHandle implements WorkerSessionHandle {
|
|
|
267
351
|
private readonly reusable: boolean,
|
|
268
352
|
sessionFile: string,
|
|
269
353
|
private readonly scope: Scope.Closeable,
|
|
354
|
+
private readonly cleanupReporter: WorkerSessionCleanupReporter,
|
|
270
355
|
) {
|
|
271
356
|
this.sessionFile = sessionFile;
|
|
272
357
|
}
|
|
@@ -323,7 +408,10 @@ class DefaultWorkerSessionHandle implements WorkerSessionHandle {
|
|
|
323
408
|
const previousAssistant = lastAssistant(this.runtime.session.messages);
|
|
324
409
|
let failureMessage: string | undefined;
|
|
325
410
|
try {
|
|
326
|
-
await this.runtime.session.prompt(instructions
|
|
411
|
+
await this.runtime.session.prompt(instructions, {
|
|
412
|
+
expandPromptTemplates: false,
|
|
413
|
+
source: "extension",
|
|
414
|
+
});
|
|
327
415
|
} catch (error) {
|
|
328
416
|
failureMessage = describeError(error, "Worker prompt failed");
|
|
329
417
|
} finally {
|
|
@@ -346,7 +434,21 @@ class DefaultWorkerSessionHandle implements WorkerSessionHandle {
|
|
|
346
434
|
|
|
347
435
|
async abort(): Promise<void> {
|
|
348
436
|
if (this.prompting) this.abortRequested = true;
|
|
349
|
-
|
|
437
|
+
|
|
438
|
+
let firstFailure: unknown;
|
|
439
|
+
try {
|
|
440
|
+
this.runtime.session.abortCompaction();
|
|
441
|
+
} catch (error) {
|
|
442
|
+
firstFailure = error;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
await this.runtime.session.abort();
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (firstFailure === undefined) firstFailure = error;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (firstFailure !== undefined) throw firstFailure;
|
|
350
452
|
}
|
|
351
453
|
|
|
352
454
|
dispose(): Promise<void> {
|
|
@@ -357,7 +459,9 @@ class DefaultWorkerSessionHandle implements WorkerSessionHandle {
|
|
|
357
459
|
this.usageListeners.clear();
|
|
358
460
|
this.activityListeners.clear();
|
|
359
461
|
this.messageDirectionListeners.clear();
|
|
360
|
-
this.disposePromise = Effect.runPromise(
|
|
462
|
+
this.disposePromise = Effect.runPromise(
|
|
463
|
+
disposeWorkerSession(this.scope, this.cleanupReporter),
|
|
464
|
+
);
|
|
361
465
|
return this.disposePromise;
|
|
362
466
|
}
|
|
363
467
|
|
|
@@ -395,19 +499,32 @@ function selectedModelCoordinates(
|
|
|
395
499
|
throw new Error(`Worker "${definition.name}" has no configured model and no parent model is available`);
|
|
396
500
|
}
|
|
397
501
|
|
|
398
|
-
function modelAcquisitionError(
|
|
399
|
-
|
|
502
|
+
function modelAcquisitionError(
|
|
503
|
+
operation: WorkerModelAcquisitionOperation,
|
|
504
|
+
fallback: string,
|
|
505
|
+
): (cause: unknown) => WorkerModelAcquisitionError {
|
|
506
|
+
return (cause) => new WorkerModelAcquisitionError({
|
|
507
|
+
operation,
|
|
508
|
+
message: describeError(cause, fallback),
|
|
509
|
+
cause,
|
|
510
|
+
});
|
|
400
511
|
}
|
|
401
512
|
|
|
402
|
-
function resourceAcquisitionError(
|
|
403
|
-
|
|
513
|
+
function resourceAcquisitionError(
|
|
514
|
+
operation: WorkerResourceAcquisitionOperation,
|
|
515
|
+
): (cause: unknown) => WorkerResourceAcquisitionError {
|
|
516
|
+
return (cause) => new WorkerResourceAcquisitionError({
|
|
517
|
+
operation,
|
|
404
518
|
message: describeError(cause, "Worker resource acquisition failed"),
|
|
405
519
|
cause,
|
|
406
520
|
});
|
|
407
521
|
}
|
|
408
522
|
|
|
409
|
-
function agentSessionAcquisitionError(
|
|
410
|
-
|
|
523
|
+
function agentSessionAcquisitionError(
|
|
524
|
+
operation: WorkerAgentSessionAcquisitionOperation,
|
|
525
|
+
): (cause: unknown) => WorkerAgentSessionAcquisitionError {
|
|
526
|
+
return (cause) => new WorkerAgentSessionAcquisitionError({
|
|
527
|
+
operation,
|
|
411
528
|
message: describeError(cause, "Worker agent session acquisition failed"),
|
|
412
529
|
cause,
|
|
413
530
|
});
|
|
@@ -417,20 +534,24 @@ const refreshModelRuntime = Effect.fn("WorkerSession.refreshModelRuntime")(funct
|
|
|
417
534
|
modelRuntime: ModelRuntime,
|
|
418
535
|
definition: WorkerDefinition,
|
|
419
536
|
) {
|
|
537
|
+
const refreshError = modelAcquisitionError(
|
|
538
|
+
"refresh-providers",
|
|
539
|
+
"Worker model refresh failed",
|
|
540
|
+
);
|
|
420
541
|
const result = yield* Effect.tryPromise({
|
|
421
542
|
try: () => modelRuntime.refresh({ allowNetwork: false }),
|
|
422
|
-
catch:
|
|
543
|
+
catch: refreshError,
|
|
423
544
|
});
|
|
424
545
|
if (result.errors.size > 0) {
|
|
425
546
|
const errors = [...result.errors].map(([id, error]) => `${id}: ${error.message}`).join("; ");
|
|
426
547
|
const cause = new Error(
|
|
427
548
|
`Worker "${definition.name}" failed to refresh child model providers: ${errors}`,
|
|
428
549
|
);
|
|
429
|
-
return yield* Effect.fail(
|
|
550
|
+
return yield* Effect.fail(refreshError(cause));
|
|
430
551
|
}
|
|
431
552
|
if (result.aborted) {
|
|
432
553
|
const cause = new Error(`Worker "${definition.name}" child model refresh was aborted`);
|
|
433
|
-
return yield* Effect.fail(
|
|
554
|
+
return yield* Effect.fail(refreshError(cause));
|
|
434
555
|
}
|
|
435
556
|
});
|
|
436
557
|
|
|
@@ -440,14 +561,14 @@ const prepareChildModelRuntime = Effect.fn("WorkerSession.prepareChildModelRunti
|
|
|
440
561
|
) {
|
|
441
562
|
const selected = yield* Effect.try({
|
|
442
563
|
try: () => selectedModelCoordinates(options.definition, options.parentModel),
|
|
443
|
-
catch:
|
|
564
|
+
catch: modelAcquisitionError("select-model", "Worker model selection failed"),
|
|
444
565
|
});
|
|
445
566
|
const modelRuntime = yield* Effect.tryPromise({
|
|
446
567
|
try: () => dependencies.createModelRuntime({
|
|
447
568
|
authPath: join(options.agentDir, "auth.json"),
|
|
448
569
|
modelsPath: join(options.agentDir, "models.json"),
|
|
449
570
|
}),
|
|
450
|
-
catch:
|
|
571
|
+
catch: modelAcquisitionError("create-model-runtime", "Worker model runtime creation failed"),
|
|
451
572
|
});
|
|
452
573
|
|
|
453
574
|
yield* Effect.try({
|
|
@@ -457,18 +578,20 @@ const prepareChildModelRuntime = Effect.fn("WorkerSession.prepareChildModelRunti
|
|
|
457
578
|
if (config) modelRuntime.registerProvider(providerId, { ...config });
|
|
458
579
|
}
|
|
459
580
|
},
|
|
460
|
-
catch:
|
|
581
|
+
catch: modelAcquisitionError("register-providers", "Worker model provider registration failed"),
|
|
461
582
|
});
|
|
583
|
+
// Worker-session owns this pre-service refresh so dynamic parent providers exist
|
|
584
|
+
// before selected-model authentication is copied.
|
|
462
585
|
yield* refreshModelRuntime(modelRuntime, options.definition);
|
|
463
586
|
|
|
464
587
|
const model = yield* Effect.try({
|
|
465
588
|
try: () => modelRuntime.getModel(selected.provider, selected.modelId),
|
|
466
|
-
catch:
|
|
589
|
+
catch: modelAcquisitionError("resolve-model", "Worker model resolution failed"),
|
|
467
590
|
});
|
|
468
591
|
if (model) {
|
|
469
592
|
const auth = yield* Effect.tryPromise({
|
|
470
593
|
try: () => options.modelRegistry.getApiKeyAndHeaders(model),
|
|
471
|
-
catch:
|
|
594
|
+
catch: modelAcquisitionError("authenticate-model", "Worker model authentication failed"),
|
|
472
595
|
});
|
|
473
596
|
if (auth.ok) {
|
|
474
597
|
yield* Effect.tryPromise({
|
|
@@ -480,7 +603,7 @@ const prepareChildModelRuntime = Effect.fn("WorkerSession.prepareChildModelRunti
|
|
|
480
603
|
await modelRuntime.setRuntimeApiKey(selected.provider, auth.apiKey);
|
|
481
604
|
}
|
|
482
605
|
},
|
|
483
|
-
catch:
|
|
606
|
+
catch: modelAcquisitionError("configure-runtime-auth", "Worker model runtime key setup failed"),
|
|
484
607
|
});
|
|
485
608
|
}
|
|
486
609
|
}
|
|
@@ -515,8 +638,13 @@ function contextLoaderOptions(
|
|
|
515
638
|
|
|
516
639
|
const disposeWorkerSession = Effect.fn("WorkerSession.dispose")(function* (
|
|
517
640
|
scope: Scope.Closeable,
|
|
641
|
+
reporter: WorkerSessionCleanupReporter,
|
|
518
642
|
) {
|
|
519
|
-
yield* Scope.close(scope, Exit.void).pipe(
|
|
643
|
+
yield* Scope.close(scope, Exit.void).pipe(
|
|
644
|
+
Effect.catchCause((cause) =>
|
|
645
|
+
reportCleanupFailure(reporter, "scope-close", Cause.squash(cause))
|
|
646
|
+
),
|
|
647
|
+
);
|
|
520
648
|
});
|
|
521
649
|
|
|
522
650
|
const acquireWorkerServices = Effect.fn("WorkerSession.acquireServices")(function* (
|
|
@@ -532,7 +660,7 @@ const acquireWorkerServices = Effect.fn("WorkerSession.acquireServices")(functio
|
|
|
532
660
|
projectTrusted: options.projectTrusted,
|
|
533
661
|
compaction: definition.compaction,
|
|
534
662
|
}),
|
|
535
|
-
catch: resourceAcquisitionError,
|
|
663
|
+
catch: resourceAcquisitionError("create-settings"),
|
|
536
664
|
});
|
|
537
665
|
const extensionPaths = yield* Effect.tryPromise({
|
|
538
666
|
try: () => dependencies.resolveExtensionPaths({
|
|
@@ -540,7 +668,7 @@ const acquireWorkerServices = Effect.fn("WorkerSession.acquireServices")(functio
|
|
|
540
668
|
agentDir: options.agentDir,
|
|
541
669
|
settingsManager,
|
|
542
670
|
}),
|
|
543
|
-
catch: resourceAcquisitionError,
|
|
671
|
+
catch: resourceAcquisitionError("resolve-extensions"),
|
|
544
672
|
}).pipe(Effect.map((paths) => paths.filter((path) => !isOrchestrationExtensionPath(path))));
|
|
545
673
|
|
|
546
674
|
return yield* Effect.acquireRelease(
|
|
@@ -562,9 +690,12 @@ const acquireWorkerServices = Effect.fn("WorkerSession.acquireServices")(functio
|
|
|
562
690
|
appendSystemPrompt: [definition.systemPrompt, DIRECT_CHILD_BOUNDARY],
|
|
563
691
|
},
|
|
564
692
|
}),
|
|
565
|
-
catch: resourceAcquisitionError,
|
|
693
|
+
catch: resourceAcquisitionError("create-services"),
|
|
566
694
|
}),
|
|
567
|
-
(services) => resourceLoaderFinalizer(
|
|
695
|
+
(services) => resourceLoaderFinalizer(
|
|
696
|
+
services.resourceLoader,
|
|
697
|
+
dependencies.reportCleanupFailure,
|
|
698
|
+
),
|
|
568
699
|
);
|
|
569
700
|
});
|
|
570
701
|
|
|
@@ -573,16 +704,28 @@ interface AgentSessionOwnership {
|
|
|
573
704
|
runtime: OwnedWorkerRuntime | undefined;
|
|
574
705
|
}
|
|
575
706
|
|
|
576
|
-
function rawSessionFinalizer(
|
|
577
|
-
|
|
707
|
+
function rawSessionFinalizer(
|
|
708
|
+
session: WorkerAgentSession,
|
|
709
|
+
reporter: WorkerSessionCleanupReporter,
|
|
710
|
+
): Effect.Effect<void> {
|
|
711
|
+
return bestEffortCleanup(reporter, "raw-session", () => session.dispose());
|
|
578
712
|
}
|
|
579
713
|
|
|
580
|
-
function agentSessionFinalizer(
|
|
714
|
+
function agentSessionFinalizer(
|
|
715
|
+
ownership: AgentSessionOwnership,
|
|
716
|
+
reporter: WorkerSessionCleanupReporter,
|
|
717
|
+
): Effect.Effect<void> {
|
|
581
718
|
const runtime = ownership.runtime;
|
|
582
|
-
if (!runtime) return rawSessionFinalizer(ownership.session);
|
|
583
|
-
return Effect.tryPromise(
|
|
584
|
-
|
|
585
|
-
|
|
719
|
+
if (!runtime) return rawSessionFinalizer(ownership.session, reporter);
|
|
720
|
+
return Effect.tryPromise({
|
|
721
|
+
try: () => runtime.dispose(),
|
|
722
|
+
catch: (cause) => cause,
|
|
723
|
+
}).pipe(
|
|
724
|
+
Effect.catch((cause) =>
|
|
725
|
+
reportCleanupFailure(reporter, "runtime", cause).pipe(
|
|
726
|
+
Effect.andThen(rawSessionFinalizer(ownership.session, reporter)),
|
|
727
|
+
)
|
|
728
|
+
),
|
|
586
729
|
);
|
|
587
730
|
}
|
|
588
731
|
|
|
@@ -593,12 +736,15 @@ const acquireAgentSession = Effect.fn("WorkerSession.acquireAgentSession")(funct
|
|
|
593
736
|
return yield* Effect.acquireRelease(
|
|
594
737
|
Effect.tryPromise({
|
|
595
738
|
try: () => dependencies.createAgentSession(input),
|
|
596
|
-
catch: agentSessionAcquisitionError,
|
|
739
|
+
catch: agentSessionAcquisitionError("create-session"),
|
|
597
740
|
}).pipe(Effect.map(({ session }) => {
|
|
598
741
|
const ownership: AgentSessionOwnership = { session, runtime: undefined };
|
|
599
742
|
return ownership;
|
|
600
743
|
})),
|
|
601
|
-
agentSessionFinalizer
|
|
744
|
+
(ownership) => agentSessionFinalizer(
|
|
745
|
+
ownership,
|
|
746
|
+
dependencies.reportCleanupFailure,
|
|
747
|
+
),
|
|
602
748
|
);
|
|
603
749
|
});
|
|
604
750
|
|
|
@@ -609,22 +755,27 @@ const acquireAgentSessionRuntime = Effect.fn("WorkerSession.acquireAgentSessionR
|
|
|
609
755
|
) {
|
|
610
756
|
const runtime = yield* Effect.try({
|
|
611
757
|
try: () => dependencies.createRuntime({ session: ownership.session, services }),
|
|
612
|
-
catch: agentSessionAcquisitionError,
|
|
758
|
+
catch: agentSessionAcquisitionError("create-runtime"),
|
|
613
759
|
});
|
|
614
760
|
ownership.runtime = runtime;
|
|
615
761
|
return runtime;
|
|
616
762
|
});
|
|
617
763
|
|
|
618
764
|
const acquireSessionSubscription = Effect.fn("WorkerSession.acquireSubscription")(function* (
|
|
765
|
+
dependencies: WorkerSessionDependencies,
|
|
619
766
|
runtime: OwnedWorkerRuntime,
|
|
620
767
|
handle: DefaultWorkerSessionHandle,
|
|
621
768
|
) {
|
|
622
769
|
return yield* Effect.acquireRelease(
|
|
623
770
|
Effect.try({
|
|
624
771
|
try: () => runtime.session.subscribe((event) => handle.receiveSessionEvent(event)),
|
|
625
|
-
catch: agentSessionAcquisitionError,
|
|
772
|
+
catch: agentSessionAcquisitionError("subscribe-events"),
|
|
626
773
|
}),
|
|
627
|
-
(unsubscribe) =>
|
|
774
|
+
(unsubscribe) => bestEffortCleanup(
|
|
775
|
+
dependencies.reportCleanupFailure,
|
|
776
|
+
"unsubscribe",
|
|
777
|
+
unsubscribe,
|
|
778
|
+
),
|
|
628
779
|
);
|
|
629
780
|
});
|
|
630
781
|
|
|
@@ -638,6 +789,9 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
|
638
789
|
const { selected, modelRuntime } = yield* prepareChildModelRuntime(options, dependencies);
|
|
639
790
|
const services = yield* acquireWorkerServices(options, dependencies, modelRuntime);
|
|
640
791
|
|
|
792
|
+
// createAgentSessionServices owns extension-provider registration and refresh, but
|
|
793
|
+
// Pi 0.80.10 discards that refresh result. Keep this worker-owned probe so provider
|
|
794
|
+
// errors and aborts remain typed acquisition failures before session creation.
|
|
641
795
|
yield* refreshModelRuntime(modelRuntime, definition);
|
|
642
796
|
const model = yield* Effect.try({
|
|
643
797
|
try: () => {
|
|
@@ -649,14 +803,14 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
|
649
803
|
}
|
|
650
804
|
return resolvedModel;
|
|
651
805
|
},
|
|
652
|
-
catch:
|
|
806
|
+
catch: modelAcquisitionError("resolve-model", "Worker model resolution failed"),
|
|
653
807
|
});
|
|
654
808
|
const sessionManager = yield* Effect.try({
|
|
655
809
|
try: () => dependencies.createSessionManager({
|
|
656
810
|
cwd: options.cwd,
|
|
657
811
|
parentSessionFile: options.parentSessionFile,
|
|
658
812
|
}),
|
|
659
|
-
catch: agentSessionAcquisitionError,
|
|
813
|
+
catch: agentSessionAcquisitionError("create-session-manager"),
|
|
660
814
|
});
|
|
661
815
|
const ownership = yield* acquireAgentSession(dependencies, {
|
|
662
816
|
services,
|
|
@@ -668,7 +822,7 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
|
668
822
|
const runtime = yield* acquireAgentSessionRuntime(dependencies, ownership, services);
|
|
669
823
|
yield* Effect.tryPromise({
|
|
670
824
|
try: () => runtime.session.bindExtensions({ mode: "print" }),
|
|
671
|
-
catch: agentSessionAcquisitionError,
|
|
825
|
+
catch: agentSessionAcquisitionError("bind-extensions"),
|
|
672
826
|
});
|
|
673
827
|
|
|
674
828
|
yield* Effect.try({
|
|
@@ -684,7 +838,7 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
|
684
838
|
);
|
|
685
839
|
}
|
|
686
840
|
},
|
|
687
|
-
catch: resourceAcquisitionError,
|
|
841
|
+
catch: resourceAcquisitionError("validate-skills"),
|
|
688
842
|
});
|
|
689
843
|
const sessionFile = yield* Effect.try({
|
|
690
844
|
try: () => {
|
|
@@ -693,7 +847,7 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
|
693
847
|
}
|
|
694
848
|
return runtime.session.sessionFile;
|
|
695
849
|
},
|
|
696
|
-
catch: agentSessionAcquisitionError,
|
|
850
|
+
catch: agentSessionAcquisitionError("verify-durability"),
|
|
697
851
|
});
|
|
698
852
|
|
|
699
853
|
const handle = new DefaultWorkerSessionHandle(
|
|
@@ -701,15 +855,24 @@ const createWorkerSession = Effect.fn("WorkerSession.create")(function* (
|
|
|
701
855
|
definition.lifecycle === "reusable",
|
|
702
856
|
sessionFile,
|
|
703
857
|
scope,
|
|
858
|
+
dependencies.reportCleanupFailure,
|
|
704
859
|
);
|
|
705
|
-
yield* acquireSessionSubscription(runtime, handle);
|
|
860
|
+
yield* acquireSessionSubscription(dependencies, runtime, handle);
|
|
706
861
|
return handle;
|
|
707
862
|
}).pipe(Scope.provide(scope));
|
|
708
863
|
|
|
709
864
|
return yield* acquisition.pipe(
|
|
710
865
|
Effect.catchCause((cause) =>
|
|
711
866
|
Effect.gen(function* () {
|
|
712
|
-
yield* Scope.close(scope, Exit.failCause(cause)).pipe(
|
|
867
|
+
yield* Scope.close(scope, Exit.failCause(cause)).pipe(
|
|
868
|
+
Effect.catchCause((cleanupCause) =>
|
|
869
|
+
reportCleanupFailure(
|
|
870
|
+
dependencies.reportCleanupFailure,
|
|
871
|
+
"scope-close",
|
|
872
|
+
Cause.squash(cleanupCause),
|
|
873
|
+
)
|
|
874
|
+
),
|
|
875
|
+
);
|
|
713
876
|
return yield* Effect.failCause(cause);
|
|
714
877
|
})
|
|
715
878
|
),
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import type { RunId, WorkerId } from "./domain.js";
|
|
3
|
+
|
|
4
|
+
const NonnegativeFinite = Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0));
|
|
5
|
+
const NonnegativeInteger = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
6
|
+
|
|
7
|
+
const WorkerUsage = Schema.Struct({
|
|
8
|
+
input: NonnegativeFinite,
|
|
9
|
+
output: NonnegativeFinite,
|
|
10
|
+
cacheRead: NonnegativeFinite,
|
|
11
|
+
cacheWrite: NonnegativeFinite,
|
|
12
|
+
cost: NonnegativeFinite,
|
|
13
|
+
contextTokens: NonnegativeFinite,
|
|
14
|
+
turns: NonnegativeInteger,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const WorkerOutcome = Schema.Union([
|
|
18
|
+
Schema.Struct({
|
|
19
|
+
status: Schema.Literal("completed"),
|
|
20
|
+
assistantText: Schema.String,
|
|
21
|
+
}),
|
|
22
|
+
Schema.Struct({
|
|
23
|
+
status: Schema.Literal("ready"),
|
|
24
|
+
assistantText: Schema.String,
|
|
25
|
+
}),
|
|
26
|
+
Schema.Struct({
|
|
27
|
+
status: Schema.Literal("failed"),
|
|
28
|
+
message: Schema.String,
|
|
29
|
+
assistantText: Schema.optionalKey(Schema.String),
|
|
30
|
+
}),
|
|
31
|
+
Schema.Struct({
|
|
32
|
+
status: Schema.Literal("aborted"),
|
|
33
|
+
message: Schema.optionalKey(Schema.String),
|
|
34
|
+
assistantText: Schema.optionalKey(Schema.String),
|
|
35
|
+
}),
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const FailureStage = Schema.Literals([
|
|
39
|
+
"startup",
|
|
40
|
+
"prompt",
|
|
41
|
+
"workflow",
|
|
42
|
+
"cancellation",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/** Canonical schema for settlement details written by the current runtime. */
|
|
46
|
+
export const WorkerSettlementDetails = Schema.Struct({
|
|
47
|
+
eventId: Schema.String,
|
|
48
|
+
sequence: NonnegativeInteger,
|
|
49
|
+
ownerSessionId: Schema.String,
|
|
50
|
+
runId: Schema.String,
|
|
51
|
+
workerId: Schema.String,
|
|
52
|
+
generation: NonnegativeInteger,
|
|
53
|
+
mode: Schema.Literals(["async", "inline"]),
|
|
54
|
+
worker: Schema.String,
|
|
55
|
+
title: Schema.String,
|
|
56
|
+
lifecycle: Schema.Literals(["one-shot", "reusable"]),
|
|
57
|
+
status: Schema.Literals(["completed", "ready", "failed", "aborted"]),
|
|
58
|
+
outcome: WorkerOutcome,
|
|
59
|
+
usage: WorkerUsage,
|
|
60
|
+
startedAt: NonnegativeInteger,
|
|
61
|
+
settledAt: NonnegativeInteger,
|
|
62
|
+
synthesisGroupId: Schema.optionalKey(Schema.String),
|
|
63
|
+
synthesisGroupSize: Schema.optionalKey(NonnegativeInteger),
|
|
64
|
+
sessionFile: Schema.optionalKey(Schema.String),
|
|
65
|
+
failureStage: Schema.optionalKey(FailureStage),
|
|
66
|
+
}).check(
|
|
67
|
+
Schema.makeFilter((settlement) => {
|
|
68
|
+
if (settlement.outcome.status !== settlement.status) {
|
|
69
|
+
return "outcome status must match settlement status";
|
|
70
|
+
}
|
|
71
|
+
if (settlement.settledAt < settlement.startedAt) {
|
|
72
|
+
return "settlement timestamp must not precede start timestamp";
|
|
73
|
+
}
|
|
74
|
+
if (
|
|
75
|
+
settlement.failureStage !== undefined &&
|
|
76
|
+
settlement.status !== "failed" &&
|
|
77
|
+
settlement.status !== "aborted"
|
|
78
|
+
) {
|
|
79
|
+
return "failure stage requires a failed or aborted settlement";
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export interface WorkerSettlementDetails
|
|
85
|
+
extends Schema.Schema.Type<typeof WorkerSettlementDetails> {}
|
|
86
|
+
|
|
87
|
+
export type SettlementFailureStage = NonNullable<
|
|
88
|
+
WorkerSettlementDetails["failureStage"]
|
|
89
|
+
>;
|
|
90
|
+
|
|
91
|
+
/** Runtime refinement preserves the nominal IDs already allocated by the domain. */
|
|
92
|
+
export type WorkerSettlement = Omit<
|
|
93
|
+
WorkerSettlementDetails,
|
|
94
|
+
"runId" | "workerId"
|
|
95
|
+
> & {
|
|
96
|
+
readonly runId: RunId;
|
|
97
|
+
readonly workerId: WorkerId;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const decodeCurrentWorkerSettlement = Schema.decodeUnknownResult(
|
|
101
|
+
WorkerSettlementDetails,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export function decodePersistedWorkerSettlementDetails(value: unknown) {
|
|
105
|
+
return decodeCurrentWorkerSettlement(value);
|
|
106
|
+
}
|