@abelxiaoxing/cadence 1.4.0 → 1.4.1

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 (49) hide show
  1. package/README.md +58 -4
  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/package.json +5 -3
  8. package/prompts/abel-design.md +93 -44
  9. package/src/activity-contracts.ts +82 -0
  10. package/src/candidate-artifact.ts +143 -0
  11. package/src/candidate-patch.ts +31 -11
  12. package/src/candidate-workspace.ts +74 -0
  13. package/src/change-contract-schema.ts +191 -0
  14. package/src/change-contract.ts +5 -0
  15. package/src/change-verification.ts +896 -0
  16. package/src/child-model.ts +129 -0
  17. package/src/child-session.ts +293 -326
  18. package/src/contracts.ts +184 -76
  19. package/src/delivery-compiler.ts +103 -120
  20. package/src/delivery-revision.ts +42 -0
  21. package/src/design-control.ts +113 -4
  22. package/src/design-diagnostics.ts +150 -0
  23. package/src/durable-contracts.ts +210 -0
  24. package/src/durable-workflow.ts +148 -3570
  25. package/src/evidence-draft.ts +122 -0
  26. package/src/implement-graph.ts +3 -0
  27. package/src/implement-plan.ts +70 -0
  28. package/src/index.ts +59 -522
  29. package/src/model-source.ts +22 -0
  30. package/src/package-candidate.ts +224 -0
  31. package/src/package-workflow.ts +253 -0
  32. package/src/packet-runtime.ts +27 -48
  33. package/src/parent-provider.ts +86 -198
  34. package/src/phase-execution.ts +2248 -0
  35. package/src/pi-adapter.ts +19 -0
  36. package/src/plan-draft-summary.ts +146 -0
  37. package/src/plan-draft.ts +493 -0
  38. package/src/subagent-activity.ts +32 -66
  39. package/src/submit-tool.ts +20 -59
  40. package/src/transport-budget.ts +32 -0
  41. package/src/worker-broker.ts +37 -57
  42. package/src/workflow-policy.ts +14 -53
  43. package/src/workflow-recovery-policy.ts +95 -0
  44. package/src/workflow-scheduling.ts +105 -0
  45. package/src/workflow-state-machine.ts +105 -497
  46. package/src/workflow-status.ts +384 -0
  47. package/src/workspace-io-worker.mjs +7 -0
  48. package/src/empty-resource-loader.ts +0 -38
  49. 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
+ }
@@ -391,7 +391,14 @@ var LIMITS = {
391
391
  phaseTimeoutMs: 20 * 60 * 1000,
392
392
  maxCompleteResultBytes: 64 * 1024
393
393
  };
394
+ var VERIFICATION_LIMITS = Object.freeze({
395
+ minSteps: 1,
396
+ maxSteps: 8,
397
+ minTestFiles: 1,
398
+ maxTestFiles: 64
399
+ });
394
400
  var NONCANONICAL = /(^|\/)\.\.(\/|$)|(^|\/)\/|^\//;
401
+ var RELATIVE_PATH_PATTERN = String.raw`^(?:\.|(?!/)(?![a-zA-Z]:/)(?!\./)(?![\s\S]*\.\.)(?![\s\S]*[\\\u0000])(?![\s\S]*//)(?![\s\S]*/\.(?:/|$))(?![\s\S]*/$)[\s\S]+)$`;
395
402
  function isValidRelativePath(p) {
396
403
  return typeof p === "string" && p.length > 0 && p.length <= 512 && !NONCANONICAL.test(p) && !p.startsWith("/") && !/^[a-z]:\//iu.test(p) && (p === "." || !p.startsWith("./") && !p.endsWith("/") && !p.split("/").includes(".")) && !p.includes("..") && !p.includes("\\") && !p.includes("\x00");
397
404
  }
@@ -1,38 +0,0 @@
1
- import {
2
- createExtensionRuntime,
3
- type ResourceLoader,
4
- } from "@earendil-works/pi-coding-agent";
5
-
6
- /** ResourceLoader that discovers nothing and supplies only the approved prompt. */
7
- export class EmptyResourceLoader implements ResourceLoader {
8
- constructor(private readonly systemPrompt?: string) {}
9
- getExtensions() {
10
- return { extensions: [], errors: [], runtime: createExtensionRuntime() };
11
- }
12
- getSkills() {
13
- return { skills: [], diagnostics: [] };
14
- }
15
- getPrompts() {
16
- return { prompts: [], diagnostics: [] };
17
- }
18
- getThemes() {
19
- return { themes: [], diagnostics: [] };
20
- }
21
- getAgentsFiles() {
22
- return { agentsFiles: [] };
23
- }
24
- getSystemPrompt() {
25
- return this.systemPrompt;
26
- }
27
- getSystemPromptSource() {
28
- return undefined;
29
- }
30
- getAppendSystemPrompt() {
31
- return [];
32
- }
33
- getAppendSystemPromptSources() {
34
- return [];
35
- }
36
- extendResources() {}
37
- async reload() {}
38
- }