@abelxiaoxing/cadence 1.0.2 → 1.0.3
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/agents/contract-reviewer.md +2 -2
- package/agents/diagnosis-worker.md +3 -2
- package/agents/implementation-worker.md +3 -2
- package/package.json +1 -1
- package/prompts/abel-design.md +13 -3
- package/prompts/abel-implement.md +50 -21
- package/skills/abel-workflow/SKILL.md +57 -25
- package/src/candidate-preflight.ts +361 -56
- package/src/child-session.ts +92 -27
- package/src/contracts.ts +987 -38
- package/src/index.ts +50 -24
- package/src/parent-provider.ts +89 -23
- package/src/patch.ts +269 -41
- package/src/result-store.ts +254 -16
- package/src/runtime.ts +1281 -560
- package/src/scheduler.ts +59 -16
- package/src/subagent-activity.ts +56 -25
- package/src/submit-tool.ts +12 -2
- package/src/worker.ts +114 -195
package/src/child-session.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
SettingsManager,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { Type } from "typebox";
|
|
11
|
-
import type { DiffResult, EvidenceResult } from "./contracts.ts";
|
|
11
|
+
import type { ChildFailure, DiffResult, EvidenceResult } from "./contracts.ts";
|
|
12
12
|
import { EmptyResourceLoader } from "./empty-resource-loader.ts";
|
|
13
13
|
import { createScopedTools } from "./scoped-tools.ts";
|
|
14
14
|
import {
|
|
@@ -31,6 +31,22 @@ function abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const ABORT_SETTLE_GRACE_MS = 250;
|
|
35
|
+
|
|
36
|
+
async function settleAbort(promise: Promise<void>): Promise<void> {
|
|
37
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
38
|
+
try {
|
|
39
|
+
await Promise.race([
|
|
40
|
+
promise.catch(() => undefined),
|
|
41
|
+
new Promise<void>((resolve) => {
|
|
42
|
+
timer = setTimeout(resolve, ABORT_SETTLE_GRACE_MS);
|
|
43
|
+
}),
|
|
44
|
+
]);
|
|
45
|
+
} finally {
|
|
46
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
34
50
|
const ZERO_USAGE: Usage = {
|
|
35
51
|
input: 0,
|
|
36
52
|
output: 0,
|
|
@@ -46,11 +62,21 @@ export class UsageAggregator {
|
|
|
46
62
|
add(id: string, value: Usage): boolean {
|
|
47
63
|
if (this.ids.has(id)) return false;
|
|
48
64
|
this.ids.add(id);
|
|
65
|
+
const cacheWrite1h =
|
|
66
|
+
this.usage.cacheWrite1h === undefined && value.cacheWrite1h === undefined
|
|
67
|
+
? undefined
|
|
68
|
+
: (this.usage.cacheWrite1h ?? 0) + (value.cacheWrite1h ?? 0);
|
|
69
|
+
const reasoning =
|
|
70
|
+
this.usage.reasoning === undefined && value.reasoning === undefined
|
|
71
|
+
? undefined
|
|
72
|
+
: (this.usage.reasoning ?? 0) + (value.reasoning ?? 0);
|
|
49
73
|
this.usage = {
|
|
50
74
|
input: this.usage.input + value.input,
|
|
51
75
|
output: this.usage.output + value.output,
|
|
52
76
|
cacheRead: this.usage.cacheRead + value.cacheRead,
|
|
53
77
|
cacheWrite: this.usage.cacheWrite + value.cacheWrite,
|
|
78
|
+
...(cacheWrite1h === undefined ? {} : { cacheWrite1h }),
|
|
79
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
54
80
|
totalTokens: this.usage.totalTokens + value.totalTokens,
|
|
55
81
|
cost: {
|
|
56
82
|
input: this.usage.cost.input + value.cost.input,
|
|
@@ -62,6 +88,9 @@ export class UsageAggregator {
|
|
|
62
88
|
};
|
|
63
89
|
return true;
|
|
64
90
|
}
|
|
91
|
+
hasUsage(): boolean {
|
|
92
|
+
return this.ids.size > 0;
|
|
93
|
+
}
|
|
65
94
|
total(): Usage {
|
|
66
95
|
return structuredClone(this.usage);
|
|
67
96
|
}
|
|
@@ -108,21 +137,7 @@ function wrapScopedTools(
|
|
|
108
137
|
|
|
109
138
|
export type ChildFailureKind = "failed" | "cancelled" | "timed-out";
|
|
110
139
|
|
|
111
|
-
export
|
|
112
|
-
cwd: string;
|
|
113
|
-
modelRuntime: ModelRuntime;
|
|
114
|
-
model: Model<string>;
|
|
115
|
-
systemPrompt: string;
|
|
116
|
-
requestId: string;
|
|
117
|
-
taskId?: string;
|
|
118
|
-
role: string;
|
|
119
|
-
phase?: string;
|
|
120
|
-
output: "evidence" | "diff";
|
|
121
|
-
roots: string[];
|
|
122
|
-
allowedPaths?: string[];
|
|
123
|
-
timeoutMs: number;
|
|
124
|
-
signal?: AbortSignal;
|
|
125
|
-
}): Promise<
|
|
140
|
+
export type ChildSessionResult =
|
|
126
141
|
| {
|
|
127
142
|
ok: true;
|
|
128
143
|
result: EvidenceResult | DiffResult;
|
|
@@ -135,12 +150,29 @@ export async function runChildSession(input: {
|
|
|
135
150
|
| {
|
|
136
151
|
ok: false;
|
|
137
152
|
error: string;
|
|
153
|
+
failure: ChildFailure;
|
|
138
154
|
failureKind: ChildFailureKind;
|
|
139
155
|
transportFailure: boolean;
|
|
140
156
|
disposeCount: number;
|
|
157
|
+
usage: Usage;
|
|
141
158
|
classification: SubmitClassification;
|
|
142
|
-
}
|
|
143
|
-
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export async function runChildSession(input: {
|
|
162
|
+
cwd: string;
|
|
163
|
+
modelRuntime: ModelRuntime;
|
|
164
|
+
model: Model<string>;
|
|
165
|
+
systemPrompt: string;
|
|
166
|
+
requestId: string;
|
|
167
|
+
taskId?: string;
|
|
168
|
+
role: string;
|
|
169
|
+
phase?: string;
|
|
170
|
+
output: "evidence" | "diff";
|
|
171
|
+
roots: string[];
|
|
172
|
+
allowedPaths?: string[];
|
|
173
|
+
timeoutMs: number;
|
|
174
|
+
signal?: AbortSignal;
|
|
175
|
+
}): Promise<ChildSessionResult> {
|
|
144
176
|
const submit = createSubmitTool({
|
|
145
177
|
requestId: input.requestId,
|
|
146
178
|
taskId: input.taskId,
|
|
@@ -170,6 +202,8 @@ export async function runChildSession(input: {
|
|
|
170
202
|
| Awaited<ReturnType<typeof createAgentSession>>["session"]
|
|
171
203
|
| undefined;
|
|
172
204
|
let unsubscribe: (() => void) | undefined;
|
|
205
|
+
let abortSettlement: Promise<void> | undefined;
|
|
206
|
+
let assistantSequence = 0;
|
|
173
207
|
const disposeOnce = () => {
|
|
174
208
|
if (session) {
|
|
175
209
|
session.dispose();
|
|
@@ -244,19 +278,24 @@ export async function runChildSession(input: {
|
|
|
244
278
|
.catch(() => undefined);
|
|
245
279
|
throw error;
|
|
246
280
|
}
|
|
247
|
-
abort.signal.throwIfAborted();
|
|
248
281
|
unsubscribe = session.subscribe((event) => {
|
|
249
282
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
250
|
-
usage.add(
|
|
251
|
-
`${event.message.provider}:${event.message.model}:${event.message.timestamp}`,
|
|
252
|
-
event.message.usage,
|
|
253
|
-
);
|
|
283
|
+
usage.add(`assistant:${assistantSequence++}`, event.message.usage);
|
|
254
284
|
}
|
|
255
285
|
});
|
|
256
|
-
const
|
|
286
|
+
const startAbortSettlement = () => {
|
|
287
|
+
if (session && !abortSettlement) abortSettlement = session.abort();
|
|
288
|
+
return abortSettlement;
|
|
289
|
+
};
|
|
290
|
+
const onAbort = () => {
|
|
291
|
+
void startAbortSettlement()?.catch(() => undefined);
|
|
292
|
+
};
|
|
257
293
|
abort.signal.addEventListener("abort", onAbort, { once: true });
|
|
258
294
|
try {
|
|
259
|
-
if (abort.signal.aborted)
|
|
295
|
+
if (abort.signal.aborted) {
|
|
296
|
+
onAbort();
|
|
297
|
+
throw abort.signal.reason;
|
|
298
|
+
}
|
|
260
299
|
await Promise.race([
|
|
261
300
|
session.prompt(input.systemPrompt, { expandPromptTemplates: false }),
|
|
262
301
|
new Promise<never>((_, reject) =>
|
|
@@ -275,21 +314,30 @@ export async function runChildSession(input: {
|
|
|
275
314
|
const classification = classifySession();
|
|
276
315
|
if (!result || attempts !== 1) {
|
|
277
316
|
const transportFailure = transportFailed();
|
|
317
|
+
const failure =
|
|
318
|
+
submit.getFailure() ??
|
|
319
|
+
(transportFailure
|
|
320
|
+
? ({ kind: "transport", code: "transport-failure" } as const)
|
|
321
|
+
: ({
|
|
322
|
+
kind: "artifact",
|
|
323
|
+
code: "invalid-structural-result",
|
|
324
|
+
} as const));
|
|
278
325
|
disposeOnce();
|
|
279
326
|
return {
|
|
280
327
|
ok: false,
|
|
281
328
|
error: "child did not retain exactly one structural submission",
|
|
329
|
+
failure,
|
|
282
330
|
failureKind: "failed",
|
|
283
331
|
transportFailure,
|
|
284
332
|
disposeCount,
|
|
333
|
+
usage: usage.total(),
|
|
285
334
|
classification,
|
|
286
335
|
};
|
|
287
336
|
}
|
|
288
337
|
const assistants = session.messages.filter((m) => m.role === "assistant");
|
|
289
338
|
const final = assistants.at(-1);
|
|
290
339
|
if (
|
|
291
|
-
|
|
292
|
-
final.role !== "assistant" ||
|
|
340
|
+
final?.role !== "assistant" ||
|
|
293
341
|
final.content.length !== 1 ||
|
|
294
342
|
final.content[0]?.type !== "toolCall" ||
|
|
295
343
|
final.content[0]?.name !== "abel_submit_result"
|
|
@@ -298,9 +346,11 @@ export async function runChildSession(input: {
|
|
|
298
346
|
return {
|
|
299
347
|
ok: false,
|
|
300
348
|
error: "final assistant message is not one structural submit",
|
|
349
|
+
failure: { kind: "artifact", code: "invalid-structural-result" },
|
|
301
350
|
failureKind: "failed",
|
|
302
351
|
transportFailure: false,
|
|
303
352
|
disposeCount,
|
|
353
|
+
usage: usage.total(),
|
|
304
354
|
classification,
|
|
305
355
|
};
|
|
306
356
|
}
|
|
@@ -315,6 +365,11 @@ export async function runChildSession(input: {
|
|
|
315
365
|
classification,
|
|
316
366
|
};
|
|
317
367
|
} catch (error) {
|
|
368
|
+
if (abort.signal.aborted && session) {
|
|
369
|
+
const settlement = abortSettlement ?? session.abort();
|
|
370
|
+
abortSettlement = settlement;
|
|
371
|
+
await settleAbort(settlement);
|
|
372
|
+
}
|
|
318
373
|
const failureKind: ChildFailureKind = timedOut
|
|
319
374
|
? "timed-out"
|
|
320
375
|
: input.signal !== undefined &&
|
|
@@ -323,13 +378,23 @@ export async function runChildSession(input: {
|
|
|
323
378
|
: "failed";
|
|
324
379
|
const classification = classifySession();
|
|
325
380
|
const transportFailure = transportFailed();
|
|
381
|
+
const failure: ChildFailure = timedOut
|
|
382
|
+
? { kind: "transport", code: "timeout" }
|
|
383
|
+
: failureKind === "cancelled"
|
|
384
|
+
? { kind: "cancelled", code: "cancelled" }
|
|
385
|
+
: (submit.getFailure() ??
|
|
386
|
+
(transportFailure
|
|
387
|
+
? { kind: "transport", code: "transport-failure" }
|
|
388
|
+
: { kind: "artifact", code: "invalid-structural-result" }));
|
|
326
389
|
disposeOnce();
|
|
327
390
|
return {
|
|
328
391
|
ok: false,
|
|
329
392
|
error: (error as Error).message,
|
|
393
|
+
failure,
|
|
330
394
|
failureKind,
|
|
331
395
|
transportFailure,
|
|
332
396
|
disposeCount,
|
|
397
|
+
usage: usage.total(),
|
|
333
398
|
classification,
|
|
334
399
|
};
|
|
335
400
|
} finally {
|