@factiii/runner 0.12.0 → 0.12.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +72 -48
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6499,7 +6499,7 @@ var require_websocket = __commonJS({
6499
6499
  var http = require("http");
6500
6500
  var net3 = require("net");
6501
6501
  var tls = require("tls");
6502
- var { randomBytes, createHash: createHash2 } = require("crypto");
6502
+ var { randomBytes: randomBytes2, createHash: createHash2 } = require("crypto");
6503
6503
  var { Duplex, Readable } = require("stream");
6504
6504
  var { URL: URL2 } = require("url");
6505
6505
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -7037,7 +7037,7 @@ var require_websocket = __commonJS({
7037
7037
  }
7038
7038
  }
7039
7039
  const defaultPort = isSecure ? 443 : 80;
7040
- const key = randomBytes(16).toString("base64");
7040
+ const key = randomBytes2(16).toString("base64");
7041
7041
  const request = isSecure ? https.request : http.request;
7042
7042
  const protocolSet = /* @__PURE__ */ new Set();
7043
7043
  let perMessageDeflate;
@@ -13805,6 +13805,7 @@ async function teardownSession(s, target) {
13805
13805
 
13806
13806
  // ../../shared/all/helpers/board-agent-core/exec.ts
13807
13807
  var import_child_process2 = require("child_process");
13808
+ var import_crypto = require("crypto");
13808
13809
  var import_fs2 = require("fs");
13809
13810
  var import_promises2 = __toESM(require("fs/promises"));
13810
13811
  var import_net = __toESM(require("net"));
@@ -13845,6 +13846,22 @@ async function allocateSpacePorts(spaceDir) {
13845
13846
  await import_promises2.default.writeFile(portsFilePath(spaceDir), ports.join(","), "utf-8");
13846
13847
  return ports;
13847
13848
  }
13849
+ var SECRET_FILE = ".secret";
13850
+ function spaceSecret(spaceDir) {
13851
+ const file = import_path4.default.join(spaceDir, SECRET_FILE);
13852
+ try {
13853
+ const existing = (0, import_fs2.readFileSync)(file, "utf-8").trim();
13854
+ if (existing) return existing;
13855
+ } catch {
13856
+ }
13857
+ const secret = (0, import_crypto.randomBytes)(24).toString("hex");
13858
+ try {
13859
+ (0, import_fs2.mkdirSync)(spaceDir, { recursive: true });
13860
+ (0, import_fs2.writeFileSync)(file, secret, { encoding: "utf-8", mode: 384 });
13861
+ } catch {
13862
+ }
13863
+ return secret;
13864
+ }
13848
13865
  function readSpacePorts(spaceDir) {
13849
13866
  try {
13850
13867
  return (0, import_fs2.readFileSync)(portsFilePath(spaceDir), "utf-8").split(",").map((p) => Number(p.trim())).filter((p) => Number.isInteger(p) && p > 0);
@@ -16903,7 +16920,7 @@ ${buildPrompt(prompts_default.implement)}`;
16903
16920
  };
16904
16921
 
16905
16922
  // ../../shared/all/helpers/terminal-ai/terminal-mode.ts
16906
- var import_crypto3 = require("crypto");
16923
+ var import_crypto4 = require("crypto");
16907
16924
 
16908
16925
  // ../../shared/all/helpers/board-agent-core/boardTerminal.ts
16909
16926
  function boardTerminalKey(spaceSlug) {
@@ -16959,12 +16976,12 @@ function drainStateMessages(buffer) {
16959
16976
  }
16960
16977
 
16961
16978
  // ../../shared/all/helpers/terminal-ai/digest.ts
16962
- var import_crypto2 = __toESM(require("crypto"));
16979
+ var import_crypto3 = __toESM(require("crypto"));
16963
16980
  var import_promises8 = require("fs/promises");
16964
16981
  var import_path10 = __toESM(require("path"));
16965
16982
 
16966
16983
  // ../../shared/all/helpers/board-agent-core/plugin.ts
16967
- var import_crypto = __toESM(require("crypto"));
16984
+ var import_crypto2 = __toESM(require("crypto"));
16968
16985
  var import_fs4 = require("fs");
16969
16986
  var import_promises7 = require("fs/promises");
16970
16987
  var import_os3 = __toESM(require("os"));
@@ -17013,7 +17030,7 @@ async function treeFiles(dir) {
17013
17030
  return files.sort();
17014
17031
  }
17015
17032
  async function fingerprint(source, url2, version = "") {
17016
- const hash = import_crypto.default.createHash("sha256").update(url2).update(version);
17033
+ const hash = import_crypto2.default.createHash("sha256").update(url2).update(version);
17017
17034
  for (const file of await treeFiles(source)) {
17018
17035
  hash.update(import_path9.default.relative(source, file));
17019
17036
  hash.update(await (0, import_promises7.readFile)(file));
@@ -17131,7 +17148,7 @@ async function pluginContribution() {
17131
17148
  return value2;
17132
17149
  }
17133
17150
  async function terminalDigest(target) {
17134
- const hash = import_crypto2.default.createHash("sha256").update(TMUX_CONF).update(terminalPrelude(CWD_PLACEHOLDER, target.paths.bin));
17151
+ const hash = import_crypto3.default.createHash("sha256").update(TMUX_CONF).update(terminalPrelude(CWD_PLACEHOLDER, target.paths.bin));
17135
17152
  const env = target.stateEnv();
17136
17153
  for (const key of Object.keys(env).sort()) {
17137
17154
  if (VOLATILE_ENV_KEYS.has(key)) continue;
@@ -18330,7 +18347,7 @@ var TerminalModeEngine = class {
18330
18347
  }
18331
18348
  const safeExt = (ext || "png").toLowerCase().replace(/[^a-z0-9]/g, "") || "png";
18332
18349
  const oneShot = !dropId;
18333
- const safeId2 = oneShot ? (0, import_crypto3.randomUUID)() : dropId.toLowerCase().replace(/[^a-z0-9-]/g, "");
18350
+ const safeId2 = oneShot ? (0, import_crypto4.randomUUID)() : dropId.toLowerCase().replace(/[^a-z0-9-]/g, "");
18334
18351
  if (!safeId2) throw new Error("Invalid drop id.");
18335
18352
  const dropsDir = `${target.paths.root}/drops`;
18336
18353
  const abs = `${dropsDir}/${safeId2}.${safeExt}`;
@@ -18531,15 +18548,7 @@ var TerminalModeEngine = class {
18531
18548
  `${tmuxCli(target)} list-panes -a -F '${TAB_LIST_FORMAT}' 2>/dev/null || true`
18532
18549
  );
18533
18550
  const rows = parseTerminalTabs(stdout, prefix);
18534
- const current = await terminalDigest(target).catch(() => "");
18535
- return rows.map(({ id, name, fp, dead }) => ({
18536
- id,
18537
- name,
18538
- // No stamp means a session older than the stamp, as blind to the
18539
- // current build as a mismatched one.
18540
- stale: !!current && fp !== current,
18541
- dead
18542
- }));
18551
+ return rows.map(({ id, name, dead }) => ({ id, name, dead }));
18543
18552
  } catch {
18544
18553
  return [];
18545
18554
  }
@@ -19195,7 +19204,7 @@ async function listGithubBranches(token, fullName) {
19195
19204
  var import_path13 = __toESM(require("path"));
19196
19205
 
19197
19206
  // ../../shared/all/helpers/board-agent-core/secrets-gate.ts
19198
- var import_crypto4 = __toESM(require("crypto"));
19207
+ var import_crypto5 = __toESM(require("crypto"));
19199
19208
  var DEPLOY_ORIGIN = "Deploy run";
19200
19209
  var UNATTRIBUTED = "This runner did not start the session that asked, so the request was refused. `factiii-secrets` only works inside a session the runner spawned.";
19201
19210
  var SecretsGate = class {
@@ -19220,7 +19229,14 @@ var SecretsGate = class {
19220
19229
  /** Mint a token for one session, labelled with what the owner should see on
19221
19230
  * the prompt - the label is most of the decision, so name the session. */
19222
19231
  issueToken(origin) {
19223
- const token = import_crypto4.default.randomBytes(24).toString("hex");
19232
+ const token = import_crypto5.default.randomBytes(24).toString("hex");
19233
+ this.tokens.set(token, origin);
19234
+ return token;
19235
+ }
19236
+ /** Register a durable token the caller already holds, instead of minting a
19237
+ * fresh one. For the space token: terminals outlive this process, so a
19238
+ * minted one leaves them unattributed after a restart. Idempotent. */
19239
+ adoptToken(token, origin) {
19224
19240
  this.tokens.set(token, origin);
19225
19241
  return token;
19226
19242
  }
@@ -19244,7 +19260,7 @@ var SecretsGate = class {
19244
19260
  return new Promise((resolve2) => {
19245
19261
  this.waiting = {
19246
19262
  request: {
19247
- id: import_crypto4.default.randomUUID(),
19263
+ id: import_crypto5.default.randomUUID(),
19248
19264
  command: opts.command,
19249
19265
  keys: opts.keys,
19250
19266
  origin: opts.origin,
@@ -19288,7 +19304,7 @@ var import_path12 = __toESM(require("path"));
19288
19304
 
19289
19305
  // ../../shared/all/helpers/board-agent-core/host-secrets.ts
19290
19306
  var import_child_process4 = require("child_process");
19291
- var import_crypto5 = __toESM(require("crypto"));
19307
+ var import_crypto6 = __toESM(require("crypto"));
19292
19308
  var import_promises9 = __toESM(require("fs/promises"));
19293
19309
  var import_path11 = __toESM(require("path"));
19294
19310
  var import_util6 = require("util");
@@ -19316,7 +19332,7 @@ async function assertBinaries() {
19316
19332
  }
19317
19333
  }
19318
19334
  function deriveKey(password, salt) {
19319
- return import_crypto5.default.scryptSync(password, salt, 32, {
19335
+ return import_crypto6.default.scryptSync(password, salt, 32, {
19320
19336
  N: SCRYPT_N,
19321
19337
  maxmem: 64 * 1024 * 1024
19322
19338
  });
@@ -19345,9 +19361,9 @@ async function initSecrets(configDir, password) {
19345
19361
  if (!identity || !recipient) {
19346
19362
  throw new Error("age-keygen returned no usable keypair.");
19347
19363
  }
19348
- const salt = import_crypto5.default.randomBytes(16);
19349
- const iv = import_crypto5.default.randomBytes(12);
19350
- const cipher = import_crypto5.default.createCipheriv(
19364
+ const salt = import_crypto6.default.randomBytes(16);
19365
+ const iv = import_crypto6.default.randomBytes(12);
19366
+ const cipher = import_crypto6.default.createCipheriv(
19351
19367
  "aes-256-gcm",
19352
19368
  deriveKey(password, salt),
19353
19369
  iv
@@ -19372,7 +19388,7 @@ async function initSecrets(configDir, password) {
19372
19388
  return { success: true };
19373
19389
  }
19374
19390
  function decryptIdentity(box, password) {
19375
- const decipher = import_crypto5.default.createDecipheriv(
19391
+ const decipher = import_crypto6.default.createDecipheriv(
19376
19392
  "aes-256-gcm",
19377
19393
  deriveKey(password, Buffer.from(box.salt, "base64")),
19378
19394
  Buffer.from(box.iv, "base64")
@@ -20037,6 +20053,7 @@ var SpaceCore = class {
20037
20053
  this.runnerToken = options.runnerToken;
20038
20054
  this.serverUrl = options.serverUrl;
20039
20055
  this.mcp = options.mcp;
20056
+ this.mcpToken = options.mcpToken;
20040
20057
  this.requestSudo = options.requestSudo;
20041
20058
  this.activity = new ActivityTracker(this.emitter);
20042
20059
  this.gate.subscribe(() => {
@@ -20131,7 +20148,11 @@ var SpaceCore = class {
20131
20148
  * that DOES know itself - the deploy run - mints its own and overrides this.
20132
20149
  */
20133
20150
  secretsToken() {
20134
- this.spaceSecretsToken ||= this.gate.issueToken("A session on this board");
20151
+ if (this.spaceSecretsToken) return this.spaceSecretsToken;
20152
+ this.spaceSecretsToken = this.gate.adoptToken(
20153
+ spaceSecret(this.spaceDir()),
20154
+ "A session on this board"
20155
+ );
20135
20156
  return this.spaceSecretsToken;
20136
20157
  }
20137
20158
  readConfig() {
@@ -20142,7 +20163,8 @@ var SpaceCore = class {
20142
20163
  target() {
20143
20164
  this.execTarget ??= hostTarget(this.spaceDir(), () => {
20144
20165
  const env = {};
20145
- for (const lane of this.mcpLanes()) env[lane.tokenEnv] = lane.mcp.token;
20166
+ const onedrive = this.mcpToken?.();
20167
+ if (onedrive) env[MCP_TOKEN_ENV] = onedrive;
20146
20168
  const board = this.boardMcp();
20147
20169
  if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
20148
20170
  env.FACTIII_SECRETS_TOKEN = this.secretsToken();
@@ -22306,7 +22328,7 @@ ${c.content.trim() || "(no description)"}`
22306
22328
 
22307
22329
  // ../../shared/all/helpers/board-agent-core/index-service.ts
22308
22330
  var import_child_process5 = require("child_process");
22309
- var import_crypto6 = require("crypto");
22331
+ var import_crypto7 = require("crypto");
22310
22332
  var import_fs5 = require("fs");
22311
22333
  var import_promises11 = __toESM(require("fs/promises"));
22312
22334
  var import_net3 = require("net");
@@ -22324,7 +22346,7 @@ var IndexService = class {
22324
22346
  /** Per-space bearer for MCP + file, derived from the control token (the
22325
22347
  * service computes the same). */
22326
22348
  bearerToken(spaceSlug) {
22327
- return (0, import_crypto6.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
22349
+ return (0, import_crypto7.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
22328
22350
  }
22329
22351
  /** MCP bridge for a space. Null until the service is up (which it kicks
22330
22352
  * off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
@@ -22602,13 +22624,13 @@ var import_os6 = __toESM(require("os"));
22602
22624
  var import_path21 = __toESM(require("path"));
22603
22625
 
22604
22626
  // src/secureFile.ts
22605
- var import_crypto9 = __toESM(require("crypto"));
22627
+ var import_crypto10 = __toESM(require("crypto"));
22606
22628
  var import_fs8 = __toESM(require("fs"));
22607
22629
  var import_path20 = __toESM(require("path"));
22608
22630
 
22609
22631
  // src/keychain.ts
22610
22632
  var import_child_process7 = require("child_process");
22611
- var import_crypto7 = __toESM(require("crypto"));
22633
+ var import_crypto8 = __toESM(require("crypto"));
22612
22634
  var import_fs6 = __toESM(require("fs"));
22613
22635
  var import_os4 = __toESM(require("os"));
22614
22636
  var import_path17 = __toESM(require("path"));
@@ -22860,7 +22882,7 @@ function resolve() {
22860
22882
  cached = { key: null, status: diagnose() };
22861
22883
  return cached;
22862
22884
  }
22863
- const fresh = import_crypto7.default.randomBytes(32);
22885
+ const fresh = import_crypto8.default.randomBytes(32);
22864
22886
  writeKey(fresh);
22865
22887
  const verify = readKey();
22866
22888
  if (verify.state !== "found" || !verify.key.equals(fresh)) {
@@ -22882,7 +22904,7 @@ function keychainStatus() {
22882
22904
  }
22883
22905
 
22884
22906
  // src/secureStore.ts
22885
- var import_crypto8 = __toESM(require("crypto"));
22907
+ var import_crypto9 = __toESM(require("crypto"));
22886
22908
  var import_fs7 = __toESM(require("fs"));
22887
22909
  var import_path19 = __toESM(require("path"));
22888
22910
 
@@ -22925,7 +22947,7 @@ function requireKeychainKey() {
22925
22947
  );
22926
22948
  }
22927
22949
  function passwordKey(password, salt) {
22928
- return import_crypto8.default.scryptSync(password, salt, 32, {
22950
+ return import_crypto9.default.scryptSync(password, salt, 32, {
22929
22951
  N: SCRYPT_N2,
22930
22952
  maxmem: 64 * 1024 * 1024
22931
22953
  });
@@ -22933,7 +22955,7 @@ function passwordKey(password, salt) {
22933
22955
  function wrapKey(machineKey, pwKey) {
22934
22956
  const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
22935
22957
  return Buffer.from(
22936
- import_crypto8.default.hkdfSync(
22958
+ import_crypto9.default.hkdfSync(
22937
22959
  "sha256",
22938
22960
  ikm,
22939
22961
  Buffer.alloc(0),
@@ -22943,8 +22965,8 @@ function wrapKey(machineKey, pwKey) {
22943
22965
  );
22944
22966
  }
22945
22967
  function seal(plaintext, key) {
22946
- const iv = import_crypto8.default.randomBytes(12);
22947
- const cipher = import_crypto8.default.createCipheriv("aes-256-gcm", key, iv);
22968
+ const iv = import_crypto9.default.randomBytes(12);
22969
+ const cipher = import_crypto9.default.createCipheriv("aes-256-gcm", key, iv);
22948
22970
  const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
22949
22971
  return {
22950
22972
  iv: iv.toString("base64"),
@@ -22954,7 +22976,7 @@ function seal(plaintext, key) {
22954
22976
  }
22955
22977
  function unseal(box, key) {
22956
22978
  try {
22957
- const decipher = import_crypto8.default.createDecipheriv(
22979
+ const decipher = import_crypto9.default.createDecipheriv(
22958
22980
  "aes-256-gcm",
22959
22981
  key,
22960
22982
  Buffer.from(box.iv, "base64")
@@ -22987,7 +23009,7 @@ function writeVaultFile(file) {
22987
23009
  }
22988
23010
  var unlockedDek = null;
22989
23011
  function createVault(machineKey) {
22990
- const dek = import_crypto8.default.randomBytes(32);
23012
+ const dek = import_crypto9.default.randomBytes(32);
22991
23013
  writeVaultFile({
22992
23014
  v: 1,
22993
23015
  passwordEnabled: false,
@@ -23082,7 +23104,7 @@ function enablePassword(password) {
23082
23104
  );
23083
23105
  }
23084
23106
  const dek = dataKey();
23085
- const salt = import_crypto8.default.randomBytes(16);
23107
+ const salt = import_crypto9.default.randomBytes(16);
23086
23108
  writeVaultFile({
23087
23109
  v: 1,
23088
23110
  passwordEnabled: true,
@@ -23110,7 +23132,7 @@ function changePassword(current, next) {
23110
23132
  "The current password is not correct."
23111
23133
  );
23112
23134
  }
23113
- const salt = import_crypto8.default.randomBytes(16);
23135
+ const salt = import_crypto9.default.randomBytes(16);
23114
23136
  writeVaultFile({
23115
23137
  v: 1,
23116
23138
  passwordEnabled: true,
@@ -23187,8 +23209,8 @@ function storeStatus() {
23187
23209
  // src/secureFile.ts
23188
23210
  var MAGIC = "FACTIII1";
23189
23211
  function encrypt(plain, key, version) {
23190
- const iv = import_crypto9.default.randomBytes(12);
23191
- const cipher = import_crypto9.default.createCipheriv("aes-256-gcm", key, iv);
23212
+ const iv = import_crypto10.default.randomBytes(12);
23213
+ const cipher = import_crypto10.default.createCipheriv("aes-256-gcm", key, iv);
23192
23214
  const data = Buffer.concat([cipher.update(plain), cipher.final()]);
23193
23215
  const envelope = {
23194
23216
  v: version,
@@ -23199,7 +23221,7 @@ function encrypt(plain, key, version) {
23199
23221
  return `${MAGIC}${JSON.stringify(envelope)}`;
23200
23222
  }
23201
23223
  function decrypt(envelope, key) {
23202
- const decipher = import_crypto9.default.createDecipheriv(
23224
+ const decipher = import_crypto10.default.createDecipheriv(
23203
23225
  "aes-256-gcm",
23204
23226
  key,
23205
23227
  Buffer.from(envelope.iv, "base64")
@@ -23457,7 +23479,7 @@ function installLatest() {
23457
23479
  var import_node_child_process2 = require("node:child_process");
23458
23480
 
23459
23481
  // src/daemon.ts
23460
- var import_crypto10 = require("crypto");
23482
+ var import_crypto11 = require("crypto");
23461
23483
  var import_fs11 = __toESM(require("fs"));
23462
23484
  var import_node_datachannel = require("node-datachannel");
23463
23485
  var import_path23 = __toESM(require("path"));
@@ -27372,7 +27394,7 @@ async function startDaemon(config) {
27372
27394
  ];
27373
27395
  const indexDir = indexCandidates.find((p) => import_fs11.default.existsSync(p)) ?? indexCandidates[0];
27374
27396
  const odIndex = new IndexService({
27375
- controlToken: (0, import_crypto10.createHash)("sha256").update(config.authToken).digest("hex"),
27397
+ controlToken: (0, import_crypto11.createHash)("sha256").update(config.authToken).digest("hex"),
27376
27398
  embedUrl: () => config.serverUrl,
27377
27399
  indexDir,
27378
27400
  dataDir: import_path23.default.join(CONFIG_DIR, "index-data")
@@ -27472,7 +27494,9 @@ async function startDaemon(config) {
27472
27494
  // Board MCP is server-hosted, so the base URL is all it needs.
27473
27495
  serverUrl: config.serverUrl,
27474
27496
  // Point the agent at the index MCP endpoint (lazy for the port).
27475
- mcp: () => odIndex.mcpBridge(spaceSlug)
27497
+ mcp: () => odIndex.mcpBridge(spaceSlug),
27498
+ // The bearer alone, available before the service has a port for a URL.
27499
+ mcpToken: () => odIndex.bearerToken(spaceSlug)
27476
27500
  });
27477
27501
  engines.set(spaceSlug, engine);
27478
27502
  void engine.reattachAndHydrate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factiii/runner",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "Factiii Runner, run Board AI agents on a machine you control. Pairs with the Factiii web/mobile clients over WebRTC.",
5
5
  "license": "ISC",
6
6
  "keywords": [