@abelxiaoxing/cadence 1.0.13 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -13
- package/agents/contract-reviewer.md +2 -2
- package/agents/design-explorer.md +1 -1
- package/agents/diagnosis-worker.md +6 -3
- package/agents/implementation-worker.md +14 -9
- package/config/.env.example +5 -17
- package/config/routes.example.json +42 -0
- package/package.json +3 -3
- package/prompts/abel-design.md +150 -228
- package/prompts/abel-diagnose.md +37 -18
- package/prompts/abel-implement.md +111 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +156 -168
- package/src/apply-transaction.ts +1427 -0
- package/src/artifact-store.ts +204 -0
- package/src/child-session.ts +136 -17
- package/src/contracts.ts +105 -696
- package/src/control-contracts.ts +259 -0
- package/src/delivery-compiler.ts +1088 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1465 -133
- package/src/isolation-backend.ts +269 -0
- package/src/packet-runtime.ts +533 -0
- package/src/parent-provider.ts +23 -4
- package/src/route-policy.ts +483 -0
- package/src/run-state.ts +393 -0
- package/src/run-store.ts +918 -0
- package/src/safe-path.ts +11 -0
- package/src/scoped-tools.ts +36 -0
- package/src/state-root.ts +215 -0
- package/src/subagent-activity.ts +526 -27
- package/src/submit-tool.ts +274 -0
- package/src/task-ledger.ts +1380 -0
- package/src/verification-capability.ts +529 -42
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7391 -0
- package/src/workspace-store.ts +544 -0
- package/src/candidate-preflight.ts +0 -1618
- package/src/drain.ts +0 -20
- package/src/patch.ts +0 -505
- package/src/result-store.ts +0 -312
- package/src/runtime.ts +0 -2436
- package/src/scheduler.ts +0 -384
- package/src/subagent-endpoint.ts +0 -158
- package/src/worker.ts +0 -204
package/src/subagent-activity.ts
CHANGED
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
visibleWidth,
|
|
11
11
|
} from "@earendil-works/pi-tui";
|
|
12
12
|
import type {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "./runtime.ts";
|
|
13
|
+
PacketActivityEvent,
|
|
14
|
+
PacketActivityObserver,
|
|
15
|
+
PacketActivityState,
|
|
16
|
+
} from "./packet-runtime.ts";
|
|
17
17
|
|
|
18
18
|
export const ACTIVITY_DETAILS_KEY = "activityDisplay" as const;
|
|
19
19
|
export const ACTIVITY_WIDGET_KEY = "abel-subagents" as const;
|
|
@@ -22,10 +22,51 @@ export const ACTIVITY_REFRESH_MS = 100;
|
|
|
22
22
|
export const ACTIVITY_WIDGET_MAX_LINES = 12;
|
|
23
23
|
export const ACTIVITY_WIDGET_MIN_WIDTH = 3;
|
|
24
24
|
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
25
|
+
export const WORKFLOW_ACTIVITY_STATES = [
|
|
26
|
+
"queued",
|
|
27
|
+
"connecting",
|
|
28
|
+
"waiting-first-response",
|
|
29
|
+
"running",
|
|
30
|
+
"validating",
|
|
31
|
+
"retrying",
|
|
32
|
+
"verifying",
|
|
33
|
+
"paused",
|
|
34
|
+
"approval-needed",
|
|
35
|
+
"applying",
|
|
36
|
+
"recovering",
|
|
37
|
+
"operation-cancelled",
|
|
38
|
+
"discarded",
|
|
39
|
+
"rejected",
|
|
40
|
+
"completed",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
export type WorkflowActivityState = (typeof WORKFLOW_ACTIVITY_STATES)[number];
|
|
44
|
+
export type ActivityState = PacketActivityState | WorkflowActivityState;
|
|
45
|
+
export type TerminalActivityState =
|
|
46
|
+
| Exclude<
|
|
47
|
+
PacketActivityState,
|
|
48
|
+
| "queued"
|
|
49
|
+
| "connecting"
|
|
50
|
+
| "waiting-first-response"
|
|
51
|
+
| "running"
|
|
52
|
+
| "retrying"
|
|
53
|
+
>
|
|
54
|
+
| WorkflowActivityState;
|
|
55
|
+
|
|
56
|
+
export interface WorkflowActivityUpdate {
|
|
57
|
+
state: WorkflowActivityState;
|
|
58
|
+
stage?: "abel-design" | "abel-implement";
|
|
59
|
+
runId?: string;
|
|
60
|
+
change?: string;
|
|
61
|
+
taskId?: string;
|
|
62
|
+
phase?: string;
|
|
63
|
+
objective?: string;
|
|
64
|
+
code?: string;
|
|
65
|
+
attempt?: number;
|
|
66
|
+
maxAttempts?: number;
|
|
67
|
+
wait?: string;
|
|
68
|
+
legalCommands?: string[];
|
|
69
|
+
}
|
|
29
70
|
|
|
30
71
|
export interface EvidenceActivitySummary {
|
|
31
72
|
kind: "evidence";
|
|
@@ -43,7 +84,7 @@ export interface DiffActivitySummary {
|
|
|
43
84
|
}
|
|
44
85
|
|
|
45
86
|
export type ActivitySummary = EvidenceActivitySummary | DiffActivitySummary;
|
|
46
|
-
export type ActivityTone = "success" | "warning" | "muted" | "error";
|
|
87
|
+
export type ActivityTone = "accent" | "success" | "warning" | "muted" | "error";
|
|
47
88
|
|
|
48
89
|
export interface ActivityDisplay {
|
|
49
90
|
version: 1;
|
|
@@ -57,6 +98,13 @@ export interface ActivityDisplay {
|
|
|
57
98
|
elapsedMs: number;
|
|
58
99
|
summary?: ActivitySummary;
|
|
59
100
|
reason?: string;
|
|
101
|
+
runId?: string;
|
|
102
|
+
taskId?: string;
|
|
103
|
+
code?: string;
|
|
104
|
+
nextAction?: string;
|
|
105
|
+
attempt?: number;
|
|
106
|
+
maxAttempts?: number;
|
|
107
|
+
wait?: string;
|
|
60
108
|
}
|
|
61
109
|
|
|
62
110
|
export interface ActivitySnapshot {
|
|
@@ -65,11 +113,19 @@ export interface ActivitySnapshot {
|
|
|
65
113
|
role: string;
|
|
66
114
|
phase: string;
|
|
67
115
|
objective: string;
|
|
68
|
-
state:
|
|
116
|
+
state: ActivityState;
|
|
69
117
|
sequence: number;
|
|
70
118
|
startedAt: number;
|
|
71
119
|
elapsedMs: number;
|
|
72
120
|
spinnerFrame?: number;
|
|
121
|
+
tone?: ActivityTone;
|
|
122
|
+
runId?: string;
|
|
123
|
+
taskId?: string;
|
|
124
|
+
code?: string;
|
|
125
|
+
nextAction?: string;
|
|
126
|
+
attempt?: number;
|
|
127
|
+
maxAttempts?: number;
|
|
128
|
+
wait?: string;
|
|
73
129
|
}
|
|
74
130
|
|
|
75
131
|
interface ActivityEntry extends ActivitySnapshot {
|
|
@@ -131,6 +187,26 @@ export function sanitizeDisplayText(value: unknown, maxLength = 240): string {
|
|
|
131
187
|
return safe.slice(0, Math.max(0, maxLength));
|
|
132
188
|
}
|
|
133
189
|
|
|
190
|
+
const SAFE_WORKFLOW_CODE = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
191
|
+
const SAFE_CONTROL_ACTIONS = new Set([
|
|
192
|
+
"start",
|
|
193
|
+
"status",
|
|
194
|
+
"resume",
|
|
195
|
+
"rebind",
|
|
196
|
+
"cancel",
|
|
197
|
+
"discard",
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
export function sanitizeWorkflowCode(value: unknown): string {
|
|
201
|
+
const code = typeof value === "string" ? value : "";
|
|
202
|
+
return SAFE_WORKFLOW_CODE.test(code) ? code : "workflow-state-unavailable";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function sanitizeControlAction(value: unknown): string {
|
|
206
|
+
const action = typeof value === "string" ? value : "";
|
|
207
|
+
return SAFE_CONTROL_ACTIONS.has(action) ? action : "status";
|
|
208
|
+
}
|
|
209
|
+
|
|
134
210
|
const SAFE_FAILURE_REASONS = new Set([
|
|
135
211
|
"subagent failed",
|
|
136
212
|
"subagent cancelled",
|
|
@@ -159,18 +235,32 @@ export function formatElapsed(milliseconds: number): string {
|
|
|
159
235
|
|
|
160
236
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
161
237
|
|
|
162
|
-
function stateGlyph(state:
|
|
238
|
+
function stateGlyph(state: ActivityState, spinnerFrame = 0): string {
|
|
163
239
|
switch (state) {
|
|
164
240
|
case "queued":
|
|
165
241
|
return "…";
|
|
242
|
+
case "connecting":
|
|
243
|
+
case "waiting-first-response":
|
|
166
244
|
case "running":
|
|
245
|
+
case "validating":
|
|
246
|
+
case "retrying":
|
|
247
|
+
case "verifying":
|
|
248
|
+
case "applying":
|
|
249
|
+
case "recovering":
|
|
167
250
|
return SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length] ?? "⠋";
|
|
168
251
|
case "completed":
|
|
169
252
|
return "✓";
|
|
170
253
|
case "failed":
|
|
254
|
+
case "rejected":
|
|
171
255
|
return "✗";
|
|
172
256
|
case "cancelled":
|
|
257
|
+
case "operation-cancelled":
|
|
173
258
|
return "⊘";
|
|
259
|
+
case "discarded":
|
|
260
|
+
return "◇";
|
|
261
|
+
case "paused":
|
|
262
|
+
case "approval-needed":
|
|
263
|
+
return "!";
|
|
174
264
|
case "timed-out":
|
|
175
265
|
return "⌛";
|
|
176
266
|
}
|
|
@@ -183,10 +273,40 @@ function line(value: string, width: number): string {
|
|
|
183
273
|
: truncateToWidth(bounded, boundedWidth(width), "");
|
|
184
274
|
}
|
|
185
275
|
|
|
186
|
-
function stateText(state:
|
|
276
|
+
function stateText(state: ActivityState): string {
|
|
187
277
|
return state;
|
|
188
278
|
}
|
|
189
279
|
|
|
280
|
+
function activityMetadata(
|
|
281
|
+
snapshot: ActivitySnapshot | ActivityDisplay,
|
|
282
|
+
): string[] {
|
|
283
|
+
const metadata: string[] = [];
|
|
284
|
+
if (snapshot.taskId) {
|
|
285
|
+
metadata.push(`task ${sanitizeDisplayText(snapshot.taskId, 80)}`);
|
|
286
|
+
}
|
|
287
|
+
if (snapshot.code) {
|
|
288
|
+
metadata.push(`code ${sanitizeWorkflowCode(snapshot.code)}`);
|
|
289
|
+
}
|
|
290
|
+
if (
|
|
291
|
+
Number.isSafeInteger(snapshot.attempt) &&
|
|
292
|
+
(snapshot.attempt as number) > 0
|
|
293
|
+
) {
|
|
294
|
+
metadata.push(
|
|
295
|
+
Number.isSafeInteger(snapshot.maxAttempts) &&
|
|
296
|
+
(snapshot.maxAttempts as number) >= (snapshot.attempt as number)
|
|
297
|
+
? `attempt ${snapshot.attempt}/${snapshot.maxAttempts}`
|
|
298
|
+
: `attempt ${snapshot.attempt}`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (snapshot.wait) {
|
|
302
|
+
metadata.push(`wait ${sanitizeDisplayText(snapshot.wait, 80)}`);
|
|
303
|
+
}
|
|
304
|
+
if (snapshot.nextAction) {
|
|
305
|
+
metadata.push(`next ${sanitizeControlAction(snapshot.nextAction)}`);
|
|
306
|
+
}
|
|
307
|
+
return metadata;
|
|
308
|
+
}
|
|
309
|
+
|
|
190
310
|
function styled(
|
|
191
311
|
theme: ActivityTheme | undefined,
|
|
192
312
|
color: ActivityColor,
|
|
@@ -221,8 +341,10 @@ function renderActivityLines(
|
|
|
221
341
|
stateText(snapshot.state),
|
|
222
342
|
formatElapsed(elapsed),
|
|
223
343
|
].join(" · ");
|
|
224
|
-
const
|
|
225
|
-
const
|
|
344
|
+
const metadata = activityMetadata(snapshot);
|
|
345
|
+
const objective = sanitizeDisplayText(snapshot.objective, 240);
|
|
346
|
+
const second = ` ${[objective, ...metadata].filter(Boolean).join(" · ")}`;
|
|
347
|
+
const color = "tone" in snapshot && snapshot.tone ? snapshot.tone : "accent";
|
|
226
348
|
return [
|
|
227
349
|
line(styled(theme, color, first), width),
|
|
228
350
|
line(styled(theme, "muted", second), width),
|
|
@@ -246,7 +368,7 @@ export function renderActivityWidgetLines(
|
|
|
246
368
|
lines.push(...renderActivityLines(entry, width, undefined, _now));
|
|
247
369
|
}
|
|
248
370
|
if (hidden.length > 0) {
|
|
249
|
-
const running = hidden.filter((entry) => entry.state
|
|
371
|
+
const running = hidden.filter((entry) => entry.state !== "queued").length;
|
|
250
372
|
const queued = hidden.filter((entry) => entry.state === "queued").length;
|
|
251
373
|
const overflow = [
|
|
252
374
|
`+${hidden.length} more (${running} running, ${queued} queued)`,
|
|
@@ -334,6 +456,243 @@ function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
334
456
|
: undefined;
|
|
335
457
|
}
|
|
336
458
|
|
|
459
|
+
function workflowStateOf(
|
|
460
|
+
result: Record<string, unknown>,
|
|
461
|
+
): WorkflowActivityState {
|
|
462
|
+
const pause = asRecord(result.pause);
|
|
463
|
+
const operation = asRecord(result.operation);
|
|
464
|
+
if (
|
|
465
|
+
pause?.code === "operation-cancelled" ||
|
|
466
|
+
operation?.kind === "operation-cancelled" ||
|
|
467
|
+
result.state === "operation-cancelled"
|
|
468
|
+
) {
|
|
469
|
+
return "operation-cancelled";
|
|
470
|
+
}
|
|
471
|
+
const state = result.state;
|
|
472
|
+
switch (state) {
|
|
473
|
+
case "created":
|
|
474
|
+
case "ready":
|
|
475
|
+
case "queued":
|
|
476
|
+
return "queued";
|
|
477
|
+
case "connecting":
|
|
478
|
+
return "connecting";
|
|
479
|
+
case "waiting-first-response":
|
|
480
|
+
return "waiting-first-response";
|
|
481
|
+
case "running":
|
|
482
|
+
return "running";
|
|
483
|
+
case "validating-delivery":
|
|
484
|
+
case "validating":
|
|
485
|
+
return "validating";
|
|
486
|
+
case "retryable":
|
|
487
|
+
case "retrying":
|
|
488
|
+
return "retrying";
|
|
489
|
+
case "verifying":
|
|
490
|
+
case "change-verifying":
|
|
491
|
+
case "ready-to-apply":
|
|
492
|
+
return "verifying";
|
|
493
|
+
case "paused":
|
|
494
|
+
return "paused";
|
|
495
|
+
case "approval-needed":
|
|
496
|
+
return "approval-needed";
|
|
497
|
+
case "applying":
|
|
498
|
+
return "applying";
|
|
499
|
+
case "recovering":
|
|
500
|
+
return "recovering";
|
|
501
|
+
case "discarded":
|
|
502
|
+
return "discarded";
|
|
503
|
+
case "rejected":
|
|
504
|
+
return "rejected";
|
|
505
|
+
case "completed":
|
|
506
|
+
return result.completed === true ? "completed" : "rejected";
|
|
507
|
+
default:
|
|
508
|
+
return "running";
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function workflowTone(state: WorkflowActivityState): ActivityTone {
|
|
513
|
+
switch (state) {
|
|
514
|
+
case "completed":
|
|
515
|
+
return "success";
|
|
516
|
+
case "rejected":
|
|
517
|
+
return "error";
|
|
518
|
+
case "retrying":
|
|
519
|
+
case "paused":
|
|
520
|
+
case "approval-needed":
|
|
521
|
+
return "warning";
|
|
522
|
+
case "operation-cancelled":
|
|
523
|
+
case "discarded":
|
|
524
|
+
return "muted";
|
|
525
|
+
default:
|
|
526
|
+
return "accent";
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function legalWorkflowCommands(result: Record<string, unknown>): string[] {
|
|
531
|
+
return Array.isArray(result.legalCommands)
|
|
532
|
+
? result.legalCommands.filter(
|
|
533
|
+
(command): command is string =>
|
|
534
|
+
typeof command === "string" && SAFE_CONTROL_ACTIONS.has(command),
|
|
535
|
+
)
|
|
536
|
+
: [];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function nextWorkflowAction(
|
|
540
|
+
state: WorkflowActivityState,
|
|
541
|
+
commands: readonly string[],
|
|
542
|
+
): string | undefined {
|
|
543
|
+
const preferred =
|
|
544
|
+
state === "paused" ||
|
|
545
|
+
state === "approval-needed" ||
|
|
546
|
+
state === "retrying" ||
|
|
547
|
+
state === "operation-cancelled"
|
|
548
|
+
? ["resume", "status", "discard"]
|
|
549
|
+
: state === "completed" || state === "discarded" || state === "rejected"
|
|
550
|
+
? ["status"]
|
|
551
|
+
: ["status", "cancel", "discard"];
|
|
552
|
+
return preferred.find((command) => commands.includes(command)) ?? commands[0];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function firstWorkflowTask(result: Record<string, unknown>):
|
|
556
|
+
| {
|
|
557
|
+
taskId?: string;
|
|
558
|
+
phase?: string;
|
|
559
|
+
}
|
|
560
|
+
| undefined {
|
|
561
|
+
if (!Array.isArray(result.tasks)) return undefined;
|
|
562
|
+
const records = result.tasks
|
|
563
|
+
.map(asRecord)
|
|
564
|
+
.filter((task): task is Record<string, unknown> => task !== undefined);
|
|
565
|
+
const task =
|
|
566
|
+
records.find((candidate) => candidate.state !== "verified") ?? records[0];
|
|
567
|
+
return task
|
|
568
|
+
? {
|
|
569
|
+
...(typeof task.taskId === "string" ? { taskId: task.taskId } : {}),
|
|
570
|
+
...(typeof task.phase === "string" ? { phase: task.phase } : {}),
|
|
571
|
+
}
|
|
572
|
+
: undefined;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function positiveInteger(value: unknown): number | undefined {
|
|
576
|
+
return Number.isSafeInteger(value) && (value as number) > 0
|
|
577
|
+
? (value as number)
|
|
578
|
+
: undefined;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function workflowWait(
|
|
582
|
+
state: WorkflowActivityState,
|
|
583
|
+
result: Record<string, unknown>,
|
|
584
|
+
): string | undefined {
|
|
585
|
+
if (typeof result.wait === "string") {
|
|
586
|
+
return sanitizeDisplayText(result.wait, 80);
|
|
587
|
+
}
|
|
588
|
+
const approval = asRecord(result.approval);
|
|
589
|
+
if (
|
|
590
|
+
state === "approval-needed" &&
|
|
591
|
+
(approval?.gate === "gate-a" || approval?.gate === "gate-b")
|
|
592
|
+
) {
|
|
593
|
+
return approval.gate;
|
|
594
|
+
}
|
|
595
|
+
if (state === "connecting") return "connection";
|
|
596
|
+
if (state === "waiting-first-response") return "first-response";
|
|
597
|
+
if (state === "retrying") return "bounded-policy";
|
|
598
|
+
const queue = Array.isArray(result.queue)
|
|
599
|
+
? asRecord(result.queue[0])
|
|
600
|
+
: undefined;
|
|
601
|
+
const position = positiveInteger(queue?.position);
|
|
602
|
+
return state === "queued" && position ? `queue-${position}` : undefined;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function workflowCode(result: Record<string, unknown>): string | undefined {
|
|
606
|
+
const pause = asRecord(result.pause);
|
|
607
|
+
const raw =
|
|
608
|
+
typeof result.code === "string"
|
|
609
|
+
? result.code
|
|
610
|
+
: typeof pause?.code === "string"
|
|
611
|
+
? pause.code
|
|
612
|
+
: undefined;
|
|
613
|
+
return raw === undefined ? undefined : sanitizeWorkflowCode(raw);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function projectWorkflowActivity(
|
|
617
|
+
args: unknown,
|
|
618
|
+
result: unknown,
|
|
619
|
+
elapsedMs: number,
|
|
620
|
+
): ActivityDisplay {
|
|
621
|
+
const command = asRecord(args) ?? {};
|
|
622
|
+
const payload = asRecord(result) ?? {};
|
|
623
|
+
const state = workflowStateOf(payload);
|
|
624
|
+
const task = firstWorkflowTask(payload);
|
|
625
|
+
const stage =
|
|
626
|
+
payload.stage === "abel-design" || payload.stage === "abel-implement"
|
|
627
|
+
? payload.stage
|
|
628
|
+
: command.stage === "abel-design" || command.stage === "abel-implement"
|
|
629
|
+
? command.stage
|
|
630
|
+
: "abel-control";
|
|
631
|
+
const change =
|
|
632
|
+
typeof payload.change === "string"
|
|
633
|
+
? payload.change
|
|
634
|
+
: typeof command.change === "string"
|
|
635
|
+
? command.change
|
|
636
|
+
: undefined;
|
|
637
|
+
const runId = typeof payload.runId === "string" ? payload.runId : undefined;
|
|
638
|
+
const taskId =
|
|
639
|
+
typeof payload.taskId === "string" ? payload.taskId : task?.taskId;
|
|
640
|
+
const phase =
|
|
641
|
+
typeof payload.phase === "string"
|
|
642
|
+
? payload.phase
|
|
643
|
+
: (task?.phase ??
|
|
644
|
+
(typeof command.command === "string" ? command.command : "control"));
|
|
645
|
+
const objective =
|
|
646
|
+
typeof payload.objective === "string"
|
|
647
|
+
? payload.objective
|
|
648
|
+
: change
|
|
649
|
+
? `change ${change}`
|
|
650
|
+
: `${String(command.command ?? "control")} workflow run`;
|
|
651
|
+
const legalCommands = legalWorkflowCommands(payload);
|
|
652
|
+
const policy = asRecord(payload.policy);
|
|
653
|
+
const attempt = positiveInteger(payload.attempt ?? policy?.attempt);
|
|
654
|
+
const maxAttempts = positiveInteger(
|
|
655
|
+
payload.maxAttempts ?? policy?.maxAttempts,
|
|
656
|
+
);
|
|
657
|
+
const code =
|
|
658
|
+
state === "rejected" && payload.state === "completed"
|
|
659
|
+
? "completion-state-inconsistent"
|
|
660
|
+
: workflowCode(payload);
|
|
661
|
+
return {
|
|
662
|
+
version: 1,
|
|
663
|
+
kind: "activityDisplay",
|
|
664
|
+
requestId: sanitizeDisplayText(
|
|
665
|
+
runId ?? taskId ?? change ?? command.operationId ?? "pending",
|
|
666
|
+
128,
|
|
667
|
+
),
|
|
668
|
+
role: sanitizeDisplayText(stage, 80),
|
|
669
|
+
phase: sanitizeDisplayText(phase, 40),
|
|
670
|
+
objective: sanitizeDisplayText(objective, 240),
|
|
671
|
+
state,
|
|
672
|
+
tone: workflowTone(state),
|
|
673
|
+
elapsedMs: Math.max(0, elapsedMs),
|
|
674
|
+
...(runId ? { runId: sanitizeDisplayText(runId, 128) } : {}),
|
|
675
|
+
...(taskId ? { taskId: sanitizeDisplayText(taskId, 128) } : {}),
|
|
676
|
+
...(code ? { code } : {}),
|
|
677
|
+
...(attempt ? { attempt } : {}),
|
|
678
|
+
...(maxAttempts ? { maxAttempts } : {}),
|
|
679
|
+
...(workflowWait(state, payload)
|
|
680
|
+
? { wait: workflowWait(state, payload) }
|
|
681
|
+
: {}),
|
|
682
|
+
...(nextWorkflowAction(state, legalCommands)
|
|
683
|
+
? { nextAction: nextWorkflowAction(state, legalCommands) }
|
|
684
|
+
: {}),
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function activityResultFromUpdate(update: WorkflowActivityUpdate): unknown {
|
|
689
|
+
return {
|
|
690
|
+
...update,
|
|
691
|
+
...(update.state === "completed" ? { completed: true } : {}),
|
|
692
|
+
...(update.code ? { pause: { code: update.code } } : {}),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
337
696
|
export function summarizeDispatchResult(
|
|
338
697
|
result: unknown,
|
|
339
698
|
): ActivitySummary | undefined {
|
|
@@ -413,16 +772,22 @@ function toneForResult(result: unknown): ActivityTone | undefined {
|
|
|
413
772
|
}
|
|
414
773
|
|
|
415
774
|
function terminalState(
|
|
416
|
-
event:
|
|
417
|
-
): event is
|
|
775
|
+
event: PacketActivityEvent,
|
|
776
|
+
): event is PacketActivityEvent & {
|
|
418
777
|
state: TerminalActivityState;
|
|
419
778
|
} {
|
|
420
|
-
return ![
|
|
779
|
+
return ![
|
|
780
|
+
"queued",
|
|
781
|
+
"connecting",
|
|
782
|
+
"waiting-first-response",
|
|
783
|
+
"running",
|
|
784
|
+
"retrying",
|
|
785
|
+
].includes(event.state);
|
|
421
786
|
}
|
|
422
787
|
|
|
423
788
|
function displayFromEvent(
|
|
424
789
|
entry: ActivityEntry,
|
|
425
|
-
event:
|
|
790
|
+
event: PacketActivityEvent,
|
|
426
791
|
elapsedMs: number,
|
|
427
792
|
): ActivityDisplay {
|
|
428
793
|
const reason = event.failureReason;
|
|
@@ -434,6 +799,10 @@ function displayFromEvent(
|
|
|
434
799
|
phase: entry.phase,
|
|
435
800
|
objective: sanitizeDisplayText(entry.objective),
|
|
436
801
|
state: event.state as TerminalActivityState,
|
|
802
|
+
...(entry.attempt ? { attempt: entry.attempt } : {}),
|
|
803
|
+
...(entry.maxAttempts ? { maxAttempts: entry.maxAttempts } : {}),
|
|
804
|
+
...(entry.code ? { code: entry.code } : {}),
|
|
805
|
+
...(entry.wait ? { wait: entry.wait } : {}),
|
|
437
806
|
tone:
|
|
438
807
|
event.state === "failed" || event.state === "timed-out"
|
|
439
808
|
? "error"
|
|
@@ -446,7 +815,7 @@ function displayFromEvent(
|
|
|
446
815
|
}
|
|
447
816
|
|
|
448
817
|
export function createActivityDisplay(
|
|
449
|
-
event:
|
|
818
|
+
event: PacketActivityEvent,
|
|
450
819
|
elapsedMs: number,
|
|
451
820
|
result?: unknown,
|
|
452
821
|
): ActivityDisplay {
|
|
@@ -456,10 +825,14 @@ export function createActivityDisplay(
|
|
|
456
825
|
role: event.role,
|
|
457
826
|
phase: event.phase,
|
|
458
827
|
objective: event.objective,
|
|
459
|
-
state: event.state
|
|
828
|
+
state: event.state,
|
|
460
829
|
sequence: event.sequence,
|
|
461
830
|
startedAt: 0,
|
|
462
831
|
elapsedMs,
|
|
832
|
+
...(event.attempt ? { attempt: event.attempt } : {}),
|
|
833
|
+
...(event.maxAttempts ? { maxAttempts: event.maxAttempts } : {}),
|
|
834
|
+
...(event.code ? { code: event.code } : {}),
|
|
835
|
+
...(event.wait ? { wait: event.wait } : {}),
|
|
463
836
|
};
|
|
464
837
|
const display = displayFromEvent(entry, event, elapsedMs);
|
|
465
838
|
const summary = result ? summarizeDispatchResult(result) : undefined;
|
|
@@ -496,6 +869,7 @@ export class ActivityController {
|
|
|
496
869
|
private timer?: TimerHandle | unknown;
|
|
497
870
|
private requestRender?: () => void;
|
|
498
871
|
private widgetInstalled = false;
|
|
872
|
+
private controlSequence = 0;
|
|
499
873
|
private readonly now: () => number;
|
|
500
874
|
private readonly setIntervalFn: (
|
|
501
875
|
callback: () => void,
|
|
@@ -535,17 +909,113 @@ export class ActivityController {
|
|
|
535
909
|
this.clearUi();
|
|
536
910
|
}
|
|
537
911
|
|
|
912
|
+
beginWorkflow(
|
|
913
|
+
toolCallId: string,
|
|
914
|
+
args: unknown,
|
|
915
|
+
onUpdate?: (result: unknown) => void,
|
|
916
|
+
): void {
|
|
917
|
+
if (!this.accepting || this.entries.has(toolCallId)) return;
|
|
918
|
+
const display = projectWorkflowActivity(args, { state: "queued" }, 0);
|
|
919
|
+
const entry: ActivityEntry = {
|
|
920
|
+
toolCallId,
|
|
921
|
+
requestId: display.requestId,
|
|
922
|
+
role: display.role,
|
|
923
|
+
phase: display.phase,
|
|
924
|
+
objective: display.objective,
|
|
925
|
+
state: display.state,
|
|
926
|
+
sequence: ++this.controlSequence,
|
|
927
|
+
startedAt: this.now(),
|
|
928
|
+
elapsedMs: 0,
|
|
929
|
+
spinnerFrame: this.spinnerFrame,
|
|
930
|
+
tone: display.tone,
|
|
931
|
+
onUpdate,
|
|
932
|
+
};
|
|
933
|
+
this.entries.set(toolCallId, entry);
|
|
934
|
+
this.emit(entry, entry);
|
|
935
|
+
this.syncUi();
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
updateWorkflow(
|
|
939
|
+
toolCallId: string,
|
|
940
|
+
args: unknown,
|
|
941
|
+
update: WorkflowActivityUpdate,
|
|
942
|
+
): void {
|
|
943
|
+
const entry = this.entries.get(toolCallId);
|
|
944
|
+
if (!this.accepting || !entry) return;
|
|
945
|
+
const display = projectWorkflowActivity(
|
|
946
|
+
args,
|
|
947
|
+
activityResultFromUpdate(update),
|
|
948
|
+
Math.max(0, this.now() - entry.startedAt),
|
|
949
|
+
);
|
|
950
|
+
Object.assign(entry, {
|
|
951
|
+
requestId: display.requestId,
|
|
952
|
+
role: display.role,
|
|
953
|
+
phase: display.phase,
|
|
954
|
+
objective: display.objective,
|
|
955
|
+
state: display.state,
|
|
956
|
+
elapsedMs: display.elapsedMs,
|
|
957
|
+
tone: display.tone,
|
|
958
|
+
runId: display.runId,
|
|
959
|
+
taskId: display.taskId,
|
|
960
|
+
code: display.code,
|
|
961
|
+
nextAction: display.nextAction,
|
|
962
|
+
attempt: display.attempt,
|
|
963
|
+
maxAttempts: display.maxAttempts,
|
|
964
|
+
wait: display.wait,
|
|
965
|
+
});
|
|
966
|
+
this.emit(entry, entry);
|
|
967
|
+
this.syncUi();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
finalizeWorkflow(
|
|
971
|
+
toolCallId: string,
|
|
972
|
+
args: unknown,
|
|
973
|
+
result: unknown,
|
|
974
|
+
): ActivityDisplay | undefined {
|
|
975
|
+
const entry = this.entries.get(toolCallId);
|
|
976
|
+
if (!entry) return undefined;
|
|
977
|
+
const display = projectWorkflowActivity(
|
|
978
|
+
args,
|
|
979
|
+
result,
|
|
980
|
+
Math.max(0, this.now() - entry.startedAt),
|
|
981
|
+
);
|
|
982
|
+
this.emit(entry, display);
|
|
983
|
+
this.entries.delete(toolCallId);
|
|
984
|
+
this.syncUi();
|
|
985
|
+
return display;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
failWorkflow(toolCallId: string): void {
|
|
989
|
+
const entry = this.entries.get(toolCallId);
|
|
990
|
+
if (!entry) return;
|
|
991
|
+
const display: ActivityDisplay = {
|
|
992
|
+
version: 1,
|
|
993
|
+
kind: "activityDisplay",
|
|
994
|
+
requestId: entry.requestId,
|
|
995
|
+
role: entry.role,
|
|
996
|
+
phase: entry.phase,
|
|
997
|
+
objective: entry.objective,
|
|
998
|
+
state: "failed",
|
|
999
|
+
tone: "error",
|
|
1000
|
+
elapsedMs: Math.max(0, this.now() - entry.startedAt),
|
|
1001
|
+
reason: "subagent failed",
|
|
1002
|
+
};
|
|
1003
|
+
this.emit(entry, display);
|
|
1004
|
+
this.entries.delete(toolCallId);
|
|
1005
|
+
this.syncUi();
|
|
1006
|
+
}
|
|
1007
|
+
|
|
538
1008
|
observe(
|
|
539
1009
|
toolCallId: string,
|
|
540
1010
|
onUpdate?: (result: unknown) => void,
|
|
541
|
-
):
|
|
1011
|
+
): PacketActivityObserver {
|
|
542
1012
|
return (event) => this.accept(toolCallId, onUpdate, event);
|
|
543
1013
|
}
|
|
544
1014
|
|
|
545
1015
|
accept(
|
|
546
1016
|
toolCallId: string,
|
|
547
1017
|
onUpdate: ((result: unknown) => void) | undefined,
|
|
548
|
-
event:
|
|
1018
|
+
event: PacketActivityEvent,
|
|
549
1019
|
): void {
|
|
550
1020
|
if (!this.accepting || this.terminals.has(toolCallId)) return;
|
|
551
1021
|
let entry = this.entries.get(toolCallId);
|
|
@@ -556,17 +1026,25 @@ export class ActivityController {
|
|
|
556
1026
|
role: event.role,
|
|
557
1027
|
phase: event.phase,
|
|
558
1028
|
objective: sanitizeDisplayText(event.objective),
|
|
559
|
-
state: event.state
|
|
1029
|
+
state: event.state,
|
|
560
1030
|
sequence: event.sequence,
|
|
561
1031
|
startedAt: this.now(),
|
|
562
1032
|
elapsedMs: 0,
|
|
563
1033
|
spinnerFrame: this.spinnerFrame,
|
|
1034
|
+
...(event.attempt ? { attempt: event.attempt } : {}),
|
|
1035
|
+
...(event.maxAttempts ? { maxAttempts: event.maxAttempts } : {}),
|
|
1036
|
+
...(event.code ? { code: event.code } : {}),
|
|
1037
|
+
...(event.wait ? { wait: event.wait } : {}),
|
|
564
1038
|
onUpdate,
|
|
565
1039
|
};
|
|
566
1040
|
this.entries.set(toolCallId, entry);
|
|
567
1041
|
} else {
|
|
568
1042
|
entry.onUpdate = onUpdate ?? entry.onUpdate;
|
|
569
|
-
|
|
1043
|
+
entry.state = event.state;
|
|
1044
|
+
entry.attempt = event.attempt;
|
|
1045
|
+
entry.maxAttempts = event.maxAttempts;
|
|
1046
|
+
entry.code = event.code;
|
|
1047
|
+
entry.wait = event.wait;
|
|
570
1048
|
}
|
|
571
1049
|
if (terminalState(event)) {
|
|
572
1050
|
const display = displayFromEvent(
|
|
@@ -701,7 +1179,7 @@ export class ActivityController {
|
|
|
701
1179
|
this.startTimer();
|
|
702
1180
|
try {
|
|
703
1181
|
const running = [...this.entries.values()].filter(
|
|
704
|
-
(entry) => entry.state
|
|
1182
|
+
(entry) => entry.state !== "queued",
|
|
705
1183
|
).length;
|
|
706
1184
|
const queued = this.entries.size - running;
|
|
707
1185
|
this.ui.setStatus(
|
|
@@ -772,7 +1250,11 @@ export function renderActivityResult(
|
|
|
772
1250
|
return new ActivityInlineComponent(display, theme);
|
|
773
1251
|
}
|
|
774
1252
|
if (failed) {
|
|
775
|
-
return new Text(
|
|
1253
|
+
return new Text(
|
|
1254
|
+
`${dispatchLabel(args?.action ?? args?.command)} failed`,
|
|
1255
|
+
0,
|
|
1256
|
+
0,
|
|
1257
|
+
);
|
|
776
1258
|
}
|
|
777
1259
|
const content = asRecord(result)?.content;
|
|
778
1260
|
const text = Array.isArray(content)
|
|
@@ -838,6 +1320,23 @@ export function renderActivityCall(
|
|
|
838
1320
|
context?: ActivityRenderContext,
|
|
839
1321
|
): Component {
|
|
840
1322
|
const value = asRecord(args);
|
|
1323
|
+
if (typeof value?.command === "string" && value.version === 2) {
|
|
1324
|
+
if (context?.executionStarted || context?.isError) return new Text("");
|
|
1325
|
+
const projected = projectWorkflowActivity(value, { state: "queued" }, 0);
|
|
1326
|
+
const display: ActivitySnapshot = {
|
|
1327
|
+
toolCallId: "call",
|
|
1328
|
+
requestId: projected.requestId,
|
|
1329
|
+
role: projected.role,
|
|
1330
|
+
phase: projected.phase,
|
|
1331
|
+
objective: projected.objective,
|
|
1332
|
+
state: projected.state,
|
|
1333
|
+
sequence: 0,
|
|
1334
|
+
startedAt: Date.now(),
|
|
1335
|
+
elapsedMs: 0,
|
|
1336
|
+
tone: projected.tone,
|
|
1337
|
+
};
|
|
1338
|
+
return new ActivityInlineComponent(display, theme);
|
|
1339
|
+
}
|
|
841
1340
|
if (value?.action !== "run") {
|
|
842
1341
|
return new Text(context?.isError ? "" : dispatchLabel(value?.action), 0, 0);
|
|
843
1342
|
}
|