@abelxiaoxing/cadence 2.0.4 → 2.0.6

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.
@@ -3,11 +3,48 @@
3
3
 
4
4
  // src/service/cli.ts
5
5
  import { spawn } from "node:child_process";
6
- import { existsSync, mkdirSync as mkdirSync4 } from "node:fs";
6
+ import { existsSync, mkdirSync as mkdirSync5 } from "node:fs";
7
7
  import { request } from "node:http";
8
- import { dirname as dirname8, join as join8, resolve as resolve5 } from "node:path";
8
+ import { dirname as dirname8, join as join10, resolve as resolve5 } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
 
11
+ // src/install-config.mjs
12
+ import { closeSync, constants, mkdirSync, openSync, writeSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import path from "node:path";
15
+ var CONFIG_TEMPLATE = `# Cadence research configuration. Keep this file local and never commit it.
16
+ # Context7 works anonymously with its default URL.
17
+ # CONTEXT7_API_URL=https://context7.com/api/v2
18
+ # CONTEXT7_API_KEY=
19
+
20
+ # Required when using the Grok search skill.
21
+ GROK_API_URL=https://api.x.ai/v1
22
+ GROK_API_KEY=
23
+
24
+ # Optional Tavily integration.
25
+ # TAVILY_ENABLED=true
26
+ # TAVILY_API_URL=https://api.tavily.com
27
+ # TAVILY_API_KEY=
28
+ `;
29
+ function ensureUserConfig(home = homedir()) {
30
+ const directory = path.join(home, ".pi", "agent", "cadence");
31
+ const file = path.join(directory, ".env");
32
+ mkdirSync(directory, { recursive: true, mode: 448 });
33
+ try {
34
+ const fd = openSync(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
35
+ try {
36
+ writeSync(fd, CONFIG_TEMPLATE, undefined, "utf8");
37
+ } finally {
38
+ closeSync(fd);
39
+ }
40
+ return { created: true, path: file };
41
+ } catch (error) {
42
+ if (error?.code === "EEXIST")
43
+ return { created: false, path: file };
44
+ throw error;
45
+ }
46
+ }
47
+
11
48
  // src/task-types.ts
12
49
  var TASK_SCHEMA = "cadence-workflow-assistant/1";
13
50
  var MAX_RECORD_BYTES = 128 * 1024;
@@ -2049,13 +2086,13 @@ class CaptureBudgetLease {
2049
2086
  import { spawnSync } from "node:child_process";
2050
2087
  import { createHash, randomUUID } from "node:crypto";
2051
2088
  import {
2052
- closeSync,
2053
- constants,
2089
+ closeSync as closeSync2,
2090
+ constants as constants2,
2054
2091
  fstatSync,
2055
2092
  fsyncSync,
2056
2093
  lstatSync,
2057
- mkdirSync,
2058
- openSync,
2094
+ mkdirSync as mkdirSync2,
2095
+ openSync as openSync2,
2059
2096
  readdirSync,
2060
2097
  readSync,
2061
2098
  realpathSync,
@@ -2063,7 +2100,7 @@ import {
2063
2100
  unlinkSync,
2064
2101
  writeFileSync
2065
2102
  } from "node:fs";
2066
- import { homedir, hostname } from "node:os";
2103
+ import { homedir as homedir2, hostname } from "node:os";
2067
2104
  import {
2068
2105
  dirname,
2069
2106
  isAbsolute,
@@ -2100,8 +2137,8 @@ function parseRecord(data) {
2100
2137
  function absent(error) {
2101
2138
  return object(error) && error.code === "ENOENT";
2102
2139
  }
2103
- function safeDirectory(path, create = false, inspectExisting) {
2104
- const full = resolve(path);
2140
+ function safeDirectory(path2, create = false, inspectExisting) {
2141
+ const full = resolve(path2);
2105
2142
  const root = parse(full).root;
2106
2143
  if (inspectExisting) {
2107
2144
  const st = lstatSync(root);
@@ -2124,7 +2161,7 @@ function safeDirectory(path, create = false, inspectExisting) {
2124
2161
  if (!create)
2125
2162
  return false;
2126
2163
  try {
2127
- mkdirSync(at, { mode: 448 });
2164
+ mkdirSync2(at, { mode: 448 });
2128
2165
  } catch (mkdirError) {
2129
2166
  if (!object(mkdirError) || mkdirError.code !== "EEXIST")
2130
2167
  throw mkdirError;
@@ -2154,15 +2191,15 @@ function retryWindowsFileOperation(operation, recheck) {
2154
2191
  }
2155
2192
  }
2156
2193
  }
2157
- function readBounded(path, max, packaged = false) {
2158
- if (!safeDirectory(dirname(path)))
2194
+ function readBounded(path2, max, packaged = false) {
2195
+ if (!safeDirectory(dirname(path2)))
2159
2196
  return;
2160
2197
  let fd;
2161
2198
  try {
2162
- const st = lstatSync(path);
2199
+ const st = lstatSync(path2);
2163
2200
  if (!st.isFile() || st.isSymbolicLink() || !packaged && st.nlink !== 1 || st.size > max)
2164
2201
  throw new Error("Unsafe or oversized file");
2165
- fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
2202
+ fd = openSync2(path2, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
2166
2203
  } catch (error) {
2167
2204
  if (absent(error))
2168
2205
  return;
@@ -2184,7 +2221,7 @@ function readBounded(path, max, packaged = false) {
2184
2221
  throw new Error("Oversized file");
2185
2222
  return buffer.subarray(0, size);
2186
2223
  } finally {
2187
- closeSync(fd);
2224
+ closeSync2(fd);
2188
2225
  }
2189
2226
  }
2190
2227
  function gitEnvironment() {
@@ -2219,7 +2256,7 @@ function defaultStateHome() {
2219
2256
  const candidate = process.platform === "win32" ? process.env.LOCALAPPDATA : process.env.XDG_STATE_HOME;
2220
2257
  if (candidate && !isAbsolute(candidate))
2221
2258
  throw new Error("State home must be absolute");
2222
- return candidate ?? join(homedir(), process.platform === "win32" ? "AppData/Local" : ".local/state");
2259
+ return candidate ?? join(homedir2(), process.platform === "win32" ? "AppData/Local" : ".local/state");
2223
2260
  }
2224
2261
  function recordText(record) {
2225
2262
  return `${JSON.stringify(record, null, 2)}
@@ -2228,8 +2265,8 @@ function recordText(record) {
2228
2265
  function contentIdentity(text2) {
2229
2266
  return createHash("sha256").update(text2).digest("hex");
2230
2267
  }
2231
- function canonicalAmbientRoot(path) {
2232
- const full = resolve(path);
2268
+ function canonicalAmbientRoot(path2) {
2269
+ const full = resolve(path2);
2233
2270
  try {
2234
2271
  return realpathSync(full);
2235
2272
  } catch (error) {
@@ -2251,8 +2288,8 @@ function canonicalAmbientRoot(path) {
2251
2288
  function privateRootFor(canonicalRoot, stateHome) {
2252
2289
  return join(canonicalAmbientRoot(stateHome), "cadence-assistant-v1", createHash("sha256").update(canonicalRoot).digest("hex"));
2253
2290
  }
2254
- function contains(root, path) {
2255
- const within = relative(root, resolve(path));
2291
+ function contains(root, path2) {
2292
+ const within = relative(root, resolve(path2));
2256
2293
  return !within || !within.startsWith(`..${sep}`) && within !== ".." && !isAbsolute(within);
2257
2294
  }
2258
2295
  function stateHomeInside(root, stateHome = defaultStateHome()) {
@@ -2260,7 +2297,7 @@ function stateHomeInside(root, stateHome = defaultStateHome()) {
2260
2297
  return contains(canonical, privateRootFor(canonical, stateHome));
2261
2298
  }
2262
2299
  var LANDING_WORKSPACE = "cadence";
2263
- function effectiveWorkspace(requested, stateHome, home = homedir()) {
2300
+ function effectiveWorkspace(requested, stateHome, home = homedir2()) {
2264
2301
  if (!stateHomeInside(resolveWorkspace(requested).root, stateHome))
2265
2302
  return { workspace: requested, redirected: false };
2266
2303
  return { workspace: join(home, LANDING_WORKSPACE), redirected: true };
@@ -2282,20 +2319,20 @@ class TaskStore {
2282
2319
  }
2283
2320
  inspectPrivate() {
2284
2321
  const namespace = dirname(this.privateRoot);
2285
- for (const path of [namespace, this.privateRoot]) {
2286
- if (!safeDirectory(path))
2322
+ for (const path2 of [namespace, this.privateRoot]) {
2323
+ if (!safeDirectory(path2))
2287
2324
  continue;
2288
- const st = lstatSync(path);
2325
+ const st = lstatSync(path2);
2289
2326
  if (process.platform !== "win32" && ((st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
2290
2327
  throw new Error("Private state permissions refused");
2291
2328
  }
2292
2329
  }
2293
- inspectPrivateFile(path, max) {
2330
+ inspectPrivateFile(path2, max) {
2294
2331
  this.inspectPrivate();
2295
2332
  if (!safeDirectory(this.privateRoot))
2296
2333
  return;
2297
2334
  try {
2298
- const st = lstatSync(path);
2335
+ const st = lstatSync(path2);
2299
2336
  if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > max || process.platform !== "win32" && ((st.mode & 63) !== 0 || process.getuid && st.uid !== process.getuid()))
2300
2337
  throw new Error("Unsafe private state file");
2301
2338
  } catch (error) {
@@ -2326,7 +2363,7 @@ class TaskStore {
2326
2363
  };
2327
2364
  let fd;
2328
2365
  try {
2329
- fd = retryWindowsFileOperation(() => openSync(this.lockPath, "wx", 384));
2366
+ fd = retryWindowsFileOperation(() => openSync2(this.lockPath, "wx", 384));
2330
2367
  } catch {
2331
2368
  throw new Error("Checkout busy or unresolved lock; inspect and explicitly recover only a dead owner");
2332
2369
  }
@@ -2334,7 +2371,7 @@ class TaskStore {
2334
2371
  writeFileSync(fd, JSON.stringify(lock));
2335
2372
  fsyncSync(fd);
2336
2373
  } finally {
2337
- closeSync(fd);
2374
+ closeSync2(fd);
2338
2375
  }
2339
2376
  return lock;
2340
2377
  }
@@ -2415,12 +2452,12 @@ class TaskStore {
2415
2452
  if (!current && readdirSync(dir).length)
2416
2453
  throw new Error("Unknown task directory");
2417
2454
  const temporary = join(dir, `task.${randomUUID()}.tmp`);
2418
- const fd = openSync(temporary, "wx", 384);
2455
+ const fd = openSync2(temporary, "wx", 384);
2419
2456
  try {
2420
2457
  writeFileSync(fd, text2);
2421
2458
  fsyncSync(fd);
2422
2459
  } finally {
2423
- closeSync(fd);
2460
+ closeSync2(fd);
2424
2461
  }
2425
2462
  this.assertOwner(lock);
2426
2463
  const latest = readBounded(join(dir, "task.json"), MAX_RECORD_BYTES);
@@ -2475,7 +2512,7 @@ var TASK_CONFIG_SCHEMA = "cadence-assistant-checks/1";
2475
2512
  import { spawnSync as spawnSync2 } from "node:child_process";
2476
2513
  import {
2477
2514
  lstatSync as lstatSync2,
2478
- mkdirSync as mkdirSync2,
2515
+ mkdirSync as mkdirSync3,
2479
2516
  readFileSync,
2480
2517
  realpathSync as realpathSync2,
2481
2518
  renameSync as renameSync2,
@@ -2483,25 +2520,39 @@ import {
2483
2520
  statSync,
2484
2521
  writeFileSync as writeFileSync2
2485
2522
  } from "node:fs";
2486
- import { homedir as homedir2 } from "node:os";
2487
- import { delimiter, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
2523
+ import { homedir as homedir3 } from "node:os";
2524
+ import {
2525
+ delimiter,
2526
+ dirname as dirname2,
2527
+ isAbsolute as isAbsolute2,
2528
+ join as join2,
2529
+ resolve as resolve2,
2530
+ sep as sep2,
2531
+ win32
2532
+ } from "node:path";
2488
2533
  var UNIT = "cadence.service";
2489
2534
  var MARKER = "# Generated by @abelxiaoxing/cadence";
2490
- function hostSystem(root) {
2535
+ function wscript(env) {
2536
+ const root = env.SystemRoot;
2537
+ return win32.join(root && win32.isAbsolute(root) ? root : "C:\\Windows", "System32", "wscript.exe");
2538
+ }
2539
+ function hostSystem(root, owner) {
2491
2540
  const run = (command, args) => {
2492
2541
  const r = spawnSync2(command, args, {
2493
2542
  encoding: "utf8",
2494
- stdio: ["ignore", "pipe", "ignore"]
2543
+ stdio: ["ignore", "pipe", "ignore"],
2544
+ windowsHide: true
2495
2545
  });
2496
2546
  return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
2497
2547
  };
2498
2548
  return {
2499
2549
  platform: process.platform,
2500
2550
  env: process.env,
2501
- home: homedir2(),
2551
+ home: homedir3(),
2502
2552
  node: process.execPath,
2503
2553
  root,
2504
2554
  unit: UNIT,
2555
+ uid: process.getuid?.(),
2505
2556
  systemctl: (args) => run("systemctl", ["--user", ...args]),
2506
2557
  linger: () => {
2507
2558
  const r = run("loginctl", [
@@ -2528,7 +2579,18 @@ function hostSystem(root) {
2528
2579
  "false"
2529
2580
  ], { stdio: "ignore", timeout: 20000 });
2530
2581
  return !r.error && r.status === 0;
2531
- }
2582
+ },
2583
+ launchctl: (args) => run("launchctl", args),
2584
+ reg: (args) => run("reg", args),
2585
+ launch: (path2) => {
2586
+ const r = spawnSync2(wscript(process.env), [path2], {
2587
+ stdio: "ignore",
2588
+ windowsHide: true,
2589
+ timeout: 20000
2590
+ });
2591
+ return !r.error && r.status === 0;
2592
+ },
2593
+ ...owner
2532
2594
  };
2533
2595
  }
2534
2596
  function expandHome(value, home) {
@@ -2602,14 +2664,14 @@ var argument = (value) => quoted(value).replaceAll("$", "$$$$");
2602
2664
  function buildUnit(u) {
2603
2665
  const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
2604
2666
  return [
2605
- `${MARKER} on Pi package install/update; upgrades rewrite this file.`,
2667
+ `${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
2606
2668
  "# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
2607
2669
  "[Unit]",
2608
2670
  "Description=Cadence shared task service and WebUI",
2609
2671
  "Documentation=https://github.com/abelxiaoxing/cadence/blob/main/docs/service-management.md",
2610
2672
  `ConditionPathExists=${specifiers(u.cli)}`,
2611
2673
  `ConditionPathExists=${specifiers(join2(u.agentDir, "auth.json"))}`,
2612
- ...u.sdk.map((path) => `ConditionPathExists=${specifiers(path)}`),
2674
+ ...u.sdk.map((path2) => `ConditionPathExists=${specifiers(path2)}`),
2613
2675
  "StartLimitIntervalSec=300",
2614
2676
  "StartLimitBurst=3",
2615
2677
  "",
@@ -2630,67 +2692,76 @@ function buildUnit(u) {
2630
2692
  ].join(`
2631
2693
  `);
2632
2694
  }
2633
- function readGenerated(path) {
2695
+ function readGenerated(path2, marker) {
2634
2696
  try {
2635
- if (!lstatSync2(path).isFile())
2697
+ if (!lstatSync2(path2).isFile())
2636
2698
  return null;
2637
- const text2 = readFileSync(path, "utf8");
2638
- return text2.startsWith(MARKER) ? text2 : null;
2699
+ const bytes = readFileSync(path2);
2700
+ const text2 = bytes[0] === 255 && bytes[1] === 254 ? bytes.subarray(2).toString("utf16le") : bytes.toString("utf8");
2701
+ return text2.startsWith(marker) ? text2 : null;
2639
2702
  } catch (error) {
2640
2703
  if (error.code === "ENOENT")
2641
2704
  return;
2642
2705
  throw error;
2643
2706
  }
2644
2707
  }
2645
- function generatedWorkspace(text2) {
2708
+ function unitWorkspace(text2) {
2646
2709
  return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
2647
2710
  }
2648
- function directory(path) {
2711
+ function directory(path2) {
2649
2712
  try {
2650
- return isAbsolute2(path) && statSync(path).isDirectory();
2713
+ return isAbsolute2(path2) && statSync(path2).isDirectory();
2651
2714
  } catch {
2652
2715
  return false;
2653
2716
  }
2654
2717
  }
2655
- function unitStateHome(s) {
2656
- const state = s.env.XDG_STATE_HOME;
2657
- return state && isAbsolute2(state) ? state : join2(s.home, ".local", "state");
2718
+ function serviceStateHome(s) {
2719
+ const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
2720
+ return state && isAbsolute2(state) ? state : join2(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
2658
2721
  }
2659
- function selectWorkspace(s, requested, previous) {
2722
+ function selectWorkspace(s, requested, kept) {
2660
2723
  let chosen;
2661
2724
  if (requested) {
2662
2725
  chosen = expandHome(requested, s.home);
2663
2726
  if (!directory(chosen))
2664
2727
  throw new ContractError("invalid");
2665
2728
  chosen = resolve2(chosen);
2666
- } else {
2667
- const kept = generatedWorkspace(previous);
2668
- if (kept && directory(kept))
2669
- chosen = kept;
2670
- }
2671
- const landing = chosen ? effectiveWorkspace(chosen, unitStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
2729
+ } else if (kept && directory(kept))
2730
+ chosen = kept;
2731
+ const landing = chosen ? effectiveWorkspace(chosen, serviceStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
2672
2732
  if (landing.workspace !== chosen)
2673
- mkdirSync2(landing.workspace, { recursive: true });
2733
+ mkdirSync3(landing.workspace, { recursive: true });
2674
2734
  return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
2675
2735
  }
2676
- function write(path, content) {
2677
- mkdirSync2(dirname2(path), { recursive: true });
2678
- const temporary = `${path}.${process.pid}.tmp`;
2736
+ function write(path2, content) {
2737
+ mkdirSync3(dirname2(path2), { recursive: true });
2738
+ const temporary = `${path2}.${process.pid}.tmp`;
2679
2739
  try {
2680
2740
  writeFileSync2(temporary, content, { mode: 420, flag: "wx" });
2681
- renameSync2(temporary, path);
2741
+ renameSync2(temporary, path2);
2682
2742
  } finally {
2683
2743
  rmSync(temporary, { force: true });
2684
2744
  }
2685
2745
  }
2686
- function regularFile(path) {
2746
+ function regularFile(path2) {
2687
2747
  try {
2688
- return statSync(path).isFile();
2748
+ return statSync(path2).isFile();
2689
2749
  } catch {
2690
2750
  return false;
2691
2751
  }
2692
2752
  }
2693
- function bootStart(s, attempt, interactive) {
2753
+ async function settle(s, workspace) {
2754
+ if (!workspace)
2755
+ return;
2756
+ const status = await s.stopOwner(workspace);
2757
+ if (status === "stopped")
2758
+ return;
2759
+ return {
2760
+ status: status === "quarantined" || status === "unavailable" ? status : "unconfirmed",
2761
+ workspace
2762
+ };
2763
+ }
2764
+ function bootStart(s, attempt) {
2694
2765
  const current = s.linger();
2695
2766
  if (current === undefined)
2696
2767
  return {};
@@ -2698,23 +2769,25 @@ function bootStart(s, attempt, interactive) {
2698
2769
  return { linger: "on" };
2699
2770
  if (!attempt)
2700
2771
  return { linger: "off" };
2701
- return { linger: s.enableLinger(interactive) ? "enabled" : "refused" };
2772
+ return {
2773
+ linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
2774
+ };
2702
2775
  }
2703
2776
  function configureAutostart(s, mode, requested, linger = false) {
2704
2777
  if (s.platform !== "linux")
2705
- return { status: "skipped" };
2706
- if (mode === "refresh" && (s.env.CADENCE_AUTOSTART === "0" || !piUserInstall(s)))
2707
- return { status: "skipped" };
2778
+ return { status: "unsupported" };
2708
2779
  if (s.systemctl(["show-environment"]).status !== 0)
2709
2780
  return { status: "unavailable" };
2710
- const path = unitPath(s);
2711
- const previous = readGenerated(path);
2781
+ const path2 = unitPath(s);
2782
+ const previous = readGenerated(path2, MARKER);
2712
2783
  if (previous === null)
2713
- return { status: "foreign", unit: path };
2784
+ return { status: "foreign", unit: path2 };
2785
+ if (mode === "restart" && previous === undefined)
2786
+ return { status: "absent" };
2714
2787
  const sdk = sdkPackages(s.root);
2715
2788
  if (!sdk)
2716
2789
  return { status: "missing-pi-sdk" };
2717
- const { workspace, redirectedFrom } = selectWorkspace(s, requested ?? s.env.CADENCE_WORKSPACE, previous);
2790
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
2718
2791
  const agentDir = agentDirectory(s);
2719
2792
  const content = buildUnit({
2720
2793
  node: s.node,
@@ -2728,60 +2801,74 @@ function configureAutostart(s, mode, requested, linger = false) {
2728
2801
  const active = s.systemctl(["is-active", "--quiet", s.unit]).status === 0;
2729
2802
  if (active)
2730
2803
  s.systemctl(["stop", s.unit]);
2731
- write(path, content);
2804
+ write(path2, content);
2732
2805
  s.systemctl(["daemon-reload"]);
2733
2806
  const result = {
2734
- unit: path,
2807
+ unit: path2,
2735
2808
  workspace,
2736
2809
  ...redirectedFrom ? { redirectedFrom } : {}
2737
2810
  };
2738
2811
  if (enabled.startsWith("masked"))
2739
2812
  return { status: "masked", ...result };
2740
- const wanted = mode === "install" || previous === undefined || enabled === "enabled";
2741
- if (wanted && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2813
+ if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
2742
2814
  return { status: "failed", ...result };
2743
- if (!wanted && !active)
2744
- return { status: "disabled", ...result };
2745
- const boot = () => wanted ? bootStart(s, mode === "install" ? linger : previous === undefined && s.env.CADENCE_LINGER !== "0", mode === "install") : {};
2815
+ const boot = () => mode === "install" ? bootStart(s, linger) : {};
2746
2816
  if (!regularFile(join2(agentDir, "auth.json")))
2747
2817
  return { status: "waiting-for-pi-login", ...result, ...boot() };
2748
2818
  s.systemctl(["reset-failed", s.unit]);
2749
2819
  const started = s.systemctl(["start", s.unit]).status === 0;
2750
2820
  return { status: started ? "started" : "failed", ...result, ...boot() };
2751
2821
  }
2822
+ function stopAutostart(s) {
2823
+ if (s.platform !== "linux")
2824
+ return { status: "unsupported" };
2825
+ const path2 = unitPath(s);
2826
+ const previous = readGenerated(path2, MARKER);
2827
+ if (previous === null)
2828
+ return { status: "foreign", unit: path2 };
2829
+ if (previous === undefined)
2830
+ return { status: "absent" };
2831
+ if (s.systemctl(["show-environment"]).status !== 0)
2832
+ return { status: "unavailable", unit: path2 };
2833
+ return {
2834
+ status: s.systemctl(["stop", s.unit]).status === 0 ? "stopped" : "failed",
2835
+ unit: path2,
2836
+ workspace: unitWorkspace(previous)
2837
+ };
2838
+ }
2752
2839
  function removeAutostart(s) {
2753
2840
  if (s.platform !== "linux")
2754
- return { status: "skipped" };
2755
- const path = unitPath(s);
2756
- const previous = readGenerated(path);
2841
+ return { status: "unsupported" };
2842
+ const path2 = unitPath(s);
2843
+ const previous = readGenerated(path2, MARKER);
2757
2844
  if (previous === null)
2758
- return { status: "foreign", unit: path };
2845
+ return { status: "foreign", unit: path2 };
2759
2846
  if (previous === undefined)
2760
2847
  return { status: "absent" };
2761
2848
  if (s.systemctl(["show-environment"]).status !== 0)
2762
- return { status: "unavailable", unit: path };
2849
+ return { status: "unavailable", unit: path2 };
2763
2850
  if (s.systemctl(["disable", "--now", s.unit]).status !== 0)
2764
- return { status: "failed", unit: path };
2765
- rmSync(path);
2851
+ return { status: "failed", unit: path2 };
2852
+ rmSync(path2);
2766
2853
  s.systemctl(["daemon-reload"]);
2767
2854
  s.systemctl(["reset-failed", s.unit]);
2768
- return { status: "removed", unit: path };
2855
+ return { status: "removed", unit: path2 };
2769
2856
  }
2770
2857
  function autostartStatus(s) {
2771
2858
  if (s.platform !== "linux")
2772
- return { status: "skipped" };
2773
- const path = unitPath(s);
2774
- const previous = readGenerated(path);
2859
+ return { status: "unsupported" };
2860
+ const path2 = unitPath(s);
2861
+ const previous = readGenerated(path2, MARKER);
2775
2862
  if (previous === null)
2776
- return { status: "foreign", unit: path };
2863
+ return { status: "foreign", unit: path2 };
2777
2864
  if (previous === undefined)
2778
2865
  return { status: "absent" };
2779
2866
  return {
2780
2867
  status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
2781
2868
  enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
2782
- unit: path,
2783
- workspace: generatedWorkspace(previous),
2784
- ...bootStart(s, false, false)
2869
+ unit: path2,
2870
+ workspace: unitWorkspace(previous),
2871
+ ...bootStart(s, false)
2785
2872
  };
2786
2873
  }
2787
2874
 
@@ -2794,13 +2881,13 @@ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join4, relative
2794
2881
  // src/service/private-files.ts
2795
2882
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
2796
2883
  import {
2797
- closeSync as closeSync2,
2798
- constants as constants2,
2884
+ closeSync as closeSync3,
2885
+ constants as constants3,
2799
2886
  fstatSync as fstatSync2,
2800
2887
  fsyncSync as fsyncSync2,
2801
2888
  lstatSync as lstatSync3,
2802
- mkdirSync as mkdirSync3,
2803
- openSync as openSync2,
2889
+ mkdirSync as mkdirSync4,
2890
+ openSync as openSync3,
2804
2891
  readSync as readSync2,
2805
2892
  renameSync as renameSync3,
2806
2893
  unlinkSync as unlinkSync2,
@@ -2848,25 +2935,25 @@ class DirectoryWalks {
2848
2935
  this.walked.clear();
2849
2936
  return false;
2850
2937
  }
2851
- remember(path, st) {
2938
+ remember(path2, st) {
2852
2939
  if (this.open())
2853
- this.walked.set(path, { dev: st.dev, ino: st.ino });
2940
+ this.walked.set(path2, { dev: st.dev, ino: st.ino });
2854
2941
  }
2855
- reuse(path, ownerOnly = true) {
2856
- const walked = this.open() ? this.walked.get(path) : undefined;
2942
+ reuse(path2, ownerOnly = true) {
2943
+ const walked = this.open() ? this.walked.get(path2) : undefined;
2857
2944
  if (!walked)
2858
2945
  return false;
2859
2946
  let st;
2860
2947
  try {
2861
- st = lstatSync3(path);
2948
+ st = lstatSync3(path2);
2862
2949
  } catch (error) {
2863
2950
  if (!missing(error))
2864
2951
  throw error;
2865
- this.walked.delete(path);
2952
+ this.walked.delete(path2);
2866
2953
  return false;
2867
2954
  }
2868
2955
  if (!st.isDirectory() || st.isSymbolicLink() || st.dev !== walked.dev || st.ino !== walked.ino) {
2869
- this.walked.delete(path);
2956
+ this.walked.delete(path2);
2870
2957
  return false;
2871
2958
  }
2872
2959
  privateMode(st, ownerOnly);
@@ -2881,17 +2968,17 @@ class PrivateFiles {
2881
2968
  this.namespace = namespace;
2882
2969
  this.ownerOnly = ownerOnly;
2883
2970
  }
2884
- directory(path, create = false, walks) {
2885
- const tail = relative2(this.namespace, path);
2971
+ directory(path2, create = false, walks) {
2972
+ const tail = relative2(this.namespace, path2);
2886
2973
  if (isAbsolute3(tail) || tail === ".." || tail.startsWith(`..${sep3}`))
2887
2974
  refused();
2888
2975
  if (!create) {
2889
- const full = resolve3(path);
2976
+ const full = resolve3(path2);
2890
2977
  if (walks?.reuse(full, this.ownerOnly))
2891
2978
  return true;
2892
2979
  const namespace = resolve3(this.namespace);
2893
2980
  let inside = false, last;
2894
- const found = safeDirectory(path, false, (at2, st) => {
2981
+ const found = safeDirectory(path2, false, (at2, st) => {
2895
2982
  if (at2 === namespace)
2896
2983
  inside = true;
2897
2984
  if (inside)
@@ -2915,7 +3002,7 @@ class PrivateFiles {
2915
3002
  if (!missing(error))
2916
3003
  throw error;
2917
3004
  try {
2918
- mkdirSync3(at, { mode: 448 });
3005
+ mkdirSync4(at, { mode: 448 });
2919
3006
  } catch (mkdirError) {
2920
3007
  if (!(mkdirError instanceof Error && ("code" in mkdirError) && mkdirError.code === "EEXIST"))
2921
3008
  throw mkdirError;
@@ -2928,12 +3015,12 @@ class PrivateFiles {
2928
3015
  }
2929
3016
  return true;
2930
3017
  }
2931
- read(path, maximum = METADATA_BYTES, walks) {
2932
- if (!this.directory(dirname3(path), false, walks))
3018
+ read(path2, maximum = METADATA_BYTES, walks) {
3019
+ if (!this.directory(dirname3(path2), false, walks))
2933
3020
  return;
2934
3021
  let before;
2935
3022
  try {
2936
- before = lstatSync3(path);
3023
+ before = lstatSync3(path2);
2937
3024
  } catch (error) {
2938
3025
  if (missing(error))
2939
3026
  return;
@@ -2942,7 +3029,7 @@ class PrivateFiles {
2942
3029
  if (!before.isFile() || before.nlink !== 1 || before.size > maximum)
2943
3030
  refused();
2944
3031
  privateMode(before, this.ownerOnly);
2945
- const fd = openSync2(path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
3032
+ const fd = openSync3(path2, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
2946
3033
  try {
2947
3034
  const opened = fstatSync2(fd);
2948
3035
  if (!same(before, opened))
@@ -2956,60 +3043,60 @@ class PrivateFiles {
2956
3043
  size += n;
2957
3044
  }
2958
3045
  const bytes = buffer.subarray(0, size);
2959
- if (size !== opened.size || !same(opened, fstatSync2(fd)) || !same(opened, lstatSync3(path)))
3046
+ if (size !== opened.size || !same(opened, fstatSync2(fd)) || !same(opened, lstatSync3(path2)))
2960
3047
  refused();
2961
3048
  return { bytes, identity: hash(bytes), stamp: stamp(opened) };
2962
3049
  } finally {
2963
- closeSync2(fd);
3050
+ closeSync3(fd);
2964
3051
  }
2965
3052
  }
2966
- assert(path, expected, maximum = METADATA_BYTES, walks) {
2967
- const current = this.read(path, maximum, walks);
3053
+ assert(path2, expected, maximum = METADATA_BYTES, walks) {
3054
+ const current = this.read(path2, maximum, walks);
2968
3055
  if (current?.identity !== expected?.identity || current?.stamp !== expected?.stamp)
2969
3056
  throw new ContractError("conflict");
2970
3057
  }
2971
- create(path, bytes, maximum = METADATA_BYTES) {
3058
+ create(path2, bytes, maximum = METADATA_BYTES) {
2972
3059
  if (Buffer.byteLength(bytes) > maximum)
2973
3060
  throw new ContractError("oversize");
2974
- this.directory(dirname3(path), true);
2975
- const fd = retryWindowsFileOperation(() => openSync2(path, "wx", 384));
3061
+ this.directory(dirname3(path2), true);
3062
+ const fd = retryWindowsFileOperation(() => openSync3(path2, "wx", 384));
2976
3063
  try {
2977
3064
  writeFileSync3(fd, bytes);
2978
3065
  fsyncSync2(fd);
2979
3066
  } finally {
2980
- closeSync2(fd);
3067
+ closeSync3(fd);
2981
3068
  }
2982
- this.syncDirectory(dirname3(path));
2983
- const result = this.read(path, maximum);
3069
+ this.syncDirectory(dirname3(path2));
3070
+ const result = this.read(path2, maximum);
2984
3071
  if (!result || result.identity !== hash(bytes))
2985
3072
  refused();
2986
3073
  return result;
2987
3074
  }
2988
- replace(path, expected, bytes, maximum = METADATA_BYTES) {
2989
- this.assert(path, expected, maximum);
2990
- const temporary = join3(dirname3(path), `pending-${randomUUID2()}.json`);
3075
+ replace(path2, expected, bytes, maximum = METADATA_BYTES) {
3076
+ this.assert(path2, expected, maximum);
3077
+ const temporary = join3(dirname3(path2), `pending-${randomUUID2()}.json`);
2991
3078
  this.create(temporary, bytes, maximum);
2992
- this.assert(path, expected, maximum);
2993
- retryWindowsFileOperation(() => renameSync3(temporary, path), () => this.assert(path, expected, maximum));
2994
- this.syncDirectory(dirname3(path));
2995
- const result = this.read(path, maximum);
3079
+ this.assert(path2, expected, maximum);
3080
+ retryWindowsFileOperation(() => renameSync3(temporary, path2), () => this.assert(path2, expected, maximum));
3081
+ this.syncDirectory(dirname3(path2));
3082
+ const result = this.read(path2, maximum);
2996
3083
  if (!result || result.identity !== hash(bytes))
2997
3084
  refused();
2998
3085
  return result;
2999
3086
  }
3000
- remove(path, expected, maximum = METADATA_BYTES) {
3001
- this.assert(path, expected, maximum);
3002
- retryWindowsFileOperation(() => unlinkSync2(path), () => this.assert(path, expected, maximum));
3003
- this.syncDirectory(dirname3(path));
3087
+ remove(path2, expected, maximum = METADATA_BYTES) {
3088
+ this.assert(path2, expected, maximum);
3089
+ retryWindowsFileOperation(() => unlinkSync2(path2), () => this.assert(path2, expected, maximum));
3090
+ this.syncDirectory(dirname3(path2));
3004
3091
  }
3005
- syncDirectory(path) {
3092
+ syncDirectory(path2) {
3006
3093
  if (process.platform === "win32")
3007
3094
  return;
3008
- const fd = openSync2(path, constants2.O_RDONLY | constants2.O_DIRECTORY);
3095
+ const fd = openSync3(path2, constants3.O_RDONLY | constants3.O_DIRECTORY);
3009
3096
  try {
3010
3097
  fsyncSync2(fd);
3011
3098
  } finally {
3012
- closeSync2(fd);
3099
+ closeSync3(fd);
3013
3100
  }
3014
3101
  }
3015
3102
  }
@@ -3053,10 +3140,10 @@ var registrationKeys = [
3053
3140
  ];
3054
3141
  var reservationKeys = registrationKeys.filter((k) => !["schema", "phase", "checkoutToken"].includes(k));
3055
3142
  function pathValue(value) {
3056
- const path = text(value, 4096, true);
3057
- if (!isAbsolute4(path))
3143
+ const path2 = text(value, 4096, true);
3144
+ if (!isAbsolute4(path2))
3058
3145
  refused();
3059
- return path;
3146
+ return path2;
3060
3147
  }
3061
3148
  function endpoint(value, allowZero = false) {
3062
3149
  const s = text(value, 128, true);
@@ -3115,8 +3202,8 @@ function rootsOverlap(a, b) {
3115
3202
  };
3116
3203
  return within(a, b) || within(b, a);
3117
3204
  }
3118
- function names(path, allowed) {
3119
- const dir = opendirSync(path);
3205
+ function names(path2, allowed) {
3206
+ const dir = opendirSync(path2);
3120
3207
  try {
3121
3208
  let count = 0;
3122
3209
  for (let entry = dir.readSync();entry; entry = dir.readSync()) {
@@ -3641,13 +3728,277 @@ class OwnerLease {
3641
3728
  }
3642
3729
  }
3643
3730
 
3731
+ // src/service/launchd.ts
3732
+ import { rmSync as rmSync2 } from "node:fs";
3733
+ import { join as join5 } from "node:path";
3734
+ var LABEL = "com.abelxiaoxing.cadence";
3735
+ var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
3736
+ <!-- Generated by @abelxiaoxing/cadence`;
3737
+ function plistPath(s) {
3738
+ return join5(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
3739
+ }
3740
+ var xml = (value) => checked(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3741
+ function buildPlist(u) {
3742
+ const string = (value) => ` <string>${xml(value)}</string>`;
3743
+ return [
3744
+ '<?xml version="1.0" encoding="UTF-8"?>',
3745
+ "<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
3746
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
3747
+ '<plist version="1.0">',
3748
+ "<dict>",
3749
+ "\t<key>Label</key>",
3750
+ ` <string>${LABEL}</string>`,
3751
+ "\t<key>ProgramArguments</key>",
3752
+ "\t<array>",
3753
+ ...[u.node, u.cli, "start", "--foreground", "--workspace", u.workspace].map(string),
3754
+ "\t</array>",
3755
+ "\t<key>WorkingDirectory</key>",
3756
+ ` <string>${xml(u.workspace)}</string>`,
3757
+ "\t<key>EnvironmentVariables</key>",
3758
+ "\t<dict>",
3759
+ ...u.environment.flatMap(([k, v]) => [
3760
+ ` <key>${xml(k)}</key>`,
3761
+ string(v)
3762
+ ]),
3763
+ "\t</dict>",
3764
+ "\t<key>RunAtLoad</key>",
3765
+ "\t<true/>",
3766
+ "\t<key>ProcessType</key>",
3767
+ "\t<string>Background</string>",
3768
+ "\t<key>StandardOutPath</key>",
3769
+ ` <string>${xml(u.log)}</string>`,
3770
+ "\t<key>StandardErrorPath</key>",
3771
+ ` <string>${xml(u.log)}</string>`,
3772
+ "</dict>",
3773
+ "</plist>",
3774
+ ""
3775
+ ].join(`
3776
+ `);
3777
+ }
3778
+ function plistWorkspace(text2) {
3779
+ const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
3780
+ return m?.[1].replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
3781
+ }
3782
+ var domain = (s) => `gui/${s.uid}`;
3783
+ var target = (s) => `${domain(s)}/${LABEL}`;
3784
+ var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
3785
+ function disabled(s) {
3786
+ return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
3787
+ }
3788
+ async function configureLaunchAgent(s, mode, requested) {
3789
+ if (s.uid === undefined)
3790
+ return { status: "unavailable" };
3791
+ const path2 = plistPath(s);
3792
+ const previous = readGenerated(path2, MARKER2);
3793
+ if (previous === null)
3794
+ return { status: "foreign", unit: path2 };
3795
+ if (mode === "restart" && previous === undefined)
3796
+ return { status: "absent" };
3797
+ const sdk = sdkPackages(s.root);
3798
+ if (!sdk)
3799
+ return { status: "missing-pi-sdk" };
3800
+ const kept = plistWorkspace(previous);
3801
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
3802
+ const refused2 = await settle(s, kept);
3803
+ if (refused2)
3804
+ return { ...refused2, unit: path2 };
3805
+ if (loaded(s))
3806
+ s.launchctl(["bootout", target(s)]);
3807
+ write(path2, buildPlist({
3808
+ node: s.node,
3809
+ cli: join5(s.root, "src", "service-cli.mjs"),
3810
+ workspace,
3811
+ environment: serviceEnvironment(s),
3812
+ log: join5(s.home, "Library", "Logs", "cadence.log")
3813
+ }));
3814
+ const result = {
3815
+ unit: path2,
3816
+ workspace,
3817
+ ...redirectedFrom ? { redirectedFrom } : {}
3818
+ };
3819
+ if (mode === "install")
3820
+ s.launchctl(["enable", target(s)]);
3821
+ else if (disabled(s))
3822
+ return { status: "disabled", ...result };
3823
+ if (!regularFile(join5(agentDirectory(s), "auth.json")))
3824
+ return { status: "waiting-for-pi-login", ...result };
3825
+ const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
3826
+ return { status: started ? "started" : "failed", ...result };
3827
+ }
3828
+ async function stopLaunchAgent(s) {
3829
+ const path2 = plistPath(s);
3830
+ const previous = readGenerated(path2, MARKER2);
3831
+ if (previous === null)
3832
+ return { status: "foreign", unit: path2 };
3833
+ if (previous === undefined)
3834
+ return { status: "absent" };
3835
+ const workspace = plistWorkspace(previous);
3836
+ const refused2 = await settle(s, workspace);
3837
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
3838
+ }
3839
+ async function removeLaunchAgent(s) {
3840
+ const path2 = plistPath(s);
3841
+ const previous = readGenerated(path2, MARKER2);
3842
+ if (previous === null)
3843
+ return { status: "foreign", unit: path2 };
3844
+ if (previous === undefined)
3845
+ return { status: "absent" };
3846
+ const refused2 = await settle(s, plistWorkspace(previous));
3847
+ if (refused2)
3848
+ return { ...refused2, unit: path2 };
3849
+ if (s.uid !== undefined && loaded(s))
3850
+ s.launchctl(["bootout", target(s)]);
3851
+ rmSync2(path2);
3852
+ return { status: "removed", unit: path2 };
3853
+ }
3854
+ function launchAgentStatus(s) {
3855
+ const path2 = plistPath(s);
3856
+ const previous = readGenerated(path2, MARKER2);
3857
+ if (previous === null)
3858
+ return { status: "foreign", unit: path2 };
3859
+ if (previous === undefined)
3860
+ return { status: "absent" };
3861
+ const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
3862
+ return {
3863
+ status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
3864
+ enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
3865
+ unit: path2,
3866
+ workspace: plistWorkspace(previous)
3867
+ };
3868
+ }
3869
+
3870
+ // src/service/login-run.ts
3871
+ import { rmSync as rmSync3 } from "node:fs";
3872
+ import { isAbsolute as isAbsolute5, join as join6 } from "node:path";
3873
+ var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
3874
+ var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
3875
+ var RUN_VALUE = "Cadence";
3876
+ var MARKER3 = "' Generated by @abelxiaoxing/cadence";
3877
+ function launcherPath(s) {
3878
+ const local = s.env.LOCALAPPDATA;
3879
+ return join6(local && isAbsolute5(local) ? local : join6(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
3880
+ }
3881
+ var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
3882
+ function argument2(value) {
3883
+ if (checked(value).includes('"'))
3884
+ throw new ContractError("invalid");
3885
+ return `"${value}"`;
3886
+ }
3887
+ function buildLauncher(u) {
3888
+ const command = `${argument2(u.node)} ${argument2(u.cli)} start --workspace ${argument2(u.workspace)}`;
3889
+ return [
3890
+ `${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
3891
+ `' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
3892
+ 'Set shell = CreateObject("WScript.Shell")',
3893
+ ...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
3894
+ ...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
3895
+ `shell.CurrentDirectory = ${literal2(u.workspace)}`,
3896
+ `shell.Run ${literal2(command)}, 0, False`,
3897
+ ""
3898
+ ].join(`\r
3899
+ `);
3900
+ }
3901
+ var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
3902
+ function launcherWorkspace(text2) {
3903
+ return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
3904
+ }
3905
+ var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
3906
+ var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
3907
+ function approvedOff(s) {
3908
+ const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
3909
+ const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
3910
+ return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
3911
+ }
3912
+ async function configureLoginRun(s, mode, requested) {
3913
+ const path2 = launcherPath(s);
3914
+ const previous = readGenerated(path2, MARKER3);
3915
+ if (previous === null)
3916
+ return { status: "foreign", unit: path2 };
3917
+ if (previous === undefined && present(s, RUN_KEY))
3918
+ return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
3919
+ if (mode === "restart" && previous === undefined)
3920
+ return { status: "absent" };
3921
+ const sdk = sdkPackages(s.root);
3922
+ if (!sdk)
3923
+ return { status: "missing-pi-sdk" };
3924
+ const kept = launcherWorkspace(previous);
3925
+ const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
3926
+ const refused2 = await settle(s, kept);
3927
+ if (refused2)
3928
+ return { ...refused2, unit: path2 };
3929
+ const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
3930
+ write(path2, utf16(buildLauncher({
3931
+ node: s.node,
3932
+ cli: join6(s.root, "src", "service-cli.mjs"),
3933
+ workspace,
3934
+ environment
3935
+ })));
3936
+ const result = {
3937
+ unit: path2,
3938
+ workspace,
3939
+ ...redirectedFrom ? { redirectedFrom } : {}
3940
+ };
3941
+ if (mode === "install") {
3942
+ const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path2, s)];
3943
+ if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
3944
+ return { status: "failed", ...result };
3945
+ s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
3946
+ }
3947
+ if (!regularFile(join6(agentDirectory(s), "auth.json")))
3948
+ return { status: "waiting-for-pi-login", ...result };
3949
+ return { status: s.launch(path2) ? "started" : "failed", ...result };
3950
+ }
3951
+ async function stopLoginRun(s) {
3952
+ const path2 = launcherPath(s);
3953
+ const previous = readGenerated(path2, MARKER3);
3954
+ if (previous === null)
3955
+ return { status: "foreign", unit: path2 };
3956
+ if (previous === undefined)
3957
+ return { status: "absent" };
3958
+ const workspace = launcherWorkspace(previous);
3959
+ const refused2 = await settle(s, workspace);
3960
+ return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
3961
+ }
3962
+ async function removeLoginRun(s) {
3963
+ const path2 = launcherPath(s);
3964
+ const previous = readGenerated(path2, MARKER3);
3965
+ if (previous === null)
3966
+ return { status: "foreign", unit: path2 };
3967
+ if (previous === undefined)
3968
+ return { status: "absent" };
3969
+ const refused2 = await settle(s, launcherWorkspace(previous));
3970
+ if (refused2)
3971
+ return { ...refused2, unit: path2 };
3972
+ for (const key of [RUN_KEY, APPROVED_KEY])
3973
+ if (present(s, key))
3974
+ s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
3975
+ rmSync3(path2);
3976
+ return { status: "removed", unit: path2 };
3977
+ }
3978
+ async function loginRunStatus(s) {
3979
+ const path2 = launcherPath(s);
3980
+ const previous = readGenerated(path2, MARKER3);
3981
+ if (previous === null)
3982
+ return { status: "foreign", unit: path2 };
3983
+ if (previous === undefined)
3984
+ return { status: "absent" };
3985
+ const workspace = launcherWorkspace(previous);
3986
+ const owner = workspace ? await s.ownerStatus(workspace) : "unavailable";
3987
+ return {
3988
+ status: owner === "alive" ? "active" : "inactive",
3989
+ enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
3990
+ unit: path2,
3991
+ workspace
3992
+ };
3993
+ }
3994
+
3644
3995
  // src/service/recovery-cli.ts
3645
3996
  import { isatty } from "node:tty";
3646
3997
 
3647
3998
  // src/history/recovery.ts
3648
3999
  import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
3649
4000
  import { lstatSync as lstatSync6, opendirSync as opendirSync3 } from "node:fs";
3650
- import { join as join6, relative as relative4 } from "node:path";
4001
+ import { join as join8, relative as relative4 } from "node:path";
3651
4002
 
3652
4003
  // src/service/recovery.ts
3653
4004
  async function inspectRecovery(coordination, history) {
@@ -3697,17 +4048,17 @@ async function resolveRecovery(coordination, confirmation, history) {
3697
4048
  // src/history/files.ts
3698
4049
  import { createHash as createHash4, randomBytes as randomBytes2, randomUUID as randomUUID4 } from "node:crypto";
3699
4050
  import {
3700
- closeSync as closeSync4,
3701
- constants as constants4,
4051
+ closeSync as closeSync5,
4052
+ constants as constants5,
3702
4053
  fstatSync as fstatSync4,
3703
4054
  fsyncSync as fsyncSync3,
3704
4055
  lstatSync as lstatSync5,
3705
4056
  opendirSync as opendirSync2,
3706
- openSync as openSync4,
4057
+ openSync as openSync5,
3707
4058
  statfsSync
3708
4059
  } from "node:fs";
3709
4060
  import { open } from "node:fs/promises";
3710
- import { dirname as dirname6, join as join5 } from "node:path";
4061
+ import { dirname as dirname6, join as join7 } from "node:path";
3711
4062
 
3712
4063
  // src/history/admission.ts
3713
4064
  function decodeRunAdmission(value) {
@@ -3728,11 +4079,11 @@ function decodeRunAdmission(value) {
3728
4079
 
3729
4080
  // src/history/read-io.ts
3730
4081
  import {
3731
- closeSync as closeSync3,
3732
- constants as constants3,
4082
+ closeSync as closeSync4,
4083
+ constants as constants4,
3733
4084
  fstatSync as fstatSync3,
3734
4085
  lstatSync as lstatSync4,
3735
- openSync as openSync3,
4086
+ openSync as openSync4,
3736
4087
  readSync as readSync3
3737
4088
  } from "node:fs";
3738
4089
  import { dirname as dirname5 } from "node:path";
@@ -3740,32 +4091,32 @@ function bad() {
3740
4091
  throw new ContractError("corrupt-selected");
3741
4092
  }
3742
4093
  var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
3743
- function selected(files, path, read, walks) {
3744
- if (!files.files.directory(dirname5(path), false, walks))
4094
+ function selected(files, path2, read, walks) {
4095
+ if (!files.files.directory(dirname5(path2), false, walks))
3745
4096
  bad();
3746
- const before = lstatSync4(path);
4097
+ const before = lstatSync4(path2);
3747
4098
  if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
3748
4099
  bad();
3749
- const fd = openSync3(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
4100
+ const fd = openSync4(path2, constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0));
3750
4101
  try {
3751
4102
  if (stamp2(fstatSync3(fd)) !== stamp2(before))
3752
4103
  bad();
3753
4104
  const result = read(fd, before.size, stamp2(before));
3754
- if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync4(path)) !== stamp2(before))
4105
+ if (stamp2(fstatSync3(fd)) !== stamp2(before) || stamp2(lstatSync4(path2)) !== stamp2(before))
3755
4106
  bad();
3756
4107
  return result;
3757
4108
  } finally {
3758
- closeSync3(fd);
4109
+ closeSync4(fd);
3759
4110
  }
3760
4111
  }
3761
- function assertIncarnation(files, path, expected) {
3762
- selected(files, path, (_fd, _size, current) => {
4112
+ function assertIncarnation(files, path2, expected) {
4113
+ selected(files, path2, (_fd, _size, current) => {
3763
4114
  if (current !== expected)
3764
4115
  bad();
3765
4116
  });
3766
4117
  }
3767
- function statSize(files, path, maximum) {
3768
- return selected(files, path, (_fd, size) => {
4118
+ function statSize(files, path2, maximum) {
4119
+ return selected(files, path2, (_fd, size) => {
3769
4120
  if (size > maximum)
3770
4121
  bad();
3771
4122
  return size;
@@ -4238,14 +4589,14 @@ class HistoryFiles {
4238
4589
  this.coordination = coordination;
4239
4590
  this.format = format;
4240
4591
  this.formatSnapshot = formatSnapshot;
4241
- this.directory = join5(coordination.directory, "history-v2");
4592
+ this.directory = join7(coordination.directory, "history-v2");
4242
4593
  this.files = coordination.files;
4243
4594
  }
4244
4595
  static open(coordination) {
4245
- const dir = join5(coordination.directory, "history-v2");
4596
+ const dir = join7(coordination.directory, "history-v2");
4246
4597
  if (!coordination.files.directory(dir))
4247
4598
  return;
4248
- const file = coordination.files.read(join5(dir, "format.json"));
4599
+ const file = coordination.files.read(join7(dir, "format.json"));
4249
4600
  if (!file)
4250
4601
  corrupt();
4251
4602
  const format = decodeFormat(json(file));
@@ -4261,7 +4612,7 @@ class HistoryFiles {
4261
4612
  existing.assertWritable();
4262
4613
  return existing;
4263
4614
  }
4264
- const dir = join5(c.directory, "history-v2");
4615
+ const dir = join7(c.directory, "history-v2");
4265
4616
  const format = {
4266
4617
  schema: FORMAT,
4267
4618
  storeId: randomUUID4(),
@@ -4271,19 +4622,19 @@ class HistoryFiles {
4271
4622
  c.files.directory(dir, true);
4272
4623
  for (const name of ["commands", "ledger", "runs", "indexes"]) {
4273
4624
  lease.assert();
4274
- c.files.directory(join5(dir, name), true);
4625
+ c.files.directory(join7(dir, name), true);
4275
4626
  }
4276
4627
  lease.assert();
4277
- const keyPath = join5(dir, "read-proof-key.json");
4628
+ const keyPath = join7(dir, "read-proof-key.json");
4278
4629
  c.files.create(keyPath, encoded({
4279
4630
  schema: "cadence-history-read-key/1",
4280
4631
  storeId: format.storeId,
4281
4632
  key: randomBytes2(32).toString("base64url")
4282
4633
  }));
4283
4634
  lease.assert();
4284
- const snapshot = c.files.create(join5(dir, "format.json"), encoded(format));
4635
+ const snapshot = c.files.create(join7(dir, "format.json"), encoded(format));
4285
4636
  const result = new HistoryFiles(c, format, snapshot);
4286
- result.syncParents(join5(dir, "format.json"));
4637
+ result.syncParents(join7(dir, "format.json"));
4287
4638
  result.assertWritable();
4288
4639
  return result;
4289
4640
  }
@@ -4318,7 +4669,7 @@ class HistoryFiles {
4318
4669
  throw new ContractError("quarantine");
4319
4670
  }
4320
4671
  assertFormat() {
4321
- this.files.assert(join5(this.directory, "format.json"), this.formatSnapshot);
4672
+ this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot);
4322
4673
  }
4323
4674
  readProofMaterial(budget) {
4324
4675
  if (this.format.schema !== FORMAT)
@@ -4327,7 +4678,7 @@ class HistoryFiles {
4327
4678
  this.assertReadFormat(budget);
4328
4679
  else
4329
4680
  this.assertFormat();
4330
- const selected2 = this.readBudgeted(join5(this.directory, "read-proof-key.json"), 512, budget);
4681
+ const selected2 = this.readBudgeted(join7(this.directory, "read-proof-key.json"), 512, budget);
4331
4682
  if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
4332
4683
  corrupt();
4333
4684
  try {
@@ -4355,17 +4706,17 @@ class HistoryFiles {
4355
4706
  return;
4356
4707
  }
4357
4708
  this.assertFormat();
4358
- assertIncarnation(this, join5(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
4709
+ assertIncarnation(this, join7(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
4359
4710
  }
4360
4711
  segmentPath(segment) {
4361
- return join5(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4712
+ return join7(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
4362
4713
  }
4363
4714
  artifactPath(ref) {
4364
- return join5(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
4715
+ return join7(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
4365
4716
  }
4366
4717
  admissionPath(runId) {
4367
4718
  decodeId(runId);
4368
- return join5(this.directory, "runs", runId, "admission.json");
4719
+ return join7(this.directory, "runs", runId, "admission.json");
4369
4720
  }
4370
4721
  writeAdmission(lease, input) {
4371
4722
  lease.assert();
@@ -4374,24 +4725,24 @@ class HistoryFiles {
4374
4725
  this.files.create(this.admissionPath(value.runId), encoded(value), RECORD_BYTES);
4375
4726
  this.syncParents(this.admissionPath(value.runId));
4376
4727
  }
4377
- readBudgeted(path, maximum, budget) {
4728
+ readBudgeted(path2, maximum, budget) {
4378
4729
  if (!budget)
4379
- return this.files.read(path, maximum);
4730
+ return this.files.read(path2, maximum);
4380
4731
  let size;
4381
4732
  try {
4382
- size = lstatSync5(path).size;
4733
+ size = lstatSync5(path2).size;
4383
4734
  } catch {
4384
- return this.files.read(path, 0, budget.walks);
4735
+ return this.files.read(path2, 0, budget.walks);
4385
4736
  }
4386
4737
  if (size > maximum)
4387
4738
  corrupt();
4388
4739
  budget.reserve(size);
4389
- return this.files.read(path, size, budget.walks);
4740
+ return this.files.read(path2, size, budget.walks);
4390
4741
  }
4391
4742
  readCommand(commandId, budget) {
4392
4743
  decodeId(commandId);
4393
4744
  this.assertReadFormat(budget);
4394
- return this.readBudgeted(join5(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
4745
+ return this.readBudgeted(join7(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
4395
4746
  }
4396
4747
  readAdmission(runId, identity, budget) {
4397
4748
  return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
@@ -4412,24 +4763,24 @@ class HistoryFiles {
4412
4763
  syncParents(file) {
4413
4764
  if (process.platform === "win32")
4414
4765
  return;
4415
- for (let path = dirname6(file);; path = dirname6(path)) {
4416
- if (!this.files.directory(path))
4766
+ for (let path2 = dirname6(file);; path2 = dirname6(path2)) {
4767
+ if (!this.files.directory(path2))
4417
4768
  corrupt();
4418
- const fd = openSync4(path, constants4.O_RDONLY | constants4.O_DIRECTORY);
4769
+ const fd = openSync5(path2, constants5.O_RDONLY | constants5.O_DIRECTORY);
4419
4770
  try {
4420
4771
  fsyncSync3(fd);
4421
4772
  } finally {
4422
- closeSync4(fd);
4773
+ closeSync5(fd);
4423
4774
  }
4424
- if (path === this.coordination.directory)
4775
+ if (path2 === this.coordination.directory)
4425
4776
  break;
4426
- if (path === dirname6(path))
4777
+ if (path2 === dirname6(path2))
4427
4778
  corrupt();
4428
4779
  }
4429
4780
  }
4430
4781
  assertReadFormat(budget) {
4431
4782
  budget.reserve(this.formatSnapshot.bytes.length);
4432
- this.files.assert(join5(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
4783
+ this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
4433
4784
  }
4434
4785
  assertPublished(segment, end, seq) {
4435
4786
  const fence = publicationFences.get(this.directory);
@@ -4460,14 +4811,14 @@ class HistoryFiles {
4460
4811
  this.syncParents(this.artifactPath(ref));
4461
4812
  }
4462
4813
  usage() {
4463
- const walk = (path, depth) => {
4464
- if (depth > 8 || !this.files.directory(path))
4814
+ const walk = (path2, depth) => {
4815
+ if (depth > 8 || !this.files.directory(path2))
4465
4816
  corrupt();
4466
4817
  let bytes = 0;
4467
- const dir = opendirSync2(path);
4818
+ const dir = opendirSync2(path2);
4468
4819
  try {
4469
4820
  for (let e = dir.readSync();e; e = dir.readSync()) {
4470
- const p = join5(path, e.name), st = lstatSync5(p);
4821
+ const p = join7(path2, e.name), st = lstatSync5(p);
4471
4822
  if (st.isDirectory())
4472
4823
  bytes += walk(p, depth + 1);
4473
4824
  else {
@@ -4502,7 +4853,7 @@ class HistoryFiles {
4502
4853
  state.bytes = 0;
4503
4854
  state.identity = null;
4504
4855
  let count = 0;
4505
- const ledger = join5(this.directory, "ledger");
4856
+ const ledger = join7(this.directory, "ledger");
4506
4857
  if (!this.files.directory(ledger))
4507
4858
  corrupt();
4508
4859
  const dir = opendirSync2(ledger);
@@ -4660,17 +5011,17 @@ class HistoryFiles {
4660
5011
  this.assertRecoveryIdle();
4661
5012
  this.validateRecovery(row.value);
4662
5013
  const { body, trailer, marker } = this.encodeBatch(state, [row]);
4663
- const segment = state.segment + 1, path = this.segmentPath(segment);
5014
+ const segment = state.segment + 1, path2 = this.segmentPath(segment);
4664
5015
  publicationFences.set(this.directory, { ...state });
4665
5016
  this.activity().generation++;
4666
- const before = this.files.create(path, "", LIMITS.segmentBytes);
5017
+ const before = this.files.create(path2, "", LIMITS.segmentBytes);
4667
5018
  let committed;
4668
- const fd = await open(path, constants4.O_WRONLY | constants4.O_APPEND | (constants4.O_NOFOLLOW ?? 0));
5019
+ const fd = await open(path2, constants5.O_WRONLY | constants5.O_APPEND | (constants5.O_NOFOLLOW ?? 0));
4669
5020
  const assertOriginal = (bytes) => {
4670
- const original = fstatSync4(fd.fd), selected2 = lstatSync5(path);
5021
+ const original = fstatSync4(fd.fd), selected2 = lstatSync5(path2);
4671
5022
  if (original.dev !== selected2.dev || original.ino !== selected2.ino || original.nlink !== 1)
4672
5023
  corrupt();
4673
- const snapshot = this.files.read(path, LIMITS.segmentBytes);
5024
+ const snapshot = this.files.read(path2, LIMITS.segmentBytes);
4674
5025
  if (!snapshot || snapshot.identity !== hash(bytes))
4675
5026
  corrupt();
4676
5027
  return snapshot;
@@ -4678,7 +5029,7 @@ class HistoryFiles {
4678
5029
  try {
4679
5030
  await assertCurrent();
4680
5031
  this.assertWritable();
4681
- this.files.assert(path, before, LIMITS.segmentBytes);
5032
+ this.files.assert(path2, before, LIMITS.segmentBytes);
4682
5033
  assertOriginal("");
4683
5034
  await fd.writeFile(body);
4684
5035
  await observer?.("journal");
@@ -4687,7 +5038,7 @@ class HistoryFiles {
4687
5038
  assertOriginal(body);
4688
5039
  await fd.writeFile(trailer);
4689
5040
  await fd.sync();
4690
- this.syncParents(path);
5041
+ this.syncParents(path2);
4691
5042
  await observer?.("commit");
4692
5043
  await assertCurrent();
4693
5044
  this.assertWritable();
@@ -4699,7 +5050,7 @@ class HistoryFiles {
4699
5050
  this.assertWritable();
4700
5051
  if (!committed)
4701
5052
  corrupt();
4702
- this.files.assert(path, committed, LIMITS.segmentBytes);
5053
+ this.files.assert(path2, committed, LIMITS.segmentBytes);
4703
5054
  publicationFences.delete(this.directory);
4704
5055
  return {
4705
5056
  snapshot: committed,
@@ -4771,20 +5122,20 @@ class HistoryFiles {
4771
5122
  if (state.bytes + Buffer.byteLength(body + trailer) > LIMITS.segmentBytes || state.records + rows.length + 1 > LIMITS.segmentRecords)
4772
5123
  segment++;
4773
5124
  lease.assert();
4774
- const path = this.segmentPath(segment);
4775
- let before = this.files.read(path, LIMITS.segmentBytes);
5125
+ const path2 = this.segmentPath(segment);
5126
+ let before = this.files.read(path2, LIMITS.segmentBytes);
4776
5127
  if (segment !== state.segment) {
4777
5128
  if (before)
4778
5129
  corrupt();
4779
- before = this.files.create(path, "", LIMITS.segmentBytes);
5130
+ before = this.files.create(path2, "", LIMITS.segmentBytes);
4780
5131
  }
4781
5132
  if (!before || segment === state.segment && (before.bytes.length !== state.bytes || before.identity !== state.identity))
4782
5133
  corrupt();
4783
- const fd = await open(path, constants4.O_WRONLY | constants4.O_APPEND | (constants4.O_NOFOLLOW ?? 0));
5134
+ const fd = await open(path2, constants5.O_WRONLY | constants5.O_APPEND | (constants5.O_NOFOLLOW ?? 0));
4784
5135
  try {
4785
5136
  lease.assert();
4786
- this.files.assert(path, before, LIMITS.segmentBytes);
4787
- const opened = await fd.stat(), selected2 = lstatSync5(path);
5137
+ this.files.assert(path2, before, LIMITS.segmentBytes);
5138
+ const opened = await fd.stat(), selected2 = lstatSync5(path2);
4788
5139
  if (opened.ino !== selected2.ino || opened.dev !== selected2.dev || opened.nlink !== 1)
4789
5140
  corrupt();
4790
5141
  beforeEffect?.();
@@ -4798,7 +5149,7 @@ class HistoryFiles {
4798
5149
  await observer?.("commit");
4799
5150
  lease.assert();
4800
5151
  this.assertWritable();
4801
- const after = this.files.read(path, LIMITS.segmentBytes);
5152
+ const after = this.files.read(path2, LIMITS.segmentBytes);
4802
5153
  if (!after || after.identity !== createHash4("sha256").update(before.bytes).update(body).update(trailer).digest("hex"))
4803
5154
  corrupt();
4804
5155
  return {
@@ -4819,7 +5170,7 @@ class HistoryFiles {
4819
5170
  checkpoint(lease, state) {
4820
5171
  lease.assert();
4821
5172
  this.assertWritable();
4822
- const path = join5(this.directory, "checkpoint.json"), old = this.files.read(path);
5173
+ const path2 = join7(this.directory, "checkpoint.json"), old = this.files.read(path2);
4823
5174
  const body = encoded({
4824
5175
  schema: "cadence-history-checkpoint/2",
4825
5176
  storeId: this.format.storeId,
@@ -4827,9 +5178,9 @@ class HistoryFiles {
4827
5178
  segment: state.segment
4828
5179
  });
4829
5180
  if (old)
4830
- this.files.replace(path, old, body);
5181
+ this.files.replace(path2, old, body);
4831
5182
  else
4832
- this.files.create(path, body);
5183
+ this.files.create(path2, body);
4833
5184
  }
4834
5185
  }
4835
5186
  function emptyScan() {
@@ -5229,13 +5580,13 @@ function digestHistory(files, exclude) {
5229
5580
  files.assertFormat();
5230
5581
  const digest = createHash5("sha256");
5231
5582
  let count = 0;
5232
- const walk = (path, depth) => {
5233
- if (depth > 8 || !files.files.directory(path))
5583
+ const walk = (path2, depth) => {
5584
+ if (depth > 8 || !files.files.directory(path2))
5234
5585
  throw new ContractError("corrupt-selected");
5235
- const directory2 = lstatSync6(path);
5586
+ const directory2 = lstatSync6(path2);
5236
5587
  digest.update(`incarnation:${directory2.dev}:${directory2.ino}
5237
5588
  `);
5238
- const dir = opendirSync3(path), names2 = [];
5589
+ const dir = opendirSync3(path2), names2 = [];
5239
5590
  try {
5240
5591
  for (let e = dir.readSync();e; e = dir.readSync()) {
5241
5592
  if (++count > LIMITS.dataFrames)
@@ -5246,7 +5597,7 @@ function digestHistory(files, exclude) {
5246
5597
  dir.closeSync();
5247
5598
  }
5248
5599
  for (const name of names2.sort()) {
5249
- const selected2 = join6(path, name);
5600
+ const selected2 = join8(path2, name);
5250
5601
  if (selected2 === exclude)
5251
5602
  continue;
5252
5603
  const rel = relative4(files.directory, selected2);
@@ -5386,13 +5737,13 @@ class HistoryRecovery {
5386
5737
  },
5387
5738
  acknowledgments: { processes: true, credentials: true, storage: true }
5388
5739
  };
5389
- const path = files.segmentPath(state.segment + 1);
5740
+ const path2 = files.segmentPath(state.segment + 1);
5390
5741
  const assertCurrent = async () => {
5391
5742
  await confirmation.assertLocalAuthority(structuredClone(expected));
5392
5743
  const selected2 = await inspectRecovery(this.coordination);
5393
5744
  if (selected2.owner !== "absent")
5394
5745
  throw new ContractError("quarantine");
5395
- if (selected2.epoch !== expected.epoch || selected2.coordinationDigest !== expected.coordinationDigest || digestHistory(files, path) !== expected.history?.digest || this.coordination.recoverySnapshot().digest !== expected.coordinationDigest)
5746
+ if (selected2.epoch !== expected.epoch || selected2.coordinationDigest !== expected.coordinationDigest || digestHistory(files, path2) !== expected.history?.digest || this.coordination.recoverySnapshot().digest !== expected.coordinationDigest)
5396
5747
  throw new ContractError("conflict");
5397
5748
  };
5398
5749
  await confirmation.assertLocalAuthority(structuredClone(expected));
@@ -5416,8 +5767,8 @@ class HistoryRecovery {
5416
5767
  const assert = () => {
5417
5768
  files.assertWritable();
5418
5769
  files.assertRecoveryIdle();
5419
- files.files.assert(path, after.snapshot, LIMITS.segmentBytes);
5420
- if (digestHistory(files, path) !== expected.history?.digest)
5770
+ files.files.assert(path2, after.snapshot, LIMITS.segmentBytes);
5771
+ if (digestHistory(files, path2) !== expected.history?.digest)
5421
5772
  throw new ContractError("conflict");
5422
5773
  };
5423
5774
  assert();
@@ -5556,8 +5907,8 @@ async function localRecovery(c, operation) {
5556
5907
  }
5557
5908
 
5558
5909
  // src/service/runtime.ts
5559
- import { homedir as homedir3 } from "node:os";
5560
- import { dirname as dirname7, isAbsolute as isAbsolute5, join as join7, resolve as resolve4 } from "node:path";
5910
+ import { homedir as homedir4 } from "node:os";
5911
+ import { dirname as dirname7, isAbsolute as isAbsolute6, join as join9, resolve as resolve4 } from "node:path";
5561
5912
 
5562
5913
  // src/service/config.ts
5563
5914
  import { isIP } from "node:net";
@@ -5611,7 +5962,7 @@ function localOrigin(policy, actualPort) {
5611
5962
  var STARTUP_INPUT_BYTES = 16 * 1024;
5612
5963
  function absolute(value) {
5613
5964
  const result = text(value, 4096, true);
5614
- if (!isAbsolute5(result))
5965
+ if (!isAbsolute6(result))
5615
5966
  throw new ContractError("invalid");
5616
5967
  return result;
5617
5968
  }
@@ -5691,8 +6042,8 @@ function selectStart(options, ambient) {
5691
6042
  throw new ContractError("invalid");
5692
6043
  let fromFile;
5693
6044
  if (options.config) {
5694
- const path = resolve4(options.config), f = new PrivateFiles(dirname7(path));
5695
- const bytes = f.read(path);
6045
+ const path2 = resolve4(options.config), f = new PrivateFiles(dirname7(path2));
6046
+ const bytes = f.read(path2);
5696
6047
  if (!bytes)
5697
6048
  throw new ContractError("corrupt-selected");
5698
6049
  fromFile = decodeHostConfig(json(bytes));
@@ -5706,7 +6057,7 @@ function selectStart(options, ambient) {
5706
6057
  port: options.port ?? 17423,
5707
6058
  proxy: null
5708
6059
  },
5709
- agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join7(ambient.HOME ?? ambient.USERPROFILE ?? homedir3(), ".pi", "agent")),
6060
+ agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join9(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
5710
6061
  tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
5711
6062
  tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
5712
6063
  });
@@ -5878,10 +6229,10 @@ function startupGuidance(result, input) {
5878
6229
  return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
5879
6230
  `;
5880
6231
  case "pi-auth":
5881
- return `${join8(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
6232
+ return `${join10(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
5882
6233
  `;
5883
6234
  case "pi-models":
5884
- return `${join8(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
6235
+ return `${join10(agent, "models.json")} must be your own single-link regular file using only fields Cadence's pinned Pi SDK supports; a field only newer Pi understands is refused.
5885
6236
  `;
5886
6237
  case "pi-runtime":
5887
6238
  return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
@@ -6007,14 +6358,59 @@ async function observeStarted(workspace) {
6007
6358
  }
6008
6359
  return { service };
6009
6360
  }
6010
- function autostartGuidance(s, result) {
6011
- const name = s.unit.replace(/\.service$/, "");
6012
- const cli = JSON.stringify(join8(s.root, "src", "service-cli.mjs"));
6013
- const workspace = JSON.stringify(result.workspace ?? "");
6014
- const manage = `Manage: systemctl --user status|restart|stop ${name}; logs: journalctl --user -u ${name}; opt out: systemctl --user disable --now ${name}`;
6361
+ var ownerControl = {
6362
+ async ownerStatus(workspace) {
6363
+ try {
6364
+ const observed = await serviceStatus(new ServiceCoordination(workspace));
6365
+ return observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
6366
+ } catch {
6367
+ return "unavailable";
6368
+ }
6369
+ },
6370
+ async stopOwner(workspace) {
6371
+ try {
6372
+ const c = new ServiceCoordination(workspace);
6373
+ const observed = await serviceStatus(c);
6374
+ const status = observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
6375
+ if (status === "absent" || status === "stopped")
6376
+ return "stopped";
6377
+ return status === "alive" ? (await stopService(c)).status : status;
6378
+ } catch {
6379
+ return "unavailable";
6380
+ }
6381
+ }
6382
+ };
6383
+ async function serverAction(s, action, requested, linger) {
6384
+ const install = action === "install" || action === "restart";
6385
+ const mode = action === "install" ? "install" : "restart";
6386
+ switch (s.platform) {
6387
+ case "linux":
6388
+ if (install)
6389
+ return configureAutostart(s, mode, requested, linger);
6390
+ if (action === "stop")
6391
+ return stopAutostart(s);
6392
+ return action === "uninstall" ? removeAutostart(s) : autostartStatus(s);
6393
+ case "darwin":
6394
+ if (install)
6395
+ return configureLaunchAgent(s, mode, requested);
6396
+ if (action === "stop")
6397
+ return stopLaunchAgent(s);
6398
+ return action === "uninstall" ? removeLaunchAgent(s) : launchAgentStatus(s);
6399
+ case "win32":
6400
+ if (install)
6401
+ return configureLoginRun(s, mode, requested);
6402
+ if (action === "stop")
6403
+ return stopLoginRun(s);
6404
+ return action === "uninstall" ? removeLoginRun(s) : loginRunStatus(s);
6405
+ default:
6406
+ return { status: "unsupported" };
6407
+ }
6408
+ }
6409
+ function serverGuidance(s, action, result) {
6410
+ const cli = piUserInstall(s) ? `node ${JSON.stringify(join10(s.root, "src", "service-cli.mjs"))}` : "cadence";
6015
6411
  const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
6016
6412
  ` : "";
6017
- return note + autostartStatusGuidance(s, result, name, cli, workspace, manage);
6413
+ return note + serverStatusGuidance(s, action, result, cli);
6018
6414
  }
6019
6415
  function lingerGuidance(linger) {
6020
6416
  switch (linger) {
@@ -6031,83 +6427,136 @@ function lingerGuidance(linger) {
6031
6427
  return "";
6032
6428
  }
6033
6429
  }
6034
- function autostartStatusGuidance(s, result, name, cli, workspace, manage) {
6430
+ function managerDetail(s) {
6431
+ switch (s.platform) {
6432
+ case "linux":
6433
+ return "see systemctl --user status cadence and journalctl --user -u cadence";
6434
+ case "darwin":
6435
+ return `see launchctl print gui/${s.uid}/${LABEL} and ~/Library/Logs/cadence.log`;
6436
+ default:
6437
+ return `run its launcher to see why: wscript ${JSON.stringify(launcherPath(s))}`;
6438
+ }
6439
+ }
6440
+ function serverStatusGuidance(s, action, result, cli) {
6441
+ const workspace = JSON.stringify(result.workspace ?? "");
6442
+ const manage = `Manage: ${cli} server status|restart|stop|uninstall`;
6443
+ const when = s.platform === "linux" ? "at boot or login" : "at login";
6035
6444
  switch (result.status) {
6036
6445
  case "started": {
6037
6446
  const linger = lingerGuidance(result.linger);
6038
6447
  if (result.service !== "alive")
6039
- return `${s.unit} started for ${result.workspace}, but the owner reports ${result.service}.
6040
- Inspect: node ${cli} diagnose --workspace ${workspace}
6448
+ return `Cadence started for ${result.workspace}, but the owner reports ${result.service}.
6449
+ Inspect: ${cli} diagnose --workspace ${workspace}
6041
6450
  ${manage}
6042
6451
  ${linger}`;
6043
- return `Cadence WebUI: ${result.endpoint} (${s.unit}, workspace ${result.workspace})
6044
- Enter with your private link: node ${cli} token-link --workspace ${workspace} --reveal
6452
+ return `Cadence WebUI: ${result.endpoint} (workspace ${result.workspace}; starts ${when})
6453
+ Enter with your private link: ${cli} token-link --workspace ${workspace} --reveal
6045
6454
  ${manage}
6046
6455
  ${linger}`;
6047
6456
  }
6048
6457
  case "waiting-for-pi-login":
6049
- return `${s.unit} is enabled for ${result.workspace}; it starts once Pi has ${join8(agentDirectory(s), "auth.json")}.
6050
- Log in to Pi, then: systemctl --user start ${name}
6458
+ return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join10(agentDirectory(s), "auth.json")}.
6459
+ Log in to Pi, then: ${cli} server restart
6051
6460
  ${lingerGuidance(result.linger)}`;
6461
+ case "stopped":
6462
+ return action === "stop" ? `Cadence stopped; it starts again ${when}, or now with: ${cli} server restart
6463
+ ` : "";
6464
+ case "absent":
6465
+ return action === "restart" || action === "stop" ? `Cadence's background service is not installed: ${cli} server install
6466
+ ` : "";
6052
6467
  case "disabled":
6053
- return `${s.unit} refreshed for ${result.workspace}; left disabled as configured.
6468
+ return `Cadence's service definition was rewritten for ${result.workspace}; left disabled as configured.
6054
6469
  `;
6055
6470
  case "masked":
6056
- return `${s.unit} is masked; Cadence did not enable or start it.
6471
+ return `cadence.service is masked; Cadence did not enable or start it.
6057
6472
  `;
6058
6473
  case "missing-pi-sdk":
6059
- return `Cadence did not register or refresh ${s.unit}: the Pi SDK packages pi-coding-agent and pi-ai (scope @earendil-works) do not resolve from ${s.root}, for example after installing without optional dependencies; an owner without them would quarantine.
6474
+ return `Cadence did not register its service: the Pi SDK packages pi-coding-agent and pi-ai (scope @earendil-works) do not resolve from ${s.root}, for example after installing without optional dependencies; an owner without them would quarantine.
6060
6475
  `;
6061
6476
  case "foreign":
6062
6477
  return `${result.unit} was not generated by Cadence; left unchanged.
6063
6478
  `;
6064
6479
  case "unavailable":
6065
- return `No systemd user manager; start Cadence explicitly: node ${cli} start --workspace /absolute/project
6480
+ return result.workspace ? `Cadence changed nothing: its owner could not be observed. Inspect: ${cli} diagnose --workspace ${workspace}
6481
+ ` : `No user service manager; start Cadence explicitly: ${cli} start --workspace /absolute/project
6482
+ `;
6483
+ case "unsupported":
6484
+ return `Cadence has no background service for ${s.platform}; start it explicitly: ${cli} start --workspace /absolute/project
6485
+ `;
6486
+ case "unconfirmed":
6487
+ case "quarantined":
6488
+ return `Cadence changed nothing: its owner's orderly Stop was not confirmed (${result.status}). Inspect: ${cli} diagnose --workspace ${workspace}
6066
6489
  `;
6067
6490
  case "failed":
6068
- return `Could not enable or start ${s.unit}; see systemctl --user status ${name} and journalctl --user -u ${name}.
6491
+ return `Could not register or start Cadence's service; ${managerDetail(s)}.
6069
6492
  ${lingerGuidance(result.linger)}`;
6070
6493
  default:
6071
6494
  return "";
6072
6495
  }
6073
6496
  }
6074
- async function autostartMain(argv, hostUrl) {
6075
- const action = argv.shift();
6497
+ function researchTemplate(home) {
6498
+ try {
6499
+ const result = ensureUserConfig(home);
6500
+ return result.created ? `Cadence created the research configuration template ${result.path}
6501
+ ` : "";
6502
+ } catch (error) {
6503
+ return `Cadence could not create the research configuration template: ${error instanceof Error ? error.message : String(error)}
6504
+ `;
6505
+ }
6506
+ }
6507
+ var SERVER_ACTIONS = [
6508
+ "install",
6509
+ "status",
6510
+ "restart",
6511
+ "stop",
6512
+ "uninstall"
6513
+ ];
6514
+ async function serverMain(argv, hostUrl) {
6515
+ const action = SERVER_ACTIONS.find((a) => a === argv[0]);
6516
+ if (!action)
6517
+ throw new ContractError("invalid");
6518
+ argv.shift();
6076
6519
  let requested;
6077
- let linger = false;
6520
+ let linger = process.env.CADENCE_LINGER !== "0";
6521
+ let noLinger = false;
6078
6522
  while (action === "install" && argv.length) {
6079
6523
  if (argv[0] === "--workspace" && argv[1] && requested === undefined)
6080
6524
  requested = resolve5(argv.splice(0, 2)[1]);
6081
- else if (argv[0] === "--linger" && !linger) {
6525
+ else if (argv[0] === "--no-linger" && !noLinger) {
6082
6526
  argv.shift();
6083
- linger = true;
6527
+ noLinger = true;
6528
+ linger = false;
6084
6529
  } else
6085
6530
  break;
6086
6531
  }
6087
- if (argv.length || !["install", "refresh", "remove", "status"].includes(action ?? ""))
6532
+ if (argv.length)
6088
6533
  throw new ContractError("invalid");
6089
- const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))));
6534
+ const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))), ownerControl);
6535
+ if (action === "install")
6536
+ process.stderr.write(researchTemplate(s.home));
6090
6537
  let result;
6091
- if (action === "remove")
6092
- result = removeAutostart(s);
6093
- else if (action === "status")
6094
- result = autostartStatus(s);
6095
- else {
6096
- try {
6097
- result = configureAutostart(s, action === "install" ? "install" : "refresh", requested, linger);
6098
- } catch (error) {
6099
- if (error instanceof ContractError && error.code === "invalid")
6100
- process.stderr.write(`Cadence autostart needs an existing absolute workspace directory (--workspace or CADENCE_WORKSPACE).
6538
+ try {
6539
+ result = await serverAction(s, action, requested, linger);
6540
+ } catch (error) {
6541
+ if (error instanceof ContractError && error.code === "invalid")
6542
+ process.stderr.write(`Cadence server install needs an existing absolute --workspace directory.
6101
6543
  `);
6102
- throw error;
6103
- }
6104
- if (result.status === "started" && result.workspace)
6105
- Object.assign(result, await observeStarted(result.workspace));
6106
- process.stderr.write(autostartGuidance(s, result));
6544
+ throw error;
6107
6545
  }
6546
+ if (result.status === "started" && result.workspace)
6547
+ Object.assign(result, await observeStarted(result.workspace));
6548
+ process.stderr.write(serverGuidance(s, action, result));
6108
6549
  process.stdout.write(`${JSON.stringify(result)}
6109
6550
  `);
6110
- if (["failed", "foreign", "unavailable"].includes(result.status))
6551
+ if ([
6552
+ "failed",
6553
+ "foreign",
6554
+ "unavailable",
6555
+ "unsupported",
6556
+ "unconfirmed",
6557
+ "quarantined",
6558
+ "missing-pi-sdk"
6559
+ ].includes(result.status) || result.status === "absent" && (action === "restart" || action === "stop"))
6111
6560
  process.exitCode = 1;
6112
6561
  }
6113
6562
  async function managementMain(argv, hostUrl) {
@@ -6116,16 +6565,16 @@ async function managementMain(argv, hostUrl) {
6116
6565
  throw new ContractError("unsupported-context");
6117
6566
  const command = argv.shift();
6118
6567
  if (command === "--help" && !argv.length) {
6119
- process.stdout.write(`cadence-service start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6120
- cadence-service autostart install [--workspace PATH] [--linger]|refresh|remove|status
6121
- Start is explicit, or the Linux systemd user unit that Pi's user-scope install registers (CADENCE_AUTOSTART=0, disable or mask opts out); its first registration also enables linger for boot start when logind allows it without a password (CADENCE_LINGER=0 opts out), and --linger asks for it explicitly. Default detached or --foreground. Start accepts --config, --agent-dir, --host, --port, --token-file.
6568
+ process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
6569
+ cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
6570
+ Start is explicit, or the background service that server install registers: a systemd user unit on Linux, started at boot through linger (--no-linger or CADENCE_LINGER=0 keeps it to login), a LaunchAgent on macOS and a Run entry on Windows, both started at login. Server restart, stop and uninstall use the orderly Stop. cadence-service is the same command. Default detached or --foreground. Start accepts --config, --agent-dir, --host, --port, --token-file.
6122
6571
  Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
6123
6572
  Recovery resolve requires this local native terminal, exact unchanged dead-owner resources and separate process/credential/storage acknowledgments. No --yes, network recovery, killing, repair or cleanup.
6124
6573
  `);
6125
6574
  return;
6126
6575
  }
6127
- if (command === "autostart") {
6128
- await autostartMain(argv, hostUrl);
6576
+ if (command === "server") {
6577
+ await serverMain(argv, hostUrl);
6129
6578
  return;
6130
6579
  }
6131
6580
  let recovery;
@@ -6192,7 +6641,7 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
6192
6641
  `);
6193
6642
  options.workspace = landing.workspace;
6194
6643
  if (command === "start")
6195
- mkdirSync4(landing.workspace, { recursive: true });
6644
+ mkdirSync5(landing.workspace, { recursive: true });
6196
6645
  }
6197
6646
  let result;
6198
6647
  if (command === "start")