@factiii/runner 0.11.1 → 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 +558 -272
- 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
|
}
|
|
@@ -13853,6 +13854,9 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13853
13854
|
emitDeployEnv(payload) {
|
|
13854
13855
|
send(channel("deploy-env"), payload);
|
|
13855
13856
|
},
|
|
13857
|
+
emitSecretsAuth(payload) {
|
|
13858
|
+
send(channel("secrets-auth"), payload);
|
|
13859
|
+
},
|
|
13856
13860
|
emitBareFsChange(postId, change) {
|
|
13857
13861
|
send(channel(`bare-fs:${postId}`), change);
|
|
13858
13862
|
},
|
|
@@ -14388,6 +14392,7 @@ var import_util5 = require("util");
|
|
|
14388
14392
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
14389
14393
|
var import_net2 = __toESM(require("net"));
|
|
14390
14394
|
var import_path4 = __toESM(require("path"));
|
|
14395
|
+
var SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14391
14396
|
function secretsSocketPath(spaceDir) {
|
|
14392
14397
|
return import_path4.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
|
|
14393
14398
|
}
|
|
@@ -14415,9 +14420,23 @@ async function handleRun(request, handlers, reply) {
|
|
|
14415
14420
|
return;
|
|
14416
14421
|
}
|
|
14417
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
|
+
}
|
|
14418
14435
|
const result = await handlers.run({
|
|
14419
14436
|
command,
|
|
14420
14437
|
cwd,
|
|
14438
|
+
keys,
|
|
14439
|
+
token: typeof request.token === "string" ? request.token : "",
|
|
14421
14440
|
status: (text) => reply({ type: "status", text })
|
|
14422
14441
|
});
|
|
14423
14442
|
if ("denied" in result) {
|
|
@@ -14458,7 +14477,9 @@ async function startSecretsBridge(opts) {
|
|
|
14458
14477
|
return;
|
|
14459
14478
|
}
|
|
14460
14479
|
if (request.op === "list") {
|
|
14461
|
-
void handlers.list().then(
|
|
14480
|
+
void handlers.list().then(
|
|
14481
|
+
(entries) => reply({ type: "keys", keys: entries.map((e) => e.key), entries })
|
|
14482
|
+
).catch(
|
|
14462
14483
|
(err) => reply({ type: "error", message: errorText(err) })
|
|
14463
14484
|
).finally(() => conn.end());
|
|
14464
14485
|
return;
|
|
@@ -15094,9 +15115,9 @@ function summarize(raw, base) {
|
|
|
15094
15115
|
}
|
|
15095
15116
|
async function rootLinkState(root) {
|
|
15096
15117
|
const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15097
|
-
const
|
|
15098
|
-
if (!
|
|
15099
|
-
if (!
|
|
15118
|
+
const stat = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15119
|
+
if (!stat) return "missing";
|
|
15120
|
+
if (!stat.isSymbolicLink()) return "blocked";
|
|
15100
15121
|
const target = await import_promises4.default.readlink(linkPath).catch(() => "");
|
|
15101
15122
|
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
15102
15123
|
}
|
|
@@ -15435,8 +15456,8 @@ async function migrateGlobal() {
|
|
|
15435
15456
|
const moved = [];
|
|
15436
15457
|
for (const root of CLI_ROOTS) {
|
|
15437
15458
|
const dir = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15438
|
-
const
|
|
15439
|
-
if (!
|
|
15459
|
+
const stat = await import_promises4.default.lstat(dir).catch(() => null);
|
|
15460
|
+
if (!stat || stat.isSymbolicLink()) continue;
|
|
15440
15461
|
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15441
15462
|
for (const entry of entries) {
|
|
15442
15463
|
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
@@ -15556,9 +15577,9 @@ async function installBundledSkill(opts) {
|
|
|
15556
15577
|
await write(import_path6.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15557
15578
|
for (const root of CLI_ROOTS) {
|
|
15558
15579
|
const linkPath = import_path6.default.join(home, ...root.split("/"));
|
|
15559
|
-
const
|
|
15560
|
-
if (
|
|
15561
|
-
if (!
|
|
15580
|
+
const stat = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15581
|
+
if (stat?.isSymbolicLink()) continue;
|
|
15582
|
+
if (!stat) {
|
|
15562
15583
|
await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
|
|
15563
15584
|
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15564
15585
|
continue;
|
|
@@ -16116,18 +16137,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16116
16137
|
await this.core.ensureSpaceWorkspace();
|
|
16117
16138
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16118
16139
|
if (hasGit) {
|
|
16119
|
-
const { path:
|
|
16140
|
+
const { path: path21 } = await addWorktree(
|
|
16120
16141
|
this.core.target(),
|
|
16121
16142
|
session.taskId,
|
|
16122
16143
|
baseBranch || config.mainBranch || "main",
|
|
16123
16144
|
"card"
|
|
16124
16145
|
);
|
|
16125
|
-
session.workspacePath =
|
|
16146
|
+
session.workspacePath = path21;
|
|
16126
16147
|
await setGitIdentity(
|
|
16127
16148
|
this.core.target(),
|
|
16128
16149
|
config.gitName,
|
|
16129
16150
|
config.gitEmail,
|
|
16130
|
-
|
|
16151
|
+
path21
|
|
16131
16152
|
);
|
|
16132
16153
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
16133
16154
|
} else {
|
|
@@ -16349,18 +16370,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16349
16370
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16350
16371
|
await this.core.ensureSpaceWorkspace();
|
|
16351
16372
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16352
|
-
const { path:
|
|
16373
|
+
const { path: path21 } = await addWorktree(
|
|
16353
16374
|
this.core.target(),
|
|
16354
16375
|
session.taskId,
|
|
16355
16376
|
config.mainBranch || "main",
|
|
16356
16377
|
"card"
|
|
16357
16378
|
);
|
|
16358
|
-
session.workspacePath =
|
|
16379
|
+
session.workspacePath = path21;
|
|
16359
16380
|
await setGitIdentity(
|
|
16360
16381
|
this.core.target(),
|
|
16361
16382
|
config.gitName,
|
|
16362
16383
|
config.gitEmail,
|
|
16363
|
-
|
|
16384
|
+
path21
|
|
16364
16385
|
);
|
|
16365
16386
|
await this.putCard(session);
|
|
16366
16387
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16907,12 +16928,12 @@ var FLUSH_MS = 120;
|
|
|
16907
16928
|
var SELF_WRITE_MS = 2e3;
|
|
16908
16929
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16909
16930
|
var importFs = new Function("s", "return import(s)");
|
|
16910
|
-
async function resolveReal(
|
|
16931
|
+
async function resolveReal(path21) {
|
|
16911
16932
|
try {
|
|
16912
16933
|
const { realpath } = await importFs("fs/promises");
|
|
16913
|
-
return await realpath(
|
|
16934
|
+
return await realpath(path21);
|
|
16914
16935
|
} catch {
|
|
16915
|
-
return
|
|
16936
|
+
return path21;
|
|
16916
16937
|
}
|
|
16917
16938
|
}
|
|
16918
16939
|
var loadSubscribe = null;
|
|
@@ -17026,19 +17047,19 @@ var WorkspaceWatcher = class {
|
|
|
17026
17047
|
}
|
|
17027
17048
|
record(kind, relPath) {
|
|
17028
17049
|
if (this.closed) return;
|
|
17029
|
-
const
|
|
17030
|
-
const expiry = this.selfWrites.get(
|
|
17050
|
+
const path21 = "/" + relPath;
|
|
17051
|
+
const expiry = this.selfWrites.get(path21);
|
|
17031
17052
|
if (expiry !== void 0) {
|
|
17032
17053
|
if (expiry > Date.now()) return;
|
|
17033
|
-
this.selfWrites.delete(
|
|
17054
|
+
this.selfWrites.delete(path21);
|
|
17034
17055
|
}
|
|
17035
17056
|
const { added, changed, removed } = this.pending;
|
|
17036
|
-
added.delete(
|
|
17037
|
-
changed.delete(
|
|
17038
|
-
removed.delete(
|
|
17039
|
-
if (kind === "added") added.add(
|
|
17040
|
-
else if (kind === "changed") changed.add(
|
|
17041
|
-
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);
|
|
17042
17063
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
17043
17064
|
this.pending = { ...emptyPending(), resync: true };
|
|
17044
17065
|
}
|
|
@@ -17057,8 +17078,8 @@ var WorkspaceWatcher = class {
|
|
|
17057
17078
|
this.pending = emptyPending();
|
|
17058
17079
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
17059
17080
|
const now = Date.now();
|
|
17060
|
-
for (const [
|
|
17061
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
17081
|
+
for (const [path21, expiry] of this.selfWrites) {
|
|
17082
|
+
if (expiry <= now) this.selfWrites.delete(path21);
|
|
17062
17083
|
}
|
|
17063
17084
|
this.emit({
|
|
17064
17085
|
added: resync ? [] : [...added],
|
|
@@ -17262,8 +17283,8 @@ var TerminalModeEngine = class {
|
|
|
17262
17283
|
/** Suppress the change event our own write is about to produce, so a save
|
|
17263
17284
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
17264
17285
|
* the watcher emits, since callers spell the path either way. */
|
|
17265
|
-
expectWrite(postId,
|
|
17266
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17286
|
+
expectWrite(postId, path21) {
|
|
17287
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path21));
|
|
17267
17288
|
}
|
|
17268
17289
|
// ── Lifecycle / ModeEngine ──
|
|
17269
17290
|
start(base, args) {
|
|
@@ -17460,18 +17481,18 @@ var TerminalModeEngine = class {
|
|
|
17460
17481
|
this.reservedNames.values()
|
|
17461
17482
|
);
|
|
17462
17483
|
this.reservedNames.set(session.postId, workName);
|
|
17463
|
-
const { path:
|
|
17484
|
+
const { path: path21 } = await addWorktree(
|
|
17464
17485
|
this.core.target(),
|
|
17465
17486
|
workName,
|
|
17466
17487
|
branch,
|
|
17467
17488
|
"terminal"
|
|
17468
17489
|
);
|
|
17469
|
-
session.workspacePath =
|
|
17490
|
+
session.workspacePath = path21;
|
|
17470
17491
|
await setGitIdentity(
|
|
17471
17492
|
this.core.target(),
|
|
17472
17493
|
config.gitName,
|
|
17473
17494
|
config.gitEmail,
|
|
17474
|
-
|
|
17495
|
+
path21
|
|
17475
17496
|
);
|
|
17476
17497
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17477
17498
|
} else {
|
|
@@ -17533,11 +17554,11 @@ var TerminalModeEngine = class {
|
|
|
17533
17554
|
}
|
|
17534
17555
|
async bareFsList({
|
|
17535
17556
|
postId,
|
|
17536
|
-
path:
|
|
17557
|
+
path: path21
|
|
17537
17558
|
}) {
|
|
17538
17559
|
const target = this.getBareTarget(postId);
|
|
17539
17560
|
const root = this.getBareRoot(postId);
|
|
17540
|
-
const abs = this.resolveBarePath(
|
|
17561
|
+
const abs = this.resolveBarePath(path21, root);
|
|
17541
17562
|
const relRoot = abs.slice(root.length) || "/";
|
|
17542
17563
|
const out = await target.run([
|
|
17543
17564
|
"sh",
|
|
@@ -17587,30 +17608,30 @@ var TerminalModeEngine = class {
|
|
|
17587
17608
|
}
|
|
17588
17609
|
async bareFsRead({
|
|
17589
17610
|
postId,
|
|
17590
|
-
path:
|
|
17611
|
+
path: path21
|
|
17591
17612
|
}) {
|
|
17592
17613
|
const target = this.getBareTarget(postId);
|
|
17593
|
-
const abs = this.resolveBarePath(
|
|
17614
|
+
const abs = this.resolveBarePath(path21, this.getBareRoot(postId));
|
|
17594
17615
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17595
17616
|
'`)).trim();
|
|
17596
17617
|
return { content: b64, encoding: "base64" };
|
|
17597
17618
|
}
|
|
17598
17619
|
async bareFsWrite({
|
|
17599
17620
|
postId,
|
|
17600
|
-
path:
|
|
17621
|
+
path: path21,
|
|
17601
17622
|
content,
|
|
17602
17623
|
encoding
|
|
17603
17624
|
}) {
|
|
17604
17625
|
const target = this.getBareTarget(postId);
|
|
17605
|
-
const abs = this.resolveBarePath(
|
|
17626
|
+
const abs = this.resolveBarePath(path21, this.getBareRoot(postId));
|
|
17606
17627
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17607
17628
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17608
17629
|
throw new Error("Invalid base64 payload.");
|
|
17609
17630
|
}
|
|
17610
|
-
this.expectWrite(String(postId),
|
|
17631
|
+
this.expectWrite(String(postId), path21);
|
|
17611
17632
|
await writeBase64File(target, abs, b64);
|
|
17612
17633
|
const session = this.sessions.get(String(postId));
|
|
17613
|
-
if (session) delete session.buffers[
|
|
17634
|
+
if (session) delete session.buffers[path21];
|
|
17614
17635
|
}
|
|
17615
17636
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17616
17637
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17660,39 +17681,39 @@ var TerminalModeEngine = class {
|
|
|
17660
17681
|
}
|
|
17661
17682
|
bareOpenFile({
|
|
17662
17683
|
postId: rawPostId,
|
|
17663
|
-
path:
|
|
17684
|
+
path: path21
|
|
17664
17685
|
}) {
|
|
17665
17686
|
const session = this.sessions.get(String(rawPostId));
|
|
17666
17687
|
if (!session || session.mode !== "bare") return;
|
|
17667
|
-
if (!session.openedFiles.includes(
|
|
17668
|
-
session.openedFiles.push(
|
|
17688
|
+
if (!session.openedFiles.includes(path21)) {
|
|
17689
|
+
session.openedFiles.push(path21);
|
|
17669
17690
|
}
|
|
17670
17691
|
}
|
|
17671
17692
|
bareCloseFile({
|
|
17672
17693
|
postId: rawPostId,
|
|
17673
|
-
path:
|
|
17694
|
+
path: path21
|
|
17674
17695
|
}) {
|
|
17675
17696
|
const session = this.sessions.get(String(rawPostId));
|
|
17676
17697
|
if (!session || session.mode !== "bare") return;
|
|
17677
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17678
|
-
delete session.buffers[
|
|
17698
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path21);
|
|
17699
|
+
delete session.buffers[path21];
|
|
17679
17700
|
}
|
|
17680
17701
|
bareBufferSet({
|
|
17681
17702
|
postId: rawPostId,
|
|
17682
|
-
path:
|
|
17703
|
+
path: path21,
|
|
17683
17704
|
content
|
|
17684
17705
|
}) {
|
|
17685
17706
|
const session = this.sessions.get(String(rawPostId));
|
|
17686
17707
|
if (!session || session.mode !== "bare") return;
|
|
17687
|
-
session.buffers[
|
|
17708
|
+
session.buffers[path21] = content;
|
|
17688
17709
|
}
|
|
17689
17710
|
async bareFsDelete({
|
|
17690
17711
|
postId,
|
|
17691
|
-
path:
|
|
17712
|
+
path: path21
|
|
17692
17713
|
}) {
|
|
17693
17714
|
const target = this.getBareTarget(postId);
|
|
17694
17715
|
const root = this.getBareRoot(postId);
|
|
17695
|
-
const abs = this.resolveBarePath(
|
|
17716
|
+
const abs = this.resolveBarePath(path21, root);
|
|
17696
17717
|
if (abs === root) {
|
|
17697
17718
|
throw new Error("Refusing to delete the repo root.");
|
|
17698
17719
|
}
|
|
@@ -17700,10 +17721,10 @@ var TerminalModeEngine = class {
|
|
|
17700
17721
|
}
|
|
17701
17722
|
async bareFsMkdir({
|
|
17702
17723
|
postId,
|
|
17703
|
-
path:
|
|
17724
|
+
path: path21
|
|
17704
17725
|
}) {
|
|
17705
17726
|
const target = this.getBareTarget(postId);
|
|
17706
|
-
const abs = this.resolveBarePath(
|
|
17727
|
+
const abs = this.resolveBarePath(path21, this.getBareRoot(postId));
|
|
17707
17728
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17708
17729
|
}
|
|
17709
17730
|
async bareReview({
|
|
@@ -18150,13 +18171,13 @@ var ClaudeLoginFlow = class {
|
|
|
18150
18171
|
async onClose() {
|
|
18151
18172
|
if (this.settled) return;
|
|
18152
18173
|
const errLine = this.output.slice(Math.max(this.submitMark, 0)).match(/(?:Login failed|OAuth error):[^\r\n]+/)?.[0];
|
|
18153
|
-
const
|
|
18174
|
+
const tail3 = errLine ? `${errLine.trim()} Try signing in again.` : this.output.trim().slice(-300);
|
|
18154
18175
|
if (this.urlRejecter) {
|
|
18155
|
-
this.fail(
|
|
18176
|
+
this.fail(tail3 || "Claude sign-in ended unexpectedly.");
|
|
18156
18177
|
return;
|
|
18157
18178
|
}
|
|
18158
18179
|
this.settled = true;
|
|
18159
|
-
await this.verifyAndComplete(
|
|
18180
|
+
await this.verifyAndComplete(tail3);
|
|
18160
18181
|
}
|
|
18161
18182
|
fail(message) {
|
|
18162
18183
|
if (this.settled) return;
|
|
@@ -18286,13 +18307,13 @@ var CodexLoginFlow = class {
|
|
|
18286
18307
|
}
|
|
18287
18308
|
async onClose() {
|
|
18288
18309
|
if (this.settled) return;
|
|
18289
|
-
const
|
|
18310
|
+
const tail3 = this.output.trim().slice(-300);
|
|
18290
18311
|
if (this.startRejecter) {
|
|
18291
|
-
this.fail(
|
|
18312
|
+
this.fail(tail3 || "Codex sign-in ended unexpectedly.");
|
|
18292
18313
|
return;
|
|
18293
18314
|
}
|
|
18294
18315
|
this.settled = true;
|
|
18295
|
-
await this.verifyAndComplete(
|
|
18316
|
+
await this.verifyAndComplete(tail3);
|
|
18296
18317
|
}
|
|
18297
18318
|
fail(message) {
|
|
18298
18319
|
if (this.settled) return;
|
|
@@ -18385,8 +18406,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
18385
18406
|
};
|
|
18386
18407
|
}
|
|
18387
18408
|
}
|
|
18388
|
-
async function githubApi(token,
|
|
18389
|
-
const res = await fetch(`${API_BASE}${
|
|
18409
|
+
async function githubApi(token, path21) {
|
|
18410
|
+
const res = await fetch(`${API_BASE}${path21}`, {
|
|
18390
18411
|
headers: {
|
|
18391
18412
|
Authorization: `Bearer ${token}`,
|
|
18392
18413
|
Accept: "application/vnd.github+json",
|
|
@@ -18395,7 +18416,7 @@ async function githubApi(token, path20) {
|
|
|
18395
18416
|
});
|
|
18396
18417
|
if (!res.ok) {
|
|
18397
18418
|
throw new Error(
|
|
18398
|
-
`GitHub ${
|
|
18419
|
+
`GitHub ${path21} failed (${res.status}): ${await res.text()}`
|
|
18399
18420
|
);
|
|
18400
18421
|
}
|
|
18401
18422
|
return await res.json();
|
|
@@ -18440,11 +18461,103 @@ async function listGithubBranches(token, fullName) {
|
|
|
18440
18461
|
}
|
|
18441
18462
|
|
|
18442
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
|
|
18443
18556
|
var import_path9 = __toESM(require("path"));
|
|
18444
18557
|
|
|
18445
18558
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
18446
18559
|
var import_child_process4 = require("child_process");
|
|
18447
|
-
var
|
|
18560
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
18448
18561
|
var import_promises6 = __toESM(require("fs/promises"));
|
|
18449
18562
|
var import_path8 = __toESM(require("path"));
|
|
18450
18563
|
var import_util6 = require("util");
|
|
@@ -18472,7 +18585,7 @@ async function assertBinaries() {
|
|
|
18472
18585
|
}
|
|
18473
18586
|
}
|
|
18474
18587
|
function deriveKey(password, salt) {
|
|
18475
|
-
return
|
|
18588
|
+
return import_crypto3.default.scryptSync(password, salt, 32, {
|
|
18476
18589
|
N: SCRYPT_N,
|
|
18477
18590
|
maxmem: 64 * 1024 * 1024
|
|
18478
18591
|
});
|
|
@@ -18501,9 +18614,9 @@ async function initSecrets(configDir, password) {
|
|
|
18501
18614
|
if (!identity || !recipient) {
|
|
18502
18615
|
throw new Error("age-keygen returned no usable keypair.");
|
|
18503
18616
|
}
|
|
18504
|
-
const salt =
|
|
18505
|
-
const iv =
|
|
18506
|
-
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(
|
|
18507
18620
|
"aes-256-gcm",
|
|
18508
18621
|
deriveKey(password, salt),
|
|
18509
18622
|
iv
|
|
@@ -18528,7 +18641,7 @@ async function initSecrets(configDir, password) {
|
|
|
18528
18641
|
return { success: true };
|
|
18529
18642
|
}
|
|
18530
18643
|
function decryptIdentity(box, password) {
|
|
18531
|
-
const decipher =
|
|
18644
|
+
const decipher = import_crypto3.default.createDecipheriv(
|
|
18532
18645
|
"aes-256-gcm",
|
|
18533
18646
|
deriveKey(password, Buffer.from(box.salt, "base64")),
|
|
18534
18647
|
Buffer.from(box.iv, "base64")
|
|
@@ -18614,6 +18727,18 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
18614
18727
|
await import_promises6.default.rm(tmp, { force: true });
|
|
18615
18728
|
}
|
|
18616
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
|
+
}
|
|
18617
18742
|
async function listSecretKeys(configDir, spaceDir, password) {
|
|
18618
18743
|
const identity = await identityFor(configDir, password);
|
|
18619
18744
|
return Object.keys(await readEnv(spaceDir, identity));
|
|
@@ -18641,31 +18766,10 @@ async function secretsForInjection(configDir, spaceDir, password) {
|
|
|
18641
18766
|
return readEnv(spaceDir, identity);
|
|
18642
18767
|
}
|
|
18643
18768
|
|
|
18644
|
-
// ../../shared/all/helpers/board-agent-core/
|
|
18645
|
-
var HUMAN_MARKER = "AWAITING_INPUT:";
|
|
18646
|
-
var SECRETS_MARKER = "NEED_SECRETS:";
|
|
18647
|
-
var COMPLETE_MARKER = "DEPLOY_COMPLETE:";
|
|
18648
|
-
var FAILED_MARKER = "DEPLOY_FAILED:";
|
|
18649
|
-
var MAX_MARKERLESS_TURNS = 3;
|
|
18769
|
+
// ../../shared/all/helpers/board-agent-core/secrets-run.ts
|
|
18650
18770
|
var COMMAND_IDLE_TIMEOUT_MS = 45 * 6e4;
|
|
18651
18771
|
var COMMAND_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
18652
18772
|
var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18653
|
-
function tailMarker(text) {
|
|
18654
|
-
const zone = text.slice(-2e3);
|
|
18655
|
-
let best = null;
|
|
18656
|
-
for (const marker of [
|
|
18657
|
-
SECRETS_MARKER,
|
|
18658
|
-
HUMAN_MARKER,
|
|
18659
|
-
COMPLETE_MARKER,
|
|
18660
|
-
FAILED_MARKER
|
|
18661
|
-
]) {
|
|
18662
|
-
const at = zone.lastIndexOf(marker);
|
|
18663
|
-
if (at !== -1 && (!best || at > best.at)) best = { marker, at };
|
|
18664
|
-
}
|
|
18665
|
-
if (!best) return null;
|
|
18666
|
-
const value2 = zone.slice(best.at + best.marker.length).trim();
|
|
18667
|
-
return value2 ? { marker: best.marker, value: value2 } : null;
|
|
18668
|
-
}
|
|
18669
18773
|
function containedCwd(cwd, spaceDir) {
|
|
18670
18774
|
if (!cwd) return null;
|
|
18671
18775
|
const resolved = import_path9.default.resolve(cwd);
|
|
@@ -18673,46 +18777,90 @@ function containedCwd(cwd, spaceDir) {
|
|
|
18673
18777
|
return resolved === root || resolved.startsWith(root + import_path9.default.sep) ? resolved : null;
|
|
18674
18778
|
}
|
|
18675
18779
|
async function authorizeAndRun(opts) {
|
|
18676
|
-
const { command,
|
|
18677
|
-
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
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
|
+
});
|
|
18682
18794
|
if ("denied" in auth) return { denied: true };
|
|
18683
18795
|
try {
|
|
18684
|
-
|
|
18796
|
+
store = await secretsForInjection(
|
|
18685
18797
|
opts.configDir,
|
|
18686
18798
|
opts.spaceDir,
|
|
18687
18799
|
auth.password
|
|
18688
18800
|
);
|
|
18689
18801
|
} catch (err) {
|
|
18690
|
-
|
|
18691
|
-
opts.status?.(`authorization failed: ${
|
|
18802
|
+
error = err instanceof Error ? err.message : String(err);
|
|
18803
|
+
opts.status?.(`authorization failed: ${error}`);
|
|
18692
18804
|
}
|
|
18693
18805
|
}
|
|
18694
|
-
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);
|
|
18695
18819
|
opts.status?.("authorized, running");
|
|
18696
|
-
|
|
18820
|
+
opts.sendLog?.({
|
|
18697
18821
|
type: "system",
|
|
18698
|
-
content:
|
|
18822
|
+
content: `Authorized. Running the command with ${names.length ? `${names.join(", ")} injected` : "no secrets injected"}...`
|
|
18699
18823
|
});
|
|
18700
18824
|
const res = await execLogin(command, {
|
|
18701
18825
|
cwd: opts.cwd,
|
|
18702
|
-
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...
|
|
18826
|
+
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...injected },
|
|
18703
18827
|
timeout: COMMAND_TIMEOUT_MS,
|
|
18704
18828
|
idleTimeout: COMMAND_IDLE_TIMEOUT_MS,
|
|
18705
18829
|
maxBuffer: 32 * 1024 * 1024
|
|
18706
18830
|
});
|
|
18707
18831
|
const output = redact(res.output);
|
|
18708
|
-
|
|
18832
|
+
opts.sendLog?.({ type: "tool", content: `$ ${command}
|
|
18709
18833
|
${tail(output)}` });
|
|
18710
18834
|
return { code: res.code, output };
|
|
18711
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
|
+
}
|
|
18712
18860
|
async function startDeployRun(opts) {
|
|
18713
18861
|
const { hooks } = opts;
|
|
18714
|
-
const workspace =
|
|
18715
|
-
const configDir =
|
|
18862
|
+
const workspace = import_path10.default.join(opts.spaceDir, "workspace");
|
|
18863
|
+
const configDir = import_path10.default.dirname(opts.spaceDir);
|
|
18716
18864
|
const status = await checkDeployStatus(opts);
|
|
18717
18865
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
18718
18866
|
throw new Error(
|
|
@@ -18747,26 +18895,8 @@ async function startDeployRun(opts) {
|
|
|
18747
18895
|
let sessionId = "";
|
|
18748
18896
|
let markerless = 0;
|
|
18749
18897
|
let todos = [];
|
|
18750
|
-
const
|
|
18751
|
-
|
|
18752
|
-
handlers: {
|
|
18753
|
-
run: ({ command, cwd, status: report }) => authorizeAndRun({
|
|
18754
|
-
configDir,
|
|
18755
|
-
spaceDir: opts.spaceDir,
|
|
18756
|
-
// A skill legitimately cds into subdirectories, so the CLI's own cwd
|
|
18757
|
-
// is the right place to run - but only while it is still inside the
|
|
18758
|
-
// space. Anywhere else is a confused caller, not a step, and the
|
|
18759
|
-
// workspace is the safe reading of what it meant.
|
|
18760
|
-
cwd: containedCwd(cwd, opts.spaceDir) ?? workspace,
|
|
18761
|
-
command,
|
|
18762
|
-
hooks,
|
|
18763
|
-
status: report
|
|
18764
|
-
}),
|
|
18765
|
-
// Names only. The contract on the default branch already declares them
|
|
18766
|
-
// in public, so this reveals nothing the repo does not.
|
|
18767
|
-
list: () => Promise.resolve(status.variables.map((v) => v.key))
|
|
18768
|
-
}
|
|
18769
|
-
});
|
|
18898
|
+
const declared = status.variables.map((v) => v.key);
|
|
18899
|
+
const deployToken = opts.gate.issueToken(DEPLOY_ORIGIN);
|
|
18770
18900
|
try {
|
|
18771
18901
|
for (; ; ) {
|
|
18772
18902
|
if (hooks.isCancelled()) throw new Error("Cancelled by user.");
|
|
@@ -18784,7 +18914,10 @@ async function startDeployRun(opts) {
|
|
|
18784
18914
|
includePartialMessages: true,
|
|
18785
18915
|
resumeSessionId: sessionId || void 0,
|
|
18786
18916
|
appendSystemPrompt: sessionId ? void 0 : contract,
|
|
18787
|
-
env:
|
|
18917
|
+
env: {
|
|
18918
|
+
...spaceEnv(opts.spaceDir),
|
|
18919
|
+
FACTIII_SECRETS_TOKEN: deployToken
|
|
18920
|
+
},
|
|
18788
18921
|
callbacks: {
|
|
18789
18922
|
initialTodos: todos,
|
|
18790
18923
|
onLog: (entry) => hooks.sendLog(entry),
|
|
@@ -18808,14 +18941,22 @@ async function startDeployRun(opts) {
|
|
|
18808
18941
|
spaceDir: opts.spaceDir,
|
|
18809
18942
|
cwd: workspace,
|
|
18810
18943
|
command,
|
|
18811
|
-
|
|
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
|
|
18812
18953
|
});
|
|
18813
18954
|
if ("denied" in res) {
|
|
18814
18955
|
prompt2 = "The owner declined to run that command. Adapt or stop and summarize where the release stands.";
|
|
18815
18956
|
continue;
|
|
18816
18957
|
}
|
|
18817
18958
|
markerless = 0;
|
|
18818
|
-
const clean =
|
|
18959
|
+
const clean = tail2(res.output);
|
|
18819
18960
|
prompt2 = `The authorized command finished (exit ${res.code}). Redacted output:
|
|
18820
18961
|
|
|
18821
18962
|
${clean}`;
|
|
@@ -18845,7 +18986,7 @@ ${clean}`;
|
|
|
18845
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.";
|
|
18846
18987
|
}
|
|
18847
18988
|
} finally {
|
|
18848
|
-
|
|
18989
|
+
opts.gate.revokeToken(deployToken);
|
|
18849
18990
|
}
|
|
18850
18991
|
}
|
|
18851
18992
|
|
|
@@ -18939,37 +19080,37 @@ async function getDriveRoot(accessToken) {
|
|
|
18939
19080
|
|
|
18940
19081
|
// ../../shared/all/helpers/board-agent-core/space-core.ts
|
|
18941
19082
|
var import_promises8 = require("fs/promises");
|
|
18942
|
-
var
|
|
19083
|
+
var import_path12 = __toESM(require("path"));
|
|
18943
19084
|
|
|
18944
19085
|
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
18945
|
-
var
|
|
19086
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
18946
19087
|
var import_fs3 = require("fs");
|
|
18947
19088
|
var import_promises7 = require("fs/promises");
|
|
18948
19089
|
var import_os3 = __toESM(require("os"));
|
|
18949
|
-
var
|
|
19090
|
+
var import_path11 = __toESM(require("path"));
|
|
18950
19091
|
var MARKETPLACE_NAME = "factiii-runner";
|
|
18951
19092
|
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
18952
19093
|
var SECRETS_BIN = "factiii-secrets";
|
|
18953
19094
|
function candidates() {
|
|
18954
19095
|
return [
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
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")
|
|
18958
19099
|
];
|
|
18959
19100
|
}
|
|
18960
19101
|
function pluginRoot() {
|
|
18961
19102
|
for (const dir of candidates()) {
|
|
18962
|
-
if ((0, import_fs3.existsSync)(
|
|
19103
|
+
if ((0, import_fs3.existsSync)(import_path11.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
18963
19104
|
return dir;
|
|
18964
19105
|
}
|
|
18965
19106
|
}
|
|
18966
19107
|
return null;
|
|
18967
19108
|
}
|
|
18968
19109
|
function renderedPluginRoot() {
|
|
18969
|
-
return
|
|
19110
|
+
return import_path11.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
18970
19111
|
}
|
|
18971
19112
|
async function runnerVersion(source) {
|
|
18972
|
-
const pkg =
|
|
19113
|
+
const pkg = import_path11.default.join(source, "..", "package.json");
|
|
18973
19114
|
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
18974
19115
|
if (!raw) return FALLBACK_VERSION;
|
|
18975
19116
|
try {
|
|
@@ -18984,16 +19125,16 @@ async function treeFiles(dir) {
|
|
|
18984
19125
|
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
18985
19126
|
const files = [];
|
|
18986
19127
|
for (const entry of entries) {
|
|
18987
|
-
const full =
|
|
19128
|
+
const full = import_path11.default.join(dir, entry.name);
|
|
18988
19129
|
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
18989
19130
|
else if (entry.isFile()) files.push(full);
|
|
18990
19131
|
}
|
|
18991
19132
|
return files.sort();
|
|
18992
19133
|
}
|
|
18993
19134
|
async function fingerprint(source, url2, version = "") {
|
|
18994
|
-
const hash =
|
|
19135
|
+
const hash = import_crypto4.default.createHash("sha256").update(url2).update(version);
|
|
18995
19136
|
for (const file of await treeFiles(source)) {
|
|
18996
|
-
hash.update(
|
|
19137
|
+
hash.update(import_path11.default.relative(source, file));
|
|
18997
19138
|
hash.update(await (0, import_promises7.readFile)(file));
|
|
18998
19139
|
}
|
|
18999
19140
|
return hash.digest("hex");
|
|
@@ -19019,10 +19160,10 @@ async function renderPluginImpl(boardMcpUrl) {
|
|
|
19019
19160
|
const want = await fingerprint(source, boardMcpUrl, version);
|
|
19020
19161
|
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
19021
19162
|
if (have === want) return dest;
|
|
19022
|
-
await (0, import_promises7.mkdir)(
|
|
19163
|
+
await (0, import_promises7.mkdir)(import_path11.default.dirname(dest), { recursive: true });
|
|
19023
19164
|
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
19024
19165
|
await (0, import_promises7.writeFile)(
|
|
19025
|
-
|
|
19166
|
+
import_path11.default.join(dest, "factiii", ".mcp.json"),
|
|
19026
19167
|
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
19027
19168
|
`
|
|
19028
19169
|
);
|
|
@@ -19031,7 +19172,7 @@ async function renderPluginImpl(boardMcpUrl) {
|
|
|
19031
19172
|
return dest;
|
|
19032
19173
|
}
|
|
19033
19174
|
async function stampVersion(dest, version) {
|
|
19034
|
-
const manifest =
|
|
19175
|
+
const manifest = import_path11.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
19035
19176
|
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
19036
19177
|
if (!raw) return;
|
|
19037
19178
|
const parsed = JSON.parse(raw);
|
|
@@ -19257,6 +19398,17 @@ var SpaceCore = class {
|
|
|
19257
19398
|
// Positive-only cache per provider: true sticks until sign-out, false is
|
|
19258
19399
|
// re-probed (an in-terminal login can flip it).
|
|
19259
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 = "";
|
|
19260
19412
|
// ── Persistent per-space workspace: clone, worktrees, redis state ──
|
|
19261
19413
|
// Just a directory, so a restart needs no reattach.
|
|
19262
19414
|
this.execTarget = null;
|
|
@@ -19271,10 +19423,101 @@ var SpaceCore = class {
|
|
|
19271
19423
|
this.mcp = options.mcp;
|
|
19272
19424
|
this.requestSudo = options.requestSudo;
|
|
19273
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
|
+
});
|
|
19274
19440
|
}
|
|
19275
19441
|
setLoggedIn(provider, value2) {
|
|
19276
19442
|
this.loggedIn[provider] = value2;
|
|
19277
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
|
+
}
|
|
19278
19521
|
readConfig() {
|
|
19279
19522
|
return this.configProvider.readConfig(this.spaceSlug);
|
|
19280
19523
|
}
|
|
@@ -19286,6 +19529,7 @@ var SpaceCore = class {
|
|
|
19286
19529
|
for (const lane of this.mcpLanes()) env[lane.tokenEnv] = lane.mcp.token;
|
|
19287
19530
|
const board = this.boardMcp();
|
|
19288
19531
|
if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
|
|
19532
|
+
env.FACTIII_SECRETS_TOKEN = this.secretsToken();
|
|
19289
19533
|
return env;
|
|
19290
19534
|
});
|
|
19291
19535
|
return this.execTarget;
|
|
@@ -19461,6 +19705,7 @@ var SpaceCore = class {
|
|
|
19461
19705
|
`mkdir -p ${paths.root} ${paths.worktrees} ${paths.state} ${paths.backups} ${paths.builds} ${paths.bin}`
|
|
19462
19706
|
);
|
|
19463
19707
|
await allocateSpacePorts(this.spaceDir());
|
|
19708
|
+
await this.ensureSecretsBridge().catch(() => void 0);
|
|
19464
19709
|
await ensureRedis(target);
|
|
19465
19710
|
await Promise.all([
|
|
19466
19711
|
this.provisionGitLane(status),
|
|
@@ -19557,14 +19802,14 @@ var SpaceCore = class {
|
|
|
19557
19802
|
const source = pluginRoot();
|
|
19558
19803
|
if (!source) return;
|
|
19559
19804
|
const { bin } = this.target().paths;
|
|
19560
|
-
const from =
|
|
19561
|
-
const to =
|
|
19805
|
+
const from = import_path12.default.join(source, "factiii", "bin", SECRETS_BIN);
|
|
19806
|
+
const to = import_path12.default.join(bin, SECRETS_BIN);
|
|
19562
19807
|
try {
|
|
19563
19808
|
const [src, staged] = await Promise.all([
|
|
19564
|
-
(0, import_promises8.
|
|
19565
|
-
(0, import_promises8.
|
|
19809
|
+
(0, import_promises8.readFile)(from),
|
|
19810
|
+
(0, import_promises8.readFile)(to).catch(() => null)
|
|
19566
19811
|
]);
|
|
19567
|
-
if (staged?.
|
|
19812
|
+
if (staged?.equals(src)) return;
|
|
19568
19813
|
await (0, import_promises8.mkdir)(bin, { recursive: true });
|
|
19569
19814
|
await (0, import_promises8.copyFile)(from, to);
|
|
19570
19815
|
await (0, import_promises8.chmod)(to, 493);
|
|
@@ -19623,10 +19868,12 @@ var SpaceCore = class {
|
|
|
19623
19868
|
} catch {
|
|
19624
19869
|
return false;
|
|
19625
19870
|
}
|
|
19871
|
+
await this.ensureSecretsBridge().catch(() => void 0);
|
|
19626
19872
|
await ensureRedis(target).catch(() => {
|
|
19627
19873
|
});
|
|
19628
19874
|
await pruneWorktrees(target).catch(() => {
|
|
19629
19875
|
});
|
|
19876
|
+
await Promise.all([this.provisionPluginLane(), this.provisionBinLane()]);
|
|
19630
19877
|
await this.provisionMcpLane();
|
|
19631
19878
|
this.ready = true;
|
|
19632
19879
|
return true;
|
|
@@ -19651,6 +19898,9 @@ var SpaceCore = class {
|
|
|
19651
19898
|
destroy() {
|
|
19652
19899
|
this.ready = false;
|
|
19653
19900
|
this.setupPromise = null;
|
|
19901
|
+
this.gate.destroy();
|
|
19902
|
+
void this.secretsBridge?.close();
|
|
19903
|
+
this.secretsBridge = null;
|
|
19654
19904
|
this.activity.destroy();
|
|
19655
19905
|
}
|
|
19656
19906
|
};
|
|
@@ -19701,6 +19951,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19701
19951
|
logs: [],
|
|
19702
19952
|
question: "",
|
|
19703
19953
|
pendingCommand: "",
|
|
19954
|
+
pendingKeys: [],
|
|
19704
19955
|
error: "",
|
|
19705
19956
|
summary: "",
|
|
19706
19957
|
startedAt: 0,
|
|
@@ -19722,6 +19973,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19722
19973
|
this.emitter = options.emitter;
|
|
19723
19974
|
this.claude = new ClaudeModeEngine(this.core, this.emitter);
|
|
19724
19975
|
this.terminal = new TerminalModeEngine(this.core, this.emitter);
|
|
19976
|
+
this.gate.subscribe(() => this.projectGateOntoRun());
|
|
19725
19977
|
}
|
|
19726
19978
|
/** Everything the runner does for this space that isn't a card session, so
|
|
19727
19979
|
* the Running jobs panel can report the whole machine. Owned by the core,
|
|
@@ -19729,6 +19981,39 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19729
19981
|
get activity() {
|
|
19730
19982
|
return this.core.activity;
|
|
19731
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
|
+
}
|
|
19732
20017
|
/** How this host protects stored credentials. Read-only: the daemon answers
|
|
19733
20018
|
* unlock and password changes itself (SECURE_ACTIONS in daemon.ts). */
|
|
19734
20019
|
secureStatus() {
|
|
@@ -19939,7 +20224,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19939
20224
|
* sit on `awaiting-input` for hours, which is exactly the state worth
|
|
19940
20225
|
* seeing without opening the modal. */
|
|
19941
20226
|
syncRunActivity() {
|
|
19942
|
-
const { phase, question, pendingCommand, error } = this.runState;
|
|
20227
|
+
const { phase, question, pendingCommand, pendingKeys, error } = this.runState;
|
|
19943
20228
|
switch (phase) {
|
|
19944
20229
|
case "running":
|
|
19945
20230
|
return this.activity.set("deploy-run", "working", "Deploying");
|
|
@@ -19955,7 +20240,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19955
20240
|
"deploy-run",
|
|
19956
20241
|
"awaiting-input",
|
|
19957
20242
|
"Deploy needs authorization",
|
|
19958
|
-
pendingCommand
|
|
20243
|
+
pendingKeys.length ? `${pendingCommand} (${pendingKeys.join(", ")})` : pendingCommand
|
|
19959
20244
|
);
|
|
19960
20245
|
case "failed":
|
|
19961
20246
|
return this.activity.set("deploy-run", "error", "Deploy failed", error);
|
|
@@ -19977,6 +20262,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19977
20262
|
logs: [],
|
|
19978
20263
|
question: "",
|
|
19979
20264
|
pendingCommand: "",
|
|
20265
|
+
pendingKeys: [],
|
|
19980
20266
|
error: "",
|
|
19981
20267
|
summary: "",
|
|
19982
20268
|
startedAt: Date.now(),
|
|
@@ -20001,6 +20287,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20001
20287
|
}
|
|
20002
20288
|
sendLog(entry);
|
|
20003
20289
|
};
|
|
20290
|
+
this.core.deployLog = bufferLog;
|
|
20004
20291
|
void startDeployRun({
|
|
20005
20292
|
spaceDir: opts.spaceDir,
|
|
20006
20293
|
repoUrl: opts.repoUrl,
|
|
@@ -20008,6 +20295,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20008
20295
|
githubToken: opts.githubToken,
|
|
20009
20296
|
provider: opts.provider,
|
|
20010
20297
|
releaseBranch,
|
|
20298
|
+
gate: this.gate,
|
|
20011
20299
|
hooks: {
|
|
20012
20300
|
sendLog: bufferLog,
|
|
20013
20301
|
onTodos: (todos) => this.setRun({ todos }),
|
|
@@ -20015,19 +20303,13 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20015
20303
|
this.runResolvers.human = resolve2;
|
|
20016
20304
|
this.setRun({ phase: "awaiting-input", question });
|
|
20017
20305
|
}),
|
|
20018
|
-
requestAuth: (command, errorNote) => new Promise((resolve2) => {
|
|
20019
|
-
this.runResolvers.auth = resolve2;
|
|
20020
|
-
this.setRun({
|
|
20021
|
-
phase: "awaiting-auth",
|
|
20022
|
-
pendingCommand: command,
|
|
20023
|
-
error: errorNote ?? ""
|
|
20024
|
-
});
|
|
20025
|
-
}),
|
|
20026
20306
|
isCancelled: () => this.runCancelled,
|
|
20027
20307
|
registerProc: (proc) => {
|
|
20028
20308
|
this.runProc = proc;
|
|
20029
20309
|
}
|
|
20030
20310
|
}
|
|
20311
|
+
}).finally(() => {
|
|
20312
|
+
this.core.deployLog = null;
|
|
20031
20313
|
}).then(({ shipped, summary }) => {
|
|
20032
20314
|
if (shipped) {
|
|
20033
20315
|
this.setRun({ phase: "success", summary, finishedAt: Date.now() });
|
|
@@ -20060,31 +20342,28 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20060
20342
|
}
|
|
20061
20343
|
// The password arrives RUNNER-DIRECT here and only feeds decryption; it
|
|
20062
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.
|
|
20063
20349
|
deployRunAuthorize(payload) {
|
|
20064
|
-
|
|
20065
|
-
if (!resolve2 || this.runState.phase !== "awaiting-auth") {
|
|
20066
|
-
throw new Error("No command is awaiting authorization.");
|
|
20067
|
-
}
|
|
20068
|
-
this.runResolvers.auth = void 0;
|
|
20069
|
-
this.setRun({ phase: "running", pendingCommand: "", error: "" });
|
|
20070
|
-
resolve2({ password: payload.password });
|
|
20071
|
-
return { success: true };
|
|
20350
|
+
return this.answerPendingAuth({ password: payload.password });
|
|
20072
20351
|
}
|
|
20073
20352
|
deployRunDeny() {
|
|
20074
|
-
|
|
20075
|
-
|
|
20076
|
-
|
|
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.");
|
|
20077
20359
|
}
|
|
20078
|
-
this.
|
|
20079
|
-
this.setRun({ phase: "running", pendingCommand: "" });
|
|
20080
|
-
resolve2({ denied: true });
|
|
20081
|
-
return { success: true };
|
|
20360
|
+
return this.gate.answer(pending.id, result);
|
|
20082
20361
|
}
|
|
20083
20362
|
deployRunCancel() {
|
|
20084
20363
|
this.runCancelled = true;
|
|
20085
20364
|
if (this.runProc) killTree(this.runProc, "SIGTERM");
|
|
20086
20365
|
this.runResolvers.human?.("cancel");
|
|
20087
|
-
this.
|
|
20366
|
+
if (this.gate.pending()?.origin === DEPLOY_ORIGIN) this.gate.cancel();
|
|
20088
20367
|
this.runResolvers = {};
|
|
20089
20368
|
return { success: true };
|
|
20090
20369
|
}
|
|
@@ -20101,6 +20380,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20101
20380
|
logs: [],
|
|
20102
20381
|
question: "",
|
|
20103
20382
|
pendingCommand: "",
|
|
20383
|
+
pendingKeys: [],
|
|
20104
20384
|
error: "",
|
|
20105
20385
|
summary: "",
|
|
20106
20386
|
startedAt: 0,
|
|
@@ -20248,7 +20528,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20248
20528
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
20249
20529
|
secretsDirs() {
|
|
20250
20530
|
const spaceDir = this.core.spaceDir();
|
|
20251
|
-
return { configDir: (0,
|
|
20531
|
+
return { configDir: (0, import_path13.dirname)(spaceDir), spaceDir };
|
|
20252
20532
|
}
|
|
20253
20533
|
async deploySecretsStatus() {
|
|
20254
20534
|
return {
|
|
@@ -21132,6 +21412,12 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21132
21412
|
return this.deployRunDismiss();
|
|
21133
21413
|
case "deployApproveSkill":
|
|
21134
21414
|
return await this.deployApproveSkill();
|
|
21415
|
+
case "secretsAuthState":
|
|
21416
|
+
return this.secretsAuthState();
|
|
21417
|
+
case "secretsAuthAnswer":
|
|
21418
|
+
return this.secretsAuthAnswer(
|
|
21419
|
+
payload
|
|
21420
|
+
);
|
|
21135
21421
|
case "deploySecretsStatus":
|
|
21136
21422
|
return await this.deploySecretsStatus();
|
|
21137
21423
|
case "deploySecretsInit":
|
|
@@ -21312,11 +21598,11 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21312
21598
|
|
|
21313
21599
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
21314
21600
|
var import_child_process5 = require("child_process");
|
|
21315
|
-
var
|
|
21601
|
+
var import_crypto5 = require("crypto");
|
|
21316
21602
|
var import_fs4 = require("fs");
|
|
21317
21603
|
var import_promises9 = __toESM(require("fs/promises"));
|
|
21318
21604
|
var import_net3 = require("net");
|
|
21319
|
-
var
|
|
21605
|
+
var import_path14 = require("path");
|
|
21320
21606
|
var import_util7 = require("util");
|
|
21321
21607
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
21322
21608
|
var IndexService = class {
|
|
@@ -21330,7 +21616,7 @@ var IndexService = class {
|
|
|
21330
21616
|
/** Per-space bearer for MCP + file, derived from the control token (the
|
|
21331
21617
|
* service computes the same). */
|
|
21332
21618
|
bearerToken(spaceSlug) {
|
|
21333
|
-
return (0,
|
|
21619
|
+
return (0, import_crypto5.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
|
|
21334
21620
|
}
|
|
21335
21621
|
/** MCP bridge for a space. Null until the service is up (which it kicks
|
|
21336
21622
|
* off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
|
|
@@ -21375,7 +21661,7 @@ var IndexService = class {
|
|
|
21375
21661
|
const log = await this.openLog();
|
|
21376
21662
|
this.proc = (0, import_child_process5.spawn)(
|
|
21377
21663
|
process.execPath,
|
|
21378
|
-
[(0,
|
|
21664
|
+
[(0, import_path14.join)(this.opts.indexDir, "index.js")],
|
|
21379
21665
|
{
|
|
21380
21666
|
cwd: this.opts.indexDir,
|
|
21381
21667
|
env: {
|
|
@@ -21404,7 +21690,7 @@ var IndexService = class {
|
|
|
21404
21690
|
async openLog() {
|
|
21405
21691
|
try {
|
|
21406
21692
|
await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
21407
|
-
return (0, import_fs4.openSync)((0,
|
|
21693
|
+
return (0, import_fs4.openSync)((0, import_path14.join)(this.opts.dataDir, "index.log"), "a");
|
|
21408
21694
|
} catch {
|
|
21409
21695
|
return null;
|
|
21410
21696
|
}
|
|
@@ -21413,10 +21699,10 @@ var IndexService = class {
|
|
|
21413
21699
|
* platform needing node-gyp fails here rather than at query time. */
|
|
21414
21700
|
async installDeps() {
|
|
21415
21701
|
const dir = this.opts.indexDir;
|
|
21416
|
-
if ((0, import_fs4.existsSync)((0,
|
|
21702
|
+
if ((0, import_fs4.existsSync)((0, import_path14.join)(dir, "node_modules"))) return;
|
|
21417
21703
|
await import_promises9.default.copyFile(
|
|
21418
|
-
(0,
|
|
21419
|
-
(0,
|
|
21704
|
+
(0, import_path14.join)(dir, "image-package.json"),
|
|
21705
|
+
(0, import_path14.join)(dir, "package.json")
|
|
21420
21706
|
);
|
|
21421
21707
|
await execFileAsync4(
|
|
21422
21708
|
"npm",
|
|
@@ -21428,7 +21714,7 @@ var IndexService = class {
|
|
|
21428
21714
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
21429
21715
|
* registration pointing at a dead one. */
|
|
21430
21716
|
async stablePort() {
|
|
21431
|
-
const file = (0,
|
|
21717
|
+
const file = (0, import_path14.join)(this.opts.dataDir, ".port");
|
|
21432
21718
|
const saved = Number(await import_promises9.default.readFile(file, "utf-8").catch(() => ""));
|
|
21433
21719
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
21434
21720
|
return saved;
|
|
@@ -21605,22 +21891,22 @@ async function pairWithBrowser(serverUrl) {
|
|
|
21605
21891
|
// src/config.ts
|
|
21606
21892
|
var import_fs8 = __toESM(require("fs"));
|
|
21607
21893
|
var import_os6 = __toESM(require("os"));
|
|
21608
|
-
var
|
|
21894
|
+
var import_path19 = __toESM(require("path"));
|
|
21609
21895
|
|
|
21610
21896
|
// src/secureFile.ts
|
|
21611
|
-
var
|
|
21897
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
21612
21898
|
var import_fs7 = __toESM(require("fs"));
|
|
21613
|
-
var
|
|
21899
|
+
var import_path18 = __toESM(require("path"));
|
|
21614
21900
|
|
|
21615
21901
|
// src/keychain.ts
|
|
21616
21902
|
var import_child_process7 = require("child_process");
|
|
21617
|
-
var
|
|
21903
|
+
var import_crypto6 = __toESM(require("crypto"));
|
|
21618
21904
|
var import_fs5 = __toESM(require("fs"));
|
|
21619
21905
|
var import_os4 = __toESM(require("os"));
|
|
21620
|
-
var
|
|
21906
|
+
var import_path15 = __toESM(require("path"));
|
|
21621
21907
|
var SERVICE = "factiii-runner";
|
|
21622
21908
|
var ACCOUNT = "config-encryption-key";
|
|
21623
|
-
var DPAPI_KEY_FILE =
|
|
21909
|
+
var DPAPI_KEY_FILE = import_path15.default.join(
|
|
21624
21910
|
import_os4.default.homedir(),
|
|
21625
21911
|
".factiii-runner",
|
|
21626
21912
|
"config-key.dpapi"
|
|
@@ -21726,7 +22012,7 @@ function winWrite(key) {
|
|
|
21726
22012
|
"-Command",
|
|
21727
22013
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
21728
22014
|
]);
|
|
21729
|
-
import_fs5.default.mkdirSync(
|
|
22015
|
+
import_fs5.default.mkdirSync(import_path15.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
21730
22016
|
import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
21731
22017
|
`, { mode: 384 });
|
|
21732
22018
|
}
|
|
@@ -21866,7 +22152,7 @@ function resolve() {
|
|
|
21866
22152
|
cached = { key: null, status: diagnose() };
|
|
21867
22153
|
return cached;
|
|
21868
22154
|
}
|
|
21869
|
-
const fresh =
|
|
22155
|
+
const fresh = import_crypto6.default.randomBytes(32);
|
|
21870
22156
|
writeKey(fresh);
|
|
21871
22157
|
const verify = readKey();
|
|
21872
22158
|
if (verify.state !== "found" || !verify.key.equals(fresh)) {
|
|
@@ -21888,23 +22174,23 @@ function keychainStatus() {
|
|
|
21888
22174
|
}
|
|
21889
22175
|
|
|
21890
22176
|
// src/secureStore.ts
|
|
21891
|
-
var
|
|
22177
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
21892
22178
|
var import_fs6 = __toESM(require("fs"));
|
|
21893
|
-
var
|
|
22179
|
+
var import_path17 = __toESM(require("path"));
|
|
21894
22180
|
|
|
21895
22181
|
// src/paths.ts
|
|
21896
22182
|
var import_os5 = __toESM(require("os"));
|
|
21897
|
-
var
|
|
21898
|
-
var CONFIG_DIR =
|
|
22183
|
+
var import_path16 = __toESM(require("path"));
|
|
22184
|
+
var CONFIG_DIR = import_path16.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
21899
22185
|
function safeSlug(spaceSlug) {
|
|
21900
22186
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21901
22187
|
}
|
|
21902
22188
|
function spaceDirPath(spaceSlug) {
|
|
21903
|
-
return
|
|
22189
|
+
return import_path16.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
21904
22190
|
}
|
|
21905
22191
|
|
|
21906
22192
|
// src/secureStore.ts
|
|
21907
|
-
var VAULT_KEY_FILE =
|
|
22193
|
+
var VAULT_KEY_FILE = import_path17.default.join(CONFIG_DIR, "vault-key.json");
|
|
21908
22194
|
var SCRYPT_N2 = 1 << 15;
|
|
21909
22195
|
var MIN_PASSWORD_LENGTH = 8;
|
|
21910
22196
|
var SecureStoreError = class extends Error {
|
|
@@ -21931,7 +22217,7 @@ function requireKeychainKey() {
|
|
|
21931
22217
|
);
|
|
21932
22218
|
}
|
|
21933
22219
|
function passwordKey(password, salt) {
|
|
21934
|
-
return
|
|
22220
|
+
return import_crypto7.default.scryptSync(password, salt, 32, {
|
|
21935
22221
|
N: SCRYPT_N2,
|
|
21936
22222
|
maxmem: 64 * 1024 * 1024
|
|
21937
22223
|
});
|
|
@@ -21939,7 +22225,7 @@ function passwordKey(password, salt) {
|
|
|
21939
22225
|
function wrapKey(machineKey, pwKey) {
|
|
21940
22226
|
const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
|
|
21941
22227
|
return Buffer.from(
|
|
21942
|
-
|
|
22228
|
+
import_crypto7.default.hkdfSync(
|
|
21943
22229
|
"sha256",
|
|
21944
22230
|
ikm,
|
|
21945
22231
|
Buffer.alloc(0),
|
|
@@ -21949,8 +22235,8 @@ function wrapKey(machineKey, pwKey) {
|
|
|
21949
22235
|
);
|
|
21950
22236
|
}
|
|
21951
22237
|
function seal(plaintext, key) {
|
|
21952
|
-
const iv =
|
|
21953
|
-
const cipher =
|
|
22238
|
+
const iv = import_crypto7.default.randomBytes(12);
|
|
22239
|
+
const cipher = import_crypto7.default.createCipheriv("aes-256-gcm", key, iv);
|
|
21954
22240
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
21955
22241
|
return {
|
|
21956
22242
|
iv: iv.toString("base64"),
|
|
@@ -21960,7 +22246,7 @@ function seal(plaintext, key) {
|
|
|
21960
22246
|
}
|
|
21961
22247
|
function unseal(box, key) {
|
|
21962
22248
|
try {
|
|
21963
|
-
const decipher =
|
|
22249
|
+
const decipher = import_crypto7.default.createDecipheriv(
|
|
21964
22250
|
"aes-256-gcm",
|
|
21965
22251
|
key,
|
|
21966
22252
|
Buffer.from(box.iv, "base64")
|
|
@@ -21993,7 +22279,7 @@ function writeVaultFile(file) {
|
|
|
21993
22279
|
}
|
|
21994
22280
|
var unlockedDek = null;
|
|
21995
22281
|
function createVault(machineKey) {
|
|
21996
|
-
const dek =
|
|
22282
|
+
const dek = import_crypto7.default.randomBytes(32);
|
|
21997
22283
|
writeVaultFile({
|
|
21998
22284
|
v: 1,
|
|
21999
22285
|
passwordEnabled: false,
|
|
@@ -22088,7 +22374,7 @@ function enablePassword(password) {
|
|
|
22088
22374
|
);
|
|
22089
22375
|
}
|
|
22090
22376
|
const dek = dataKey();
|
|
22091
|
-
const salt =
|
|
22377
|
+
const salt = import_crypto7.default.randomBytes(16);
|
|
22092
22378
|
writeVaultFile({
|
|
22093
22379
|
v: 1,
|
|
22094
22380
|
passwordEnabled: true,
|
|
@@ -22116,7 +22402,7 @@ function changePassword(current, next) {
|
|
|
22116
22402
|
"The current password is not correct."
|
|
22117
22403
|
);
|
|
22118
22404
|
}
|
|
22119
|
-
const salt =
|
|
22405
|
+
const salt = import_crypto7.default.randomBytes(16);
|
|
22120
22406
|
writeVaultFile({
|
|
22121
22407
|
v: 1,
|
|
22122
22408
|
passwordEnabled: true,
|
|
@@ -22193,8 +22479,8 @@ function storeStatus() {
|
|
|
22193
22479
|
// src/secureFile.ts
|
|
22194
22480
|
var MAGIC = "FACTIII1";
|
|
22195
22481
|
function encrypt(plain, key, version) {
|
|
22196
|
-
const iv =
|
|
22197
|
-
const cipher =
|
|
22482
|
+
const iv = import_crypto8.default.randomBytes(12);
|
|
22483
|
+
const cipher = import_crypto8.default.createCipheriv("aes-256-gcm", key, iv);
|
|
22198
22484
|
const data = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
22199
22485
|
const envelope = {
|
|
22200
22486
|
v: version,
|
|
@@ -22205,7 +22491,7 @@ function encrypt(plain, key, version) {
|
|
|
22205
22491
|
return `${MAGIC}${JSON.stringify(envelope)}`;
|
|
22206
22492
|
}
|
|
22207
22493
|
function decrypt(envelope, key) {
|
|
22208
|
-
const decipher =
|
|
22494
|
+
const decipher = import_crypto8.default.createDecipheriv(
|
|
22209
22495
|
"aes-256-gcm",
|
|
22210
22496
|
key,
|
|
22211
22497
|
Buffer.from(envelope.iv, "base64")
|
|
@@ -22226,7 +22512,7 @@ function parseEnvelope(raw) {
|
|
|
22226
22512
|
}
|
|
22227
22513
|
}
|
|
22228
22514
|
function atomicWrite(filePath, body) {
|
|
22229
|
-
import_fs7.default.mkdirSync(
|
|
22515
|
+
import_fs7.default.mkdirSync(import_path18.default.dirname(filePath), { recursive: true });
|
|
22230
22516
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
22231
22517
|
import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
|
|
22232
22518
|
import_fs7.default.renameSync(tmp, filePath);
|
|
@@ -22341,8 +22627,8 @@ function writeBootJson(filePath, value2) {
|
|
|
22341
22627
|
}
|
|
22342
22628
|
|
|
22343
22629
|
// src/config.ts
|
|
22344
|
-
var CONFIG_DIR2 =
|
|
22345
|
-
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");
|
|
22346
22632
|
function readRunnerConfig() {
|
|
22347
22633
|
return readBootJson(CONFIG_FILE);
|
|
22348
22634
|
}
|
|
@@ -22511,10 +22797,10 @@ function makeChunkReassembler() {
|
|
|
22511
22797
|
}
|
|
22512
22798
|
|
|
22513
22799
|
// src/daemon.ts
|
|
22514
|
-
var
|
|
22800
|
+
var import_crypto9 = require("crypto");
|
|
22515
22801
|
var import_fs10 = __toESM(require("fs"));
|
|
22516
22802
|
var import_node_datachannel = require("node-datachannel");
|
|
22517
|
-
var
|
|
22803
|
+
var import_path21 = __toESM(require("path"));
|
|
22518
22804
|
|
|
22519
22805
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
22520
22806
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23694,12 +23980,12 @@ function parse2(str) {
|
|
|
23694
23980
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23695
23981
|
return uri;
|
|
23696
23982
|
}
|
|
23697
|
-
function pathNames(obj,
|
|
23698
|
-
const regx = /\/{2,9}/g, names =
|
|
23699
|
-
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) {
|
|
23700
23986
|
names.splice(0, 1);
|
|
23701
23987
|
}
|
|
23702
|
-
if (
|
|
23988
|
+
if (path21.slice(-1) == "/") {
|
|
23703
23989
|
names.splice(names.length - 1, 1);
|
|
23704
23990
|
}
|
|
23705
23991
|
return names;
|
|
@@ -24318,7 +24604,7 @@ var protocol2 = Socket.protocol;
|
|
|
24318
24604
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
24319
24605
|
var import_debug7 = __toESM(require_src(), 1);
|
|
24320
24606
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
24321
|
-
function url(uri,
|
|
24607
|
+
function url(uri, path21 = "", loc) {
|
|
24322
24608
|
let obj = uri;
|
|
24323
24609
|
loc = loc || typeof location !== "undefined" && location;
|
|
24324
24610
|
if (null == uri)
|
|
@@ -24352,7 +24638,7 @@ function url(uri, path20 = "", loc) {
|
|
|
24352
24638
|
obj.path = obj.path || "/";
|
|
24353
24639
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
24354
24640
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
24355
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
24641
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path21;
|
|
24356
24642
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
24357
24643
|
return obj;
|
|
24358
24644
|
}
|
|
@@ -25986,8 +26272,8 @@ function lookup(uri, opts) {
|
|
|
25986
26272
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
25987
26273
|
const source = parsed.source;
|
|
25988
26274
|
const id = parsed.id;
|
|
25989
|
-
const
|
|
25990
|
-
const sameNamespace = cache[id] &&
|
|
26275
|
+
const path21 = parsed.path;
|
|
26276
|
+
const sameNamespace = cache[id] && path21 in cache[id]["nsps"];
|
|
25991
26277
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
25992
26278
|
let io;
|
|
25993
26279
|
if (newConnection) {
|
|
@@ -26029,8 +26315,8 @@ function cardContextPath(target, postId) {
|
|
|
26029
26315
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
26030
26316
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
26031
26317
|
}
|
|
26032
|
-
function cardContextPrompt(
|
|
26033
|
-
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.`;
|
|
26034
26320
|
}
|
|
26035
26321
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
26036
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.";
|
|
@@ -26195,10 +26481,10 @@ var TerminalManager = class {
|
|
|
26195
26481
|
|
|
26196
26482
|
// src/agent-adapter.ts
|
|
26197
26483
|
var import_fs9 = __toESM(require("fs"));
|
|
26198
|
-
var
|
|
26199
|
-
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");
|
|
26200
26486
|
function connectionPath(spaceSlug) {
|
|
26201
|
-
return
|
|
26487
|
+
return import_path20.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
26202
26488
|
}
|
|
26203
26489
|
function writeJson(filePath, value2) {
|
|
26204
26490
|
writeSecureJson(filePath, value2);
|
|
@@ -26232,7 +26518,7 @@ function migrateLegacyConfigs() {
|
|
|
26232
26518
|
for (const name of legacy) {
|
|
26233
26519
|
const slug = name.slice("space-".length, -".json".length);
|
|
26234
26520
|
const parsed = readSecureJson(
|
|
26235
|
-
|
|
26521
|
+
import_path20.default.join(CONFIG_DIR, name)
|
|
26236
26522
|
);
|
|
26237
26523
|
if (!parsed) continue;
|
|
26238
26524
|
if (!file.defaultSlug) {
|
|
@@ -26257,7 +26543,7 @@ function encryptStrayPlaintextConfigs() {
|
|
|
26257
26543
|
return;
|
|
26258
26544
|
}
|
|
26259
26545
|
for (const name of names) {
|
|
26260
|
-
const filePath =
|
|
26546
|
+
const filePath = import_path20.default.join(CONFIG_DIR, name);
|
|
26261
26547
|
try {
|
|
26262
26548
|
const raw = import_fs9.default.readFileSync(filePath, "utf-8");
|
|
26263
26549
|
if (!raw.startsWith("{")) continue;
|
|
@@ -26386,7 +26672,7 @@ var localConfigProvider = {
|
|
|
26386
26672
|
"claude",
|
|
26387
26673
|
"codex"
|
|
26388
26674
|
]) {
|
|
26389
|
-
import_fs9.default.mkdirSync(
|
|
26675
|
+
import_fs9.default.mkdirSync(import_path20.default.join(dir, sub), { recursive: true });
|
|
26390
26676
|
}
|
|
26391
26677
|
return dir;
|
|
26392
26678
|
}
|
|
@@ -26560,15 +26846,15 @@ async function startDaemon(config) {
|
|
|
26560
26846
|
}
|
|
26561
26847
|
);
|
|
26562
26848
|
const indexCandidates = [
|
|
26563
|
-
|
|
26564
|
-
|
|
26849
|
+
import_path21.default.join(__dirname, "..", "index"),
|
|
26850
|
+
import_path21.default.join(__dirname, "..", "..", "index", "image")
|
|
26565
26851
|
];
|
|
26566
26852
|
const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
|
|
26567
26853
|
const odIndex = new IndexService({
|
|
26568
|
-
controlToken: (0,
|
|
26854
|
+
controlToken: (0, import_crypto9.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
26569
26855
|
embedUrl: () => config.serverUrl,
|
|
26570
26856
|
indexDir,
|
|
26571
|
-
dataDir:
|
|
26857
|
+
dataDir: import_path21.default.join(CONFIG_DIR, "index-data")
|
|
26572
26858
|
});
|
|
26573
26859
|
void odIndex.ensureRunning().catch((err) => {
|
|
26574
26860
|
console.error(
|