@abelxiaoxing/cadence 1.2.2 → 1.2.3

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,6 +15,7 @@ import {
15
15
  compileImplementPlan,
16
16
  compileReadyReceipt,
17
17
  type DeliveryArtifactBinding,
18
+ DesignPlanValidationError,
18
19
  type GateApprovalProof,
19
20
  parseGateAReceipt,
20
21
  parseImplementPlan,
@@ -88,6 +89,10 @@ export type DesignControlRequest =
88
89
  operationId: string;
89
90
  gate: "gate-b";
90
91
  }
92
+ | {
93
+ operation: "validate-plan-draft";
94
+ runId: string;
95
+ }
91
96
  | {
92
97
  operation: "compile-plan";
93
98
  runId: string;
@@ -198,7 +203,10 @@ export function validateDesignControlRequest(
198
203
  ? { ok: true, value: structuredClone(value) as DesignControlRequest }
199
204
  : { ok: false, code: "invalid-design-control-request" };
200
205
  }
201
- if (value.operation === "status") {
206
+ if (
207
+ value.operation === "status" ||
208
+ value.operation === "validate-plan-draft"
209
+ ) {
202
210
  return exactKeys(value, ["operation", "runId"]) &&
203
211
  typeof value.runId === "string" &&
204
212
  IDENTIFIER.test(value.runId)
@@ -508,6 +516,8 @@ export class DesignController {
508
516
  return this.#journal.recordDecision(request);
509
517
  case "approve-gate":
510
518
  return this.#journal.approveGate(request);
519
+ case "validate-plan-draft":
520
+ return this.#validatePlanDraft(request);
511
521
  case "compile-plan":
512
522
  return this.#compilePlan(request);
513
523
  case "finalize-delivery":
@@ -539,7 +549,7 @@ export class DesignController {
539
549
  const canApproveGate =
540
550
  !design.gates.gateA.current ||
541
551
  (planReadyForGateB && !design.gates.gateB.current);
542
- const legalActions = terminal
552
+ const legalOperations = terminal
543
553
  ? ["status"]
544
554
  : [
545
555
  "status",
@@ -549,7 +559,12 @@ export class DesignController {
549
559
  ? ["bind-change"]
550
560
  : []),
551
561
  ...(design.gates.gateA.current && design.change
552
- ? ["write-artifact", "delete-artifact", "compile-plan"]
562
+ ? [
563
+ "write-artifact",
564
+ "delete-artifact",
565
+ "validate-plan-draft",
566
+ "compile-plan",
567
+ ]
553
568
  : []),
554
569
  ...(design.change &&
555
570
  design.plan &&
@@ -557,7 +572,6 @@ export class DesignController {
557
572
  design.gates.gateB.current
558
573
  ? ["finalize-delivery"]
559
574
  : []),
560
- "finish",
561
575
  ];
562
576
  return {
563
577
  runId: run.runId,
@@ -566,7 +580,8 @@ export class DesignController {
566
580
  state: run.state,
567
581
  completed: run.state === "completed",
568
582
  ...(run.pauseCode ? { pause: { code: run.pauseCode } } : {}),
569
- legalActions,
583
+ legalOperations,
584
+ packetActions: terminal ? [] : ["finish"],
570
585
  design,
571
586
  };
572
587
  }
@@ -578,7 +593,7 @@ export class DesignController {
578
593
  ...(fromRequirement
579
594
  ? {
580
595
  provisionalKey: transientHash(
581
- "abel-design-requirement-v1",
596
+ "abel-design-requirement",
582
597
  request.requirement,
583
598
  ),
584
599
  }
@@ -772,6 +787,51 @@ export class DesignController {
772
787
  }
773
788
  }
774
789
 
790
+ #readCompiledDraft(runId: string) {
791
+ const status = this.#journal.status(runId);
792
+ if (!status.change) throw new Error("design-change-required");
793
+ if (!status.gates.gateA.current) throw new Error("design-gate-a-required");
794
+ const draftPath = safeRelative(status.change, "plan-draft.json");
795
+ const draftBytes = readSafeFile(this.consumerRoot, draftPath);
796
+ let draft: unknown;
797
+ try {
798
+ draft = JSON.parse(draftBytes.toString("utf8"));
799
+ } catch {
800
+ throw new DesignPlanValidationError("design-plan-draft-invalid", [
801
+ { code: "design-plan-draft-invalid", field: "plan-draft.json" },
802
+ ]);
803
+ }
804
+ const compiled = compileImplementPlan(draft, {
805
+ consumerRoot: this.consumerRoot,
806
+ });
807
+ if (compiled.plan.changeId !== status.change) {
808
+ throw new DesignPlanValidationError("design-plan-change-mismatch", [
809
+ { code: "design-plan-change-mismatch", field: "changeId" },
810
+ ]);
811
+ }
812
+ return { status, compiled };
813
+ }
814
+
815
+ #validatePlanDraft(
816
+ request: Extract<
817
+ DesignControlRequest,
818
+ { operation: "validate-plan-draft" }
819
+ >,
820
+ ) {
821
+ const { compiled } = this.#readCompiledDraft(request.runId);
822
+ return {
823
+ operation: request.operation,
824
+ runId: request.runId,
825
+ valid: true,
826
+ plan: {
827
+ taskCount: compiled.plan.tasks.length,
828
+ outputCount: compiled.plan.outputs.length,
829
+ rawSha256: compiled.rawSha256,
830
+ canonicalHash: compiled.planHash,
831
+ },
832
+ };
833
+ }
834
+
775
835
  #compilePlan(
776
836
  request: Extract<DesignControlRequest, { operation: "compile-plan" }>,
777
837
  ) {
@@ -787,32 +847,17 @@ export class DesignController {
787
847
  });
788
848
  try {
789
849
  this.#journal.assertNoPendingArtifactOperations(request.runId);
790
- const status = this.#journal.status(request.runId);
791
- if (!status.change) throw new Error("design-change-required");
792
- if (!status.gates.gateA.current)
793
- throw new Error("design-gate-a-required");
794
- const readyPath = safeRelative(status.change, "ready.yaml");
795
- const gateAPath = safeRelative(status.change, "gate-a.yaml");
850
+ const { status, compiled } = this.#readCompiledDraft(request.runId);
851
+ const change = status.change;
852
+ if (!change) throw new Error("design-change-required");
853
+ const readyPath = safeRelative(change, "ready.yaml");
854
+ const gateAPath = safeRelative(change, "gate-a.yaml");
796
855
  removeSafeFile(this.consumerRoot, readyPath);
797
856
  removeSafeFile(this.consumerRoot, gateAPath);
798
- const draftPath = safeRelative(status.change, "plan-draft.json");
799
- const draftBytes = readSafeFile(this.consumerRoot, draftPath);
800
- let draft: unknown;
801
- try {
802
- draft = JSON.parse(draftBytes.toString("utf8"));
803
- } catch {
804
- throw new Error("design-plan-draft-invalid");
805
- }
806
- const compiled = compileImplementPlan(draft as never, {
807
- consumerRoot: this.consumerRoot,
808
- });
809
- if (compiled.plan.changeId !== status.change) {
810
- throw new Error("design-plan-change-mismatch");
811
- }
812
857
  this.#journal.assertFinalizationLease(lease);
813
858
  atomicWrite(
814
859
  this.consumerRoot,
815
- safeRelative(status.change, "implement-plan.json"),
860
+ safeRelative(change, "implement-plan.json"),
816
861
  compiled.bytes,
817
862
  );
818
863
  return this.#journal.recordCompiledPlan({
@@ -899,7 +899,7 @@ export class DesignJournal {
899
899
  }): Record<string, unknown> {
900
900
  requireIdentifier(input.decisionId, "design-decision-id");
901
901
  const decisionContractHash = contractHash(
902
- "abel-design-decision-v1",
902
+ "abel-design-decision",
903
903
  input.contract,
904
904
  );
905
905
  if (input.category !== "behavior" && input.category !== "technical") {
@@ -1024,7 +1024,7 @@ export class DesignJournal {
1024
1024
  }
1025
1025
  const gateAContractHash =
1026
1026
  input.gate === "gate-a"
1027
- ? contractHash("abel-design-gate-a-v1", input.contract)
1027
+ ? contractHash("abel-design-gate-a", input.contract)
1028
1028
  : undefined;
1029
1029
  return this.#transaction(() => {
1030
1030
  this.#run(input.runId, true);
@@ -56,7 +56,7 @@ function updateManifest(
56
56
 
57
57
  function recursiveDirectoryManifest(abs: string): string {
58
58
  const hash = createHash("sha256");
59
- updateManifest(hash, "cadence-directory-manifest-v2");
59
+ updateManifest(hash, "cadence-directory-manifest");
60
60
  const visit = (directory: string, prefix: string): void => {
61
61
  const names = readdirSync(directory).sort();
62
62
  for (const name of names) {
package/src/index.ts CHANGED
@@ -38,6 +38,7 @@ import {
38
38
  verificationSteps,
39
39
  } from "./contracts.ts";
40
40
  import {
41
+ CONTROL_COMMAND_PARAMETERS,
41
42
  canonicalizeControlCommandToolInput,
42
43
  validateControlCommand,
43
44
  } from "./control-contracts.ts";
@@ -45,14 +46,15 @@ import {
45
46
  assessDeliveryTraceability,
46
47
  compileImplementPlan,
47
48
  DeliveryValidationError,
49
+ DesignPlanValidationError,
48
50
  type GateApprovalProof,
49
- IMPLEMENT_PLAN_SCHEMA_VERSION,
50
51
  parseGateAReceipt,
51
52
  parseImplementPlan,
52
53
  parseReadyReceipt,
53
54
  } from "./delivery-compiler.ts";
54
55
  import {
55
56
  DesignController,
57
+ DesignFinalizationError,
56
58
  validateDesignControlRequest,
57
59
  } from "./design-control.ts";
58
60
  import { canonicalJson, hashCanonicalValue } from "./implement-graph.ts";
@@ -67,6 +69,7 @@ import {
67
69
  unavailableRoutePolicy,
68
70
  type WorkerRoutePolicy,
69
71
  } from "./route-policy.ts";
72
+ import { RunStoreFormatError } from "./run-store.ts";
70
73
  import { observeSafePath } from "./safe-path.ts";
71
74
  import { resolveStateRoot } from "./state-root.ts";
72
75
  import {
@@ -229,15 +232,15 @@ const DESIGN_CONTROL_REQUEST_SCHEMA = {
229
232
  required: ["operation", "operationId", field],
230
233
  additionalProperties: false,
231
234
  })),
232
- {
233
- type: "object",
235
+ ...(["status", "validate-plan-draft"] as const).map((operation) => ({
236
+ type: "object" as const,
234
237
  properties: {
235
- operation: { type: "string", enum: ["status"] },
236
- runId: { type: "string" },
238
+ operation: { type: "string" as const, enum: [operation] },
239
+ runId: { type: "string" as const },
237
240
  },
238
241
  required: ["operation", "runId"],
239
242
  additionalProperties: false,
240
- },
243
+ })),
241
244
  {
242
245
  type: "object",
243
246
  properties: {
@@ -421,6 +424,89 @@ function splitUsage(result: unknown): { payload: unknown; usage?: unknown } {
421
424
  return { payload, usage };
422
425
  }
423
426
 
427
+ const SAFE_DESIGN_ERROR_CODE = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
428
+ const MAX_DESIGN_ERROR_DIAGNOSTICS = 64;
429
+
430
+ type SafeDesignFailure = {
431
+ kind: "design-control-failure";
432
+ operation: string;
433
+ code: string;
434
+ diagnostics: ReadonlyArray<Record<string, string>>;
435
+ };
436
+
437
+ function safeDesignFailure(
438
+ error: unknown,
439
+ operation: string,
440
+ ): SafeDesignFailure {
441
+ const message = error instanceof Error ? error.message : "";
442
+ const code =
443
+ error instanceof DesignPlanValidationError
444
+ ? "design-plan-validation-invalid"
445
+ : SAFE_DESIGN_ERROR_CODE.test(message)
446
+ ? message
447
+ : "design-control-failed";
448
+ const rawDiagnostics: readonly unknown[] =
449
+ error instanceof DesignFinalizationError ||
450
+ error instanceof DesignPlanValidationError ||
451
+ error instanceof DeliveryValidationError
452
+ ? error.diagnostics
453
+ : [];
454
+ const diagnostics = rawDiagnostics
455
+ .flatMap((candidate) => {
456
+ if (typeof candidate === "string") {
457
+ const diagnosticCode = candidate.split(":", 1)[0] ?? "";
458
+ return SAFE_DESIGN_ERROR_CODE.test(diagnosticCode)
459
+ ? [{ code: diagnosticCode }]
460
+ : [];
461
+ }
462
+ if (
463
+ candidate === null ||
464
+ typeof candidate !== "object" ||
465
+ Array.isArray(candidate)
466
+ ) {
467
+ return [];
468
+ }
469
+ const admitted = Object.fromEntries(
470
+ Object.entries(candidate as Record<string, unknown>)
471
+ .filter(
472
+ (entry): entry is [string, string] =>
473
+ [
474
+ "code",
475
+ "taskId",
476
+ "phase",
477
+ "field",
478
+ "category",
479
+ "owner",
480
+ "verificationId",
481
+ "outputId",
482
+ "dependencyTaskId",
483
+ "producerTaskId",
484
+ "producerPhase",
485
+ ].includes(entry[0]) &&
486
+ typeof entry[1] === "string" &&
487
+ SAFE_DESIGN_ERROR_CODE.test(entry[1]),
488
+ )
489
+ .sort(([left], [right]) => left.localeCompare(right)),
490
+ );
491
+ return typeof admitted.code === "string" ? [admitted] : [];
492
+ })
493
+ .sort((left, right) =>
494
+ canonicalJson(left).localeCompare(canonicalJson(right)),
495
+ )
496
+ .filter(
497
+ (candidate, index, values) =>
498
+ index === 0 ||
499
+ canonicalJson(candidate) !== canonicalJson(values[index - 1]),
500
+ )
501
+ .slice(0, MAX_DESIGN_ERROR_DIAGNOSTICS);
502
+ return {
503
+ kind: "design-control-failure",
504
+ operation: SAFE_DESIGN_ERROR_CODE.test(operation) ? operation : "unknown",
505
+ code,
506
+ diagnostics,
507
+ };
508
+ }
509
+
424
510
  export interface WorkflowControlEngine {
425
511
  execute(
426
512
  command: unknown,
@@ -733,7 +819,10 @@ export function packageDeliverySource(
733
819
  try {
734
820
  receipt = parseReadyReceipt(receiptBytes);
735
821
  } catch {
736
- throw new DeliveryValidationError(["delivery-receipt-invalid"]);
822
+ throw new DeliveryValidationError([
823
+ "delivery-receipt-invalid",
824
+ "delivery-recompile-required",
825
+ ]);
737
826
  }
738
827
  const diagnostics = new Set<string>();
739
828
  if (receipt.change !== input.change) {
@@ -899,7 +988,6 @@ export function packageDeliverySource(
899
988
  }
900
989
  if (
901
990
  receipt.plan.path !== "implement-plan.json" ||
902
- receipt.plan.schemaVersion !== IMPLEMENT_PLAN_SCHEMA_VERSION ||
903
991
  receipt.plan.rawSha256 !== sha256(planBytes) ||
904
992
  receipt.plan.canonicalHash !== hashCanonicalValue(plan)
905
993
  ) {
@@ -982,7 +1070,6 @@ export function packageDeliverySource(
982
1070
  throw new DeliveryValidationError([...diagnostics]);
983
1071
  }
984
1072
  return {
985
- version: 2,
986
1073
  gate: "gate-b",
987
1074
  revision: receipt.deliveryRevision,
988
1075
  receiptHash,
@@ -1251,7 +1338,7 @@ function normalizedFailureIdentities(input: {
1251
1338
  if (vitestFailures.length > 0) {
1252
1339
  return [...new Set(vitestFailures)]
1253
1340
  .sort()
1254
- .map((failure) => sha256(`vitest-failure-v1\0${failure}`));
1341
+ .map((failure) => sha256(`vitest-failure\0${failure}`));
1255
1342
  }
1256
1343
  const normalizedOutput = input.output
1257
1344
  .replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "gu"), "")
@@ -1261,7 +1348,6 @@ function normalizedFailureIdentities(input: {
1261
1348
  return [
1262
1349
  sha256(
1263
1350
  canonicalJson({
1264
- version: 1,
1265
1351
  verificationId: input.step.id,
1266
1352
  exitCode: input.exitCode,
1267
1353
  output: normalizedOutput,
@@ -1514,6 +1600,8 @@ export function openPackageWorkflowControlEngine(
1514
1600
  context,
1515
1601
  parentPayloadBridge,
1516
1602
  input.signal,
1603
+ process.env,
1604
+ { onResponse: input.onHeaders },
1517
1605
  );
1518
1606
  if (!phaseRuntime.ok) {
1519
1607
  if (phaseRuntime.failure.kind === "cancelled") {
@@ -1565,6 +1653,9 @@ export function openPackageWorkflowControlEngine(
1565
1653
  ...(input.artifactCorrection
1566
1654
  ? { artifactCorrection: structuredClone(input.artifactCorrection) }
1567
1655
  : {}),
1656
+ ...(input.contextRequest
1657
+ ? { requestedContext: structuredClone(input.contextRequest) }
1658
+ : {}),
1568
1659
  ...(input.repair
1569
1660
  ? {
1570
1661
  repair: {
@@ -1608,7 +1699,6 @@ export function openPackageWorkflowControlEngine(
1608
1699
  failureOverride: phaseRuntime.failureOverride,
1609
1700
  ledgerProjection: input.ledgerProjection,
1610
1701
  candidateArtifact: input.candidateArtifact,
1611
- onStreamStart: input.onHeaders,
1612
1702
  onStreamProgress: input.onProgress,
1613
1703
  });
1614
1704
  if (!child.ok) {
@@ -1624,10 +1714,18 @@ export function openPackageWorkflowControlEngine(
1624
1714
  if (child.failure.kind === "result-limit") {
1625
1715
  return { kind: "paused", code: "needs-task-split" };
1626
1716
  }
1717
+ const attemptDiagnostic = {
1718
+ finalCategory: child.classification.finalCategory,
1719
+ submitAttempts: child.classification.attempts,
1720
+ schema: child.classification.schema,
1721
+ identityMismatch: Object.entries(child.classification.identity)
1722
+ .filter(([, matches]) => !matches)
1723
+ .map(([dimension]) => dimension),
1724
+ };
1627
1725
  return child.failure.kind === "environment" ||
1628
1726
  child.failure.kind === "verification-adapter"
1629
- ? { kind: "paused", code: child.failure.code }
1630
- : { kind: "retryable", code: child.failure.code };
1727
+ ? { kind: "paused", code: child.failure.code, attemptDiagnostic }
1728
+ : { kind: "retryable", code: child.failure.code, attemptDiagnostic };
1631
1729
  }
1632
1730
  const result = child.result;
1633
1731
  if (result.kind === "context-request") {
@@ -1764,6 +1862,39 @@ export function openPackageWorkflowControlEngine(
1764
1862
  };
1765
1863
  }
1766
1864
 
1865
+ function runStoreUnavailableStatus(
1866
+ error: RunStoreFormatError,
1867
+ command: {
1868
+ stage: "abel-implement";
1869
+ change: string;
1870
+ },
1871
+ ): Record<string, unknown> {
1872
+ return {
1873
+ stage: command.stage,
1874
+ change: command.change,
1875
+ state: "paused",
1876
+ durable: false,
1877
+ completed: false,
1878
+ pause: { code: error.code },
1879
+ legalCommands: ["status", "start"],
1880
+ tasks: [],
1881
+ queue: [],
1882
+ controlStore: {
1883
+ code: error.code,
1884
+ databasePath: error.databasePath,
1885
+ recovery: structuredClone(error.recovery),
1886
+ },
1887
+ };
1888
+ }
1889
+
1890
+ function runStoreResetError(error: RunStoreFormatError): Error {
1891
+ const reset = new Error(
1892
+ `${error.code}: back up and remove the private run store at ${error.databasePath}, then retry the operation`,
1893
+ );
1894
+ reset.name = "RunStoreResetError";
1895
+ return reset;
1896
+ }
1897
+
1767
1898
  function visibleRoutePolicyStatus(
1768
1899
  resolution: RoutePolicyResolution,
1769
1900
  brokerStatus: Record<string, unknown> | undefined,
@@ -1783,6 +1914,7 @@ export function registerWorkflowControl(
1783
1914
  const parentPayloadBridge = new ParentPayloadBridge();
1784
1915
  const activity = new ActivityController();
1785
1916
  const engines = new Map<string, Promise<WorkflowControlEngine>>();
1917
+ const designFailures = new Map<string, SafeDesignFailure>();
1786
1918
  let pendingPrompt: EligiblePrompt | undefined;
1787
1919
  let activePrompt: EligiblePrompt | undefined;
1788
1920
  let designToolSnapshot: string[] | undefined;
@@ -1875,30 +2007,15 @@ export function registerWorkflowControl(
1875
2007
 
1876
2008
  // Pi validates tool arguments before execute(), so expose only the schema
1877
2009
  // legal for the active stage instead of one ambiguous command/packet union.
1878
- const CONTROL_COMMAND_PARAMETERS = {
2010
+ const PACKET_PARAMETERS = {
1879
2011
  type: "object",
1880
2012
  properties: {
1881
- command: {
1882
- type: "string",
1883
- enum: ["start", "status", "resume", "rebind", "cancel", "discard"],
1884
- },
1885
- stage: {
2013
+ action: {
1886
2014
  type: "string",
1887
- enum: ["abel-implement"],
2015
+ enum: [...PACKET_ACTIONS, "design"],
2016
+ description:
2017
+ "Top-level dispatch action. design and run require request; finish is explicit stage exit and MUST omit request.",
1888
2018
  },
1889
- change: { type: "string" },
1890
- operationId: { type: "string" },
1891
- deliveryRevision: { type: "integer", minimum: 1 },
1892
- receiptHash: { type: "string" },
1893
- routeId: { type: "string" },
1894
- },
1895
- required: ["command", "stage", "change"],
1896
- additionalProperties: false,
1897
- } as const;
1898
- const PACKET_PARAMETERS = {
1899
- type: "object",
1900
- properties: {
1901
- action: { type: "string", enum: [...PACKET_ACTIONS, "design"] },
1902
2019
  request: {
1903
2020
  anyOf: [
1904
2021
  DESIGN_REQUEST_SCHEMA,
@@ -1950,6 +2067,9 @@ export function registerWorkflowControl(
1950
2067
  ) {
1951
2068
  throw new Error("control-envelope-ambiguous");
1952
2069
  }
2070
+ if (record.action === "finish" && Object.hasOwn(record, "request")) {
2071
+ throw new Error("control-envelope-ambiguous");
2072
+ }
1953
2073
  if (
1954
2074
  activePrompt !== "abel-design" &&
1955
2075
  activePrompt !== "abel-diagnose"
@@ -1962,22 +2082,40 @@ export function registerWorkflowControl(
1962
2082
  }
1963
2083
  const designRequest = validateDesignControlRequest(record.request);
1964
2084
  if (!designRequest.ok) throw new Error(designRequest.code);
1965
- const engine = await engineFor(ctx);
2085
+ let engine: WorkflowControlEngine;
2086
+ try {
2087
+ engine = await engineFor(ctx);
2088
+ } catch (error) {
2089
+ if (error instanceof RunStoreFormatError)
2090
+ throw runStoreResetError(error);
2091
+ throw error;
2092
+ }
1966
2093
  if (!engine.executeDesign)
1967
2094
  throw new Error("design-control-unavailable");
1968
- const payload = await engine.executeDesign(designRequest.value);
1969
- if (
1970
- designRequest.value.operation === "finalize-delivery" &&
1971
- payload.state === "completed"
1972
- ) {
1973
- await deactivateStage();
2095
+ try {
2096
+ const payload = await engine.executeDesign(designRequest.value);
2097
+ if (
2098
+ designRequest.value.operation === "finalize-delivery" &&
2099
+ payload.state === "completed"
2100
+ ) {
2101
+ await deactivateStage();
2102
+ }
2103
+ return {
2104
+ content: [
2105
+ { type: "text" as const, text: JSON.stringify(payload) },
2106
+ ],
2107
+ details: payload,
2108
+ };
2109
+ } catch (error) {
2110
+ const failure = safeDesignFailure(
2111
+ error,
2112
+ designRequest.value.operation,
2113
+ );
2114
+ designFailures.set(toolCallId, failure);
2115
+ const sanitized = new Error(failure.code);
2116
+ sanitized.name = "DesignControlError";
2117
+ throw sanitized;
1974
2118
  }
1975
- return {
1976
- content: [
1977
- { type: "text" as const, text: JSON.stringify(payload) },
1978
- ],
1979
- details: payload,
1980
- };
1981
2119
  }
1982
2120
  let designEngine: WorkflowControlEngine | undefined;
1983
2121
  if (record.action === "run") {
@@ -1988,7 +2126,13 @@ export function registerWorkflowControl(
1988
2126
  );
1989
2127
  }
1990
2128
  if (packet.value.stage === "abel-design") {
1991
- designEngine = await engineFor(ctx);
2129
+ try {
2130
+ designEngine = await engineFor(ctx);
2131
+ } catch (error) {
2132
+ if (error instanceof RunStoreFormatError)
2133
+ throw runStoreResetError(error);
2134
+ throw error;
2135
+ }
1992
2136
  if (!designEngine.assertDesignRun || !packet.value.runId) {
1993
2137
  throw new Error("design-control-unavailable");
1994
2138
  }
@@ -2076,7 +2220,17 @@ export function registerWorkflowControl(
2076
2220
  ) {
2077
2221
  throw new Error("stage-control-mismatch");
2078
2222
  }
2079
- const engine = await engineFor(ctx);
2223
+ let engine: WorkflowControlEngine;
2224
+ try {
2225
+ engine = await engineFor(ctx);
2226
+ } catch (error) {
2227
+ if (!(error instanceof RunStoreFormatError)) throw error;
2228
+ const payload = runStoreUnavailableStatus(error, validation.value);
2229
+ return {
2230
+ content: [{ type: "text" as const, text: JSON.stringify(payload) }],
2231
+ details: payload,
2232
+ };
2233
+ }
2080
2234
  const tuiControl = ctx.mode === "tui";
2081
2235
  if (tuiControl) {
2082
2236
  try {
@@ -2161,6 +2315,39 @@ export function registerWorkflowControl(
2161
2315
  };
2162
2316
  registerDispatchTool("packet");
2163
2317
 
2318
+ pi.on("tool_result", (event) => {
2319
+ if (
2320
+ event.toolName !== DISPATCH_TOOL ||
2321
+ !event.isError ||
2322
+ event.input.action !== "design"
2323
+ ) {
2324
+ return;
2325
+ }
2326
+ const errorText = event.content
2327
+ .flatMap((item) =>
2328
+ item.type === "text" && typeof item.text === "string"
2329
+ ? [item.text]
2330
+ : [],
2331
+ )
2332
+ .join("\n");
2333
+ const failure = safeDesignFailure(
2334
+ new Error(errorText.trim()),
2335
+ typeof (event.input.request as Record<string, unknown> | undefined)
2336
+ ?.operation === "string"
2337
+ ? String((event.input.request as Record<string, unknown>).operation)
2338
+ : "unknown",
2339
+ );
2340
+ const details =
2341
+ event.details && typeof event.details === "object"
2342
+ ? (event.details as Record<string, unknown>)
2343
+ : {};
2344
+ const visible = designFailures.get(event.toolCallId) ?? failure;
2345
+ designFailures.delete(event.toolCallId);
2346
+ return {
2347
+ content: [{ type: "text" as const, text: JSON.stringify(visible) }],
2348
+ details: { ...details, designFailure: visible },
2349
+ };
2350
+ });
2164
2351
  pi.on("input", (event) => {
2165
2352
  pendingPrompt = invokedPrompt(event.text);
2166
2353
  return { action: "continue" };
@@ -2214,6 +2401,7 @@ export function registerWorkflowControl(
2214
2401
  pi.on("session_start", async (_event, ctx) => {
2215
2402
  pendingPrompt = undefined;
2216
2403
  activePrompt = undefined;
2404
+ designFailures.clear();
2217
2405
  activity.detach();
2218
2406
  await packetRuntime.drain();
2219
2407
  await closeEngines();
@@ -2229,6 +2417,7 @@ export function registerWorkflowControl(
2229
2417
  });
2230
2418
  pi.on("session_shutdown", async () => {
2231
2419
  activePrompt = undefined;
2420
+ designFailures.clear();
2232
2421
  activity.detach();
2233
2422
  await packetRuntime.drain();
2234
2423
  await closeEngines();