@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.
@@ -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 join17 } from "node:path";
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 dirname7, isAbsolute as isAbsolute4, join as join10 } from "node:path";
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/auth.ts
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 token(value) {
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
- if (source.environment !== undefined && source.tokenFile !== undefined)
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
- token(source.environment);
14849
- if (source.tokenFile !== undefined && (!isAbsolute4(source.tokenFile) || source.tokenFile.includes("\x00")))
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: source.tokenFile ?? null
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
- const config = sourceConfig(source);
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 = join10(directory, "access-config.json"), access = join10(directory, "access.json");
14879
- if (files.read(marker2) || files.read(access) || files.read(join10(directory, "registration.json")) || files.read(join10(directory, "owner.lock")))
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
- if (config.path)
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(join10(directory, "access-config.json"));
14896
- const accessFile = files.read(join10(directory, "access.json"));
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 : token(r.token)
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 = token(this.record.token ?? source.environment);
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(join10(this.directory, "access-config.json"), this.configFile);
14957
- this.files.assert(join10(this.directory, "access.json"), this.accessFile);
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(join10(this.directory, "access.json"), this.accessFile, encoded(record2));
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(dirname7(path));
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: token(new TextDecoder("utf-8", { fatal: true }).decode(file.bytes))
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 dirname8, isAbsolute as isAbsolute5, join as join11, relative as relative4, sep as sep4 } from "node:path";
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 = dirname8(this.store.privateRoot);
15614
+ const namespace = dirname9(this.store.privateRoot);
15454
15615
  this.files = new PrivateFiles(namespace);
15455
- this.stateHome = dirname8(namespace);
15456
- this.directory = join11(this.store.privateRoot, "service-v1");
15457
- this.registryDirectory = join11(namespace, "service-registry-v1");
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 join11(this.directory, name);
15621
+ return join12(this.directory, name);
15461
15622
  }
15462
15623
  registryPath(name) {
15463
- return join11(this.registryDirectory, name);
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(join11(this.directory, "history-v2"), false, walks);
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
- if (old && old.config.identity !== hash(encoded(config)))
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
- this.initialize(config);
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 (the `cadence server install` service uses ~/cadence by default).\n");
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 decoded = decodeHttpOutput(route.operation, value);
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, decoded);
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 dirname9, isAbsolute as isAbsolute6, join as join12, resolve as resolve3 } from "node:path";
17038
- var CONFIG_BYTES = 1024 * 1024;
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 > CONFIG_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 ?? join12(home, ".pi", "agent");
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 = join12(realpathSync4(dirname9(selected2)), selected2.slice(dirname9(selected2).length + 1));
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: join12(directory, "auth.json"),
17120
- modelsPath: join12(directory, "models.json")
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, CONFIG_BYTES);
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, CONFIG_BYTES);
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, CONFIG_BYTES)))) {
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, CONFIG_BYTES);
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) > CONFIG_BYTES)
17729
+ if (Buffer.byteLength(encoded2) > CONFIG_BYTES2)
17558
17730
  throw new ContractError("oversize");
17559
- return parseWireJson(encoded2, CONFIG_BYTES);
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 dirname10, isAbsolute as isAbsolute7, join as join13, resolve as resolve4 } from "node:path";
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 (o.tokenSource === "file" !== (tokenFile !== null))
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 join14, relative as relative5 } from "node:path";
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 = join14(root, name);
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 = join14(dir, name);
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 decoded = decodeStoredCommand(record2);
19112
- const receipt = decoded.receipt;
19113
- if (receipt.commandId !== commandId || canonicalJson(decoded.identity) !== canonicalJson(requested))
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 join15 } from "node:path";
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(join15(root, "AGENTS.md"), 32768);
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 join16 } from "node:path";
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 ? join16(root, "System32", "taskkill.exe") : "taskkill.exe", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
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 decoded = capture.accept(value, Buffer.byteLength(line) + 1);
20264
- if (decoded.event.kind === "ready") {
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 (decoded.event.kind === "tool-start")
20269
- spec.onProgress?.(clean(`Tool: ${decoded.event.name}`, 128));
20270
- if (decoded.event.kind === "final")
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(decoded, lease);
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 = join17(store.tasks, entry.name, "task.json");
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
- if (old && encoded(old) !== encoded(input.config))
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 source = input.environmentToken !== null ? { environment: input.environmentToken } : input.config.tokenFile ? { tokenFile: input.config.tokenFile } : {};
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
- new AccessMaterial(c.files, c.directory, source);
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(),