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