@abelxiaoxing/cadence 1.6.0 → 1.7.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/src/index.ts CHANGED
@@ -48,6 +48,11 @@ import {
48
48
  DesignFinalizationError,
49
49
  validateDesignControlRequest,
50
50
  } from "./design-control.ts";
51
+ import {
52
+ ImplementContinuationDriver,
53
+ implementChangeFromInvocation,
54
+ implementPromptBindsChange,
55
+ } from "./implement-continuation.ts";
51
56
  import { canonicalJson } from "./implement-graph.ts";
52
57
  import { PACKET_ACTIONS, PacketRuntime } from "./packet-runtime.ts";
53
58
 
@@ -707,7 +712,9 @@ export function registerWorkflowControl(
707
712
  const engines = new Map<string, Promise<WorkflowControlEngine>>();
708
713
  const designOperations = new Set<Promise<void>>();
709
714
  const designFailures = new Map<string, SafeDesignFailure>();
715
+ const implementContinuation = new ImplementContinuationDriver();
710
716
  let pendingPrompt: EligiblePrompt | undefined;
717
+ let pendingImplementChange: string | undefined;
711
718
  let pendingInit = false;
712
719
  let exitingStage = false;
713
720
  let activePrompt: EligiblePrompt | undefined;
@@ -797,6 +804,7 @@ export function registerWorkflowControl(
797
804
  };
798
805
  const deactivateStage = async () => {
799
806
  activePrompt = undefined;
807
+ implementContinuation.deactivate();
800
808
  await packetRuntime.drain();
801
809
  deactivate();
802
810
  };
@@ -1349,6 +1357,13 @@ export function registerWorkflowControl(
1349
1357
  registerDispatchTool("packet");
1350
1358
 
1351
1359
  pi.on("tool_result", (event) => {
1360
+ implementContinuation.noteToolResult({
1361
+ toolName: event.toolName,
1362
+ input: event.input,
1363
+ content: event.content,
1364
+ details: event.details,
1365
+ isError: event.isError,
1366
+ });
1352
1367
  if (
1353
1368
  event.toolName !== DISPATCH_TOOL ||
1354
1369
  !event.isError ||
@@ -1385,7 +1400,9 @@ export function registerWorkflowControl(
1385
1400
  };
1386
1401
  });
1387
1402
  pi.on("input", (event) => {
1403
+ implementContinuation.noteInput();
1388
1404
  pendingPrompt = undefined;
1405
+ pendingImplementChange = undefined;
1389
1406
  pendingInit = false;
1390
1407
  const prompt = invokedPrompt(event.text);
1391
1408
  const init = /^\/abel-init(?:\s|$)/u.test(event.text);
@@ -1395,6 +1412,10 @@ export function registerWorkflowControl(
1395
1412
  return { action: prompt || init ? "handled" : "continue" };
1396
1413
  }
1397
1414
  pendingPrompt = prompt;
1415
+ pendingImplementChange =
1416
+ prompt === "abel-implement"
1417
+ ? implementChangeFromInvocation(event.text)
1418
+ : undefined;
1398
1419
  pendingInit = init && hasPackageProvenance(pi, "abel-init");
1399
1420
  if (
1400
1421
  activePrompt &&
@@ -1408,11 +1429,13 @@ export function registerWorkflowControl(
1408
1429
  }
1409
1430
  return { action: "continue" };
1410
1431
  });
1411
- pi.on("before_agent_start", (event) => {
1432
+ pi.on("before_agent_start", (event, ctx) => {
1412
1433
  const prompt = pendingPrompt;
1434
+ const implementChange = pendingImplementChange;
1413
1435
  const init =
1414
1436
  pendingInit && hasExpandedPromptMarker(event.prompt, "abel-init");
1415
1437
  pendingPrompt = undefined;
1438
+ pendingImplementChange = undefined;
1416
1439
  pendingInit = false;
1417
1440
  const verified =
1418
1441
  prompt && isVerifiedStageInvocation(pi, activation, prompt, event.prompt);
@@ -1422,10 +1445,30 @@ export function registerWorkflowControl(
1422
1445
  }
1423
1446
  registerDispatchTool(prompt === "abel-implement" ? "command" : "packet");
1424
1447
  activePrompt = prompt;
1448
+ if (prompt === "abel-implement") {
1449
+ if (
1450
+ implementChange &&
1451
+ implementPromptBindsChange(event.prompt, implementChange) &&
1452
+ typeof ctx.cwd === "string"
1453
+ )
1454
+ implementContinuation.activate({
1455
+ cwd: ctx.cwd,
1456
+ change: implementChange,
1457
+ });
1458
+ else implementContinuation.deactivate();
1459
+ } else {
1460
+ implementContinuation.deactivate();
1461
+ }
1425
1462
  }
1426
1463
  if (prompt) activateDispatcher(pi, activation, prompt, event.prompt);
1427
1464
  if (activation.isActive() && activePrompt === "abel-design")
1428
1465
  enforceDesignTools();
1466
+ if (
1467
+ activation.isActive() &&
1468
+ activePrompt === "abel-implement" &&
1469
+ typeof ctx.cwd === "string"
1470
+ )
1471
+ implementContinuation.beginParentTurn(ctx.cwd);
1429
1472
  const boundary = activePrompt
1430
1473
  ? `Abel stage ${activePrompt} is active only for the invoked task and its direct follow-ups. If the user ends it or requests an unrelated task, first call abel_dispatch with {"action":"finish"}, then handle that task normally with the restored tools. A successful finish ends stage authority immediately, including within this turn. Do not extend Gates or workflow rules to that task. A direct Gate answer or same-task continuation stays in this stage. Never invoke another Abel stage automatically.`
1431
1474
  : init
@@ -1445,10 +1488,113 @@ export function registerWorkflowControl(
1445
1488
  }
1446
1489
  return { ...event.payload, parallel_tool_calls: true };
1447
1490
  });
1491
+ pi.on("agent_end", async (event, ctx) => {
1492
+ // The host may replace its current signal after abort/settlement. Retain
1493
+ // this run's signal so a late status result cannot revive an aborted turn.
1494
+ const runSignal = ctx.signal;
1495
+ const aborted = () => runSignal?.aborted || ctx.signal?.aborted;
1496
+ if (
1497
+ aborted() ||
1498
+ exitingStage ||
1499
+ !activation.isActive() ||
1500
+ activePrompt !== "abel-implement"
1501
+ ) {
1502
+ return;
1503
+ }
1504
+ if (
1505
+ typeof ctx.hasPendingMessages === "function" &&
1506
+ ctx.hasPendingMessages()
1507
+ ) {
1508
+ return;
1509
+ }
1510
+ const probe = implementContinuation.prepareSettlement({
1511
+ cwd: ctx.cwd,
1512
+ messages: event.messages,
1513
+ });
1514
+ if (!probe) return;
1515
+
1516
+ let status: Record<string, unknown>;
1517
+ try {
1518
+ const engine = await engineFor(ctx);
1519
+ if (
1520
+ aborted() ||
1521
+ exitingStage ||
1522
+ !activation.isActive() ||
1523
+ activePrompt !== "abel-implement"
1524
+ ) {
1525
+ return;
1526
+ }
1527
+ status = await engine.execute(
1528
+ {
1529
+ command: "status",
1530
+ stage: "abel-implement",
1531
+ change: probe.change,
1532
+ },
1533
+ ctx,
1534
+ runSignal,
1535
+ );
1536
+ if (
1537
+ aborted() ||
1538
+ (typeof ctx.hasPendingMessages === "function" &&
1539
+ ctx.hasPendingMessages())
1540
+ ) {
1541
+ return;
1542
+ }
1543
+ } catch {
1544
+ // A host reminder cannot replace a trustworthy local status read.
1545
+ return;
1546
+ }
1547
+
1548
+ const decision = implementContinuation.finishSettlement(probe, status);
1549
+ if (decision?.kind === "terminal") {
1550
+ await deactivateStage();
1551
+ return;
1552
+ }
1553
+ if (!decision) return;
1554
+ if (
1555
+ aborted() ||
1556
+ (typeof ctx.hasPendingMessages === "function" && ctx.hasPendingMessages())
1557
+ ) {
1558
+ return;
1559
+ }
1560
+
1561
+ if (decision.kind === "stalled") {
1562
+ pi.sendMessage(
1563
+ {
1564
+ customType: "abel-implement-continuation-stalled",
1565
+ content: [{ type: "text", text: decision.message }],
1566
+ display: true,
1567
+ details: {
1568
+ code: decision.code,
1569
+ change: decision.change,
1570
+ state: decision.state,
1571
+ statusFingerprint: decision.statusFingerprint,
1572
+ },
1573
+ },
1574
+ { triggerTurn: false },
1575
+ );
1576
+ return;
1577
+ }
1578
+
1579
+ pi.sendMessage(
1580
+ {
1581
+ customType: "abel-implement-continuation",
1582
+ content: [{ type: "text", text: decision.message }],
1583
+ display: false,
1584
+ details: {
1585
+ change: decision.change,
1586
+ statusFingerprint: decision.statusFingerprint,
1587
+ },
1588
+ },
1589
+ { triggerTurn: true, deliverAs: "followUp" },
1590
+ );
1591
+ });
1448
1592
  pi.on("session_start", async (_event, ctx) => {
1449
1593
  pendingPrompt = undefined;
1594
+ pendingImplementChange = undefined;
1450
1595
  pendingInit = false;
1451
1596
  activePrompt = undefined;
1597
+ implementContinuation.deactivate();
1452
1598
  designFailures.clear();
1453
1599
  activity.detach();
1454
1600
  await packetRuntime.drain();
@@ -1457,7 +1603,11 @@ export function registerWorkflowControl(
1457
1603
  if (ctx.mode === "tui") activity.attach(ctx.ui);
1458
1604
  });
1459
1605
  pi.on("session_shutdown", async () => {
1606
+ pendingPrompt = undefined;
1607
+ pendingImplementChange = undefined;
1608
+ pendingInit = false;
1460
1609
  activePrompt = undefined;
1610
+ implementContinuation.deactivate();
1461
1611
  designFailures.clear();
1462
1612
  activity.detach();
1463
1613
  await packetRuntime.drain();
@@ -652,9 +652,6 @@ function observeSafePath(root, relative) {
652
652
  }
653
653
  return { kind: "unsafe", reason: "invalid-path" };
654
654
  }
655
- function isSafeRegularFile(root, relative) {
656
- return observeSafePath(root, relative).kind === "file";
657
- }
658
655
 
659
656
  // src/file-snapshot.ts
660
657
  function sha256(bytes) {
@@ -697,10 +694,16 @@ function capabilitySuccess(verificationId, runnerBindings = []) {
697
694
  }))
698
695
  };
699
696
  }
700
- function adapterFailure(verificationId, code, message) {
697
+ function adapterFailure(verificationId, code, message, inputObservation) {
701
698
  return {
702
699
  ok: false,
703
- diagnostic: { kind: "verification-adapter", code, verificationId, message }
700
+ diagnostic: {
701
+ kind: "verification-adapter",
702
+ code,
703
+ verificationId,
704
+ message,
705
+ ...inputObservation ? { inputObservation } : {}
706
+ }
704
707
  };
705
708
  }
706
709
  function fileStatus(root, relative) {
@@ -1223,8 +1226,10 @@ function validateCapability(root, value, options, requireInputAvailability) {
1223
1226
  function validateAcceptedVerificationCapability(root, verification, dependencyOwner, runnerEnvironment, requireInputAvailability, executionWritePaths) {
1224
1227
  if (requireInputAvailability) {
1225
1228
  for (const input of verificationInputPaths(verification)) {
1226
- if (!isSafeRegularFile(root, input)) {
1227
- return adapterFailure(verification.id, "input-missing", `verification input ${input} is missing or unsafe`);
1229
+ const status = fileStatus(root, input);
1230
+ if (status !== "regular") {
1231
+ const inputObservation = { path: input, kind: status };
1232
+ return adapterFailure(verification.id, status === "absent" ? "input-missing" : "input-unsafe", `verification input ${input} is ${status}`, inputObservation);
1228
1233
  }
1229
1234
  }
1230
1235
  }
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import {
5
5
  type AtomicVerificationContract,
6
6
  type StructuredVerificationContract,
7
+ type VerificationInputObservation,
7
8
  type VerificationObservation,
8
9
  verificationSteps,
9
10
  } from "./contracts.ts";
@@ -321,11 +322,13 @@ export async function executePackageVerification(input: {
321
322
  const unavailable = (
322
323
  code: string,
323
324
  category: "environment" | "adapter" | "resource" = "adapter",
325
+ inputObservation?: VerificationInputObservation,
324
326
  ): VerificationObservation => ({
325
327
  kind: "unavailable",
326
328
  category,
327
329
  code,
328
330
  verificationId: input.verification.id,
331
+ ...(inputObservation ? { inputObservation } : {}),
329
332
  ...(executionDiagnostic
330
333
  ? {
331
334
  diagnostic: {
@@ -349,7 +352,14 @@ export async function executePackageVerification(input: {
349
352
  executionWritePaths: input.executionWritePaths,
350
353
  },
351
354
  );
352
- if (!capability.ok) return unavailable(capability.diagnostic.code);
355
+ if (!capability.ok)
356
+ return unavailable(
357
+ capability.diagnostic.code,
358
+ "adapter",
359
+ capability.diagnostic.kind === "verification-adapter"
360
+ ? capability.diagnostic.inputObservation
361
+ : undefined,
362
+ );
353
363
  let environment: Awaited<ReturnType<typeof prepareVerificationEnvironment>>;
354
364
  try {
355
365
  environment = await prepareVerificationEnvironment(
@@ -552,6 +562,9 @@ export function phaseVerificationResult(result: VerificationObservation) {
552
562
  kind: "paused" as const,
553
563
  code: result.code,
554
564
  category: result.category,
565
+ ...(result.inputObservation
566
+ ? { inputObservation: result.inputObservation }
567
+ : {}),
555
568
  };
556
569
  }
557
570
 
@@ -583,5 +596,8 @@ export function changeVerificationResult(result: VerificationObservation) {
583
596
  ? ("verification-adapter" as const)
584
597
  : ("environment" as const),
585
598
  code: result.code,
599
+ ...(result.inputObservation
600
+ ? { inputObservation: result.inputObservation }
601
+ : {}),
586
602
  };
587
603
  }
@@ -171,6 +171,9 @@ function openPreparedPackageWorkflowService(
171
171
  (phase) => phase.verification,
172
172
  ),
173
173
  task.affectedVerification,
174
+ ...(task.baselineVerification
175
+ ? [task.baselineVerification]
176
+ : []),
174
177
  task.repairVerification,
175
178
  ]),
176
179
  plan.verification.baseline.fullSuite,
@@ -43,6 +43,7 @@ import {
43
43
  LOCKFILES,
44
44
  SHA256,
45
45
  semanticRouteFailure,
46
+ taskExecutionContract,
46
47
  type WorkflowAttemptOutcome,
47
48
  type WorkflowRouteFacts,
48
49
  type WorkflowWorker,
@@ -60,6 +61,7 @@ export interface PhaseExecutionServices {
60
61
  resources: PhaseResources;
61
62
  ledger: TaskLedger;
62
63
  signal: AbortSignal;
64
+ taskId?: string;
63
65
  }): Promise<DurableBaselineResult>;
64
66
  verifyTaskAffected(input: {
65
67
  resources: PhaseResources;
@@ -197,7 +199,10 @@ export class PhaseExecution {
197
199
  runId: input.runId,
198
200
  deliveryRevision: input.deliveryRevision,
199
201
  taskId: input.taskId,
200
- boundaryHash: hash("durable-task-boundary", JSON.stringify(input.task)),
202
+ boundaryHash: hash(
203
+ "durable-task-boundary",
204
+ JSON.stringify(taskExecutionContract(input.task)),
205
+ ),
201
206
  objective: input.task.objective,
202
207
  contextRefs,
203
208
  initialPhase:
@@ -1562,6 +1567,7 @@ export class PhaseExecution {
1562
1567
  resources,
1563
1568
  ledger,
1564
1569
  signal: input.signal,
1570
+ taskId: input.taskId,
1565
1571
  });
1566
1572
  if (!captured.ok) {
1567
1573
  const outcome = captured.outcome;