@abelxiaoxing/cadence 1.0.13 → 1.1.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.
Files changed (46) hide show
  1. package/README.md +29 -13
  2. package/agents/contract-reviewer.md +2 -2
  3. package/agents/design-explorer.md +1 -1
  4. package/agents/diagnosis-worker.md +6 -3
  5. package/agents/implementation-worker.md +14 -9
  6. package/config/.env.example +5 -17
  7. package/config/routes.example.json +42 -0
  8. package/package.json +3 -3
  9. package/prompts/abel-design.md +150 -228
  10. package/prompts/abel-diagnose.md +37 -18
  11. package/prompts/abel-implement.md +111 -178
  12. package/prompts/abel-init.md +36 -33
  13. package/skills/abel-workflow/SKILL.md +156 -168
  14. package/src/apply-transaction.ts +1427 -0
  15. package/src/artifact-store.ts +204 -0
  16. package/src/child-session.ts +136 -17
  17. package/src/contracts.ts +105 -696
  18. package/src/control-contracts.ts +259 -0
  19. package/src/delivery-compiler.ts +1088 -0
  20. package/src/file-snapshot.ts +5 -0
  21. package/src/implement-graph.ts +11 -4
  22. package/src/index.ts +1465 -133
  23. package/src/isolation-backend.ts +269 -0
  24. package/src/packet-runtime.ts +533 -0
  25. package/src/parent-provider.ts +23 -4
  26. package/src/route-policy.ts +483 -0
  27. package/src/run-state.ts +393 -0
  28. package/src/run-store.ts +918 -0
  29. package/src/safe-path.ts +11 -0
  30. package/src/scoped-tools.ts +36 -0
  31. package/src/state-root.ts +215 -0
  32. package/src/subagent-activity.ts +526 -27
  33. package/src/submit-tool.ts +274 -0
  34. package/src/task-ledger.ts +1380 -0
  35. package/src/verification-capability.ts +529 -42
  36. package/src/worker-broker.ts +703 -0
  37. package/src/workflow-engine.ts +7391 -0
  38. package/src/workspace-store.ts +544 -0
  39. package/src/candidate-preflight.ts +0 -1618
  40. package/src/drain.ts +0 -20
  41. package/src/patch.ts +0 -505
  42. package/src/result-store.ts +0 -312
  43. package/src/runtime.ts +0 -2436
  44. package/src/scheduler.ts +0 -384
  45. package/src/subagent-endpoint.ts +0 -158
  46. package/src/worker.ts +0 -204
@@ -0,0 +1,393 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { ControlStage } from "./control-contracts.ts";
3
+
4
+ export const RUN_STATES = [
5
+ "created",
6
+ "validating-delivery",
7
+ "ready",
8
+ "queued",
9
+ "connecting",
10
+ "running",
11
+ "validating",
12
+ "verifying",
13
+ "retryable",
14
+ "paused",
15
+ "approval-needed",
16
+ "change-verifying",
17
+ "ready-to-apply",
18
+ "applying",
19
+ "recovering",
20
+ "completed",
21
+ "discarded",
22
+ "rejected",
23
+ ] as const;
24
+
25
+ export const TASK_STATES = [
26
+ "pending",
27
+ "queued",
28
+ "phase-ready",
29
+ "phase-running",
30
+ "validating",
31
+ "phase-verified",
32
+ "repairable",
33
+ "retryable",
34
+ "paused",
35
+ "approval-needed",
36
+ "verified",
37
+ ] as const;
38
+
39
+ export type RunState = (typeof RUN_STATES)[number];
40
+ export type TaskState = (typeof TASK_STATES)[number];
41
+ export type DeliveryGate = "gate-a" | "gate-b";
42
+
43
+ export const ZERO_EVENT_HASH = "0".repeat(64);
44
+ const SHA256 = /^[a-f0-9]{64}$/u;
45
+
46
+ export interface DeliveryBindingProjection {
47
+ gate: DeliveryGate;
48
+ revision: number;
49
+ receiptHash: string;
50
+ }
51
+
52
+ export interface RunProjection {
53
+ runId: string;
54
+ rootHash: string;
55
+ stage: ControlStage;
56
+ change?: string;
57
+ provisionalKey?: string;
58
+ state: RunState;
59
+ sequence: number;
60
+ eventHash: string;
61
+ deliveryRevision?: number;
62
+ deliveryBindings: DeliveryBindingProjection[];
63
+ pauseCode?: string;
64
+ terminal?: "completed" | "discarded" | "rejected";
65
+ }
66
+
67
+ export type RunEventType =
68
+ | "run-created"
69
+ | "change-bound"
70
+ | "delivery-bound"
71
+ | "state-transitioned"
72
+ | "operation-interrupted";
73
+
74
+ export interface RunEvent {
75
+ sequence: number;
76
+ type: RunEventType;
77
+ payload: Record<string, unknown>;
78
+ priorHash: string;
79
+ hash: string;
80
+ }
81
+
82
+ const TERMINAL_STATES = new Set<RunState>([
83
+ "completed",
84
+ "discarded",
85
+ "rejected",
86
+ ]);
87
+
88
+ const LEGAL_TRANSITIONS: Readonly<Record<RunState, readonly RunState[]>> = {
89
+ created: ["validating-delivery", "paused", "discarded", "rejected"],
90
+ "validating-delivery": [
91
+ "ready",
92
+ "paused",
93
+ "approval-needed",
94
+ "discarded",
95
+ "rejected",
96
+ ],
97
+ ready: ["queued", "running", "paused", "discarded", "rejected"],
98
+ queued: ["connecting", "running", "paused", "discarded"],
99
+ connecting: ["running", "retryable", "paused", "discarded"],
100
+ running: [
101
+ "validating",
102
+ "verifying",
103
+ "queued",
104
+ "retryable",
105
+ "paused",
106
+ "approval-needed",
107
+ "change-verifying",
108
+ "discarded",
109
+ ],
110
+ validating: [
111
+ "running",
112
+ "verifying",
113
+ "retryable",
114
+ "paused",
115
+ "approval-needed",
116
+ "discarded",
117
+ ],
118
+ verifying: [
119
+ "running",
120
+ "retryable",
121
+ "paused",
122
+ "approval-needed",
123
+ "change-verifying",
124
+ "discarded",
125
+ ],
126
+ retryable: ["queued", "connecting", "running", "paused", "discarded"],
127
+ paused: [
128
+ "paused",
129
+ "validating-delivery",
130
+ "ready",
131
+ "queued",
132
+ "connecting",
133
+ "running",
134
+ "approval-needed",
135
+ "recovering",
136
+ "discarded",
137
+ "rejected",
138
+ ],
139
+ "approval-needed": ["validating-delivery", "paused", "discarded", "rejected"],
140
+ "change-verifying": [
141
+ "running",
142
+ "retryable",
143
+ "paused",
144
+ "approval-needed",
145
+ "ready-to-apply",
146
+ "discarded",
147
+ ],
148
+ "ready-to-apply": ["applying", "paused", "discarded"],
149
+ applying: ["recovering", "completed"],
150
+ recovering: ["applying", "paused", "completed", "discarded", "rejected"],
151
+ completed: [],
152
+ discarded: [],
153
+ rejected: [],
154
+ };
155
+
156
+ export function canonicalJson(value: unknown): string {
157
+ if (Array.isArray(value)) {
158
+ return `[${value.map(canonicalJson).join(",")}]`;
159
+ }
160
+ if (value && typeof value === "object") {
161
+ const entries = Object.entries(value as Record<string, unknown>)
162
+ .filter(([, entry]) => entry !== undefined)
163
+ .sort(([left], [right]) => left.localeCompare(right));
164
+ return `{${entries
165
+ .map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
166
+ .join(",")}}`;
167
+ }
168
+ return JSON.stringify(value) ?? "null";
169
+ }
170
+
171
+ export function hashRunEvent(event: Omit<RunEvent, "hash">): string {
172
+ return createHash("sha256").update(canonicalJson(event)).digest("hex");
173
+ }
174
+
175
+ export function createRunEvent(
176
+ sequence: number,
177
+ type: RunEventType,
178
+ payload: Record<string, unknown>,
179
+ priorHash: string,
180
+ ): RunEvent {
181
+ if (
182
+ !Number.isSafeInteger(sequence) ||
183
+ sequence < 1 ||
184
+ !SHA256.test(priorHash)
185
+ ) {
186
+ throw new Error("event-integrity-invalid-header");
187
+ }
188
+ const unsigned = {
189
+ sequence,
190
+ type,
191
+ payload: structuredClone(payload),
192
+ priorHash,
193
+ };
194
+ return { ...unsigned, hash: hashRunEvent(unsigned) };
195
+ }
196
+
197
+ export function canTransition(from: RunState, to: RunState): boolean {
198
+ return LEGAL_TRANSITIONS[from].includes(to);
199
+ }
200
+
201
+ export function assertRunTransition(from: RunState, to: RunState): void {
202
+ if (!canTransition(from, to)) {
203
+ throw new Error(`invalid-run-transition:${from}:${to}`);
204
+ }
205
+ }
206
+
207
+ function requireString(payload: Record<string, unknown>, key: string): string {
208
+ const value = payload[key];
209
+ if (typeof value !== "string" || value.length === 0) {
210
+ throw new Error(`event-integrity-invalid-${key}`);
211
+ }
212
+ return value;
213
+ }
214
+
215
+ function applyVerifiedEvent(
216
+ projection: RunProjection | undefined,
217
+ event: RunEvent,
218
+ ): RunProjection {
219
+ switch (event.type) {
220
+ case "run-created": {
221
+ if (projection || event.sequence !== 1) {
222
+ throw new Error("event-integrity-duplicate-create");
223
+ }
224
+ const stage = event.payload.stage;
225
+ if (stage !== "abel-design" && stage !== "abel-implement") {
226
+ throw new Error("event-integrity-invalid-stage");
227
+ }
228
+ const change = event.payload.change;
229
+ const provisionalKey = event.payload.provisionalKey;
230
+ if (
231
+ (typeof change !== "string" || change.length === 0) ===
232
+ (typeof provisionalKey !== "string" || provisionalKey.length === 0)
233
+ ) {
234
+ throw new Error("event-integrity-invalid-run-key");
235
+ }
236
+ return {
237
+ runId: requireString(event.payload, "runId"),
238
+ rootHash: requireString(event.payload, "rootHash"),
239
+ stage,
240
+ ...(typeof change === "string" ? { change } : {}),
241
+ ...(typeof provisionalKey === "string" ? { provisionalKey } : {}),
242
+ state: "created",
243
+ sequence: event.sequence,
244
+ eventHash: event.hash,
245
+ deliveryBindings: [],
246
+ };
247
+ }
248
+ case "change-bound": {
249
+ if (projection?.stage !== "abel-design") {
250
+ throw new Error("event-integrity-change-binding");
251
+ }
252
+ const { provisionalKey: _provisionalKey, ...boundProjection } =
253
+ projection;
254
+ return {
255
+ ...boundProjection,
256
+ change: requireString(event.payload, "change"),
257
+ sequence: event.sequence,
258
+ eventHash: event.hash,
259
+ };
260
+ }
261
+ case "delivery-bound": {
262
+ if (!projection) throw new Error("event-integrity-missing-run");
263
+ const gate = event.payload.gate;
264
+ const revision = event.payload.revision;
265
+ const receiptHash = event.payload.receiptHash;
266
+ if (
267
+ (gate !== "gate-a" && gate !== "gate-b") ||
268
+ !Number.isSafeInteger(revision) ||
269
+ (revision as number) < 1 ||
270
+ typeof receiptHash !== "string" ||
271
+ !SHA256.test(receiptHash)
272
+ ) {
273
+ throw new Error("event-integrity-delivery-binding");
274
+ }
275
+ const binding: DeliveryBindingProjection = {
276
+ gate: gate as DeliveryGate,
277
+ revision: revision as number,
278
+ receiptHash,
279
+ };
280
+ const existing = projection.deliveryBindings.find(
281
+ (candidate) =>
282
+ candidate.gate === binding.gate &&
283
+ candidate.revision === binding.revision,
284
+ );
285
+ if (existing && existing.receiptHash !== binding.receiptHash) {
286
+ throw new Error("event-integrity-delivery-conflict");
287
+ }
288
+ const deliveryBindings = existing
289
+ ? projection.deliveryBindings
290
+ : [...projection.deliveryBindings, binding].sort(
291
+ (left, right) =>
292
+ left.revision - right.revision ||
293
+ left.gate.localeCompare(right.gate),
294
+ );
295
+ return {
296
+ ...projection,
297
+ deliveryBindings,
298
+ deliveryRevision: Math.max(
299
+ projection.deliveryRevision ?? 0,
300
+ binding.revision,
301
+ ),
302
+ sequence: event.sequence,
303
+ eventHash: event.hash,
304
+ };
305
+ }
306
+ case "state-transitioned": {
307
+ if (!projection) throw new Error("event-integrity-missing-run");
308
+ const from = event.payload.from;
309
+ const to = event.payload.to;
310
+ if (
311
+ from !== projection.state ||
312
+ !(RUN_STATES as readonly unknown[]).includes(to)
313
+ ) {
314
+ throw new Error("event-integrity-state-mismatch");
315
+ }
316
+ assertRunTransition(projection.state, to as RunState);
317
+ const nextState = to as RunState;
318
+ const {
319
+ pauseCode: _pauseCode,
320
+ terminal: _terminal,
321
+ ...transitionedProjection
322
+ } = projection;
323
+ return {
324
+ ...transitionedProjection,
325
+ state: nextState,
326
+ sequence: event.sequence,
327
+ eventHash: event.hash,
328
+ ...(typeof event.payload.code === "string"
329
+ ? { pauseCode: event.payload.code }
330
+ : {}),
331
+ ...(TERMINAL_STATES.has(nextState)
332
+ ? { terminal: nextState as "completed" | "discarded" | "rejected" }
333
+ : {}),
334
+ };
335
+ }
336
+ case "operation-interrupted": {
337
+ if (!projection || TERMINAL_STATES.has(projection.state)) {
338
+ throw new Error("event-integrity-interruption");
339
+ }
340
+ const nextState =
341
+ projection.state === "applying" ? "recovering" : "paused";
342
+ if (nextState !== projection.state) {
343
+ assertRunTransition(projection.state, nextState);
344
+ }
345
+ return {
346
+ ...projection,
347
+ state: nextState,
348
+ pauseCode: "operation-interrupted",
349
+ sequence: event.sequence,
350
+ eventHash: event.hash,
351
+ };
352
+ }
353
+ }
354
+ }
355
+
356
+ export function reduceRunEvents(events: readonly RunEvent[]): RunProjection {
357
+ if (events.length === 0) throw new Error("event-integrity-empty");
358
+ let projection: RunProjection | undefined;
359
+ let priorHash = ZERO_EVENT_HASH;
360
+ for (let index = 0; index < events.length; index += 1) {
361
+ const event = events[index];
362
+ if (
363
+ event.sequence !== index + 1 ||
364
+ event.priorHash !== priorHash ||
365
+ !SHA256.test(event.hash) ||
366
+ hashRunEvent({
367
+ sequence: event.sequence,
368
+ type: event.type,
369
+ payload: event.payload,
370
+ priorHash: event.priorHash,
371
+ }) !== event.hash
372
+ ) {
373
+ throw new Error("event-integrity-hash-chain");
374
+ }
375
+ projection = applyVerifiedEvent(projection, event);
376
+ priorHash = event.hash;
377
+ }
378
+ if (!projection) throw new Error("event-integrity-empty");
379
+ return projection;
380
+ }
381
+
382
+ export function legalControlCommands(state: RunState): string[] {
383
+ if (TERMINAL_STATES.has(state)) return ["status"];
384
+ if (
385
+ state === "paused" ||
386
+ state === "retryable" ||
387
+ state === "approval-needed"
388
+ ) {
389
+ return ["status", "resume", "rebind", "discard"];
390
+ }
391
+ if (state === "recovering") return ["status", "resume", "discard"];
392
+ return ["status", "cancel", "discard"];
393
+ }