@abelxiaoxing/cadence 1.0.14 → 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
@@ -1,14 +1,17 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { defineTool } from "@earendil-works/pi-coding-agent";
2
3
  import { Type } from "typebox";
3
4
  import {
4
5
  type CandidateFailure,
5
6
  type DiffResult,
6
7
  type EvidenceResult,
8
+ isValidRelativePath,
7
9
  type SubmitFinalCategory,
8
10
  type SubmitSchemaState,
9
11
  validateDiffResult,
10
12
  validateEvidenceResult,
11
13
  } from "./contracts.ts";
14
+ import type { BeginCandidateInput, TaskLedger } from "./task-ledger.ts";
12
15
 
13
16
  const compactEvidenceSchema = Type.Object({
14
17
  id: Type.String(),
@@ -76,6 +79,277 @@ const diffSchema = Type.Object({
76
79
  contractCompliant: Type.Literal(true),
77
80
  });
78
81
 
82
+ function candidateArtifactSchema(candidateId: string) {
83
+ return Type.Union([
84
+ Type.Object(
85
+ {
86
+ kind: Type.Literal("candidate-segment"),
87
+ candidateId: Type.Literal(candidateId),
88
+ sequence: Type.Integer({ minimum: 0 }),
89
+ text: Type.String({ minLength: 1 }),
90
+ },
91
+ { additionalProperties: false },
92
+ ),
93
+ Type.Object(
94
+ {
95
+ kind: Type.Literal("candidate-seal"),
96
+ candidateId: Type.Literal(candidateId),
97
+ },
98
+ { additionalProperties: false },
99
+ ),
100
+ Type.Object(
101
+ {
102
+ kind: Type.Literal("context-request"),
103
+ candidateId: Type.Literal(candidateId),
104
+ code: Type.Union([
105
+ Type.Literal("approved-context-needed"),
106
+ Type.Literal("task-split-needed"),
107
+ Type.Literal("boundary-review-needed"),
108
+ ]),
109
+ refs: Type.Array(Type.String(), { maxItems: 32 }),
110
+ },
111
+ { additionalProperties: false },
112
+ ),
113
+ ]);
114
+ }
115
+
116
+ export type CandidateArtifactSubmissionResult =
117
+ | {
118
+ kind: "sealed-candidate";
119
+ candidateId: string;
120
+ state: "sealed";
121
+ artifactHash: string;
122
+ bytes: number;
123
+ paths: string[];
124
+ replayed?: true;
125
+ }
126
+ | {
127
+ kind: "context-request";
128
+ candidateId: string;
129
+ code:
130
+ | "approved-context-needed"
131
+ | "task-split-needed"
132
+ | "boundary-review-needed";
133
+ refs: string[];
134
+ };
135
+
136
+ export type CandidateContextRequest = Extract<
137
+ CandidateArtifactSubmissionResult,
138
+ { kind: "context-request" }
139
+ >;
140
+
141
+ export interface ClassifiedCandidateContextRequest {
142
+ kind: "paused" | "approval-needed";
143
+ code:
144
+ | "approved-context-needed"
145
+ | "task-split-needed"
146
+ | "boundary-review-needed";
147
+ contextRequest: {
148
+ code: CandidateContextRequest["code"];
149
+ refs: string[];
150
+ };
151
+ }
152
+
153
+ function contextRefWithinBoundary(relative: string, approved: string): boolean {
154
+ return (
155
+ approved === "." ||
156
+ relative === approved ||
157
+ relative.startsWith(`${approved}/`)
158
+ );
159
+ }
160
+
161
+ export function classifyCandidateContextRequest(
162
+ request: CandidateContextRequest,
163
+ approvedPaths: readonly string[],
164
+ ): ClassifiedCandidateContextRequest {
165
+ const refs = [...request.refs];
166
+ const inBoundary =
167
+ refs.length > 0 &&
168
+ refs.every((relative) =>
169
+ approvedPaths.some((approved) =>
170
+ contextRefWithinBoundary(relative, approved),
171
+ ),
172
+ );
173
+ const approvalNeeded =
174
+ !inBoundary || request.code === "boundary-review-needed";
175
+ return {
176
+ kind: approvalNeeded ? "approval-needed" : "paused",
177
+ code: inBoundary ? request.code : "boundary-review-needed",
178
+ contextRequest: {
179
+ code: request.code,
180
+ refs,
181
+ },
182
+ };
183
+ }
184
+
185
+ export interface CandidateArtifactSubmission {
186
+ ledger: Pick<
187
+ TaskLedger,
188
+ "beginCandidate" | "appendCandidateSegment" | "sealCandidate"
189
+ >;
190
+ identity: BeginCandidateInput;
191
+ }
192
+
193
+ export function createCandidateArtifactTool(
194
+ input: CandidateArtifactSubmission,
195
+ ) {
196
+ input.ledger.beginCandidate(input.identity);
197
+ const parameters = candidateArtifactSchema(input.identity.candidateId);
198
+ let result: CandidateArtifactSubmissionResult | undefined;
199
+ let failure: CandidateFailure | undefined;
200
+ let attempts = 0;
201
+ let schema: SubmitSchemaState = "not-submitted";
202
+ let segmentCount = 0;
203
+ let totalBytes = 0;
204
+ const candidateHash = createHash("sha256");
205
+ const identity: IdentityOutcome = {
206
+ request: true,
207
+ role: true,
208
+ task: true,
209
+ phase: true,
210
+ };
211
+ const tool = defineTool<typeof parameters, unknown>({
212
+ name: "abel_submit_result",
213
+ label: "Submit Abel Candidate Artifact",
214
+ description:
215
+ "Submit ordered raw-text candidate segments, atomically seal the complete candidate, or request bounded approved context. Encoding and hashes are computed by this trusted tool.",
216
+ executionMode: "sequential",
217
+ parameters,
218
+ async execute(_toolCallId, params) {
219
+ if (result !== undefined) {
220
+ throw new Error("candidate terminal result already submitted");
221
+ }
222
+ attempts++;
223
+ const value = params as unknown as Record<string, unknown>;
224
+ if (value.candidateId !== input.identity.candidateId) {
225
+ identity.request = false;
226
+ schema = "invalid";
227
+ failure = {
228
+ kind: "artifact",
229
+ code: "structural-identity-mismatch",
230
+ stage: "structural-submit",
231
+ };
232
+ throw new Error("candidate identity mismatch");
233
+ }
234
+ schema = "valid";
235
+ if (value.kind === "candidate-segment") {
236
+ const bytes = Buffer.from(value.text as string, "utf8");
237
+ const segmentHash = createHash("sha256").update(bytes).digest("hex");
238
+ let accepted: ReturnType<
239
+ CandidateArtifactSubmission["ledger"]["appendCandidateSegment"]
240
+ >;
241
+ try {
242
+ accepted = input.ledger.appendCandidateSegment({
243
+ ...input.identity,
244
+ sequence: value.sequence as number,
245
+ bytes,
246
+ segmentHash,
247
+ });
248
+ } catch (error) {
249
+ schema = "invalid";
250
+ failure = {
251
+ kind: "artifact",
252
+ code: "invalid-diff",
253
+ stage: "candidate-diff",
254
+ };
255
+ throw error;
256
+ }
257
+ if (!accepted.ok) {
258
+ failure = {
259
+ kind: "result-limit",
260
+ limitBytes: accepted.limitBytes,
261
+ };
262
+ } else {
263
+ candidateHash.update(bytes);
264
+ segmentCount = accepted.nextSequence;
265
+ totalBytes = accepted.totalBytes;
266
+ }
267
+ return {
268
+ content: [
269
+ { type: "text" as const, text: "Candidate segment accepted." },
270
+ ],
271
+ details: accepted,
272
+ ...(!accepted.ok ? { terminate: true } : {}),
273
+ };
274
+ }
275
+ if (value.kind === "candidate-seal") {
276
+ let sealed: ReturnType<
277
+ CandidateArtifactSubmission["ledger"]["sealCandidate"]
278
+ >;
279
+ try {
280
+ sealed = input.ledger.sealCandidate({
281
+ ...input.identity,
282
+ segmentCount,
283
+ totalBytes,
284
+ candidateHash: candidateHash.digest("hex"),
285
+ });
286
+ } catch (error) {
287
+ schema = "invalid";
288
+ const mismatch =
289
+ error instanceof Error &&
290
+ error.message === "candidate-write-set-mismatch";
291
+ failure = {
292
+ kind: "artifact",
293
+ code: mismatch ? "write-set-mismatch" : "invalid-diff",
294
+ stage: "candidate-diff",
295
+ };
296
+ throw error;
297
+ }
298
+ if (!sealed.ok) return { content: [], details: sealed };
299
+ result = {
300
+ kind: "sealed-candidate",
301
+ candidateId: input.identity.candidateId,
302
+ state: "sealed",
303
+ artifactHash: sealed.artifactHash,
304
+ bytes: sealed.bytes,
305
+ paths: [...sealed.paths],
306
+ ...(sealed.replayed ? { replayed: true } : {}),
307
+ };
308
+ return {
309
+ content: [
310
+ { type: "text" as const, text: "Candidate artifact sealed." },
311
+ ],
312
+ details: sealed,
313
+ terminate: true,
314
+ };
315
+ }
316
+ const refs = value.refs as string[];
317
+ if (
318
+ refs.some((relative) => !isValidRelativePath(relative)) ||
319
+ new Set(refs).size !== refs.length
320
+ ) {
321
+ throw new Error("context request refs are invalid");
322
+ }
323
+ result = {
324
+ kind: "context-request",
325
+ candidateId: input.identity.candidateId,
326
+ code: value.code as Extract<
327
+ CandidateArtifactSubmissionResult,
328
+ { kind: "context-request" }
329
+ >["code"],
330
+ refs: [...refs],
331
+ };
332
+ return {
333
+ content: [
334
+ { type: "text" as const, text: "Bounded context request accepted." },
335
+ ],
336
+ details: result,
337
+ terminate: true,
338
+ };
339
+ },
340
+ });
341
+ return {
342
+ tool,
343
+ getResult: () =>
344
+ result === undefined ? undefined : structuredClone(result),
345
+ getAttempts: () => attempts,
346
+ getSchema: () => schema,
347
+ getIdentity: () => ({ ...identity }),
348
+ getFailure: () =>
349
+ failure === undefined ? undefined : structuredClone(failure),
350
+ };
351
+ }
352
+
79
353
  export type FinalCategory = SubmitFinalCategory;
80
354
 
81
355
  export interface IdentityOutcome {