@abelxiaoxing/cadence 2.0.3 → 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 +68 -25
- package/docs/service-management.md +81 -51
- 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-build.ts +1 -1
- package/src/native-client.ts +60 -34
- package/src/service/contracts/models.ts +3 -5
- package/src/service-cli.mjs +513 -112
- package/src/service-host.mjs +6 -6
- package/src/task-shell.ts +1 -1
- package/src/worker-public.mjs +3 -0
- package/web/dist/assets/cadence.js +4 -4
- package/web/dist/manifest.json +5 -5
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
|
|
@@ -564,7 +564,8 @@ var thinkingLevels = [
|
|
|
564
564
|
"low",
|
|
565
565
|
"medium",
|
|
566
566
|
"high",
|
|
567
|
-
"xhigh"
|
|
567
|
+
"xhigh",
|
|
568
|
+
"max"
|
|
568
569
|
];
|
|
569
570
|
function decodeModelSelector(v) {
|
|
570
571
|
const o = closed(v, ["provider", "model", "api", "thinking"]);
|
|
@@ -2483,14 +2484,27 @@ import {
|
|
|
2483
2484
|
writeFileSync as writeFileSync2
|
|
2484
2485
|
} from "node:fs";
|
|
2485
2486
|
import { homedir as homedir2 } from "node:os";
|
|
2486
|
-
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";
|
|
2487
2496
|
var UNIT = "cadence.service";
|
|
2488
2497
|
var MARKER = "# Generated by @abelxiaoxing/cadence";
|
|
2489
|
-
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) {
|
|
2490
2503
|
const run = (command, args) => {
|
|
2491
2504
|
const r = spawnSync2(command, args, {
|
|
2492
2505
|
encoding: "utf8",
|
|
2493
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2506
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2507
|
+
windowsHide: true
|
|
2494
2508
|
});
|
|
2495
2509
|
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2496
2510
|
};
|
|
@@ -2501,6 +2515,7 @@ function hostSystem(root) {
|
|
|
2501
2515
|
node: process.execPath,
|
|
2502
2516
|
root,
|
|
2503
2517
|
unit: UNIT,
|
|
2518
|
+
uid: process.getuid?.(),
|
|
2504
2519
|
systemctl: (args) => run("systemctl", ["--user", ...args]),
|
|
2505
2520
|
linger: () => {
|
|
2506
2521
|
const r = run("loginctl", [
|
|
@@ -2527,7 +2542,18 @@ function hostSystem(root) {
|
|
|
2527
2542
|
"false"
|
|
2528
2543
|
], { stdio: "ignore", timeout: 20000 });
|
|
2529
2544
|
return !r.error && r.status === 0;
|
|
2530
|
-
}
|
|
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
|
|
2531
2557
|
};
|
|
2532
2558
|
}
|
|
2533
2559
|
function expandHome(value, home) {
|
|
@@ -2601,7 +2627,7 @@ var argument = (value) => quoted(value).replaceAll("$", "$$$$");
|
|
|
2601
2627
|
function buildUnit(u) {
|
|
2602
2628
|
const command = (args) => [u.node, u.cli, ...args].map(argument).join(" ");
|
|
2603
2629
|
return [
|
|
2604
|
-
`${MARKER}
|
|
2630
|
+
`${MARKER}; \`cadence server install\` and \`restart\` rewrite this file.`,
|
|
2605
2631
|
"# Override with `systemctl --user edit cadence`; opt out with `systemctl --user disable --now cadence` or `mask`.",
|
|
2606
2632
|
"[Unit]",
|
|
2607
2633
|
"Description=Cadence shared task service and WebUI",
|
|
@@ -2629,19 +2655,20 @@ function buildUnit(u) {
|
|
|
2629
2655
|
].join(`
|
|
2630
2656
|
`);
|
|
2631
2657
|
}
|
|
2632
|
-
function readGenerated(path) {
|
|
2658
|
+
function readGenerated(path, marker) {
|
|
2633
2659
|
try {
|
|
2634
2660
|
if (!lstatSync2(path).isFile())
|
|
2635
2661
|
return null;
|
|
2636
|
-
const
|
|
2637
|
-
|
|
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;
|
|
2638
2665
|
} catch (error) {
|
|
2639
2666
|
if (error.code === "ENOENT")
|
|
2640
2667
|
return;
|
|
2641
2668
|
throw error;
|
|
2642
2669
|
}
|
|
2643
2670
|
}
|
|
2644
|
-
function
|
|
2671
|
+
function unitWorkspace(text2) {
|
|
2645
2672
|
return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
|
|
2646
2673
|
}
|
|
2647
2674
|
function directory(path) {
|
|
@@ -2651,23 +2678,20 @@ function directory(path) {
|
|
|
2651
2678
|
return false;
|
|
2652
2679
|
}
|
|
2653
2680
|
}
|
|
2654
|
-
function
|
|
2655
|
-
const state = s.env.XDG_STATE_HOME;
|
|
2656
|
-
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"]);
|
|
2657
2684
|
}
|
|
2658
|
-
function selectWorkspace(s, requested,
|
|
2685
|
+
function selectWorkspace(s, requested, kept) {
|
|
2659
2686
|
let chosen;
|
|
2660
2687
|
if (requested) {
|
|
2661
2688
|
chosen = expandHome(requested, s.home);
|
|
2662
2689
|
if (!directory(chosen))
|
|
2663
2690
|
throw new ContractError("invalid");
|
|
2664
2691
|
chosen = resolve2(chosen);
|
|
2665
|
-
} else
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
chosen = kept;
|
|
2669
|
-
}
|
|
2670
|
-
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 };
|
|
2671
2695
|
if (landing.workspace !== chosen)
|
|
2672
2696
|
mkdirSync2(landing.workspace, { recursive: true });
|
|
2673
2697
|
return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
|
|
@@ -2689,7 +2713,18 @@ function regularFile(path) {
|
|
|
2689
2713
|
return false;
|
|
2690
2714
|
}
|
|
2691
2715
|
}
|
|
2692
|
-
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) {
|
|
2693
2728
|
const current = s.linger();
|
|
2694
2729
|
if (current === undefined)
|
|
2695
2730
|
return {};
|
|
@@ -2697,23 +2732,25 @@ function bootStart(s, attempt, interactive) {
|
|
|
2697
2732
|
return { linger: "on" };
|
|
2698
2733
|
if (!attempt)
|
|
2699
2734
|
return { linger: "off" };
|
|
2700
|
-
return {
|
|
2735
|
+
return {
|
|
2736
|
+
linger: s.enableLinger(false) || s.enableLinger(true) ? "enabled" : "refused"
|
|
2737
|
+
};
|
|
2701
2738
|
}
|
|
2702
2739
|
function configureAutostart(s, mode, requested, linger = false) {
|
|
2703
2740
|
if (s.platform !== "linux")
|
|
2704
|
-
return { status: "
|
|
2705
|
-
if (mode === "refresh" && (s.env.CADENCE_AUTOSTART === "0" || !piUserInstall(s)))
|
|
2706
|
-
return { status: "skipped" };
|
|
2741
|
+
return { status: "unsupported" };
|
|
2707
2742
|
if (s.systemctl(["show-environment"]).status !== 0)
|
|
2708
2743
|
return { status: "unavailable" };
|
|
2709
2744
|
const path = unitPath(s);
|
|
2710
|
-
const previous = readGenerated(path);
|
|
2745
|
+
const previous = readGenerated(path, MARKER);
|
|
2711
2746
|
if (previous === null)
|
|
2712
2747
|
return { status: "foreign", unit: path };
|
|
2748
|
+
if (mode === "restart" && previous === undefined)
|
|
2749
|
+
return { status: "absent" };
|
|
2713
2750
|
const sdk = sdkPackages(s.root);
|
|
2714
2751
|
if (!sdk)
|
|
2715
2752
|
return { status: "missing-pi-sdk" };
|
|
2716
|
-
const { workspace, redirectedFrom } = selectWorkspace(s, requested
|
|
2753
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
|
|
2717
2754
|
const agentDir = agentDirectory(s);
|
|
2718
2755
|
const content = buildUnit({
|
|
2719
2756
|
node: s.node,
|
|
@@ -2736,23 +2773,37 @@ function configureAutostart(s, mode, requested, linger = false) {
|
|
|
2736
2773
|
};
|
|
2737
2774
|
if (enabled.startsWith("masked"))
|
|
2738
2775
|
return { status: "masked", ...result };
|
|
2739
|
-
|
|
2740
|
-
if (wanted && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
2776
|
+
if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
2741
2777
|
return { status: "failed", ...result };
|
|
2742
|
-
|
|
2743
|
-
return { status: "disabled", ...result };
|
|
2744
|
-
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) : {};
|
|
2745
2779
|
if (!regularFile(join2(agentDir, "auth.json")))
|
|
2746
2780
|
return { status: "waiting-for-pi-login", ...result, ...boot() };
|
|
2747
2781
|
s.systemctl(["reset-failed", s.unit]);
|
|
2748
2782
|
const started = s.systemctl(["start", s.unit]).status === 0;
|
|
2749
2783
|
return { status: started ? "started" : "failed", ...result, ...boot() };
|
|
2750
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
|
+
}
|
|
2751
2802
|
function removeAutostart(s) {
|
|
2752
2803
|
if (s.platform !== "linux")
|
|
2753
|
-
return { status: "
|
|
2804
|
+
return { status: "unsupported" };
|
|
2754
2805
|
const path = unitPath(s);
|
|
2755
|
-
const previous = readGenerated(path);
|
|
2806
|
+
const previous = readGenerated(path, MARKER);
|
|
2756
2807
|
if (previous === null)
|
|
2757
2808
|
return { status: "foreign", unit: path };
|
|
2758
2809
|
if (previous === undefined)
|
|
@@ -2768,9 +2819,9 @@ function removeAutostart(s) {
|
|
|
2768
2819
|
}
|
|
2769
2820
|
function autostartStatus(s) {
|
|
2770
2821
|
if (s.platform !== "linux")
|
|
2771
|
-
return { status: "
|
|
2822
|
+
return { status: "unsupported" };
|
|
2772
2823
|
const path = unitPath(s);
|
|
2773
|
-
const previous = readGenerated(path);
|
|
2824
|
+
const previous = readGenerated(path, MARKER);
|
|
2774
2825
|
if (previous === null)
|
|
2775
2826
|
return { status: "foreign", unit: path };
|
|
2776
2827
|
if (previous === undefined)
|
|
@@ -2779,8 +2830,8 @@ function autostartStatus(s) {
|
|
|
2779
2830
|
status: s.systemctl(["is-active", s.unit]).stdout.trim() || "unknown",
|
|
2780
2831
|
enabled: s.systemctl(["is-enabled", s.unit]).stdout.trim() || "unknown",
|
|
2781
2832
|
unit: path,
|
|
2782
|
-
workspace:
|
|
2783
|
-
...bootStart(s, false
|
|
2833
|
+
workspace: unitWorkspace(previous),
|
|
2834
|
+
...bootStart(s, false)
|
|
2784
2835
|
};
|
|
2785
2836
|
}
|
|
2786
2837
|
|
|
@@ -3640,13 +3691,277 @@ class OwnerLease {
|
|
|
3640
3691
|
}
|
|
3641
3692
|
}
|
|
3642
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
|
+
|
|
3643
3958
|
// src/service/recovery-cli.ts
|
|
3644
3959
|
import { isatty } from "node:tty";
|
|
3645
3960
|
|
|
3646
3961
|
// src/history/recovery.ts
|
|
3647
3962
|
import { createHash as createHash5, randomUUID as randomUUID5 } from "node:crypto";
|
|
3648
3963
|
import { lstatSync as lstatSync6, opendirSync as opendirSync3 } from "node:fs";
|
|
3649
|
-
import { join as
|
|
3964
|
+
import { join as join8, relative as relative4 } from "node:path";
|
|
3650
3965
|
|
|
3651
3966
|
// src/service/recovery.ts
|
|
3652
3967
|
async function inspectRecovery(coordination, history) {
|
|
@@ -3706,7 +4021,7 @@ import {
|
|
|
3706
4021
|
statfsSync
|
|
3707
4022
|
} from "node:fs";
|
|
3708
4023
|
import { open } from "node:fs/promises";
|
|
3709
|
-
import { dirname as dirname6, join as
|
|
4024
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
3710
4025
|
|
|
3711
4026
|
// src/history/admission.ts
|
|
3712
4027
|
function decodeRunAdmission(value) {
|
|
@@ -4237,14 +4552,14 @@ class HistoryFiles {
|
|
|
4237
4552
|
this.coordination = coordination;
|
|
4238
4553
|
this.format = format;
|
|
4239
4554
|
this.formatSnapshot = formatSnapshot;
|
|
4240
|
-
this.directory =
|
|
4555
|
+
this.directory = join7(coordination.directory, "history-v2");
|
|
4241
4556
|
this.files = coordination.files;
|
|
4242
4557
|
}
|
|
4243
4558
|
static open(coordination) {
|
|
4244
|
-
const dir =
|
|
4559
|
+
const dir = join7(coordination.directory, "history-v2");
|
|
4245
4560
|
if (!coordination.files.directory(dir))
|
|
4246
4561
|
return;
|
|
4247
|
-
const file = coordination.files.read(
|
|
4562
|
+
const file = coordination.files.read(join7(dir, "format.json"));
|
|
4248
4563
|
if (!file)
|
|
4249
4564
|
corrupt();
|
|
4250
4565
|
const format = decodeFormat(json(file));
|
|
@@ -4260,7 +4575,7 @@ class HistoryFiles {
|
|
|
4260
4575
|
existing.assertWritable();
|
|
4261
4576
|
return existing;
|
|
4262
4577
|
}
|
|
4263
|
-
const dir =
|
|
4578
|
+
const dir = join7(c.directory, "history-v2");
|
|
4264
4579
|
const format = {
|
|
4265
4580
|
schema: FORMAT,
|
|
4266
4581
|
storeId: randomUUID4(),
|
|
@@ -4270,19 +4585,19 @@ class HistoryFiles {
|
|
|
4270
4585
|
c.files.directory(dir, true);
|
|
4271
4586
|
for (const name of ["commands", "ledger", "runs", "indexes"]) {
|
|
4272
4587
|
lease.assert();
|
|
4273
|
-
c.files.directory(
|
|
4588
|
+
c.files.directory(join7(dir, name), true);
|
|
4274
4589
|
}
|
|
4275
4590
|
lease.assert();
|
|
4276
|
-
const keyPath =
|
|
4591
|
+
const keyPath = join7(dir, "read-proof-key.json");
|
|
4277
4592
|
c.files.create(keyPath, encoded({
|
|
4278
4593
|
schema: "cadence-history-read-key/1",
|
|
4279
4594
|
storeId: format.storeId,
|
|
4280
4595
|
key: randomBytes2(32).toString("base64url")
|
|
4281
4596
|
}));
|
|
4282
4597
|
lease.assert();
|
|
4283
|
-
const snapshot = c.files.create(
|
|
4598
|
+
const snapshot = c.files.create(join7(dir, "format.json"), encoded(format));
|
|
4284
4599
|
const result = new HistoryFiles(c, format, snapshot);
|
|
4285
|
-
result.syncParents(
|
|
4600
|
+
result.syncParents(join7(dir, "format.json"));
|
|
4286
4601
|
result.assertWritable();
|
|
4287
4602
|
return result;
|
|
4288
4603
|
}
|
|
@@ -4317,7 +4632,7 @@ class HistoryFiles {
|
|
|
4317
4632
|
throw new ContractError("quarantine");
|
|
4318
4633
|
}
|
|
4319
4634
|
assertFormat() {
|
|
4320
|
-
this.files.assert(
|
|
4635
|
+
this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot);
|
|
4321
4636
|
}
|
|
4322
4637
|
readProofMaterial(budget) {
|
|
4323
4638
|
if (this.format.schema !== FORMAT)
|
|
@@ -4326,7 +4641,7 @@ class HistoryFiles {
|
|
|
4326
4641
|
this.assertReadFormat(budget);
|
|
4327
4642
|
else
|
|
4328
4643
|
this.assertFormat();
|
|
4329
|
-
const selected2 = this.readBudgeted(
|
|
4644
|
+
const selected2 = this.readBudgeted(join7(this.directory, "read-proof-key.json"), 512, budget);
|
|
4330
4645
|
if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
|
|
4331
4646
|
corrupt();
|
|
4332
4647
|
try {
|
|
@@ -4354,17 +4669,17 @@ class HistoryFiles {
|
|
|
4354
4669
|
return;
|
|
4355
4670
|
}
|
|
4356
4671
|
this.assertFormat();
|
|
4357
|
-
assertIncarnation(this,
|
|
4672
|
+
assertIncarnation(this, join7(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
|
|
4358
4673
|
}
|
|
4359
4674
|
segmentPath(segment) {
|
|
4360
|
-
return
|
|
4675
|
+
return join7(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
|
|
4361
4676
|
}
|
|
4362
4677
|
artifactPath(ref) {
|
|
4363
|
-
return
|
|
4678
|
+
return join7(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
|
|
4364
4679
|
}
|
|
4365
4680
|
admissionPath(runId) {
|
|
4366
4681
|
decodeId(runId);
|
|
4367
|
-
return
|
|
4682
|
+
return join7(this.directory, "runs", runId, "admission.json");
|
|
4368
4683
|
}
|
|
4369
4684
|
writeAdmission(lease, input) {
|
|
4370
4685
|
lease.assert();
|
|
@@ -4390,7 +4705,7 @@ class HistoryFiles {
|
|
|
4390
4705
|
readCommand(commandId, budget) {
|
|
4391
4706
|
decodeId(commandId);
|
|
4392
4707
|
this.assertReadFormat(budget);
|
|
4393
|
-
return this.readBudgeted(
|
|
4708
|
+
return this.readBudgeted(join7(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
|
|
4394
4709
|
}
|
|
4395
4710
|
readAdmission(runId, identity, budget) {
|
|
4396
4711
|
return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
|
|
@@ -4428,7 +4743,7 @@ class HistoryFiles {
|
|
|
4428
4743
|
}
|
|
4429
4744
|
assertReadFormat(budget) {
|
|
4430
4745
|
budget.reserve(this.formatSnapshot.bytes.length);
|
|
4431
|
-
this.files.assert(
|
|
4746
|
+
this.files.assert(join7(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
|
|
4432
4747
|
}
|
|
4433
4748
|
assertPublished(segment, end, seq) {
|
|
4434
4749
|
const fence = publicationFences.get(this.directory);
|
|
@@ -4466,7 +4781,7 @@ class HistoryFiles {
|
|
|
4466
4781
|
const dir = opendirSync2(path);
|
|
4467
4782
|
try {
|
|
4468
4783
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
4469
|
-
const p =
|
|
4784
|
+
const p = join7(path, e.name), st = lstatSync5(p);
|
|
4470
4785
|
if (st.isDirectory())
|
|
4471
4786
|
bytes += walk(p, depth + 1);
|
|
4472
4787
|
else {
|
|
@@ -4501,7 +4816,7 @@ class HistoryFiles {
|
|
|
4501
4816
|
state.bytes = 0;
|
|
4502
4817
|
state.identity = null;
|
|
4503
4818
|
let count = 0;
|
|
4504
|
-
const ledger =
|
|
4819
|
+
const ledger = join7(this.directory, "ledger");
|
|
4505
4820
|
if (!this.files.directory(ledger))
|
|
4506
4821
|
corrupt();
|
|
4507
4822
|
const dir = opendirSync2(ledger);
|
|
@@ -4818,7 +5133,7 @@ class HistoryFiles {
|
|
|
4818
5133
|
checkpoint(lease, state) {
|
|
4819
5134
|
lease.assert();
|
|
4820
5135
|
this.assertWritable();
|
|
4821
|
-
const path =
|
|
5136
|
+
const path = join7(this.directory, "checkpoint.json"), old = this.files.read(path);
|
|
4822
5137
|
const body = encoded({
|
|
4823
5138
|
schema: "cadence-history-checkpoint/2",
|
|
4824
5139
|
storeId: this.format.storeId,
|
|
@@ -5245,7 +5560,7 @@ function digestHistory(files, exclude) {
|
|
|
5245
5560
|
dir.closeSync();
|
|
5246
5561
|
}
|
|
5247
5562
|
for (const name of names2.sort()) {
|
|
5248
|
-
const selected2 =
|
|
5563
|
+
const selected2 = join8(path, name);
|
|
5249
5564
|
if (selected2 === exclude)
|
|
5250
5565
|
continue;
|
|
5251
5566
|
const rel = relative4(files.directory, selected2);
|
|
@@ -5556,7 +5871,7 @@ async function localRecovery(c, operation) {
|
|
|
5556
5871
|
|
|
5557
5872
|
// src/service/runtime.ts
|
|
5558
5873
|
import { homedir as homedir3 } from "node:os";
|
|
5559
|
-
import { dirname as dirname7, isAbsolute as
|
|
5874
|
+
import { dirname as dirname7, isAbsolute as isAbsolute6, join as join9, resolve as resolve4 } from "node:path";
|
|
5560
5875
|
|
|
5561
5876
|
// src/service/config.ts
|
|
5562
5877
|
import { isIP } from "node:net";
|
|
@@ -5610,7 +5925,7 @@ function localOrigin(policy, actualPort) {
|
|
|
5610
5925
|
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
5611
5926
|
function absolute(value) {
|
|
5612
5927
|
const result = text(value, 4096, true);
|
|
5613
|
-
if (!
|
|
5928
|
+
if (!isAbsolute6(result))
|
|
5614
5929
|
throw new ContractError("invalid");
|
|
5615
5930
|
return result;
|
|
5616
5931
|
}
|
|
@@ -5705,7 +6020,7 @@ function selectStart(options, ambient) {
|
|
|
5705
6020
|
port: options.port ?? 17423,
|
|
5706
6021
|
proxy: null
|
|
5707
6022
|
},
|
|
5708
|
-
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")),
|
|
5709
6024
|
tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
|
|
5710
6025
|
tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
|
|
5711
6026
|
});
|
|
@@ -5877,10 +6192,10 @@ function startupGuidance(result, input) {
|
|
|
5877
6192
|
return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
|
|
5878
6193
|
`;
|
|
5879
6194
|
case "pi-auth":
|
|
5880
|
-
return `${
|
|
6195
|
+
return `${join10(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
|
|
5881
6196
|
`;
|
|
5882
6197
|
case "pi-models":
|
|
5883
|
-
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.
|
|
5884
6199
|
`;
|
|
5885
6200
|
case "pi-runtime":
|
|
5886
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.
|
|
@@ -6006,14 +6321,59 @@ async function observeStarted(workspace) {
|
|
|
6006
6321
|
}
|
|
6007
6322
|
return { service };
|
|
6008
6323
|
}
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
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";
|
|
6014
6374
|
const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
|
|
6015
6375
|
` : "";
|
|
6016
|
-
return note +
|
|
6376
|
+
return note + serverStatusGuidance(s, action, result, cli);
|
|
6017
6377
|
}
|
|
6018
6378
|
function lingerGuidance(linger) {
|
|
6019
6379
|
switch (linger) {
|
|
@@ -6030,83 +6390,124 @@ function lingerGuidance(linger) {
|
|
|
6030
6390
|
return "";
|
|
6031
6391
|
}
|
|
6032
6392
|
}
|
|
6033
|
-
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";
|
|
6034
6407
|
switch (result.status) {
|
|
6035
6408
|
case "started": {
|
|
6036
6409
|
const linger = lingerGuidance(result.linger);
|
|
6037
6410
|
if (result.service !== "alive")
|
|
6038
|
-
return
|
|
6039
|
-
Inspect:
|
|
6411
|
+
return `Cadence started for ${result.workspace}, but the owner reports ${result.service}.
|
|
6412
|
+
Inspect: ${cli} diagnose --workspace ${workspace}
|
|
6040
6413
|
${manage}
|
|
6041
6414
|
${linger}`;
|
|
6042
|
-
return `Cadence WebUI: ${result.endpoint} (${
|
|
6043
|
-
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
|
|
6044
6417
|
${manage}
|
|
6045
6418
|
${linger}`;
|
|
6046
6419
|
}
|
|
6047
6420
|
case "waiting-for-pi-login":
|
|
6048
|
-
return
|
|
6049
|
-
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
|
|
6050
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
|
+
` : "";
|
|
6051
6430
|
case "disabled":
|
|
6052
|
-
return
|
|
6431
|
+
return `Cadence's service definition was rewritten for ${result.workspace}; left disabled as configured.
|
|
6053
6432
|
`;
|
|
6054
6433
|
case "masked":
|
|
6055
|
-
return
|
|
6434
|
+
return `cadence.service is masked; Cadence did not enable or start it.
|
|
6056
6435
|
`;
|
|
6057
6436
|
case "missing-pi-sdk":
|
|
6058
|
-
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.
|
|
6059
6438
|
`;
|
|
6060
6439
|
case "foreign":
|
|
6061
6440
|
return `${result.unit} was not generated by Cadence; left unchanged.
|
|
6062
6441
|
`;
|
|
6063
6442
|
case "unavailable":
|
|
6064
|
-
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}
|
|
6065
6452
|
`;
|
|
6066
6453
|
case "failed":
|
|
6067
|
-
return `Could not
|
|
6454
|
+
return `Could not register or start Cadence's service; ${managerDetail(s)}.
|
|
6068
6455
|
${lingerGuidance(result.linger)}`;
|
|
6069
6456
|
default:
|
|
6070
6457
|
return "";
|
|
6071
6458
|
}
|
|
6072
6459
|
}
|
|
6073
|
-
|
|
6074
|
-
|
|
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();
|
|
6075
6472
|
let requested;
|
|
6076
|
-
let linger =
|
|
6473
|
+
let linger = process.env.CADENCE_LINGER !== "0";
|
|
6474
|
+
let noLinger = false;
|
|
6077
6475
|
while (action === "install" && argv.length) {
|
|
6078
6476
|
if (argv[0] === "--workspace" && argv[1] && requested === undefined)
|
|
6079
6477
|
requested = resolve5(argv.splice(0, 2)[1]);
|
|
6080
|
-
else if (argv[0] === "--linger" && !
|
|
6478
|
+
else if (argv[0] === "--no-linger" && !noLinger) {
|
|
6081
6479
|
argv.shift();
|
|
6082
|
-
|
|
6480
|
+
noLinger = true;
|
|
6481
|
+
linger = false;
|
|
6083
6482
|
} else
|
|
6084
6483
|
break;
|
|
6085
6484
|
}
|
|
6086
|
-
if (argv.length
|
|
6485
|
+
if (argv.length)
|
|
6087
6486
|
throw new ContractError("invalid");
|
|
6088
|
-
const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))));
|
|
6487
|
+
const s = hostSystem(dirname8(dirname8(fileURLToPath(hostUrl))), ownerControl);
|
|
6089
6488
|
let result;
|
|
6090
|
-
|
|
6091
|
-
result =
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
try {
|
|
6096
|
-
result = configureAutostart(s, action === "install" ? "install" : "refresh", requested, linger);
|
|
6097
|
-
} catch (error) {
|
|
6098
|
-
if (error instanceof ContractError && error.code === "invalid")
|
|
6099
|
-
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.
|
|
6100
6494
|
`);
|
|
6101
|
-
|
|
6102
|
-
}
|
|
6103
|
-
if (result.status === "started" && result.workspace)
|
|
6104
|
-
Object.assign(result, await observeStarted(result.workspace));
|
|
6105
|
-
process.stderr.write(autostartGuidance(s, result));
|
|
6495
|
+
throw error;
|
|
6106
6496
|
}
|
|
6497
|
+
if (result.status === "started" && result.workspace)
|
|
6498
|
+
Object.assign(result, await observeStarted(result.workspace));
|
|
6499
|
+
process.stderr.write(serverGuidance(s, action, result));
|
|
6107
6500
|
process.stdout.write(`${JSON.stringify(result)}
|
|
6108
6501
|
`);
|
|
6109
|
-
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"))
|
|
6110
6511
|
process.exitCode = 1;
|
|
6111
6512
|
}
|
|
6112
6513
|
async function managementMain(argv, hostUrl) {
|
|
@@ -6115,16 +6516,16 @@ async function managementMain(argv, hostUrl) {
|
|
|
6115
6516
|
throw new ContractError("unsupported-context");
|
|
6116
6517
|
const command = argv.shift();
|
|
6117
6518
|
if (command === "--help" && !argv.length) {
|
|
6118
|
-
process.stdout.write(`cadence
|
|
6119
|
-
cadence
|
|
6120
|
-
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.
|
|
6121
6522
|
Return/quit detaches clients; only explicit Cancel/Stop requests stop. Unconfirmed/quarantined is not stopped.
|
|
6122
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.
|
|
6123
6524
|
`);
|
|
6124
6525
|
return;
|
|
6125
6526
|
}
|
|
6126
|
-
if (command === "
|
|
6127
|
-
await
|
|
6527
|
+
if (command === "server") {
|
|
6528
|
+
await serverMain(argv, hostUrl);
|
|
6128
6529
|
return;
|
|
6129
6530
|
}
|
|
6130
6531
|
let recovery;
|