@abelxiaoxing/cadence 1.4.0 → 1.5.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 (66) hide show
  1. package/README.md +130 -8
  2. package/agents/design-explorer.md +17 -11
  3. package/agents/diagnosis-worker.md +9 -1
  4. package/agents/implementation-worker.md +3 -1
  5. package/config/plan-draft.example.json +122 -0
  6. package/config/plan-draft.multiple-tasks.example.json +345 -0
  7. package/config/plan-draft.quick.example.json +78 -0
  8. package/package.json +10 -4
  9. package/prompts/abel-design.md +101 -44
  10. package/prompts/abel-implement.md +9 -0
  11. package/src/activity-contracts.ts +82 -0
  12. package/src/candidate-artifact.ts +143 -0
  13. package/src/candidate-patch.ts +31 -11
  14. package/src/candidate-workspace.ts +74 -0
  15. package/src/change-contract-schema.ts +191 -0
  16. package/src/change-contract.ts +5 -0
  17. package/src/change-verification.ts +920 -0
  18. package/src/child-budget.ts +58 -0
  19. package/src/child-model.ts +142 -0
  20. package/src/child-session.ts +289 -326
  21. package/src/contracts.ts +200 -76
  22. package/src/delivery-compiler.ts +105 -120
  23. package/src/delivery-revision.ts +42 -0
  24. package/src/design-control.ts +113 -4
  25. package/src/design-diagnostics.ts +150 -0
  26. package/src/durable-contracts.ts +213 -0
  27. package/src/durable-workflow.ts +148 -3570
  28. package/src/evidence-draft.ts +122 -0
  29. package/src/execution-profile.ts +38 -0
  30. package/src/implement-graph.ts +3 -0
  31. package/src/implement-plan.ts +70 -0
  32. package/src/index.ts +59 -522
  33. package/src/isolation-backend.ts +62 -40
  34. package/src/model-source.ts +22 -0
  35. package/src/operator-cli-entry.mjs +24 -0
  36. package/src/operator-cli.mjs +1421 -0
  37. package/src/operator-tools.ts +249 -0
  38. package/src/package-candidate.ts +227 -0
  39. package/src/package-verification.ts +86 -8
  40. package/src/package-workflow.ts +291 -0
  41. package/src/packet-runtime.ts +27 -48
  42. package/src/parent-provider.ts +86 -198
  43. package/src/phase-execution.ts +2248 -0
  44. package/src/pi-adapter.ts +19 -0
  45. package/src/plan-draft-summary.ts +149 -0
  46. package/src/plan-draft.ts +493 -0
  47. package/src/single-task-draft.ts +142 -0
  48. package/src/subagent-activity.ts +32 -66
  49. package/src/submit-tool.ts +20 -59
  50. package/src/transport-budget.ts +52 -0
  51. package/src/verification-capability.ts +5 -3
  52. package/src/verification-diagnostics.ts +76 -0
  53. package/src/verification-environment-io.ts +280 -0
  54. package/src/verification-environment.ts +56 -154
  55. package/src/verification-scan.ts +64 -0
  56. package/src/worker-broker.ts +39 -57
  57. package/src/workflow-policy.ts +14 -53
  58. package/src/workflow-recovery-policy.ts +95 -0
  59. package/src/workflow-scheduling.ts +105 -0
  60. package/src/workflow-state-machine.ts +105 -497
  61. package/src/workflow-status.ts +384 -0
  62. package/src/workspace-io-entry.mjs +19 -1
  63. package/src/workspace-io-worker.mjs +275 -50
  64. package/src/workspace-io.ts +6 -1
  65. package/src/empty-resource-loader.ts +0 -38
  66. package/src/parent-payload-bridge.ts +0 -523
@@ -0,0 +1,384 @@
1
+ import {
2
+ canonicalJson,
3
+ legalControlCommands,
4
+ type RunProjection,
5
+ } from "./run-state.ts";
6
+ import {
7
+ approvalRequirement,
8
+ type EngineRunRow,
9
+ type EngineTaskRow,
10
+ hash,
11
+ normalizeWorkflowContextRequest,
12
+ normalizeWorkflowVerificationStatus,
13
+ parseDeliveryDiagnostics,
14
+ permitsPlanAmendment,
15
+ type SafeAttemptDiagnostic,
16
+ } from "./workflow-policy.ts";
17
+
18
+ export interface WorkflowResourceBudget {
19
+ used: number;
20
+ maximum: number;
21
+ phaseHighWater: number;
22
+ hardLimit: number;
23
+ }
24
+
25
+ export interface WorkflowStatusFacts {
26
+ projection: RunProjection;
27
+ rows: readonly EngineTaskRow[];
28
+ engineRun?: EngineRunRow;
29
+ attemptDiagnostics: ReadonlyMap<string, SafeAttemptDiagnostic>;
30
+ failureSequences: ReadonlyMap<string, number>;
31
+ amendmentUsed: number;
32
+ resourceBudget?: WorkflowResourceBudget;
33
+ }
34
+
35
+ export function firstPausedTask(
36
+ rows: readonly EngineTaskRow[],
37
+ ): EngineTaskRow | undefined {
38
+ return (
39
+ rows.find((row) => row.state === "approval-needed") ??
40
+ rows.find((row) => ["paused", "retryable"].includes(row.state))
41
+ );
42
+ }
43
+
44
+ /** Project already-read facts; no storage access, callbacks or workflow transitions. */
45
+ export function projectWorkflowStatus({
46
+ projection,
47
+ rows,
48
+ engineRun,
49
+ attemptDiagnostics,
50
+ failureSequences,
51
+ amendmentUsed,
52
+ resourceBudget,
53
+ }: WorkflowStatusFacts): Record<string, unknown> {
54
+ const runId = projection.runId;
55
+ const queued = rows
56
+ .filter((row) => row.state === "queued")
57
+ .sort(
58
+ (left, right) =>
59
+ (left.queue_position ?? Number.MAX_SAFE_INTEGER) -
60
+ (right.queue_position ?? Number.MAX_SAFE_INTEGER) ||
61
+ left.task_order - right.task_order,
62
+ );
63
+ const firstPaused = firstPausedTask(rows);
64
+ const contextRequest = firstPaused?.context_request_json
65
+ ? normalizeWorkflowContextRequest(
66
+ JSON.parse(firstPaused.context_request_json) as unknown,
67
+ )
68
+ : undefined;
69
+ const pauseCode =
70
+ projection.state === "approval-needed" &&
71
+ firstPaused?.state === "approval-needed"
72
+ ? (firstPaused.pause_code ?? projection.pauseCode ?? "task-paused")
73
+ : (projection.pauseCode ?? firstPaused?.pause_code ?? "task-paused");
74
+ const approval =
75
+ projection.state === "approval-needed"
76
+ ? approvalRequirement(pauseCode, contextRequest)
77
+ : undefined;
78
+ const attemptDiagnostic = firstPaused
79
+ ? attemptDiagnostics.get(firstPaused.task_id)
80
+ : undefined;
81
+ const recovery =
82
+ rows
83
+ .map((row) => attemptDiagnostics.get(row.task_id)?.recovery)
84
+ .find((fact) => fact && fact.failures >= fact.feedback.maxAttempts) ??
85
+ attemptDiagnostic?.recovery;
86
+ const recoveryExhausted =
87
+ recovery && recovery.failures >= recovery.feedback.maxAttempts;
88
+ const blockers = rows
89
+ .filter((row) =>
90
+ ["approval-needed", "paused", "retryable"].includes(row.state),
91
+ )
92
+ .map((row) => {
93
+ const context = row.context_request_json
94
+ ? normalizeWorkflowContextRequest(JSON.parse(row.context_request_json))
95
+ : undefined;
96
+ const code = row.pause_code ?? "task-paused";
97
+ const requirement =
98
+ row.state === "approval-needed"
99
+ ? approvalRequirement(code, context)
100
+ : undefined;
101
+ return {
102
+ id: hash(runId, row.task_id, code, canonicalJson(context ?? {})),
103
+ taskId: row.task_id,
104
+ phase: row.phase,
105
+ code,
106
+ kind: requirement ? "decision" : "execution",
107
+ ...(requirement ?? {}),
108
+ ...(context ? { contextRequest: context } : {}),
109
+ };
110
+ });
111
+ if (
112
+ projection.state === "approval-needed" &&
113
+ !blockers.some((item) => item.kind === "decision") &&
114
+ approval
115
+ ) {
116
+ blockers.push({
117
+ id: hash(runId, pauseCode),
118
+ taskId: "change",
119
+ phase: "green",
120
+ code: pauseCode,
121
+ kind: "decision",
122
+ ...approval,
123
+ });
124
+ }
125
+ const decisions = blockers.filter((item) => item.kind === "decision");
126
+ const amendments = blockers.filter(
127
+ (item) => item.kind === "decision" || permitsPlanAmendment(item.code),
128
+ );
129
+ if (
130
+ projection.state === "paused" &&
131
+ permitsPlanAmendment(pauseCode) &&
132
+ !amendments.some((item) => item.code === pauseCode)
133
+ ) {
134
+ const item = {
135
+ id: hash(runId, pauseCode, engineRun?.delivery_diagnostics_json ?? ""),
136
+ taskId: "change",
137
+ phase: "red" as const,
138
+ code: pauseCode,
139
+ kind: "execution",
140
+ };
141
+ blockers.push(item);
142
+ amendments.push(item);
143
+ }
144
+ const amendmentBudget = {
145
+ used: amendmentUsed,
146
+ maximum: 64,
147
+ remaining: 64 - amendmentUsed,
148
+ exhausted: amendmentUsed >= 64,
149
+ };
150
+ const decisionBatch =
151
+ amendments.length &&
152
+ pauseCode !== "operation-cancelled" &&
153
+ ["approval-needed", "paused"].includes(projection.state)
154
+ ? {
155
+ id: hash(
156
+ runId,
157
+ String(projection.deliveryRevision ?? 0),
158
+ canonicalJson(amendments),
159
+ ),
160
+ continuation: {
161
+ action: "amend",
162
+ change: projection.change,
163
+ batchId: hash(
164
+ runId,
165
+ String(projection.deliveryRevision ?? 0),
166
+ canonicalJson(amendments),
167
+ ),
168
+ },
169
+ resolution: {
170
+ owner: "parent",
171
+ strategy: "recommended",
172
+ requiresUserInput: false,
173
+ },
174
+ items: amendments,
175
+ requiredGates: [
176
+ ...new Set(
177
+ amendments.flatMap((item) => item.requiredGates ?? ["gate-b"]),
178
+ ),
179
+ ].sort(),
180
+ }
181
+ : undefined;
182
+ const budgetCode = rows.find(
183
+ (row) =>
184
+ row.pause_code === "change-work-budget-exhausted" ||
185
+ row.pause_code === "change-recovery-budget-exhausted",
186
+ )?.pause_code;
187
+ const budgetExhausted = budgetCode !== undefined;
188
+ const currentRevision = projection.deliveryRevision ?? 0;
189
+ return {
190
+ runId: projection.runId,
191
+ stage: projection.stage,
192
+ ...(projection.change ? { change: projection.change } : {}),
193
+ state: projection.state,
194
+ ...(projection.deliveryRevision === undefined
195
+ ? {}
196
+ : { deliveryRevision: projection.deliveryRevision }),
197
+ ...(projection.deliveryBindings.length > 0
198
+ ? { deliveryBindings: structuredClone(projection.deliveryBindings) }
199
+ : {}),
200
+ completed: projection.state === "completed",
201
+ blockers,
202
+ ...(resourceBudget
203
+ ? {
204
+ resourceBudget: {
205
+ ...resourceBudget,
206
+ remaining: resourceBudget.maximum - resourceBudget.used,
207
+ },
208
+ }
209
+ : {}),
210
+ ...(budgetExhausted
211
+ ? {
212
+ recovery: {
213
+ automatic: false,
214
+ exhausted: true,
215
+ code: budgetCode,
216
+ },
217
+ }
218
+ : {}),
219
+ ...(decisionBatch
220
+ ? {
221
+ decisionBatch,
222
+ amendmentBudget,
223
+ ...(!amendmentBudget.exhausted &&
224
+ !budgetExhausted &&
225
+ (!resourceBudget || resourceBudget.used < resourceBudget.maximum)
226
+ ? {
227
+ continuation: {
228
+ owner: "parent",
229
+ automatic: true,
230
+ ...decisionBatch.continuation,
231
+ },
232
+ }
233
+ : {}),
234
+ }
235
+ : {}),
236
+ ...(recoveryExhausted
237
+ ? {
238
+ recovery: {
239
+ automatic: false,
240
+ exhausted: true,
241
+ code: recovery.feedback.code,
242
+ attempts: recovery.failures,
243
+ maxAttempts: recovery.feedback.maxAttempts,
244
+ automaticRetryExhausted: true,
245
+ ...(!budgetExhausted &&
246
+ resourceBudget &&
247
+ resourceBudget.used < resourceBudget.maximum
248
+ ? {
249
+ additionalAttempt: {
250
+ incidentKey: recovery.key,
251
+ failureSequence: failureSequences.get(recovery.key) ?? 0,
252
+ reason: "parent-directed-retry",
253
+ },
254
+ }
255
+ : {}),
256
+ },
257
+ }
258
+ : {}),
259
+ ...(projection.terminal ? { terminal: projection.terminal } : {}),
260
+ ...(projection.pauseCode || firstPaused?.pause_code
261
+ ? {
262
+ pause: {
263
+ code: pauseCode,
264
+ ...(attemptDiagnostic ? { diagnostic: attemptDiagnostic } : {}),
265
+ },
266
+ }
267
+ : {}),
268
+ legalCommands:
269
+ projection.state === "approval-needed"
270
+ ? ["status", "discard"]
271
+ : legalControlCommands(projection.state).filter(
272
+ (command) =>
273
+ (command !== "rebind" || firstPaused !== undefined) &&
274
+ (command !== "resume" ||
275
+ (!recoveryExhausted && !budgetExhausted)),
276
+ ),
277
+ ...(approval && projection.change
278
+ ? {
279
+ approval: {
280
+ category: approval.category,
281
+ requiredGates:
282
+ decisionBatch?.requiredGates ?? approval.requiredGates,
283
+ categories: [
284
+ ...new Set(
285
+ decisions.flatMap((item) =>
286
+ item.category ? [item.category] : [],
287
+ ),
288
+ ),
289
+ ],
290
+ refs: [...new Set(decisions.flatMap((item) => item.refs ?? []))],
291
+ continuation: decisionBatch?.continuation,
292
+ retainedRun: {
293
+ runId: projection.runId,
294
+ deliveryRevision: currentRevision,
295
+ },
296
+ receiptPrecondition: {
297
+ deliveryRevision: { greaterThan: currentRevision },
298
+ receiptHash: "matching-ready-receipt",
299
+ },
300
+ },
301
+ conditionalCommands: [
302
+ {
303
+ command: "resume",
304
+ stage: "abel-implement",
305
+ change: projection.change,
306
+ requires: {
307
+ deliveryRevision: { greaterThan: currentRevision },
308
+ receiptHash: "matching-ready-receipt",
309
+ },
310
+ },
311
+ ],
312
+ }
313
+ : {}),
314
+ tasks: rows.map((row) => ({
315
+ taskId: row.task_id,
316
+ state: row.state,
317
+ ...(row.state === "verified" ? {} : { phase: row.phase }),
318
+ ...(row.context_request_json
319
+ ? {
320
+ contextRequest: normalizeWorkflowContextRequest(
321
+ JSON.parse(row.context_request_json) as unknown,
322
+ ),
323
+ }
324
+ : {}),
325
+ })),
326
+ queue: queued.map((row, index) => ({
327
+ taskId: row.task_id,
328
+ position: index + 1,
329
+ reason: row.pause_code === "worker-capacity" ? "capacity" : "conflict",
330
+ })),
331
+ ...(engineRun?.verification_json
332
+ ? {
333
+ verification: normalizeWorkflowVerificationStatus(
334
+ JSON.parse(engineRun.verification_json) as unknown,
335
+ ),
336
+ }
337
+ : {}),
338
+ ...(engineRun?.delivery_diagnostics_json
339
+ ? {
340
+ delivery: {
341
+ code: "delivery-invalid",
342
+ diagnostics: parseDeliveryDiagnostics(
343
+ engineRun.delivery_diagnostics_json,
344
+ ),
345
+ },
346
+ }
347
+ : {}),
348
+ ...(engineRun?.route_id
349
+ ? {
350
+ routeBinding: {
351
+ routeId: engineRun.route_id,
352
+ ...(engineRun.route_fingerprint
353
+ ? { routeFingerprint: engineRun.route_fingerprint }
354
+ : {}),
355
+ },
356
+ }
357
+ : {}),
358
+ ...(engineRun?.cleanup_state === "retained"
359
+ ? {
360
+ privateData: {
361
+ retained: true,
362
+ cleanup: "retained",
363
+ ...(engineRun.baseline_workspace_revision
364
+ ? {
365
+ baselineRevisionId: engineRun.baseline_workspace_revision,
366
+ }
367
+ : {}),
368
+ ...(engineRun.current_workspace_revision
369
+ ? {
370
+ currentRevisionId: engineRun.current_workspace_revision,
371
+ }
372
+ : {}),
373
+ },
374
+ }
375
+ : engineRun?.cleanup_state === "complete"
376
+ ? {
377
+ privateData: {
378
+ retained: false,
379
+ cleanup: "complete",
380
+ },
381
+ }
382
+ : {}),
383
+ };
384
+ }
@@ -1,5 +1,7 @@
1
+ import { rmSync } from "node:fs";
1
2
  import { parentPort, workerData } from "node:worker_threads";
2
3
  import { ArtifactStore } from "./artifact-store.ts";
4
+ import { prepareVerificationEnvironmentIo } from "./verification-environment-io.ts";
3
5
  import { verificationEnvironmentDigest } from "./verification-identity.ts";
4
6
  import { WorkspaceStore } from "./workspace-store.ts";
5
7
 
@@ -10,7 +12,23 @@ const checkCancelled = () => {
10
12
  if (Atomics.load(flag, 0)) throw new Error("cancelled");
11
13
  };
12
14
  try {
13
- if (workerData.operation === "verificationIdentity") {
15
+ if (workerData.operation === "prepareVerification") {
16
+ const [root, owner, bindings, profile, environment, scratch] =
17
+ workerData.args;
18
+ const result = prepareVerificationEnvironmentIo(
19
+ root,
20
+ owner,
21
+ bindings,
22
+ profile,
23
+ environment,
24
+ checkCancelled,
25
+ scratch,
26
+ );
27
+ parentPort.postMessage({ ok: true, result, files: 0, bytes: 0 });
28
+ } else if (workerData.operation === "cleanupVerification") {
29
+ rmSync(workerData.args[0], { recursive: true, force: true });
30
+ parentPort.postMessage({ ok: true, files: 0, bytes: 0 });
31
+ } else if (workerData.operation === "verificationIdentity") {
14
32
  const result = verificationEnvironmentDigest(
15
33
  workerData.args[0],
16
34
  checkCancelled,