@factiii/runner 0.10.2 → 0.11.0

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
@@ -963,8 +963,8 @@ 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 path18 = require("node:path");
967
- var fs14 = require("node:fs");
966
+ var path20 = require("node:path");
967
+ var fs15 = require("node:fs");
968
968
  var process2 = require("node:process");
969
969
  var { Argument: Argument2, humanReadableArgName } = require_argument();
970
970
  var { CommanderError: CommanderError2 } = require_error();
@@ -1896,11 +1896,11 @@ 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 = path18.resolve(baseDir, baseName);
1900
- if (fs14.existsSync(localBin)) return localBin;
1901
- if (sourceExt.includes(path18.extname(baseName))) return void 0;
1899
+ const localBin = path20.resolve(baseDir, baseName);
1900
+ if (fs15.existsSync(localBin)) return localBin;
1901
+ if (sourceExt.includes(path20.extname(baseName))) return void 0;
1902
1902
  const foundExt = sourceExt.find(
1903
- (ext) => fs14.existsSync(`${localBin}${ext}`)
1903
+ (ext) => fs15.existsSync(`${localBin}${ext}`)
1904
1904
  );
1905
1905
  if (foundExt) return `${localBin}${foundExt}`;
1906
1906
  return void 0;
@@ -1912,21 +1912,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1912
1912
  if (this._scriptPath) {
1913
1913
  let resolvedScriptPath;
1914
1914
  try {
1915
- resolvedScriptPath = fs14.realpathSync(this._scriptPath);
1915
+ resolvedScriptPath = fs15.realpathSync(this._scriptPath);
1916
1916
  } catch (err) {
1917
1917
  resolvedScriptPath = this._scriptPath;
1918
1918
  }
1919
- executableDir = path18.resolve(
1920
- path18.dirname(resolvedScriptPath),
1919
+ executableDir = path20.resolve(
1920
+ path20.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 = path18.basename(
1927
+ const legacyName = path20.basename(
1928
1928
  this._scriptPath,
1929
- path18.extname(this._scriptPath)
1929
+ path20.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(path18.extname(executableFile));
1940
+ launchWithNode = sourceExt.includes(path20.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 = path18.basename(filename, path18.extname(filename));
2780
+ this._name = path20.basename(filename, path20.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(path19) {
2795
- if (path19 === void 0) return this._executableDir;
2796
- this._executableDir = path19;
2794
+ executableDir(path21) {
2795
+ if (path21 === void 0) return this._executableDir;
2796
+ this._executableDir = path21;
2797
2797
  return this;
2798
2798
  }
2799
2799
  /**
@@ -3026,7 +3026,7 @@ var require_commander = __commonJS({
3026
3026
  // ../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
3027
3027
  var require_XMLHttpRequest = __commonJS({
3028
3028
  "../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js"(exports2, module2) {
3029
- var fs14 = require("fs");
3029
+ var fs15 = require("fs");
3030
3030
  var Url = require("url");
3031
3031
  var spawn5 = require("child_process").spawn;
3032
3032
  module2.exports = XMLHttpRequest3;
@@ -3184,7 +3184,7 @@ var require_XMLHttpRequest = __commonJS({
3184
3184
  throw new Error("XMLHttpRequest: Only GET method is supported");
3185
3185
  }
3186
3186
  if (settings.async) {
3187
- fs14.readFile(unescape(url2.pathname), function(error, data2) {
3187
+ fs15.readFile(unescape(url2.pathname), function(error, data2) {
3188
3188
  if (error) {
3189
3189
  self.handleError(error, error.errno || -1);
3190
3190
  } else {
@@ -3196,7 +3196,7 @@ var require_XMLHttpRequest = __commonJS({
3196
3196
  });
3197
3197
  } else {
3198
3198
  try {
3199
- this.response = fs14.readFileSync(unescape(url2.pathname));
3199
+ this.response = fs15.readFileSync(unescape(url2.pathname));
3200
3200
  this.responseText = this.response.toString("utf8");
3201
3201
  this.status = 200;
3202
3202
  setState(self.DONE);
@@ -3322,15 +3322,15 @@ var require_XMLHttpRequest = __commonJS({
3322
3322
  } else {
3323
3323
  var contentFile = ".node-xmlhttprequest-content-" + process.pid;
3324
3324
  var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
3325
- fs14.writeFileSync(syncFile, "", "utf8");
3325
+ fs15.writeFileSync(syncFile, "", "utf8");
3326
3326
  var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
3327
3327
  var syncProc = spawn5(process.argv[0], ["-e", execString]);
3328
3328
  var statusText;
3329
- while (fs14.existsSync(syncFile)) {
3329
+ while (fs15.existsSync(syncFile)) {
3330
3330
  }
3331
- self.responseText = fs14.readFileSync(contentFile, "utf8");
3331
+ self.responseText = fs15.readFileSync(contentFile, "utf8");
3332
3332
  syncProc.stdin.end();
3333
- fs14.unlinkSync(contentFile);
3333
+ fs15.unlinkSync(contentFile);
3334
3334
  if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
3335
3335
  var errorObj = JSON.parse(self.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, ""));
3336
3336
  self.handleError(errorObj, 503);
@@ -6497,7 +6497,7 @@ var require_websocket = __commonJS({
6497
6497
  var EventEmitter = require("events");
6498
6498
  var https = require("https");
6499
6499
  var http = require("http");
6500
- var net2 = require("net");
6500
+ var net3 = require("net");
6501
6501
  var tls = require("tls");
6502
6502
  var { randomBytes, createHash: createHash2 } = require("crypto");
6503
6503
  var { Duplex, Readable } = require("stream");
@@ -7241,12 +7241,12 @@ var require_websocket = __commonJS({
7241
7241
  }
7242
7242
  function netConnect(options) {
7243
7243
  options.path = options.socketPath;
7244
- return net2.connect(options);
7244
+ return net3.connect(options);
7245
7245
  }
7246
7246
  function tlsConnect(options) {
7247
7247
  options.path = void 0;
7248
7248
  if (!options.servername && options.servername !== "") {
7249
- options.servername = net2.isIP(options.host) ? "" : options.host;
7249
+ options.servername = net3.isIP(options.host) ? "" : options.host;
7250
7250
  }
7251
7251
  return tls.connect(options);
7252
7252
  }
@@ -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: path18, errorMaps, issueData } = params;
8432
- const fullPath = [...path18, ...issueData.path || []];
8431
+ const { data, path: path20, errorMaps, issueData } = params;
8432
+ const fullPath = [...path20, ...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, path18, key) {
8548
+ constructor(parent, value2, path20, key) {
8549
8549
  this._cachedPath = [];
8550
8550
  this.parent = parent;
8551
8551
  this.data = value2;
8552
- this._path = path18;
8552
+ this._path = path20;
8553
8553
  this._key = key;
8554
8554
  }
8555
8555
  get path() {
@@ -12197,9 +12197,9 @@ function processLine(trimmed, state, cb) {
12197
12197
  if (e.type === "result" /* Result */) {
12198
12198
  if (e.is_error) {
12199
12199
  state.isError = true;
12200
- const errorText = e.result || "Claude encountered an error";
12201
- state.fullText = errorText;
12202
- cb.onLog?.({ type: "error", content: errorText });
12200
+ const errorText2 = e.result || "Claude encountered an error";
12201
+ state.fullText = errorText2;
12202
+ cb.onLog?.({ type: "error", content: errorText2 });
12203
12203
  return;
12204
12204
  }
12205
12205
  if (e.result && !state.fullText) {
@@ -12647,7 +12647,8 @@ var prompts_default = {
12647
12647
  "- $WORKSPACE - the repo checkout (the working directory), reset to the default branch at run start",
12648
12648
  "- $BACKUPS_DIR - durable backups; every backup (db dumps etc) is written here and nowhere else",
12649
12649
  "- $BUILDS_DIR - build artifacts; each build writes into $BUILDS_DIR/<commit-hash>/",
12650
- "Reference paths ONLY via these variables or relative to the working directory. NEVER hardcode absolute machine paths (like /Users/...) anywhere in the skill - it must run unchanged on any runner. Secrets live in an encrypted store you never touch: their values reach commands only via runner-side injection of the keys declared in required-variables.json.",
12650
+ "Reference paths ONLY via these variables or relative to the working directory. NEVER hardcode absolute machine paths (like /Users/...) anywhere in the skill - it must run unchanged on any runner.",
12651
+ "Secrets live in an encrypted store you never touch, and `factiii-secrets` is how a command gets them - it is on the PATH of every run. Load the `factiii-secrets` skill installed on this machine (it is not part of this repo) for exactly how it behaves, and write the skill's commands in that shape.",
12651
12652
  "",
12652
12653
  "## Produce exactly three files",
12653
12654
  "1. .agents/skills/deploy/SKILL.md - the runbook, written for a competent operator. It MUST start with YAML frontmatter (--- name: deploy / description: one line saying this is the repository's release runbook and when to run it ---) so agent CLIs discover it as a skill, followed by: named steps in ship order, the exact commands, what success looks like per step, and explicit stop-and-ask points wherever human judgment or confirmation belongs (e.g. 'confirm before the production cutover'). Plain language; no secret values or key material anywhere in the file - reference secrets ONLY as $KEY env names.",
@@ -12658,7 +12659,7 @@ var prompts_default = {
12658
12659
  "- Write ONLY those three files under .agents/skills/deploy/.",
12659
12660
  "- Do NOT git commit or git push; the harness pushes for review.",
12660
12661
  "- Cloud CLIs on this machine (aws, vercel, wrangler, gh...) may already be signed in. You may use them READ-ONLY to verify what exists (project names, repo names, regions) so the skill is accurate - but NEVER create, modify, deploy, or delete anything, and never echo a credential or token value.",
12661
- "- The harness injects required variables per-command at execution time (the owner authorizes each secret-bearing command individually). Write the skill accordingly: commands simply reference the variables; NEVER include steps that check whether variables are set in the environment, and never instruct anyone to export them or put them in a .env/shell/CI config.",
12662
+ '- Secret-bearing commands: any command needing a variable declared in required-variables.json MUST be written in the skill as `factiii-secrets run -- <command>`, with the variables referenced unexpanded and quoted (e.g. "$DATABASE_URL"). The owner authorizes each one at run time and the runner injects the values. Wrap only the commands that need a secret. NEVER include steps that check whether variables are set in the environment, and never instruct anyone to export them or put them in a .env/shell/CI config.',
12662
12663
  "- No secret values in any file, ever."
12663
12664
  ],
12664
12665
  deployCreateRevise: [
@@ -12666,18 +12667,18 @@ var prompts_default = {
12666
12667
  "",
12667
12668
  "{{FEEDBACK}}",
12668
12669
  "",
12669
- "Apply them by editing .agents/skills/deploy/SKILL.md, .agents/skills/deploy/required-variables.json, and/or .agents/skills/deploy/check-environment.sh in your working directory - the same contract as before: only those three files, keys declared exactly when used, no secret values anywhere, no git commit/push, and paths referenced ONLY via $WORKSPACE / $BACKUPS_DIR / $BUILDS_DIR or relative to the working directory (never absolute machine paths). Keep everything the owner did NOT ask to change untouched."
12670
+ "Apply them by editing .agents/skills/deploy/SKILL.md, .agents/skills/deploy/required-variables.json, and/or .agents/skills/deploy/check-environment.sh in your working directory - the same contract as before: only those three files, keys declared exactly when used, every secret-bearing command still written as `factiii-secrets run -- <command>`, no secret values anywhere, no git commit/push, and paths referenced ONLY via $WORKSPACE / $BACKUPS_DIR / $BUILDS_DIR or relative to the working directory (never absolute machine paths). Keep everything the owner did NOT ask to change untouched."
12670
12671
  ],
12671
12672
  deployRun: [
12672
12673
  "## Deploy run protocol",
12673
12674
  "You are executing this repository's deploy skill NOW, on the owner's machine, for real. Invoke it and follow it exactly - the skill is the program; do not improvise steps it doesn't contain or change any step's intent.",
12674
12675
  "",
12675
- "## The secrets model (read this before anything else)",
12676
- "Every variable declared in required-variables.json is UNSET in your shell BY DESIGN, and will stay unset for the whole run. That is the security model working, not a problem: the owner already stored the values encrypted on this machine, and the runner injects them into commands you request through the NEED_SECRETS gate below. So:",
12677
- "- NEVER check whether these variables are set (no `env`, `printenv`, `test -n`, echo checks). Their absence tells you nothing.",
12678
- "- NEVER report them as missing, ask the owner to configure them, or abort the run because your shell lacks them. Aborting for unset declared variables is ALWAYS wrong - the gate is how every one of them gets used.",
12679
- "- If the skill has a step that verifies variables are set, SKIP it - it predates this protocol.",
12680
- '- To run a command that needs any declared variable, print as the LAST line of your message exactly: NEED_SECRETS: <the complete command> - then END your turn. Write the variables unexpanded in that command (e.g. "$DATABASE_URL"). The owner inspects and authorizes it with their password; the runner executes it with every declared variable injected, and your next message contains its redacted output. One command per request.',
12676
+ "## Secrets (read this before anything else)",
12677
+ "Every variable declared in required-variables.json is UNSET in your shell BY DESIGN, and will stay unset for the whole run. The values are stored encrypted on this machine, and `factiii-secrets` is how a command gets them - it is on your PATH, and the `factiii-secrets` skill describes it in full.",
12678
+ "- To run a command that needs any declared variable, prefix it: `factiii-secrets run -- <command>`. The owner authorizes it with their password, the runner runs it with every declared value injected, and you get the output back with the values redacted. It exits with the command's own code, so `&&`, `||` and `set -e` behave normally. Exit 77 means the owner declined - adapt or stop, and never retry the same command.",
12679
+ '- Quote the variables (`"$DATABASE_URL"`): they expand on the runner\'s side, not in your shell.',
12680
+ "- NEVER check whether these variables are set (no `env`, `printenv`, `test -n`, echo checks), never report them as missing, and never abort because your shell lacks them. Their absence tells you nothing. If the skill has a step that verifies variables are set, SKIP it - it predates this protocol.",
12681
+ "- Wrap ONLY the commands that actually need a secret. Everything else runs unwrapped and needs no authorization.",
12681
12682
  "",
12682
12683
  "- Track the release with your todo/plan tool: one todo per step in ship order, updated as you go. This is the owner's live timeline.",
12683
12684
  "- HUMAN GATES: wherever the skill says to stop and ask (and before anything irreversible the skill flags), print as the LAST line: AWAITING_INPUT: <the question> - then END your turn. The owner's answer arrives as your next message.",
@@ -12908,7 +12909,7 @@ ${diff.slice(0, 8e3)}`,
12908
12909
  }
12909
12910
 
12910
12911
  // ../../shared/all/helpers/board-agent-core/engine.ts
12911
- var import_path10 = require("path");
12912
+ var import_path12 = require("path");
12912
12913
 
12913
12914
  // ../../shared/all/domains/electron.ts
12914
12915
  var SETUP_LOG_PREFIX = "log ";
@@ -13017,7 +13018,7 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
13017
13018
  await target.sh(`mkdir -p ${target.paths.worktrees}`);
13018
13019
  }
13019
13020
  async function addWorktree(target, workName, baseBranch, kind) {
13020
- const path18 = worktreeFor(target.paths, workName);
13021
+ const path20 = worktreeFor(target.paths, workName);
13021
13022
  const branch = workBranch(workName, kind);
13022
13023
  await gitInDir(
13023
13024
  target,
@@ -13027,7 +13028,7 @@ async function addWorktree(target, workName, baseBranch, kind) {
13027
13028
  baseBranch
13028
13029
  ).catch(() => {
13029
13030
  });
13030
- await removeWorktree(target, path18).catch(() => {
13031
+ await removeWorktree(target, path20).catch(() => {
13031
13032
  });
13032
13033
  await gitInDir(
13033
13034
  target,
@@ -13036,30 +13037,30 @@ async function addWorktree(target, workName, baseBranch, kind) {
13036
13037
  "add",
13037
13038
  "-B",
13038
13039
  branch,
13039
- path18,
13040
+ path20,
13040
13041
  `origin/${baseBranch}`
13041
13042
  );
13042
- return { path: path18, branch };
13043
+ return { path: path20, branch };
13043
13044
  }
13044
- async function removeWorktree(target, path18) {
13045
+ async function removeWorktree(target, path20) {
13045
13046
  await gitInDir(
13046
13047
  target,
13047
13048
  target.paths.repo,
13048
13049
  "worktree",
13049
13050
  "remove",
13050
13051
  "--force",
13051
- path18
13052
+ path20
13052
13053
  );
13053
13054
  }
13054
- async function removeWorktreeAndBranch(target, path18) {
13055
+ async function removeWorktreeAndBranch(target, path20) {
13055
13056
  const branch = await gitInDir(
13056
13057
  target,
13057
- path18,
13058
+ path20,
13058
13059
  "rev-parse",
13059
13060
  "--abbrev-ref",
13060
13061
  "HEAD"
13061
13062
  ).then((out) => out.trim()).catch(() => "");
13062
- await removeWorktree(target, path18);
13063
+ await removeWorktree(target, path20);
13063
13064
  if (branch && branch !== "HEAD") {
13064
13065
  await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
13065
13066
  () => {
@@ -13073,8 +13074,8 @@ async function pruneWorktrees(target) {
13073
13074
  }
13074
13075
  );
13075
13076
  }
13076
- function isWorktreePath(target, path18) {
13077
- return path18.startsWith(`${target.paths.worktrees}/`);
13077
+ function isWorktreePath(target, path20) {
13078
+ return path20.startsWith(`${target.paths.worktrees}/`);
13078
13079
  }
13079
13080
  async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
13080
13081
  if (name) await gitInDir(target, workdir, "config", "user.name", name);
@@ -13100,13 +13101,13 @@ function parseStatus(raw) {
13100
13101
  const y = line[1];
13101
13102
  const rest = line.slice(3);
13102
13103
  const arrow = rest.indexOf(" -> ");
13103
- const path18 = arrow === -1 ? rest : rest.slice(arrow + 4);
13104
+ const path20 = arrow === -1 ? rest : rest.slice(arrow + 4);
13104
13105
  if (x === "?" || y === "?") {
13105
- unstaged.push({ path: path18, code: "A" });
13106
+ unstaged.push({ path: path20, code: "A" });
13106
13107
  continue;
13107
13108
  }
13108
- if (x !== " ") staged.push({ path: path18, code: x });
13109
- if (y !== " ") unstaged.push({ path: path18, code: y });
13109
+ if (x !== " ") staged.push({ path: path20, code: x });
13110
+ if (y !== " ") unstaged.push({ path: path20, code: y });
13110
13111
  }
13111
13112
  return { staged, unstaged };
13112
13113
  }
@@ -13848,13 +13849,13 @@ function createEventSink(send, spaceSlug, notify) {
13848
13849
 
13849
13850
  // ../../shared/all/helpers/card-ai/frpc.ts
13850
13851
  var import_fs2 = require("fs");
13851
- var import_promises4 = __toESM(require("fs/promises"));
13852
- var import_path6 = __toESM(require("path"));
13852
+ var import_promises5 = __toESM(require("fs/promises"));
13853
+ var import_path7 = __toESM(require("path"));
13853
13854
 
13854
13855
  // ../../shared/all/helpers/board-agent-core/host-skills.ts
13855
- var import_promises3 = __toESM(require("fs/promises"));
13856
+ var import_promises4 = __toESM(require("fs/promises"));
13856
13857
  var import_os2 = __toESM(require("os"));
13857
- var import_path5 = __toESM(require("path"));
13858
+ var import_path6 = __toESM(require("path"));
13858
13859
 
13859
13860
  // ../../shared/all/domains/posts.ts
13860
13861
  var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
@@ -14364,9 +14365,134 @@ var skillScopeSchema = external_exports.enum(["global", "repo"]);
14364
14365
 
14365
14366
  // ../../shared/all/helpers/board-agent-core/host-deploy.ts
14366
14367
  var import_child_process3 = require("child_process");
14368
+ var import_promises3 = __toESM(require("fs/promises"));
14369
+ var import_path5 = __toESM(require("path"));
14370
+ var import_util5 = require("util");
14371
+
14372
+ // ../../shared/all/helpers/board-agent-core/secrets-bridge.ts
14367
14373
  var import_promises2 = __toESM(require("fs/promises"));
14374
+ var import_net2 = __toESM(require("net"));
14368
14375
  var import_path4 = __toESM(require("path"));
14369
- var import_util5 = require("util");
14376
+ function secretsSocketPath(spaceDir) {
14377
+ return import_path4.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
14378
+ }
14379
+ function redactor(values) {
14380
+ const significant = values.filter((v) => v.length >= 4);
14381
+ return (text) => {
14382
+ let out = text;
14383
+ for (const value2 of significant) {
14384
+ out = out.split(value2).join("[REDACTED]");
14385
+ }
14386
+ return out;
14387
+ };
14388
+ }
14389
+ function replier(conn) {
14390
+ return (message) => {
14391
+ if (conn.destroyed) return;
14392
+ conn.write(`${JSON.stringify(message)}
14393
+ `);
14394
+ };
14395
+ }
14396
+ async function handleRun(request, handlers, reply) {
14397
+ const command = typeof request.command === "string" ? request.command.trim() : "";
14398
+ if (!command) {
14399
+ reply({ type: "error", message: "No command was sent." });
14400
+ return;
14401
+ }
14402
+ const cwd = typeof request.cwd === "string" ? request.cwd : "";
14403
+ const result = await handlers.run({
14404
+ command,
14405
+ cwd,
14406
+ status: (text) => reply({ type: "status", text })
14407
+ });
14408
+ if ("denied" in result) {
14409
+ reply({ type: "denied" });
14410
+ return;
14411
+ }
14412
+ if (result.output) reply({ type: "output", chunk: result.output });
14413
+ reply({ type: "exit", code: result.code });
14414
+ }
14415
+ async function startSecretsBridge(opts) {
14416
+ const socketPath = secretsSocketPath(opts.spaceDir);
14417
+ await import_promises2.default.mkdir(import_path4.default.dirname(socketPath), { recursive: true });
14418
+ await import_promises2.default.rm(socketPath, { force: true });
14419
+ const { handlers } = opts;
14420
+ let queue = Promise.resolve();
14421
+ let queueDepth = 0;
14422
+ const live = /* @__PURE__ */ new Set();
14423
+ const server = import_net2.default.createServer((conn) => {
14424
+ live.add(conn);
14425
+ conn.on("close", () => live.delete(conn));
14426
+ const reply = replier(conn);
14427
+ let buffer = "";
14428
+ let handled = false;
14429
+ conn.on("error", () => {
14430
+ });
14431
+ conn.on("data", (chunk) => {
14432
+ if (handled) return;
14433
+ buffer += chunk.toString("utf-8");
14434
+ const newline = buffer.indexOf("\n");
14435
+ if (newline === -1) return;
14436
+ handled = true;
14437
+ let request;
14438
+ try {
14439
+ request = JSON.parse(buffer.slice(0, newline));
14440
+ } catch {
14441
+ reply({ type: "error", message: "Malformed request." });
14442
+ conn.end();
14443
+ return;
14444
+ }
14445
+ if (request.op === "list") {
14446
+ void handlers.list().then((keys) => reply({ type: "keys", keys })).catch(
14447
+ (err) => reply({ type: "error", message: errorText(err) })
14448
+ ).finally(() => conn.end());
14449
+ return;
14450
+ }
14451
+ if (request.op !== "run") {
14452
+ reply({ type: "error", message: "Unknown operation." });
14453
+ conn.end();
14454
+ return;
14455
+ }
14456
+ if (queueDepth > 0) {
14457
+ reply({
14458
+ type: "status",
14459
+ text: "queued behind another command awaiting authorization"
14460
+ });
14461
+ }
14462
+ queueDepth += 1;
14463
+ queue = queue.then(() => {
14464
+ if (conn.destroyed) return void 0;
14465
+ return handleRun(request, handlers, reply);
14466
+ }).catch(
14467
+ (err) => reply({ type: "error", message: errorText(err) })
14468
+ ).finally(() => {
14469
+ queueDepth -= 1;
14470
+ conn.end();
14471
+ });
14472
+ });
14473
+ });
14474
+ await new Promise((resolve2, reject) => {
14475
+ server.once("error", reject);
14476
+ server.listen(socketPath, () => {
14477
+ server.removeListener("error", reject);
14478
+ resolve2();
14479
+ });
14480
+ });
14481
+ await import_promises2.default.chmod(socketPath, 384).catch(() => void 0);
14482
+ return {
14483
+ close: async () => {
14484
+ for (const conn of live) conn.destroy();
14485
+ live.clear();
14486
+ await new Promise((resolve2) => server.close(() => resolve2()));
14487
+ await import_promises2.default.rm(socketPath, { force: true });
14488
+ }
14489
+ };
14490
+ }
14491
+ function errorText(err) {
14492
+ return err instanceof Error ? err.message : String(err);
14493
+ }
14494
+
14495
+ // ../../shared/all/helpers/board-agent-core/host-deploy.ts
14370
14496
  var execFileAsync2 = (0, import_util5.promisify)(import_child_process3.execFile);
14371
14497
  var SKILL_DIR = ".agents/skills/deploy";
14372
14498
  var SKILL_PATH = `${SKILL_DIR}/SKILL.md`;
@@ -14375,14 +14501,14 @@ var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
14375
14501
  var CLAUDE_SKILLS_DIR = ".claude/skills";
14376
14502
  var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
14377
14503
  async function ensureClaudeSkillLink(workspace) {
14378
- const linkPath = import_path4.default.join(workspace, CLAUDE_LINK);
14504
+ const linkPath = import_path5.default.join(workspace, CLAUDE_LINK);
14379
14505
  try {
14380
- await import_promises2.default.lstat(linkPath);
14506
+ await import_promises3.default.lstat(linkPath);
14381
14507
  return;
14382
14508
  } catch {
14383
14509
  }
14384
- await import_promises2.default.mkdir(import_path4.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
14385
- await import_promises2.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
14510
+ await import_promises3.default.mkdir(import_path5.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
14511
+ await import_promises3.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
14386
14512
  }
14387
14513
  var GIT_NO_HELPER = ["-c", "credential.helper="];
14388
14514
  var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
@@ -14427,7 +14553,7 @@ async function gitCatFileBatch(cwd, shas) {
14427
14553
  return out;
14428
14554
  }
14429
14555
  async function ensureWorkspaceClone(opts) {
14430
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14556
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14431
14557
  const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
14432
14558
  const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
14433
14559
  let cloned = false;
@@ -14435,14 +14561,14 @@ async function ensureWorkspaceClone(opts) {
14435
14561
  await hostGit(workspace, "rev-parse", "--git-dir");
14436
14562
  await hostGit(workspace, "remote", "set-url", "origin", url2);
14437
14563
  } catch {
14438
- const leftovers = await import_promises2.default.readdir(workspace).catch(() => []);
14564
+ const leftovers = await import_promises3.default.readdir(workspace).catch(() => []);
14439
14565
  if (leftovers.includes(".git")) {
14440
14566
  throw new Error(
14441
14567
  `${workspace} holds a git repository that cannot be read. Remove it and retry.`
14442
14568
  );
14443
14569
  }
14444
14570
  for (const name of leftovers) {
14445
- await import_promises2.default.rm(import_path4.default.join(workspace, name), { recursive: true, force: true });
14571
+ await import_promises3.default.rm(import_path5.default.join(workspace, name), { recursive: true, force: true });
14446
14572
  }
14447
14573
  await execFileAsync2(
14448
14574
  "git",
@@ -14526,6 +14652,10 @@ function spaceEnv(spaceDir) {
14526
14652
  WORKSPACE: paths.workspace,
14527
14653
  BACKUPS_DIR: paths.backups,
14528
14654
  BUILDS_DIR: paths.builds,
14655
+ // Where `factiii-secrets` finds the runner. It can also walk up for the
14656
+ // socket, but only from inside the space tree - a step that cd'd into a
14657
+ // build dir elsewhere would not find it.
14658
+ FACTIII_SECRETS_SOCK: secretsSocketPath(spaceDir),
14529
14659
  // No keychain credential helper and no interactive git prompts anywhere
14530
14660
  // in the deploy surface (agent sessions + authorized commands): auth
14531
14661
  // comes from the secrets store or the tokened URL, never the keychain.
@@ -14536,15 +14666,15 @@ function spaceEnv(spaceDir) {
14536
14666
  };
14537
14667
  }
14538
14668
  async function readContract(workspace) {
14539
- const skill = await import_promises2.default.readFile(import_path4.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
14669
+ const skill = await import_promises3.default.readFile(import_path5.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
14540
14670
  throw new Error("The creator wrote no SKILL.md.");
14541
14671
  });
14542
- const checker = await import_promises2.default.readFile(import_path4.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
14672
+ const checker = await import_promises3.default.readFile(import_path5.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
14543
14673
  throw new Error("The creator wrote no check-environment.sh.");
14544
14674
  });
14545
14675
  try {
14546
14676
  const raw = JSON.parse(
14547
- await import_promises2.default.readFile(import_path4.default.join(workspace, VARIABLES_PATH), "utf-8")
14677
+ await import_promises3.default.readFile(import_path5.default.join(workspace, VARIABLES_PATH), "utf-8")
14548
14678
  );
14549
14679
  return {
14550
14680
  skill,
@@ -14561,7 +14691,7 @@ async function generateDeploySkill(opts) {
14561
14691
  throw new Error("Describe your deployment process first.");
14562
14692
  }
14563
14693
  const status = await checkDeployStatus(opts);
14564
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14694
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14565
14695
  sendLog({
14566
14696
  type: "system",
14567
14697
  content: `Resetting workspace to origin/${status.branch}...`
@@ -14586,13 +14716,12 @@ async function generateDeploySkill(opts) {
14586
14716
  prompt: buildPrompt(prompts_default.deployCreate, {
14587
14717
  INSTRUCTIONS: instructions
14588
14718
  }),
14589
- allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
14719
+ allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch,Skill",
14590
14720
  // auto: the per-action classifier blocks escalations/unrecognized-infra
14591
14721
  // actions - a host session with no human reviewing each command should
14592
14722
  // not run bypassPermissions. (Codex maps this to a workspace-write
14593
14723
  // sandbox; see agent.ts.)
14594
14724
  permissionMode: "auto",
14595
- model: "claude-opus-4-6",
14596
14725
  includePartialMessages: true,
14597
14726
  env: spaceEnv(opts.spaceDir),
14598
14727
  callbacks: { onLog: (entry) => sendLog(entry) }
@@ -14605,7 +14734,7 @@ async function generateDeploySkill(opts) {
14605
14734
  async function reviseDeploySkill(opts) {
14606
14735
  const { feedback, sendLog } = opts;
14607
14736
  if (!feedback.trim()) throw new Error("Corrections are empty.");
14608
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14737
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14609
14738
  sendLog({ type: "system", content: "Applying your corrections..." });
14610
14739
  const { result } = await spawnAgent(
14611
14740
  opts.provider,
@@ -14615,9 +14744,8 @@ async function reviseDeploySkill(opts) {
14615
14744
  prompt: buildPrompt(prompts_default.deployCreateRevise, {
14616
14745
  FEEDBACK: feedback
14617
14746
  }),
14618
- allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
14747
+ allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch,Skill",
14619
14748
  permissionMode: "auto",
14620
- model: "claude-opus-4-6",
14621
14749
  includePartialMessages: true,
14622
14750
  resumeSessionId: opts.resumeSessionId || void 0,
14623
14751
  env: spaceEnv(opts.spaceDir),
@@ -14634,7 +14762,7 @@ async function loadSkillDraft(opts) {
14634
14762
  throw new Error("Not a deploy-skill branch.");
14635
14763
  }
14636
14764
  await checkDeployStatus(opts);
14637
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14765
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14638
14766
  await hostGit(
14639
14767
  workspace,
14640
14768
  "fetch",
@@ -14646,7 +14774,7 @@ async function loadSkillDraft(opts) {
14646
14774
  return readContract(workspace);
14647
14775
  }
14648
14776
  async function approveDeploySkill(opts) {
14649
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14777
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14650
14778
  await readContract(workspace);
14651
14779
  await ensureClaudeSkillLink(workspace);
14652
14780
  if (opts.gitName)
@@ -14689,19 +14817,19 @@ async function runEnvironmentCheck(opts) {
14689
14817
  "No environment checker on the default branch. Generate the deploy skill first."
14690
14818
  );
14691
14819
  }
14692
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14820
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14693
14821
  const script = await hostGit(
14694
14822
  workspace,
14695
14823
  "cat-file",
14696
14824
  "-p",
14697
14825
  `origin/${status.branch}:${CHECKER_PATH}`
14698
14826
  );
14699
- const tmp = import_path4.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
14700
- await import_promises2.default.writeFile(tmp, script, { mode: 448 });
14827
+ const tmp = import_path5.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
14828
+ await import_promises3.default.writeFile(tmp, script, { mode: 448 });
14701
14829
  let ok = true;
14702
14830
  let output = "";
14703
14831
  try {
14704
- const scriptArg = tmp.split(import_path4.default.sep).join("/");
14832
+ const scriptArg = tmp.split(import_path5.default.sep).join("/");
14705
14833
  const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
14706
14834
  cwd: workspace,
14707
14835
  env: { ...process.env, ...spaceEnv(opts.spaceDir) },
@@ -14710,7 +14838,7 @@ async function runEnvironmentCheck(opts) {
14710
14838
  output = res.output;
14711
14839
  if (res.code !== 0) ok = false;
14712
14840
  } finally {
14713
- await import_promises2.default.rm(tmp, { force: true });
14841
+ await import_promises3.default.rm(tmp, { force: true });
14714
14842
  }
14715
14843
  const checks = [];
14716
14844
  for (const line of output.split("\n")) {
@@ -14732,7 +14860,7 @@ async function fixEnvironment(opts) {
14732
14860
  const before = await runEnvironmentCheck(opts);
14733
14861
  if (before.ok) return before;
14734
14862
  const status = await checkDeployStatus(opts);
14735
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14863
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14736
14864
  const script = await hostGit(
14737
14865
  workspace,
14738
14866
  "cat-file",
@@ -14757,7 +14885,6 @@ async function fixEnvironment(opts) {
14757
14885
  // auto: installs are in-scope of the owner's explicit "fix environment"
14758
14886
  // request; the classifier still blocks anything beyond it.
14759
14887
  permissionMode: "auto",
14760
- model: "claude-opus-4-6",
14761
14888
  includePartialMessages: true,
14762
14889
  env: spaceEnv(opts.spaceDir),
14763
14890
  callbacks: { onLog: (entry) => sendLog(entry) }
@@ -14768,7 +14895,7 @@ async function fixEnvironment(opts) {
14768
14895
  return runEnvironmentCheck(opts);
14769
14896
  }
14770
14897
  async function discardDeploySkill(opts) {
14771
- const workspace = import_path4.default.join(opts.spaceDir, "workspace");
14898
+ const workspace = import_path5.default.join(opts.spaceDir, "workspace");
14772
14899
  await hostGit(workspace, "checkout", "--", ".").catch(() => null);
14773
14900
  await hostGit(workspace, "clean", "-fd");
14774
14901
  return { success: true };
@@ -14887,7 +15014,7 @@ var AGENTS_ROOT = ".agents/skills";
14887
15014
  var CLAUDE_ROOT = ".claude/skills";
14888
15015
  var CLI_ROOTS = [CLAUDE_ROOT, ".codex/skills"];
14889
15016
  function globalAgentsRoot() {
14890
- return import_path5.default.join(import_os2.default.homedir(), ".agents", "skills");
15017
+ return import_path6.default.join(import_os2.default.homedir(), ".agents", "skills");
14891
15018
  }
14892
15019
  var AGENTS_LINK_TARGET = "../.agents/skills";
14893
15020
  var BRANCH_PREFIX = "skill/";
@@ -14897,11 +15024,11 @@ function summarize(raw, base) {
14897
15024
  return { ...base, description: frontmatter.description, errors };
14898
15025
  }
14899
15026
  async function rootLinkState(root) {
14900
- const linkPath = import_path5.default.join(import_os2.default.homedir(), ...root.split("/"));
14901
- const stat = await import_promises3.default.lstat(linkPath).catch(() => null);
14902
- if (!stat) return "missing";
14903
- if (!stat.isSymbolicLink()) return "blocked";
14904
- const target = await import_promises3.default.readlink(linkPath).catch(() => "");
15027
+ const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
15028
+ const stat2 = await import_promises4.default.lstat(linkPath).catch(() => null);
15029
+ if (!stat2) return "missing";
15030
+ if (!stat2.isSymbolicLink()) return "blocked";
15031
+ const target = await import_promises4.default.readlink(linkPath).catch(() => "");
14905
15032
  return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
14906
15033
  }
14907
15034
  async function globalLinkState() {
@@ -14911,12 +15038,12 @@ async function globalLinkState() {
14911
15038
  }
14912
15039
  async function listGlobal() {
14913
15040
  const root = globalAgentsRoot();
14914
- const entries = await import_promises3.default.readdir(root, { withFileTypes: true }).catch(() => []);
15041
+ const entries = await import_promises4.default.readdir(root, { withFileTypes: true }).catch(() => []);
14915
15042
  const out = [];
14916
15043
  for (const entry of entries) {
14917
15044
  if (entry.isFile()) continue;
14918
- const dir = import_path5.default.join(root, entry.name);
14919
- const raw = await import_promises3.default.readFile(import_path5.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
15045
+ const dir = import_path6.default.join(root, entry.name);
15046
+ const raw = await import_promises4.default.readFile(import_path6.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
14920
15047
  if (!raw) continue;
14921
15048
  out.push(
14922
15049
  summarize(raw, {
@@ -15085,7 +15212,7 @@ async function ensureSkillsWorktree(spaceDir, branch) {
15085
15212
  } catch {
15086
15213
  await hostGit(repo, "worktree", "prune").catch(() => {
15087
15214
  });
15088
- await import_promises3.default.rm(worktree, { recursive: true, force: true });
15215
+ await import_promises4.default.rm(worktree, { recursive: true, force: true });
15089
15216
  await hostGit(
15090
15217
  repo,
15091
15218
  "worktree",
@@ -15168,11 +15295,11 @@ async function bundleFilesOnDisk(dir) {
15168
15295
  const out = [];
15169
15296
  const walk = async (current, prefix, depth) => {
15170
15297
  if (depth > 3 || out.length >= 200) return;
15171
- const entries = await import_promises3.default.readdir(current, { withFileTypes: true }).catch(() => []);
15298
+ const entries = await import_promises4.default.readdir(current, { withFileTypes: true }).catch(() => []);
15172
15299
  for (const entry of entries) {
15173
15300
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
15174
15301
  if (entry.isDirectory()) {
15175
- await walk(import_path5.default.join(current, entry.name), rel, depth + 1);
15302
+ await walk(import_path6.default.join(current, entry.name), rel, depth + 1);
15176
15303
  } else if (rel !== "SKILL.md") {
15177
15304
  out.push(rel);
15178
15305
  }
@@ -15184,7 +15311,7 @@ async function bundleFilesOnDisk(dir) {
15184
15311
  async function readSkillImpl(opts) {
15185
15312
  if (opts.scope === "global") {
15186
15313
  const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
15187
- const content2 = await import_promises3.default.readFile(import_path5.default.join(dir, "SKILL.md"), "utf-8");
15314
+ const content2 = await import_promises4.default.readFile(import_path6.default.join(dir, "SKILL.md"), "utf-8");
15188
15315
  const { frontmatter: frontmatter2, errors: errors2 } = parseSkill(content2);
15189
15316
  return {
15190
15317
  name: opts.name,
@@ -15219,55 +15346,55 @@ async function readSkillImpl(opts) {
15219
15346
  };
15220
15347
  }
15221
15348
  async function moveIntoAgents(from, to) {
15222
- if (await import_promises3.default.access(to).then(
15349
+ if (await import_promises4.default.access(to).then(
15223
15350
  () => true,
15224
15351
  () => false
15225
15352
  )) {
15226
15353
  throw new Error(
15227
- `${import_path5.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
15354
+ `${import_path6.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
15228
15355
  );
15229
15356
  }
15230
- await import_promises3.default.mkdir(import_path5.default.dirname(to), { recursive: true });
15357
+ await import_promises4.default.mkdir(import_path6.default.dirname(to), { recursive: true });
15231
15358
  try {
15232
- await import_promises3.default.rename(from, to);
15359
+ await import_promises4.default.rename(from, to);
15233
15360
  } catch {
15234
- await import_promises3.default.cp(from, to, { recursive: true });
15235
- await import_promises3.default.rm(from, { recursive: true, force: true });
15361
+ await import_promises4.default.cp(from, to, { recursive: true });
15362
+ await import_promises4.default.rm(from, { recursive: true, force: true });
15236
15363
  }
15237
15364
  }
15238
15365
  async function migrateGlobal() {
15239
15366
  const moved = [];
15240
15367
  for (const root of CLI_ROOTS) {
15241
- const dir = import_path5.default.join(import_os2.default.homedir(), ...root.split("/"));
15242
- const stat = await import_promises3.default.lstat(dir).catch(() => null);
15243
- if (!stat || stat.isSymbolicLink()) continue;
15244
- const entries = await import_promises3.default.readdir(dir, { withFileTypes: true }).catch(() => []);
15368
+ const dir = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
15369
+ const stat2 = await import_promises4.default.lstat(dir).catch(() => null);
15370
+ if (!stat2 || stat2.isSymbolicLink()) continue;
15371
+ const entries = await import_promises4.default.readdir(dir, { withFileTypes: true }).catch(() => []);
15245
15372
  for (const entry of entries) {
15246
15373
  if (entry.isFile() || entry.isSymbolicLink()) continue;
15247
- const src = import_path5.default.join(dir, entry.name);
15248
- if (!await import_promises3.default.access(import_path5.default.join(src, "SKILL.md")).then(
15374
+ const src = import_path6.default.join(dir, entry.name);
15375
+ if (!await import_promises4.default.access(import_path6.default.join(src, "SKILL.md")).then(
15249
15376
  () => true,
15250
15377
  () => false
15251
15378
  )) {
15252
15379
  continue;
15253
15380
  }
15254
- await moveIntoAgents(src, import_path5.default.join(globalAgentsRoot(), entry.name));
15381
+ await moveIntoAgents(src, import_path6.default.join(globalAgentsRoot(), entry.name));
15255
15382
  moved.push(entry.name);
15256
15383
  }
15257
- await import_promises3.default.rmdir(dir).catch(() => {
15384
+ await import_promises4.default.rmdir(dir).catch(() => {
15258
15385
  });
15259
15386
  }
15260
15387
  for (const root of CLI_ROOTS) {
15261
- const linkPath = import_path5.default.join(import_os2.default.homedir(), ...root.split("/"));
15262
- const after = await import_promises3.default.lstat(linkPath).catch(() => null);
15263
- if (after?.isSymbolicLink()) await import_promises3.default.rm(linkPath, { force: true });
15388
+ const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
15389
+ const after = await import_promises4.default.lstat(linkPath).catch(() => null);
15390
+ if (after?.isSymbolicLink()) await import_promises4.default.rm(linkPath, { force: true });
15264
15391
  else if (after) {
15265
15392
  throw new Error(
15266
15393
  `~/${root} still has files that are not skills. Empty it, then migrate.`
15267
15394
  );
15268
15395
  }
15269
- await import_promises3.default.mkdir(import_path5.default.dirname(linkPath), { recursive: true });
15270
- await import_promises3.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
15396
+ await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
15397
+ await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
15271
15398
  }
15272
15399
  return moved;
15273
15400
  }
@@ -15301,13 +15428,13 @@ async function migrateSkillsImpl(opts) {
15301
15428
  `${name} exists in both ${AGENTS_ROOT} and ${stray}. Remove one copy, then migrate.`
15302
15429
  );
15303
15430
  }
15304
- await import_promises3.default.mkdir(import_path5.default.join(workspace, AGENTS_ROOT), { recursive: true });
15431
+ await import_promises4.default.mkdir(import_path6.default.join(workspace, AGENTS_ROOT), { recursive: true });
15305
15432
  await hostGit(workspace, "mv", stray, dest);
15306
15433
  moved.push(name);
15307
15434
  }
15308
- const linkPath = import_path5.default.join(workspace, CLAUDE_ROOT);
15309
- await import_promises3.default.rm(linkPath, { recursive: true, force: true });
15310
- await import_promises3.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
15435
+ const linkPath = import_path6.default.join(workspace, CLAUDE_ROOT);
15436
+ await import_promises4.default.rm(linkPath, { recursive: true, force: true });
15437
+ await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
15311
15438
  await hostGit(workspace, "add", "-A");
15312
15439
  const dirty = await hostGit(workspace, "status", "--porcelain");
15313
15440
  if (!dirty.trim()) return { branch: "", moved: [] };
@@ -15333,16 +15460,16 @@ function assertWritable(name, content) {
15333
15460
  }
15334
15461
  async function writeSkillFile(opts) {
15335
15462
  const dir = resolveSkillDir(opts.root, opts.name);
15336
- const file = import_path5.default.join(dir, "SKILL.md");
15463
+ const file = import_path6.default.join(dir, "SKILL.md");
15337
15464
  if (opts.keepExisting) {
15338
- const exists = await import_promises3.default.access(file).then(
15465
+ const exists = await import_promises4.default.access(file).then(
15339
15466
  () => true,
15340
15467
  () => false
15341
15468
  );
15342
15469
  if (exists) return;
15343
15470
  }
15344
- await import_promises3.default.mkdir(dir, { recursive: true });
15345
- await import_promises3.default.writeFile(file, opts.content, "utf-8");
15471
+ await import_promises4.default.mkdir(dir, { recursive: true });
15472
+ await import_promises4.default.writeFile(file, opts.content, "utf-8");
15346
15473
  }
15347
15474
  async function writeGlobalSkill(opts) {
15348
15475
  assertWritable(opts.name, opts.content);
@@ -15357,14 +15484,14 @@ async function installBundledSkill(opts) {
15357
15484
  content: opts.content,
15358
15485
  keepExisting: true
15359
15486
  });
15360
- await write(import_path5.default.join(home, ...AGENTS_ROOT.split("/")));
15487
+ await write(import_path6.default.join(home, ...AGENTS_ROOT.split("/")));
15361
15488
  for (const root of CLI_ROOTS) {
15362
- const linkPath = import_path5.default.join(home, ...root.split("/"));
15363
- const stat = await import_promises3.default.lstat(linkPath).catch(() => null);
15364
- if (stat?.isSymbolicLink()) continue;
15365
- if (!stat) {
15366
- await import_promises3.default.mkdir(import_path5.default.dirname(linkPath), { recursive: true });
15367
- await import_promises3.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
15489
+ const linkPath = import_path6.default.join(home, ...root.split("/"));
15490
+ const stat2 = await import_promises4.default.lstat(linkPath).catch(() => null);
15491
+ if (stat2?.isSymbolicLink()) continue;
15492
+ if (!stat2) {
15493
+ await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
15494
+ await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
15368
15495
  continue;
15369
15496
  }
15370
15497
  await write(linkPath);
@@ -15372,7 +15499,7 @@ async function installBundledSkill(opts) {
15372
15499
  }
15373
15500
  async function deleteGlobalSkill(opts) {
15374
15501
  const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
15375
- await import_promises3.default.rm(dir, { recursive: true, force: true });
15502
+ await import_promises4.default.rm(dir, { recursive: true, force: true });
15376
15503
  }
15377
15504
  async function proposeRepoSkillImpl(opts) {
15378
15505
  assertWritable(opts.name, opts.content);
@@ -15410,9 +15537,9 @@ async function proposeRepoSkillImpl(opts) {
15410
15537
  if (opts.gitEmail) {
15411
15538
  await hostGit(workspace, "config", "user.email", opts.gitEmail);
15412
15539
  }
15413
- await import_promises3.default.mkdir(import_path5.default.join(workspace, dir), { recursive: true });
15414
- await import_promises3.default.writeFile(
15415
- import_path5.default.join(workspace, dir, "SKILL.md"),
15540
+ await import_promises4.default.mkdir(import_path6.default.join(workspace, dir), { recursive: true });
15541
+ await import_promises4.default.writeFile(
15542
+ import_path6.default.join(workspace, dir, "SKILL.md"),
15416
15543
  opts.content,
15417
15544
  "utf-8"
15418
15545
  );
@@ -15519,18 +15646,18 @@ function vendoredBinaryName() {
15519
15646
  async function installFrpcBinary(target) {
15520
15647
  const p = frpcPaths(target);
15521
15648
  const binaryName = vendoredBinaryName();
15522
- const src = import_path6.default.join(__dirname, "..", "vendor", binaryName);
15649
+ const src = import_path7.default.join(__dirname, "..", "vendor", binaryName);
15523
15650
  if (!(0, import_fs2.existsSync)(src)) {
15524
15651
  throw new Error(
15525
15652
  `Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
15526
15653
  );
15527
15654
  }
15528
- const staged = await import_promises4.default.stat(p.binary).catch(() => null);
15529
- const source = await import_promises4.default.stat(src);
15655
+ const staged = await import_promises5.default.stat(p.binary).catch(() => null);
15656
+ const source = await import_promises5.default.stat(src);
15530
15657
  if (staged?.size === source.size) return;
15531
- await import_promises4.default.mkdir(p.binDir, { recursive: true });
15532
- await import_promises4.default.copyFile(src, p.binary);
15533
- await import_promises4.default.chmod(p.binary, 493);
15658
+ await import_promises5.default.mkdir(p.binDir, { recursive: true });
15659
+ await import_promises5.default.copyFile(src, p.binary);
15660
+ await import_promises5.default.chmod(p.binary, 493);
15534
15661
  }
15535
15662
  var EXPOSE_SKILL_NAME = "share-a-port";
15536
15663
  var FENCE = "```";
@@ -15920,18 +16047,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
15920
16047
  await this.core.ensureSpaceWorkspace();
15921
16048
  await assertAgentSignedIn(session.provider, this.core.target());
15922
16049
  if (hasGit) {
15923
- const { path: path18 } = await addWorktree(
16050
+ const { path: path20 } = await addWorktree(
15924
16051
  this.core.target(),
15925
16052
  session.taskId,
15926
16053
  baseBranch || config.mainBranch || "main",
15927
16054
  "card"
15928
16055
  );
15929
- session.workspacePath = path18;
16056
+ session.workspacePath = path20;
15930
16057
  await setGitIdentity(
15931
16058
  this.core.target(),
15932
16059
  config.gitName,
15933
16060
  config.gitEmail,
15934
- path18
16061
+ path20
15935
16062
  );
15936
16063
  this.emitter.addLog(session, "system", "Worktree ready.");
15937
16064
  } else {
@@ -16153,18 +16280,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
16153
16280
  this.emitter.addLog(session, "system", "Preparing environment...");
16154
16281
  await this.core.ensureSpaceWorkspace();
16155
16282
  await assertAgentSignedIn(session.provider, this.core.target());
16156
- const { path: path18 } = await addWorktree(
16283
+ const { path: path20 } = await addWorktree(
16157
16284
  this.core.target(),
16158
16285
  session.taskId,
16159
16286
  config.mainBranch || "main",
16160
16287
  "card"
16161
16288
  );
16162
- session.workspacePath = path18;
16289
+ session.workspacePath = path20;
16163
16290
  await setGitIdentity(
16164
16291
  this.core.target(),
16165
16292
  config.gitName,
16166
16293
  config.gitEmail,
16167
- path18
16294
+ path20
16168
16295
  );
16169
16296
  await this.putCard(session);
16170
16297
  const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
@@ -16711,12 +16838,12 @@ var FLUSH_MS = 120;
16711
16838
  var SELF_WRITE_MS = 2e3;
16712
16839
  var dynamicImport = new Function("s", "return import(s)");
16713
16840
  var importFs = new Function("s", "return import(s)");
16714
- async function resolveReal(path18) {
16841
+ async function resolveReal(path20) {
16715
16842
  try {
16716
16843
  const { realpath } = await importFs("fs/promises");
16717
- return await realpath(path18);
16844
+ return await realpath(path20);
16718
16845
  } catch {
16719
- return path18;
16846
+ return path20;
16720
16847
  }
16721
16848
  }
16722
16849
  var loadSubscribe = null;
@@ -16830,19 +16957,19 @@ var WorkspaceWatcher = class {
16830
16957
  }
16831
16958
  record(kind, relPath) {
16832
16959
  if (this.closed) return;
16833
- const path18 = "/" + relPath;
16834
- const expiry = this.selfWrites.get(path18);
16960
+ const path20 = "/" + relPath;
16961
+ const expiry = this.selfWrites.get(path20);
16835
16962
  if (expiry !== void 0) {
16836
16963
  if (expiry > Date.now()) return;
16837
- this.selfWrites.delete(path18);
16964
+ this.selfWrites.delete(path20);
16838
16965
  }
16839
16966
  const { added, changed, removed } = this.pending;
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);
16967
+ added.delete(path20);
16968
+ changed.delete(path20);
16969
+ removed.delete(path20);
16970
+ if (kind === "added") added.add(path20);
16971
+ else if (kind === "changed") changed.add(path20);
16972
+ else removed.add(path20);
16846
16973
  if (added.size + changed.size + removed.size > MAX_BATCH) {
16847
16974
  this.pending = { ...emptyPending(), resync: true };
16848
16975
  }
@@ -16861,8 +16988,8 @@ var WorkspaceWatcher = class {
16861
16988
  this.pending = emptyPending();
16862
16989
  if (!resync && !added.size && !changed.size && !removed.size) return;
16863
16990
  const now = Date.now();
16864
- for (const [path18, expiry] of this.selfWrites) {
16865
- if (expiry <= now) this.selfWrites.delete(path18);
16991
+ for (const [path20, expiry] of this.selfWrites) {
16992
+ if (expiry <= now) this.selfWrites.delete(path20);
16866
16993
  }
16867
16994
  this.emit({
16868
16995
  added: resync ? [] : [...added],
@@ -17066,8 +17193,8 @@ var TerminalModeEngine = class {
17066
17193
  /** Suppress the change event our own write is about to produce, so a save
17067
17194
  * never comes back to the editor as an outside edit. Normalized to the form
17068
17195
  * the watcher emits, since callers spell the path either way. */
17069
- expectWrite(postId, path18) {
17070
- this.watchers.get(postId)?.expectWrite(posixNormalize(path18));
17196
+ expectWrite(postId, path20) {
17197
+ this.watchers.get(postId)?.expectWrite(posixNormalize(path20));
17071
17198
  }
17072
17199
  // ── Lifecycle / ModeEngine ──
17073
17200
  start(base, args) {
@@ -17264,18 +17391,18 @@ var TerminalModeEngine = class {
17264
17391
  this.reservedNames.values()
17265
17392
  );
17266
17393
  this.reservedNames.set(session.postId, workName);
17267
- const { path: path18 } = await addWorktree(
17394
+ const { path: path20 } = await addWorktree(
17268
17395
  this.core.target(),
17269
17396
  workName,
17270
17397
  branch,
17271
17398
  "terminal"
17272
17399
  );
17273
- session.workspacePath = path18;
17400
+ session.workspacePath = path20;
17274
17401
  await setGitIdentity(
17275
17402
  this.core.target(),
17276
17403
  config.gitName,
17277
17404
  config.gitEmail,
17278
- path18
17405
+ path20
17279
17406
  );
17280
17407
  this.emitter.addLog(session, "system", "Worktree ready.");
17281
17408
  } else {
@@ -17337,11 +17464,11 @@ var TerminalModeEngine = class {
17337
17464
  }
17338
17465
  async bareFsList({
17339
17466
  postId,
17340
- path: path18
17467
+ path: path20
17341
17468
  }) {
17342
17469
  const target = this.getBareTarget(postId);
17343
17470
  const root = this.getBareRoot(postId);
17344
- const abs = this.resolveBarePath(path18, root);
17471
+ const abs = this.resolveBarePath(path20, root);
17345
17472
  const relRoot = abs.slice(root.length) || "/";
17346
17473
  const out = await target.run([
17347
17474
  "sh",
@@ -17391,30 +17518,30 @@ var TerminalModeEngine = class {
17391
17518
  }
17392
17519
  async bareFsRead({
17393
17520
  postId,
17394
- path: path18
17521
+ path: path20
17395
17522
  }) {
17396
17523
  const target = this.getBareTarget(postId);
17397
- const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
17524
+ const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
17398
17525
  const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
17399
17526
  '`)).trim();
17400
17527
  return { content: b64, encoding: "base64" };
17401
17528
  }
17402
17529
  async bareFsWrite({
17403
17530
  postId,
17404
- path: path18,
17531
+ path: path20,
17405
17532
  content,
17406
17533
  encoding
17407
17534
  }) {
17408
17535
  const target = this.getBareTarget(postId);
17409
- const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
17536
+ const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
17410
17537
  const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
17411
17538
  if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
17412
17539
  throw new Error("Invalid base64 payload.");
17413
17540
  }
17414
- this.expectWrite(String(postId), path18);
17541
+ this.expectWrite(String(postId), path20);
17415
17542
  await writeBase64File(target, abs, b64);
17416
17543
  const session = this.sessions.get(String(postId));
17417
- if (session) delete session.buffers[path18];
17544
+ if (session) delete session.buffers[path20];
17418
17545
  }
17419
17546
  /** Append one chunk of a dropped image to the per-space drops dir
17420
17547
  * (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
@@ -17464,39 +17591,39 @@ var TerminalModeEngine = class {
17464
17591
  }
17465
17592
  bareOpenFile({
17466
17593
  postId: rawPostId,
17467
- path: path18
17594
+ path: path20
17468
17595
  }) {
17469
17596
  const session = this.sessions.get(String(rawPostId));
17470
17597
  if (!session || session.mode !== "bare") return;
17471
- if (!session.openedFiles.includes(path18)) {
17472
- session.openedFiles.push(path18);
17598
+ if (!session.openedFiles.includes(path20)) {
17599
+ session.openedFiles.push(path20);
17473
17600
  }
17474
17601
  }
17475
17602
  bareCloseFile({
17476
17603
  postId: rawPostId,
17477
- path: path18
17604
+ path: path20
17478
17605
  }) {
17479
17606
  const session = this.sessions.get(String(rawPostId));
17480
17607
  if (!session || session.mode !== "bare") return;
17481
- session.openedFiles = session.openedFiles.filter((p) => p !== path18);
17482
- delete session.buffers[path18];
17608
+ session.openedFiles = session.openedFiles.filter((p) => p !== path20);
17609
+ delete session.buffers[path20];
17483
17610
  }
17484
17611
  bareBufferSet({
17485
17612
  postId: rawPostId,
17486
- path: path18,
17613
+ path: path20,
17487
17614
  content
17488
17615
  }) {
17489
17616
  const session = this.sessions.get(String(rawPostId));
17490
17617
  if (!session || session.mode !== "bare") return;
17491
- session.buffers[path18] = content;
17618
+ session.buffers[path20] = content;
17492
17619
  }
17493
17620
  async bareFsDelete({
17494
17621
  postId,
17495
- path: path18
17622
+ path: path20
17496
17623
  }) {
17497
17624
  const target = this.getBareTarget(postId);
17498
17625
  const root = this.getBareRoot(postId);
17499
- const abs = this.resolveBarePath(path18, root);
17626
+ const abs = this.resolveBarePath(path20, root);
17500
17627
  if (abs === root) {
17501
17628
  throw new Error("Refusing to delete the repo root.");
17502
17629
  }
@@ -17504,10 +17631,10 @@ var TerminalModeEngine = class {
17504
17631
  }
17505
17632
  async bareFsMkdir({
17506
17633
  postId,
17507
- path: path18
17634
+ path: path20
17508
17635
  }) {
17509
17636
  const target = this.getBareTarget(postId);
17510
- const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
17637
+ const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
17511
17638
  await target.sh(`mkdir -p ${shEscape(abs)}`);
17512
17639
  }
17513
17640
  async bareReview({
@@ -18189,8 +18316,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
18189
18316
  };
18190
18317
  }
18191
18318
  }
18192
- async function githubApi(token, path18) {
18193
- const res = await fetch(`${API_BASE}${path18}`, {
18319
+ async function githubApi(token, path20) {
18320
+ const res = await fetch(`${API_BASE}${path20}`, {
18194
18321
  headers: {
18195
18322
  Authorization: `Bearer ${token}`,
18196
18323
  Accept: "application/vnd.github+json",
@@ -18199,7 +18326,7 @@ async function githubApi(token, path18) {
18199
18326
  });
18200
18327
  if (!res.ok) {
18201
18328
  throw new Error(
18202
- `GitHub ${path18} failed (${res.status}): ${await res.text()}`
18329
+ `GitHub ${path20} failed (${res.status}): ${await res.text()}`
18203
18330
  );
18204
18331
  }
18205
18332
  return await res.json();
@@ -18244,13 +18371,13 @@ async function listGithubBranches(token, fullName) {
18244
18371
  }
18245
18372
 
18246
18373
  // ../../shared/all/helpers/board-agent-core/host-run.ts
18247
- var import_path8 = __toESM(require("path"));
18374
+ var import_path9 = __toESM(require("path"));
18248
18375
 
18249
18376
  // ../../shared/all/helpers/board-agent-core/host-secrets.ts
18250
18377
  var import_child_process4 = require("child_process");
18251
18378
  var import_crypto2 = __toESM(require("crypto"));
18252
- var import_promises5 = __toESM(require("fs/promises"));
18253
- var import_path7 = __toESM(require("path"));
18379
+ var import_promises6 = __toESM(require("fs/promises"));
18380
+ var import_path8 = __toESM(require("path"));
18254
18381
  var import_util6 = require("util");
18255
18382
  var execFileAsync3 = (0, import_util6.promisify)(import_child_process4.execFile);
18256
18383
  var IDENTITY_FILE = "age-identity.enc";
@@ -18283,7 +18410,7 @@ function deriveKey(password, salt) {
18283
18410
  }
18284
18411
  async function secretsInitialized(configDir) {
18285
18412
  try {
18286
- await import_promises5.default.access(import_path7.default.join(configDir, IDENTITY_FILE));
18413
+ await import_promises6.default.access(import_path8.default.join(configDir, IDENTITY_FILE));
18287
18414
  return true;
18288
18415
  } catch {
18289
18416
  return false;
@@ -18322,10 +18449,10 @@ async function initSecrets(configDir, password) {
18322
18449
  tag: cipher.getAuthTag().toString("base64"),
18323
18450
  data: data.toString("base64")
18324
18451
  };
18325
- await import_promises5.default.writeFile(import_path7.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
18452
+ await import_promises6.default.writeFile(import_path8.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
18326
18453
  mode: 384
18327
18454
  });
18328
- await import_promises5.default.writeFile(import_path7.default.join(configDir, RECIPIENT_FILE), `${recipient}
18455
+ await import_promises6.default.writeFile(import_path8.default.join(configDir, RECIPIENT_FILE), `${recipient}
18329
18456
  `, {
18330
18457
  mode: 384
18331
18458
  });
@@ -18348,17 +18475,17 @@ function decryptIdentity(box, password) {
18348
18475
  }
18349
18476
  }
18350
18477
  function envPath(spaceDir) {
18351
- return import_path7.default.join(spaceDir, "secrets", ENV_FILE);
18478
+ return import_path8.default.join(spaceDir, "secrets", ENV_FILE);
18352
18479
  }
18353
18480
  async function identityFor(configDir, password) {
18354
18481
  const box = JSON.parse(
18355
- await import_promises5.default.readFile(import_path7.default.join(configDir, IDENTITY_FILE), "utf-8")
18482
+ await import_promises6.default.readFile(import_path8.default.join(configDir, IDENTITY_FILE), "utf-8")
18356
18483
  );
18357
18484
  return decryptIdentity(box, password);
18358
18485
  }
18359
18486
  async function readEnv(spaceDir, identity) {
18360
18487
  try {
18361
- await import_promises5.default.access(envPath(spaceDir));
18488
+ await import_promises6.default.access(envPath(spaceDir));
18362
18489
  } catch {
18363
18490
  return {};
18364
18491
  }
@@ -18387,14 +18514,14 @@ async function readEnv(spaceDir, identity) {
18387
18514
  return values;
18388
18515
  }
18389
18516
  async function writeEnv(configDir, spaceDir, identity, values) {
18390
- const recipient = (await import_promises5.default.readFile(import_path7.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
18517
+ const recipient = (await import_promises6.default.readFile(import_path8.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
18391
18518
  const plain = `${Object.entries(values).map(([k, v]) => `${k}=${v}`).join("\n")}
18392
18519
  `;
18393
18520
  const tmp = envPath(spaceDir).replace(
18394
18521
  /\.enc\.env$/,
18395
18522
  `.${process.pid}.tmp.env`
18396
18523
  );
18397
- await import_promises5.default.writeFile(tmp, plain, { mode: 384 });
18524
+ await import_promises6.default.writeFile(tmp, plain, { mode: 384 });
18398
18525
  try {
18399
18526
  const { stdout } = await execFileAsync3(
18400
18527
  "sops",
@@ -18413,9 +18540,9 @@ async function writeEnv(configDir, spaceDir, identity, values) {
18413
18540
  maxBuffer: 4 * 1024 * 1024
18414
18541
  }
18415
18542
  );
18416
- await import_promises5.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
18543
+ await import_promises6.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
18417
18544
  } finally {
18418
- await import_promises5.default.rm(tmp, { force: true });
18545
+ await import_promises6.default.rm(tmp, { force: true });
18419
18546
  }
18420
18547
  }
18421
18548
  async function listSecretKeys(configDir, spaceDir, password) {
@@ -18453,16 +18580,6 @@ var FAILED_MARKER = "DEPLOY_FAILED:";
18453
18580
  var MAX_MARKERLESS_TURNS = 3;
18454
18581
  var COMMAND_TIMEOUT_MS = 45 * 6e4;
18455
18582
  var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
18456
- function redactor(values) {
18457
- const significant = values.filter((v) => v.length >= 4);
18458
- return (text) => {
18459
- let out = text;
18460
- for (const value2 of significant) {
18461
- out = out.split(value2).join("[REDACTED]");
18462
- }
18463
- return out;
18464
- };
18465
- }
18466
18583
  function tailMarker(text) {
18467
18584
  const zone = text.slice(-2e3);
18468
18585
  let best = null;
@@ -18479,10 +18596,52 @@ function tailMarker(text) {
18479
18596
  const value2 = zone.slice(best.at + best.marker.length).trim();
18480
18597
  return value2 ? { marker: best.marker, value: value2 } : null;
18481
18598
  }
18599
+ function containedCwd(cwd, spaceDir) {
18600
+ if (!cwd) return null;
18601
+ const resolved = import_path9.default.resolve(cwd);
18602
+ const root = import_path9.default.resolve(spaceDir);
18603
+ return resolved === root || resolved.startsWith(root + import_path9.default.sep) ? resolved : null;
18604
+ }
18605
+ async function authorizeAndRun(opts) {
18606
+ const { command, hooks } = opts;
18607
+ opts.status?.("waiting for the owner to authorize this command");
18608
+ let secrets = null;
18609
+ let authError = "";
18610
+ while (!secrets) {
18611
+ const auth = await hooks.requestAuth(command, authError);
18612
+ if ("denied" in auth) return { denied: true };
18613
+ try {
18614
+ secrets = await secretsForInjection(
18615
+ opts.configDir,
18616
+ opts.spaceDir,
18617
+ auth.password
18618
+ );
18619
+ } catch (err) {
18620
+ authError = err instanceof Error ? err.message : String(err);
18621
+ opts.status?.(`authorization failed: ${authError}`);
18622
+ }
18623
+ }
18624
+ const redact = redactor(Object.values(secrets));
18625
+ opts.status?.("authorized, running");
18626
+ hooks.sendLog({
18627
+ type: "system",
18628
+ content: "Authorized. Running the command with secrets injected..."
18629
+ });
18630
+ const res = await execLogin(command, {
18631
+ cwd: opts.cwd,
18632
+ env: { ...process.env, ...spaceEnv(opts.spaceDir), ...secrets },
18633
+ timeout: COMMAND_TIMEOUT_MS,
18634
+ maxBuffer: 32 * 1024 * 1024
18635
+ });
18636
+ const output = redact(res.output);
18637
+ hooks.sendLog({ type: "tool", content: `$ ${command}
18638
+ ${tail(output)}` });
18639
+ return { code: res.code, output };
18640
+ }
18482
18641
  async function startDeployRun(opts) {
18483
18642
  const { hooks } = opts;
18484
- const workspace = import_path8.default.join(opts.spaceDir, "workspace");
18485
- const configDir = import_path8.default.dirname(opts.spaceDir);
18643
+ const workspace = import_path9.default.join(opts.spaceDir, "workspace");
18644
+ const configDir = import_path9.default.dirname(opts.spaceDir);
18486
18645
  const status = await checkDeployStatus(opts);
18487
18646
  if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
18488
18647
  throw new Error(
@@ -18503,7 +18662,7 @@ async function startDeployRun(opts) {
18503
18662
  await hostGit(workspace, "clean", "-fd");
18504
18663
  await ensureClaudeSkillLink(workspace);
18505
18664
  const contract = prompts_default.deployRun.join("\n");
18506
- const reminder = "Reminder: every declared deploy variable is intentionally unset in your shell. Never check for them or abort over them - route any command that needs one through NEED_SECRETS and the runner injects the values.";
18665
+ const reminder = "Reminder: every declared deploy variable is intentionally unset in your shell. Never check for them or abort over them - prefix any command that needs one with `factiii-secrets run --` and the runner injects the values.";
18507
18666
  let prompt2 = opts.provider === "codex" ? `Use the "deploy" skill: load it and execute it step by step.
18508
18667
 
18509
18668
  ${reminder}` : `/deploy
@@ -18512,109 +18671,105 @@ ${reminder}`;
18512
18671
  let sessionId = "";
18513
18672
  let markerless = 0;
18514
18673
  let todos = [];
18515
- for (; ; ) {
18516
- if (hooks.isCancelled()) throw new Error("Cancelled by user.");
18517
- const { proc, result } = await spawnAgent(
18518
- opts.provider,
18519
- hostTarget(opts.spaceDir),
18520
- {
18521
- cwd: workspace,
18522
- prompt: prompt2,
18523
- allowedTools: "Bash,Read,Grep,Glob,TodoWrite,WebSearch,WebFetch",
18524
- // The run's guardrails are structural (merge-gated skill, per-command
18525
- // authorization, runner-side injection); the auto classifier fights
18526
- // legitimate infrastructure commands, so the session itself runs open.
18527
- permissionMode: "bypassPermissions",
18528
- model: "claude-opus-4-6",
18529
- includePartialMessages: true,
18530
- resumeSessionId: sessionId || void 0,
18531
- appendSystemPrompt: sessionId ? void 0 : contract,
18532
- env: spaceEnv(opts.spaceDir),
18533
- callbacks: {
18534
- initialTodos: todos,
18535
- onLog: (entry) => hooks.sendLog(entry),
18536
- onTodos: (updated) => {
18537
- todos = updated;
18538
- hooks.onTodos(updated);
18674
+ const bridge = await startSecretsBridge({
18675
+ spaceDir: opts.spaceDir,
18676
+ handlers: {
18677
+ run: ({ command, cwd, status: report }) => authorizeAndRun({
18678
+ configDir,
18679
+ spaceDir: opts.spaceDir,
18680
+ // A skill legitimately cds into subdirectories, so the CLI's own cwd
18681
+ // is the right place to run - but only while it is still inside the
18682
+ // space. Anywhere else is a confused caller, not a step, and the
18683
+ // workspace is the safe reading of what it meant.
18684
+ cwd: containedCwd(cwd, opts.spaceDir) ?? workspace,
18685
+ command,
18686
+ hooks,
18687
+ status: report
18688
+ }),
18689
+ // Names only. The contract on the default branch already declares them
18690
+ // in public, so this reveals nothing the repo does not.
18691
+ list: () => Promise.resolve(status.variables.map((v) => v.key))
18692
+ }
18693
+ });
18694
+ try {
18695
+ for (; ; ) {
18696
+ if (hooks.isCancelled()) throw new Error("Cancelled by user.");
18697
+ const { proc, result } = await spawnAgent(
18698
+ opts.provider,
18699
+ hostTarget(opts.spaceDir),
18700
+ {
18701
+ cwd: workspace,
18702
+ prompt: prompt2,
18703
+ allowedTools: "Bash,Read,Grep,Glob,TodoWrite,WebSearch,WebFetch,Skill",
18704
+ // The run's guardrails are structural (merge-gated skill, per-command
18705
+ // authorization, runner-side injection); the auto classifier fights
18706
+ // legitimate infrastructure commands, so the session itself runs open.
18707
+ permissionMode: "bypassPermissions",
18708
+ includePartialMessages: true,
18709
+ resumeSessionId: sessionId || void 0,
18710
+ appendSystemPrompt: sessionId ? void 0 : contract,
18711
+ env: spaceEnv(opts.spaceDir),
18712
+ callbacks: {
18713
+ initialTodos: todos,
18714
+ onLog: (entry) => hooks.sendLog(entry),
18715
+ onTodos: (updated) => {
18716
+ todos = updated;
18717
+ hooks.onTodos(updated);
18718
+ }
18539
18719
  }
18540
18720
  }
18541
- }
18542
- );
18543
- hooks.registerProc(proc);
18544
- const { text, sessionId: sid } = await result;
18545
- hooks.registerProc(null);
18546
- sessionId = sid || sessionId;
18547
- if (hooks.isCancelled()) throw new Error("Cancelled by user.");
18548
- const found = tailMarker(text);
18549
- if (found?.marker === SECRETS_MARKER) {
18550
- const command = found.value.split("\n")[0].trim();
18551
- let secrets = null;
18552
- let denied = false;
18553
- let authError = "";
18554
- while (!secrets && !denied) {
18555
- const auth = await hooks.requestAuth(command, authError);
18556
- if ("denied" in auth) {
18557
- denied = true;
18558
- break;
18559
- }
18560
- try {
18561
- secrets = await secretsForInjection(
18562
- configDir,
18563
- opts.spaceDir,
18564
- auth.password
18565
- );
18566
- } catch (err) {
18567
- authError = err instanceof Error ? err.message : String(err);
18721
+ );
18722
+ hooks.registerProc(proc);
18723
+ const { text, sessionId: sid } = await result;
18724
+ hooks.registerProc(null);
18725
+ sessionId = sid || sessionId;
18726
+ if (hooks.isCancelled()) throw new Error("Cancelled by user.");
18727
+ const found = tailMarker(text);
18728
+ if (found?.marker === SECRETS_MARKER) {
18729
+ const command = found.value.split("\n")[0].trim();
18730
+ const res = await authorizeAndRun({
18731
+ configDir,
18732
+ spaceDir: opts.spaceDir,
18733
+ cwd: workspace,
18734
+ command,
18735
+ hooks
18736
+ });
18737
+ if ("denied" in res) {
18738
+ prompt2 = "The owner declined to run that command. Adapt or stop and summarize where the release stands.";
18739
+ continue;
18568
18740
  }
18741
+ markerless = 0;
18742
+ const clean = tail(res.output);
18743
+ prompt2 = `The authorized command finished (exit ${res.code}). Redacted output:
18744
+
18745
+ ${clean}`;
18746
+ continue;
18569
18747
  }
18570
- if (denied || !secrets) {
18571
- prompt2 = "The owner declined to run that command. Adapt or stop and summarize where the release stands.";
18748
+ if (found?.marker === HUMAN_MARKER) {
18749
+ markerless = 0;
18750
+ prompt2 = await hooks.requestHuman(found.value);
18572
18751
  continue;
18573
18752
  }
18574
- const redact = redactor(Object.values(secrets));
18753
+ if (found?.marker === COMPLETE_MARKER) {
18754
+ return { shipped: true, summary: found.value };
18755
+ }
18756
+ if (found?.marker === FAILED_MARKER) {
18757
+ return { shipped: false, summary: found.value };
18758
+ }
18759
+ markerless += 1;
18760
+ if (markerless >= MAX_MARKERLESS_TURNS) {
18761
+ throw new Error(
18762
+ "The run ended without declaring completion (no DEPLOY_COMPLETE after repeated turns)."
18763
+ );
18764
+ }
18575
18765
  hooks.sendLog({
18576
18766
  type: "system",
18577
- content: `Authorized. Running the command with secrets injected...`
18578
- });
18579
- const res = await execLogin(command, {
18580
- cwd: workspace,
18581
- env: { ...process.env, ...spaceEnv(opts.spaceDir), ...secrets },
18582
- timeout: COMMAND_TIMEOUT_MS,
18583
- maxBuffer: 32 * 1024 * 1024
18767
+ content: "Turn ended without a protocol marker - resuming the run..."
18584
18768
  });
18585
- const output = res.output;
18586
- const exitNote = `exit ${res.code}`;
18587
- markerless = 0;
18588
- const clean = redact(tail(output));
18589
- hooks.sendLog({ type: "tool", content: `$ ${command}
18590
- ${clean}` });
18591
- prompt2 = `The authorized command finished (${exitNote}). Redacted output:
18592
-
18593
- ${clean}`;
18594
- continue;
18595
- }
18596
- if (found?.marker === HUMAN_MARKER) {
18597
- markerless = 0;
18598
- prompt2 = await hooks.requestHuman(found.value);
18599
- continue;
18600
- }
18601
- if (found?.marker === COMPLETE_MARKER) {
18602
- return { shipped: true, summary: found.value };
18769
+ prompt2 = "Your turn ended with no protocol marker. If the release fully shipped, end your message with DEPLOY_COMPLETE: followed by your run report; if you are stopping without shipping, end with DEPLOY_FAILED: followed by the report. Otherwise CONTINUE executing the skill from where you left off. If you had background tasks running, they were killed when your turn ended - rerun them, and reap every background task before ending a turn.";
18603
18770
  }
18604
- if (found?.marker === FAILED_MARKER) {
18605
- return { shipped: false, summary: found.value };
18606
- }
18607
- markerless += 1;
18608
- if (markerless >= MAX_MARKERLESS_TURNS) {
18609
- throw new Error(
18610
- "The run ended without declaring completion (no DEPLOY_COMPLETE after repeated turns)."
18611
- );
18612
- }
18613
- hooks.sendLog({
18614
- type: "system",
18615
- content: "Turn ended without a protocol marker - resuming the run..."
18616
- });
18617
- prompt2 = "Your turn ended with no protocol marker. If the release fully shipped, end your message with DEPLOY_COMPLETE: followed by your run report; if you are stopping without shipping, end with DEPLOY_FAILED: followed by the report. Otherwise CONTINUE executing the skill from where you left off. If you had background tasks running, they were killed when your turn ended - rerun them, and reap every background task before ending a turn.";
18771
+ } finally {
18772
+ await bridge.close().catch(() => void 0);
18618
18773
  }
18619
18774
  }
18620
18775
 
@@ -18706,44 +18861,110 @@ async function getDriveRoot(accessToken) {
18706
18861
  };
18707
18862
  }
18708
18863
 
18864
+ // ../../shared/all/helpers/board-agent-core/space-core.ts
18865
+ var import_promises8 = require("fs/promises");
18866
+ var import_path11 = __toESM(require("path"));
18867
+
18709
18868
  // ../../shared/all/helpers/board-agent-core/plugin.ts
18869
+ var import_crypto3 = __toESM(require("crypto"));
18710
18870
  var import_fs3 = require("fs");
18711
- var import_promises6 = require("fs/promises");
18871
+ var import_promises7 = require("fs/promises");
18712
18872
  var import_os3 = __toESM(require("os"));
18713
- var import_path9 = __toESM(require("path"));
18714
- var PLUGIN_ID = "factiii@factiii-runner";
18873
+ var import_path10 = __toESM(require("path"));
18874
+ var MARKETPLACE_NAME = "factiii-runner";
18875
+ var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
18876
+ var SECRETS_BIN = "factiii-secrets";
18715
18877
  function candidates() {
18716
18878
  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")
18879
+ import_path10.default.join(__dirname, "..", "plugin"),
18880
+ import_path10.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
18881
+ import_path10.default.join(process.cwd(), "apps", "runner", "plugin")
18720
18882
  ];
18721
18883
  }
18722
18884
  function pluginRoot() {
18723
18885
  for (const dir of candidates()) {
18724
- if ((0, import_fs3.existsSync)(import_path9.default.join(dir, ".claude-plugin", "marketplace.json"))) {
18886
+ if ((0, import_fs3.existsSync)(import_path10.default.join(dir, ".claude-plugin", "marketplace.json"))) {
18725
18887
  return dir;
18726
18888
  }
18727
18889
  }
18728
18890
  return null;
18729
18891
  }
18730
18892
  function renderedPluginRoot() {
18731
- return import_path9.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
18893
+ return import_path10.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
18894
+ }
18895
+ async function runnerVersion(source) {
18896
+ const pkg = import_path10.default.join(source, "..", "package.json");
18897
+ const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
18898
+ if (!raw) return FALLBACK_VERSION;
18899
+ try {
18900
+ const { version } = JSON.parse(raw);
18901
+ return version || FALLBACK_VERSION;
18902
+ } catch {
18903
+ return FALLBACK_VERSION;
18904
+ }
18905
+ }
18906
+ var FALLBACK_VERSION = "0.0.0-unknown";
18907
+ async function treeFiles(dir) {
18908
+ const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
18909
+ const files = [];
18910
+ for (const entry of entries) {
18911
+ const full = import_path10.default.join(dir, entry.name);
18912
+ if (entry.isDirectory()) files.push(...await treeFiles(full));
18913
+ else if (entry.isFile()) files.push(full);
18914
+ }
18915
+ return files.sort();
18732
18916
  }
18733
- async function renderPlugin(boardMcpUrl) {
18917
+ async function fingerprint(source, url2, version = "") {
18918
+ const hash = import_crypto3.default.createHash("sha256").update(url2).update(version);
18919
+ for (const file of await treeFiles(source)) {
18920
+ hash.update(import_path10.default.relative(source, file));
18921
+ hash.update(await (0, import_promises7.readFile)(file));
18922
+ }
18923
+ return hash.digest("hex");
18924
+ }
18925
+ function stampPath(dest) {
18926
+ return `${dest}.stamp`;
18927
+ }
18928
+ var inFlight = /* @__PURE__ */ new Map();
18929
+ function renderPlugin(boardMcpUrl) {
18930
+ const running = inFlight.get(boardMcpUrl);
18931
+ if (running) return running;
18932
+ const started = renderPluginImpl(boardMcpUrl).finally(
18933
+ () => inFlight.delete(boardMcpUrl)
18934
+ );
18935
+ inFlight.set(boardMcpUrl, started);
18936
+ return started;
18937
+ }
18938
+ async function renderPluginImpl(boardMcpUrl) {
18734
18939
  const source = pluginRoot();
18735
18940
  if (!source) return null;
18736
18941
  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);
18942
+ const version = await runnerVersion(source);
18943
+ const want = await fingerprint(source, boardMcpUrl, version);
18944
+ const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
18741
18945
  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);
18946
+ await (0, import_promises7.mkdir)(import_path10.default.dirname(dest), { recursive: true });
18947
+ await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
18948
+ await (0, import_promises7.writeFile)(
18949
+ import_path10.default.join(dest, "factiii", ".mcp.json"),
18950
+ `${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
18951
+ `
18952
+ );
18953
+ await stampVersion(dest, version);
18954
+ await (0, import_promises7.writeFile)(stampPath(dest), want);
18745
18955
  return dest;
18746
18956
  }
18957
+ async function stampVersion(dest, version) {
18958
+ const manifest = import_path10.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
18959
+ const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
18960
+ if (!raw) return;
18961
+ const parsed = JSON.parse(raw);
18962
+ await (0, import_promises7.writeFile)(
18963
+ manifest,
18964
+ `${JSON.stringify({ ...parsed, version }, null, 2)}
18965
+ `
18966
+ );
18967
+ }
18747
18968
  function boardMcpConfig(url2) {
18748
18969
  return {
18749
18970
  mcpServers: {
@@ -18987,6 +19208,8 @@ var SpaceCore = class {
18987
19208
  this.execTarget ??= hostTarget(this.spaceDir(), () => {
18988
19209
  const env = {};
18989
19210
  for (const lane of this.mcpLanes()) env[lane.tokenEnv] = lane.mcp.token;
19211
+ const board = this.boardMcp();
19212
+ if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
18990
19213
  return env;
18991
19214
  });
18992
19215
  return this.execTarget;
@@ -19167,6 +19390,7 @@ var SpaceCore = class {
19167
19390
  this.provisionGitLane(status),
19168
19391
  this.provisionMcpLane(),
19169
19392
  this.provisionPluginLane(),
19393
+ this.provisionBinLane(),
19170
19394
  // Covers a host signed in outside the one-click button, which would
19171
19395
  // otherwise meet the wizard the first time it opens a terminal.
19172
19396
  markOnboardingComplete(target).catch(() => {
@@ -19227,6 +19451,13 @@ var SpaceCore = class {
19227
19451
  const cmd = [
19228
19452
  `claude plugin marketplace add ${q} >/dev/null 2>&1 || true`,
19229
19453
  `claude plugin install ${PLUGIN_ID} --scope user >/dev/null 2>&1 || true`,
19454
+ // Both CLIs install by COPYING the plugin into a cache keyed by its
19455
+ // version, and that copy is what loads. claude's `install` refuses once
19456
+ // something is installed ("already installed"), so without these two an
19457
+ // upgrade renders new files into a directory nothing reads again. Codex
19458
+ // needs no equivalent: its `add` re-installs from a local marketplace.
19459
+ `claude plugin marketplace update ${MARKETPLACE_NAME} >/dev/null 2>&1 || true`,
19460
+ `claude plugin update ${PLUGIN_ID} >/dev/null 2>&1 || true`,
19230
19461
  `codex plugin marketplace add ${q} >/dev/null 2>&1 || true`,
19231
19462
  `codex plugin add ${PLUGIN_ID} >/dev/null 2>&1 || true`
19232
19463
  ].join("; ");
@@ -19235,6 +19466,35 @@ var SpaceCore = class {
19235
19466
  } catch {
19236
19467
  }
19237
19468
  }
19469
+ /** Stage the plugin's `factiii-secrets` onto the agent's PATH.
19470
+ *
19471
+ * A skill can only teach a command that exists. `paths.bin` is already the
19472
+ * PATH prefix `agentEnv()` hands every spawn (it is how `factiii-expose`
19473
+ * resolves), but nothing put anything there for a deploy run - so the
19474
+ * skill would name a command the shell could not find.
19475
+ *
19476
+ * Copied rather than symlinked: an `npm i -g @factiii/runner` upgrade
19477
+ * replaces the package directory, and a link into the old one dangles.
19478
+ * Re-copied whenever the sizes differ, which is what carries an upgrade
19479
+ * through without re-copying on every ensure. */
19480
+ async provisionBinLane() {
19481
+ const source = pluginRoot();
19482
+ if (!source) return;
19483
+ const { bin } = this.target().paths;
19484
+ const from = import_path11.default.join(source, "factiii", "bin", SECRETS_BIN);
19485
+ const to = import_path11.default.join(bin, SECRETS_BIN);
19486
+ try {
19487
+ const [src, staged] = await Promise.all([
19488
+ (0, import_promises8.stat)(from),
19489
+ (0, import_promises8.stat)(to).catch(() => null)
19490
+ ]);
19491
+ if (staged?.size === src.size) return;
19492
+ await (0, import_promises8.mkdir)(bin, { recursive: true });
19493
+ await (0, import_promises8.copyFile)(from, to);
19494
+ await (0, import_promises8.chmod)(to, 493);
19495
+ } catch {
19496
+ }
19497
+ }
19238
19498
  // Register this space's MCPs with both CLIs, user-scoped, and clear the ones
19239
19499
  // it cannot currently offer. Registering regardless left every space holding
19240
19500
  // an entry it never uses, which the CLI then fails to handshake with on every
@@ -19335,6 +19595,10 @@ var BoardAgentEngine = class _BoardAgentEngine {
19335
19595
  // In-flight creator session, memory-only (the repo is the source of truth
19336
19596
  // once merged). Survives modal reopen via deployCreatorState.
19337
19597
  this.skillCreatorSessionId = null;
19598
+ // Which CLI produced that session. A resume id belongs to ONE provider, so
19599
+ // switching the picker between drafting and revising has to start fresh
19600
+ // rather than hand a claude session id to codex.
19601
+ this.skillCreatorProvider = null;
19338
19602
  // Set when the draft was resumed from a pushed deploy-skill branch:
19339
19603
  // approve commits onto that branch instead of cutting a new one.
19340
19604
  this.skillDraftBranch = null;
@@ -19355,6 +19619,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
19355
19619
  // ── Deploy run (host executor; see host-run.ts + .specs/deploy.md) ──
19356
19620
  // Memory-only: reopened clients reattach, a restart loses the run and says so.
19357
19621
  this.runState = {
19622
+ provider: "claude",
19358
19623
  phase: "idle",
19359
19624
  todos: [],
19360
19625
  logs: [],
@@ -19498,6 +19763,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
19498
19763
  });
19499
19764
  const draft = await loadSkillDraft({ ...opts, branch: payload.branch });
19500
19765
  this.skillCreatorSessionId = null;
19766
+ this.skillCreatorProvider = null;
19501
19767
  this.skillDraftBranch = payload.branch;
19502
19768
  this.creatorState = { phase: "review", logs: [], draft, error: "" };
19503
19769
  return draft;
@@ -19543,8 +19809,8 @@ var BoardAgentEngine = class _BoardAgentEngine {
19543
19809
  throw err;
19544
19810
  }
19545
19811
  }
19546
- deployFixEnvironment(sendLog) {
19547
- const opts = this.deployCreatorOpts(sendLog);
19812
+ deployFixEnvironment(sendLog, provider) {
19813
+ const opts = this.deployCreatorOpts(sendLog, provider);
19548
19814
  return this.runEnvPass(
19549
19815
  "fixing",
19550
19816
  () => fixEnvironment({
@@ -19608,13 +19874,14 @@ var BoardAgentEngine = class _BoardAgentEngine {
19608
19874
  return this.activity.clear("deploy-run");
19609
19875
  }
19610
19876
  }
19611
- deployRunStart(sendLog) {
19877
+ deployRunStart(sendLog, provider) {
19612
19878
  if (this.runState.phase === "running" || this.runState.phase === "awaiting-input" || this.runState.phase === "awaiting-auth") {
19613
19879
  throw new Error("A deploy run is already in progress.");
19614
19880
  }
19615
- const opts = this.deployCreatorOpts(sendLog);
19881
+ const opts = this.deployCreatorOpts(sendLog, provider);
19616
19882
  this.runCancelled = false;
19617
19883
  this.runState = {
19884
+ provider: opts.provider,
19618
19885
  phase: "running",
19619
19886
  todos: [],
19620
19887
  logs: [],
@@ -19735,6 +20002,9 @@ var BoardAgentEngine = class _BoardAgentEngine {
19735
20002
  throw new Error("A deploy run is still in progress.");
19736
20003
  }
19737
20004
  this.runState = {
20005
+ // Unread while idle - the next run sets its own. Carried rather than
20006
+ // reset so nothing has to invent a provider that means nothing yet.
20007
+ provider: this.runState.provider,
19738
20008
  phase: "idle",
19739
20009
  todos: [],
19740
20010
  logs: [],
@@ -19833,7 +20103,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
19833
20103
  throw err;
19834
20104
  }
19835
20105
  }
19836
- deployCreatorOpts(sendLog) {
20106
+ deployCreatorOpts(sendLog, provider) {
19837
20107
  const config = this.core.readConfig();
19838
20108
  if (!config.repoUrl || !config.githubToken) {
19839
20109
  throw new Error("Set the repository URL and GitHub token first.");
@@ -19843,36 +20113,51 @@ var BoardAgentEngine = class _BoardAgentEngine {
19843
20113
  repoUrl: config.repoUrl,
19844
20114
  mainBranch: config.mainBranch,
19845
20115
  githubToken: config.githubToken,
19846
- provider: this.core.agentProvider(),
20116
+ // resolveProvider coerces anything unknown to claude, so it can only be
20117
+ // asked about a value that is actually there.
20118
+ provider: provider ? resolveProvider(provider) : this.core.agentProvider(),
19847
20119
  sendLog
19848
20120
  };
19849
20121
  }
19850
20122
  deployCreateSkill(payload, sendLog) {
19851
20123
  this.skillDraftBranch = null;
19852
20124
  return this.runCreatorPass(async () => {
20125
+ const opts = this.deployCreatorOpts(
20126
+ this.creatorLogger(sendLog),
20127
+ payload.provider
20128
+ );
19853
20129
  const { sessionId, ...draft } = await generateDeploySkill({
19854
- ...this.deployCreatorOpts(this.creatorLogger(sendLog)),
20130
+ ...opts,
19855
20131
  instructions: payload.instructions
19856
20132
  });
19857
20133
  this.skillCreatorSessionId = sessionId || null;
20134
+ this.skillCreatorProvider = opts.provider;
19858
20135
  return draft;
19859
20136
  });
19860
20137
  }
19861
20138
  deployReviseSkill(payload, sendLog) {
19862
20139
  return this.runCreatorPass(async () => {
20140
+ const opts = this.deployCreatorOpts(
20141
+ this.creatorLogger(sendLog),
20142
+ payload.provider
20143
+ );
20144
+ const resumable = this.skillCreatorProvider === opts.provider;
19863
20145
  const { sessionId, ...draft } = await reviseDeploySkill({
19864
- ...this.deployCreatorOpts(this.creatorLogger(sendLog)),
20146
+ ...opts,
19865
20147
  feedback: payload.feedback,
19866
- resumeSessionId: this.skillCreatorSessionId ?? void 0
20148
+ // A different CLI than drafted this: its session id means nothing to
20149
+ // the new one, so the revision starts from the files on disk.
20150
+ resumeSessionId: resumable ? this.skillCreatorSessionId ?? void 0 : void 0
19867
20151
  });
19868
20152
  this.skillCreatorSessionId = sessionId || this.skillCreatorSessionId;
20153
+ this.skillCreatorProvider = opts.provider;
19869
20154
  return draft;
19870
20155
  });
19871
20156
  }
19872
20157
  // ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
19873
20158
  secretsDirs() {
19874
20159
  const spaceDir = this.core.spaceDir();
19875
- return { configDir: (0, import_path10.dirname)(spaceDir), spaceDir };
20160
+ return { configDir: (0, import_path12.dirname)(spaceDir), spaceDir };
19876
20161
  }
19877
20162
  async deploySecretsStatus() {
19878
20163
  return {
@@ -19906,6 +20191,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
19906
20191
  const config = this.core.readConfig();
19907
20192
  const onBranch = this.skillDraftBranch ?? void 0;
19908
20193
  this.skillCreatorSessionId = null;
20194
+ this.skillCreatorProvider = null;
19909
20195
  this.skillDraftBranch = null;
19910
20196
  this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
19911
20197
  this.activity.clear("deploy-skill");
@@ -19918,6 +20204,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
19918
20204
  }
19919
20205
  deployDiscardSkill() {
19920
20206
  this.skillCreatorSessionId = null;
20207
+ this.skillCreatorProvider = null;
19921
20208
  this.skillDraftBranch = null;
19922
20209
  this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
19923
20210
  this.activity.clear("deploy-skill");
@@ -20722,14 +21009,16 @@ ${c.content.trim() || "(no description)"}`
20722
21009
  case "deployFixEnvironment":
20723
21010
  return await this.deployFixEnvironment(
20724
21011
  askLogSender ?? (() => {
20725
- })
21012
+ }),
21013
+ payload?.provider
20726
21014
  );
20727
21015
  case "deployEnvState":
20728
21016
  return this.deployEnvState();
20729
21017
  case "deployRunStart":
20730
21018
  return this.deployRunStart(
20731
21019
  askLogSender ?? (() => {
20732
- })
21020
+ }),
21021
+ payload?.provider
20733
21022
  );
20734
21023
  case "deployRunState":
20735
21024
  return this.deployRunState();
@@ -20929,11 +21218,11 @@ ${c.content.trim() || "(no description)"}`
20929
21218
 
20930
21219
  // ../../shared/all/helpers/board-agent-core/index-service.ts
20931
21220
  var import_child_process5 = require("child_process");
20932
- var import_crypto3 = require("crypto");
21221
+ var import_crypto4 = require("crypto");
20933
21222
  var import_fs4 = require("fs");
20934
- var import_promises7 = __toESM(require("fs/promises"));
20935
- var import_net2 = require("net");
20936
- var import_path11 = require("path");
21223
+ var import_promises9 = __toESM(require("fs/promises"));
21224
+ var import_net3 = require("net");
21225
+ var import_path13 = require("path");
20937
21226
  var import_util7 = require("util");
20938
21227
  var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
20939
21228
  var IndexService = class {
@@ -20947,7 +21236,7 @@ var IndexService = class {
20947
21236
  /** Per-space bearer for MCP + file, derived from the control token (the
20948
21237
  * service computes the same). */
20949
21238
  bearerToken(spaceSlug) {
20950
- return (0, import_crypto3.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
21239
+ return (0, import_crypto4.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
20951
21240
  }
20952
21241
  /** MCP bridge for a space. Null until the service is up (which it kicks
20953
21242
  * off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
@@ -20992,7 +21281,7 @@ var IndexService = class {
20992
21281
  const log = await this.openLog();
20993
21282
  this.proc = (0, import_child_process5.spawn)(
20994
21283
  process.execPath,
20995
- [(0, import_path11.join)(this.opts.indexDir, "index.js")],
21284
+ [(0, import_path13.join)(this.opts.indexDir, "index.js")],
20996
21285
  {
20997
21286
  cwd: this.opts.indexDir,
20998
21287
  env: {
@@ -21020,8 +21309,8 @@ var IndexService = class {
21020
21309
  * opened (a missing log must never stop the service from starting). */
21021
21310
  async openLog() {
21022
21311
  try {
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");
21312
+ await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true });
21313
+ return (0, import_fs4.openSync)((0, import_path13.join)(this.opts.dataDir, "index.log"), "a");
21025
21314
  } catch {
21026
21315
  return null;
21027
21316
  }
@@ -21030,10 +21319,10 @@ var IndexService = class {
21030
21319
  * platform needing node-gyp fails here rather than at query time. */
21031
21320
  async installDeps() {
21032
21321
  const dir = this.opts.indexDir;
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")
21322
+ if ((0, import_fs4.existsSync)((0, import_path13.join)(dir, "node_modules"))) return;
21323
+ await import_promises9.default.copyFile(
21324
+ (0, import_path13.join)(dir, "image-package.json"),
21325
+ (0, import_path13.join)(dir, "package.json")
21037
21326
  );
21038
21327
  await execFileAsync4(
21039
21328
  "npm",
@@ -21045,15 +21334,15 @@ var IndexService = class {
21045
21334
  * CLIs' persistent MCP config, so a fresh port each start would leave every
21046
21335
  * registration pointing at a dead one. */
21047
21336
  async stablePort() {
21048
- const file = (0, import_path11.join)(this.opts.dataDir, ".port");
21049
- const saved = Number(await import_promises7.default.readFile(file, "utf-8").catch(() => ""));
21337
+ const file = (0, import_path13.join)(this.opts.dataDir, ".port");
21338
+ const saved = Number(await import_promises9.default.readFile(file, "utf-8").catch(() => ""));
21050
21339
  if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
21051
21340
  return saved;
21052
21341
  }
21053
21342
  const port = await freePort();
21054
- await import_promises7.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
21343
+ await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
21055
21344
  });
21056
- await import_promises7.default.writeFile(file, String(port), "utf-8").catch(() => {
21345
+ await import_promises9.default.writeFile(file, String(port), "utf-8").catch(() => {
21057
21346
  });
21058
21347
  return port;
21059
21348
  }
@@ -21147,14 +21436,14 @@ var IndexService = class {
21147
21436
  };
21148
21437
  function isFree(port) {
21149
21438
  return new Promise((resolve2) => {
21150
- const srv = (0, import_net2.createServer)();
21439
+ const srv = (0, import_net3.createServer)();
21151
21440
  srv.once("error", () => resolve2(false));
21152
21441
  srv.listen(port, "127.0.0.1", () => srv.close(() => resolve2(true)));
21153
21442
  });
21154
21443
  }
21155
21444
  function freePort() {
21156
21445
  return new Promise((resolve2, reject) => {
21157
- const srv = (0, import_net2.createServer)();
21446
+ const srv = (0, import_net3.createServer)();
21158
21447
  srv.on("error", reject);
21159
21448
  srv.listen(0, "127.0.0.1", () => {
21160
21449
  const addr = srv.address();
@@ -21222,22 +21511,22 @@ async function pairWithBrowser(serverUrl) {
21222
21511
  // src/config.ts
21223
21512
  var import_fs8 = __toESM(require("fs"));
21224
21513
  var import_os6 = __toESM(require("os"));
21225
- var import_path16 = __toESM(require("path"));
21514
+ var import_path18 = __toESM(require("path"));
21226
21515
 
21227
21516
  // src/secureFile.ts
21228
- var import_crypto6 = __toESM(require("crypto"));
21517
+ var import_crypto7 = __toESM(require("crypto"));
21229
21518
  var import_fs7 = __toESM(require("fs"));
21230
- var import_path15 = __toESM(require("path"));
21519
+ var import_path17 = __toESM(require("path"));
21231
21520
 
21232
21521
  // src/keychain.ts
21233
21522
  var import_child_process7 = require("child_process");
21234
- var import_crypto4 = __toESM(require("crypto"));
21523
+ var import_crypto5 = __toESM(require("crypto"));
21235
21524
  var import_fs5 = __toESM(require("fs"));
21236
21525
  var import_os4 = __toESM(require("os"));
21237
- var import_path12 = __toESM(require("path"));
21526
+ var import_path14 = __toESM(require("path"));
21238
21527
  var SERVICE = "factiii-runner";
21239
21528
  var ACCOUNT = "config-encryption-key";
21240
- var DPAPI_KEY_FILE = import_path12.default.join(
21529
+ var DPAPI_KEY_FILE = import_path14.default.join(
21241
21530
  import_os4.default.homedir(),
21242
21531
  ".factiii-runner",
21243
21532
  "config-key.dpapi"
@@ -21343,7 +21632,7 @@ function winWrite(key) {
21343
21632
  "-Command",
21344
21633
  `ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
21345
21634
  ]);
21346
- import_fs5.default.mkdirSync(import_path12.default.dirname(DPAPI_KEY_FILE), { recursive: true });
21635
+ import_fs5.default.mkdirSync(import_path14.default.dirname(DPAPI_KEY_FILE), { recursive: true });
21347
21636
  import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
21348
21637
  `, { mode: 384 });
21349
21638
  }
@@ -21483,7 +21772,7 @@ function resolve() {
21483
21772
  cached = { key: null, status: diagnose() };
21484
21773
  return cached;
21485
21774
  }
21486
- const fresh = import_crypto4.default.randomBytes(32);
21775
+ const fresh = import_crypto5.default.randomBytes(32);
21487
21776
  writeKey(fresh);
21488
21777
  const verify = readKey();
21489
21778
  if (verify.state !== "found" || !verify.key.equals(fresh)) {
@@ -21505,23 +21794,23 @@ function keychainStatus() {
21505
21794
  }
21506
21795
 
21507
21796
  // src/secureStore.ts
21508
- var import_crypto5 = __toESM(require("crypto"));
21797
+ var import_crypto6 = __toESM(require("crypto"));
21509
21798
  var import_fs6 = __toESM(require("fs"));
21510
- var import_path14 = __toESM(require("path"));
21799
+ var import_path16 = __toESM(require("path"));
21511
21800
 
21512
21801
  // src/paths.ts
21513
21802
  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");
21803
+ var import_path15 = __toESM(require("path"));
21804
+ var CONFIG_DIR = import_path15.default.join(import_os5.default.homedir(), ".factiii-runner");
21516
21805
  function safeSlug(spaceSlug) {
21517
21806
  return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
21518
21807
  }
21519
21808
  function spaceDirPath(spaceSlug) {
21520
- return import_path13.default.join(CONFIG_DIR, safeSlug(spaceSlug));
21809
+ return import_path15.default.join(CONFIG_DIR, safeSlug(spaceSlug));
21521
21810
  }
21522
21811
 
21523
21812
  // src/secureStore.ts
21524
- var VAULT_KEY_FILE = import_path14.default.join(CONFIG_DIR, "vault-key.json");
21813
+ var VAULT_KEY_FILE = import_path16.default.join(CONFIG_DIR, "vault-key.json");
21525
21814
  var SCRYPT_N2 = 1 << 15;
21526
21815
  var MIN_PASSWORD_LENGTH = 8;
21527
21816
  var SecureStoreError = class extends Error {
@@ -21548,7 +21837,7 @@ function requireKeychainKey() {
21548
21837
  );
21549
21838
  }
21550
21839
  function passwordKey(password, salt) {
21551
- return import_crypto5.default.scryptSync(password, salt, 32, {
21840
+ return import_crypto6.default.scryptSync(password, salt, 32, {
21552
21841
  N: SCRYPT_N2,
21553
21842
  maxmem: 64 * 1024 * 1024
21554
21843
  });
@@ -21556,7 +21845,7 @@ function passwordKey(password, salt) {
21556
21845
  function wrapKey(machineKey, pwKey) {
21557
21846
  const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
21558
21847
  return Buffer.from(
21559
- import_crypto5.default.hkdfSync(
21848
+ import_crypto6.default.hkdfSync(
21560
21849
  "sha256",
21561
21850
  ikm,
21562
21851
  Buffer.alloc(0),
@@ -21566,8 +21855,8 @@ function wrapKey(machineKey, pwKey) {
21566
21855
  );
21567
21856
  }
21568
21857
  function seal(plaintext, key) {
21569
- const iv = import_crypto5.default.randomBytes(12);
21570
- const cipher = import_crypto5.default.createCipheriv("aes-256-gcm", key, iv);
21858
+ const iv = import_crypto6.default.randomBytes(12);
21859
+ const cipher = import_crypto6.default.createCipheriv("aes-256-gcm", key, iv);
21571
21860
  const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
21572
21861
  return {
21573
21862
  iv: iv.toString("base64"),
@@ -21577,7 +21866,7 @@ function seal(plaintext, key) {
21577
21866
  }
21578
21867
  function unseal(box, key) {
21579
21868
  try {
21580
- const decipher = import_crypto5.default.createDecipheriv(
21869
+ const decipher = import_crypto6.default.createDecipheriv(
21581
21870
  "aes-256-gcm",
21582
21871
  key,
21583
21872
  Buffer.from(box.iv, "base64")
@@ -21610,7 +21899,7 @@ function writeVaultFile(file) {
21610
21899
  }
21611
21900
  var unlockedDek = null;
21612
21901
  function createVault(machineKey) {
21613
- const dek = import_crypto5.default.randomBytes(32);
21902
+ const dek = import_crypto6.default.randomBytes(32);
21614
21903
  writeVaultFile({
21615
21904
  v: 1,
21616
21905
  passwordEnabled: false,
@@ -21705,7 +21994,7 @@ function enablePassword(password) {
21705
21994
  );
21706
21995
  }
21707
21996
  const dek = dataKey();
21708
- const salt = import_crypto5.default.randomBytes(16);
21997
+ const salt = import_crypto6.default.randomBytes(16);
21709
21998
  writeVaultFile({
21710
21999
  v: 1,
21711
22000
  passwordEnabled: true,
@@ -21733,7 +22022,7 @@ function changePassword(current, next) {
21733
22022
  "The current password is not correct."
21734
22023
  );
21735
22024
  }
21736
- const salt = import_crypto5.default.randomBytes(16);
22025
+ const salt = import_crypto6.default.randomBytes(16);
21737
22026
  writeVaultFile({
21738
22027
  v: 1,
21739
22028
  passwordEnabled: true,
@@ -21810,8 +22099,8 @@ function storeStatus() {
21810
22099
  // src/secureFile.ts
21811
22100
  var MAGIC = "FACTIII1";
21812
22101
  function encrypt(plain, key, version) {
21813
- const iv = import_crypto6.default.randomBytes(12);
21814
- const cipher = import_crypto6.default.createCipheriv("aes-256-gcm", key, iv);
22102
+ const iv = import_crypto7.default.randomBytes(12);
22103
+ const cipher = import_crypto7.default.createCipheriv("aes-256-gcm", key, iv);
21815
22104
  const data = Buffer.concat([cipher.update(plain), cipher.final()]);
21816
22105
  const envelope = {
21817
22106
  v: version,
@@ -21822,7 +22111,7 @@ function encrypt(plain, key, version) {
21822
22111
  return `${MAGIC}${JSON.stringify(envelope)}`;
21823
22112
  }
21824
22113
  function decrypt(envelope, key) {
21825
- const decipher = import_crypto6.default.createDecipheriv(
22114
+ const decipher = import_crypto7.default.createDecipheriv(
21826
22115
  "aes-256-gcm",
21827
22116
  key,
21828
22117
  Buffer.from(envelope.iv, "base64")
@@ -21843,7 +22132,7 @@ function parseEnvelope(raw) {
21843
22132
  }
21844
22133
  }
21845
22134
  function atomicWrite(filePath, body) {
21846
- import_fs7.default.mkdirSync(import_path15.default.dirname(filePath), { recursive: true });
22135
+ import_fs7.default.mkdirSync(import_path17.default.dirname(filePath), { recursive: true });
21847
22136
  const tmp = `${filePath}.${process.pid}.tmp`;
21848
22137
  import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
21849
22138
  import_fs7.default.renameSync(tmp, filePath);
@@ -21958,8 +22247,8 @@ function writeBootJson(filePath, value2) {
21958
22247
  }
21959
22248
 
21960
22249
  // src/config.ts
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");
22250
+ var CONFIG_DIR2 = import_path18.default.join(import_os6.default.homedir(), ".factiii-runner");
22251
+ var CONFIG_FILE = import_path18.default.join(CONFIG_DIR2, "config.json");
21963
22252
  function readRunnerConfig() {
21964
22253
  return readBootJson(CONFIG_FILE);
21965
22254
  }
@@ -22128,10 +22417,10 @@ function makeChunkReassembler() {
22128
22417
  }
22129
22418
 
22130
22419
  // src/daemon.ts
22131
- var import_crypto7 = require("crypto");
22420
+ var import_crypto8 = require("crypto");
22132
22421
  var import_fs10 = __toESM(require("fs"));
22133
22422
  var import_node_datachannel = require("node-datachannel");
22134
- var import_path18 = __toESM(require("path"));
22423
+ var import_path20 = __toESM(require("path"));
22135
22424
 
22136
22425
  // ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
22137
22426
  var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
@@ -23311,12 +23600,12 @@ function parse2(str) {
23311
23600
  uri.queryKey = queryKey(uri, uri["query"]);
23312
23601
  return uri;
23313
23602
  }
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) {
23603
+ function pathNames(obj, path20) {
23604
+ const regx = /\/{2,9}/g, names = path20.replace(regx, "/").split("/");
23605
+ if (path20.slice(0, 1) == "/" || path20.length === 0) {
23317
23606
  names.splice(0, 1);
23318
23607
  }
23319
- if (path18.slice(-1) == "/") {
23608
+ if (path20.slice(-1) == "/") {
23320
23609
  names.splice(names.length - 1, 1);
23321
23610
  }
23322
23611
  return names;
@@ -23935,7 +24224,7 @@ var protocol2 = Socket.protocol;
23935
24224
  // ../../node_modules/socket.io-client/build/esm-debug/url.js
23936
24225
  var import_debug7 = __toESM(require_src(), 1);
23937
24226
  var debug7 = (0, import_debug7.default)("socket.io-client:url");
23938
- function url(uri, path18 = "", loc) {
24227
+ function url(uri, path20 = "", loc) {
23939
24228
  let obj = uri;
23940
24229
  loc = loc || typeof location !== "undefined" && location;
23941
24230
  if (null == uri)
@@ -23969,7 +24258,7 @@ function url(uri, path18 = "", loc) {
23969
24258
  obj.path = obj.path || "/";
23970
24259
  const ipv6 = obj.host.indexOf(":") !== -1;
23971
24260
  const host = ipv6 ? "[" + obj.host + "]" : obj.host;
23972
- obj.id = obj.protocol + "://" + host + ":" + obj.port + path18;
24261
+ obj.id = obj.protocol + "://" + host + ":" + obj.port + path20;
23973
24262
  obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
23974
24263
  return obj;
23975
24264
  }
@@ -25603,8 +25892,8 @@ function lookup(uri, opts) {
25603
25892
  const parsed = url(uri, opts.path || "/socket.io");
25604
25893
  const source = parsed.source;
25605
25894
  const id = parsed.id;
25606
- const path18 = parsed.path;
25607
- const sameNamespace = cache[id] && path18 in cache[id]["nsps"];
25895
+ const path20 = parsed.path;
25896
+ const sameNamespace = cache[id] && path20 in cache[id]["nsps"];
25608
25897
  const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
25609
25898
  let io;
25610
25899
  if (newConnection) {
@@ -25646,8 +25935,8 @@ function cardContextPath(target, postId) {
25646
25935
  const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
25647
25936
  return `${target.paths.root}/.cards/${safe}.md`;
25648
25937
  }
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.`;
25938
+ function cardContextPrompt(path20) {
25939
+ return `Read ${path20} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
25651
25940
  }
25652
25941
  var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
25653
25942
  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.";
@@ -25812,10 +26101,10 @@ var TerminalManager = class {
25812
26101
 
25813
26102
  // src/agent-adapter.ts
25814
26103
  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");
26104
+ var import_path19 = __toESM(require("path"));
26105
+ var RUNNER_CONFIG_PATH = import_path19.default.join(CONFIG_DIR, "runner-config.json");
25817
26106
  function connectionPath(spaceSlug) {
25818
- return import_path17.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
26107
+ return import_path19.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
25819
26108
  }
25820
26109
  function writeJson(filePath, value2) {
25821
26110
  writeSecureJson(filePath, value2);
@@ -25849,7 +26138,7 @@ function migrateLegacyConfigs() {
25849
26138
  for (const name of legacy) {
25850
26139
  const slug = name.slice("space-".length, -".json".length);
25851
26140
  const parsed = readSecureJson(
25852
- import_path17.default.join(CONFIG_DIR, name)
26141
+ import_path19.default.join(CONFIG_DIR, name)
25853
26142
  );
25854
26143
  if (!parsed) continue;
25855
26144
  if (!file.defaultSlug) {
@@ -25874,7 +26163,7 @@ function encryptStrayPlaintextConfigs() {
25874
26163
  return;
25875
26164
  }
25876
26165
  for (const name of names) {
25877
- const filePath = import_path17.default.join(CONFIG_DIR, name);
26166
+ const filePath = import_path19.default.join(CONFIG_DIR, name);
25878
26167
  try {
25879
26168
  const raw = import_fs9.default.readFileSync(filePath, "utf-8");
25880
26169
  if (!raw.startsWith("{")) continue;
@@ -26003,7 +26292,7 @@ var localConfigProvider = {
26003
26292
  "claude",
26004
26293
  "codex"
26005
26294
  ]) {
26006
- import_fs9.default.mkdirSync(import_path17.default.join(dir, sub), { recursive: true });
26295
+ import_fs9.default.mkdirSync(import_path19.default.join(dir, sub), { recursive: true });
26007
26296
  }
26008
26297
  return dir;
26009
26298
  }
@@ -26177,15 +26466,15 @@ async function startDaemon(config) {
26177
26466
  }
26178
26467
  );
26179
26468
  const indexCandidates = [
26180
- import_path18.default.join(__dirname, "..", "index"),
26181
- import_path18.default.join(__dirname, "..", "..", "index", "image")
26469
+ import_path20.default.join(__dirname, "..", "index"),
26470
+ import_path20.default.join(__dirname, "..", "..", "index", "image")
26182
26471
  ];
26183
26472
  const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
26184
26473
  const odIndex = new IndexService({
26185
- controlToken: (0, import_crypto7.createHash)("sha256").update(config.authToken).digest("hex"),
26474
+ controlToken: (0, import_crypto8.createHash)("sha256").update(config.authToken).digest("hex"),
26186
26475
  embedUrl: () => config.serverUrl,
26187
26476
  indexDir,
26188
- dataDir: import_path18.default.join(CONFIG_DIR, "index-data")
26477
+ dataDir: import_path20.default.join(CONFIG_DIR, "index-data")
26189
26478
  });
26190
26479
  void odIndex.ensureRunning().catch((err) => {
26191
26480
  console.error(