@zachwill/pi-orchestrate 0.2.0 → 0.3.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 +9 -9
- 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 +241 -332
- package/extension/tools.ts +206 -214
- package/extension/worker-session.ts +190 -46
- package/extension/worker-settlement.ts +106 -0
- package/package.json +3 -2
package/extension/runtime.ts
CHANGED
|
@@ -4,20 +4,17 @@ import { Effect, Option } from "effect";
|
|
|
4
4
|
import {
|
|
5
5
|
CANCELLATION_GRACE_MS,
|
|
6
6
|
EMPTY_WORKER_USAGE,
|
|
7
|
-
MAX_TASKS_PER_WAVE,
|
|
8
7
|
MAX_WORKER_INSTRUCTIONS_LENGTH,
|
|
9
8
|
MAX_WORKER_TITLE_LENGTH,
|
|
10
9
|
createRandomIdFactories,
|
|
11
10
|
findWorkerByName,
|
|
12
11
|
isTerminalWorkerStatus,
|
|
13
|
-
isWorkerCompleteForWave,
|
|
14
12
|
transitionWorkerStatus,
|
|
15
13
|
type OrchestrateIdFactories,
|
|
16
14
|
type OrchestrateTaskInput,
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type WaveRecord,
|
|
15
|
+
type RunId,
|
|
16
|
+
type RunMode,
|
|
17
|
+
type RunRecord,
|
|
21
18
|
type WorkerCatalog,
|
|
22
19
|
type WorkerDefinition,
|
|
23
20
|
type WorkerId,
|
|
@@ -34,9 +31,18 @@ import {
|
|
|
34
31
|
type WorkerSessionFactory,
|
|
35
32
|
type WorkerSessionHandle,
|
|
36
33
|
} from "./worker-session.js";
|
|
34
|
+
import type {
|
|
35
|
+
SettlementFailureStage,
|
|
36
|
+
WorkerSettlement,
|
|
37
|
+
} from "./worker-settlement.js";
|
|
38
|
+
|
|
39
|
+
export type {
|
|
40
|
+
SettlementFailureStage,
|
|
41
|
+
WorkerSettlement,
|
|
42
|
+
} from "./worker-settlement.js";
|
|
37
43
|
|
|
38
44
|
export const MAX_TERMINAL_WORKER_HISTORY = 100;
|
|
39
|
-
export const
|
|
45
|
+
export const MAX_COMPLETED_RUN_HISTORY = 100;
|
|
40
46
|
/** Shutdown waits this long for interrupted bootstrap/prompt promises, then returns best-effort. */
|
|
41
47
|
export const SHUTDOWN_CLEANUP_GRACE_MS = CANCELLATION_GRACE_MS;
|
|
42
48
|
|
|
@@ -49,62 +55,41 @@ export interface OrchestrationContext {
|
|
|
49
55
|
readonly catalog: WorkerCatalog;
|
|
50
56
|
readonly parentModel?: Model<Api>;
|
|
51
57
|
readonly modelRegistry: ModelRegistry;
|
|
58
|
+
readonly synthesisGroup?: {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly size: number;
|
|
61
|
+
};
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
export interface
|
|
55
|
-
readonly id:
|
|
56
|
-
readonly
|
|
64
|
+
export interface AcceptedRun {
|
|
65
|
+
readonly id: RunId;
|
|
66
|
+
readonly workerId: WorkerId;
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
export interface CompletedResult {
|
|
60
70
|
readonly workerId: WorkerId;
|
|
61
71
|
readonly worker: string;
|
|
62
72
|
readonly title: string;
|
|
63
|
-
readonly status:
|
|
64
|
-
readonly outcome: WorkerOutcome
|
|
73
|
+
readonly status: "completed" | "ready" | "failed" | "aborted";
|
|
74
|
+
readonly outcome: Exclude<WorkerOutcome, { readonly status: "closed" }>;
|
|
65
75
|
readonly usage: WorkerUsage;
|
|
76
|
+
readonly startedAt: number;
|
|
77
|
+
readonly settledAt: number;
|
|
66
78
|
readonly sessionFile: string | undefined;
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
export interface
|
|
70
|
-
readonly id:
|
|
81
|
+
export interface CompletedRun {
|
|
82
|
+
readonly id: RunId;
|
|
71
83
|
readonly ownerSessionId: string;
|
|
72
|
-
readonly mode:
|
|
73
|
-
readonly
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type SettlementFailureStage = "startup" | "prompt" | "workflow" | "cancellation";
|
|
77
|
-
|
|
78
|
-
export interface WorkerSettlement {
|
|
79
|
-
readonly eventId: string;
|
|
80
|
-
readonly sequence: number;
|
|
81
|
-
readonly ownerSessionId: string;
|
|
82
|
-
readonly waveId: WaveId;
|
|
83
|
-
readonly workerId: WorkerId;
|
|
84
|
-
readonly generation: number;
|
|
85
|
-
readonly mode: WaveMode;
|
|
86
|
-
readonly worker: string;
|
|
87
|
-
readonly title: string;
|
|
88
|
-
readonly lifecycle: WorkerRecord["lifecycle"];
|
|
89
|
-
readonly status: Exclude<WaveCompleteWorkerStatus, "closed">;
|
|
90
|
-
readonly outcome: WorkerOutcome;
|
|
91
|
-
readonly failureStage?: SettlementFailureStage;
|
|
92
|
-
readonly usage: WorkerUsage;
|
|
93
|
-
readonly startedAt: number;
|
|
94
|
-
readonly settledAt: number;
|
|
95
|
-
readonly remainingActive: number;
|
|
96
|
-
readonly waveSize: number;
|
|
97
|
-
readonly waveComplete: boolean;
|
|
98
|
-
readonly sessionFile: string | undefined;
|
|
84
|
+
readonly mode: RunMode;
|
|
85
|
+
readonly result: CompletedResult;
|
|
99
86
|
}
|
|
100
87
|
|
|
101
88
|
export interface RuntimeSnapshot {
|
|
102
|
-
readonly
|
|
89
|
+
readonly runs: readonly RunRecord[];
|
|
103
90
|
readonly workers: readonly WorkerRecord[];
|
|
104
91
|
}
|
|
105
92
|
|
|
106
|
-
export type CompletionListener = (wave: CompletedWave) => void;
|
|
107
|
-
export type UnsubscribeCompletion = () => void;
|
|
108
93
|
export type SettlementListener = (settlement: WorkerSettlement) => void;
|
|
109
94
|
export type UnsubscribeSettlement = () => void;
|
|
110
95
|
export type StateListener = (ownerSessionId: string) => void;
|
|
@@ -112,18 +97,11 @@ export type StateListener = (ownerSessionId: string) => void;
|
|
|
112
97
|
export type AbortTarget =
|
|
113
98
|
| {
|
|
114
99
|
readonly workerIds: readonly WorkerId[];
|
|
115
|
-
readonly waveId?: never;
|
|
116
|
-
readonly all?: never;
|
|
117
|
-
}
|
|
118
|
-
| {
|
|
119
|
-
readonly waveId: WaveId;
|
|
120
|
-
readonly workerIds?: never;
|
|
121
100
|
readonly all?: never;
|
|
122
101
|
}
|
|
123
102
|
| {
|
|
124
103
|
readonly all: true;
|
|
125
104
|
readonly workerIds?: never;
|
|
126
|
-
readonly waveId?: never;
|
|
127
105
|
};
|
|
128
106
|
|
|
129
107
|
export type DeadlineResult = "settled" | "timed-out";
|
|
@@ -143,25 +121,25 @@ export interface OrchestratorRuntimeOptions {
|
|
|
143
121
|
export interface OrchestratorRuntime {
|
|
144
122
|
orchestrate(
|
|
145
123
|
context: OrchestrationContext,
|
|
146
|
-
|
|
124
|
+
task: OrchestrateTaskInput,
|
|
147
125
|
mode: "async",
|
|
148
126
|
signal?: AbortSignal,
|
|
149
127
|
onSettlement?: SettlementListener,
|
|
150
|
-
): Promise<
|
|
128
|
+
): Promise<AcceptedRun>;
|
|
151
129
|
orchestrate(
|
|
152
130
|
context: OrchestrationContext,
|
|
153
|
-
|
|
131
|
+
task: OrchestrateTaskInput,
|
|
154
132
|
mode: "inline",
|
|
155
133
|
signal?: AbortSignal,
|
|
156
134
|
onSettlement?: SettlementListener,
|
|
157
|
-
): Promise<
|
|
135
|
+
): Promise<CompletedRun>;
|
|
158
136
|
orchestrate(
|
|
159
137
|
context: OrchestrationContext,
|
|
160
|
-
|
|
161
|
-
mode:
|
|
138
|
+
task: OrchestrateTaskInput,
|
|
139
|
+
mode: RunMode,
|
|
162
140
|
signal?: AbortSignal,
|
|
163
141
|
onSettlement?: SettlementListener,
|
|
164
|
-
): Promise<
|
|
142
|
+
): Promise<AcceptedRun | CompletedRun>;
|
|
165
143
|
send(
|
|
166
144
|
context: OrchestrationContext,
|
|
167
145
|
workerId: WorkerId,
|
|
@@ -169,7 +147,7 @@ export interface OrchestratorRuntime {
|
|
|
169
147
|
mode: "async",
|
|
170
148
|
signal?: AbortSignal,
|
|
171
149
|
onSettlement?: SettlementListener,
|
|
172
|
-
): Promise<
|
|
150
|
+
): Promise<AcceptedRun>;
|
|
173
151
|
send(
|
|
174
152
|
context: OrchestrationContext,
|
|
175
153
|
workerId: WorkerId,
|
|
@@ -177,19 +155,18 @@ export interface OrchestratorRuntime {
|
|
|
177
155
|
mode: "inline",
|
|
178
156
|
signal?: AbortSignal,
|
|
179
157
|
onSettlement?: SettlementListener,
|
|
180
|
-
): Promise<
|
|
158
|
+
): Promise<CompletedRun>;
|
|
181
159
|
send(
|
|
182
160
|
context: OrchestrationContext,
|
|
183
161
|
workerId: WorkerId,
|
|
184
162
|
instructions: string,
|
|
185
|
-
mode:
|
|
163
|
+
mode: RunMode,
|
|
186
164
|
signal?: AbortSignal,
|
|
187
165
|
onSettlement?: SettlementListener,
|
|
188
|
-
): Promise<
|
|
166
|
+
): Promise<AcceptedRun | CompletedRun>;
|
|
189
167
|
abort(ownerSessionId: string, target: AbortTarget): Promise<void>;
|
|
190
168
|
close(ownerSessionId: string, workerId: WorkerId): Promise<void>;
|
|
191
169
|
snapshot(ownerSessionId: string): Promise<RuntimeSnapshot>;
|
|
192
|
-
subscribeCompletion(listener: CompletionListener): UnsubscribeCompletion;
|
|
193
170
|
subscribeSettlement(listener: SettlementListener): UnsubscribeSettlement;
|
|
194
171
|
subscribeState(listener: StateListener): () => void;
|
|
195
172
|
shutdown(): Promise<void>;
|
|
@@ -205,11 +182,11 @@ interface RuntimeEntry {
|
|
|
205
182
|
unsubscribeMessageDirection?: () => void;
|
|
206
183
|
}
|
|
207
184
|
|
|
208
|
-
interface
|
|
209
|
-
readonly promise: Promise<
|
|
185
|
+
interface RunWaiter {
|
|
186
|
+
readonly promise: Promise<CompletedRun>;
|
|
210
187
|
settled: boolean;
|
|
211
188
|
onSettled?: () => void;
|
|
212
|
-
resolve(
|
|
189
|
+
resolve(run: CompletedRun): void;
|
|
213
190
|
}
|
|
214
191
|
|
|
215
192
|
const defaultBestEffortDeadline: BestEffortDeadline = {
|
|
@@ -234,17 +211,16 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
234
211
|
private readonly scheduler: WorkflowScheduler<WorkerId>;
|
|
235
212
|
private readonly bestEffortDeadline: BestEffortDeadline;
|
|
236
213
|
private readonly workers = new Map<WorkerId, WorkerRecord>();
|
|
237
|
-
private readonly
|
|
214
|
+
private readonly runs = new Map<RunId, RunRecord>();
|
|
238
215
|
private readonly entries = new Map<WorkerId, RuntimeEntry>();
|
|
239
|
-
private readonly
|
|
240
|
-
private readonly
|
|
216
|
+
private readonly runWaiters = new Map<RunId, RunWaiter>();
|
|
217
|
+
private readonly completedRuns = new Map<RunId, CompletedRun>();
|
|
241
218
|
private readonly cancellationPromises = new Map<WorkerId, Promise<void>>();
|
|
242
219
|
private readonly cleanupOperations = new Set<Promise<void>>();
|
|
243
220
|
private readonly terminalWorkerOrder: WorkerId[] = [];
|
|
244
|
-
private readonly
|
|
245
|
-
private readonly completionListeners = new Set<CompletionListener>();
|
|
221
|
+
private readonly completedRunOrder: RunId[] = [];
|
|
246
222
|
private readonly settlementListeners = new Set<SettlementListener>();
|
|
247
|
-
private readonly
|
|
223
|
+
private readonly runSettlementListeners = new Map<RunId, SettlementListener>();
|
|
248
224
|
private readonly stateListeners = new Set<StateListener>();
|
|
249
225
|
private settlementSequence = 0;
|
|
250
226
|
private readonly disposedSessions = new WeakSet<WorkerSessionHandle>();
|
|
@@ -261,86 +237,81 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
261
237
|
|
|
262
238
|
orchestrate(
|
|
263
239
|
context: OrchestrationContext,
|
|
264
|
-
|
|
240
|
+
task: OrchestrateTaskInput,
|
|
265
241
|
mode: "async",
|
|
266
242
|
signal?: AbortSignal,
|
|
267
243
|
onSettlement?: SettlementListener,
|
|
268
|
-
): Promise<
|
|
244
|
+
): Promise<AcceptedRun>;
|
|
269
245
|
orchestrate(
|
|
270
246
|
context: OrchestrationContext,
|
|
271
|
-
|
|
247
|
+
task: OrchestrateTaskInput,
|
|
272
248
|
mode: "inline",
|
|
273
249
|
signal?: AbortSignal,
|
|
274
250
|
onSettlement?: SettlementListener,
|
|
275
|
-
): Promise<
|
|
251
|
+
): Promise<CompletedRun>;
|
|
276
252
|
orchestrate(
|
|
277
253
|
context: OrchestrationContext,
|
|
278
|
-
|
|
279
|
-
mode:
|
|
254
|
+
task: OrchestrateTaskInput,
|
|
255
|
+
mode: RunMode,
|
|
280
256
|
signal?: AbortSignal,
|
|
281
257
|
onSettlement?: SettlementListener,
|
|
282
|
-
): Promise<
|
|
258
|
+
): Promise<AcceptedRun | CompletedRun>;
|
|
283
259
|
async orchestrate(
|
|
284
260
|
context: OrchestrationContext,
|
|
285
|
-
|
|
286
|
-
mode:
|
|
261
|
+
task: OrchestrateTaskInput,
|
|
262
|
+
mode: RunMode,
|
|
287
263
|
signal?: AbortSignal,
|
|
288
264
|
onSettlement?: SettlementListener,
|
|
289
|
-
): Promise<
|
|
265
|
+
): Promise<AcceptedRun | CompletedRun> {
|
|
290
266
|
this.assertOpen();
|
|
291
267
|
throwIfAborted(signal);
|
|
292
|
-
const
|
|
268
|
+
const definition = this.validateTask(context, task, mode);
|
|
293
269
|
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
this.assertFreshIds(
|
|
270
|
+
const runId = this.idFactories.runId();
|
|
271
|
+
const workerId = this.idFactories.workerId();
|
|
272
|
+
this.assertFreshIds(runId, workerId);
|
|
297
273
|
|
|
298
|
-
const
|
|
299
|
-
id:
|
|
274
|
+
const run: RunRecord = {
|
|
275
|
+
id: runId,
|
|
300
276
|
ownerSessionId: context.ownerSessionId,
|
|
301
|
-
|
|
277
|
+
workerId,
|
|
302
278
|
mode,
|
|
303
279
|
state: "running",
|
|
304
280
|
createdAt: this.clock(),
|
|
281
|
+
...(context.synthesisGroup
|
|
282
|
+
? {
|
|
283
|
+
synthesisGroupId: context.synthesisGroup.id,
|
|
284
|
+
synthesisGroupSize: context.synthesisGroup.size,
|
|
285
|
+
}
|
|
286
|
+
: {}),
|
|
287
|
+
};
|
|
288
|
+
const record: WorkerRecord = {
|
|
289
|
+
id: workerId,
|
|
290
|
+
worker: definition.name,
|
|
291
|
+
ownerSessionId: context.ownerSessionId,
|
|
292
|
+
runId,
|
|
293
|
+
title: task.title,
|
|
294
|
+
instructions: task.instructions,
|
|
295
|
+
lifecycle: definition.lifecycle,
|
|
296
|
+
status: "starting",
|
|
297
|
+
usage: copyUsage(EMPTY_WORKER_USAGE),
|
|
298
|
+
messageDirection: "to-model",
|
|
299
|
+
startedAt: this.clock(),
|
|
305
300
|
};
|
|
306
|
-
const records = tasks.map<WorkerRecord>((task, index) => {
|
|
307
|
-
const definition = definitions[index];
|
|
308
|
-
const id = workerIds[index];
|
|
309
|
-
if (!definition || !id) throw new Error("Validated orchestration input became inconsistent");
|
|
310
|
-
return {
|
|
311
|
-
id,
|
|
312
|
-
worker: definition.name,
|
|
313
|
-
ownerSessionId: context.ownerSessionId,
|
|
314
|
-
waveId,
|
|
315
|
-
title: task.title,
|
|
316
|
-
instructions: task.instructions,
|
|
317
|
-
lifecycle: definition.lifecycle,
|
|
318
|
-
status: "starting",
|
|
319
|
-
usage: copyUsage(EMPTY_WORKER_USAGE),
|
|
320
|
-
messageDirection: "to-model",
|
|
321
|
-
startedAt: this.clock(),
|
|
322
|
-
};
|
|
323
|
-
});
|
|
324
301
|
|
|
325
|
-
const waiter =
|
|
326
|
-
this.
|
|
327
|
-
this.
|
|
328
|
-
if (onSettlement) this.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const definition = definitions[index];
|
|
332
|
-
if (!record || !definition) throw new Error("Validated orchestration input became inconsistent");
|
|
333
|
-
this.workers.set(record.id, record);
|
|
334
|
-
this.entries.set(record.id, { context, definition, generation: 1 });
|
|
335
|
-
}
|
|
302
|
+
const waiter = makeRunWaiter();
|
|
303
|
+
this.runs.set(runId, run);
|
|
304
|
+
this.runWaiters.set(runId, waiter);
|
|
305
|
+
if (onSettlement) this.runSettlementListeners.set(runId, onSettlement);
|
|
306
|
+
this.workers.set(workerId, record);
|
|
307
|
+
this.entries.set(workerId, { context, definition, generation: 1 });
|
|
336
308
|
this.emitState(context.ownerSessionId);
|
|
337
|
-
|
|
338
|
-
for (const record of records) this.launchBootstrap(record.id, 1);
|
|
309
|
+
this.launchBootstrap(workerId, 1);
|
|
339
310
|
|
|
340
311
|
if (mode === "inline") {
|
|
341
|
-
return this.
|
|
312
|
+
return this.awaitInlineRun(run, waiter, signal);
|
|
342
313
|
}
|
|
343
|
-
return
|
|
314
|
+
return freezeAcceptedRun(runId, workerId);
|
|
344
315
|
}
|
|
345
316
|
|
|
346
317
|
send(
|
|
@@ -350,7 +321,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
350
321
|
mode: "async",
|
|
351
322
|
signal?: AbortSignal,
|
|
352
323
|
onSettlement?: SettlementListener,
|
|
353
|
-
): Promise<
|
|
324
|
+
): Promise<AcceptedRun>;
|
|
354
325
|
send(
|
|
355
326
|
context: OrchestrationContext,
|
|
356
327
|
workerId: WorkerId,
|
|
@@ -358,23 +329,23 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
358
329
|
mode: "inline",
|
|
359
330
|
signal?: AbortSignal,
|
|
360
331
|
onSettlement?: SettlementListener,
|
|
361
|
-
): Promise<
|
|
332
|
+
): Promise<CompletedRun>;
|
|
362
333
|
send(
|
|
363
334
|
context: OrchestrationContext,
|
|
364
335
|
workerId: WorkerId,
|
|
365
336
|
instructions: string,
|
|
366
|
-
mode:
|
|
337
|
+
mode: RunMode,
|
|
367
338
|
signal?: AbortSignal,
|
|
368
339
|
onSettlement?: SettlementListener,
|
|
369
|
-
): Promise<
|
|
340
|
+
): Promise<AcceptedRun | CompletedRun>;
|
|
370
341
|
async send(
|
|
371
342
|
context: OrchestrationContext,
|
|
372
343
|
workerId: WorkerId,
|
|
373
344
|
instructions: string,
|
|
374
|
-
mode:
|
|
345
|
+
mode: RunMode,
|
|
375
346
|
signal?: AbortSignal,
|
|
376
347
|
onSettlement?: SettlementListener,
|
|
377
|
-
): Promise<
|
|
348
|
+
): Promise<AcceptedRun | CompletedRun> {
|
|
378
349
|
this.assertOpen();
|
|
379
350
|
throwIfAborted(signal);
|
|
380
351
|
validateContextOwner(context.ownerSessionId);
|
|
@@ -388,41 +359,41 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
388
359
|
const entry = this.entries.get(workerId);
|
|
389
360
|
if (!entry?.session) throw new Error("Ready reusable worker has no session handle");
|
|
390
361
|
|
|
391
|
-
const
|
|
392
|
-
if (this.
|
|
393
|
-
const
|
|
394
|
-
id:
|
|
362
|
+
const runId = this.idFactories.runId();
|
|
363
|
+
if (this.runs.has(runId)) throw new Error(`Duplicate run ID: ${runId}`);
|
|
364
|
+
const run: RunRecord = {
|
|
365
|
+
id: runId,
|
|
395
366
|
ownerSessionId: context.ownerSessionId,
|
|
396
|
-
|
|
367
|
+
workerId,
|
|
397
368
|
mode,
|
|
398
369
|
state: "running",
|
|
399
370
|
createdAt: this.clock(),
|
|
400
371
|
};
|
|
401
372
|
const running: WorkerRecord = {
|
|
402
373
|
...transitionWorkerStatus(current, "running"),
|
|
403
|
-
|
|
374
|
+
runId,
|
|
404
375
|
instructions,
|
|
405
376
|
activity: undefined,
|
|
406
377
|
messageDirection: "to-model",
|
|
407
378
|
startedAt: this.clock(),
|
|
408
379
|
settledAt: undefined,
|
|
409
380
|
};
|
|
410
|
-
const waiter =
|
|
381
|
+
const waiter = makeRunWaiter();
|
|
411
382
|
|
|
412
383
|
entry.generation += 1;
|
|
413
384
|
const generation = entry.generation;
|
|
414
|
-
this.
|
|
415
|
-
this.
|
|
416
|
-
if (onSettlement) this.
|
|
385
|
+
this.runs.set(runId, run);
|
|
386
|
+
this.runWaiters.set(runId, waiter);
|
|
387
|
+
if (onSettlement) this.runSettlementListeners.set(runId, onSettlement);
|
|
417
388
|
this.workers.set(workerId, running);
|
|
418
389
|
this.subscribeEntryObservability(workerId, entry, entry.session, generation);
|
|
419
390
|
this.emitState(context.ownerSessionId);
|
|
420
391
|
this.launchPrompt(workerId, generation, entry.session, instructions);
|
|
421
392
|
|
|
422
393
|
if (mode === "inline") {
|
|
423
|
-
return this.
|
|
394
|
+
return this.awaitInlineRun(run, waiter, signal);
|
|
424
395
|
}
|
|
425
|
-
return
|
|
396
|
+
return freezeAcceptedRun(runId, workerId);
|
|
426
397
|
}
|
|
427
398
|
|
|
428
399
|
async abort(ownerSessionId: string, target: AbortTarget): Promise<void> {
|
|
@@ -444,29 +415,18 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
444
415
|
|
|
445
416
|
async snapshot(ownerSessionId: string): Promise<RuntimeSnapshot> {
|
|
446
417
|
validateContextOwner(ownerSessionId);
|
|
447
|
-
const
|
|
448
|
-
.filter((
|
|
449
|
-
.map(
|
|
418
|
+
const runs = [...this.runs.values()]
|
|
419
|
+
.filter((run) => run.ownerSessionId === ownerSessionId)
|
|
420
|
+
.map(copyRunRecord);
|
|
450
421
|
const workers = [...this.workers.values()]
|
|
451
422
|
.filter((worker) => worker.ownerSessionId === ownerSessionId)
|
|
452
423
|
.map(copyWorkerRecord);
|
|
453
424
|
return Object.freeze({
|
|
454
|
-
|
|
425
|
+
runs: Object.freeze(runs),
|
|
455
426
|
workers: Object.freeze(workers),
|
|
456
427
|
});
|
|
457
428
|
}
|
|
458
429
|
|
|
459
|
-
subscribeCompletion(listener: CompletionListener): UnsubscribeCompletion {
|
|
460
|
-
if (typeof listener !== "function") throw new Error("Completion listener must be a function");
|
|
461
|
-
this.completionListeners.add(listener);
|
|
462
|
-
let subscribed = true;
|
|
463
|
-
return () => {
|
|
464
|
-
if (!subscribed) return;
|
|
465
|
-
subscribed = false;
|
|
466
|
-
this.completionListeners.delete(listener);
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
430
|
subscribeSettlement(listener: SettlementListener): UnsubscribeSettlement {
|
|
471
431
|
if (typeof listener !== "function") throw new Error("Settlement listener must be a function");
|
|
472
432
|
this.settlementListeners.add(listener);
|
|
@@ -510,51 +470,43 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
510
470
|
// Scheduler closure is best-effort during process shutdown.
|
|
511
471
|
} finally {
|
|
512
472
|
await this.awaitTrackedCleanupBestEffort();
|
|
513
|
-
this.
|
|
514
|
-
this.completionListeners.clear();
|
|
473
|
+
this.runSettlementListeners.clear();
|
|
515
474
|
this.settlementListeners.clear();
|
|
516
475
|
this.stateListeners.clear();
|
|
517
476
|
}
|
|
518
477
|
}
|
|
519
478
|
}
|
|
520
479
|
|
|
521
|
-
private
|
|
480
|
+
private validateTask(
|
|
522
481
|
context: OrchestrationContext,
|
|
523
|
-
|
|
524
|
-
mode:
|
|
525
|
-
): WorkerDefinition
|
|
482
|
+
task: OrchestrateTaskInput,
|
|
483
|
+
mode: RunMode,
|
|
484
|
+
): WorkerDefinition {
|
|
526
485
|
validateContextOwner(context.ownerSessionId);
|
|
527
486
|
validateMode(mode);
|
|
528
|
-
if (!
|
|
529
|
-
throw new Error(
|
|
487
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) {
|
|
488
|
+
throw new Error("orchestrate requires one task object");
|
|
530
489
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (!
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
validateText("instructions", task.instructions, MAX_WORKER_INSTRUCTIONS_LENGTH);
|
|
538
|
-
const definition = findWorkerByName(context.catalog, task.worker);
|
|
539
|
-
if (!definition) throw new Error(`Unknown worker: ${task.worker}`);
|
|
540
|
-
definitions.push(definition);
|
|
490
|
+
if (context.synthesisGroup) {
|
|
491
|
+
validateText("synthesis group ID", context.synthesisGroup.id, MAX_WORKER_TITLE_LENGTH);
|
|
492
|
+
if (mode !== "async") throw new Error("Sibling synthesis requires an async task");
|
|
493
|
+
if (!Number.isSafeInteger(context.synthesisGroup.size) || context.synthesisGroup.size < 2) {
|
|
494
|
+
throw new Error("Synthesis group size must be an integer of at least 2");
|
|
495
|
+
}
|
|
541
496
|
}
|
|
542
497
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
498
|
+
validateText("worker", task.worker, MAX_WORKER_TITLE_LENGTH);
|
|
499
|
+
validateText("title", task.title, MAX_WORKER_TITLE_LENGTH);
|
|
500
|
+
validateText("instructions", task.instructions, MAX_WORKER_INSTRUCTIONS_LENGTH);
|
|
501
|
+
const definition = findWorkerByName(context.catalog, task.worker);
|
|
502
|
+
if (!definition) throw new Error(`Unknown worker: ${task.worker}`);
|
|
503
|
+
resolveWorkerModel(definition, context.parentModel, context.modelRegistry);
|
|
504
|
+
return definition;
|
|
547
505
|
}
|
|
548
506
|
|
|
549
|
-
private assertFreshIds(
|
|
550
|
-
if (this.
|
|
551
|
-
|
|
552
|
-
for (const workerId of workerIds) {
|
|
553
|
-
if (unique.has(workerId) || this.workers.has(workerId)) {
|
|
554
|
-
throw new Error(`Duplicate worker ID: ${workerId}`);
|
|
555
|
-
}
|
|
556
|
-
unique.add(workerId);
|
|
557
|
-
}
|
|
507
|
+
private assertFreshIds(runId: RunId, workerId: WorkerId): void {
|
|
508
|
+
if (this.runs.has(runId)) throw new Error(`Duplicate run ID: ${runId}`);
|
|
509
|
+
if (this.workers.has(workerId)) throw new Error(`Duplicate worker ID: ${workerId}`);
|
|
558
510
|
}
|
|
559
511
|
|
|
560
512
|
private launchBootstrap(workerId: WorkerId, generation: number): void {
|
|
@@ -763,7 +715,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
763
715
|
const entry = this.entries.get(workerId);
|
|
764
716
|
if (!current || entry?.generation !== generation) return;
|
|
765
717
|
if (!isActiveWorkerStatus(current.status)) {
|
|
766
|
-
this.
|
|
718
|
+
this.maybeCompleteRun(current.runId);
|
|
767
719
|
return;
|
|
768
720
|
}
|
|
769
721
|
|
|
@@ -816,8 +768,8 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
816
768
|
settledAt,
|
|
817
769
|
});
|
|
818
770
|
if (status !== "ready") this.disposeEntrySession(entry);
|
|
819
|
-
|
|
820
|
-
affectedOwners
|
|
771
|
+
this.maybeCompleteRun(current.runId);
|
|
772
|
+
const affectedOwners = new Set([current.ownerSessionId]);
|
|
821
773
|
this.emitSettlement(
|
|
822
774
|
workerId,
|
|
823
775
|
generation,
|
|
@@ -845,8 +797,8 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
845
797
|
outcome: copyOutcome(outcome),
|
|
846
798
|
settledAt,
|
|
847
799
|
});
|
|
848
|
-
|
|
849
|
-
affectedOwners
|
|
800
|
+
this.maybeCompleteRun(current.runId);
|
|
801
|
+
const affectedOwners = new Set([current.ownerSessionId]);
|
|
850
802
|
const generation = this.entries.get(current.id)?.generation;
|
|
851
803
|
if (generation !== undefined) {
|
|
852
804
|
this.emitSettlement(current.id, generation, settledAt, failureStage);
|
|
@@ -862,29 +814,19 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
862
814
|
failureStage?: SettlementFailureStage,
|
|
863
815
|
): void {
|
|
864
816
|
const worker = this.workers.get(workerId);
|
|
865
|
-
const
|
|
866
|
-
if (!worker || !
|
|
867
|
-
if (worker.status === "closed") return;
|
|
868
|
-
|
|
869
|
-
let remainingActive = 0;
|
|
870
|
-
let waveComplete = true;
|
|
871
|
-
for (const waveWorkerId of wave.workerIds) {
|
|
872
|
-
const waveWorker = this.workers.get(waveWorkerId);
|
|
873
|
-
if (!waveWorker || waveWorker.waveId !== wave.id || !isWorkerCompleteForWave(waveWorker.status)) {
|
|
874
|
-
waveComplete = false;
|
|
875
|
-
if (waveWorker && isActiveWorkerStatus(waveWorker.status)) remainingActive += 1;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
817
|
+
const run = worker ? this.runs.get(worker.runId) : undefined;
|
|
818
|
+
if (!worker || !run || !worker.outcome || !isSettledWorkerStatus(worker.status)) return;
|
|
819
|
+
if (worker.outcome.status === "closed") return;
|
|
878
820
|
|
|
879
821
|
const sequence = ++this.settlementSequence;
|
|
880
822
|
const settlement: WorkerSettlement = Object.freeze({
|
|
881
|
-
eventId: `${sequence}:${
|
|
823
|
+
eventId: `${sequence}:${run.id}:${workerId}:${generation}`,
|
|
882
824
|
sequence,
|
|
883
825
|
ownerSessionId: worker.ownerSessionId,
|
|
884
|
-
|
|
826
|
+
runId: run.id,
|
|
885
827
|
workerId,
|
|
886
828
|
generation,
|
|
887
|
-
mode:
|
|
829
|
+
mode: run.mode,
|
|
888
830
|
worker: worker.worker,
|
|
889
831
|
title: worker.title,
|
|
890
832
|
lifecycle: worker.lifecycle,
|
|
@@ -894,60 +836,38 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
894
836
|
usage: Object.freeze(copyUsage(worker.usage)),
|
|
895
837
|
startedAt: worker.startedAt,
|
|
896
838
|
settledAt,
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
839
|
+
...(run.synthesisGroupId && run.synthesisGroupSize
|
|
840
|
+
? {
|
|
841
|
+
synthesisGroupId: run.synthesisGroupId,
|
|
842
|
+
synthesisGroupSize: run.synthesisGroupSize,
|
|
843
|
+
}
|
|
844
|
+
: {}),
|
|
845
|
+
...(worker.sessionFile !== undefined ? { sessionFile: worker.sessionFile } : {}),
|
|
901
846
|
});
|
|
902
847
|
|
|
903
|
-
const localListener = this.
|
|
904
|
-
|
|
848
|
+
const localListener = this.runSettlementListeners.get(run.id);
|
|
849
|
+
this.runSettlementListeners.delete(run.id);
|
|
905
850
|
notifySettlementListener(localListener, settlement);
|
|
906
851
|
for (const listener of [...this.settlementListeners]) {
|
|
907
852
|
notifySettlementListener(listener, settlement);
|
|
908
853
|
}
|
|
909
|
-
if (waveComplete) this.emitCompletedWave(wave.id);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
private emitCompletedWave(waveId: WaveId): void {
|
|
913
|
-
const completed = this.completedWaves.get(waveId);
|
|
914
|
-
if (!completed || completed.mode !== "async") return;
|
|
915
|
-
for (const listener of [...this.completionListeners]) {
|
|
916
|
-
try {
|
|
917
|
-
listener(completed);
|
|
918
|
-
} catch {
|
|
919
|
-
// One subscriber cannot prevent other subscribers from receiving completion.
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
854
|
}
|
|
923
855
|
|
|
924
|
-
private
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const worker = this.workers.get(workerId);
|
|
932
|
-
if (
|
|
933
|
-
!worker ||
|
|
934
|
-
worker.waveId !== waveId ||
|
|
935
|
-
!isWorkerCompleteForWave(worker.status) ||
|
|
936
|
-
!worker.outcome
|
|
937
|
-
) {
|
|
938
|
-
return affectedOwners;
|
|
939
|
-
}
|
|
940
|
-
records.push(worker);
|
|
856
|
+
private maybeCompleteRun(runId: RunId): void {
|
|
857
|
+
if (this.completedRuns.has(runId)) return;
|
|
858
|
+
const run = this.runs.get(runId);
|
|
859
|
+
if (!run) return;
|
|
860
|
+
const worker = this.workers.get(run.workerId);
|
|
861
|
+
if (!worker || worker.runId !== runId || !worker.outcome || isActiveWorkerStatus(worker.status)) {
|
|
862
|
+
return;
|
|
941
863
|
}
|
|
942
864
|
|
|
943
|
-
const completed =
|
|
944
|
-
this.
|
|
945
|
-
this.
|
|
946
|
-
this.
|
|
947
|
-
this.
|
|
948
|
-
this.
|
|
949
|
-
|
|
950
|
-
return affectedOwners;
|
|
865
|
+
const completed = freezeCompletedRun(run, worker);
|
|
866
|
+
this.completedRuns.set(runId, completed);
|
|
867
|
+
this.completedRunOrder.push(runId);
|
|
868
|
+
this.runs.set(runId, { ...run, state: "complete" });
|
|
869
|
+
this.runWaiters.get(runId)?.resolve(completed);
|
|
870
|
+
this.runWaiters.delete(runId);
|
|
951
871
|
}
|
|
952
872
|
|
|
953
873
|
private rememberTerminalWorker(workerId: WorkerId): Set<string> {
|
|
@@ -959,20 +879,20 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
959
879
|
|
|
960
880
|
private pruneHistory(): Set<string> {
|
|
961
881
|
const affectedOwners = new Set<string>();
|
|
962
|
-
while (this.
|
|
963
|
-
const
|
|
964
|
-
if (!
|
|
965
|
-
this.
|
|
966
|
-
const
|
|
967
|
-
if (
|
|
968
|
-
this.
|
|
882
|
+
while (this.completedRunOrder.length > MAX_COMPLETED_RUN_HISTORY) {
|
|
883
|
+
const runId = this.completedRunOrder.shift();
|
|
884
|
+
if (!runId) break;
|
|
885
|
+
this.completedRuns.delete(runId);
|
|
886
|
+
const run = this.runs.get(runId);
|
|
887
|
+
if (run) affectedOwners.add(run.ownerSessionId);
|
|
888
|
+
this.runs.delete(runId);
|
|
969
889
|
}
|
|
970
890
|
|
|
971
891
|
while (this.terminalWorkerOrder.length > MAX_TERMINAL_WORKER_HISTORY) {
|
|
972
892
|
const removableIndex = this.terminalWorkerOrder.findIndex((workerId) => {
|
|
973
893
|
const worker = this.workers.get(workerId);
|
|
974
894
|
if (!worker || !isTerminalWorkerStatus(worker.status)) return true;
|
|
975
|
-
return this.
|
|
895
|
+
return this.runs.get(worker.runId)?.state !== "running";
|
|
976
896
|
});
|
|
977
897
|
if (removableIndex < 0) return affectedOwners;
|
|
978
898
|
const [workerId] = this.terminalWorkerOrder.splice(removableIndex, 1);
|
|
@@ -990,16 +910,14 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
990
910
|
if (!target || typeof target !== "object") throw new Error("Invalid abort target");
|
|
991
911
|
const candidate = target as {
|
|
992
912
|
workerIds?: readonly WorkerId[];
|
|
993
|
-
waveId?: WaveId;
|
|
994
913
|
all?: boolean;
|
|
995
914
|
};
|
|
996
915
|
const selected = [
|
|
997
916
|
candidate.workerIds !== undefined,
|
|
998
|
-
candidate.waveId !== undefined,
|
|
999
917
|
candidate.all !== undefined,
|
|
1000
918
|
].filter(Boolean).length;
|
|
1001
919
|
if (selected !== 1 || (candidate.all !== undefined && candidate.all !== true)) {
|
|
1002
|
-
throw new Error("Abort target must specify exactly one of workerIds
|
|
920
|
+
throw new Error("Abort target must specify exactly one of workerIds or all: true");
|
|
1003
921
|
}
|
|
1004
922
|
|
|
1005
923
|
if (candidate.workerIds !== undefined) {
|
|
@@ -1019,21 +937,6 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
1019
937
|
return unique;
|
|
1020
938
|
}
|
|
1021
939
|
|
|
1022
|
-
if (candidate.waveId !== undefined) {
|
|
1023
|
-
const wave = this.waves.get(candidate.waveId);
|
|
1024
|
-
if (!wave || wave.ownerSessionId !== ownerSessionId) {
|
|
1025
|
-
throw new Error("Wave is not owned by this session");
|
|
1026
|
-
}
|
|
1027
|
-
return wave.workerIds.filter((workerId) => {
|
|
1028
|
-
const worker = this.workers.get(workerId);
|
|
1029
|
-
return (
|
|
1030
|
-
worker?.ownerSessionId === ownerSessionId &&
|
|
1031
|
-
worker.waveId === wave.id &&
|
|
1032
|
-
isActiveWorkerStatus(worker.status)
|
|
1033
|
-
);
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
940
|
return [...this.workers.values()]
|
|
1038
941
|
.filter(
|
|
1039
942
|
(worker) =>
|
|
@@ -1122,27 +1025,25 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
1122
1025
|
}, "cancellation");
|
|
1123
1026
|
}
|
|
1124
1027
|
|
|
1125
|
-
private async
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
worker?.ownerSessionId === wave.ownerSessionId &&
|
|
1130
|
-
worker.waveId === wave.id &&
|
|
1028
|
+
private async cancelExactRun(run: RunRecord): Promise<void> {
|
|
1029
|
+
const worker = this.workers.get(run.workerId);
|
|
1030
|
+
const active = worker?.ownerSessionId === run.ownerSessionId &&
|
|
1031
|
+
worker.runId === run.id &&
|
|
1131
1032
|
isActiveWorkerStatus(worker.status)
|
|
1132
|
-
|
|
1133
|
-
|
|
1033
|
+
? [worker.id]
|
|
1034
|
+
: [];
|
|
1134
1035
|
await this.cancelWorkers(active);
|
|
1135
|
-
this.
|
|
1036
|
+
this.maybeCompleteRun(run.id);
|
|
1136
1037
|
}
|
|
1137
1038
|
|
|
1138
|
-
private
|
|
1139
|
-
|
|
1140
|
-
waiter:
|
|
1039
|
+
private awaitInlineRun(
|
|
1040
|
+
run: RunRecord,
|
|
1041
|
+
waiter: RunWaiter,
|
|
1141
1042
|
signal: AbortSignal | undefined,
|
|
1142
|
-
): Promise<
|
|
1043
|
+
): Promise<CompletedRun> {
|
|
1143
1044
|
if (!signal) return waiter.promise;
|
|
1144
1045
|
|
|
1145
|
-
return new Promise<
|
|
1046
|
+
return new Promise<CompletedRun>((resolve, reject) => {
|
|
1146
1047
|
let abortClaimed = false;
|
|
1147
1048
|
const removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
1148
1049
|
const onAbort = () => {
|
|
@@ -1150,7 +1051,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
1150
1051
|
abortClaimed = true;
|
|
1151
1052
|
removeAbortListener();
|
|
1152
1053
|
const reason = abortSignalReason(signal);
|
|
1153
|
-
void this.
|
|
1054
|
+
void this.cancelExactRun(run).then(
|
|
1154
1055
|
() => reject(reason),
|
|
1155
1056
|
() => reject(reason),
|
|
1156
1057
|
);
|
|
@@ -1176,8 +1077,8 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
1176
1077
|
outcome: { status: "closed" },
|
|
1177
1078
|
settledAt: this.clock(),
|
|
1178
1079
|
});
|
|
1179
|
-
|
|
1180
|
-
|
|
1080
|
+
this.maybeCompleteRun(current.runId);
|
|
1081
|
+
const affectedOwners = this.rememberTerminalWorker(current.id);
|
|
1181
1082
|
affectedOwners.add(current.ownerSessionId);
|
|
1182
1083
|
this.emitStateForOwners(affectedOwners);
|
|
1183
1084
|
}
|
|
@@ -1308,7 +1209,7 @@ function validateContextOwner(ownerSessionId: string): void {
|
|
|
1308
1209
|
}
|
|
1309
1210
|
}
|
|
1310
1211
|
|
|
1311
|
-
function validateMode(mode:
|
|
1212
|
+
function validateMode(mode: RunMode): void {
|
|
1312
1213
|
if (mode !== "async" && mode !== "inline") throw new Error("Invalid orchestration mode");
|
|
1313
1214
|
}
|
|
1314
1215
|
|
|
@@ -1331,6 +1232,12 @@ function isActiveWorkerStatus(status: WorkerRecord["status"]): boolean {
|
|
|
1331
1232
|
return status === "starting" || status === "running" || status === "stopping";
|
|
1332
1233
|
}
|
|
1333
1234
|
|
|
1235
|
+
function isSettledWorkerStatus(
|
|
1236
|
+
status: WorkerRecord["status"],
|
|
1237
|
+
): status is CompletedResult["status"] {
|
|
1238
|
+
return status === "completed" || status === "ready" || status === "failed" || status === "aborted";
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1334
1241
|
function safelyCall(callback: (() => void) | undefined): void {
|
|
1335
1242
|
if (!callback) return;
|
|
1336
1243
|
try {
|
|
@@ -1345,7 +1252,8 @@ function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
|
1345
1252
|
}
|
|
1346
1253
|
|
|
1347
1254
|
function abortSignalReason(signal: AbortSignal): unknown {
|
|
1348
|
-
|
|
1255
|
+
if ("reason" in signal) return signal.reason;
|
|
1256
|
+
return new DOMException("This operation was aborted", "AbortError");
|
|
1349
1257
|
}
|
|
1350
1258
|
|
|
1351
1259
|
function addAll(target: Set<string>, source: ReadonlySet<string>): void {
|
|
@@ -1364,18 +1272,18 @@ function notifySettlementListener(
|
|
|
1364
1272
|
}
|
|
1365
1273
|
}
|
|
1366
1274
|
|
|
1367
|
-
function
|
|
1368
|
-
let complete!: (
|
|
1369
|
-
const waiter:
|
|
1370
|
-
promise: new Promise<
|
|
1275
|
+
function makeRunWaiter(): RunWaiter {
|
|
1276
|
+
let complete!: (run: CompletedRun) => void;
|
|
1277
|
+
const waiter: RunWaiter = {
|
|
1278
|
+
promise: new Promise<CompletedRun>((resolve) => {
|
|
1371
1279
|
complete = resolve;
|
|
1372
1280
|
}),
|
|
1373
1281
|
settled: false,
|
|
1374
|
-
resolve(
|
|
1282
|
+
resolve(run) {
|
|
1375
1283
|
if (waiter.settled) return;
|
|
1376
1284
|
waiter.settled = true;
|
|
1377
1285
|
waiter.onSettled?.();
|
|
1378
|
-
complete(
|
|
1286
|
+
complete(run);
|
|
1379
1287
|
},
|
|
1380
1288
|
};
|
|
1381
1289
|
return waiter;
|
|
@@ -1393,15 +1301,12 @@ function copyUsage(usage: WorkerUsage): WorkerUsage {
|
|
|
1393
1301
|
};
|
|
1394
1302
|
}
|
|
1395
1303
|
|
|
1396
|
-
function copyOutcome(outcome:
|
|
1304
|
+
function copyOutcome<Outcome extends WorkerOutcome>(outcome: Outcome): Outcome {
|
|
1397
1305
|
return { ...outcome };
|
|
1398
1306
|
}
|
|
1399
1307
|
|
|
1400
|
-
function
|
|
1401
|
-
return Object.freeze({
|
|
1402
|
-
...wave,
|
|
1403
|
-
workerIds: Object.freeze([...wave.workerIds]),
|
|
1404
|
-
});
|
|
1308
|
+
function copyRunRecord(run: RunRecord): RunRecord {
|
|
1309
|
+
return Object.freeze({ ...run });
|
|
1405
1310
|
}
|
|
1406
1311
|
|
|
1407
1312
|
function copyWorkerRecord(worker: WorkerRecord): WorkerRecord {
|
|
@@ -1412,32 +1317,36 @@ function copyWorkerRecord(worker: WorkerRecord): WorkerRecord {
|
|
|
1412
1317
|
});
|
|
1413
1318
|
}
|
|
1414
1319
|
|
|
1415
|
-
function
|
|
1416
|
-
id
|
|
1417
|
-
workerIds: readonly WorkerId[],
|
|
1418
|
-
): AcceptedWave {
|
|
1419
|
-
return Object.freeze({ id, workerIds: Object.freeze([...workerIds]) });
|
|
1320
|
+
function freezeAcceptedRun(id: RunId, workerId: WorkerId): AcceptedRun {
|
|
1321
|
+
return Object.freeze({ id, workerId });
|
|
1420
1322
|
}
|
|
1421
1323
|
|
|
1422
|
-
function
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
):
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1324
|
+
function freezeCompletedRun(
|
|
1325
|
+
run: RunRecord,
|
|
1326
|
+
record: WorkerRecord,
|
|
1327
|
+
): CompletedRun {
|
|
1328
|
+
const outcome = record.outcome;
|
|
1329
|
+
if (!outcome || outcome.status === "closed") {
|
|
1330
|
+
throw new Error("Completed run requires a worker response outcome");
|
|
1331
|
+
}
|
|
1332
|
+
if (record.settledAt === undefined) {
|
|
1333
|
+
throw new Error("Completed run requires a settlement timestamp");
|
|
1334
|
+
}
|
|
1335
|
+
const result: CompletedResult = Object.freeze({
|
|
1336
|
+
workerId: record.id,
|
|
1337
|
+
worker: record.worker,
|
|
1338
|
+
title: record.title,
|
|
1339
|
+
status: record.status as CompletedResult["status"],
|
|
1340
|
+
outcome: Object.freeze(copyOutcome(outcome)),
|
|
1341
|
+
usage: Object.freeze(copyUsage(record.usage)),
|
|
1342
|
+
startedAt: record.startedAt,
|
|
1343
|
+
settledAt: record.settledAt,
|
|
1344
|
+
sessionFile: record.sessionFile,
|
|
1345
|
+
});
|
|
1437
1346
|
return Object.freeze({
|
|
1438
|
-
id:
|
|
1439
|
-
ownerSessionId:
|
|
1440
|
-
mode:
|
|
1441
|
-
|
|
1347
|
+
id: run.id,
|
|
1348
|
+
ownerSessionId: run.ownerSessionId,
|
|
1349
|
+
mode: run.mode,
|
|
1350
|
+
result,
|
|
1442
1351
|
});
|
|
1443
1352
|
}
|