@factiii/runner 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +660 -280
- package/package.json +1 -1
- package/plugin/factiii/bin/factiii-secrets +110 -19
- package/plugin/factiii/skills/factiii-secrets/SKILL.md +59 -17
package/dist/cli.js
CHANGED
|
@@ -963,7 +963,7 @@ var require_command = __commonJS({
|
|
|
963
963
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
964
964
|
var EventEmitter = require("node:events").EventEmitter;
|
|
965
965
|
var childProcess = require("node:child_process");
|
|
966
|
-
var
|
|
966
|
+
var path21 = require("node:path");
|
|
967
967
|
var fs15 = require("node:fs");
|
|
968
968
|
var process2 = require("node:process");
|
|
969
969
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
@@ -1896,9 +1896,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1896
1896
|
let launchWithNode = false;
|
|
1897
1897
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1898
1898
|
function findFile(baseDir, baseName) {
|
|
1899
|
-
const localBin =
|
|
1899
|
+
const localBin = path21.resolve(baseDir, baseName);
|
|
1900
1900
|
if (fs15.existsSync(localBin)) return localBin;
|
|
1901
|
-
if (sourceExt.includes(
|
|
1901
|
+
if (sourceExt.includes(path21.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
1903
|
(ext) => fs15.existsSync(`${localBin}${ext}`)
|
|
1904
1904
|
);
|
|
@@ -1916,17 +1916,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1916
1916
|
} catch (err) {
|
|
1917
1917
|
resolvedScriptPath = this._scriptPath;
|
|
1918
1918
|
}
|
|
1919
|
-
executableDir =
|
|
1920
|
-
|
|
1919
|
+
executableDir = path21.resolve(
|
|
1920
|
+
path21.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 =
|
|
1927
|
+
const legacyName = path21.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path21.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(
|
|
1940
|
+
launchWithNode = sourceExt.includes(path21.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 =
|
|
2780
|
+
this._name = path21.basename(filename, path21.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(
|
|
2795
|
-
if (
|
|
2796
|
-
this._executableDir =
|
|
2794
|
+
executableDir(path22) {
|
|
2795
|
+
if (path22 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path22;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -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:
|
|
8432
|
-
const fullPath = [...
|
|
8431
|
+
const { data, path: path21, errorMaps, issueData } = params;
|
|
8432
|
+
const fullPath = [...path21, ...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,
|
|
8548
|
+
constructor(parent, value2, path21, key) {
|
|
8549
8549
|
this._cachedPath = [];
|
|
8550
8550
|
this.parent = parent;
|
|
8551
8551
|
this.data = value2;
|
|
8552
|
-
this._path =
|
|
8552
|
+
this._path = path21;
|
|
8553
8553
|
this._key = key;
|
|
8554
8554
|
}
|
|
8555
8555
|
get path() {
|
|
@@ -12659,7 +12659,7 @@ var prompts_default = {
|
|
|
12659
12659
|
"- Write ONLY those three files under .agents/skills/deploy/.",
|
|
12660
12660
|
"- Do NOT git commit or git push; the harness pushes for review.",
|
|
12661
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.",
|
|
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
|
+
'- Secret-bearing commands: any command needing a variable declared in required-variables.json MUST be written in the skill as `factiii-secrets run --key NAME [--key NAME]... -- <command>`, naming exactly the keys THAT command reads (a wrapper script\'s keys are what the script reads, not what its command line shows), with the variables referenced unexpanded and quoted (e.g. "$DATABASE_URL"). Only the named keys are injected: a step asks for its own keys, never for every key the skill declares. 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.',
|
|
12663
12663
|
"- No secret values in any file, ever."
|
|
12664
12664
|
],
|
|
12665
12665
|
deployCreateRevise: [
|
|
@@ -12667,7 +12667,7 @@ var prompts_default = {
|
|
|
12667
12667
|
"",
|
|
12668
12668
|
"{{FEEDBACK}}",
|
|
12669
12669
|
"",
|
|
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
|
|
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 --key NAME ... -- <command>` naming exactly the keys that command reads, 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."
|
|
12671
12671
|
],
|
|
12672
12672
|
deployRun: [
|
|
12673
12673
|
"## Deploy run protocol",
|
|
@@ -12675,9 +12675,10 @@ var prompts_default = {
|
|
|
12675
12675
|
"",
|
|
12676
12676
|
"## Secrets (read this before anything else)",
|
|
12677
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
|
|
12678
|
+
"- To run a command that needs any declared variable, prefix it and NAME the keys that command reads: `factiii-secrets run --key NAME [--key NAME]... -- <command>`. The owner authorizes it with their password, the runner runs it with ONLY the keys you named injected, and you get the output back with the values redacted. A command that names no key gets no secrets, so always name them. Repeat --key per name (or comma-separate them), always BEFORE the `--`. 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
12679
|
'- Quote the variables (`"$DATABASE_URL"`): they expand on the runner\'s side, not in your shell.',
|
|
12680
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
|
+
"- Name what the command actually reads. For a wrapper script, that is what the SCRIPT reads, not what appears on its command line. If you are unsure of a name, run `factiii-secrets list` - it prints the key names and whether each is set, never a value. A name that is not in the store expands empty, and the runner tells you which one it was.",
|
|
12681
12682
|
"- Wrap ONLY the commands that actually need a secret. Everything else runs unwrapped and needs no authorization.",
|
|
12682
12683
|
"",
|
|
12683
12684
|
"- 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.",
|
|
@@ -12909,7 +12910,7 @@ ${diff.slice(0, 8e3)}`,
|
|
|
12909
12910
|
}
|
|
12910
12911
|
|
|
12911
12912
|
// ../../shared/all/helpers/board-agent-core/engine.ts
|
|
12912
|
-
var
|
|
12913
|
+
var import_path13 = require("path");
|
|
12913
12914
|
|
|
12914
12915
|
// ../../shared/all/domains/electron.ts
|
|
12915
12916
|
var SETUP_LOG_PREFIX = "log ";
|
|
@@ -13018,7 +13019,7 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
|
|
|
13018
13019
|
await target.sh(`mkdir -p ${target.paths.worktrees}`);
|
|
13019
13020
|
}
|
|
13020
13021
|
async function addWorktree(target, workName, baseBranch, kind) {
|
|
13021
|
-
const
|
|
13022
|
+
const path21 = worktreeFor(target.paths, workName);
|
|
13022
13023
|
const branch = workBranch(workName, kind);
|
|
13023
13024
|
await gitInDir(
|
|
13024
13025
|
target,
|
|
@@ -13028,7 +13029,7 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13028
13029
|
baseBranch
|
|
13029
13030
|
).catch(() => {
|
|
13030
13031
|
});
|
|
13031
|
-
await removeWorktree(target,
|
|
13032
|
+
await removeWorktree(target, path21).catch(() => {
|
|
13032
13033
|
});
|
|
13033
13034
|
await gitInDir(
|
|
13034
13035
|
target,
|
|
@@ -13037,30 +13038,30 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13037
13038
|
"add",
|
|
13038
13039
|
"-B",
|
|
13039
13040
|
branch,
|
|
13040
|
-
|
|
13041
|
+
path21,
|
|
13041
13042
|
`origin/${baseBranch}`
|
|
13042
13043
|
);
|
|
13043
|
-
return { path:
|
|
13044
|
+
return { path: path21, branch };
|
|
13044
13045
|
}
|
|
13045
|
-
async function removeWorktree(target,
|
|
13046
|
+
async function removeWorktree(target, path21) {
|
|
13046
13047
|
await gitInDir(
|
|
13047
13048
|
target,
|
|
13048
13049
|
target.paths.repo,
|
|
13049
13050
|
"worktree",
|
|
13050
13051
|
"remove",
|
|
13051
13052
|
"--force",
|
|
13052
|
-
|
|
13053
|
+
path21
|
|
13053
13054
|
);
|
|
13054
13055
|
}
|
|
13055
|
-
async function removeWorktreeAndBranch(target,
|
|
13056
|
+
async function removeWorktreeAndBranch(target, path21) {
|
|
13056
13057
|
const branch = await gitInDir(
|
|
13057
13058
|
target,
|
|
13058
|
-
|
|
13059
|
+
path21,
|
|
13059
13060
|
"rev-parse",
|
|
13060
13061
|
"--abbrev-ref",
|
|
13061
13062
|
"HEAD"
|
|
13062
13063
|
).then((out) => out.trim()).catch(() => "");
|
|
13063
|
-
await removeWorktree(target,
|
|
13064
|
+
await removeWorktree(target, path21);
|
|
13064
13065
|
if (branch && branch !== "HEAD") {
|
|
13065
13066
|
await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
|
|
13066
13067
|
() => {
|
|
@@ -13074,8 +13075,8 @@ async function pruneWorktrees(target) {
|
|
|
13074
13075
|
}
|
|
13075
13076
|
);
|
|
13076
13077
|
}
|
|
13077
|
-
function isWorktreePath(target,
|
|
13078
|
-
return
|
|
13078
|
+
function isWorktreePath(target, path21) {
|
|
13079
|
+
return path21.startsWith(`${target.paths.worktrees}/`);
|
|
13079
13080
|
}
|
|
13080
13081
|
async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
|
|
13081
13082
|
if (name) await gitInDir(target, workdir, "config", "user.name", name);
|
|
@@ -13101,13 +13102,13 @@ function parseStatus(raw) {
|
|
|
13101
13102
|
const y = line[1];
|
|
13102
13103
|
const rest = line.slice(3);
|
|
13103
13104
|
const arrow = rest.indexOf(" -> ");
|
|
13104
|
-
const
|
|
13105
|
+
const path21 = arrow === -1 ? rest : rest.slice(arrow + 4);
|
|
13105
13106
|
if (x === "?" || y === "?") {
|
|
13106
|
-
unstaged.push({ path:
|
|
13107
|
+
unstaged.push({ path: path21, code: "A" });
|
|
13107
13108
|
continue;
|
|
13108
13109
|
}
|
|
13109
|
-
if (x !== " ") staged.push({ path:
|
|
13110
|
-
if (y !== " ") unstaged.push({ path:
|
|
13110
|
+
if (x !== " ") staged.push({ path: path21, code: x });
|
|
13111
|
+
if (y !== " ") unstaged.push({ path: path21, code: y });
|
|
13111
13112
|
}
|
|
13112
13113
|
return { staged, unstaged };
|
|
13113
13114
|
}
|
|
@@ -13465,10 +13466,12 @@ function execLogin(command, opts) {
|
|
|
13465
13466
|
let output = "";
|
|
13466
13467
|
let settled = false;
|
|
13467
13468
|
let timer;
|
|
13469
|
+
let idleTimer;
|
|
13468
13470
|
const done = (code, extra = "") => {
|
|
13469
13471
|
if (settled) return;
|
|
13470
13472
|
settled = true;
|
|
13471
13473
|
if (timer) clearTimeout(timer);
|
|
13474
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
13472
13475
|
resolve2({ code, output: (output + extra).slice(-cap) });
|
|
13473
13476
|
};
|
|
13474
13477
|
let proc;
|
|
@@ -13490,9 +13493,22 @@ function execLogin(command, opts) {
|
|
|
13490
13493
|
done(-1, `
|
|
13491
13494
|
[timed out after ${Math.round(opts.timeout / 1e3)}s]`);
|
|
13492
13495
|
}, opts.timeout);
|
|
13496
|
+
const idleMs = opts.idleTimeout;
|
|
13497
|
+
const bumpIdle = () => {
|
|
13498
|
+
if (!idleMs) return;
|
|
13499
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
13500
|
+
idleTimer = setTimeout(() => {
|
|
13501
|
+
killTree(proc);
|
|
13502
|
+
const secs = Math.round(idleMs / 1e3);
|
|
13503
|
+
done(-1, `
|
|
13504
|
+
[no output for ${secs}s - timed out]`);
|
|
13505
|
+
}, idleMs);
|
|
13506
|
+
};
|
|
13507
|
+
bumpIdle();
|
|
13493
13508
|
const collect = (chunk) => {
|
|
13494
13509
|
output += chunk.toString("utf-8");
|
|
13495
13510
|
if (output.length > cap * 2) output = output.slice(-cap);
|
|
13511
|
+
bumpIdle();
|
|
13496
13512
|
opts.onOutput?.(output);
|
|
13497
13513
|
};
|
|
13498
13514
|
proc.stdout.on("data", collect);
|
|
@@ -13838,6 +13854,9 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13838
13854
|
emitDeployEnv(payload) {
|
|
13839
13855
|
send(channel("deploy-env"), payload);
|
|
13840
13856
|
},
|
|
13857
|
+
emitSecretsAuth(payload) {
|
|
13858
|
+
send(channel("secrets-auth"), payload);
|
|
13859
|
+
},
|
|
13841
13860
|
emitBareFsChange(postId, change) {
|
|
13842
13861
|
send(channel(`bare-fs:${postId}`), change);
|
|
13843
13862
|
},
|
|
@@ -14373,6 +14392,7 @@ var import_util5 = require("util");
|
|
|
14373
14392
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
14374
14393
|
var import_net2 = __toESM(require("net"));
|
|
14375
14394
|
var import_path4 = __toESM(require("path"));
|
|
14395
|
+
var SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14376
14396
|
function secretsSocketPath(spaceDir) {
|
|
14377
14397
|
return import_path4.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
|
|
14378
14398
|
}
|
|
@@ -14400,9 +14420,23 @@ async function handleRun(request, handlers, reply) {
|
|
|
14400
14420
|
return;
|
|
14401
14421
|
}
|
|
14402
14422
|
const cwd = typeof request.cwd === "string" ? request.cwd : "";
|
|
14423
|
+
const requested = Array.isArray(request.keys) ? request.keys : [];
|
|
14424
|
+
const keys = [];
|
|
14425
|
+
for (const entry of requested) {
|
|
14426
|
+
if (typeof entry !== "string" || !SECRET_KEY_PATTERN.test(entry)) {
|
|
14427
|
+
reply({
|
|
14428
|
+
type: "error",
|
|
14429
|
+
message: `"${String(entry)}" is not a key name.`
|
|
14430
|
+
});
|
|
14431
|
+
return;
|
|
14432
|
+
}
|
|
14433
|
+
if (!keys.includes(entry)) keys.push(entry);
|
|
14434
|
+
}
|
|
14403
14435
|
const result = await handlers.run({
|
|
14404
14436
|
command,
|
|
14405
14437
|
cwd,
|
|
14438
|
+
keys,
|
|
14439
|
+
token: typeof request.token === "string" ? request.token : "",
|
|
14406
14440
|
status: (text) => reply({ type: "status", text })
|
|
14407
14441
|
});
|
|
14408
14442
|
if ("denied" in result) {
|
|
@@ -14443,7 +14477,9 @@ async function startSecretsBridge(opts) {
|
|
|
14443
14477
|
return;
|
|
14444
14478
|
}
|
|
14445
14479
|
if (request.op === "list") {
|
|
14446
|
-
void handlers.list().then(
|
|
14480
|
+
void handlers.list().then(
|
|
14481
|
+
(entries) => reply({ type: "keys", keys: entries.map((e) => e.key), entries })
|
|
14482
|
+
).catch(
|
|
14447
14483
|
(err) => reply({ type: "error", message: errorText(err) })
|
|
14448
14484
|
).finally(() => conn.end());
|
|
14449
14485
|
return;
|
|
@@ -14639,6 +14675,60 @@ async function checkDeployStatus(opts) {
|
|
|
14639
14675
|
variables
|
|
14640
14676
|
};
|
|
14641
14677
|
}
|
|
14678
|
+
var RELEASE_BRANCH_LIMIT = 25;
|
|
14679
|
+
var BRANCH_FIELDS = "%(refname:short)%09%(committerdate:unix)";
|
|
14680
|
+
async function listReleaseBranches(opts) {
|
|
14681
|
+
const { workspace, branch } = await ensureWorkspaceClone(opts);
|
|
14682
|
+
await hostGit(workspace, "fetch", "origin", "--prune");
|
|
14683
|
+
let counted = true;
|
|
14684
|
+
let raw = "";
|
|
14685
|
+
try {
|
|
14686
|
+
raw = await hostGit(
|
|
14687
|
+
workspace,
|
|
14688
|
+
"for-each-ref",
|
|
14689
|
+
"--sort=-committerdate",
|
|
14690
|
+
`--format=${BRANCH_FIELDS}%09%(ahead-behind:refs/remotes/origin/${branch})%09%(contents:subject)`,
|
|
14691
|
+
"refs/remotes/origin"
|
|
14692
|
+
);
|
|
14693
|
+
} catch {
|
|
14694
|
+
counted = false;
|
|
14695
|
+
raw = await hostGit(
|
|
14696
|
+
workspace,
|
|
14697
|
+
"for-each-ref",
|
|
14698
|
+
"--sort=-committerdate",
|
|
14699
|
+
`--format=${BRANCH_FIELDS}%09%(contents:subject)`,
|
|
14700
|
+
"refs/remotes/origin"
|
|
14701
|
+
);
|
|
14702
|
+
}
|
|
14703
|
+
const branches = [];
|
|
14704
|
+
for (const row of raw.split("\n")) {
|
|
14705
|
+
if (branches.length >= RELEASE_BRANCH_LIMIT) break;
|
|
14706
|
+
const [ref = "", unix = "", ...rest] = row.split(" ");
|
|
14707
|
+
if (!ref.startsWith("origin/")) continue;
|
|
14708
|
+
const name = ref.slice("origin/".length);
|
|
14709
|
+
if (name === "HEAD" || name === branch) continue;
|
|
14710
|
+
let ahead = counted ? Number(rest.shift()?.split(" ")[0]) || 0 : 0;
|
|
14711
|
+
if (!counted) {
|
|
14712
|
+
try {
|
|
14713
|
+
const count = await hostGit(
|
|
14714
|
+
workspace,
|
|
14715
|
+
"rev-list",
|
|
14716
|
+
"--count",
|
|
14717
|
+
`origin/${branch}..${ref}`
|
|
14718
|
+
);
|
|
14719
|
+
ahead = Number(count.trim()) || 0;
|
|
14720
|
+
} catch {
|
|
14721
|
+
}
|
|
14722
|
+
}
|
|
14723
|
+
branches.push({
|
|
14724
|
+
name,
|
|
14725
|
+
ahead,
|
|
14726
|
+
updated: Number(unix) || 0,
|
|
14727
|
+
subject: rest.join(" ").trim()
|
|
14728
|
+
});
|
|
14729
|
+
}
|
|
14730
|
+
return branches;
|
|
14731
|
+
}
|
|
14642
14732
|
var requiredVariableSchema = external_exports.object({
|
|
14643
14733
|
key: external_exports.string().min(1),
|
|
14644
14734
|
title: external_exports.string().catch(""),
|
|
@@ -15025,9 +15115,9 @@ function summarize(raw, base) {
|
|
|
15025
15115
|
}
|
|
15026
15116
|
async function rootLinkState(root) {
|
|
15027
15117
|
const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15028
|
-
const
|
|
15029
|
-
if (!
|
|
15030
|
-
if (!
|
|
15118
|
+
const stat = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15119
|
+
if (!stat) return "missing";
|
|
15120
|
+
if (!stat.isSymbolicLink()) return "blocked";
|
|
15031
15121
|
const target = await import_promises4.default.readlink(linkPath).catch(() => "");
|
|
15032
15122
|
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
15033
15123
|
}
|
|
@@ -15366,8 +15456,8 @@ async function migrateGlobal() {
|
|
|
15366
15456
|
const moved = [];
|
|
15367
15457
|
for (const root of CLI_ROOTS) {
|
|
15368
15458
|
const dir = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15369
|
-
const
|
|
15370
|
-
if (!
|
|
15459
|
+
const stat = await import_promises4.default.lstat(dir).catch(() => null);
|
|
15460
|
+
if (!stat || stat.isSymbolicLink()) continue;
|
|
15371
15461
|
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15372
15462
|
for (const entry of entries) {
|
|
15373
15463
|
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
@@ -15487,9 +15577,9 @@ async function installBundledSkill(opts) {
|
|
|
15487
15577
|
await write(import_path6.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15488
15578
|
for (const root of CLI_ROOTS) {
|
|
15489
15579
|
const linkPath = import_path6.default.join(home, ...root.split("/"));
|
|
15490
|
-
const
|
|
15491
|
-
if (
|
|
15492
|
-
if (!
|
|
15580
|
+
const stat = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15581
|
+
if (stat?.isSymbolicLink()) continue;
|
|
15582
|
+
if (!stat) {
|
|
15493
15583
|
await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
|
|
15494
15584
|
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15495
15585
|
continue;
|
|
@@ -16047,18 +16137,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16047
16137
|
await this.core.ensureSpaceWorkspace();
|
|
16048
16138
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16049
16139
|
if (hasGit) {
|
|
16050
|
-
const { path:
|
|
16140
|
+
const { path: path21 } = await addWorktree(
|
|
16051
16141
|
this.core.target(),
|
|
16052
16142
|
session.taskId,
|
|
16053
16143
|
baseBranch || config.mainBranch || "main",
|
|
16054
16144
|
"card"
|
|
16055
16145
|
);
|
|
16056
|
-
session.workspacePath =
|
|
16146
|
+
session.workspacePath = path21;
|
|
16057
16147
|
await setGitIdentity(
|
|
16058
16148
|
this.core.target(),
|
|
16059
16149
|
config.gitName,
|
|
16060
16150
|
config.gitEmail,
|
|
16061
|
-
|
|
16151
|
+
path21
|
|
16062
16152
|
);
|
|
16063
16153
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
16064
16154
|
} else {
|
|
@@ -16280,18 +16370,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16280
16370
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16281
16371
|
await this.core.ensureSpaceWorkspace();
|
|
16282
16372
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16283
|
-
const { path:
|
|
16373
|
+
const { path: path21 } = await addWorktree(
|
|
16284
16374
|
this.core.target(),
|
|
16285
16375
|
session.taskId,
|
|
16286
16376
|
config.mainBranch || "main",
|
|
16287
16377
|
"card"
|
|
16288
16378
|
);
|
|
16289
|
-
session.workspacePath =
|
|
16379
|
+
session.workspacePath = path21;
|
|
16290
16380
|
await setGitIdentity(
|
|
16291
16381
|
this.core.target(),
|
|
16292
16382
|
config.gitName,
|
|
16293
16383
|
config.gitEmail,
|
|
16294
|
-
|
|
16384
|
+
path21
|
|
16295
16385
|
);
|
|
16296
16386
|
await this.putCard(session);
|
|
16297
16387
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16838,12 +16928,12 @@ var FLUSH_MS = 120;
|
|
|
16838
16928
|
var SELF_WRITE_MS = 2e3;
|
|
16839
16929
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16840
16930
|
var importFs = new Function("s", "return import(s)");
|
|
16841
|
-
async function resolveReal(
|
|
16931
|
+
async function resolveReal(path21) {
|
|
16842
16932
|
try {
|
|
16843
16933
|
const { realpath } = await importFs("fs/promises");
|
|
16844
|
-
return await realpath(
|
|
16934
|
+
return await realpath(path21);
|
|
16845
16935
|
} catch {
|
|
16846
|
-
return
|
|
16936
|
+
return path21;
|
|
16847
16937
|
}
|
|
16848
16938
|
}
|
|
16849
16939
|
var loadSubscribe = null;
|
|
@@ -16957,19 +17047,19 @@ var WorkspaceWatcher = class {
|
|
|
16957
17047
|
}
|
|
16958
17048
|
record(kind, relPath) {
|
|
16959
17049
|
if (this.closed) return;
|
|
16960
|
-
const
|
|
16961
|
-
const expiry = this.selfWrites.get(
|
|
17050
|
+
const path21 = "/" + relPath;
|
|
17051
|
+
const expiry = this.selfWrites.get(path21);
|
|
16962
17052
|
if (expiry !== void 0) {
|
|
16963
17053
|
if (expiry > Date.now()) return;
|
|
16964
|
-
this.selfWrites.delete(
|
|
17054
|
+
this.selfWrites.delete(path21);
|
|
16965
17055
|
}
|
|
16966
17056
|
const { added, changed, removed } = this.pending;
|
|
16967
|
-
added.delete(
|
|
16968
|
-
changed.delete(
|
|
16969
|
-
removed.delete(
|
|
16970
|
-
if (kind === "added") added.add(
|
|
16971
|
-
else if (kind === "changed") changed.add(
|
|
16972
|
-
else removed.add(
|
|
17057
|
+
added.delete(path21);
|
|
17058
|
+
changed.delete(path21);
|
|
17059
|
+
removed.delete(path21);
|
|
17060
|
+
if (kind === "added") added.add(path21);
|
|
17061
|
+
else if (kind === "changed") changed.add(path21);
|
|
17062
|
+
else removed.add(path21);
|
|
16973
17063
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
16974
17064
|
this.pending = { ...emptyPending(), resync: true };
|
|
16975
17065
|
}
|
|
@@ -16988,8 +17078,8 @@ var WorkspaceWatcher = class {
|
|
|
16988
17078
|
this.pending = emptyPending();
|
|
16989
17079
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
16990
17080
|
const now = Date.now();
|
|
16991
|
-
for (const [
|
|
16992
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
17081
|
+
for (const [path21, expiry] of this.selfWrites) {
|
|
17082
|
+
if (expiry <= now) this.selfWrites.delete(path21);
|
|
16993
17083
|
}
|
|
16994
17084
|
this.emit({
|
|
16995
17085
|
added: resync ? [] : [...added],
|
|
@@ -17193,8 +17283,8 @@ var TerminalModeEngine = class {
|
|
|
17193
17283
|
/** Suppress the change event our own write is about to produce, so a save
|
|
17194
17284
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
17195
17285
|
* the watcher emits, since callers spell the path either way. */
|
|
17196
|
-
expectWrite(postId,
|
|
17197
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17286
|
+
expectWrite(postId, path21) {
|
|
17287
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path21));
|
|
17198
17288
|
}
|
|
17199
17289
|
// ── Lifecycle / ModeEngine ──
|
|
17200
17290
|
start(base, args) {
|
|
@@ -17391,18 +17481,18 @@ var TerminalModeEngine = class {
|
|
|
17391
17481
|
this.reservedNames.values()
|
|
17392
17482
|
);
|
|
17393
17483
|
this.reservedNames.set(session.postId, workName);
|
|
17394
|
-
const { path:
|
|
17484
|
+
const { path: path21 } = await addWorktree(
|
|
17395
17485
|
this.core.target(),
|
|
17396
17486
|
workName,
|
|
17397
17487
|
branch,
|
|
17398
17488
|
"terminal"
|
|
17399
17489
|
);
|
|
17400
|
-
session.workspacePath =
|
|
17490
|
+
session.workspacePath = path21;
|
|
17401
17491
|
await setGitIdentity(
|
|
17402
17492
|
this.core.target(),
|
|
17403
17493
|
config.gitName,
|
|
17404
17494
|
config.gitEmail,
|
|
17405
|
-
|
|
17495
|
+
path21
|
|
17406
17496
|
);
|
|
17407
17497
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17408
17498
|
} else {
|
|
@@ -17464,11 +17554,11 @@ var TerminalModeEngine = class {
|
|
|
17464
17554
|
}
|
|
17465
17555
|
async bareFsList({
|
|
17466
17556
|
postId,
|
|
17467
|
-
path:
|
|
17557
|
+
path: path21
|
|
17468
17558
|
}) {
|
|
17469
17559
|
const target = this.getBareTarget(postId);
|
|
17470
17560
|
const root = this.getBareRoot(postId);
|
|
17471
|
-
const abs = this.resolveBarePath(
|
|
17561
|
+
const abs = this.resolveBarePath(path21, root);
|
|
17472
17562
|
const relRoot = abs.slice(root.length) || "/";
|
|
17473
17563
|
const out = await target.run([
|
|
17474
17564
|
"sh",
|
|
@@ -17518,30 +17608,30 @@ var TerminalModeEngine = class {
|
|
|
17518
17608
|
}
|
|
17519
17609
|
async bareFsRead({
|
|
17520
17610
|
postId,
|
|
17521
|
-
path:
|
|
17611
|
+
path: path21
|
|
17522
17612
|
}) {
|
|
17523
17613
|
const target = this.getBareTarget(postId);
|
|
17524
|
-
const abs = this.resolveBarePath(
|
|
17614
|
+
const abs = this.resolveBarePath(path21, this.getBareRoot(postId));
|
|
17525
17615
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17526
17616
|
'`)).trim();
|
|
17527
17617
|
return { content: b64, encoding: "base64" };
|
|
17528
17618
|
}
|
|
17529
17619
|
async bareFsWrite({
|
|
17530
17620
|
postId,
|
|
17531
|
-
path:
|
|
17621
|
+
path: path21,
|
|
17532
17622
|
content,
|
|
17533
17623
|
encoding
|
|
17534
17624
|
}) {
|
|
17535
17625
|
const target = this.getBareTarget(postId);
|
|
17536
|
-
const abs = this.resolveBarePath(
|
|
17626
|
+
const abs = this.resolveBarePath(path21, this.getBareRoot(postId));
|
|
17537
17627
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17538
17628
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17539
17629
|
throw new Error("Invalid base64 payload.");
|
|
17540
17630
|
}
|
|
17541
|
-
this.expectWrite(String(postId),
|
|
17631
|
+
this.expectWrite(String(postId), path21);
|
|
17542
17632
|
await writeBase64File(target, abs, b64);
|
|
17543
17633
|
const session = this.sessions.get(String(postId));
|
|
17544
|
-
if (session) delete session.buffers[
|
|
17634
|
+
if (session) delete session.buffers[path21];
|
|
17545
17635
|
}
|
|
17546
17636
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17547
17637
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17591,39 +17681,39 @@ var TerminalModeEngine = class {
|
|
|
17591
17681
|
}
|
|
17592
17682
|
bareOpenFile({
|
|
17593
17683
|
postId: rawPostId,
|
|
17594
|
-
path:
|
|
17684
|
+
path: path21
|
|
17595
17685
|
}) {
|
|
17596
17686
|
const session = this.sessions.get(String(rawPostId));
|
|
17597
17687
|
if (!session || session.mode !== "bare") return;
|
|
17598
|
-
if (!session.openedFiles.includes(
|
|
17599
|
-
session.openedFiles.push(
|
|
17688
|
+
if (!session.openedFiles.includes(path21)) {
|
|
17689
|
+
session.openedFiles.push(path21);
|
|
17600
17690
|
}
|
|
17601
17691
|
}
|
|
17602
17692
|
bareCloseFile({
|
|
17603
17693
|
postId: rawPostId,
|
|
17604
|
-
path:
|
|
17694
|
+
path: path21
|
|
17605
17695
|
}) {
|
|
17606
17696
|
const session = this.sessions.get(String(rawPostId));
|
|
17607
17697
|
if (!session || session.mode !== "bare") return;
|
|
17608
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17609
|
-
delete session.buffers[
|
|
17698
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path21);
|
|
17699
|
+
delete session.buffers[path21];
|
|
17610
17700
|
}
|
|
17611
17701
|
bareBufferSet({
|
|
17612
17702
|
postId: rawPostId,
|
|
17613
|
-
path:
|
|
17703
|
+
path: path21,
|
|
17614
17704
|
content
|
|
17615
17705
|
}) {
|
|
17616
17706
|
const session = this.sessions.get(String(rawPostId));
|
|
17617
17707
|
if (!session || session.mode !== "bare") return;
|
|
17618
|
-
session.buffers[
|
|
17708
|
+
session.buffers[path21] = content;
|
|
17619
17709
|
}
|
|
17620
17710
|
async bareFsDelete({
|
|
17621
17711
|
postId,
|
|
17622
|
-
path:
|
|
17712
|
+
path: path21
|
|
17623
17713
|
}) {
|
|
17624
17714
|
const target = this.getBareTarget(postId);
|
|
17625
17715
|
const root = this.getBareRoot(postId);
|
|
17626
|
-
const abs = this.resolveBarePath(
|
|
17716
|
+
const abs = this.resolveBarePath(path21, root);
|
|
17627
17717
|
if (abs === root) {
|
|
17628
17718
|
throw new Error("Refusing to delete the repo root.");
|
|
17629
17719
|
}
|
|
@@ -17631,10 +17721,10 @@ var TerminalModeEngine = class {
|
|
|
17631
17721
|
}
|
|
17632
17722
|
async bareFsMkdir({
|
|
17633
17723
|
postId,
|
|
17634
|
-
path:
|
|
17724
|
+
path: path21
|
|
17635
17725
|
}) {
|
|
17636
17726
|
const target = this.getBareTarget(postId);
|
|
17637
|
-
const abs = this.resolveBarePath(
|
|
17727
|
+
const abs = this.resolveBarePath(path21, this.getBareRoot(postId));
|
|
17638
17728
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17639
17729
|
}
|
|
17640
17730
|
async bareReview({
|
|
@@ -18081,13 +18171,13 @@ var ClaudeLoginFlow = class {
|
|
|
18081
18171
|
async onClose() {
|
|
18082
18172
|
if (this.settled) return;
|
|
18083
18173
|
const errLine = this.output.slice(Math.max(this.submitMark, 0)).match(/(?:Login failed|OAuth error):[^\r\n]+/)?.[0];
|
|
18084
|
-
const
|
|
18174
|
+
const tail3 = errLine ? `${errLine.trim()} Try signing in again.` : this.output.trim().slice(-300);
|
|
18085
18175
|
if (this.urlRejecter) {
|
|
18086
|
-
this.fail(
|
|
18176
|
+
this.fail(tail3 || "Claude sign-in ended unexpectedly.");
|
|
18087
18177
|
return;
|
|
18088
18178
|
}
|
|
18089
18179
|
this.settled = true;
|
|
18090
|
-
await this.verifyAndComplete(
|
|
18180
|
+
await this.verifyAndComplete(tail3);
|
|
18091
18181
|
}
|
|
18092
18182
|
fail(message) {
|
|
18093
18183
|
if (this.settled) return;
|
|
@@ -18217,13 +18307,13 @@ var CodexLoginFlow = class {
|
|
|
18217
18307
|
}
|
|
18218
18308
|
async onClose() {
|
|
18219
18309
|
if (this.settled) return;
|
|
18220
|
-
const
|
|
18310
|
+
const tail3 = this.output.trim().slice(-300);
|
|
18221
18311
|
if (this.startRejecter) {
|
|
18222
|
-
this.fail(
|
|
18312
|
+
this.fail(tail3 || "Codex sign-in ended unexpectedly.");
|
|
18223
18313
|
return;
|
|
18224
18314
|
}
|
|
18225
18315
|
this.settled = true;
|
|
18226
|
-
await this.verifyAndComplete(
|
|
18316
|
+
await this.verifyAndComplete(tail3);
|
|
18227
18317
|
}
|
|
18228
18318
|
fail(message) {
|
|
18229
18319
|
if (this.settled) return;
|
|
@@ -18316,8 +18406,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
18316
18406
|
};
|
|
18317
18407
|
}
|
|
18318
18408
|
}
|
|
18319
|
-
async function githubApi(token,
|
|
18320
|
-
const res = await fetch(`${API_BASE}${
|
|
18409
|
+
async function githubApi(token, path21) {
|
|
18410
|
+
const res = await fetch(`${API_BASE}${path21}`, {
|
|
18321
18411
|
headers: {
|
|
18322
18412
|
Authorization: `Bearer ${token}`,
|
|
18323
18413
|
Accept: "application/vnd.github+json",
|
|
@@ -18326,7 +18416,7 @@ async function githubApi(token, path20) {
|
|
|
18326
18416
|
});
|
|
18327
18417
|
if (!res.ok) {
|
|
18328
18418
|
throw new Error(
|
|
18329
|
-
`GitHub ${
|
|
18419
|
+
`GitHub ${path21} failed (${res.status}): ${await res.text()}`
|
|
18330
18420
|
);
|
|
18331
18421
|
}
|
|
18332
18422
|
return await res.json();
|
|
@@ -18371,11 +18461,103 @@ async function listGithubBranches(token, fullName) {
|
|
|
18371
18461
|
}
|
|
18372
18462
|
|
|
18373
18463
|
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
18464
|
+
var import_path10 = __toESM(require("path"));
|
|
18465
|
+
|
|
18466
|
+
// ../../shared/all/helpers/board-agent-core/secrets-gate.ts
|
|
18467
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
18468
|
+
var DEPLOY_ORIGIN = "Deploy run";
|
|
18469
|
+
var UNATTRIBUTED = "This runner did not start the session that asked, so the request was refused. `factiii-secrets` only works inside a session the runner spawned.";
|
|
18470
|
+
var SecretsGate = class {
|
|
18471
|
+
constructor() {
|
|
18472
|
+
/** token -> origin label. Lives as long as the session that holds it. */
|
|
18473
|
+
this.tokens = /* @__PURE__ */ new Map();
|
|
18474
|
+
this.waiting = null;
|
|
18475
|
+
/** Notified on appearance, replacement AND clearing, so a modal that opened
|
|
18476
|
+
* on one request closes when it is answered elsewhere. More than one,
|
|
18477
|
+
* because the space publishes the change and the deploy run separately
|
|
18478
|
+
* projects its own requests onto the release timeline. */
|
|
18479
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
18480
|
+
}
|
|
18481
|
+
subscribe(listener) {
|
|
18482
|
+
this.listeners.add(listener);
|
|
18483
|
+
return () => this.listeners.delete(listener);
|
|
18484
|
+
}
|
|
18485
|
+
onChange() {
|
|
18486
|
+
for (const listener of this.listeners) listener();
|
|
18487
|
+
}
|
|
18488
|
+
// ── Who is asking ──
|
|
18489
|
+
/** Mint a token for one session, labelled with what the owner should see on
|
|
18490
|
+
* the prompt - the label is most of the decision, so name the session. */
|
|
18491
|
+
issueToken(origin) {
|
|
18492
|
+
const token = import_crypto2.default.randomBytes(24).toString("hex");
|
|
18493
|
+
this.tokens.set(token, origin);
|
|
18494
|
+
return token;
|
|
18495
|
+
}
|
|
18496
|
+
/** Drop a session's token when it ends, so a leaked one stops working. */
|
|
18497
|
+
revokeToken(token) {
|
|
18498
|
+
this.tokens.delete(token);
|
|
18499
|
+
}
|
|
18500
|
+
/** The origin for a token, or null when this runner never issued it. */
|
|
18501
|
+
originFor(token) {
|
|
18502
|
+
return this.tokens.get(token) ?? null;
|
|
18503
|
+
}
|
|
18504
|
+
// ── The pending request ──
|
|
18505
|
+
pending() {
|
|
18506
|
+
return this.waiting?.request ?? null;
|
|
18507
|
+
}
|
|
18508
|
+
/** Open the gate for one command and block until the owner answers. A second
|
|
18509
|
+
* request denies the older one rather than dropping it: that caller is
|
|
18510
|
+
* blocked on the promise and would otherwise hang forever. */
|
|
18511
|
+
request(opts) {
|
|
18512
|
+
this.waiting?.resolve({ denied: true });
|
|
18513
|
+
return new Promise((resolve2) => {
|
|
18514
|
+
this.waiting = {
|
|
18515
|
+
request: {
|
|
18516
|
+
id: import_crypto2.default.randomUUID(),
|
|
18517
|
+
command: opts.command,
|
|
18518
|
+
keys: opts.keys,
|
|
18519
|
+
origin: opts.origin,
|
|
18520
|
+
error: opts.error ?? "",
|
|
18521
|
+
requestedAt: Date.now()
|
|
18522
|
+
},
|
|
18523
|
+
resolve: resolve2
|
|
18524
|
+
};
|
|
18525
|
+
this.onChange();
|
|
18526
|
+
});
|
|
18527
|
+
}
|
|
18528
|
+
/** Answer the pending request. `id` must match what the client was shown: a
|
|
18529
|
+
* wrong-password re-prompt replaces the request, and an answer aimed at the
|
|
18530
|
+
* previous one must not resolve its successor with a stale password. */
|
|
18531
|
+
answer(id, result) {
|
|
18532
|
+
const waiting = this.waiting;
|
|
18533
|
+
if (!waiting || waiting.request.id !== id) return { success: false };
|
|
18534
|
+
this.waiting = null;
|
|
18535
|
+
waiting.resolve(result);
|
|
18536
|
+
this.onChange();
|
|
18537
|
+
return { success: true };
|
|
18538
|
+
}
|
|
18539
|
+
/** Deny whatever is waiting, without needing to know its id. The run was
|
|
18540
|
+
* cancelled, or the space is going down: nothing may stay blocked. */
|
|
18541
|
+
cancel() {
|
|
18542
|
+
const waiting = this.waiting;
|
|
18543
|
+
if (!waiting) return;
|
|
18544
|
+
this.waiting = null;
|
|
18545
|
+
waiting.resolve({ denied: true });
|
|
18546
|
+
this.onChange();
|
|
18547
|
+
}
|
|
18548
|
+
destroy() {
|
|
18549
|
+
this.cancel();
|
|
18550
|
+
this.tokens.clear();
|
|
18551
|
+
this.listeners.clear();
|
|
18552
|
+
}
|
|
18553
|
+
};
|
|
18554
|
+
|
|
18555
|
+
// ../../shared/all/helpers/board-agent-core/secrets-run.ts
|
|
18374
18556
|
var import_path9 = __toESM(require("path"));
|
|
18375
18557
|
|
|
18376
18558
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
18377
18559
|
var import_child_process4 = require("child_process");
|
|
18378
|
-
var
|
|
18560
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
18379
18561
|
var import_promises6 = __toESM(require("fs/promises"));
|
|
18380
18562
|
var import_path8 = __toESM(require("path"));
|
|
18381
18563
|
var import_util6 = require("util");
|
|
@@ -18403,7 +18585,7 @@ async function assertBinaries() {
|
|
|
18403
18585
|
}
|
|
18404
18586
|
}
|
|
18405
18587
|
function deriveKey(password, salt) {
|
|
18406
|
-
return
|
|
18588
|
+
return import_crypto3.default.scryptSync(password, salt, 32, {
|
|
18407
18589
|
N: SCRYPT_N,
|
|
18408
18590
|
maxmem: 64 * 1024 * 1024
|
|
18409
18591
|
});
|
|
@@ -18432,9 +18614,9 @@ async function initSecrets(configDir, password) {
|
|
|
18432
18614
|
if (!identity || !recipient) {
|
|
18433
18615
|
throw new Error("age-keygen returned no usable keypair.");
|
|
18434
18616
|
}
|
|
18435
|
-
const salt =
|
|
18436
|
-
const iv =
|
|
18437
|
-
const cipher =
|
|
18617
|
+
const salt = import_crypto3.default.randomBytes(16);
|
|
18618
|
+
const iv = import_crypto3.default.randomBytes(12);
|
|
18619
|
+
const cipher = import_crypto3.default.createCipheriv(
|
|
18438
18620
|
"aes-256-gcm",
|
|
18439
18621
|
deriveKey(password, salt),
|
|
18440
18622
|
iv
|
|
@@ -18459,7 +18641,7 @@ async function initSecrets(configDir, password) {
|
|
|
18459
18641
|
return { success: true };
|
|
18460
18642
|
}
|
|
18461
18643
|
function decryptIdentity(box, password) {
|
|
18462
|
-
const decipher =
|
|
18644
|
+
const decipher = import_crypto3.default.createDecipheriv(
|
|
18463
18645
|
"aes-256-gcm",
|
|
18464
18646
|
deriveKey(password, Buffer.from(box.salt, "base64")),
|
|
18465
18647
|
Buffer.from(box.iv, "base64")
|
|
@@ -18545,6 +18727,18 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
18545
18727
|
await import_promises6.default.rm(tmp, { force: true });
|
|
18546
18728
|
}
|
|
18547
18729
|
}
|
|
18730
|
+
async function storedSecretKeys(spaceDir) {
|
|
18731
|
+
const raw = await import_promises6.default.readFile(envPath(spaceDir), "utf-8").catch(() => "");
|
|
18732
|
+
const keys = [];
|
|
18733
|
+
for (const line of raw.split("\n")) {
|
|
18734
|
+
const eq = line.indexOf("=");
|
|
18735
|
+
if (eq <= 0 || line.startsWith("#")) continue;
|
|
18736
|
+
const key = line.slice(0, eq);
|
|
18737
|
+
if (key.startsWith("sops_")) continue;
|
|
18738
|
+
keys.push(key);
|
|
18739
|
+
}
|
|
18740
|
+
return keys;
|
|
18741
|
+
}
|
|
18548
18742
|
async function listSecretKeys(configDir, spaceDir, password) {
|
|
18549
18743
|
const identity = await identityFor(configDir, password);
|
|
18550
18744
|
return Object.keys(await readEnv(spaceDir, identity));
|
|
@@ -18572,30 +18766,10 @@ async function secretsForInjection(configDir, spaceDir, password) {
|
|
|
18572
18766
|
return readEnv(spaceDir, identity);
|
|
18573
18767
|
}
|
|
18574
18768
|
|
|
18575
|
-
// ../../shared/all/helpers/board-agent-core/
|
|
18576
|
-
var
|
|
18577
|
-
var
|
|
18578
|
-
var COMPLETE_MARKER = "DEPLOY_COMPLETE:";
|
|
18579
|
-
var FAILED_MARKER = "DEPLOY_FAILED:";
|
|
18580
|
-
var MAX_MARKERLESS_TURNS = 3;
|
|
18581
|
-
var COMMAND_TIMEOUT_MS = 45 * 6e4;
|
|
18769
|
+
// ../../shared/all/helpers/board-agent-core/secrets-run.ts
|
|
18770
|
+
var COMMAND_IDLE_TIMEOUT_MS = 45 * 6e4;
|
|
18771
|
+
var COMMAND_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
18582
18772
|
var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18583
|
-
function tailMarker(text) {
|
|
18584
|
-
const zone = text.slice(-2e3);
|
|
18585
|
-
let best = null;
|
|
18586
|
-
for (const marker of [
|
|
18587
|
-
SECRETS_MARKER,
|
|
18588
|
-
HUMAN_MARKER,
|
|
18589
|
-
COMPLETE_MARKER,
|
|
18590
|
-
FAILED_MARKER
|
|
18591
|
-
]) {
|
|
18592
|
-
const at = zone.lastIndexOf(marker);
|
|
18593
|
-
if (at !== -1 && (!best || at > best.at)) best = { marker, at };
|
|
18594
|
-
}
|
|
18595
|
-
if (!best) return null;
|
|
18596
|
-
const value2 = zone.slice(best.at + best.marker.length).trim();
|
|
18597
|
-
return value2 ? { marker: best.marker, value: value2 } : null;
|
|
18598
|
-
}
|
|
18599
18773
|
function containedCwd(cwd, spaceDir) {
|
|
18600
18774
|
if (!cwd) return null;
|
|
18601
18775
|
const resolved = import_path9.default.resolve(cwd);
|
|
@@ -18603,45 +18777,90 @@ function containedCwd(cwd, spaceDir) {
|
|
|
18603
18777
|
return resolved === root || resolved.startsWith(root + import_path9.default.sep) ? resolved : null;
|
|
18604
18778
|
}
|
|
18605
18779
|
async function authorizeAndRun(opts) {
|
|
18606
|
-
const { command,
|
|
18607
|
-
|
|
18608
|
-
|
|
18609
|
-
|
|
18610
|
-
|
|
18611
|
-
|
|
18780
|
+
const { command, gate } = opts;
|
|
18781
|
+
const scope = opts.keys ?? opts.declared;
|
|
18782
|
+
opts.status?.(
|
|
18783
|
+
scope.length ? `waiting for the owner to authorize this command (${scope.join(", ")})` : "waiting for the owner to authorize this command (no secrets requested)"
|
|
18784
|
+
);
|
|
18785
|
+
let store = null;
|
|
18786
|
+
let error = "";
|
|
18787
|
+
while (!store) {
|
|
18788
|
+
const auth = await gate.request({
|
|
18789
|
+
command,
|
|
18790
|
+
keys: scope,
|
|
18791
|
+
origin: opts.origin,
|
|
18792
|
+
error
|
|
18793
|
+
});
|
|
18612
18794
|
if ("denied" in auth) return { denied: true };
|
|
18613
18795
|
try {
|
|
18614
|
-
|
|
18796
|
+
store = await secretsForInjection(
|
|
18615
18797
|
opts.configDir,
|
|
18616
18798
|
opts.spaceDir,
|
|
18617
18799
|
auth.password
|
|
18618
18800
|
);
|
|
18619
18801
|
} catch (err) {
|
|
18620
|
-
|
|
18621
|
-
opts.status?.(`authorization failed: ${
|
|
18802
|
+
error = err instanceof Error ? err.message : String(err);
|
|
18803
|
+
opts.status?.(`authorization failed: ${error}`);
|
|
18622
18804
|
}
|
|
18623
18805
|
}
|
|
18624
|
-
const
|
|
18806
|
+
const injected = {};
|
|
18807
|
+
const missing = [];
|
|
18808
|
+
for (const key of scope) {
|
|
18809
|
+
if (key in store) injected[key] = store[key];
|
|
18810
|
+
else missing.push(key);
|
|
18811
|
+
}
|
|
18812
|
+
if (missing.length && opts.keys) {
|
|
18813
|
+
const note = `${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not in the store, so the command sees ${missing.length === 1 ? "it" : "them"} empty.`;
|
|
18814
|
+
opts.status?.(note);
|
|
18815
|
+
opts.sendLog?.({ type: "system", content: note });
|
|
18816
|
+
}
|
|
18817
|
+
const redact = redactor(Object.values(store));
|
|
18818
|
+
const names = Object.keys(injected);
|
|
18625
18819
|
opts.status?.("authorized, running");
|
|
18626
|
-
|
|
18820
|
+
opts.sendLog?.({
|
|
18627
18821
|
type: "system",
|
|
18628
|
-
content:
|
|
18822
|
+
content: `Authorized. Running the command with ${names.length ? `${names.join(", ")} injected` : "no secrets injected"}...`
|
|
18629
18823
|
});
|
|
18630
18824
|
const res = await execLogin(command, {
|
|
18631
18825
|
cwd: opts.cwd,
|
|
18632
|
-
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...
|
|
18826
|
+
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...injected },
|
|
18633
18827
|
timeout: COMMAND_TIMEOUT_MS,
|
|
18828
|
+
idleTimeout: COMMAND_IDLE_TIMEOUT_MS,
|
|
18634
18829
|
maxBuffer: 32 * 1024 * 1024
|
|
18635
18830
|
});
|
|
18636
18831
|
const output = redact(res.output);
|
|
18637
|
-
|
|
18832
|
+
opts.sendLog?.({ type: "tool", content: `$ ${command}
|
|
18638
18833
|
${tail(output)}` });
|
|
18639
18834
|
return { code: res.code, output };
|
|
18640
18835
|
}
|
|
18836
|
+
|
|
18837
|
+
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
18838
|
+
var HUMAN_MARKER = "AWAITING_INPUT:";
|
|
18839
|
+
var SECRETS_MARKER = "NEED_SECRETS:";
|
|
18840
|
+
var COMPLETE_MARKER = "DEPLOY_COMPLETE:";
|
|
18841
|
+
var FAILED_MARKER = "DEPLOY_FAILED:";
|
|
18842
|
+
var MAX_MARKERLESS_TURNS = 3;
|
|
18843
|
+
var tail2 = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18844
|
+
function tailMarker(text) {
|
|
18845
|
+
const zone = text.slice(-2e3);
|
|
18846
|
+
let best = null;
|
|
18847
|
+
for (const marker of [
|
|
18848
|
+
SECRETS_MARKER,
|
|
18849
|
+
HUMAN_MARKER,
|
|
18850
|
+
COMPLETE_MARKER,
|
|
18851
|
+
FAILED_MARKER
|
|
18852
|
+
]) {
|
|
18853
|
+
const at = zone.lastIndexOf(marker);
|
|
18854
|
+
if (at !== -1 && (!best || at > best.at)) best = { marker, at };
|
|
18855
|
+
}
|
|
18856
|
+
if (!best) return null;
|
|
18857
|
+
const value2 = zone.slice(best.at + best.marker.length).trim();
|
|
18858
|
+
return value2 ? { marker: best.marker, value: value2 } : null;
|
|
18859
|
+
}
|
|
18641
18860
|
async function startDeployRun(opts) {
|
|
18642
18861
|
const { hooks } = opts;
|
|
18643
|
-
const workspace =
|
|
18644
|
-
const configDir =
|
|
18862
|
+
const workspace = import_path10.default.join(opts.spaceDir, "workspace");
|
|
18863
|
+
const configDir = import_path10.default.dirname(opts.spaceDir);
|
|
18645
18864
|
const status = await checkDeployStatus(opts);
|
|
18646
18865
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
18647
18866
|
throw new Error(
|
|
@@ -18661,36 +18880,23 @@ async function startDeployRun(opts) {
|
|
|
18661
18880
|
);
|
|
18662
18881
|
await hostGit(workspace, "clean", "-fd");
|
|
18663
18882
|
await ensureClaudeSkillLink(workspace);
|
|
18883
|
+
const release = (opts.releaseBranch || "").replace(/[^\w./-]/g, "");
|
|
18884
|
+
const releaseNote = release && release !== status.branch ? `The release branch for this run is \`${release}\` - it was chosen in the Deploy modal, so do not ask which branch to ship. Check it out as your first step.` : "";
|
|
18885
|
+
if (releaseNote) {
|
|
18886
|
+
hooks.sendLog({
|
|
18887
|
+
type: "system",
|
|
18888
|
+
content: `Release branch: ${release}`
|
|
18889
|
+
});
|
|
18890
|
+
}
|
|
18664
18891
|
const contract = prompts_default.deployRun.join("\n");
|
|
18665
18892
|
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.";
|
|
18666
|
-
|
|
18667
|
-
|
|
18668
|
-
${reminder}` : `/deploy
|
|
18669
|
-
|
|
18670
|
-
${reminder}`;
|
|
18893
|
+
const opening = opts.provider === "codex" ? 'Use the "deploy" skill: load it and execute it step by step.' : "/deploy";
|
|
18894
|
+
let prompt2 = [opening, releaseNote, reminder].filter(Boolean).join("\n\n");
|
|
18671
18895
|
let sessionId = "";
|
|
18672
18896
|
let markerless = 0;
|
|
18673
18897
|
let todos = [];
|
|
18674
|
-
const
|
|
18675
|
-
|
|
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
|
-
});
|
|
18898
|
+
const declared = status.variables.map((v) => v.key);
|
|
18899
|
+
const deployToken = opts.gate.issueToken(DEPLOY_ORIGIN);
|
|
18694
18900
|
try {
|
|
18695
18901
|
for (; ; ) {
|
|
18696
18902
|
if (hooks.isCancelled()) throw new Error("Cancelled by user.");
|
|
@@ -18708,7 +18914,10 @@ ${reminder}`;
|
|
|
18708
18914
|
includePartialMessages: true,
|
|
18709
18915
|
resumeSessionId: sessionId || void 0,
|
|
18710
18916
|
appendSystemPrompt: sessionId ? void 0 : contract,
|
|
18711
|
-
env:
|
|
18917
|
+
env: {
|
|
18918
|
+
...spaceEnv(opts.spaceDir),
|
|
18919
|
+
FACTIII_SECRETS_TOKEN: deployToken
|
|
18920
|
+
},
|
|
18712
18921
|
callbacks: {
|
|
18713
18922
|
initialTodos: todos,
|
|
18714
18923
|
onLog: (entry) => hooks.sendLog(entry),
|
|
@@ -18732,14 +18941,22 @@ ${reminder}`;
|
|
|
18732
18941
|
spaceDir: opts.spaceDir,
|
|
18733
18942
|
cwd: workspace,
|
|
18734
18943
|
command,
|
|
18735
|
-
|
|
18944
|
+
// The marker carries a command and nothing else, so a skill speaking
|
|
18945
|
+
// it cannot name its keys. null takes the declared contract - still
|
|
18946
|
+
// a narrowing from the whole store, and the most this protocol can
|
|
18947
|
+
// say. New skills name their keys and get less.
|
|
18948
|
+
keys: null,
|
|
18949
|
+
declared,
|
|
18950
|
+
origin: DEPLOY_ORIGIN,
|
|
18951
|
+
gate: opts.gate,
|
|
18952
|
+
sendLog: hooks.sendLog
|
|
18736
18953
|
});
|
|
18737
18954
|
if ("denied" in res) {
|
|
18738
18955
|
prompt2 = "The owner declined to run that command. Adapt or stop and summarize where the release stands.";
|
|
18739
18956
|
continue;
|
|
18740
18957
|
}
|
|
18741
18958
|
markerless = 0;
|
|
18742
|
-
const clean =
|
|
18959
|
+
const clean = tail2(res.output);
|
|
18743
18960
|
prompt2 = `The authorized command finished (exit ${res.code}). Redacted output:
|
|
18744
18961
|
|
|
18745
18962
|
${clean}`;
|
|
@@ -18769,7 +18986,7 @@ ${clean}`;
|
|
|
18769
18986
|
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.";
|
|
18770
18987
|
}
|
|
18771
18988
|
} finally {
|
|
18772
|
-
|
|
18989
|
+
opts.gate.revokeToken(deployToken);
|
|
18773
18990
|
}
|
|
18774
18991
|
}
|
|
18775
18992
|
|
|
@@ -18863,37 +19080,37 @@ async function getDriveRoot(accessToken) {
|
|
|
18863
19080
|
|
|
18864
19081
|
// ../../shared/all/helpers/board-agent-core/space-core.ts
|
|
18865
19082
|
var import_promises8 = require("fs/promises");
|
|
18866
|
-
var
|
|
19083
|
+
var import_path12 = __toESM(require("path"));
|
|
18867
19084
|
|
|
18868
19085
|
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
18869
|
-
var
|
|
19086
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
18870
19087
|
var import_fs3 = require("fs");
|
|
18871
19088
|
var import_promises7 = require("fs/promises");
|
|
18872
19089
|
var import_os3 = __toESM(require("os"));
|
|
18873
|
-
var
|
|
19090
|
+
var import_path11 = __toESM(require("path"));
|
|
18874
19091
|
var MARKETPLACE_NAME = "factiii-runner";
|
|
18875
19092
|
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
18876
19093
|
var SECRETS_BIN = "factiii-secrets";
|
|
18877
19094
|
function candidates() {
|
|
18878
19095
|
return [
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
19096
|
+
import_path11.default.join(__dirname, "..", "plugin"),
|
|
19097
|
+
import_path11.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
19098
|
+
import_path11.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
18882
19099
|
];
|
|
18883
19100
|
}
|
|
18884
19101
|
function pluginRoot() {
|
|
18885
19102
|
for (const dir of candidates()) {
|
|
18886
|
-
if ((0, import_fs3.existsSync)(
|
|
19103
|
+
if ((0, import_fs3.existsSync)(import_path11.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
18887
19104
|
return dir;
|
|
18888
19105
|
}
|
|
18889
19106
|
}
|
|
18890
19107
|
return null;
|
|
18891
19108
|
}
|
|
18892
19109
|
function renderedPluginRoot() {
|
|
18893
|
-
return
|
|
19110
|
+
return import_path11.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
18894
19111
|
}
|
|
18895
19112
|
async function runnerVersion(source) {
|
|
18896
|
-
const pkg =
|
|
19113
|
+
const pkg = import_path11.default.join(source, "..", "package.json");
|
|
18897
19114
|
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
18898
19115
|
if (!raw) return FALLBACK_VERSION;
|
|
18899
19116
|
try {
|
|
@@ -18908,16 +19125,16 @@ async function treeFiles(dir) {
|
|
|
18908
19125
|
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
18909
19126
|
const files = [];
|
|
18910
19127
|
for (const entry of entries) {
|
|
18911
|
-
const full =
|
|
19128
|
+
const full = import_path11.default.join(dir, entry.name);
|
|
18912
19129
|
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
18913
19130
|
else if (entry.isFile()) files.push(full);
|
|
18914
19131
|
}
|
|
18915
19132
|
return files.sort();
|
|
18916
19133
|
}
|
|
18917
19134
|
async function fingerprint(source, url2, version = "") {
|
|
18918
|
-
const hash =
|
|
19135
|
+
const hash = import_crypto4.default.createHash("sha256").update(url2).update(version);
|
|
18919
19136
|
for (const file of await treeFiles(source)) {
|
|
18920
|
-
hash.update(
|
|
19137
|
+
hash.update(import_path11.default.relative(source, file));
|
|
18921
19138
|
hash.update(await (0, import_promises7.readFile)(file));
|
|
18922
19139
|
}
|
|
18923
19140
|
return hash.digest("hex");
|
|
@@ -18943,10 +19160,10 @@ async function renderPluginImpl(boardMcpUrl) {
|
|
|
18943
19160
|
const want = await fingerprint(source, boardMcpUrl, version);
|
|
18944
19161
|
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
18945
19162
|
if (have === want) return dest;
|
|
18946
|
-
await (0, import_promises7.mkdir)(
|
|
19163
|
+
await (0, import_promises7.mkdir)(import_path11.default.dirname(dest), { recursive: true });
|
|
18947
19164
|
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
18948
19165
|
await (0, import_promises7.writeFile)(
|
|
18949
|
-
|
|
19166
|
+
import_path11.default.join(dest, "factiii", ".mcp.json"),
|
|
18950
19167
|
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
18951
19168
|
`
|
|
18952
19169
|
);
|
|
@@ -18955,7 +19172,7 @@ async function renderPluginImpl(boardMcpUrl) {
|
|
|
18955
19172
|
return dest;
|
|
18956
19173
|
}
|
|
18957
19174
|
async function stampVersion(dest, version) {
|
|
18958
|
-
const manifest =
|
|
19175
|
+
const manifest = import_path11.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
18959
19176
|
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
18960
19177
|
if (!raw) return;
|
|
18961
19178
|
const parsed = JSON.parse(raw);
|
|
@@ -19181,6 +19398,17 @@ var SpaceCore = class {
|
|
|
19181
19398
|
// Positive-only cache per provider: true sticks until sign-out, false is
|
|
19182
19399
|
// re-probed (an in-terminal login can flip it).
|
|
19183
19400
|
this.loggedIn = {};
|
|
19401
|
+
/** The space's authorization gate, and the registry of which sessions may
|
|
19402
|
+
* ask at all (see secrets-gate.ts). Here rather than on the engine because
|
|
19403
|
+
* every session type has a SpaceCore and needs a token minted for it. */
|
|
19404
|
+
this.gate = new SecretsGate();
|
|
19405
|
+
/** The live deploy run's log sink, so a command it authorizes still lands on
|
|
19406
|
+
* the release timeline now that the bridge is not the run's own. Set by the
|
|
19407
|
+
* engine for the length of a run. */
|
|
19408
|
+
this.deployLog = null;
|
|
19409
|
+
this.secretsBridge = null;
|
|
19410
|
+
this.bridgeStarting = null;
|
|
19411
|
+
this.spaceSecretsToken = "";
|
|
19184
19412
|
// ── Persistent per-space workspace: clone, worktrees, redis state ──
|
|
19185
19413
|
// Just a directory, so a restart needs no reattach.
|
|
19186
19414
|
this.execTarget = null;
|
|
@@ -19195,10 +19423,101 @@ var SpaceCore = class {
|
|
|
19195
19423
|
this.mcp = options.mcp;
|
|
19196
19424
|
this.requestSudo = options.requestSudo;
|
|
19197
19425
|
this.activity = new ActivityTracker(this.emitter);
|
|
19426
|
+
this.gate.subscribe(() => {
|
|
19427
|
+
const pending = this.gate.pending();
|
|
19428
|
+
if (pending) {
|
|
19429
|
+
this.activity.set(
|
|
19430
|
+
"secrets-auth",
|
|
19431
|
+
"awaiting-input",
|
|
19432
|
+
"Needs your store password",
|
|
19433
|
+
`${pending.origin}: ${pending.command}`
|
|
19434
|
+
);
|
|
19435
|
+
} else {
|
|
19436
|
+
this.activity.clear("secrets-auth");
|
|
19437
|
+
}
|
|
19438
|
+
this.emitter.emitSecretsAuth({ pending: pending !== null });
|
|
19439
|
+
});
|
|
19198
19440
|
}
|
|
19199
19441
|
setLoggedIn(provider, value2) {
|
|
19200
19442
|
this.loggedIn[provider] = value2;
|
|
19201
19443
|
}
|
|
19444
|
+
/** Serve `factiii-secrets` for this space, once. Lazy rather than
|
|
19445
|
+
* constructor-time: the socket lives under `state`, which does not exist
|
|
19446
|
+
* until the workspace is provisioned. Provisioning and reattach both call
|
|
19447
|
+
* it, so the socket is up before anything can reach for it. */
|
|
19448
|
+
async ensureSecretsBridge() {
|
|
19449
|
+
if (this.secretsBridge) return;
|
|
19450
|
+
this.bridgeStarting ??= this.startSecretsBridgeOnce().finally(() => {
|
|
19451
|
+
this.bridgeStarting = null;
|
|
19452
|
+
});
|
|
19453
|
+
return this.bridgeStarting;
|
|
19454
|
+
}
|
|
19455
|
+
async startSecretsBridgeOnce() {
|
|
19456
|
+
const spaceDir = this.spaceDir();
|
|
19457
|
+
this.secretsBridge = await startSecretsBridge({
|
|
19458
|
+
spaceDir,
|
|
19459
|
+
handlers: {
|
|
19460
|
+
run: async ({ command, cwd, keys, token, status }) => {
|
|
19461
|
+
const origin = token ? this.gate.originFor(token) : null;
|
|
19462
|
+
if (!origin) {
|
|
19463
|
+
status(UNATTRIBUTED);
|
|
19464
|
+
return { code: 3, output: "" };
|
|
19465
|
+
}
|
|
19466
|
+
return authorizeAndRun({
|
|
19467
|
+
configDir: import_path12.default.dirname(spaceDir),
|
|
19468
|
+
spaceDir,
|
|
19469
|
+
// A step legitimately cds into subdirectories, so the caller's own
|
|
19470
|
+
// cwd is right - but only inside the space. Anywhere else is a
|
|
19471
|
+
// confused caller, and the workspace is the safe reading.
|
|
19472
|
+
cwd: containedCwd(cwd, spaceDir) ?? this.target().paths.workspace,
|
|
19473
|
+
command,
|
|
19474
|
+
keys,
|
|
19475
|
+
// Only the legacy marker needs a declared fallback, and that never
|
|
19476
|
+
// arrives here - a bridge caller always names its own keys.
|
|
19477
|
+
declared: [],
|
|
19478
|
+
origin,
|
|
19479
|
+
gate: this.gate,
|
|
19480
|
+
// A deploy-origin command belongs on the release timeline; every
|
|
19481
|
+
// other origin has no log of its own and reads the CLI's stderr.
|
|
19482
|
+
sendLog: this.deployLog ?? void 0
|
|
19483
|
+
});
|
|
19484
|
+
},
|
|
19485
|
+
list: () => this.secretsKeyInfo()
|
|
19486
|
+
}
|
|
19487
|
+
});
|
|
19488
|
+
}
|
|
19489
|
+
/** Key NAMES and status, no password. The contract declares its keys in
|
|
19490
|
+
* public on the default branch, and sops leaves names in cleartext, so this
|
|
19491
|
+
* reveals nothing the repo and the file do not already say. */
|
|
19492
|
+
async secretsKeyInfo() {
|
|
19493
|
+
const stored = new Set(await storedSecretKeys(this.spaceDir()));
|
|
19494
|
+
const config = this.readConfig();
|
|
19495
|
+
const declared = new Set(
|
|
19496
|
+
config.repoUrl && config.githubToken ? await checkDeployStatus({
|
|
19497
|
+
spaceDir: this.spaceDir(),
|
|
19498
|
+
repoUrl: config.repoUrl,
|
|
19499
|
+
mainBranch: config.mainBranch,
|
|
19500
|
+
githubToken: config.githubToken
|
|
19501
|
+
}).then((status) => status.variables.map((v) => v.key)).catch(() => []) : []
|
|
19502
|
+
);
|
|
19503
|
+
return [.../* @__PURE__ */ new Set([...declared, ...stored])].sort().map((key) => ({
|
|
19504
|
+
key,
|
|
19505
|
+
stored: stored.has(key),
|
|
19506
|
+
declared: declared.has(key)
|
|
19507
|
+
}));
|
|
19508
|
+
}
|
|
19509
|
+
/**
|
|
19510
|
+
* The token every session this space spawns carries, so `factiii-secrets`
|
|
19511
|
+
* inside one is attributable and everything else is refused.
|
|
19512
|
+
*
|
|
19513
|
+
* One per space, not per session: `extraEnv` is the only place every spawn
|
|
19514
|
+
* passes through and it cannot tell which session it is building for. A run
|
|
19515
|
+
* that DOES know itself - the deploy run - mints its own and overrides this.
|
|
19516
|
+
*/
|
|
19517
|
+
secretsToken() {
|
|
19518
|
+
this.spaceSecretsToken ||= this.gate.issueToken("A session on this board");
|
|
19519
|
+
return this.spaceSecretsToken;
|
|
19520
|
+
}
|
|
19202
19521
|
readConfig() {
|
|
19203
19522
|
return this.configProvider.readConfig(this.spaceSlug);
|
|
19204
19523
|
}
|
|
@@ -19210,6 +19529,7 @@ var SpaceCore = class {
|
|
|
19210
19529
|
for (const lane of this.mcpLanes()) env[lane.tokenEnv] = lane.mcp.token;
|
|
19211
19530
|
const board = this.boardMcp();
|
|
19212
19531
|
if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
|
|
19532
|
+
env.FACTIII_SECRETS_TOKEN = this.secretsToken();
|
|
19213
19533
|
return env;
|
|
19214
19534
|
});
|
|
19215
19535
|
return this.execTarget;
|
|
@@ -19385,6 +19705,7 @@ var SpaceCore = class {
|
|
|
19385
19705
|
`mkdir -p ${paths.root} ${paths.worktrees} ${paths.state} ${paths.backups} ${paths.builds} ${paths.bin}`
|
|
19386
19706
|
);
|
|
19387
19707
|
await allocateSpacePorts(this.spaceDir());
|
|
19708
|
+
await this.ensureSecretsBridge().catch(() => void 0);
|
|
19388
19709
|
await ensureRedis(target);
|
|
19389
19710
|
await Promise.all([
|
|
19390
19711
|
this.provisionGitLane(status),
|
|
@@ -19481,14 +19802,14 @@ var SpaceCore = class {
|
|
|
19481
19802
|
const source = pluginRoot();
|
|
19482
19803
|
if (!source) return;
|
|
19483
19804
|
const { bin } = this.target().paths;
|
|
19484
|
-
const from =
|
|
19485
|
-
const to =
|
|
19805
|
+
const from = import_path12.default.join(source, "factiii", "bin", SECRETS_BIN);
|
|
19806
|
+
const to = import_path12.default.join(bin, SECRETS_BIN);
|
|
19486
19807
|
try {
|
|
19487
19808
|
const [src, staged] = await Promise.all([
|
|
19488
|
-
(0, import_promises8.
|
|
19489
|
-
(0, import_promises8.
|
|
19809
|
+
(0, import_promises8.readFile)(from),
|
|
19810
|
+
(0, import_promises8.readFile)(to).catch(() => null)
|
|
19490
19811
|
]);
|
|
19491
|
-
if (staged?.
|
|
19812
|
+
if (staged?.equals(src)) return;
|
|
19492
19813
|
await (0, import_promises8.mkdir)(bin, { recursive: true });
|
|
19493
19814
|
await (0, import_promises8.copyFile)(from, to);
|
|
19494
19815
|
await (0, import_promises8.chmod)(to, 493);
|
|
@@ -19547,10 +19868,12 @@ var SpaceCore = class {
|
|
|
19547
19868
|
} catch {
|
|
19548
19869
|
return false;
|
|
19549
19870
|
}
|
|
19871
|
+
await this.ensureSecretsBridge().catch(() => void 0);
|
|
19550
19872
|
await ensureRedis(target).catch(() => {
|
|
19551
19873
|
});
|
|
19552
19874
|
await pruneWorktrees(target).catch(() => {
|
|
19553
19875
|
});
|
|
19876
|
+
await Promise.all([this.provisionPluginLane(), this.provisionBinLane()]);
|
|
19554
19877
|
await this.provisionMcpLane();
|
|
19555
19878
|
this.ready = true;
|
|
19556
19879
|
return true;
|
|
@@ -19575,6 +19898,9 @@ var SpaceCore = class {
|
|
|
19575
19898
|
destroy() {
|
|
19576
19899
|
this.ready = false;
|
|
19577
19900
|
this.setupPromise = null;
|
|
19901
|
+
this.gate.destroy();
|
|
19902
|
+
void this.secretsBridge?.close();
|
|
19903
|
+
this.secretsBridge = null;
|
|
19578
19904
|
this.activity.destroy();
|
|
19579
19905
|
}
|
|
19580
19906
|
};
|
|
@@ -19625,6 +19951,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19625
19951
|
logs: [],
|
|
19626
19952
|
question: "",
|
|
19627
19953
|
pendingCommand: "",
|
|
19954
|
+
pendingKeys: [],
|
|
19628
19955
|
error: "",
|
|
19629
19956
|
summary: "",
|
|
19630
19957
|
startedAt: 0,
|
|
@@ -19646,6 +19973,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19646
19973
|
this.emitter = options.emitter;
|
|
19647
19974
|
this.claude = new ClaudeModeEngine(this.core, this.emitter);
|
|
19648
19975
|
this.terminal = new TerminalModeEngine(this.core, this.emitter);
|
|
19976
|
+
this.gate.subscribe(() => this.projectGateOntoRun());
|
|
19649
19977
|
}
|
|
19650
19978
|
/** Everything the runner does for this space that isn't a card session, so
|
|
19651
19979
|
* the Running jobs panel can report the whole machine. Owned by the core,
|
|
@@ -19653,6 +19981,39 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19653
19981
|
get activity() {
|
|
19654
19982
|
return this.core.activity;
|
|
19655
19983
|
}
|
|
19984
|
+
/** The space's gate (space-core.ts). Reached through here because most of
|
|
19985
|
+
* what uses it - the bridge, the deploy run - lives on the engine. */
|
|
19986
|
+
get gate() {
|
|
19987
|
+
return this.core.gate;
|
|
19988
|
+
}
|
|
19989
|
+
/** Mirror the gate onto the deploy run's phase, for clients older than the
|
|
19990
|
+
* global modal - they know a password is needed only from `awaiting-auth`. */
|
|
19991
|
+
projectGateOntoRun() {
|
|
19992
|
+
const pending = this.gate.pending();
|
|
19993
|
+
const runLive = this.runState.phase === "running" || this.runState.phase === "awaiting-auth";
|
|
19994
|
+
if (runLive && pending?.origin === DEPLOY_ORIGIN) {
|
|
19995
|
+
this.setRun({
|
|
19996
|
+
phase: "awaiting-auth",
|
|
19997
|
+
pendingCommand: pending.command,
|
|
19998
|
+
pendingKeys: pending.keys,
|
|
19999
|
+
error: pending.error
|
|
20000
|
+
});
|
|
20001
|
+
} else if (this.runState.phase === "awaiting-auth") {
|
|
20002
|
+
this.setRun({ phase: "running", pendingCommand: "", pendingKeys: [] });
|
|
20003
|
+
}
|
|
20004
|
+
}
|
|
20005
|
+
// ── Secrets authorization (see secrets-gate.ts + .specs/deploy.md) ──
|
|
20006
|
+
/** What the global authorization modal renders. Null when nothing is
|
|
20007
|
+
* waiting, which is also how an answered prompt tells clients to close. */
|
|
20008
|
+
secretsAuthState() {
|
|
20009
|
+
return { pending: this.gate.pending() };
|
|
20010
|
+
}
|
|
20011
|
+
secretsAuthAnswer(payload) {
|
|
20012
|
+
return this.gate.answer(
|
|
20013
|
+
payload.id,
|
|
20014
|
+
payload.password === void 0 ? { denied: true } : { password: payload.password }
|
|
20015
|
+
);
|
|
20016
|
+
}
|
|
19656
20017
|
/** How this host protects stored credentials. Read-only: the daemon answers
|
|
19657
20018
|
* unlock and password changes itself (SECURE_ACTIONS in daemon.ts). */
|
|
19658
20019
|
secureStatus() {
|
|
@@ -19713,6 +20074,20 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19713
20074
|
githubToken: config.githubToken
|
|
19714
20075
|
});
|
|
19715
20076
|
}
|
|
20077
|
+
// What the Deploy modal's release picker offers: branches on origin that
|
|
20078
|
+
// could ship, newest first. See listReleaseBranches.
|
|
20079
|
+
async deployBranches() {
|
|
20080
|
+
const config = this.core.readConfig();
|
|
20081
|
+
if (!config.repoUrl || !config.githubToken) {
|
|
20082
|
+
throw new Error("Set the repository URL and GitHub token first.");
|
|
20083
|
+
}
|
|
20084
|
+
return listReleaseBranches({
|
|
20085
|
+
spaceDir: this.core.spaceDir(),
|
|
20086
|
+
repoUrl: config.repoUrl,
|
|
20087
|
+
mainBranch: config.mainBranch,
|
|
20088
|
+
githubToken: config.githubToken
|
|
20089
|
+
});
|
|
20090
|
+
}
|
|
19716
20091
|
// ── Agent skills (SKILL.md) ──
|
|
19717
20092
|
// Repo opts are optional here, unlike deploy: the global roots are readable
|
|
19718
20093
|
// with no repo configured, and listSkills reports repoAvailable: false.
|
|
@@ -19849,7 +20224,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19849
20224
|
* sit on `awaiting-input` for hours, which is exactly the state worth
|
|
19850
20225
|
* seeing without opening the modal. */
|
|
19851
20226
|
syncRunActivity() {
|
|
19852
|
-
const { phase, question, pendingCommand, error } = this.runState;
|
|
20227
|
+
const { phase, question, pendingCommand, pendingKeys, error } = this.runState;
|
|
19853
20228
|
switch (phase) {
|
|
19854
20229
|
case "running":
|
|
19855
20230
|
return this.activity.set("deploy-run", "working", "Deploying");
|
|
@@ -19865,7 +20240,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19865
20240
|
"deploy-run",
|
|
19866
20241
|
"awaiting-input",
|
|
19867
20242
|
"Deploy needs authorization",
|
|
19868
|
-
pendingCommand
|
|
20243
|
+
pendingKeys.length ? `${pendingCommand} (${pendingKeys.join(", ")})` : pendingCommand
|
|
19869
20244
|
);
|
|
19870
20245
|
case "failed":
|
|
19871
20246
|
return this.activity.set("deploy-run", "error", "Deploy failed", error);
|
|
@@ -19874,7 +20249,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19874
20249
|
return this.activity.clear("deploy-run");
|
|
19875
20250
|
}
|
|
19876
20251
|
}
|
|
19877
|
-
deployRunStart(sendLog, provider) {
|
|
20252
|
+
deployRunStart(sendLog, provider, releaseBranch) {
|
|
19878
20253
|
if (this.runState.phase === "running" || this.runState.phase === "awaiting-input" || this.runState.phase === "awaiting-auth") {
|
|
19879
20254
|
throw new Error("A deploy run is already in progress.");
|
|
19880
20255
|
}
|
|
@@ -19887,6 +20262,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19887
20262
|
logs: [],
|
|
19888
20263
|
question: "",
|
|
19889
20264
|
pendingCommand: "",
|
|
20265
|
+
pendingKeys: [],
|
|
19890
20266
|
error: "",
|
|
19891
20267
|
summary: "",
|
|
19892
20268
|
startedAt: Date.now(),
|
|
@@ -19911,12 +20287,15 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19911
20287
|
}
|
|
19912
20288
|
sendLog(entry);
|
|
19913
20289
|
};
|
|
20290
|
+
this.core.deployLog = bufferLog;
|
|
19914
20291
|
void startDeployRun({
|
|
19915
20292
|
spaceDir: opts.spaceDir,
|
|
19916
20293
|
repoUrl: opts.repoUrl,
|
|
19917
20294
|
mainBranch: opts.mainBranch,
|
|
19918
20295
|
githubToken: opts.githubToken,
|
|
19919
20296
|
provider: opts.provider,
|
|
20297
|
+
releaseBranch,
|
|
20298
|
+
gate: this.gate,
|
|
19920
20299
|
hooks: {
|
|
19921
20300
|
sendLog: bufferLog,
|
|
19922
20301
|
onTodos: (todos) => this.setRun({ todos }),
|
|
@@ -19924,19 +20303,13 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19924
20303
|
this.runResolvers.human = resolve2;
|
|
19925
20304
|
this.setRun({ phase: "awaiting-input", question });
|
|
19926
20305
|
}),
|
|
19927
|
-
requestAuth: (command, errorNote) => new Promise((resolve2) => {
|
|
19928
|
-
this.runResolvers.auth = resolve2;
|
|
19929
|
-
this.setRun({
|
|
19930
|
-
phase: "awaiting-auth",
|
|
19931
|
-
pendingCommand: command,
|
|
19932
|
-
error: errorNote ?? ""
|
|
19933
|
-
});
|
|
19934
|
-
}),
|
|
19935
20306
|
isCancelled: () => this.runCancelled,
|
|
19936
20307
|
registerProc: (proc) => {
|
|
19937
20308
|
this.runProc = proc;
|
|
19938
20309
|
}
|
|
19939
20310
|
}
|
|
20311
|
+
}).finally(() => {
|
|
20312
|
+
this.core.deployLog = null;
|
|
19940
20313
|
}).then(({ shipped, summary }) => {
|
|
19941
20314
|
if (shipped) {
|
|
19942
20315
|
this.setRun({ phase: "success", summary, finishedAt: Date.now() });
|
|
@@ -19969,31 +20342,28 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19969
20342
|
}
|
|
19970
20343
|
// The password arrives RUNNER-DIRECT here and only feeds decryption; it
|
|
19971
20344
|
// never enters the agent session (see .specs/deploy.md).
|
|
20345
|
+
//
|
|
20346
|
+
// Both predate the global modal, when a deploy run was the only thing that
|
|
20347
|
+
// could ask, and are kept so a client too old to send `secretsAuthAnswer`
|
|
20348
|
+
// still works.
|
|
19972
20349
|
deployRunAuthorize(payload) {
|
|
19973
|
-
|
|
19974
|
-
if (!resolve2 || this.runState.phase !== "awaiting-auth") {
|
|
19975
|
-
throw new Error("No command is awaiting authorization.");
|
|
19976
|
-
}
|
|
19977
|
-
this.runResolvers.auth = void 0;
|
|
19978
|
-
this.setRun({ phase: "running", pendingCommand: "", error: "" });
|
|
19979
|
-
resolve2({ password: payload.password });
|
|
19980
|
-
return { success: true };
|
|
20350
|
+
return this.answerPendingAuth({ password: payload.password });
|
|
19981
20351
|
}
|
|
19982
20352
|
deployRunDeny() {
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
|
|
20353
|
+
return this.answerPendingAuth({ denied: true });
|
|
20354
|
+
}
|
|
20355
|
+
answerPendingAuth(result) {
|
|
20356
|
+
const pending = this.gate.pending();
|
|
20357
|
+
if (pending?.origin !== DEPLOY_ORIGIN) {
|
|
20358
|
+
throw new Error("No deploy command is awaiting authorization.");
|
|
19986
20359
|
}
|
|
19987
|
-
this.
|
|
19988
|
-
this.setRun({ phase: "running", pendingCommand: "" });
|
|
19989
|
-
resolve2({ denied: true });
|
|
19990
|
-
return { success: true };
|
|
20360
|
+
return this.gate.answer(pending.id, result);
|
|
19991
20361
|
}
|
|
19992
20362
|
deployRunCancel() {
|
|
19993
20363
|
this.runCancelled = true;
|
|
19994
20364
|
if (this.runProc) killTree(this.runProc, "SIGTERM");
|
|
19995
20365
|
this.runResolvers.human?.("cancel");
|
|
19996
|
-
this.
|
|
20366
|
+
if (this.gate.pending()?.origin === DEPLOY_ORIGIN) this.gate.cancel();
|
|
19997
20367
|
this.runResolvers = {};
|
|
19998
20368
|
return { success: true };
|
|
19999
20369
|
}
|
|
@@ -20010,6 +20380,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20010
20380
|
logs: [],
|
|
20011
20381
|
question: "",
|
|
20012
20382
|
pendingCommand: "",
|
|
20383
|
+
pendingKeys: [],
|
|
20013
20384
|
error: "",
|
|
20014
20385
|
summary: "",
|
|
20015
20386
|
startedAt: 0,
|
|
@@ -20157,7 +20528,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20157
20528
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
20158
20529
|
secretsDirs() {
|
|
20159
20530
|
const spaceDir = this.core.spaceDir();
|
|
20160
|
-
return { configDir: (0,
|
|
20531
|
+
return { configDir: (0, import_path13.dirname)(spaceDir), spaceDir };
|
|
20161
20532
|
}
|
|
20162
20533
|
async deploySecretsStatus() {
|
|
20163
20534
|
return {
|
|
@@ -20964,6 +21335,8 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20964
21335
|
return await this.bareWorkNames();
|
|
20965
21336
|
case "deployStatus":
|
|
20966
21337
|
return await this.deployStatus();
|
|
21338
|
+
case "deployBranches":
|
|
21339
|
+
return await this.deployBranches();
|
|
20967
21340
|
case "skillsList":
|
|
20968
21341
|
return await this.skillsList();
|
|
20969
21342
|
case "skillRead":
|
|
@@ -21018,7 +21391,8 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21018
21391
|
return this.deployRunStart(
|
|
21019
21392
|
askLogSender ?? (() => {
|
|
21020
21393
|
}),
|
|
21021
|
-
payload?.provider
|
|
21394
|
+
payload?.provider,
|
|
21395
|
+
payload?.branch
|
|
21022
21396
|
);
|
|
21023
21397
|
case "deployRunState":
|
|
21024
21398
|
return this.deployRunState();
|
|
@@ -21038,6 +21412,12 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21038
21412
|
return this.deployRunDismiss();
|
|
21039
21413
|
case "deployApproveSkill":
|
|
21040
21414
|
return await this.deployApproveSkill();
|
|
21415
|
+
case "secretsAuthState":
|
|
21416
|
+
return this.secretsAuthState();
|
|
21417
|
+
case "secretsAuthAnswer":
|
|
21418
|
+
return this.secretsAuthAnswer(
|
|
21419
|
+
payload
|
|
21420
|
+
);
|
|
21041
21421
|
case "deploySecretsStatus":
|
|
21042
21422
|
return await this.deploySecretsStatus();
|
|
21043
21423
|
case "deploySecretsInit":
|
|
@@ -21218,11 +21598,11 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21218
21598
|
|
|
21219
21599
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
21220
21600
|
var import_child_process5 = require("child_process");
|
|
21221
|
-
var
|
|
21601
|
+
var import_crypto5 = require("crypto");
|
|
21222
21602
|
var import_fs4 = require("fs");
|
|
21223
21603
|
var import_promises9 = __toESM(require("fs/promises"));
|
|
21224
21604
|
var import_net3 = require("net");
|
|
21225
|
-
var
|
|
21605
|
+
var import_path14 = require("path");
|
|
21226
21606
|
var import_util7 = require("util");
|
|
21227
21607
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
21228
21608
|
var IndexService = class {
|
|
@@ -21236,7 +21616,7 @@ var IndexService = class {
|
|
|
21236
21616
|
/** Per-space bearer for MCP + file, derived from the control token (the
|
|
21237
21617
|
* service computes the same). */
|
|
21238
21618
|
bearerToken(spaceSlug) {
|
|
21239
|
-
return (0,
|
|
21619
|
+
return (0, import_crypto5.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
|
|
21240
21620
|
}
|
|
21241
21621
|
/** MCP bridge for a space. Null until the service is up (which it kicks
|
|
21242
21622
|
* off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
|
|
@@ -21281,7 +21661,7 @@ var IndexService = class {
|
|
|
21281
21661
|
const log = await this.openLog();
|
|
21282
21662
|
this.proc = (0, import_child_process5.spawn)(
|
|
21283
21663
|
process.execPath,
|
|
21284
|
-
[(0,
|
|
21664
|
+
[(0, import_path14.join)(this.opts.indexDir, "index.js")],
|
|
21285
21665
|
{
|
|
21286
21666
|
cwd: this.opts.indexDir,
|
|
21287
21667
|
env: {
|
|
@@ -21310,7 +21690,7 @@ var IndexService = class {
|
|
|
21310
21690
|
async openLog() {
|
|
21311
21691
|
try {
|
|
21312
21692
|
await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
21313
|
-
return (0, import_fs4.openSync)((0,
|
|
21693
|
+
return (0, import_fs4.openSync)((0, import_path14.join)(this.opts.dataDir, "index.log"), "a");
|
|
21314
21694
|
} catch {
|
|
21315
21695
|
return null;
|
|
21316
21696
|
}
|
|
@@ -21319,10 +21699,10 @@ var IndexService = class {
|
|
|
21319
21699
|
* platform needing node-gyp fails here rather than at query time. */
|
|
21320
21700
|
async installDeps() {
|
|
21321
21701
|
const dir = this.opts.indexDir;
|
|
21322
|
-
if ((0, import_fs4.existsSync)((0,
|
|
21702
|
+
if ((0, import_fs4.existsSync)((0, import_path14.join)(dir, "node_modules"))) return;
|
|
21323
21703
|
await import_promises9.default.copyFile(
|
|
21324
|
-
(0,
|
|
21325
|
-
(0,
|
|
21704
|
+
(0, import_path14.join)(dir, "image-package.json"),
|
|
21705
|
+
(0, import_path14.join)(dir, "package.json")
|
|
21326
21706
|
);
|
|
21327
21707
|
await execFileAsync4(
|
|
21328
21708
|
"npm",
|
|
@@ -21334,7 +21714,7 @@ var IndexService = class {
|
|
|
21334
21714
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
21335
21715
|
* registration pointing at a dead one. */
|
|
21336
21716
|
async stablePort() {
|
|
21337
|
-
const file = (0,
|
|
21717
|
+
const file = (0, import_path14.join)(this.opts.dataDir, ".port");
|
|
21338
21718
|
const saved = Number(await import_promises9.default.readFile(file, "utf-8").catch(() => ""));
|
|
21339
21719
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
21340
21720
|
return saved;
|
|
@@ -21511,22 +21891,22 @@ async function pairWithBrowser(serverUrl) {
|
|
|
21511
21891
|
// src/config.ts
|
|
21512
21892
|
var import_fs8 = __toESM(require("fs"));
|
|
21513
21893
|
var import_os6 = __toESM(require("os"));
|
|
21514
|
-
var
|
|
21894
|
+
var import_path19 = __toESM(require("path"));
|
|
21515
21895
|
|
|
21516
21896
|
// src/secureFile.ts
|
|
21517
|
-
var
|
|
21897
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
21518
21898
|
var import_fs7 = __toESM(require("fs"));
|
|
21519
|
-
var
|
|
21899
|
+
var import_path18 = __toESM(require("path"));
|
|
21520
21900
|
|
|
21521
21901
|
// src/keychain.ts
|
|
21522
21902
|
var import_child_process7 = require("child_process");
|
|
21523
|
-
var
|
|
21903
|
+
var import_crypto6 = __toESM(require("crypto"));
|
|
21524
21904
|
var import_fs5 = __toESM(require("fs"));
|
|
21525
21905
|
var import_os4 = __toESM(require("os"));
|
|
21526
|
-
var
|
|
21906
|
+
var import_path15 = __toESM(require("path"));
|
|
21527
21907
|
var SERVICE = "factiii-runner";
|
|
21528
21908
|
var ACCOUNT = "config-encryption-key";
|
|
21529
|
-
var DPAPI_KEY_FILE =
|
|
21909
|
+
var DPAPI_KEY_FILE = import_path15.default.join(
|
|
21530
21910
|
import_os4.default.homedir(),
|
|
21531
21911
|
".factiii-runner",
|
|
21532
21912
|
"config-key.dpapi"
|
|
@@ -21632,7 +22012,7 @@ function winWrite(key) {
|
|
|
21632
22012
|
"-Command",
|
|
21633
22013
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
21634
22014
|
]);
|
|
21635
|
-
import_fs5.default.mkdirSync(
|
|
22015
|
+
import_fs5.default.mkdirSync(import_path15.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
21636
22016
|
import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
21637
22017
|
`, { mode: 384 });
|
|
21638
22018
|
}
|
|
@@ -21772,7 +22152,7 @@ function resolve() {
|
|
|
21772
22152
|
cached = { key: null, status: diagnose() };
|
|
21773
22153
|
return cached;
|
|
21774
22154
|
}
|
|
21775
|
-
const fresh =
|
|
22155
|
+
const fresh = import_crypto6.default.randomBytes(32);
|
|
21776
22156
|
writeKey(fresh);
|
|
21777
22157
|
const verify = readKey();
|
|
21778
22158
|
if (verify.state !== "found" || !verify.key.equals(fresh)) {
|
|
@@ -21794,23 +22174,23 @@ function keychainStatus() {
|
|
|
21794
22174
|
}
|
|
21795
22175
|
|
|
21796
22176
|
// src/secureStore.ts
|
|
21797
|
-
var
|
|
22177
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
21798
22178
|
var import_fs6 = __toESM(require("fs"));
|
|
21799
|
-
var
|
|
22179
|
+
var import_path17 = __toESM(require("path"));
|
|
21800
22180
|
|
|
21801
22181
|
// src/paths.ts
|
|
21802
22182
|
var import_os5 = __toESM(require("os"));
|
|
21803
|
-
var
|
|
21804
|
-
var CONFIG_DIR =
|
|
22183
|
+
var import_path16 = __toESM(require("path"));
|
|
22184
|
+
var CONFIG_DIR = import_path16.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
21805
22185
|
function safeSlug(spaceSlug) {
|
|
21806
22186
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21807
22187
|
}
|
|
21808
22188
|
function spaceDirPath(spaceSlug) {
|
|
21809
|
-
return
|
|
22189
|
+
return import_path16.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
21810
22190
|
}
|
|
21811
22191
|
|
|
21812
22192
|
// src/secureStore.ts
|
|
21813
|
-
var VAULT_KEY_FILE =
|
|
22193
|
+
var VAULT_KEY_FILE = import_path17.default.join(CONFIG_DIR, "vault-key.json");
|
|
21814
22194
|
var SCRYPT_N2 = 1 << 15;
|
|
21815
22195
|
var MIN_PASSWORD_LENGTH = 8;
|
|
21816
22196
|
var SecureStoreError = class extends Error {
|
|
@@ -21837,7 +22217,7 @@ function requireKeychainKey() {
|
|
|
21837
22217
|
);
|
|
21838
22218
|
}
|
|
21839
22219
|
function passwordKey(password, salt) {
|
|
21840
|
-
return
|
|
22220
|
+
return import_crypto7.default.scryptSync(password, salt, 32, {
|
|
21841
22221
|
N: SCRYPT_N2,
|
|
21842
22222
|
maxmem: 64 * 1024 * 1024
|
|
21843
22223
|
});
|
|
@@ -21845,7 +22225,7 @@ function passwordKey(password, salt) {
|
|
|
21845
22225
|
function wrapKey(machineKey, pwKey) {
|
|
21846
22226
|
const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
|
|
21847
22227
|
return Buffer.from(
|
|
21848
|
-
|
|
22228
|
+
import_crypto7.default.hkdfSync(
|
|
21849
22229
|
"sha256",
|
|
21850
22230
|
ikm,
|
|
21851
22231
|
Buffer.alloc(0),
|
|
@@ -21855,8 +22235,8 @@ function wrapKey(machineKey, pwKey) {
|
|
|
21855
22235
|
);
|
|
21856
22236
|
}
|
|
21857
22237
|
function seal(plaintext, key) {
|
|
21858
|
-
const iv =
|
|
21859
|
-
const cipher =
|
|
22238
|
+
const iv = import_crypto7.default.randomBytes(12);
|
|
22239
|
+
const cipher = import_crypto7.default.createCipheriv("aes-256-gcm", key, iv);
|
|
21860
22240
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
21861
22241
|
return {
|
|
21862
22242
|
iv: iv.toString("base64"),
|
|
@@ -21866,7 +22246,7 @@ function seal(plaintext, key) {
|
|
|
21866
22246
|
}
|
|
21867
22247
|
function unseal(box, key) {
|
|
21868
22248
|
try {
|
|
21869
|
-
const decipher =
|
|
22249
|
+
const decipher = import_crypto7.default.createDecipheriv(
|
|
21870
22250
|
"aes-256-gcm",
|
|
21871
22251
|
key,
|
|
21872
22252
|
Buffer.from(box.iv, "base64")
|
|
@@ -21899,7 +22279,7 @@ function writeVaultFile(file) {
|
|
|
21899
22279
|
}
|
|
21900
22280
|
var unlockedDek = null;
|
|
21901
22281
|
function createVault(machineKey) {
|
|
21902
|
-
const dek =
|
|
22282
|
+
const dek = import_crypto7.default.randomBytes(32);
|
|
21903
22283
|
writeVaultFile({
|
|
21904
22284
|
v: 1,
|
|
21905
22285
|
passwordEnabled: false,
|
|
@@ -21994,7 +22374,7 @@ function enablePassword(password) {
|
|
|
21994
22374
|
);
|
|
21995
22375
|
}
|
|
21996
22376
|
const dek = dataKey();
|
|
21997
|
-
const salt =
|
|
22377
|
+
const salt = import_crypto7.default.randomBytes(16);
|
|
21998
22378
|
writeVaultFile({
|
|
21999
22379
|
v: 1,
|
|
22000
22380
|
passwordEnabled: true,
|
|
@@ -22022,7 +22402,7 @@ function changePassword(current, next) {
|
|
|
22022
22402
|
"The current password is not correct."
|
|
22023
22403
|
);
|
|
22024
22404
|
}
|
|
22025
|
-
const salt =
|
|
22405
|
+
const salt = import_crypto7.default.randomBytes(16);
|
|
22026
22406
|
writeVaultFile({
|
|
22027
22407
|
v: 1,
|
|
22028
22408
|
passwordEnabled: true,
|
|
@@ -22099,8 +22479,8 @@ function storeStatus() {
|
|
|
22099
22479
|
// src/secureFile.ts
|
|
22100
22480
|
var MAGIC = "FACTIII1";
|
|
22101
22481
|
function encrypt(plain, key, version) {
|
|
22102
|
-
const iv =
|
|
22103
|
-
const cipher =
|
|
22482
|
+
const iv = import_crypto8.default.randomBytes(12);
|
|
22483
|
+
const cipher = import_crypto8.default.createCipheriv("aes-256-gcm", key, iv);
|
|
22104
22484
|
const data = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
22105
22485
|
const envelope = {
|
|
22106
22486
|
v: version,
|
|
@@ -22111,7 +22491,7 @@ function encrypt(plain, key, version) {
|
|
|
22111
22491
|
return `${MAGIC}${JSON.stringify(envelope)}`;
|
|
22112
22492
|
}
|
|
22113
22493
|
function decrypt(envelope, key) {
|
|
22114
|
-
const decipher =
|
|
22494
|
+
const decipher = import_crypto8.default.createDecipheriv(
|
|
22115
22495
|
"aes-256-gcm",
|
|
22116
22496
|
key,
|
|
22117
22497
|
Buffer.from(envelope.iv, "base64")
|
|
@@ -22132,7 +22512,7 @@ function parseEnvelope(raw) {
|
|
|
22132
22512
|
}
|
|
22133
22513
|
}
|
|
22134
22514
|
function atomicWrite(filePath, body) {
|
|
22135
|
-
import_fs7.default.mkdirSync(
|
|
22515
|
+
import_fs7.default.mkdirSync(import_path18.default.dirname(filePath), { recursive: true });
|
|
22136
22516
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
22137
22517
|
import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
|
|
22138
22518
|
import_fs7.default.renameSync(tmp, filePath);
|
|
@@ -22247,8 +22627,8 @@ function writeBootJson(filePath, value2) {
|
|
|
22247
22627
|
}
|
|
22248
22628
|
|
|
22249
22629
|
// src/config.ts
|
|
22250
|
-
var CONFIG_DIR2 =
|
|
22251
|
-
var CONFIG_FILE =
|
|
22630
|
+
var CONFIG_DIR2 = import_path19.default.join(import_os6.default.homedir(), ".factiii-runner");
|
|
22631
|
+
var CONFIG_FILE = import_path19.default.join(CONFIG_DIR2, "config.json");
|
|
22252
22632
|
function readRunnerConfig() {
|
|
22253
22633
|
return readBootJson(CONFIG_FILE);
|
|
22254
22634
|
}
|
|
@@ -22417,10 +22797,10 @@ function makeChunkReassembler() {
|
|
|
22417
22797
|
}
|
|
22418
22798
|
|
|
22419
22799
|
// src/daemon.ts
|
|
22420
|
-
var
|
|
22800
|
+
var import_crypto9 = require("crypto");
|
|
22421
22801
|
var import_fs10 = __toESM(require("fs"));
|
|
22422
22802
|
var import_node_datachannel = require("node-datachannel");
|
|
22423
|
-
var
|
|
22803
|
+
var import_path21 = __toESM(require("path"));
|
|
22424
22804
|
|
|
22425
22805
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
22426
22806
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23600,12 +23980,12 @@ function parse2(str) {
|
|
|
23600
23980
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23601
23981
|
return uri;
|
|
23602
23982
|
}
|
|
23603
|
-
function pathNames(obj,
|
|
23604
|
-
const regx = /\/{2,9}/g, names =
|
|
23605
|
-
if (
|
|
23983
|
+
function pathNames(obj, path21) {
|
|
23984
|
+
const regx = /\/{2,9}/g, names = path21.replace(regx, "/").split("/");
|
|
23985
|
+
if (path21.slice(0, 1) == "/" || path21.length === 0) {
|
|
23606
23986
|
names.splice(0, 1);
|
|
23607
23987
|
}
|
|
23608
|
-
if (
|
|
23988
|
+
if (path21.slice(-1) == "/") {
|
|
23609
23989
|
names.splice(names.length - 1, 1);
|
|
23610
23990
|
}
|
|
23611
23991
|
return names;
|
|
@@ -24224,7 +24604,7 @@ var protocol2 = Socket.protocol;
|
|
|
24224
24604
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
24225
24605
|
var import_debug7 = __toESM(require_src(), 1);
|
|
24226
24606
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
24227
|
-
function url(uri,
|
|
24607
|
+
function url(uri, path21 = "", loc) {
|
|
24228
24608
|
let obj = uri;
|
|
24229
24609
|
loc = loc || typeof location !== "undefined" && location;
|
|
24230
24610
|
if (null == uri)
|
|
@@ -24258,7 +24638,7 @@ function url(uri, path20 = "", loc) {
|
|
|
24258
24638
|
obj.path = obj.path || "/";
|
|
24259
24639
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
24260
24640
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
24261
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
24641
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path21;
|
|
24262
24642
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
24263
24643
|
return obj;
|
|
24264
24644
|
}
|
|
@@ -25892,8 +26272,8 @@ function lookup(uri, opts) {
|
|
|
25892
26272
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
25893
26273
|
const source = parsed.source;
|
|
25894
26274
|
const id = parsed.id;
|
|
25895
|
-
const
|
|
25896
|
-
const sameNamespace = cache[id] &&
|
|
26275
|
+
const path21 = parsed.path;
|
|
26276
|
+
const sameNamespace = cache[id] && path21 in cache[id]["nsps"];
|
|
25897
26277
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
25898
26278
|
let io;
|
|
25899
26279
|
if (newConnection) {
|
|
@@ -25935,8 +26315,8 @@ function cardContextPath(target, postId) {
|
|
|
25935
26315
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
25936
26316
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
25937
26317
|
}
|
|
25938
|
-
function cardContextPrompt(
|
|
25939
|
-
return `Read ${
|
|
26318
|
+
function cardContextPrompt(path21) {
|
|
26319
|
+
return `Read ${path21} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
|
|
25940
26320
|
}
|
|
25941
26321
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
25942
26322
|
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.";
|
|
@@ -26101,10 +26481,10 @@ var TerminalManager = class {
|
|
|
26101
26481
|
|
|
26102
26482
|
// src/agent-adapter.ts
|
|
26103
26483
|
var import_fs9 = __toESM(require("fs"));
|
|
26104
|
-
var
|
|
26105
|
-
var RUNNER_CONFIG_PATH =
|
|
26484
|
+
var import_path20 = __toESM(require("path"));
|
|
26485
|
+
var RUNNER_CONFIG_PATH = import_path20.default.join(CONFIG_DIR, "runner-config.json");
|
|
26106
26486
|
function connectionPath(spaceSlug) {
|
|
26107
|
-
return
|
|
26487
|
+
return import_path20.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
26108
26488
|
}
|
|
26109
26489
|
function writeJson(filePath, value2) {
|
|
26110
26490
|
writeSecureJson(filePath, value2);
|
|
@@ -26138,7 +26518,7 @@ function migrateLegacyConfigs() {
|
|
|
26138
26518
|
for (const name of legacy) {
|
|
26139
26519
|
const slug = name.slice("space-".length, -".json".length);
|
|
26140
26520
|
const parsed = readSecureJson(
|
|
26141
|
-
|
|
26521
|
+
import_path20.default.join(CONFIG_DIR, name)
|
|
26142
26522
|
);
|
|
26143
26523
|
if (!parsed) continue;
|
|
26144
26524
|
if (!file.defaultSlug) {
|
|
@@ -26163,7 +26543,7 @@ function encryptStrayPlaintextConfigs() {
|
|
|
26163
26543
|
return;
|
|
26164
26544
|
}
|
|
26165
26545
|
for (const name of names) {
|
|
26166
|
-
const filePath =
|
|
26546
|
+
const filePath = import_path20.default.join(CONFIG_DIR, name);
|
|
26167
26547
|
try {
|
|
26168
26548
|
const raw = import_fs9.default.readFileSync(filePath, "utf-8");
|
|
26169
26549
|
if (!raw.startsWith("{")) continue;
|
|
@@ -26292,7 +26672,7 @@ var localConfigProvider = {
|
|
|
26292
26672
|
"claude",
|
|
26293
26673
|
"codex"
|
|
26294
26674
|
]) {
|
|
26295
|
-
import_fs9.default.mkdirSync(
|
|
26675
|
+
import_fs9.default.mkdirSync(import_path20.default.join(dir, sub), { recursive: true });
|
|
26296
26676
|
}
|
|
26297
26677
|
return dir;
|
|
26298
26678
|
}
|
|
@@ -26466,15 +26846,15 @@ async function startDaemon(config) {
|
|
|
26466
26846
|
}
|
|
26467
26847
|
);
|
|
26468
26848
|
const indexCandidates = [
|
|
26469
|
-
|
|
26470
|
-
|
|
26849
|
+
import_path21.default.join(__dirname, "..", "index"),
|
|
26850
|
+
import_path21.default.join(__dirname, "..", "..", "index", "image")
|
|
26471
26851
|
];
|
|
26472
26852
|
const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
|
|
26473
26853
|
const odIndex = new IndexService({
|
|
26474
|
-
controlToken: (0,
|
|
26854
|
+
controlToken: (0, import_crypto9.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
26475
26855
|
embedUrl: () => config.serverUrl,
|
|
26476
26856
|
indexDir,
|
|
26477
|
-
dataDir:
|
|
26857
|
+
dataDir: import_path21.default.join(CONFIG_DIR, "index-data")
|
|
26478
26858
|
});
|
|
26479
26859
|
void odIndex.ensureRunning().catch((err) => {
|
|
26480
26860
|
console.error(
|