@abelxiaoxing/cadence 1.2.2 → 1.2.4
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 +7 -1
- package/agents/diagnosis-worker.md +6 -2
- package/config/routes.example.json +0 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +15 -4
- package/skills/abel-workflow/SKILL.md +5 -3
- package/src/apply-transaction.ts +1 -5
- package/src/child-session.ts +37 -46
- package/src/contracts.ts +278 -32
- package/src/control-contracts.ts +62 -0
- package/src/delivery-compiler.ts +239 -124
- package/src/design-control.ts +112 -44
- package/src/design-journal.ts +22 -9
- package/src/file-snapshot.ts +1 -1
- package/src/index.ts +393 -52
- package/src/packet-runtime.ts +79 -21
- package/src/parent-provider.ts +63 -30
- package/src/route-policy.ts +58 -14
- package/src/run-store.ts +308 -37
- package/src/subagent-activity.ts +82 -6
- package/src/submit-tool.ts +182 -10
- package/src/task-ledger.ts +1 -2
- package/src/worker-broker.ts +72 -13
- package/src/workflow-engine.ts +219 -31
- package/src/workspace-store.ts +2 -10
package/src/run-store.ts
CHANGED
|
@@ -17,7 +17,32 @@ import {
|
|
|
17
17
|
StateRootError,
|
|
18
18
|
} from "./state-root.ts";
|
|
19
19
|
|
|
20
|
-
export
|
|
20
|
+
export class RunStoreFormatError extends Error {
|
|
21
|
+
readonly code = "run-store-reset-required" as const;
|
|
22
|
+
readonly databasePath: string;
|
|
23
|
+
readonly recovery = Object.freeze({
|
|
24
|
+
action: "reset-private-run-store" as const,
|
|
25
|
+
requiresBackup: true as const,
|
|
26
|
+
retryCommand: "status-or-start" as const,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
constructor(databasePath: string) {
|
|
30
|
+
super("run-store-reset-required");
|
|
31
|
+
this.name = "RunStoreFormatError";
|
|
32
|
+
this.databasePath = databasePath;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class RunStoreMigrationError extends Error {
|
|
37
|
+
readonly code = "run-store-migration-failed" as const;
|
|
38
|
+
readonly schemaVersion: number;
|
|
39
|
+
|
|
40
|
+
constructor(schemaVersion: number, cause: unknown) {
|
|
41
|
+
super("run-store-migration-failed", { cause });
|
|
42
|
+
this.name = "RunStoreMigrationError";
|
|
43
|
+
this.schemaVersion = schemaVersion;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
21
46
|
|
|
22
47
|
const CHANGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/u;
|
|
23
48
|
const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
@@ -53,6 +78,7 @@ export interface StartRunInput {
|
|
|
53
78
|
stage: RunStage;
|
|
54
79
|
change?: string;
|
|
55
80
|
provisionalKey?: string;
|
|
81
|
+
provisionalAliases?: readonly string[];
|
|
56
82
|
operationId: string;
|
|
57
83
|
}
|
|
58
84
|
|
|
@@ -84,10 +110,6 @@ export interface OperationLeaseStatus {
|
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
const SCHEMA = `
|
|
87
|
-
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
88
|
-
version INTEGER PRIMARY KEY CHECK (version = 4)
|
|
89
|
-
) STRICT;
|
|
90
|
-
|
|
91
113
|
CREATE TABLE IF NOT EXISTS runs (
|
|
92
114
|
run_id TEXT PRIMARY KEY,
|
|
93
115
|
root_hash TEXT NOT NULL,
|
|
@@ -191,6 +213,180 @@ function requireIdentifier(value: string, label: string): void {
|
|
|
191
213
|
if (!IDENTIFIER.test(value)) throw new Error(`invalid-${label}`);
|
|
192
214
|
}
|
|
193
215
|
|
|
216
|
+
function configureDatabase(
|
|
217
|
+
database: DatabaseSync,
|
|
218
|
+
busyTimeoutMs: number | undefined,
|
|
219
|
+
): void {
|
|
220
|
+
database.exec("PRAGMA foreign_keys = ON");
|
|
221
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
222
|
+
database.exec("PRAGMA synchronous = FULL");
|
|
223
|
+
database.exec(
|
|
224
|
+
`PRAGMA busy_timeout = ${Math.max(1, Math.min(30_000, busyTimeoutMs ?? 5_000))}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const REQUIRED_TABLES = [
|
|
229
|
+
"runs",
|
|
230
|
+
"delivery_bindings",
|
|
231
|
+
"events",
|
|
232
|
+
"tasks",
|
|
233
|
+
"operations",
|
|
234
|
+
"route_health",
|
|
235
|
+
"artifacts",
|
|
236
|
+
"workspace_revisions",
|
|
237
|
+
"apply_transactions",
|
|
238
|
+
"bootstrap_handoffs",
|
|
239
|
+
] as const;
|
|
240
|
+
|
|
241
|
+
const REQUIRED_DELIVERY_BINDING_COLUMNS = [
|
|
242
|
+
"run_id",
|
|
243
|
+
"gate",
|
|
244
|
+
"revision",
|
|
245
|
+
"receipt_hash",
|
|
246
|
+
"approval_revision",
|
|
247
|
+
"contract_hash",
|
|
248
|
+
"record_hash",
|
|
249
|
+
"operation_id",
|
|
250
|
+
] as const;
|
|
251
|
+
const LEGACY_SCHEMA_VERSION = 4;
|
|
252
|
+
|
|
253
|
+
function applicationTables(database: DatabaseSync): string[] {
|
|
254
|
+
return (
|
|
255
|
+
database
|
|
256
|
+
.prepare(
|
|
257
|
+
`SELECT name FROM sqlite_master
|
|
258
|
+
WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
|
|
259
|
+
ORDER BY name`,
|
|
260
|
+
)
|
|
261
|
+
.all() as unknown as Array<{ name: string }>
|
|
262
|
+
).map(({ name }) => name);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function tableColumns(database: DatabaseSync, table: string): string[] {
|
|
266
|
+
return (
|
|
267
|
+
database.prepare(`PRAGMA table_info(${table})`).all() as unknown as Array<{
|
|
268
|
+
name: string;
|
|
269
|
+
}>
|
|
270
|
+
).map(({ name }) => name);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function hasRequiredCoreSchema(
|
|
274
|
+
database: DatabaseSync,
|
|
275
|
+
tables: readonly string[],
|
|
276
|
+
): boolean {
|
|
277
|
+
const deliveryBindingColumns = tableColumns(database, "delivery_bindings");
|
|
278
|
+
return (
|
|
279
|
+
REQUIRED_TABLES.every((table) => tables.includes(table)) &&
|
|
280
|
+
REQUIRED_DELIVERY_BINDING_COLUMNS.every((column) =>
|
|
281
|
+
deliveryBindingColumns.includes(column),
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function hasCurrentSchema(
|
|
287
|
+
database: DatabaseSync,
|
|
288
|
+
tables: readonly string[],
|
|
289
|
+
): boolean {
|
|
290
|
+
return (
|
|
291
|
+
!tables.includes("schema_meta") && hasRequiredCoreSchema(database, tables)
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isSupportedLegacySchema(
|
|
296
|
+
database: DatabaseSync,
|
|
297
|
+
tables: readonly string[],
|
|
298
|
+
): boolean {
|
|
299
|
+
if (
|
|
300
|
+
!tables.includes("schema_meta") ||
|
|
301
|
+
!hasRequiredCoreSchema(database, tables) ||
|
|
302
|
+
tableColumns(database, "schema_meta").join("\0") !== "version"
|
|
303
|
+
) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
const rows = database
|
|
307
|
+
.prepare("SELECT version FROM schema_meta")
|
|
308
|
+
.all() as unknown as Array<{
|
|
309
|
+
version: number;
|
|
310
|
+
}>;
|
|
311
|
+
return (
|
|
312
|
+
rows.length === 1 && Number(rows[0]?.version) === LEGACY_SCHEMA_VERSION
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function isSqliteLocked(error: unknown): boolean {
|
|
317
|
+
return (
|
|
318
|
+
typeof error === "object" &&
|
|
319
|
+
error !== null &&
|
|
320
|
+
"errcode" in error &&
|
|
321
|
+
((error as { errcode?: unknown }).errcode === 5 ||
|
|
322
|
+
(error as { errcode?: unknown }).errcode === 6)
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isStoreUnavailable(error: unknown): boolean {
|
|
327
|
+
if (typeof error !== "object" || error === null) return false;
|
|
328
|
+
const errcode = (error as { errcode?: unknown }).errcode;
|
|
329
|
+
if ([8, 10, 13, 14, 23, 24].includes(Number(errcode))) return true;
|
|
330
|
+
const code = (error as { code?: unknown }).code;
|
|
331
|
+
return (
|
|
332
|
+
typeof code === "string" &&
|
|
333
|
+
[
|
|
334
|
+
"EACCES",
|
|
335
|
+
"EBUSY",
|
|
336
|
+
"EMFILE",
|
|
337
|
+
"ENFILE",
|
|
338
|
+
"ENOSPC",
|
|
339
|
+
"EPERM",
|
|
340
|
+
"EROFS",
|
|
341
|
+
].includes(code)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function ensureCurrentSchema(
|
|
346
|
+
database: DatabaseSync,
|
|
347
|
+
databasePath: string,
|
|
348
|
+
): void {
|
|
349
|
+
const initialTables = applicationTables(database);
|
|
350
|
+
if (hasCurrentSchema(database, initialTables)) return;
|
|
351
|
+
let transactionStarted = false;
|
|
352
|
+
let migrating = false;
|
|
353
|
+
try {
|
|
354
|
+
database.exec("BEGIN IMMEDIATE");
|
|
355
|
+
transactionStarted = true;
|
|
356
|
+
const tables = applicationTables(database);
|
|
357
|
+
if (tables.length === 0) {
|
|
358
|
+
database.exec(SCHEMA);
|
|
359
|
+
} else if (hasCurrentSchema(database, tables)) {
|
|
360
|
+
// Another opener may already have completed the same migration.
|
|
361
|
+
} else if (isSupportedLegacySchema(database, tables)) {
|
|
362
|
+
migrating = true;
|
|
363
|
+
database.exec("DROP TABLE schema_meta");
|
|
364
|
+
} else {
|
|
365
|
+
throw new RunStoreFormatError(databasePath);
|
|
366
|
+
}
|
|
367
|
+
database.exec("COMMIT");
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (transactionStarted) {
|
|
370
|
+
try {
|
|
371
|
+
database.exec("ROLLBACK");
|
|
372
|
+
} catch {
|
|
373
|
+
// The original schema or commit failure remains authoritative.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
error instanceof RunStoreFormatError ||
|
|
378
|
+
error instanceof RunStoreMigrationError ||
|
|
379
|
+
isSqliteLocked(error)
|
|
380
|
+
) {
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
if (migrating) {
|
|
384
|
+
throw new RunStoreMigrationError(LEGACY_SCHEMA_VERSION, error);
|
|
385
|
+
}
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
194
390
|
function parseProjection(value: string): RunProjection {
|
|
195
391
|
const parsed = JSON.parse(value) as RunProjection;
|
|
196
392
|
if (
|
|
@@ -203,22 +399,35 @@ function parseProjection(value: string): RunProjection {
|
|
|
203
399
|
return parsed;
|
|
204
400
|
}
|
|
205
401
|
|
|
206
|
-
function
|
|
402
|
+
function lookupKeys(input: StartRunInput): string[] {
|
|
207
403
|
const hasChange = typeof input.change === "string";
|
|
208
404
|
const hasProvisional = typeof input.provisionalKey === "string";
|
|
209
405
|
if (hasChange === hasProvisional) throw new Error("invalid-run-lookup-key");
|
|
210
406
|
if (hasChange) {
|
|
407
|
+
if (input.provisionalAliases !== undefined) {
|
|
408
|
+
throw new Error("invalid-run-lookup-key");
|
|
409
|
+
}
|
|
211
410
|
if (!CHANGE_NAME.test(input.change as string))
|
|
212
411
|
throw new Error("invalid-change");
|
|
213
|
-
return `change:${input.change}
|
|
412
|
+
return [`change:${input.change}`];
|
|
214
413
|
}
|
|
215
414
|
if (!SHA256.test(input.provisionalKey as string)) {
|
|
216
415
|
throw new Error("invalid-provisional-key");
|
|
217
416
|
}
|
|
417
|
+
if (
|
|
418
|
+
input.provisionalAliases !== undefined &&
|
|
419
|
+
(!Array.isArray(input.provisionalAliases) ||
|
|
420
|
+
input.provisionalAliases.length > 4 ||
|
|
421
|
+
input.provisionalAliases.some((alias) => !SHA256.test(alias)))
|
|
422
|
+
) {
|
|
423
|
+
throw new Error("invalid-provisional-key");
|
|
424
|
+
}
|
|
218
425
|
if (input.stage !== "abel-design") {
|
|
219
426
|
throw new Error("provisional-run-requires-design");
|
|
220
427
|
}
|
|
221
|
-
return
|
|
428
|
+
return [
|
|
429
|
+
...new Set([input.provisionalKey, ...(input.provisionalAliases ?? [])]),
|
|
430
|
+
].map((hash) => `provisional:${hash}`);
|
|
222
431
|
}
|
|
223
432
|
|
|
224
433
|
export class RunStore {
|
|
@@ -262,29 +471,41 @@ export class RunStore {
|
|
|
262
471
|
}
|
|
263
472
|
chmodSync(stateRoot.databasePath, 0o600);
|
|
264
473
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
474
|
+
let database: DatabaseSync | undefined;
|
|
475
|
+
try {
|
|
476
|
+
database = new DatabaseSync(stateRoot.databasePath);
|
|
477
|
+
configureDatabase(database, options.busyTimeoutMs);
|
|
478
|
+
ensureCurrentSchema(database, stateRoot.databasePath);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
try {
|
|
481
|
+
database?.close();
|
|
482
|
+
} catch {
|
|
483
|
+
// The run-store initialization failure remains authoritative.
|
|
484
|
+
}
|
|
485
|
+
if (
|
|
486
|
+
error instanceof RunStoreFormatError ||
|
|
487
|
+
error instanceof RunStoreMigrationError ||
|
|
488
|
+
isSqliteLocked(error) ||
|
|
489
|
+
isStoreUnavailable(error)
|
|
490
|
+
) {
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
throw new RunStoreFormatError(stateRoot.databasePath);
|
|
283
494
|
}
|
|
495
|
+
if (!database) throw new RunStoreFormatError(stateRoot.databasePath);
|
|
284
496
|
const store = new RunStore(stateRoot, database, options);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
497
|
+
try {
|
|
498
|
+
store.#secureDatabaseFiles();
|
|
499
|
+
store.recoverExpiredLeases();
|
|
500
|
+
return store;
|
|
501
|
+
} catch (error) {
|
|
502
|
+
try {
|
|
503
|
+
database.close();
|
|
504
|
+
} catch {
|
|
505
|
+
// The permission or lease-recovery failure remains authoritative.
|
|
506
|
+
}
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
288
509
|
}
|
|
289
510
|
|
|
290
511
|
#assertOpen(): void {
|
|
@@ -299,7 +520,11 @@ export class RunStore {
|
|
|
299
520
|
value = work();
|
|
300
521
|
this.#database.exec("COMMIT");
|
|
301
522
|
} catch (error) {
|
|
302
|
-
|
|
523
|
+
try {
|
|
524
|
+
this.#database.exec("ROLLBACK");
|
|
525
|
+
} catch {
|
|
526
|
+
// The original operation or commit failure remains authoritative.
|
|
527
|
+
}
|
|
303
528
|
throw error;
|
|
304
529
|
}
|
|
305
530
|
this.#secureDatabaseFiles();
|
|
@@ -442,16 +667,62 @@ export class RunStore {
|
|
|
442
667
|
if (input.stage !== "abel-design" && input.stage !== "abel-implement") {
|
|
443
668
|
throw new Error("invalid-run-stage");
|
|
444
669
|
}
|
|
445
|
-
const
|
|
670
|
+
const keys = lookupKeys(input);
|
|
671
|
+
const key = keys[0];
|
|
672
|
+
if (!key) throw new Error("invalid-run-lookup-key");
|
|
446
673
|
return this.#transaction(() => {
|
|
447
|
-
|
|
674
|
+
const operationRows = this.#database
|
|
448
675
|
.prepare(
|
|
449
|
-
`SELECT run_id,
|
|
450
|
-
|
|
676
|
+
`SELECT operations.run_id, operations.kind, operations.state,
|
|
677
|
+
operations.outcome_json, runs.lookup_key
|
|
678
|
+
FROM operations
|
|
679
|
+
JOIN runs ON runs.run_id = operations.run_id
|
|
680
|
+
WHERE runs.root_hash = ? AND runs.stage = ?
|
|
681
|
+
AND operations.operation_id = ?`,
|
|
451
682
|
)
|
|
452
|
-
.
|
|
453
|
-
|
|
454
|
-
|
|
683
|
+
.all(
|
|
684
|
+
this.stateRoot.consumerRootHash,
|
|
685
|
+
input.stage,
|
|
686
|
+
input.operationId,
|
|
687
|
+
) as unknown as Array<{
|
|
688
|
+
run_id: string;
|
|
689
|
+
kind: string;
|
|
690
|
+
state: string;
|
|
691
|
+
outcome_json: string | null;
|
|
692
|
+
lookup_key: string;
|
|
693
|
+
}>;
|
|
694
|
+
if (operationRows.length > 0) {
|
|
695
|
+
const replay = operationRows.find(
|
|
696
|
+
(row) =>
|
|
697
|
+
row.kind === "start" &&
|
|
698
|
+
row.state === "committed" &&
|
|
699
|
+
row.outcome_json !== null &&
|
|
700
|
+
keys.includes(row.lookup_key),
|
|
701
|
+
);
|
|
702
|
+
if (operationRows.length !== 1 || !replay) {
|
|
703
|
+
throw new Error("operation-id-conflict");
|
|
704
|
+
}
|
|
705
|
+
const outcome = parseProjection(replay.outcome_json as string);
|
|
706
|
+
if (outcome.runId !== replay.run_id) {
|
|
707
|
+
throw new Error("operation-outcome-invalid");
|
|
708
|
+
}
|
|
709
|
+
return outcome;
|
|
710
|
+
}
|
|
711
|
+
let existing: RunRow | undefined;
|
|
712
|
+
for (const compatibleKey of keys) {
|
|
713
|
+
const candidate = this.#database
|
|
714
|
+
.prepare(
|
|
715
|
+
`SELECT run_id, projection_json FROM runs
|
|
716
|
+
WHERE root_hash = ? AND stage = ? AND lookup_key = ?`,
|
|
717
|
+
)
|
|
718
|
+
.get(this.stateRoot.consumerRootHash, input.stage, compatibleKey) as
|
|
719
|
+
| RunRow
|
|
720
|
+
| undefined;
|
|
721
|
+
if (candidate && existing && candidate.run_id !== existing.run_id) {
|
|
722
|
+
throw new Error("run-lookup-conflict");
|
|
723
|
+
}
|
|
724
|
+
existing ??= candidate;
|
|
725
|
+
}
|
|
455
726
|
if (existing) {
|
|
456
727
|
const projection = parseProjection(existing.projection_json);
|
|
457
728
|
if (
|
package/src/subagent-activity.ts
CHANGED
|
@@ -87,7 +87,6 @@ export type ActivitySummary = EvidenceActivitySummary | DiffActivitySummary;
|
|
|
87
87
|
export type ActivityTone = "accent" | "success" | "warning" | "muted" | "error";
|
|
88
88
|
|
|
89
89
|
export interface ActivityDisplay {
|
|
90
|
-
version: 1;
|
|
91
90
|
kind: "activityDisplay";
|
|
92
91
|
requestId: string;
|
|
93
92
|
role: string;
|
|
@@ -188,6 +187,7 @@ export function sanitizeDisplayText(value: unknown, maxLength = 240): string {
|
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
const SAFE_WORKFLOW_CODE = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
190
|
+
const SAFE_DESIGN_DETAIL_VALUE = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
191
191
|
const SAFE_CONTROL_ACTIONS = new Set([
|
|
192
192
|
"start",
|
|
193
193
|
"status",
|
|
@@ -221,6 +221,82 @@ export function sanitizeFailureReason(value: unknown, maxLength = 240): string {
|
|
|
221
221
|
return safe.slice(0, Math.max(0, maxLength));
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
function safeDesignFailure(value: unknown): {
|
|
225
|
+
operation: string;
|
|
226
|
+
code: string;
|
|
227
|
+
diagnostics: Array<Record<string, string>>;
|
|
228
|
+
} | null {
|
|
229
|
+
const failure = asRecord(value);
|
|
230
|
+
if (
|
|
231
|
+
failure?.kind !== "design-control-failure" ||
|
|
232
|
+
typeof failure.operation !== "string" ||
|
|
233
|
+
!SAFE_DESIGN_DETAIL_VALUE.test(failure.operation) ||
|
|
234
|
+
typeof failure.code !== "string" ||
|
|
235
|
+
!SAFE_DESIGN_DETAIL_VALUE.test(failure.code) ||
|
|
236
|
+
!Array.isArray(failure.diagnostics)
|
|
237
|
+
) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const diagnostics = failure.diagnostics.flatMap((candidate) => {
|
|
241
|
+
const record = asRecord(candidate);
|
|
242
|
+
if (
|
|
243
|
+
!record ||
|
|
244
|
+
typeof record.code !== "string" ||
|
|
245
|
+
!SAFE_DESIGN_DETAIL_VALUE.test(record.code)
|
|
246
|
+
) {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
return [
|
|
250
|
+
Object.fromEntries(
|
|
251
|
+
Object.entries(record).filter(
|
|
252
|
+
([, entry]) =>
|
|
253
|
+
typeof entry === "string" && SAFE_DESIGN_DETAIL_VALUE.test(entry),
|
|
254
|
+
),
|
|
255
|
+
) as Record<string, string>,
|
|
256
|
+
];
|
|
257
|
+
});
|
|
258
|
+
return {
|
|
259
|
+
operation: failure.operation,
|
|
260
|
+
code: failure.code,
|
|
261
|
+
diagnostics,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function renderDesignFailure(
|
|
266
|
+
failure: ReturnType<typeof safeDesignFailure> & object,
|
|
267
|
+
expanded: boolean,
|
|
268
|
+
theme?: ActivityTheme,
|
|
269
|
+
): Component {
|
|
270
|
+
const title = styled(
|
|
271
|
+
theme,
|
|
272
|
+
"error",
|
|
273
|
+
`${dispatchLabel("design")} ${failure.operation} failed: ${failure.code}`,
|
|
274
|
+
);
|
|
275
|
+
if (!expanded || failure.diagnostics.length === 0) {
|
|
276
|
+
const suffix =
|
|
277
|
+
failure.diagnostics.length > 0
|
|
278
|
+
? styled(
|
|
279
|
+
theme,
|
|
280
|
+
"muted",
|
|
281
|
+
` (${failure.diagnostics.length} diagnostic${failure.diagnostics.length === 1 ? "" : "s"})`,
|
|
282
|
+
)
|
|
283
|
+
: "";
|
|
284
|
+
return new Text(`${title}${suffix}`, 0, 0);
|
|
285
|
+
}
|
|
286
|
+
const lines = failure.diagnostics.map((diagnostic) => {
|
|
287
|
+
const details = Object.entries(diagnostic)
|
|
288
|
+
.filter(([key]) => key !== "code")
|
|
289
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
290
|
+
.join(" · ");
|
|
291
|
+
return ` - ${diagnostic.code}${details ? ` · ${details}` : ""}`;
|
|
292
|
+
});
|
|
293
|
+
return new Text(
|
|
294
|
+
[title, ...lines.map((entry) => styled(theme, "muted", entry))].join("\n"),
|
|
295
|
+
0,
|
|
296
|
+
0,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
224
300
|
export function formatElapsed(milliseconds: number): string {
|
|
225
301
|
const value = Math.max(
|
|
226
302
|
0,
|
|
@@ -470,6 +546,7 @@ function workflowStateOf(
|
|
|
470
546
|
}
|
|
471
547
|
const state = result.state;
|
|
472
548
|
switch (state) {
|
|
549
|
+
case "not-started":
|
|
473
550
|
case "created":
|
|
474
551
|
case "ready":
|
|
475
552
|
case "queued":
|
|
@@ -659,7 +736,6 @@ export function projectWorkflowActivity(
|
|
|
659
736
|
? "completion-state-inconsistent"
|
|
660
737
|
: workflowCode(payload);
|
|
661
738
|
return {
|
|
662
|
-
version: 1,
|
|
663
739
|
kind: "activityDisplay",
|
|
664
740
|
requestId: sanitizeDisplayText(
|
|
665
741
|
runId ?? taskId ?? change ?? command.operationId ?? "pending",
|
|
@@ -792,7 +868,6 @@ function displayFromEvent(
|
|
|
792
868
|
): ActivityDisplay {
|
|
793
869
|
const reason = event.failureReason;
|
|
794
870
|
return {
|
|
795
|
-
version: 1,
|
|
796
871
|
kind: "activityDisplay",
|
|
797
872
|
requestId: entry.requestId,
|
|
798
873
|
role: entry.role,
|
|
@@ -989,7 +1064,6 @@ export class ActivityController {
|
|
|
989
1064
|
const entry = this.entries.get(toolCallId);
|
|
990
1065
|
if (!entry) return;
|
|
991
1066
|
const display: ActivityDisplay = {
|
|
992
|
-
version: 1,
|
|
993
1067
|
kind: "activityDisplay",
|
|
994
1068
|
requestId: entry.requestId,
|
|
995
1069
|
role: entry.role,
|
|
@@ -1230,7 +1304,6 @@ export function renderActivityResult(
|
|
|
1230
1304
|
if (failedRun) {
|
|
1231
1305
|
const snapshot = identityFromDispatchArgs(asRecord(context?.args)?.request);
|
|
1232
1306
|
const display: ActivityDisplay = {
|
|
1233
|
-
version: 1,
|
|
1234
1307
|
kind: "activityDisplay",
|
|
1235
1308
|
requestId: isActivityDisplay(activity)
|
|
1236
1309
|
? activity.requestId
|
|
@@ -1250,6 +1323,10 @@ export function renderActivityResult(
|
|
|
1250
1323
|
return new ActivityInlineComponent(display, theme);
|
|
1251
1324
|
}
|
|
1252
1325
|
if (failed) {
|
|
1326
|
+
const designFailure = safeDesignFailure(details?.designFailure);
|
|
1327
|
+
if (args?.action === "design" && designFailure) {
|
|
1328
|
+
return renderDesignFailure(designFailure, options.expanded, theme);
|
|
1329
|
+
}
|
|
1253
1330
|
return new Text(
|
|
1254
1331
|
`${dispatchLabel(args?.action ?? args?.command)} failed`,
|
|
1255
1332
|
0,
|
|
@@ -1275,7 +1352,6 @@ function isActivityDisplay(value: unknown): value is ActivityDisplay {
|
|
|
1275
1352
|
const record = asRecord(value);
|
|
1276
1353
|
return (
|
|
1277
1354
|
record?.kind === "activityDisplay" &&
|
|
1278
|
-
record.version === 1 &&
|
|
1279
1355
|
typeof record.requestId === "string" &&
|
|
1280
1356
|
typeof record.role === "string" &&
|
|
1281
1357
|
typeof record.phase === "string" &&
|