@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.
- package/README.md +29 -13
- package/agents/contract-reviewer.md +2 -2
- package/agents/design-explorer.md +1 -1
- package/agents/diagnosis-worker.md +6 -3
- package/agents/implementation-worker.md +14 -9
- package/config/.env.example +5 -17
- package/config/routes.example.json +42 -0
- package/package.json +3 -3
- package/prompts/abel-design.md +150 -228
- package/prompts/abel-diagnose.md +37 -18
- package/prompts/abel-implement.md +111 -178
- package/prompts/abel-init.md +36 -33
- package/skills/abel-workflow/SKILL.md +156 -168
- package/src/apply-transaction.ts +1427 -0
- package/src/artifact-store.ts +204 -0
- package/src/child-session.ts +136 -17
- package/src/contracts.ts +105 -696
- package/src/control-contracts.ts +259 -0
- package/src/delivery-compiler.ts +1088 -0
- package/src/file-snapshot.ts +5 -0
- package/src/implement-graph.ts +11 -4
- package/src/index.ts +1465 -133
- package/src/isolation-backend.ts +269 -0
- package/src/packet-runtime.ts +533 -0
- package/src/parent-provider.ts +23 -4
- package/src/route-policy.ts +483 -0
- package/src/run-state.ts +393 -0
- package/src/run-store.ts +918 -0
- package/src/safe-path.ts +11 -0
- package/src/scoped-tools.ts +36 -0
- package/src/state-root.ts +215 -0
- package/src/subagent-activity.ts +526 -27
- package/src/submit-tool.ts +274 -0
- package/src/task-ledger.ts +1380 -0
- package/src/verification-capability.ts +529 -42
- package/src/worker-broker.ts +703 -0
- package/src/workflow-engine.ts +7391 -0
- package/src/workspace-store.ts +544 -0
- package/src/candidate-preflight.ts +0 -1618
- package/src/drain.ts +0 -20
- package/src/patch.ts +0 -505
- package/src/result-store.ts +0 -312
- package/src/runtime.ts +0 -2436
- package/src/scheduler.ts +0 -384
- package/src/subagent-endpoint.ts +0 -158
- package/src/worker.ts +0 -204
package/src/submit-tool.ts
CHANGED
|
@@ -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 {
|