@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.
Files changed (45) hide show
  1. package/README.md +25 -4
  2. package/config/.env.example +6 -0
  3. package/config/plan-draft.example.json +3 -0
  4. package/config/plan-draft.multiple-tasks.example.json +30 -0
  5. package/package.json +1 -1
  6. package/prompts/abel-design.md +33 -3
  7. package/prompts/abel-implement.md +36 -4
  8. package/src/apply-transaction.ts +28 -8
  9. package/src/build-windows-job.mjs +95 -0
  10. package/src/candidate-workspace.ts +6 -0
  11. package/src/change-verification.ts +764 -67
  12. package/src/contracts.ts +8 -0
  13. package/src/delivery-compiler.ts +452 -9
  14. package/src/design-control.ts +2 -2
  15. package/src/design-diagnostics.ts +6 -4
  16. package/src/durable-contracts.ts +21 -3
  17. package/src/durable-workflow.ts +55 -40
  18. package/src/execution-profile.ts +28 -2
  19. package/src/execution-retention.ts +77 -0
  20. package/src/implement-continuation.ts +559 -0
  21. package/src/implement-graph.ts +114 -0
  22. package/src/implement-plan.ts +2 -0
  23. package/src/index.ts +151 -1
  24. package/src/isolation-backend.ts +2 -1
  25. package/src/operator-cli.mjs +664 -104
  26. package/src/operator-tools.ts +7 -1
  27. package/src/package-state.ts +11 -0
  28. package/src/package-verification.ts +66 -18
  29. package/src/package-workflow.ts +6 -0
  30. package/src/phase-execution.ts +18 -7
  31. package/src/plan-draft-summary.ts +385 -7
  32. package/src/plan-draft.ts +9 -0
  33. package/src/remove-path.ts +32 -0
  34. package/src/subagent-activity.ts +3 -1
  35. package/src/verification-capability.ts +18 -6
  36. package/src/verification-environment-io.ts +123 -14
  37. package/src/verification-environment.ts +50 -3
  38. package/src/windows-job-backend.ts +419 -0
  39. package/src/windows-job.c +149 -0
  40. package/src/workflow-policy.ts +296 -5
  41. package/src/workflow-recovery-policy.ts +2 -0
  42. package/src/workflow-state-machine.ts +314 -80
  43. package/src/workflow-status.ts +251 -59
  44. package/src/workspace-io-entry.mjs +2 -2
  45. 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 = () => rmSync(privateRoot, { recursive: true, force: true });
38
+ const cleanup = () =>
39
+ removePathSync(privateRoot, { recursive: true, force: true });
38
40
  try {
39
- const local = profile.mode === "local-trusted";
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
- executablePath: translate(executable),
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) => path.posix.dirname(binding.executablePath)),
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
- "/usr/bin",
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
- rmSync(file, { recursive: stat.isDirectory(), force: true });
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) throw new Error("verification-environment-unavailable");
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
- ["PATH", ...profile.inheritEnvironment].flatMap((name) =>
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
  ),