@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.
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
@@ -0,0 +1,269 @@
1
+ import { spawn } from "node:child_process";
2
+ import { accessSync, constants, lstatSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ export interface IsolationMount {
6
+ source: string;
7
+ target: string;
8
+ writable?: boolean;
9
+ }
10
+
11
+ export interface IsolationRunInput {
12
+ root: string;
13
+ executable: string;
14
+ args?: string[];
15
+ mounts?: IsolationMount[];
16
+ environment?: Record<string, string>;
17
+ signal?: AbortSignal;
18
+ }
19
+
20
+ export type IsolationRunResult =
21
+ | {
22
+ ok: true;
23
+ state: "completed";
24
+ exitCode: number;
25
+ stdout: string;
26
+ stderr: string;
27
+ }
28
+ | {
29
+ ok: false;
30
+ state: "paused";
31
+ code:
32
+ | "isolation-backend-unavailable"
33
+ | "isolation-backend-launch-failed"
34
+ | "isolation-execution-timeout"
35
+ | "isolation-output-limit-exceeded";
36
+ }
37
+ | { ok: false; state: "cancelled"; code: "cancelled" };
38
+
39
+ export const ISOLATION_RUN_LIMITS = Object.freeze({
40
+ timeoutMs: 10 * 60_000,
41
+ maxOutputBytes: 1024 * 1024,
42
+ terminateGraceMs: 1_000,
43
+ });
44
+
45
+ export interface BubblewrapIsolationOptions {
46
+ bwrapPath?: string;
47
+ probe?: (path: string) => boolean | Promise<boolean>;
48
+ spawnProcess?: typeof spawn;
49
+ timeoutMs?: number;
50
+ maxOutputBytes?: number;
51
+ terminateGraceMs?: number;
52
+ }
53
+
54
+ function defaultProbe(executable: string): boolean {
55
+ try {
56
+ accessSync(executable, constants.X_OK);
57
+ return true;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ function validEnvironmentName(name: string): boolean {
64
+ return /^[A-Za-z_][A-Za-z0-9_]*$/u.test(name);
65
+ }
66
+
67
+ /** Linux isolation capability. Lack of Bubblewrap is a pause, never a fallback. */
68
+ export class BubblewrapIsolationBackend {
69
+ readonly #bwrapPath: string;
70
+ readonly #probe: (path: string) => boolean | Promise<boolean>;
71
+ readonly #spawn: typeof spawn;
72
+ readonly #timeoutMs: number;
73
+ readonly #maxOutputBytes: number;
74
+ readonly #terminateGraceMs: number;
75
+
76
+ constructor(options: BubblewrapIsolationOptions = {}) {
77
+ this.#bwrapPath = options.bwrapPath ?? "/usr/bin/bwrap";
78
+ this.#probe = options.probe ?? defaultProbe;
79
+ this.#spawn = options.spawnProcess ?? spawn;
80
+ this.#timeoutMs = options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
81
+ this.#maxOutputBytes =
82
+ options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes;
83
+ this.#terminateGraceMs =
84
+ options.terminateGraceMs ?? ISOLATION_RUN_LIMITS.terminateGraceMs;
85
+ if (
86
+ !Number.isSafeInteger(this.#timeoutMs) ||
87
+ this.#timeoutMs < 1 ||
88
+ !Number.isSafeInteger(this.#maxOutputBytes) ||
89
+ this.#maxOutputBytes < 1 ||
90
+ !Number.isSafeInteger(this.#terminateGraceMs) ||
91
+ this.#terminateGraceMs < 1
92
+ ) {
93
+ throw new Error("isolation-run-limits-invalid");
94
+ }
95
+ }
96
+
97
+ async available(): Promise<boolean> {
98
+ try {
99
+ return Boolean(await this.#probe(this.#bwrapPath));
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ async run(input: IsolationRunInput): Promise<IsolationRunResult> {
106
+ if (!(await this.available())) {
107
+ return {
108
+ ok: false,
109
+ state: "paused",
110
+ code: "isolation-backend-unavailable",
111
+ };
112
+ }
113
+ if (input.signal?.aborted) {
114
+ return { ok: false, state: "cancelled", code: "cancelled" };
115
+ }
116
+ const root = path.resolve(input.root);
117
+ const rootStat = lstatSync(root, { throwIfNoEntry: false });
118
+ if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
119
+ throw new Error("isolation-root-invalid");
120
+ }
121
+ const argv = [
122
+ "--die-with-parent",
123
+ "--new-session",
124
+ "--unshare-all",
125
+ "--proc",
126
+ "/proc",
127
+ "--dev",
128
+ "/dev",
129
+ "--tmpfs",
130
+ "/tmp",
131
+ "--bind",
132
+ root,
133
+ "/workspace",
134
+ "--chdir",
135
+ "/workspace",
136
+ "--clearenv",
137
+ "--setenv",
138
+ "PATH",
139
+ "/usr/local/bin:/usr/bin:/bin",
140
+ ];
141
+ for (const systemRoot of ["/usr", "/bin", "/lib", "/lib64"]) {
142
+ const stat = lstatSync(systemRoot, { throwIfNoEntry: false });
143
+ if (!stat) continue;
144
+ argv.push("--ro-bind", systemRoot, systemRoot);
145
+ }
146
+ for (const mount of input.mounts ?? []) {
147
+ if (!path.isAbsolute(mount.source) || !path.isAbsolute(mount.target)) {
148
+ throw new Error("isolation-mount-invalid");
149
+ }
150
+ argv.push(
151
+ mount.writable ? "--bind" : "--ro-bind",
152
+ mount.source,
153
+ mount.target,
154
+ );
155
+ }
156
+ for (const [name, value] of Object.entries(
157
+ input.environment ?? {},
158
+ ).sort()) {
159
+ if (!validEnvironmentName(name) || value.includes("\0")) {
160
+ throw new Error("isolation-environment-invalid");
161
+ }
162
+ argv.push("--setenv", name, value);
163
+ }
164
+ argv.push("--", input.executable, ...(input.args ?? []));
165
+
166
+ return new Promise((resolve) => {
167
+ let settled = false;
168
+ const stdout: Buffer[] = [];
169
+ const stderr: Buffer[] = [];
170
+ let capturedBytes = 0;
171
+ let child: ReturnType<typeof spawn>;
172
+ let executionTimer: ReturnType<typeof setTimeout> | undefined;
173
+ let killTimer: ReturnType<typeof setTimeout> | undefined;
174
+ let terminationResult: IsolationRunResult | undefined;
175
+ const settle = (result: IsolationRunResult): void => {
176
+ if (settled) return;
177
+ settled = true;
178
+ if (executionTimer) clearTimeout(executionTimer);
179
+ input.signal?.removeEventListener("abort", abort);
180
+ resolve(result);
181
+ };
182
+ const terminate = (result: IsolationRunResult): void => {
183
+ if (settled || terminationResult) return;
184
+ terminationResult = result;
185
+ if (executionTimer) clearTimeout(executionTimer);
186
+ killTimer = setTimeout(
187
+ () => child?.kill("SIGKILL"),
188
+ this.#terminateGraceMs,
189
+ );
190
+ killTimer.unref?.();
191
+ child?.kill("SIGTERM");
192
+ };
193
+ const abort = (): void => {
194
+ terminate({ ok: false, state: "cancelled", code: "cancelled" });
195
+ };
196
+ try {
197
+ child = this.#spawn(this.#bwrapPath, argv, {
198
+ cwd: root,
199
+ shell: false,
200
+ stdio: ["ignore", "pipe", "pipe"],
201
+ });
202
+ } catch {
203
+ settle({
204
+ ok: false,
205
+ state: "paused",
206
+ code: "isolation-backend-launch-failed",
207
+ });
208
+ return;
209
+ }
210
+ const capture = (destination: Buffer[], chunk: Buffer | string): void => {
211
+ if (settled || terminationResult) return;
212
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
213
+ const remaining = this.#maxOutputBytes - capturedBytes;
214
+ if (remaining > 0) destination.push(bytes.subarray(0, remaining));
215
+ if (bytes.byteLength > remaining) {
216
+ capturedBytes = this.#maxOutputBytes;
217
+ terminate({
218
+ ok: false,
219
+ state: "paused",
220
+ code: "isolation-output-limit-exceeded",
221
+ });
222
+ return;
223
+ }
224
+ capturedBytes += bytes.byteLength;
225
+ };
226
+ child.stdout?.on("data", (chunk: Buffer | string) => {
227
+ capture(stdout, chunk);
228
+ });
229
+ child.stderr?.on("data", (chunk: Buffer | string) => {
230
+ capture(stderr, chunk);
231
+ });
232
+ child.once("error", () => {
233
+ if (terminationResult) return;
234
+ settle({
235
+ ok: false,
236
+ state: "paused",
237
+ code: "isolation-backend-launch-failed",
238
+ });
239
+ });
240
+ child.once("close", (code) => {
241
+ if (killTimer) clearTimeout(killTimer);
242
+ settle(
243
+ terminationResult ?? {
244
+ ok: true,
245
+ state: "completed",
246
+ exitCode: code ?? 1,
247
+ stdout: Buffer.concat(stdout).toString("utf8"),
248
+ stderr: Buffer.concat(stderr).toString("utf8"),
249
+ },
250
+ );
251
+ });
252
+ input.signal?.addEventListener("abort", abort, { once: true });
253
+ if (input.signal?.aborted) {
254
+ abort();
255
+ return;
256
+ }
257
+ executionTimer = setTimeout(
258
+ () =>
259
+ terminate({
260
+ ok: false,
261
+ state: "paused",
262
+ code: "isolation-execution-timeout",
263
+ }),
264
+ this.#timeoutMs,
265
+ );
266
+ executionTimer.unref?.();
267
+ });
268
+ }
269
+ }