@abelxiaoxing/cadence 2.0.7 → 2.0.8
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 +11 -1
- package/docs/service-management.md +5 -2
- package/package.json +1 -1
- package/src/service-cli.mjs +166 -75
- package/src/service-host.mjs +139 -58
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
package/src/service-host.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/service/host.ts
|
|
6
|
-
import { lstatSync as
|
|
6
|
+
import { lstatSync as lstatSync11, opendirSync as opendirSync3 } from "node:fs";
|
|
7
7
|
import { join as join18 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/agent-registry.ts
|
|
@@ -14800,6 +14800,7 @@ function localOrigin(policy, actualPort) {
|
|
|
14800
14800
|
}
|
|
14801
14801
|
|
|
14802
14802
|
// src/service/server-config.ts
|
|
14803
|
+
import { lstatSync as lstatSync8, realpathSync as realpathSync3, statSync } from "node:fs";
|
|
14803
14804
|
import { dirname as dirname7, join as join10 } from "node:path";
|
|
14804
14805
|
|
|
14805
14806
|
// skills/_shared/load-config.mjs
|
|
@@ -14845,8 +14846,38 @@ function accessToken(value) {
|
|
|
14845
14846
|
throw new ContractError("invalid");
|
|
14846
14847
|
return result;
|
|
14847
14848
|
}
|
|
14849
|
+
class ServerConfigRefusal extends ContractError {
|
|
14850
|
+
path;
|
|
14851
|
+
reason;
|
|
14852
|
+
constructor(path, reason) {
|
|
14853
|
+
super("corrupt-selected");
|
|
14854
|
+
this.path = path;
|
|
14855
|
+
this.reason = reason;
|
|
14856
|
+
}
|
|
14857
|
+
}
|
|
14858
|
+
|
|
14859
|
+
class Unusable extends Error {
|
|
14860
|
+
}
|
|
14861
|
+
var unusable = (reason) => {
|
|
14862
|
+
throw new Unusable(reason);
|
|
14863
|
+
};
|
|
14864
|
+
function syntax(error) {
|
|
14865
|
+
const message = error instanceof Error ? error.message : "";
|
|
14866
|
+
const line = /at line (\d+)$/.exec(message)?.[1];
|
|
14867
|
+
if (!line)
|
|
14868
|
+
return "is not in KEY=value lines";
|
|
14869
|
+
if (message.startsWith("Invalid configuration name"))
|
|
14870
|
+
return `has an invalid name on line ${line} (letters, digits and _ only, no "export")`;
|
|
14871
|
+
if (message.startsWith("Unterminated"))
|
|
14872
|
+
return `has an unterminated quote on line ${line}`;
|
|
14873
|
+
return `has no KEY=value setting on line ${line}`;
|
|
14874
|
+
}
|
|
14848
14875
|
function settings(source) {
|
|
14849
|
-
|
|
14876
|
+
try {
|
|
14877
|
+
parseEnvFile(source);
|
|
14878
|
+
} catch (error) {
|
|
14879
|
+
unusable(`${syntax(error)}, which the research skills refuse too`);
|
|
14880
|
+
}
|
|
14850
14881
|
const parts = source.split(/(\r\n|\r|\n)/);
|
|
14851
14882
|
const found = {};
|
|
14852
14883
|
for (let at = 0;at < parts.length; at += 2) {
|
|
@@ -14858,53 +14889,103 @@ function settings(source) {
|
|
|
14858
14889
|
const name = key2 === PORT_KEY ? "port" : key2 === TOKEN_KEY ? "token" : undefined;
|
|
14859
14890
|
if (!name)
|
|
14860
14891
|
continue;
|
|
14861
|
-
|
|
14862
|
-
|
|
14892
|
+
const previous = found[name];
|
|
14893
|
+
if (previous)
|
|
14894
|
+
unusable(`sets ${key2} twice (lines ${previous.line} and ${at / 2 + 1}); keep one`);
|
|
14863
14895
|
let value = line.slice(separator + 1).trim();
|
|
14864
14896
|
if (value[0] === '"' || value[0] === "'")
|
|
14865
14897
|
value = value.slice(1, -1);
|
|
14866
|
-
found[name] = { at, value };
|
|
14898
|
+
found[name] = { at, line: at / 2 + 1, value };
|
|
14867
14899
|
}
|
|
14868
14900
|
return { parts, ...found };
|
|
14869
14901
|
}
|
|
14870
|
-
function
|
|
14902
|
+
function decode(source) {
|
|
14871
14903
|
const { port, token } = settings(source);
|
|
14872
|
-
if (!port
|
|
14873
|
-
|
|
14904
|
+
if (!port)
|
|
14905
|
+
unusable(`has no ${PORT_KEY} line`);
|
|
14874
14906
|
if (!token)
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14907
|
+
unusable(`has no ${TOKEN_KEY} line`);
|
|
14908
|
+
const p = port, t = token;
|
|
14909
|
+
const number = Number(p.value);
|
|
14910
|
+
if (!/^[1-9][0-9]{0,4}$/.test(p.value) || number > 65535)
|
|
14911
|
+
unusable(`has ${PORT_KEY} on line ${p.line} that is not a whole number from 1 to 65535`);
|
|
14912
|
+
try {
|
|
14913
|
+
return { port: number, token: accessToken(t.value) };
|
|
14914
|
+
} catch {
|
|
14915
|
+
return unusable(`has ${TOKEN_KEY} on line ${t.line} shorter than 32 characters or with control characters`);
|
|
14916
|
+
}
|
|
14880
14917
|
}
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14918
|
+
var code = (error) => error?.code ?? "error";
|
|
14919
|
+
function locate(path) {
|
|
14920
|
+
let real;
|
|
14921
|
+
try {
|
|
14922
|
+
real = realpathSync3(path);
|
|
14923
|
+
} catch (error) {
|
|
14924
|
+
if (code(error) !== "ENOENT")
|
|
14925
|
+
unusable(`cannot be read (${code(error)})`);
|
|
14926
|
+
let link = false;
|
|
14927
|
+
try {
|
|
14928
|
+
link = lstatSync8(path).isSymbolicLink();
|
|
14929
|
+
} catch {}
|
|
14930
|
+
return unusable(link ? "is a link to a file that does not exist" : "does not exist");
|
|
14931
|
+
}
|
|
14932
|
+
return { real, files: new PrivateFiles(dirname7(real), false) };
|
|
14933
|
+
}
|
|
14934
|
+
function explain(real) {
|
|
14935
|
+
const uid = process.getuid?.();
|
|
14936
|
+
try {
|
|
14937
|
+
const st = lstatSync8(real);
|
|
14938
|
+
if (!st.isFile())
|
|
14939
|
+
return "is not a regular file";
|
|
14940
|
+
if (uid !== undefined && st.uid !== uid)
|
|
14941
|
+
return "is not owned by you";
|
|
14942
|
+
if (uid !== undefined && lstatSync8(dirname7(real)).uid !== uid)
|
|
14943
|
+
return `is in ${dirname7(real)}, which is not owned by you`;
|
|
14944
|
+
if (st.nlink !== 1)
|
|
14945
|
+
return "has more than one hard link";
|
|
14946
|
+
if (st.size > CONFIG_BYTES)
|
|
14947
|
+
return "is larger than 64 KiB";
|
|
14948
|
+
return "changed while Cadence read it; try again";
|
|
14949
|
+
} catch (error) {
|
|
14950
|
+
return `cannot be read (${code(error)})`;
|
|
14951
|
+
}
|
|
14952
|
+
}
|
|
14953
|
+
function load(path) {
|
|
14954
|
+
const { real, files } = locate(path);
|
|
14955
|
+
let snapshot;
|
|
14956
|
+
try {
|
|
14957
|
+
snapshot = files.read(real, CONFIG_BYTES);
|
|
14958
|
+
} catch {
|
|
14959
|
+
return unusable(explain(real));
|
|
14960
|
+
}
|
|
14961
|
+
if (!snapshot)
|
|
14962
|
+
return unusable("does not exist");
|
|
14963
|
+
let source;
|
|
14964
|
+
try {
|
|
14965
|
+
source = decoded(snapshot);
|
|
14966
|
+
} catch {
|
|
14967
|
+
return unusable("is not UTF-8 text");
|
|
14887
14968
|
}
|
|
14969
|
+
return { path, real, files, snapshot, source };
|
|
14888
14970
|
}
|
|
14889
14971
|
function decoded(snapshot) {
|
|
14890
14972
|
return new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes);
|
|
14891
14973
|
}
|
|
14892
|
-
function
|
|
14974
|
+
function refusing(path, read2) {
|
|
14893
14975
|
try {
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
if (
|
|
14897
|
-
throw
|
|
14898
|
-
|
|
14899
|
-
path,
|
|
14900
|
-
config: decodeServerConfig(decoded(snapshot)),
|
|
14901
|
-
files,
|
|
14902
|
-
snapshot
|
|
14903
|
-
};
|
|
14904
|
-
} catch {
|
|
14905
|
-
throw new ServerConfigRefusal(path);
|
|
14976
|
+
return read2();
|
|
14977
|
+
} catch (error) {
|
|
14978
|
+
if (error instanceof ServerConfigRefusal)
|
|
14979
|
+
throw error;
|
|
14980
|
+
throw new ServerConfigRefusal(path, error instanceof Unusable ? error.message : `cannot be read (${code(error)})`);
|
|
14906
14981
|
}
|
|
14907
14982
|
}
|
|
14983
|
+
function readServerConfig(path) {
|
|
14984
|
+
return refusing(path, () => {
|
|
14985
|
+
const file = load(path);
|
|
14986
|
+
return { ...file, config: decode(file.source) };
|
|
14987
|
+
});
|
|
14988
|
+
}
|
|
14908
14989
|
function withToken(file, token) {
|
|
14909
14990
|
const { parts, token: line } = settings(decoded(file.snapshot));
|
|
14910
14991
|
if (!line)
|
|
@@ -15142,7 +15223,7 @@ class AccessMaterial {
|
|
|
15142
15223
|
if (this.config.path && this.external && this.externalFile)
|
|
15143
15224
|
this.externalFile = this.external.replace(this.config.path, this.externalFile, secret, TOKEN_BYTES);
|
|
15144
15225
|
if (configured)
|
|
15145
|
-
configured.files.replace(configured.
|
|
15226
|
+
configured.files.replace(configured.real, configured.snapshot, withToken(configured, secret), CONFIG_BYTES);
|
|
15146
15227
|
const record2 = {
|
|
15147
15228
|
...this.record,
|
|
15148
15229
|
generation,
|
|
@@ -15483,7 +15564,7 @@ class AuthNetwork {
|
|
|
15483
15564
|
|
|
15484
15565
|
// src/service/coordination.ts
|
|
15485
15566
|
import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
|
|
15486
|
-
import { opendirSync as opendirSync2, realpathSync as
|
|
15567
|
+
import { opendirSync as opendirSync2, realpathSync as realpathSync4 } from "node:fs";
|
|
15487
15568
|
import { hostname as hostname2 } from "node:os";
|
|
15488
15569
|
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative4, sep as sep4 } from "node:path";
|
|
15489
15570
|
var registrationKeys = [
|
|
@@ -15641,7 +15722,7 @@ class ServiceCoordination {
|
|
|
15641
15722
|
const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
|
|
15642
15723
|
const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
|
|
15643
15724
|
for (const [i, row] of entries.entries()) {
|
|
15644
|
-
if (row.stateHome !== this.stateHome ||
|
|
15725
|
+
if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
|
|
15645
15726
|
refused();
|
|
15646
15727
|
if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
|
|
15647
15728
|
refused();
|
|
@@ -16320,10 +16401,10 @@ function decodeSocketInput(value) {
|
|
|
16320
16401
|
}
|
|
16321
16402
|
|
|
16322
16403
|
// src/service/http.ts
|
|
16323
|
-
function refusal(
|
|
16404
|
+
function refusal(code2 = "invalid", uncertain = false) {
|
|
16324
16405
|
return {
|
|
16325
16406
|
kind: "error",
|
|
16326
|
-
error: publicError(
|
|
16407
|
+
error: publicError(code2, uncertain ? "uncertain" : "none")
|
|
16327
16408
|
};
|
|
16328
16409
|
}
|
|
16329
16410
|
function fixedHeaders(response) {
|
|
@@ -16727,9 +16808,9 @@ Content-Length: 0\r
|
|
|
16727
16808
|
if (res.headersSent)
|
|
16728
16809
|
res.destroy();
|
|
16729
16810
|
else {
|
|
16730
|
-
const
|
|
16811
|
+
const code2 = e instanceof ContractError ? e.code : "unavailable";
|
|
16731
16812
|
res.setHeader("Connection", "close");
|
|
16732
|
-
send(res, errorStatus(
|
|
16813
|
+
send(res, errorStatus(code2), refusal(code2, dispatched));
|
|
16733
16814
|
}
|
|
16734
16815
|
} finally {
|
|
16735
16816
|
clearTimeout(timer);
|
|
@@ -17205,7 +17286,7 @@ class OwnerModelChoice {
|
|
|
17205
17286
|
}
|
|
17206
17287
|
|
|
17207
17288
|
// src/service/model-config.ts
|
|
17208
|
-
import { lstatSync as
|
|
17289
|
+
import { lstatSync as lstatSync9, realpathSync as realpathSync5 } from "node:fs";
|
|
17209
17290
|
import { dirname as dirname10, isAbsolute as isAbsolute6, join as join13, resolve as resolve3 } from "node:path";
|
|
17210
17291
|
var CONFIG_BYTES2 = 1024 * 1024;
|
|
17211
17292
|
var ENTRY_LIMIT = 256;
|
|
@@ -17279,8 +17360,8 @@ function piSource(agentDir, home, cwd, env) {
|
|
|
17279
17360
|
if (selected2 === "~" || selected2.startsWith("~/"))
|
|
17280
17361
|
selected2 = home + selected2.slice(1);
|
|
17281
17362
|
selected2 = resolve3(cwd, selected2);
|
|
17282
|
-
const directory = join13(
|
|
17283
|
-
if (!
|
|
17363
|
+
const directory = join13(realpathSync5(dirname10(selected2)), selected2.slice(dirname10(selected2).length + 1));
|
|
17364
|
+
if (!lstatSync9(directory).isDirectory() || lstatSync9(directory).isSymbolicLink())
|
|
17284
17365
|
refuse2();
|
|
17285
17366
|
const files = new PrivateFiles(directory, false);
|
|
17286
17367
|
if (!files.directory(directory))
|
|
@@ -18789,8 +18870,8 @@ function decodeHostInput(value) {
|
|
|
18789
18870
|
class StartupRefusal extends ContractError {
|
|
18790
18871
|
stage;
|
|
18791
18872
|
quarantined;
|
|
18792
|
-
constructor(
|
|
18793
|
-
super(
|
|
18873
|
+
constructor(code2, stage, quarantined = false) {
|
|
18874
|
+
super(code2);
|
|
18794
18875
|
this.stage = stage;
|
|
18795
18876
|
this.quarantined = quarantined;
|
|
18796
18877
|
}
|
|
@@ -18811,12 +18892,12 @@ function startupReport(error) {
|
|
|
18811
18892
|
|
|
18812
18893
|
// src/service/task-actions.ts
|
|
18813
18894
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
18814
|
-
import { realpathSync as
|
|
18895
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
18815
18896
|
|
|
18816
18897
|
// src/task-diff.ts
|
|
18817
18898
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18818
18899
|
import { createHash as createHash20 } from "node:crypto";
|
|
18819
|
-
import { lstatSync as
|
|
18900
|
+
import { lstatSync as lstatSync10, readdirSync as readdirSync3, readlinkSync } from "node:fs";
|
|
18820
18901
|
import { join as join15, relative as relative5 } from "node:path";
|
|
18821
18902
|
var admin = (path) => path === ".cadence/assistant/tasks" || path.startsWith(".cadence/assistant/tasks/");
|
|
18822
18903
|
function captureChanges(root) {
|
|
@@ -18859,7 +18940,7 @@ function captureChanges(root) {
|
|
|
18859
18940
|
}
|
|
18860
18941
|
try {
|
|
18861
18942
|
const path = join15(root, name);
|
|
18862
|
-
const st =
|
|
18943
|
+
const st = lstatSync10(path);
|
|
18863
18944
|
add(name, `${st.mode}:${st.size}`);
|
|
18864
18945
|
if (st.isSymbolicLink()) {
|
|
18865
18946
|
add(name, readlinkSync(path));
|
|
@@ -19003,7 +19084,7 @@ ${data2.toString("utf8")}`);
|
|
|
19003
19084
|
break;
|
|
19004
19085
|
}
|
|
19005
19086
|
try {
|
|
19006
|
-
const st =
|
|
19087
|
+
const st = lstatSync10(path);
|
|
19007
19088
|
if (st.isDirectory())
|
|
19008
19089
|
walk(path, depth + 1);
|
|
19009
19090
|
else
|
|
@@ -20291,8 +20372,8 @@ function terminateWindowsTree(pid, fallback) {
|
|
|
20291
20372
|
const root = process.env.SystemRoot;
|
|
20292
20373
|
const taskkill = spawn(root ? join17(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
20293
20374
|
taskkill.once("error", fallback);
|
|
20294
|
-
taskkill.once("close", (
|
|
20295
|
-
if (
|
|
20375
|
+
taskkill.once("close", (code2) => {
|
|
20376
|
+
if (code2 !== 0)
|
|
20296
20377
|
fallback();
|
|
20297
20378
|
});
|
|
20298
20379
|
}
|
|
@@ -20554,7 +20635,7 @@ function runProcess(spec) {
|
|
|
20554
20635
|
child.on("error", () => {
|
|
20555
20636
|
stopping = "failed";
|
|
20556
20637
|
});
|
|
20557
|
-
child.on("close", (
|
|
20638
|
+
child.on("close", (code2, signal) => {
|
|
20558
20639
|
nativeClosed = true;
|
|
20559
20640
|
clearTimeout(timeout);
|
|
20560
20641
|
clearTimeout(killTimer);
|
|
@@ -20570,7 +20651,7 @@ function runProcess(spec) {
|
|
|
20570
20651
|
}
|
|
20571
20652
|
}
|
|
20572
20653
|
output += stderrDecoder.end();
|
|
20573
|
-
finish(true,
|
|
20654
|
+
finish(true, code2, signal);
|
|
20574
20655
|
};
|
|
20575
20656
|
if (spec.checkRecording) {
|
|
20576
20657
|
Promise.all([stdoutWork, stderrWork]).then(async () => {
|
|
@@ -21823,9 +21904,9 @@ class TaskActions {
|
|
|
21823
21904
|
selection: structuredClone(options.selection),
|
|
21824
21905
|
ownerSecrets: [...options.ownerSecrets]
|
|
21825
21906
|
};
|
|
21826
|
-
this.cwd =
|
|
21907
|
+
this.cwd = realpathSync6(options.cwd);
|
|
21827
21908
|
const config = readHostConfig(options.host.coordination);
|
|
21828
|
-
if (!config ||
|
|
21909
|
+
if (!config || realpathSync6(config.cwd) !== this.cwd)
|
|
21829
21910
|
throw new ContractError("unsupported-context");
|
|
21830
21911
|
this.launchCwd = config.cwd;
|
|
21831
21912
|
this.checkEnvironment = options.host.checkEnvironment;
|
|
@@ -21941,7 +22022,7 @@ class TaskActions {
|
|
|
21941
22022
|
if (this.closed || this.options.host.owner.state !== "idle" || this.options.host.writer.quarantined)
|
|
21942
22023
|
throw new ContractError("quarantine");
|
|
21943
22024
|
this.options.host.owner.lease.assert();
|
|
21944
|
-
if (!this.sources.current() ||
|
|
22025
|
+
if (!this.sources.current() || realpathSync6(this.cwd) !== this.cwd || realpathSync6(this.launchCwd) !== this.cwd || this.options.host.checkEnvironment !== this.checkEnvironment)
|
|
21945
22026
|
throw new ContractError("stale");
|
|
21946
22027
|
}
|
|
21947
22028
|
choiceSnapshot() {
|
|
@@ -22059,7 +22140,7 @@ class TaskActions {
|
|
|
22059
22140
|
const selected2 = taskId ? h.coordination.store.loadSnapshot(taskId) : null;
|
|
22060
22141
|
if (taskId && !selected2)
|
|
22061
22142
|
throw new ContractError("stale");
|
|
22062
|
-
if (selected2 &&
|
|
22143
|
+
if (selected2 && realpathSync6(selected2.record.cwd) !== this.cwd)
|
|
22063
22144
|
throw new ContractError("unsupported-context");
|
|
22064
22145
|
const secrets = this.viewSecrets();
|
|
22065
22146
|
const roles = this.sources.roles.map(({ role: role2, sha256 }) => ({
|
|
@@ -22563,7 +22644,7 @@ class TaskActions {
|
|
|
22563
22644
|
this.assertChoice(expected);
|
|
22564
22645
|
held.assertHeld();
|
|
22565
22646
|
const actual = h.coordination.store.loadSnapshot(task.id), want = published ?? task;
|
|
22566
|
-
if (!actual || actual.identity !== want.identity || actual.record.revision !== want.revision ||
|
|
22647
|
+
if (!actual || actual.identity !== want.identity || actual.record.revision !== want.revision || realpathSync6(actual.record.cwd) !== this.cwd || this.generation !== expected.checkoutGeneration || h.writer.controlRevision(task.id) !== expected.controlRev + 1 || this.scheduler.activeTask(task.id))
|
|
22567
22648
|
throw new ContractError("stale");
|
|
22568
22649
|
const changes2 = captureChanges(h.coordination.store.root);
|
|
22569
22650
|
if (changes2.fingerprint !== expected.observation.identity || changes2.complete !== expected.observation.complete)
|
|
@@ -22768,7 +22849,7 @@ function legacy(store, budget, reader) {
|
|
|
22768
22849
|
if (created.has(entry.name))
|
|
22769
22850
|
continue;
|
|
22770
22851
|
const path = join18(store.tasks, entry.name, "task.json");
|
|
22771
|
-
const size =
|
|
22852
|
+
const size = lstatSync11(path).size;
|
|
22772
22853
|
if (size > 128 * 1024)
|
|
22773
22854
|
throw new ContractError("corrupt-selected");
|
|
22774
22855
|
budget.reserve(size);
|