@factiii/runner 0.10.2 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +798 -415
- package/package.json +1 -1
- package/plugin/factiii/bin/factiii-secrets +208 -0
- package/plugin/factiii/skills/factiii-secrets/SKILL.md +82 -0
package/dist/cli.js
CHANGED
|
@@ -963,8 +963,8 @@ var require_command = __commonJS({
|
|
|
963
963
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
964
964
|
var EventEmitter = require("node:events").EventEmitter;
|
|
965
965
|
var childProcess = require("node:child_process");
|
|
966
|
-
var
|
|
967
|
-
var
|
|
966
|
+
var path20 = require("node:path");
|
|
967
|
+
var fs15 = require("node:fs");
|
|
968
968
|
var process2 = require("node:process");
|
|
969
969
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
970
970
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1896,11 +1896,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1896
1896
|
let launchWithNode = false;
|
|
1897
1897
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1898
1898
|
function findFile(baseDir, baseName) {
|
|
1899
|
-
const localBin =
|
|
1900
|
-
if (
|
|
1901
|
-
if (sourceExt.includes(
|
|
1899
|
+
const localBin = path20.resolve(baseDir, baseName);
|
|
1900
|
+
if (fs15.existsSync(localBin)) return localBin;
|
|
1901
|
+
if (sourceExt.includes(path20.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
|
-
(ext) =>
|
|
1903
|
+
(ext) => fs15.existsSync(`${localBin}${ext}`)
|
|
1904
1904
|
);
|
|
1905
1905
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1906
1906
|
return void 0;
|
|
@@ -1912,21 +1912,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1912
1912
|
if (this._scriptPath) {
|
|
1913
1913
|
let resolvedScriptPath;
|
|
1914
1914
|
try {
|
|
1915
|
-
resolvedScriptPath =
|
|
1915
|
+
resolvedScriptPath = fs15.realpathSync(this._scriptPath);
|
|
1916
1916
|
} catch (err) {
|
|
1917
1917
|
resolvedScriptPath = this._scriptPath;
|
|
1918
1918
|
}
|
|
1919
|
-
executableDir =
|
|
1920
|
-
|
|
1919
|
+
executableDir = path20.resolve(
|
|
1920
|
+
path20.dirname(resolvedScriptPath),
|
|
1921
1921
|
executableDir
|
|
1922
1922
|
);
|
|
1923
1923
|
}
|
|
1924
1924
|
if (executableDir) {
|
|
1925
1925
|
let localFile = findFile(executableDir, executableFile);
|
|
1926
1926
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1927
|
-
const legacyName =
|
|
1927
|
+
const legacyName = path20.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path20.extname(this._scriptPath)
|
|
1930
1930
|
);
|
|
1931
1931
|
if (legacyName !== this._name) {
|
|
1932
1932
|
localFile = findFile(
|
|
@@ -1937,7 +1937,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1937
1937
|
}
|
|
1938
1938
|
executableFile = localFile || executableFile;
|
|
1939
1939
|
}
|
|
1940
|
-
launchWithNode = sourceExt.includes(
|
|
1940
|
+
launchWithNode = sourceExt.includes(path20.extname(executableFile));
|
|
1941
1941
|
let proc;
|
|
1942
1942
|
if (process2.platform !== "win32") {
|
|
1943
1943
|
if (launchWithNode) {
|
|
@@ -2777,7 +2777,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2777
2777
|
* @return {Command}
|
|
2778
2778
|
*/
|
|
2779
2779
|
nameFromFilename(filename) {
|
|
2780
|
-
this._name =
|
|
2780
|
+
this._name = path20.basename(filename, path20.extname(filename));
|
|
2781
2781
|
return this;
|
|
2782
2782
|
}
|
|
2783
2783
|
/**
|
|
@@ -2791,9 +2791,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2791
2791
|
* @param {string} [path]
|
|
2792
2792
|
* @return {(string|null|Command)}
|
|
2793
2793
|
*/
|
|
2794
|
-
executableDir(
|
|
2795
|
-
if (
|
|
2796
|
-
this._executableDir =
|
|
2794
|
+
executableDir(path21) {
|
|
2795
|
+
if (path21 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path21;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -3026,7 +3026,7 @@ var require_commander = __commonJS({
|
|
|
3026
3026
|
// ../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js
|
|
3027
3027
|
var require_XMLHttpRequest = __commonJS({
|
|
3028
3028
|
"../../node_modules/xmlhttprequest-ssl/lib/XMLHttpRequest.js"(exports2, module2) {
|
|
3029
|
-
var
|
|
3029
|
+
var fs15 = require("fs");
|
|
3030
3030
|
var Url = require("url");
|
|
3031
3031
|
var spawn5 = require("child_process").spawn;
|
|
3032
3032
|
module2.exports = XMLHttpRequest3;
|
|
@@ -3184,7 +3184,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3184
3184
|
throw new Error("XMLHttpRequest: Only GET method is supported");
|
|
3185
3185
|
}
|
|
3186
3186
|
if (settings.async) {
|
|
3187
|
-
|
|
3187
|
+
fs15.readFile(unescape(url2.pathname), function(error, data2) {
|
|
3188
3188
|
if (error) {
|
|
3189
3189
|
self.handleError(error, error.errno || -1);
|
|
3190
3190
|
} else {
|
|
@@ -3196,7 +3196,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3196
3196
|
});
|
|
3197
3197
|
} else {
|
|
3198
3198
|
try {
|
|
3199
|
-
this.response =
|
|
3199
|
+
this.response = fs15.readFileSync(unescape(url2.pathname));
|
|
3200
3200
|
this.responseText = this.response.toString("utf8");
|
|
3201
3201
|
this.status = 200;
|
|
3202
3202
|
setState(self.DONE);
|
|
@@ -3322,15 +3322,15 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
3322
3322
|
} else {
|
|
3323
3323
|
var contentFile = ".node-xmlhttprequest-content-" + process.pid;
|
|
3324
3324
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
3325
|
-
|
|
3325
|
+
fs15.writeFileSync(syncFile, "", "utf8");
|
|
3326
3326
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
3327
3327
|
var syncProc = spawn5(process.argv[0], ["-e", execString]);
|
|
3328
3328
|
var statusText;
|
|
3329
|
-
while (
|
|
3329
|
+
while (fs15.existsSync(syncFile)) {
|
|
3330
3330
|
}
|
|
3331
|
-
self.responseText =
|
|
3331
|
+
self.responseText = fs15.readFileSync(contentFile, "utf8");
|
|
3332
3332
|
syncProc.stdin.end();
|
|
3333
|
-
|
|
3333
|
+
fs15.unlinkSync(contentFile);
|
|
3334
3334
|
if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
|
|
3335
3335
|
var errorObj = JSON.parse(self.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, ""));
|
|
3336
3336
|
self.handleError(errorObj, 503);
|
|
@@ -6497,7 +6497,7 @@ var require_websocket = __commonJS({
|
|
|
6497
6497
|
var EventEmitter = require("events");
|
|
6498
6498
|
var https = require("https");
|
|
6499
6499
|
var http = require("http");
|
|
6500
|
-
var
|
|
6500
|
+
var net3 = require("net");
|
|
6501
6501
|
var tls = require("tls");
|
|
6502
6502
|
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
6503
6503
|
var { Duplex, Readable } = require("stream");
|
|
@@ -7241,12 +7241,12 @@ var require_websocket = __commonJS({
|
|
|
7241
7241
|
}
|
|
7242
7242
|
function netConnect(options) {
|
|
7243
7243
|
options.path = options.socketPath;
|
|
7244
|
-
return
|
|
7244
|
+
return net3.connect(options);
|
|
7245
7245
|
}
|
|
7246
7246
|
function tlsConnect(options) {
|
|
7247
7247
|
options.path = void 0;
|
|
7248
7248
|
if (!options.servername && options.servername !== "") {
|
|
7249
|
-
options.servername =
|
|
7249
|
+
options.servername = net3.isIP(options.host) ? "" : options.host;
|
|
7250
7250
|
}
|
|
7251
7251
|
return tls.connect(options);
|
|
7252
7252
|
}
|
|
@@ -8428,8 +8428,8 @@ function getErrorMap() {
|
|
|
8428
8428
|
|
|
8429
8429
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
8430
8430
|
var makeIssue = (params) => {
|
|
8431
|
-
const { data, path:
|
|
8432
|
-
const fullPath = [...
|
|
8431
|
+
const { data, path: path20, errorMaps, issueData } = params;
|
|
8432
|
+
const fullPath = [...path20, ...issueData.path || []];
|
|
8433
8433
|
const fullIssue = {
|
|
8434
8434
|
...issueData,
|
|
8435
8435
|
path: fullPath
|
|
@@ -8545,11 +8545,11 @@ var errorUtil;
|
|
|
8545
8545
|
|
|
8546
8546
|
// ../../node_modules/zod/v3/types.js
|
|
8547
8547
|
var ParseInputLazyPath = class {
|
|
8548
|
-
constructor(parent, value2,
|
|
8548
|
+
constructor(parent, value2, path20, key) {
|
|
8549
8549
|
this._cachedPath = [];
|
|
8550
8550
|
this.parent = parent;
|
|
8551
8551
|
this.data = value2;
|
|
8552
|
-
this._path =
|
|
8552
|
+
this._path = path20;
|
|
8553
8553
|
this._key = key;
|
|
8554
8554
|
}
|
|
8555
8555
|
get path() {
|
|
@@ -12197,9 +12197,9 @@ function processLine(trimmed, state, cb) {
|
|
|
12197
12197
|
if (e.type === "result" /* Result */) {
|
|
12198
12198
|
if (e.is_error) {
|
|
12199
12199
|
state.isError = true;
|
|
12200
|
-
const
|
|
12201
|
-
state.fullText =
|
|
12202
|
-
cb.onLog?.({ type: "error", content:
|
|
12200
|
+
const errorText2 = e.result || "Claude encountered an error";
|
|
12201
|
+
state.fullText = errorText2;
|
|
12202
|
+
cb.onLog?.({ type: "error", content: errorText2 });
|
|
12203
12203
|
return;
|
|
12204
12204
|
}
|
|
12205
12205
|
if (e.result && !state.fullText) {
|
|
@@ -12647,7 +12647,8 @@ var prompts_default = {
|
|
|
12647
12647
|
"- $WORKSPACE - the repo checkout (the working directory), reset to the default branch at run start",
|
|
12648
12648
|
"- $BACKUPS_DIR - durable backups; every backup (db dumps etc) is written here and nowhere else",
|
|
12649
12649
|
"- $BUILDS_DIR - build artifacts; each build writes into $BUILDS_DIR/<commit-hash>/",
|
|
12650
|
-
"Reference paths ONLY via these variables or relative to the working directory. NEVER hardcode absolute machine paths (like /Users/...) anywhere in the skill - it must run unchanged on any runner.
|
|
12650
|
+
"Reference paths ONLY via these variables or relative to the working directory. NEVER hardcode absolute machine paths (like /Users/...) anywhere in the skill - it must run unchanged on any runner.",
|
|
12651
|
+
"Secrets live in an encrypted store you never touch, and `factiii-secrets` is how a command gets them - it is on the PATH of every run. Load the `factiii-secrets` skill installed on this machine (it is not part of this repo) for exactly how it behaves, and write the skill's commands in that shape.",
|
|
12651
12652
|
"",
|
|
12652
12653
|
"## Produce exactly three files",
|
|
12653
12654
|
"1. .agents/skills/deploy/SKILL.md - the runbook, written for a competent operator. It MUST start with YAML frontmatter (--- name: deploy / description: one line saying this is the repository's release runbook and when to run it ---) so agent CLIs discover it as a skill, followed by: named steps in ship order, the exact commands, what success looks like per step, and explicit stop-and-ask points wherever human judgment or confirmation belongs (e.g. 'confirm before the production cutover'). Plain language; no secret values or key material anywhere in the file - reference secrets ONLY as $KEY env names.",
|
|
@@ -12658,7 +12659,7 @@ var prompts_default = {
|
|
|
12658
12659
|
"- Write ONLY those three files under .agents/skills/deploy/.",
|
|
12659
12660
|
"- Do NOT git commit or git push; the harness pushes for review.",
|
|
12660
12661
|
"- Cloud CLIs on this machine (aws, vercel, wrangler, gh...) may already be signed in. You may use them READ-ONLY to verify what exists (project names, repo names, regions) so the skill is accurate - but NEVER create, modify, deploy, or delete anything, and never echo a credential or token value.",
|
|
12661
|
-
|
|
12662
|
+
'- Secret-bearing commands: any command needing a variable declared in required-variables.json MUST be written in the skill as `factiii-secrets run -- <command>`, with the variables referenced unexpanded and quoted (e.g. "$DATABASE_URL"). The owner authorizes each one at run time and the runner injects the values. Wrap only the commands that need a secret. NEVER include steps that check whether variables are set in the environment, and never instruct anyone to export them or put them in a .env/shell/CI config.',
|
|
12662
12663
|
"- No secret values in any file, ever."
|
|
12663
12664
|
],
|
|
12664
12665
|
deployCreateRevise: [
|
|
@@ -12666,18 +12667,18 @@ var prompts_default = {
|
|
|
12666
12667
|
"",
|
|
12667
12668
|
"{{FEEDBACK}}",
|
|
12668
12669
|
"",
|
|
12669
|
-
"Apply them by editing .agents/skills/deploy/SKILL.md, .agents/skills/deploy/required-variables.json, and/or .agents/skills/deploy/check-environment.sh in your working directory - the same contract as before: only those three files, keys declared exactly when used, no secret values anywhere, no git commit/push, and paths referenced ONLY via $WORKSPACE / $BACKUPS_DIR / $BUILDS_DIR or relative to the working directory (never absolute machine paths). Keep everything the owner did NOT ask to change untouched."
|
|
12670
|
+
"Apply them by editing .agents/skills/deploy/SKILL.md, .agents/skills/deploy/required-variables.json, and/or .agents/skills/deploy/check-environment.sh in your working directory - the same contract as before: only those three files, keys declared exactly when used, every secret-bearing command still written as `factiii-secrets run -- <command>`, no secret values anywhere, no git commit/push, and paths referenced ONLY via $WORKSPACE / $BACKUPS_DIR / $BUILDS_DIR or relative to the working directory (never absolute machine paths). Keep everything the owner did NOT ask to change untouched."
|
|
12670
12671
|
],
|
|
12671
12672
|
deployRun: [
|
|
12672
12673
|
"## Deploy run protocol",
|
|
12673
12674
|
"You are executing this repository's deploy skill NOW, on the owner's machine, for real. Invoke it and follow it exactly - the skill is the program; do not improvise steps it doesn't contain or change any step's intent.",
|
|
12674
12675
|
"",
|
|
12675
|
-
"##
|
|
12676
|
-
"Every variable declared in required-variables.json is UNSET in your shell BY DESIGN, and will stay unset for the whole run.
|
|
12677
|
-
"-
|
|
12678
|
-
|
|
12679
|
-
"- If the skill has a step that verifies variables are set, SKIP it - it predates this protocol.",
|
|
12680
|
-
|
|
12676
|
+
"## Secrets (read this before anything else)",
|
|
12677
|
+
"Every variable declared in required-variables.json is UNSET in your shell BY DESIGN, and will stay unset for the whole run. The values are stored encrypted on this machine, and `factiii-secrets` is how a command gets them - it is on your PATH, and the `factiii-secrets` skill describes it in full.",
|
|
12678
|
+
"- To run a command that needs any declared variable, prefix it: `factiii-secrets run -- <command>`. The owner authorizes it with their password, the runner runs it with every declared value injected, and you get the output back with the values redacted. It exits with the command's own code, so `&&`, `||` and `set -e` behave normally. Exit 77 means the owner declined - adapt or stop, and never retry the same command.",
|
|
12679
|
+
'- Quote the variables (`"$DATABASE_URL"`): they expand on the runner\'s side, not in your shell.',
|
|
12680
|
+
"- NEVER check whether these variables are set (no `env`, `printenv`, `test -n`, echo checks), never report them as missing, and never abort because your shell lacks them. Their absence tells you nothing. If the skill has a step that verifies variables are set, SKIP it - it predates this protocol.",
|
|
12681
|
+
"- Wrap ONLY the commands that actually need a secret. Everything else runs unwrapped and needs no authorization.",
|
|
12681
12682
|
"",
|
|
12682
12683
|
"- Track the release with your todo/plan tool: one todo per step in ship order, updated as you go. This is the owner's live timeline.",
|
|
12683
12684
|
"- HUMAN GATES: wherever the skill says to stop and ask (and before anything irreversible the skill flags), print as the LAST line: AWAITING_INPUT: <the question> - then END your turn. The owner's answer arrives as your next message.",
|
|
@@ -12908,7 +12909,7 @@ ${diff.slice(0, 8e3)}`,
|
|
|
12908
12909
|
}
|
|
12909
12910
|
|
|
12910
12911
|
// ../../shared/all/helpers/board-agent-core/engine.ts
|
|
12911
|
-
var
|
|
12912
|
+
var import_path12 = require("path");
|
|
12912
12913
|
|
|
12913
12914
|
// ../../shared/all/domains/electron.ts
|
|
12914
12915
|
var SETUP_LOG_PREFIX = "log ";
|
|
@@ -13017,7 +13018,7 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
|
|
|
13017
13018
|
await target.sh(`mkdir -p ${target.paths.worktrees}`);
|
|
13018
13019
|
}
|
|
13019
13020
|
async function addWorktree(target, workName, baseBranch, kind) {
|
|
13020
|
-
const
|
|
13021
|
+
const path20 = worktreeFor(target.paths, workName);
|
|
13021
13022
|
const branch = workBranch(workName, kind);
|
|
13022
13023
|
await gitInDir(
|
|
13023
13024
|
target,
|
|
@@ -13027,7 +13028,7 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13027
13028
|
baseBranch
|
|
13028
13029
|
).catch(() => {
|
|
13029
13030
|
});
|
|
13030
|
-
await removeWorktree(target,
|
|
13031
|
+
await removeWorktree(target, path20).catch(() => {
|
|
13031
13032
|
});
|
|
13032
13033
|
await gitInDir(
|
|
13033
13034
|
target,
|
|
@@ -13036,30 +13037,30 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13036
13037
|
"add",
|
|
13037
13038
|
"-B",
|
|
13038
13039
|
branch,
|
|
13039
|
-
|
|
13040
|
+
path20,
|
|
13040
13041
|
`origin/${baseBranch}`
|
|
13041
13042
|
);
|
|
13042
|
-
return { path:
|
|
13043
|
+
return { path: path20, branch };
|
|
13043
13044
|
}
|
|
13044
|
-
async function removeWorktree(target,
|
|
13045
|
+
async function removeWorktree(target, path20) {
|
|
13045
13046
|
await gitInDir(
|
|
13046
13047
|
target,
|
|
13047
13048
|
target.paths.repo,
|
|
13048
13049
|
"worktree",
|
|
13049
13050
|
"remove",
|
|
13050
13051
|
"--force",
|
|
13051
|
-
|
|
13052
|
+
path20
|
|
13052
13053
|
);
|
|
13053
13054
|
}
|
|
13054
|
-
async function removeWorktreeAndBranch(target,
|
|
13055
|
+
async function removeWorktreeAndBranch(target, path20) {
|
|
13055
13056
|
const branch = await gitInDir(
|
|
13056
13057
|
target,
|
|
13057
|
-
|
|
13058
|
+
path20,
|
|
13058
13059
|
"rev-parse",
|
|
13059
13060
|
"--abbrev-ref",
|
|
13060
13061
|
"HEAD"
|
|
13061
13062
|
).then((out) => out.trim()).catch(() => "");
|
|
13062
|
-
await removeWorktree(target,
|
|
13063
|
+
await removeWorktree(target, path20);
|
|
13063
13064
|
if (branch && branch !== "HEAD") {
|
|
13064
13065
|
await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
|
|
13065
13066
|
() => {
|
|
@@ -13073,8 +13074,8 @@ async function pruneWorktrees(target) {
|
|
|
13073
13074
|
}
|
|
13074
13075
|
);
|
|
13075
13076
|
}
|
|
13076
|
-
function isWorktreePath(target,
|
|
13077
|
-
return
|
|
13077
|
+
function isWorktreePath(target, path20) {
|
|
13078
|
+
return path20.startsWith(`${target.paths.worktrees}/`);
|
|
13078
13079
|
}
|
|
13079
13080
|
async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
|
|
13080
13081
|
if (name) await gitInDir(target, workdir, "config", "user.name", name);
|
|
@@ -13100,13 +13101,13 @@ function parseStatus(raw) {
|
|
|
13100
13101
|
const y = line[1];
|
|
13101
13102
|
const rest = line.slice(3);
|
|
13102
13103
|
const arrow = rest.indexOf(" -> ");
|
|
13103
|
-
const
|
|
13104
|
+
const path20 = arrow === -1 ? rest : rest.slice(arrow + 4);
|
|
13104
13105
|
if (x === "?" || y === "?") {
|
|
13105
|
-
unstaged.push({ path:
|
|
13106
|
+
unstaged.push({ path: path20, code: "A" });
|
|
13106
13107
|
continue;
|
|
13107
13108
|
}
|
|
13108
|
-
if (x !== " ") staged.push({ path:
|
|
13109
|
-
if (y !== " ") unstaged.push({ path:
|
|
13109
|
+
if (x !== " ") staged.push({ path: path20, code: x });
|
|
13110
|
+
if (y !== " ") unstaged.push({ path: path20, code: y });
|
|
13110
13111
|
}
|
|
13111
13112
|
return { staged, unstaged };
|
|
13112
13113
|
}
|
|
@@ -13464,10 +13465,12 @@ function execLogin(command, opts) {
|
|
|
13464
13465
|
let output = "";
|
|
13465
13466
|
let settled = false;
|
|
13466
13467
|
let timer;
|
|
13468
|
+
let idleTimer;
|
|
13467
13469
|
const done = (code, extra = "") => {
|
|
13468
13470
|
if (settled) return;
|
|
13469
13471
|
settled = true;
|
|
13470
13472
|
if (timer) clearTimeout(timer);
|
|
13473
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
13471
13474
|
resolve2({ code, output: (output + extra).slice(-cap) });
|
|
13472
13475
|
};
|
|
13473
13476
|
let proc;
|
|
@@ -13489,9 +13492,22 @@ function execLogin(command, opts) {
|
|
|
13489
13492
|
done(-1, `
|
|
13490
13493
|
[timed out after ${Math.round(opts.timeout / 1e3)}s]`);
|
|
13491
13494
|
}, opts.timeout);
|
|
13495
|
+
const idleMs = opts.idleTimeout;
|
|
13496
|
+
const bumpIdle = () => {
|
|
13497
|
+
if (!idleMs) return;
|
|
13498
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
13499
|
+
idleTimer = setTimeout(() => {
|
|
13500
|
+
killTree(proc);
|
|
13501
|
+
const secs = Math.round(idleMs / 1e3);
|
|
13502
|
+
done(-1, `
|
|
13503
|
+
[no output for ${secs}s - timed out]`);
|
|
13504
|
+
}, idleMs);
|
|
13505
|
+
};
|
|
13506
|
+
bumpIdle();
|
|
13492
13507
|
const collect = (chunk) => {
|
|
13493
13508
|
output += chunk.toString("utf-8");
|
|
13494
13509
|
if (output.length > cap * 2) output = output.slice(-cap);
|
|
13510
|
+
bumpIdle();
|
|
13495
13511
|
opts.onOutput?.(output);
|
|
13496
13512
|
};
|
|
13497
13513
|
proc.stdout.on("data", collect);
|
|
@@ -13848,13 +13864,13 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13848
13864
|
|
|
13849
13865
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
13850
13866
|
var import_fs2 = require("fs");
|
|
13851
|
-
var
|
|
13852
|
-
var
|
|
13867
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
13868
|
+
var import_path7 = __toESM(require("path"));
|
|
13853
13869
|
|
|
13854
13870
|
// ../../shared/all/helpers/board-agent-core/host-skills.ts
|
|
13855
|
-
var
|
|
13871
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
13856
13872
|
var import_os2 = __toESM(require("os"));
|
|
13857
|
-
var
|
|
13873
|
+
var import_path6 = __toESM(require("path"));
|
|
13858
13874
|
|
|
13859
13875
|
// ../../shared/all/domains/posts.ts
|
|
13860
13876
|
var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
|
|
@@ -14364,9 +14380,134 @@ var skillScopeSchema = external_exports.enum(["global", "repo"]);
|
|
|
14364
14380
|
|
|
14365
14381
|
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14366
14382
|
var import_child_process3 = require("child_process");
|
|
14383
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
14384
|
+
var import_path5 = __toESM(require("path"));
|
|
14385
|
+
var import_util5 = require("util");
|
|
14386
|
+
|
|
14387
|
+
// ../../shared/all/helpers/board-agent-core/secrets-bridge.ts
|
|
14367
14388
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
14389
|
+
var import_net2 = __toESM(require("net"));
|
|
14368
14390
|
var import_path4 = __toESM(require("path"));
|
|
14369
|
-
|
|
14391
|
+
function secretsSocketPath(spaceDir) {
|
|
14392
|
+
return import_path4.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
|
|
14393
|
+
}
|
|
14394
|
+
function redactor(values) {
|
|
14395
|
+
const significant = values.filter((v) => v.length >= 4);
|
|
14396
|
+
return (text) => {
|
|
14397
|
+
let out = text;
|
|
14398
|
+
for (const value2 of significant) {
|
|
14399
|
+
out = out.split(value2).join("[REDACTED]");
|
|
14400
|
+
}
|
|
14401
|
+
return out;
|
|
14402
|
+
};
|
|
14403
|
+
}
|
|
14404
|
+
function replier(conn) {
|
|
14405
|
+
return (message) => {
|
|
14406
|
+
if (conn.destroyed) return;
|
|
14407
|
+
conn.write(`${JSON.stringify(message)}
|
|
14408
|
+
`);
|
|
14409
|
+
};
|
|
14410
|
+
}
|
|
14411
|
+
async function handleRun(request, handlers, reply) {
|
|
14412
|
+
const command = typeof request.command === "string" ? request.command.trim() : "";
|
|
14413
|
+
if (!command) {
|
|
14414
|
+
reply({ type: "error", message: "No command was sent." });
|
|
14415
|
+
return;
|
|
14416
|
+
}
|
|
14417
|
+
const cwd = typeof request.cwd === "string" ? request.cwd : "";
|
|
14418
|
+
const result = await handlers.run({
|
|
14419
|
+
command,
|
|
14420
|
+
cwd,
|
|
14421
|
+
status: (text) => reply({ type: "status", text })
|
|
14422
|
+
});
|
|
14423
|
+
if ("denied" in result) {
|
|
14424
|
+
reply({ type: "denied" });
|
|
14425
|
+
return;
|
|
14426
|
+
}
|
|
14427
|
+
if (result.output) reply({ type: "output", chunk: result.output });
|
|
14428
|
+
reply({ type: "exit", code: result.code });
|
|
14429
|
+
}
|
|
14430
|
+
async function startSecretsBridge(opts) {
|
|
14431
|
+
const socketPath = secretsSocketPath(opts.spaceDir);
|
|
14432
|
+
await import_promises2.default.mkdir(import_path4.default.dirname(socketPath), { recursive: true });
|
|
14433
|
+
await import_promises2.default.rm(socketPath, { force: true });
|
|
14434
|
+
const { handlers } = opts;
|
|
14435
|
+
let queue = Promise.resolve();
|
|
14436
|
+
let queueDepth = 0;
|
|
14437
|
+
const live = /* @__PURE__ */ new Set();
|
|
14438
|
+
const server = import_net2.default.createServer((conn) => {
|
|
14439
|
+
live.add(conn);
|
|
14440
|
+
conn.on("close", () => live.delete(conn));
|
|
14441
|
+
const reply = replier(conn);
|
|
14442
|
+
let buffer = "";
|
|
14443
|
+
let handled = false;
|
|
14444
|
+
conn.on("error", () => {
|
|
14445
|
+
});
|
|
14446
|
+
conn.on("data", (chunk) => {
|
|
14447
|
+
if (handled) return;
|
|
14448
|
+
buffer += chunk.toString("utf-8");
|
|
14449
|
+
const newline = buffer.indexOf("\n");
|
|
14450
|
+
if (newline === -1) return;
|
|
14451
|
+
handled = true;
|
|
14452
|
+
let request;
|
|
14453
|
+
try {
|
|
14454
|
+
request = JSON.parse(buffer.slice(0, newline));
|
|
14455
|
+
} catch {
|
|
14456
|
+
reply({ type: "error", message: "Malformed request." });
|
|
14457
|
+
conn.end();
|
|
14458
|
+
return;
|
|
14459
|
+
}
|
|
14460
|
+
if (request.op === "list") {
|
|
14461
|
+
void handlers.list().then((keys) => reply({ type: "keys", keys })).catch(
|
|
14462
|
+
(err) => reply({ type: "error", message: errorText(err) })
|
|
14463
|
+
).finally(() => conn.end());
|
|
14464
|
+
return;
|
|
14465
|
+
}
|
|
14466
|
+
if (request.op !== "run") {
|
|
14467
|
+
reply({ type: "error", message: "Unknown operation." });
|
|
14468
|
+
conn.end();
|
|
14469
|
+
return;
|
|
14470
|
+
}
|
|
14471
|
+
if (queueDepth > 0) {
|
|
14472
|
+
reply({
|
|
14473
|
+
type: "status",
|
|
14474
|
+
text: "queued behind another command awaiting authorization"
|
|
14475
|
+
});
|
|
14476
|
+
}
|
|
14477
|
+
queueDepth += 1;
|
|
14478
|
+
queue = queue.then(() => {
|
|
14479
|
+
if (conn.destroyed) return void 0;
|
|
14480
|
+
return handleRun(request, handlers, reply);
|
|
14481
|
+
}).catch(
|
|
14482
|
+
(err) => reply({ type: "error", message: errorText(err) })
|
|
14483
|
+
).finally(() => {
|
|
14484
|
+
queueDepth -= 1;
|
|
14485
|
+
conn.end();
|
|
14486
|
+
});
|
|
14487
|
+
});
|
|
14488
|
+
});
|
|
14489
|
+
await new Promise((resolve2, reject) => {
|
|
14490
|
+
server.once("error", reject);
|
|
14491
|
+
server.listen(socketPath, () => {
|
|
14492
|
+
server.removeListener("error", reject);
|
|
14493
|
+
resolve2();
|
|
14494
|
+
});
|
|
14495
|
+
});
|
|
14496
|
+
await import_promises2.default.chmod(socketPath, 384).catch(() => void 0);
|
|
14497
|
+
return {
|
|
14498
|
+
close: async () => {
|
|
14499
|
+
for (const conn of live) conn.destroy();
|
|
14500
|
+
live.clear();
|
|
14501
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
14502
|
+
await import_promises2.default.rm(socketPath, { force: true });
|
|
14503
|
+
}
|
|
14504
|
+
};
|
|
14505
|
+
}
|
|
14506
|
+
function errorText(err) {
|
|
14507
|
+
return err instanceof Error ? err.message : String(err);
|
|
14508
|
+
}
|
|
14509
|
+
|
|
14510
|
+
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14370
14511
|
var execFileAsync2 = (0, import_util5.promisify)(import_child_process3.execFile);
|
|
14371
14512
|
var SKILL_DIR = ".agents/skills/deploy";
|
|
14372
14513
|
var SKILL_PATH = `${SKILL_DIR}/SKILL.md`;
|
|
@@ -14375,14 +14516,14 @@ var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
|
|
|
14375
14516
|
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
14376
14517
|
var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
|
|
14377
14518
|
async function ensureClaudeSkillLink(workspace) {
|
|
14378
|
-
const linkPath =
|
|
14519
|
+
const linkPath = import_path5.default.join(workspace, CLAUDE_LINK);
|
|
14379
14520
|
try {
|
|
14380
|
-
await
|
|
14521
|
+
await import_promises3.default.lstat(linkPath);
|
|
14381
14522
|
return;
|
|
14382
14523
|
} catch {
|
|
14383
14524
|
}
|
|
14384
|
-
await
|
|
14385
|
-
await
|
|
14525
|
+
await import_promises3.default.mkdir(import_path5.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
|
|
14526
|
+
await import_promises3.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
|
|
14386
14527
|
}
|
|
14387
14528
|
var GIT_NO_HELPER = ["-c", "credential.helper="];
|
|
14388
14529
|
var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -14427,7 +14568,7 @@ async function gitCatFileBatch(cwd, shas) {
|
|
|
14427
14568
|
return out;
|
|
14428
14569
|
}
|
|
14429
14570
|
async function ensureWorkspaceClone(opts) {
|
|
14430
|
-
const workspace =
|
|
14571
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14431
14572
|
const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
14432
14573
|
const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
|
|
14433
14574
|
let cloned = false;
|
|
@@ -14435,14 +14576,14 @@ async function ensureWorkspaceClone(opts) {
|
|
|
14435
14576
|
await hostGit(workspace, "rev-parse", "--git-dir");
|
|
14436
14577
|
await hostGit(workspace, "remote", "set-url", "origin", url2);
|
|
14437
14578
|
} catch {
|
|
14438
|
-
const leftovers = await
|
|
14579
|
+
const leftovers = await import_promises3.default.readdir(workspace).catch(() => []);
|
|
14439
14580
|
if (leftovers.includes(".git")) {
|
|
14440
14581
|
throw new Error(
|
|
14441
14582
|
`${workspace} holds a git repository that cannot be read. Remove it and retry.`
|
|
14442
14583
|
);
|
|
14443
14584
|
}
|
|
14444
14585
|
for (const name of leftovers) {
|
|
14445
|
-
await
|
|
14586
|
+
await import_promises3.default.rm(import_path5.default.join(workspace, name), { recursive: true, force: true });
|
|
14446
14587
|
}
|
|
14447
14588
|
await execFileAsync2(
|
|
14448
14589
|
"git",
|
|
@@ -14513,6 +14654,60 @@ async function checkDeployStatus(opts) {
|
|
|
14513
14654
|
variables
|
|
14514
14655
|
};
|
|
14515
14656
|
}
|
|
14657
|
+
var RELEASE_BRANCH_LIMIT = 25;
|
|
14658
|
+
var BRANCH_FIELDS = "%(refname:short)%09%(committerdate:unix)";
|
|
14659
|
+
async function listReleaseBranches(opts) {
|
|
14660
|
+
const { workspace, branch } = await ensureWorkspaceClone(opts);
|
|
14661
|
+
await hostGit(workspace, "fetch", "origin", "--prune");
|
|
14662
|
+
let counted = true;
|
|
14663
|
+
let raw = "";
|
|
14664
|
+
try {
|
|
14665
|
+
raw = await hostGit(
|
|
14666
|
+
workspace,
|
|
14667
|
+
"for-each-ref",
|
|
14668
|
+
"--sort=-committerdate",
|
|
14669
|
+
`--format=${BRANCH_FIELDS}%09%(ahead-behind:refs/remotes/origin/${branch})%09%(contents:subject)`,
|
|
14670
|
+
"refs/remotes/origin"
|
|
14671
|
+
);
|
|
14672
|
+
} catch {
|
|
14673
|
+
counted = false;
|
|
14674
|
+
raw = await hostGit(
|
|
14675
|
+
workspace,
|
|
14676
|
+
"for-each-ref",
|
|
14677
|
+
"--sort=-committerdate",
|
|
14678
|
+
`--format=${BRANCH_FIELDS}%09%(contents:subject)`,
|
|
14679
|
+
"refs/remotes/origin"
|
|
14680
|
+
);
|
|
14681
|
+
}
|
|
14682
|
+
const branches = [];
|
|
14683
|
+
for (const row of raw.split("\n")) {
|
|
14684
|
+
if (branches.length >= RELEASE_BRANCH_LIMIT) break;
|
|
14685
|
+
const [ref = "", unix = "", ...rest] = row.split(" ");
|
|
14686
|
+
if (!ref.startsWith("origin/")) continue;
|
|
14687
|
+
const name = ref.slice("origin/".length);
|
|
14688
|
+
if (name === "HEAD" || name === branch) continue;
|
|
14689
|
+
let ahead = counted ? Number(rest.shift()?.split(" ")[0]) || 0 : 0;
|
|
14690
|
+
if (!counted) {
|
|
14691
|
+
try {
|
|
14692
|
+
const count = await hostGit(
|
|
14693
|
+
workspace,
|
|
14694
|
+
"rev-list",
|
|
14695
|
+
"--count",
|
|
14696
|
+
`origin/${branch}..${ref}`
|
|
14697
|
+
);
|
|
14698
|
+
ahead = Number(count.trim()) || 0;
|
|
14699
|
+
} catch {
|
|
14700
|
+
}
|
|
14701
|
+
}
|
|
14702
|
+
branches.push({
|
|
14703
|
+
name,
|
|
14704
|
+
ahead,
|
|
14705
|
+
updated: Number(unix) || 0,
|
|
14706
|
+
subject: rest.join(" ").trim()
|
|
14707
|
+
});
|
|
14708
|
+
}
|
|
14709
|
+
return branches;
|
|
14710
|
+
}
|
|
14516
14711
|
var requiredVariableSchema = external_exports.object({
|
|
14517
14712
|
key: external_exports.string().min(1),
|
|
14518
14713
|
title: external_exports.string().catch(""),
|
|
@@ -14526,6 +14721,10 @@ function spaceEnv(spaceDir) {
|
|
|
14526
14721
|
WORKSPACE: paths.workspace,
|
|
14527
14722
|
BACKUPS_DIR: paths.backups,
|
|
14528
14723
|
BUILDS_DIR: paths.builds,
|
|
14724
|
+
// Where `factiii-secrets` finds the runner. It can also walk up for the
|
|
14725
|
+
// socket, but only from inside the space tree - a step that cd'd into a
|
|
14726
|
+
// build dir elsewhere would not find it.
|
|
14727
|
+
FACTIII_SECRETS_SOCK: secretsSocketPath(spaceDir),
|
|
14529
14728
|
// No keychain credential helper and no interactive git prompts anywhere
|
|
14530
14729
|
// in the deploy surface (agent sessions + authorized commands): auth
|
|
14531
14730
|
// comes from the secrets store or the tokened URL, never the keychain.
|
|
@@ -14536,15 +14735,15 @@ function spaceEnv(spaceDir) {
|
|
|
14536
14735
|
};
|
|
14537
14736
|
}
|
|
14538
14737
|
async function readContract(workspace) {
|
|
14539
|
-
const skill = await
|
|
14738
|
+
const skill = await import_promises3.default.readFile(import_path5.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
|
|
14540
14739
|
throw new Error("The creator wrote no SKILL.md.");
|
|
14541
14740
|
});
|
|
14542
|
-
const checker = await
|
|
14741
|
+
const checker = await import_promises3.default.readFile(import_path5.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
|
|
14543
14742
|
throw new Error("The creator wrote no check-environment.sh.");
|
|
14544
14743
|
});
|
|
14545
14744
|
try {
|
|
14546
14745
|
const raw = JSON.parse(
|
|
14547
|
-
await
|
|
14746
|
+
await import_promises3.default.readFile(import_path5.default.join(workspace, VARIABLES_PATH), "utf-8")
|
|
14548
14747
|
);
|
|
14549
14748
|
return {
|
|
14550
14749
|
skill,
|
|
@@ -14561,7 +14760,7 @@ async function generateDeploySkill(opts) {
|
|
|
14561
14760
|
throw new Error("Describe your deployment process first.");
|
|
14562
14761
|
}
|
|
14563
14762
|
const status = await checkDeployStatus(opts);
|
|
14564
|
-
const workspace =
|
|
14763
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14565
14764
|
sendLog({
|
|
14566
14765
|
type: "system",
|
|
14567
14766
|
content: `Resetting workspace to origin/${status.branch}...`
|
|
@@ -14586,13 +14785,12 @@ async function generateDeploySkill(opts) {
|
|
|
14586
14785
|
prompt: buildPrompt(prompts_default.deployCreate, {
|
|
14587
14786
|
INSTRUCTIONS: instructions
|
|
14588
14787
|
}),
|
|
14589
|
-
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
14788
|
+
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch,Skill",
|
|
14590
14789
|
// auto: the per-action classifier blocks escalations/unrecognized-infra
|
|
14591
14790
|
// actions - a host session with no human reviewing each command should
|
|
14592
14791
|
// not run bypassPermissions. (Codex maps this to a workspace-write
|
|
14593
14792
|
// sandbox; see agent.ts.)
|
|
14594
14793
|
permissionMode: "auto",
|
|
14595
|
-
model: "claude-opus-4-6",
|
|
14596
14794
|
includePartialMessages: true,
|
|
14597
14795
|
env: spaceEnv(opts.spaceDir),
|
|
14598
14796
|
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
@@ -14605,7 +14803,7 @@ async function generateDeploySkill(opts) {
|
|
|
14605
14803
|
async function reviseDeploySkill(opts) {
|
|
14606
14804
|
const { feedback, sendLog } = opts;
|
|
14607
14805
|
if (!feedback.trim()) throw new Error("Corrections are empty.");
|
|
14608
|
-
const workspace =
|
|
14806
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14609
14807
|
sendLog({ type: "system", content: "Applying your corrections..." });
|
|
14610
14808
|
const { result } = await spawnAgent(
|
|
14611
14809
|
opts.provider,
|
|
@@ -14615,9 +14813,8 @@ async function reviseDeploySkill(opts) {
|
|
|
14615
14813
|
prompt: buildPrompt(prompts_default.deployCreateRevise, {
|
|
14616
14814
|
FEEDBACK: feedback
|
|
14617
14815
|
}),
|
|
14618
|
-
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
14816
|
+
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch,Skill",
|
|
14619
14817
|
permissionMode: "auto",
|
|
14620
|
-
model: "claude-opus-4-6",
|
|
14621
14818
|
includePartialMessages: true,
|
|
14622
14819
|
resumeSessionId: opts.resumeSessionId || void 0,
|
|
14623
14820
|
env: spaceEnv(opts.spaceDir),
|
|
@@ -14634,7 +14831,7 @@ async function loadSkillDraft(opts) {
|
|
|
14634
14831
|
throw new Error("Not a deploy-skill branch.");
|
|
14635
14832
|
}
|
|
14636
14833
|
await checkDeployStatus(opts);
|
|
14637
|
-
const workspace =
|
|
14834
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14638
14835
|
await hostGit(
|
|
14639
14836
|
workspace,
|
|
14640
14837
|
"fetch",
|
|
@@ -14646,7 +14843,7 @@ async function loadSkillDraft(opts) {
|
|
|
14646
14843
|
return readContract(workspace);
|
|
14647
14844
|
}
|
|
14648
14845
|
async function approveDeploySkill(opts) {
|
|
14649
|
-
const workspace =
|
|
14846
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14650
14847
|
await readContract(workspace);
|
|
14651
14848
|
await ensureClaudeSkillLink(workspace);
|
|
14652
14849
|
if (opts.gitName)
|
|
@@ -14689,19 +14886,19 @@ async function runEnvironmentCheck(opts) {
|
|
|
14689
14886
|
"No environment checker on the default branch. Generate the deploy skill first."
|
|
14690
14887
|
);
|
|
14691
14888
|
}
|
|
14692
|
-
const workspace =
|
|
14889
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14693
14890
|
const script = await hostGit(
|
|
14694
14891
|
workspace,
|
|
14695
14892
|
"cat-file",
|
|
14696
14893
|
"-p",
|
|
14697
14894
|
`origin/${status.branch}:${CHECKER_PATH}`
|
|
14698
14895
|
);
|
|
14699
|
-
const tmp =
|
|
14700
|
-
await
|
|
14896
|
+
const tmp = import_path5.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
|
|
14897
|
+
await import_promises3.default.writeFile(tmp, script, { mode: 448 });
|
|
14701
14898
|
let ok = true;
|
|
14702
14899
|
let output = "";
|
|
14703
14900
|
try {
|
|
14704
|
-
const scriptArg = tmp.split(
|
|
14901
|
+
const scriptArg = tmp.split(import_path5.default.sep).join("/");
|
|
14705
14902
|
const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
|
|
14706
14903
|
cwd: workspace,
|
|
14707
14904
|
env: { ...process.env, ...spaceEnv(opts.spaceDir) },
|
|
@@ -14710,7 +14907,7 @@ async function runEnvironmentCheck(opts) {
|
|
|
14710
14907
|
output = res.output;
|
|
14711
14908
|
if (res.code !== 0) ok = false;
|
|
14712
14909
|
} finally {
|
|
14713
|
-
await
|
|
14910
|
+
await import_promises3.default.rm(tmp, { force: true });
|
|
14714
14911
|
}
|
|
14715
14912
|
const checks = [];
|
|
14716
14913
|
for (const line of output.split("\n")) {
|
|
@@ -14732,7 +14929,7 @@ async function fixEnvironment(opts) {
|
|
|
14732
14929
|
const before = await runEnvironmentCheck(opts);
|
|
14733
14930
|
if (before.ok) return before;
|
|
14734
14931
|
const status = await checkDeployStatus(opts);
|
|
14735
|
-
const workspace =
|
|
14932
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14736
14933
|
const script = await hostGit(
|
|
14737
14934
|
workspace,
|
|
14738
14935
|
"cat-file",
|
|
@@ -14757,7 +14954,6 @@ async function fixEnvironment(opts) {
|
|
|
14757
14954
|
// auto: installs are in-scope of the owner's explicit "fix environment"
|
|
14758
14955
|
// request; the classifier still blocks anything beyond it.
|
|
14759
14956
|
permissionMode: "auto",
|
|
14760
|
-
model: "claude-opus-4-6",
|
|
14761
14957
|
includePartialMessages: true,
|
|
14762
14958
|
env: spaceEnv(opts.spaceDir),
|
|
14763
14959
|
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
@@ -14768,7 +14964,7 @@ async function fixEnvironment(opts) {
|
|
|
14768
14964
|
return runEnvironmentCheck(opts);
|
|
14769
14965
|
}
|
|
14770
14966
|
async function discardDeploySkill(opts) {
|
|
14771
|
-
const workspace =
|
|
14967
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14772
14968
|
await hostGit(workspace, "checkout", "--", ".").catch(() => null);
|
|
14773
14969
|
await hostGit(workspace, "clean", "-fd");
|
|
14774
14970
|
return { success: true };
|
|
@@ -14887,7 +15083,7 @@ var AGENTS_ROOT = ".agents/skills";
|
|
|
14887
15083
|
var CLAUDE_ROOT = ".claude/skills";
|
|
14888
15084
|
var CLI_ROOTS = [CLAUDE_ROOT, ".codex/skills"];
|
|
14889
15085
|
function globalAgentsRoot() {
|
|
14890
|
-
return
|
|
15086
|
+
return import_path6.default.join(import_os2.default.homedir(), ".agents", "skills");
|
|
14891
15087
|
}
|
|
14892
15088
|
var AGENTS_LINK_TARGET = "../.agents/skills";
|
|
14893
15089
|
var BRANCH_PREFIX = "skill/";
|
|
@@ -14897,11 +15093,11 @@ function summarize(raw, base) {
|
|
|
14897
15093
|
return { ...base, description: frontmatter.description, errors };
|
|
14898
15094
|
}
|
|
14899
15095
|
async function rootLinkState(root) {
|
|
14900
|
-
const linkPath =
|
|
14901
|
-
const
|
|
14902
|
-
if (!
|
|
14903
|
-
if (!
|
|
14904
|
-
const target = await
|
|
15096
|
+
const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15097
|
+
const stat2 = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15098
|
+
if (!stat2) return "missing";
|
|
15099
|
+
if (!stat2.isSymbolicLink()) return "blocked";
|
|
15100
|
+
const target = await import_promises4.default.readlink(linkPath).catch(() => "");
|
|
14905
15101
|
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
14906
15102
|
}
|
|
14907
15103
|
async function globalLinkState() {
|
|
@@ -14911,12 +15107,12 @@ async function globalLinkState() {
|
|
|
14911
15107
|
}
|
|
14912
15108
|
async function listGlobal() {
|
|
14913
15109
|
const root = globalAgentsRoot();
|
|
14914
|
-
const entries = await
|
|
15110
|
+
const entries = await import_promises4.default.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
14915
15111
|
const out = [];
|
|
14916
15112
|
for (const entry of entries) {
|
|
14917
15113
|
if (entry.isFile()) continue;
|
|
14918
|
-
const dir =
|
|
14919
|
-
const raw = await
|
|
15114
|
+
const dir = import_path6.default.join(root, entry.name);
|
|
15115
|
+
const raw = await import_promises4.default.readFile(import_path6.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
|
|
14920
15116
|
if (!raw) continue;
|
|
14921
15117
|
out.push(
|
|
14922
15118
|
summarize(raw, {
|
|
@@ -15085,7 +15281,7 @@ async function ensureSkillsWorktree(spaceDir, branch) {
|
|
|
15085
15281
|
} catch {
|
|
15086
15282
|
await hostGit(repo, "worktree", "prune").catch(() => {
|
|
15087
15283
|
});
|
|
15088
|
-
await
|
|
15284
|
+
await import_promises4.default.rm(worktree, { recursive: true, force: true });
|
|
15089
15285
|
await hostGit(
|
|
15090
15286
|
repo,
|
|
15091
15287
|
"worktree",
|
|
@@ -15168,11 +15364,11 @@ async function bundleFilesOnDisk(dir) {
|
|
|
15168
15364
|
const out = [];
|
|
15169
15365
|
const walk = async (current, prefix, depth) => {
|
|
15170
15366
|
if (depth > 3 || out.length >= 200) return;
|
|
15171
|
-
const entries = await
|
|
15367
|
+
const entries = await import_promises4.default.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
15172
15368
|
for (const entry of entries) {
|
|
15173
15369
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15174
15370
|
if (entry.isDirectory()) {
|
|
15175
|
-
await walk(
|
|
15371
|
+
await walk(import_path6.default.join(current, entry.name), rel, depth + 1);
|
|
15176
15372
|
} else if (rel !== "SKILL.md") {
|
|
15177
15373
|
out.push(rel);
|
|
15178
15374
|
}
|
|
@@ -15184,7 +15380,7 @@ async function bundleFilesOnDisk(dir) {
|
|
|
15184
15380
|
async function readSkillImpl(opts) {
|
|
15185
15381
|
if (opts.scope === "global") {
|
|
15186
15382
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15187
|
-
const content2 = await
|
|
15383
|
+
const content2 = await import_promises4.default.readFile(import_path6.default.join(dir, "SKILL.md"), "utf-8");
|
|
15188
15384
|
const { frontmatter: frontmatter2, errors: errors2 } = parseSkill(content2);
|
|
15189
15385
|
return {
|
|
15190
15386
|
name: opts.name,
|
|
@@ -15219,55 +15415,55 @@ async function readSkillImpl(opts) {
|
|
|
15219
15415
|
};
|
|
15220
15416
|
}
|
|
15221
15417
|
async function moveIntoAgents(from, to) {
|
|
15222
|
-
if (await
|
|
15418
|
+
if (await import_promises4.default.access(to).then(
|
|
15223
15419
|
() => true,
|
|
15224
15420
|
() => false
|
|
15225
15421
|
)) {
|
|
15226
15422
|
throw new Error(
|
|
15227
|
-
`${
|
|
15423
|
+
`${import_path6.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
|
|
15228
15424
|
);
|
|
15229
15425
|
}
|
|
15230
|
-
await
|
|
15426
|
+
await import_promises4.default.mkdir(import_path6.default.dirname(to), { recursive: true });
|
|
15231
15427
|
try {
|
|
15232
|
-
await
|
|
15428
|
+
await import_promises4.default.rename(from, to);
|
|
15233
15429
|
} catch {
|
|
15234
|
-
await
|
|
15235
|
-
await
|
|
15430
|
+
await import_promises4.default.cp(from, to, { recursive: true });
|
|
15431
|
+
await import_promises4.default.rm(from, { recursive: true, force: true });
|
|
15236
15432
|
}
|
|
15237
15433
|
}
|
|
15238
15434
|
async function migrateGlobal() {
|
|
15239
15435
|
const moved = [];
|
|
15240
15436
|
for (const root of CLI_ROOTS) {
|
|
15241
|
-
const dir =
|
|
15242
|
-
const
|
|
15243
|
-
if (!
|
|
15244
|
-
const entries = await
|
|
15437
|
+
const dir = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15438
|
+
const stat2 = await import_promises4.default.lstat(dir).catch(() => null);
|
|
15439
|
+
if (!stat2 || stat2.isSymbolicLink()) continue;
|
|
15440
|
+
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15245
15441
|
for (const entry of entries) {
|
|
15246
15442
|
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
15247
|
-
const src =
|
|
15248
|
-
if (!await
|
|
15443
|
+
const src = import_path6.default.join(dir, entry.name);
|
|
15444
|
+
if (!await import_promises4.default.access(import_path6.default.join(src, "SKILL.md")).then(
|
|
15249
15445
|
() => true,
|
|
15250
15446
|
() => false
|
|
15251
15447
|
)) {
|
|
15252
15448
|
continue;
|
|
15253
15449
|
}
|
|
15254
|
-
await moveIntoAgents(src,
|
|
15450
|
+
await moveIntoAgents(src, import_path6.default.join(globalAgentsRoot(), entry.name));
|
|
15255
15451
|
moved.push(entry.name);
|
|
15256
15452
|
}
|
|
15257
|
-
await
|
|
15453
|
+
await import_promises4.default.rmdir(dir).catch(() => {
|
|
15258
15454
|
});
|
|
15259
15455
|
}
|
|
15260
15456
|
for (const root of CLI_ROOTS) {
|
|
15261
|
-
const linkPath =
|
|
15262
|
-
const after = await
|
|
15263
|
-
if (after?.isSymbolicLink()) await
|
|
15457
|
+
const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15458
|
+
const after = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15459
|
+
if (after?.isSymbolicLink()) await import_promises4.default.rm(linkPath, { force: true });
|
|
15264
15460
|
else if (after) {
|
|
15265
15461
|
throw new Error(
|
|
15266
15462
|
`~/${root} still has files that are not skills. Empty it, then migrate.`
|
|
15267
15463
|
);
|
|
15268
15464
|
}
|
|
15269
|
-
await
|
|
15270
|
-
await
|
|
15465
|
+
await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
|
|
15466
|
+
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15271
15467
|
}
|
|
15272
15468
|
return moved;
|
|
15273
15469
|
}
|
|
@@ -15301,13 +15497,13 @@ async function migrateSkillsImpl(opts) {
|
|
|
15301
15497
|
`${name} exists in both ${AGENTS_ROOT} and ${stray}. Remove one copy, then migrate.`
|
|
15302
15498
|
);
|
|
15303
15499
|
}
|
|
15304
|
-
await
|
|
15500
|
+
await import_promises4.default.mkdir(import_path6.default.join(workspace, AGENTS_ROOT), { recursive: true });
|
|
15305
15501
|
await hostGit(workspace, "mv", stray, dest);
|
|
15306
15502
|
moved.push(name);
|
|
15307
15503
|
}
|
|
15308
|
-
const linkPath =
|
|
15309
|
-
await
|
|
15310
|
-
await
|
|
15504
|
+
const linkPath = import_path6.default.join(workspace, CLAUDE_ROOT);
|
|
15505
|
+
await import_promises4.default.rm(linkPath, { recursive: true, force: true });
|
|
15506
|
+
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15311
15507
|
await hostGit(workspace, "add", "-A");
|
|
15312
15508
|
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
15313
15509
|
if (!dirty.trim()) return { branch: "", moved: [] };
|
|
@@ -15333,16 +15529,16 @@ function assertWritable(name, content) {
|
|
|
15333
15529
|
}
|
|
15334
15530
|
async function writeSkillFile(opts) {
|
|
15335
15531
|
const dir = resolveSkillDir(opts.root, opts.name);
|
|
15336
|
-
const file =
|
|
15532
|
+
const file = import_path6.default.join(dir, "SKILL.md");
|
|
15337
15533
|
if (opts.keepExisting) {
|
|
15338
|
-
const exists = await
|
|
15534
|
+
const exists = await import_promises4.default.access(file).then(
|
|
15339
15535
|
() => true,
|
|
15340
15536
|
() => false
|
|
15341
15537
|
);
|
|
15342
15538
|
if (exists) return;
|
|
15343
15539
|
}
|
|
15344
|
-
await
|
|
15345
|
-
await
|
|
15540
|
+
await import_promises4.default.mkdir(dir, { recursive: true });
|
|
15541
|
+
await import_promises4.default.writeFile(file, opts.content, "utf-8");
|
|
15346
15542
|
}
|
|
15347
15543
|
async function writeGlobalSkill(opts) {
|
|
15348
15544
|
assertWritable(opts.name, opts.content);
|
|
@@ -15357,14 +15553,14 @@ async function installBundledSkill(opts) {
|
|
|
15357
15553
|
content: opts.content,
|
|
15358
15554
|
keepExisting: true
|
|
15359
15555
|
});
|
|
15360
|
-
await write(
|
|
15556
|
+
await write(import_path6.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15361
15557
|
for (const root of CLI_ROOTS) {
|
|
15362
|
-
const linkPath =
|
|
15363
|
-
const
|
|
15364
|
-
if (
|
|
15365
|
-
if (!
|
|
15366
|
-
await
|
|
15367
|
-
await
|
|
15558
|
+
const linkPath = import_path6.default.join(home, ...root.split("/"));
|
|
15559
|
+
const stat2 = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15560
|
+
if (stat2?.isSymbolicLink()) continue;
|
|
15561
|
+
if (!stat2) {
|
|
15562
|
+
await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
|
|
15563
|
+
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15368
15564
|
continue;
|
|
15369
15565
|
}
|
|
15370
15566
|
await write(linkPath);
|
|
@@ -15372,7 +15568,7 @@ async function installBundledSkill(opts) {
|
|
|
15372
15568
|
}
|
|
15373
15569
|
async function deleteGlobalSkill(opts) {
|
|
15374
15570
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15375
|
-
await
|
|
15571
|
+
await import_promises4.default.rm(dir, { recursive: true, force: true });
|
|
15376
15572
|
}
|
|
15377
15573
|
async function proposeRepoSkillImpl(opts) {
|
|
15378
15574
|
assertWritable(opts.name, opts.content);
|
|
@@ -15410,9 +15606,9 @@ async function proposeRepoSkillImpl(opts) {
|
|
|
15410
15606
|
if (opts.gitEmail) {
|
|
15411
15607
|
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
15412
15608
|
}
|
|
15413
|
-
await
|
|
15414
|
-
await
|
|
15415
|
-
|
|
15609
|
+
await import_promises4.default.mkdir(import_path6.default.join(workspace, dir), { recursive: true });
|
|
15610
|
+
await import_promises4.default.writeFile(
|
|
15611
|
+
import_path6.default.join(workspace, dir, "SKILL.md"),
|
|
15416
15612
|
opts.content,
|
|
15417
15613
|
"utf-8"
|
|
15418
15614
|
);
|
|
@@ -15519,18 +15715,18 @@ function vendoredBinaryName() {
|
|
|
15519
15715
|
async function installFrpcBinary(target) {
|
|
15520
15716
|
const p = frpcPaths(target);
|
|
15521
15717
|
const binaryName = vendoredBinaryName();
|
|
15522
|
-
const src =
|
|
15718
|
+
const src = import_path7.default.join(__dirname, "..", "vendor", binaryName);
|
|
15523
15719
|
if (!(0, import_fs2.existsSync)(src)) {
|
|
15524
15720
|
throw new Error(
|
|
15525
15721
|
`Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
|
|
15526
15722
|
);
|
|
15527
15723
|
}
|
|
15528
|
-
const staged = await
|
|
15529
|
-
const source = await
|
|
15724
|
+
const staged = await import_promises5.default.stat(p.binary).catch(() => null);
|
|
15725
|
+
const source = await import_promises5.default.stat(src);
|
|
15530
15726
|
if (staged?.size === source.size) return;
|
|
15531
|
-
await
|
|
15532
|
-
await
|
|
15533
|
-
await
|
|
15727
|
+
await import_promises5.default.mkdir(p.binDir, { recursive: true });
|
|
15728
|
+
await import_promises5.default.copyFile(src, p.binary);
|
|
15729
|
+
await import_promises5.default.chmod(p.binary, 493);
|
|
15534
15730
|
}
|
|
15535
15731
|
var EXPOSE_SKILL_NAME = "share-a-port";
|
|
15536
15732
|
var FENCE = "```";
|
|
@@ -15920,18 +16116,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
15920
16116
|
await this.core.ensureSpaceWorkspace();
|
|
15921
16117
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
15922
16118
|
if (hasGit) {
|
|
15923
|
-
const { path:
|
|
16119
|
+
const { path: path20 } = await addWorktree(
|
|
15924
16120
|
this.core.target(),
|
|
15925
16121
|
session.taskId,
|
|
15926
16122
|
baseBranch || config.mainBranch || "main",
|
|
15927
16123
|
"card"
|
|
15928
16124
|
);
|
|
15929
|
-
session.workspacePath =
|
|
16125
|
+
session.workspacePath = path20;
|
|
15930
16126
|
await setGitIdentity(
|
|
15931
16127
|
this.core.target(),
|
|
15932
16128
|
config.gitName,
|
|
15933
16129
|
config.gitEmail,
|
|
15934
|
-
|
|
16130
|
+
path20
|
|
15935
16131
|
);
|
|
15936
16132
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
15937
16133
|
} else {
|
|
@@ -16153,18 +16349,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16153
16349
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16154
16350
|
await this.core.ensureSpaceWorkspace();
|
|
16155
16351
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16156
|
-
const { path:
|
|
16352
|
+
const { path: path20 } = await addWorktree(
|
|
16157
16353
|
this.core.target(),
|
|
16158
16354
|
session.taskId,
|
|
16159
16355
|
config.mainBranch || "main",
|
|
16160
16356
|
"card"
|
|
16161
16357
|
);
|
|
16162
|
-
session.workspacePath =
|
|
16358
|
+
session.workspacePath = path20;
|
|
16163
16359
|
await setGitIdentity(
|
|
16164
16360
|
this.core.target(),
|
|
16165
16361
|
config.gitName,
|
|
16166
16362
|
config.gitEmail,
|
|
16167
|
-
|
|
16363
|
+
path20
|
|
16168
16364
|
);
|
|
16169
16365
|
await this.putCard(session);
|
|
16170
16366
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16711,12 +16907,12 @@ var FLUSH_MS = 120;
|
|
|
16711
16907
|
var SELF_WRITE_MS = 2e3;
|
|
16712
16908
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16713
16909
|
var importFs = new Function("s", "return import(s)");
|
|
16714
|
-
async function resolveReal(
|
|
16910
|
+
async function resolveReal(path20) {
|
|
16715
16911
|
try {
|
|
16716
16912
|
const { realpath } = await importFs("fs/promises");
|
|
16717
|
-
return await realpath(
|
|
16913
|
+
return await realpath(path20);
|
|
16718
16914
|
} catch {
|
|
16719
|
-
return
|
|
16915
|
+
return path20;
|
|
16720
16916
|
}
|
|
16721
16917
|
}
|
|
16722
16918
|
var loadSubscribe = null;
|
|
@@ -16830,19 +17026,19 @@ var WorkspaceWatcher = class {
|
|
|
16830
17026
|
}
|
|
16831
17027
|
record(kind, relPath) {
|
|
16832
17028
|
if (this.closed) return;
|
|
16833
|
-
const
|
|
16834
|
-
const expiry = this.selfWrites.get(
|
|
17029
|
+
const path20 = "/" + relPath;
|
|
17030
|
+
const expiry = this.selfWrites.get(path20);
|
|
16835
17031
|
if (expiry !== void 0) {
|
|
16836
17032
|
if (expiry > Date.now()) return;
|
|
16837
|
-
this.selfWrites.delete(
|
|
17033
|
+
this.selfWrites.delete(path20);
|
|
16838
17034
|
}
|
|
16839
17035
|
const { added, changed, removed } = this.pending;
|
|
16840
|
-
added.delete(
|
|
16841
|
-
changed.delete(
|
|
16842
|
-
removed.delete(
|
|
16843
|
-
if (kind === "added") added.add(
|
|
16844
|
-
else if (kind === "changed") changed.add(
|
|
16845
|
-
else removed.add(
|
|
17036
|
+
added.delete(path20);
|
|
17037
|
+
changed.delete(path20);
|
|
17038
|
+
removed.delete(path20);
|
|
17039
|
+
if (kind === "added") added.add(path20);
|
|
17040
|
+
else if (kind === "changed") changed.add(path20);
|
|
17041
|
+
else removed.add(path20);
|
|
16846
17042
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
16847
17043
|
this.pending = { ...emptyPending(), resync: true };
|
|
16848
17044
|
}
|
|
@@ -16861,8 +17057,8 @@ var WorkspaceWatcher = class {
|
|
|
16861
17057
|
this.pending = emptyPending();
|
|
16862
17058
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
16863
17059
|
const now = Date.now();
|
|
16864
|
-
for (const [
|
|
16865
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
17060
|
+
for (const [path20, expiry] of this.selfWrites) {
|
|
17061
|
+
if (expiry <= now) this.selfWrites.delete(path20);
|
|
16866
17062
|
}
|
|
16867
17063
|
this.emit({
|
|
16868
17064
|
added: resync ? [] : [...added],
|
|
@@ -17066,8 +17262,8 @@ var TerminalModeEngine = class {
|
|
|
17066
17262
|
/** Suppress the change event our own write is about to produce, so a save
|
|
17067
17263
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
17068
17264
|
* the watcher emits, since callers spell the path either way. */
|
|
17069
|
-
expectWrite(postId,
|
|
17070
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17265
|
+
expectWrite(postId, path20) {
|
|
17266
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path20));
|
|
17071
17267
|
}
|
|
17072
17268
|
// ── Lifecycle / ModeEngine ──
|
|
17073
17269
|
start(base, args) {
|
|
@@ -17264,18 +17460,18 @@ var TerminalModeEngine = class {
|
|
|
17264
17460
|
this.reservedNames.values()
|
|
17265
17461
|
);
|
|
17266
17462
|
this.reservedNames.set(session.postId, workName);
|
|
17267
|
-
const { path:
|
|
17463
|
+
const { path: path20 } = await addWorktree(
|
|
17268
17464
|
this.core.target(),
|
|
17269
17465
|
workName,
|
|
17270
17466
|
branch,
|
|
17271
17467
|
"terminal"
|
|
17272
17468
|
);
|
|
17273
|
-
session.workspacePath =
|
|
17469
|
+
session.workspacePath = path20;
|
|
17274
17470
|
await setGitIdentity(
|
|
17275
17471
|
this.core.target(),
|
|
17276
17472
|
config.gitName,
|
|
17277
17473
|
config.gitEmail,
|
|
17278
|
-
|
|
17474
|
+
path20
|
|
17279
17475
|
);
|
|
17280
17476
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17281
17477
|
} else {
|
|
@@ -17337,11 +17533,11 @@ var TerminalModeEngine = class {
|
|
|
17337
17533
|
}
|
|
17338
17534
|
async bareFsList({
|
|
17339
17535
|
postId,
|
|
17340
|
-
path:
|
|
17536
|
+
path: path20
|
|
17341
17537
|
}) {
|
|
17342
17538
|
const target = this.getBareTarget(postId);
|
|
17343
17539
|
const root = this.getBareRoot(postId);
|
|
17344
|
-
const abs = this.resolveBarePath(
|
|
17540
|
+
const abs = this.resolveBarePath(path20, root);
|
|
17345
17541
|
const relRoot = abs.slice(root.length) || "/";
|
|
17346
17542
|
const out = await target.run([
|
|
17347
17543
|
"sh",
|
|
@@ -17391,30 +17587,30 @@ var TerminalModeEngine = class {
|
|
|
17391
17587
|
}
|
|
17392
17588
|
async bareFsRead({
|
|
17393
17589
|
postId,
|
|
17394
|
-
path:
|
|
17590
|
+
path: path20
|
|
17395
17591
|
}) {
|
|
17396
17592
|
const target = this.getBareTarget(postId);
|
|
17397
|
-
const abs = this.resolveBarePath(
|
|
17593
|
+
const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
|
|
17398
17594
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17399
17595
|
'`)).trim();
|
|
17400
17596
|
return { content: b64, encoding: "base64" };
|
|
17401
17597
|
}
|
|
17402
17598
|
async bareFsWrite({
|
|
17403
17599
|
postId,
|
|
17404
|
-
path:
|
|
17600
|
+
path: path20,
|
|
17405
17601
|
content,
|
|
17406
17602
|
encoding
|
|
17407
17603
|
}) {
|
|
17408
17604
|
const target = this.getBareTarget(postId);
|
|
17409
|
-
const abs = this.resolveBarePath(
|
|
17605
|
+
const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
|
|
17410
17606
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17411
17607
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17412
17608
|
throw new Error("Invalid base64 payload.");
|
|
17413
17609
|
}
|
|
17414
|
-
this.expectWrite(String(postId),
|
|
17610
|
+
this.expectWrite(String(postId), path20);
|
|
17415
17611
|
await writeBase64File(target, abs, b64);
|
|
17416
17612
|
const session = this.sessions.get(String(postId));
|
|
17417
|
-
if (session) delete session.buffers[
|
|
17613
|
+
if (session) delete session.buffers[path20];
|
|
17418
17614
|
}
|
|
17419
17615
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17420
17616
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17464,39 +17660,39 @@ var TerminalModeEngine = class {
|
|
|
17464
17660
|
}
|
|
17465
17661
|
bareOpenFile({
|
|
17466
17662
|
postId: rawPostId,
|
|
17467
|
-
path:
|
|
17663
|
+
path: path20
|
|
17468
17664
|
}) {
|
|
17469
17665
|
const session = this.sessions.get(String(rawPostId));
|
|
17470
17666
|
if (!session || session.mode !== "bare") return;
|
|
17471
|
-
if (!session.openedFiles.includes(
|
|
17472
|
-
session.openedFiles.push(
|
|
17667
|
+
if (!session.openedFiles.includes(path20)) {
|
|
17668
|
+
session.openedFiles.push(path20);
|
|
17473
17669
|
}
|
|
17474
17670
|
}
|
|
17475
17671
|
bareCloseFile({
|
|
17476
17672
|
postId: rawPostId,
|
|
17477
|
-
path:
|
|
17673
|
+
path: path20
|
|
17478
17674
|
}) {
|
|
17479
17675
|
const session = this.sessions.get(String(rawPostId));
|
|
17480
17676
|
if (!session || session.mode !== "bare") return;
|
|
17481
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17482
|
-
delete session.buffers[
|
|
17677
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path20);
|
|
17678
|
+
delete session.buffers[path20];
|
|
17483
17679
|
}
|
|
17484
17680
|
bareBufferSet({
|
|
17485
17681
|
postId: rawPostId,
|
|
17486
|
-
path:
|
|
17682
|
+
path: path20,
|
|
17487
17683
|
content
|
|
17488
17684
|
}) {
|
|
17489
17685
|
const session = this.sessions.get(String(rawPostId));
|
|
17490
17686
|
if (!session || session.mode !== "bare") return;
|
|
17491
|
-
session.buffers[
|
|
17687
|
+
session.buffers[path20] = content;
|
|
17492
17688
|
}
|
|
17493
17689
|
async bareFsDelete({
|
|
17494
17690
|
postId,
|
|
17495
|
-
path:
|
|
17691
|
+
path: path20
|
|
17496
17692
|
}) {
|
|
17497
17693
|
const target = this.getBareTarget(postId);
|
|
17498
17694
|
const root = this.getBareRoot(postId);
|
|
17499
|
-
const abs = this.resolveBarePath(
|
|
17695
|
+
const abs = this.resolveBarePath(path20, root);
|
|
17500
17696
|
if (abs === root) {
|
|
17501
17697
|
throw new Error("Refusing to delete the repo root.");
|
|
17502
17698
|
}
|
|
@@ -17504,10 +17700,10 @@ var TerminalModeEngine = class {
|
|
|
17504
17700
|
}
|
|
17505
17701
|
async bareFsMkdir({
|
|
17506
17702
|
postId,
|
|
17507
|
-
path:
|
|
17703
|
+
path: path20
|
|
17508
17704
|
}) {
|
|
17509
17705
|
const target = this.getBareTarget(postId);
|
|
17510
|
-
const abs = this.resolveBarePath(
|
|
17706
|
+
const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
|
|
17511
17707
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17512
17708
|
}
|
|
17513
17709
|
async bareReview({
|
|
@@ -18189,8 +18385,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
18189
18385
|
};
|
|
18190
18386
|
}
|
|
18191
18387
|
}
|
|
18192
|
-
async function githubApi(token,
|
|
18193
|
-
const res = await fetch(`${API_BASE}${
|
|
18388
|
+
async function githubApi(token, path20) {
|
|
18389
|
+
const res = await fetch(`${API_BASE}${path20}`, {
|
|
18194
18390
|
headers: {
|
|
18195
18391
|
Authorization: `Bearer ${token}`,
|
|
18196
18392
|
Accept: "application/vnd.github+json",
|
|
@@ -18199,7 +18395,7 @@ async function githubApi(token, path18) {
|
|
|
18199
18395
|
});
|
|
18200
18396
|
if (!res.ok) {
|
|
18201
18397
|
throw new Error(
|
|
18202
|
-
`GitHub ${
|
|
18398
|
+
`GitHub ${path20} failed (${res.status}): ${await res.text()}`
|
|
18203
18399
|
);
|
|
18204
18400
|
}
|
|
18205
18401
|
return await res.json();
|
|
@@ -18244,13 +18440,13 @@ async function listGithubBranches(token, fullName) {
|
|
|
18244
18440
|
}
|
|
18245
18441
|
|
|
18246
18442
|
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
18247
|
-
var
|
|
18443
|
+
var import_path9 = __toESM(require("path"));
|
|
18248
18444
|
|
|
18249
18445
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
18250
18446
|
var import_child_process4 = require("child_process");
|
|
18251
18447
|
var import_crypto2 = __toESM(require("crypto"));
|
|
18252
|
-
var
|
|
18253
|
-
var
|
|
18448
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
18449
|
+
var import_path8 = __toESM(require("path"));
|
|
18254
18450
|
var import_util6 = require("util");
|
|
18255
18451
|
var execFileAsync3 = (0, import_util6.promisify)(import_child_process4.execFile);
|
|
18256
18452
|
var IDENTITY_FILE = "age-identity.enc";
|
|
@@ -18283,7 +18479,7 @@ function deriveKey(password, salt) {
|
|
|
18283
18479
|
}
|
|
18284
18480
|
async function secretsInitialized(configDir) {
|
|
18285
18481
|
try {
|
|
18286
|
-
await
|
|
18482
|
+
await import_promises6.default.access(import_path8.default.join(configDir, IDENTITY_FILE));
|
|
18287
18483
|
return true;
|
|
18288
18484
|
} catch {
|
|
18289
18485
|
return false;
|
|
@@ -18322,10 +18518,10 @@ async function initSecrets(configDir, password) {
|
|
|
18322
18518
|
tag: cipher.getAuthTag().toString("base64"),
|
|
18323
18519
|
data: data.toString("base64")
|
|
18324
18520
|
};
|
|
18325
|
-
await
|
|
18521
|
+
await import_promises6.default.writeFile(import_path8.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
|
|
18326
18522
|
mode: 384
|
|
18327
18523
|
});
|
|
18328
|
-
await
|
|
18524
|
+
await import_promises6.default.writeFile(import_path8.default.join(configDir, RECIPIENT_FILE), `${recipient}
|
|
18329
18525
|
`, {
|
|
18330
18526
|
mode: 384
|
|
18331
18527
|
});
|
|
@@ -18348,17 +18544,17 @@ function decryptIdentity(box, password) {
|
|
|
18348
18544
|
}
|
|
18349
18545
|
}
|
|
18350
18546
|
function envPath(spaceDir) {
|
|
18351
|
-
return
|
|
18547
|
+
return import_path8.default.join(spaceDir, "secrets", ENV_FILE);
|
|
18352
18548
|
}
|
|
18353
18549
|
async function identityFor(configDir, password) {
|
|
18354
18550
|
const box = JSON.parse(
|
|
18355
|
-
await
|
|
18551
|
+
await import_promises6.default.readFile(import_path8.default.join(configDir, IDENTITY_FILE), "utf-8")
|
|
18356
18552
|
);
|
|
18357
18553
|
return decryptIdentity(box, password);
|
|
18358
18554
|
}
|
|
18359
18555
|
async function readEnv(spaceDir, identity) {
|
|
18360
18556
|
try {
|
|
18361
|
-
await
|
|
18557
|
+
await import_promises6.default.access(envPath(spaceDir));
|
|
18362
18558
|
} catch {
|
|
18363
18559
|
return {};
|
|
18364
18560
|
}
|
|
@@ -18387,14 +18583,14 @@ async function readEnv(spaceDir, identity) {
|
|
|
18387
18583
|
return values;
|
|
18388
18584
|
}
|
|
18389
18585
|
async function writeEnv(configDir, spaceDir, identity, values) {
|
|
18390
|
-
const recipient = (await
|
|
18586
|
+
const recipient = (await import_promises6.default.readFile(import_path8.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
|
|
18391
18587
|
const plain = `${Object.entries(values).map(([k, v]) => `${k}=${v}`).join("\n")}
|
|
18392
18588
|
`;
|
|
18393
18589
|
const tmp = envPath(spaceDir).replace(
|
|
18394
18590
|
/\.enc\.env$/,
|
|
18395
18591
|
`.${process.pid}.tmp.env`
|
|
18396
18592
|
);
|
|
18397
|
-
await
|
|
18593
|
+
await import_promises6.default.writeFile(tmp, plain, { mode: 384 });
|
|
18398
18594
|
try {
|
|
18399
18595
|
const { stdout } = await execFileAsync3(
|
|
18400
18596
|
"sops",
|
|
@@ -18413,9 +18609,9 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
18413
18609
|
maxBuffer: 4 * 1024 * 1024
|
|
18414
18610
|
}
|
|
18415
18611
|
);
|
|
18416
|
-
await
|
|
18612
|
+
await import_promises6.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
|
|
18417
18613
|
} finally {
|
|
18418
|
-
await
|
|
18614
|
+
await import_promises6.default.rm(tmp, { force: true });
|
|
18419
18615
|
}
|
|
18420
18616
|
}
|
|
18421
18617
|
async function listSecretKeys(configDir, spaceDir, password) {
|
|
@@ -18451,18 +18647,9 @@ var SECRETS_MARKER = "NEED_SECRETS:";
|
|
|
18451
18647
|
var COMPLETE_MARKER = "DEPLOY_COMPLETE:";
|
|
18452
18648
|
var FAILED_MARKER = "DEPLOY_FAILED:";
|
|
18453
18649
|
var MAX_MARKERLESS_TURNS = 3;
|
|
18454
|
-
var
|
|
18650
|
+
var COMMAND_IDLE_TIMEOUT_MS = 45 * 6e4;
|
|
18651
|
+
var COMMAND_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
18455
18652
|
var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18456
|
-
function redactor(values) {
|
|
18457
|
-
const significant = values.filter((v) => v.length >= 4);
|
|
18458
|
-
return (text) => {
|
|
18459
|
-
let out = text;
|
|
18460
|
-
for (const value2 of significant) {
|
|
18461
|
-
out = out.split(value2).join("[REDACTED]");
|
|
18462
|
-
}
|
|
18463
|
-
return out;
|
|
18464
|
-
};
|
|
18465
|
-
}
|
|
18466
18653
|
function tailMarker(text) {
|
|
18467
18654
|
const zone = text.slice(-2e3);
|
|
18468
18655
|
let best = null;
|
|
@@ -18479,10 +18666,53 @@ function tailMarker(text) {
|
|
|
18479
18666
|
const value2 = zone.slice(best.at + best.marker.length).trim();
|
|
18480
18667
|
return value2 ? { marker: best.marker, value: value2 } : null;
|
|
18481
18668
|
}
|
|
18669
|
+
function containedCwd(cwd, spaceDir) {
|
|
18670
|
+
if (!cwd) return null;
|
|
18671
|
+
const resolved = import_path9.default.resolve(cwd);
|
|
18672
|
+
const root = import_path9.default.resolve(spaceDir);
|
|
18673
|
+
return resolved === root || resolved.startsWith(root + import_path9.default.sep) ? resolved : null;
|
|
18674
|
+
}
|
|
18675
|
+
async function authorizeAndRun(opts) {
|
|
18676
|
+
const { command, hooks } = opts;
|
|
18677
|
+
opts.status?.("waiting for the owner to authorize this command");
|
|
18678
|
+
let secrets = null;
|
|
18679
|
+
let authError = "";
|
|
18680
|
+
while (!secrets) {
|
|
18681
|
+
const auth = await hooks.requestAuth(command, authError);
|
|
18682
|
+
if ("denied" in auth) return { denied: true };
|
|
18683
|
+
try {
|
|
18684
|
+
secrets = await secretsForInjection(
|
|
18685
|
+
opts.configDir,
|
|
18686
|
+
opts.spaceDir,
|
|
18687
|
+
auth.password
|
|
18688
|
+
);
|
|
18689
|
+
} catch (err) {
|
|
18690
|
+
authError = err instanceof Error ? err.message : String(err);
|
|
18691
|
+
opts.status?.(`authorization failed: ${authError}`);
|
|
18692
|
+
}
|
|
18693
|
+
}
|
|
18694
|
+
const redact = redactor(Object.values(secrets));
|
|
18695
|
+
opts.status?.("authorized, running");
|
|
18696
|
+
hooks.sendLog({
|
|
18697
|
+
type: "system",
|
|
18698
|
+
content: "Authorized. Running the command with secrets injected..."
|
|
18699
|
+
});
|
|
18700
|
+
const res = await execLogin(command, {
|
|
18701
|
+
cwd: opts.cwd,
|
|
18702
|
+
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...secrets },
|
|
18703
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
18704
|
+
idleTimeout: COMMAND_IDLE_TIMEOUT_MS,
|
|
18705
|
+
maxBuffer: 32 * 1024 * 1024
|
|
18706
|
+
});
|
|
18707
|
+
const output = redact(res.output);
|
|
18708
|
+
hooks.sendLog({ type: "tool", content: `$ ${command}
|
|
18709
|
+
${tail(output)}` });
|
|
18710
|
+
return { code: res.code, output };
|
|
18711
|
+
}
|
|
18482
18712
|
async function startDeployRun(opts) {
|
|
18483
18713
|
const { hooks } = opts;
|
|
18484
|
-
const workspace =
|
|
18485
|
-
const configDir =
|
|
18714
|
+
const workspace = import_path9.default.join(opts.spaceDir, "workspace");
|
|
18715
|
+
const configDir = import_path9.default.dirname(opts.spaceDir);
|
|
18486
18716
|
const status = await checkDeployStatus(opts);
|
|
18487
18717
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
18488
18718
|
throw new Error(
|
|
@@ -18502,119 +18732,120 @@ async function startDeployRun(opts) {
|
|
|
18502
18732
|
);
|
|
18503
18733
|
await hostGit(workspace, "clean", "-fd");
|
|
18504
18734
|
await ensureClaudeSkillLink(workspace);
|
|
18735
|
+
const release = (opts.releaseBranch || "").replace(/[^\w./-]/g, "");
|
|
18736
|
+
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.` : "";
|
|
18737
|
+
if (releaseNote) {
|
|
18738
|
+
hooks.sendLog({
|
|
18739
|
+
type: "system",
|
|
18740
|
+
content: `Release branch: ${release}`
|
|
18741
|
+
});
|
|
18742
|
+
}
|
|
18505
18743
|
const contract = prompts_default.deployRun.join("\n");
|
|
18506
|
-
const reminder = "Reminder: every declared deploy variable is intentionally unset in your shell. Never check for them or abort over them -
|
|
18507
|
-
|
|
18508
|
-
|
|
18509
|
-
${reminder}` : `/deploy
|
|
18510
|
-
|
|
18511
|
-
${reminder}`;
|
|
18744
|
+
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.";
|
|
18745
|
+
const opening = opts.provider === "codex" ? 'Use the "deploy" skill: load it and execute it step by step.' : "/deploy";
|
|
18746
|
+
let prompt2 = [opening, releaseNote, reminder].filter(Boolean).join("\n\n");
|
|
18512
18747
|
let sessionId = "";
|
|
18513
18748
|
let markerless = 0;
|
|
18514
18749
|
let todos = [];
|
|
18515
|
-
|
|
18516
|
-
|
|
18517
|
-
|
|
18518
|
-
|
|
18519
|
-
|
|
18520
|
-
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
//
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
|
|
18750
|
+
const bridge = await startSecretsBridge({
|
|
18751
|
+
spaceDir: opts.spaceDir,
|
|
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
|
+
});
|
|
18770
|
+
try {
|
|
18771
|
+
for (; ; ) {
|
|
18772
|
+
if (hooks.isCancelled()) throw new Error("Cancelled by user.");
|
|
18773
|
+
const { proc, result } = await spawnAgent(
|
|
18774
|
+
opts.provider,
|
|
18775
|
+
hostTarget(opts.spaceDir),
|
|
18776
|
+
{
|
|
18777
|
+
cwd: workspace,
|
|
18778
|
+
prompt: prompt2,
|
|
18779
|
+
allowedTools: "Bash,Read,Grep,Glob,TodoWrite,WebSearch,WebFetch,Skill",
|
|
18780
|
+
// The run's guardrails are structural (merge-gated skill, per-command
|
|
18781
|
+
// authorization, runner-side injection); the auto classifier fights
|
|
18782
|
+
// legitimate infrastructure commands, so the session itself runs open.
|
|
18783
|
+
permissionMode: "bypassPermissions",
|
|
18784
|
+
includePartialMessages: true,
|
|
18785
|
+
resumeSessionId: sessionId || void 0,
|
|
18786
|
+
appendSystemPrompt: sessionId ? void 0 : contract,
|
|
18787
|
+
env: spaceEnv(opts.spaceDir),
|
|
18788
|
+
callbacks: {
|
|
18789
|
+
initialTodos: todos,
|
|
18790
|
+
onLog: (entry) => hooks.sendLog(entry),
|
|
18791
|
+
onTodos: (updated) => {
|
|
18792
|
+
todos = updated;
|
|
18793
|
+
hooks.onTodos(updated);
|
|
18794
|
+
}
|
|
18539
18795
|
}
|
|
18540
18796
|
}
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
|
|
18550
|
-
|
|
18551
|
-
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
try {
|
|
18561
|
-
secrets = await secretsForInjection(
|
|
18562
|
-
configDir,
|
|
18563
|
-
opts.spaceDir,
|
|
18564
|
-
auth.password
|
|
18565
|
-
);
|
|
18566
|
-
} catch (err) {
|
|
18567
|
-
authError = err instanceof Error ? err.message : String(err);
|
|
18797
|
+
);
|
|
18798
|
+
hooks.registerProc(proc);
|
|
18799
|
+
const { text, sessionId: sid } = await result;
|
|
18800
|
+
hooks.registerProc(null);
|
|
18801
|
+
sessionId = sid || sessionId;
|
|
18802
|
+
if (hooks.isCancelled()) throw new Error("Cancelled by user.");
|
|
18803
|
+
const found = tailMarker(text);
|
|
18804
|
+
if (found?.marker === SECRETS_MARKER) {
|
|
18805
|
+
const command = found.value.split("\n")[0].trim();
|
|
18806
|
+
const res = await authorizeAndRun({
|
|
18807
|
+
configDir,
|
|
18808
|
+
spaceDir: opts.spaceDir,
|
|
18809
|
+
cwd: workspace,
|
|
18810
|
+
command,
|
|
18811
|
+
hooks
|
|
18812
|
+
});
|
|
18813
|
+
if ("denied" in res) {
|
|
18814
|
+
prompt2 = "The owner declined to run that command. Adapt or stop and summarize where the release stands.";
|
|
18815
|
+
continue;
|
|
18568
18816
|
}
|
|
18817
|
+
markerless = 0;
|
|
18818
|
+
const clean = tail(res.output);
|
|
18819
|
+
prompt2 = `The authorized command finished (exit ${res.code}). Redacted output:
|
|
18820
|
+
|
|
18821
|
+
${clean}`;
|
|
18822
|
+
continue;
|
|
18569
18823
|
}
|
|
18570
|
-
if (
|
|
18571
|
-
|
|
18824
|
+
if (found?.marker === HUMAN_MARKER) {
|
|
18825
|
+
markerless = 0;
|
|
18826
|
+
prompt2 = await hooks.requestHuman(found.value);
|
|
18572
18827
|
continue;
|
|
18573
18828
|
}
|
|
18574
|
-
|
|
18829
|
+
if (found?.marker === COMPLETE_MARKER) {
|
|
18830
|
+
return { shipped: true, summary: found.value };
|
|
18831
|
+
}
|
|
18832
|
+
if (found?.marker === FAILED_MARKER) {
|
|
18833
|
+
return { shipped: false, summary: found.value };
|
|
18834
|
+
}
|
|
18835
|
+
markerless += 1;
|
|
18836
|
+
if (markerless >= MAX_MARKERLESS_TURNS) {
|
|
18837
|
+
throw new Error(
|
|
18838
|
+
"The run ended without declaring completion (no DEPLOY_COMPLETE after repeated turns)."
|
|
18839
|
+
);
|
|
18840
|
+
}
|
|
18575
18841
|
hooks.sendLog({
|
|
18576
18842
|
type: "system",
|
|
18577
|
-
content:
|
|
18578
|
-
});
|
|
18579
|
-
const res = await execLogin(command, {
|
|
18580
|
-
cwd: workspace,
|
|
18581
|
-
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...secrets },
|
|
18582
|
-
timeout: COMMAND_TIMEOUT_MS,
|
|
18583
|
-
maxBuffer: 32 * 1024 * 1024
|
|
18843
|
+
content: "Turn ended without a protocol marker - resuming the run..."
|
|
18584
18844
|
});
|
|
18585
|
-
|
|
18586
|
-
const exitNote = `exit ${res.code}`;
|
|
18587
|
-
markerless = 0;
|
|
18588
|
-
const clean = redact(tail(output));
|
|
18589
|
-
hooks.sendLog({ type: "tool", content: `$ ${command}
|
|
18590
|
-
${clean}` });
|
|
18591
|
-
prompt2 = `The authorized command finished (${exitNote}). Redacted output:
|
|
18592
|
-
|
|
18593
|
-
${clean}`;
|
|
18594
|
-
continue;
|
|
18595
|
-
}
|
|
18596
|
-
if (found?.marker === HUMAN_MARKER) {
|
|
18597
|
-
markerless = 0;
|
|
18598
|
-
prompt2 = await hooks.requestHuman(found.value);
|
|
18599
|
-
continue;
|
|
18845
|
+
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.";
|
|
18600
18846
|
}
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
}
|
|
18604
|
-
if (found?.marker === FAILED_MARKER) {
|
|
18605
|
-
return { shipped: false, summary: found.value };
|
|
18606
|
-
}
|
|
18607
|
-
markerless += 1;
|
|
18608
|
-
if (markerless >= MAX_MARKERLESS_TURNS) {
|
|
18609
|
-
throw new Error(
|
|
18610
|
-
"The run ended without declaring completion (no DEPLOY_COMPLETE after repeated turns)."
|
|
18611
|
-
);
|
|
18612
|
-
}
|
|
18613
|
-
hooks.sendLog({
|
|
18614
|
-
type: "system",
|
|
18615
|
-
content: "Turn ended without a protocol marker - resuming the run..."
|
|
18616
|
-
});
|
|
18617
|
-
prompt2 = "Your turn ended with no protocol marker. If the release fully shipped, end your message with DEPLOY_COMPLETE: followed by your run report; if you are stopping without shipping, end with DEPLOY_FAILED: followed by the report. Otherwise CONTINUE executing the skill from where you left off. If you had background tasks running, they were killed when your turn ended - rerun them, and reap every background task before ending a turn.";
|
|
18847
|
+
} finally {
|
|
18848
|
+
await bridge.close().catch(() => void 0);
|
|
18618
18849
|
}
|
|
18619
18850
|
}
|
|
18620
18851
|
|
|
@@ -18706,44 +18937,110 @@ async function getDriveRoot(accessToken) {
|
|
|
18706
18937
|
};
|
|
18707
18938
|
}
|
|
18708
18939
|
|
|
18940
|
+
// ../../shared/all/helpers/board-agent-core/space-core.ts
|
|
18941
|
+
var import_promises8 = require("fs/promises");
|
|
18942
|
+
var import_path11 = __toESM(require("path"));
|
|
18943
|
+
|
|
18709
18944
|
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
18945
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
18710
18946
|
var import_fs3 = require("fs");
|
|
18711
|
-
var
|
|
18947
|
+
var import_promises7 = require("fs/promises");
|
|
18712
18948
|
var import_os3 = __toESM(require("os"));
|
|
18713
|
-
var
|
|
18714
|
-
var
|
|
18949
|
+
var import_path10 = __toESM(require("path"));
|
|
18950
|
+
var MARKETPLACE_NAME = "factiii-runner";
|
|
18951
|
+
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
18952
|
+
var SECRETS_BIN = "factiii-secrets";
|
|
18715
18953
|
function candidates() {
|
|
18716
18954
|
return [
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18955
|
+
import_path10.default.join(__dirname, "..", "plugin"),
|
|
18956
|
+
import_path10.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
18957
|
+
import_path10.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
18720
18958
|
];
|
|
18721
18959
|
}
|
|
18722
18960
|
function pluginRoot() {
|
|
18723
18961
|
for (const dir of candidates()) {
|
|
18724
|
-
if ((0, import_fs3.existsSync)(
|
|
18962
|
+
if ((0, import_fs3.existsSync)(import_path10.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
18725
18963
|
return dir;
|
|
18726
18964
|
}
|
|
18727
18965
|
}
|
|
18728
18966
|
return null;
|
|
18729
18967
|
}
|
|
18730
18968
|
function renderedPluginRoot() {
|
|
18731
|
-
return
|
|
18969
|
+
return import_path10.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
18970
|
+
}
|
|
18971
|
+
async function runnerVersion(source) {
|
|
18972
|
+
const pkg = import_path10.default.join(source, "..", "package.json");
|
|
18973
|
+
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
18974
|
+
if (!raw) return FALLBACK_VERSION;
|
|
18975
|
+
try {
|
|
18976
|
+
const { version } = JSON.parse(raw);
|
|
18977
|
+
return version || FALLBACK_VERSION;
|
|
18978
|
+
} catch {
|
|
18979
|
+
return FALLBACK_VERSION;
|
|
18980
|
+
}
|
|
18981
|
+
}
|
|
18982
|
+
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
18983
|
+
async function treeFiles(dir) {
|
|
18984
|
+
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
18985
|
+
const files = [];
|
|
18986
|
+
for (const entry of entries) {
|
|
18987
|
+
const full = import_path10.default.join(dir, entry.name);
|
|
18988
|
+
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
18989
|
+
else if (entry.isFile()) files.push(full);
|
|
18990
|
+
}
|
|
18991
|
+
return files.sort();
|
|
18992
|
+
}
|
|
18993
|
+
async function fingerprint(source, url2, version = "") {
|
|
18994
|
+
const hash = import_crypto3.default.createHash("sha256").update(url2).update(version);
|
|
18995
|
+
for (const file of await treeFiles(source)) {
|
|
18996
|
+
hash.update(import_path10.default.relative(source, file));
|
|
18997
|
+
hash.update(await (0, import_promises7.readFile)(file));
|
|
18998
|
+
}
|
|
18999
|
+
return hash.digest("hex");
|
|
19000
|
+
}
|
|
19001
|
+
function stampPath(dest) {
|
|
19002
|
+
return `${dest}.stamp`;
|
|
19003
|
+
}
|
|
19004
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
19005
|
+
function renderPlugin(boardMcpUrl) {
|
|
19006
|
+
const running = inFlight.get(boardMcpUrl);
|
|
19007
|
+
if (running) return running;
|
|
19008
|
+
const started = renderPluginImpl(boardMcpUrl).finally(
|
|
19009
|
+
() => inFlight.delete(boardMcpUrl)
|
|
19010
|
+
);
|
|
19011
|
+
inFlight.set(boardMcpUrl, started);
|
|
19012
|
+
return started;
|
|
18732
19013
|
}
|
|
18733
|
-
async function
|
|
19014
|
+
async function renderPluginImpl(boardMcpUrl) {
|
|
18734
19015
|
const source = pluginRoot();
|
|
18735
19016
|
if (!source) return null;
|
|
18736
19017
|
const dest = renderedPluginRoot();
|
|
18737
|
-
const
|
|
18738
|
-
const want =
|
|
18739
|
-
|
|
18740
|
-
const have = await (0, import_promises6.readFile)(manifest, "utf8").catch(() => null);
|
|
19018
|
+
const version = await runnerVersion(source);
|
|
19019
|
+
const want = await fingerprint(source, boardMcpUrl, version);
|
|
19020
|
+
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
18741
19021
|
if (have === want) return dest;
|
|
18742
|
-
await (0,
|
|
18743
|
-
await (0,
|
|
18744
|
-
await (0,
|
|
19022
|
+
await (0, import_promises7.mkdir)(import_path10.default.dirname(dest), { recursive: true });
|
|
19023
|
+
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
19024
|
+
await (0, import_promises7.writeFile)(
|
|
19025
|
+
import_path10.default.join(dest, "factiii", ".mcp.json"),
|
|
19026
|
+
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
19027
|
+
`
|
|
19028
|
+
);
|
|
19029
|
+
await stampVersion(dest, version);
|
|
19030
|
+
await (0, import_promises7.writeFile)(stampPath(dest), want);
|
|
18745
19031
|
return dest;
|
|
18746
19032
|
}
|
|
19033
|
+
async function stampVersion(dest, version) {
|
|
19034
|
+
const manifest = import_path10.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
19035
|
+
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
19036
|
+
if (!raw) return;
|
|
19037
|
+
const parsed = JSON.parse(raw);
|
|
19038
|
+
await (0, import_promises7.writeFile)(
|
|
19039
|
+
manifest,
|
|
19040
|
+
`${JSON.stringify({ ...parsed, version }, null, 2)}
|
|
19041
|
+
`
|
|
19042
|
+
);
|
|
19043
|
+
}
|
|
18747
19044
|
function boardMcpConfig(url2) {
|
|
18748
19045
|
return {
|
|
18749
19046
|
mcpServers: {
|
|
@@ -18987,6 +19284,8 @@ var SpaceCore = class {
|
|
|
18987
19284
|
this.execTarget ??= hostTarget(this.spaceDir(), () => {
|
|
18988
19285
|
const env = {};
|
|
18989
19286
|
for (const lane of this.mcpLanes()) env[lane.tokenEnv] = lane.mcp.token;
|
|
19287
|
+
const board = this.boardMcp();
|
|
19288
|
+
if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
|
|
18990
19289
|
return env;
|
|
18991
19290
|
});
|
|
18992
19291
|
return this.execTarget;
|
|
@@ -19167,6 +19466,7 @@ var SpaceCore = class {
|
|
|
19167
19466
|
this.provisionGitLane(status),
|
|
19168
19467
|
this.provisionMcpLane(),
|
|
19169
19468
|
this.provisionPluginLane(),
|
|
19469
|
+
this.provisionBinLane(),
|
|
19170
19470
|
// Covers a host signed in outside the one-click button, which would
|
|
19171
19471
|
// otherwise meet the wizard the first time it opens a terminal.
|
|
19172
19472
|
markOnboardingComplete(target).catch(() => {
|
|
@@ -19227,6 +19527,13 @@ var SpaceCore = class {
|
|
|
19227
19527
|
const cmd = [
|
|
19228
19528
|
`claude plugin marketplace add ${q} >/dev/null 2>&1 || true`,
|
|
19229
19529
|
`claude plugin install ${PLUGIN_ID} --scope user >/dev/null 2>&1 || true`,
|
|
19530
|
+
// Both CLIs install by COPYING the plugin into a cache keyed by its
|
|
19531
|
+
// version, and that copy is what loads. claude's `install` refuses once
|
|
19532
|
+
// something is installed ("already installed"), so without these two an
|
|
19533
|
+
// upgrade renders new files into a directory nothing reads again. Codex
|
|
19534
|
+
// needs no equivalent: its `add` re-installs from a local marketplace.
|
|
19535
|
+
`claude plugin marketplace update ${MARKETPLACE_NAME} >/dev/null 2>&1 || true`,
|
|
19536
|
+
`claude plugin update ${PLUGIN_ID} >/dev/null 2>&1 || true`,
|
|
19230
19537
|
`codex plugin marketplace add ${q} >/dev/null 2>&1 || true`,
|
|
19231
19538
|
`codex plugin add ${PLUGIN_ID} >/dev/null 2>&1 || true`
|
|
19232
19539
|
].join("; ");
|
|
@@ -19235,6 +19542,35 @@ var SpaceCore = class {
|
|
|
19235
19542
|
} catch {
|
|
19236
19543
|
}
|
|
19237
19544
|
}
|
|
19545
|
+
/** Stage the plugin's `factiii-secrets` onto the agent's PATH.
|
|
19546
|
+
*
|
|
19547
|
+
* A skill can only teach a command that exists. `paths.bin` is already the
|
|
19548
|
+
* PATH prefix `agentEnv()` hands every spawn (it is how `factiii-expose`
|
|
19549
|
+
* resolves), but nothing put anything there for a deploy run - so the
|
|
19550
|
+
* skill would name a command the shell could not find.
|
|
19551
|
+
*
|
|
19552
|
+
* Copied rather than symlinked: an `npm i -g @factiii/runner` upgrade
|
|
19553
|
+
* replaces the package directory, and a link into the old one dangles.
|
|
19554
|
+
* Re-copied whenever the sizes differ, which is what carries an upgrade
|
|
19555
|
+
* through without re-copying on every ensure. */
|
|
19556
|
+
async provisionBinLane() {
|
|
19557
|
+
const source = pluginRoot();
|
|
19558
|
+
if (!source) return;
|
|
19559
|
+
const { bin } = this.target().paths;
|
|
19560
|
+
const from = import_path11.default.join(source, "factiii", "bin", SECRETS_BIN);
|
|
19561
|
+
const to = import_path11.default.join(bin, SECRETS_BIN);
|
|
19562
|
+
try {
|
|
19563
|
+
const [src, staged] = await Promise.all([
|
|
19564
|
+
(0, import_promises8.stat)(from),
|
|
19565
|
+
(0, import_promises8.stat)(to).catch(() => null)
|
|
19566
|
+
]);
|
|
19567
|
+
if (staged?.size === src.size) return;
|
|
19568
|
+
await (0, import_promises8.mkdir)(bin, { recursive: true });
|
|
19569
|
+
await (0, import_promises8.copyFile)(from, to);
|
|
19570
|
+
await (0, import_promises8.chmod)(to, 493);
|
|
19571
|
+
} catch {
|
|
19572
|
+
}
|
|
19573
|
+
}
|
|
19238
19574
|
// Register this space's MCPs with both CLIs, user-scoped, and clear the ones
|
|
19239
19575
|
// it cannot currently offer. Registering regardless left every space holding
|
|
19240
19576
|
// an entry it never uses, which the CLI then fails to handshake with on every
|
|
@@ -19335,6 +19671,10 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19335
19671
|
// In-flight creator session, memory-only (the repo is the source of truth
|
|
19336
19672
|
// once merged). Survives modal reopen via deployCreatorState.
|
|
19337
19673
|
this.skillCreatorSessionId = null;
|
|
19674
|
+
// Which CLI produced that session. A resume id belongs to ONE provider, so
|
|
19675
|
+
// switching the picker between drafting and revising has to start fresh
|
|
19676
|
+
// rather than hand a claude session id to codex.
|
|
19677
|
+
this.skillCreatorProvider = null;
|
|
19338
19678
|
// Set when the draft was resumed from a pushed deploy-skill branch:
|
|
19339
19679
|
// approve commits onto that branch instead of cutting a new one.
|
|
19340
19680
|
this.skillDraftBranch = null;
|
|
@@ -19355,6 +19695,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19355
19695
|
// ── Deploy run (host executor; see host-run.ts + .specs/deploy.md) ──
|
|
19356
19696
|
// Memory-only: reopened clients reattach, a restart loses the run and says so.
|
|
19357
19697
|
this.runState = {
|
|
19698
|
+
provider: "claude",
|
|
19358
19699
|
phase: "idle",
|
|
19359
19700
|
todos: [],
|
|
19360
19701
|
logs: [],
|
|
@@ -19448,6 +19789,20 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19448
19789
|
githubToken: config.githubToken
|
|
19449
19790
|
});
|
|
19450
19791
|
}
|
|
19792
|
+
// What the Deploy modal's release picker offers: branches on origin that
|
|
19793
|
+
// could ship, newest first. See listReleaseBranches.
|
|
19794
|
+
async deployBranches() {
|
|
19795
|
+
const config = this.core.readConfig();
|
|
19796
|
+
if (!config.repoUrl || !config.githubToken) {
|
|
19797
|
+
throw new Error("Set the repository URL and GitHub token first.");
|
|
19798
|
+
}
|
|
19799
|
+
return listReleaseBranches({
|
|
19800
|
+
spaceDir: this.core.spaceDir(),
|
|
19801
|
+
repoUrl: config.repoUrl,
|
|
19802
|
+
mainBranch: config.mainBranch,
|
|
19803
|
+
githubToken: config.githubToken
|
|
19804
|
+
});
|
|
19805
|
+
}
|
|
19451
19806
|
// ── Agent skills (SKILL.md) ──
|
|
19452
19807
|
// Repo opts are optional here, unlike deploy: the global roots are readable
|
|
19453
19808
|
// with no repo configured, and listSkills reports repoAvailable: false.
|
|
@@ -19498,6 +19853,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19498
19853
|
});
|
|
19499
19854
|
const draft = await loadSkillDraft({ ...opts, branch: payload.branch });
|
|
19500
19855
|
this.skillCreatorSessionId = null;
|
|
19856
|
+
this.skillCreatorProvider = null;
|
|
19501
19857
|
this.skillDraftBranch = payload.branch;
|
|
19502
19858
|
this.creatorState = { phase: "review", logs: [], draft, error: "" };
|
|
19503
19859
|
return draft;
|
|
@@ -19543,8 +19899,8 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19543
19899
|
throw err;
|
|
19544
19900
|
}
|
|
19545
19901
|
}
|
|
19546
|
-
deployFixEnvironment(sendLog) {
|
|
19547
|
-
const opts = this.deployCreatorOpts(sendLog);
|
|
19902
|
+
deployFixEnvironment(sendLog, provider) {
|
|
19903
|
+
const opts = this.deployCreatorOpts(sendLog, provider);
|
|
19548
19904
|
return this.runEnvPass(
|
|
19549
19905
|
"fixing",
|
|
19550
19906
|
() => fixEnvironment({
|
|
@@ -19608,13 +19964,14 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19608
19964
|
return this.activity.clear("deploy-run");
|
|
19609
19965
|
}
|
|
19610
19966
|
}
|
|
19611
|
-
deployRunStart(sendLog) {
|
|
19967
|
+
deployRunStart(sendLog, provider, releaseBranch) {
|
|
19612
19968
|
if (this.runState.phase === "running" || this.runState.phase === "awaiting-input" || this.runState.phase === "awaiting-auth") {
|
|
19613
19969
|
throw new Error("A deploy run is already in progress.");
|
|
19614
19970
|
}
|
|
19615
|
-
const opts = this.deployCreatorOpts(sendLog);
|
|
19971
|
+
const opts = this.deployCreatorOpts(sendLog, provider);
|
|
19616
19972
|
this.runCancelled = false;
|
|
19617
19973
|
this.runState = {
|
|
19974
|
+
provider: opts.provider,
|
|
19618
19975
|
phase: "running",
|
|
19619
19976
|
todos: [],
|
|
19620
19977
|
logs: [],
|
|
@@ -19650,6 +20007,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19650
20007
|
mainBranch: opts.mainBranch,
|
|
19651
20008
|
githubToken: opts.githubToken,
|
|
19652
20009
|
provider: opts.provider,
|
|
20010
|
+
releaseBranch,
|
|
19653
20011
|
hooks: {
|
|
19654
20012
|
sendLog: bufferLog,
|
|
19655
20013
|
onTodos: (todos) => this.setRun({ todos }),
|
|
@@ -19735,6 +20093,9 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19735
20093
|
throw new Error("A deploy run is still in progress.");
|
|
19736
20094
|
}
|
|
19737
20095
|
this.runState = {
|
|
20096
|
+
// Unread while idle - the next run sets its own. Carried rather than
|
|
20097
|
+
// reset so nothing has to invent a provider that means nothing yet.
|
|
20098
|
+
provider: this.runState.provider,
|
|
19738
20099
|
phase: "idle",
|
|
19739
20100
|
todos: [],
|
|
19740
20101
|
logs: [],
|
|
@@ -19833,7 +20194,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19833
20194
|
throw err;
|
|
19834
20195
|
}
|
|
19835
20196
|
}
|
|
19836
|
-
deployCreatorOpts(sendLog) {
|
|
20197
|
+
deployCreatorOpts(sendLog, provider) {
|
|
19837
20198
|
const config = this.core.readConfig();
|
|
19838
20199
|
if (!config.repoUrl || !config.githubToken) {
|
|
19839
20200
|
throw new Error("Set the repository URL and GitHub token first.");
|
|
@@ -19843,36 +20204,51 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19843
20204
|
repoUrl: config.repoUrl,
|
|
19844
20205
|
mainBranch: config.mainBranch,
|
|
19845
20206
|
githubToken: config.githubToken,
|
|
19846
|
-
|
|
20207
|
+
// resolveProvider coerces anything unknown to claude, so it can only be
|
|
20208
|
+
// asked about a value that is actually there.
|
|
20209
|
+
provider: provider ? resolveProvider(provider) : this.core.agentProvider(),
|
|
19847
20210
|
sendLog
|
|
19848
20211
|
};
|
|
19849
20212
|
}
|
|
19850
20213
|
deployCreateSkill(payload, sendLog) {
|
|
19851
20214
|
this.skillDraftBranch = null;
|
|
19852
20215
|
return this.runCreatorPass(async () => {
|
|
20216
|
+
const opts = this.deployCreatorOpts(
|
|
20217
|
+
this.creatorLogger(sendLog),
|
|
20218
|
+
payload.provider
|
|
20219
|
+
);
|
|
19853
20220
|
const { sessionId, ...draft } = await generateDeploySkill({
|
|
19854
|
-
...
|
|
20221
|
+
...opts,
|
|
19855
20222
|
instructions: payload.instructions
|
|
19856
20223
|
});
|
|
19857
20224
|
this.skillCreatorSessionId = sessionId || null;
|
|
20225
|
+
this.skillCreatorProvider = opts.provider;
|
|
19858
20226
|
return draft;
|
|
19859
20227
|
});
|
|
19860
20228
|
}
|
|
19861
20229
|
deployReviseSkill(payload, sendLog) {
|
|
19862
20230
|
return this.runCreatorPass(async () => {
|
|
20231
|
+
const opts = this.deployCreatorOpts(
|
|
20232
|
+
this.creatorLogger(sendLog),
|
|
20233
|
+
payload.provider
|
|
20234
|
+
);
|
|
20235
|
+
const resumable = this.skillCreatorProvider === opts.provider;
|
|
19863
20236
|
const { sessionId, ...draft } = await reviseDeploySkill({
|
|
19864
|
-
...
|
|
20237
|
+
...opts,
|
|
19865
20238
|
feedback: payload.feedback,
|
|
19866
|
-
|
|
20239
|
+
// A different CLI than drafted this: its session id means nothing to
|
|
20240
|
+
// the new one, so the revision starts from the files on disk.
|
|
20241
|
+
resumeSessionId: resumable ? this.skillCreatorSessionId ?? void 0 : void 0
|
|
19867
20242
|
});
|
|
19868
20243
|
this.skillCreatorSessionId = sessionId || this.skillCreatorSessionId;
|
|
20244
|
+
this.skillCreatorProvider = opts.provider;
|
|
19869
20245
|
return draft;
|
|
19870
20246
|
});
|
|
19871
20247
|
}
|
|
19872
20248
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
19873
20249
|
secretsDirs() {
|
|
19874
20250
|
const spaceDir = this.core.spaceDir();
|
|
19875
|
-
return { configDir: (0,
|
|
20251
|
+
return { configDir: (0, import_path12.dirname)(spaceDir), spaceDir };
|
|
19876
20252
|
}
|
|
19877
20253
|
async deploySecretsStatus() {
|
|
19878
20254
|
return {
|
|
@@ -19906,6 +20282,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19906
20282
|
const config = this.core.readConfig();
|
|
19907
20283
|
const onBranch = this.skillDraftBranch ?? void 0;
|
|
19908
20284
|
this.skillCreatorSessionId = null;
|
|
20285
|
+
this.skillCreatorProvider = null;
|
|
19909
20286
|
this.skillDraftBranch = null;
|
|
19910
20287
|
this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
|
|
19911
20288
|
this.activity.clear("deploy-skill");
|
|
@@ -19918,6 +20295,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19918
20295
|
}
|
|
19919
20296
|
deployDiscardSkill() {
|
|
19920
20297
|
this.skillCreatorSessionId = null;
|
|
20298
|
+
this.skillCreatorProvider = null;
|
|
19921
20299
|
this.skillDraftBranch = null;
|
|
19922
20300
|
this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
|
|
19923
20301
|
this.activity.clear("deploy-skill");
|
|
@@ -20677,6 +21055,8 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20677
21055
|
return await this.bareWorkNames();
|
|
20678
21056
|
case "deployStatus":
|
|
20679
21057
|
return await this.deployStatus();
|
|
21058
|
+
case "deployBranches":
|
|
21059
|
+
return await this.deployBranches();
|
|
20680
21060
|
case "skillsList":
|
|
20681
21061
|
return await this.skillsList();
|
|
20682
21062
|
case "skillRead":
|
|
@@ -20722,14 +21102,17 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20722
21102
|
case "deployFixEnvironment":
|
|
20723
21103
|
return await this.deployFixEnvironment(
|
|
20724
21104
|
askLogSender ?? (() => {
|
|
20725
|
-
})
|
|
21105
|
+
}),
|
|
21106
|
+
payload?.provider
|
|
20726
21107
|
);
|
|
20727
21108
|
case "deployEnvState":
|
|
20728
21109
|
return this.deployEnvState();
|
|
20729
21110
|
case "deployRunStart":
|
|
20730
21111
|
return this.deployRunStart(
|
|
20731
21112
|
askLogSender ?? (() => {
|
|
20732
|
-
})
|
|
21113
|
+
}),
|
|
21114
|
+
payload?.provider,
|
|
21115
|
+
payload?.branch
|
|
20733
21116
|
);
|
|
20734
21117
|
case "deployRunState":
|
|
20735
21118
|
return this.deployRunState();
|
|
@@ -20929,11 +21312,11 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20929
21312
|
|
|
20930
21313
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
20931
21314
|
var import_child_process5 = require("child_process");
|
|
20932
|
-
var
|
|
21315
|
+
var import_crypto4 = require("crypto");
|
|
20933
21316
|
var import_fs4 = require("fs");
|
|
20934
|
-
var
|
|
20935
|
-
var
|
|
20936
|
-
var
|
|
21317
|
+
var import_promises9 = __toESM(require("fs/promises"));
|
|
21318
|
+
var import_net3 = require("net");
|
|
21319
|
+
var import_path13 = require("path");
|
|
20937
21320
|
var import_util7 = require("util");
|
|
20938
21321
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
20939
21322
|
var IndexService = class {
|
|
@@ -20947,7 +21330,7 @@ var IndexService = class {
|
|
|
20947
21330
|
/** Per-space bearer for MCP + file, derived from the control token (the
|
|
20948
21331
|
* service computes the same). */
|
|
20949
21332
|
bearerToken(spaceSlug) {
|
|
20950
|
-
return (0,
|
|
21333
|
+
return (0, import_crypto4.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
|
|
20951
21334
|
}
|
|
20952
21335
|
/** MCP bridge for a space. Null until the service is up (which it kicks
|
|
20953
21336
|
* off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
|
|
@@ -20992,7 +21375,7 @@ var IndexService = class {
|
|
|
20992
21375
|
const log = await this.openLog();
|
|
20993
21376
|
this.proc = (0, import_child_process5.spawn)(
|
|
20994
21377
|
process.execPath,
|
|
20995
|
-
[(0,
|
|
21378
|
+
[(0, import_path13.join)(this.opts.indexDir, "index.js")],
|
|
20996
21379
|
{
|
|
20997
21380
|
cwd: this.opts.indexDir,
|
|
20998
21381
|
env: {
|
|
@@ -21020,8 +21403,8 @@ var IndexService = class {
|
|
|
21020
21403
|
* opened (a missing log must never stop the service from starting). */
|
|
21021
21404
|
async openLog() {
|
|
21022
21405
|
try {
|
|
21023
|
-
await
|
|
21024
|
-
return (0, import_fs4.openSync)((0,
|
|
21406
|
+
await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
21407
|
+
return (0, import_fs4.openSync)((0, import_path13.join)(this.opts.dataDir, "index.log"), "a");
|
|
21025
21408
|
} catch {
|
|
21026
21409
|
return null;
|
|
21027
21410
|
}
|
|
@@ -21030,10 +21413,10 @@ var IndexService = class {
|
|
|
21030
21413
|
* platform needing node-gyp fails here rather than at query time. */
|
|
21031
21414
|
async installDeps() {
|
|
21032
21415
|
const dir = this.opts.indexDir;
|
|
21033
|
-
if ((0, import_fs4.existsSync)((0,
|
|
21034
|
-
await
|
|
21035
|
-
(0,
|
|
21036
|
-
(0,
|
|
21416
|
+
if ((0, import_fs4.existsSync)((0, import_path13.join)(dir, "node_modules"))) return;
|
|
21417
|
+
await import_promises9.default.copyFile(
|
|
21418
|
+
(0, import_path13.join)(dir, "image-package.json"),
|
|
21419
|
+
(0, import_path13.join)(dir, "package.json")
|
|
21037
21420
|
);
|
|
21038
21421
|
await execFileAsync4(
|
|
21039
21422
|
"npm",
|
|
@@ -21045,15 +21428,15 @@ var IndexService = class {
|
|
|
21045
21428
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
21046
21429
|
* registration pointing at a dead one. */
|
|
21047
21430
|
async stablePort() {
|
|
21048
|
-
const file = (0,
|
|
21049
|
-
const saved = Number(await
|
|
21431
|
+
const file = (0, import_path13.join)(this.opts.dataDir, ".port");
|
|
21432
|
+
const saved = Number(await import_promises9.default.readFile(file, "utf-8").catch(() => ""));
|
|
21050
21433
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
21051
21434
|
return saved;
|
|
21052
21435
|
}
|
|
21053
21436
|
const port = await freePort();
|
|
21054
|
-
await
|
|
21437
|
+
await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
21055
21438
|
});
|
|
21056
|
-
await
|
|
21439
|
+
await import_promises9.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
21057
21440
|
});
|
|
21058
21441
|
return port;
|
|
21059
21442
|
}
|
|
@@ -21147,14 +21530,14 @@ var IndexService = class {
|
|
|
21147
21530
|
};
|
|
21148
21531
|
function isFree(port) {
|
|
21149
21532
|
return new Promise((resolve2) => {
|
|
21150
|
-
const srv = (0,
|
|
21533
|
+
const srv = (0, import_net3.createServer)();
|
|
21151
21534
|
srv.once("error", () => resolve2(false));
|
|
21152
21535
|
srv.listen(port, "127.0.0.1", () => srv.close(() => resolve2(true)));
|
|
21153
21536
|
});
|
|
21154
21537
|
}
|
|
21155
21538
|
function freePort() {
|
|
21156
21539
|
return new Promise((resolve2, reject) => {
|
|
21157
|
-
const srv = (0,
|
|
21540
|
+
const srv = (0, import_net3.createServer)();
|
|
21158
21541
|
srv.on("error", reject);
|
|
21159
21542
|
srv.listen(0, "127.0.0.1", () => {
|
|
21160
21543
|
const addr = srv.address();
|
|
@@ -21222,22 +21605,22 @@ async function pairWithBrowser(serverUrl) {
|
|
|
21222
21605
|
// src/config.ts
|
|
21223
21606
|
var import_fs8 = __toESM(require("fs"));
|
|
21224
21607
|
var import_os6 = __toESM(require("os"));
|
|
21225
|
-
var
|
|
21608
|
+
var import_path18 = __toESM(require("path"));
|
|
21226
21609
|
|
|
21227
21610
|
// src/secureFile.ts
|
|
21228
|
-
var
|
|
21611
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
21229
21612
|
var import_fs7 = __toESM(require("fs"));
|
|
21230
|
-
var
|
|
21613
|
+
var import_path17 = __toESM(require("path"));
|
|
21231
21614
|
|
|
21232
21615
|
// src/keychain.ts
|
|
21233
21616
|
var import_child_process7 = require("child_process");
|
|
21234
|
-
var
|
|
21617
|
+
var import_crypto5 = __toESM(require("crypto"));
|
|
21235
21618
|
var import_fs5 = __toESM(require("fs"));
|
|
21236
21619
|
var import_os4 = __toESM(require("os"));
|
|
21237
|
-
var
|
|
21620
|
+
var import_path14 = __toESM(require("path"));
|
|
21238
21621
|
var SERVICE = "factiii-runner";
|
|
21239
21622
|
var ACCOUNT = "config-encryption-key";
|
|
21240
|
-
var DPAPI_KEY_FILE =
|
|
21623
|
+
var DPAPI_KEY_FILE = import_path14.default.join(
|
|
21241
21624
|
import_os4.default.homedir(),
|
|
21242
21625
|
".factiii-runner",
|
|
21243
21626
|
"config-key.dpapi"
|
|
@@ -21343,7 +21726,7 @@ function winWrite(key) {
|
|
|
21343
21726
|
"-Command",
|
|
21344
21727
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
21345
21728
|
]);
|
|
21346
|
-
import_fs5.default.mkdirSync(
|
|
21729
|
+
import_fs5.default.mkdirSync(import_path14.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
21347
21730
|
import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
21348
21731
|
`, { mode: 384 });
|
|
21349
21732
|
}
|
|
@@ -21483,7 +21866,7 @@ function resolve() {
|
|
|
21483
21866
|
cached = { key: null, status: diagnose() };
|
|
21484
21867
|
return cached;
|
|
21485
21868
|
}
|
|
21486
|
-
const fresh =
|
|
21869
|
+
const fresh = import_crypto5.default.randomBytes(32);
|
|
21487
21870
|
writeKey(fresh);
|
|
21488
21871
|
const verify = readKey();
|
|
21489
21872
|
if (verify.state !== "found" || !verify.key.equals(fresh)) {
|
|
@@ -21505,23 +21888,23 @@ function keychainStatus() {
|
|
|
21505
21888
|
}
|
|
21506
21889
|
|
|
21507
21890
|
// src/secureStore.ts
|
|
21508
|
-
var
|
|
21891
|
+
var import_crypto6 = __toESM(require("crypto"));
|
|
21509
21892
|
var import_fs6 = __toESM(require("fs"));
|
|
21510
|
-
var
|
|
21893
|
+
var import_path16 = __toESM(require("path"));
|
|
21511
21894
|
|
|
21512
21895
|
// src/paths.ts
|
|
21513
21896
|
var import_os5 = __toESM(require("os"));
|
|
21514
|
-
var
|
|
21515
|
-
var CONFIG_DIR =
|
|
21897
|
+
var import_path15 = __toESM(require("path"));
|
|
21898
|
+
var CONFIG_DIR = import_path15.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
21516
21899
|
function safeSlug(spaceSlug) {
|
|
21517
21900
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21518
21901
|
}
|
|
21519
21902
|
function spaceDirPath(spaceSlug) {
|
|
21520
|
-
return
|
|
21903
|
+
return import_path15.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
21521
21904
|
}
|
|
21522
21905
|
|
|
21523
21906
|
// src/secureStore.ts
|
|
21524
|
-
var VAULT_KEY_FILE =
|
|
21907
|
+
var VAULT_KEY_FILE = import_path16.default.join(CONFIG_DIR, "vault-key.json");
|
|
21525
21908
|
var SCRYPT_N2 = 1 << 15;
|
|
21526
21909
|
var MIN_PASSWORD_LENGTH = 8;
|
|
21527
21910
|
var SecureStoreError = class extends Error {
|
|
@@ -21548,7 +21931,7 @@ function requireKeychainKey() {
|
|
|
21548
21931
|
);
|
|
21549
21932
|
}
|
|
21550
21933
|
function passwordKey(password, salt) {
|
|
21551
|
-
return
|
|
21934
|
+
return import_crypto6.default.scryptSync(password, salt, 32, {
|
|
21552
21935
|
N: SCRYPT_N2,
|
|
21553
21936
|
maxmem: 64 * 1024 * 1024
|
|
21554
21937
|
});
|
|
@@ -21556,7 +21939,7 @@ function passwordKey(password, salt) {
|
|
|
21556
21939
|
function wrapKey(machineKey, pwKey) {
|
|
21557
21940
|
const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
|
|
21558
21941
|
return Buffer.from(
|
|
21559
|
-
|
|
21942
|
+
import_crypto6.default.hkdfSync(
|
|
21560
21943
|
"sha256",
|
|
21561
21944
|
ikm,
|
|
21562
21945
|
Buffer.alloc(0),
|
|
@@ -21566,8 +21949,8 @@ function wrapKey(machineKey, pwKey) {
|
|
|
21566
21949
|
);
|
|
21567
21950
|
}
|
|
21568
21951
|
function seal(plaintext, key) {
|
|
21569
|
-
const iv =
|
|
21570
|
-
const cipher =
|
|
21952
|
+
const iv = import_crypto6.default.randomBytes(12);
|
|
21953
|
+
const cipher = import_crypto6.default.createCipheriv("aes-256-gcm", key, iv);
|
|
21571
21954
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
21572
21955
|
return {
|
|
21573
21956
|
iv: iv.toString("base64"),
|
|
@@ -21577,7 +21960,7 @@ function seal(plaintext, key) {
|
|
|
21577
21960
|
}
|
|
21578
21961
|
function unseal(box, key) {
|
|
21579
21962
|
try {
|
|
21580
|
-
const decipher =
|
|
21963
|
+
const decipher = import_crypto6.default.createDecipheriv(
|
|
21581
21964
|
"aes-256-gcm",
|
|
21582
21965
|
key,
|
|
21583
21966
|
Buffer.from(box.iv, "base64")
|
|
@@ -21610,7 +21993,7 @@ function writeVaultFile(file) {
|
|
|
21610
21993
|
}
|
|
21611
21994
|
var unlockedDek = null;
|
|
21612
21995
|
function createVault(machineKey) {
|
|
21613
|
-
const dek =
|
|
21996
|
+
const dek = import_crypto6.default.randomBytes(32);
|
|
21614
21997
|
writeVaultFile({
|
|
21615
21998
|
v: 1,
|
|
21616
21999
|
passwordEnabled: false,
|
|
@@ -21705,7 +22088,7 @@ function enablePassword(password) {
|
|
|
21705
22088
|
);
|
|
21706
22089
|
}
|
|
21707
22090
|
const dek = dataKey();
|
|
21708
|
-
const salt =
|
|
22091
|
+
const salt = import_crypto6.default.randomBytes(16);
|
|
21709
22092
|
writeVaultFile({
|
|
21710
22093
|
v: 1,
|
|
21711
22094
|
passwordEnabled: true,
|
|
@@ -21733,7 +22116,7 @@ function changePassword(current, next) {
|
|
|
21733
22116
|
"The current password is not correct."
|
|
21734
22117
|
);
|
|
21735
22118
|
}
|
|
21736
|
-
const salt =
|
|
22119
|
+
const salt = import_crypto6.default.randomBytes(16);
|
|
21737
22120
|
writeVaultFile({
|
|
21738
22121
|
v: 1,
|
|
21739
22122
|
passwordEnabled: true,
|
|
@@ -21810,8 +22193,8 @@ function storeStatus() {
|
|
|
21810
22193
|
// src/secureFile.ts
|
|
21811
22194
|
var MAGIC = "FACTIII1";
|
|
21812
22195
|
function encrypt(plain, key, version) {
|
|
21813
|
-
const iv =
|
|
21814
|
-
const cipher =
|
|
22196
|
+
const iv = import_crypto7.default.randomBytes(12);
|
|
22197
|
+
const cipher = import_crypto7.default.createCipheriv("aes-256-gcm", key, iv);
|
|
21815
22198
|
const data = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
21816
22199
|
const envelope = {
|
|
21817
22200
|
v: version,
|
|
@@ -21822,7 +22205,7 @@ function encrypt(plain, key, version) {
|
|
|
21822
22205
|
return `${MAGIC}${JSON.stringify(envelope)}`;
|
|
21823
22206
|
}
|
|
21824
22207
|
function decrypt(envelope, key) {
|
|
21825
|
-
const decipher =
|
|
22208
|
+
const decipher = import_crypto7.default.createDecipheriv(
|
|
21826
22209
|
"aes-256-gcm",
|
|
21827
22210
|
key,
|
|
21828
22211
|
Buffer.from(envelope.iv, "base64")
|
|
@@ -21843,7 +22226,7 @@ function parseEnvelope(raw) {
|
|
|
21843
22226
|
}
|
|
21844
22227
|
}
|
|
21845
22228
|
function atomicWrite(filePath, body) {
|
|
21846
|
-
import_fs7.default.mkdirSync(
|
|
22229
|
+
import_fs7.default.mkdirSync(import_path17.default.dirname(filePath), { recursive: true });
|
|
21847
22230
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
21848
22231
|
import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
|
|
21849
22232
|
import_fs7.default.renameSync(tmp, filePath);
|
|
@@ -21958,8 +22341,8 @@ function writeBootJson(filePath, value2) {
|
|
|
21958
22341
|
}
|
|
21959
22342
|
|
|
21960
22343
|
// src/config.ts
|
|
21961
|
-
var CONFIG_DIR2 =
|
|
21962
|
-
var CONFIG_FILE =
|
|
22344
|
+
var CONFIG_DIR2 = import_path18.default.join(import_os6.default.homedir(), ".factiii-runner");
|
|
22345
|
+
var CONFIG_FILE = import_path18.default.join(CONFIG_DIR2, "config.json");
|
|
21963
22346
|
function readRunnerConfig() {
|
|
21964
22347
|
return readBootJson(CONFIG_FILE);
|
|
21965
22348
|
}
|
|
@@ -22128,10 +22511,10 @@ function makeChunkReassembler() {
|
|
|
22128
22511
|
}
|
|
22129
22512
|
|
|
22130
22513
|
// src/daemon.ts
|
|
22131
|
-
var
|
|
22514
|
+
var import_crypto8 = require("crypto");
|
|
22132
22515
|
var import_fs10 = __toESM(require("fs"));
|
|
22133
22516
|
var import_node_datachannel = require("node-datachannel");
|
|
22134
|
-
var
|
|
22517
|
+
var import_path20 = __toESM(require("path"));
|
|
22135
22518
|
|
|
22136
22519
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
22137
22520
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23311,12 +23694,12 @@ function parse2(str) {
|
|
|
23311
23694
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23312
23695
|
return uri;
|
|
23313
23696
|
}
|
|
23314
|
-
function pathNames(obj,
|
|
23315
|
-
const regx = /\/{2,9}/g, names =
|
|
23316
|
-
if (
|
|
23697
|
+
function pathNames(obj, path20) {
|
|
23698
|
+
const regx = /\/{2,9}/g, names = path20.replace(regx, "/").split("/");
|
|
23699
|
+
if (path20.slice(0, 1) == "/" || path20.length === 0) {
|
|
23317
23700
|
names.splice(0, 1);
|
|
23318
23701
|
}
|
|
23319
|
-
if (
|
|
23702
|
+
if (path20.slice(-1) == "/") {
|
|
23320
23703
|
names.splice(names.length - 1, 1);
|
|
23321
23704
|
}
|
|
23322
23705
|
return names;
|
|
@@ -23935,7 +24318,7 @@ var protocol2 = Socket.protocol;
|
|
|
23935
24318
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
23936
24319
|
var import_debug7 = __toESM(require_src(), 1);
|
|
23937
24320
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
23938
|
-
function url(uri,
|
|
24321
|
+
function url(uri, path20 = "", loc) {
|
|
23939
24322
|
let obj = uri;
|
|
23940
24323
|
loc = loc || typeof location !== "undefined" && location;
|
|
23941
24324
|
if (null == uri)
|
|
@@ -23969,7 +24352,7 @@ function url(uri, path18 = "", loc) {
|
|
|
23969
24352
|
obj.path = obj.path || "/";
|
|
23970
24353
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
23971
24354
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
23972
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
24355
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path20;
|
|
23973
24356
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
23974
24357
|
return obj;
|
|
23975
24358
|
}
|
|
@@ -25603,8 +25986,8 @@ function lookup(uri, opts) {
|
|
|
25603
25986
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
25604
25987
|
const source = parsed.source;
|
|
25605
25988
|
const id = parsed.id;
|
|
25606
|
-
const
|
|
25607
|
-
const sameNamespace = cache[id] &&
|
|
25989
|
+
const path20 = parsed.path;
|
|
25990
|
+
const sameNamespace = cache[id] && path20 in cache[id]["nsps"];
|
|
25608
25991
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
25609
25992
|
let io;
|
|
25610
25993
|
if (newConnection) {
|
|
@@ -25646,8 +26029,8 @@ function cardContextPath(target, postId) {
|
|
|
25646
26029
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
25647
26030
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
25648
26031
|
}
|
|
25649
|
-
function cardContextPrompt(
|
|
25650
|
-
return `Read ${
|
|
26032
|
+
function cardContextPrompt(path20) {
|
|
26033
|
+
return `Read ${path20} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
|
|
25651
26034
|
}
|
|
25652
26035
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
25653
26036
|
var RESUME_SEED = "You are now in an interactive terminal, not the board UI. Respond in plain text and use your normal tools; do not use openui-lang or component schemas anymore.";
|
|
@@ -25812,10 +26195,10 @@ var TerminalManager = class {
|
|
|
25812
26195
|
|
|
25813
26196
|
// src/agent-adapter.ts
|
|
25814
26197
|
var import_fs9 = __toESM(require("fs"));
|
|
25815
|
-
var
|
|
25816
|
-
var RUNNER_CONFIG_PATH =
|
|
26198
|
+
var import_path19 = __toESM(require("path"));
|
|
26199
|
+
var RUNNER_CONFIG_PATH = import_path19.default.join(CONFIG_DIR, "runner-config.json");
|
|
25817
26200
|
function connectionPath(spaceSlug) {
|
|
25818
|
-
return
|
|
26201
|
+
return import_path19.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
25819
26202
|
}
|
|
25820
26203
|
function writeJson(filePath, value2) {
|
|
25821
26204
|
writeSecureJson(filePath, value2);
|
|
@@ -25849,7 +26232,7 @@ function migrateLegacyConfigs() {
|
|
|
25849
26232
|
for (const name of legacy) {
|
|
25850
26233
|
const slug = name.slice("space-".length, -".json".length);
|
|
25851
26234
|
const parsed = readSecureJson(
|
|
25852
|
-
|
|
26235
|
+
import_path19.default.join(CONFIG_DIR, name)
|
|
25853
26236
|
);
|
|
25854
26237
|
if (!parsed) continue;
|
|
25855
26238
|
if (!file.defaultSlug) {
|
|
@@ -25874,7 +26257,7 @@ function encryptStrayPlaintextConfigs() {
|
|
|
25874
26257
|
return;
|
|
25875
26258
|
}
|
|
25876
26259
|
for (const name of names) {
|
|
25877
|
-
const filePath =
|
|
26260
|
+
const filePath = import_path19.default.join(CONFIG_DIR, name);
|
|
25878
26261
|
try {
|
|
25879
26262
|
const raw = import_fs9.default.readFileSync(filePath, "utf-8");
|
|
25880
26263
|
if (!raw.startsWith("{")) continue;
|
|
@@ -26003,7 +26386,7 @@ var localConfigProvider = {
|
|
|
26003
26386
|
"claude",
|
|
26004
26387
|
"codex"
|
|
26005
26388
|
]) {
|
|
26006
|
-
import_fs9.default.mkdirSync(
|
|
26389
|
+
import_fs9.default.mkdirSync(import_path19.default.join(dir, sub), { recursive: true });
|
|
26007
26390
|
}
|
|
26008
26391
|
return dir;
|
|
26009
26392
|
}
|
|
@@ -26177,15 +26560,15 @@ async function startDaemon(config) {
|
|
|
26177
26560
|
}
|
|
26178
26561
|
);
|
|
26179
26562
|
const indexCandidates = [
|
|
26180
|
-
|
|
26181
|
-
|
|
26563
|
+
import_path20.default.join(__dirname, "..", "index"),
|
|
26564
|
+
import_path20.default.join(__dirname, "..", "..", "index", "image")
|
|
26182
26565
|
];
|
|
26183
26566
|
const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
|
|
26184
26567
|
const odIndex = new IndexService({
|
|
26185
|
-
controlToken: (0,
|
|
26568
|
+
controlToken: (0, import_crypto8.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
26186
26569
|
embedUrl: () => config.serverUrl,
|
|
26187
26570
|
indexDir,
|
|
26188
|
-
dataDir:
|
|
26571
|
+
dataDir: import_path20.default.join(CONFIG_DIR, "index-data")
|
|
26189
26572
|
});
|
|
26190
26573
|
void odIndex.ensureRunning().catch((err) => {
|
|
26191
26574
|
console.error(
|