@factiii/runner 0.10.0 → 0.10.2

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/dist/cli.js CHANGED
@@ -915,18 +915,18 @@ var require_suggestSimilar = __commonJS({
915
915
  }
916
916
  return d[a.length][b.length];
917
917
  }
918
- function suggestSimilar(word, candidates) {
919
- if (!candidates || candidates.length === 0) return "";
920
- candidates = Array.from(new Set(candidates));
918
+ function suggestSimilar(word, candidates2) {
919
+ if (!candidates2 || candidates2.length === 0) return "";
920
+ candidates2 = Array.from(new Set(candidates2));
921
921
  const searchingOptions = word.startsWith("--");
922
922
  if (searchingOptions) {
923
923
  word = word.slice(2);
924
- candidates = candidates.map((candidate) => candidate.slice(2));
924
+ candidates2 = candidates2.map((candidate) => candidate.slice(2));
925
925
  }
926
926
  let similar = [];
927
927
  let bestDistance = maxDistance;
928
928
  const minSimilarity = 0.4;
929
- candidates.forEach((candidate) => {
929
+ candidates2.forEach((candidate) => {
930
930
  if (candidate.length <= 1) return;
931
931
  const distance = editDistance(word, candidate);
932
932
  const length = Math.max(word.length, candidate.length);
@@ -963,7 +963,7 @@ var require_command = __commonJS({
963
963
  "node_modules/commander/lib/command.js"(exports2) {
964
964
  var EventEmitter = require("node:events").EventEmitter;
965
965
  var childProcess = require("node:child_process");
966
- var path17 = require("node:path");
966
+ var path18 = require("node:path");
967
967
  var fs14 = require("node:fs");
968
968
  var process2 = require("node:process");
969
969
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -1896,9 +1896,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1896
1896
  let launchWithNode = false;
1897
1897
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1898
1898
  function findFile(baseDir, baseName) {
1899
- const localBin = path17.resolve(baseDir, baseName);
1899
+ const localBin = path18.resolve(baseDir, baseName);
1900
1900
  if (fs14.existsSync(localBin)) return localBin;
1901
- if (sourceExt.includes(path17.extname(baseName))) return void 0;
1901
+ if (sourceExt.includes(path18.extname(baseName))) return void 0;
1902
1902
  const foundExt = sourceExt.find(
1903
1903
  (ext) => fs14.existsSync(`${localBin}${ext}`)
1904
1904
  );
@@ -1916,17 +1916,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
1916
1916
  } catch (err) {
1917
1917
  resolvedScriptPath = this._scriptPath;
1918
1918
  }
1919
- executableDir = path17.resolve(
1920
- path17.dirname(resolvedScriptPath),
1919
+ executableDir = path18.resolve(
1920
+ path18.dirname(resolvedScriptPath),
1921
1921
  executableDir
1922
1922
  );
1923
1923
  }
1924
1924
  if (executableDir) {
1925
1925
  let localFile = findFile(executableDir, executableFile);
1926
1926
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1927
- const legacyName = path17.basename(
1927
+ const legacyName = path18.basename(
1928
1928
  this._scriptPath,
1929
- path17.extname(this._scriptPath)
1929
+ path18.extname(this._scriptPath)
1930
1930
  );
1931
1931
  if (legacyName !== this._name) {
1932
1932
  localFile = findFile(
@@ -1937,7 +1937,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1937
1937
  }
1938
1938
  executableFile = localFile || executableFile;
1939
1939
  }
1940
- launchWithNode = sourceExt.includes(path17.extname(executableFile));
1940
+ launchWithNode = sourceExt.includes(path18.extname(executableFile));
1941
1941
  let proc;
1942
1942
  if (process2.platform !== "win32") {
1943
1943
  if (launchWithNode) {
@@ -2777,7 +2777,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2777
2777
  * @return {Command}
2778
2778
  */
2779
2779
  nameFromFilename(filename) {
2780
- this._name = path17.basename(filename, path17.extname(filename));
2780
+ this._name = path18.basename(filename, path18.extname(filename));
2781
2781
  return this;
2782
2782
  }
2783
2783
  /**
@@ -2791,9 +2791,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2791
2791
  * @param {string} [path]
2792
2792
  * @return {(string|null|Command)}
2793
2793
  */
2794
- executableDir(path18) {
2795
- if (path18 === void 0) return this._executableDir;
2796
- this._executableDir = path18;
2794
+ executableDir(path19) {
2795
+ if (path19 === void 0) return this._executableDir;
2796
+ this._executableDir = path19;
2797
2797
  return this;
2798
2798
  }
2799
2799
  /**
@@ -3984,7 +3984,7 @@ var require_has_flag = __commonJS({
3984
3984
  var require_supports_color = __commonJS({
3985
3985
  "../../node_modules/debug/node_modules/supports-color/index.js"(exports2, module2) {
3986
3986
  "use strict";
3987
- var os6 = require("os");
3987
+ var os7 = require("os");
3988
3988
  var hasFlag = require_has_flag();
3989
3989
  var env = process.env;
3990
3990
  var forceColor;
@@ -4022,7 +4022,7 @@ var require_supports_color = __commonJS({
4022
4022
  }
4023
4023
  const min = forceColor ? 1 : 0;
4024
4024
  if (process.platform === "win32") {
4025
- const osRelease = os6.release().split(".");
4025
+ const osRelease = os7.release().split(".");
4026
4026
  if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
4027
4027
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
4028
4028
  }
@@ -8428,8 +8428,8 @@ function getErrorMap() {
8428
8428
 
8429
8429
  // ../../node_modules/zod/v3/helpers/parseUtil.js
8430
8430
  var makeIssue = (params) => {
8431
- const { data, path: path17, errorMaps, issueData } = params;
8432
- const fullPath = [...path17, ...issueData.path || []];
8431
+ const { data, path: path18, errorMaps, issueData } = params;
8432
+ const fullPath = [...path18, ...issueData.path || []];
8433
8433
  const fullIssue = {
8434
8434
  ...issueData,
8435
8435
  path: fullPath
@@ -8545,11 +8545,11 @@ var errorUtil;
8545
8545
 
8546
8546
  // ../../node_modules/zod/v3/types.js
8547
8547
  var ParseInputLazyPath = class {
8548
- constructor(parent, value2, path17, key) {
8548
+ constructor(parent, value2, path18, key) {
8549
8549
  this._cachedPath = [];
8550
8550
  this.parent = parent;
8551
8551
  this.data = value2;
8552
- this._path = path17;
8552
+ this._path = path18;
8553
8553
  this._key = key;
8554
8554
  }
8555
8555
  get path() {
@@ -12908,7 +12908,7 @@ ${diff.slice(0, 8e3)}`,
12908
12908
  }
12909
12909
 
12910
12910
  // ../../shared/all/helpers/board-agent-core/engine.ts
12911
- var import_path9 = require("path");
12911
+ var import_path10 = require("path");
12912
12912
 
12913
12913
  // ../../shared/all/domains/electron.ts
12914
12914
  var SETUP_LOG_PREFIX = "log ";
@@ -13017,7 +13017,7 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
13017
13017
  await target.sh(`mkdir -p ${target.paths.worktrees}`);
13018
13018
  }
13019
13019
  async function addWorktree(target, workName, baseBranch, kind) {
13020
- const path17 = worktreeFor(target.paths, workName);
13020
+ const path18 = worktreeFor(target.paths, workName);
13021
13021
  const branch = workBranch(workName, kind);
13022
13022
  await gitInDir(
13023
13023
  target,
@@ -13027,7 +13027,7 @@ async function addWorktree(target, workName, baseBranch, kind) {
13027
13027
  baseBranch
13028
13028
  ).catch(() => {
13029
13029
  });
13030
- await removeWorktree(target, path17).catch(() => {
13030
+ await removeWorktree(target, path18).catch(() => {
13031
13031
  });
13032
13032
  await gitInDir(
13033
13033
  target,
@@ -13036,30 +13036,30 @@ async function addWorktree(target, workName, baseBranch, kind) {
13036
13036
  "add",
13037
13037
  "-B",
13038
13038
  branch,
13039
- path17,
13039
+ path18,
13040
13040
  `origin/${baseBranch}`
13041
13041
  );
13042
- return { path: path17, branch };
13042
+ return { path: path18, branch };
13043
13043
  }
13044
- async function removeWorktree(target, path17) {
13044
+ async function removeWorktree(target, path18) {
13045
13045
  await gitInDir(
13046
13046
  target,
13047
13047
  target.paths.repo,
13048
13048
  "worktree",
13049
13049
  "remove",
13050
13050
  "--force",
13051
- path17
13051
+ path18
13052
13052
  );
13053
13053
  }
13054
- async function removeWorktreeAndBranch(target, path17) {
13054
+ async function removeWorktreeAndBranch(target, path18) {
13055
13055
  const branch = await gitInDir(
13056
13056
  target,
13057
- path17,
13057
+ path18,
13058
13058
  "rev-parse",
13059
13059
  "--abbrev-ref",
13060
13060
  "HEAD"
13061
13061
  ).then((out) => out.trim()).catch(() => "");
13062
- await removeWorktree(target, path17);
13062
+ await removeWorktree(target, path18);
13063
13063
  if (branch && branch !== "HEAD") {
13064
13064
  await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
13065
13065
  () => {
@@ -13073,8 +13073,8 @@ async function pruneWorktrees(target) {
13073
13073
  }
13074
13074
  );
13075
13075
  }
13076
- function isWorktreePath(target, path17) {
13077
- return path17.startsWith(`${target.paths.worktrees}/`);
13076
+ function isWorktreePath(target, path18) {
13077
+ return path18.startsWith(`${target.paths.worktrees}/`);
13078
13078
  }
13079
13079
  async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
13080
13080
  if (name) await gitInDir(target, workdir, "config", "user.name", name);
@@ -13100,13 +13100,13 @@ function parseStatus(raw) {
13100
13100
  const y = line[1];
13101
13101
  const rest = line.slice(3);
13102
13102
  const arrow = rest.indexOf(" -> ");
13103
- const path17 = arrow === -1 ? rest : rest.slice(arrow + 4);
13103
+ const path18 = arrow === -1 ? rest : rest.slice(arrow + 4);
13104
13104
  if (x === "?" || y === "?") {
13105
- unstaged.push({ path: path17, code: "A" });
13105
+ unstaged.push({ path: path18, code: "A" });
13106
13106
  continue;
13107
13107
  }
13108
- if (x !== " ") staged.push({ path: path17, code: x });
13109
- if (y !== " ") unstaged.push({ path: path17, code: y });
13108
+ if (x !== " ") staged.push({ path: path18, code: x });
13109
+ if (y !== " ") unstaged.push({ path: path18, code: y });
13110
13110
  }
13111
13111
  return { staged, unstaged };
13112
13112
  }
@@ -13300,16 +13300,18 @@ function cardKey(postId) {
13300
13300
  return `card:${safeId(postId)}`;
13301
13301
  }
13302
13302
  var INDEX_KEY = "cards";
13303
- function socketPath(target) {
13303
+ function redisSocketPath(target) {
13304
13304
  return `${target.paths.state}/redis.sock`;
13305
13305
  }
13306
13306
  async function redisCli(target, args) {
13307
- const out = await target.sh(`redis-cli -s ${socketPath(target)} ${args}`);
13307
+ const out = await target.sh(
13308
+ `redis-cli -s ${redisSocketPath(target)} ${args}`
13309
+ );
13308
13310
  return out.replace(/\n$/, "");
13309
13311
  }
13310
13312
  function redisCliStdin(target, args, stdin) {
13311
13313
  return target.runWithStdin(
13312
- ["redis-cli", "-s", socketPath(target), "-x", ...args],
13314
+ ["redis-cli", "-s", redisSocketPath(target), "-x", ...args],
13313
13315
  stdin,
13314
13316
  `redis-cli ${args.join(" ")}`
13315
13317
  );
@@ -13322,7 +13324,7 @@ async function ensureRedis(target) {
13322
13324
  }
13323
13325
  const stateDir = target.paths.state;
13324
13326
  await target.sh(
13325
- `mkdir -p ${stateDir} && redis-server --daemonize yes --appendonly yes --save '' --dir ${stateDir} --port 0 --unixsocket ${socketPath(target)} --unixsocketperm 700`
13327
+ `mkdir -p ${stateDir} && redis-server --daemonize yes --appendonly yes --save '' --dir ${stateDir} --port 0 --unixsocket ${redisSocketPath(target)} --unixsocketperm 700`
13326
13328
  );
13327
13329
  for (let i = 0; i < 20; i += 1) {
13328
13330
  try {
@@ -13461,10 +13463,11 @@ function execLogin(command, opts) {
13461
13463
  return new Promise((resolve2) => {
13462
13464
  let output = "";
13463
13465
  let settled = false;
13466
+ let timer;
13464
13467
  const done = (code, extra = "") => {
13465
13468
  if (settled) return;
13466
13469
  settled = true;
13467
- clearTimeout(timer);
13470
+ if (timer) clearTimeout(timer);
13468
13471
  resolve2({ code, output: (output + extra).slice(-cap) });
13469
13472
  };
13470
13473
  let proc;
@@ -13481,7 +13484,7 @@ function execLogin(command, opts) {
13481
13484
  done(-1, err instanceof Error ? err.message : String(err));
13482
13485
  return;
13483
13486
  }
13484
- const timer = setTimeout(() => {
13487
+ timer = setTimeout(() => {
13485
13488
  killTree(proc);
13486
13489
  done(-1, `
13487
13490
  [timed out after ${Math.round(opts.timeout / 1e3)}s]`);
@@ -13687,6 +13690,7 @@ function toSummary(session) {
13687
13690
  pendingQuestion: session.pendingQuestion,
13688
13691
  error: session.error,
13689
13692
  startedAt: session.startedAt,
13693
+ agentState: session.agentState,
13690
13694
  targetBranch: session.targetBranch,
13691
13695
  taskTitle: session.taskTitle,
13692
13696
  todos: session.todos,
@@ -15916,18 +15920,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
15916
15920
  await this.core.ensureSpaceWorkspace();
15917
15921
  await assertAgentSignedIn(session.provider, this.core.target());
15918
15922
  if (hasGit) {
15919
- const { path: path17 } = await addWorktree(
15923
+ const { path: path18 } = await addWorktree(
15920
15924
  this.core.target(),
15921
15925
  session.taskId,
15922
15926
  baseBranch || config.mainBranch || "main",
15923
15927
  "card"
15924
15928
  );
15925
- session.workspacePath = path17;
15929
+ session.workspacePath = path18;
15926
15930
  await setGitIdentity(
15927
15931
  this.core.target(),
15928
15932
  config.gitName,
15929
15933
  config.gitEmail,
15930
- path17
15934
+ path18
15931
15935
  );
15932
15936
  this.emitter.addLog(session, "system", "Worktree ready.");
15933
15937
  } else {
@@ -16149,18 +16153,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
16149
16153
  this.emitter.addLog(session, "system", "Preparing environment...");
16150
16154
  await this.core.ensureSpaceWorkspace();
16151
16155
  await assertAgentSignedIn(session.provider, this.core.target());
16152
- const { path: path17 } = await addWorktree(
16156
+ const { path: path18 } = await addWorktree(
16153
16157
  this.core.target(),
16154
16158
  session.taskId,
16155
16159
  config.mainBranch || "main",
16156
16160
  "card"
16157
16161
  );
16158
- session.workspacePath = path17;
16162
+ session.workspacePath = path18;
16159
16163
  await setGitIdentity(
16160
16164
  this.core.target(),
16161
16165
  config.gitName,
16162
16166
  config.gitEmail,
16163
- path17
16167
+ path18
16164
16168
  );
16165
16169
  await this.putCard(session);
16166
16170
  const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
@@ -16455,6 +16459,50 @@ async function startTmuxSession(opts) {
16455
16459
  };
16456
16460
  }
16457
16461
 
16462
+ // ../../shared/all/helpers/terminal-ai/agentState.ts
16463
+ var STATES = /* @__PURE__ */ new Set(["working", "idle", "blocked"]);
16464
+ var AGENT_STATE_CHANNEL = "agent-state";
16465
+ function agentStateKey(paneId) {
16466
+ return `agent-state:${paneId}`;
16467
+ }
16468
+ function parseAgentMessage(payload) {
16469
+ const at = payload.indexOf(" ");
16470
+ if (at <= 0) return null;
16471
+ const state = parseAgentState(payload.slice(at + 1));
16472
+ return state ? { paneId: payload.slice(0, at), state } : null;
16473
+ }
16474
+ function postIdForSessionName(sessionName, postIds) {
16475
+ for (const postId of postIds) {
16476
+ const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
16477
+ if (sessionName.startsWith(prefix)) return postId;
16478
+ }
16479
+ return null;
16480
+ }
16481
+ function parseAgentState(text) {
16482
+ const word = text.trim();
16483
+ return STATES.has(word) ? word : null;
16484
+ }
16485
+ function drainStateMessages(buffer) {
16486
+ const lines = buffer.split("\n");
16487
+ const partial = lines.pop() ?? "";
16488
+ const states = [];
16489
+ let i = 0;
16490
+ let consumed = 0;
16491
+ while (i < lines.length) {
16492
+ if (lines[i] !== "message") {
16493
+ i += 1;
16494
+ consumed = i;
16495
+ continue;
16496
+ }
16497
+ if (i + 2 >= lines.length) break;
16498
+ const parsed = parseAgentMessage(lines[i + 2]);
16499
+ if (parsed) states.push(parsed);
16500
+ i += 3;
16501
+ consumed = i;
16502
+ }
16503
+ return { states, rest: [...lines.slice(consumed), partial].join("\n") };
16504
+ }
16505
+
16458
16506
  // ../../shared/all/helpers/terminal-ai/processes.ts
16459
16507
  var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
16460
16508
  var PS_FIELDS = 5;
@@ -16500,6 +16548,45 @@ function descendantsOf(rows, roots) {
16500
16548
  }
16501
16549
  return found.sort((a, b) => a.pid - b.pid);
16502
16550
  }
16551
+ function panesByPid(raw) {
16552
+ const found = /* @__PURE__ */ new Map();
16553
+ for (const line of raw.split("\n")) {
16554
+ const trimmed = line.trim();
16555
+ const pid = Number(trimmed.split(/\s+/)[0]);
16556
+ if (!Number.isInteger(pid) || pid <= 0) continue;
16557
+ const match = /(?:^|\s)TMUX_PANE=(%\d+)(?:\s|$)/.exec(trimmed);
16558
+ if (match) found.set(pid, match[1]);
16559
+ }
16560
+ return found;
16561
+ }
16562
+ async function processesForCard(panes, table, lookupPanes) {
16563
+ const mine = /* @__PURE__ */ new Map();
16564
+ for (const pane of panes) {
16565
+ for (const row of processesForPane(table, pane.pid)) {
16566
+ mine.set(row.pid, { tabId: pane.tabId, row });
16567
+ }
16568
+ }
16569
+ const shells = new Set(panes.map((pane) => pane.pid));
16570
+ const rest = table.filter((row) => row.pid > 1 && !mine.has(row.pid) && !shells.has(row.pid)).map((row) => row.pid);
16571
+ if (rest.length === 0) return mine;
16572
+ const paneOf = await lookupPanes(rest);
16573
+ const tabByPane = new Map(panes.map((pane) => [pane.paneId, pane.tabId]));
16574
+ const adopted = /* @__PURE__ */ new Map();
16575
+ for (const row of table) {
16576
+ const tabId = tabByPane.get(paneOf.get(row.pid) ?? "");
16577
+ if (!tabId || mine.has(row.pid)) continue;
16578
+ mine.set(row.pid, { tabId, row });
16579
+ const roots = adopted.get(tabId);
16580
+ if (roots) roots.push(row.pid);
16581
+ else adopted.set(tabId, [row.pid]);
16582
+ }
16583
+ for (const [tabId, roots] of adopted) {
16584
+ for (const row of descendantsOf(table, roots)) {
16585
+ if (!mine.has(row.pid)) mine.set(row.pid, { tabId, row });
16586
+ }
16587
+ }
16588
+ return mine;
16589
+ }
16503
16590
  function commandOf(args) {
16504
16591
  const first = args.trim().split(/\s+/)[0] ?? "";
16505
16592
  const base = first.split("/").pop() ?? first;
@@ -16624,12 +16711,12 @@ var FLUSH_MS = 120;
16624
16711
  var SELF_WRITE_MS = 2e3;
16625
16712
  var dynamicImport = new Function("s", "return import(s)");
16626
16713
  var importFs = new Function("s", "return import(s)");
16627
- async function resolveReal(path17) {
16714
+ async function resolveReal(path18) {
16628
16715
  try {
16629
16716
  const { realpath } = await importFs("fs/promises");
16630
- return await realpath(path17);
16717
+ return await realpath(path18);
16631
16718
  } catch {
16632
- return path17;
16719
+ return path18;
16633
16720
  }
16634
16721
  }
16635
16722
  var loadSubscribe = null;
@@ -16743,19 +16830,19 @@ var WorkspaceWatcher = class {
16743
16830
  }
16744
16831
  record(kind, relPath) {
16745
16832
  if (this.closed) return;
16746
- const path17 = "/" + relPath;
16747
- const expiry = this.selfWrites.get(path17);
16833
+ const path18 = "/" + relPath;
16834
+ const expiry = this.selfWrites.get(path18);
16748
16835
  if (expiry !== void 0) {
16749
16836
  if (expiry > Date.now()) return;
16750
- this.selfWrites.delete(path17);
16837
+ this.selfWrites.delete(path18);
16751
16838
  }
16752
16839
  const { added, changed, removed } = this.pending;
16753
- added.delete(path17);
16754
- changed.delete(path17);
16755
- removed.delete(path17);
16756
- if (kind === "added") added.add(path17);
16757
- else if (kind === "changed") changed.add(path17);
16758
- else removed.add(path17);
16840
+ added.delete(path18);
16841
+ changed.delete(path18);
16842
+ removed.delete(path18);
16843
+ if (kind === "added") added.add(path18);
16844
+ else if (kind === "changed") changed.add(path18);
16845
+ else removed.add(path18);
16759
16846
  if (added.size + changed.size + removed.size > MAX_BATCH) {
16760
16847
  this.pending = { ...emptyPending(), resync: true };
16761
16848
  }
@@ -16774,8 +16861,8 @@ var WorkspaceWatcher = class {
16774
16861
  this.pending = emptyPending();
16775
16862
  if (!resync && !added.size && !changed.size && !removed.size) return;
16776
16863
  const now = Date.now();
16777
- for (const [path17, expiry] of this.selfWrites) {
16778
- if (expiry <= now) this.selfWrites.delete(path17);
16864
+ for (const [path18, expiry] of this.selfWrites) {
16865
+ if (expiry <= now) this.selfWrites.delete(path18);
16779
16866
  }
16780
16867
  this.emit({
16781
16868
  added: resync ? [] : [...added],
@@ -16808,6 +16895,20 @@ function posixDirname(p) {
16808
16895
  function shEscape(s) {
16809
16896
  return `'${s.replace(/'/g, `'\\''`)}'`;
16810
16897
  }
16898
+ async function writeBase64File(target, abs, b64, opts = {}) {
16899
+ const file = shEscape(abs);
16900
+ const cap = opts.maxBytes ? ` && if [ $(wc -c < ${file}) -gt ${opts.maxBytes} ]; then rm -f ${file}; echo 'file too large' >&2; exit 1; fi` : "";
16901
+ await target.runWithStdin(
16902
+ [
16903
+ "bash",
16904
+ "-c",
16905
+ `mkdir -p ${shEscape(posixDirname(abs))} && base64 -d ${opts.append ? ">>" : ">"} ${file}${cap}`
16906
+ ],
16907
+ b64,
16908
+ `Write ${abs}`
16909
+ );
16910
+ }
16911
+ var PANE_REREAD_MS = 1e3;
16811
16912
  var TerminalModeEngine = class {
16812
16913
  constructor(core, emitter) {
16813
16914
  this.core = core;
@@ -16818,12 +16919,117 @@ var TerminalModeEngine = class {
16818
16919
  /** Worktree name each session claimed, keyed by postId. Covers the window
16819
16920
  * `claimWorkName`'s disk scan cannot: a name taken but not yet created. */
16820
16921
  this.reservedNames = /* @__PURE__ */ new Map();
16922
+ // ── agent activity ──
16923
+ /** Long-lived `redis-cli subscribe` on the space's own socket. One for every
16924
+ * session: the channel is space-wide and the pane rides in the payload, so
16925
+ * a session starting does not mean re-subscribing. */
16926
+ this.stateSub = null;
16927
+ /** Applies states one at a time; see the comment at the push site. */
16928
+ this.stateQueue = Promise.resolve();
16929
+ /** pane id -> tmux session name, refreshed only when a pane we have not seen
16930
+ * turns up. Panes outlive individual messages, so re-reading the whole map
16931
+ * per message would be a tmux call per hook. */
16932
+ this.paneSessions = /* @__PURE__ */ new Map();
16933
+ this.paneReadAt = 0;
16934
+ }
16935
+ /** Idempotent, like startWatching: re-entering a session must not stack
16936
+ * subscribers. */
16937
+ async startStateSub() {
16938
+ if (this.stateSub) return;
16939
+ const target = this.core.target();
16940
+ const child = await target.spawn([
16941
+ "redis-cli",
16942
+ "-s",
16943
+ redisSocketPath(target),
16944
+ "subscribe",
16945
+ AGENT_STATE_CHANNEL
16946
+ ]).catch(() => null);
16947
+ if (!child) return;
16948
+ this.stateSub = child;
16949
+ let buffer = "";
16950
+ child.stdout?.on("data", (chunk) => {
16951
+ buffer += String(chunk);
16952
+ const { states, rest } = drainStateMessages(buffer);
16953
+ buffer = rest;
16954
+ for (const { paneId, state } of states) {
16955
+ this.stateQueue = this.stateQueue.then(() => this.applyPaneState(paneId, state)).catch(() => {
16956
+ });
16957
+ }
16958
+ });
16959
+ child.once("exit", () => {
16960
+ if (this.stateSub === child) this.stateSub = null;
16961
+ });
16962
+ void this.hydrateAgentStates();
16963
+ }
16964
+ /** Read the last reported state for every live session's pane.
16965
+ *
16966
+ * Subscribing only catches what happens next. After a runner restart the
16967
+ * sessions come back but no hook has fired yet, so an idle session would
16968
+ * show as working until someone typed in it — which for an idle session may
16969
+ * be never. This is what the hook's SET alongside its PUBLISH is for. */
16970
+ async hydrateAgentStates() {
16971
+ if (this.sessions.size === 0) return;
16972
+ await this.refreshPanes();
16973
+ const target = this.core.target();
16974
+ for (const [paneId, name] of this.paneSessions) {
16975
+ const postId = postIdForSessionName(name, this.sessions.keys());
16976
+ const session = postId ? this.sessions.get(postId) : null;
16977
+ if (!session || session.agentState) continue;
16978
+ const raw = await target.sh(
16979
+ `redis-cli -s ${redisSocketPath(target)} get '${agentStateKey(paneId)}'`
16980
+ ).catch(() => "");
16981
+ const state = parseAgentState(raw);
16982
+ if (!state) continue;
16983
+ session.agentState = state;
16984
+ this.emitter.emitSessionUpdate(session);
16985
+ }
16986
+ }
16987
+ /** Called after a session is removed, never before: every stopWatching call
16988
+ * site runs while the session is still in the map, so keying this off
16989
+ * stopWatching would leave the subscriber running for the life of the
16990
+ * process. */
16991
+ releaseStateSub() {
16992
+ if (this.sessions.size > 0) return;
16993
+ this.stateSub?.kill();
16994
+ this.stateSub = null;
16995
+ }
16996
+ async applyPaneState(paneId, state) {
16997
+ let name = this.paneSessions.get(paneId);
16998
+ if (!name) {
16999
+ await this.refreshPanes();
17000
+ name = this.paneSessions.get(paneId);
17001
+ }
17002
+ if (!name) return;
17003
+ const postId = postIdForSessionName(name, this.sessions.keys());
17004
+ if (!postId) return;
17005
+ const session = this.sessions.get(postId);
17006
+ if (!session || session.agentState === state) return;
17007
+ session.agentState = state;
17008
+ this.emitter.emitSessionUpdate(session);
17009
+ }
17010
+ /** Rate-limited: a pane that will never resolve — one whose session has
17011
+ * since closed — would otherwise re-list on every message it sent, which is
17012
+ * a tmux subprocess per hook. */
17013
+ async refreshPanes() {
17014
+ const now = Date.now();
17015
+ if (now - this.paneReadAt < PANE_REREAD_MS) return;
17016
+ this.paneReadAt = now;
17017
+ const target = this.core.target();
17018
+ const out = await target.sh(
17019
+ `${tmuxCli(target)} list-panes -a -F '#{pane_id}|#{session_name}' 2>/dev/null || true`
17020
+ ).catch(() => "");
17021
+ this.paneSessions.clear();
17022
+ for (const raw of out.split("\n")) {
17023
+ const [pane, name] = raw.trim().split("|");
17024
+ if (pane && name) this.paneSessions.set(pane, name);
17025
+ }
16821
17026
  }
16822
17027
  // ── working-tree watching ──
16823
17028
  /** Start watching a session's tree. Idempotent: re-entering a session (the
16824
17029
  * modal reopening, a hydrate after restart) must not stack watchers. */
16825
17030
  startWatching(session) {
16826
17031
  const postId = session.postId;
17032
+ void this.startStateSub();
16827
17033
  if (this.watchers.has(postId)) return;
16828
17034
  const paths = this.core.target().paths;
16829
17035
  const root = session.workspacePath || paths.root;
@@ -16860,8 +17066,8 @@ var TerminalModeEngine = class {
16860
17066
  /** Suppress the change event our own write is about to produce, so a save
16861
17067
  * never comes back to the editor as an outside edit. Normalized to the form
16862
17068
  * the watcher emits, since callers spell the path either way. */
16863
- expectWrite(postId, path17) {
16864
- this.watchers.get(postId)?.expectWrite(posixNormalize(path17));
17069
+ expectWrite(postId, path18) {
17070
+ this.watchers.get(postId)?.expectWrite(posixNormalize(path18));
16865
17071
  }
16866
17072
  // ── Lifecycle / ModeEngine ──
16867
17073
  start(base, args) {
@@ -16907,6 +17113,7 @@ var TerminalModeEngine = class {
16907
17113
  this.stopWatching(postId);
16908
17114
  this.reservedNames.delete(postId);
16909
17115
  this.sessions.delete(postId);
17116
+ this.releaseStateSub();
16910
17117
  }
16911
17118
  async kill(postId) {
16912
17119
  let session = this.sessions.get(postId);
@@ -16924,6 +17131,7 @@ var TerminalModeEngine = class {
16924
17131
  const removedPostId = session.postId;
16925
17132
  this.reservedNames.delete(removedPostId);
16926
17133
  this.sessions.delete(removedPostId);
17134
+ this.releaseStateSub();
16927
17135
  this.emitter.emitSessionRemoved(removedPostId);
16928
17136
  }
16929
17137
  destroy() {
@@ -17056,18 +17264,18 @@ var TerminalModeEngine = class {
17056
17264
  this.reservedNames.values()
17057
17265
  );
17058
17266
  this.reservedNames.set(session.postId, workName);
17059
- const { path: path17 } = await addWorktree(
17267
+ const { path: path18 } = await addWorktree(
17060
17268
  this.core.target(),
17061
17269
  workName,
17062
17270
  branch,
17063
17271
  "terminal"
17064
17272
  );
17065
- session.workspacePath = path17;
17273
+ session.workspacePath = path18;
17066
17274
  await setGitIdentity(
17067
17275
  this.core.target(),
17068
17276
  config.gitName,
17069
17277
  config.gitEmail,
17070
- path17
17278
+ path18
17071
17279
  );
17072
17280
  this.emitter.addLog(session, "system", "Worktree ready.");
17073
17281
  } else {
@@ -17129,11 +17337,11 @@ var TerminalModeEngine = class {
17129
17337
  }
17130
17338
  async bareFsList({
17131
17339
  postId,
17132
- path: path17
17340
+ path: path18
17133
17341
  }) {
17134
17342
  const target = this.getBareTarget(postId);
17135
17343
  const root = this.getBareRoot(postId);
17136
- const abs = this.resolveBarePath(path17, root);
17344
+ const abs = this.resolveBarePath(path18, root);
17137
17345
  const relRoot = abs.slice(root.length) || "/";
17138
17346
  const out = await target.run([
17139
17347
  "sh",
@@ -17183,53 +17391,56 @@ var TerminalModeEngine = class {
17183
17391
  }
17184
17392
  async bareFsRead({
17185
17393
  postId,
17186
- path: path17
17394
+ path: path18
17187
17395
  }) {
17188
17396
  const target = this.getBareTarget(postId);
17189
- const abs = this.resolveBarePath(path17, this.getBareRoot(postId));
17397
+ const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
17190
17398
  const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
17191
17399
  '`)).trim();
17192
17400
  return { content: b64, encoding: "base64" };
17193
17401
  }
17194
17402
  async bareFsWrite({
17195
17403
  postId,
17196
- path: path17,
17404
+ path: path18,
17197
17405
  content,
17198
17406
  encoding
17199
17407
  }) {
17200
17408
  const target = this.getBareTarget(postId);
17201
- const abs = this.resolveBarePath(path17, this.getBareRoot(postId));
17409
+ const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
17202
17410
  const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
17203
17411
  if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
17204
17412
  throw new Error("Invalid base64 payload.");
17205
17413
  }
17206
- this.expectWrite(String(postId), path17);
17207
- await target.sh(
17208
- `mkdir -p ${shEscape(posixDirname(abs))} && printf %s ${shEscape(b64)} | base64 -d > ${shEscape(abs)}`
17209
- );
17414
+ this.expectWrite(String(postId), path18);
17415
+ await writeBase64File(target, abs, b64);
17210
17416
  const session = this.sessions.get(String(postId));
17211
- if (session) delete session.buffers[path17];
17417
+ if (session) delete session.buffers[path18];
17212
17418
  }
17213
- /** Write a dropped image to the per-space drops dir (`paths.root`, OUTSIDE the
17214
- * card worktree so it never touches the repo) and return its absolute path. */
17419
+ /** Append one chunk of a dropped image to the per-space drops dir
17420
+ * (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
17421
+ * Returns the absolute path on the last chunk, '' before that. */
17215
17422
  async terminalDropImage({
17216
17423
  postId,
17217
17424
  content,
17218
- ext
17425
+ ext,
17426
+ dropId,
17427
+ last
17219
17428
  }) {
17220
17429
  const target = this.getBareTarget(postId);
17221
17430
  if (!/^[A-Za-z0-9+/=\n\r]*$/.test(content)) {
17222
17431
  throw new Error("Invalid base64 payload.");
17223
17432
  }
17224
- if (content.length > 14e6) {
17225
- throw new Error("Image too large (max ~10MB).");
17226
- }
17227
17433
  const safeExt = (ext || "png").toLowerCase().replace(/[^a-z0-9]/g, "") || "png";
17434
+ const oneShot = !dropId;
17435
+ const safeId2 = oneShot ? (0, import_crypto.randomUUID)() : dropId.toLowerCase().replace(/[^a-z0-9-]/g, "");
17436
+ if (!safeId2) throw new Error("Invalid drop id.");
17228
17437
  const dropsDir = `${target.paths.root}/drops`;
17229
- const abs = `${dropsDir}/${(0, import_crypto.randomUUID)()}.${safeExt}`;
17230
- await target.sh(
17231
- `mkdir -p ${shEscape(dropsDir)} && printf %s ${shEscape(content)} | base64 -d > ${shEscape(abs)}`
17232
- );
17438
+ const abs = `${dropsDir}/${safeId2}.${safeExt}`;
17439
+ await writeBase64File(target, abs, content, {
17440
+ append: !oneShot,
17441
+ maxBytes: 1e7
17442
+ });
17443
+ if (!last && !oneShot) return "";
17233
17444
  try {
17234
17445
  await target.sh(
17235
17446
  `find ${shEscape(dropsDir)} -type f -mtime +1 -delete 2>/dev/null || true`
@@ -17253,39 +17464,39 @@ var TerminalModeEngine = class {
17253
17464
  }
17254
17465
  bareOpenFile({
17255
17466
  postId: rawPostId,
17256
- path: path17
17467
+ path: path18
17257
17468
  }) {
17258
17469
  const session = this.sessions.get(String(rawPostId));
17259
17470
  if (!session || session.mode !== "bare") return;
17260
- if (!session.openedFiles.includes(path17)) {
17261
- session.openedFiles.push(path17);
17471
+ if (!session.openedFiles.includes(path18)) {
17472
+ session.openedFiles.push(path18);
17262
17473
  }
17263
17474
  }
17264
17475
  bareCloseFile({
17265
17476
  postId: rawPostId,
17266
- path: path17
17477
+ path: path18
17267
17478
  }) {
17268
17479
  const session = this.sessions.get(String(rawPostId));
17269
17480
  if (!session || session.mode !== "bare") return;
17270
- session.openedFiles = session.openedFiles.filter((p) => p !== path17);
17271
- delete session.buffers[path17];
17481
+ session.openedFiles = session.openedFiles.filter((p) => p !== path18);
17482
+ delete session.buffers[path18];
17272
17483
  }
17273
17484
  bareBufferSet({
17274
17485
  postId: rawPostId,
17275
- path: path17,
17486
+ path: path18,
17276
17487
  content
17277
17488
  }) {
17278
17489
  const session = this.sessions.get(String(rawPostId));
17279
17490
  if (!session || session.mode !== "bare") return;
17280
- session.buffers[path17] = content;
17491
+ session.buffers[path18] = content;
17281
17492
  }
17282
17493
  async bareFsDelete({
17283
17494
  postId,
17284
- path: path17
17495
+ path: path18
17285
17496
  }) {
17286
17497
  const target = this.getBareTarget(postId);
17287
17498
  const root = this.getBareRoot(postId);
17288
- const abs = this.resolveBarePath(path17, root);
17499
+ const abs = this.resolveBarePath(path18, root);
17289
17500
  if (abs === root) {
17290
17501
  throw new Error("Refusing to delete the repo root.");
17291
17502
  }
@@ -17293,10 +17504,10 @@ var TerminalModeEngine = class {
17293
17504
  }
17294
17505
  async bareFsMkdir({
17295
17506
  postId,
17296
- path: path17
17507
+ path: path18
17297
17508
  }) {
17298
17509
  const target = this.getBareTarget(postId);
17299
- const abs = this.resolveBarePath(path17, this.getBareRoot(postId));
17510
+ const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
17300
17511
  await target.sh(`mkdir -p ${shEscape(abs)}`);
17301
17512
  }
17302
17513
  async bareReview({
@@ -17439,53 +17650,63 @@ var TerminalModeEngine = class {
17439
17650
  return [];
17440
17651
  }
17441
17652
  }
17442
- /** Pane pids for this card's tabs, keyed by tab id. The pane's own shell is
17443
- * the root of everything the user started in that tab. */
17653
+ /** This card's panes: the pid its tree hangs off, and the pane id its
17654
+ * processes carry in their environment. */
17444
17655
  async panePids(target, postId) {
17445
17656
  const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
17446
17657
  const stdout = await target.sh(
17447
- `${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}' 2>/dev/null || true`
17658
+ `${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}|#{pane_id}' 2>/dev/null || true`
17448
17659
  ).catch(() => "");
17449
17660
  const panes = [];
17450
17661
  for (const raw of stdout.split("\n")) {
17451
17662
  const line = raw.trim();
17452
17663
  if (!line.startsWith(prefix)) continue;
17453
- const rest = line.slice(prefix.length);
17454
- const sep = rest.indexOf("|");
17455
- if (sep === -1) continue;
17456
- const pid = Number(rest.slice(sep + 1));
17457
- if (!Number.isInteger(pid) || pid <= 0) continue;
17458
- panes.push({ tabId: rest.slice(0, sep), pid });
17664
+ const [tabId, rawPid, paneId] = line.slice(prefix.length).split("|");
17665
+ const pid = Number(rawPid);
17666
+ if (!tabId || !paneId || !Number.isInteger(pid) || pid <= 0) continue;
17667
+ panes.push({ tabId, pid, paneId });
17459
17668
  }
17460
17669
  return panes;
17461
17670
  }
17671
+ /**
17672
+ * Every process this card is running, by pid. Shared with bareKillProcess,
17673
+ * so stopping a process accepts exactly the set the panel shows.
17674
+ *
17675
+ * The `-p <pids>` on the environment call is required, not a saving: macOS
17676
+ * omits environments from an unscoped `ps -axeww` — see panesByPid.
17677
+ */
17678
+ async cardProcesses(target, postId) {
17679
+ const panes = await this.panePids(target, postId);
17680
+ if (panes.length === 0) return /* @__PURE__ */ new Map();
17681
+ const table = parsePsTable(
17682
+ await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
17683
+ );
17684
+ return processesForCard(
17685
+ panes,
17686
+ table,
17687
+ async (pids) => panesByPid(
17688
+ await target.run(["ps", "eww", "-o", "pid=,command=", "-p", pids.join(",")]).catch(() => "")
17689
+ )
17690
+ );
17691
+ }
17462
17692
  async bareListProcesses({
17463
17693
  postId: rawPostId
17464
17694
  }) {
17465
17695
  const postId = String(rawPostId);
17466
17696
  const target = this.getBareTarget(postId);
17467
- const panes = await this.panePids(target, postId);
17468
- if (panes.length === 0) return [];
17469
- const table = parsePsTable(
17470
- await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
17471
- );
17472
- const found = [];
17473
- for (const pane of panes) {
17474
- for (const row of processesForPane(table, pane.pid)) {
17475
- found.push({
17476
- pid: row.pid,
17477
- tabId: pane.tabId,
17478
- command: commandOf(row.args),
17479
- args: row.args,
17480
- cpu: row.cpu,
17481
- memoryKb: row.rssKb,
17482
- elapsed: row.elapsed,
17483
- ports: [],
17484
- connections: 0
17485
- });
17486
- }
17487
- }
17488
- if (found.length === 0) return [];
17697
+ const mine = await this.cardProcesses(target, postId);
17698
+ if (mine.size === 0) return [];
17699
+ const found = Array.from(mine.values(), ({ tabId, row }) => ({
17700
+ pid: row.pid,
17701
+ tabId,
17702
+ command: commandOf(row.args),
17703
+ args: row.args,
17704
+ cpu: row.cpu,
17705
+ memoryKb: row.rssKb,
17706
+ elapsed: row.elapsed,
17707
+ ports: [],
17708
+ connections: 0
17709
+ }));
17489
17710
  const sockets = await socketsFor(
17490
17711
  target,
17491
17712
  found.map((p) => p.pid)
@@ -17508,14 +17729,8 @@ var TerminalModeEngine = class {
17508
17729
  if (!Number.isInteger(pid) || pid <= 1) {
17509
17730
  throw new Error("Not a process this card can stop.");
17510
17731
  }
17511
- const panes = await this.panePids(target, postId);
17512
- const table = parsePsTable(
17513
- await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
17514
- );
17515
- const mine = panes.some(
17516
- (pane) => processesForPane(table, pane.pid).some((row) => row.pid === pid)
17517
- );
17518
- if (!mine) {
17732
+ const mine = await this.cardProcesses(target, postId);
17733
+ if (!mine.has(pid)) {
17519
17734
  throw new Error("That process is not running in this card.");
17520
17735
  }
17521
17736
  await target.run(["kill", "-TERM", String(pid)]);
@@ -17974,8 +18189,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
17974
18189
  };
17975
18190
  }
17976
18191
  }
17977
- async function githubApi(token, path17) {
17978
- const res = await fetch(`${API_BASE}${path17}`, {
18192
+ async function githubApi(token, path18) {
18193
+ const res = await fetch(`${API_BASE}${path18}`, {
17979
18194
  headers: {
17980
18195
  Authorization: `Bearer ${token}`,
17981
18196
  Accept: "application/vnd.github+json",
@@ -17984,7 +18199,7 @@ async function githubApi(token, path17) {
17984
18199
  });
17985
18200
  if (!res.ok) {
17986
18201
  throw new Error(
17987
- `GitHub ${path17} failed (${res.status}): ${await res.text()}`
18202
+ `GitHub ${path18} failed (${res.status}): ${await res.text()}`
17988
18203
  );
17989
18204
  }
17990
18205
  return await res.json();
@@ -18491,6 +18706,59 @@ async function getDriveRoot(accessToken) {
18491
18706
  };
18492
18707
  }
18493
18708
 
18709
+ // ../../shared/all/helpers/board-agent-core/plugin.ts
18710
+ var import_fs3 = require("fs");
18711
+ var import_promises6 = require("fs/promises");
18712
+ var import_os3 = __toESM(require("os"));
18713
+ var import_path9 = __toESM(require("path"));
18714
+ var PLUGIN_ID = "factiii@factiii-runner";
18715
+ function candidates() {
18716
+ return [
18717
+ import_path9.default.join(__dirname, "..", "plugin"),
18718
+ import_path9.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
18719
+ import_path9.default.join(process.cwd(), "apps", "runner", "plugin")
18720
+ ];
18721
+ }
18722
+ function pluginRoot() {
18723
+ for (const dir of candidates()) {
18724
+ if ((0, import_fs3.existsSync)(import_path9.default.join(dir, ".claude-plugin", "marketplace.json"))) {
18725
+ return dir;
18726
+ }
18727
+ }
18728
+ return null;
18729
+ }
18730
+ function renderedPluginRoot() {
18731
+ return import_path9.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
18732
+ }
18733
+ async function renderPlugin(boardMcpUrl) {
18734
+ const source = pluginRoot();
18735
+ if (!source) return null;
18736
+ const dest = renderedPluginRoot();
18737
+ const manifest = import_path9.default.join(dest, "factiii", ".mcp.json");
18738
+ const want = `${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
18739
+ `;
18740
+ const have = await (0, import_promises6.readFile)(manifest, "utf8").catch(() => null);
18741
+ if (have === want) return dest;
18742
+ await (0, import_promises6.mkdir)(import_path9.default.dirname(dest), { recursive: true });
18743
+ await (0, import_promises6.cp)(source, dest, { recursive: true, force: true });
18744
+ await (0, import_promises6.writeFile)(manifest, want);
18745
+ return dest;
18746
+ }
18747
+ function boardMcpConfig(url2) {
18748
+ return {
18749
+ mcpServers: {
18750
+ "factiii-board": {
18751
+ type: "http",
18752
+ url: url2,
18753
+ bearer_token_env_var: BOARD_MCP_TOKEN_ENV,
18754
+ // Written literally, for claude to expand at connect time. Not a
18755
+ // template string: the `${…}` has to survive into the file.
18756
+ headers: { Authorization: "Bearer ${" + BOARD_MCP_TOKEN_ENV + "}" }
18757
+ }
18758
+ }
18759
+ };
18760
+ }
18761
+
18494
18762
  // ../../shared/all/helpers/board-agent-core/toolchain.ts
18495
18763
  var TOOLS = [
18496
18764
  { bin: "git" },
@@ -18748,10 +19016,6 @@ var SpaceCore = class {
18748
19016
  enabled: this.isOneDriveConnected()
18749
19017
  });
18750
19018
  }
18751
- const board = this.boardMcp();
18752
- if (board) {
18753
- lanes.push({ mcp: board, tokenEnv: BOARD_MCP_TOKEN_ENV, enabled: true });
18754
- }
18755
19019
  return lanes;
18756
19020
  }
18757
19021
  isReady() {
@@ -18902,6 +19166,7 @@ var SpaceCore = class {
18902
19166
  await Promise.all([
18903
19167
  this.provisionGitLane(status),
18904
19168
  this.provisionMcpLane(),
19169
+ this.provisionPluginLane(),
18905
19170
  // Covers a host signed in outside the one-click button, which would
18906
19171
  // otherwise meet the wizard the first time it opens a terminal.
18907
19172
  markOnboardingComplete(target).catch(() => {
@@ -18944,17 +19209,53 @@ var SpaceCore = class {
18944
19209
  target.paths.repo
18945
19210
  );
18946
19211
  }
19212
+ /** Install the factiii plugin into both CLIs: the hooks behind the session
19213
+ * status dot, the skills this workspace provides, and the board MCP.
19214
+ *
19215
+ * A plugin rather than hand-written config because it is additive — unlike
19216
+ * pointing CLAUDE_CONFIG_DIR or CODEX_HOME at our own directory, which
19217
+ * would hide the user's own settings, skills, MCP entries and auth.
19218
+ *
19219
+ * Both CLIs read the same manifest, so one marketplace serves both, and
19220
+ * adding a marketplace or plugin that is already there is a no-op in each —
19221
+ * which is what makes this safe on every ensure. */
19222
+ async provisionPluginLane() {
19223
+ const board = this.boardMcp();
19224
+ const root = board ? await renderPlugin(board.url).catch(() => null) : null;
19225
+ if (!root) return;
19226
+ const q = `'${root}'`;
19227
+ const cmd = [
19228
+ `claude plugin marketplace add ${q} >/dev/null 2>&1 || true`,
19229
+ `claude plugin install ${PLUGIN_ID} --scope user >/dev/null 2>&1 || true`,
19230
+ `codex plugin marketplace add ${q} >/dev/null 2>&1 || true`,
19231
+ `codex plugin add ${PLUGIN_ID} >/dev/null 2>&1 || true`
19232
+ ].join("; ");
19233
+ try {
19234
+ await this.target().sh(cmd);
19235
+ } catch {
19236
+ }
19237
+ }
18947
19238
  // Register this space's MCPs with both CLIs, user-scoped, and clear the ones
18948
19239
  // it cannot currently offer. Registering regardless left every space holding
18949
19240
  // an entry it never uses, which the CLI then fails to handshake with on every
18950
19241
  // run. Removal is unconditional so a disconnect cleans itself up.
19242
+ //
19243
+ // No early return on an empty lane list: since the board lane moved into the
19244
+ // plugin, a host with no OneDrive has nothing to add and still has the old
19245
+ // board entries to drop.
18951
19246
  async provisionMcpLane() {
18952
19247
  const lanes = this.mcpLanes();
18953
- if (!lanes.length) return;
19248
+ const board = this.boardMcp();
19249
+ const boardCodexTable = board?.name.replace(/-/g, "_");
18954
19250
  const cmd = [
18955
19251
  // Pre-rename entries, when the name was the same for every space.
18956
19252
  `claude mcp remove factiii-onedrive --scope user >/dev/null 2>&1 || true`,
18957
19253
  `codex mcp remove factiii_onedrive >/dev/null 2>&1 || true`,
19254
+ // The board lane moved into the plugin; drop what the old path wrote.
19255
+ ...board ? [
19256
+ `claude mcp remove ${board.name} --scope user >/dev/null 2>&1 || true`,
19257
+ `codex mcp remove ${boardCodexTable} >/dev/null 2>&1 || true`
19258
+ ] : [],
18958
19259
  ...lanes.flatMap(({ mcp, tokenEnv, enabled }) => {
18959
19260
  const codexTable = mcp.name.replace(/-/g, "_");
18960
19261
  const claudeJson = JSON.stringify({
@@ -19571,7 +19872,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
19571
19872
  // ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
19572
19873
  secretsDirs() {
19573
19874
  const spaceDir = this.core.spaceDir();
19574
- return { configDir: (0, import_path9.dirname)(spaceDir), spaceDir };
19875
+ return { configDir: (0, import_path10.dirname)(spaceDir), spaceDir };
19575
19876
  }
19576
19877
  async deploySecretsStatus() {
19577
19878
  return {
@@ -20629,10 +20930,10 @@ ${c.content.trim() || "(no description)"}`
20629
20930
  // ../../shared/all/helpers/board-agent-core/index-service.ts
20630
20931
  var import_child_process5 = require("child_process");
20631
20932
  var import_crypto3 = require("crypto");
20632
- var import_fs3 = require("fs");
20633
- var import_promises6 = __toESM(require("fs/promises"));
20933
+ var import_fs4 = require("fs");
20934
+ var import_promises7 = __toESM(require("fs/promises"));
20634
20935
  var import_net2 = require("net");
20635
- var import_path10 = require("path");
20936
+ var import_path11 = require("path");
20636
20937
  var import_util7 = require("util");
20637
20938
  var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
20638
20939
  var IndexService = class {
@@ -20691,7 +20992,7 @@ var IndexService = class {
20691
20992
  const log = await this.openLog();
20692
20993
  this.proc = (0, import_child_process5.spawn)(
20693
20994
  process.execPath,
20694
- [(0, import_path10.join)(this.opts.indexDir, "index.js")],
20995
+ [(0, import_path11.join)(this.opts.indexDir, "index.js")],
20695
20996
  {
20696
20997
  cwd: this.opts.indexDir,
20697
20998
  env: {
@@ -20719,8 +21020,8 @@ var IndexService = class {
20719
21020
  * opened (a missing log must never stop the service from starting). */
20720
21021
  async openLog() {
20721
21022
  try {
20722
- await import_promises6.default.mkdir(this.opts.dataDir, { recursive: true });
20723
- return (0, import_fs3.openSync)((0, import_path10.join)(this.opts.dataDir, "index.log"), "a");
21023
+ await import_promises7.default.mkdir(this.opts.dataDir, { recursive: true });
21024
+ return (0, import_fs4.openSync)((0, import_path11.join)(this.opts.dataDir, "index.log"), "a");
20724
21025
  } catch {
20725
21026
  return null;
20726
21027
  }
@@ -20729,10 +21030,10 @@ var IndexService = class {
20729
21030
  * platform needing node-gyp fails here rather than at query time. */
20730
21031
  async installDeps() {
20731
21032
  const dir = this.opts.indexDir;
20732
- if ((0, import_fs3.existsSync)((0, import_path10.join)(dir, "node_modules"))) return;
20733
- await import_promises6.default.copyFile(
20734
- (0, import_path10.join)(dir, "image-package.json"),
20735
- (0, import_path10.join)(dir, "package.json")
21033
+ if ((0, import_fs4.existsSync)((0, import_path11.join)(dir, "node_modules"))) return;
21034
+ await import_promises7.default.copyFile(
21035
+ (0, import_path11.join)(dir, "image-package.json"),
21036
+ (0, import_path11.join)(dir, "package.json")
20736
21037
  );
20737
21038
  await execFileAsync4(
20738
21039
  "npm",
@@ -20744,15 +21045,15 @@ var IndexService = class {
20744
21045
  * CLIs' persistent MCP config, so a fresh port each start would leave every
20745
21046
  * registration pointing at a dead one. */
20746
21047
  async stablePort() {
20747
- const file = (0, import_path10.join)(this.opts.dataDir, ".port");
20748
- const saved = Number(await import_promises6.default.readFile(file, "utf-8").catch(() => ""));
21048
+ const file = (0, import_path11.join)(this.opts.dataDir, ".port");
21049
+ const saved = Number(await import_promises7.default.readFile(file, "utf-8").catch(() => ""));
20749
21050
  if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
20750
21051
  return saved;
20751
21052
  }
20752
21053
  const port = await freePort();
20753
- await import_promises6.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
21054
+ await import_promises7.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
20754
21055
  });
20755
- await import_promises6.default.writeFile(file, String(port), "utf-8").catch(() => {
21056
+ await import_promises7.default.writeFile(file, String(port), "utf-8").catch(() => {
20756
21057
  });
20757
21058
  return port;
20758
21059
  }
@@ -20919,25 +21220,25 @@ async function pairWithBrowser(serverUrl) {
20919
21220
  }
20920
21221
 
20921
21222
  // src/config.ts
20922
- var import_fs7 = __toESM(require("fs"));
20923
- var import_os5 = __toESM(require("os"));
20924
- var import_path15 = __toESM(require("path"));
21223
+ var import_fs8 = __toESM(require("fs"));
21224
+ var import_os6 = __toESM(require("os"));
21225
+ var import_path16 = __toESM(require("path"));
20925
21226
 
20926
21227
  // src/secureFile.ts
20927
21228
  var import_crypto6 = __toESM(require("crypto"));
20928
- var import_fs6 = __toESM(require("fs"));
20929
- var import_path14 = __toESM(require("path"));
21229
+ var import_fs7 = __toESM(require("fs"));
21230
+ var import_path15 = __toESM(require("path"));
20930
21231
 
20931
21232
  // src/keychain.ts
20932
21233
  var import_child_process7 = require("child_process");
20933
21234
  var import_crypto4 = __toESM(require("crypto"));
20934
- var import_fs4 = __toESM(require("fs"));
20935
- var import_os3 = __toESM(require("os"));
20936
- var import_path11 = __toESM(require("path"));
21235
+ var import_fs5 = __toESM(require("fs"));
21236
+ var import_os4 = __toESM(require("os"));
21237
+ var import_path12 = __toESM(require("path"));
20937
21238
  var SERVICE = "factiii-runner";
20938
21239
  var ACCOUNT = "config-encryption-key";
20939
- var DPAPI_KEY_FILE = import_path11.default.join(
20940
- import_os3.default.homedir(),
21240
+ var DPAPI_KEY_FILE = import_path12.default.join(
21241
+ import_os4.default.homedir(),
20941
21242
  ".factiii-runner",
20942
21243
  "config-key.dpapi"
20943
21244
  );
@@ -21017,7 +21318,7 @@ function linuxWrite(key) {
21017
21318
  function winRead() {
21018
21319
  let wrapped;
21019
21320
  try {
21020
- wrapped = import_fs4.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
21321
+ wrapped = import_fs5.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
21021
21322
  } catch {
21022
21323
  return { state: "missing" };
21023
21324
  }
@@ -21042,8 +21343,8 @@ function winWrite(key) {
21042
21343
  "-Command",
21043
21344
  `ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
21044
21345
  ]);
21045
- import_fs4.default.mkdirSync(import_path11.default.dirname(DPAPI_KEY_FILE), { recursive: true });
21046
- import_fs4.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
21346
+ import_fs5.default.mkdirSync(import_path12.default.dirname(DPAPI_KEY_FILE), { recursive: true });
21347
+ import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
21047
21348
  `, { mode: 384 });
21048
21349
  }
21049
21350
  function keychainBackend() {
@@ -21205,22 +21506,22 @@ function keychainStatus() {
21205
21506
 
21206
21507
  // src/secureStore.ts
21207
21508
  var import_crypto5 = __toESM(require("crypto"));
21208
- var import_fs5 = __toESM(require("fs"));
21209
- var import_path13 = __toESM(require("path"));
21509
+ var import_fs6 = __toESM(require("fs"));
21510
+ var import_path14 = __toESM(require("path"));
21210
21511
 
21211
21512
  // src/paths.ts
21212
- var import_os4 = __toESM(require("os"));
21213
- var import_path12 = __toESM(require("path"));
21214
- var CONFIG_DIR = import_path12.default.join(import_os4.default.homedir(), ".factiii-runner");
21513
+ var import_os5 = __toESM(require("os"));
21514
+ var import_path13 = __toESM(require("path"));
21515
+ var CONFIG_DIR = import_path13.default.join(import_os5.default.homedir(), ".factiii-runner");
21215
21516
  function safeSlug(spaceSlug) {
21216
21517
  return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
21217
21518
  }
21218
21519
  function spaceDirPath(spaceSlug) {
21219
- return import_path12.default.join(CONFIG_DIR, safeSlug(spaceSlug));
21520
+ return import_path13.default.join(CONFIG_DIR, safeSlug(spaceSlug));
21220
21521
  }
21221
21522
 
21222
21523
  // src/secureStore.ts
21223
- var VAULT_KEY_FILE = import_path13.default.join(CONFIG_DIR, "vault-key.json");
21524
+ var VAULT_KEY_FILE = import_path14.default.join(CONFIG_DIR, "vault-key.json");
21224
21525
  var SCRYPT_N2 = 1 << 15;
21225
21526
  var MIN_PASSWORD_LENGTH = 8;
21226
21527
  var SecureStoreError = class extends Error {
@@ -21293,7 +21594,7 @@ function unseal(box, key) {
21293
21594
  function readVaultFile() {
21294
21595
  try {
21295
21596
  const parsed = JSON.parse(
21296
- import_fs5.default.readFileSync(VAULT_KEY_FILE, "utf-8")
21597
+ import_fs6.default.readFileSync(VAULT_KEY_FILE, "utf-8")
21297
21598
  );
21298
21599
  return parsed.v === 1 ? parsed : null;
21299
21600
  } catch {
@@ -21301,11 +21602,11 @@ function readVaultFile() {
21301
21602
  }
21302
21603
  }
21303
21604
  function writeVaultFile(file) {
21304
- import_fs5.default.mkdirSync(CONFIG_DIR, { recursive: true });
21605
+ import_fs6.default.mkdirSync(CONFIG_DIR, { recursive: true });
21305
21606
  const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
21306
- import_fs5.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
21607
+ import_fs6.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
21307
21608
  `, { mode: 384 });
21308
- import_fs5.default.renameSync(tmp, VAULT_KEY_FILE);
21609
+ import_fs6.default.renameSync(tmp, VAULT_KEY_FILE);
21309
21610
  }
21310
21611
  var unlockedDek = null;
21311
21612
  function createVault(machineKey) {
@@ -21542,15 +21843,15 @@ function parseEnvelope(raw) {
21542
21843
  }
21543
21844
  }
21544
21845
  function atomicWrite(filePath, body) {
21545
- import_fs6.default.mkdirSync(import_path14.default.dirname(filePath), { recursive: true });
21846
+ import_fs7.default.mkdirSync(import_path15.default.dirname(filePath), { recursive: true });
21546
21847
  const tmp = `${filePath}.${process.pid}.tmp`;
21547
- import_fs6.default.writeFileSync(tmp, body, { mode: 384 });
21548
- import_fs6.default.renameSync(tmp, filePath);
21848
+ import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
21849
+ import_fs7.default.renameSync(tmp, filePath);
21549
21850
  }
21550
21851
  function readSecureBuffer(filePath) {
21551
21852
  let raw;
21552
21853
  try {
21553
- raw = import_fs6.default.readFileSync(filePath, "utf-8");
21854
+ raw = import_fs7.default.readFileSync(filePath, "utf-8");
21554
21855
  } catch {
21555
21856
  return null;
21556
21857
  }
@@ -21607,7 +21908,7 @@ function writeSecureJson(filePath, value2) {
21607
21908
  function readBootJson(filePath) {
21608
21909
  let raw;
21609
21910
  try {
21610
- raw = import_fs6.default.readFileSync(filePath, "utf-8");
21911
+ raw = import_fs7.default.readFileSync(filePath, "utf-8");
21611
21912
  } catch {
21612
21913
  return null;
21613
21914
  }
@@ -21657,8 +21958,8 @@ function writeBootJson(filePath, value2) {
21657
21958
  }
21658
21959
 
21659
21960
  // src/config.ts
21660
- var CONFIG_DIR2 = import_path15.default.join(import_os5.default.homedir(), ".factiii-runner");
21661
- var CONFIG_FILE = import_path15.default.join(CONFIG_DIR2, "config.json");
21961
+ var CONFIG_DIR2 = import_path16.default.join(import_os6.default.homedir(), ".factiii-runner");
21962
+ var CONFIG_FILE = import_path16.default.join(CONFIG_DIR2, "config.json");
21662
21963
  function readRunnerConfig() {
21663
21964
  return readBootJson(CONFIG_FILE);
21664
21965
  }
@@ -21666,7 +21967,7 @@ function writeRunnerConfig(config) {
21666
21967
  writeBootJson(CONFIG_FILE, config);
21667
21968
  }
21668
21969
  function configExists() {
21669
- return import_fs7.default.existsSync(CONFIG_FILE);
21970
+ return import_fs8.default.existsSync(CONFIG_FILE);
21670
21971
  }
21671
21972
 
21672
21973
  // src/promptPassword.ts
@@ -21828,9 +22129,9 @@ function makeChunkReassembler() {
21828
22129
 
21829
22130
  // src/daemon.ts
21830
22131
  var import_crypto7 = require("crypto");
21831
- var import_fs9 = __toESM(require("fs"));
22132
+ var import_fs10 = __toESM(require("fs"));
21832
22133
  var import_node_datachannel = require("node-datachannel");
21833
- var import_path17 = __toESM(require("path"));
22134
+ var import_path18 = __toESM(require("path"));
21834
22135
 
21835
22136
  // ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
21836
22137
  var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
@@ -23010,12 +23311,12 @@ function parse2(str) {
23010
23311
  uri.queryKey = queryKey(uri, uri["query"]);
23011
23312
  return uri;
23012
23313
  }
23013
- function pathNames(obj, path17) {
23014
- const regx = /\/{2,9}/g, names = path17.replace(regx, "/").split("/");
23015
- if (path17.slice(0, 1) == "/" || path17.length === 0) {
23314
+ function pathNames(obj, path18) {
23315
+ const regx = /\/{2,9}/g, names = path18.replace(regx, "/").split("/");
23316
+ if (path18.slice(0, 1) == "/" || path18.length === 0) {
23016
23317
  names.splice(0, 1);
23017
23318
  }
23018
- if (path17.slice(-1) == "/") {
23319
+ if (path18.slice(-1) == "/") {
23019
23320
  names.splice(names.length - 1, 1);
23020
23321
  }
23021
23322
  return names;
@@ -23634,7 +23935,7 @@ var protocol2 = Socket.protocol;
23634
23935
  // ../../node_modules/socket.io-client/build/esm-debug/url.js
23635
23936
  var import_debug7 = __toESM(require_src(), 1);
23636
23937
  var debug7 = (0, import_debug7.default)("socket.io-client:url");
23637
- function url(uri, path17 = "", loc) {
23938
+ function url(uri, path18 = "", loc) {
23638
23939
  let obj = uri;
23639
23940
  loc = loc || typeof location !== "undefined" && location;
23640
23941
  if (null == uri)
@@ -23668,7 +23969,7 @@ function url(uri, path17 = "", loc) {
23668
23969
  obj.path = obj.path || "/";
23669
23970
  const ipv6 = obj.host.indexOf(":") !== -1;
23670
23971
  const host = ipv6 ? "[" + obj.host + "]" : obj.host;
23671
- obj.id = obj.protocol + "://" + host + ":" + obj.port + path17;
23972
+ obj.id = obj.protocol + "://" + host + ":" + obj.port + path18;
23672
23973
  obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
23673
23974
  return obj;
23674
23975
  }
@@ -25302,8 +25603,8 @@ function lookup(uri, opts) {
25302
25603
  const parsed = url(uri, opts.path || "/socket.io");
25303
25604
  const source = parsed.source;
25304
25605
  const id = parsed.id;
25305
- const path17 = parsed.path;
25306
- const sameNamespace = cache[id] && path17 in cache[id]["nsps"];
25606
+ const path18 = parsed.path;
25607
+ const sameNamespace = cache[id] && path18 in cache[id]["nsps"];
25307
25608
  const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
25308
25609
  let io;
25309
25610
  if (newConnection) {
@@ -25345,8 +25646,8 @@ function cardContextPath(target, postId) {
25345
25646
  const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
25346
25647
  return `${target.paths.root}/.cards/${safe}.md`;
25347
25648
  }
25348
- function cardContextPrompt(path17) {
25349
- return `Read ${path17} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
25649
+ function cardContextPrompt(path18) {
25650
+ return `Read ${path18} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
25350
25651
  }
25351
25652
  var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
25352
25653
  var RESUME_SEED = "You are now in an interactive terminal, not the board UI. Respond in plain text and use your normal tools; do not use openui-lang or component schemas anymore.";
@@ -25510,11 +25811,11 @@ var TerminalManager = class {
25510
25811
  };
25511
25812
 
25512
25813
  // src/agent-adapter.ts
25513
- var import_fs8 = __toESM(require("fs"));
25514
- var import_path16 = __toESM(require("path"));
25515
- var RUNNER_CONFIG_PATH = import_path16.default.join(CONFIG_DIR, "runner-config.json");
25814
+ var import_fs9 = __toESM(require("fs"));
25815
+ var import_path17 = __toESM(require("path"));
25816
+ var RUNNER_CONFIG_PATH = import_path17.default.join(CONFIG_DIR, "runner-config.json");
25516
25817
  function connectionPath(spaceSlug) {
25517
- return import_path16.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
25818
+ return import_path17.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
25518
25819
  }
25519
25820
  function writeJson(filePath, value2) {
25520
25821
  writeSecureJson(filePath, value2);
@@ -25541,14 +25842,14 @@ function migrateLegacyConfigs() {
25541
25842
  const file = { boards: {} };
25542
25843
  let legacy = [];
25543
25844
  try {
25544
- legacy = import_fs8.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
25845
+ legacy = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
25545
25846
  } catch {
25546
25847
  return file;
25547
25848
  }
25548
25849
  for (const name of legacy) {
25549
25850
  const slug = name.slice("space-".length, -".json".length);
25550
25851
  const parsed = readSecureJson(
25551
- import_path16.default.join(CONFIG_DIR, name)
25852
+ import_path17.default.join(CONFIG_DIR, name)
25552
25853
  );
25553
25854
  if (!parsed) continue;
25554
25855
  if (!file.defaultSlug) {
@@ -25568,14 +25869,14 @@ function encryptStrayPlaintextConfigs() {
25568
25869
  const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
25569
25870
  let names = [];
25570
25871
  try {
25571
- names = import_fs8.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
25872
+ names = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
25572
25873
  } catch {
25573
25874
  return;
25574
25875
  }
25575
25876
  for (const name of names) {
25576
- const filePath = import_path16.default.join(CONFIG_DIR, name);
25877
+ const filePath = import_path17.default.join(CONFIG_DIR, name);
25577
25878
  try {
25578
- const raw = import_fs8.default.readFileSync(filePath, "utf-8");
25879
+ const raw = import_fs9.default.readFileSync(filePath, "utf-8");
25579
25880
  if (!raw.startsWith("{")) continue;
25580
25881
  writeSecureJson(filePath, JSON.parse(raw));
25581
25882
  } catch {
@@ -25606,7 +25907,7 @@ async function getConfiguredConnections() {
25606
25907
  }
25607
25908
  if (await isSignedIn(hostTarget(CONFIG_DIR), "claude")) kinds.add("claude");
25608
25909
  try {
25609
- const hasOneDrive = import_fs8.default.readdirSync(CONFIG_DIR).some((f) => f.endsWith(".onedrive.json"));
25910
+ const hasOneDrive = import_fs9.default.readdirSync(CONFIG_DIR).some((f) => f.endsWith(".onedrive.json"));
25610
25911
  if (hasOneDrive) kinds.add("onedrive");
25611
25912
  } catch {
25612
25913
  }
@@ -25677,7 +25978,7 @@ var localConfigProvider = {
25677
25978
  writeOneDriveConnection(spaceSlug, connection) {
25678
25979
  const filePath = connectionPath(spaceSlug);
25679
25980
  if (!connection) {
25680
- import_fs8.default.rmSync(filePath, { force: true });
25981
+ import_fs9.default.rmSync(filePath, { force: true });
25681
25982
  return;
25682
25983
  }
25683
25984
  writeJson(filePath, connection);
@@ -25702,7 +26003,7 @@ var localConfigProvider = {
25702
26003
  "claude",
25703
26004
  "codex"
25704
26005
  ]) {
25705
- import_fs8.default.mkdirSync(import_path16.default.join(dir, sub), { recursive: true });
26006
+ import_fs9.default.mkdirSync(import_path17.default.join(dir, sub), { recursive: true });
25706
26007
  }
25707
26008
  return dir;
25708
26009
  }
@@ -25876,15 +26177,15 @@ async function startDaemon(config) {
25876
26177
  }
25877
26178
  );
25878
26179
  const indexCandidates = [
25879
- import_path17.default.join(__dirname, "..", "index"),
25880
- import_path17.default.join(__dirname, "..", "..", "index", "image")
26180
+ import_path18.default.join(__dirname, "..", "index"),
26181
+ import_path18.default.join(__dirname, "..", "..", "index", "image")
25881
26182
  ];
25882
- const indexDir = indexCandidates.find((p) => import_fs9.default.existsSync(p)) ?? indexCandidates[0];
26183
+ const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
25883
26184
  const odIndex = new IndexService({
25884
26185
  controlToken: (0, import_crypto7.createHash)("sha256").update(config.authToken).digest("hex"),
25885
26186
  embedUrl: () => config.serverUrl,
25886
26187
  indexDir,
25887
- dataDir: import_path17.default.join(CONFIG_DIR, "index-data")
26188
+ dataDir: import_path18.default.join(CONFIG_DIR, "index-data")
25888
26189
  });
25889
26190
  void odIndex.ensureRunning().catch((err) => {
25890
26191
  console.error(