@abelxiaoxing/cadence 1.2.1 → 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.
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") {
@@ -1525,19 +1613,49 @@ export function openPackageWorkflowControlEngine(
1525
1613
  if (!phase) {
1526
1614
  return { kind: "paused", code: "task-phase-unavailable" };
1527
1615
  }
1616
+ const taskPhases = Object.values(input.task.phases);
1617
+ const executionBoundary = input.artifactCorrection
1618
+ ? {
1619
+ read: [
1620
+ ...new Set(
1621
+ taskPhases.flatMap((boundary) => [
1622
+ ...boundary.read,
1623
+ ...boundary.write,
1624
+ ...boundary.delete,
1625
+ ]),
1626
+ ),
1627
+ ].sort(),
1628
+ write: [
1629
+ ...new Set(taskPhases.flatMap((boundary) => boundary.write)),
1630
+ ].sort(),
1631
+ delete: [
1632
+ ...new Set(taskPhases.flatMap((boundary) => boundary.delete)),
1633
+ ].sort(),
1634
+ }
1635
+ : {
1636
+ read: [...phase.read],
1637
+ write: [...phase.write],
1638
+ delete: [...phase.delete],
1639
+ };
1528
1640
  const phaseContract = {
1529
1641
  candidateId: input.candidateArtifact.identity.candidateId,
1530
1642
  taskId: input.taskId,
1531
1643
  phase: input.phase,
1532
- readSet: [...phase.read],
1533
- writeSet: [...phase.write],
1534
- deleteSet: [...phase.delete],
1644
+ readSet: executionBoundary.read,
1645
+ writeSet: executionBoundary.write,
1646
+ deleteSet: executionBoundary.delete,
1535
1647
  verification: structuredClone(phase.verification),
1536
1648
  agentsImpact: input.task.agents.impact,
1537
1649
  agentsTarget: input.task.agents.target ?? null,
1538
1650
  agentsManagedOnly: true,
1539
1651
  agentsWriteAllowed: false,
1540
1652
  impactClosure: structuredClone(input.task.impactClosure),
1653
+ ...(input.artifactCorrection
1654
+ ? { artifactCorrection: structuredClone(input.artifactCorrection) }
1655
+ : {}),
1656
+ ...(input.contextRequest
1657
+ ? { requestedContext: structuredClone(input.contextRequest) }
1658
+ : {}),
1541
1659
  ...(input.repair
1542
1660
  ? {
1543
1661
  repair: {
@@ -1570,14 +1688,17 @@ export function openPackageWorkflowControlEngine(
1570
1688
  path.resolve(input.workspaceRoot, root),
1571
1689
  ),
1572
1690
  allowedPaths: [
1573
- ...new Set([...phase.read, ...phase.write, ...phase.delete]),
1691
+ ...new Set([
1692
+ ...executionBoundary.read,
1693
+ ...executionBoundary.write,
1694
+ ...executionBoundary.delete,
1695
+ ]),
1574
1696
  ],
1575
1697
  timeoutMs: LIMITS.phaseTimeoutMs,
1576
1698
  signal: input.signal,
1577
1699
  failureOverride: phaseRuntime.failureOverride,
1578
1700
  ledgerProjection: input.ledgerProjection,
1579
1701
  candidateArtifact: input.candidateArtifact,
1580
- onStreamStart: input.onHeaders,
1581
1702
  onStreamProgress: input.onProgress,
1582
1703
  });
1583
1704
  if (!child.ok) {
@@ -1593,18 +1714,43 @@ export function openPackageWorkflowControlEngine(
1593
1714
  if (child.failure.kind === "result-limit") {
1594
1715
  return { kind: "paused", code: "needs-task-split" };
1595
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
+ };
1596
1725
  return child.failure.kind === "environment" ||
1597
1726
  child.failure.kind === "verification-adapter"
1598
- ? { kind: "paused", code: child.failure.code }
1599
- : { kind: "retryable", code: child.failure.code };
1727
+ ? { kind: "paused", code: child.failure.code, attemptDiagnostic }
1728
+ : { kind: "retryable", code: child.failure.code, attemptDiagnostic };
1600
1729
  }
1601
1730
  const result = child.result;
1602
1731
  if (result.kind === "context-request") {
1603
- return classifyCandidateContextRequest(result, [
1604
- ...phase.read,
1605
- ...phase.write,
1606
- ...phase.delete,
1607
- ]);
1732
+ return classifyCandidateContextRequest(result, {
1733
+ phase: input.phase,
1734
+ readPaths: phase.read,
1735
+ writePaths: phase.write,
1736
+ deletePaths: phase.delete,
1737
+ taskPaths: [
1738
+ ...new Set(
1739
+ Object.values(input.task.phases).flatMap((boundary) => [
1740
+ ...boundary.read,
1741
+ ...boundary.write,
1742
+ ...boundary.delete,
1743
+ ]),
1744
+ ),
1745
+ ],
1746
+ redWritePaths: input.task.phases.red.write,
1747
+ agents: {
1748
+ impact: input.task.agents.impact,
1749
+ ...(input.task.agents.target
1750
+ ? { target: input.task.agents.target }
1751
+ : {}),
1752
+ },
1753
+ });
1608
1754
  }
1609
1755
  if (result.kind !== "sealed-candidate") {
1610
1756
  return { kind: "retryable", code: "candidate-diff-invalid" };
@@ -1716,6 +1862,39 @@ export function openPackageWorkflowControlEngine(
1716
1862
  };
1717
1863
  }
1718
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
+
1719
1898
  function visibleRoutePolicyStatus(
1720
1899
  resolution: RoutePolicyResolution,
1721
1900
  brokerStatus: Record<string, unknown> | undefined,
@@ -1735,6 +1914,7 @@ export function registerWorkflowControl(
1735
1914
  const parentPayloadBridge = new ParentPayloadBridge();
1736
1915
  const activity = new ActivityController();
1737
1916
  const engines = new Map<string, Promise<WorkflowControlEngine>>();
1917
+ const designFailures = new Map<string, SafeDesignFailure>();
1738
1918
  let pendingPrompt: EligiblePrompt | undefined;
1739
1919
  let activePrompt: EligiblePrompt | undefined;
1740
1920
  let designToolSnapshot: string[] | undefined;
@@ -1827,30 +2007,15 @@ export function registerWorkflowControl(
1827
2007
 
1828
2008
  // Pi validates tool arguments before execute(), so expose only the schema
1829
2009
  // legal for the active stage instead of one ambiguous command/packet union.
1830
- const CONTROL_COMMAND_PARAMETERS = {
2010
+ const PACKET_PARAMETERS = {
1831
2011
  type: "object",
1832
2012
  properties: {
1833
- command: {
1834
- type: "string",
1835
- enum: ["start", "status", "resume", "rebind", "cancel", "discard"],
1836
- },
1837
- stage: {
2013
+ action: {
1838
2014
  type: "string",
1839
- 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.",
1840
2018
  },
1841
- change: { type: "string" },
1842
- operationId: { type: "string" },
1843
- deliveryRevision: { type: "integer", minimum: 1 },
1844
- receiptHash: { type: "string" },
1845
- routeId: { type: "string" },
1846
- },
1847
- required: ["command", "stage", "change"],
1848
- additionalProperties: false,
1849
- } as const;
1850
- const PACKET_PARAMETERS = {
1851
- type: "object",
1852
- properties: {
1853
- action: { type: "string", enum: [...PACKET_ACTIONS, "design"] },
1854
2019
  request: {
1855
2020
  anyOf: [
1856
2021
  DESIGN_REQUEST_SCHEMA,
@@ -1902,6 +2067,9 @@ export function registerWorkflowControl(
1902
2067
  ) {
1903
2068
  throw new Error("control-envelope-ambiguous");
1904
2069
  }
2070
+ if (record.action === "finish" && Object.hasOwn(record, "request")) {
2071
+ throw new Error("control-envelope-ambiguous");
2072
+ }
1905
2073
  if (
1906
2074
  activePrompt !== "abel-design" &&
1907
2075
  activePrompt !== "abel-diagnose"
@@ -1914,22 +2082,40 @@ export function registerWorkflowControl(
1914
2082
  }
1915
2083
  const designRequest = validateDesignControlRequest(record.request);
1916
2084
  if (!designRequest.ok) throw new Error(designRequest.code);
1917
- 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
+ }
1918
2093
  if (!engine.executeDesign)
1919
2094
  throw new Error("design-control-unavailable");
1920
- const payload = await engine.executeDesign(designRequest.value);
1921
- if (
1922
- designRequest.value.operation === "finalize-delivery" &&
1923
- payload.state === "completed"
1924
- ) {
1925
- 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;
1926
2118
  }
1927
- return {
1928
- content: [
1929
- { type: "text" as const, text: JSON.stringify(payload) },
1930
- ],
1931
- details: payload,
1932
- };
1933
2119
  }
1934
2120
  let designEngine: WorkflowControlEngine | undefined;
1935
2121
  if (record.action === "run") {
@@ -1940,7 +2126,13 @@ export function registerWorkflowControl(
1940
2126
  );
1941
2127
  }
1942
2128
  if (packet.value.stage === "abel-design") {
1943
- 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
+ }
1944
2136
  if (!designEngine.assertDesignRun || !packet.value.runId) {
1945
2137
  throw new Error("design-control-unavailable");
1946
2138
  }
@@ -2028,7 +2220,17 @@ export function registerWorkflowControl(
2028
2220
  ) {
2029
2221
  throw new Error("stage-control-mismatch");
2030
2222
  }
2031
- 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
+ }
2032
2234
  const tuiControl = ctx.mode === "tui";
2033
2235
  if (tuiControl) {
2034
2236
  try {
@@ -2113,6 +2315,39 @@ export function registerWorkflowControl(
2113
2315
  };
2114
2316
  registerDispatchTool("packet");
2115
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
+ });
2116
2351
  pi.on("input", (event) => {
2117
2352
  pendingPrompt = invokedPrompt(event.text);
2118
2353
  return { action: "continue" };
@@ -2166,6 +2401,7 @@ export function registerWorkflowControl(
2166
2401
  pi.on("session_start", async (_event, ctx) => {
2167
2402
  pendingPrompt = undefined;
2168
2403
  activePrompt = undefined;
2404
+ designFailures.clear();
2169
2405
  activity.detach();
2170
2406
  await packetRuntime.drain();
2171
2407
  await closeEngines();
@@ -2181,6 +2417,7 @@ export function registerWorkflowControl(
2181
2417
  });
2182
2418
  pi.on("session_shutdown", async () => {
2183
2419
  activePrompt = undefined;
2420
+ designFailures.clear();
2184
2421
  activity.detach();
2185
2422
  await packetRuntime.drain();
2186
2423
  await closeEngines();