@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/README.md +18 -1
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/change-verification.ts +758 -65
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +19 -3
- package/src/durable-workflow.ts +47 -38
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/operator-cli.mjs +12 -7
- package/src/package-verification.ts +17 -1
- package/src/package-workflow.ts +3 -0
- package/src/phase-execution.ts +7 -1
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +17 -5
- package/src/verification-environment.ts +20 -2
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
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();
|
package/src/operator-cli.mjs
CHANGED
|
@@ -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: {
|
|
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
|
-
|
|
1227
|
-
|
|
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)
|
|
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
|
}
|
package/src/package-workflow.ts
CHANGED
|
@@ -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,
|
package/src/phase-execution.ts
CHANGED
|
@@ -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(
|
|
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;
|