@zachwill/pi-orchestrate 0.9.2 → 0.10.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.
@@ -8,7 +8,9 @@ import {
8
8
  WorkerReadyOutcome,
9
9
  WorkerResponseOutcome,
10
10
  WorkerUsage,
11
- } from "./domain.js";
11
+ type RunRecord,
12
+ type SettledWorkerRecord,
13
+ } from "./model.js";
12
14
 
13
15
  const NonnegativeInteger = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
14
16
  const PositiveInteger = Schema.Int.check(Schema.isGreaterThan(0));
@@ -20,8 +22,8 @@ const FailureStage = Schema.Literals([
20
22
  "cancellation",
21
23
  ]);
22
24
 
23
- /** Canonical schema for settlement details written by the current runtime. */
24
- export const WorkerSettlementDetails = Schema.Struct({
25
+ /** Canonical schema for settlements written and persisted by orchestration. */
26
+ export const WorkerSettlement = Schema.Struct({
25
27
  eventId: Schema.String,
26
28
  sequence: PositiveInteger,
27
29
  ownerSessionId: Schema.String,
@@ -77,25 +79,69 @@ export const WorkerSettlementDetails = Schema.Struct({
77
79
  }),
78
80
  );
79
81
 
80
- export interface WorkerSettlementDetails
81
- extends Schema.Schema.Type<typeof WorkerSettlementDetails> {}
82
+ export interface WorkerSettlement
83
+ extends Schema.Schema.Type<typeof WorkerSettlement> {}
82
84
 
83
85
  export type SettlementFailureStage = NonNullable<
84
- WorkerSettlementDetails["failureStage"]
85
- >;
86
-
87
- export type WorkerSettlement = Schema.Schema.Type<
88
- typeof WorkerSettlementDetails
86
+ WorkerSettlement["failureStage"]
89
87
  >;
90
88
 
91
89
  const decodeCurrentWorkerSettlement = Schema.decodeUnknownResult(
92
- WorkerSettlementDetails,
90
+ WorkerSettlement,
93
91
  );
94
92
 
95
- export function decodePersistedWorkerSettlementDetails(value: unknown) {
93
+ export function decodePersistedWorkerSettlement(value: unknown) {
96
94
  return decodeCurrentWorkerSettlement(value);
97
95
  }
98
96
 
97
+ /** Every input a settlement needs; orchestration state entries stay private. */
98
+ export interface WorkerSettlementInput {
99
+ readonly sequence: number;
100
+ readonly generation: number;
101
+ readonly run: RunRecord;
102
+ readonly worker: SettledWorkerRecord;
103
+ readonly settledAt: number;
104
+ readonly failureStage?: SettlementFailureStage;
105
+ }
106
+
107
+ /** Builds the canonical settlement published to listeners and persisted by Pi. */
108
+ export function createWorkerSettlement({
109
+ sequence,
110
+ generation,
111
+ run,
112
+ worker,
113
+ settledAt,
114
+ failureStage,
115
+ }: WorkerSettlementInput): WorkerSettlement {
116
+ return Object.freeze({
117
+ eventId: `${sequence}:${run.id}:${worker.id}:${generation}`,
118
+ sequence,
119
+ ownerSessionId: worker.ownerSessionId,
120
+ runId: run.id,
121
+ workerId: worker.id,
122
+ generation,
123
+ mode: run.mode,
124
+ worker: worker.worker,
125
+ title: worker.title,
126
+ lifecycle: worker.lifecycle,
127
+ status: worker.status,
128
+ outcome: Object.freeze({ ...worker.outcome }),
129
+ ...(failureStage ? { failureStage } : {}),
130
+ usage: Object.freeze({ ...worker.usage }),
131
+ startedAt: worker.startedAt,
132
+ settledAt,
133
+ ...(run.synthesisGroupId && run.synthesisGroupSize
134
+ ? {
135
+ synthesisGroupId: run.synthesisGroupId,
136
+ synthesisGroupSize: run.synthesisGroupSize,
137
+ }
138
+ : {}),
139
+ ...(worker.sessionFile !== undefined
140
+ ? { sessionFile: worker.sessionFile }
141
+ : {}),
142
+ });
143
+ }
144
+
99
145
  const InlineWorkerOutcome = Schema.Union([
100
146
  WorkerCompletedOutcome.pipe(
101
147
  Schema.encodeKeys({ assistantText: "assistant_text" }),
@@ -121,15 +167,15 @@ const InlineWorkerUsage = WorkerUsage.pipe(
121
167
 
122
168
  /** Tool transport projection derived from the canonical settlement field schemas. */
123
169
  export const InlineWorkerSettlementDetails = Schema.Struct({
124
- workerId: WorkerSettlementDetails.fields.workerId,
125
- worker: WorkerSettlementDetails.fields.worker,
126
- title: WorkerSettlementDetails.fields.title,
127
- status: WorkerSettlementDetails.fields.status,
170
+ workerId: WorkerSettlement.fields.workerId,
171
+ worker: WorkerSettlement.fields.worker,
172
+ title: WorkerSettlement.fields.title,
173
+ status: WorkerSettlement.fields.status,
128
174
  outcome: InlineWorkerOutcome,
129
175
  usage: InlineWorkerUsage,
130
- startedAt: WorkerSettlementDetails.fields.startedAt,
131
- settledAt: WorkerSettlementDetails.fields.settledAt,
132
- sessionFile: WorkerSettlementDetails.fields.sessionFile,
176
+ startedAt: WorkerSettlement.fields.startedAt,
177
+ settledAt: WorkerSettlement.fields.settledAt,
178
+ sessionFile: WorkerSettlement.fields.sessionFile,
133
179
  }).pipe(
134
180
  Schema.encodeKeys({
135
181
  workerId: "worker_id",
@@ -153,9 +199,9 @@ export interface InlineWorkerSettlementDetails
153
199
 
154
200
  export const InlineWorkerToolDetails = Schema.Struct({
155
201
  mode: Schema.Literal("inline"),
156
- runId: Schema.optionalKey(WorkerSettlementDetails.fields.runId),
202
+ runId: Schema.optionalKey(WorkerSettlement.fields.runId),
157
203
  ownerSessionId: Schema.optionalKey(
158
- WorkerSettlementDetails.fields.ownerSessionId,
204
+ WorkerSettlement.fields.ownerSessionId,
159
205
  ),
160
206
  result: InlineWorkerSettlementDetails,
161
207
  }).pipe(
@@ -0,0 +1,3 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ export const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
@@ -1,4 +1,4 @@
1
- import type { WorkerCatalog } from "./domain.js";
1
+ import type { WorkerCatalog } from "../catalog/definition.js";
2
2
 
3
3
  const CONTRACT_START = "<!-- pi-orchestrate:contract:start -->";
4
4
  const CONTRACT_END = "<!-- pi-orchestrate:contract:end -->";
@@ -124,7 +124,7 @@ ${formatCatalog(catalog)}
124
124
  ${CONTRACT_END}`;
125
125
  }
126
126
 
127
- export function appendOrchestratorContract(
127
+ export function applyOrchestratorContract(
128
128
  systemPrompt: string,
129
129
  catalog: WorkerCatalog,
130
130
  ): string {
@@ -1,6 +1,6 @@
1
1
  import { Context, Effect, Layer } from "effect";
2
- import { Orchestration } from "./runtime.js";
3
- import type { WorkerSettlement } from "./worker-settlement.js";
2
+ import { Orchestration } from "../orchestration/service.js";
3
+ import type { WorkerSettlement } from "../orchestration/settlement.js";
4
4
 
5
5
  export const MAX_DELIVERY_MARKDOWN_BYTES = 50 * 1024;
6
6
  export const MAX_WORKER_DELIVERY_MARKDOWN_BYTES = 16 * 1024;
@@ -11,14 +11,11 @@ export const DELIVERY_PARENT_INSTRUCTIONS =
11
11
 
12
12
  export type ParentBindingGeneration = string | number | symbol;
13
13
 
14
- /** Complete, immutable worker output for presentation and history consumers. */
15
- export type WorkerDeliveryDetails = WorkerSettlement;
16
-
17
14
  export interface WorkerDeliveryMessage {
18
15
  readonly customType: "pi-orchestrate-worker-result";
19
16
  readonly content: string;
20
17
  readonly display: true;
21
- readonly details: WorkerDeliveryDetails;
18
+ readonly details: WorkerSettlement;
22
19
  }
23
20
 
24
21
  export interface WorkerDeliveryOptions {
@@ -67,8 +64,8 @@ export class DeliveryCoordinator implements DeliveryService {
67
64
  private readonly pendingSettlements: WorkerSettlement[] = [];
68
65
  private readonly flushingOwners = new Set<string>();
69
66
  private readonly synthesisGroups = new Map<string, SynthesisGroupState>();
70
- // Runtime settlement sequences are process-scoped and monotonic across owners,
71
- // so one watermark is valid.
67
+ // Orchestration settlement sequences are process-scoped and monotonic across
68
+ // owners, so one watermark is valid.
72
69
  private highestAcceptedSequence = 0;
73
70
 
74
71
  bind(binding: ParentBinding): void {
@@ -0,0 +1,49 @@
1
+ import type { SynthesisGroup } from "../orchestration/model.js";
2
+
3
+ export interface ParentToolCall {
4
+ readonly id: string;
5
+ readonly name: string;
6
+ }
7
+
8
+ export interface DispatchDecision {
9
+ readonly mode: "async" | "inline";
10
+ readonly synthesisGroup?: SynthesisGroup;
11
+ }
12
+
13
+ export interface ClassifiedParentDispatch {
14
+ readonly toolCallId: string;
15
+ readonly decision: DispatchDecision;
16
+ }
17
+
18
+ const DISPATCH_TOOL_NAMES: ReadonlySet<string> = new Set([
19
+ "orchestrate",
20
+ "interactive_send",
21
+ ]);
22
+
23
+ // Sole dispatches and homogeneous orchestrate waves detach so the parent turn can
24
+ // end while work continues. Mixed tools stay inline because their shared parent
25
+ // turn still has sibling work; one wave boundary defers one synthesis turn until
26
+ // every admitted member has settled.
27
+ export function classifyParentDispatches(
28
+ toolCalls: readonly ParentToolCall[],
29
+ ): readonly ClassifiedParentDispatch[] {
30
+ const isOrchestrateGroup =
31
+ toolCalls.length > 1 &&
32
+ toolCalls.every((toolCall) => toolCall.name === "orchestrate");
33
+ const synthesisGroup = isOrchestrateGroup
34
+ ? { id: `orchestrate:${toolCalls[0]?.id ?? "group"}`, size: toolCalls.length }
35
+ : undefined;
36
+
37
+ return toolCalls.flatMap((toolCall): ClassifiedParentDispatch[] => {
38
+ if (!DISPATCH_TOOL_NAMES.has(toolCall.name)) return [];
39
+ return [{
40
+ toolCallId: toolCall.id,
41
+ decision: {
42
+ mode: isOrchestrateGroup || toolCalls.length === 1 ? "async" : "inline",
43
+ ...(toolCall.name === "orchestrate" && synthesisGroup
44
+ ? { synthesisGroup }
45
+ : {}),
46
+ },
47
+ }];
48
+ });
49
+ }
@@ -7,33 +7,36 @@ import {
7
7
  import {
8
8
  Orchestration,
9
9
  orchestrationLayer,
10
- type AbortTarget,
11
10
  type AcceptedRun,
12
11
  type CompletedRun,
13
- type OrchestrationContext,
14
12
  type OrchestrationService,
15
13
  SHUTDOWN_CLEANUP_GRACE_MS,
16
- type RuntimeSnapshot,
14
+ type OwnerSnapshot,
17
15
  type SettlementListener,
18
16
  type UnsubscribeSettlement,
19
- } from "./runtime.js";
20
- import { createChildSessionsLayer } from "./worker-session.js";
21
- import type { OrchestrateTaskInput, RunMode } from "./domain.js";
17
+ } from "../orchestration/service.js";
18
+ import type {
19
+ AbortTarget,
20
+ OrchestrationContext,
21
+ } from "../orchestration/admission.js";
22
+ import { createChildSessionsLayer } from "../worker/child-sessions.js";
23
+ import type { OrchestrateTaskInput, RunMode } from "../orchestration/model.js";
22
24
 
23
25
  const PROCESS_HOST_KEY = Symbol.for("@zachwill/pi-orchestrate/process-host/v3");
24
26
 
25
- type RunResult<M extends RunMode> = M extends "async"
27
+ type DispatchResult<M extends RunMode> = M extends "async"
26
28
  ? AcceptedRun
27
29
  : CompletedRun;
28
30
 
29
- export interface OrchestratorRuntime {
31
+ /** Promise-facing orchestration API used by Pi tools and presentation. */
32
+ export interface OrchestrationClient {
30
33
  orchestrate<M extends RunMode>(
31
34
  context: OrchestrationContext,
32
35
  task: OrchestrateTaskInput,
33
36
  mode: M,
34
37
  signal?: AbortSignal,
35
38
  onSettlement?: SettlementListener,
36
- ): Promise<RunResult<M>>;
39
+ ): Promise<DispatchResult<M>>;
37
40
  sendInteractive<M extends RunMode>(
38
41
  context: OrchestrationContext,
39
42
  workerId: string,
@@ -41,20 +44,20 @@ export interface OrchestratorRuntime {
41
44
  mode: M,
42
45
  signal?: AbortSignal,
43
46
  onSettlement?: SettlementListener,
44
- ): Promise<RunResult<M>>;
47
+ ): Promise<DispatchResult<M>>;
45
48
  abort(ownerSessionId: string, target: AbortTarget): Promise<void>;
46
49
  closeInteractive(ownerSessionId: string, workerId: string): Promise<void>;
47
- snapshot(ownerSessionId: string): Promise<RuntimeSnapshot>;
50
+ snapshot(ownerSessionId: string): Promise<OwnerSnapshot>;
48
51
  subscribeSettlement(listener: SettlementListener): UnsubscribeSettlement;
49
52
  subscribeState(
50
53
  ownerSessionId: string,
51
- listener: (snapshot: RuntimeSnapshot) => void,
54
+ listener: (snapshot: OwnerSnapshot) => void,
52
55
  ): () => void;
53
56
  shutdown(): Promise<void>;
54
57
  }
55
58
 
56
59
  export interface ProcessHost {
57
- readonly runtime: OrchestratorRuntime;
60
+ readonly orchestration: OrchestrationClient;
58
61
  readonly delivery: DeliveryService;
59
62
  }
60
63
 
@@ -100,7 +103,7 @@ export function getProcessHost(): ProcessHost | undefined {
100
103
  }
101
104
 
102
105
  /** Pi-facing adapter. Every Promise operation executes one complete Orchestration Effect. */
103
- export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime {
106
+ export class ManagedOrchestrationClient<R = never> implements OrchestrationClient {
104
107
  constructor(
105
108
  private readonly effectRuntime: ManagedRuntime.ManagedRuntime<Orchestration | R, never>,
106
109
  private readonly orchestration: OrchestrationService,
@@ -192,7 +195,7 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
192
195
  return this.run(this.orchestration.closeInteractive(ownerSessionId, workerId));
193
196
  }
194
197
 
195
- snapshot(ownerSessionId: string): Promise<RuntimeSnapshot> {
198
+ snapshot(ownerSessionId: string): Promise<OwnerSnapshot> {
196
199
  // Snapshot is dependency-free and remains readable from a retained host reference
197
200
  // after the process root has been disposed.
198
201
  return Effect.runPromise(this.orchestration.snapshot(ownerSessionId));
@@ -204,7 +207,7 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
204
207
 
205
208
  subscribeState(
206
209
  ownerSessionId: string,
207
- listener: (snapshot: RuntimeSnapshot) => void,
210
+ listener: (snapshot: OwnerSnapshot) => void,
208
211
  ): () => void {
209
212
  return this.orchestration.subscribeState(ownerSessionId, listener);
210
213
  }
@@ -232,15 +235,15 @@ export class ProcessHostRuntimeAdapter<R = never> implements OrchestratorRuntime
232
235
  }
233
236
  }
234
237
 
235
- export function createProcessHostRuntimeAdapter<R>(
238
+ export function createOrchestrationClient<R>(
236
239
  effectRuntime: ManagedRuntime.ManagedRuntime<Orchestration | R, never>,
237
- ): OrchestratorRuntime {
240
+ ): OrchestrationClient {
238
241
  // Orchestration acquisition is synchronous; subscriptions must remain reentrant.
239
242
  const orchestration = effectRuntime.runSync(Orchestration);
240
- return new ProcessHostRuntimeAdapter(effectRuntime, orchestration);
243
+ return new ManagedOrchestrationClient(effectRuntime, orchestration);
241
244
  }
242
245
 
243
- export function createProcessApplicationLayer(): Layer.Layer<Orchestration | Delivery> {
246
+ export function makeProcessHostLayer(): Layer.Layer<Orchestration | Delivery> {
244
247
  const orchestration = orchestrationLayer().pipe(
245
248
  Layer.provide(createChildSessionsLayer()),
246
249
  );
@@ -258,11 +261,11 @@ export function createProcessHost(): ProcessHost {
258
261
  }
259
262
  if (existing) return existing;
260
263
 
261
- const effectRuntime = ManagedRuntime.make(createProcessApplicationLayer());
262
- const runtime = createProcessHostRuntimeAdapter(effectRuntime);
264
+ const effectRuntime = ManagedRuntime.make(makeProcessHostLayer());
265
+ const orchestration = createOrchestrationClient(effectRuntime);
263
266
  const delivery = effectRuntime.runSync(Delivery);
264
267
  const host: OwnedProcessHost = {
265
- runtime,
268
+ orchestration,
266
269
  delivery,
267
270
  effectRuntime,
268
271
  attachments: new Set(),
@@ -315,7 +318,7 @@ export function destroyProcessHost(
315
318
  let shutdown: Promise<void>;
316
319
  try {
317
320
  // shutdown() closes Orchestration admission before returning its bounded teardown Promise.
318
- shutdown = ownedHost.runtime.shutdown();
321
+ shutdown = ownedHost.orchestration.shutdown();
319
322
  } catch (error) {
320
323
  shutdown = Promise.reject(error);
321
324
  }
@@ -15,9 +15,12 @@ import {
15
15
  type Component,
16
16
  } from "@earendil-works/pi-tui";
17
17
  import { Result } from "effect";
18
- import type { WorkerDeliveryDetails } from "./delivery.js";
19
- import type { WorkerOutcome, WorkerRecord, WorkerStatus } from "./domain.js";
20
- import type { RuntimeSnapshot } from "./runtime.js";
18
+ import type {
19
+ WorkerOutcome,
20
+ WorkerRecord,
21
+ WorkerStatus,
22
+ } from "../orchestration/model.js";
23
+ import type { OwnerSnapshot } from "../orchestration/service.js";
21
24
  import {
22
25
  disposeComponent,
23
26
  formatElapsed,
@@ -25,9 +28,9 @@ import {
25
28
  WidthBoundComponent,
26
29
  } from "./tui.js";
27
30
  import {
28
- decodePersistedWorkerSettlementDetails,
29
- type WorkerSettlementDetails,
30
- } from "./worker-settlement.js";
31
+ decodePersistedWorkerSettlement,
32
+ type WorkerSettlement,
33
+ } from "../orchestration/settlement.js";
31
34
 
32
35
  export const ORCHESTRATION_PRESENTATION_KEY = "pi-orchestrate";
33
36
  export const MAX_RESULT_PREVIEW_LINES = 6;
@@ -51,10 +54,11 @@ const ANIMATION_CYCLE_TICKS = 40;
51
54
  const SPINNER_INTERVAL_MS = 140;
52
55
  const ACTIVE_STATUSES: ReadonlySet<WorkerStatus> = new Set(["starting", "running", "stopping"]);
53
56
 
54
- export interface PresentationRuntime {
57
+ /** Owner-scoped worker state feed consumed by the parent's status presentation. */
58
+ export interface WorkerStateSource {
55
59
  subscribeState(
56
60
  ownerSessionId: string,
57
- listener: (snapshot: RuntimeSnapshot) => void,
61
+ listener: (snapshot: OwnerSnapshot) => void,
58
62
  ): () => void;
59
63
  }
60
64
 
@@ -66,14 +70,14 @@ interface StatusBinding {
66
70
  interface RenderRequester { requestRender(): void }
67
71
 
68
72
  export function registerOrchestrationPresentation(pi: ExtensionAPI): void {
69
- pi.registerMessageRenderer<WorkerDeliveryDetails>(
73
+ pi.registerMessageRenderer<WorkerSettlement>(
70
74
  "pi-orchestrate-worker-result",
71
75
  (message, { expanded }, theme) =>
72
76
  new WorkerResultComponent(messageText(message.content), message.details, expanded, theme),
73
77
  );
74
78
  }
75
79
 
76
- export function formatFooterStatus(snapshot: RuntimeSnapshot): string | undefined {
80
+ export function formatFooterStatus(snapshot: OwnerSnapshot): string | undefined {
77
81
  const ready = snapshot.workers.filter((worker) => worker.status === "ready").length;
78
82
  return ready > 0 ? `${ready} interactive ready` : undefined;
79
83
  }
@@ -84,14 +88,14 @@ export class StatusController {
84
88
  private unsubscribeState: (() => void) | undefined;
85
89
  private widget: WorkerStatusComponent | undefined;
86
90
 
87
- constructor(private readonly runtime: PresentationRuntime) {}
91
+ constructor(private readonly workerState: WorkerStateSource) {}
88
92
 
89
93
  bind(ownerSessionId: string, ctx: ExtensionContext): void {
90
94
  if (this.disposed) return;
91
95
  this.clearBinding();
92
96
  const binding = { ownerSessionId, ctx };
93
97
  this.binding = binding;
94
- this.unsubscribeState = this.runtime.subscribeState(ownerSessionId, (snapshot) => {
98
+ this.unsubscribeState = this.workerState.subscribeState(ownerSessionId, (snapshot) => {
95
99
  if (this.binding !== binding) return;
96
100
  this.present(binding.ctx, snapshot);
97
101
  });
@@ -108,7 +112,7 @@ export class StatusController {
108
112
  this.clearBinding();
109
113
  }
110
114
 
111
- private present(ctx: ExtensionContext, snapshot: RuntimeSnapshot): void {
115
+ private present(ctx: ExtensionContext, snapshot: OwnerSnapshot): void {
112
116
  ctx.ui.setStatus(ORCHESTRATION_PRESENTATION_KEY, formatFooterStatus(snapshot));
113
117
  if (ctx.mode !== "tui") return;
114
118
  const active = activeWorkers(snapshot);
@@ -145,21 +149,23 @@ export class StatusController {
145
149
  }
146
150
  }
147
151
 
148
- export function createStatusController(runtime: PresentationRuntime): StatusController {
149
- return new StatusController(runtime);
152
+ export function createStatusController(
153
+ workerState: WorkerStateSource,
154
+ ): StatusController {
155
+ return new StatusController(workerState);
150
156
  }
151
157
 
152
158
  export class WorkerStatusComponent implements Component {
153
159
  private frameIndex = 0;
154
- private snapshot: RuntimeSnapshot;
160
+ private snapshot: OwnerSnapshot;
155
161
  private timer: ReturnType<typeof setInterval> | undefined;
156
162
 
157
- constructor(snapshot: RuntimeSnapshot, private readonly theme: Theme, private readonly tui?: RenderRequester) {
163
+ constructor(snapshot: OwnerSnapshot, private readonly theme: Theme, private readonly tui?: RenderRequester) {
158
164
  this.snapshot = snapshot;
159
165
  this.startTimer();
160
166
  }
161
167
 
162
- update(snapshot: RuntimeSnapshot): void {
168
+ update(snapshot: OwnerSnapshot): void {
163
169
  this.snapshot = snapshot;
164
170
  if (activeWorkers(snapshot).length > 0) this.startTimer();
165
171
  else this.stopTimer();
@@ -289,8 +295,8 @@ export class WorkerResultComponent implements Component {
289
295
  }
290
296
  }
291
297
 
292
- function readSettlement(value: unknown): WorkerSettlementDetails | undefined {
293
- const decoded = decodePersistedWorkerSettlementDetails(value);
298
+ function readSettlement(value: unknown): WorkerSettlement | undefined {
299
+ const decoded = decodePersistedWorkerSettlement(value);
294
300
  return Result.isSuccess(decoded) ? decoded.success : undefined;
295
301
  }
296
302
 
@@ -307,7 +313,7 @@ function outcomeText(outcome: WorkerOutcome): string {
307
313
  return "Worker session closed.";
308
314
  }
309
315
 
310
- function presentedOutcome(result: WorkerSettlementDetails): string {
316
+ function presentedOutcome(result: WorkerSettlement): string {
311
317
  const body = outcomeText(result.outcome);
312
318
  if (result.status !== "completed" && result.status !== "ready") return body;
313
319
 
@@ -322,7 +328,7 @@ function presentedOutcome(result: WorkerSettlementDetails): string {
322
328
  return lines.join("\n").trimEnd();
323
329
  }
324
330
 
325
- function settlementMetadata(result: WorkerSettlementDetails): string[] {
331
+ function settlementMetadata(result: WorkerSettlement): string[] {
326
332
  return [
327
333
  `worker ID ${result.workerId} · run ID ${result.runId}`,
328
334
  `status ${result.status} · generation ${result.generation}`,
@@ -338,7 +344,7 @@ function messageText(content: unknown): string {
338
344
  return content.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n");
339
345
  }
340
346
 
341
- function activeWorkers(snapshot: RuntimeSnapshot): WorkerRecord[] {
347
+ function activeWorkers(snapshot: OwnerSnapshot): WorkerRecord[] {
342
348
  return snapshot.workers.filter((worker) => ACTIVE_STATUSES.has(worker.status));
343
349
  }
344
350