@abelxiaoxing/cadence 2.0.12 → 2.0.14
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 +141 -54
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/docs/remote-access.md +13 -0
- package/docs/reuse-and-distribution.md +13 -2
- package/docs/service-management.md +31 -12
- package/licenses/pi-web-ui-reuse.md +2 -0
- package/package.json +5 -2
- package/src/conversation-context.mjs +147 -0
- package/src/conversation-resources.mjs +47 -0
- package/src/native-build.ts +1 -1
- package/src/native-client.ts +2 -2
- package/src/operator-cli.mjs +4 -7
- package/src/sdk-worker.mjs +41 -8
- package/src/service/contracts/workbench.ts +303 -0
- package/src/service/coordination.ts +1 -0
- package/src/service-cli.mjs +1227 -577
- package/src/service-client.ts +49 -0
- package/src/service-host.mjs +15768 -13011
- package/src/service-http-client.ts +50 -24
- package/src/service-wire.ts +26 -4
- package/src/task-store.ts +12 -11
- package/web/dist/assets/cadence.css +1 -1
- package/web/dist/assets/cadence.js +13 -7
- package/web/dist/manifest.json +7 -7
package/src/service-cli.mjs
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
// src/service/cli.ts
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import {
|
|
6
|
+
import { mkdirSync as mkdirSync6 } from "node:fs";
|
|
7
7
|
import { request } from "node:http";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { homedir as homedir6 } from "node:os";
|
|
9
|
+
import { dirname as dirname13, join as join15, resolve as resolve7 } from "node:path";
|
|
10
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10
11
|
|
|
11
12
|
// src/install-config.mjs
|
|
12
13
|
import { closeSync, constants, mkdirSync, openSync, writeSync } from "node:fs";
|
|
@@ -2303,13 +2304,10 @@ function stateHomeInside(root, stateHome = defaultStateHome()) {
|
|
|
2303
2304
|
function privateWorkspace(requested, stateHome = defaultStateHome()) {
|
|
2304
2305
|
return contains(join(canonicalAmbientRoot(stateHome), "cadence-assistant-v1"), canonicalAmbientRoot(requested));
|
|
2305
2306
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
if (!stateHomeInside(resolveWorkspace(requested).root, stateHome))
|
|
2311
|
-
return { workspace: requested, redirected: false };
|
|
2312
|
-
return { workspace: join(home, LANDING_WORKSPACE), redirected: true };
|
|
2307
|
+
function effectiveWorkspace(requested, stateHome, _home) {
|
|
2308
|
+
if (privateWorkspace(requested, stateHome) || stateHomeInside(resolveWorkspace(requested).root, stateHome))
|
|
2309
|
+
throw new Error("Choose a project outside Cadence private state; no default workspace was created");
|
|
2310
|
+
return { workspace: requested, redirected: false };
|
|
2313
2311
|
}
|
|
2314
2312
|
|
|
2315
2313
|
class TaskStore {
|
|
@@ -2993,6 +2991,14 @@ function readServerConfig(path2) {
|
|
|
2993
2991
|
return { ...file, config: decode(file.source) };
|
|
2994
2992
|
});
|
|
2995
2993
|
}
|
|
2994
|
+
function withToken(file, token) {
|
|
2995
|
+
const { parts, token: line } = settings(decoded(file.snapshot));
|
|
2996
|
+
if (!line)
|
|
2997
|
+
throw new ContractError("invalid");
|
|
2998
|
+
const indent = /^\s*/.exec(parts[line.at])?.[0] ?? "";
|
|
2999
|
+
parts[line.at] = `${indent}${TOKEN_KEY}=${accessToken(token)}`;
|
|
3000
|
+
return parts.join("");
|
|
3001
|
+
}
|
|
2996
3002
|
function ensureServerConfig(home, options = {}) {
|
|
2997
3003
|
const path2 = serverConfigPath(home, options.name);
|
|
2998
3004
|
if (options.port !== undefined && (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535))
|
|
@@ -3210,18 +3216,19 @@ function buildUnit(u) {
|
|
|
3210
3216
|
"Description=Cadence shared task service and WebUI",
|
|
3211
3217
|
"Documentation=https://github.com/abelxiaoxing/cadence/blob/main/docs/service-management.md",
|
|
3212
3218
|
`ConditionPathExists=${specifiers(u.cli)}`,
|
|
3213
|
-
`ConditionPathExists=${specifiers(join4(u.agentDir, "auth.json"))}
|
|
3214
|
-
...u.sdk.map((path2) => `ConditionPathExists=${specifiers(path2)}`),
|
|
3219
|
+
...u.workspace === null ? [] : [`ConditionPathExists=${specifiers(join4(u.agentDir, "auth.json"))}`],
|
|
3220
|
+
...(u.workspace === null ? [] : u.sdk).map((path2) => `ConditionPathExists=${specifiers(path2)}`),
|
|
3215
3221
|
"StartLimitIntervalSec=300",
|
|
3216
3222
|
"StartLimitBurst=3",
|
|
3217
3223
|
"",
|
|
3218
3224
|
"[Service]",
|
|
3219
3225
|
"Type=simple",
|
|
3220
3226
|
...u.system ? ["User=root"] : [],
|
|
3221
|
-
|
|
3227
|
+
...u.workspace === null ? ["# Cadence workbench gateway; no project workspace"] : [],
|
|
3228
|
+
`WorkingDirectory=${specifiers(u.workspace ?? dirname4(dirname4(u.cli)))}`,
|
|
3222
3229
|
...u.environment.map(([k, v]) => `Environment=${quoted(`${k}=${v}`)}`),
|
|
3223
|
-
`ExecStart=${command(["start", "--foreground", "--workspace", u.workspace, "--server-config", u.serverConfig])}`,
|
|
3224
|
-
`ExecStop=-${command(["stop", "--workspace", u.workspace])}`,
|
|
3230
|
+
`ExecStart=${command([...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace], "--server-config", u.serverConfig])}`,
|
|
3231
|
+
`ExecStop=-${command(u.workspace === null ? ["workbench", "stop", "--server-config", u.serverConfig] : ["stop", "--workspace", u.workspace])}`,
|
|
3225
3232
|
"KillMode=mixed",
|
|
3226
3233
|
"TimeoutStopSec=90",
|
|
3227
3234
|
"Restart=on-failure",
|
|
@@ -3247,6 +3254,8 @@ function readGenerated(path2, marker) {
|
|
|
3247
3254
|
}
|
|
3248
3255
|
}
|
|
3249
3256
|
function unitWorkspace(text2) {
|
|
3257
|
+
if (text2?.includes("# Cadence workbench gateway; no project workspace"))
|
|
3258
|
+
return null;
|
|
3250
3259
|
return text2?.match(/^WorkingDirectory=(.+)$/m)?.[1].replaceAll("%%", "%");
|
|
3251
3260
|
}
|
|
3252
3261
|
function directory(path2) {
|
|
@@ -3260,19 +3269,15 @@ function serviceStateHome(s) {
|
|
|
3260
3269
|
const state = s.platform === "win32" ? s.env.LOCALAPPDATA : s.env.XDG_STATE_HOME;
|
|
3261
3270
|
return state && isAbsolute3(state) ? state : join4(s.home, ...s.platform === "win32" ? ["AppData", "Local"] : [".local", "state"]);
|
|
3262
3271
|
}
|
|
3263
|
-
function selectWorkspace(s, requested, kept,
|
|
3264
|
-
|
|
3265
|
-
if (
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
const landing = chosen ? effectiveWorkspace(chosen, serviceStateHome(s), s.home) : { workspace: join4(s.home, LANDING_WORKSPACE), redirected: false };
|
|
3273
|
-
if (create && landing.workspace !== chosen)
|
|
3274
|
-
mkdirSync5(landing.workspace, { recursive: true });
|
|
3275
|
-
return landing.redirected ? { workspace: landing.workspace, redirectedFrom: chosen } : { workspace: landing.workspace };
|
|
3272
|
+
function selectWorkspace(s, requested, kept, _create = false) {
|
|
3273
|
+
const chosen = requested ? expandHome(requested, s.home) : kept;
|
|
3274
|
+
if (!chosen)
|
|
3275
|
+
return { workspace: null };
|
|
3276
|
+
if (!directory(chosen))
|
|
3277
|
+
throw new ContractError("invalid");
|
|
3278
|
+
return {
|
|
3279
|
+
workspace: effectiveWorkspace(resolve3(chosen), serviceStateHome(s)).workspace
|
|
3280
|
+
};
|
|
3276
3281
|
}
|
|
3277
3282
|
function write(path2, content) {
|
|
3278
3283
|
mkdirSync5(dirname4(path2), { recursive: true });
|
|
@@ -3292,9 +3297,9 @@ function regularFile(path2) {
|
|
|
3292
3297
|
}
|
|
3293
3298
|
}
|
|
3294
3299
|
async function settle(s, workspace) {
|
|
3295
|
-
if (
|
|
3300
|
+
if (workspace === undefined)
|
|
3296
3301
|
return;
|
|
3297
|
-
const status = await s.stopOwner(workspace);
|
|
3302
|
+
const status = await s.stopOwner(workspace, serviceConfigPath(s));
|
|
3298
3303
|
if (status === "stopped")
|
|
3299
3304
|
return;
|
|
3300
3305
|
return {
|
|
@@ -3326,9 +3331,9 @@ function configureAutostart(s, mode, requested, linger = false) {
|
|
|
3326
3331
|
if (mode === "restart" && previous === undefined)
|
|
3327
3332
|
return { status: "absent" };
|
|
3328
3333
|
const sdk = sdkPackages(s.root);
|
|
3329
|
-
|
|
3334
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : unitWorkspace(previous));
|
|
3335
|
+
if (workspace !== null && !sdk)
|
|
3330
3336
|
return { status: "missing-pi-sdk" };
|
|
3331
|
-
const { workspace, redirectedFrom } = selectWorkspace(s, requested, unitWorkspace(previous));
|
|
3332
3337
|
const agentDir = agentDirectory(s);
|
|
3333
3338
|
const content = buildUnit({
|
|
3334
3339
|
node: s.node,
|
|
@@ -3336,7 +3341,7 @@ function configureAutostart(s, mode, requested, linger = false) {
|
|
|
3336
3341
|
workspace,
|
|
3337
3342
|
serverConfig: serviceConfigPath(s),
|
|
3338
3343
|
agentDir,
|
|
3339
|
-
sdk,
|
|
3344
|
+
sdk: sdk ?? [],
|
|
3340
3345
|
environment: serviceEnvironment(s)
|
|
3341
3346
|
});
|
|
3342
3347
|
const enabled = s.systemctl(["is-enabled", s.unit]).stdout.trim();
|
|
@@ -3359,7 +3364,7 @@ function configureAutostart(s, mode, requested, linger = false) {
|
|
|
3359
3364
|
if (mode === "install" && enabled !== "enabled" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
3360
3365
|
return { status: "failed", ...result };
|
|
3361
3366
|
const boot = () => mode === "install" ? bootStart(s, linger) : {};
|
|
3362
|
-
if (!regularFile(join4(agentDir, "auth.json")))
|
|
3367
|
+
if (workspace !== null && !regularFile(join4(agentDir, "auth.json")))
|
|
3363
3368
|
return { status: "waiting-for-pi-login", ...result, ...boot() };
|
|
3364
3369
|
s.systemctl(["reset-failed", s.unit]);
|
|
3365
3370
|
const started = s.systemctl(["start", s.unit]).status === 0;
|
|
@@ -3604,7 +3609,8 @@ class ServiceCoordination {
|
|
|
3604
3609
|
"access-config.json",
|
|
3605
3610
|
"access.json",
|
|
3606
3611
|
"host-config.json",
|
|
3607
|
-
"history-v2"
|
|
3612
|
+
"history-v2",
|
|
3613
|
+
"conversations-v1"
|
|
3608
3614
|
]);
|
|
3609
3615
|
this.files.directory(join5(this.directory, "history-v2"), false, walks);
|
|
3610
3616
|
}
|
|
@@ -4037,218 +4043,1072 @@ class OwnerLease {
|
|
|
4037
4043
|
}
|
|
4038
4044
|
}
|
|
4039
4045
|
|
|
4040
|
-
// src/service/
|
|
4041
|
-
import {
|
|
4042
|
-
import { join as
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
"
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
string(v)
|
|
4080
|
-
]),
|
|
4081
|
-
"\t</dict>",
|
|
4082
|
-
"\t<key>RunAtLoad</key>",
|
|
4083
|
-
"\t<true/>",
|
|
4084
|
-
"\t<key>ProcessType</key>",
|
|
4085
|
-
"\t<string>Background</string>",
|
|
4086
|
-
"\t<key>StandardOutPath</key>",
|
|
4087
|
-
` <string>${xml(u.log)}</string>`,
|
|
4088
|
-
"\t<key>StandardErrorPath</key>",
|
|
4089
|
-
` <string>${xml(u.log)}</string>`,
|
|
4090
|
-
"</dict>",
|
|
4091
|
-
"</plist>",
|
|
4092
|
-
""
|
|
4093
|
-
].join(`
|
|
4094
|
-
`);
|
|
4095
|
-
}
|
|
4096
|
-
function plistWorkspace(text2) {
|
|
4097
|
-
const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
|
|
4098
|
-
return m?.[1].replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
4046
|
+
// src/service/gateway-cli.ts
|
|
4047
|
+
import { homedir as homedir5 } from "node:os";
|
|
4048
|
+
import { dirname as dirname8, join as join9, resolve as resolve6 } from "node:path";
|
|
4049
|
+
import { fileURLToPath } from "node:url";
|
|
4050
|
+
|
|
4051
|
+
// src/service/config.ts
|
|
4052
|
+
import { isIP } from "node:net";
|
|
4053
|
+
function decodeHttpPolicy(value) {
|
|
4054
|
+
const o = closed(value, ["schema", "host", "port", "proxy"]);
|
|
4055
|
+
if (o.host !== "127.0.0.1" && o.host !== "::1")
|
|
4056
|
+
throw new ContractError("invalid");
|
|
4057
|
+
let proxy = null;
|
|
4058
|
+
if (o.proxy !== null) {
|
|
4059
|
+
const p = closed(o.proxy, ["origin", "trustedAddresses"]);
|
|
4060
|
+
const origin = text(p.origin, 256, true);
|
|
4061
|
+
let url;
|
|
4062
|
+
try {
|
|
4063
|
+
url = new URL(origin);
|
|
4064
|
+
} catch {
|
|
4065
|
+
throw new ContractError("invalid");
|
|
4066
|
+
}
|
|
4067
|
+
if (url.protocol !== "https:" || url.origin !== origin || url.username || url.password)
|
|
4068
|
+
throw new ContractError("invalid");
|
|
4069
|
+
const trustedAddresses = list(p.trustedAddresses, 8, (v) => {
|
|
4070
|
+
const address = text(v, 64, true);
|
|
4071
|
+
if (!isIP(address) || !loopbackAddress(address))
|
|
4072
|
+
throw new ContractError("invalid");
|
|
4073
|
+
return address;
|
|
4074
|
+
});
|
|
4075
|
+
if (!trustedAddresses.length || new Set(trustedAddresses).size !== trustedAddresses.length)
|
|
4076
|
+
throw new ContractError("invalid");
|
|
4077
|
+
proxy = { origin, trustedAddresses };
|
|
4078
|
+
}
|
|
4079
|
+
return {
|
|
4080
|
+
schema: literal(o.schema, "cadence-service-http/1"),
|
|
4081
|
+
host: o.host,
|
|
4082
|
+
port: integer(o.port, 0, 65535),
|
|
4083
|
+
proxy
|
|
4084
|
+
};
|
|
4099
4085
|
}
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4086
|
+
function loopbackAddress(host) {
|
|
4087
|
+
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
|
|
4088
|
+
const h = mapped ? mapped[1] : host;
|
|
4089
|
+
if (h.includes(":"))
|
|
4090
|
+
return h === "::1";
|
|
4091
|
+
const parts = h.split(".");
|
|
4092
|
+
return parts.length === 4 && parts.every((p) => /^\d{1,3}$/.test(p) && Number(p) <= 255) && Number(parts[0]) === 127;
|
|
4105
4093
|
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
const path2 = plistPath(s);
|
|
4110
|
-
const previous = readGenerated(path2, MARKER2);
|
|
4111
|
-
if (previous === null)
|
|
4112
|
-
return { status: "foreign", unit: path2 };
|
|
4113
|
-
if (mode === "restart" && previous === undefined)
|
|
4114
|
-
return { status: "absent" };
|
|
4115
|
-
const sdk = sdkPackages(s.root);
|
|
4116
|
-
if (!sdk)
|
|
4117
|
-
return { status: "missing-pi-sdk" };
|
|
4118
|
-
const kept = plistWorkspace(previous);
|
|
4119
|
-
const { workspace, redirectedFrom } = selectWorkspace(s, requested, kept);
|
|
4120
|
-
const refused2 = await settle(s, kept);
|
|
4121
|
-
if (refused2)
|
|
4122
|
-
return { ...refused2, unit: path2 };
|
|
4123
|
-
if (loaded(s))
|
|
4124
|
-
s.launchctl(["bootout", target(s)]);
|
|
4125
|
-
write(path2, buildPlist({
|
|
4126
|
-
node: s.node,
|
|
4127
|
-
cli: join6(s.root, "src", "service-cli.mjs"),
|
|
4128
|
-
workspace,
|
|
4129
|
-
serverConfig: serverConfigPath(s.home),
|
|
4130
|
-
environment: serviceEnvironment(s),
|
|
4131
|
-
log: join6(s.home, "Library", "Logs", "cadence.log")
|
|
4132
|
-
}));
|
|
4133
|
-
const result = {
|
|
4134
|
-
unit: path2,
|
|
4135
|
-
workspace,
|
|
4136
|
-
...redirectedFrom ? { redirectedFrom } : {}
|
|
4137
|
-
};
|
|
4138
|
-
if (mode === "install")
|
|
4139
|
-
s.launchctl(["enable", target(s)]);
|
|
4140
|
-
else if (disabled(s))
|
|
4141
|
-
return { status: "disabled", ...result };
|
|
4142
|
-
if (!regularFile(join6(agentDirectory(s), "auth.json")))
|
|
4143
|
-
return { status: "waiting-for-pi-login", ...result };
|
|
4144
|
-
const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
|
|
4145
|
-
return { status: started ? "started" : "failed", ...result };
|
|
4094
|
+
function localOrigin(policy, actualPort) {
|
|
4095
|
+
integer(actualPort, 1, 65535);
|
|
4096
|
+
return `http://${policy.host === "::1" ? "[::1]" : policy.host}:${actualPort}`;
|
|
4146
4097
|
}
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4098
|
+
|
|
4099
|
+
// src/service/gateway-store.ts
|
|
4100
|
+
import { randomBytes as randomBytes4, randomUUID as randomUUID5 } from "node:crypto";
|
|
4101
|
+
import { opendirSync as opendirSync2 } from "node:fs";
|
|
4102
|
+
import { join as join7, resolve as resolve4 } from "node:path";
|
|
4103
|
+
|
|
4104
|
+
// src/service/auth.ts
|
|
4105
|
+
import {
|
|
4106
|
+
createHash as createHash3,
|
|
4107
|
+
randomBytes as randomBytes3,
|
|
4108
|
+
randomUUID as randomUUID4,
|
|
4109
|
+
timingSafeEqual
|
|
4110
|
+
} from "node:crypto";
|
|
4111
|
+
import { dirname as dirname6, isAbsolute as isAbsolute5, join as join6 } from "node:path";
|
|
4112
|
+
function sameSecret(candidate, expected) {
|
|
4113
|
+
const a = createHash3("sha256").update(candidate).digest();
|
|
4114
|
+
const b = createHash3("sha256").update(expected).digest();
|
|
4115
|
+
return timingSafeEqual(a, b);
|
|
4116
|
+
}
|
|
4117
|
+
function buildTokenCookie(name, encodedValue, maxAge, secure) {
|
|
4118
|
+
return `${name}=${encodedValue}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${maxAge}${secure ? "; Secure" : ""}`;
|
|
4119
|
+
}
|
|
4120
|
+
function decodeCookieToken(raw) {
|
|
4121
|
+
if (!raw)
|
|
4122
|
+
return "";
|
|
4123
|
+
try {
|
|
4124
|
+
return decodeURIComponent(raw);
|
|
4125
|
+
} catch {
|
|
4126
|
+
return raw;
|
|
4127
|
+
}
|
|
4157
4128
|
}
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4129
|
+
function cookieTokens(header, name) {
|
|
4130
|
+
if (!header)
|
|
4131
|
+
return [];
|
|
4132
|
+
let found = false;
|
|
4133
|
+
const out = [];
|
|
4134
|
+
for (const part of header.split(";")) {
|
|
4135
|
+
const [key, ...rest] = part.trim().split("=");
|
|
4136
|
+
if (key !== name)
|
|
4137
|
+
continue;
|
|
4138
|
+
if (found)
|
|
4139
|
+
throw new ContractError("invalid");
|
|
4140
|
+
found = true;
|
|
4141
|
+
const raw = rest.join("=").trim();
|
|
4142
|
+
out.push(raw, decodeCookieToken(raw));
|
|
4143
|
+
}
|
|
4144
|
+
return out;
|
|
4172
4145
|
}
|
|
4173
|
-
function
|
|
4174
|
-
const
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4146
|
+
function sourceConfig(source) {
|
|
4147
|
+
const given = [source.environment, source.tokenFile, source.serverConfig];
|
|
4148
|
+
if (given.filter((v) => v !== undefined).length > 1)
|
|
4149
|
+
throw new ContractError("invalid");
|
|
4150
|
+
if (source.environment !== undefined)
|
|
4151
|
+
accessToken(source.environment);
|
|
4152
|
+
if (source.serverConfig !== undefined)
|
|
4153
|
+
accessToken(source.token);
|
|
4154
|
+
const path2 = source.tokenFile ?? source.serverConfig;
|
|
4155
|
+
if (path2 !== undefined && (!isAbsolute5(path2) || path2.includes("\x00")))
|
|
4156
|
+
throw new ContractError("invalid");
|
|
4181
4157
|
return {
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
workspace: plistWorkspace(previous)
|
|
4158
|
+
schema: "cadence-service-access-config/1",
|
|
4159
|
+
source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : source.serverConfig !== undefined ? "config" : "generated",
|
|
4160
|
+
path: path2 ?? null
|
|
4186
4161
|
};
|
|
4187
4162
|
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
import { isAbsolute as isAbsolute5, join as join7 } from "node:path";
|
|
4192
|
-
var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
4193
|
-
var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
|
|
4194
|
-
var RUN_VALUE = "Cadence";
|
|
4195
|
-
var MARKER3 = "' Generated by @abelxiaoxing/cadence";
|
|
4196
|
-
function launcherPath(s) {
|
|
4197
|
-
const local = s.env.LOCALAPPDATA;
|
|
4198
|
-
return join7(local && isAbsolute5(local) ? local : join7(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
|
|
4163
|
+
function readSource(config) {
|
|
4164
|
+
if (config.source === "file" && config.path)
|
|
4165
|
+
readExternal(config.path);
|
|
4199
4166
|
}
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
if (
|
|
4167
|
+
function selectedMaterial(files, directory2, source) {
|
|
4168
|
+
const config = sourceConfig(source);
|
|
4169
|
+
if (config.source !== "config")
|
|
4203
4170
|
throw new ContractError("invalid");
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
|
|
4226
|
-
function approvedOff(s) {
|
|
4227
|
-
const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
|
|
4228
|
-
const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
|
|
4229
|
-
return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
|
|
4171
|
+
const marker = join6(directory2, "access-config.json"), access = join6(directory2, "access.json");
|
|
4172
|
+
const markerFile = files.read(marker), accessFile = files.read(access);
|
|
4173
|
+
if (!markerFile || !accessFile)
|
|
4174
|
+
throw new ContractError("corrupt-selected");
|
|
4175
|
+
const previous = closed(json(markerFile), ["schema", "source", "path"]);
|
|
4176
|
+
literal(previous.schema, "cadence-service-access-config/1");
|
|
4177
|
+
oneOf(previous.source, ["generated", "environment", "file", "config"]);
|
|
4178
|
+
const r = closed(json(accessFile), ["schema", "generation", "token"]);
|
|
4179
|
+
literal(r.schema, "cadence-service-access/1");
|
|
4180
|
+
const generation = integer(r.generation, 1);
|
|
4181
|
+
if (previous.source === "generated" !== (r.token !== null))
|
|
4182
|
+
throw new ContractError("corrupt-selected");
|
|
4183
|
+
return {
|
|
4184
|
+
config,
|
|
4185
|
+
previous,
|
|
4186
|
+
generation,
|
|
4187
|
+
marker,
|
|
4188
|
+
markerFile,
|
|
4189
|
+
access,
|
|
4190
|
+
accessFile
|
|
4191
|
+
};
|
|
4230
4192
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4193
|
+
|
|
4194
|
+
class AccessMaterial {
|
|
4195
|
+
files;
|
|
4196
|
+
directory;
|
|
4197
|
+
assertOwner;
|
|
4198
|
+
configFile;
|
|
4199
|
+
accessFile;
|
|
4200
|
+
externalFile;
|
|
4201
|
+
external;
|
|
4202
|
+
record;
|
|
4203
|
+
secret;
|
|
4204
|
+
fenced = false;
|
|
4205
|
+
listeners = new Set;
|
|
4206
|
+
config;
|
|
4207
|
+
static validateSource(source) {
|
|
4208
|
+
readSource(sourceConfig(source));
|
|
4209
|
+
}
|
|
4210
|
+
static initialize(files, directory2, source = {}) {
|
|
4211
|
+
const config = sourceConfig(source);
|
|
4212
|
+
const marker = join6(directory2, "access-config.json"), access = join6(directory2, "access.json");
|
|
4213
|
+
if (files.read(marker) || files.read(access) || files.read(join6(directory2, "registration.json")) || files.read(join6(directory2, "owner.lock")))
|
|
4214
|
+
throw new ContractError("conflict");
|
|
4215
|
+
readSource(config);
|
|
4216
|
+
files.create(marker, encoded(config));
|
|
4217
|
+
files.create(access, encoded({
|
|
4218
|
+
schema: "cadence-service-access/1",
|
|
4219
|
+
generation: 1,
|
|
4220
|
+
token: config.source === "generated" ? randomBytes3(32).toString("base64url") : null
|
|
4221
|
+
}));
|
|
4222
|
+
}
|
|
4223
|
+
static validateReselection(files, directory2, source) {
|
|
4224
|
+
selectedMaterial(files, directory2, source);
|
|
4225
|
+
}
|
|
4226
|
+
static reselect(files, directory2, source) {
|
|
4227
|
+
const m = selectedMaterial(files, directory2, source);
|
|
4228
|
+
if (encoded(m.previous) === encoded(m.config))
|
|
4229
|
+
return;
|
|
4230
|
+
files.replace(m.marker, m.markerFile, encoded(m.config));
|
|
4231
|
+
files.replace(m.access, m.accessFile, encoded({
|
|
4232
|
+
schema: "cadence-service-access/1",
|
|
4233
|
+
generation: integer(m.generation + 1, 1),
|
|
4234
|
+
token: null
|
|
4235
|
+
}));
|
|
4236
|
+
}
|
|
4237
|
+
constructor(files, directory2, source = {}, assertOwner) {
|
|
4238
|
+
this.files = files;
|
|
4239
|
+
this.directory = directory2;
|
|
4240
|
+
this.assertOwner = assertOwner;
|
|
4241
|
+
this.config = sourceConfig(source);
|
|
4242
|
+
const configFile = files.read(join6(directory2, "access-config.json"));
|
|
4243
|
+
const accessFile = files.read(join6(directory2, "access.json"));
|
|
4244
|
+
if (!configFile || !accessFile)
|
|
4245
|
+
throw new ContractError("corrupt-selected");
|
|
4246
|
+
const c = closed(json(configFile), ["schema", "source", "path"]);
|
|
4247
|
+
if (encoded(c) !== encoded(this.config))
|
|
4248
|
+
throw new ContractError("conflict");
|
|
4249
|
+
const r = closed(json(accessFile), ["schema", "generation", "token"]);
|
|
4250
|
+
this.record = {
|
|
4251
|
+
schema: literal(r.schema, "cadence-service-access/1"),
|
|
4252
|
+
generation: integer(r.generation, 1),
|
|
4253
|
+
token: r.token === null ? null : accessToken(r.token)
|
|
4254
|
+
};
|
|
4255
|
+
if (this.config.source === "generated" !== (this.record.token !== null))
|
|
4256
|
+
throw new ContractError("corrupt-selected");
|
|
4257
|
+
if (this.config.source === "file" && this.config.path) {
|
|
4258
|
+
const loaded = readExternal(this.config.path);
|
|
4259
|
+
this.external = loaded.files;
|
|
4260
|
+
this.externalFile = loaded.file;
|
|
4261
|
+
this.secret = loaded.token;
|
|
4262
|
+
} else
|
|
4263
|
+
this.secret = accessToken(this.record.token ?? source.environment ?? source.token);
|
|
4264
|
+
this.configFile = configFile;
|
|
4265
|
+
this.accessFile = accessFile;
|
|
4266
|
+
}
|
|
4267
|
+
get generation() {
|
|
4268
|
+
return this.record.generation;
|
|
4269
|
+
}
|
|
4270
|
+
get available() {
|
|
4271
|
+
return !this.fenced;
|
|
4272
|
+
}
|
|
4273
|
+
matches(candidate) {
|
|
4274
|
+
return !this.fenced && utf8Bytes(candidate) <= TOKEN_BYTES && sameSecret(candidate, this.secret);
|
|
4275
|
+
}
|
|
4276
|
+
onRevoke(listener) {
|
|
4277
|
+
this.listeners.add(listener);
|
|
4278
|
+
return () => {
|
|
4279
|
+
this.listeners.delete(listener);
|
|
4280
|
+
};
|
|
4281
|
+
}
|
|
4282
|
+
revoke() {
|
|
4283
|
+
for (const listener of this.listeners) {
|
|
4284
|
+
try {
|
|
4285
|
+
listener();
|
|
4286
|
+
} catch {
|
|
4287
|
+
this.fenced = true;
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
assertCurrent() {
|
|
4292
|
+
try {
|
|
4293
|
+
this.assert();
|
|
4294
|
+
} catch {
|
|
4295
|
+
this.fenced = true;
|
|
4296
|
+
this.revoke();
|
|
4297
|
+
throw new ContractError("quarantine");
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
assert() {
|
|
4301
|
+
if (this.fenced)
|
|
4302
|
+
throw new ContractError("quarantine");
|
|
4303
|
+
this.files.assert(join6(this.directory, "access-config.json"), this.configFile);
|
|
4304
|
+
this.files.assert(join6(this.directory, "access.json"), this.accessFile);
|
|
4305
|
+
if (this.config.source === "file" && this.config.path)
|
|
4306
|
+
this.external?.assert(this.config.path, this.externalFile, TOKEN_BYTES);
|
|
4307
|
+
}
|
|
4308
|
+
redactionLiterals() {
|
|
4309
|
+
this.assertCurrent();
|
|
4310
|
+
return [this.secret];
|
|
4311
|
+
}
|
|
4312
|
+
tokenLink(origin) {
|
|
4313
|
+
this.assert();
|
|
4314
|
+
const url = new URL(origin);
|
|
4315
|
+
if (url.origin !== origin)
|
|
4316
|
+
throw new ContractError("invalid");
|
|
4317
|
+
return `${origin}/?token=${encodeURIComponent(this.secret)}`;
|
|
4318
|
+
}
|
|
4319
|
+
assertRotation() {
|
|
4320
|
+
this.assertOwner?.();
|
|
4321
|
+
this.assert();
|
|
4322
|
+
}
|
|
4323
|
+
rotate() {
|
|
4324
|
+
if (this.config.source === "environment")
|
|
4325
|
+
throw new ContractError("unsupported-context");
|
|
4326
|
+
let configured;
|
|
4327
|
+
if (this.config.source === "config" && this.config.path) {
|
|
4328
|
+
configured = readServerConfig(this.config.path);
|
|
4329
|
+
if (!sameSecret(configured.config.token, this.secret))
|
|
4330
|
+
throw new ContractError("conflict");
|
|
4331
|
+
}
|
|
4332
|
+
try {
|
|
4333
|
+
this.assertRotation();
|
|
4334
|
+
const secret = randomBytes3(32).toString("base64url");
|
|
4335
|
+
const generation = integer(this.record.generation + 1, 1);
|
|
4336
|
+
if (this.config.path && this.external && this.externalFile)
|
|
4337
|
+
this.externalFile = this.external.replace(this.config.path, this.externalFile, secret, TOKEN_BYTES);
|
|
4338
|
+
if (configured)
|
|
4339
|
+
configured.files.replace(configured.real, configured.snapshot, withToken(configured, secret), CONFIG_BYTES);
|
|
4340
|
+
const record = {
|
|
4341
|
+
...this.record,
|
|
4342
|
+
generation,
|
|
4343
|
+
token: this.config.source === "generated" ? secret : null
|
|
4344
|
+
};
|
|
4345
|
+
this.assertRotation();
|
|
4346
|
+
this.accessFile = this.files.replace(join6(this.directory, "access.json"), this.accessFile, encoded(record));
|
|
4347
|
+
this.assertRotation();
|
|
4348
|
+
this.record = record;
|
|
4349
|
+
this.secret = secret;
|
|
4350
|
+
this.revoke();
|
|
4351
|
+
if (this.fenced)
|
|
4352
|
+
throw new ContractError("quarantine");
|
|
4353
|
+
} catch {
|
|
4354
|
+
this.fenced = true;
|
|
4355
|
+
this.revoke();
|
|
4356
|
+
throw new ContractError("quarantine");
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
function readExternal(path2) {
|
|
4361
|
+
const files = new PrivateFiles(dirname6(path2));
|
|
4362
|
+
const file = files.read(path2, TOKEN_BYTES);
|
|
4363
|
+
if (!file)
|
|
4364
|
+
throw new ContractError("corrupt-selected");
|
|
4365
|
+
try {
|
|
4366
|
+
return {
|
|
4367
|
+
files,
|
|
4368
|
+
file,
|
|
4369
|
+
token: accessToken(new TextDecoder("utf-8", { fatal: true }).decode(file.bytes))
|
|
4370
|
+
};
|
|
4371
|
+
} catch {
|
|
4372
|
+
throw new ContractError("corrupt-selected");
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
class HttpAuthentication {
|
|
4376
|
+
material;
|
|
4377
|
+
epoch;
|
|
4378
|
+
localCapability;
|
|
4379
|
+
csrf = randomUUID4();
|
|
4380
|
+
cookieName;
|
|
4381
|
+
constructor(material, epoch, workspaceId, localCapability) {
|
|
4382
|
+
this.material = material;
|
|
4383
|
+
this.epoch = epoch;
|
|
4384
|
+
this.localCapability = localCapability;
|
|
4385
|
+
decodeId(epoch);
|
|
4386
|
+
decodeId(workspaceId);
|
|
4387
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(localCapability))
|
|
4388
|
+
throw new ContractError("invalid");
|
|
4389
|
+
this.cookieName = `cadence_${workspaceId.replaceAll("-", "")}`;
|
|
4390
|
+
}
|
|
4391
|
+
bootstrap(value, boundary) {
|
|
4392
|
+
this.material.assertCurrent();
|
|
4393
|
+
const h = boundary.headers;
|
|
4394
|
+
const site = h.get("sec-fetch-site");
|
|
4395
|
+
if (site !== undefined && !["same-origin", "none"].includes(site) && (!["same-site", "cross-site"].includes(site) || h.get("sec-fetch-mode") !== "navigate" || h.get("sec-fetch-dest") !== "document"))
|
|
4396
|
+
throw new ContractError("forbidden");
|
|
4397
|
+
if (!this.material.matches(value))
|
|
4398
|
+
throw new ContractError("unauthenticated");
|
|
4399
|
+
return buildTokenCookie(this.cookieName, encodeURIComponent(value), LIMITS.browserLifetimeMs / 1000, boundary.secure);
|
|
4400
|
+
}
|
|
4401
|
+
authenticate(boundary, attachmentId, mutation = false, websocket = false) {
|
|
4402
|
+
this.material.assertCurrent();
|
|
4403
|
+
if (!this.material.available)
|
|
4404
|
+
throw new ContractError("quarantine");
|
|
4405
|
+
const h = boundary.headers;
|
|
4406
|
+
const local = h.get("x-cadence-local");
|
|
4407
|
+
if (local !== undefined) {
|
|
4408
|
+
if (!boundary.local || h.has("cookie") || [...h.keys()].some((name) => name.startsWith("sec-fetch-") && (name !== "sec-fetch-mode" || h.get(name) !== "cors")) || !sameSecret(local, this.localCapability))
|
|
4409
|
+
throw new ContractError("forbidden");
|
|
4410
|
+
} else {
|
|
4411
|
+
if (h.has("authorization") || h.has("x-cadence-token"))
|
|
4412
|
+
throw new ContractError("forbidden");
|
|
4413
|
+
if (!cookieTokens(h.get("cookie"), this.cookieName).some((v) => this.material.matches(v)))
|
|
4414
|
+
throw new ContractError("unauthenticated");
|
|
4415
|
+
if (h.has("sec-fetch-site") && !["same-origin", "none"].includes(h.get("sec-fetch-site") ?? ""))
|
|
4416
|
+
throw new ContractError("forbidden");
|
|
4417
|
+
if ((mutation || websocket) && h.get("origin") !== boundary.origin)
|
|
4418
|
+
throw new ContractError("forbidden");
|
|
4419
|
+
if (mutation && h.get("x-cadence-csrf") !== this.csrf)
|
|
4420
|
+
throw new ContractError("forbidden");
|
|
4421
|
+
}
|
|
4422
|
+
return {
|
|
4423
|
+
attachmentId: decodeId(attachmentId),
|
|
4424
|
+
authority: local === undefined ? "browser" : "local",
|
|
4425
|
+
accessGeneration: this.material.generation
|
|
4426
|
+
};
|
|
4427
|
+
}
|
|
4428
|
+
current(caller) {
|
|
4429
|
+
this.material.assertCurrent();
|
|
4430
|
+
if (!this.material.available || caller.accessGeneration !== this.material.generation)
|
|
4431
|
+
throw new ContractError("unauthenticated");
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
// src/service/contracts/gateway.ts
|
|
4436
|
+
function decodeProjectEntry(value) {
|
|
4437
|
+
const o = closed(value, ["id", "path", "cwd", "epoch"]);
|
|
4438
|
+
return {
|
|
4439
|
+
id: decodeHash(o.id),
|
|
4440
|
+
path: text(o.path, 4096, true),
|
|
4441
|
+
cwd: text(o.cwd, 4096, true),
|
|
4442
|
+
epoch: decodeId(o.epoch)
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
// src/service/gateway-store.ts
|
|
4447
|
+
function decodeGatewayInput(value) {
|
|
4448
|
+
const o = closed(value, [
|
|
4449
|
+
"kind",
|
|
4450
|
+
"serverConfig",
|
|
4451
|
+
"stateHome",
|
|
4452
|
+
"agentDir",
|
|
4453
|
+
"policy"
|
|
4454
|
+
]);
|
|
4455
|
+
const absolute = (v) => {
|
|
4456
|
+
const p = text(v, 4096, true);
|
|
4457
|
+
if (resolve4(p) !== p)
|
|
4458
|
+
throw new ContractError("invalid");
|
|
4459
|
+
return p;
|
|
4460
|
+
};
|
|
4461
|
+
return {
|
|
4462
|
+
kind: literal(o.kind, "workbench"),
|
|
4463
|
+
serverConfig: absolute(o.serverConfig),
|
|
4464
|
+
stateHome: absolute(o.stateHome),
|
|
4465
|
+
agentDir: absolute(o.agentDir),
|
|
4466
|
+
policy: decodeHttpPolicy(o.policy)
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
function decodeGatewayProject(value) {
|
|
4470
|
+
const project = decodeProjectEntry(value);
|
|
4471
|
+
if (resolve4(project.path) !== project.path || resolve4(project.cwd) !== project.cwd)
|
|
4472
|
+
throw new ContractError("corrupt-selected");
|
|
4473
|
+
return project;
|
|
4474
|
+
}
|
|
4475
|
+
function registration(value) {
|
|
4476
|
+
const o = closed(value, [
|
|
4477
|
+
"schema",
|
|
4478
|
+
"epoch",
|
|
4479
|
+
"id",
|
|
4480
|
+
"endpoint",
|
|
4481
|
+
"capability",
|
|
4482
|
+
"selection",
|
|
4483
|
+
"pid",
|
|
4484
|
+
"phase"
|
|
4485
|
+
]);
|
|
4486
|
+
const endpoint2 = text(o.endpoint, 128, true), capability = text(o.capability, 43, true);
|
|
4487
|
+
if (!/^http:\/\/(?:127\.0\.0\.1|\[::1\]):[1-9][0-9]{0,4}$/.test(endpoint2) || !/^[A-Za-z0-9_-]{43}$/.test(capability))
|
|
4488
|
+
throw new ContractError("corrupt-selected");
|
|
4489
|
+
return {
|
|
4490
|
+
schema: literal(o.schema, "cadence-workbench-registration/1"),
|
|
4491
|
+
epoch: decodeId(o.epoch),
|
|
4492
|
+
id: decodeId(o.id),
|
|
4493
|
+
endpoint: endpoint2,
|
|
4494
|
+
capability,
|
|
4495
|
+
selection: decodeHash(o.selection),
|
|
4496
|
+
pid: integer(o.pid, 1),
|
|
4497
|
+
phase: oneOf(o.phase, ["alive", "stopping", "stopped", "quarantined"])
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
class GatewayStore {
|
|
4502
|
+
serverConfig;
|
|
4503
|
+
files;
|
|
4504
|
+
directory;
|
|
4505
|
+
stateHome;
|
|
4506
|
+
lock;
|
|
4507
|
+
selected;
|
|
4508
|
+
inputFile;
|
|
4509
|
+
value;
|
|
4510
|
+
constructor(serverConfig, stateHome = defaultStateHome()) {
|
|
4511
|
+
this.serverConfig = serverConfig;
|
|
4512
|
+
this.stateHome = canonicalAmbientRoot(stateHome);
|
|
4513
|
+
this.files = new PrivateFiles(join7(this.stateHome, "cadence-assistant-v1"));
|
|
4514
|
+
this.directory = join7(this.files.namespace, "workbenches-v1", hash(resolve4(serverConfig)));
|
|
4515
|
+
}
|
|
4516
|
+
path(name) {
|
|
4517
|
+
return join7(this.directory, name);
|
|
4518
|
+
}
|
|
4519
|
+
read() {
|
|
4520
|
+
const file = this.files.read(this.path("gateway.json"));
|
|
4521
|
+
if (!file) {
|
|
4522
|
+
if (this.files.directory(this.directory))
|
|
4523
|
+
throw new ContractError("corrupt-selected");
|
|
4524
|
+
return;
|
|
4525
|
+
}
|
|
4526
|
+
const value = registration(json(file));
|
|
4527
|
+
const selected = this.files.read(this.path("input.json"));
|
|
4528
|
+
if (!selected || hash(selected.bytes) !== value.selection)
|
|
4529
|
+
throw new ContractError("corrupt-selected");
|
|
4530
|
+
decodeGatewayInput(json(selected));
|
|
4531
|
+
return value;
|
|
4532
|
+
}
|
|
4533
|
+
localAccess() {
|
|
4534
|
+
const value = this.read();
|
|
4535
|
+
if (!value || value.phase === "stopped")
|
|
4536
|
+
throw new ContractError("unavailable");
|
|
4537
|
+
return {
|
|
4538
|
+
endpoint: value.endpoint,
|
|
4539
|
+
epoch: value.epoch,
|
|
4540
|
+
capability: value.capability
|
|
4541
|
+
};
|
|
4542
|
+
}
|
|
4543
|
+
inspect() {
|
|
4544
|
+
const value = this.read();
|
|
4545
|
+
if (!value)
|
|
4546
|
+
return { status: "absent" };
|
|
4547
|
+
return {
|
|
4548
|
+
status: value.phase === "stopped" && this.files.read(this.path("gateway.lock")) ? "stopping" : value.phase,
|
|
4549
|
+
epoch: value.epoch
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
acquire(endpoint2, token, input) {
|
|
4553
|
+
const previous = this.read();
|
|
4554
|
+
if (previous && previous.phase !== "stopped")
|
|
4555
|
+
throw new ContractError("quarantine");
|
|
4556
|
+
this.lock = this.files.create(this.path("gateway.lock"), encoded({ epoch: randomUUID5() }));
|
|
4557
|
+
const source = { serverConfig: this.serverConfig, token };
|
|
4558
|
+
if (!previous)
|
|
4559
|
+
AccessMaterial.initialize(this.files, this.directory, source);
|
|
4560
|
+
const access = new AccessMaterial(this.files, this.directory, source, () => this.assert());
|
|
4561
|
+
this.selected = this.files.read(this.path("gateway.json"));
|
|
4562
|
+
if (previous && (!this.selected || encoded(registration(json(this.selected))) !== encoded(previous)))
|
|
4563
|
+
throw new ContractError("conflict");
|
|
4564
|
+
const beforeInput = this.files.read(this.path("input.json"));
|
|
4565
|
+
const inputBytes = encoded(decodeGatewayInput(input));
|
|
4566
|
+
this.inputFile = beforeInput ? this.files.replace(this.path("input.json"), beforeInput, inputBytes) : this.files.create(this.path("input.json"), inputBytes);
|
|
4567
|
+
this.value = {
|
|
4568
|
+
schema: "cadence-workbench-registration/1",
|
|
4569
|
+
epoch: randomUUID5(),
|
|
4570
|
+
id: previous?.id ?? randomUUID5(),
|
|
4571
|
+
endpoint: endpoint2,
|
|
4572
|
+
capability: randomBytes4(32).toString("base64url"),
|
|
4573
|
+
selection: hash(inputBytes),
|
|
4574
|
+
pid: process.pid,
|
|
4575
|
+
phase: "alive"
|
|
4576
|
+
};
|
|
4577
|
+
this.selected = this.selected ? this.files.replace(this.path("gateway.json"), this.selected, encoded(this.value)) : this.files.create(this.path("gateway.json"), encoded(this.value));
|
|
4578
|
+
return { registration: this.value, access };
|
|
4579
|
+
}
|
|
4580
|
+
assert() {
|
|
4581
|
+
if (!this.lock || !this.selected)
|
|
4582
|
+
throw new ContractError("unavailable");
|
|
4583
|
+
this.files.assert(this.path("gateway.lock"), this.lock);
|
|
4584
|
+
this.files.assert(this.path("input.json"), this.inputFile);
|
|
4585
|
+
this.files.assert(this.path("gateway.json"), this.selected);
|
|
4586
|
+
}
|
|
4587
|
+
phase(phase) {
|
|
4588
|
+
this.assert();
|
|
4589
|
+
this.value = { ...this.value, phase };
|
|
4590
|
+
this.selected = this.files.replace(this.path("gateway.json"), this.selected, encoded(this.value));
|
|
4591
|
+
}
|
|
4592
|
+
release() {
|
|
4593
|
+
this.phase("stopped");
|
|
4594
|
+
this.files.remove(this.path("gateway.lock"), this.lock);
|
|
4595
|
+
}
|
|
4596
|
+
projects() {
|
|
4597
|
+
const directory2 = this.path("projects");
|
|
4598
|
+
if (!this.files.directory(directory2))
|
|
4599
|
+
return [];
|
|
4600
|
+
const stream = opendirSync2(directory2), out = [];
|
|
4601
|
+
try {
|
|
4602
|
+
for (let entry = stream.readSync();entry; entry = stream.readSync()) {
|
|
4603
|
+
if (!entry.isFile() || !/^[a-f0-9]{64}\.json$/.test(entry.name) || out.length >= 128)
|
|
4604
|
+
throw new ContractError("corrupt-selected");
|
|
4605
|
+
const file = this.files.read(join7(directory2, entry.name));
|
|
4606
|
+
if (!file)
|
|
4607
|
+
throw new ContractError("corrupt-selected");
|
|
4608
|
+
const project = decodeGatewayProject(json(file));
|
|
4609
|
+
if (entry.name !== `${project.id}.json` || hash(project.path) !== project.id)
|
|
4610
|
+
throw new ContractError("corrupt-selected");
|
|
4611
|
+
out.push(project);
|
|
4612
|
+
}
|
|
4613
|
+
} finally {
|
|
4614
|
+
stream.closeSync();
|
|
4615
|
+
}
|
|
4616
|
+
return out;
|
|
4617
|
+
}
|
|
4618
|
+
saveProject(project) {
|
|
4619
|
+
this.assert();
|
|
4620
|
+
const p = decodeGatewayProject(project), path2 = this.path(`projects/${p.id}.json`), before = this.files.read(path2);
|
|
4621
|
+
if (!before && this.projects().length >= 128)
|
|
4622
|
+
throw new ContractError("capacity");
|
|
4623
|
+
if (before) {
|
|
4624
|
+
const old = decodeGatewayProject(json(before));
|
|
4625
|
+
if (old.id !== p.id || old.path !== p.path)
|
|
4626
|
+
throw new ContractError("conflict");
|
|
4627
|
+
this.files.replace(path2, before, encoded(p));
|
|
4628
|
+
} else
|
|
4629
|
+
this.files.create(path2, encoded(p));
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
|
|
4633
|
+
// src/service/runtime.ts
|
|
4634
|
+
import { homedir as homedir4 } from "node:os";
|
|
4635
|
+
import { dirname as dirname7, isAbsolute as isAbsolute6, join as join8, resolve as resolve5 } from "node:path";
|
|
4636
|
+
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
4637
|
+
function absolute(value) {
|
|
4638
|
+
const result = text(value, 4096, true);
|
|
4639
|
+
if (!isAbsolute6(result))
|
|
4640
|
+
throw new ContractError("invalid");
|
|
4641
|
+
return result;
|
|
4642
|
+
}
|
|
4643
|
+
function decodeHostConfig(value) {
|
|
4644
|
+
const o = closed(value, [
|
|
4645
|
+
"schema",
|
|
4646
|
+
"http",
|
|
4647
|
+
"cwd",
|
|
4648
|
+
"agentDir",
|
|
4649
|
+
"tokenSource",
|
|
4650
|
+
"tokenFile"
|
|
4651
|
+
]);
|
|
4652
|
+
const tokenSource = oneOf(o.tokenSource, [
|
|
4653
|
+
"generated",
|
|
4654
|
+
"environment",
|
|
4655
|
+
"file",
|
|
4656
|
+
"config"
|
|
4657
|
+
]);
|
|
4658
|
+
const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
|
|
4659
|
+
if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
|
|
4660
|
+
throw new ContractError("invalid");
|
|
4661
|
+
return {
|
|
4662
|
+
schema: literal(o.schema, "cadence-service-host/1"),
|
|
4663
|
+
http: decodeHttpPolicy(o.http),
|
|
4664
|
+
cwd: absolute(o.cwd),
|
|
4665
|
+
agentDir: absolute(o.agentDir),
|
|
4666
|
+
tokenSource,
|
|
4667
|
+
tokenFile
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4670
|
+
function readHostConfig(c) {
|
|
4671
|
+
const owner = c.configuration();
|
|
4672
|
+
if (!owner)
|
|
4673
|
+
return;
|
|
4674
|
+
const snapshot = c.files.read(c.path("host-config.json"));
|
|
4675
|
+
if (!snapshot)
|
|
4676
|
+
throw new ContractError("corrupt-selected");
|
|
4677
|
+
const config = decodeHostConfig(json(snapshot));
|
|
4678
|
+
if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
|
|
4679
|
+
throw new ContractError("conflict");
|
|
4680
|
+
return config;
|
|
4681
|
+
}
|
|
4682
|
+
function hostEnvironment(ambient) {
|
|
4683
|
+
let route;
|
|
4684
|
+
const facts = networkSnapshot(ambient);
|
|
4685
|
+
try {
|
|
4686
|
+
if (facts.NODE_OPTIONS.present)
|
|
4687
|
+
throw new Error;
|
|
4688
|
+
route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
|
|
4689
|
+
} catch {
|
|
4690
|
+
throw new ContractError("unsupported-context");
|
|
4691
|
+
}
|
|
4692
|
+
const result = Object.create(null);
|
|
4693
|
+
for (const key of Object.keys(ambient)) {
|
|
4694
|
+
const d = Object.getOwnPropertyDescriptor(ambient, key);
|
|
4695
|
+
if (!d || !("value" in d) || typeof d.value !== "string")
|
|
4696
|
+
throw new ContractError("invalid");
|
|
4697
|
+
if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
|
|
4698
|
+
continue;
|
|
4699
|
+
result[key] = d.value;
|
|
4700
|
+
}
|
|
4701
|
+
if (route.redactionLiterals.length) {
|
|
4702
|
+
result.HTTP_PROXY = route.env.HTTP_PROXY;
|
|
4703
|
+
result.HTTPS_PROXY = route.env.HTTPS_PROXY;
|
|
4704
|
+
result.NODE_USE_ENV_PROXY = "1";
|
|
4705
|
+
}
|
|
4706
|
+
result.PI_OFFLINE = "1";
|
|
4707
|
+
result.NO_COLOR = "1";
|
|
4708
|
+
if (!sameNetwork(facts, networkSnapshot(ambient)))
|
|
4709
|
+
throw new ContractError("stale");
|
|
4710
|
+
return result;
|
|
4711
|
+
}
|
|
4712
|
+
function selectStart(options, ambient) {
|
|
4713
|
+
const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
|
|
4714
|
+
const old = readHostConfig(c);
|
|
4715
|
+
const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
|
|
4716
|
+
if (env && (!("value" in env) || typeof env.value !== "string"))
|
|
4717
|
+
throw new ContractError("invalid");
|
|
4718
|
+
let fromFile;
|
|
4719
|
+
if (options.config) {
|
|
4720
|
+
const path2 = resolve5(options.config), f = new PrivateFiles(dirname7(path2));
|
|
4721
|
+
const bytes = f.read(path2);
|
|
4722
|
+
if (!bytes)
|
|
4723
|
+
throw new ContractError("corrupt-selected");
|
|
4724
|
+
fromFile = decodeHostConfig(json(bytes));
|
|
4725
|
+
}
|
|
4726
|
+
const base = old ?? fromFile ?? decodeHostConfig({
|
|
4727
|
+
schema: "cadence-service-host/1",
|
|
4728
|
+
cwd: workspace.cwd,
|
|
4729
|
+
http: {
|
|
4730
|
+
schema: "cadence-service-http/1",
|
|
4731
|
+
host: options.host ?? "127.0.0.1",
|
|
4732
|
+
port: options.port ?? 17423,
|
|
4733
|
+
proxy: null
|
|
4734
|
+
},
|
|
4735
|
+
agentDir: resolve5(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join8(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
|
|
4736
|
+
tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
|
|
4737
|
+
tokenFile: options.tokenFile ? resolve5(options.tokenFile) : null
|
|
4738
|
+
});
|
|
4739
|
+
const server = options.serverConfig !== undefined ? resolve5(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
|
|
4740
|
+
let selected = base;
|
|
4741
|
+
if (server) {
|
|
4742
|
+
if (options.tokenFile !== undefined)
|
|
4743
|
+
throw new ContractError("conflict");
|
|
4744
|
+
const { port } = readServerConfig(server).config;
|
|
4745
|
+
selected = decodeHostConfig({
|
|
4746
|
+
...base,
|
|
4747
|
+
http: { ...base.http, port },
|
|
4748
|
+
tokenSource: "config",
|
|
4749
|
+
tokenFile: server
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
if (fromFile && encoded(fromFile) !== encoded(selected) || options.port !== undefined && options.port !== selected.http.port || options.host !== undefined && options.host !== selected.http.host || options.agentDir !== undefined && resolve5(options.agentDir) !== selected.agentDir || options.tokenFile !== undefined && resolve5(options.tokenFile) !== selected.tokenFile || env && selected.tokenSource !== "environment" || selected.tokenSource === "environment" && !env || resolveWorkspace(selected.cwd).root !== workspace.root)
|
|
4753
|
+
throw new ContractError("conflict");
|
|
4754
|
+
return {
|
|
4755
|
+
workspace: workspace.root,
|
|
4756
|
+
stateHome: c.stateHome,
|
|
4757
|
+
config: selected,
|
|
4758
|
+
environmentToken: env?.value ?? null
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
function decodeHostInput(value) {
|
|
4762
|
+
const o = closed(value, [
|
|
4763
|
+
"workspace",
|
|
4764
|
+
"stateHome",
|
|
4765
|
+
"config",
|
|
4766
|
+
"environmentToken"
|
|
4767
|
+
]);
|
|
4768
|
+
const config = decodeHostConfig(o.config);
|
|
4769
|
+
const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
|
|
4770
|
+
if (config.tokenSource === "environment" !== (environmentToken !== null))
|
|
4771
|
+
throw new ContractError("invalid");
|
|
4772
|
+
return {
|
|
4773
|
+
workspace: absolute(o.workspace),
|
|
4774
|
+
stateHome: absolute(o.stateHome),
|
|
4775
|
+
config,
|
|
4776
|
+
environmentToken
|
|
4777
|
+
};
|
|
4778
|
+
}
|
|
4779
|
+
function portValue(value) {
|
|
4780
|
+
if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
|
|
4781
|
+
throw new ContractError("invalid");
|
|
4782
|
+
return integer(Number(value), 0, 65535);
|
|
4783
|
+
}
|
|
4784
|
+
var STARTUP_STAGES = [
|
|
4785
|
+
"package",
|
|
4786
|
+
"pi-agent-directory",
|
|
4787
|
+
"pi-auth",
|
|
4788
|
+
"pi-models",
|
|
4789
|
+
"pi-runtime",
|
|
4790
|
+
"service"
|
|
4791
|
+
];
|
|
4792
|
+
function decodeStartupReport(value) {
|
|
4793
|
+
const o = closed(value, ["status", "error", "stage"]);
|
|
4794
|
+
return {
|
|
4795
|
+
status: oneOf(o.status, ["refused", "quarantined"]),
|
|
4796
|
+
error: oneOf(o.error, ERROR_CODES),
|
|
4797
|
+
stage: oneOf(o.stage, STARTUP_STAGES)
|
|
4798
|
+
};
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
// src/service/gateway-cli.ts
|
|
4802
|
+
async function gatewayMain(argv, hostUrl) {
|
|
4803
|
+
const operation = argv.shift();
|
|
4804
|
+
if (![
|
|
4805
|
+
"start",
|
|
4806
|
+
"status",
|
|
4807
|
+
"diagnose",
|
|
4808
|
+
"stop",
|
|
4809
|
+
"token-link",
|
|
4810
|
+
"token-rotate"
|
|
4811
|
+
].includes(operation ?? ""))
|
|
4812
|
+
throw new ContractError("invalid");
|
|
4813
|
+
let config = serverConfigPath(homedir5()), stateHome = defaultStateHome(), agentDir, policyFile;
|
|
4814
|
+
let foreground = false, reveal = false;
|
|
4815
|
+
const seen = new Set;
|
|
4816
|
+
while (argv.length) {
|
|
4817
|
+
const key = argv.shift();
|
|
4818
|
+
if (seen.has(key))
|
|
4819
|
+
throw new ContractError("invalid");
|
|
4820
|
+
seen.add(key);
|
|
4821
|
+
if (key === "--foreground" && operation === "start")
|
|
4822
|
+
foreground = true;
|
|
4823
|
+
else if (key === "--reveal" && operation === "token-link")
|
|
4824
|
+
reveal = true;
|
|
4825
|
+
else if (argv[0] && ["--server-config", "--state-home", "--agent-dir", "--config"].includes(key)) {
|
|
4826
|
+
const value = resolve6(argv.shift());
|
|
4827
|
+
if (key === "--server-config")
|
|
4828
|
+
config = value;
|
|
4829
|
+
if (key === "--state-home")
|
|
4830
|
+
stateHome = value;
|
|
4831
|
+
if (key === "--agent-dir")
|
|
4832
|
+
agentDir = value;
|
|
4833
|
+
if (key === "--config")
|
|
4834
|
+
policyFile = value;
|
|
4835
|
+
} else
|
|
4836
|
+
throw new ContractError("invalid");
|
|
4837
|
+
}
|
|
4838
|
+
const store = new GatewayStore(config, stateHome);
|
|
4839
|
+
let result;
|
|
4840
|
+
if (operation === "start") {
|
|
4841
|
+
const network = networkSnapshot(process.env), env = hostEnvironment(process.env);
|
|
4842
|
+
if (!seen.has("--server-config"))
|
|
4843
|
+
ensureServerConfig(homedir5());
|
|
4844
|
+
const server = readServerConfig(config).config;
|
|
4845
|
+
const selectedFile = store.files.read(store.path("input.json"));
|
|
4846
|
+
const selected = selectedFile ? decodeGatewayInput(json(selectedFile)) : undefined;
|
|
4847
|
+
let policy = selected?.policy ?? decodeHttpPolicy({
|
|
4848
|
+
schema: "cadence-service-http/1",
|
|
4849
|
+
host: "127.0.0.1",
|
|
4850
|
+
port: server.port,
|
|
4851
|
+
proxy: null
|
|
4852
|
+
});
|
|
4853
|
+
if (policyFile) {
|
|
4854
|
+
const file = new PrivateFiles(dirname8(policyFile)).read(policyFile);
|
|
4855
|
+
if (!file)
|
|
4856
|
+
throw new ContractError("invalid");
|
|
4857
|
+
policy = decodeHttpPolicy(json(file));
|
|
4858
|
+
}
|
|
4859
|
+
const input = decodeGatewayInput({
|
|
4860
|
+
kind: "workbench",
|
|
4861
|
+
serverConfig: config,
|
|
4862
|
+
stateHome: store.stateHome,
|
|
4863
|
+
agentDir: agentDir ?? selected?.agentDir ?? resolve6(process.env.PI_CODING_AGENT_DIR ?? join9(homedir5(), ".pi", "agent")),
|
|
4864
|
+
policy: { ...policy, port: server.port }
|
|
4865
|
+
});
|
|
4866
|
+
const status = await serviceStatus(store);
|
|
4867
|
+
if (status && typeof status === "object" && "status" in status && status.status === "alive") {
|
|
4868
|
+
if (!selected || encoded(selected) !== encoded(input))
|
|
4869
|
+
throw new ContractError("conflict");
|
|
4870
|
+
result = status;
|
|
4871
|
+
} else
|
|
4872
|
+
result = await launchHost(input, store, foreground, hostUrl, dirname8(dirname8(fileURLToPath(hostUrl))), env, network);
|
|
4873
|
+
if (!foreground && result && typeof result === "object" && "status" in result && result.status === "alive") {
|
|
4874
|
+
const reply = closed(await nativeCall(store, "token-link", true), [
|
|
4875
|
+
"link"
|
|
4876
|
+
]);
|
|
4877
|
+
process.stderr.write(`Cadence workbench: ${text(reply.link, 4096, true)}
|
|
4878
|
+
No project or default workspace was created.
|
|
4879
|
+
`);
|
|
4880
|
+
}
|
|
4881
|
+
} else if (operation === "stop")
|
|
4882
|
+
result = await stopService(store);
|
|
4883
|
+
else if (operation === "status" || operation === "diagnose")
|
|
4884
|
+
result = await serviceStatus(store, operation === "diagnose");
|
|
4885
|
+
else {
|
|
4886
|
+
if (operation === "token-link" && !reveal)
|
|
4887
|
+
throw new ContractError("forbidden");
|
|
4888
|
+
result = await nativeCall(store, operation, reveal);
|
|
4889
|
+
}
|
|
4890
|
+
if (result !== null)
|
|
4891
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
4892
|
+
`);
|
|
4893
|
+
if (result && typeof result === "object" && "status" in result && ["unconfirmed", "unavailable", "quarantined", "refused"].includes(String(result.status)))
|
|
4894
|
+
process.exitCode = 1;
|
|
4895
|
+
}
|
|
4896
|
+
|
|
4897
|
+
// src/service/launchd.ts
|
|
4898
|
+
import { rmSync as rmSync2 } from "node:fs";
|
|
4899
|
+
import { dirname as dirname9, join as join10 } from "node:path";
|
|
4900
|
+
var LABEL = "com.abelxiaoxing.cadence";
|
|
4901
|
+
var MARKER2 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
4902
|
+
<!-- Generated by @abelxiaoxing/cadence`;
|
|
4903
|
+
function plistPath(s) {
|
|
4904
|
+
return join10(s.home, "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
4905
|
+
}
|
|
4906
|
+
var xml = (value) => checked(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
4907
|
+
function buildPlist(u) {
|
|
4908
|
+
const string = (value) => ` <string>${xml(value)}</string>`;
|
|
4909
|
+
return [
|
|
4910
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
4911
|
+
"<!-- Generated by @abelxiaoxing/cadence; `cadence server install` and `restart` rewrite this file. -->",
|
|
4912
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
4913
|
+
'<plist version="1.0">',
|
|
4914
|
+
"<dict>",
|
|
4915
|
+
"\t<key>Label</key>",
|
|
4916
|
+
` <string>${LABEL}</string>`,
|
|
4917
|
+
"\t<key>ProgramArguments</key>",
|
|
4918
|
+
"\t<array>",
|
|
4919
|
+
...[
|
|
4920
|
+
u.node,
|
|
4921
|
+
u.cli,
|
|
4922
|
+
...u.workspace === null ? ["workbench", "start", "--foreground"] : ["start", "--foreground", "--workspace", u.workspace],
|
|
4923
|
+
"--server-config",
|
|
4924
|
+
u.serverConfig
|
|
4925
|
+
].map(string),
|
|
4926
|
+
"\t</array>",
|
|
4927
|
+
...u.workspace === null ? ["<!-- Cadence workbench gateway; no project workspace -->"] : [],
|
|
4928
|
+
"\t<key>WorkingDirectory</key>",
|
|
4929
|
+
` <string>${xml(u.workspace ?? dirname9(dirname9(u.cli)))}</string>`,
|
|
4930
|
+
"\t<key>EnvironmentVariables</key>",
|
|
4931
|
+
"\t<dict>",
|
|
4932
|
+
...u.environment.flatMap(([k, v]) => [
|
|
4933
|
+
` <key>${xml(k)}</key>`,
|
|
4934
|
+
string(v)
|
|
4935
|
+
]),
|
|
4936
|
+
"\t</dict>",
|
|
4937
|
+
"\t<key>RunAtLoad</key>",
|
|
4938
|
+
"\t<true/>",
|
|
4939
|
+
"\t<key>ProcessType</key>",
|
|
4940
|
+
"\t<string>Background</string>",
|
|
4941
|
+
"\t<key>StandardOutPath</key>",
|
|
4942
|
+
` <string>${xml(u.log)}</string>`,
|
|
4943
|
+
"\t<key>StandardErrorPath</key>",
|
|
4944
|
+
` <string>${xml(u.log)}</string>`,
|
|
4945
|
+
"</dict>",
|
|
4946
|
+
"</plist>",
|
|
4947
|
+
""
|
|
4948
|
+
].join(`
|
|
4949
|
+
`);
|
|
4950
|
+
}
|
|
4951
|
+
function plistWorkspace(text2) {
|
|
4952
|
+
if (text2?.includes("<!-- Cadence workbench gateway; no project workspace -->"))
|
|
4953
|
+
return null;
|
|
4954
|
+
const m = text2?.match(/^\t<key>WorkingDirectory<\/key>\n\t<string>([^<]*)<\/string>$/m);
|
|
4955
|
+
return m?.[1].replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
4956
|
+
}
|
|
4957
|
+
var domain = (s) => `gui/${s.uid}`;
|
|
4958
|
+
var target = (s) => `${domain(s)}/${LABEL}`;
|
|
4959
|
+
var loaded = (s) => s.launchctl(["print", target(s)]).status === 0;
|
|
4960
|
+
function disabled(s) {
|
|
4961
|
+
return new RegExp(`"${LABEL.replaceAll(".", "\\.")}" => (true|disabled)`).test(s.launchctl(["print-disabled", domain(s)]).stdout);
|
|
4962
|
+
}
|
|
4963
|
+
async function configureLaunchAgent(s, mode, requested) {
|
|
4964
|
+
if (s.uid === undefined)
|
|
4965
|
+
return { status: "unavailable" };
|
|
4966
|
+
const path2 = plistPath(s);
|
|
4967
|
+
const previous = readGenerated(path2, MARKER2);
|
|
4968
|
+
if (previous === null)
|
|
4969
|
+
return { status: "foreign", unit: path2 };
|
|
4970
|
+
if (mode === "restart" && previous === undefined)
|
|
4971
|
+
return { status: "absent" };
|
|
4972
|
+
const sdk = sdkPackages(s.root);
|
|
4973
|
+
const kept = plistWorkspace(previous);
|
|
4974
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
|
|
4975
|
+
if (workspace !== null && !sdk)
|
|
4976
|
+
return { status: "missing-pi-sdk" };
|
|
4977
|
+
const refused2 = await settle(s, kept);
|
|
4978
|
+
if (refused2)
|
|
4979
|
+
return { ...refused2, unit: path2 };
|
|
4980
|
+
if (loaded(s))
|
|
4981
|
+
s.launchctl(["bootout", target(s)]);
|
|
4982
|
+
write(path2, buildPlist({
|
|
4983
|
+
node: s.node,
|
|
4984
|
+
cli: join10(s.root, "src", "service-cli.mjs"),
|
|
4985
|
+
workspace,
|
|
4986
|
+
serverConfig: serverConfigPath(s.home),
|
|
4987
|
+
environment: serviceEnvironment(s),
|
|
4988
|
+
log: join10(s.home, "Library", "Logs", "cadence.log")
|
|
4989
|
+
}));
|
|
4990
|
+
const result = {
|
|
4991
|
+
unit: path2,
|
|
4992
|
+
workspace,
|
|
4993
|
+
...redirectedFrom ? { redirectedFrom } : {}
|
|
4994
|
+
};
|
|
4995
|
+
if (mode === "install")
|
|
4996
|
+
s.launchctl(["enable", target(s)]);
|
|
4997
|
+
else if (disabled(s))
|
|
4998
|
+
return { status: "disabled", ...result };
|
|
4999
|
+
if (workspace !== null && !regularFile(join10(agentDirectory(s), "auth.json")))
|
|
5000
|
+
return { status: "waiting-for-pi-login", ...result };
|
|
5001
|
+
const started = s.launchctl(["bootstrap", domain(s), path2]).status === 0;
|
|
5002
|
+
return { status: started ? "started" : "failed", ...result };
|
|
5003
|
+
}
|
|
5004
|
+
async function stopLaunchAgent(s) {
|
|
5005
|
+
const path2 = plistPath(s);
|
|
5006
|
+
const previous = readGenerated(path2, MARKER2);
|
|
5007
|
+
if (previous === null)
|
|
5008
|
+
return { status: "foreign", unit: path2 };
|
|
5009
|
+
if (previous === undefined)
|
|
5010
|
+
return { status: "absent" };
|
|
5011
|
+
const workspace = plistWorkspace(previous);
|
|
5012
|
+
const refused2 = await settle(s, workspace);
|
|
5013
|
+
return { ...refused2 ?? { status: "stopped", workspace }, unit: path2 };
|
|
5014
|
+
}
|
|
5015
|
+
async function removeLaunchAgent(s) {
|
|
5016
|
+
const path2 = plistPath(s);
|
|
5017
|
+
const previous = readGenerated(path2, MARKER2);
|
|
5018
|
+
if (previous === null)
|
|
5019
|
+
return { status: "foreign", unit: path2 };
|
|
5020
|
+
if (previous === undefined)
|
|
5021
|
+
return { status: "absent" };
|
|
5022
|
+
const refused2 = await settle(s, plistWorkspace(previous));
|
|
5023
|
+
if (refused2)
|
|
5024
|
+
return { ...refused2, unit: path2 };
|
|
5025
|
+
if (s.uid !== undefined && loaded(s))
|
|
5026
|
+
s.launchctl(["bootout", target(s)]);
|
|
5027
|
+
rmSync2(path2);
|
|
5028
|
+
return { status: "removed", unit: path2 };
|
|
5029
|
+
}
|
|
5030
|
+
function launchAgentStatus(s) {
|
|
5031
|
+
const path2 = plistPath(s);
|
|
5032
|
+
const previous = readGenerated(path2, MARKER2);
|
|
5033
|
+
if (previous === null)
|
|
5034
|
+
return { status: "foreign", unit: path2 };
|
|
5035
|
+
if (previous === undefined)
|
|
5036
|
+
return { status: "absent" };
|
|
5037
|
+
const printed = s.uid === undefined ? undefined : s.launchctl(["print", target(s)]);
|
|
5038
|
+
return {
|
|
5039
|
+
status: printed?.status !== 0 ? "unloaded" : /^\s*state = running$/m.test(printed.stdout) ? "active" : "inactive",
|
|
5040
|
+
enabled: s.uid !== undefined && disabled(s) ? "disabled" : "enabled",
|
|
5041
|
+
unit: path2,
|
|
5042
|
+
workspace: plistWorkspace(previous)
|
|
5043
|
+
};
|
|
5044
|
+
}
|
|
5045
|
+
|
|
5046
|
+
// src/service/login-run.ts
|
|
5047
|
+
import { rmSync as rmSync3 } from "node:fs";
|
|
5048
|
+
import { dirname as dirname10, isAbsolute as isAbsolute7, join as join11 } from "node:path";
|
|
5049
|
+
var RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
5050
|
+
var APPROVED_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run";
|
|
5051
|
+
var RUN_VALUE = "Cadence";
|
|
5052
|
+
var MARKER3 = "' Generated by @abelxiaoxing/cadence";
|
|
5053
|
+
function launcherPath(s) {
|
|
5054
|
+
const local = s.env.LOCALAPPDATA;
|
|
5055
|
+
return join11(local && isAbsolute7(local) ? local : join11(s.home, "AppData", "Local"), "cadence-server", "cadence.vbs");
|
|
5056
|
+
}
|
|
5057
|
+
var literal2 = (value) => `"${checked(value).replaceAll('"', '""')}"`;
|
|
5058
|
+
function argument2(value) {
|
|
5059
|
+
if (checked(value).includes('"'))
|
|
5060
|
+
throw new ContractError("invalid");
|
|
5061
|
+
return `"${value}"`;
|
|
5062
|
+
}
|
|
5063
|
+
function buildLauncher(u) {
|
|
5064
|
+
const command = `${argument2(u.node)} ${argument2(u.cli)} ${u.workspace === null ? "workbench start" : `start --workspace ${argument2(u.workspace)}`} --server-config ${argument2(u.serverConfig)}`;
|
|
5065
|
+
return [
|
|
5066
|
+
`${MARKER3}; \`cadence server install\` and \`restart\` rewrite this file.`,
|
|
5067
|
+
`' Started at login by ${RUN_KEY}\\${RUN_VALUE}; window style 0 hides the console.`,
|
|
5068
|
+
'Set shell = CreateObject("WScript.Shell")',
|
|
5069
|
+
...u.environment.length ? ['Set env = shell.Environment("Process")'] : [],
|
|
5070
|
+
...u.environment.map(([k, v]) => `env(${literal2(k)}) = ${literal2(v)}`),
|
|
5071
|
+
...u.workspace === null ? ["' Cadence workbench gateway; no project workspace"] : [],
|
|
5072
|
+
`shell.CurrentDirectory = ${literal2(u.workspace ?? dirname10(dirname10(u.cli)))}`,
|
|
5073
|
+
`shell.Run ${literal2(command)}, 0, False`,
|
|
5074
|
+
""
|
|
5075
|
+
].join(`\r
|
|
5076
|
+
`);
|
|
5077
|
+
}
|
|
5078
|
+
var utf16 = (text2) => Buffer.concat([Buffer.from([255, 254]), Buffer.from(text2, "utf16le")]);
|
|
5079
|
+
function launcherWorkspace(text2) {
|
|
5080
|
+
if (text2?.includes("' Cadence workbench gateway; no project workspace"))
|
|
5081
|
+
return null;
|
|
5082
|
+
return text2?.match(/^shell\.CurrentDirectory = "((?:[^"]|"")*)"\r?$/m)?.[1].replaceAll('""', '"');
|
|
5083
|
+
}
|
|
5084
|
+
var runEntry = (path2, s) => `"${wscript(s.env)}" "${path2}"`;
|
|
5085
|
+
var present = (s, key) => s.reg(["query", key, "/v", RUN_VALUE]).status === 0;
|
|
5086
|
+
function approvedOff(s) {
|
|
5087
|
+
const r = s.reg(["query", APPROVED_KEY, "/v", RUN_VALUE]);
|
|
5088
|
+
const first = r.status === 0 && /REG_BINARY\s+([0-9A-F]{2})/i.exec(r.stdout);
|
|
5089
|
+
return !!first && (Number.parseInt(first[1], 16) & 1) === 1;
|
|
5090
|
+
}
|
|
5091
|
+
async function configureLoginRun(s, mode, requested) {
|
|
5092
|
+
const path2 = launcherPath(s);
|
|
5093
|
+
const previous = readGenerated(path2, MARKER3);
|
|
5094
|
+
if (previous === null)
|
|
5095
|
+
return { status: "foreign", unit: path2 };
|
|
5096
|
+
if (previous === undefined && present(s, RUN_KEY))
|
|
5097
|
+
return { status: "foreign", unit: `${RUN_KEY}\\${RUN_VALUE}` };
|
|
5098
|
+
if (mode === "restart" && previous === undefined)
|
|
5099
|
+
return { status: "absent" };
|
|
5100
|
+
const sdk = sdkPackages(s.root);
|
|
5101
|
+
const kept = launcherWorkspace(previous);
|
|
5102
|
+
const { workspace, redirectedFrom } = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : kept);
|
|
5103
|
+
if (workspace !== null && !sdk)
|
|
5104
|
+
return { status: "missing-pi-sdk" };
|
|
5105
|
+
const refused2 = await settle(s, kept);
|
|
5106
|
+
if (refused2)
|
|
5107
|
+
return { ...refused2, unit: path2 };
|
|
5108
|
+
const environment = s.env.PI_CODING_AGENT_DIR ? [["PI_CODING_AGENT_DIR", agentDirectory(s)]] : [];
|
|
4249
5109
|
write(path2, utf16(buildLauncher({
|
|
4250
5110
|
node: s.node,
|
|
4251
|
-
cli:
|
|
5111
|
+
cli: join11(s.root, "src", "service-cli.mjs"),
|
|
4252
5112
|
workspace,
|
|
4253
5113
|
serverConfig: serverConfigPath(s.home),
|
|
4254
5114
|
environment
|
|
@@ -4264,7 +5124,7 @@ async function configureLoginRun(s, mode, requested) {
|
|
|
4264
5124
|
return { status: "failed", ...result };
|
|
4265
5125
|
s.reg(["delete", APPROVED_KEY, "/v", RUN_VALUE, "/f"]);
|
|
4266
5126
|
}
|
|
4267
|
-
if (!regularFile(
|
|
5127
|
+
if (workspace !== null && !regularFile(join11(agentDirectory(s), "auth.json")))
|
|
4268
5128
|
return { status: "waiting-for-pi-login", ...result };
|
|
4269
5129
|
return { status: s.launch(path2) ? "started" : "failed", ...result };
|
|
4270
5130
|
}
|
|
@@ -4303,7 +5163,7 @@ async function loginRunStatus(s) {
|
|
|
4303
5163
|
if (previous === undefined)
|
|
4304
5164
|
return { status: "absent" };
|
|
4305
5165
|
const workspace = launcherWorkspace(previous);
|
|
4306
|
-
const owner = workspace ? await s.ownerStatus(workspace) : "unavailable";
|
|
5166
|
+
const owner = workspace !== undefined ? await s.ownerStatus(workspace, serverConfigPath(s.home)) : "unavailable";
|
|
4307
5167
|
return {
|
|
4308
5168
|
status: owner === "alive" ? "active" : "inactive",
|
|
4309
5169
|
enabled: present(s, RUN_KEY) && !approvedOff(s) ? "enabled" : "disabled",
|
|
@@ -4316,9 +5176,9 @@ async function loginRunStatus(s) {
|
|
|
4316
5176
|
import { isatty } from "node:tty";
|
|
4317
5177
|
|
|
4318
5178
|
// src/history/recovery.ts
|
|
4319
|
-
import { createHash as
|
|
4320
|
-
import { lstatSync as lstatSync7, opendirSync as
|
|
4321
|
-
import { join as
|
|
5179
|
+
import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
|
|
5180
|
+
import { lstatSync as lstatSync7, opendirSync as opendirSync4 } from "node:fs";
|
|
5181
|
+
import { join as join13, relative as relative4 } from "node:path";
|
|
4322
5182
|
|
|
4323
5183
|
// src/service/recovery.ts
|
|
4324
5184
|
async function inspectRecovery(coordination, history) {
|
|
@@ -4366,19 +5226,19 @@ async function resolveRecovery(coordination, confirmation, history) {
|
|
|
4366
5226
|
}
|
|
4367
5227
|
|
|
4368
5228
|
// src/history/files.ts
|
|
4369
|
-
import { createHash as
|
|
5229
|
+
import { createHash as createHash5, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "node:crypto";
|
|
4370
5230
|
import {
|
|
4371
5231
|
closeSync as closeSync5,
|
|
4372
5232
|
constants as constants5,
|
|
4373
5233
|
fstatSync as fstatSync4,
|
|
4374
5234
|
fsyncSync as fsyncSync3,
|
|
4375
5235
|
lstatSync as lstatSync6,
|
|
4376
|
-
opendirSync as
|
|
5236
|
+
opendirSync as opendirSync3,
|
|
4377
5237
|
openSync as openSync5,
|
|
4378
5238
|
statfsSync
|
|
4379
5239
|
} from "node:fs";
|
|
4380
5240
|
import { open } from "node:fs/promises";
|
|
4381
|
-
import { dirname as
|
|
5241
|
+
import { dirname as dirname12, join as join12 } from "node:path";
|
|
4382
5242
|
|
|
4383
5243
|
// src/history/admission.ts
|
|
4384
5244
|
function decodeRunAdmission(value) {
|
|
@@ -4406,13 +5266,13 @@ import {
|
|
|
4406
5266
|
openSync as openSync4,
|
|
4407
5267
|
readSync as readSync3
|
|
4408
5268
|
} from "node:fs";
|
|
4409
|
-
import { dirname as
|
|
5269
|
+
import { dirname as dirname11 } from "node:path";
|
|
4410
5270
|
function bad() {
|
|
4411
5271
|
throw new ContractError("corrupt-selected");
|
|
4412
5272
|
}
|
|
4413
5273
|
var stamp2 = (s) => [s.dev, s.ino, s.size, s.mode, s.uid, s.nlink, s.ctimeMs, s.mtimeMs].join(":");
|
|
4414
5274
|
function selected(files, path2, read, walks) {
|
|
4415
|
-
if (!files.files.directory(
|
|
5275
|
+
if (!files.files.directory(dirname11(path2), false, walks))
|
|
4416
5276
|
bad();
|
|
4417
5277
|
const before = lstatSync5(path2);
|
|
4418
5278
|
if (!before.isFile() || before.nlink !== 1 || process.platform !== "win32" && (before.mode & 63 || process.getuid && before.uid !== process.getuid()))
|
|
@@ -4444,7 +5304,7 @@ function statSize(files, path2, maximum) {
|
|
|
4444
5304
|
}
|
|
4445
5305
|
|
|
4446
5306
|
// src/history/types.ts
|
|
4447
|
-
import { createHash as
|
|
5307
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
4448
5308
|
var RECORD_BYTES = LIMITS.controlBytes + 64 * 1024;
|
|
4449
5309
|
var LEGACY_FORMAT = "cadence-history-store/2";
|
|
4450
5310
|
var FORMAT = "cadence-history-store/3";
|
|
@@ -4810,7 +5670,7 @@ function batchArtifacts(rows) {
|
|
|
4810
5670
|
}
|
|
4811
5671
|
var decodedLines = new Map;
|
|
4812
5672
|
var decodedBytes = 0;
|
|
4813
|
-
var lineKey = (text2) =>
|
|
5673
|
+
var lineKey = (text2) => createHash4("sha256").update(text2).digest("hex");
|
|
4814
5674
|
function remember(text2, value) {
|
|
4815
5675
|
const key = lineKey(text2);
|
|
4816
5676
|
if (decodedLines.has(key))
|
|
@@ -4909,14 +5769,14 @@ class HistoryFiles {
|
|
|
4909
5769
|
this.coordination = coordination;
|
|
4910
5770
|
this.format = format;
|
|
4911
5771
|
this.formatSnapshot = formatSnapshot;
|
|
4912
|
-
this.directory =
|
|
5772
|
+
this.directory = join12(coordination.directory, "history-v2");
|
|
4913
5773
|
this.files = coordination.files;
|
|
4914
5774
|
}
|
|
4915
5775
|
static open(coordination) {
|
|
4916
|
-
const dir =
|
|
5776
|
+
const dir = join12(coordination.directory, "history-v2");
|
|
4917
5777
|
if (!coordination.files.directory(dir))
|
|
4918
5778
|
return;
|
|
4919
|
-
const file = coordination.files.read(
|
|
5779
|
+
const file = coordination.files.read(join12(dir, "format.json"));
|
|
4920
5780
|
if (!file)
|
|
4921
5781
|
corrupt();
|
|
4922
5782
|
const format = decodeFormat(json(file));
|
|
@@ -4932,29 +5792,29 @@ class HistoryFiles {
|
|
|
4932
5792
|
existing.assertWritable();
|
|
4933
5793
|
return existing;
|
|
4934
5794
|
}
|
|
4935
|
-
const dir =
|
|
5795
|
+
const dir = join12(c.directory, "history-v2");
|
|
4936
5796
|
const format = {
|
|
4937
5797
|
schema: FORMAT,
|
|
4938
|
-
storeId:
|
|
4939
|
-
workspaceId:
|
|
5798
|
+
storeId: randomUUID6(),
|
|
5799
|
+
workspaceId: randomUUID6(),
|
|
4940
5800
|
rootIdentity: hash(c.store.root)
|
|
4941
5801
|
};
|
|
4942
5802
|
c.files.directory(dir, true);
|
|
4943
5803
|
for (const name of ["commands", "ledger", "runs", "indexes"]) {
|
|
4944
5804
|
lease.assert();
|
|
4945
|
-
c.files.directory(
|
|
5805
|
+
c.files.directory(join12(dir, name), true);
|
|
4946
5806
|
}
|
|
4947
5807
|
lease.assert();
|
|
4948
|
-
const keyPath =
|
|
5808
|
+
const keyPath = join12(dir, "read-proof-key.json");
|
|
4949
5809
|
c.files.create(keyPath, encoded({
|
|
4950
5810
|
schema: "cadence-history-read-key/1",
|
|
4951
5811
|
storeId: format.storeId,
|
|
4952
|
-
key:
|
|
5812
|
+
key: randomBytes5(32).toString("base64url")
|
|
4953
5813
|
}));
|
|
4954
5814
|
lease.assert();
|
|
4955
|
-
const snapshot = c.files.create(
|
|
5815
|
+
const snapshot = c.files.create(join12(dir, "format.json"), encoded(format));
|
|
4956
5816
|
const result = new HistoryFiles(c, format, snapshot);
|
|
4957
|
-
result.syncParents(
|
|
5817
|
+
result.syncParents(join12(dir, "format.json"));
|
|
4958
5818
|
result.assertWritable();
|
|
4959
5819
|
return result;
|
|
4960
5820
|
}
|
|
@@ -4989,7 +5849,7 @@ class HistoryFiles {
|
|
|
4989
5849
|
throw new ContractError("quarantine");
|
|
4990
5850
|
}
|
|
4991
5851
|
assertFormat() {
|
|
4992
|
-
this.files.assert(
|
|
5852
|
+
this.files.assert(join12(this.directory, "format.json"), this.formatSnapshot);
|
|
4993
5853
|
}
|
|
4994
5854
|
readProofMaterial(budget) {
|
|
4995
5855
|
if (this.format.schema !== FORMAT)
|
|
@@ -4998,7 +5858,7 @@ class HistoryFiles {
|
|
|
4998
5858
|
this.assertReadFormat(budget);
|
|
4999
5859
|
else
|
|
5000
5860
|
this.assertFormat();
|
|
5001
|
-
const selected2 = this.readBudgeted(
|
|
5861
|
+
const selected2 = this.readBudgeted(join12(this.directory, "read-proof-key.json"), 512, budget);
|
|
5002
5862
|
if (!selected2 || this.proofSnapshot && (selected2.identity !== this.proofSnapshot.identity || selected2.stamp !== this.proofSnapshot.stamp))
|
|
5003
5863
|
corrupt();
|
|
5004
5864
|
try {
|
|
@@ -5026,17 +5886,17 @@ class HistoryFiles {
|
|
|
5026
5886
|
return;
|
|
5027
5887
|
}
|
|
5028
5888
|
this.assertFormat();
|
|
5029
|
-
assertIncarnation(this,
|
|
5889
|
+
assertIncarnation(this, join12(this.directory, "read-proof-key.json"), this.proofSnapshot.stamp);
|
|
5030
5890
|
}
|
|
5031
5891
|
segmentPath(segment) {
|
|
5032
|
-
return
|
|
5892
|
+
return join12(this.directory, "ledger", `${String(segment).padStart(12, "0")}.jsonl`);
|
|
5033
5893
|
}
|
|
5034
5894
|
artifactPath(ref) {
|
|
5035
|
-
return
|
|
5895
|
+
return join12(this.directory, "runs", ref.runId, "artifacts", ref.artifactId, `${ref.part}.part`);
|
|
5036
5896
|
}
|
|
5037
5897
|
admissionPath(runId) {
|
|
5038
5898
|
decodeId(runId);
|
|
5039
|
-
return
|
|
5899
|
+
return join12(this.directory, "runs", runId, "admission.json");
|
|
5040
5900
|
}
|
|
5041
5901
|
writeAdmission(lease, input) {
|
|
5042
5902
|
lease.assert();
|
|
@@ -5062,7 +5922,7 @@ class HistoryFiles {
|
|
|
5062
5922
|
readCommand(commandId, budget) {
|
|
5063
5923
|
decodeId(commandId);
|
|
5064
5924
|
this.assertReadFormat(budget);
|
|
5065
|
-
return this.readBudgeted(
|
|
5925
|
+
return this.readBudgeted(join12(this.directory, "commands", commandId, "intent.json"), RECORD_BYTES, budget);
|
|
5066
5926
|
}
|
|
5067
5927
|
readAdmission(runId, identity, budget) {
|
|
5068
5928
|
return this.readAdmissionIdentity(runId, hash(canonicalJson(identity)), budget);
|
|
@@ -5083,7 +5943,7 @@ class HistoryFiles {
|
|
|
5083
5943
|
syncParents(file) {
|
|
5084
5944
|
if (process.platform === "win32")
|
|
5085
5945
|
return;
|
|
5086
|
-
for (let path2 =
|
|
5946
|
+
for (let path2 = dirname12(file);; path2 = dirname12(path2)) {
|
|
5087
5947
|
if (!this.files.directory(path2))
|
|
5088
5948
|
corrupt();
|
|
5089
5949
|
const fd = openSync5(path2, constants5.O_RDONLY | constants5.O_DIRECTORY);
|
|
@@ -5094,13 +5954,13 @@ class HistoryFiles {
|
|
|
5094
5954
|
}
|
|
5095
5955
|
if (path2 === this.coordination.directory)
|
|
5096
5956
|
break;
|
|
5097
|
-
if (path2 ===
|
|
5957
|
+
if (path2 === dirname12(path2))
|
|
5098
5958
|
corrupt();
|
|
5099
5959
|
}
|
|
5100
5960
|
}
|
|
5101
5961
|
assertReadFormat(budget) {
|
|
5102
5962
|
budget.reserve(this.formatSnapshot.bytes.length);
|
|
5103
|
-
this.files.assert(
|
|
5963
|
+
this.files.assert(join12(this.directory, "format.json"), this.formatSnapshot, this.formatSnapshot.bytes.length);
|
|
5104
5964
|
}
|
|
5105
5965
|
assertPublished(segment, end, seq) {
|
|
5106
5966
|
const fence = publicationFences.get(this.directory);
|
|
@@ -5135,10 +5995,10 @@ class HistoryFiles {
|
|
|
5135
5995
|
if (depth > 8 || !this.files.directory(path2))
|
|
5136
5996
|
corrupt();
|
|
5137
5997
|
let bytes = 0;
|
|
5138
|
-
const dir =
|
|
5998
|
+
const dir = opendirSync3(path2);
|
|
5139
5999
|
try {
|
|
5140
6000
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
5141
|
-
const p =
|
|
6001
|
+
const p = join12(path2, e.name), st = lstatSync6(p);
|
|
5142
6002
|
if (st.isDirectory())
|
|
5143
6003
|
bytes += walk(p, depth + 1);
|
|
5144
6004
|
else {
|
|
@@ -5173,10 +6033,10 @@ class HistoryFiles {
|
|
|
5173
6033
|
state.bytes = 0;
|
|
5174
6034
|
state.identity = null;
|
|
5175
6035
|
let count = 0;
|
|
5176
|
-
const ledger =
|
|
6036
|
+
const ledger = join12(this.directory, "ledger");
|
|
5177
6037
|
if (!this.files.directory(ledger))
|
|
5178
6038
|
corrupt();
|
|
5179
|
-
const dir =
|
|
6039
|
+
const dir = opendirSync3(ledger);
|
|
5180
6040
|
try {
|
|
5181
6041
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
5182
6042
|
budget?.reserve(0);
|
|
@@ -5210,7 +6070,7 @@ class HistoryFiles {
|
|
|
5210
6070
|
state.bytes = file.bytes.length;
|
|
5211
6071
|
state.identity = file.identity;
|
|
5212
6072
|
state.records = 0;
|
|
5213
|
-
let at = 0, pending = [], pendingBytes = 0, checksum =
|
|
6073
|
+
let at = 0, pending = [], pendingBytes = 0, checksum = createHash5("sha256");
|
|
5214
6074
|
while (at < file.bytes.length) {
|
|
5215
6075
|
budget?.reserve(0);
|
|
5216
6076
|
const end = file.bytes.indexOf(10, at);
|
|
@@ -5278,7 +6138,7 @@ class HistoryFiles {
|
|
|
5278
6138
|
};
|
|
5279
6139
|
pending = [];
|
|
5280
6140
|
pendingBytes = 0;
|
|
5281
|
-
checksum =
|
|
6141
|
+
checksum = createHash5("sha256");
|
|
5282
6142
|
}
|
|
5283
6143
|
if (pending.length)
|
|
5284
6144
|
state.tail = true;
|
|
@@ -5470,7 +6330,7 @@ class HistoryFiles {
|
|
|
5470
6330
|
lease.assert();
|
|
5471
6331
|
this.assertWritable();
|
|
5472
6332
|
const after = this.files.read(path2, LIMITS.segmentBytes);
|
|
5473
|
-
if (!after || after.identity !==
|
|
6333
|
+
if (!after || after.identity !== createHash5("sha256").update(before.bytes).update(body).update(trailer).digest("hex"))
|
|
5474
6334
|
corrupt();
|
|
5475
6335
|
return {
|
|
5476
6336
|
committedSeq: marker.last,
|
|
@@ -5490,7 +6350,7 @@ class HistoryFiles {
|
|
|
5490
6350
|
checkpoint(lease, state) {
|
|
5491
6351
|
lease.assert();
|
|
5492
6352
|
this.assertWritable();
|
|
5493
|
-
const path2 =
|
|
6353
|
+
const path2 = join12(this.directory, "checkpoint.json"), old = this.files.read(path2);
|
|
5494
6354
|
const body = encoded({
|
|
5495
6355
|
schema: "cadence-history-checkpoint/2",
|
|
5496
6356
|
storeId: this.format.storeId,
|
|
@@ -5898,7 +6758,7 @@ function projectRunObservation(runId, epoch, o, child) {
|
|
|
5898
6758
|
// src/history/recovery.ts
|
|
5899
6759
|
function digestHistory(files, exclude) {
|
|
5900
6760
|
files.assertFormat();
|
|
5901
|
-
const digest =
|
|
6761
|
+
const digest = createHash6("sha256");
|
|
5902
6762
|
let count = 0;
|
|
5903
6763
|
const walk = (path2, depth) => {
|
|
5904
6764
|
if (depth > 8 || !files.files.directory(path2))
|
|
@@ -5906,7 +6766,7 @@ function digestHistory(files, exclude) {
|
|
|
5906
6766
|
const directory2 = lstatSync7(path2);
|
|
5907
6767
|
digest.update(`incarnation:${directory2.dev}:${directory2.ino}
|
|
5908
6768
|
`);
|
|
5909
|
-
const dir =
|
|
6769
|
+
const dir = opendirSync4(path2), names2 = [];
|
|
5910
6770
|
try {
|
|
5911
6771
|
for (let e = dir.readSync();e; e = dir.readSync()) {
|
|
5912
6772
|
if (++count > LIMITS.dataFrames)
|
|
@@ -5917,7 +6777,7 @@ function digestHistory(files, exclude) {
|
|
|
5917
6777
|
dir.closeSync();
|
|
5918
6778
|
}
|
|
5919
6779
|
for (const name of names2.sort()) {
|
|
5920
|
-
const selected2 =
|
|
6780
|
+
const selected2 = join13(path2, name);
|
|
5921
6781
|
if (selected2 === exclude)
|
|
5922
6782
|
continue;
|
|
5923
6783
|
const rel = relative4(files.directory, selected2);
|
|
@@ -6040,7 +6900,7 @@ class HistoryRecovery {
|
|
|
6040
6900
|
const current = await inspectRecovery(this.coordination, this);
|
|
6041
6901
|
if (JSON.stringify(current) !== JSON.stringify(expected))
|
|
6042
6902
|
throw new ContractError("conflict");
|
|
6043
|
-
const state = files.inspect(), recoveryId =
|
|
6903
|
+
const state = files.inspect(), recoveryId = randomUUID7();
|
|
6044
6904
|
const bridge = {
|
|
6045
6905
|
kind: "recovery",
|
|
6046
6906
|
recoveryId,
|
|
@@ -6071,7 +6931,7 @@ class HistoryRecovery {
|
|
|
6071
6931
|
throw new ContractError("conflict");
|
|
6072
6932
|
const row = {
|
|
6073
6933
|
kind: "record",
|
|
6074
|
-
id:
|
|
6934
|
+
id: randomUUID7(),
|
|
6075
6935
|
seq: String(BigInt(bridge.previous.highSeq) + 1n),
|
|
6076
6936
|
value: bridge
|
|
6077
6937
|
};
|
|
@@ -6135,7 +6995,7 @@ async function confirm(expected) {
|
|
|
6135
6995
|
`resolve ${expected.epoch}`
|
|
6136
6996
|
]
|
|
6137
6997
|
];
|
|
6138
|
-
await new Promise((
|
|
6998
|
+
await new Promise((resolve7, reject) => {
|
|
6139
6999
|
let index = 0, line = "", done = false;
|
|
6140
7000
|
const raw = process.stdin.isRaw;
|
|
6141
7001
|
const finish = (ok) => {
|
|
@@ -6151,7 +7011,7 @@ async function confirm(expected) {
|
|
|
6151
7011
|
process.stdout.write(`\x1B[?2004l
|
|
6152
7012
|
`);
|
|
6153
7013
|
if (ok)
|
|
6154
|
-
|
|
7014
|
+
resolve7();
|
|
6155
7015
|
else
|
|
6156
7016
|
reject(new ContractError("forbidden"));
|
|
6157
7017
|
};
|
|
@@ -6196,12 +7056,12 @@ async function localRecovery(c, operation) {
|
|
|
6196
7056
|
native();
|
|
6197
7057
|
const history = new HistoryRecovery(c);
|
|
6198
7058
|
const expected = await inspectRecovery(c, history);
|
|
6199
|
-
const
|
|
7059
|
+
const registration2 = c.recoverySnapshot().registration;
|
|
6200
7060
|
const view = {
|
|
6201
7061
|
...expected,
|
|
6202
7062
|
process: {
|
|
6203
|
-
pid:
|
|
6204
|
-
host: diagnosticText(
|
|
7063
|
+
pid: registration2.pid,
|
|
7064
|
+
host: diagnosticText(registration2.host, 255)
|
|
6205
7065
|
},
|
|
6206
7066
|
notice: warning
|
|
6207
7067
|
};
|
|
@@ -6226,227 +7086,9 @@ async function localRecovery(c, operation) {
|
|
|
6226
7086
|
}, history);
|
|
6227
7087
|
}
|
|
6228
7088
|
|
|
6229
|
-
// src/service/runtime.ts
|
|
6230
|
-
import { homedir as homedir4 } from "node:os";
|
|
6231
|
-
import { dirname as dirname8, isAbsolute as isAbsolute6, join as join10, resolve as resolve4 } from "node:path";
|
|
6232
|
-
|
|
6233
|
-
// src/service/config.ts
|
|
6234
|
-
import { isIP } from "node:net";
|
|
6235
|
-
function decodeHttpPolicy(value) {
|
|
6236
|
-
const o = closed(value, ["schema", "host", "port", "proxy"]);
|
|
6237
|
-
if (o.host !== "127.0.0.1" && o.host !== "::1")
|
|
6238
|
-
throw new ContractError("invalid");
|
|
6239
|
-
let proxy = null;
|
|
6240
|
-
if (o.proxy !== null) {
|
|
6241
|
-
const p = closed(o.proxy, ["origin", "trustedAddresses"]);
|
|
6242
|
-
const origin = text(p.origin, 256, true);
|
|
6243
|
-
let url;
|
|
6244
|
-
try {
|
|
6245
|
-
url = new URL(origin);
|
|
6246
|
-
} catch {
|
|
6247
|
-
throw new ContractError("invalid");
|
|
6248
|
-
}
|
|
6249
|
-
if (url.protocol !== "https:" || url.origin !== origin || url.username || url.password)
|
|
6250
|
-
throw new ContractError("invalid");
|
|
6251
|
-
const trustedAddresses = list(p.trustedAddresses, 8, (v) => {
|
|
6252
|
-
const address = text(v, 64, true);
|
|
6253
|
-
if (!isIP(address) || !loopbackAddress(address))
|
|
6254
|
-
throw new ContractError("invalid");
|
|
6255
|
-
return address;
|
|
6256
|
-
});
|
|
6257
|
-
if (!trustedAddresses.length || new Set(trustedAddresses).size !== trustedAddresses.length)
|
|
6258
|
-
throw new ContractError("invalid");
|
|
6259
|
-
proxy = { origin, trustedAddresses };
|
|
6260
|
-
}
|
|
6261
|
-
return {
|
|
6262
|
-
schema: literal(o.schema, "cadence-service-http/1"),
|
|
6263
|
-
host: o.host,
|
|
6264
|
-
port: integer(o.port, 0, 65535),
|
|
6265
|
-
proxy
|
|
6266
|
-
};
|
|
6267
|
-
}
|
|
6268
|
-
function loopbackAddress(host) {
|
|
6269
|
-
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
|
|
6270
|
-
const h = mapped ? mapped[1] : host;
|
|
6271
|
-
if (h.includes(":"))
|
|
6272
|
-
return h === "::1";
|
|
6273
|
-
const parts = h.split(".");
|
|
6274
|
-
return parts.length === 4 && parts.every((p) => /^\d{1,3}$/.test(p) && Number(p) <= 255) && Number(parts[0]) === 127;
|
|
6275
|
-
}
|
|
6276
|
-
function localOrigin(policy, actualPort) {
|
|
6277
|
-
integer(actualPort, 1, 65535);
|
|
6278
|
-
return `http://${policy.host === "::1" ? "[::1]" : policy.host}:${actualPort}`;
|
|
6279
|
-
}
|
|
6280
|
-
|
|
6281
|
-
// src/service/runtime.ts
|
|
6282
|
-
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
6283
|
-
function absolute(value) {
|
|
6284
|
-
const result = text(value, 4096, true);
|
|
6285
|
-
if (!isAbsolute6(result))
|
|
6286
|
-
throw new ContractError("invalid");
|
|
6287
|
-
return result;
|
|
6288
|
-
}
|
|
6289
|
-
function decodeHostConfig(value) {
|
|
6290
|
-
const o = closed(value, [
|
|
6291
|
-
"schema",
|
|
6292
|
-
"http",
|
|
6293
|
-
"cwd",
|
|
6294
|
-
"agentDir",
|
|
6295
|
-
"tokenSource",
|
|
6296
|
-
"tokenFile"
|
|
6297
|
-
]);
|
|
6298
|
-
const tokenSource = oneOf(o.tokenSource, [
|
|
6299
|
-
"generated",
|
|
6300
|
-
"environment",
|
|
6301
|
-
"file",
|
|
6302
|
-
"config"
|
|
6303
|
-
]);
|
|
6304
|
-
const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
|
|
6305
|
-
if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
|
|
6306
|
-
throw new ContractError("invalid");
|
|
6307
|
-
return {
|
|
6308
|
-
schema: literal(o.schema, "cadence-service-host/1"),
|
|
6309
|
-
http: decodeHttpPolicy(o.http),
|
|
6310
|
-
cwd: absolute(o.cwd),
|
|
6311
|
-
agentDir: absolute(o.agentDir),
|
|
6312
|
-
tokenSource,
|
|
6313
|
-
tokenFile
|
|
6314
|
-
};
|
|
6315
|
-
}
|
|
6316
|
-
function readHostConfig(c) {
|
|
6317
|
-
const owner = c.configuration();
|
|
6318
|
-
if (!owner)
|
|
6319
|
-
return;
|
|
6320
|
-
const snapshot = c.files.read(c.path("host-config.json"));
|
|
6321
|
-
if (!snapshot)
|
|
6322
|
-
throw new ContractError("corrupt-selected");
|
|
6323
|
-
const config = decodeHostConfig(json(snapshot));
|
|
6324
|
-
if (!config.http.port || owner.endpoint !== localOrigin(config.http, config.http.port) || owner.policyIdentity !== hash(encoded(config)))
|
|
6325
|
-
throw new ContractError("conflict");
|
|
6326
|
-
return config;
|
|
6327
|
-
}
|
|
6328
|
-
function hostEnvironment(ambient) {
|
|
6329
|
-
let route;
|
|
6330
|
-
const facts = networkSnapshot(ambient);
|
|
6331
|
-
try {
|
|
6332
|
-
if (facts.NODE_OPTIONS.present)
|
|
6333
|
-
throw new Error;
|
|
6334
|
-
route = sdkNetworkEnvironment(facts, networkSnapshot(undefined));
|
|
6335
|
-
} catch {
|
|
6336
|
-
throw new ContractError("unsupported-context");
|
|
6337
|
-
}
|
|
6338
|
-
const result = Object.create(null);
|
|
6339
|
-
for (const key of Object.keys(ambient)) {
|
|
6340
|
-
const d = Object.getOwnPropertyDescriptor(ambient, key);
|
|
6341
|
-
if (!d || !("value" in d) || typeof d.value !== "string")
|
|
6342
|
-
throw new ContractError("invalid");
|
|
6343
|
-
if (/^(?:CADENCE_|PI_|ABEL_|TRELLIS_|GIT_)/i.test(key) || networkKeys.some((name) => name === key))
|
|
6344
|
-
continue;
|
|
6345
|
-
result[key] = d.value;
|
|
6346
|
-
}
|
|
6347
|
-
if (route.redactionLiterals.length) {
|
|
6348
|
-
result.HTTP_PROXY = route.env.HTTP_PROXY;
|
|
6349
|
-
result.HTTPS_PROXY = route.env.HTTPS_PROXY;
|
|
6350
|
-
result.NODE_USE_ENV_PROXY = "1";
|
|
6351
|
-
}
|
|
6352
|
-
result.PI_OFFLINE = "1";
|
|
6353
|
-
result.NO_COLOR = "1";
|
|
6354
|
-
if (!sameNetwork(facts, networkSnapshot(ambient)))
|
|
6355
|
-
throw new ContractError("stale");
|
|
6356
|
-
return result;
|
|
6357
|
-
}
|
|
6358
|
-
function selectStart(options, ambient) {
|
|
6359
|
-
const workspace = resolveWorkspace(options.workspace), c = new ServiceCoordination(workspace.root, options.stateHome);
|
|
6360
|
-
const old = readHostConfig(c);
|
|
6361
|
-
const env = Object.getOwnPropertyDescriptor(ambient, "CADENCE_WEB_TOKEN");
|
|
6362
|
-
if (env && (!("value" in env) || typeof env.value !== "string"))
|
|
6363
|
-
throw new ContractError("invalid");
|
|
6364
|
-
let fromFile;
|
|
6365
|
-
if (options.config) {
|
|
6366
|
-
const path2 = resolve4(options.config), f = new PrivateFiles(dirname8(path2));
|
|
6367
|
-
const bytes = f.read(path2);
|
|
6368
|
-
if (!bytes)
|
|
6369
|
-
throw new ContractError("corrupt-selected");
|
|
6370
|
-
fromFile = decodeHostConfig(json(bytes));
|
|
6371
|
-
}
|
|
6372
|
-
const base = old ?? fromFile ?? decodeHostConfig({
|
|
6373
|
-
schema: "cadence-service-host/1",
|
|
6374
|
-
cwd: workspace.cwd,
|
|
6375
|
-
http: {
|
|
6376
|
-
schema: "cadence-service-http/1",
|
|
6377
|
-
host: options.host ?? "127.0.0.1",
|
|
6378
|
-
port: options.port ?? 17423,
|
|
6379
|
-
proxy: null
|
|
6380
|
-
},
|
|
6381
|
-
agentDir: resolve4(options.agentDir ?? ambient.PI_CODING_AGENT_DIR ?? join10(ambient.HOME ?? ambient.USERPROFILE ?? homedir4(), ".pi", "agent")),
|
|
6382
|
-
tokenSource: options.tokenFile ? "file" : env ? "environment" : "generated",
|
|
6383
|
-
tokenFile: options.tokenFile ? resolve4(options.tokenFile) : null
|
|
6384
|
-
});
|
|
6385
|
-
const server = options.serverConfig !== undefined ? resolve4(options.serverConfig) : old?.tokenSource === "config" ? old.tokenFile : null;
|
|
6386
|
-
let selected2 = base;
|
|
6387
|
-
if (server) {
|
|
6388
|
-
if (options.tokenFile !== undefined)
|
|
6389
|
-
throw new ContractError("conflict");
|
|
6390
|
-
const { port } = readServerConfig(server).config;
|
|
6391
|
-
selected2 = decodeHostConfig({
|
|
6392
|
-
...base,
|
|
6393
|
-
http: { ...base.http, port },
|
|
6394
|
-
tokenSource: "config",
|
|
6395
|
-
tokenFile: server
|
|
6396
|
-
});
|
|
6397
|
-
}
|
|
6398
|
-
if (fromFile && encoded(fromFile) !== encoded(selected2) || options.port !== undefined && options.port !== selected2.http.port || options.host !== undefined && options.host !== selected2.http.host || options.agentDir !== undefined && resolve4(options.agentDir) !== selected2.agentDir || options.tokenFile !== undefined && resolve4(options.tokenFile) !== selected2.tokenFile || env && selected2.tokenSource !== "environment" || selected2.tokenSource === "environment" && !env || resolveWorkspace(selected2.cwd).root !== workspace.root)
|
|
6399
|
-
throw new ContractError("conflict");
|
|
6400
|
-
return {
|
|
6401
|
-
workspace: workspace.root,
|
|
6402
|
-
stateHome: c.stateHome,
|
|
6403
|
-
config: selected2,
|
|
6404
|
-
environmentToken: env?.value ?? null
|
|
6405
|
-
};
|
|
6406
|
-
}
|
|
6407
|
-
function decodeHostInput(value) {
|
|
6408
|
-
const o = closed(value, [
|
|
6409
|
-
"workspace",
|
|
6410
|
-
"stateHome",
|
|
6411
|
-
"config",
|
|
6412
|
-
"environmentToken"
|
|
6413
|
-
]);
|
|
6414
|
-
const config = decodeHostConfig(o.config);
|
|
6415
|
-
const environmentToken = o.environmentToken === null ? null : text(o.environmentToken, 1024, true);
|
|
6416
|
-
if (config.tokenSource === "environment" !== (environmentToken !== null))
|
|
6417
|
-
throw new ContractError("invalid");
|
|
6418
|
-
return {
|
|
6419
|
-
workspace: absolute(o.workspace),
|
|
6420
|
-
stateHome: absolute(o.stateHome),
|
|
6421
|
-
config,
|
|
6422
|
-
environmentToken
|
|
6423
|
-
};
|
|
6424
|
-
}
|
|
6425
|
-
function portValue(value) {
|
|
6426
|
-
if (!/^(0|[1-9][0-9]{0,4})$/.test(value))
|
|
6427
|
-
throw new ContractError("invalid");
|
|
6428
|
-
return integer(Number(value), 0, 65535);
|
|
6429
|
-
}
|
|
6430
|
-
var STARTUP_STAGES = [
|
|
6431
|
-
"package",
|
|
6432
|
-
"pi-agent-directory",
|
|
6433
|
-
"pi-auth",
|
|
6434
|
-
"pi-models",
|
|
6435
|
-
"pi-runtime",
|
|
6436
|
-
"service"
|
|
6437
|
-
];
|
|
6438
|
-
function decodeStartupReport(value) {
|
|
6439
|
-
const o = closed(value, ["status", "error", "stage"]);
|
|
6440
|
-
return {
|
|
6441
|
-
status: oneOf(o.status, ["refused", "quarantined"]),
|
|
6442
|
-
error: oneOf(o.error, ERROR_CODES),
|
|
6443
|
-
stage: oneOf(o.stage, STARTUP_STAGES)
|
|
6444
|
-
};
|
|
6445
|
-
}
|
|
6446
|
-
|
|
6447
7089
|
// src/service/system-service.ts
|
|
6448
|
-
import { existsSync as existsSync2,
|
|
6449
|
-
import { isAbsolute as
|
|
7090
|
+
import { existsSync as existsSync2, realpathSync as realpathSync5, rmSync as rmSync4 } from "node:fs";
|
|
7091
|
+
import { isAbsolute as isAbsolute8, join as join14 } from "node:path";
|
|
6450
7092
|
var MARKER4 = "# Generated by @abelxiaoxing/cadence";
|
|
6451
7093
|
function boundary(s) {
|
|
6452
7094
|
if (s.platform !== "linux" || s.scope !== "system")
|
|
@@ -6460,7 +7102,7 @@ function legacyPath(s) {
|
|
|
6460
7102
|
function observed(s) {
|
|
6461
7103
|
const path2 = unitPath(s);
|
|
6462
7104
|
let previous = readGenerated(path2, MARKER4);
|
|
6463
|
-
if (typeof previous === "string" && (!
|
|
7105
|
+
if (typeof previous === "string" && (unitWorkspace(previous) !== null && !isAbsolute8(unitWorkspace(previous) ?? "") || !previous.includes(`
|
|
6464
7106
|
User=root
|
|
6465
7107
|
`)))
|
|
6466
7108
|
previous = null;
|
|
@@ -6489,13 +7131,13 @@ function sameStateEnvironment(s, previous) {
|
|
|
6489
7131
|
values.set(key, value.slice(at + 1).replaceAll("%%", "%"));
|
|
6490
7132
|
}
|
|
6491
7133
|
const state = s.env.XDG_STATE_HOME;
|
|
6492
|
-
return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state &&
|
|
7134
|
+
return values.get("HOME") === s.home && values.get("XDG_STATE_HOME") === (state && isAbsolute8(state) ? state : undefined);
|
|
6493
7135
|
} catch {
|
|
6494
7136
|
return false;
|
|
6495
7137
|
}
|
|
6496
7138
|
}
|
|
6497
7139
|
async function ownerBoundary(s, workspace, managed) {
|
|
6498
|
-
const status = await s.ownerStatus(workspace);
|
|
7140
|
+
const status = await s.ownerStatus(workspace, serviceConfigPath(s));
|
|
6499
7141
|
if (managed) {
|
|
6500
7142
|
if (status === "absent")
|
|
6501
7143
|
return { status: "unavailable", workspace };
|
|
@@ -6522,21 +7164,21 @@ async function configureSystemService(s, mode, requested) {
|
|
|
6522
7164
|
if (mode === "restart" && previous === undefined)
|
|
6523
7165
|
return { status: "absent", scope: "system" };
|
|
6524
7166
|
const sdk = sdkPackages(s.root);
|
|
6525
|
-
if (!
|
|
6526
|
-
return { status: "missing-pi-sdk", scope: "system" };
|
|
6527
|
-
if (previous && !existsSync2(unitWorkspace(previous)))
|
|
7167
|
+
if (previous && unitWorkspace(previous) && !existsSync2(unitWorkspace(previous)))
|
|
6528
7168
|
return { status: "workspace-changed", ...result };
|
|
6529
|
-
const selected2 = selectWorkspace(s, requested, unitWorkspace(previous), false);
|
|
6530
|
-
|
|
7169
|
+
const selected2 = selectWorkspace(s, requested, mode === "install" && requested === undefined ? null : unitWorkspace(previous), false);
|
|
7170
|
+
if (selected2.workspace !== null && !sdk)
|
|
7171
|
+
return { status: "missing-pi-sdk", scope: "system" };
|
|
7172
|
+
const selectedReal = selected2.workspace && existsSync2(selected2.workspace) ? realpathSync5(selected2.workspace) : undefined;
|
|
6531
7173
|
const agentDir = agentDirectory(s);
|
|
6532
7174
|
const content = buildUnit({
|
|
6533
7175
|
system: true,
|
|
6534
7176
|
node: s.node,
|
|
6535
|
-
cli:
|
|
7177
|
+
cli: join14(s.root, "src", "service-cli.mjs"),
|
|
6536
7178
|
workspace: selected2.workspace,
|
|
6537
7179
|
serverConfig: serviceConfigPath(s),
|
|
6538
7180
|
agentDir,
|
|
6539
|
-
sdk,
|
|
7181
|
+
sdk: sdk ?? [],
|
|
6540
7182
|
environment: serviceEnvironment(s)
|
|
6541
7183
|
});
|
|
6542
7184
|
if (previous && !sameStateEnvironment(s, previous))
|
|
@@ -6546,13 +7188,13 @@ async function configureSystemService(s, mode, requested) {
|
|
|
6546
7188
|
return { status: "unavailable", ...result };
|
|
6547
7189
|
if (active.status === 0 && previous === undefined)
|
|
6548
7190
|
return { status: "foreign", ...result };
|
|
6549
|
-
if (selectedReal && (previous === undefined || selected2.workspace !== unitWorkspace(previous))) {
|
|
7191
|
+
if ((selected2.workspace === null || selectedReal) && (previous === undefined || selected2.workspace !== unitWorkspace(previous))) {
|
|
6550
7192
|
const target2 = await ownerBoundary(s, selected2.workspace, false);
|
|
6551
7193
|
if (target2)
|
|
6552
7194
|
return { ...selected2, ...target2, ...result };
|
|
6553
7195
|
}
|
|
6554
7196
|
const workspace = unitWorkspace(previous);
|
|
6555
|
-
if (workspace) {
|
|
7197
|
+
if (workspace !== undefined) {
|
|
6556
7198
|
const refused3 = await ownerBoundary(s, workspace, active.status === 0);
|
|
6557
7199
|
if (refused3)
|
|
6558
7200
|
return { ...refused3, ...result };
|
|
@@ -6562,7 +7204,7 @@ async function configureSystemService(s, mode, requested) {
|
|
|
6562
7204
|
return { ...unsettled, ...result };
|
|
6563
7205
|
if (readGenerated(path2, MARKER4) !== previous)
|
|
6564
7206
|
return { status: "foreign", ...result };
|
|
6565
|
-
if (selectedReal) {
|
|
7207
|
+
if (selectedReal && selected2.workspace) {
|
|
6566
7208
|
try {
|
|
6567
7209
|
if (realpathSync5(selected2.workspace) !== selectedReal)
|
|
6568
7210
|
return { status: "workspace-changed", ...result };
|
|
@@ -6576,13 +7218,12 @@ async function configureSystemService(s, mode, requested) {
|
|
|
6576
7218
|
workspace: unitWorkspace(previous),
|
|
6577
7219
|
...result
|
|
6578
7220
|
};
|
|
6579
|
-
mkdirSync6(selected2.workspace, { recursive: true });
|
|
6580
7221
|
write(path2, content);
|
|
6581
7222
|
if (s.systemctl(["daemon-reload"]).status !== 0)
|
|
6582
7223
|
return { status: "failed", ...result, ...selected2 };
|
|
6583
7224
|
if (mode === "install" && s.systemctl(["enable", s.unit]).status !== 0)
|
|
6584
7225
|
return { status: "failed", ...result, ...selected2 };
|
|
6585
|
-
if (!regularFile(
|
|
7226
|
+
if (selected2.workspace !== null && !regularFile(join14(agentDir, "auth.json")))
|
|
6586
7227
|
return { status: "waiting-for-pi-login", ...result, ...selected2 };
|
|
6587
7228
|
s.systemctl(["reset-failed", s.unit]);
|
|
6588
7229
|
return {
|
|
@@ -6651,12 +7292,12 @@ function systemServiceStatus(s) {
|
|
|
6651
7292
|
}
|
|
6652
7293
|
|
|
6653
7294
|
// src/service/cli.ts
|
|
6654
|
-
function nativeCall(c, operation, reveal = false) {
|
|
7295
|
+
function nativeCall(c, operation, reveal = false, controlBody) {
|
|
6655
7296
|
const access = c.localAccess();
|
|
6656
7297
|
const url = new URL(`${access.endpoint}/local/v1/admin/${operation}`);
|
|
6657
7298
|
const write2 = !["status", "diagnose"].includes(operation);
|
|
6658
|
-
const body = write2 ? JSON.stringify(reveal ? { reveal: true } : {}) : undefined;
|
|
6659
|
-
return new Promise((
|
|
7299
|
+
const body = write2 ? JSON.stringify(controlBody ?? (reveal ? { reveal: true } : {})) : undefined;
|
|
7300
|
+
return new Promise((resolve8, reject) => {
|
|
6660
7301
|
let timer;
|
|
6661
7302
|
const fail = () => {
|
|
6662
7303
|
clearTimeout(timer);
|
|
@@ -6697,7 +7338,7 @@ function nativeCall(c, operation, reveal = false) {
|
|
|
6697
7338
|
throw new ContractError("stale");
|
|
6698
7339
|
}
|
|
6699
7340
|
clearTimeout(timer);
|
|
6700
|
-
|
|
7341
|
+
resolve8(reply.value);
|
|
6701
7342
|
} catch {
|
|
6702
7343
|
fail();
|
|
6703
7344
|
}
|
|
@@ -6712,7 +7353,7 @@ function nativeCall(c, operation, reveal = false) {
|
|
|
6712
7353
|
req.end(body);
|
|
6713
7354
|
});
|
|
6714
7355
|
}
|
|
6715
|
-
var pause = (ms) => new Promise((
|
|
7356
|
+
var pause = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
6716
7357
|
async function serviceStatus(c, diagnose = false) {
|
|
6717
7358
|
const observed2 = c.inspect();
|
|
6718
7359
|
if (observed2.status === "absent" || observed2.status === "stopped")
|
|
@@ -6774,10 +7415,10 @@ function startupGuidance(result, input) {
|
|
|
6774
7415
|
return `Pi's agent directory ${agent} must be an existing real directory (not a symlink) owned by you.
|
|
6775
7416
|
`;
|
|
6776
7417
|
case "pi-auth":
|
|
6777
|
-
return `${
|
|
7418
|
+
return `${join15(agent, "auth.json")} must be your own single-link regular file holding valid Pi credentials; log in to Pi first.
|
|
6778
7419
|
`;
|
|
6779
7420
|
case "pi-models":
|
|
6780
|
-
return `${
|
|
7421
|
+
return `${join15(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.
|
|
6781
7422
|
`;
|
|
6782
7423
|
case "pi-runtime":
|
|
6783
7424
|
return `Loading Pi's SDK model runtime failed after the owner registered (optional SDK packages missing, or sources the SDK refuses); it stays quarantined.
|
|
@@ -6810,31 +7451,38 @@ async function startService(options, foreground, hostUrl) {
|
|
|
6810
7451
|
throw new ContractError("conflict");
|
|
6811
7452
|
return existing;
|
|
6812
7453
|
}
|
|
7454
|
+
return launchHost(input, c, foreground, hostUrl, input.config.cwd, env, network);
|
|
7455
|
+
}
|
|
7456
|
+
async function launchHost(input, c, foreground, hostUrl, cwd, env, network) {
|
|
7457
|
+
const serialized = `${JSON.stringify(input)}
|
|
7458
|
+
`;
|
|
7459
|
+
if (Buffer.byteLength(serialized) > STARTUP_INPUT_BYTES)
|
|
7460
|
+
throw new ContractError("oversize");
|
|
6813
7461
|
if (!sameNetwork(network, networkSnapshot(process.env)))
|
|
6814
7462
|
throw new ContractError("stale");
|
|
6815
7463
|
const child = spawn(process.execPath, [
|
|
6816
7464
|
...env.NODE_USE_ENV_PROXY === "1" ? ["--use-env-proxy"] : [],
|
|
6817
|
-
|
|
7465
|
+
fileURLToPath2(hostUrl)
|
|
6818
7466
|
], {
|
|
6819
|
-
cwd
|
|
7467
|
+
cwd,
|
|
6820
7468
|
env,
|
|
6821
7469
|
detached: !foreground,
|
|
6822
7470
|
windowsHide: true,
|
|
6823
7471
|
stdio: ["pipe", "pipe", "ignore"]
|
|
6824
7472
|
});
|
|
6825
7473
|
let completed = false;
|
|
6826
|
-
const closure = new Promise((
|
|
7474
|
+
const closure = new Promise((resolve8) => child.once("close", (code2, signal) => {
|
|
6827
7475
|
completed = true;
|
|
6828
7476
|
if (foreground && (code2 !== 0 || signal !== null))
|
|
6829
7477
|
process.exitCode = 1;
|
|
6830
|
-
|
|
7478
|
+
resolve8();
|
|
6831
7479
|
}));
|
|
6832
|
-
const startup = new Promise((
|
|
7480
|
+
const startup = new Promise((resolve8) => {
|
|
6833
7481
|
let bytes = "", resolved = false;
|
|
6834
7482
|
const finish = (value) => {
|
|
6835
7483
|
if (!resolved) {
|
|
6836
7484
|
resolved = true;
|
|
6837
|
-
|
|
7485
|
+
resolve8(value);
|
|
6838
7486
|
}
|
|
6839
7487
|
};
|
|
6840
7488
|
child.on("error", () => finish({ status: "unconfirmed" }));
|
|
@@ -6874,12 +7522,12 @@ async function startService(options, foreground, hostUrl) {
|
|
|
6874
7522
|
let timer;
|
|
6875
7523
|
const result = await Promise.race([
|
|
6876
7524
|
startup,
|
|
6877
|
-
new Promise((
|
|
6878
|
-
timer = setTimeout(() =>
|
|
7525
|
+
new Promise((resolve8) => {
|
|
7526
|
+
timer = setTimeout(() => resolve8({ status: "unconfirmed" }), LIMITS.startWaitMs);
|
|
6879
7527
|
})
|
|
6880
7528
|
]);
|
|
6881
7529
|
clearTimeout(timer);
|
|
6882
|
-
const guidance = startupGuidance(result, input);
|
|
7530
|
+
const guidance = "kind" in input ? "" : startupGuidance(result, input);
|
|
6883
7531
|
if (guidance)
|
|
6884
7532
|
process.stderr.write(guidance);
|
|
6885
7533
|
child.stdout.destroy();
|
|
@@ -6896,12 +7544,12 @@ async function startService(options, foreground, hostUrl) {
|
|
|
6896
7544
|
}
|
|
6897
7545
|
return result;
|
|
6898
7546
|
}
|
|
6899
|
-
async function observeStarted(workspace) {
|
|
7547
|
+
async function observeStarted(workspace, config) {
|
|
6900
7548
|
const deadline = performance.now() + LIMITS.startWaitMs + 2000;
|
|
6901
7549
|
let service = "unconfirmed";
|
|
6902
7550
|
while (performance.now() < deadline) {
|
|
6903
7551
|
try {
|
|
6904
|
-
const c = new ServiceCoordination(workspace);
|
|
7552
|
+
const c = workspace === null ? new GatewayStore(config ?? serverConfigPath(homedir6())) : new ServiceCoordination(workspace);
|
|
6905
7553
|
const observed2 = await serviceStatus(c);
|
|
6906
7554
|
if (observed2 && typeof observed2 === "object" && "status" in observed2)
|
|
6907
7555
|
service = String(observed2.status);
|
|
@@ -6914,26 +7562,26 @@ async function observeStarted(workspace) {
|
|
|
6914
7562
|
}
|
|
6915
7563
|
return { service };
|
|
6916
7564
|
}
|
|
6917
|
-
async function privateLink(workspace, stateHome) {
|
|
7565
|
+
async function privateLink(workspace, stateHome, config) {
|
|
6918
7566
|
try {
|
|
6919
|
-
const value = closed(await nativeCall(new ServiceCoordination(workspace, stateHome), "token-link", true), ["link"]);
|
|
7567
|
+
const value = closed(await nativeCall(workspace === null ? new GatewayStore(config ?? serverConfigPath(homedir6()), stateHome) : new ServiceCoordination(workspace, stateHome), "token-link", true), ["link"]);
|
|
6920
7568
|
return text(value.link, 4096, true);
|
|
6921
7569
|
} catch {
|
|
6922
7570
|
return;
|
|
6923
7571
|
}
|
|
6924
7572
|
}
|
|
6925
7573
|
var ownerControl = {
|
|
6926
|
-
async ownerStatus(workspace) {
|
|
7574
|
+
async ownerStatus(workspace, config) {
|
|
6927
7575
|
try {
|
|
6928
|
-
const observed2 = await serviceStatus(new ServiceCoordination(workspace));
|
|
7576
|
+
const observed2 = await serviceStatus(workspace === null ? new GatewayStore(config ?? serverConfigPath(homedir6())) : new ServiceCoordination(workspace));
|
|
6929
7577
|
return observed2 && typeof observed2 === "object" && "status" in observed2 ? String(observed2.status) : "unavailable";
|
|
6930
7578
|
} catch {
|
|
6931
7579
|
return "unavailable";
|
|
6932
7580
|
}
|
|
6933
7581
|
},
|
|
6934
|
-
async stopOwner(workspace) {
|
|
7582
|
+
async stopOwner(workspace, config) {
|
|
6935
7583
|
try {
|
|
6936
|
-
const c = new ServiceCoordination(workspace);
|
|
7584
|
+
const c = workspace === null ? new GatewayStore(config ?? serverConfigPath(homedir6())) : new ServiceCoordination(workspace);
|
|
6937
7585
|
const observed2 = await serviceStatus(c);
|
|
6938
7586
|
const status = observed2 && typeof observed2 === "object" && "status" in observed2 ? String(observed2.status) : "unavailable";
|
|
6939
7587
|
if (status === "absent" || status === "stopped")
|
|
@@ -6978,7 +7626,7 @@ async function serverAction(s, action, requested, linger) {
|
|
|
6978
7626
|
}
|
|
6979
7627
|
}
|
|
6980
7628
|
function serverGuidance(s, action, result, link) {
|
|
6981
|
-
const cli = piUserInstall(s) ? `node ${JSON.stringify(
|
|
7629
|
+
const cli = piUserInstall(s) ? `node ${JSON.stringify(join15(s.root, "src", "service-cli.mjs"))}` : "cadence";
|
|
6982
7630
|
const note = result.redirectedFrom ? `Cadence workspace: ${result.workspace} (${result.redirectedFrom} contains Cadence's private state)
|
|
6983
7631
|
` : "";
|
|
6984
7632
|
return note + serverStatusGuidance(s, action, result, cli, link);
|
|
@@ -7010,6 +7658,11 @@ function managerDetail(s) {
|
|
|
7010
7658
|
}
|
|
7011
7659
|
function serverStatusGuidance(s, action, result, cli, link) {
|
|
7012
7660
|
const workspace = JSON.stringify(result.workspace ?? "");
|
|
7661
|
+
if (result.workspace === null && result.status === "started")
|
|
7662
|
+
return `Cadence unified workbench: ${link ?? result.endpoint ?? result.service ?? "unconfirmed"}
|
|
7663
|
+
No default workspace was created. Projects are opened explicitly.
|
|
7664
|
+
Manage: ${cli} server status|restart|stop|uninstall
|
|
7665
|
+
`;
|
|
7013
7666
|
const flags = `${s.scope === "system" ? " --system" : s.platform === "linux" ? " --user" : ""}${serviceName(s) ? ` --name ${serviceName(s)}` : ""}`;
|
|
7014
7667
|
const manage = `Manage: ${cli} server status|restart|stop|uninstall${flags}`;
|
|
7015
7668
|
const when = s.scope === "system" ? "at boot, as root (no linger)" : s.platform === "linux" ? "at boot or login" : "at login";
|
|
@@ -7035,7 +7688,7 @@ ${manage}
|
|
|
7035
7688
|
${linger}`;
|
|
7036
7689
|
}
|
|
7037
7690
|
case "waiting-for-pi-login":
|
|
7038
|
-
return `Cadence is registered for ${result.workspace}; it starts once Pi has ${
|
|
7691
|
+
return `Cadence is registered for ${result.workspace}; it starts once Pi has ${join15(agentDirectory(s), "auth.json")}.
|
|
7039
7692
|
Log in to Pi, then: ${cli} server restart${flags}
|
|
7040
7693
|
${config}
|
|
7041
7694
|
${lingerGuidance(result.linger)}`;
|
|
@@ -7131,7 +7784,7 @@ async function serverMain(argv, hostUrl) {
|
|
|
7131
7784
|
else if (flag === "--name" && argv[0])
|
|
7132
7785
|
name = argv.shift();
|
|
7133
7786
|
else if (flag === "--workspace" && action === "install" && argv[0])
|
|
7134
|
-
requested =
|
|
7787
|
+
requested = resolve7(argv.shift());
|
|
7135
7788
|
else if (flag === "--port" && action === "install" && argv[0]) {
|
|
7136
7789
|
port = portValue(argv.shift());
|
|
7137
7790
|
if (!port)
|
|
@@ -7139,7 +7792,7 @@ async function serverMain(argv, hostUrl) {
|
|
|
7139
7792
|
} else
|
|
7140
7793
|
throw new ContractError("invalid");
|
|
7141
7794
|
}
|
|
7142
|
-
const s = hostSystem(
|
|
7795
|
+
const s = hostSystem(dirname13(dirname13(fileURLToPath2(hostUrl))), ownerControl, {
|
|
7143
7796
|
scope,
|
|
7144
7797
|
name
|
|
7145
7798
|
});
|
|
@@ -7184,10 +7837,10 @@ async function serverMain(argv, hostUrl) {
|
|
|
7184
7837
|
throw error;
|
|
7185
7838
|
}
|
|
7186
7839
|
let link;
|
|
7187
|
-
if (result.status === "started" && result.workspace) {
|
|
7188
|
-
Object.assign(result, await observeStarted(result.workspace));
|
|
7840
|
+
if (result.status === "started" && result.workspace !== undefined) {
|
|
7841
|
+
Object.assign(result, await observeStarted(result.workspace, serviceConfigPath(s)));
|
|
7189
7842
|
if (result.service === "alive")
|
|
7190
|
-
link = await privateLink(result.workspace);
|
|
7843
|
+
link = await privateLink(result.workspace, undefined, serviceConfigPath(s));
|
|
7191
7844
|
}
|
|
7192
7845
|
process.stderr.write(serverGuidance(s, action, result, link));
|
|
7193
7846
|
process.stdout.write(`${JSON.stringify(result)}
|
|
@@ -7217,14 +7870,14 @@ async function projectMain(argv, hostUrl) {
|
|
|
7217
7870
|
while (argv.length) {
|
|
7218
7871
|
const flag = argv.shift();
|
|
7219
7872
|
if (!flag.startsWith("--") && !selected2) {
|
|
7220
|
-
options.workspace =
|
|
7873
|
+
options.workspace = resolve7(flag);
|
|
7221
7874
|
selected2 = true;
|
|
7222
7875
|
} else if (!seen.has(flag) && flag === "--create") {
|
|
7223
7876
|
seen.add(flag);
|
|
7224
7877
|
create = true;
|
|
7225
7878
|
} else if (!seen.has(flag) && (flag === "--state-home" || flag === "--agent-dir") && argv[0]) {
|
|
7226
7879
|
seen.add(flag);
|
|
7227
|
-
const value =
|
|
7880
|
+
const value = resolve7(argv.shift());
|
|
7228
7881
|
if (flag === "--state-home")
|
|
7229
7882
|
options.stateHome = value;
|
|
7230
7883
|
else
|
|
@@ -7235,14 +7888,8 @@ async function projectMain(argv, hostUrl) {
|
|
|
7235
7888
|
if (privateWorkspace(options.workspace, options.stateHome))
|
|
7236
7889
|
throw new ContractError("forbidden");
|
|
7237
7890
|
if (create)
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
if (landing.redirected) {
|
|
7241
|
-
process.stderr.write(`Cadence workspace: ${landing.workspace} (${options.workspace} contains Cadence's private state)
|
|
7242
|
-
`);
|
|
7243
|
-
mkdirSync7(landing.workspace, { recursive: true });
|
|
7244
|
-
}
|
|
7245
|
-
options.workspace = landing.workspace;
|
|
7891
|
+
mkdirSync6(options.workspace, { recursive: true });
|
|
7892
|
+
effectiveWorkspace(options.workspace, options.stateHome);
|
|
7246
7893
|
const c = new ServiceCoordination(options.workspace, options.stateHome);
|
|
7247
7894
|
const kept = readHostConfig(c);
|
|
7248
7895
|
if (!kept)
|
|
@@ -7270,8 +7917,25 @@ async function managementMain(argv, hostUrl) {
|
|
|
7270
7917
|
if (!process.versions.node || process.versions.bun)
|
|
7271
7918
|
throw new ContractError("unsupported-context");
|
|
7272
7919
|
const command = argv.shift();
|
|
7920
|
+
if (command === "workbench") {
|
|
7921
|
+
await gatewayMain(argv, hostUrl);
|
|
7922
|
+
return;
|
|
7923
|
+
}
|
|
7924
|
+
if ([
|
|
7925
|
+
"start",
|
|
7926
|
+
"status",
|
|
7927
|
+
"diagnose",
|
|
7928
|
+
"stop",
|
|
7929
|
+
"token-link",
|
|
7930
|
+
"token-rotate"
|
|
7931
|
+
].includes(command ?? "") && !argv.includes("--workspace")) {
|
|
7932
|
+
await gatewayMain([command, ...argv], hostUrl);
|
|
7933
|
+
return;
|
|
7934
|
+
}
|
|
7273
7935
|
if (command === "--help" && !argv.length) {
|
|
7274
7936
|
process.stdout.write(`cadence start|status|diagnose|stop|token-link --reveal|token-rotate|recover inspect|recover resolve [--workspace PATH] [--state-home PATH]
|
|
7937
|
+
Without --workspace these commands address the unified workbench (except project recovery); no default project directory is created.
|
|
7938
|
+
cadence workbench start|status|diagnose|stop|token-link --reveal|token-rotate [--server-config PATH] [--state-home PATH]
|
|
7275
7939
|
cadence server install [--workspace PATH] [--no-linger]|status|restart|stop|uninstall
|
|
7276
7940
|
Linux server options: --system (root, no linger) or --user; --name NAME selects an independent service; install also accepts --port PORT (required for a new named service). Root defaults to --system. No automatic sudo or profile migration.
|
|
7277
7941
|
cadence project open [PATH] [--create] [--agent-dir PATH] [--state-home PATH]
|
|
@@ -7349,23 +8013,11 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
|
|
|
7349
8013
|
else
|
|
7350
8014
|
throw new ContractError("invalid");
|
|
7351
8015
|
}
|
|
7352
|
-
|
|
7353
|
-
if (landing.redirected) {
|
|
7354
|
-
if (seen.has("--workspace"))
|
|
7355
|
-
process.stderr.write(`Cadence workspace: ${landing.workspace} (${options.workspace} contains Cadence's private state)
|
|
7356
|
-
`);
|
|
7357
|
-
options.workspace = landing.workspace;
|
|
7358
|
-
if (command === "start")
|
|
7359
|
-
mkdirSync7(landing.workspace, { recursive: true });
|
|
7360
|
-
}
|
|
8016
|
+
effectiveWorkspace(options.workspace, options.stateHome);
|
|
7361
8017
|
let result;
|
|
7362
8018
|
if (command === "start")
|
|
7363
8019
|
result = await startService(options, foreground, hostUrl);
|
|
7364
|
-
else
|
|
7365
|
-
if (command !== "status" && command !== "diagnose")
|
|
7366
|
-
throw new ContractError("unavailable");
|
|
7367
|
-
result = { status: "absent" };
|
|
7368
|
-
} else {
|
|
8020
|
+
else {
|
|
7369
8021
|
const c = new ServiceCoordination(options.workspace, options.stateHome);
|
|
7370
8022
|
if (command === "recover" && recovery)
|
|
7371
8023
|
result = await localRecovery(c, recovery);
|
|
@@ -7383,8 +8035,6 @@ Recovery resolve requires this local native terminal, exact unchanged dead-owner
|
|
|
7383
8035
|
else
|
|
7384
8036
|
throw new ContractError("invalid");
|
|
7385
8037
|
}
|
|
7386
|
-
if (landing.redirected && result && typeof result === "object")
|
|
7387
|
-
result = { ...result, workspace: landing.workspace };
|
|
7388
8038
|
if (result !== null)
|
|
7389
8039
|
process.stdout.write(`${JSON.stringify(result)}
|
|
7390
8040
|
`);
|