@abelxiaoxing/cadence 2.0.6 → 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 +37 -4
- package/docs/remote-access.md +3 -2
- package/docs/service-management.md +35 -5
- package/package.json +1 -1
- package/src/service/coordination.ts +25 -2
- package/src/service-cli.mjs +829 -504
- package/src/service-host.mjs +378 -103
- package/web/dist/assets/cadence.js +1 -1
- package/web/dist/manifest.json +3 -3
package/src/service-host.mjs
CHANGED
|
@@ -3,8 +3,8 @@ 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
|
|
7
|
-
import { join as
|
|
6
|
+
import { lstatSync as lstatSync11, opendirSync as opendirSync3 } from "node:fs";
|
|
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,203 @@ 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 { lstatSync as lstatSync8, realpathSync as realpathSync3, statSync } from "node:fs";
|
|
14804
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
14805
|
+
|
|
14806
|
+
// skills/_shared/load-config.mjs
|
|
14807
|
+
function parseEnvFile(content) {
|
|
14808
|
+
const values = {};
|
|
14809
|
+
const lines = content.replaceAll(`\r
|
|
14810
|
+
`, `
|
|
14811
|
+
`).replaceAll("\r", `
|
|
14812
|
+
`).split(`
|
|
14813
|
+
`);
|
|
14814
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
14815
|
+
const line = lines[index].trim();
|
|
14816
|
+
if (!line || line.startsWith("#"))
|
|
14817
|
+
continue;
|
|
14818
|
+
const separator = line.indexOf("=");
|
|
14819
|
+
if (separator <= 0)
|
|
14820
|
+
throw new Error(`Invalid configuration syntax at line ${index + 1}`);
|
|
14821
|
+
const name = line.slice(0, separator).trim();
|
|
14822
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
14823
|
+
throw new Error(`Invalid configuration name at line ${index + 1}`);
|
|
14824
|
+
}
|
|
14825
|
+
let value = line.slice(separator + 1).trim();
|
|
14826
|
+
const quote = value[0];
|
|
14827
|
+
if (quote === '"' || quote === "'") {
|
|
14828
|
+
if (value.at(-1) !== quote) {
|
|
14829
|
+
throw new Error(`Unterminated quoted configuration value at line ${index + 1}`);
|
|
14830
|
+
}
|
|
14831
|
+
value = value.slice(1, -1);
|
|
14832
|
+
}
|
|
14833
|
+
values[name] = value;
|
|
14834
|
+
}
|
|
14835
|
+
return values;
|
|
14836
|
+
}
|
|
14837
|
+
|
|
14838
|
+
// src/service/server-config.ts
|
|
14839
|
+
var PORT_KEY = "CADENCE_PORT";
|
|
14840
|
+
var TOKEN_KEY = "CADENCE_TOKEN";
|
|
14841
|
+
var CONFIG_BYTES = 64 * 1024;
|
|
14803
14842
|
var TOKEN_BYTES = 1024;
|
|
14804
|
-
function
|
|
14843
|
+
function accessToken(value) {
|
|
14805
14844
|
const result = text(value, TOKEN_BYTES, true);
|
|
14806
14845
|
if (utf8Bytes(result) < 32 || /[\p{Cc}\p{Cf}]/u.test(result))
|
|
14807
14846
|
throw new ContractError("invalid");
|
|
14808
14847
|
return result;
|
|
14809
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
|
+
}
|
|
14875
|
+
function settings(source) {
|
|
14876
|
+
try {
|
|
14877
|
+
parseEnvFile(source);
|
|
14878
|
+
} catch (error) {
|
|
14879
|
+
unusable(`${syntax(error)}, which the research skills refuse too`);
|
|
14880
|
+
}
|
|
14881
|
+
const parts = source.split(/(\r\n|\r|\n)/);
|
|
14882
|
+
const found = {};
|
|
14883
|
+
for (let at = 0;at < parts.length; at += 2) {
|
|
14884
|
+
const line = parts[at].trim();
|
|
14885
|
+
if (!line || line.startsWith("#"))
|
|
14886
|
+
continue;
|
|
14887
|
+
const separator = line.indexOf("=");
|
|
14888
|
+
const key2 = line.slice(0, separator).trim();
|
|
14889
|
+
const name = key2 === PORT_KEY ? "port" : key2 === TOKEN_KEY ? "token" : undefined;
|
|
14890
|
+
if (!name)
|
|
14891
|
+
continue;
|
|
14892
|
+
const previous = found[name];
|
|
14893
|
+
if (previous)
|
|
14894
|
+
unusable(`sets ${key2} twice (lines ${previous.line} and ${at / 2 + 1}); keep one`);
|
|
14895
|
+
let value = line.slice(separator + 1).trim();
|
|
14896
|
+
if (value[0] === '"' || value[0] === "'")
|
|
14897
|
+
value = value.slice(1, -1);
|
|
14898
|
+
found[name] = { at, line: at / 2 + 1, value };
|
|
14899
|
+
}
|
|
14900
|
+
return { parts, ...found };
|
|
14901
|
+
}
|
|
14902
|
+
function decode(source) {
|
|
14903
|
+
const { port, token } = settings(source);
|
|
14904
|
+
if (!port)
|
|
14905
|
+
unusable(`has no ${PORT_KEY} line`);
|
|
14906
|
+
if (!token)
|
|
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
|
+
}
|
|
14917
|
+
}
|
|
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");
|
|
14968
|
+
}
|
|
14969
|
+
return { path, real, files, snapshot, source };
|
|
14970
|
+
}
|
|
14971
|
+
function decoded(snapshot) {
|
|
14972
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(snapshot.bytes);
|
|
14973
|
+
}
|
|
14974
|
+
function refusing(path, read2) {
|
|
14975
|
+
try {
|
|
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)})`);
|
|
14981
|
+
}
|
|
14982
|
+
}
|
|
14983
|
+
function readServerConfig(path) {
|
|
14984
|
+
return refusing(path, () => {
|
|
14985
|
+
const file = load(path);
|
|
14986
|
+
return { ...file, config: decode(file.source) };
|
|
14987
|
+
});
|
|
14988
|
+
}
|
|
14989
|
+
function withToken(file, token) {
|
|
14990
|
+
const { parts, token: line } = settings(decoded(file.snapshot));
|
|
14991
|
+
if (!line)
|
|
14992
|
+
throw new ContractError("invalid");
|
|
14993
|
+
const indent = /^\s*/.exec(parts[line.at])?.[0] ?? "";
|
|
14994
|
+
parts[line.at] = `${indent}${TOKEN_KEY}=${accessToken(token)}`;
|
|
14995
|
+
return parts.join("");
|
|
14996
|
+
}
|
|
14997
|
+
|
|
14998
|
+
// src/service/auth.ts
|
|
14810
14999
|
function sameSecret(candidate, expected) {
|
|
14811
15000
|
const a = createHash18("sha256").update(candidate).digest();
|
|
14812
15001
|
const b = createHash18("sha256").update(expected).digest();
|
|
@@ -14842,16 +15031,50 @@ function cookieTokens(header, name) {
|
|
|
14842
15031
|
return out;
|
|
14843
15032
|
}
|
|
14844
15033
|
function sourceConfig(source) {
|
|
14845
|
-
|
|
15034
|
+
const given = [source.environment, source.tokenFile, source.serverConfig];
|
|
15035
|
+
if (given.filter((v) => v !== undefined).length > 1)
|
|
14846
15036
|
throw new ContractError("invalid");
|
|
14847
15037
|
if (source.environment !== undefined)
|
|
14848
|
-
|
|
14849
|
-
if (source.
|
|
15038
|
+
accessToken(source.environment);
|
|
15039
|
+
if (source.serverConfig !== undefined)
|
|
15040
|
+
accessToken(source.token);
|
|
15041
|
+
const path = source.tokenFile ?? source.serverConfig;
|
|
15042
|
+
if (path !== undefined && (!isAbsolute4(path) || path.includes("\x00")))
|
|
14850
15043
|
throw new ContractError("invalid");
|
|
14851
15044
|
return {
|
|
14852
15045
|
schema: "cadence-service-access-config/1",
|
|
14853
|
-
source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : "generated",
|
|
14854
|
-
path:
|
|
15046
|
+
source: source.environment !== undefined ? "environment" : source.tokenFile !== undefined ? "file" : source.serverConfig !== undefined ? "config" : "generated",
|
|
15047
|
+
path: path ?? null
|
|
15048
|
+
};
|
|
15049
|
+
}
|
|
15050
|
+
function readSource(config) {
|
|
15051
|
+
if (config.source === "file" && config.path)
|
|
15052
|
+
readExternal(config.path);
|
|
15053
|
+
}
|
|
15054
|
+
function selectedMaterial(files, directory, source) {
|
|
15055
|
+
const config = sourceConfig(source);
|
|
15056
|
+
if (config.source !== "config")
|
|
15057
|
+
throw new ContractError("invalid");
|
|
15058
|
+
const marker2 = join11(directory, "access-config.json"), access = join11(directory, "access.json");
|
|
15059
|
+
const markerFile = files.read(marker2), accessFile = files.read(access);
|
|
15060
|
+
if (!markerFile || !accessFile)
|
|
15061
|
+
throw new ContractError("corrupt-selected");
|
|
15062
|
+
const previous = closed(json(markerFile), ["schema", "source", "path"]);
|
|
15063
|
+
literal(previous.schema, "cadence-service-access-config/1");
|
|
15064
|
+
oneOf(previous.source, ["generated", "environment", "file", "config"]);
|
|
15065
|
+
const r = closed(json(accessFile), ["schema", "generation", "token"]);
|
|
15066
|
+
literal(r.schema, "cadence-service-access/1");
|
|
15067
|
+
const generation = integer(r.generation, 1);
|
|
15068
|
+
if (previous.source === "generated" !== (r.token !== null))
|
|
15069
|
+
throw new ContractError("corrupt-selected");
|
|
15070
|
+
return {
|
|
15071
|
+
config,
|
|
15072
|
+
previous,
|
|
15073
|
+
generation,
|
|
15074
|
+
marker: marker2,
|
|
15075
|
+
markerFile,
|
|
15076
|
+
access,
|
|
15077
|
+
accessFile
|
|
14855
15078
|
};
|
|
14856
15079
|
}
|
|
14857
15080
|
|
|
@@ -14869,17 +15092,14 @@ class AccessMaterial {
|
|
|
14869
15092
|
listeners = new Set;
|
|
14870
15093
|
config;
|
|
14871
15094
|
static validateSource(source) {
|
|
14872
|
-
|
|
14873
|
-
if (config.path)
|
|
14874
|
-
readExternal(config.path);
|
|
15095
|
+
readSource(sourceConfig(source));
|
|
14875
15096
|
}
|
|
14876
15097
|
static initialize(files, directory, source = {}) {
|
|
14877
15098
|
const config = sourceConfig(source);
|
|
14878
|
-
const marker2 =
|
|
14879
|
-
if (files.read(marker2) || files.read(access) || files.read(
|
|
15099
|
+
const marker2 = join11(directory, "access-config.json"), access = join11(directory, "access.json");
|
|
15100
|
+
if (files.read(marker2) || files.read(access) || files.read(join11(directory, "registration.json")) || files.read(join11(directory, "owner.lock")))
|
|
14880
15101
|
throw new ContractError("conflict");
|
|
14881
|
-
|
|
14882
|
-
readExternal(config.path);
|
|
15102
|
+
readSource(config);
|
|
14883
15103
|
files.create(marker2, encoded(config));
|
|
14884
15104
|
files.create(access, encoded({
|
|
14885
15105
|
schema: "cadence-service-access/1",
|
|
@@ -14887,13 +15107,27 @@ class AccessMaterial {
|
|
|
14887
15107
|
token: config.source === "generated" ? randomBytes3(32).toString("base64url") : null
|
|
14888
15108
|
}));
|
|
14889
15109
|
}
|
|
15110
|
+
static validateReselection(files, directory, source) {
|
|
15111
|
+
selectedMaterial(files, directory, source);
|
|
15112
|
+
}
|
|
15113
|
+
static reselect(files, directory, source) {
|
|
15114
|
+
const m = selectedMaterial(files, directory, source);
|
|
15115
|
+
if (encoded(m.previous) === encoded(m.config))
|
|
15116
|
+
return;
|
|
15117
|
+
files.replace(m.marker, m.markerFile, encoded(m.config));
|
|
15118
|
+
files.replace(m.access, m.accessFile, encoded({
|
|
15119
|
+
schema: "cadence-service-access/1",
|
|
15120
|
+
generation: integer(m.generation + 1, 1),
|
|
15121
|
+
token: null
|
|
15122
|
+
}));
|
|
15123
|
+
}
|
|
14890
15124
|
constructor(files, directory, source = {}, assertOwner) {
|
|
14891
15125
|
this.files = files;
|
|
14892
15126
|
this.directory = directory;
|
|
14893
15127
|
this.assertOwner = assertOwner;
|
|
14894
15128
|
this.config = sourceConfig(source);
|
|
14895
|
-
const configFile = files.read(
|
|
14896
|
-
const accessFile = files.read(
|
|
15129
|
+
const configFile = files.read(join11(directory, "access-config.json"));
|
|
15130
|
+
const accessFile = files.read(join11(directory, "access.json"));
|
|
14897
15131
|
if (!configFile || !accessFile)
|
|
14898
15132
|
throw new ContractError("corrupt-selected");
|
|
14899
15133
|
const c = closed(json(configFile), ["schema", "source", "path"]);
|
|
@@ -14903,17 +15137,17 @@ class AccessMaterial {
|
|
|
14903
15137
|
this.record = {
|
|
14904
15138
|
schema: literal(r.schema, "cadence-service-access/1"),
|
|
14905
15139
|
generation: integer(r.generation, 1),
|
|
14906
|
-
token: r.token === null ? null :
|
|
15140
|
+
token: r.token === null ? null : accessToken(r.token)
|
|
14907
15141
|
};
|
|
14908
15142
|
if (this.config.source === "generated" !== (this.record.token !== null))
|
|
14909
15143
|
throw new ContractError("corrupt-selected");
|
|
14910
|
-
if (this.config.path) {
|
|
15144
|
+
if (this.config.source === "file" && this.config.path) {
|
|
14911
15145
|
const loaded = readExternal(this.config.path);
|
|
14912
15146
|
this.external = loaded.files;
|
|
14913
15147
|
this.externalFile = loaded.file;
|
|
14914
15148
|
this.secret = loaded.token;
|
|
14915
15149
|
} else
|
|
14916
|
-
this.secret =
|
|
15150
|
+
this.secret = accessToken(this.record.token ?? source.environment ?? source.token);
|
|
14917
15151
|
this.configFile = configFile;
|
|
14918
15152
|
this.accessFile = accessFile;
|
|
14919
15153
|
}
|
|
@@ -14953,9 +15187,9 @@ class AccessMaterial {
|
|
|
14953
15187
|
assert() {
|
|
14954
15188
|
if (this.fenced)
|
|
14955
15189
|
throw new ContractError("quarantine");
|
|
14956
|
-
this.files.assert(
|
|
14957
|
-
this.files.assert(
|
|
14958
|
-
if (this.config.path)
|
|
15190
|
+
this.files.assert(join11(this.directory, "access-config.json"), this.configFile);
|
|
15191
|
+
this.files.assert(join11(this.directory, "access.json"), this.accessFile);
|
|
15192
|
+
if (this.config.source === "file" && this.config.path)
|
|
14959
15193
|
this.external?.assert(this.config.path, this.externalFile, TOKEN_BYTES);
|
|
14960
15194
|
}
|
|
14961
15195
|
redactionLiterals() {
|
|
@@ -14976,19 +15210,27 @@ class AccessMaterial {
|
|
|
14976
15210
|
rotate() {
|
|
14977
15211
|
if (this.config.source === "environment")
|
|
14978
15212
|
throw new ContractError("unsupported-context");
|
|
15213
|
+
let configured;
|
|
15214
|
+
if (this.config.source === "config" && this.config.path) {
|
|
15215
|
+
configured = readServerConfig(this.config.path);
|
|
15216
|
+
if (!sameSecret(configured.config.token, this.secret))
|
|
15217
|
+
throw new ContractError("conflict");
|
|
15218
|
+
}
|
|
14979
15219
|
try {
|
|
14980
15220
|
this.assertRotation();
|
|
14981
15221
|
const secret = randomBytes3(32).toString("base64url");
|
|
14982
15222
|
const generation = integer(this.record.generation + 1, 1);
|
|
14983
15223
|
if (this.config.path && this.external && this.externalFile)
|
|
14984
15224
|
this.externalFile = this.external.replace(this.config.path, this.externalFile, secret, TOKEN_BYTES);
|
|
15225
|
+
if (configured)
|
|
15226
|
+
configured.files.replace(configured.real, configured.snapshot, withToken(configured, secret), CONFIG_BYTES);
|
|
14985
15227
|
const record2 = {
|
|
14986
15228
|
...this.record,
|
|
14987
15229
|
generation,
|
|
14988
15230
|
token: this.config.source === "generated" ? secret : null
|
|
14989
15231
|
};
|
|
14990
15232
|
this.assertRotation();
|
|
14991
|
-
this.accessFile = this.files.replace(
|
|
15233
|
+
this.accessFile = this.files.replace(join11(this.directory, "access.json"), this.accessFile, encoded(record2));
|
|
14992
15234
|
this.assertRotation();
|
|
14993
15235
|
this.record = record2;
|
|
14994
15236
|
this.secret = secret;
|
|
@@ -15003,7 +15245,7 @@ class AccessMaterial {
|
|
|
15003
15245
|
}
|
|
15004
15246
|
}
|
|
15005
15247
|
function readExternal(path) {
|
|
15006
|
-
const files = new PrivateFiles(
|
|
15248
|
+
const files = new PrivateFiles(dirname8(path));
|
|
15007
15249
|
const file = files.read(path, TOKEN_BYTES);
|
|
15008
15250
|
if (!file)
|
|
15009
15251
|
throw new ContractError("corrupt-selected");
|
|
@@ -15011,7 +15253,7 @@ function readExternal(path) {
|
|
|
15011
15253
|
return {
|
|
15012
15254
|
files,
|
|
15013
15255
|
file,
|
|
15014
|
-
token:
|
|
15256
|
+
token: accessToken(new TextDecoder("utf-8", { fatal: true }).decode(file.bytes))
|
|
15015
15257
|
};
|
|
15016
15258
|
} catch {
|
|
15017
15259
|
throw new ContractError("corrupt-selected");
|
|
@@ -15322,9 +15564,9 @@ class AuthNetwork {
|
|
|
15322
15564
|
|
|
15323
15565
|
// src/service/coordination.ts
|
|
15324
15566
|
import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
|
|
15325
|
-
import { opendirSync as opendirSync2, realpathSync as
|
|
15567
|
+
import { opendirSync as opendirSync2, realpathSync as realpathSync4 } from "node:fs";
|
|
15326
15568
|
import { hostname as hostname2 } from "node:os";
|
|
15327
|
-
import { dirname as
|
|
15569
|
+
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join12, relative as relative4, sep as sep4 } from "node:path";
|
|
15328
15570
|
var registrationKeys = [
|
|
15329
15571
|
"schema",
|
|
15330
15572
|
"root",
|
|
@@ -15450,17 +15692,17 @@ class ServiceCoordination {
|
|
|
15450
15692
|
stateHome;
|
|
15451
15693
|
constructor(workspace, stateHome) {
|
|
15452
15694
|
this.store = new TaskStore(resolveWorkspace(workspace).root, stateHome);
|
|
15453
|
-
const namespace =
|
|
15695
|
+
const namespace = dirname9(this.store.privateRoot);
|
|
15454
15696
|
this.files = new PrivateFiles(namespace);
|
|
15455
|
-
this.stateHome =
|
|
15456
|
-
this.directory =
|
|
15457
|
-
this.registryDirectory =
|
|
15697
|
+
this.stateHome = dirname9(namespace);
|
|
15698
|
+
this.directory = join12(this.store.privateRoot, "service-v1");
|
|
15699
|
+
this.registryDirectory = join12(namespace, "service-registry-v1");
|
|
15458
15700
|
}
|
|
15459
15701
|
path(name) {
|
|
15460
|
-
return
|
|
15702
|
+
return join12(this.directory, name);
|
|
15461
15703
|
}
|
|
15462
15704
|
registryPath(name) {
|
|
15463
|
-
return
|
|
15705
|
+
return join12(this.registryDirectory, name);
|
|
15464
15706
|
}
|
|
15465
15707
|
registry() {
|
|
15466
15708
|
if (!this.files.directory(this.registryDirectory))
|
|
@@ -15480,7 +15722,7 @@ class ServiceCoordination {
|
|
|
15480
15722
|
const o = closed(json(snapshot, REGISTRY_BYTES), ["schema", "entries"]);
|
|
15481
15723
|
const entries = list(o.entries, REGISTRY_ENTRIES, decodeReservation);
|
|
15482
15724
|
for (const [i, row] of entries.entries()) {
|
|
15483
|
-
if (row.stateHome !== this.stateHome ||
|
|
15725
|
+
if (row.stateHome !== this.stateHome || realpathSync4(row.root) !== row.root)
|
|
15484
15726
|
refused();
|
|
15485
15727
|
if (entries.slice(0, i).some((other) => rootsOverlap(row.root, other.root) || row.endpoint === other.endpoint))
|
|
15486
15728
|
refused();
|
|
@@ -15508,7 +15750,7 @@ class ServiceCoordination {
|
|
|
15508
15750
|
"host-config.json",
|
|
15509
15751
|
"history-v2"
|
|
15510
15752
|
]);
|
|
15511
|
-
this.files.directory(
|
|
15753
|
+
this.files.directory(join12(this.directory, "history-v2"), false, walks);
|
|
15512
15754
|
}
|
|
15513
15755
|
selected() {
|
|
15514
15756
|
const walks = new DirectoryWalks;
|
|
@@ -15668,7 +15910,7 @@ class ServiceCoordination {
|
|
|
15668
15910
|
throw error;
|
|
15669
15911
|
}
|
|
15670
15912
|
}
|
|
15671
|
-
acquire(input, resolvedEndpoint = input.endpoint, initializeNew) {
|
|
15913
|
+
acquire(input, resolvedEndpoint = input.endpoint, initializeNew, reselectExisting) {
|
|
15672
15914
|
const config = decodeOwnerConfig(input);
|
|
15673
15915
|
const bound = endpoint(resolvedEndpoint);
|
|
15674
15916
|
if (!endpointMatches(config.endpoint, bound))
|
|
@@ -15683,7 +15925,8 @@ class ServiceCoordination {
|
|
|
15683
15925
|
throw new ContractError("quarantine");
|
|
15684
15926
|
if (this.store.inspectLock())
|
|
15685
15927
|
throw new ContractError("conflict");
|
|
15686
|
-
|
|
15928
|
+
const reselect = !!old && old.config.identity !== hash(encoded(config));
|
|
15929
|
+
if (reselect && (!reselectExisting || !old?.hostConfig))
|
|
15687
15930
|
throw new ContractError("conflict");
|
|
15688
15931
|
const row = {
|
|
15689
15932
|
root: this.store.root,
|
|
@@ -15700,7 +15943,17 @@ class ServiceCoordination {
|
|
|
15700
15943
|
...registry.value,
|
|
15701
15944
|
entries: [...registry.value.entries, row]
|
|
15702
15945
|
}), REGISTRY_BYTES);
|
|
15703
|
-
|
|
15946
|
+
if (old?.hostConfig && reselect && reselectExisting) {
|
|
15947
|
+
reselectExisting(old.hostConfig);
|
|
15948
|
+
const saved = this.files.replace(this.path("config.json"), old.config, encoded(config));
|
|
15949
|
+
this.files.replace(this.path("format.json"), old.manifest, encoded({
|
|
15950
|
+
schema: "cadence-service-format/1",
|
|
15951
|
+
root: this.store.root,
|
|
15952
|
+
stateHome: this.stateHome,
|
|
15953
|
+
configIdentity: saved.identity
|
|
15954
|
+
}));
|
|
15955
|
+
} else
|
|
15956
|
+
this.initialize(config);
|
|
15704
15957
|
if (!old)
|
|
15705
15958
|
initializeNew?.();
|
|
15706
15959
|
const checkout = this.store.acquire();
|
|
@@ -16148,10 +16401,10 @@ function decodeSocketInput(value) {
|
|
|
16148
16401
|
}
|
|
16149
16402
|
|
|
16150
16403
|
// src/service/http.ts
|
|
16151
|
-
function refusal(
|
|
16404
|
+
function refusal(code2 = "invalid", uncertain = false) {
|
|
16152
16405
|
return {
|
|
16153
16406
|
kind: "error",
|
|
16154
|
-
error: publicError(
|
|
16407
|
+
error: publicError(code2, uncertain ? "uncertain" : "none")
|
|
16155
16408
|
};
|
|
16156
16409
|
}
|
|
16157
16410
|
function fixedHeaders(response) {
|
|
@@ -16463,7 +16716,7 @@ Content-Length: 0\r
|
|
|
16463
16716
|
}
|
|
16464
16717
|
if (target2 === "/" && req.method === "GET") {
|
|
16465
16718
|
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
|
|
16719
|
+
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
16720
|
return;
|
|
16468
16721
|
}
|
|
16469
16722
|
if (target2.startsWith("/local/v1/admin/")) {
|
|
@@ -16544,9 +16797,9 @@ Content-Length: 0\r
|
|
|
16544
16797
|
unavailable();
|
|
16545
16798
|
if (route.operation === "session")
|
|
16546
16799
|
this.attachment(caller);
|
|
16547
|
-
const
|
|
16800
|
+
const decoded2 = decodeHttpOutput(route.operation, value);
|
|
16548
16801
|
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,
|
|
16802
|
+
send(res, status, decoded2);
|
|
16550
16803
|
} catch (e) {
|
|
16551
16804
|
if (createdAttachment)
|
|
16552
16805
|
this.detach(createdAttachment);
|
|
@@ -16555,9 +16808,9 @@ Content-Length: 0\r
|
|
|
16555
16808
|
if (res.headersSent)
|
|
16556
16809
|
res.destroy();
|
|
16557
16810
|
else {
|
|
16558
|
-
const
|
|
16811
|
+
const code2 = e instanceof ContractError ? e.code : "unavailable";
|
|
16559
16812
|
res.setHeader("Connection", "close");
|
|
16560
|
-
send(res, errorStatus(
|
|
16813
|
+
send(res, errorStatus(code2), refusal(code2, dispatched));
|
|
16561
16814
|
}
|
|
16562
16815
|
} finally {
|
|
16563
16816
|
clearTimeout(timer);
|
|
@@ -17033,9 +17286,9 @@ class OwnerModelChoice {
|
|
|
17033
17286
|
}
|
|
17034
17287
|
|
|
17035
17288
|
// src/service/model-config.ts
|
|
17036
|
-
import { lstatSync as
|
|
17037
|
-
import { dirname as
|
|
17038
|
-
var
|
|
17289
|
+
import { lstatSync as lstatSync9, realpathSync as realpathSync5 } from "node:fs";
|
|
17290
|
+
import { dirname as dirname10, isAbsolute as isAbsolute6, join as join13, resolve as resolve3 } from "node:path";
|
|
17291
|
+
var CONFIG_BYTES2 = 1024 * 1024;
|
|
17039
17292
|
var ENTRY_LIMIT = 256;
|
|
17040
17293
|
function refuse2() {
|
|
17041
17294
|
throw new ContractError("corrupt-selected");
|
|
@@ -17092,7 +17345,7 @@ function captureEnvironment(value) {
|
|
|
17092
17345
|
if (typeof name !== "string" || !d || !("value" in d) || d.value !== undefined && typeof d.value !== "string")
|
|
17093
17346
|
refuse2();
|
|
17094
17347
|
bytes += Buffer.byteLength(name) + Buffer.byteLength(d.value ?? "");
|
|
17095
|
-
if (bytes >
|
|
17348
|
+
if (bytes > CONFIG_BYTES2)
|
|
17096
17349
|
throw new ContractError("oversize");
|
|
17097
17350
|
out[name] = d.value;
|
|
17098
17351
|
}
|
|
@@ -17101,14 +17354,14 @@ function captureEnvironment(value) {
|
|
|
17101
17354
|
function piSource(agentDir, home, cwd, env) {
|
|
17102
17355
|
if (!isAbsolute6(home) || !isAbsolute6(cwd))
|
|
17103
17356
|
refuse2();
|
|
17104
|
-
let selected2 = agentDir ?? env.PI_CODING_AGENT_DIR ??
|
|
17357
|
+
let selected2 = agentDir ?? env.PI_CODING_AGENT_DIR ?? join13(home, ".pi", "agent");
|
|
17105
17358
|
if (!selected2 || selected2.includes("\x00"))
|
|
17106
17359
|
refuse2();
|
|
17107
17360
|
if (selected2 === "~" || selected2.startsWith("~/"))
|
|
17108
17361
|
selected2 = home + selected2.slice(1);
|
|
17109
17362
|
selected2 = resolve3(cwd, selected2);
|
|
17110
|
-
const directory =
|
|
17111
|
-
if (!
|
|
17363
|
+
const directory = join13(realpathSync5(dirname10(selected2)), selected2.slice(dirname10(selected2).length + 1));
|
|
17364
|
+
if (!lstatSync9(directory).isDirectory() || lstatSync9(directory).isSymbolicLink())
|
|
17112
17365
|
refuse2();
|
|
17113
17366
|
const files = new PrivateFiles(directory, false);
|
|
17114
17367
|
if (!files.directory(directory))
|
|
@@ -17116,8 +17369,8 @@ function piSource(agentDir, home, cwd, env) {
|
|
|
17116
17369
|
return {
|
|
17117
17370
|
directory,
|
|
17118
17371
|
files,
|
|
17119
|
-
authPath:
|
|
17120
|
-
modelsPath:
|
|
17372
|
+
authPath: join13(directory, "auth.json"),
|
|
17373
|
+
modelsPath: join13(directory, "models.json")
|
|
17121
17374
|
};
|
|
17122
17375
|
}
|
|
17123
17376
|
function templateNames(value) {
|
|
@@ -17396,7 +17649,7 @@ function modelJson(snapshot) {
|
|
|
17396
17649
|
} else if (char === '"')
|
|
17397
17650
|
quoted = true;
|
|
17398
17651
|
}
|
|
17399
|
-
return parseWireJson(normalized,
|
|
17652
|
+
return parseWireJson(normalized, CONFIG_BYTES2);
|
|
17400
17653
|
}
|
|
17401
17654
|
function readPiSnapshot(source) {
|
|
17402
17655
|
const { auth, credentials } = readPiAuth(source);
|
|
@@ -17404,11 +17657,11 @@ function readPiSnapshot(source) {
|
|
|
17404
17657
|
return { auth, models, credentials, providers };
|
|
17405
17658
|
}
|
|
17406
17659
|
function readPiAuth(source) {
|
|
17407
|
-
const auth = source.files.read(source.authPath,
|
|
17660
|
+
const auth = source.files.read(source.authPath, CONFIG_BYTES2);
|
|
17408
17661
|
if (!auth)
|
|
17409
17662
|
refuse2();
|
|
17410
17663
|
const credentials = Object.create(null);
|
|
17411
|
-
for (const [provider, input] of Object.entries(data(json(auth,
|
|
17664
|
+
for (const [provider, input] of Object.entries(data(json(auth, CONFIG_BYTES2)))) {
|
|
17412
17665
|
const credential = data(input);
|
|
17413
17666
|
if (credential.type === "api_key") {
|
|
17414
17667
|
keys2(credential, ["type", "key", "env"]);
|
|
@@ -17430,7 +17683,7 @@ function readPiAuth(source) {
|
|
|
17430
17683
|
return { auth, credentials };
|
|
17431
17684
|
}
|
|
17432
17685
|
function readPiModels(source) {
|
|
17433
|
-
const models = source.files.read(source.modelsPath,
|
|
17686
|
+
const models = source.files.read(source.modelsPath, CONFIG_BYTES2);
|
|
17434
17687
|
const providers = Object.create(null);
|
|
17435
17688
|
if (models) {
|
|
17436
17689
|
const root = data(modelJson(models));
|
|
@@ -17554,9 +17807,9 @@ function clonePrivate(value) {
|
|
|
17554
17807
|
return out;
|
|
17555
17808
|
};
|
|
17556
17809
|
const encoded2 = JSON.stringify(visit(value, 0));
|
|
17557
|
-
if (Buffer.byteLength(encoded2) >
|
|
17810
|
+
if (Buffer.byteLength(encoded2) > CONFIG_BYTES2)
|
|
17558
17811
|
throw new ContractError("oversize");
|
|
17559
|
-
return parseWireJson(encoded2,
|
|
17812
|
+
return parseWireJson(encoded2, CONFIG_BYTES2);
|
|
17560
17813
|
}
|
|
17561
17814
|
|
|
17562
17815
|
// src/service/model-context.ts
|
|
@@ -18510,7 +18763,7 @@ class ServiceOwner {
|
|
|
18510
18763
|
}
|
|
18511
18764
|
|
|
18512
18765
|
// src/service/runtime.ts
|
|
18513
|
-
import { dirname as
|
|
18766
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join14, resolve as resolve4 } from "node:path";
|
|
18514
18767
|
var STARTUP_INPUT_BYTES = 16 * 1024;
|
|
18515
18768
|
function absolute(value) {
|
|
18516
18769
|
const result = text(value, 4096, true);
|
|
@@ -18530,10 +18783,11 @@ function decodeHostConfig(value) {
|
|
|
18530
18783
|
const tokenSource = oneOf(o.tokenSource, [
|
|
18531
18784
|
"generated",
|
|
18532
18785
|
"environment",
|
|
18533
|
-
"file"
|
|
18786
|
+
"file",
|
|
18787
|
+
"config"
|
|
18534
18788
|
]);
|
|
18535
18789
|
const tokenFile = o.tokenFile === null ? null : absolute(o.tokenFile);
|
|
18536
|
-
if (
|
|
18790
|
+
if ((tokenSource === "file" || tokenSource === "config") !== !!tokenFile)
|
|
18537
18791
|
throw new ContractError("invalid");
|
|
18538
18792
|
return {
|
|
18539
18793
|
schema: literal(o.schema, "cadence-service-host/1"),
|
|
@@ -18544,6 +18798,15 @@ function decodeHostConfig(value) {
|
|
|
18544
18798
|
tokenFile
|
|
18545
18799
|
};
|
|
18546
18800
|
}
|
|
18801
|
+
function reselectable(old, next) {
|
|
18802
|
+
const kept = (c) => encoded({
|
|
18803
|
+
...c,
|
|
18804
|
+
http: { ...c.http, port: 0 },
|
|
18805
|
+
tokenSource: null,
|
|
18806
|
+
tokenFile: null
|
|
18807
|
+
});
|
|
18808
|
+
return next.tokenSource === "config" && kept(old) === kept(next);
|
|
18809
|
+
}
|
|
18547
18810
|
function readHostConfig(c) {
|
|
18548
18811
|
const owner = c.configuration();
|
|
18549
18812
|
if (!owner)
|
|
@@ -18607,8 +18870,8 @@ function decodeHostInput(value) {
|
|
|
18607
18870
|
class StartupRefusal extends ContractError {
|
|
18608
18871
|
stage;
|
|
18609
18872
|
quarantined;
|
|
18610
|
-
constructor(
|
|
18611
|
-
super(
|
|
18873
|
+
constructor(code2, stage, quarantined = false) {
|
|
18874
|
+
super(code2);
|
|
18612
18875
|
this.stage = stage;
|
|
18613
18876
|
this.quarantined = quarantined;
|
|
18614
18877
|
}
|
|
@@ -18629,13 +18892,13 @@ function startupReport(error) {
|
|
|
18629
18892
|
|
|
18630
18893
|
// src/service/task-actions.ts
|
|
18631
18894
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
18632
|
-
import { realpathSync as
|
|
18895
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
18633
18896
|
|
|
18634
18897
|
// src/task-diff.ts
|
|
18635
18898
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
18636
18899
|
import { createHash as createHash20 } from "node:crypto";
|
|
18637
|
-
import { lstatSync as
|
|
18638
|
-
import { join as
|
|
18900
|
+
import { lstatSync as lstatSync10, readdirSync as readdirSync3, readlinkSync } from "node:fs";
|
|
18901
|
+
import { join as join15, relative as relative5 } from "node:path";
|
|
18639
18902
|
var admin = (path) => path === ".cadence/assistant/tasks" || path.startsWith(".cadence/assistant/tasks/");
|
|
18640
18903
|
function captureChanges(root) {
|
|
18641
18904
|
const deadline = Date.now() + 3000;
|
|
@@ -18676,8 +18939,8 @@ function captureChanges(root) {
|
|
|
18676
18939
|
return;
|
|
18677
18940
|
}
|
|
18678
18941
|
try {
|
|
18679
|
-
const path =
|
|
18680
|
-
const st =
|
|
18942
|
+
const path = join15(root, name);
|
|
18943
|
+
const st = lstatSync10(path);
|
|
18681
18944
|
add(name, `${st.mode}:${st.size}`);
|
|
18682
18945
|
if (st.isSymbolicLink()) {
|
|
18683
18946
|
add(name, readlinkSync(path));
|
|
@@ -18812,7 +19075,7 @@ ${data2.toString("utf8")}`);
|
|
|
18812
19075
|
return;
|
|
18813
19076
|
}
|
|
18814
19077
|
for (const name of names2) {
|
|
18815
|
-
const path =
|
|
19078
|
+
const path = join15(dir, name);
|
|
18816
19079
|
const rel = relative5(root, path).split("\\").join("/");
|
|
18817
19080
|
if (rel === ".git" || admin(rel))
|
|
18818
19081
|
continue;
|
|
@@ -18821,7 +19084,7 @@ ${data2.toString("utf8")}`);
|
|
|
18821
19084
|
break;
|
|
18822
19085
|
}
|
|
18823
19086
|
try {
|
|
18824
|
-
const st =
|
|
19087
|
+
const st = lstatSync10(path);
|
|
18825
19088
|
if (st.isDirectory())
|
|
18826
19089
|
walk(path, depth + 1);
|
|
18827
19090
|
else
|
|
@@ -19108,9 +19371,9 @@ class ActionAdmission {
|
|
|
19108
19371
|
});
|
|
19109
19372
|
}
|
|
19110
19373
|
existing(record2, commandId, requested) {
|
|
19111
|
-
const
|
|
19112
|
-
const receipt =
|
|
19113
|
-
if (receipt.commandId !== commandId || canonicalJson(
|
|
19374
|
+
const decoded2 = decodeStoredCommand(record2);
|
|
19375
|
+
const receipt = decoded2.receipt;
|
|
19376
|
+
if (receipt.commandId !== commandId || canonicalJson(decoded2.identity) !== canonicalJson(requested))
|
|
19114
19377
|
return error(publicError("conflict"));
|
|
19115
19378
|
return ok(receipt);
|
|
19116
19379
|
}
|
|
@@ -19375,7 +19638,7 @@ class ActionAdmission {
|
|
|
19375
19638
|
|
|
19376
19639
|
// src/service/graph.ts
|
|
19377
19640
|
import { createHash as createHash21 } from "node:crypto";
|
|
19378
|
-
import { join as
|
|
19641
|
+
import { join as join16 } from "node:path";
|
|
19379
19642
|
var hash3 = (text2) => createHash21("sha256").update(text2).digest("hex");
|
|
19380
19643
|
|
|
19381
19644
|
class GraphContext {
|
|
@@ -19385,7 +19648,7 @@ class GraphContext {
|
|
|
19385
19648
|
identity;
|
|
19386
19649
|
constructor(root) {
|
|
19387
19650
|
this.root = root;
|
|
19388
|
-
const bytes = readBounded(
|
|
19651
|
+
const bytes = readBounded(join16(root, "AGENTS.md"), 32768);
|
|
19389
19652
|
this.hints = bytes ? new TextDecoder("utf-8", { fatal: true }).decode(bytes) : "";
|
|
19390
19653
|
this.identity = hash3(canonicalJson({
|
|
19391
19654
|
hints: bytes === undefined ? null : hash3(bytes),
|
|
@@ -19522,7 +19785,7 @@ import { randomUUID as randomUUID14 } from "node:crypto";
|
|
|
19522
19785
|
|
|
19523
19786
|
// src/subagent-process.ts
|
|
19524
19787
|
import { spawn } from "node:child_process";
|
|
19525
|
-
import { join as
|
|
19788
|
+
import { join as join17 } from "node:path";
|
|
19526
19789
|
import { StringDecoder } from "node:string_decoder";
|
|
19527
19790
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
19528
19791
|
// src/worker-capture.mjs
|
|
@@ -20107,10 +20370,10 @@ class WorkerRecording {
|
|
|
20107
20370
|
// src/subagent-process.ts
|
|
20108
20371
|
function terminateWindowsTree(pid, fallback) {
|
|
20109
20372
|
const root = process.env.SystemRoot;
|
|
20110
|
-
const taskkill = spawn(root ?
|
|
20373
|
+
const taskkill = spawn(root ? join17(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
20111
20374
|
taskkill.once("error", fallback);
|
|
20112
|
-
taskkill.once("close", (
|
|
20113
|
-
if (
|
|
20375
|
+
taskkill.once("close", (code2) => {
|
|
20376
|
+
if (code2 !== 0)
|
|
20114
20377
|
fallback();
|
|
20115
20378
|
});
|
|
20116
20379
|
}
|
|
@@ -20260,17 +20523,17 @@ function runProcess(spec) {
|
|
|
20260
20523
|
const value = parseCaptureLine(line);
|
|
20261
20524
|
if (!capture)
|
|
20262
20525
|
capture = new CaptureState(spec.childRunId ?? decodeCaptureFrame(value).childRunId, spec.expectedRequest);
|
|
20263
|
-
const
|
|
20264
|
-
if (
|
|
20526
|
+
const decoded2 = capture.accept(value, Buffer.byteLength(line) + 1);
|
|
20527
|
+
if (decoded2.event.kind === "ready") {
|
|
20265
20528
|
ready2 = true;
|
|
20266
20529
|
spec.onProgress?.("Agent ready");
|
|
20267
20530
|
}
|
|
20268
|
-
if (
|
|
20269
|
-
spec.onProgress?.(clean(`Tool: ${
|
|
20270
|
-
if (
|
|
20531
|
+
if (decoded2.event.kind === "tool-start")
|
|
20532
|
+
spec.onProgress?.(clean(`Tool: ${decoded2.event.name}`, 128));
|
|
20533
|
+
if (decoded2.event.kind === "final")
|
|
20271
20534
|
final = capture.preview;
|
|
20272
20535
|
if (lease) {
|
|
20273
|
-
spec.recording?.accept(
|
|
20536
|
+
spec.recording?.accept(decoded2, lease);
|
|
20274
20537
|
lease = undefined;
|
|
20275
20538
|
}
|
|
20276
20539
|
} catch {
|
|
@@ -20372,7 +20635,7 @@ function runProcess(spec) {
|
|
|
20372
20635
|
child.on("error", () => {
|
|
20373
20636
|
stopping = "failed";
|
|
20374
20637
|
});
|
|
20375
|
-
child.on("close", (
|
|
20638
|
+
child.on("close", (code2, signal) => {
|
|
20376
20639
|
nativeClosed = true;
|
|
20377
20640
|
clearTimeout(timeout);
|
|
20378
20641
|
clearTimeout(killTimer);
|
|
@@ -20388,7 +20651,7 @@ function runProcess(spec) {
|
|
|
20388
20651
|
}
|
|
20389
20652
|
}
|
|
20390
20653
|
output += stderrDecoder.end();
|
|
20391
|
-
finish(true,
|
|
20654
|
+
finish(true, code2, signal);
|
|
20392
20655
|
};
|
|
20393
20656
|
if (spec.checkRecording) {
|
|
20394
20657
|
Promise.all([stdoutWork, stderrWork]).then(async () => {
|
|
@@ -21641,9 +21904,9 @@ class TaskActions {
|
|
|
21641
21904
|
selection: structuredClone(options.selection),
|
|
21642
21905
|
ownerSecrets: [...options.ownerSecrets]
|
|
21643
21906
|
};
|
|
21644
|
-
this.cwd =
|
|
21907
|
+
this.cwd = realpathSync6(options.cwd);
|
|
21645
21908
|
const config = readHostConfig(options.host.coordination);
|
|
21646
|
-
if (!config ||
|
|
21909
|
+
if (!config || realpathSync6(config.cwd) !== this.cwd)
|
|
21647
21910
|
throw new ContractError("unsupported-context");
|
|
21648
21911
|
this.launchCwd = config.cwd;
|
|
21649
21912
|
this.checkEnvironment = options.host.checkEnvironment;
|
|
@@ -21759,7 +22022,7 @@ class TaskActions {
|
|
|
21759
22022
|
if (this.closed || this.options.host.owner.state !== "idle" || this.options.host.writer.quarantined)
|
|
21760
22023
|
throw new ContractError("quarantine");
|
|
21761
22024
|
this.options.host.owner.lease.assert();
|
|
21762
|
-
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)
|
|
21763
22026
|
throw new ContractError("stale");
|
|
21764
22027
|
}
|
|
21765
22028
|
choiceSnapshot() {
|
|
@@ -21877,7 +22140,7 @@ class TaskActions {
|
|
|
21877
22140
|
const selected2 = taskId ? h.coordination.store.loadSnapshot(taskId) : null;
|
|
21878
22141
|
if (taskId && !selected2)
|
|
21879
22142
|
throw new ContractError("stale");
|
|
21880
|
-
if (selected2 &&
|
|
22143
|
+
if (selected2 && realpathSync6(selected2.record.cwd) !== this.cwd)
|
|
21881
22144
|
throw new ContractError("unsupported-context");
|
|
21882
22145
|
const secrets = this.viewSecrets();
|
|
21883
22146
|
const roles = this.sources.roles.map(({ role: role2, sha256 }) => ({
|
|
@@ -22381,7 +22644,7 @@ class TaskActions {
|
|
|
22381
22644
|
this.assertChoice(expected);
|
|
22382
22645
|
held.assertHeld();
|
|
22383
22646
|
const actual = h.coordination.store.loadSnapshot(task.id), want = published ?? task;
|
|
22384
|
-
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))
|
|
22385
22648
|
throw new ContractError("stale");
|
|
22386
22649
|
const changes2 = captureChanges(h.coordination.store.root);
|
|
22387
22650
|
if (changes2.fingerprint !== expected.observation.identity || changes2.complete !== expected.observation.complete)
|
|
@@ -22585,8 +22848,8 @@ function legacy(store, budget, reader) {
|
|
|
22585
22848
|
decodeId(entry.name);
|
|
22586
22849
|
if (created.has(entry.name))
|
|
22587
22850
|
continue;
|
|
22588
|
-
const path =
|
|
22589
|
-
const size =
|
|
22851
|
+
const path = join18(store.tasks, entry.name, "task.json");
|
|
22852
|
+
const size = lstatSync11(path).size;
|
|
22590
22853
|
if (size > 128 * 1024)
|
|
22591
22854
|
throw new ContractError("corrupt-selected");
|
|
22592
22855
|
budget.reserve(size);
|
|
@@ -22632,12 +22895,19 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22632
22895
|
if (resolveWorkspace(input.config.cwd).root !== c.store.root)
|
|
22633
22896
|
throw new ContractError("conflict");
|
|
22634
22897
|
const old = readHostConfig(c);
|
|
22635
|
-
|
|
22898
|
+
const reselect = !!old && encoded(old) !== encoded(input.config);
|
|
22899
|
+
if (old && reselect && !reselectable(old, input.config))
|
|
22900
|
+
throw new ContractError("conflict");
|
|
22901
|
+
const configured = input.config.tokenSource === "config" && input.config.tokenFile ? readServerConfig(input.config.tokenFile).config : undefined;
|
|
22902
|
+
if (configured && configured.port !== input.config.http.port)
|
|
22636
22903
|
throw new ContractError("conflict");
|
|
22637
|
-
const source = input.environmentToken !== null ? { environment: input.environmentToken } : input.config.tokenFile ? { tokenFile: input.config.tokenFile } : {};
|
|
22904
|
+
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
22905
|
AccessMaterial.validateSource(source);
|
|
22639
22906
|
if (old) {
|
|
22640
|
-
|
|
22907
|
+
if (reselect)
|
|
22908
|
+
AccessMaterial.validateReselection(c.files, c.directory, source);
|
|
22909
|
+
else
|
|
22910
|
+
new AccessMaterial(c.files, c.directory, source);
|
|
22641
22911
|
const history = HistoryFiles.open(c);
|
|
22642
22912
|
if (!history)
|
|
22643
22913
|
throw new ContractError("corrupt-selected");
|
|
@@ -22666,7 +22936,12 @@ async function createServiceHost(value, sourceBuild) {
|
|
|
22666
22936
|
}, endpoint2, () => {
|
|
22667
22937
|
c.files.create(c.path("host-config.json"), encoded(resolved));
|
|
22668
22938
|
AccessMaterial.initialize(c.files, c.directory, source);
|
|
22669
|
-
})
|
|
22939
|
+
}, reselect && old ? (previous) => {
|
|
22940
|
+
if (previous.identity !== hash(encoded(old)))
|
|
22941
|
+
throw new ContractError("conflict");
|
|
22942
|
+
AccessMaterial.reselect(c.files, c.directory, source);
|
|
22943
|
+
c.files.replace(c.path("host-config.json"), previous, encoded(resolved));
|
|
22944
|
+
} : undefined);
|
|
22670
22945
|
let writer, live;
|
|
22671
22946
|
owner = new ServiceOwner(lease, {
|
|
22672
22947
|
quiesce: () => http.quiesce(),
|