@abelxiaoxing/cadence 2.0.4 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -25
- package/docs/service-management.md +79 -52
- package/package.json +4 -2
- package/src/cadence-service.mjs +4 -0
- package/src/index.ts +3 -3
- package/src/install-config.mjs +2 -25
- package/src/native-client.ts +60 -34
- package/src/service-cli.mjs +511 -111
- package/src/service-host.mjs +1 -2
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
package/src/service-cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { existsSync, mkdirSync as mkdirSync4 } from "node:fs";
|
|
7
7
|
import { request } from "node:http";
|
|
8
|
-
import { dirname as dirname8, join as
|
|
8
|
+
import { dirname as dirname8, join as join10, resolve as resolve5 } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
|
|
11
11
|
// src/task-types.ts
|
|
@@ -2484,14 +2484,27 @@ import {
|
|
|
2484
2484
|
writeFileSync as writeFileSync2
|
|
2485
2485
|
} from "node:fs";
|
|
2486
2486
|
import { homedir as homedir2 } from "node:os";
|
|
2487
|
-
import {
|
|
2487
|
+
import {
|
|
2488
|
+
delimiter,
|
|
2489
|
+
dirname as dirname2,
|
|
2490
|
+
isAbsolute as isAbsolute2,
|
|
2491
|
+
join as join2,
|
|
2492
|
+
resolve as resolve2,
|
|
2493
|
+
sep as sep2,
|
|
2494
|
+
win32
|
|
2495
|
+
} from "node:path";
|
|
2488
2496
|
var UNIT = "cadence.service";
|
|
2489
2497
|
var MARKER = "# Generated by @abelxiaoxing/cadence";
|
|
2490
|
-
function
|
|
2498
|
+
function wscript(env) {
|
|
2499
|
+
const root = env.SystemRoot;
|
|
2500
|
+
return win32.join(root && win32.isAbsolute(root) ? root : "C:\\Windows", "System32", "wscript.exe");
|
|
2501
|
+
}
|
|
2502
|
+
function hostSystem(root, owner) {
|
|
2491
2503
|
const run = (command, args) => {
|
|
2492
2504
|
const r = spawnSync2(command, args, {
|
|
2493
2505
|
encoding: "utf8",
|
|
2494
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2506
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2507
|
+
windowsHide: true
|
|
2495
2508
|
});
|
|
2496
2509
|
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2497
2510
|
};
|
|
@@ -2502,6 +2515,7 @@ function hostSystem(root) {
|
|
|
2502
2515
|
node: process.execPath,
|
|
2503
2516
|
root,
|
|
2504
2517
|
unit: UNIT,
|
|
2518
|
+
uid: process.getuid?.(),
|
|
2505
2519
|
systemctl: (args) => run("systemctl", ["--user", ...args]),
|
|
2506
2520
|
linger: () => {
|
|
2507
2521
|
const r = run("loginctl", [
|
|
@@ -2528,7 +2542,18 @@ function hostSystem(root) {
|
|
|
2528
2542
|
"false"
|
|
2529
2543
|
], { stdio: "ignore", timeout: 20000 });
|
|
2530
2544
|
return !r.error && r.status === 0;
|
|
2531
|
-
}
|
|
2545
|
+
},
|
|
2546
|
+
launchctl: (args) => run("launchctl", args),
|
|
2547
|
+
reg: (args) => run("reg", args),
|
|
2548
|
+
launch: (path) => {
|
|
2549
|
+
const r = spawnSync2(wscript(process.env), [path], {
|
|
2550
|
+
stdio: "ignore",
|
|
2551
|
+
windowsHide: true,
|
|
2552
|
+
timeout: 20000
|
|
2553
|
+
});
|
|
2554
|
+
return !r.error && r.status === 0;
|
|
2555
|
+
},
|
|
2556
|
+
...owner
|
|
2532
2557
|
};
|
|
2533
2558
|
}
|
|
2534
2559
|
function expandHome(value, home) {
|
|
@@ -2602,7 +2627,7 @@ var argument = (value) => quoted(value).replaceAll("$", "$$$$");
|
|
|
2602
2627
|
function buildUnit(u) {
|
|
2603
2628
|
const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
|
|
2604
2629
|
return [
|
|
2605
|
-
`${MARKER}
|
|
2630
|
+
`${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
|
|
2606
2631
|
"# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
|
|
2607
2632
|
"[Unit]",
|
|
2608
2633
|
"Description=Cadence shared task service and WebUI",
|
|
@@ -2630,19 +2655,20 @@ function buildUnit(u) {
|
|
|
2630
2655
|
].join(`
|
|
2631
2656
|
`);
|
|
2632
2657
|
}
|
|
2633
|
-
function readGenerated(path) {
|
|
2658
|
+
function readGenerated(path, marker) {
|
|
2634
2659
|
try {
|
|
2635
2660
|
if (!lstatSync2(path).isFile())
|
|
2636
2661
|
return null;
|
|
2637
|
-
const
|
|
2638
|
-
|
|
2662
|
+
const bytes = readFileSync(path);
|
|
2663
|
+
const text2 = bytes[0] === 255 && bytes[1] === 254 ? bytes.subarray(2).toString("utf16le") : bytes.toString("utf8");
|
|
2664
|
+
return text2.startsWith(marker) ? text2 : null;
|
|
2639
2665
|
} catch (error) {
|
|
2640
2666
|
if (error.code === "ENOENT")
|
|
2641
2667
|
return;
|
|
2642
2668
|
throw error;
|
|
2643
2669
|
}
|
|
2644
2670
|
}
|
|
2645
|
-
function
|
|
2671
|
+
function unitWorkspace(text2) {
|
|
2646
2672
|
return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
|
|
2647
2673
|
}
|
|
2648
2674
|
function directory(path) {
|
|
@@ -2652,23 +2678,20 @@ function directory(path) {
|
|
|
2652
2678
|
return false;
|
|
2653
2679
|
}
|
|
2654
2680
|
}
|
|
2655
|
-
function
|
|
2656
|
-
const state = s.env.XDG_STATE_HOME;
|
|
2657
|
-
return state && isAbsolute2(state) ? state : join2(s.home, ".local", "state");
|
|
2681
|
+
function serviceStateHome(s) {
|
|
2682
|
+
const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
|
|
2683
|
+
return state && isAbsolute2(state) ? state : join2(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
|
|
2658
2684
|
}
|
|
2659
|
-
function selectWorkspace(s, requested,
|
|
2685
|
+
function selectWorkspace(s, requested, kept) {
|
|
2660
2686
|
let chosen;
|
|
2661
2687
|
if (requested) {
|
|
2662
2688
|
chosen = expandHome(requested, s.home);
|
|
2663
2689
|
if (!directory(chosen))
|
|
2664
2690
|
throw new ContractError("invalid");
|
|
2665
2691
|
chosen = resolve2(chosen);
|
|
2666
|
-
} else
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
chosen = kept;
|
|
2670
|
-
}
|
|
2671
|
-
const landing = chosen ? effectiveWorkspace(chosen, unitStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
|
|
2692
|
+
} else if (kept && directory(kept))
|
|
2693
|
+
chosen = kept;
|
|
2694
|
+
const landing = chosen ? effectiveWorkspace(chosen, serviceStateHome(s), s.home) : { workspace: join2(s.home, LANDING_WORKSPACE), redirected: false };
|
|
2672
2695
|
if (landing.workspace !== chosen)
|
|
2673
2696
|
mkdirSync2(landing.workspace, { recursive: true });
|
|
2674
2697
|
return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
|
|
@@ -2690,7 +2713,18 @@ function regularFile(path) {
|
|
|
2690
2713
|
return false;
|
|
2691
2714
|
}
|
|
2692
2715
|
}
|
|
2693
|
-
function
|
|
2716
|
+
async function settle(s, workspace) {
|
|
2717
|
+
if (!workspace)
|
|
2718
|
+
return;
|
|
2719
|
+
const status = await s.stopOwner(workspace);
|
|
2720
|
+
if (status === "stopped")
|
|
2721
|
+
return;
|
|
2722
|
+
return {
|
|
2723
|
+
status: status === "quarantined" || status === "unavailable" ? status : "unconfirmed",
|
|
2724
|
+
workspace
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
function bootStart(s, attempt) {
|
|
2694
2728
|
const current = s.linger();
|
|
2695
2729
|
if (current === undefined)
|
|
2696
2730
|
return {};
|
|
@@ -2698,23 +2732,25 @@ function bootStart(s, attempt, interactive) {
|
|
|
2698
2732
|
return { linger: "on" };
|
|
2699
2733
|
if (!attempt)
|
|
2700
2734
|
return { linger: "off" };
|
|
2701
|
-
return {
|
|
2735
|
+
return {
|
|
2736
|
+
linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
|
|
2737
|
+
};
|
|
2702
2738
|
}
|
|
2703
2739
|
function configureAutostart(s, mode, requested, linger = false) {
|
|
2704
2740
|
if (s.platform !== "linux")
|
|
2705
|
-
return { status: "
|
|
2706
|
-
if (mode === "refresh" && (s.env.CADENCE_AUTOSTART === "0" || !piUserInstall(s)))
|
|
2707
|
-
return { status: "skipped" };
|
|
2741
|
+
return { status: "unsupported" };
|
|
2708
2742
|
if (s.systemctl(["show-environment"]).status !== 0)
|
|
2709
2743
|
return { status: "unavailable" };
|
|
2710
2744
|
const path = unitPath(s);
|
|
2711
|
-
const previous = readGenerated(path);
|
|
2745
|
+
const previous = readGenerated(path, MARKER);
|
|
2712
2746
|
if (previous === null)
|
|
2713
2747
|
return { status: "foreign", unit: path };
|
|
2748
|
+
if (mode === "restart" && previous === undefined)
|
|
2749
|
+
return { status: "absent" };
|
|
2714
2750
|
const sdk = sdkPackages(s.root);
|
|
2715
2751
|
if (!sdk)
|
|
2716
2752
|
return { status: "missing-pi-sdk" };
|
|
2717
|
-
const { workspace, redirectedFrom } = selectWorkspace(s, requested
|
|
2753
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
|
|
2718
2754
|
const agentDir = agentDirectory(s);
|
|
2719
2755
|
const content = buildUnit({
|
|
2720
2756
|
node: s.node,
|
|
@@ -2737,23 +2773,37 @@ function configureAutostart(s, mode, requested, linger = false) {
|
|
|
2737
2773
|
};
|
|
2738
2774
|
if (enabled.startsWith("masked"))
|
|
2739
2775
|
return { status: "masked", ...result };
|
|
2740
|
-
|
|
2741
|
-
if (wanted && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
2776
|
+
if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
2742
2777
|
return { status: "failed", ...result };
|
|
2743
|
-
|
|
2744
|
-
return { status: "disabled", ...result };
|
|
2745
|
-
const boot = () => wanted ? bootStart(s, mode === "install" ? linger : previous === undefined && s.env.CADENCE_LINGER !== "0", mode === "install") : {};
|
|
2778
|
+
const boot = () => mode === "install" ? bootStart(s, linger) : {};
|
|
2746
2779
|
if (!regularFile(join2(agentDir, "auth.json")))
|
|
2747
2780
|
return { status: "waiting-for-pi-login", ...result, ...boot() };
|
|
2748
2781
|
s.systemctl(["reset-failed", s.unit]);
|
|
2749
2782
|
const started = s.systemctl(["start", s.unit]).status === 0;
|
|
2750
2783
|
return { status: started ? "started" : "failed", ...result, ...boot() };
|
|
2751
2784
|
}
|
|
2785
|
+
function stopAutostart(s) {
|
|
2786
|
+
if (s.platform !== "linux")
|
|
2787
|
+
return { status: "unsupported" };
|
|
2788
|
+
const path = unitPath(s);
|
|
2789
|
+
const previous = readGenerated(path, MARKER);
|
|
2790
|
+
if (previous === null)
|
|
2791
|
+
return { status: "foreign", unit: path };
|
|
2792
|
+
if (previous === undefined)
|
|
2793
|
+
return { status: "absent" };
|
|
2794
|
+
if (s.systemctl(["show-environment"]).status !== 0)
|
|
2795
|
+
return { status: "unavailable", unit: path };
|
|
2796
|
+
return {
|
|
2797
|
+
status: s.systemctl(["stop", s.unit]).status === 0 ? "stopped" : "failed",
|
|
2798
|
+
unit: path,
|
|
2799
|
+
workspace: unitWorkspace(previous)
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2752
2802
|
function removeAutostart(s) {
|
|
2753
2803
|
if (s.platform !== "linux")
|
|
2754
|
-
return { status: "
|
|
2804
|
+
return { status: "unsupported" };
|
|
2755
2805
|
const path = unitPath(s);
|
|
2756
|
-
const previous = readGenerated(path);
|
|
2806
|
+
const previous = readGenerated(path, MARKER);
|
|
2757
2807
|
if (previous === null)
|
|
2758
2808
|
return { status: "foreign", unit: path };
|
|
2759
2809
|
if (previous === undefined)
|
|
@@ -2769,9 +2819,9 @@ function removeAutostart(s) {
|
|
|
2769
2819
|
}
|
|
2770
2820
|
function autostartStatus(s) {
|
|
2771
2821
|
if (s.platform !== "linux")
|
|
2772
|
-
return { status: "
|
|
2822
|
+
return { status: "unsupported" };
|
|
2773
2823
|
const path = unitPath(s);
|
|
2774
|
-
const previous = readGenerated(path);
|
|
2824
|
+
const previous = readGenerated(path, MARKER);
|
|
2775
2825
|
if (previous === null)
|
|
2776
2826
|
return { status: "foreign", unit: path };
|
|
2777
2827
|
if (previous === undefined)
|
|
@@ -2780,8 +2830,8 @@ function autostartStatus(s) {
|
|
|
2780
2830
|
status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
|
|
2781
2831
|
enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
|
|
2782
2832
|
unit: path,
|
|
2783
|
-
workspace:
|
|
2784
|
-
...bootStart(s, false
|
|
2833
|
+
workspace: unitWorkspace(previous),
|
|
2834
|
+
...bootStart(s, false)
|
|
2785
2835
|
};
|
|
2786
2836
|
}
|
|
2787
2837
|
|
|
@@ -3641,13 +3691,277 @@ class OwnerLease {
|
|
|
3641
3691
|
}
|
|
3642
3692
|
}
|
|
3643
3693
|
|
|
3694
|
+
// src/service/launchd.ts
|
|
3695
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
3696
|
+
import { join as join5 } from "node:path";
|
|
3697
|
+
var LABEL = "com.abelxiaoxing.cadence";
|
|
3698
|
+
var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3699
|
+
<!-- Generated by @abelxiaoxing/cadence`;
|
|
3700
|
+
function plistPath(s) {
|
|
3701
|
+
return join5(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
3702
|
+
}
|
|
3703
|
+
var xml = (value) => checked(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
3704
|
+
function buildPlist(u) {
|
|
3705
|
+
const string = (value) => ` <string>${xml(value)}</string>`;
|
|
3706
|
+
return [
|
|
3707
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
3708
|
+
"<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
|
|
3709
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
3710
|
+
'<plist version="1.0">',
|
|
3711
|
+
"<dict>",
|
|
3712
|
+
"\t<key>Label</key>",
|
|
3713
|
+
` <string>${LABEL}</string>`,
|
|
3714
|
+
"\t<key>ProgramArguments</key>",
|
|
3715
|
+
"\t<array>",
|
|
3716
|
+
...[u.node, u.cli, "start", "--foreground", "--workspace", u.workspace].map(string),
|
|
3717
|
+
"\t</array>",
|
|
3718
|
+
"\t<key>WorkingDirectory</key>",
|
|
3719
|
+
` <string>${xml(u.workspace)}</string>`,
|
|
3720
|
+
"\t<key>EnvironmentVariables</key>",
|
|
3721
|
+
"\t<dict>",
|
|
3722
|
+
...u.environment.flatMap(([k, v]) => [
|
|
3723
|
+
` <key>${xml(k)}</key>`,
|
|
3724
|
+
string(v)
|
|
3725
|
+
]),
|
|
3726
|
+
"\t</dict>",
|
|
3727
|
+
"\t<key>RunAtLoad</key>",
|
|
3728
|
+
"\t<true/>",
|
|
3729
|
+
"\t<key>ProcessType</key>",
|
|
3730
|
+
"\t<string>Background</string>",
|
|
3731
|
+
"\t<key>StandardOutPath</key>",
|
|
3732
|
+
` <string>${xml(u.log)}</string>`,
|
|
3733
|
+
"\t<key>StandardErrorPath</key>",
|
|
3734
|
+
` <string>${xml(u.log)}</string>`,
|
|
3735
|
+
"</dict>",
|
|
3736
|
+
"</plist>",
|
|
3737
|
+
""
|
|
3738
|
+
].join(`
|
|
3739
|
+
`);
|
|
3740
|
+
}
|
|
3741
|
+
function plistWorkspace(text2) {
|
|
3742
|
+
const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
|
|
3743
|
+
return m?.[1].replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
3744
|
+
}
|
|
3745
|
+
var domain = (s) => `gui/${s.uid}`;
|
|
3746
|
+
var target = (s) => `${domain(s)}/${LABEL}`;
|
|
3747
|
+
var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
|
|
3748
|
+
function disabled(s) {
|
|
3749
|
+
return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
|
|
3750
|
+
}
|
|
3751
|
+
async function configureLaunchAgent(s, mode, requested) {
|
|
3752
|
+
if (s.uid === undefined)
|
|
3753
|
+
return { status: "unavailable" };
|
|
3754
|
+
const path = plistPath(s);
|
|
3755
|
+
const previous = readGenerated(path, MARKER2);
|
|
3756
|
+
if (previous === null)
|
|
3757
|
+
return { status: "foreign", unit: path };
|
|
3758
|
+
if (mode === "restart" && previous === undefined)
|
|
3759
|
+
return { status: "absent" };
|
|
3760
|
+
const sdk = sdkPackages(s.root);
|
|
3761
|
+
if (!sdk)
|
|
3762
|
+
return { status: "missing-pi-sdk" };
|
|
3763
|
+
const kept = plistWorkspace(previous);
|
|
3764
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
|
|
3765
|
+
const refused2 = await settle(s, kept);
|
|
3766
|
+
if (refused2)
|
|
3767
|
+
return { ...refused2, unit: path };
|
|
3768
|
+
if (loaded(s))
|
|
3769
|
+
s.launchctl(["bootout", target(s)]);
|
|
3770
|
+
write(path, buildPlist({
|
|
3771
|
+
node: s.node,
|
|
3772
|
+
cli: join5(s.root, "src", "service-cli.mjs"),
|
|
3773
|
+
workspace,
|
|
3774
|
+
environment: serviceEnvironment(s),
|
|
3775
|
+
log: join5(s.home, "Library", "Logs", "cadence.log")
|
|
3776
|
+
}));
|
|
3777
|
+
const result = {
|
|
3778
|
+
unit: path,
|
|
3779
|
+
workspace,
|
|
3780
|
+
...redirectedFrom ? { redirectedFrom } : {}
|
|
3781
|
+
};
|
|
3782
|
+
if (mode === "install")
|
|
3783
|
+
s.launchctl(["enable", target(s)]);
|
|
3784
|
+
else if (disabled(s))
|
|
3785
|
+
return { status: "disabled", ...result };
|
|
3786
|
+
if (!regularFile(join5(agentDirectory(s), "auth.json")))
|
|
3787
|
+
return { status: "waiting-for-pi-login", ...result };
|
|
3788
|
+
const started = s.launchctl(["bootstrap", domain(s), path]).status === 0;
|
|
3789
|
+
return { status: started ? "started" : "failed", ...result };
|
|
3790
|
+
}
|
|
3791
|
+
async function stopLaunchAgent(s) {
|
|
3792
|
+
const path = plistPath(s);
|
|
3793
|
+
const previous = readGenerated(path, MARKER2);
|
|
3794
|
+
if (previous === null)
|
|
3795
|
+
return { status: "foreign", unit: path };
|
|
3796
|
+
if (previous === undefined)
|
|
3797
|
+
return { status: "absent" };
|
|
3798
|
+
const workspace = plistWorkspace(previous);
|
|
3799
|
+
const refused2 = await settle(s, workspace);
|
|
3800
|
+
return { ...refused2 ?? { status: "stopped", workspace }, unit: path };
|
|
3801
|
+
}
|
|
3802
|
+
async function removeLaunchAgent(s) {
|
|
3803
|
+
const path = plistPath(s);
|
|
3804
|
+
const previous = readGenerated(path, MARKER2);
|
|
3805
|
+
if (previous === null)
|
|
3806
|
+
return { status: "foreign", unit: path };
|
|
3807
|
+
if (previous === undefined)
|
|
3808
|
+
return { status: "absent" };
|
|
3809
|
+
const refused2 = await settle(s, plistWorkspace(previous));
|
|
3810
|
+
if (refused2)
|
|
3811
|
+
return { ...refused2, unit: path };
|
|
3812
|
+
if (s.uid !== undefined && loaded(s))
|
|
3813
|
+
s.launchctl(["bootout", target(s)]);
|
|
3814
|
+
rmSync2(path);
|
|
3815
|
+
return { status: "removed", unit: path };
|
|
3816
|
+
}
|
|
3817
|
+
function launchAgentStatus(s) {
|
|
3818
|
+
const path = plistPath(s);
|
|
3819
|
+
const previous = readGenerated(path, MARKER2);
|
|
3820
|
+
if (previous === null)
|
|
3821
|
+
return { status: "foreign", unit: path };
|
|
3822
|
+
if (previous === undefined)
|
|
3823
|
+
return { status: "absent" };
|
|
3824
|
+
const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
|
|
3825
|
+
return {
|
|
3826
|
+
status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
|
|
3827
|
+
enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
|
|
3828
|
+
unit: path,
|
|
3829
|
+
workspace: plistWorkspace(previous)
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
// src/service/login-run.ts
|
|
3834
|
+
import { rmSync as rmSync3 } from "node:fs";
|
|
3835
|
+
import { isAbsolute as isAbsolute5, join as join6 } from "node:path";
|
|
3836
|
+
var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
3837
|
+
var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
|
|
3838
|
+
var RUN_VALUE = "Cadence";
|
|
3839
|
+
var MARKER3 = "' Generated by @abelxiaoxing/cadence";
|
|
3840
|
+
function launcherPath(s) {
|
|
3841
|
+
const local = s.env.LOCALAPPDATA;
|
|
3842
|
+
return join6(local && isAbsolute5(local) ? local : join6(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
|
|
3843
|
+
}
|
|
3844
|
+
var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
|
|
3845
|
+
function argument2(value) {
|
|
3846
|
+
if (checked(value).includes('"'))
|
|
3847
|
+
throw new ContractError("invalid");
|
|
3848
|
+
return `"${value}"`;
|
|
3849
|
+
}
|
|
3850
|
+
function buildLauncher(u) {
|
|
3851
|
+
const command = `${argument2(u.node)} ${argument2(u.cli)} start --workspace ${argument2(u.workspace)}`;
|
|
3852
|
+
return [
|
|
3853
|
+
`${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
|
|
3854
|
+
`' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
|
|
3855
|
+
'Set shell = CreateObject("WScript.Shell")',
|
|
3856
|
+
...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
|
|
3857
|
+
...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
|
|
3858
|
+
`shell.CurrentDirectory = ${literal2(u.workspace)}`,
|
|
3859
|
+
`shell.Run ${literal2(command)}, 0, False`,
|
|
3860
|
+
""
|
|
3861
|
+
].join(`\r
|
|
3862
|
+
`);
|
|
3863
|
+
}
|
|
3864
|
+
var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
|
|
3865
|
+
function launcherWorkspace(text2) {
|
|
3866
|
+
return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
|
|
3867
|
+
}
|
|
3868
|
+
var runEntry = (path, s) => `"${wscript(s.env)}" "${path}"`;
|
|
3869
|
+
var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
|
|
3870
|
+
function approvedOff(s) {
|
|
3871
|
+
const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
|
|
3872
|
+
const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
|
|
3873
|
+
return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
|
|
3874
|
+
}
|
|
3875
|
+
async function configureLoginRun(s, mode, requested) {
|
|
3876
|
+
const path = launcherPath(s);
|
|
3877
|
+
const previous = readGenerated(path, MARKER3);
|
|
3878
|
+
if (previous === null)
|
|
3879
|
+
return { status: "foreign", unit: path };
|
|
3880
|
+
if (previous === undefined && present(s, RUN_KEY))
|
|
3881
|
+
return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
|
|
3882
|
+
if (mode === "restart" && previous === undefined)
|
|
3883
|
+
return { status: "absent" };
|
|
3884
|
+
const sdk = sdkPackages(s.root);
|
|
3885
|
+
if (!sdk)
|
|
3886
|
+
return { status: "missing-pi-sdk" };
|
|
3887
|
+
const kept = launcherWorkspace(previous);
|
|
3888
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
|
|
3889
|
+
const refused2 = await settle(s, kept);
|
|
3890
|
+
if (refused2)
|
|
3891
|
+
return { ...refused2, unit: path };
|
|
3892
|
+
const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
|
|
3893
|
+
write(path, utf16(buildLauncher({
|
|
3894
|
+
node: s.node,
|
|
3895
|
+
cli: join6(s.root, "src", "service-cli.mjs"),
|
|
3896
|
+
workspace,
|
|
3897
|
+
environment
|
|
3898
|
+
})));
|
|
3899
|
+
const result = {
|
|
3900
|
+
unit: path,
|
|
3901
|
+
workspace,
|
|
3902
|
+
...redirectedFrom ? { redirectedFrom } : {}
|
|
3903
|
+
};
|
|
3904
|
+
if (mode === "install") {
|
|
3905
|
+
const entry = ["/v", RUN_VALUE, "/t", "REG_SZ", "/d", runEntry(path, s)];
|
|
3906
|
+
if (s.reg(["add", RUN_KEY, ...entry, "/f"]).status !== 0)
|
|
3907
|
+
return { status: "failed", ...result };
|
|
3908
|
+
s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
|
|
3909
|
+
}
|
|
3910
|
+
if (!regularFile(join6(agentDirectory(s), "auth.json")))
|
|
3911
|
+
return { status: "waiting-for-pi-login", ...result };
|
|
3912
|
+
return { status: s.launch(path) ? "started" : "failed", ...result };
|
|
3913
|
+
}
|
|
3914
|
+
async function stopLoginRun(s) {
|
|
3915
|
+
const path = launcherPath(s);
|
|
3916
|
+
const previous = readGenerated(path, MARKER3);
|
|
3917
|
+
if (previous === null)
|
|
3918
|
+
return { status: "foreign", unit: path };
|
|
3919
|
+
if (previous === undefined)
|
|
3920
|
+
return { status: "absent" };
|
|
3921
|
+
const workspace = launcherWorkspace(previous);
|
|
3922
|
+
const refused2 = await settle(s, workspace);
|
|
3923
|
+
return { ...refused2 ?? { status: "stopped", workspace }, unit: path };
|
|
3924
|
+
}
|
|
3925
|
+
async function removeLoginRun(s) {
|
|
3926
|
+
const path = launcherPath(s);
|
|
3927
|
+
const previous = readGenerated(path, MARKER3);
|
|
3928
|
+
if (previous === null)
|
|
3929
|
+
return { status: "foreign", unit: path };
|
|
3930
|
+
if (previous === undefined)
|
|
3931
|
+
return { status: "absent" };
|
|
3932
|
+
const refused2 = await settle(s, launcherWorkspace(previous));
|
|
3933
|
+
if (refused2)
|
|
3934
|
+
return { ...refused2, unit: path };
|
|
3935
|
+
for (const key of [RUN_KEY, APPROVED_KEY])
|
|
3936
|
+
if (present(s, key))
|
|
3937
|
+
s.reg(["delete", key, "/v", RUN_VALUE, "/f"]);
|
|
3938
|
+
rmSync3(path);
|
|
3939
|
+
return { status: "removed", unit: path };
|
|
3940
|
+
}
|
|
3941
|
+
async function loginRunStatus(s) {
|
|
3942
|
+
const path = launcherPath(s);
|
|
3943
|
+
const previous = readGenerated(path, MARKER3);
|
|
3944
|
+
if (previous === null)
|
|
3945
|
+
return { status: "foreign", unit: path };
|
|
3946
|
+
if (previous === undefined)
|
|
3947
|
+
return { status: "absent" };
|
|
3948
|
+
const workspace = launcherWorkspace(previous);
|
|
3949
|
+
const owner = workspace ? await s.ownerStatus(workspace) : "unavailable";
|
|
3950
|
+
return {
|
|
3951
|
+
status: owner === "alive" ? "active" : "inactive",
|
|
3952
|
+
enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
|
|
3953
|
+
unit: path,
|
|
3954
|
+
workspace
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3644
3958
|
// src/service/recovery-cli.ts
|
|
3645
3959
|
import { isatty } from "node:tty";
|
|
3646
3960
|
|
|
3647
3961
|
// src/history/recovery.ts
|
|
3648
3962
|
import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
|
|
3649
3963
|
import { lstatSync as lstatSync6, opendirSync as opendirSync3 } from "node:fs";
|
|
3650
|
-
import { join as
|
|
3964
|
+
import { join as join8, relative as relative4 } from "node:path";
|
|
3651
3965
|
|
|
3652
3966
|
// src/service/recovery.ts
|
|
3653
3967
|
async function inspectRecovery(coordination, history) {
|
|
@@ -3707,7 +4021,7 @@ import {
|
|
|
3707
4021
|
statfsSync
|
|
3708
4022
|
} from "node:fs";
|
|
3709
4023
|
import { open } from "node:fs/promises";
|
|
3710
|
-
import { dirname as dirname6, join as
|
|
4024
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
3711
4025
|
|
|
3712
4026
|
// src/history/admission.ts
|
|
3713
4027
|
function decodeRunAdmission(value) {
|
|
@@ -4238,14 +4552,14 @@ class HistoryFiles {
|
|
|
4238
4552
|
this.coordination = coordination;
|
|
4239
4553
|
this.format = format;
|
|
4240
4554
|
this.formatSnapshot = formatSnapshot;
|
|
4241
|
-
this.directory =
|
|
4555
|
+
this.directory = join7(coordination.directory, "history-v2");
|
|
4242
4556
|
this.files = coordination.files;
|
|
4243
4557
|
}
|
|
4244
4558
|
static open(coordination) {
|
|
4245
|
-
const dir =
|
|
4559
|
+
const dir = join7(coordination.directory, "history-v2");
|
|
4246
4560
|
if (!coordination.files.directory(dir))
|
|
4247
4561
|
return;
|
|
4248
|
-
const file = coordination.files.read(
|
|
4562
|
+
const file = coordination.files.read(join7(dir, "format.json"));
|
|
4249
4563
|
if (!file)
|
|
4250
4564
|
corrupt();
|
|
4251
4565
|
const format = decodeFormat(json(file));
|
|
@@ -4261,7 +4575,7 @@ class HistoryFiles {
|
|
|
4261
4575
|
existing.assertWritable();
|
|
4262
4576
|
return existing;
|
|
4263
4577
|
}
|
|
4264
|
-
const dir =
|
|
4578
|
+
const dir = join7(c.directory, "history-v2");
|
|
4265
4579
|
const format = {
|
|
4266
4580
|
schema: FORMAT,
|
|
4267
4581
|
storeId: randomUUID4(),
|
|
@@ -4271,19 +4585,19 @@ class HistoryFiles {
|
|
|
4271
4585
|
c.files.directory(dir, true);
|
|
4272
4586
|
for (const name of ["commands", "ledger", "runs", "indexes"]) {
|
|
4273
4587
|
lease.assert();
|
|
4274
|
-
c.files.directory(
|
|
4588
|
+
c.files.directory(join7(dir, name), true);
|
|
4275
4589
|
}
|
|
4276
4590
|
lease.assert();
|
|
4277
|
-
const keyPath =
|
|
4591
|
+
const keyPath = join7(dir, "read-proof-key.json");
|
|
4278
4592
|
c.files.create(keyPath, encoded({
|
|
4279
4593
|
schema: "cadence-history-read-key/1",
|
|
4280
4594
|
storeId: format.storeId,
|
|
4281
4595
|
key: randomBytes2(32).toString("base64url")
|
|
4282
4596
|
}));
|
|
4283
4597
|
lease.assert();
|
|
4284
|
-
const snapshot = c.files.create(
|
|
4598
|
+
const snapshot = c.files.create(join7(dir, "format.json"), encoded(format));
|
|
4285
4599
|
const result = new HistoryFiles(c, format, snapshot);
|
|
4286
|
-
result.syncParents(
|
|
4600
|
+
result.syncParents(join7(dir, "format.json"));
|
|
4287
4601
|
result.assertWritable();
|
|
4288
4602
|
return result;
|
|
4289
4603
|
}
|
|
@@ -4318,7 +4632,7 @@ class HistoryFiles {
|
|
|
4318
4632
|
throw new ContractError("quarantine");
|
|
4319
4633
|
}
|
|
4320
4634
|
assertFormat() {
|
|
4321
|
-
this.files.assert(
|
|
4635
|
+
this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot);
|
|
4322
4636
|
}
|
|
4323
4637
|
readProofMaterial(budget) {
|
|
4324
4638
|
if (this.format.schema !== FORMAT)
|
|
@@ -4327,7 +4641,7 @@ class HistoryFiles {
|
|
|
4327
4641
|
this.assertReadFormat(budget);
|
|
4328
4642
|
else
|
|
4329
4643
|
this.assertFormat();
|
|
4330
|
-
const selected2 = this.readBudgeted(
|
|
4644
|
+
const selected2 = this.readBudgeted(join7(this.directory, "read-proof-key.json"), 512, budget);
|
|
4331
4645
|
if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
|
|
4332
4646
|
corrupt();
|
|
4333
4647
|
try {
|
|
@@ -4355,17 +4669,17 @@ class HistoryFiles {
|
|
|
4355
4669
|
return;
|
|
4356
4670
|
}
|
|
4357
4671
|
this.assertFormat();
|
|
4358
|
-
assertIncarnation(this,
|
|
4672
|
+
assertIncarnation(this, join7(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
|
|
4359
4673
|
}
|
|
4360
4674
|
segmentPath(segment) {
|
|
4361
|
-
return
|
|
4675
|
+
return join7(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
|
|
4362
4676
|
}
|
|
4363
4677
|
artifactPath(ref) {
|
|
4364
|
-
return
|
|
4678
|
+
return join7(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
|
|
4365
4679
|
}
|
|
4366
4680
|
admissionPath(runId) {
|
|
4367
4681
|
decodeId(runId);
|
|
4368
|
-
return
|
|
4682
|
+
return join7(this.directory, "runs", runId, "admission.json");
|
|
4369
4683
|
}
|
|
4370
4684
|
writeAdmission(lease, input) {
|
|
4371
4685
|
lease.assert();
|
|
@@ -4391,7 +4705,7 @@ class HistoryFiles {
|
|
|
4391
4705
|
readCommand(commandId, budget) {
|
|
4392
4706
|
decodeId(commandId);
|
|
4393
4707
|
this.assertReadFormat(budget);
|
|
4394
|
-
return this.readBudgeted(
|
|
4708
|
+
return this.readBudgeted(join7(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
|
|
4395
4709
|
}
|
|
4396
4710
|
readAdmission(runId, identity, budget) {
|
|
4397
4711
|
return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
|
|
@@ -4429,7 +4743,7 @@ class HistoryFiles {
|
|
|
4429
4743
|
}
|
|
4430
4744
|
assertReadFormat(budget) {
|
|
4431
4745
|
budget.reserve(this.formatSnapshot.bytes.length);
|
|
4432
|
-
this.files.assert(
|
|
4746
|
+
this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
|
|
4433
4747
|
}
|
|
4434
4748
|
assertPublished(segment, end, seq) {
|
|
4435
4749
|
const fence = publicationFences.get(this.directory);
|
|
@@ -4467,7 +4781,7 @@ class HistoryFiles {
|
|
|
4467
4781
|
const dir = opendirSync2(path);
|
|
4468
4782
|
try {
|
|
4469
4783
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
4470
|
-
const p =
|
|
4784
|
+
const p = join7(path, e.name), st = lstatSync5(p);
|
|
4471
4785
|
if (st.isDirectory())
|
|
4472
4786
|
bytes += walk(p, depth + 1);
|
|
4473
4787
|
else {
|
|
@@ -4502,7 +4816,7 @@ class HistoryFiles {
|
|
|
4502
4816
|
state.bytes = 0;
|
|
4503
4817
|
state.identity = null;
|
|
4504
4818
|
let count = 0;
|
|
4505
|
-
const ledger =
|
|
4819
|
+
const ledger = join7(this.directory, "ledger");
|
|
4506
4820
|
if (!this.files.directory(ledger))
|
|
4507
4821
|
corrupt();
|
|
4508
4822
|
const dir = opendirSync2(ledger);
|
|
@@ -4819,7 +5133,7 @@ class HistoryFiles {
|
|
|
4819
5133
|
checkpoint(lease, state) {
|
|
4820
5134
|
lease.assert();
|
|
4821
5135
|
this.assertWritable();
|
|
4822
|
-
const path =
|
|
5136
|
+
const path = join7(this.directory, "checkpoint.json"), old = this.files.read(path);
|
|
4823
5137
|
const body = encoded({
|
|
4824
5138
|
schema: "cadence-history-checkpoint/2",
|
|
4825
5139
|
storeId: this.format.storeId,
|
|
@@ -5246,7 +5560,7 @@ function digestHistory(files, exclude) {
|
|
|
5246
5560
|
dir.closeSync();
|
|
5247
5561
|
}
|
|
5248
5562
|
for (const name of names2.sort()) {
|
|
5249
|
-
const selected2 =
|
|
5563
|
+
const selected2 = join8(path, name);
|
|
5250
5564
|
if (selected2 === exclude)
|
|
5251
5565
|
continue;
|
|
5252
5566
|
const rel = relative4(files.directory, selected2);
|
|
@@ -5557,7 +5871,7 @@ async function localRecovery(c, operation) {
|
|
|
5557
5871
|
|
|
5558
5872
|
// src/service/runtime.ts
|
|
5559
5873
|
import { homedir as homedir3 } from "node:os";
|
|
5560
|
-
import { dirname as dirname7, isAbsolute as
|
|
5874
|
+
import { dirname as dirname7, isAbsolute as isAbsolute6, join as join9, resolve as resolve4 } from "node:path";
|
|
5561
5875
|
|
|
5562
5876
|
// src/service/config.ts
|
|
5563
5877
|
import { isIP } from "node:net";
|
|
@@ -5611,7 +5925,7 @@ function localOrigin(policy, actualPort) {
|
|
|
5611
5925
|
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
5612
5926
|
function absolute(value) {
|
|
5613
5927
|
const result = text(value, 4096, true);
|
|
5614
|
-
if (!
|
|
5928
|
+
if (!isAbsolute6(result))
|
|
5615
5929
|
throw new ContractError("invalid");
|
|
5616
5930
|
return result;
|
|
5617
5931
|
}
|
|
@@ -5706,7 +6020,7 @@ function selectStart(options, ambient) {
|
|
|
5706
6020
|
port: options.port ?? 17423,
|
|
5707
6021
|
proxy: null
|
|
5708
6022
|
},
|
|
5709
|
-
agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ??
|
|
6023
|
+
agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join9(ambient.HOME ?? ambient.USERPROFILE ?? homedir3(), ".pi", "agent")),
|
|
5710
6024
|
tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
|
|
5711
6025
|
tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
|
|
5712
6026
|
});
|
|
@@ -5878,10 +6192,10 @@ function startupGuidance(result, input) {
|
|
|
5878
6192
|
return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
|
|
5879
6193
|
`;
|
|
5880
6194
|
case "pi-auth":
|
|
5881
|
-
return `${
|
|
6195
|
+
return `${join10(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
|
|
5882
6196
|
`;
|
|
5883
6197
|
case "pi-models":
|
|
5884
|
-
return `${
|
|
6198
|
+
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
6199
|
`;
|
|
5886
6200
|
case "pi-runtime":
|
|
5887
6201
|
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 +6321,59 @@ async function observeStarted(workspace) {
|
|
|
6007
6321
|
}
|
|
6008
6322
|
return { service };
|
|
6009
6323
|
}
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6324
|
+
var ownerControl = {
|
|
6325
|
+
async ownerStatus(workspace) {
|
|
6326
|
+
try {
|
|
6327
|
+
const observed = await serviceStatus(new ServiceCoordination(workspace));
|
|
6328
|
+
return observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
|
|
6329
|
+
} catch {
|
|
6330
|
+
return "unavailable";
|
|
6331
|
+
}
|
|
6332
|
+
},
|
|
6333
|
+
async stopOwner(workspace) {
|
|
6334
|
+
try {
|
|
6335
|
+
const c = new ServiceCoordination(workspace);
|
|
6336
|
+
const observed = await serviceStatus(c);
|
|
6337
|
+
const status = observed && typeof observed === "object" && "status" in observed ? String(observed.status) : "unavailable";
|
|
6338
|
+
if (status === "absent" || status === "stopped")
|
|
6339
|
+
return "stopped";
|
|
6340
|
+
return status === "alive" ? (await stopService(c)).status : status;
|
|
6341
|
+
} catch {
|
|
6342
|
+
return "unavailable";
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6345
|
+
};
|
|
6346
|
+
async function serverAction(s, action, requested, linger) {
|
|
6347
|
+
const install = action === "install" || action === "restart";
|
|
6348
|
+
const mode = action === "install" ? "install" : "restart";
|
|
6349
|
+
switch (s.platform) {
|
|
6350
|
+
case "linux":
|
|
6351
|
+
if (install)
|
|
6352
|
+
return configureAutostart(s, mode, requested, linger);
|
|
6353
|
+
if (action === "stop")
|
|
6354
|
+
return stopAutostart(s);
|
|
6355
|
+
return action === "uninstall" ? removeAutostart(s) : autostartStatus(s);
|
|
6356
|
+
case "darwin":
|
|
6357
|
+
if (install)
|
|
6358
|
+
return configureLaunchAgent(s, mode, requested);
|
|
6359
|
+
if (action === "stop")
|
|
6360
|
+
return stopLaunchAgent(s);
|
|
6361
|
+
return action === "uninstall" ? removeLaunchAgent(s) : launchAgentStatus(s);
|
|
6362
|
+
case "win32":
|
|
6363
|
+
if (install)
|
|
6364
|
+
return configureLoginRun(s, mode, requested);
|
|
6365
|
+
if (action === "stop")
|
|
6366
|
+
return stopLoginRun(s);
|
|
6367
|
+
return action === "uninstall" ? removeLoginRun(s) : loginRunStatus(s);
|
|
6368
|
+
default:
|
|
6369
|
+
return { status: "unsupported" };
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
function serverGuidance(s, action, result) {
|
|
6373
|
+
const cli = piUserInstall(s) ? `node ${JSON.stringify(join10(s.root, "src", "service-cli.mjs"))}` : "cadence";
|
|
6015
6374
|
const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
|
|
6016
6375
|
` : "";
|
|
6017
|
-
return note +
|
|
6376
|
+
return note + serverStatusGuidance(s, action, result, cli);
|
|
6018
6377
|
}
|
|
6019
6378
|
function lingerGuidance(linger) {
|
|
6020
6379
|
switch (linger) {
|
|
@@ -6031,83 +6390,124 @@ function lingerGuidance(linger) {
|
|
|
6031
6390
|
return "";
|
|
6032
6391
|
}
|
|
6033
6392
|
}
|
|
6034
|
-
function
|
|
6393
|
+
function managerDetail(s) {
|
|
6394
|
+
switch (s.platform) {
|
|
6395
|
+
case "linux":
|
|
6396
|
+
return "see systemctl --user status cadence and journalctl --user -u cadence";
|
|
6397
|
+
case "darwin":
|
|
6398
|
+
return `see launchctl print gui/${s.uid}/${LABEL} and ~/Library/Logs/cadence.log`;
|
|
6399
|
+
default:
|
|
6400
|
+
return `run its launcher to see why: wscript ${JSON.stringify(launcherPath(s))}`;
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
function serverStatusGuidance(s, action, result, cli) {
|
|
6404
|
+
const workspace = JSON.stringify(result.workspace ?? "");
|
|
6405
|
+
const manage = `Manage: ${cli} server status|restart|stop|uninstall`;
|
|
6406
|
+
const when = s.platform === "linux" ? "at boot or login" : "at login";
|
|
6035
6407
|
switch (result.status) {
|
|
6036
6408
|
case "started": {
|
|
6037
6409
|
const linger = lingerGuidance(result.linger);
|
|
6038
6410
|
if (result.service !== "alive")
|
|
6039
|
-
return
|
|
6040
|
-
Inspect:
|
|
6411
|
+
return `Cadence started for ${result.workspace}, but the owner reports ${result.service}.
|
|
6412
|
+
Inspect: ${cli} diagnose --workspace ${workspace}
|
|
6041
6413
|
${manage}
|
|
6042
6414
|
${linger}`;
|
|
6043
|
-
return `Cadence WebUI: ${result.endpoint} (${
|
|
6044
|
-
Enter with your private link:
|
|
6415
|
+
return `Cadence WebUI: ${result.endpoint} (workspace ${result.workspace}; starts ${when})
|
|
6416
|
+
Enter with your private link: ${cli} token-link --workspace ${workspace} --reveal
|
|
6045
6417
|
${manage}
|
|
6046
6418
|
${linger}`;
|
|
6047
6419
|
}
|
|
6048
6420
|
case "waiting-for-pi-login":
|
|
6049
|
-
return
|
|
6050
|
-
Log in to Pi, then:
|
|
6421
|
+
return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join10(agentDirectory(s), "auth.json")}.
|
|
6422
|
+
Log in to Pi, then: ${cli} server restart
|
|
6051
6423
|
${lingerGuidance(result.linger)}`;
|
|
6424
|
+
case "stopped":
|
|
6425
|
+
return action === "stop" ? `Cadence stopped; it starts again ${when}, or now with: ${cli} server restart
|
|
6426
|
+
` : "";
|
|
6427
|
+
case "absent":
|
|
6428
|
+
return action === "restart" || action === "stop" ? `Cadence's background service is not installed: ${cli} server install
|
|
6429
|
+
` : "";
|
|
6052
6430
|
case "disabled":
|
|
6053
|
-
return
|
|
6431
|
+
return `Cadence's service definition was rewritten for ${result.workspace}; left disabled as configured.
|
|
6054
6432
|
`;
|
|
6055
6433
|
case "masked":
|
|
6056
|
-
return
|
|
6434
|
+
return `cadence.service is masked; Cadence did not enable or start it.
|
|
6057
6435
|
`;
|
|
6058
6436
|
case "missing-pi-sdk":
|
|
6059
|
-
return `Cadence did not register
|
|
6437
|
+
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
6438
|
`;
|
|
6061
6439
|
case "foreign":
|
|
6062
6440
|
return `${result.unit} was not generated by Cadence; left unchanged.
|
|
6063
6441
|
`;
|
|
6064
6442
|
case "unavailable":
|
|
6065
|
-
return `
|
|
6443
|
+
return result.workspace ? `Cadence changed nothing: its owner could not be observed. Inspect: ${cli} diagnose --workspace ${workspace}
|
|
6444
|
+
` : `No user service manager; start Cadence explicitly: ${cli} start --workspace /absolute/project
|
|
6445
|
+
`;
|
|
6446
|
+
case "unsupported":
|
|
6447
|
+
return `Cadence has no background service for ${s.platform}; start it explicitly: ${cli} start --workspace /absolute/project
|
|
6448
|
+
`;
|
|
6449
|
+
case "unconfirmed":
|
|
6450
|
+
case "quarantined":
|
|
6451
|
+
return `Cadence changed nothing: its owner's orderly Stop was not confirmed (${result.status}). Inspect: ${cli} diagnose --workspace ${workspace}
|
|
6066
6452
|
`;
|
|
6067
6453
|
case "failed":
|
|
6068
|
-
return `Could not
|
|
6454
|
+
return `Could not register or start Cadence's service; ${managerDetail(s)}.
|
|
6069
6455
|
${lingerGuidance(result.linger)}`;
|
|
6070
6456
|
default:
|
|
6071
6457
|
return "";
|
|
6072
6458
|
}
|
|
6073
6459
|
}
|
|
6074
|
-
|
|
6075
|
-
|
|
6460
|
+
var SERVER_ACTIONS = [
|
|
6461
|
+
"install",
|
|
6462
|
+
"status",
|
|
6463
|
+
"restart",
|
|
6464
|
+
"stop",
|
|
6465
|
+
"uninstall"
|
|
6466
|
+
];
|
|
6467
|
+
async function serverMain(argv, hostUrl) {
|
|
6468
|
+
const action = SERVER_ACTIONS.find((a) => a === argv[0]);
|
|
6469
|
+
if (!action)
|
|
6470
|
+
throw new ContractError("invalid");
|
|
6471
|
+
argv.shift();
|
|
6076
6472
|
let requested;
|
|
6077
|
-
let linger =
|
|
6473
|
+
let linger = process.env.CADENCE_LINGER !== "0";
|
|
6474
|
+
let noLinger = false;
|
|
6078
6475
|
while (action === "install" && argv.length) {
|
|
6079
6476
|
if (argv[0] === "--workspace" && argv[1] && requested === undefined)
|
|
6080
6477
|
requested = resolve5(argv.splice(0, 2)[1]);
|
|
6081
|
-
else if (argv[0] === "--linger" && !
|
|
6478
|
+
else if (argv[0] === "--no-linger" && !noLinger) {
|
|
6082
6479
|
argv.shift();
|
|
6083
|
-
|
|
6480
|
+
noLinger = true;
|
|
6481
|
+
linger = false;
|
|
6084
6482
|
} else
|
|
6085
6483
|
break;
|
|
6086
6484
|
}
|
|
6087
|
-
if (argv.length
|
|
6485
|
+
if (argv.length)
|
|
6088
6486
|
throw new ContractError("invalid");
|
|
6089
|
-
const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))));
|
|
6487
|
+
const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))), ownerControl);
|
|
6090
6488
|
let result;
|
|
6091
|
-
|
|
6092
|
-
result =
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
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).
|
|
6489
|
+
try {
|
|
6490
|
+
result = await serverAction(s, action, requested, linger);
|
|
6491
|
+
} catch (error) {
|
|
6492
|
+
if (error instanceof ContractError && error.code === "invalid")
|
|
6493
|
+
process.stderr.write(`Cadence server install needs an existing absolute --workspace directory.
|
|
6101
6494
|
`);
|
|
6102
|
-
|
|
6103
|
-
}
|
|
6104
|
-
if (result.status === "started" && result.workspace)
|
|
6105
|
-
Object.assign(result, await observeStarted(result.workspace));
|
|
6106
|
-
process.stderr.write(autostartGuidance(s, result));
|
|
6495
|
+
throw error;
|
|
6107
6496
|
}
|
|
6497
|
+
if (result.status === "started" && result.workspace)
|
|
6498
|
+
Object.assign(result, await observeStarted(result.workspace));
|
|
6499
|
+
process.stderr.write(serverGuidance(s, action, result));
|
|
6108
6500
|
process.stdout.write(`${JSON.stringify(result)}
|
|
6109
6501
|
`);
|
|
6110
|
-
if ([
|
|
6502
|
+
if ([
|
|
6503
|
+
"failed",
|
|
6504
|
+
"foreign",
|
|
6505
|
+
"unavailable",
|
|
6506
|
+
"unsupported",
|
|
6507
|
+
"unconfirmed",
|
|
6508
|
+
"quarantined",
|
|
6509
|
+
"missing-pi-sdk"
|
|
6510
|
+
].includes(result.status) || result.status === "absent" && (action === "restart" || action === "stop"))
|
|
6111
6511
|
process.exitCode = 1;
|
|
6112
6512
|
}
|
|
6113
6513
|
async function managementMain(argv, hostUrl) {
|
|
@@ -6116,16 +6516,16 @@ async function managementMain(argv, hostUrl) {
|
|
|
6116
6516
|
throw new ContractError("unsupported-context");
|
|
6117
6517
|
const command = argv.shift();
|
|
6118
6518
|
if (command === "--help" && !argv.length) {
|
|
6119
|
-
process.stdout.write(`cadence
|
|
6120
|
-
cadence
|
|
6121
|
-
Start is explicit, or the
|
|
6519
|
+
process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
|
|
6520
|
+
cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
|
|
6521
|
+
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
6522
|
Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
|
|
6123
6523
|
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
6524
|
`);
|
|
6125
6525
|
return;
|
|
6126
6526
|
}
|
|
6127
|
-
if (command === "
|
|
6128
|
-
await
|
|
6527
|
+
if (command === "server") {
|
|
6528
|
+
await serverMain(argv, hostUrl);
|
|
6129
6529
|
return;
|
|
6130
6530
|
}
|
|
6131
6531
|
let recovery;
|