@zachwill/pi-orchestrate 0.1.0 → 0.1.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 +5 -3
- package/extension/contract.ts +3 -1
- package/extension/delivery.ts +137 -89
- package/extension/domain.ts +2 -0
- package/extension/host.ts +41 -5
- package/extension/presentation.ts +304 -462
- package/extension/runtime.ts +190 -27
- package/extension/tools.ts +296 -19
- package/extension/worker-session.ts +7 -2
- package/package.json +1 -1
package/extension/runtime.ts
CHANGED
|
@@ -72,6 +72,31 @@ export interface CompletedWave {
|
|
|
72
72
|
readonly results: readonly CompletedResult[];
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export type SettlementFailureStage = "startup" | "prompt" | "workflow" | "cancellation";
|
|
76
|
+
|
|
77
|
+
export interface WorkerSettlement {
|
|
78
|
+
readonly eventId: string;
|
|
79
|
+
readonly sequence: number;
|
|
80
|
+
readonly ownerSessionId: string;
|
|
81
|
+
readonly waveId: WaveId;
|
|
82
|
+
readonly workerId: WorkerId;
|
|
83
|
+
readonly generation: number;
|
|
84
|
+
readonly mode: WaveMode;
|
|
85
|
+
readonly worker: string;
|
|
86
|
+
readonly title: string;
|
|
87
|
+
readonly lifecycle: WorkerRecord["lifecycle"];
|
|
88
|
+
readonly status: Exclude<WaveCompleteWorkerStatus, "closed">;
|
|
89
|
+
readonly outcome: WorkerOutcome;
|
|
90
|
+
readonly failureStage?: SettlementFailureStage;
|
|
91
|
+
readonly usage: WorkerUsage;
|
|
92
|
+
readonly startedAt: number;
|
|
93
|
+
readonly settledAt: number;
|
|
94
|
+
readonly remainingActive: number;
|
|
95
|
+
readonly waveSize: number;
|
|
96
|
+
readonly waveComplete: boolean;
|
|
97
|
+
readonly sessionFile: string | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
export interface RuntimeSnapshot {
|
|
76
101
|
readonly waves: readonly WaveRecord[];
|
|
77
102
|
readonly workers: readonly WorkerRecord[];
|
|
@@ -79,6 +104,8 @@ export interface RuntimeSnapshot {
|
|
|
79
104
|
|
|
80
105
|
export type CompletionListener = (wave: CompletedWave) => void;
|
|
81
106
|
export type UnsubscribeCompletion = () => void;
|
|
107
|
+
export type SettlementListener = (settlement: WorkerSettlement) => void;
|
|
108
|
+
export type UnsubscribeSettlement = () => void;
|
|
82
109
|
export type StateListener = (ownerSessionId: string) => void;
|
|
83
110
|
|
|
84
111
|
export type AbortTarget =
|
|
@@ -118,18 +145,21 @@ export interface OrchestratorRuntime {
|
|
|
118
145
|
tasks: readonly OrchestrateTaskInput[],
|
|
119
146
|
mode: "async",
|
|
120
147
|
signal?: AbortSignal,
|
|
148
|
+
onSettlement?: SettlementListener,
|
|
121
149
|
): Promise<AcceptedWave>;
|
|
122
150
|
orchestrate(
|
|
123
151
|
context: OrchestrationContext,
|
|
124
152
|
tasks: readonly OrchestrateTaskInput[],
|
|
125
153
|
mode: "inline",
|
|
126
154
|
signal?: AbortSignal,
|
|
155
|
+
onSettlement?: SettlementListener,
|
|
127
156
|
): Promise<CompletedWave>;
|
|
128
157
|
orchestrate(
|
|
129
158
|
context: OrchestrationContext,
|
|
130
159
|
tasks: readonly OrchestrateTaskInput[],
|
|
131
160
|
mode: WaveMode,
|
|
132
161
|
signal?: AbortSignal,
|
|
162
|
+
onSettlement?: SettlementListener,
|
|
133
163
|
): Promise<AcceptedWave | CompletedWave>;
|
|
134
164
|
send(
|
|
135
165
|
context: OrchestrationContext,
|
|
@@ -137,6 +167,7 @@ export interface OrchestratorRuntime {
|
|
|
137
167
|
instructions: string,
|
|
138
168
|
mode: "async",
|
|
139
169
|
signal?: AbortSignal,
|
|
170
|
+
onSettlement?: SettlementListener,
|
|
140
171
|
): Promise<AcceptedWave>;
|
|
141
172
|
send(
|
|
142
173
|
context: OrchestrationContext,
|
|
@@ -144,6 +175,7 @@ export interface OrchestratorRuntime {
|
|
|
144
175
|
instructions: string,
|
|
145
176
|
mode: "inline",
|
|
146
177
|
signal?: AbortSignal,
|
|
178
|
+
onSettlement?: SettlementListener,
|
|
147
179
|
): Promise<CompletedWave>;
|
|
148
180
|
send(
|
|
149
181
|
context: OrchestrationContext,
|
|
@@ -151,11 +183,13 @@ export interface OrchestratorRuntime {
|
|
|
151
183
|
instructions: string,
|
|
152
184
|
mode: WaveMode,
|
|
153
185
|
signal?: AbortSignal,
|
|
186
|
+
onSettlement?: SettlementListener,
|
|
154
187
|
): Promise<AcceptedWave | CompletedWave>;
|
|
155
188
|
abort(ownerSessionId: string, target: AbortTarget): Promise<void>;
|
|
156
189
|
close(ownerSessionId: string, workerId: WorkerId): Promise<void>;
|
|
157
190
|
snapshot(ownerSessionId: string): Promise<RuntimeSnapshot>;
|
|
158
191
|
subscribeCompletion(listener: CompletionListener): UnsubscribeCompletion;
|
|
192
|
+
subscribeSettlement(listener: SettlementListener): UnsubscribeSettlement;
|
|
159
193
|
subscribeState(listener: StateListener): () => void;
|
|
160
194
|
shutdown(): Promise<void>;
|
|
161
195
|
}
|
|
@@ -208,7 +242,10 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
208
242
|
private readonly terminalWorkerOrder: WorkerId[] = [];
|
|
209
243
|
private readonly completedWaveOrder: WaveId[] = [];
|
|
210
244
|
private readonly completionListeners = new Set<CompletionListener>();
|
|
245
|
+
private readonly settlementListeners = new Set<SettlementListener>();
|
|
246
|
+
private readonly waveSettlementListeners = new Map<WaveId, SettlementListener>();
|
|
211
247
|
private readonly stateListeners = new Set<StateListener>();
|
|
248
|
+
private settlementSequence = 0;
|
|
212
249
|
private readonly disposedSessions = new WeakSet<WorkerSessionHandle>();
|
|
213
250
|
private shuttingDown = false;
|
|
214
251
|
private shutdownPromise: Promise<void> | undefined;
|
|
@@ -226,24 +263,28 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
226
263
|
tasks: readonly OrchestrateTaskInput[],
|
|
227
264
|
mode: "async",
|
|
228
265
|
signal?: AbortSignal,
|
|
266
|
+
onSettlement?: SettlementListener,
|
|
229
267
|
): Promise<AcceptedWave>;
|
|
230
268
|
orchestrate(
|
|
231
269
|
context: OrchestrationContext,
|
|
232
270
|
tasks: readonly OrchestrateTaskInput[],
|
|
233
271
|
mode: "inline",
|
|
234
272
|
signal?: AbortSignal,
|
|
273
|
+
onSettlement?: SettlementListener,
|
|
235
274
|
): Promise<CompletedWave>;
|
|
236
275
|
orchestrate(
|
|
237
276
|
context: OrchestrationContext,
|
|
238
277
|
tasks: readonly OrchestrateTaskInput[],
|
|
239
278
|
mode: WaveMode,
|
|
240
279
|
signal?: AbortSignal,
|
|
280
|
+
onSettlement?: SettlementListener,
|
|
241
281
|
): Promise<AcceptedWave | CompletedWave>;
|
|
242
282
|
async orchestrate(
|
|
243
283
|
context: OrchestrationContext,
|
|
244
284
|
tasks: readonly OrchestrateTaskInput[],
|
|
245
285
|
mode: WaveMode,
|
|
246
286
|
signal?: AbortSignal,
|
|
287
|
+
onSettlement?: SettlementListener,
|
|
247
288
|
): Promise<AcceptedWave | CompletedWave> {
|
|
248
289
|
this.assertOpen();
|
|
249
290
|
throwIfAborted(signal);
|
|
@@ -275,12 +316,14 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
275
316
|
lifecycle: definition.lifecycle,
|
|
276
317
|
status: "starting",
|
|
277
318
|
usage: copyUsage(EMPTY_WORKER_USAGE),
|
|
319
|
+
startedAt: this.clock(),
|
|
278
320
|
};
|
|
279
321
|
});
|
|
280
322
|
|
|
281
323
|
const waiter = makeWaveWaiter();
|
|
282
324
|
this.waves.set(waveId, wave);
|
|
283
325
|
this.waveWaiters.set(waveId, waiter);
|
|
326
|
+
if (onSettlement) this.waveSettlementListeners.set(waveId, onSettlement);
|
|
284
327
|
for (let index = 0; index < records.length; index += 1) {
|
|
285
328
|
const record = records[index];
|
|
286
329
|
const definition = definitions[index];
|
|
@@ -304,6 +347,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
304
347
|
instructions: string,
|
|
305
348
|
mode: "async",
|
|
306
349
|
signal?: AbortSignal,
|
|
350
|
+
onSettlement?: SettlementListener,
|
|
307
351
|
): Promise<AcceptedWave>;
|
|
308
352
|
send(
|
|
309
353
|
context: OrchestrationContext,
|
|
@@ -311,6 +355,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
311
355
|
instructions: string,
|
|
312
356
|
mode: "inline",
|
|
313
357
|
signal?: AbortSignal,
|
|
358
|
+
onSettlement?: SettlementListener,
|
|
314
359
|
): Promise<CompletedWave>;
|
|
315
360
|
send(
|
|
316
361
|
context: OrchestrationContext,
|
|
@@ -318,6 +363,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
318
363
|
instructions: string,
|
|
319
364
|
mode: WaveMode,
|
|
320
365
|
signal?: AbortSignal,
|
|
366
|
+
onSettlement?: SettlementListener,
|
|
321
367
|
): Promise<AcceptedWave | CompletedWave>;
|
|
322
368
|
async send(
|
|
323
369
|
context: OrchestrationContext,
|
|
@@ -325,6 +371,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
325
371
|
instructions: string,
|
|
326
372
|
mode: WaveMode,
|
|
327
373
|
signal?: AbortSignal,
|
|
374
|
+
onSettlement?: SettlementListener,
|
|
328
375
|
): Promise<AcceptedWave | CompletedWave> {
|
|
329
376
|
this.assertOpen();
|
|
330
377
|
throwIfAborted(signal);
|
|
@@ -354,6 +401,8 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
354
401
|
waveId,
|
|
355
402
|
instructions,
|
|
356
403
|
activity: undefined,
|
|
404
|
+
startedAt: this.clock(),
|
|
405
|
+
settledAt: undefined,
|
|
357
406
|
};
|
|
358
407
|
const waiter = makeWaveWaiter();
|
|
359
408
|
|
|
@@ -361,6 +410,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
361
410
|
const generation = entry.generation;
|
|
362
411
|
this.waves.set(waveId, wave);
|
|
363
412
|
this.waveWaiters.set(waveId, waiter);
|
|
413
|
+
if (onSettlement) this.waveSettlementListeners.set(waveId, onSettlement);
|
|
364
414
|
this.workers.set(workerId, running);
|
|
365
415
|
this.subscribeEntryObservability(workerId, entry, entry.session, generation);
|
|
366
416
|
this.emitState(context.ownerSessionId);
|
|
@@ -414,6 +464,17 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
414
464
|
};
|
|
415
465
|
}
|
|
416
466
|
|
|
467
|
+
subscribeSettlement(listener: SettlementListener): UnsubscribeSettlement {
|
|
468
|
+
if (typeof listener !== "function") throw new Error("Settlement listener must be a function");
|
|
469
|
+
this.settlementListeners.add(listener);
|
|
470
|
+
let subscribed = true;
|
|
471
|
+
return () => {
|
|
472
|
+
if (!subscribed) return;
|
|
473
|
+
subscribed = false;
|
|
474
|
+
this.settlementListeners.delete(listener);
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
417
478
|
subscribeState(listener: StateListener): () => void {
|
|
418
479
|
if (typeof listener !== "function") throw new Error("State listener must be a function");
|
|
419
480
|
this.stateListeners.add(listener);
|
|
@@ -446,6 +507,10 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
446
507
|
// Scheduler closure is best-effort during process shutdown.
|
|
447
508
|
} finally {
|
|
448
509
|
await this.awaitTrackedCleanupBestEffort();
|
|
510
|
+
this.waveSettlementListeners.clear();
|
|
511
|
+
this.completionListeners.clear();
|
|
512
|
+
this.settlementListeners.clear();
|
|
513
|
+
this.stateListeners.clear();
|
|
449
514
|
}
|
|
450
515
|
}
|
|
451
516
|
}
|
|
@@ -490,11 +555,15 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
490
555
|
}
|
|
491
556
|
|
|
492
557
|
private launchBootstrap(workerId: WorkerId, generation: number): void {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
558
|
+
try {
|
|
559
|
+
this.scheduler.start(
|
|
560
|
+
workerId,
|
|
561
|
+
() => this.trackCleanup(this.bootstrapAndPrompt(workerId, generation)),
|
|
562
|
+
(error) => this.settleWorkflowDefect(workerId, generation, error),
|
|
563
|
+
);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
this.settleWorkflowDefect(workerId, generation, error);
|
|
566
|
+
}
|
|
498
567
|
}
|
|
499
568
|
|
|
500
569
|
private launchPrompt(
|
|
@@ -503,11 +572,15 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
503
572
|
session: WorkerSessionHandle,
|
|
504
573
|
instructions: string,
|
|
505
574
|
): void {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
575
|
+
try {
|
|
576
|
+
this.scheduler.start(
|
|
577
|
+
workerId,
|
|
578
|
+
() => this.trackCleanup(this.executePrompt(workerId, generation, session, instructions)),
|
|
579
|
+
(error) => this.settleWorkflowDefect(workerId, generation, error),
|
|
580
|
+
);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
this.settleWorkflowDefect(workerId, generation, error);
|
|
583
|
+
}
|
|
511
584
|
}
|
|
512
585
|
|
|
513
586
|
private async bootstrapAndPrompt(workerId: WorkerId, generation: number): Promise<void> {
|
|
@@ -606,7 +679,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
606
679
|
this.settleTerminalWorker(current, "failed", {
|
|
607
680
|
status: "failed",
|
|
608
681
|
message: describeError(error, "Worker session creation failed"),
|
|
609
|
-
});
|
|
682
|
+
}, "startup");
|
|
610
683
|
}
|
|
611
684
|
|
|
612
685
|
private settleWorkflowDefect(
|
|
@@ -626,7 +699,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
626
699
|
this.settleTerminalWorker(current, "failed", {
|
|
627
700
|
status: "failed",
|
|
628
701
|
message: describeError(error, "Worker workflow failed"),
|
|
629
|
-
});
|
|
702
|
+
}, "workflow");
|
|
630
703
|
}
|
|
631
704
|
|
|
632
705
|
private settleOutcome(
|
|
@@ -663,17 +736,27 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
663
736
|
};
|
|
664
737
|
}
|
|
665
738
|
|
|
739
|
+
const settledAt = this.clock();
|
|
666
740
|
this.workers.set(workerId, {
|
|
667
741
|
...transitionWorkerStatus(current, status),
|
|
668
742
|
activity: undefined,
|
|
669
743
|
outcome: copyOutcome(settledOutcome),
|
|
744
|
+
settledAt,
|
|
670
745
|
});
|
|
671
746
|
if (status !== "ready") this.disposeEntrySession(entry);
|
|
672
747
|
const affectedOwners = this.maybeCompleteWave(current.waveId);
|
|
748
|
+
affectedOwners.add(current.ownerSessionId);
|
|
749
|
+
this.emitSettlement(
|
|
750
|
+
workerId,
|
|
751
|
+
generation,
|
|
752
|
+
settledAt,
|
|
753
|
+
status === "failed" ? "prompt" : undefined,
|
|
754
|
+
);
|
|
673
755
|
if (isTerminalWorkerStatus(status)) {
|
|
674
756
|
addAll(affectedOwners, this.rememberTerminalWorker(workerId));
|
|
757
|
+
} else {
|
|
758
|
+
addAll(affectedOwners, this.pruneHistory());
|
|
675
759
|
}
|
|
676
|
-
affectedOwners.add(current.ownerSessionId);
|
|
677
760
|
this.emitStateForOwners(affectedOwners);
|
|
678
761
|
}
|
|
679
762
|
|
|
@@ -681,18 +764,91 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
681
764
|
current: WorkerRecord,
|
|
682
765
|
status: "failed" | "aborted",
|
|
683
766
|
outcome: WorkerOutcome,
|
|
767
|
+
failureStage?: SettlementFailureStage,
|
|
684
768
|
): void {
|
|
769
|
+
const settledAt = this.clock();
|
|
685
770
|
this.workers.set(current.id, {
|
|
686
771
|
...transitionWorkerStatus(current, status),
|
|
687
772
|
activity: undefined,
|
|
688
773
|
outcome: copyOutcome(outcome),
|
|
774
|
+
settledAt,
|
|
689
775
|
});
|
|
690
776
|
const affectedOwners = this.maybeCompleteWave(current.waveId);
|
|
691
|
-
addAll(affectedOwners, this.rememberTerminalWorker(current.id));
|
|
692
777
|
affectedOwners.add(current.ownerSessionId);
|
|
778
|
+
const generation = this.entries.get(current.id)?.generation;
|
|
779
|
+
if (generation !== undefined) {
|
|
780
|
+
this.emitSettlement(current.id, generation, settledAt, failureStage);
|
|
781
|
+
}
|
|
782
|
+
addAll(affectedOwners, this.rememberTerminalWorker(current.id));
|
|
693
783
|
this.emitStateForOwners(affectedOwners);
|
|
694
784
|
}
|
|
695
785
|
|
|
786
|
+
private emitSettlement(
|
|
787
|
+
workerId: WorkerId,
|
|
788
|
+
generation: number,
|
|
789
|
+
settledAt: number,
|
|
790
|
+
failureStage?: SettlementFailureStage,
|
|
791
|
+
): void {
|
|
792
|
+
const worker = this.workers.get(workerId);
|
|
793
|
+
const wave = worker ? this.waves.get(worker.waveId) : undefined;
|
|
794
|
+
if (!worker || !wave || !worker.outcome || !isWorkerCompleteForWave(worker.status)) return;
|
|
795
|
+
if (worker.status === "closed") return;
|
|
796
|
+
|
|
797
|
+
let remainingActive = 0;
|
|
798
|
+
let waveComplete = true;
|
|
799
|
+
for (const waveWorkerId of wave.workerIds) {
|
|
800
|
+
const waveWorker = this.workers.get(waveWorkerId);
|
|
801
|
+
if (!waveWorker || waveWorker.waveId !== wave.id || !isWorkerCompleteForWave(waveWorker.status)) {
|
|
802
|
+
waveComplete = false;
|
|
803
|
+
if (waveWorker && isActiveWorkerStatus(waveWorker.status)) remainingActive += 1;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const sequence = ++this.settlementSequence;
|
|
808
|
+
const settlement: WorkerSettlement = Object.freeze({
|
|
809
|
+
eventId: `${sequence}:${wave.id}:${workerId}:${generation}`,
|
|
810
|
+
sequence,
|
|
811
|
+
ownerSessionId: worker.ownerSessionId,
|
|
812
|
+
waveId: wave.id,
|
|
813
|
+
workerId,
|
|
814
|
+
generation,
|
|
815
|
+
mode: wave.mode,
|
|
816
|
+
worker: worker.worker,
|
|
817
|
+
title: worker.title,
|
|
818
|
+
lifecycle: worker.lifecycle,
|
|
819
|
+
status: worker.status,
|
|
820
|
+
outcome: Object.freeze(copyOutcome(worker.outcome)),
|
|
821
|
+
...(failureStage ? { failureStage } : {}),
|
|
822
|
+
usage: Object.freeze(copyUsage(worker.usage)),
|
|
823
|
+
startedAt: worker.startedAt,
|
|
824
|
+
settledAt,
|
|
825
|
+
remainingActive,
|
|
826
|
+
waveSize: wave.workerIds.length,
|
|
827
|
+
waveComplete,
|
|
828
|
+
sessionFile: worker.sessionFile,
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const localListener = this.waveSettlementListeners.get(wave.id);
|
|
832
|
+
if (waveComplete) this.waveSettlementListeners.delete(wave.id);
|
|
833
|
+
notifySettlementListener(localListener, settlement);
|
|
834
|
+
for (const listener of [...this.settlementListeners]) {
|
|
835
|
+
notifySettlementListener(listener, settlement);
|
|
836
|
+
}
|
|
837
|
+
if (waveComplete) this.emitCompletedWave(wave.id);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private emitCompletedWave(waveId: WaveId): void {
|
|
841
|
+
const completed = this.completedWaves.get(waveId);
|
|
842
|
+
if (!completed || completed.mode !== "async") return;
|
|
843
|
+
for (const listener of [...this.completionListeners]) {
|
|
844
|
+
try {
|
|
845
|
+
listener(completed);
|
|
846
|
+
} catch {
|
|
847
|
+
// One subscriber cannot prevent other subscribers from receiving completion.
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
696
852
|
private maybeCompleteWave(waveId: WaveId): Set<string> {
|
|
697
853
|
const affectedOwners = new Set<string>();
|
|
698
854
|
if (this.completedWaves.has(waveId)) return affectedOwners;
|
|
@@ -719,17 +875,6 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
719
875
|
this.waveWaiters.get(waveId)?.resolve(completed);
|
|
720
876
|
this.waveWaiters.delete(waveId);
|
|
721
877
|
|
|
722
|
-
if (wave.mode === "async") {
|
|
723
|
-
for (const listener of [...this.completionListeners]) {
|
|
724
|
-
try {
|
|
725
|
-
listener(completed);
|
|
726
|
-
} catch {
|
|
727
|
-
// One subscriber cannot prevent other subscribers from receiving completion.
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
addAll(affectedOwners, this.pruneHistory());
|
|
733
878
|
return affectedOwners;
|
|
734
879
|
}
|
|
735
880
|
|
|
@@ -884,7 +1029,12 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
884
1029
|
if (entry) this.disposeEntrySession(entry);
|
|
885
1030
|
const current = this.workers.get(workerId);
|
|
886
1031
|
if (current?.status === "stopping") {
|
|
887
|
-
this.settleTerminalWorker(
|
|
1032
|
+
this.settleTerminalWorker(
|
|
1033
|
+
current,
|
|
1034
|
+
"aborted",
|
|
1035
|
+
{ status: "aborted" },
|
|
1036
|
+
"cancellation",
|
|
1037
|
+
);
|
|
888
1038
|
}
|
|
889
1039
|
}
|
|
890
1040
|
}
|
|
@@ -897,7 +1047,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
897
1047
|
this.settleTerminalWorker(current, "failed", {
|
|
898
1048
|
status: "failed",
|
|
899
1049
|
message: describeError(error, "Worker cancellation failed"),
|
|
900
|
-
});
|
|
1050
|
+
}, "cancellation");
|
|
901
1051
|
}
|
|
902
1052
|
|
|
903
1053
|
private async cancelExactWave(wave: WaveRecord): Promise<void> {
|
|
@@ -952,6 +1102,7 @@ class DefaultOrchestratorRuntime implements OrchestratorRuntime {
|
|
|
952
1102
|
...transitionWorkerStatus(current, "closed"),
|
|
953
1103
|
activity: undefined,
|
|
954
1104
|
outcome: { status: "closed" },
|
|
1105
|
+
settledAt: this.clock(),
|
|
955
1106
|
});
|
|
956
1107
|
const affectedOwners = this.maybeCompleteWave(current.waveId);
|
|
957
1108
|
addAll(affectedOwners, this.rememberTerminalWorker(current.id));
|
|
@@ -1114,6 +1265,18 @@ function addAll(target: Set<string>, source: ReadonlySet<string>): void {
|
|
|
1114
1265
|
for (const value of source) target.add(value);
|
|
1115
1266
|
}
|
|
1116
1267
|
|
|
1268
|
+
function notifySettlementListener(
|
|
1269
|
+
listener: SettlementListener | undefined,
|
|
1270
|
+
settlement: WorkerSettlement,
|
|
1271
|
+
): void {
|
|
1272
|
+
if (!listener) return;
|
|
1273
|
+
try {
|
|
1274
|
+
listener(settlement);
|
|
1275
|
+
} catch {
|
|
1276
|
+
// One observer cannot prevent settlement or other observers from being notified.
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1117
1280
|
function makeWaveWaiter(): WaveWaiter {
|
|
1118
1281
|
let complete!: (wave: CompletedWave) => void;
|
|
1119
1282
|
const waiter: WaveWaiter = {
|