@abelxiaoxing/cadence 2.0.6 → 2.0.7
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 +27 -4
- package/docs/remote-access.md +3 -2
- package/docs/service-management.md +32 -5
- package/package.json +1 -1
- package/src/service/coordination.ts +25 -2
- package/src/service-cli.mjs +713 -479
- package/src/service-host.mjs +272 -78
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
package/src/service-host.mjs
CHANGED
|
@@ -4,7 +4,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/service/host.ts
|
|
6
6
|
import { lstatSync as lstatSync10, opendirSync as opendirSync3 } from "node:fs";
|
|
7
|
-
import { join as
|
|
7
|
+
import { join as join18 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/agent-registry.ts
|
|
10
10
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -14749,7 +14749,7 @@ import {
|
|
|
14749
14749
|
randomUUID as randomUUID7,
|
|
14750
14750
|
timingSafeEqual
|
|
14751
14751
|
} from "node:crypto";
|
|
14752
|
-
import { dirname as
|
|
14752
|
+
import { dirname as dirname8, isAbsolute as isAbsolute4, join as join11 } from "node:path";
|
|
14753
14753
|
|
|
14754
14754
|
// src/service/config.ts
|
|
14755
14755
|
import { isIP } from "node:net";
|
|
@@ -14799,14 +14799,122 @@ function localOrigin(policy, actualPort) {
|
|
|
14799
14799
|
return `http://${policy.host === "::1" ? "[::1]" : policy.host}:${actualPort}`;
|
|
14800
14800
|
}
|
|
14801
14801
|
|
|
14802
|
-
// src/service/
|
|
14802
|
+
// src/service/server-config.ts
|
|
14803
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
14804
|
+
|
|
14805
|
+
// skills/_shared/load-config.mjs
|
|
14806
|
+
function parseEnvFile(content) {
|
|
14807
|
+
const values = {};
|
|
14808
|
+
const lines = content.replaceAll(`\r
|
|
14809
|
+
`, `
|
|
14810
|
+
`).replaceAll("\r", `
|
|
14811
|
+
`).split(`
|
|
14812
|
+
`);
|
|
14813
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
14814
|
+
const line = lines[index].trim();
|
|
14815
|
+
if (!line || line.startsWith("#"))
|
|
14816
|
+
continue;
|
|
14817
|
+
const separator = line.indexOf("=");
|
|
14818
|
+
if (separator <= 0)
|
|
14819
|
+
throw new Error(`Invalid configuration syntax at line ${index + 1}`);
|
|
14820
|
+
const name = line.slice(0, separator).trim();
|
|
14821
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
14822
|
+
throw new Error(`Invalid configuration name at line ${index + 1}`);
|
|
14823
|
+
}
|
|
14824
|
+
let value = line.slice(separator + 1).trim();
|
|
14825
|
+
const quote = value[0];
|
|
14826
|
+
if (quote === '"' || quote === "'") {
|
|
14827
|
+
if (value.at(-1) !== quote) {
|
|
14828
|
+
throw new Error(`Unterminated quoted configuration value at line ${index + 1}`);
|
|
14829
|
+
}
|
|
14830
|
+
value = value.slice(1, -1);
|
|
14831
|
+
}
|
|
14832
|
+
values[name] = value;
|
|
14833
|
+
}
|
|
14834
|
+
return values;
|
|
14835
|
+
}
|
|
14836
|
+
|
|
14837
|
+
// src/service/server-config.ts
|
|
14838
|
+
var PORT_KEY = "CADENCE_PORT";
|
|
14839
|
+
var TOKEN_KEY = "CADENCE_TOKEN";
|
|
14840
|
+
var CONFIG_BYTES = 64 * 1024;
|
|
14803
14841
|
var TOKEN_BYTES = 1024;
|
|
14804
|
-
function
|
|
14842
|
+
function accessToken(value) {
|
|
14805
14843
|
const result = text(value, TOKEN_BYTES, true);
|
|
14806
14844
|
if (utf8Bytes(result) < 32 || /[\p{Cc}\p{Cf}]/u.test(result))
|
|
14807
14845
|
throw new ContractError("invalid");
|
|
14808
14846
|
return result;
|
|
14809
14847
|
}
|
|
14848
|
+
function settings(source) {
|
|
14849
|
+
parseEnvFile(source);
|
|
14850
|
+
const parts = source.split(/(\r\n|\r|\n)/);
|
|
14851
|
+
const found = {};
|
|
14852
|
+
for (let at = 0;at < parts.length; at += 2) {
|
|
14853
|
+
const line = parts[at].trim();
|
|
14854
|
+
if (!line || line.startsWith("#"))
|
|
14855
|
+
continue;
|
|
14856
|
+
const separator = line.indexOf("=");
|
|
14857
|
+
const key2 = line.slice(0, separator).trim();
|
|
14858
|
+
const name = key2 === PORT_KEY ? "port" : key2 === TOKEN_KEY ? "token" : undefined;
|
|
14859
|
+
if (!name)
|
|
14860
|
+
continue;
|
|
14861
|
+
if (found[name])
|
|
14862
|
+
throw new ContractError("invalid");
|
|
14863
|
+
let value = line.slice(separator + 1).trim();
|
|
14864
|
+
if (value[0] === '"' || value[0] === "'")
|
|
14865
|
+
value = value.slice(1, -1);
|
|
14866
|
+
found[name] = { at, value };
|
|
14867
|
+
}
|
|
14868
|
+
return { parts, ...found };
|
|
14869
|
+
}
|
|
14870
|
+
function decodeServerConfig(source) {
|
|
14871
|
+
const { port, token } = settings(source);
|
|
14872
|
+
if (!port || !/^[1-9][0-9]{0,4}$/.test(port.value))
|
|
14873
|
+
throw new ContractError("invalid");
|
|
14874
|
+
if (!token)
|
|
14875
|
+
throw new ContractError("invalid");
|
|
14876
|
+
return {
|
|
14877
|
+
port: integer(Number(port.value), 1, 65535),
|
|
14878
|
+
token: accessToken(token.value)
|
|
14879
|
+
};
|
|
14880
|
+
}
|
|
14881
|
+
|
|
14882
|
+
class ServerConfigRefusal extends ContractError {
|
|
14883
|
+
path;
|
|
14884
|
+
constructor(path) {
|
|
14885
|
+
super("corrupt-selected");
|
|
14886
|
+
this.path = path;
|
|
14887
|
+
}
|
|
14888
|
+
}
|
|
14889
|
+
function decoded(snapshot) {
|
|
14890
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes);
|
|
14891
|
+
}
|
|
14892
|
+
function readServerConfig(path) {
|
|
14893
|
+
try {
|
|
14894
|
+
const files = new PrivateFiles(dirname7(path));
|
|
14895
|
+
const snapshot = files.read(path, CONFIG_BYTES);
|
|
14896
|
+
if (!snapshot)
|
|
14897
|
+
throw new ContractError("corrupt-selected");
|
|
14898
|
+
return {
|
|
14899
|
+
path,
|
|
14900
|
+
config: decodeServerConfig(decoded(snapshot)),
|
|
14901
|
+
files,
|
|
14902
|
+
snapshot
|
|
14903
|
+
};
|
|
14904
|
+
} catch {
|
|
14905
|
+
throw new ServerConfigRefusal(path);
|
|
14906
|
+
}
|
|
14907
|
+
}
|
|
14908
|
+
function withToken(file, token) {
|
|
14909
|
+
const { parts, token: line } = settings(decoded(file.snapshot));
|
|
14910
|
+
if (!line)
|
|
14911
|
+
throw new ContractError("invalid");
|
|
14912
|
+
const indent = /^\s*/.exec(parts[line.at])?.[0] ?? "";
|
|
14913
|
+
parts[line.at] = `${indent}${TOKEN_KEY}=${accessToken(token)}`;
|
|
14914
|
+
return parts.join("");
|
|
14915
|
+
}
|
|
14916
|
+
|
|
14917
|
+
// src/service/auth.ts
|
|
14810
14918
|
function sameSecret(candidate, expected) {
|
|
14811
14919
|
const a = createHash18("sha256").update(candidate).digest();
|
|
14812
14920
|
const b = createHash18("sha256").update(expected).digest();
|
|
@@ -14842,16 +14950,50 @@ function cookieTokens(header, name) {
|
|
|
14842
14950
|
return out;
|
|
14843
14951
|
}
|
|
14844
14952
|
function sourceConfig(source) {
|
|
14845
|
-
|
|
14953
|
+
const given = [source.environment, source.tokenFile, source.serverConfig];
|
|
14954
|
+
if (given.filter((v) => v !== undefined).length > 1)
|
|
14846
14955
|
throw new ContractError("invalid");
|
|
14847
14956
|
if (source.environment !== undefined)
|
|
14848
|
-
|
|
14849
|
-
if (source.
|
|
14957
|
+
accessToken(source.environment);
|
|
14958
|
+
if (source.serverConfig !== undefined)
|
|
14959
|
+
accessToken(source.token);
|
|
14960
|
+
const path = source.tokenFile ?? source.serverConfig;
|
|
14961
|
+
if (path !== undefined && (!isAbsolute4(path) || path.includes("\x00")))
|
|
14850
14962
|
throw new ContractError("invalid");
|
|
14851
14963
|
return {
|
|
14852
14964
|
schema: "cadence-service-access-config/1",
|
|
14853
|
-
source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : "generated",
|
|
14854
|
-
path:
|
|
14965
|
+
source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : source.serverConfig !== undefined ? "config" : "generated",
|
|
14966
|
+
path: path ?? null
|
|
14967
|
+
};
|
|
14968
|
+
}
|
|
14969
|
+
function readSource(config) {
|
|
14970
|
+
if (config.source === "file" && config.path)
|
|
14971
|
+
readExternal(config.path);
|
|
14972
|
+
}
|
|
14973
|
+
function selectedMaterial(files, directory, source) {
|
|
14974
|
+
const config = sourceConfig(source);
|
|
14975
|
+
if (config.source !== "config")
|
|
14976
|
+
throw new ContractError("invalid");
|
|
14977
|
+
const marker2 = join11(directory, "access-config.json"), access = join11(directory, "access.json");
|
|
14978
|
+
const markerFile = files.read(marker2), accessFile = files.read(access);
|
|
14979
|
+
if (!markerFile || !accessFile)
|
|
14980
|
+
throw new ContractError("corrupt-selected");
|
|
14981
|
+
const previous = closed(json(markerFile), ["schema", "source", "path"]);
|
|
14982
|
+
literal(previous.schema, "cadence-service-access-config/1");
|
|
14983
|
+
oneOf(previous.source, ["generated", "environment", "file", "config"]);
|
|
14984
|
+
const r = closed(json(accessFile), ["schema", "generation", "token"]);
|
|
14985
|
+
literal(r.schema, "cadence-service-access/1");
|
|
14986
|
+
const generation = integer(r.generation, 1);
|
|
14987
|
+
if (previous.source === "generated" !== (r.token !== null))
|
|
14988
|
+
throw new ContractError("corrupt-selected");
|
|
14989
|
+
return {
|
|
14990
|
+
config,
|
|
14991
|
+
previous,
|
|
14992
|
+
generation,
|
|
14993
|
+
marker: marker2,
|
|
14994
|
+
markerFile,
|
|
14995
|
+
access,
|
|
14996
|
+
accessFile
|
|
14855
14997
|
};
|
|
14856
14998
|
}
|
|
14857
14999
|
|
|
@@ -14869,17 +15011,14 @@ class AccessMaterial {
|
|
|
14869
15011
|
listeners = new Set;
|
|
14870
15012
|
config;
|
|
14871
15013
|
static validateSource(source) {
|
|
14872
|
-
|
|
14873
|
-
if (config.path)
|
|
14874
|
-
readExternal(config.path);
|
|
15014
|
+
readSource(sourceConfig(source));
|
|
14875
15015
|
}
|
|
14876
15016
|
static initialize(files, directory, source = {}) {
|
|
14877
15017
|
const config = sourceConfig(source);
|
|
14878
|
-
const marker2 =
|
|
14879
|
-
if (files.read(marker2) || files.read(access) || files.read(
|
|
15018
|
+
const marker2 = join11(directory, "access-config.json"), access = join11(directory, "access.json");
|
|
15019
|
+
if (files.read(marker2) || files.read(access) || files.read(join11(directory, "registration.json")) || files.read(join11(directory, "owner.lock")))
|
|
14880
15020
|
throw new ContractError("conflict");
|
|
14881
|
-
|
|
14882
|
-
readExternal(config.path);
|
|
15021
|
+
readSource(config);
|
|
14883
15022
|
files.create(marker2, encoded(config));
|
|
14884
15023
|
files.create(access, encoded({
|
|
14885
15024
|
schema: "cadence-service-access/1",
|
|
@@ -14887,13 +15026,27 @@ class AccessMaterial {
|
|
|
14887
15026
|
token: config.source === "generated" ? randomBytes3(32).toString("base64url") : null
|
|
14888
15027
|
}));
|
|
14889
15028
|
}
|
|
15029
|
+
static validateReselection(files, directory, source) {
|
|
15030
|
+
selectedMaterial(files, directory, source);
|
|
15031
|
+
}
|
|
15032
|
+
static reselect(files, directory, source) {
|
|
15033
|
+
const m = selectedMaterial(files, directory, source);
|
|
15034
|
+
if (encoded(m.previous) === encoded(m.config))
|
|
15035
|
+
return;
|
|
15036
|
+
files.replace(m.marker, m.markerFile, encoded(m.config));
|
|
15037
|
+
files.replace(m.access, m.accessFile, encoded({
|
|
15038
|
+
schema: "cadence-service-access/1",
|
|
15039
|
+
generation: integer(m.generation + 1, 1),
|
|
15040
|
+
token: null
|
|
15041
|
+
}));
|
|
15042
|
+
}
|
|
14890
15043
|
constructor(files, directory, source = {}, assertOwner) {
|
|
14891
15044
|
this.files = files;
|
|
14892
15045
|
this.directory = directory;
|
|
14893
15046
|
this.assertOwner = assertOwner;
|
|
14894
15047
|
this.config = sourceConfig(source);
|
|
14895
|
-
const configFile = files.read(
|
|
14896
|
-
const accessFile = files.read(
|
|
15048
|
+
const configFile = files.read(join11(directory, "access-config.json"));
|
|
15049
|
+
const accessFile = files.read(join11(directory, "access.json"));
|
|
14897
15050
|
if (!configFile || !accessFile)
|
|
14898
15051
|
throw new ContractError("corrupt-selected");
|
|
14899
15052
|
const c = closed(json(configFile), ["schema", "source", "path"]);
|
|
@@ -14903,17 +15056,17 @@ class AccessMaterial {
|
|
|
14903
15056
|
this.record = {
|
|
14904
15057
|
schema: literal(r.schema, "cadence-service-access/1"),
|
|
14905
15058
|
generation: integer(r.generation, 1),
|
|
14906
|
-
token: r.token === null ? null :
|
|
15059
|
+
token: r.token === null ? null : accessToken(r.token)
|
|
14907
15060
|
};
|
|
14908
15061
|
if (this.config.source === "generated" !== (this.record.token !== null))
|
|
14909
15062
|
throw new ContractError("corrupt-selected");
|
|
14910
|
-
if (this.config.path) {
|
|
15063
|
+
if (this.config.source === "file" && this.config.path) {
|
|
14911
15064
|
const loaded = readExternal(this.config.path);
|
|
14912
15065
|
this.external = loaded.files;
|
|
14913
15066
|
this.externalFile = loaded.file;
|
|
14914
15067
|
this.secret = loaded.token;
|
|
14915
15068
|
} else
|
|
14916
|
-
this.secret =
|
|
15069
|
+
this.secret = accessToken(this.record.token ?? source.environment ?? source.token);
|
|
14917
15070
|
this.configFile = configFile;
|
|
14918
15071
|
this.accessFile = accessFile;
|
|
14919
15072
|
}
|
|
@@ -14953,9 +15106,9 @@ class AccessMaterial {
|
|
|
14953
15106
|
assert() {
|
|
14954
15107
|
if (this.fenced)
|
|
14955
15108
|
throw new ContractError("quarantine");
|
|
14956
|
-
this.files.assert(
|
|
14957
|
-
this.files.assert(
|
|
14958
|
-
if (this.config.path)
|
|
15109
|
+
this.files.assert(join11(this.directory, "access-config.json"), this.configFile);
|
|
15110
|
+
this.files.assert(join11(this.directory, "access.json"), this.accessFile);
|
|
15111
|
+
if (this.config.source === "file" && this.config.path)
|
|
14959
15112
|
this.external?.assert(this.config.path, this.externalFile, TOKEN_BYTES);
|
|
14960
15113
|
}
|
|
14961
15114
|
redactionLiterals() {
|
|
@@ -14976,19 +15129,27 @@ class AccessMaterial {
|
|
|
14976
15129
|
rotate() {
|
|
14977
15130
|
if (this.config.source === "environment")
|
|
14978
15131
|
throw new ContractError("unsupported-context");
|
|
15132
|
+
let configured;
|
|
15133
|
+
if (this.config.source === "config" && this.config.path) {
|
|
15134
|
+
configured = readServerConfig(this.config.path);
|
|
15135
|
+
if (!sameSecret(configured.config.token, this.secret))
|
|
15136
|
+
throw new ContractError("conflict");
|
|
15137
|
+
}
|
|
14979
15138
|
try {
|
|
14980
15139
|
this.assertRotation();
|
|
14981
15140
|
const secret = randomBytes3(32).toString("base64url");
|
|
14982
15141
|
const generation = integer(this.record.generation + 1, 1);
|
|
14983
15142
|
if (this.config.path && this.external && this.externalFile)
|
|
14984
15143
|
this.externalFile = this.external.replace(this.config.path, this.externalFile, secret, TOKEN_BYTES);
|
|
15144
|
+
if (configured)
|
|
15145
|
+
configured.files.replace(configured.path, configured.snapshot, withToken(configured, secret), CONFIG_BYTES);
|
|
14985
15146
|
const record2 = {
|
|
14986
15147
|
...this.record,
|
|
14987
15148
|
generation,
|
|
14988
15149
|
token: this.config.source === "generated" ? secret : null
|
|
14989
15150
|
};
|
|
14990
15151
|
this.assertRotation();
|
|
14991
|
-
this.accessFile = this.files.replace(
|
|
15152
|
+
this.accessFile = this.files.replace(join11(this.directory, "access.json"), this.accessFile, encoded(record2));
|
|
14992
15153
|
this.assertRotation();
|
|
14993
15154
|
this.record = record2;
|
|
14994
15155
|
this.secret = secret;
|
|
@@ -15003,7 +15164,7 @@ class AccessMaterial {
|
|
|
15003
15164
|
}
|
|
15004
15165
|
}
|
|
15005
15166
|
function readExternal(path) {
|
|
15006
|
-
const files = new PrivateFiles(
|
|
15167
|
+
const files = new PrivateFiles(dirname8(path));
|
|
15007
15168
|
const file = files.read(path, TOKEN_BYTES);
|
|
15008
15169
|
if (!file)
|
|
15009
15170
|
throw new ContractError("corrupt-selected");
|
|
@@ -15011,7 +15172,7 @@ function readExternal(path) {
|
|
|
15011
15172
|
return {
|
|
15012
15173
|
files,
|
|
15013
15174
|
file,
|
|
15014
|
-
token:
|
|
15175
|
+
token: accessToken(new TextDecoder("utf-8", { fatal: true }).decode(file.bytes))
|
|
15015
15176
|
};
|
|
15016
15177
|
} catch {
|
|
15017
15178
|
throw new ContractError("corrupt-selected");
|
|
@@ -15324,7 +15485,7 @@ class AuthNetwork {
|
|
|
15324
15485
|
import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
|
|
15325
15486
|
import { opendirSync as opendirSync2, realpathSync as realpathSync3 } from "node:fs";
|
|
15326
15487
|
import { hostname as hostname2 } from "node:os";
|
|
15327
|
-
import { dirname as
|
|
15488
|
+
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative4, sep as sep4 } from "node:path";
|
|
15328
15489
|
var registrationKeys = [
|
|
15329
15490
|
"schema",
|
|
15330
15491
|
"root",
|
|
@@ -15450,17 +15611,17 @@ class ServiceCoordination {
|
|
|
15450
15611
|
stateHome;
|
|
15451
15612
|
constructor(workspace, stateHome) {
|
|
15452
15613
|
this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
|
|
15453
|
-
const namespace =
|
|
15614
|
+
const namespace = dirname9(this.store.privateRoot);
|
|
15454
15615
|
this.files = new PrivateFiles(namespace);
|
|
15455
|
-
this.stateHome =
|
|
15456
|
-
this.directory =
|
|
15457
|
-
this.registryDirectory =
|
|
15616
|
+
this.stateHome = dirname9(namespace);
|
|
15617
|
+
this.directory = join12(this.store.privateRoot, "service-v1");
|
|
15618
|
+
this.registryDirectory = join12(namespace, "service-registry-v1");
|
|
15458
15619
|
}
|
|
15459
15620
|
path(name) {
|
|
15460
|
-
return
|
|
15621
|
+
return join12(this.directory, name);
|
|
15461
15622
|
}
|
|
15462
15623
|
registryPath(name) {
|
|
15463
|
-
return
|
|
15624
|
+
return join12(this.registryDirectory, name);
|
|
15464
15625
|
}
|
|
15465
15626
|
registry() {
|
|
15466
15627
|
if (!this.files.directory(this.registryDirectory))
|
|
@@ -15508,7 +15669,7 @@ class ServiceCoordination {
|
|
|
15508
15669
|
"host-config.json",
|
|
15509
15670
|
"history-v2"
|
|
15510
15671
|
]);
|
|
15511
|
-
this.files.directory(
|
|
15672
|
+
this.files.directory(join12(this.directory, "history-v2"), false, walks);
|
|
15512
15673
|
}
|
|
15513
15674
|
selected() {
|
|
15514
15675
|
const walks = new DirectoryWalks;
|
|
@@ -15668,7 +15829,7 @@ class ServiceCoordination {
|
|
|
15668
15829
|
throw error;
|
|
15669
15830
|
}
|
|
15670
15831
|
}
|
|
15671
|
-
acquire(input, resolvedEndpoint = input.endpoint, initializeNew) {
|
|
15832
|
+
acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
|
|
15672
15833
|
const config = decodeOwnerConfig(input);
|
|
15673
15834
|
const bound = endpoint(resolvedEndpoint);
|
|
15674
15835
|
if (!endpointMatches(config.endpoint, bound))
|
|
@@ -15683,7 +15844,8 @@ class ServiceCoordination {
|
|
|
15683
15844
|
throw new ContractError("quarantine");
|
|
15684
15845
|
if (this.store.inspectLock())
|
|
15685
15846
|
throw new ContractError("conflict");
|
|
15686
|
-
|
|
15847
|
+
const reselect = !!old && old.config.identity !== hash(encoded(config));
|
|
15848
|
+
if (reselect && (!reselectExisting || !old?.hostConfig))
|
|
15687
15849
|
throw new ContractError("conflict");
|
|
15688
15850
|
const row = {
|
|
15689
15851
|
root: this.store.root,
|
|
@@ -15700,7 +15862,17 @@ class ServiceCoordination {
|
|
|
15700
15862
|
...registry.value,
|
|
15701
15863
|
entries: [...registry.value.entries, row]
|
|
15702
15864
|
}), REGISTRY_BYTES);
|
|
15703
|
-
|
|
15865
|
+
if (old?.hostConfig && reselect && reselectExisting) {
|
|
15866
|
+
reselectExisting(old.hostConfig);
|
|
15867
|
+
const saved = this.files.replace(this.path("config.json"), old.config, encoded(config));
|
|
15868
|
+
this.files.replace(this.path("format.json"), old.manifest, encoded({
|
|
15869
|
+
schema: "cadence-service-format/1",
|
|
15870
|
+
root: this.store.root,
|
|
15871
|
+
stateHome: this.stateHome,
|
|
15872
|
+
configIdentity: saved.identity
|
|
15873
|
+
}));
|
|
15874
|
+
} else
|
|
15875
|
+
this.initialize(config);
|
|
15704
15876
|
if (!old)
|
|
15705
15877
|
initializeNew?.();
|
|
15706
15878
|
const checkout = this.store.acquire();
|
|
@@ -16463,7 +16635,7 @@ Content-Length: 0\r
|
|
|
16463
16635
|
}
|
|
16464
16636
|
if (target2 === "/" && req.method === "GET") {
|
|
16465
16637
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
16466
|
-
res.end("Cadence: enter with the private link that `cadence token-link --workspace <path> --reveal` prints on this machine
|
|
16638
|
+
res.end("Cadence: enter with the private link that `cadence server install` printed, http://127.0.0.1:<CADENCE_PORT>/?token=<CADENCE_TOKEN> from ~/.pi/agent/cadence/.env, or that `cadence token-link --workspace <path> --reveal` prints on this machine.\n");
|
|
16467
16639
|
return;
|
|
16468
16640
|
}
|
|
16469
16641
|
if (target2.startsWith("/local/v1/admin/")) {
|
|
@@ -16544,9 +16716,9 @@ Content-Length: 0\r
|
|
|
16544
16716
|
unavailable();
|
|
16545
16717
|
if (route.operation === "session")
|
|
16546
16718
|
this.attachment(caller);
|
|
16547
|
-
const
|
|
16719
|
+
const decoded2 = decodeHttpOutput(route.operation, value);
|
|
16548
16720
|
const status = value && typeof value === "object" && "kind" in value && value.kind === "error" && "error" in value ? errorStatus(decodePublicError(value.error).code) : 200;
|
|
16549
|
-
send(res, status,
|
|
16721
|
+
send(res, status, decoded2);
|
|
16550
16722
|
} catch (e) {
|
|
16551
16723
|
if (createdAttachment)
|
|
16552
16724
|
this.detach(createdAttachment);
|
|
@@ -17034,8 +17206,8 @@ class OwnerModelChoice {
|
|
|
17034
17206
|
|
|
17035
17207
|
// src/service/model-config.ts
|
|
17036
17208
|
import { lstatSync as lstatSync8, realpathSync as realpathSync4 } from "node:fs";
|
|
17037
|
-
import { dirname as
|
|
17038
|
-
var
|
|
17209
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, join as join13, resolve as resolve3 } from "node:path";
|
|
17210
|
+
var CONFIG_BYTES2 = 1024 * 1024;
|
|
17039
17211
|
var ENTRY_LIMIT = 256;
|
|
17040
17212
|
function refuse2() {
|
|
17041
17213
|
throw new ContractError("corrupt-selected");
|
|
@@ -17092,7 +17264,7 @@ function captureEnvironment(value) {
|
|
|
17092
17264
|
if (typeof name !== "string" || !d || !("value" in d) || d.value !== undefined && typeof d.value !== "string")
|
|
17093
17265
|
refuse2();
|
|
17094
17266
|
bytes += Buffer.byteLength(name) + Buffer.byteLength(d.value ?? "");
|
|
17095
|
-
if (bytes >
|
|
17267
|
+
if (bytes > CONFIG_BYTES2)
|
|
17096
17268
|
throw new ContractError("oversize");
|
|
17097
17269
|
out[name] = d.value;
|
|
17098
17270
|
}
|
|
@@ -17101,13 +17273,13 @@ function captureEnvironment(value) {
|
|
|
17101
17273
|
function piSource(agentDir, home, cwd, env) {
|
|
17102
17274
|
if (!isAbsolute6(home) || !isAbsolute6(cwd))
|
|
17103
17275
|
refuse2();
|
|
17104
|
-
let selected2 = agentDir ?? env.PI_CODING_AGENT_DIR ??
|
|
17276
|
+
let selected2 = agentDir ?? env.PI_CODING_AGENT_DIR ?? join13(home, ".pi", "agent");
|
|
17105
17277
|
if (!selected2 || selected2.includes("\x00"))
|
|
17106
17278
|
refuse2();
|
|
17107
17279
|
if (selected2 === "~" || selected2.startsWith("~/"))
|
|
17108
17280
|
selected2 = home + selected2.slice(1);
|
|
17109
17281
|
selected2 = resolve3(cwd, selected2);
|
|
17110
|
-
const directory =
|
|
17282
|
+
const directory = join13(realpathSync4(dirname10(selected2)), selected2.slice(dirname10(selected2).length + 1));
|
|
17111
17283
|
if (!lstatSync8(directory).isDirectory() || lstatSync8(directory).isSymbolicLink())
|
|
17112
17284
|
refuse2();
|
|
17113
17285
|
const files = new PrivateFiles(directory, false);
|
|
@@ -17116,8 +17288,8 @@ function piSource(agentDir, home, cwd, env) {
|
|
|
17116
17288
|
return {
|
|
17117
17289
|
directory,
|
|
17118
17290
|
files,
|
|
17119
|
-
authPath:
|
|
17120
|
-
modelsPath:
|
|
17291
|
+
authPath: join13(directory, "auth.json"),
|
|
17292
|
+
modelsPath: join13(directory, "models.json")
|
|
17121
17293
|
};
|
|
17122
17294
|
}
|
|
17123
17295
|
function templateNames(value) {
|
|
@@ -17396,7 +17568,7 @@ function modelJson(snapshot) {
|
|
|
17396
17568
|
} else if (char === '"')
|
|
17397
17569
|
quoted = true;
|
|
17398
17570
|
}
|
|
17399
|
-
return parseWireJson(normalized,
|
|
17571
|
+
return parseWireJson(normalized, CONFIG_BYTES2);
|
|
17400
17572
|
}
|
|
17401
17573
|
function readPiSnapshot(source) {
|
|
17402
17574
|
const { auth, credentials } = readPiAuth(source);
|
|
@@ -17404,11 +17576,11 @@ function readPiSnapshot(source) {
|
|
|
17404
17576
|
return { auth, models, credentials, providers };
|
|
17405
17577
|
}
|
|
17406
17578
|
function readPiAuth(source) {
|
|
17407
|
-
const auth = source.files.read(source.authPath,
|
|
17579
|
+
const auth = source.files.read(source.authPath, CONFIG_BYTES2);
|
|
17408
17580
|
if (!auth)
|
|
17409
17581
|
refuse2();
|
|
17410
17582
|
const credentials = Object.create(null);
|
|
17411
|
-
for (const [provider, input] of Object.entries(data(json(auth,
|
|
17583
|
+
for (const [provider, input] of Object.entries(data(json(auth, CONFIG_BYTES2)))) {
|
|
17412
17584
|
const credential = data(input);
|
|
17413
17585
|
if (credential.type === "api_key") {
|
|
17414
17586
|
keys2(credential, ["type", "key", "env"]);
|
|
@@ -17430,7 +17602,7 @@ function readPiAuth(source) {
|
|
|
17430
17602
|
return { auth, credentials };
|
|
17431
17603
|
}
|
|
17432
17604
|
function readPiModels(source) {
|
|
17433
|
-
const models = source.files.read(source.modelsPath,
|
|
17605
|
+
const models = source.files.read(source.modelsPath, CONFIG_BYTES2);
|
|
17434
17606
|
const providers = Object.create(null);
|
|
17435
17607
|
if (models) {
|
|
17436
17608
|
const root = data(modelJson(models));
|
|
@@ -17554,9 +17726,9 @@ function clonePrivate(value) {
|
|
|
17554
17726
|
return out;
|
|
17555
17727
|
};
|
|
17556
17728
|
const encoded2 = JSON.stringify(visit(value, 0));
|
|
17557
|
-
if (Buffer.byteLength(encoded2) >
|
|
17729
|
+
if (Buffer.byteLength(encoded2) > CONFIG_BYTES2)
|
|
17558
17730
|
throw new ContractError("oversize");
|
|
17559
|
-
return parseWireJson(encoded2,
|
|
17731
|
+
return parseWireJson(encoded2, CONFIG_BYTES2);
|
|
17560
17732
|
}
|
|
17561
17733
|
|
|
17562
17734
|
// src/service/model-context.ts
|
|
@@ -18510,7 +18682,7 @@ class ServiceOwner {
|
|
|
18510
18682
|
}
|
|
18511
18683
|
|
|
18512
18684
|
// src/service/runtime.ts
|
|
18513
|
-
import { dirname as
|
|
18685
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join14, resolve as resolve4 } from "node:path";
|
|
18514
18686
|
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
18515
18687
|
function absolute(value) {
|
|
18516
18688
|
const result = text(value, 4096, true);
|
|
@@ -18530,10 +18702,11 @@ function decodeHostConfig(value) {
|
|
|
18530
18702
|
const tokenSource = oneOf(o.tokenSource, [
|
|
18531
18703
|
"generated",
|
|
18532
18704
|
"environment",
|
|
18533
|
-
"file"
|
|
18705
|
+
"file",
|
|
18706
|
+
"config"
|
|
18534
18707
|
]);
|
|
18535
18708
|
const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
|
|
18536
|
-
if (
|
|
18709
|
+
if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
|
|
18537
18710
|
throw new ContractError("invalid");
|
|
18538
18711
|
return {
|
|
18539
18712
|
schema: literal(o.schema, "cadence-service-host/1"),
|
|
@@ -18544,6 +18717,15 @@ function decodeHostConfig(value) {
|
|
|
18544
18717
|
tokenFile
|
|
18545
18718
|
};
|
|
18546
18719
|
}
|
|
18720
|
+
function reselectable(old, next) {
|
|
18721
|
+
const kept = (c) => encoded({
|
|
18722
|
+
...c,
|
|
18723
|
+
http: { ...c.http, port: 0 },
|
|
18724
|
+
tokenSource: null,
|
|
18725
|
+
tokenFile: null
|
|
18726
|
+
});
|
|
18727
|
+
return next.tokenSource === "config" && kept(old) === kept(next);
|
|
18728
|
+
}
|
|
18547
18729
|
function readHostConfig(c) {
|
|
18548
18730
|
const owner = c.configuration();
|
|
18549
18731
|
if (!owner)
|
|
@@ -18635,7 +18817,7 @@ import { realpathSync as realpathSync5 } from "node:fs";
|
|
|
18635
18817
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18636
18818
|
import { createHash as createHash20 } from "node:crypto";
|
|
18637
18819
|
import { lstatSync as lstatSync9, readdirSync as readdirSync3, readlinkSync } from "node:fs";
|
|
18638
|
-
import { join as
|
|
18820
|
+
import { join as join15, relative as relative5 } from "node:path";
|
|
18639
18821
|
var admin = (path) => path === ".cadence/assistant/tasks" || path.startsWith(".cadence/assistant/tasks/");
|
|
18640
18822
|
function captureChanges(root) {
|
|
18641
18823
|
const deadline = Date.now() + 3000;
|
|
@@ -18676,7 +18858,7 @@ function captureChanges(root) {
|
|
|
18676
18858
|
return;
|
|
18677
18859
|
}
|
|
18678
18860
|
try {
|
|
18679
|
-
const path =
|
|
18861
|
+
const path = join15(root, name);
|
|
18680
18862
|
const st = lstatSync9(path);
|
|
18681
18863
|
add(name, `${st.mode}:${st.size}`);
|
|
18682
18864
|
if (st.isSymbolicLink()) {
|
|
@@ -18812,7 +18994,7 @@ ${data2.toString("utf8")}`);
|
|
|
18812
18994
|
return;
|
|
18813
18995
|
}
|
|
18814
18996
|
for (const name of names2) {
|
|
18815
|
-
const path =
|
|
18997
|
+
const path = join15(dir, name);
|
|
18816
18998
|
const rel = relative5(root, path).split("\\").join("/");
|
|
18817
18999
|
if (rel === ".git" || admin(rel))
|
|
18818
19000
|
continue;
|
|
@@ -19108,9 +19290,9 @@ class ActionAdmission {
|
|
|
19108
19290
|
});
|
|
19109
19291
|
}
|
|
19110
19292
|
existing(record2, commandId, requested) {
|
|
19111
|
-
const
|
|
19112
|
-
const receipt =
|
|
19113
|
-
if (receipt.commandId !== commandId || canonicalJson(
|
|
19293
|
+
const decoded2 = decodeStoredCommand(record2);
|
|
19294
|
+
const receipt = decoded2.receipt;
|
|
19295
|
+
if (receipt.commandId !== commandId || canonicalJson(decoded2.identity) !== canonicalJson(requested))
|
|
19114
19296
|
return error(publicError("conflict"));
|
|
19115
19297
|
return ok(receipt);
|
|
19116
19298
|
}
|
|
@@ -19375,7 +19557,7 @@ class ActionAdmission {
|
|
|
19375
19557
|
|
|
19376
19558
|
// src/service/graph.ts
|
|
19377
19559
|
import { createHash as createHash21 } from "node:crypto";
|
|
19378
|
-
import { join as
|
|
19560
|
+
import { join as join16 } from "node:path";
|
|
19379
19561
|
var hash3 = (text2) => createHash21("sha256").update(text2).digest("hex");
|
|
19380
19562
|
|
|
19381
19563
|
class GraphContext {
|
|
@@ -19385,7 +19567,7 @@ class GraphContext {
|
|
|
19385
19567
|
identity;
|
|
19386
19568
|
constructor(root) {
|
|
19387
19569
|
this.root = root;
|
|
19388
|
-
const bytes = readBounded(
|
|
19570
|
+
const bytes = readBounded(join16(root, "AGENTS.md"), 32768);
|
|
19389
19571
|
this.hints = bytes ? new TextDecoder("utf-8", { fatal: true }).decode(bytes) : "";
|
|
19390
19572
|
this.identity = hash3(canonicalJson({
|
|
19391
19573
|
hints: bytes === undefined ? null : hash3(bytes),
|
|
@@ -19522,7 +19704,7 @@ import { randomUUID as randomUUID14 } from "node:crypto";
|
|
|
19522
19704
|
|
|
19523
19705
|
// src/subagent-process.ts
|
|
19524
19706
|
import { spawn } from "node:child_process";
|
|
19525
|
-
import { join as
|
|
19707
|
+
import { join as join17 } from "node:path";
|
|
19526
19708
|
import { StringDecoder } from "node:string_decoder";
|
|
19527
19709
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
19528
19710
|
// src/worker-capture.mjs
|
|
@@ -20107,7 +20289,7 @@ class WorkerRecording {
|
|
|
20107
20289
|
// src/subagent-process.ts
|
|
20108
20290
|
function terminateWindowsTree(pid, fallback) {
|
|
20109
20291
|
const root = process.env.SystemRoot;
|
|
20110
|
-
const taskkill = spawn(root ?
|
|
20292
|
+
const taskkill = spawn(root ? join17(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
20111
20293
|
taskkill.once("error", fallback);
|
|
20112
20294
|
taskkill.once("close", (code) => {
|
|
20113
20295
|
if (code !== 0)
|
|
@@ -20260,17 +20442,17 @@ function runProcess(spec) {
|
|
|
20260
20442
|
const value = parseCaptureLine(line);
|
|
20261
20443
|
if (!capture)
|
|
20262
20444
|
capture = new CaptureState(spec.childRunId ?? decodeCaptureFrame(value).childRunId, spec.expectedRequest);
|
|
20263
|
-
const
|
|
20264
|
-
if (
|
|
20445
|
+
const decoded2 = capture.accept(value, Buffer.byteLength(line) + 1);
|
|
20446
|
+
if (decoded2.event.kind === "ready") {
|
|
20265
20447
|
ready2 = true;
|
|
20266
20448
|
spec.onProgress?.("Agent ready");
|
|
20267
20449
|
}
|
|
20268
|
-
if (
|
|
20269
|
-
spec.onProgress?.(clean(`Tool: ${
|
|
20270
|
-
if (
|
|
20450
|
+
if (decoded2.event.kind === "tool-start")
|
|
20451
|
+
spec.onProgress?.(clean(`Tool: ${decoded2.event.name}`, 128));
|
|
20452
|
+
if (decoded2.event.kind === "final")
|
|
20271
20453
|
final = capture.preview;
|
|
20272
20454
|
if (lease) {
|
|
20273
|
-
spec.recording?.accept(
|
|
20455
|
+
spec.recording?.accept(decoded2, lease);
|
|
20274
20456
|
lease = undefined;
|
|
20275
20457
|
}
|
|
20276
20458
|
} catch {
|
|
@@ -22585,7 +22767,7 @@ function legacy(store, budget, reader) {
|
|
|
22585
22767
|
decodeId(entry.name);
|
|
22586
22768
|
if (created.has(entry.name))
|
|
22587
22769
|
continue;
|
|
22588
|
-
const path =
|
|
22770
|
+
const path = join18(store.tasks, entry.name, "task.json");
|
|
22589
22771
|
const size = lstatSync10(path).size;
|
|
22590
22772
|
if (size > 128 * 1024)
|
|
22591
22773
|
throw new ContractError("corrupt-selected");
|
|
@@ -22632,12 +22814,19 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22632
22814
|
if (resolveWorkspace(input.config.cwd).root !== c.store.root)
|
|
22633
22815
|
throw new ContractError("conflict");
|
|
22634
22816
|
const old = readHostConfig(c);
|
|
22635
|
-
|
|
22817
|
+
const reselect = !!old && encoded(old) !== encoded(input.config);
|
|
22818
|
+
if (old && reselect && !reselectable(old, input.config))
|
|
22636
22819
|
throw new ContractError("conflict");
|
|
22637
|
-
const
|
|
22820
|
+
const configured = input.config.tokenSource === "config" && input.config.tokenFile ? readServerConfig(input.config.tokenFile).config : undefined;
|
|
22821
|
+
if (configured && configured.port !== input.config.http.port)
|
|
22822
|
+
throw new ContractError("conflict");
|
|
22823
|
+
const source = input.environmentToken !== null ? { environment: input.environmentToken } : configured && input.config.tokenFile ? { serverConfig: input.config.tokenFile, token: configured.token } : input.config.tokenFile ? { tokenFile: input.config.tokenFile } : {};
|
|
22638
22824
|
AccessMaterial.validateSource(source);
|
|
22639
22825
|
if (old) {
|
|
22640
|
-
|
|
22826
|
+
if (reselect)
|
|
22827
|
+
AccessMaterial.validateReselection(c.files, c.directory, source);
|
|
22828
|
+
else
|
|
22829
|
+
new AccessMaterial(c.files, c.directory, source);
|
|
22641
22830
|
const history = HistoryFiles.open(c);
|
|
22642
22831
|
if (!history)
|
|
22643
22832
|
throw new ContractError("corrupt-selected");
|
|
@@ -22666,7 +22855,12 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22666
22855
|
}, endpoint2, () => {
|
|
22667
22856
|
c.files.create(c.path("host-config.json"), encoded(resolved));
|
|
22668
22857
|
AccessMaterial.initialize(c.files, c.directory, source);
|
|
22669
|
-
})
|
|
22858
|
+
}, reselect && old ? (previous) => {
|
|
22859
|
+
if (previous.identity !== hash(encoded(old)))
|
|
22860
|
+
throw new ContractError("conflict");
|
|
22861
|
+
AccessMaterial.reselect(c.files, c.directory, source);
|
|
22862
|
+
c.files.replace(c.path("host-config.json"), previous, encoded(resolved));
|
|
22863
|
+
} : undefined);
|
|
22670
22864
|
let writer, live;
|
|
22671
22865
|
owner = new ServiceOwner(lease, {
|
|
22672
22866
|
quiesce: () => http.quiesce(),
|