@zachwill/pi-orchestrate 0.9.0 → 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.
@@ -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
  }
@@ -83,17 +87,15 @@ export class StatusController {
83
87
  private disposed = false;
84
88
  private unsubscribeState: (() => void) | undefined;
85
89
  private widget: WorkerStatusComponent | undefined;
86
- private widgetInstalled = false;
87
- private pendingSnapshot: RuntimeSnapshot | undefined;
88
90
 
89
- constructor(private readonly runtime: PresentationRuntime) {}
91
+ constructor(private readonly workerState: WorkerStateSource) {}
90
92
 
91
93
  bind(ownerSessionId: string, ctx: ExtensionContext): void {
92
94
  if (this.disposed) return;
93
95
  this.clearBinding();
94
96
  const binding = { ownerSessionId, ctx };
95
97
  this.binding = binding;
96
- this.unsubscribeState = this.runtime.subscribeState(ownerSessionId, (snapshot) => {
98
+ this.unsubscribeState = this.workerState.subscribeState(ownerSessionId, (snapshot) => {
97
99
  if (this.binding !== binding) return;
98
100
  this.present(binding.ctx, snapshot);
99
101
  });
@@ -110,59 +112,60 @@ export class StatusController {
110
112
  this.clearBinding();
111
113
  }
112
114
 
113
- private present(ctx: ExtensionContext, snapshot: RuntimeSnapshot): void {
115
+ private present(ctx: ExtensionContext, snapshot: OwnerSnapshot): void {
114
116
  ctx.ui.setStatus(ORCHESTRATION_PRESENTATION_KEY, formatFooterStatus(snapshot));
115
117
  if (ctx.mode !== "tui") return;
116
118
  const active = activeWorkers(snapshot);
117
- this.pendingSnapshot = snapshot;
118
119
  if (active.length === 0) {
119
- if (this.widgetInstalled) ctx.ui.setWidget(ORCHESTRATION_PRESENTATION_KEY, undefined);
120
- this.widget = undefined;
121
- this.widgetInstalled = false;
120
+ if (this.widget !== undefined) {
121
+ ctx.ui.setWidget(ORCHESTRATION_PRESENTATION_KEY, undefined);
122
+ this.widget = undefined;
123
+ }
122
124
  return;
123
125
  }
124
- if (this.widget) {
126
+ if (this.widget !== undefined) {
125
127
  this.widget.update(snapshot);
126
128
  return;
127
129
  }
128
- if (this.widgetInstalled) return;
129
130
  ctx.ui.setWidget(ORCHESTRATION_PRESENTATION_KEY, (tui, theme) => {
130
- this.widget = new WorkerStatusComponent(this.pendingSnapshot ?? snapshot, theme, tui);
131
- return this.widget;
131
+ const widget = new WorkerStatusComponent(snapshot, theme, tui);
132
+ this.widget = widget;
133
+ return widget;
132
134
  }, { placement: "aboveEditor" });
133
- this.widgetInstalled = true;
134
135
  }
135
136
 
136
137
  private clearBinding(): void {
137
138
  const unsubscribeState = this.unsubscribeState;
138
139
  this.unsubscribeState = undefined;
139
140
  unsubscribeState?.();
140
- this.widget = undefined;
141
- this.pendingSnapshot = undefined;
142
141
  const current = this.binding;
143
142
  if (!current) return;
144
143
  current.ctx.ui.setStatus(ORCHESTRATION_PRESENTATION_KEY, undefined);
145
- if (current.ctx.mode === "tui" && this.widgetInstalled) current.ctx.ui.setWidget(ORCHESTRATION_PRESENTATION_KEY, undefined);
146
- this.widgetInstalled = false;
144
+ if (current.ctx.mode === "tui" && this.widget !== undefined) {
145
+ current.ctx.ui.setWidget(ORCHESTRATION_PRESENTATION_KEY, undefined);
146
+ this.widget = undefined;
147
+ }
147
148
  this.binding = undefined;
148
149
  }
149
150
  }
150
151
 
151
- export function createStatusController(runtime: PresentationRuntime): StatusController {
152
- return new StatusController(runtime);
152
+ export function createStatusController(
153
+ workerState: WorkerStateSource,
154
+ ): StatusController {
155
+ return new StatusController(workerState);
153
156
  }
154
157
 
155
158
  export class WorkerStatusComponent implements Component {
156
159
  private frameIndex = 0;
157
- private snapshot: RuntimeSnapshot;
160
+ private snapshot: OwnerSnapshot;
158
161
  private timer: ReturnType<typeof setInterval> | undefined;
159
162
 
160
- constructor(snapshot: RuntimeSnapshot, private readonly theme: Theme, private readonly tui?: RenderRequester) {
163
+ constructor(snapshot: OwnerSnapshot, private readonly theme: Theme, private readonly tui?: RenderRequester) {
161
164
  this.snapshot = snapshot;
162
165
  this.startTimer();
163
166
  }
164
167
 
165
- update(snapshot: RuntimeSnapshot): void {
168
+ update(snapshot: OwnerSnapshot): void {
166
169
  this.snapshot = snapshot;
167
170
  if (activeWorkers(snapshot).length > 0) this.startTimer();
168
171
  else this.stopTimer();
@@ -292,8 +295,8 @@ export class WorkerResultComponent implements Component {
292
295
  }
293
296
  }
294
297
 
295
- function readSettlement(value: unknown): WorkerSettlementDetails | undefined {
296
- const decoded = decodePersistedWorkerSettlementDetails(value);
298
+ function readSettlement(value: unknown): WorkerSettlement | undefined {
299
+ const decoded = decodePersistedWorkerSettlement(value);
297
300
  return Result.isSuccess(decoded) ? decoded.success : undefined;
298
301
  }
299
302
 
@@ -310,7 +313,7 @@ function outcomeText(outcome: WorkerOutcome): string {
310
313
  return "Worker session closed.";
311
314
  }
312
315
 
313
- function presentedOutcome(result: WorkerSettlementDetails): string {
316
+ function presentedOutcome(result: WorkerSettlement): string {
314
317
  const body = outcomeText(result.outcome);
315
318
  if (result.status !== "completed" && result.status !== "ready") return body;
316
319
 
@@ -325,7 +328,7 @@ function presentedOutcome(result: WorkerSettlementDetails): string {
325
328
  return lines.join("\n").trimEnd();
326
329
  }
327
330
 
328
- function settlementMetadata(result: WorkerSettlementDetails): string[] {
331
+ function settlementMetadata(result: WorkerSettlement): string[] {
329
332
  return [
330
333
  `worker ID ${result.workerId} · run ID ${result.runId}`,
331
334
  `status ${result.status} · generation ${result.generation}`,
@@ -341,7 +344,7 @@ function messageText(content: unknown): string {
341
344
  return content.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("\n");
342
345
  }
343
346
 
344
- function activeWorkers(snapshot: RuntimeSnapshot): WorkerRecord[] {
347
+ function activeWorkers(snapshot: OwnerSnapshot): WorkerRecord[] {
345
348
  return snapshot.workers.filter((worker) => ACTIVE_STATUSES.has(worker.status));
346
349
  }
347
350
 
@@ -0,0 +1,422 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import type { Theme } from "@earendil-works/pi-coding-agent";
3
+ import {
4
+ getMarkdownTheme,
5
+ keyHint,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import {
8
+ Container,
9
+ Markdown,
10
+ Spacer,
11
+ Text,
12
+ truncateToWidth,
13
+ wrapTextWithAnsi,
14
+ type Component,
15
+ } from "@earendil-works/pi-tui";
16
+ import { Result, Schema } from "effect";
17
+ import {
18
+ disposeComponent,
19
+ formatElapsed,
20
+ resultAppearance,
21
+ WidthBoundComponent,
22
+ } from "./tui.js";
23
+ import {
24
+ decodeInlineWorkerToolDetails,
25
+ type InlineWorkerSettlementDetails,
26
+ WorkerSettlement,
27
+ } from "../orchestration/settlement.js";
28
+
29
+ const MAX_INSTRUCTION_PREVIEW_LINES = 2;
30
+
31
+ const AcceptedRunRenderDetails = Schema.Struct({
32
+ mode: Schema.Literal("async"),
33
+ run_id: WorkerSettlement.fields.runId,
34
+ worker_id: WorkerSettlement.fields.workerId,
35
+ });
36
+ const UnavailableWorkerRenderDetails = Schema.Union([
37
+ Schema.Struct({ result: Schema.Unknown }),
38
+ Schema.Struct({ worker_id: Schema.Unknown }),
39
+ ]);
40
+ const decodeAcceptedRunRenderDetails = Schema.decodeUnknownResult(
41
+ AcceptedRunRenderDetails,
42
+ );
43
+ const decodeUnavailableWorkerRenderDetails = Schema.decodeUnknownResult(
44
+ UnavailableWorkerRenderDetails,
45
+ );
46
+
47
+ interface RenderOptions {
48
+ readonly isPartial: boolean;
49
+ readonly expanded: boolean;
50
+ }
51
+
52
+ interface RenderContext {
53
+ readonly isError: boolean;
54
+ readonly lastComponent?: unknown;
55
+ }
56
+
57
+ export const orchestrateToolRenderer = {
58
+ renderCall(args: unknown, theme: Theme, { expanded }: RenderOptions) {
59
+ return renderDispatchCall(theme, args, expanded);
60
+ },
61
+ renderResult(
62
+ result: AgentToolResult<unknown>,
63
+ { isPartial, expanded }: RenderOptions,
64
+ theme: Theme,
65
+ context: RenderContext,
66
+ ) {
67
+ return renderOrchestrationResult(
68
+ result,
69
+ isPartial,
70
+ expanded,
71
+ theme,
72
+ context.isError,
73
+ context.lastComponent,
74
+ );
75
+ },
76
+ };
77
+
78
+ export const workerStatusToolRenderer = {
79
+ renderCall(_args: unknown, theme: Theme) {
80
+ return new Text(theme.fg("toolTitle", theme.bold("worker_status")), 0, 0);
81
+ },
82
+ renderResult(
83
+ result: AgentToolResult<unknown>,
84
+ { isPartial }: RenderOptions,
85
+ theme: Theme,
86
+ context: RenderContext,
87
+ ) {
88
+ return renderDiagnosticsResult(result, isPartial, context.isError, theme);
89
+ },
90
+ };
91
+
92
+ export const interactiveSendToolRenderer = {
93
+ renderCall(args: unknown, theme: Theme, { expanded }: RenderOptions) {
94
+ const fields = objectFields(args);
95
+ return renderInteractiveMessageCall(
96
+ theme,
97
+ "interactive_send",
98
+ fields.worker_id,
99
+ fields.instructions,
100
+ expanded,
101
+ );
102
+ },
103
+ renderResult: orchestrateToolRenderer.renderResult,
104
+ };
105
+
106
+ export const workerAbortToolRenderer = {
107
+ renderCall(args: unknown, theme: Theme) {
108
+ const fields = objectFields(args);
109
+ const workerIds = Array.isArray(fields.worker_ids)
110
+ ? fields.worker_ids
111
+ : undefined;
112
+ const target = workerIds
113
+ ? `${workerIds.length} worker${workerIds.length === 1 ? "" : "s"}`
114
+ : fields.all === true ? "all workers" : "";
115
+ return renderCompactCall(theme, "worker_abort", target);
116
+ },
117
+ renderResult(
118
+ result: AgentToolResult<unknown>,
119
+ { isPartial }: RenderOptions,
120
+ theme: Theme,
121
+ context: RenderContext,
122
+ ) {
123
+ return renderSimpleResult(
124
+ result,
125
+ context.isError,
126
+ isPartial ? "Requesting worker stop…" : "Worker stop requested",
127
+ theme,
128
+ "warning",
129
+ );
130
+ },
131
+ };
132
+
133
+ export const interactiveCloseToolRenderer = {
134
+ renderCall(args: unknown, theme: Theme) {
135
+ return renderCompactCall(
136
+ theme,
137
+ "interactive_close",
138
+ objectFields(args).worker_id,
139
+ );
140
+ },
141
+ renderResult(
142
+ result: AgentToolResult<unknown>,
143
+ { isPartial }: RenderOptions,
144
+ theme: Theme,
145
+ context: RenderContext,
146
+ ) {
147
+ return renderSimpleResult(
148
+ result,
149
+ context.isError,
150
+ isPartial ? "Closing worker…" : "✓ Worker closed",
151
+ theme,
152
+ );
153
+ },
154
+ };
155
+
156
+ function renderDispatchCall(
157
+ theme: Theme,
158
+ task: unknown,
159
+ expanded: boolean,
160
+ ): Component {
161
+ const fields = isRecord(task) ? task : {};
162
+ const container = new Container();
163
+ container.addChild(new Text(
164
+ theme.fg("toolTitle", theme.bold("orchestrate ")) + theme.fg("muted", safeTerminalText(fields.worker)),
165
+ 0, 0,
166
+ ));
167
+ container.addChild(new Text(
168
+ `${theme.fg("accent", "→")} ${theme.fg("text", theme.bold(safeTerminalText(fields.title)))}`,
169
+ 0, 0,
170
+ ));
171
+ if (expanded) {
172
+ container.addChild(new Text(safeTerminalText(fields.instructions), 2, 0));
173
+ return new WidthBoundComponent(container);
174
+ }
175
+ container.addChild(new InstructionPreview(fields.instructions, theme));
176
+ container.addChild(new Text(theme.fg("dim", keyHint("app.tools.expand", "to inspect full instructions")), 0, 0));
177
+ return new WidthBoundComponent(container);
178
+ }
179
+
180
+ class InstructionPreview implements Component {
181
+ constructor(
182
+ private readonly instructions: unknown,
183
+ private readonly theme: Theme,
184
+ ) {}
185
+ render(width: number): string[] {
186
+ const bounded = Math.max(1, width);
187
+ const contentWidth = Math.max(1, bounded - 2);
188
+ const characterLimit = Math.max(256, Math.min(4096, contentWidth * 3));
189
+ const preview = compactInstructionPreview(this.instructions, characterLimit);
190
+ if (!preview.text) return [];
191
+
192
+ const wrapped = wrapTextWithAnsi(preview.text, contentWidth);
193
+ const previewLines = wrapped.slice(0, MAX_INSTRUCTION_PREVIEW_LINES);
194
+ if (preview.truncated || wrapped.length > MAX_INSTRUCTION_PREVIEW_LINES) {
195
+ const lastIndex = previewLines.length - 1;
196
+ previewLines[lastIndex] = truncateToWidth(`${previewLines[lastIndex] ?? ""}…`, contentWidth, "…");
197
+ }
198
+ return previewLines.map((line) =>
199
+ truncateToWidth(this.theme.fg("dim", ` ${line}`), bounded, "…")
200
+ );
201
+ }
202
+ invalidate(): void {}
203
+ }
204
+
205
+ function renderInteractiveMessageCall(
206
+ theme: Theme,
207
+ tool: string,
208
+ workerId: unknown,
209
+ instructions: unknown,
210
+ expanded: boolean,
211
+ ): Component {
212
+ const container = new Container();
213
+ container.addChild(new Text(theme.fg("toolTitle", theme.bold(`${tool} `)) + theme.fg("muted", safeTerminalText(workerId)), 0, 0));
214
+ if (expanded) container.addChild(new Text(safeTerminalText(instructions), 2, 0));
215
+ else {
216
+ container.addChild(new Text(`${theme.fg("accent", "→")} ${truncateInstruction(instructions, 240)}`, 0, 0));
217
+ container.addChild(new Text(theme.fg("dim", keyHint("app.tools.expand", "to inspect full message")), 0, 0));
218
+ }
219
+ return new WidthBoundComponent(container);
220
+ }
221
+
222
+ function safeTerminalText(value: unknown): string {
223
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
224
+ return text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\x00-\x08\x0B-\x1F\x7F]/g, (character) => {
225
+ const code = character.charCodeAt(0);
226
+ return code === 0x7f ? "␡" : String.fromCodePoint(0x2400 + code);
227
+ });
228
+ }
229
+
230
+ function compactInstructionPreview(instructions: unknown, characterLimit: number): { text: string; truncated: boolean } {
231
+ const text = typeof instructions === "string" ? instructions : instructions == null ? "" : String(instructions);
232
+ const source = text.slice(0, characterLimit);
233
+ return {
234
+ text: safeTerminalText(source).replace(/\s+/g, " ").trim(),
235
+ truncated: source.length < text.length,
236
+ };
237
+ }
238
+
239
+ function firstInstructionLine(instructions: unknown): string | undefined {
240
+ const text = typeof instructions === "string" ? instructions : instructions == null ? "" : String(instructions);
241
+ return text.split(/\r\n?|\n/).find((line) => line.trim().length > 0);
242
+ }
243
+
244
+ function truncateInstruction(instructions: unknown, limit: number): string {
245
+ const first = firstInstructionLine(instructions) ?? "";
246
+ return first.length > limit ? `${first.slice(0, limit - 1)}…` : first;
247
+ }
248
+
249
+ function renderCompactCall(theme: Theme, tool: string, target: unknown): Text {
250
+ return new Text(
251
+ theme.fg("toolTitle", theme.bold(`${tool} `)) + theme.fg("muted", safeTerminalText(target)),
252
+ 0,
253
+ 0,
254
+ );
255
+ }
256
+
257
+ function renderOrchestrationResult(
258
+ result: AgentToolResult<unknown>,
259
+ isPartial: boolean,
260
+ expanded: boolean,
261
+ theme: Theme,
262
+ isError: boolean,
263
+ lastComponent: unknown,
264
+ ): Component {
265
+ if (isError) {
266
+ return new WidthBoundComponent(renderSimpleResult(
267
+ result,
268
+ true,
269
+ firstResultLine(result) || "Worker operation failed",
270
+ theme,
271
+ "warning",
272
+ ));
273
+ }
274
+ const details = result.details;
275
+ if (Result.isSuccess(decodeAcceptedRunRenderDetails(details))) {
276
+ return new WidthBoundComponent(new Text(theme.fg("success", "Sent to worker") + theme.fg("dim", " · response arrives when complete"), 0, 0));
277
+ }
278
+ const inlineResult = readInlineResult(details);
279
+ if (inlineResult) {
280
+ const component = lastComponent instanceof InlineResultComponent
281
+ ? lastComponent
282
+ : new InlineResultComponent(theme);
283
+ component.update(inlineResult, isPartial, expanded);
284
+ return component;
285
+ }
286
+ if (Result.isSuccess(decodeUnavailableWorkerRenderDetails(details))) {
287
+ return new WidthBoundComponent(new Text(theme.fg("warning", "Worker result details unavailable"), 0, 0));
288
+ }
289
+ if (isPartial) return new WidthBoundComponent(new Text(theme.fg("warning", "Sending work…"), 0, 0));
290
+ return new WidthBoundComponent(renderSimpleResult(
291
+ result,
292
+ false,
293
+ firstResultLine(result) || "Work sent",
294
+ theme,
295
+ "warning",
296
+ ));
297
+ }
298
+
299
+ interface RenderedInlineSettlement {
300
+ worker: string;
301
+ title: string;
302
+ status: InlineWorkerSettlementDetails["status"];
303
+ response: string;
304
+ elapsed?: string;
305
+ }
306
+
307
+ class InlineResultComponent implements Component {
308
+ private result: RenderedInlineSettlement | undefined;
309
+ private partial = false;
310
+ private expanded = false;
311
+ private child: Component = new Container();
312
+ constructor(private readonly theme: Theme) {}
313
+ update(result: RenderedInlineSettlement, partial: boolean, expanded: boolean): void {
314
+ this.result = result;
315
+ this.partial = partial;
316
+ this.expanded = expanded;
317
+ this.rebuild();
318
+ }
319
+ render(width: number): string[] { return new WidthBoundComponent(this.child).render(width); }
320
+ invalidate(): void { this.rebuild(); }
321
+ dispose(): void { disposeComponent(this.child); }
322
+ private rebuild(): void {
323
+ disposeComponent(this.child);
324
+ const container = new Container();
325
+ const result = this.result;
326
+ if (!result) {
327
+ this.child = container;
328
+ return;
329
+ }
330
+ const appearance = resultAppearance(result.status, "ready for follow-up");
331
+ const suffix = [appearance.qualifier, result.elapsed].filter(Boolean).join(" · ");
332
+ const title = this.theme.bold(result.title);
333
+ const workerName = this.theme.fg("muted", this.theme.italic(result.worker));
334
+ const header = [
335
+ this.theme.fg(appearance.color, `${appearance.icon} ${title}`),
336
+ workerName,
337
+ ...(suffix ? [this.theme.fg(appearance.color, suffix)] : []),
338
+ ].join(" · ");
339
+ container.addChild(new WidthBoundComponent(new Text(header, 0, 0), 1));
340
+ if (result.response) {
341
+ const markdown = new Markdown(result.response, this.expanded ? 2 : 0, 0, getMarkdownTheme());
342
+ container.addChild(new WidthBoundComponent(markdown, this.expanded ? undefined : 2));
343
+ }
344
+ container.addChild(new Spacer(1));
345
+ if (this.partial) container.addChild(new Text(this.theme.fg("warning", "Receiving worker response…"), 0, 0));
346
+ else if (!this.expanded) container.addChild(new Text(this.theme.fg("dim", keyHint("app.tools.expand", "to inspect full response")), 0, 0));
347
+ this.child = container;
348
+ }
349
+ }
350
+
351
+ function readInlineResult(details: unknown): RenderedInlineSettlement | undefined {
352
+ const decoded = decodeInlineWorkerToolDetails(details);
353
+ if (Result.isFailure(decoded)) return undefined;
354
+ const settlement = decoded.success.result;
355
+ const outcome = settlement.outcome;
356
+ const message = outcome.status === "failed" || outcome.status === "aborted"
357
+ ? outcome.message
358
+ : undefined;
359
+ const assistantText = outcome.assistantText;
360
+ const response = [message, assistantText]
361
+ .filter((item): item is string => typeof item === "string" && item.length > 0)
362
+ .join("\n\n");
363
+ return {
364
+ worker: settlement.worker,
365
+ title: settlement.title,
366
+ status: settlement.status,
367
+ response,
368
+ elapsed: formatElapsed(settlement.settledAt - settlement.startedAt),
369
+ };
370
+ }
371
+
372
+ function renderDiagnosticsResult(
373
+ result: AgentToolResult<unknown>,
374
+ isPartial: boolean,
375
+ isError: boolean,
376
+ theme: Theme,
377
+ ): Text {
378
+ if (isError) {
379
+ return renderSimpleResult(
380
+ result,
381
+ true,
382
+ firstResultLine(result) || "Worker diagnostics failed",
383
+ theme,
384
+ );
385
+ }
386
+ if (isPartial) return new Text(theme.fg("muted", "Reading worker diagnostics…"), 0, 0);
387
+ const details = result.details;
388
+ if (isRecord(details) && isRecord(details.state) && Array.isArray(details.state.workers)) {
389
+ const workers = details.state.workers.filter(isRecord);
390
+ const active = workers.filter((worker) => ["starting", "running", "stopping"].includes(String(worker.status))).length;
391
+ const ready = workers.filter((worker) => worker.status === "ready").length;
392
+ const diagnostics = isRecord(details.catalog) && Array.isArray(details.catalog.diagnostics) ? details.catalog.diagnostics.length : 0;
393
+ const facts = [active ? `${active} active` : "No active workers", ready ? `${ready} available for follow-up` : undefined, diagnostics ? `${diagnostics} catalog diagnostic${diagnostics === 1 ? "" : "s"}` : undefined].filter(Boolean);
394
+ return new Text(theme.fg("muted", facts.join(" · ")), 0, 0);
395
+ }
396
+ return new Text(theme.fg("muted", firstResultLine(result) || "Diagnostics unavailable"), 0, 0);
397
+ }
398
+
399
+ function renderSimpleResult(
400
+ result: AgentToolResult<unknown>,
401
+ isError: boolean,
402
+ message: string,
403
+ theme: Theme,
404
+ normalColor: "success" | "warning" = "success",
405
+ ): Text {
406
+ const text = isError ? firstResultLine(result) || message : message;
407
+ return new Text(theme.fg(isError ? "error" : normalColor, text), 0, 0);
408
+ }
409
+
410
+ function firstResultLine(result: AgentToolResult<unknown>): string | undefined {
411
+ const first = result.content[0];
412
+ if (first?.type !== "text") return undefined;
413
+ return first.text.split("\n").find((line) => line.trim())?.trim();
414
+ }
415
+
416
+ function objectFields(value: unknown): Record<string, unknown> {
417
+ return isRecord(value) ? value : {};
418
+ }
419
+
420
+ function isRecord(value: unknown): value is Record<string, unknown> {
421
+ return typeof value === "object" && value !== null;
422
+ }