@abelxiaoxing/cadence 1.0.11 → 1.0.13
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/package.json +1 -1
- package/prompts/abel-design.md +19 -10
- package/prompts/abel-implement.md +19 -6
- package/skills/abel-workflow/SKILL.md +32 -10
- package/src/candidate-preflight.ts +9 -6
- package/src/child-session.ts +100 -28
- package/src/contracts.ts +438 -230
- package/src/file-snapshot.ts +11 -20
- package/src/implement-graph.ts +571 -0
- package/src/index.ts +14 -4
- package/src/parent-payload-bridge.ts +31 -7
- package/src/patch.ts +103 -21
- package/src/runtime.ts +445 -119
- package/src/safe-path.ts +66 -0
- package/src/scoped-tools.ts +278 -38
- package/src/subagent-activity.ts +80 -12
- package/src/submit-tool.ts +26 -13
- package/src/verification-capability.ts +52 -67
- package/src/worker.ts +49 -0
package/src/file-snapshot.ts
CHANGED
|
@@ -3,16 +3,11 @@
|
|
|
3
3
|
// markers for proposed new paths. Currency is decided per bound path; no
|
|
4
4
|
// global workspace revision participates.
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import {
|
|
7
|
-
existsSync,
|
|
8
|
-
lstatSync,
|
|
9
|
-
readdirSync,
|
|
10
|
-
readFileSync,
|
|
11
|
-
readlinkSync,
|
|
12
|
-
statSync,
|
|
13
|
-
} from "node:fs";
|
|
6
|
+
import { lstatSync, readdirSync, readFileSync, readlinkSync } from "node:fs";
|
|
14
7
|
import { join, resolve } from "node:path";
|
|
15
8
|
|
|
9
|
+
import { observeSafePath } from "./safe-path.ts";
|
|
10
|
+
|
|
16
11
|
export interface FileBound {
|
|
17
12
|
kind: "file";
|
|
18
13
|
sha256: string;
|
|
@@ -37,9 +32,8 @@ function sha256(bytes: Buffer): string {
|
|
|
37
32
|
|
|
38
33
|
/** Snapshot one existing regular file under root. */
|
|
39
34
|
export function snapshotFile(root: string, relPath: string): FileBound | null {
|
|
35
|
+
if (observeSafePath(root, relPath).kind !== "file") return null;
|
|
40
36
|
const abs = resolve(root, relPath);
|
|
41
|
-
const st = statSync(abs, { throwIfNoEntry: false });
|
|
42
|
-
if (!st?.isFile()) return null;
|
|
43
37
|
const bytes = readFileSync(abs);
|
|
44
38
|
return { sha256: sha256(bytes), bytes: bytes.length, kind: "file" };
|
|
45
39
|
}
|
|
@@ -99,9 +93,8 @@ export function snapshotDirManifest(
|
|
|
99
93
|
root: string,
|
|
100
94
|
relDir: string,
|
|
101
95
|
): DirBound | null {
|
|
96
|
+
if (observeSafePath(root, relDir).kind !== "directory") return null;
|
|
102
97
|
const abs = resolve(root, relDir);
|
|
103
|
-
const st = statSync(abs, { throwIfNoEntry: false });
|
|
104
|
-
if (!st?.isDirectory()) return null;
|
|
105
98
|
return { manifest: recursiveDirectoryManifest(abs), kind: "dir" };
|
|
106
99
|
}
|
|
107
100
|
|
|
@@ -117,8 +110,9 @@ export function snapshotFiles(
|
|
|
117
110
|
if (entry) bound[p] = entry;
|
|
118
111
|
else bound[p] = { kind: "absent", absent: true };
|
|
119
112
|
}
|
|
120
|
-
for (const p of opts.absent ?? [])
|
|
113
|
+
for (const p of opts.absent ?? []) {
|
|
121
114
|
bound[p] = { kind: "absent", absent: true };
|
|
115
|
+
}
|
|
122
116
|
return bound;
|
|
123
117
|
}
|
|
124
118
|
|
|
@@ -141,12 +135,9 @@ export function isCurrent(root: string, bound: Bound): boolean {
|
|
|
141
135
|
if (!now || now.manifest !== expected.manifest) return false;
|
|
142
136
|
continue;
|
|
143
137
|
}
|
|
144
|
-
const
|
|
145
|
-
const exists = existsSync(abs);
|
|
138
|
+
const observation = observeSafePath(root, rel);
|
|
146
139
|
if (expected.kind === "file") {
|
|
147
|
-
if (
|
|
148
|
-
const st = statSync(abs);
|
|
149
|
-
if (!st.isFile()) return false;
|
|
140
|
+
if (observation.kind !== "file") return false;
|
|
150
141
|
const now = snapshotFile(root, rel);
|
|
151
142
|
if (
|
|
152
143
|
!now ||
|
|
@@ -154,8 +145,8 @@ export function isCurrent(root: string, bound: Bound): boolean {
|
|
|
154
145
|
now.bytes !== expected.bytes
|
|
155
146
|
)
|
|
156
147
|
return false;
|
|
157
|
-
} else if (expected.absent) {
|
|
158
|
-
|
|
148
|
+
} else if (expected.absent && observation.kind !== "absent") {
|
|
149
|
+
return false;
|
|
159
150
|
}
|
|
160
151
|
}
|
|
161
152
|
return true;
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
type ImplementationPhase,
|
|
4
|
+
type ImplementGraphBoundary,
|
|
5
|
+
type ImplementGraphOutput,
|
|
6
|
+
type ImplementGraphReadinessCode,
|
|
7
|
+
type ImplementGraphReadinessDiagnostic,
|
|
8
|
+
type ImplementTaskBoundary,
|
|
9
|
+
type VerificationInputBinding,
|
|
10
|
+
validateImplementGraphBoundary,
|
|
11
|
+
verificationInputPaths,
|
|
12
|
+
} from "./contracts.ts";
|
|
13
|
+
import { isSafeRegularFile } from "./safe-path.ts";
|
|
14
|
+
import { validateVerificationAdapterCapability } from "./verification-capability.ts";
|
|
15
|
+
|
|
16
|
+
function canonicalJson(value: unknown): string {
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
19
|
+
}
|
|
20
|
+
if (value && typeof value === "object") {
|
|
21
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
22
|
+
.filter(([, entry]) => entry !== undefined)
|
|
23
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
24
|
+
return `{${entries
|
|
25
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`)
|
|
26
|
+
.join(",")}}`;
|
|
27
|
+
}
|
|
28
|
+
return JSON.stringify(value) ?? "null";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function hashImplementGraphBoundary(value: unknown): string {
|
|
32
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ImplementExecutionFacts {
|
|
36
|
+
completedTasks: readonly string[];
|
|
37
|
+
blockedTasks?: readonly string[];
|
|
38
|
+
appliedPhases?: ReadonlyArray<{
|
|
39
|
+
taskId: string;
|
|
40
|
+
phase: ImplementationPhase;
|
|
41
|
+
}>;
|
|
42
|
+
candidate?: { taskId: string; phase: ImplementationPhase };
|
|
43
|
+
dependencyOwner?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ImplementPhaseReadiness {
|
|
47
|
+
taskId: string;
|
|
48
|
+
phase: ImplementationPhase;
|
|
49
|
+
ready: boolean;
|
|
50
|
+
completed?: true;
|
|
51
|
+
diagnostics: ImplementGraphReadinessDiagnostic[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ImplementGraphReadiness {
|
|
55
|
+
closure: {
|
|
56
|
+
executable: boolean;
|
|
57
|
+
diagnostics: ImplementGraphReadinessDiagnostic[];
|
|
58
|
+
};
|
|
59
|
+
phases: ImplementPhaseReadiness[];
|
|
60
|
+
outputs: ImplementOutputReadiness[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ImplementOutputReadiness {
|
|
64
|
+
outputId: string;
|
|
65
|
+
producerTaskId: string;
|
|
66
|
+
producerPhase: ImplementationPhase;
|
|
67
|
+
status: "pending" | "available" | "unavailable";
|
|
68
|
+
diagnostic?: ImplementGraphReadinessDiagnostic;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const IMPLEMENT_PHASE_INDEX: Record<ImplementationPhase, number> = {
|
|
72
|
+
red: 0,
|
|
73
|
+
green: 1,
|
|
74
|
+
refactor: 2,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function graphDiagnostic(
|
|
78
|
+
code: ImplementGraphReadinessCode,
|
|
79
|
+
details: Omit<
|
|
80
|
+
Extract<ImplementGraphReadinessDiagnostic, { kind: "graph-readiness" }>,
|
|
81
|
+
"kind" | "code"
|
|
82
|
+
> = {},
|
|
83
|
+
): ImplementGraphReadinessDiagnostic {
|
|
84
|
+
return { kind: "graph-readiness", code, ...details };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function taskPhases(
|
|
88
|
+
task: ImplementTaskBoundary,
|
|
89
|
+
): Array<[ImplementationPhase, ImplementTaskBoundary["phases"]["red"]]> {
|
|
90
|
+
return [
|
|
91
|
+
["red", task.phases.red],
|
|
92
|
+
["green", task.phases.green],
|
|
93
|
+
...(task.phases.refactor
|
|
94
|
+
? ([["refactor", task.phases.refactor]] as Array<
|
|
95
|
+
[ImplementationPhase, ImplementTaskBoundary["phases"]["red"]]
|
|
96
|
+
>)
|
|
97
|
+
: []),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function withinTaskRoots(
|
|
102
|
+
task: ImplementTaskBoundary,
|
|
103
|
+
candidate: string,
|
|
104
|
+
): boolean {
|
|
105
|
+
return task.roots.some(
|
|
106
|
+
(root) =>
|
|
107
|
+
root === "." || candidate === root || candidate.startsWith(`${root}/`),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function dependencyAncestors(
|
|
112
|
+
taskId: string,
|
|
113
|
+
tasks: ReadonlyMap<string, ImplementTaskBoundary>,
|
|
114
|
+
): Set<string> {
|
|
115
|
+
const ancestors = new Set<string>();
|
|
116
|
+
const pending = [...(tasks.get(taskId)?.dependsOn ?? [])];
|
|
117
|
+
while (pending.length > 0) {
|
|
118
|
+
const dependency = pending.pop() as string;
|
|
119
|
+
if (ancestors.has(dependency)) continue;
|
|
120
|
+
ancestors.add(dependency);
|
|
121
|
+
pending.push(...(tasks.get(dependency)?.dependsOn ?? []));
|
|
122
|
+
}
|
|
123
|
+
return ancestors;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function bindingPath(
|
|
127
|
+
binding: VerificationInputBinding,
|
|
128
|
+
outputs: ReadonlyMap<string, ImplementGraphOutput>,
|
|
129
|
+
): string | undefined {
|
|
130
|
+
return binding.kind === "workspace"
|
|
131
|
+
? binding.path
|
|
132
|
+
: outputs.get(binding.outputId)?.path;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function sameStringSet(
|
|
136
|
+
left: readonly string[],
|
|
137
|
+
right: readonly string[],
|
|
138
|
+
): boolean {
|
|
139
|
+
return (
|
|
140
|
+
left.length === right.length && left.every((entry) => right.includes(entry))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function appendDistinct(
|
|
145
|
+
diagnostics: ImplementGraphReadinessDiagnostic[],
|
|
146
|
+
diagnostic: ImplementGraphReadinessDiagnostic,
|
|
147
|
+
): void {
|
|
148
|
+
if (
|
|
149
|
+
!diagnostics.some(
|
|
150
|
+
(candidate) => JSON.stringify(candidate) === JSON.stringify(diagnostic),
|
|
151
|
+
)
|
|
152
|
+
) {
|
|
153
|
+
diagnostics.push(diagnostic);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function assessImplementGraphReadiness(
|
|
158
|
+
root: string,
|
|
159
|
+
value: unknown,
|
|
160
|
+
facts: ImplementExecutionFacts,
|
|
161
|
+
): ImplementGraphReadiness {
|
|
162
|
+
const validation = validateImplementGraphBoundary(value);
|
|
163
|
+
if (!validation.ok) {
|
|
164
|
+
const diagnostic = graphDiagnostic(validation.diagnostic.code, {
|
|
165
|
+
...(validation.diagnostic.code === "invalid-output-path" &&
|
|
166
|
+
validation.diagnostic.outputId
|
|
167
|
+
? { outputId: validation.diagnostic.outputId }
|
|
168
|
+
: {}),
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
closure: { executable: false, diagnostics: [diagnostic] },
|
|
172
|
+
phases: [],
|
|
173
|
+
outputs: [],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const graph: ImplementGraphBoundary = validation.value;
|
|
178
|
+
const diagnostics: ImplementGraphReadinessDiagnostic[] = [];
|
|
179
|
+
const tasks = new Map<string, ImplementTaskBoundary>();
|
|
180
|
+
for (const task of graph.tasks) {
|
|
181
|
+
if (tasks.has(task.taskId)) {
|
|
182
|
+
diagnostics.push(
|
|
183
|
+
graphDiagnostic("duplicate-task-id", { taskId: task.taskId }),
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
tasks.set(task.taskId, task);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const task of graph.tasks) {
|
|
191
|
+
for (const dependencyTaskId of task.dependsOn) {
|
|
192
|
+
if (!tasks.has(dependencyTaskId) || dependencyTaskId === task.taskId) {
|
|
193
|
+
diagnostics.push(
|
|
194
|
+
graphDiagnostic("unknown-dependency", {
|
|
195
|
+
taskId: task.taskId,
|
|
196
|
+
dependencyTaskId,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (dependencyAncestors(task.taskId, tasks).has(task.taskId)) {
|
|
202
|
+
diagnostics.push(
|
|
203
|
+
graphDiagnostic("dependency-cycle", { taskId: task.taskId }),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const outputs = new Map<string, ImplementGraphOutput>();
|
|
209
|
+
const outputPaths = new Map<string, ImplementGraphOutput>();
|
|
210
|
+
for (const output of graph.outputs) {
|
|
211
|
+
if (outputs.has(output.id)) {
|
|
212
|
+
diagnostics.push(
|
|
213
|
+
graphDiagnostic("duplicate-output-id", { outputId: output.id }),
|
|
214
|
+
);
|
|
215
|
+
} else {
|
|
216
|
+
outputs.set(output.id, output);
|
|
217
|
+
}
|
|
218
|
+
const prior = outputPaths.get(output.path);
|
|
219
|
+
if (prior) {
|
|
220
|
+
diagnostics.push(
|
|
221
|
+
graphDiagnostic("multiple-output-producers", {
|
|
222
|
+
outputId: output.id,
|
|
223
|
+
path: output.path,
|
|
224
|
+
producerTaskId: output.producer.taskId,
|
|
225
|
+
producerPhase: output.producer.phase,
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
} else {
|
|
229
|
+
outputPaths.set(output.path, output);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const producer = tasks.get(output.producer.taskId);
|
|
233
|
+
const producerPhase = producer?.phases[output.producer.phase];
|
|
234
|
+
if (
|
|
235
|
+
!producer ||
|
|
236
|
+
!producerPhase ||
|
|
237
|
+
!withinTaskRoots(producer, output.path)
|
|
238
|
+
) {
|
|
239
|
+
diagnostics.push(
|
|
240
|
+
graphDiagnostic("output-producer-invalid", {
|
|
241
|
+
outputId: output.id,
|
|
242
|
+
producerTaskId: output.producer.taskId,
|
|
243
|
+
producerPhase: output.producer.phase,
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
} else if (!producerPhase.write.includes(output.path)) {
|
|
247
|
+
diagnostics.push(
|
|
248
|
+
graphDiagnostic("output-write-not-approved", {
|
|
249
|
+
outputId: output.id,
|
|
250
|
+
producerTaskId: output.producer.taskId,
|
|
251
|
+
producerPhase: output.producer.phase,
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const phaseStaticDiagnostics = new Map<
|
|
258
|
+
string,
|
|
259
|
+
ImplementGraphReadinessDiagnostic[]
|
|
260
|
+
>();
|
|
261
|
+
for (const task of graph.tasks) {
|
|
262
|
+
const ancestors = dependencyAncestors(task.taskId, tasks);
|
|
263
|
+
for (const [phase, boundary] of taskPhases(task)) {
|
|
264
|
+
const key = `${task.taskId}\0${phase}`;
|
|
265
|
+
const current: ImplementGraphReadinessDiagnostic[] = [];
|
|
266
|
+
phaseStaticDiagnostics.set(key, current);
|
|
267
|
+
const verificationId = boundary.verification.id;
|
|
268
|
+
const requiredPaths = verificationInputPaths(boundary.verification);
|
|
269
|
+
const boundPaths = boundary.verificationInputs
|
|
270
|
+
.map((binding) => bindingPath(binding, outputs))
|
|
271
|
+
.filter((entry): entry is string => entry !== undefined);
|
|
272
|
+
|
|
273
|
+
for (const binding of boundary.verificationInputs) {
|
|
274
|
+
if (binding.kind === "workspace") {
|
|
275
|
+
const graphOutput = outputPaths.get(binding.path);
|
|
276
|
+
if (graphOutput) {
|
|
277
|
+
appendDistinct(
|
|
278
|
+
current,
|
|
279
|
+
graphDiagnostic("workspace-input-has-producer", {
|
|
280
|
+
taskId: task.taskId,
|
|
281
|
+
phase,
|
|
282
|
+
verificationId,
|
|
283
|
+
outputId: graphOutput.id,
|
|
284
|
+
path: binding.path,
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (!isSafeRegularFile(root, binding.path)) {
|
|
289
|
+
appendDistinct(
|
|
290
|
+
current,
|
|
291
|
+
graphDiagnostic("workspace-input-unavailable", {
|
|
292
|
+
taskId: task.taskId,
|
|
293
|
+
phase,
|
|
294
|
+
verificationId,
|
|
295
|
+
path: binding.path,
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const output = outputs.get(binding.outputId);
|
|
303
|
+
if (!output) {
|
|
304
|
+
appendDistinct(
|
|
305
|
+
current,
|
|
306
|
+
graphDiagnostic("output-not-declared", {
|
|
307
|
+
taskId: task.taskId,
|
|
308
|
+
phase,
|
|
309
|
+
verificationId,
|
|
310
|
+
outputId: binding.outputId,
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (output.producer.taskId === task.taskId) {
|
|
316
|
+
if (
|
|
317
|
+
IMPLEMENT_PHASE_INDEX[output.producer.phase] >
|
|
318
|
+
IMPLEMENT_PHASE_INDEX[phase]
|
|
319
|
+
) {
|
|
320
|
+
appendDistinct(
|
|
321
|
+
current,
|
|
322
|
+
graphDiagnostic("producer-phase-after-consumer", {
|
|
323
|
+
taskId: task.taskId,
|
|
324
|
+
phase,
|
|
325
|
+
verificationId,
|
|
326
|
+
outputId: output.id,
|
|
327
|
+
producerTaskId: output.producer.taskId,
|
|
328
|
+
producerPhase: output.producer.phase,
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
} else if (!ancestors.has(output.producer.taskId)) {
|
|
333
|
+
appendDistinct(
|
|
334
|
+
current,
|
|
335
|
+
graphDiagnostic("producer-not-dependency", {
|
|
336
|
+
taskId: task.taskId,
|
|
337
|
+
phase,
|
|
338
|
+
verificationId,
|
|
339
|
+
outputId: output.id,
|
|
340
|
+
producerTaskId: output.producer.taskId,
|
|
341
|
+
producerPhase: output.producer.phase,
|
|
342
|
+
}),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!sameStringSet(requiredPaths, boundPaths)) {
|
|
348
|
+
appendDistinct(
|
|
349
|
+
current,
|
|
350
|
+
graphDiagnostic("verification-input-binding-mismatch", {
|
|
351
|
+
taskId: task.taskId,
|
|
352
|
+
phase,
|
|
353
|
+
verificationId,
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const capability = validateVerificationAdapterCapability(
|
|
359
|
+
root,
|
|
360
|
+
boundary.verification,
|
|
361
|
+
{ dependencyOwner: facts.dependencyOwner ?? root },
|
|
362
|
+
);
|
|
363
|
+
if (!capability.ok) {
|
|
364
|
+
const { message: _message, ...diagnostic } = capability.diagnostic;
|
|
365
|
+
appendDistinct(current, {
|
|
366
|
+
...diagnostic,
|
|
367
|
+
taskId: task.taskId,
|
|
368
|
+
phase,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
diagnostics.push(...current);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const closure = {
|
|
376
|
+
executable: diagnostics.length === 0,
|
|
377
|
+
diagnostics,
|
|
378
|
+
};
|
|
379
|
+
const completedTasks = new Set(facts.completedTasks);
|
|
380
|
+
const blockedTasks = new Set(facts.blockedTasks ?? []);
|
|
381
|
+
const appliedPhases = new Set(
|
|
382
|
+
(facts.appliedPhases ?? []).map(
|
|
383
|
+
(entry) => `${entry.taskId}\0${entry.phase}`,
|
|
384
|
+
),
|
|
385
|
+
);
|
|
386
|
+
const outputReadiness: ImplementOutputReadiness[] = graph.outputs.map(
|
|
387
|
+
(output) => {
|
|
388
|
+
const observed =
|
|
389
|
+
completedTasks.has(output.producer.taskId) ||
|
|
390
|
+
appliedPhases.has(
|
|
391
|
+
`${output.producer.taskId}\0${output.producer.phase}`,
|
|
392
|
+
) ||
|
|
393
|
+
(facts.candidate?.taskId === output.producer.taskId &&
|
|
394
|
+
facts.candidate.phase === output.producer.phase);
|
|
395
|
+
if (!observed) {
|
|
396
|
+
return {
|
|
397
|
+
outputId: output.id,
|
|
398
|
+
producerTaskId: output.producer.taskId,
|
|
399
|
+
producerPhase: output.producer.phase,
|
|
400
|
+
status: "pending" as const,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (isSafeRegularFile(root, output.path)) {
|
|
404
|
+
return {
|
|
405
|
+
outputId: output.id,
|
|
406
|
+
producerTaskId: output.producer.taskId,
|
|
407
|
+
producerPhase: output.producer.phase,
|
|
408
|
+
status: "available" as const,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
outputId: output.id,
|
|
413
|
+
producerTaskId: output.producer.taskId,
|
|
414
|
+
producerPhase: output.producer.phase,
|
|
415
|
+
status: "unavailable" as const,
|
|
416
|
+
diagnostic: graphDiagnostic("producer-output-unavailable", {
|
|
417
|
+
outputId: output.id,
|
|
418
|
+
producerTaskId: output.producer.taskId,
|
|
419
|
+
producerPhase: output.producer.phase,
|
|
420
|
+
}),
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
);
|
|
424
|
+
const outputStatus = new Map(
|
|
425
|
+
outputReadiness.map((entry) => [entry.outputId, entry]),
|
|
426
|
+
);
|
|
427
|
+
const phases: ImplementPhaseReadiness[] = [];
|
|
428
|
+
|
|
429
|
+
for (const task of graph.tasks) {
|
|
430
|
+
for (const [phase, boundary] of taskPhases(task)) {
|
|
431
|
+
const staticDiagnostics = phaseStaticDiagnostics.get(
|
|
432
|
+
`${task.taskId}\0${phase}`,
|
|
433
|
+
) as ImplementGraphReadinessDiagnostic[];
|
|
434
|
+
if (!closure.executable) {
|
|
435
|
+
phases.push({
|
|
436
|
+
taskId: task.taskId,
|
|
437
|
+
phase,
|
|
438
|
+
ready: false,
|
|
439
|
+
diagnostics:
|
|
440
|
+
staticDiagnostics.length > 0 ? staticDiagnostics : diagnostics,
|
|
441
|
+
});
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (completedTasks.has(task.taskId)) {
|
|
445
|
+
phases.push({
|
|
446
|
+
taskId: task.taskId,
|
|
447
|
+
phase,
|
|
448
|
+
ready: false,
|
|
449
|
+
completed: true,
|
|
450
|
+
diagnostics: [],
|
|
451
|
+
});
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const current: ImplementGraphReadinessDiagnostic[] = [];
|
|
456
|
+
if (blockedTasks.has(task.taskId)) {
|
|
457
|
+
current.push(
|
|
458
|
+
graphDiagnostic("dependency-blocked", {
|
|
459
|
+
taskId: task.taskId,
|
|
460
|
+
phase,
|
|
461
|
+
dependencyTaskId: task.taskId,
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
for (const dependencyTaskId of task.dependsOn) {
|
|
466
|
+
if (!completedTasks.has(dependencyTaskId)) {
|
|
467
|
+
appendDistinct(
|
|
468
|
+
current,
|
|
469
|
+
graphDiagnostic("dependency-blocked", {
|
|
470
|
+
taskId: task.taskId,
|
|
471
|
+
phase,
|
|
472
|
+
dependencyTaskId,
|
|
473
|
+
}),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const earlierPhases = taskPhases(task)
|
|
479
|
+
.map(([candidate]) => candidate)
|
|
480
|
+
.filter(
|
|
481
|
+
(candidate) =>
|
|
482
|
+
IMPLEMENT_PHASE_INDEX[candidate] < IMPLEMENT_PHASE_INDEX[phase],
|
|
483
|
+
);
|
|
484
|
+
if (
|
|
485
|
+
earlierPhases.some(
|
|
486
|
+
(candidate) => !appliedPhases.has(`${task.taskId}\0${candidate}`),
|
|
487
|
+
)
|
|
488
|
+
) {
|
|
489
|
+
appendDistinct(
|
|
490
|
+
current,
|
|
491
|
+
graphDiagnostic("phase-blocked", { taskId: task.taskId, phase }),
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (current.length === 0) {
|
|
496
|
+
for (const binding of boundary.verificationInputs) {
|
|
497
|
+
if (binding.kind !== "output") continue;
|
|
498
|
+
const output = outputs.get(binding.outputId);
|
|
499
|
+
if (!output) continue;
|
|
500
|
+
const sameTask = output.producer.taskId === task.taskId;
|
|
501
|
+
const samePhase = sameTask && output.producer.phase === phase;
|
|
502
|
+
const readiness = outputStatus.get(output.id);
|
|
503
|
+
const published =
|
|
504
|
+
!sameTask && completedTasks.has(output.producer.taskId);
|
|
505
|
+
if (readiness?.status === "unavailable") {
|
|
506
|
+
appendDistinct(
|
|
507
|
+
current,
|
|
508
|
+
graphDiagnostic("producer-output-unavailable", {
|
|
509
|
+
taskId: task.taskId,
|
|
510
|
+
phase,
|
|
511
|
+
outputId: output.id,
|
|
512
|
+
producerTaskId: output.producer.taskId,
|
|
513
|
+
producerPhase: output.producer.phase,
|
|
514
|
+
}),
|
|
515
|
+
);
|
|
516
|
+
} else if (
|
|
517
|
+
(!sameTask && !published) ||
|
|
518
|
+
(sameTask && !samePhase && readiness?.status !== "available")
|
|
519
|
+
) {
|
|
520
|
+
appendDistinct(
|
|
521
|
+
current,
|
|
522
|
+
sameTask
|
|
523
|
+
? graphDiagnostic("phase-blocked", {
|
|
524
|
+
taskId: task.taskId,
|
|
525
|
+
phase,
|
|
526
|
+
})
|
|
527
|
+
: graphDiagnostic("dependency-blocked", {
|
|
528
|
+
taskId: task.taskId,
|
|
529
|
+
phase,
|
|
530
|
+
dependencyTaskId: output.producer.taskId,
|
|
531
|
+
}),
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (
|
|
537
|
+
facts.candidate?.taskId === task.taskId &&
|
|
538
|
+
facts.candidate.phase === phase
|
|
539
|
+
) {
|
|
540
|
+
for (const output of graph.outputs.filter(
|
|
541
|
+
(entry) =>
|
|
542
|
+
entry.producer.taskId === task.taskId &&
|
|
543
|
+
entry.producer.phase === phase,
|
|
544
|
+
)) {
|
|
545
|
+
if (outputStatus.get(output.id)?.status !== "available") {
|
|
546
|
+
appendDistinct(
|
|
547
|
+
current,
|
|
548
|
+
graphDiagnostic("producer-output-unavailable", {
|
|
549
|
+
taskId: task.taskId,
|
|
550
|
+
phase,
|
|
551
|
+
outputId: output.id,
|
|
552
|
+
producerTaskId: output.producer.taskId,
|
|
553
|
+
producerPhase: output.producer.phase,
|
|
554
|
+
}),
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
phases.push({
|
|
562
|
+
taskId: task.taskId,
|
|
563
|
+
phase,
|
|
564
|
+
ready: current.length === 0,
|
|
565
|
+
diagnostics: current,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return { closure, phases, outputs: outputReadiness };
|
|
571
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -232,6 +232,7 @@ export default function register(pi: ExtensionAPI): void {
|
|
|
232
232
|
? "Run exactly one bounded read-only design-explorer packet. When parallel packets are required, emit every sibling abel_dispatch call together in one assistant response before waiting for any result."
|
|
233
233
|
: "Private Abel workflow delegation: run bounded read-only evidence or Worker phase requests, apply or discard retained results, apply a parent-only stable AGENTS checkpoint, cancel work, or finish the stage. Inactive unless an eligible Abel stage verified its invocation.",
|
|
234
234
|
executionMode: "parallel",
|
|
235
|
+
renderShell: "self",
|
|
235
236
|
parameters: {
|
|
236
237
|
type: "object",
|
|
237
238
|
properties: {
|
|
@@ -304,16 +305,25 @@ export default function register(pi: ExtensionAPI): void {
|
|
|
304
305
|
...(usage === undefined ? {} : { usage }),
|
|
305
306
|
};
|
|
306
307
|
},
|
|
307
|
-
renderCall(args: unknown, theme: Theme,
|
|
308
|
-
return renderActivityCall(
|
|
308
|
+
renderCall(args: unknown, theme: Theme, context: unknown) {
|
|
309
|
+
return renderActivityCall(
|
|
310
|
+
args,
|
|
311
|
+
theme,
|
|
312
|
+
context as Parameters<typeof renderActivityCall>[2],
|
|
313
|
+
);
|
|
309
314
|
},
|
|
310
315
|
renderResult(
|
|
311
316
|
result: AgentToolResult<unknown>,
|
|
312
317
|
options: ToolRenderResultOptions,
|
|
313
318
|
theme: Theme,
|
|
314
|
-
|
|
319
|
+
context: unknown,
|
|
315
320
|
) {
|
|
316
|
-
return renderActivityResult(
|
|
321
|
+
return renderActivityResult(
|
|
322
|
+
result,
|
|
323
|
+
options,
|
|
324
|
+
theme,
|
|
325
|
+
context as Parameters<typeof renderActivityResult>[3],
|
|
326
|
+
);
|
|
317
327
|
},
|
|
318
328
|
} as never);
|
|
319
329
|
};
|