@abelxiaoxing/cadence 1.1.0 → 1.2.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 +19 -3
- package/agents/implementation-worker.md +17 -11
- package/config/routes.example.json +0 -2
- package/package.json +2 -2
- package/prompts/abel-design.md +77 -17
- package/prompts/abel-diagnose.md +2 -0
- package/prompts/abel-implement.md +23 -11
- package/skills/abel-workflow/SKILL.md +42 -5
- package/src/agent-registry.ts +0 -1
- package/src/candidate-patch.ts +250 -0
- package/src/child-session.ts +27 -24
- package/src/contracts.ts +19 -10
- package/src/control-contracts.ts +15 -62
- package/src/delivery-compiler.ts +47 -17
- package/src/design-control.ts +1053 -0
- package/src/design-journal.ts +1616 -0
- package/src/index.ts +626 -196
- package/src/packet-runtime.ts +6 -6
- package/src/route-policy.ts +45 -17
- package/src/run-state.ts +43 -4
- package/src/run-store.ts +103 -15
- package/src/subagent-activity.ts +1 -1
- package/src/submit-tool.ts +149 -49
- package/src/verification-capability.ts +43 -15
- package/src/worker-broker.ts +0 -1
- package/src/workflow-engine.ts +416 -250
- package/agents/contract-reviewer.md +0 -11
package/src/submit-tool.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
+
import {
|
|
5
|
+
type CandidatePatchOperation,
|
|
6
|
+
compileCandidatePatch,
|
|
7
|
+
} from "./candidate-patch.ts";
|
|
4
8
|
import {
|
|
5
9
|
type CandidateFailure,
|
|
6
10
|
type DiffResult,
|
|
@@ -11,7 +15,11 @@ import {
|
|
|
11
15
|
validateDiffResult,
|
|
12
16
|
validateEvidenceResult,
|
|
13
17
|
} from "./contracts.ts";
|
|
14
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
type BeginCandidateInput,
|
|
20
|
+
TASK_LEDGER_LIMITS,
|
|
21
|
+
type TaskLedger,
|
|
22
|
+
} from "./task-ledger.ts";
|
|
15
23
|
|
|
16
24
|
const compactEvidenceSchema = Type.Object({
|
|
17
25
|
id: Type.String(),
|
|
@@ -83,17 +91,49 @@ function candidateArtifactSchema(candidateId: string) {
|
|
|
83
91
|
return Type.Union([
|
|
84
92
|
Type.Object(
|
|
85
93
|
{
|
|
86
|
-
kind: Type.Literal("candidate-
|
|
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"),
|
|
94
|
+
kind: Type.Literal("candidate-patch"),
|
|
96
95
|
candidateId: Type.Literal(candidateId),
|
|
96
|
+
operations: Type.Array(
|
|
97
|
+
Type.Union([
|
|
98
|
+
Type.Object(
|
|
99
|
+
{
|
|
100
|
+
kind: Type.Literal("replace"),
|
|
101
|
+
path: Type.String(),
|
|
102
|
+
oldText: Type.String({ minLength: 1 }),
|
|
103
|
+
newText: Type.String(),
|
|
104
|
+
},
|
|
105
|
+
{ additionalProperties: false },
|
|
106
|
+
),
|
|
107
|
+
Type.Object(
|
|
108
|
+
{
|
|
109
|
+
kind: Type.Literal("rewrite"),
|
|
110
|
+
path: Type.String(),
|
|
111
|
+
content: Type.String(),
|
|
112
|
+
},
|
|
113
|
+
{ additionalProperties: false },
|
|
114
|
+
),
|
|
115
|
+
Type.Object(
|
|
116
|
+
{
|
|
117
|
+
kind: Type.Literal("create"),
|
|
118
|
+
path: Type.String(),
|
|
119
|
+
content: Type.String({ minLength: 1 }),
|
|
120
|
+
mode: Type.Union([
|
|
121
|
+
Type.Literal("regular"),
|
|
122
|
+
Type.Literal("executable"),
|
|
123
|
+
]),
|
|
124
|
+
},
|
|
125
|
+
{ additionalProperties: false },
|
|
126
|
+
),
|
|
127
|
+
Type.Object(
|
|
128
|
+
{
|
|
129
|
+
kind: Type.Literal("delete"),
|
|
130
|
+
path: Type.String(),
|
|
131
|
+
},
|
|
132
|
+
{ additionalProperties: false },
|
|
133
|
+
),
|
|
134
|
+
]),
|
|
135
|
+
{ minItems: 1, maxItems: 128 },
|
|
136
|
+
),
|
|
97
137
|
},
|
|
98
138
|
{ additionalProperties: false },
|
|
99
139
|
),
|
|
@@ -188,6 +228,9 @@ export interface CandidateArtifactSubmission {
|
|
|
188
228
|
"beginCandidate" | "appendCandidateSegment" | "sealCandidate"
|
|
189
229
|
>;
|
|
190
230
|
identity: BeginCandidateInput;
|
|
231
|
+
workspaceRoot: string;
|
|
232
|
+
writePaths: readonly string[];
|
|
233
|
+
deletePaths: readonly string[];
|
|
191
234
|
}
|
|
192
235
|
|
|
193
236
|
export function createCandidateArtifactTool(
|
|
@@ -199,10 +242,7 @@ export function createCandidateArtifactTool(
|
|
|
199
242
|
let failure: CandidateFailure | undefined;
|
|
200
243
|
let attempts = 0;
|
|
201
244
|
let schema: SubmitSchemaState = "not-submitted";
|
|
202
|
-
let
|
|
203
|
-
let totalBytes = 0;
|
|
204
|
-
const candidateHash = createHash("sha256");
|
|
205
|
-
const identity: IdentityOutcome = {
|
|
245
|
+
let identity: IdentityOutcome = {
|
|
206
246
|
request: true,
|
|
207
247
|
role: true,
|
|
208
248
|
task: true,
|
|
@@ -212,7 +252,7 @@ export function createCandidateArtifactTool(
|
|
|
212
252
|
name: "abel_submit_result",
|
|
213
253
|
label: "Submit Abel Candidate Artifact",
|
|
214
254
|
description:
|
|
215
|
-
"Submit
|
|
255
|
+
"Submit one complete structured candidate patch or request bounded approved context. Diff generation, chunking, encoding, hashes, and atomic sealing are owned by this trusted tool.",
|
|
216
256
|
executionMode: "sequential",
|
|
217
257
|
parameters,
|
|
218
258
|
async execute(_toolCallId, params) {
|
|
@@ -220,6 +260,22 @@ export function createCandidateArtifactTool(
|
|
|
220
260
|
throw new Error("candidate terminal result already submitted");
|
|
221
261
|
}
|
|
222
262
|
attempts++;
|
|
263
|
+
if (attempts > 2) {
|
|
264
|
+
schema = "invalid";
|
|
265
|
+
failure = {
|
|
266
|
+
kind: "artifact",
|
|
267
|
+
code: "invalid-structural-result",
|
|
268
|
+
stage: "structural-submit",
|
|
269
|
+
};
|
|
270
|
+
throw new Error("structural submission correction limit exceeded");
|
|
271
|
+
}
|
|
272
|
+
identity = {
|
|
273
|
+
request: true,
|
|
274
|
+
role: true,
|
|
275
|
+
task: true,
|
|
276
|
+
phase: true,
|
|
277
|
+
};
|
|
278
|
+
failure = undefined;
|
|
223
279
|
const value = params as unknown as Record<string, unknown>;
|
|
224
280
|
if (value.candidateId !== input.identity.candidateId) {
|
|
225
281
|
identity.request = false;
|
|
@@ -232,18 +288,15 @@ export function createCandidateArtifactTool(
|
|
|
232
288
|
throw new Error("candidate identity mismatch");
|
|
233
289
|
}
|
|
234
290
|
schema = "valid";
|
|
235
|
-
if (value.kind === "candidate-
|
|
236
|
-
|
|
237
|
-
const segmentHash = createHash("sha256").update(bytes).digest("hex");
|
|
238
|
-
let accepted: ReturnType<
|
|
239
|
-
CandidateArtifactSubmission["ledger"]["appendCandidateSegment"]
|
|
240
|
-
>;
|
|
291
|
+
if (value.kind === "candidate-patch") {
|
|
292
|
+
let diff: string;
|
|
241
293
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
294
|
+
diff = compileCandidatePatch({
|
|
295
|
+
root: input.workspaceRoot,
|
|
296
|
+
writePaths: input.writePaths,
|
|
297
|
+
deletePaths: input.deletePaths,
|
|
298
|
+
operations: value.operations as CandidatePatchOperation[],
|
|
299
|
+
maxBytes: TASK_LEDGER_LIMITS.maxCandidateBytes,
|
|
247
300
|
});
|
|
248
301
|
} catch (error) {
|
|
249
302
|
schema = "invalid";
|
|
@@ -254,34 +307,59 @@ export function createCandidateArtifactTool(
|
|
|
254
307
|
};
|
|
255
308
|
throw error;
|
|
256
309
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
310
|
+
const bytes = Buffer.from(diff, "utf8");
|
|
311
|
+
const candidateHash = createHash("sha256").update(bytes).digest("hex");
|
|
312
|
+
let sequence = 0;
|
|
313
|
+
for (
|
|
314
|
+
let offset = 0;
|
|
315
|
+
offset < bytes.length;
|
|
316
|
+
offset += TASK_LEDGER_LIMITS.maxSegmentBytes
|
|
317
|
+
) {
|
|
318
|
+
const segment = bytes.subarray(
|
|
319
|
+
offset,
|
|
320
|
+
Math.min(offset + TASK_LEDGER_LIMITS.maxSegmentBytes, bytes.length),
|
|
321
|
+
);
|
|
322
|
+
let accepted: ReturnType<
|
|
323
|
+
CandidateArtifactSubmission["ledger"]["appendCandidateSegment"]
|
|
324
|
+
>;
|
|
325
|
+
try {
|
|
326
|
+
accepted = input.ledger.appendCandidateSegment({
|
|
327
|
+
...input.identity,
|
|
328
|
+
sequence,
|
|
329
|
+
bytes: segment,
|
|
330
|
+
segmentHash: createHash("sha256").update(segment).digest("hex"),
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
schema = "invalid";
|
|
334
|
+
failure = {
|
|
335
|
+
kind: "artifact",
|
|
336
|
+
code: "invalid-diff",
|
|
337
|
+
stage: "candidate-diff",
|
|
338
|
+
};
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
if (!accepted.ok) {
|
|
342
|
+
failure = {
|
|
343
|
+
kind: "result-limit",
|
|
344
|
+
limitBytes: accepted.limitBytes,
|
|
345
|
+
};
|
|
346
|
+
return {
|
|
347
|
+
content: [],
|
|
348
|
+
details: accepted,
|
|
349
|
+
terminate: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
sequence = accepted.nextSequence;
|
|
266
353
|
}
|
|
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
354
|
let sealed: ReturnType<
|
|
277
355
|
CandidateArtifactSubmission["ledger"]["sealCandidate"]
|
|
278
356
|
>;
|
|
279
357
|
try {
|
|
280
358
|
sealed = input.ledger.sealCandidate({
|
|
281
359
|
...input.identity,
|
|
282
|
-
segmentCount,
|
|
283
|
-
totalBytes,
|
|
284
|
-
candidateHash
|
|
360
|
+
segmentCount: sequence,
|
|
361
|
+
totalBytes: bytes.length,
|
|
362
|
+
candidateHash,
|
|
285
363
|
});
|
|
286
364
|
} catch (error) {
|
|
287
365
|
schema = "invalid";
|
|
@@ -318,6 +396,12 @@ export function createCandidateArtifactTool(
|
|
|
318
396
|
refs.some((relative) => !isValidRelativePath(relative)) ||
|
|
319
397
|
new Set(refs).size !== refs.length
|
|
320
398
|
) {
|
|
399
|
+
schema = "invalid";
|
|
400
|
+
failure = {
|
|
401
|
+
kind: "artifact",
|
|
402
|
+
code: "invalid-structural-result",
|
|
403
|
+
stage: "structural-submit",
|
|
404
|
+
};
|
|
321
405
|
throw new Error("context request refs are invalid");
|
|
322
406
|
}
|
|
323
407
|
result = {
|
|
@@ -377,7 +461,7 @@ export function createSubmitTool(input: {
|
|
|
377
461
|
let failure: CandidateFailure | undefined;
|
|
378
462
|
let attempts = 0;
|
|
379
463
|
let schema: SubmitSchemaState = "not-submitted";
|
|
380
|
-
|
|
464
|
+
let identity: IdentityOutcome = {
|
|
381
465
|
request: true,
|
|
382
466
|
role: true,
|
|
383
467
|
task: true,
|
|
@@ -398,6 +482,22 @@ export function createSubmitTool(input: {
|
|
|
398
482
|
: diffSchema,
|
|
399
483
|
async execute(_toolCallId, params) {
|
|
400
484
|
attempts++;
|
|
485
|
+
if (attempts > 2) {
|
|
486
|
+
schema = "invalid";
|
|
487
|
+
failure = {
|
|
488
|
+
kind: "artifact",
|
|
489
|
+
code: "invalid-structural-result",
|
|
490
|
+
stage: "structural-submit",
|
|
491
|
+
};
|
|
492
|
+
throw new Error("structural submission correction limit exceeded");
|
|
493
|
+
}
|
|
494
|
+
identity = {
|
|
495
|
+
request: true,
|
|
496
|
+
role: true,
|
|
497
|
+
task: true,
|
|
498
|
+
phase: true,
|
|
499
|
+
};
|
|
500
|
+
failure = undefined;
|
|
401
501
|
const value = params as unknown as Record<string, unknown>;
|
|
402
502
|
if (value === null || typeof value !== "object") {
|
|
403
503
|
identity.request = false;
|
|
@@ -166,17 +166,39 @@ function localExecutable(
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function pathApi(
|
|
170
|
+
platform: NodeJS.Platform,
|
|
171
|
+
...values: readonly string[]
|
|
172
|
+
): typeof path.posix {
|
|
173
|
+
if (platform !== "win32") return path.posix;
|
|
174
|
+
if (
|
|
175
|
+
process.platform === "win32" ||
|
|
176
|
+
values.some(
|
|
177
|
+
(value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"),
|
|
178
|
+
)
|
|
179
|
+
) {
|
|
180
|
+
return path.win32;
|
|
181
|
+
}
|
|
182
|
+
// Tests may model Windows executable semantics over a POSIX fixture tree.
|
|
183
|
+
return path.posix;
|
|
184
|
+
}
|
|
185
|
+
|
|
169
186
|
function pathWithin(
|
|
170
187
|
root: string,
|
|
171
188
|
candidate: string,
|
|
172
189
|
platform: NodeJS.Platform,
|
|
173
190
|
): boolean {
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
191
|
+
const platformPath = pathApi(platform, root, candidate);
|
|
192
|
+
const normalizedRoot = platformPath.resolve(root);
|
|
193
|
+
const relative = platformPath.relative(
|
|
194
|
+
normalizedRoot,
|
|
195
|
+
platformPath.resolve(candidate),
|
|
196
|
+
);
|
|
177
197
|
return (
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
relative === "" ||
|
|
199
|
+
(relative !== ".." &&
|
|
200
|
+
!relative.startsWith(`..${platformPath.sep}`) &&
|
|
201
|
+
!platformPath.isAbsolute(relative))
|
|
180
202
|
);
|
|
181
203
|
}
|
|
182
204
|
|
|
@@ -261,13 +283,24 @@ function within(root: string, candidate: string): boolean {
|
|
|
261
283
|
return candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
262
284
|
}
|
|
263
285
|
|
|
264
|
-
function commonDirectory(
|
|
286
|
+
export function commonDirectory(
|
|
265
287
|
left: string,
|
|
266
288
|
right: string,
|
|
267
289
|
platform: NodeJS.Platform = process.platform,
|
|
268
290
|
): string {
|
|
269
|
-
const
|
|
270
|
-
const
|
|
291
|
+
const platformPath = pathApi(platform, left, right);
|
|
292
|
+
const leftResolved = platformPath.resolve(left);
|
|
293
|
+
const rightResolved = platformPath.resolve(right);
|
|
294
|
+
const leftRoot = platformPath.parse(leftResolved).root;
|
|
295
|
+
const rightRoot = platformPath.parse(rightResolved).root;
|
|
296
|
+
const comparable = (value: string) =>
|
|
297
|
+
platform === "win32" ? value.toLowerCase() : value;
|
|
298
|
+
if (comparable(leftRoot) !== comparable(rightRoot)) return leftRoot;
|
|
299
|
+
|
|
300
|
+
const parts = (value: string, root: string) =>
|
|
301
|
+
platformPath.relative(root, value).split(platformPath.sep).filter(Boolean);
|
|
302
|
+
const leftParts = parts(leftResolved, leftRoot);
|
|
303
|
+
const rightParts = parts(rightResolved, rightRoot);
|
|
271
304
|
const common: string[] = [];
|
|
272
305
|
for (
|
|
273
306
|
let index = 0;
|
|
@@ -276,15 +309,10 @@ function commonDirectory(
|
|
|
276
309
|
) {
|
|
277
310
|
const leftPart = leftParts[index] as string;
|
|
278
311
|
const rightPart = rightParts[index] as string;
|
|
279
|
-
if (
|
|
280
|
-
(platform === "win32" ? leftPart.toLowerCase() : leftPart) !==
|
|
281
|
-
(platform === "win32" ? rightPart.toLowerCase() : rightPart)
|
|
282
|
-
) {
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
312
|
+
if (comparable(leftPart) !== comparable(rightPart)) break;
|
|
285
313
|
common.push(leftPart);
|
|
286
314
|
}
|
|
287
|
-
return
|
|
315
|
+
return platformPath.join(leftRoot, ...common);
|
|
288
316
|
}
|
|
289
317
|
|
|
290
318
|
function unsafeMountSource(source: string): boolean {
|
package/src/worker-broker.ts
CHANGED