@zachwill/pi-orchestrate 0.8.0 → 0.9.2
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/extension/catalog.ts +70 -75
- package/extension/contract.ts +73 -7
- package/extension/delivery.ts +65 -17
- package/extension/domain.ts +83 -58
- package/extension/host.ts +316 -30
- package/extension/index.ts +28 -31
- package/extension/presentation.ts +48 -98
- package/extension/runtime.ts +1690 -884
- package/extension/tools.ts +236 -270
- package/extension/tui.ts +50 -0
- package/extension/worker-session.ts +514 -188
- package/extension/worker-settlement.ts +105 -44
- package/package.json +1 -1
- package/extension/scheduler.ts +0 -85
package/extension/tools.ts
CHANGED
|
@@ -20,13 +20,15 @@ import {
|
|
|
20
20
|
keyHint,
|
|
21
21
|
truncateHead,
|
|
22
22
|
} from "@earendil-works/pi-coding-agent";
|
|
23
|
+
import { Result, Schema } from "effect";
|
|
23
24
|
import { Type } from "typebox";
|
|
24
25
|
import {
|
|
26
|
+
MAX_WORKER_INSTRUCTIONS_LENGTH,
|
|
27
|
+
MAX_WORKER_TITLE_LENGTH,
|
|
25
28
|
type CatalogDiagnostic,
|
|
26
29
|
type RunRecord,
|
|
27
30
|
type WorkerCatalog,
|
|
28
31
|
type WorkerDefinition,
|
|
29
|
-
type WorkerId,
|
|
30
32
|
type WorkerOutcome,
|
|
31
33
|
type WorkerRecord,
|
|
32
34
|
type WorkerUsage,
|
|
@@ -36,33 +38,68 @@ import type {
|
|
|
36
38
|
AcceptedRun,
|
|
37
39
|
CompletedRun,
|
|
38
40
|
OrchestrationContext,
|
|
39
|
-
OrchestratorRuntime,
|
|
40
41
|
RunResult,
|
|
41
42
|
RuntimeSnapshot,
|
|
42
43
|
SettlementListener,
|
|
43
|
-
WorkerSettlement,
|
|
44
44
|
} from "./runtime.js";
|
|
45
|
+
import type { OrchestratorRuntime } from "./host.js";
|
|
46
|
+
import {
|
|
47
|
+
disposeComponent,
|
|
48
|
+
formatElapsed,
|
|
49
|
+
resultAppearance,
|
|
50
|
+
WidthBoundComponent,
|
|
51
|
+
} from "./tui.js";
|
|
52
|
+
import {
|
|
53
|
+
decodeInlineWorkerToolDetails,
|
|
54
|
+
encodeInlineWorkerToolDetails,
|
|
55
|
+
type InlineWorkerSettlementDetails,
|
|
56
|
+
WorkerSettlementDetails,
|
|
57
|
+
type WorkerSettlement,
|
|
58
|
+
} from "./worker-settlement.js";
|
|
45
59
|
|
|
46
60
|
const STRICT_OBJECT = { additionalProperties: false } as const;
|
|
47
61
|
const MAX_INSTRUCTION_PREVIEW_LINES = 2;
|
|
62
|
+
const shortTextSchema = Type.String({
|
|
63
|
+
pattern: "\\S",
|
|
64
|
+
maxLength: MAX_WORKER_TITLE_LENGTH,
|
|
65
|
+
});
|
|
66
|
+
const instructionsSchema = Type.String({
|
|
67
|
+
pattern: "\\S",
|
|
68
|
+
maxLength: MAX_WORKER_INSTRUCTIONS_LENGTH,
|
|
69
|
+
});
|
|
70
|
+
const workerIdSchema = Type.String({ pattern: "^worker-\\S+$" });
|
|
71
|
+
|
|
72
|
+
const AcceptedRunRenderDetails = Schema.Struct({
|
|
73
|
+
mode: Schema.Literal("async"),
|
|
74
|
+
run_id: WorkerSettlementDetails.fields.runId,
|
|
75
|
+
worker_id: WorkerSettlementDetails.fields.workerId,
|
|
76
|
+
});
|
|
77
|
+
const UnavailableWorkerRenderDetails = Schema.Union([
|
|
78
|
+
Schema.Struct({ result: Schema.Unknown }),
|
|
79
|
+
Schema.Struct({ worker_id: Schema.Unknown }),
|
|
80
|
+
]);
|
|
81
|
+
const decodeAcceptedRunRenderDetails = Schema.decodeUnknownResult(
|
|
82
|
+
AcceptedRunRenderDetails,
|
|
83
|
+
);
|
|
84
|
+
const decodeUnavailableWorkerRenderDetails = Schema.decodeUnknownResult(
|
|
85
|
+
UnavailableWorkerRenderDetails,
|
|
86
|
+
);
|
|
48
87
|
|
|
49
88
|
const taskSchema = Type.Object(
|
|
50
89
|
{
|
|
51
|
-
worker:
|
|
52
|
-
title:
|
|
53
|
-
instructions:
|
|
90
|
+
worker: shortTextSchema,
|
|
91
|
+
title: shortTextSchema,
|
|
92
|
+
instructions: instructionsSchema,
|
|
54
93
|
},
|
|
55
94
|
STRICT_OBJECT,
|
|
56
95
|
);
|
|
57
96
|
|
|
58
|
-
const orchestrateSchema = taskSchema;
|
|
59
|
-
|
|
60
97
|
const statusSchema = Type.Object({}, STRICT_OBJECT);
|
|
61
98
|
|
|
62
99
|
const interactiveSendSchema = Type.Object(
|
|
63
100
|
{
|
|
64
|
-
worker_id:
|
|
65
|
-
instructions:
|
|
101
|
+
worker_id: workerIdSchema,
|
|
102
|
+
instructions: instructionsSchema,
|
|
66
103
|
},
|
|
67
104
|
STRICT_OBJECT,
|
|
68
105
|
);
|
|
@@ -70,7 +107,7 @@ const interactiveSendSchema = Type.Object(
|
|
|
70
107
|
const workerAbortSchema = Type.Union([
|
|
71
108
|
Type.Object(
|
|
72
109
|
{
|
|
73
|
-
worker_ids: Type.Array(
|
|
110
|
+
worker_ids: Type.Array(workerIdSchema, { minItems: 1 }),
|
|
74
111
|
},
|
|
75
112
|
STRICT_OBJECT,
|
|
76
113
|
),
|
|
@@ -84,7 +121,7 @@ const workerAbortSchema = Type.Union([
|
|
|
84
121
|
|
|
85
122
|
const interactiveCloseSchema = Type.Object(
|
|
86
123
|
{
|
|
87
|
-
worker_id:
|
|
124
|
+
worker_id: workerIdSchema,
|
|
88
125
|
},
|
|
89
126
|
STRICT_OBJECT,
|
|
90
127
|
);
|
|
@@ -99,7 +136,7 @@ export interface DispatchDecision {
|
|
|
99
136
|
|
|
100
137
|
export interface OrchestrationToolDependencies {
|
|
101
138
|
readonly runtime: OrchestratorRuntime;
|
|
102
|
-
getCatalog(ctx: ExtensionContext): WorkerCatalog
|
|
139
|
+
getCatalog(ctx: ExtensionContext): WorkerCatalog;
|
|
103
140
|
getDispatchDecision(toolCallId: string): DispatchDecision;
|
|
104
141
|
}
|
|
105
142
|
|
|
@@ -120,17 +157,24 @@ export function registerOrchestrationTools(
|
|
|
120
157
|
"Form all N calls before emitting or finalizing the response. Never emit one call and wait for its result before forming the rest of the wave: a successfully admitted sole async orchestrate call returns terminate=true and ends the turn.",
|
|
121
158
|
],
|
|
122
159
|
executionMode: "parallel",
|
|
123
|
-
parameters:
|
|
160
|
+
parameters: taskSchema,
|
|
124
161
|
renderCall(args, theme, { expanded }) {
|
|
125
162
|
return renderDispatchCall(theme, args, expanded);
|
|
126
163
|
},
|
|
127
164
|
renderResult(result, { isPartial, expanded }, theme, context) {
|
|
128
|
-
return renderOrchestrationResult(
|
|
165
|
+
return renderOrchestrationResult(
|
|
166
|
+
result,
|
|
167
|
+
isPartial,
|
|
168
|
+
expanded,
|
|
169
|
+
theme,
|
|
170
|
+
context.isError,
|
|
171
|
+
context.lastComponent,
|
|
172
|
+
);
|
|
129
173
|
},
|
|
130
174
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
131
175
|
const decision = deps.getDispatchDecision(toolCallId);
|
|
132
176
|
const mode = decision.mode;
|
|
133
|
-
const runtimeContext =
|
|
177
|
+
const runtimeContext = buildRuntimeContext(ctx, deps, decision.synthesisGroup);
|
|
134
178
|
if (mode === "async") {
|
|
135
179
|
const acceptedRun = await deps.runtime.orchestrate(
|
|
136
180
|
runtimeContext,
|
|
@@ -140,13 +184,7 @@ export function registerOrchestrationTools(
|
|
|
140
184
|
);
|
|
141
185
|
const readable = acceptedRunDetails(acceptedRun);
|
|
142
186
|
return {
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
type: "text",
|
|
146
|
-
text: readableDetails(`Accepted async run ${readable.run_id}.`, readable),
|
|
147
|
-
},
|
|
148
|
-
],
|
|
149
|
-
details: readable,
|
|
187
|
+
...readableToolResult(`Accepted async run ${readable.run_id}.`, readable),
|
|
150
188
|
terminate: true,
|
|
151
189
|
};
|
|
152
190
|
}
|
|
@@ -159,18 +197,10 @@ export function registerOrchestrationTools(
|
|
|
159
197
|
createInlineSettlementListener(onUpdate),
|
|
160
198
|
);
|
|
161
199
|
const readable = completedRunDetails(completedRun);
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
text: readableDetails(
|
|
167
|
-
`Completed inline run ${readable.run_id}.`,
|
|
168
|
-
readable,
|
|
169
|
-
),
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
details: readable,
|
|
173
|
-
};
|
|
200
|
+
return readableToolResult(
|
|
201
|
+
`Completed inline run ${readable.run_id}.`,
|
|
202
|
+
readable,
|
|
203
|
+
);
|
|
174
204
|
},
|
|
175
205
|
});
|
|
176
206
|
|
|
@@ -187,28 +217,18 @@ export function registerOrchestrationTools(
|
|
|
187
217
|
renderCall(_args, theme) {
|
|
188
218
|
return new Text(theme.fg("toolTitle", theme.bold("worker_status")), 0, 0);
|
|
189
219
|
},
|
|
190
|
-
renderResult(result, { isPartial }, theme) {
|
|
191
|
-
return renderDiagnosticsResult(result, isPartial, theme);
|
|
220
|
+
renderResult(result, { isPartial }, theme, context) {
|
|
221
|
+
return renderDiagnosticsResult(result, isPartial, context.isError, theme);
|
|
192
222
|
},
|
|
193
223
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
194
|
-
const ownerSessionId =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
198
|
-
const [catalog, snapshot] = await Promise.all([
|
|
199
|
-
deps.getCatalog(ctx),
|
|
200
|
-
deps.runtime.snapshot(ownerSessionId),
|
|
201
|
-
]);
|
|
224
|
+
const ownerSessionId = ctx.sessionManager.getSessionId();
|
|
225
|
+
const catalog = deps.getCatalog(ctx);
|
|
226
|
+
const snapshot = await deps.runtime.snapshot(ownerSessionId);
|
|
202
227
|
const readable = statusDetails(catalog, snapshot);
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
text: readableDetails("Worker diagnostics and recovery snapshot.", readable),
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
details: readable,
|
|
211
|
-
};
|
|
228
|
+
return readableToolResult(
|
|
229
|
+
"Worker diagnostics and recovery snapshot.",
|
|
230
|
+
readable,
|
|
231
|
+
);
|
|
212
232
|
},
|
|
213
233
|
});
|
|
214
234
|
|
|
@@ -223,15 +243,29 @@ export function registerOrchestrationTools(
|
|
|
223
243
|
],
|
|
224
244
|
parameters: interactiveSendSchema,
|
|
225
245
|
renderCall(args, theme, { expanded }) {
|
|
226
|
-
|
|
246
|
+
const fields = recordFields(args);
|
|
247
|
+
return renderInteractiveMessageCall(
|
|
248
|
+
theme,
|
|
249
|
+
"interactive_send",
|
|
250
|
+
fields.worker_id,
|
|
251
|
+
fields.instructions,
|
|
252
|
+
expanded,
|
|
253
|
+
);
|
|
227
254
|
},
|
|
228
255
|
renderResult(result, { isPartial, expanded }, theme, context) {
|
|
229
|
-
return renderOrchestrationResult(
|
|
256
|
+
return renderOrchestrationResult(
|
|
257
|
+
result,
|
|
258
|
+
isPartial,
|
|
259
|
+
expanded,
|
|
260
|
+
theme,
|
|
261
|
+
context.isError,
|
|
262
|
+
context.lastComponent,
|
|
263
|
+
);
|
|
230
264
|
},
|
|
231
265
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
232
|
-
const workerId =
|
|
266
|
+
const workerId = params.worker_id;
|
|
233
267
|
const mode = deps.getDispatchDecision(toolCallId).mode;
|
|
234
|
-
const runtimeContext =
|
|
268
|
+
const runtimeContext = buildRuntimeContext(ctx, deps);
|
|
235
269
|
if (mode === "async") {
|
|
236
270
|
const acceptedRun = await deps.runtime.sendInteractive(
|
|
237
271
|
runtimeContext,
|
|
@@ -242,13 +276,7 @@ export function registerOrchestrationTools(
|
|
|
242
276
|
);
|
|
243
277
|
const readable = acceptedRunDetails(acceptedRun);
|
|
244
278
|
return {
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
type: "text",
|
|
248
|
-
text: readableDetails(`Accepted async run ${readable.run_id}.`, readable),
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
details: readable,
|
|
279
|
+
...readableToolResult(`Accepted async run ${readable.run_id}.`, readable),
|
|
252
280
|
terminate: true,
|
|
253
281
|
};
|
|
254
282
|
}
|
|
@@ -262,18 +290,10 @@ export function registerOrchestrationTools(
|
|
|
262
290
|
createInlineSettlementListener(onUpdate),
|
|
263
291
|
);
|
|
264
292
|
const readable = completedRunDetails(completedRun);
|
|
265
|
-
return
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
text: readableDetails(
|
|
270
|
-
`Completed inline run ${readable.run_id}.`,
|
|
271
|
-
readable,
|
|
272
|
-
),
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
details: readable,
|
|
276
|
-
};
|
|
293
|
+
return readableToolResult(
|
|
294
|
+
`Completed inline run ${readable.run_id}.`,
|
|
295
|
+
readable,
|
|
296
|
+
);
|
|
277
297
|
},
|
|
278
298
|
});
|
|
279
299
|
|
|
@@ -288,31 +308,34 @@ export function registerOrchestrationTools(
|
|
|
288
308
|
],
|
|
289
309
|
parameters: workerAbortSchema,
|
|
290
310
|
renderCall(args, theme) {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
311
|
+
const fields = recordFields(args);
|
|
312
|
+
const workerIds = Array.isArray(fields.worker_ids)
|
|
313
|
+
? fields.worker_ids
|
|
314
|
+
: undefined;
|
|
315
|
+
const target = workerIds
|
|
316
|
+
? `${workerIds.length} worker${workerIds.length === 1 ? "" : "s"}`
|
|
317
|
+
: fields.all === true ? "all workers" : "";
|
|
294
318
|
return renderCompactCall(theme, "worker_abort", target);
|
|
295
319
|
},
|
|
296
|
-
renderResult(result, { isPartial }, theme) {
|
|
297
|
-
return renderSimpleResult(
|
|
320
|
+
renderResult(result, { isPartial }, theme, context) {
|
|
321
|
+
return renderSimpleResult(
|
|
322
|
+
result,
|
|
323
|
+
context.isError,
|
|
324
|
+
isPartial ? "Requesting worker stop…" : "Worker stop requested",
|
|
325
|
+
theme,
|
|
326
|
+
"warning",
|
|
327
|
+
);
|
|
298
328
|
},
|
|
299
329
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
300
|
-
const ownerSessionId =
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
content: [
|
|
309
|
-
{
|
|
310
|
-
type: "text",
|
|
311
|
-
text: readableDetails("Abort request completed.", readable),
|
|
312
|
-
},
|
|
313
|
-
],
|
|
314
|
-
details: { target: target.external },
|
|
330
|
+
const ownerSessionId = ctx.sessionManager.getSessionId();
|
|
331
|
+
const target = normalizeAbortTarget(params);
|
|
332
|
+
await deps.runtime.abort(ownerSessionId, target);
|
|
333
|
+
const readable = {
|
|
334
|
+
target: "worker_ids" in params
|
|
335
|
+
? { worker_ids: params.worker_ids }
|
|
336
|
+
: { all: params.all },
|
|
315
337
|
};
|
|
338
|
+
return readableToolResult("Abort request completed.", readable);
|
|
316
339
|
},
|
|
317
340
|
});
|
|
318
341
|
|
|
@@ -326,47 +349,39 @@ export function registerOrchestrationTools(
|
|
|
326
349
|
],
|
|
327
350
|
parameters: interactiveCloseSchema,
|
|
328
351
|
renderCall(args, theme) {
|
|
329
|
-
|
|
352
|
+
const fields = recordFields(args);
|
|
353
|
+
return renderCompactCall(theme, "interactive_close", fields.worker_id);
|
|
330
354
|
},
|
|
331
|
-
renderResult(result, { isPartial }, theme) {
|
|
332
|
-
return renderSimpleResult(
|
|
355
|
+
renderResult(result, { isPartial }, theme, context) {
|
|
356
|
+
return renderSimpleResult(
|
|
357
|
+
result,
|
|
358
|
+
context.isError,
|
|
359
|
+
isPartial ? "Closing worker…" : "✓ Worker closed",
|
|
360
|
+
theme,
|
|
361
|
+
);
|
|
333
362
|
},
|
|
334
363
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
335
|
-
const ownerSessionId =
|
|
336
|
-
|
|
337
|
-
ctx.sessionManager.getSessionId(),
|
|
338
|
-
);
|
|
339
|
-
const workerId = asWorkerId(params.worker_id);
|
|
364
|
+
const ownerSessionId = ctx.sessionManager.getSessionId();
|
|
365
|
+
const workerId = params.worker_id;
|
|
340
366
|
await deps.runtime.closeInteractive(ownerSessionId, workerId);
|
|
341
367
|
const readable = { worker_id: workerId };
|
|
342
|
-
return {
|
|
343
|
-
content: [
|
|
344
|
-
{
|
|
345
|
-
type: "text",
|
|
346
|
-
text: readableDetails(`Closed worker ${workerId}.`, readable),
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
details: readable,
|
|
350
|
-
};
|
|
368
|
+
return readableToolResult(`Closed worker ${workerId}.`, readable);
|
|
351
369
|
},
|
|
352
370
|
});
|
|
353
371
|
}
|
|
354
372
|
|
|
355
|
-
|
|
373
|
+
function buildRuntimeContext(
|
|
356
374
|
ctx: ExtensionContext,
|
|
357
375
|
deps: OrchestrationToolDependencies,
|
|
358
376
|
synthesisGroup?: DispatchDecision["synthesisGroup"],
|
|
359
|
-
):
|
|
377
|
+
): OrchestrationContext {
|
|
360
378
|
return {
|
|
361
|
-
ownerSessionId:
|
|
362
|
-
"owner session ID",
|
|
363
|
-
ctx.sessionManager.getSessionId(),
|
|
364
|
-
),
|
|
379
|
+
ownerSessionId: ctx.sessionManager.getSessionId(),
|
|
365
380
|
cwd: ctx.cwd,
|
|
366
381
|
agentDir: getAgentDir(),
|
|
367
382
|
parentSessionFile: ctx.sessionManager.getSessionFile(),
|
|
368
383
|
projectTrusted: ctx.isProjectTrusted(),
|
|
369
|
-
catalog:
|
|
384
|
+
catalog: deps.getCatalog(ctx),
|
|
370
385
|
parentModel: ctx.model,
|
|
371
386
|
modelRegistry: ctx.modelRegistry,
|
|
372
387
|
...(synthesisGroup ? { synthesisGroup } : {}),
|
|
@@ -379,53 +394,21 @@ function createInlineSettlementListener(
|
|
|
379
394
|
return (settlement) => {
|
|
380
395
|
onUpdate?.({
|
|
381
396
|
content: [{ type: "text", text: "Worker response received." }],
|
|
382
|
-
details: {
|
|
397
|
+
details: encodeInlineWorkerToolDetails({
|
|
383
398
|
mode: "inline",
|
|
384
|
-
result:
|
|
385
|
-
},
|
|
399
|
+
result: inlineResultValue(settlement),
|
|
400
|
+
}),
|
|
386
401
|
});
|
|
387
402
|
};
|
|
388
403
|
}
|
|
389
404
|
|
|
390
|
-
function
|
|
391
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
392
|
-
throw new Error(`${name} must not be blank`);
|
|
393
|
-
}
|
|
394
|
-
return value;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function asWorkerId(value: string): WorkerId {
|
|
398
|
-
return requireNonblank("worker_id", value) as WorkerId;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function abortTarget(params: {
|
|
405
|
+
function normalizeAbortTarget(params: {
|
|
402
406
|
worker_ids?: string[];
|
|
403
|
-
all?:
|
|
404
|
-
}): {
|
|
405
|
-
runtime: AbortTarget;
|
|
406
|
-
external: { worker_ids: readonly WorkerId[] } | { all: true };
|
|
407
|
-
} {
|
|
408
|
-
const selectedTargetCount = [
|
|
409
|
-
params.worker_ids !== undefined,
|
|
410
|
-
params.all !== undefined,
|
|
411
|
-
].filter(Boolean).length;
|
|
412
|
-
if (selectedTargetCount !== 1 || (params.all !== undefined && params.all !== true)) {
|
|
413
|
-
throw new Error("Abort target must specify exactly one target");
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (params.worker_ids !== undefined) {
|
|
417
|
-
if (!Array.isArray(params.worker_ids) || params.worker_ids.length === 0) {
|
|
418
|
-
throw new Error("worker_ids must contain at least one worker ID");
|
|
419
|
-
}
|
|
420
|
-
const workerIds = params.worker_ids.map(asWorkerId);
|
|
421
|
-
return {
|
|
422
|
-
runtime: { workerIds },
|
|
423
|
-
external: { worker_ids: workerIds },
|
|
424
|
-
};
|
|
425
|
-
}
|
|
407
|
+
all?: boolean;
|
|
408
|
+
}): AbortTarget {
|
|
426
409
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
410
|
+
...(params.worker_ids !== undefined ? { workerIds: params.worker_ids } : {}),
|
|
411
|
+
...(params.all !== undefined ? { all: params.all } : {}),
|
|
429
412
|
};
|
|
430
413
|
}
|
|
431
414
|
|
|
@@ -438,39 +421,29 @@ function acceptedRunDetails(run: AcceptedRun) {
|
|
|
438
421
|
}
|
|
439
422
|
|
|
440
423
|
function completedRunDetails(run: CompletedRun) {
|
|
441
|
-
return {
|
|
442
|
-
mode:
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
result:
|
|
446
|
-
};
|
|
424
|
+
return encodeInlineWorkerToolDetails({
|
|
425
|
+
mode: "inline",
|
|
426
|
+
runId: run.id,
|
|
427
|
+
ownerSessionId: run.ownerSessionId,
|
|
428
|
+
result: inlineResultValue(run.result),
|
|
429
|
+
});
|
|
447
430
|
}
|
|
448
431
|
|
|
449
|
-
function
|
|
432
|
+
function inlineResultValue(
|
|
433
|
+
result: RunResult | WorkerSettlement,
|
|
434
|
+
): InlineWorkerSettlementDetails {
|
|
450
435
|
return {
|
|
451
|
-
|
|
436
|
+
workerId: result.workerId,
|
|
452
437
|
worker: result.worker,
|
|
453
438
|
title: result.title,
|
|
454
439
|
status: result.status,
|
|
455
|
-
outcome:
|
|
456
|
-
usage:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function inlineResultDetails(settlement: WorkerSettlement) {
|
|
464
|
-
return {
|
|
465
|
-
worker_id: settlement.workerId,
|
|
466
|
-
worker: settlement.worker,
|
|
467
|
-
title: settlement.title,
|
|
468
|
-
status: settlement.status,
|
|
469
|
-
outcome: outcomeDetails(settlement.outcome),
|
|
470
|
-
usage: usageDetails(settlement.usage),
|
|
471
|
-
started_at: settlement.startedAt,
|
|
472
|
-
settled_at: settlement.settledAt,
|
|
473
|
-
session_file: settlement.sessionFile,
|
|
440
|
+
outcome: result.outcome,
|
|
441
|
+
usage: result.usage,
|
|
442
|
+
startedAt: result.startedAt,
|
|
443
|
+
settledAt: result.settledAt,
|
|
444
|
+
...(result.sessionFile === undefined
|
|
445
|
+
? {}
|
|
446
|
+
: { sessionFile: result.sessionFile }),
|
|
474
447
|
};
|
|
475
448
|
}
|
|
476
449
|
|
|
@@ -580,6 +553,13 @@ function outcomeDetails(outcome: WorkerOutcome) {
|
|
|
580
553
|
}
|
|
581
554
|
}
|
|
582
555
|
|
|
556
|
+
function readableToolResult<T>(title: string, details: T) {
|
|
557
|
+
return {
|
|
558
|
+
content: [{ type: "text" as const, text: readableDetails(title, details) }],
|
|
559
|
+
details,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
583
563
|
function readableDetails(title: string, details: unknown): string {
|
|
584
564
|
const content = `${title}\n\n${JSON.stringify(details, null, 2)}`;
|
|
585
565
|
const truncation = truncateHead(content);
|
|
@@ -588,31 +568,26 @@ function readableDetails(title: string, details: unknown): string {
|
|
|
588
568
|
return `${truncation.content}\n\n[Output truncated: ${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}. Full structured details remain available.]`;
|
|
589
569
|
}
|
|
590
570
|
|
|
591
|
-
interface RenderableTask {
|
|
592
|
-
readonly worker?: unknown;
|
|
593
|
-
readonly title?: unknown;
|
|
594
|
-
readonly instructions?: unknown;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
571
|
function renderDispatchCall(
|
|
598
572
|
theme: Theme,
|
|
599
|
-
task:
|
|
573
|
+
task: unknown,
|
|
600
574
|
expanded: boolean,
|
|
601
575
|
): Component {
|
|
576
|
+
const fields = isRecord(task) ? task : {};
|
|
602
577
|
const container = new Container();
|
|
603
578
|
container.addChild(new Text(
|
|
604
|
-
theme.fg("toolTitle", theme.bold("orchestrate ")) + theme.fg("muted", safeTerminalText(
|
|
579
|
+
theme.fg("toolTitle", theme.bold("orchestrate ")) + theme.fg("muted", safeTerminalText(fields.worker)),
|
|
605
580
|
0, 0,
|
|
606
581
|
));
|
|
607
582
|
container.addChild(new Text(
|
|
608
|
-
`${theme.fg("accent", "→")} ${theme.fg("text", theme.bold(safeTerminalText(
|
|
583
|
+
`${theme.fg("accent", "→")} ${theme.fg("text", theme.bold(safeTerminalText(fields.title)))}`,
|
|
609
584
|
0, 0,
|
|
610
585
|
));
|
|
611
586
|
if (expanded) {
|
|
612
|
-
container.addChild(new Text(safeTerminalText(
|
|
587
|
+
container.addChild(new Text(safeTerminalText(fields.instructions), 2, 0));
|
|
613
588
|
return new WidthBoundComponent(container);
|
|
614
589
|
}
|
|
615
|
-
container.addChild(new InstructionPreview(
|
|
590
|
+
container.addChild(new InstructionPreview(fields.instructions, theme));
|
|
616
591
|
container.addChild(new Text(theme.fg("dim", keyHint("app.tools.expand", "to inspect full instructions")), 0, 0));
|
|
617
592
|
return new WidthBoundComponent(container);
|
|
618
593
|
}
|
|
@@ -659,18 +634,6 @@ function renderInteractiveMessageCall(
|
|
|
659
634
|
return new WidthBoundComponent(container);
|
|
660
635
|
}
|
|
661
636
|
|
|
662
|
-
class WidthBoundComponent implements Component {
|
|
663
|
-
constructor(private readonly child: Component, private readonly maxLines?: number) {}
|
|
664
|
-
render(width: number): string[] {
|
|
665
|
-
const bounded = Math.max(1, Math.floor(width));
|
|
666
|
-
const lines = this.child.render(bounded);
|
|
667
|
-
return (this.maxLines === undefined ? lines : lines.slice(0, this.maxLines))
|
|
668
|
-
.map((line) => truncateToWidth(line, bounded, "…"));
|
|
669
|
-
}
|
|
670
|
-
invalidate(): void { this.child.invalidate(); }
|
|
671
|
-
dispose(): void { (this.child as Component & { dispose?: () => void }).dispose?.(); }
|
|
672
|
-
}
|
|
673
|
-
|
|
674
637
|
function safeTerminalText(value: unknown): string {
|
|
675
638
|
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
676
639
|
return text.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\x00-\x08\x0B-\x1F\x7F]/g, (character) => {
|
|
@@ -711,10 +674,20 @@ function renderOrchestrationResult(
|
|
|
711
674
|
isPartial: boolean,
|
|
712
675
|
expanded: boolean,
|
|
713
676
|
theme: Theme,
|
|
677
|
+
isError: boolean,
|
|
714
678
|
lastComponent: unknown,
|
|
715
679
|
): Component {
|
|
680
|
+
if (isError) {
|
|
681
|
+
return new WidthBoundComponent(renderSimpleResult(
|
|
682
|
+
result,
|
|
683
|
+
true,
|
|
684
|
+
firstResultLine(result) || "Worker operation failed",
|
|
685
|
+
theme,
|
|
686
|
+
"warning",
|
|
687
|
+
));
|
|
688
|
+
}
|
|
716
689
|
const details = result.details;
|
|
717
|
-
if (
|
|
690
|
+
if (Result.isSuccess(decodeAcceptedRunRenderDetails(details))) {
|
|
718
691
|
return new WidthBoundComponent(new Text(theme.fg("success", "Sent to worker") + theme.fg("dim", " · response arrives when complete"), 0, 0));
|
|
719
692
|
}
|
|
720
693
|
const inlineResult = readInlineResult(details);
|
|
@@ -725,28 +698,34 @@ function renderOrchestrationResult(
|
|
|
725
698
|
component.update(inlineResult, isPartial, expanded);
|
|
726
699
|
return component;
|
|
727
700
|
}
|
|
728
|
-
if (
|
|
701
|
+
if (Result.isSuccess(decodeUnavailableWorkerRenderDetails(details))) {
|
|
729
702
|
return new WidthBoundComponent(new Text(theme.fg("warning", "Worker result details unavailable"), 0, 0));
|
|
730
703
|
}
|
|
731
704
|
if (isPartial) return new WidthBoundComponent(new Text(theme.fg("warning", "Sending work…"), 0, 0));
|
|
732
|
-
return new WidthBoundComponent(renderSimpleResult(
|
|
705
|
+
return new WidthBoundComponent(renderSimpleResult(
|
|
706
|
+
result,
|
|
707
|
+
false,
|
|
708
|
+
firstResultLine(result) || "Work sent",
|
|
709
|
+
theme,
|
|
710
|
+
"warning",
|
|
711
|
+
));
|
|
733
712
|
}
|
|
734
713
|
|
|
735
|
-
interface
|
|
714
|
+
interface RenderedInlineSettlement {
|
|
736
715
|
worker: string;
|
|
737
716
|
title: string;
|
|
738
|
-
status: "
|
|
717
|
+
status: InlineWorkerSettlementDetails["status"];
|
|
739
718
|
response: string;
|
|
740
719
|
elapsed?: string;
|
|
741
720
|
}
|
|
742
721
|
|
|
743
722
|
class InlineResultComponent implements Component {
|
|
744
|
-
private result:
|
|
723
|
+
private result: RenderedInlineSettlement | undefined;
|
|
745
724
|
private partial = false;
|
|
746
725
|
private expanded = false;
|
|
747
726
|
private child: Component = new Container();
|
|
748
727
|
constructor(private readonly theme: Theme) {}
|
|
749
|
-
update(result:
|
|
728
|
+
update(result: RenderedInlineSettlement, partial: boolean, expanded: boolean): void {
|
|
750
729
|
this.result = result;
|
|
751
730
|
this.partial = partial;
|
|
752
731
|
this.expanded = expanded;
|
|
@@ -754,16 +733,16 @@ class InlineResultComponent implements Component {
|
|
|
754
733
|
}
|
|
755
734
|
render(width: number): string[] { return new WidthBoundComponent(this.child).render(width); }
|
|
756
735
|
invalidate(): void { this.rebuild(); }
|
|
757
|
-
dispose(): void { (this.child
|
|
736
|
+
dispose(): void { disposeComponent(this.child); }
|
|
758
737
|
private rebuild(): void {
|
|
759
|
-
(this.child
|
|
738
|
+
disposeComponent(this.child);
|
|
760
739
|
const container = new Container();
|
|
761
740
|
const result = this.result;
|
|
762
741
|
if (!result) {
|
|
763
742
|
this.child = container;
|
|
764
743
|
return;
|
|
765
744
|
}
|
|
766
|
-
const appearance =
|
|
745
|
+
const appearance = resultAppearance(result.status, "ready for follow-up");
|
|
767
746
|
const suffix = [appearance.qualifier, result.elapsed].filter(Boolean).join(" · ");
|
|
768
747
|
const title = this.theme.bold(result.title);
|
|
769
748
|
const workerName = this.theme.fg("muted", this.theme.italic(result.worker));
|
|
@@ -784,59 +763,41 @@ class InlineResultComponent implements Component {
|
|
|
784
763
|
}
|
|
785
764
|
}
|
|
786
765
|
|
|
787
|
-
function readInlineResult(details: unknown):
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
const outcome = value.outcome;
|
|
795
|
-
const statuses = ["completed", "ready", "failed", "aborted"] as const;
|
|
796
|
-
const status = statuses.find((item) => item === value.status);
|
|
797
|
-
const outcomeStatus = statuses.find((item) => item === outcome.status);
|
|
798
|
-
if (!status || outcomeStatus !== status) return undefined;
|
|
799
|
-
const message = outcome.message;
|
|
800
|
-
const assistantText = outcome.assistant_text;
|
|
801
|
-
if (message !== undefined && typeof message !== "string") return undefined;
|
|
802
|
-
if (assistantText !== undefined && typeof assistantText !== "string") return undefined;
|
|
803
|
-
if ((status === "completed" || status === "ready") && typeof assistantText !== "string") return undefined;
|
|
804
|
-
if (status === "failed" && typeof message !== "string") return undefined;
|
|
805
|
-
const startedAt = value.started_at;
|
|
806
|
-
const settledAt = value.settled_at;
|
|
807
|
-
const elapsed = typeof startedAt === "number" && typeof settledAt === "number" && settledAt >= startedAt
|
|
808
|
-
? formatElapsed(settledAt - startedAt)
|
|
766
|
+
function readInlineResult(details: unknown): RenderedInlineSettlement | undefined {
|
|
767
|
+
const decoded = decodeInlineWorkerToolDetails(details);
|
|
768
|
+
if (Result.isFailure(decoded)) return undefined;
|
|
769
|
+
const settlement = decoded.success.result;
|
|
770
|
+
const outcome = settlement.outcome;
|
|
771
|
+
const message = outcome.status === "failed" || outcome.status === "aborted"
|
|
772
|
+
? outcome.message
|
|
809
773
|
: undefined;
|
|
774
|
+
const assistantText = outcome.assistantText;
|
|
775
|
+
const response = [message, assistantText]
|
|
776
|
+
.filter((item): item is string => typeof item === "string" && item.length > 0)
|
|
777
|
+
.join("\n\n");
|
|
810
778
|
return {
|
|
811
|
-
worker:
|
|
812
|
-
title:
|
|
813
|
-
status,
|
|
814
|
-
response
|
|
815
|
-
|
|
779
|
+
worker: settlement.worker,
|
|
780
|
+
title: settlement.title,
|
|
781
|
+
status: settlement.status,
|
|
782
|
+
response,
|
|
783
|
+
elapsed: formatElapsed(settlement.settledAt - settlement.startedAt),
|
|
816
784
|
};
|
|
817
785
|
}
|
|
818
786
|
|
|
819
|
-
function
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
|
|
827
|
-
|
|
787
|
+
function renderDiagnosticsResult(
|
|
788
|
+
result: AgentToolResult<unknown>,
|
|
789
|
+
isPartial: boolean,
|
|
790
|
+
isError: boolean,
|
|
791
|
+
theme: Theme,
|
|
792
|
+
): Text {
|
|
793
|
+
if (isError) {
|
|
794
|
+
return renderSimpleResult(
|
|
795
|
+
result,
|
|
796
|
+
true,
|
|
797
|
+
firstResultLine(result) || "Worker diagnostics failed",
|
|
798
|
+
theme,
|
|
799
|
+
);
|
|
828
800
|
}
|
|
829
|
-
return { color: "success", icon: "✓" };
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function formatElapsed(milliseconds: number): string {
|
|
833
|
-
const seconds = Math.floor(milliseconds / 1000);
|
|
834
|
-
if (seconds < 60) return `${seconds}s`;
|
|
835
|
-
const minutes = Math.floor(seconds / 60);
|
|
836
|
-
return seconds % 60 === 0 ? `${minutes}m` : `${minutes}m ${seconds % 60}s`;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function renderDiagnosticsResult(result: AgentToolResult<unknown>, isPartial: boolean, theme: Theme): Text {
|
|
840
801
|
if (isPartial) return new Text(theme.fg("muted", "Reading worker diagnostics…"), 0, 0);
|
|
841
802
|
const details = result.details;
|
|
842
803
|
if (isRecord(details) && isRecord(details.state) && Array.isArray(details.state.workers)) {
|
|
@@ -852,12 +813,13 @@ function renderDiagnosticsResult(result: AgentToolResult<unknown>, isPartial: bo
|
|
|
852
813
|
|
|
853
814
|
function renderSimpleResult(
|
|
854
815
|
result: AgentToolResult<unknown>,
|
|
816
|
+
isError: boolean,
|
|
855
817
|
message: string,
|
|
856
818
|
theme: Theme,
|
|
857
819
|
normalColor: "success" | "warning" = "success",
|
|
858
820
|
): Text {
|
|
859
|
-
const
|
|
860
|
-
return new Text(theme.fg(
|
|
821
|
+
const text = isError ? firstResultLine(result) || message : message;
|
|
822
|
+
return new Text(theme.fg(isError ? "error" : normalColor, text), 0, 0);
|
|
861
823
|
}
|
|
862
824
|
|
|
863
825
|
function firstResultLine(result: AgentToolResult<unknown>): string | undefined {
|
|
@@ -866,6 +828,10 @@ function firstResultLine(result: AgentToolResult<unknown>): string | undefined {
|
|
|
866
828
|
return first.text.split("\n").find((line) => line.trim())?.trim();
|
|
867
829
|
}
|
|
868
830
|
|
|
831
|
+
function recordFields(value: unknown): Record<string, unknown> {
|
|
832
|
+
return isRecord(value) ? value : {};
|
|
833
|
+
}
|
|
834
|
+
|
|
869
835
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
870
836
|
return typeof value === "object" && value !== null;
|
|
871
837
|
}
|