@abelxiaoxing/cadence 1.6.0 → 1.8.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 +25 -4
- package/config/.env.example +6 -0
- package/config/plan-draft.example.json +3 -0
- package/config/plan-draft.multiple-tasks.example.json +30 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +33 -3
- package/prompts/abel-implement.md +36 -4
- package/src/apply-transaction.ts +28 -8
- package/src/build-windows-job.mjs +95 -0
- package/src/candidate-workspace.ts +6 -0
- package/src/change-verification.ts +764 -67
- package/src/contracts.ts +8 -0
- package/src/delivery-compiler.ts +452 -9
- package/src/design-control.ts +2 -2
- package/src/design-diagnostics.ts +6 -4
- package/src/durable-contracts.ts +21 -3
- package/src/durable-workflow.ts +55 -40
- package/src/execution-profile.ts +28 -2
- package/src/execution-retention.ts +77 -0
- package/src/implement-continuation.ts +559 -0
- package/src/implement-graph.ts +114 -0
- package/src/implement-plan.ts +2 -0
- package/src/index.ts +151 -1
- package/src/isolation-backend.ts +2 -1
- package/src/operator-cli.mjs +664 -104
- package/src/operator-tools.ts +7 -1
- package/src/package-state.ts +11 -0
- package/src/package-verification.ts +66 -18
- package/src/package-workflow.ts +6 -0
- package/src/phase-execution.ts +18 -7
- package/src/plan-draft-summary.ts +385 -7
- package/src/plan-draft.ts +9 -0
- package/src/remove-path.ts +32 -0
- package/src/subagent-activity.ts +3 -1
- package/src/verification-capability.ts +18 -6
- package/src/verification-environment-io.ts +123 -14
- package/src/verification-environment.ts +50 -3
- package/src/windows-job-backend.ts +419 -0
- package/src/windows-job.c +149 -0
- package/src/workflow-policy.ts +296 -5
- package/src/workflow-recovery-policy.ts +2 -0
- package/src/workflow-state-machine.ts +314 -80
- package/src/workflow-status.ts +251 -59
- package/src/workspace-io-entry.mjs +2 -2
- package/src/workspace-io-worker.mjs +207 -110
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
lstatSync,
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readdirSync,
|
|
7
|
+
readlinkSync,
|
|
7
8
|
realpathSync,
|
|
8
|
-
rmSync,
|
|
9
9
|
symlinkSync,
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import type { ExecutionProfile } from "./execution-profile.ts";
|
|
14
14
|
import type { IsolationMount } from "./isolation-backend.ts";
|
|
15
|
+
import { removePathSync } from "./remove-path.ts";
|
|
15
16
|
import type { VerificationRunnerBinding } from "./verification-capability.ts";
|
|
16
17
|
|
|
17
18
|
const CACHES = [".vite", ".vite-temp"];
|
|
@@ -34,9 +35,11 @@ export function prepareVerificationEnvironmentIo(
|
|
|
34
35
|
checkCancelled: () => void,
|
|
35
36
|
privateRoot: string,
|
|
36
37
|
) {
|
|
37
|
-
const cleanup = () =>
|
|
38
|
+
const cleanup = () =>
|
|
39
|
+
removePathSync(privateRoot, { recursive: true, force: true });
|
|
38
40
|
try {
|
|
39
|
-
const local = profile.mode
|
|
41
|
+
const local = profile.mode !== "isolated";
|
|
42
|
+
const native = profile.mode === "host-trusted";
|
|
40
43
|
if (local && realpathSync(root) === realpathSync(dependencyOwner))
|
|
41
44
|
throw new Error("trusted-candidate-required");
|
|
42
45
|
const mounts: IsolationMount[] = [
|
|
@@ -51,11 +54,19 @@ export function prepareVerificationEnvironmentIo(
|
|
|
51
54
|
]) {
|
|
52
55
|
mkdirSync(path.join(privateRoot, directory), { mode: 0o700 });
|
|
53
56
|
}
|
|
57
|
+
if (native) {
|
|
58
|
+
mkdirSync(path.join(privateRoot, "home/AppData/Roaming"), {
|
|
59
|
+
recursive: true,
|
|
60
|
+
});
|
|
61
|
+
mkdirSync(path.join(privateRoot, "home/AppData/Local"), {
|
|
62
|
+
recursive: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
54
65
|
writeFileSync(path.join(privateRoot, "home/user.npmrc"), "");
|
|
55
66
|
writeFileSync(path.join(privateRoot, "home/global.npmrc"), "");
|
|
56
67
|
const dependencies = path.join(dependencyOwner, "node_modules");
|
|
57
68
|
const stat = lstatSync(dependencies, { throwIfNoEntry: false });
|
|
58
|
-
if (stat) {
|
|
69
|
+
if (stat && !native) {
|
|
59
70
|
const target = path.join(root, "node_modules");
|
|
60
71
|
const targetStat = lstatSync(target, { throwIfNoEntry: false });
|
|
61
72
|
if (
|
|
@@ -133,7 +144,7 @@ export function prepareVerificationEnvironmentIo(
|
|
|
133
144
|
});
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
|
-
if (local && stat) {
|
|
147
|
+
if (local && stat && !native) {
|
|
137
148
|
// Private copies keep package caches/writes away from consumer dependencies.
|
|
138
149
|
const copyEntry = (name: string) => {
|
|
139
150
|
checkCancelled();
|
|
@@ -177,8 +188,77 @@ export function prepareVerificationEnvironmentIo(
|
|
|
177
188
|
copyEntry(name);
|
|
178
189
|
}
|
|
179
190
|
|
|
191
|
+
if (native && stat) {
|
|
192
|
+
const target = path.join(root, "node_modules");
|
|
193
|
+
const targetStat = lstatSync(target, { throwIfNoEntry: false });
|
|
194
|
+
if (
|
|
195
|
+
!stat.isDirectory() ||
|
|
196
|
+
stat.isSymbolicLink() ||
|
|
197
|
+
(targetStat &&
|
|
198
|
+
(!targetStat.isDirectory() || targetStat.isSymbolicLink()))
|
|
199
|
+
)
|
|
200
|
+
throw new Error("dependency-path-unsafe");
|
|
201
|
+
const owner = realpathSync(dependencyOwner);
|
|
202
|
+
let count = 0;
|
|
203
|
+
const copy = (source: string, destination: string) => {
|
|
204
|
+
checkCancelled();
|
|
205
|
+
if (++count > 500_000) throw new Error("dependency-view-too-large");
|
|
206
|
+
const entry = lstatSync(source);
|
|
207
|
+
if (entry.isSymbolicLink()) {
|
|
208
|
+
const resolved = realpathSync(source);
|
|
209
|
+
if (!within(owner, resolved))
|
|
210
|
+
throw new Error("dependency-path-unsafe");
|
|
211
|
+
const candidate = path.join(root, path.relative(owner, resolved));
|
|
212
|
+
if (
|
|
213
|
+
!within(realpathSync(dependencies), resolved) &&
|
|
214
|
+
!lstatSync(candidate, { throwIfNoEntry: false })
|
|
215
|
+
)
|
|
216
|
+
throw new Error("workspace-dependency-missing");
|
|
217
|
+
const linked = lstatSync(resolved);
|
|
218
|
+
if (linked.isDirectory()) {
|
|
219
|
+
// Junctions do not require Windows Developer Mode/admin privileges.
|
|
220
|
+
symlinkSync(
|
|
221
|
+
candidate,
|
|
222
|
+
destination,
|
|
223
|
+
process.platform === "win32" ? "junction" : "dir",
|
|
224
|
+
);
|
|
225
|
+
} else if (linked.isFile()) {
|
|
226
|
+
const file = within(realpathSync(dependencies), resolved)
|
|
227
|
+
? resolved
|
|
228
|
+
: candidate;
|
|
229
|
+
cpSync(file, destination, { mode: constants.COPYFILE_FICLONE });
|
|
230
|
+
} else throw new Error("dependency-path-unsafe");
|
|
231
|
+
} else if (entry.isDirectory()) {
|
|
232
|
+
mkdirSync(destination, { recursive: true });
|
|
233
|
+
for (const name of readdirSync(source)) {
|
|
234
|
+
if (source === dependencies && CACHES.includes(name)) {
|
|
235
|
+
mkdirSync(path.join(destination, name));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
copy(path.join(source, name), path.join(destination, name));
|
|
239
|
+
}
|
|
240
|
+
} else if (entry.isFile())
|
|
241
|
+
cpSync(source, destination, { mode: constants.COPYFILE_FICLONE });
|
|
242
|
+
else throw new Error("dependency-path-unsafe");
|
|
243
|
+
};
|
|
244
|
+
removeEntry(target, checkCancelled);
|
|
245
|
+
copy(dependencies, target);
|
|
246
|
+
}
|
|
180
247
|
const sources = new Map<string, string>();
|
|
181
248
|
const bindings = runnerBindings.map((binding) => {
|
|
249
|
+
if (native) {
|
|
250
|
+
const translate = (value: string) =>
|
|
251
|
+
path.isAbsolute(value) && within(dependencyOwner, value)
|
|
252
|
+
? path.join(root, path.relative(dependencyOwner, value))
|
|
253
|
+
: value;
|
|
254
|
+
return {
|
|
255
|
+
...binding,
|
|
256
|
+
executablePath: translate(binding.executablePath),
|
|
257
|
+
...(binding.fixedArgs
|
|
258
|
+
? { fixedArgs: binding.fixedArgs.map(translate) }
|
|
259
|
+
: {}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
182
262
|
if (local || !binding.mountSource) return { ...binding };
|
|
183
263
|
const source = path.resolve(binding.mountSource);
|
|
184
264
|
const stat = lstatSync(source);
|
|
@@ -200,7 +280,15 @@ export function prepareVerificationEnvironmentIo(
|
|
|
200
280
|
path.posix.join(target, ...path.relative(source, file).split(path.sep));
|
|
201
281
|
return {
|
|
202
282
|
...binding,
|
|
203
|
-
|
|
283
|
+
// Preserve an installation's relative launcher link (e.g. bin/npm).
|
|
284
|
+
// Exposing lib/node_modules/npm/bin on PATH selects npm's Windows
|
|
285
|
+
// installer shell shim during a nested `npm run` on Linux.
|
|
286
|
+
executablePath: translate(
|
|
287
|
+
lstatSync(binding.executablePath).isSymbolicLink() &&
|
|
288
|
+
!path.isAbsolute(readlinkSync(binding.executablePath))
|
|
289
|
+
? binding.executablePath
|
|
290
|
+
: executable,
|
|
291
|
+
),
|
|
204
292
|
...(binding.fixedArgs
|
|
205
293
|
? {
|
|
206
294
|
fixedArgs: binding.fixedArgs.map((arg) =>
|
|
@@ -215,7 +303,7 @@ export function prepareVerificationEnvironmentIo(
|
|
|
215
303
|
checkCancelled();
|
|
216
304
|
return {
|
|
217
305
|
privateRoot,
|
|
218
|
-
mounts,
|
|
306
|
+
mounts: native ? [] : mounts,
|
|
219
307
|
bindings,
|
|
220
308
|
reportRoot: local ? privateRoot : "/cadence",
|
|
221
309
|
environment: {
|
|
@@ -228,6 +316,24 @@ export function prepareVerificationEnvironmentIo(
|
|
|
228
316
|
),
|
|
229
317
|
)
|
|
230
318
|
: {}),
|
|
319
|
+
...(native
|
|
320
|
+
? Object.fromEntries(
|
|
321
|
+
Object.entries(environment).filter(([key]) =>
|
|
322
|
+
["SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"].includes(
|
|
323
|
+
key.toUpperCase(),
|
|
324
|
+
),
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
: {}),
|
|
328
|
+
...(native
|
|
329
|
+
? {
|
|
330
|
+
USERPROFILE: path.join(privateRoot, "home"),
|
|
331
|
+
APPDATA: path.join(privateRoot, "home", "AppData", "Roaming"),
|
|
332
|
+
LOCALAPPDATA: path.join(privateRoot, "home", "AppData", "Local"),
|
|
333
|
+
TEMP: path.join(privateRoot, "tmp"),
|
|
334
|
+
TMP: path.join(privateRoot, "tmp"),
|
|
335
|
+
}
|
|
336
|
+
: {}),
|
|
231
337
|
CI: "1",
|
|
232
338
|
HOME: local ? path.join(privateRoot, "home") : "/cadence/home",
|
|
233
339
|
XDG_CACHE_HOME: local
|
|
@@ -249,13 +355,13 @@ export function prepareVerificationEnvironmentIo(
|
|
|
249
355
|
[
|
|
250
356
|
...bindings.filter((binding) => binding.mountSource),
|
|
251
357
|
...bindings.filter((binding) => !binding.mountSource),
|
|
252
|
-
].map((binding) =>
|
|
358
|
+
].map((binding) =>
|
|
359
|
+
(local ? path : path.posix).dirname(binding.executablePath),
|
|
360
|
+
),
|
|
253
361
|
),
|
|
254
362
|
...(local ? (environment.PATH ?? "").split(path.delimiter) : []),
|
|
255
|
-
"/usr/local/bin",
|
|
256
|
-
|
|
257
|
-
"/bin",
|
|
258
|
-
].join(":"),
|
|
363
|
+
...(native ? [] : ["/usr/local/bin", "/usr/bin", "/bin"]),
|
|
364
|
+
].join(local ? path.delimiter : ":"),
|
|
259
365
|
},
|
|
260
366
|
};
|
|
261
367
|
} catch (error) {
|
|
@@ -273,8 +379,11 @@ function removeEntry(file: string, checkCancelled: () => void): void {
|
|
|
273
379
|
checkCancelled();
|
|
274
380
|
const stat = lstatSync(file, { throwIfNoEntry: false });
|
|
275
381
|
if (!stat) return;
|
|
276
|
-
if (stat.isDirectory())
|
|
382
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
277
383
|
for (const name of readdirSync(file))
|
|
278
384
|
removeEntry(path.join(file, name), checkCancelled);
|
|
279
|
-
|
|
385
|
+
removePathSync(file, {
|
|
386
|
+
recursive: stat.isDirectory() && !stat.isSymbolicLink(),
|
|
387
|
+
force: true,
|
|
388
|
+
});
|
|
280
389
|
}
|
|
@@ -23,6 +23,20 @@ export async function captureVerificationEnvironmentIdentity(
|
|
|
23
23
|
signal?: AbortSignal,
|
|
24
24
|
): Promise<string> {
|
|
25
25
|
const roots = [path.join(dependencyOwner, "node_modules")];
|
|
26
|
+
const unavailable: string[] = [];
|
|
27
|
+
const profile = executionProfile();
|
|
28
|
+
if (profile.mode === "host-trusted")
|
|
29
|
+
roots.push(
|
|
30
|
+
path.resolve(profile.windowsHelperPath ?? ""),
|
|
31
|
+
path.resolve(`${profile.windowsHelperPath}.json`),
|
|
32
|
+
);
|
|
33
|
+
else roots.push(path.resolve(profile.bwrapPath));
|
|
34
|
+
if (
|
|
35
|
+
profile.mode === "host-trusted" &&
|
|
36
|
+
process.env.ComSpec &&
|
|
37
|
+
path.isAbsolute(process.env.ComSpec)
|
|
38
|
+
)
|
|
39
|
+
roots.push(process.env.ComSpec);
|
|
26
40
|
const node = resolveVerificationRunner("node");
|
|
27
41
|
if (node) roots.push(realpathSync(node.executablePath));
|
|
28
42
|
for (const verification of verifications) {
|
|
@@ -32,7 +46,22 @@ export async function captureVerificationEnvironmentIdentity(
|
|
|
32
46
|
verification,
|
|
33
47
|
{ executionWritePaths: VERIFICATION_CONFIGURATION_PATHS },
|
|
34
48
|
);
|
|
35
|
-
if (!capability.ok)
|
|
49
|
+
if (!capability.ok) {
|
|
50
|
+
if (
|
|
51
|
+
![
|
|
52
|
+
"runner-missing",
|
|
53
|
+
"local-executable-missing",
|
|
54
|
+
"script-missing",
|
|
55
|
+
"script-command-mismatch",
|
|
56
|
+
"verification-config-mismatch",
|
|
57
|
+
].includes(capability.diagnostic.code)
|
|
58
|
+
)
|
|
59
|
+
throw new Error("verification-environment-unavailable");
|
|
60
|
+
// Missing task capabilities are observable identities. The exact adapter
|
|
61
|
+
// still rejects that task; independent tasks may use their own runners.
|
|
62
|
+
unavailable.push(capability.diagnostic.code);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
36
65
|
roots.push(
|
|
37
66
|
...verificationRunnerFiles(
|
|
38
67
|
dependencyOwner,
|
|
@@ -50,10 +79,22 @@ export async function captureVerificationEnvironmentIdentity(
|
|
|
50
79
|
args: [roots],
|
|
51
80
|
signal,
|
|
52
81
|
});
|
|
53
|
-
const profile = executionProfile();
|
|
54
82
|
return createHash("sha256")
|
|
55
83
|
.update(installed)
|
|
84
|
+
.update(JSON.stringify(unavailable.sort()))
|
|
56
85
|
.update(JSON.stringify(profile))
|
|
86
|
+
.update(
|
|
87
|
+
profile.mode === "host-trusted"
|
|
88
|
+
? JSON.stringify([
|
|
89
|
+
process.platform,
|
|
90
|
+
process.arch,
|
|
91
|
+
process.versions.node,
|
|
92
|
+
["PATH", "SystemRoot", "WINDIR", "ComSpec", "PATHEXT"].map(
|
|
93
|
+
(name) => [name, process.env[name] ?? null],
|
|
94
|
+
),
|
|
95
|
+
])
|
|
96
|
+
: "",
|
|
97
|
+
)
|
|
57
98
|
.update(
|
|
58
99
|
JSON.stringify(
|
|
59
100
|
profile.inheritEnvironment.map((name) => [
|
|
@@ -98,7 +139,13 @@ export async function prepareVerificationEnvironment(
|
|
|
98
139
|
runnerBindings,
|
|
99
140
|
profile,
|
|
100
141
|
Object.fromEntries(
|
|
101
|
-
[
|
|
142
|
+
[
|
|
143
|
+
"PATH",
|
|
144
|
+
...(profile.mode === "host-trusted"
|
|
145
|
+
? ["SystemRoot", "WINDIR", "ComSpec", "PATHEXT"]
|
|
146
|
+
: []),
|
|
147
|
+
...profile.inheritEnvironment,
|
|
148
|
+
].flatMap((name) =>
|
|
102
149
|
process.env[name] === undefined ? [] : [[name, process.env[name]]],
|
|
103
150
|
),
|
|
104
151
|
),
|