@factiii/runner 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1049 -476
- package/index/index.js +10 -4
- package/package.json +2 -1
- package/plugin/.claude-plugin/marketplace.json +12 -0
- package/plugin/factiii/.claude-plugin/plugin.json +7 -0
- package/plugin/factiii/bin/factiii-secrets +208 -0
- package/plugin/factiii/bin/report-state.sh +43 -0
- package/plugin/factiii/hooks/hooks.json +59 -0
- package/plugin/factiii/skills/factiii-secrets/SKILL.md +82 -0
- package/plugin/factiii/skills/share-a-port/SKILL.md +41 -0
package/dist/cli.js
CHANGED
|
@@ -915,18 +915,18 @@ var require_suggestSimilar = __commonJS({
|
|
|
915
915
|
}
|
|
916
916
|
return d[a.length][b.length];
|
|
917
917
|
}
|
|
918
|
-
function suggestSimilar(word,
|
|
919
|
-
if (!
|
|
920
|
-
|
|
918
|
+
function suggestSimilar(word, candidates2) {
|
|
919
|
+
if (!candidates2 || candidates2.length === 0) return "";
|
|
920
|
+
candidates2 = Array.from(new Set(candidates2));
|
|
921
921
|
const searchingOptions = word.startsWith("--");
|
|
922
922
|
if (searchingOptions) {
|
|
923
923
|
word = word.slice(2);
|
|
924
|
-
|
|
924
|
+
candidates2 = candidates2.map((candidate) => candidate.slice(2));
|
|
925
925
|
}
|
|
926
926
|
let similar = [];
|
|
927
927
|
let bestDistance = maxDistance;
|
|
928
928
|
const minSimilarity = 0.4;
|
|
929
|
-
|
|
929
|
+
candidates2.forEach((candidate) => {
|
|
930
930
|
if (candidate.length <= 1) return;
|
|
931
931
|
const distance = editDistance(word, candidate);
|
|
932
932
|
const length = Math.max(word.length, candidate.length);
|
|
@@ -963,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);
|
|
@@ -3984,7 +3984,7 @@ var require_has_flag = __commonJS({
|
|
|
3984
3984
|
var require_supports_color = __commonJS({
|
|
3985
3985
|
"../../node_modules/debug/node_modules/supports-color/index.js"(exports2, module2) {
|
|
3986
3986
|
"use strict";
|
|
3987
|
-
var
|
|
3987
|
+
var os7 = require("os");
|
|
3988
3988
|
var hasFlag = require_has_flag();
|
|
3989
3989
|
var env = process.env;
|
|
3990
3990
|
var forceColor;
|
|
@@ -4022,7 +4022,7 @@ var require_supports_color = __commonJS({
|
|
|
4022
4022
|
}
|
|
4023
4023
|
const min = forceColor ? 1 : 0;
|
|
4024
4024
|
if (process.platform === "win32") {
|
|
4025
|
-
const osRelease =
|
|
4025
|
+
const osRelease = os7.release().split(".");
|
|
4026
4026
|
if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
4027
4027
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
4028
4028
|
}
|
|
@@ -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
|
}
|
|
@@ -13300,16 +13301,18 @@ function cardKey(postId) {
|
|
|
13300
13301
|
return `card:${safeId(postId)}`;
|
|
13301
13302
|
}
|
|
13302
13303
|
var INDEX_KEY = "cards";
|
|
13303
|
-
function
|
|
13304
|
+
function redisSocketPath(target) {
|
|
13304
13305
|
return `${target.paths.state}/redis.sock`;
|
|
13305
13306
|
}
|
|
13306
13307
|
async function redisCli(target, args) {
|
|
13307
|
-
const out = await target.sh(
|
|
13308
|
+
const out = await target.sh(
|
|
13309
|
+
`redis-cli -s ${redisSocketPath(target)} ${args}`
|
|
13310
|
+
);
|
|
13308
13311
|
return out.replace(/\n$/, "");
|
|
13309
13312
|
}
|
|
13310
13313
|
function redisCliStdin(target, args, stdin) {
|
|
13311
13314
|
return target.runWithStdin(
|
|
13312
|
-
["redis-cli", "-s",
|
|
13315
|
+
["redis-cli", "-s", redisSocketPath(target), "-x", ...args],
|
|
13313
13316
|
stdin,
|
|
13314
13317
|
`redis-cli ${args.join(" ")}`
|
|
13315
13318
|
);
|
|
@@ -13322,7 +13325,7 @@ async function ensureRedis(target) {
|
|
|
13322
13325
|
}
|
|
13323
13326
|
const stateDir = target.paths.state;
|
|
13324
13327
|
await target.sh(
|
|
13325
|
-
`mkdir -p ${stateDir} && redis-server --daemonize yes --appendonly yes --save '' --dir ${stateDir} --port 0 --unixsocket ${
|
|
13328
|
+
`mkdir -p ${stateDir} && redis-server --daemonize yes --appendonly yes --save '' --dir ${stateDir} --port 0 --unixsocket ${redisSocketPath(target)} --unixsocketperm 700`
|
|
13326
13329
|
);
|
|
13327
13330
|
for (let i = 0; i < 20; i += 1) {
|
|
13328
13331
|
try {
|
|
@@ -13688,6 +13691,7 @@ function toSummary(session) {
|
|
|
13688
13691
|
pendingQuestion: session.pendingQuestion,
|
|
13689
13692
|
error: session.error,
|
|
13690
13693
|
startedAt: session.startedAt,
|
|
13694
|
+
agentState: session.agentState,
|
|
13691
13695
|
targetBranch: session.targetBranch,
|
|
13692
13696
|
taskTitle: session.taskTitle,
|
|
13693
13697
|
todos: session.todos,
|
|
@@ -13845,13 +13849,13 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13845
13849
|
|
|
13846
13850
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
13847
13851
|
var import_fs2 = require("fs");
|
|
13848
|
-
var
|
|
13849
|
-
var
|
|
13852
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
13853
|
+
var import_path7 = __toESM(require("path"));
|
|
13850
13854
|
|
|
13851
13855
|
// ../../shared/all/helpers/board-agent-core/host-skills.ts
|
|
13852
|
-
var
|
|
13856
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
13853
13857
|
var import_os2 = __toESM(require("os"));
|
|
13854
|
-
var
|
|
13858
|
+
var import_path6 = __toESM(require("path"));
|
|
13855
13859
|
|
|
13856
13860
|
// ../../shared/all/domains/posts.ts
|
|
13857
13861
|
var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
|
|
@@ -14361,9 +14365,134 @@ var skillScopeSchema = external_exports.enum(["global", "repo"]);
|
|
|
14361
14365
|
|
|
14362
14366
|
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14363
14367
|
var import_child_process3 = require("child_process");
|
|
14368
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
14369
|
+
var import_path5 = __toESM(require("path"));
|
|
14370
|
+
var import_util5 = require("util");
|
|
14371
|
+
|
|
14372
|
+
// ../../shared/all/helpers/board-agent-core/secrets-bridge.ts
|
|
14364
14373
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
14374
|
+
var import_net2 = __toESM(require("net"));
|
|
14365
14375
|
var import_path4 = __toESM(require("path"));
|
|
14366
|
-
|
|
14376
|
+
function secretsSocketPath(spaceDir) {
|
|
14377
|
+
return import_path4.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
|
|
14378
|
+
}
|
|
14379
|
+
function redactor(values) {
|
|
14380
|
+
const significant = values.filter((v) => v.length >= 4);
|
|
14381
|
+
return (text) => {
|
|
14382
|
+
let out = text;
|
|
14383
|
+
for (const value2 of significant) {
|
|
14384
|
+
out = out.split(value2).join("[REDACTED]");
|
|
14385
|
+
}
|
|
14386
|
+
return out;
|
|
14387
|
+
};
|
|
14388
|
+
}
|
|
14389
|
+
function replier(conn) {
|
|
14390
|
+
return (message) => {
|
|
14391
|
+
if (conn.destroyed) return;
|
|
14392
|
+
conn.write(`${JSON.stringify(message)}
|
|
14393
|
+
`);
|
|
14394
|
+
};
|
|
14395
|
+
}
|
|
14396
|
+
async function handleRun(request, handlers, reply) {
|
|
14397
|
+
const command = typeof request.command === "string" ? request.command.trim() : "";
|
|
14398
|
+
if (!command) {
|
|
14399
|
+
reply({ type: "error", message: "No command was sent." });
|
|
14400
|
+
return;
|
|
14401
|
+
}
|
|
14402
|
+
const cwd = typeof request.cwd === "string" ? request.cwd : "";
|
|
14403
|
+
const result = await handlers.run({
|
|
14404
|
+
command,
|
|
14405
|
+
cwd,
|
|
14406
|
+
status: (text) => reply({ type: "status", text })
|
|
14407
|
+
});
|
|
14408
|
+
if ("denied" in result) {
|
|
14409
|
+
reply({ type: "denied" });
|
|
14410
|
+
return;
|
|
14411
|
+
}
|
|
14412
|
+
if (result.output) reply({ type: "output", chunk: result.output });
|
|
14413
|
+
reply({ type: "exit", code: result.code });
|
|
14414
|
+
}
|
|
14415
|
+
async function startSecretsBridge(opts) {
|
|
14416
|
+
const socketPath = secretsSocketPath(opts.spaceDir);
|
|
14417
|
+
await import_promises2.default.mkdir(import_path4.default.dirname(socketPath), { recursive: true });
|
|
14418
|
+
await import_promises2.default.rm(socketPath, { force: true });
|
|
14419
|
+
const { handlers } = opts;
|
|
14420
|
+
let queue = Promise.resolve();
|
|
14421
|
+
let queueDepth = 0;
|
|
14422
|
+
const live = /* @__PURE__ */ new Set();
|
|
14423
|
+
const server = import_net2.default.createServer((conn) => {
|
|
14424
|
+
live.add(conn);
|
|
14425
|
+
conn.on("close", () => live.delete(conn));
|
|
14426
|
+
const reply = replier(conn);
|
|
14427
|
+
let buffer = "";
|
|
14428
|
+
let handled = false;
|
|
14429
|
+
conn.on("error", () => {
|
|
14430
|
+
});
|
|
14431
|
+
conn.on("data", (chunk) => {
|
|
14432
|
+
if (handled) return;
|
|
14433
|
+
buffer += chunk.toString("utf-8");
|
|
14434
|
+
const newline = buffer.indexOf("\n");
|
|
14435
|
+
if (newline === -1) return;
|
|
14436
|
+
handled = true;
|
|
14437
|
+
let request;
|
|
14438
|
+
try {
|
|
14439
|
+
request = JSON.parse(buffer.slice(0, newline));
|
|
14440
|
+
} catch {
|
|
14441
|
+
reply({ type: "error", message: "Malformed request." });
|
|
14442
|
+
conn.end();
|
|
14443
|
+
return;
|
|
14444
|
+
}
|
|
14445
|
+
if (request.op === "list") {
|
|
14446
|
+
void handlers.list().then((keys) => reply({ type: "keys", keys })).catch(
|
|
14447
|
+
(err) => reply({ type: "error", message: errorText(err) })
|
|
14448
|
+
).finally(() => conn.end());
|
|
14449
|
+
return;
|
|
14450
|
+
}
|
|
14451
|
+
if (request.op !== "run") {
|
|
14452
|
+
reply({ type: "error", message: "Unknown operation." });
|
|
14453
|
+
conn.end();
|
|
14454
|
+
return;
|
|
14455
|
+
}
|
|
14456
|
+
if (queueDepth > 0) {
|
|
14457
|
+
reply({
|
|
14458
|
+
type: "status",
|
|
14459
|
+
text: "queued behind another command awaiting authorization"
|
|
14460
|
+
});
|
|
14461
|
+
}
|
|
14462
|
+
queueDepth += 1;
|
|
14463
|
+
queue = queue.then(() => {
|
|
14464
|
+
if (conn.destroyed) return void 0;
|
|
14465
|
+
return handleRun(request, handlers, reply);
|
|
14466
|
+
}).catch(
|
|
14467
|
+
(err) => reply({ type: "error", message: errorText(err) })
|
|
14468
|
+
).finally(() => {
|
|
14469
|
+
queueDepth -= 1;
|
|
14470
|
+
conn.end();
|
|
14471
|
+
});
|
|
14472
|
+
});
|
|
14473
|
+
});
|
|
14474
|
+
await new Promise((resolve2, reject) => {
|
|
14475
|
+
server.once("error", reject);
|
|
14476
|
+
server.listen(socketPath, () => {
|
|
14477
|
+
server.removeListener("error", reject);
|
|
14478
|
+
resolve2();
|
|
14479
|
+
});
|
|
14480
|
+
});
|
|
14481
|
+
await import_promises2.default.chmod(socketPath, 384).catch(() => void 0);
|
|
14482
|
+
return {
|
|
14483
|
+
close: async () => {
|
|
14484
|
+
for (const conn of live) conn.destroy();
|
|
14485
|
+
live.clear();
|
|
14486
|
+
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
14487
|
+
await import_promises2.default.rm(socketPath, { force: true });
|
|
14488
|
+
}
|
|
14489
|
+
};
|
|
14490
|
+
}
|
|
14491
|
+
function errorText(err) {
|
|
14492
|
+
return err instanceof Error ? err.message : String(err);
|
|
14493
|
+
}
|
|
14494
|
+
|
|
14495
|
+
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14367
14496
|
var execFileAsync2 = (0, import_util5.promisify)(import_child_process3.execFile);
|
|
14368
14497
|
var SKILL_DIR = ".agents/skills/deploy";
|
|
14369
14498
|
var SKILL_PATH = `${SKILL_DIR}/SKILL.md`;
|
|
@@ -14372,14 +14501,14 @@ var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
|
|
|
14372
14501
|
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
14373
14502
|
var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
|
|
14374
14503
|
async function ensureClaudeSkillLink(workspace) {
|
|
14375
|
-
const linkPath =
|
|
14504
|
+
const linkPath = import_path5.default.join(workspace, CLAUDE_LINK);
|
|
14376
14505
|
try {
|
|
14377
|
-
await
|
|
14506
|
+
await import_promises3.default.lstat(linkPath);
|
|
14378
14507
|
return;
|
|
14379
14508
|
} catch {
|
|
14380
14509
|
}
|
|
14381
|
-
await
|
|
14382
|
-
await
|
|
14510
|
+
await import_promises3.default.mkdir(import_path5.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
|
|
14511
|
+
await import_promises3.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
|
|
14383
14512
|
}
|
|
14384
14513
|
var GIT_NO_HELPER = ["-c", "credential.helper="];
|
|
14385
14514
|
var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -14424,7 +14553,7 @@ async function gitCatFileBatch(cwd, shas) {
|
|
|
14424
14553
|
return out;
|
|
14425
14554
|
}
|
|
14426
14555
|
async function ensureWorkspaceClone(opts) {
|
|
14427
|
-
const workspace =
|
|
14556
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14428
14557
|
const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
14429
14558
|
const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
|
|
14430
14559
|
let cloned = false;
|
|
@@ -14432,14 +14561,14 @@ async function ensureWorkspaceClone(opts) {
|
|
|
14432
14561
|
await hostGit(workspace, "rev-parse", "--git-dir");
|
|
14433
14562
|
await hostGit(workspace, "remote", "set-url", "origin", url2);
|
|
14434
14563
|
} catch {
|
|
14435
|
-
const leftovers = await
|
|
14564
|
+
const leftovers = await import_promises3.default.readdir(workspace).catch(() => []);
|
|
14436
14565
|
if (leftovers.includes(".git")) {
|
|
14437
14566
|
throw new Error(
|
|
14438
14567
|
`${workspace} holds a git repository that cannot be read. Remove it and retry.`
|
|
14439
14568
|
);
|
|
14440
14569
|
}
|
|
14441
14570
|
for (const name of leftovers) {
|
|
14442
|
-
await
|
|
14571
|
+
await import_promises3.default.rm(import_path5.default.join(workspace, name), { recursive: true, force: true });
|
|
14443
14572
|
}
|
|
14444
14573
|
await execFileAsync2(
|
|
14445
14574
|
"git",
|
|
@@ -14523,6 +14652,10 @@ function spaceEnv(spaceDir) {
|
|
|
14523
14652
|
WORKSPACE: paths.workspace,
|
|
14524
14653
|
BACKUPS_DIR: paths.backups,
|
|
14525
14654
|
BUILDS_DIR: paths.builds,
|
|
14655
|
+
// Where `factiii-secrets` finds the runner. It can also walk up for the
|
|
14656
|
+
// socket, but only from inside the space tree - a step that cd'd into a
|
|
14657
|
+
// build dir elsewhere would not find it.
|
|
14658
|
+
FACTIII_SECRETS_SOCK: secretsSocketPath(spaceDir),
|
|
14526
14659
|
// No keychain credential helper and no interactive git prompts anywhere
|
|
14527
14660
|
// in the deploy surface (agent sessions + authorized commands): auth
|
|
14528
14661
|
// comes from the secrets store or the tokened URL, never the keychain.
|
|
@@ -14533,15 +14666,15 @@ function spaceEnv(spaceDir) {
|
|
|
14533
14666
|
};
|
|
14534
14667
|
}
|
|
14535
14668
|
async function readContract(workspace) {
|
|
14536
|
-
const skill = await
|
|
14669
|
+
const skill = await import_promises3.default.readFile(import_path5.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
|
|
14537
14670
|
throw new Error("The creator wrote no SKILL.md.");
|
|
14538
14671
|
});
|
|
14539
|
-
const checker = await
|
|
14672
|
+
const checker = await import_promises3.default.readFile(import_path5.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
|
|
14540
14673
|
throw new Error("The creator wrote no check-environment.sh.");
|
|
14541
14674
|
});
|
|
14542
14675
|
try {
|
|
14543
14676
|
const raw = JSON.parse(
|
|
14544
|
-
await
|
|
14677
|
+
await import_promises3.default.readFile(import_path5.default.join(workspace, VARIABLES_PATH), "utf-8")
|
|
14545
14678
|
);
|
|
14546
14679
|
return {
|
|
14547
14680
|
skill,
|
|
@@ -14558,7 +14691,7 @@ async function generateDeploySkill(opts) {
|
|
|
14558
14691
|
throw new Error("Describe your deployment process first.");
|
|
14559
14692
|
}
|
|
14560
14693
|
const status = await checkDeployStatus(opts);
|
|
14561
|
-
const workspace =
|
|
14694
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14562
14695
|
sendLog({
|
|
14563
14696
|
type: "system",
|
|
14564
14697
|
content: `Resetting workspace to origin/${status.branch}...`
|
|
@@ -14583,13 +14716,12 @@ async function generateDeploySkill(opts) {
|
|
|
14583
14716
|
prompt: buildPrompt(prompts_default.deployCreate, {
|
|
14584
14717
|
INSTRUCTIONS: instructions
|
|
14585
14718
|
}),
|
|
14586
|
-
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
14719
|
+
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch,Skill",
|
|
14587
14720
|
// auto: the per-action classifier blocks escalations/unrecognized-infra
|
|
14588
14721
|
// actions - a host session with no human reviewing each command should
|
|
14589
14722
|
// not run bypassPermissions. (Codex maps this to a workspace-write
|
|
14590
14723
|
// sandbox; see agent.ts.)
|
|
14591
14724
|
permissionMode: "auto",
|
|
14592
|
-
model: "claude-opus-4-6",
|
|
14593
14725
|
includePartialMessages: true,
|
|
14594
14726
|
env: spaceEnv(opts.spaceDir),
|
|
14595
14727
|
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
@@ -14602,7 +14734,7 @@ async function generateDeploySkill(opts) {
|
|
|
14602
14734
|
async function reviseDeploySkill(opts) {
|
|
14603
14735
|
const { feedback, sendLog } = opts;
|
|
14604
14736
|
if (!feedback.trim()) throw new Error("Corrections are empty.");
|
|
14605
|
-
const workspace =
|
|
14737
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14606
14738
|
sendLog({ type: "system", content: "Applying your corrections..." });
|
|
14607
14739
|
const { result } = await spawnAgent(
|
|
14608
14740
|
opts.provider,
|
|
@@ -14612,9 +14744,8 @@ async function reviseDeploySkill(opts) {
|
|
|
14612
14744
|
prompt: buildPrompt(prompts_default.deployCreateRevise, {
|
|
14613
14745
|
FEEDBACK: feedback
|
|
14614
14746
|
}),
|
|
14615
|
-
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch",
|
|
14747
|
+
allowedTools: "Read,Grep,Glob,Bash,Edit,Write,WebSearch,WebFetch,Skill",
|
|
14616
14748
|
permissionMode: "auto",
|
|
14617
|
-
model: "claude-opus-4-6",
|
|
14618
14749
|
includePartialMessages: true,
|
|
14619
14750
|
resumeSessionId: opts.resumeSessionId || void 0,
|
|
14620
14751
|
env: spaceEnv(opts.spaceDir),
|
|
@@ -14631,7 +14762,7 @@ async function loadSkillDraft(opts) {
|
|
|
14631
14762
|
throw new Error("Not a deploy-skill branch.");
|
|
14632
14763
|
}
|
|
14633
14764
|
await checkDeployStatus(opts);
|
|
14634
|
-
const workspace =
|
|
14765
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14635
14766
|
await hostGit(
|
|
14636
14767
|
workspace,
|
|
14637
14768
|
"fetch",
|
|
@@ -14643,7 +14774,7 @@ async function loadSkillDraft(opts) {
|
|
|
14643
14774
|
return readContract(workspace);
|
|
14644
14775
|
}
|
|
14645
14776
|
async function approveDeploySkill(opts) {
|
|
14646
|
-
const workspace =
|
|
14777
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14647
14778
|
await readContract(workspace);
|
|
14648
14779
|
await ensureClaudeSkillLink(workspace);
|
|
14649
14780
|
if (opts.gitName)
|
|
@@ -14686,19 +14817,19 @@ async function runEnvironmentCheck(opts) {
|
|
|
14686
14817
|
"No environment checker on the default branch. Generate the deploy skill first."
|
|
14687
14818
|
);
|
|
14688
14819
|
}
|
|
14689
|
-
const workspace =
|
|
14820
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14690
14821
|
const script = await hostGit(
|
|
14691
14822
|
workspace,
|
|
14692
14823
|
"cat-file",
|
|
14693
14824
|
"-p",
|
|
14694
14825
|
`origin/${status.branch}:${CHECKER_PATH}`
|
|
14695
14826
|
);
|
|
14696
|
-
const tmp =
|
|
14697
|
-
await
|
|
14827
|
+
const tmp = import_path5.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
|
|
14828
|
+
await import_promises3.default.writeFile(tmp, script, { mode: 448 });
|
|
14698
14829
|
let ok = true;
|
|
14699
14830
|
let output = "";
|
|
14700
14831
|
try {
|
|
14701
|
-
const scriptArg = tmp.split(
|
|
14832
|
+
const scriptArg = tmp.split(import_path5.default.sep).join("/");
|
|
14702
14833
|
const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
|
|
14703
14834
|
cwd: workspace,
|
|
14704
14835
|
env: { ...process.env, ...spaceEnv(opts.spaceDir) },
|
|
@@ -14707,7 +14838,7 @@ async function runEnvironmentCheck(opts) {
|
|
|
14707
14838
|
output = res.output;
|
|
14708
14839
|
if (res.code !== 0) ok = false;
|
|
14709
14840
|
} finally {
|
|
14710
|
-
await
|
|
14841
|
+
await import_promises3.default.rm(tmp, { force: true });
|
|
14711
14842
|
}
|
|
14712
14843
|
const checks = [];
|
|
14713
14844
|
for (const line of output.split("\n")) {
|
|
@@ -14729,7 +14860,7 @@ async function fixEnvironment(opts) {
|
|
|
14729
14860
|
const before = await runEnvironmentCheck(opts);
|
|
14730
14861
|
if (before.ok) return before;
|
|
14731
14862
|
const status = await checkDeployStatus(opts);
|
|
14732
|
-
const workspace =
|
|
14863
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14733
14864
|
const script = await hostGit(
|
|
14734
14865
|
workspace,
|
|
14735
14866
|
"cat-file",
|
|
@@ -14754,7 +14885,6 @@ async function fixEnvironment(opts) {
|
|
|
14754
14885
|
// auto: installs are in-scope of the owner's explicit "fix environment"
|
|
14755
14886
|
// request; the classifier still blocks anything beyond it.
|
|
14756
14887
|
permissionMode: "auto",
|
|
14757
|
-
model: "claude-opus-4-6",
|
|
14758
14888
|
includePartialMessages: true,
|
|
14759
14889
|
env: spaceEnv(opts.spaceDir),
|
|
14760
14890
|
callbacks: { onLog: (entry) => sendLog(entry) }
|
|
@@ -14765,7 +14895,7 @@ async function fixEnvironment(opts) {
|
|
|
14765
14895
|
return runEnvironmentCheck(opts);
|
|
14766
14896
|
}
|
|
14767
14897
|
async function discardDeploySkill(opts) {
|
|
14768
|
-
const workspace =
|
|
14898
|
+
const workspace = import_path5.default.join(opts.spaceDir, "workspace");
|
|
14769
14899
|
await hostGit(workspace, "checkout", "--", ".").catch(() => null);
|
|
14770
14900
|
await hostGit(workspace, "clean", "-fd");
|
|
14771
14901
|
return { success: true };
|
|
@@ -14884,7 +15014,7 @@ var AGENTS_ROOT = ".agents/skills";
|
|
|
14884
15014
|
var CLAUDE_ROOT = ".claude/skills";
|
|
14885
15015
|
var CLI_ROOTS = [CLAUDE_ROOT, ".codex/skills"];
|
|
14886
15016
|
function globalAgentsRoot() {
|
|
14887
|
-
return
|
|
15017
|
+
return import_path6.default.join(import_os2.default.homedir(), ".agents", "skills");
|
|
14888
15018
|
}
|
|
14889
15019
|
var AGENTS_LINK_TARGET = "../.agents/skills";
|
|
14890
15020
|
var BRANCH_PREFIX = "skill/";
|
|
@@ -14894,11 +15024,11 @@ function summarize(raw, base) {
|
|
|
14894
15024
|
return { ...base, description: frontmatter.description, errors };
|
|
14895
15025
|
}
|
|
14896
15026
|
async function rootLinkState(root) {
|
|
14897
|
-
const linkPath =
|
|
14898
|
-
const
|
|
14899
|
-
if (!
|
|
14900
|
-
if (!
|
|
14901
|
-
const target = await
|
|
15027
|
+
const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15028
|
+
const stat2 = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15029
|
+
if (!stat2) return "missing";
|
|
15030
|
+
if (!stat2.isSymbolicLink()) return "blocked";
|
|
15031
|
+
const target = await import_promises4.default.readlink(linkPath).catch(() => "");
|
|
14902
15032
|
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
14903
15033
|
}
|
|
14904
15034
|
async function globalLinkState() {
|
|
@@ -14908,12 +15038,12 @@ async function globalLinkState() {
|
|
|
14908
15038
|
}
|
|
14909
15039
|
async function listGlobal() {
|
|
14910
15040
|
const root = globalAgentsRoot();
|
|
14911
|
-
const entries = await
|
|
15041
|
+
const entries = await import_promises4.default.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
14912
15042
|
const out = [];
|
|
14913
15043
|
for (const entry of entries) {
|
|
14914
15044
|
if (entry.isFile()) continue;
|
|
14915
|
-
const dir =
|
|
14916
|
-
const raw = await
|
|
15045
|
+
const dir = import_path6.default.join(root, entry.name);
|
|
15046
|
+
const raw = await import_promises4.default.readFile(import_path6.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
|
|
14917
15047
|
if (!raw) continue;
|
|
14918
15048
|
out.push(
|
|
14919
15049
|
summarize(raw, {
|
|
@@ -15082,7 +15212,7 @@ async function ensureSkillsWorktree(spaceDir, branch) {
|
|
|
15082
15212
|
} catch {
|
|
15083
15213
|
await hostGit(repo, "worktree", "prune").catch(() => {
|
|
15084
15214
|
});
|
|
15085
|
-
await
|
|
15215
|
+
await import_promises4.default.rm(worktree, { recursive: true, force: true });
|
|
15086
15216
|
await hostGit(
|
|
15087
15217
|
repo,
|
|
15088
15218
|
"worktree",
|
|
@@ -15165,11 +15295,11 @@ async function bundleFilesOnDisk(dir) {
|
|
|
15165
15295
|
const out = [];
|
|
15166
15296
|
const walk = async (current, prefix, depth) => {
|
|
15167
15297
|
if (depth > 3 || out.length >= 200) return;
|
|
15168
|
-
const entries = await
|
|
15298
|
+
const entries = await import_promises4.default.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
15169
15299
|
for (const entry of entries) {
|
|
15170
15300
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15171
15301
|
if (entry.isDirectory()) {
|
|
15172
|
-
await walk(
|
|
15302
|
+
await walk(import_path6.default.join(current, entry.name), rel, depth + 1);
|
|
15173
15303
|
} else if (rel !== "SKILL.md") {
|
|
15174
15304
|
out.push(rel);
|
|
15175
15305
|
}
|
|
@@ -15181,7 +15311,7 @@ async function bundleFilesOnDisk(dir) {
|
|
|
15181
15311
|
async function readSkillImpl(opts) {
|
|
15182
15312
|
if (opts.scope === "global") {
|
|
15183
15313
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15184
|
-
const content2 = await
|
|
15314
|
+
const content2 = await import_promises4.default.readFile(import_path6.default.join(dir, "SKILL.md"), "utf-8");
|
|
15185
15315
|
const { frontmatter: frontmatter2, errors: errors2 } = parseSkill(content2);
|
|
15186
15316
|
return {
|
|
15187
15317
|
name: opts.name,
|
|
@@ -15216,55 +15346,55 @@ async function readSkillImpl(opts) {
|
|
|
15216
15346
|
};
|
|
15217
15347
|
}
|
|
15218
15348
|
async function moveIntoAgents(from, to) {
|
|
15219
|
-
if (await
|
|
15349
|
+
if (await import_promises4.default.access(to).then(
|
|
15220
15350
|
() => true,
|
|
15221
15351
|
() => false
|
|
15222
15352
|
)) {
|
|
15223
15353
|
throw new Error(
|
|
15224
|
-
`${
|
|
15354
|
+
`${import_path6.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
|
|
15225
15355
|
);
|
|
15226
15356
|
}
|
|
15227
|
-
await
|
|
15357
|
+
await import_promises4.default.mkdir(import_path6.default.dirname(to), { recursive: true });
|
|
15228
15358
|
try {
|
|
15229
|
-
await
|
|
15359
|
+
await import_promises4.default.rename(from, to);
|
|
15230
15360
|
} catch {
|
|
15231
|
-
await
|
|
15232
|
-
await
|
|
15361
|
+
await import_promises4.default.cp(from, to, { recursive: true });
|
|
15362
|
+
await import_promises4.default.rm(from, { recursive: true, force: true });
|
|
15233
15363
|
}
|
|
15234
15364
|
}
|
|
15235
15365
|
async function migrateGlobal() {
|
|
15236
15366
|
const moved = [];
|
|
15237
15367
|
for (const root of CLI_ROOTS) {
|
|
15238
|
-
const dir =
|
|
15239
|
-
const
|
|
15240
|
-
if (!
|
|
15241
|
-
const entries = await
|
|
15368
|
+
const dir = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15369
|
+
const stat2 = await import_promises4.default.lstat(dir).catch(() => null);
|
|
15370
|
+
if (!stat2 || stat2.isSymbolicLink()) continue;
|
|
15371
|
+
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15242
15372
|
for (const entry of entries) {
|
|
15243
15373
|
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
15244
|
-
const src =
|
|
15245
|
-
if (!await
|
|
15374
|
+
const src = import_path6.default.join(dir, entry.name);
|
|
15375
|
+
if (!await import_promises4.default.access(import_path6.default.join(src, "SKILL.md")).then(
|
|
15246
15376
|
() => true,
|
|
15247
15377
|
() => false
|
|
15248
15378
|
)) {
|
|
15249
15379
|
continue;
|
|
15250
15380
|
}
|
|
15251
|
-
await moveIntoAgents(src,
|
|
15381
|
+
await moveIntoAgents(src, import_path6.default.join(globalAgentsRoot(), entry.name));
|
|
15252
15382
|
moved.push(entry.name);
|
|
15253
15383
|
}
|
|
15254
|
-
await
|
|
15384
|
+
await import_promises4.default.rmdir(dir).catch(() => {
|
|
15255
15385
|
});
|
|
15256
15386
|
}
|
|
15257
15387
|
for (const root of CLI_ROOTS) {
|
|
15258
|
-
const linkPath =
|
|
15259
|
-
const after = await
|
|
15260
|
-
if (after?.isSymbolicLink()) await
|
|
15388
|
+
const linkPath = import_path6.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15389
|
+
const after = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15390
|
+
if (after?.isSymbolicLink()) await import_promises4.default.rm(linkPath, { force: true });
|
|
15261
15391
|
else if (after) {
|
|
15262
15392
|
throw new Error(
|
|
15263
15393
|
`~/${root} still has files that are not skills. Empty it, then migrate.`
|
|
15264
15394
|
);
|
|
15265
15395
|
}
|
|
15266
|
-
await
|
|
15267
|
-
await
|
|
15396
|
+
await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
|
|
15397
|
+
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15268
15398
|
}
|
|
15269
15399
|
return moved;
|
|
15270
15400
|
}
|
|
@@ -15298,13 +15428,13 @@ async function migrateSkillsImpl(opts) {
|
|
|
15298
15428
|
`${name} exists in both ${AGENTS_ROOT} and ${stray}. Remove one copy, then migrate.`
|
|
15299
15429
|
);
|
|
15300
15430
|
}
|
|
15301
|
-
await
|
|
15431
|
+
await import_promises4.default.mkdir(import_path6.default.join(workspace, AGENTS_ROOT), { recursive: true });
|
|
15302
15432
|
await hostGit(workspace, "mv", stray, dest);
|
|
15303
15433
|
moved.push(name);
|
|
15304
15434
|
}
|
|
15305
|
-
const linkPath =
|
|
15306
|
-
await
|
|
15307
|
-
await
|
|
15435
|
+
const linkPath = import_path6.default.join(workspace, CLAUDE_ROOT);
|
|
15436
|
+
await import_promises4.default.rm(linkPath, { recursive: true, force: true });
|
|
15437
|
+
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15308
15438
|
await hostGit(workspace, "add", "-A");
|
|
15309
15439
|
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
15310
15440
|
if (!dirty.trim()) return { branch: "", moved: [] };
|
|
@@ -15330,16 +15460,16 @@ function assertWritable(name, content) {
|
|
|
15330
15460
|
}
|
|
15331
15461
|
async function writeSkillFile(opts) {
|
|
15332
15462
|
const dir = resolveSkillDir(opts.root, opts.name);
|
|
15333
|
-
const file =
|
|
15463
|
+
const file = import_path6.default.join(dir, "SKILL.md");
|
|
15334
15464
|
if (opts.keepExisting) {
|
|
15335
|
-
const exists = await
|
|
15465
|
+
const exists = await import_promises4.default.access(file).then(
|
|
15336
15466
|
() => true,
|
|
15337
15467
|
() => false
|
|
15338
15468
|
);
|
|
15339
15469
|
if (exists) return;
|
|
15340
15470
|
}
|
|
15341
|
-
await
|
|
15342
|
-
await
|
|
15471
|
+
await import_promises4.default.mkdir(dir, { recursive: true });
|
|
15472
|
+
await import_promises4.default.writeFile(file, opts.content, "utf-8");
|
|
15343
15473
|
}
|
|
15344
15474
|
async function writeGlobalSkill(opts) {
|
|
15345
15475
|
assertWritable(opts.name, opts.content);
|
|
@@ -15354,14 +15484,14 @@ async function installBundledSkill(opts) {
|
|
|
15354
15484
|
content: opts.content,
|
|
15355
15485
|
keepExisting: true
|
|
15356
15486
|
});
|
|
15357
|
-
await write(
|
|
15487
|
+
await write(import_path6.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15358
15488
|
for (const root of CLI_ROOTS) {
|
|
15359
|
-
const linkPath =
|
|
15360
|
-
const
|
|
15361
|
-
if (
|
|
15362
|
-
if (!
|
|
15363
|
-
await
|
|
15364
|
-
await
|
|
15489
|
+
const linkPath = import_path6.default.join(home, ...root.split("/"));
|
|
15490
|
+
const stat2 = await import_promises4.default.lstat(linkPath).catch(() => null);
|
|
15491
|
+
if (stat2?.isSymbolicLink()) continue;
|
|
15492
|
+
if (!stat2) {
|
|
15493
|
+
await import_promises4.default.mkdir(import_path6.default.dirname(linkPath), { recursive: true });
|
|
15494
|
+
await import_promises4.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15365
15495
|
continue;
|
|
15366
15496
|
}
|
|
15367
15497
|
await write(linkPath);
|
|
@@ -15369,7 +15499,7 @@ async function installBundledSkill(opts) {
|
|
|
15369
15499
|
}
|
|
15370
15500
|
async function deleteGlobalSkill(opts) {
|
|
15371
15501
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15372
|
-
await
|
|
15502
|
+
await import_promises4.default.rm(dir, { recursive: true, force: true });
|
|
15373
15503
|
}
|
|
15374
15504
|
async function proposeRepoSkillImpl(opts) {
|
|
15375
15505
|
assertWritable(opts.name, opts.content);
|
|
@@ -15407,9 +15537,9 @@ async function proposeRepoSkillImpl(opts) {
|
|
|
15407
15537
|
if (opts.gitEmail) {
|
|
15408
15538
|
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
15409
15539
|
}
|
|
15410
|
-
await
|
|
15411
|
-
await
|
|
15412
|
-
|
|
15540
|
+
await import_promises4.default.mkdir(import_path6.default.join(workspace, dir), { recursive: true });
|
|
15541
|
+
await import_promises4.default.writeFile(
|
|
15542
|
+
import_path6.default.join(workspace, dir, "SKILL.md"),
|
|
15413
15543
|
opts.content,
|
|
15414
15544
|
"utf-8"
|
|
15415
15545
|
);
|
|
@@ -15516,18 +15646,18 @@ function vendoredBinaryName() {
|
|
|
15516
15646
|
async function installFrpcBinary(target) {
|
|
15517
15647
|
const p = frpcPaths(target);
|
|
15518
15648
|
const binaryName = vendoredBinaryName();
|
|
15519
|
-
const src =
|
|
15649
|
+
const src = import_path7.default.join(__dirname, "..", "vendor", binaryName);
|
|
15520
15650
|
if (!(0, import_fs2.existsSync)(src)) {
|
|
15521
15651
|
throw new Error(
|
|
15522
15652
|
`Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
|
|
15523
15653
|
);
|
|
15524
15654
|
}
|
|
15525
|
-
const staged = await
|
|
15526
|
-
const source = await
|
|
15655
|
+
const staged = await import_promises5.default.stat(p.binary).catch(() => null);
|
|
15656
|
+
const source = await import_promises5.default.stat(src);
|
|
15527
15657
|
if (staged?.size === source.size) return;
|
|
15528
|
-
await
|
|
15529
|
-
await
|
|
15530
|
-
await
|
|
15658
|
+
await import_promises5.default.mkdir(p.binDir, { recursive: true });
|
|
15659
|
+
await import_promises5.default.copyFile(src, p.binary);
|
|
15660
|
+
await import_promises5.default.chmod(p.binary, 493);
|
|
15531
15661
|
}
|
|
15532
15662
|
var EXPOSE_SKILL_NAME = "share-a-port";
|
|
15533
15663
|
var FENCE = "```";
|
|
@@ -15917,18 +16047,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
15917
16047
|
await this.core.ensureSpaceWorkspace();
|
|
15918
16048
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
15919
16049
|
if (hasGit) {
|
|
15920
|
-
const { path:
|
|
16050
|
+
const { path: path20 } = await addWorktree(
|
|
15921
16051
|
this.core.target(),
|
|
15922
16052
|
session.taskId,
|
|
15923
16053
|
baseBranch || config.mainBranch || "main",
|
|
15924
16054
|
"card"
|
|
15925
16055
|
);
|
|
15926
|
-
session.workspacePath =
|
|
16056
|
+
session.workspacePath = path20;
|
|
15927
16057
|
await setGitIdentity(
|
|
15928
16058
|
this.core.target(),
|
|
15929
16059
|
config.gitName,
|
|
15930
16060
|
config.gitEmail,
|
|
15931
|
-
|
|
16061
|
+
path20
|
|
15932
16062
|
);
|
|
15933
16063
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
15934
16064
|
} else {
|
|
@@ -16150,18 +16280,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16150
16280
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16151
16281
|
await this.core.ensureSpaceWorkspace();
|
|
16152
16282
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16153
|
-
const { path:
|
|
16283
|
+
const { path: path20 } = await addWorktree(
|
|
16154
16284
|
this.core.target(),
|
|
16155
16285
|
session.taskId,
|
|
16156
16286
|
config.mainBranch || "main",
|
|
16157
16287
|
"card"
|
|
16158
16288
|
);
|
|
16159
|
-
session.workspacePath =
|
|
16289
|
+
session.workspacePath = path20;
|
|
16160
16290
|
await setGitIdentity(
|
|
16161
16291
|
this.core.target(),
|
|
16162
16292
|
config.gitName,
|
|
16163
16293
|
config.gitEmail,
|
|
16164
|
-
|
|
16294
|
+
path20
|
|
16165
16295
|
);
|
|
16166
16296
|
await this.putCard(session);
|
|
16167
16297
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16456,6 +16586,50 @@ async function startTmuxSession(opts) {
|
|
|
16456
16586
|
};
|
|
16457
16587
|
}
|
|
16458
16588
|
|
|
16589
|
+
// ../../shared/all/helpers/terminal-ai/agentState.ts
|
|
16590
|
+
var STATES = /* @__PURE__ */ new Set(["working", "idle", "blocked"]);
|
|
16591
|
+
var AGENT_STATE_CHANNEL = "agent-state";
|
|
16592
|
+
function agentStateKey(paneId) {
|
|
16593
|
+
return `agent-state:${paneId}`;
|
|
16594
|
+
}
|
|
16595
|
+
function parseAgentMessage(payload) {
|
|
16596
|
+
const at = payload.indexOf(" ");
|
|
16597
|
+
if (at <= 0) return null;
|
|
16598
|
+
const state = parseAgentState(payload.slice(at + 1));
|
|
16599
|
+
return state ? { paneId: payload.slice(0, at), state } : null;
|
|
16600
|
+
}
|
|
16601
|
+
function postIdForSessionName(sessionName, postIds) {
|
|
16602
|
+
for (const postId of postIds) {
|
|
16603
|
+
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
16604
|
+
if (sessionName.startsWith(prefix)) return postId;
|
|
16605
|
+
}
|
|
16606
|
+
return null;
|
|
16607
|
+
}
|
|
16608
|
+
function parseAgentState(text) {
|
|
16609
|
+
const word = text.trim();
|
|
16610
|
+
return STATES.has(word) ? word : null;
|
|
16611
|
+
}
|
|
16612
|
+
function drainStateMessages(buffer) {
|
|
16613
|
+
const lines = buffer.split("\n");
|
|
16614
|
+
const partial = lines.pop() ?? "";
|
|
16615
|
+
const states = [];
|
|
16616
|
+
let i = 0;
|
|
16617
|
+
let consumed = 0;
|
|
16618
|
+
while (i < lines.length) {
|
|
16619
|
+
if (lines[i] !== "message") {
|
|
16620
|
+
i += 1;
|
|
16621
|
+
consumed = i;
|
|
16622
|
+
continue;
|
|
16623
|
+
}
|
|
16624
|
+
if (i + 2 >= lines.length) break;
|
|
16625
|
+
const parsed = parseAgentMessage(lines[i + 2]);
|
|
16626
|
+
if (parsed) states.push(parsed);
|
|
16627
|
+
i += 3;
|
|
16628
|
+
consumed = i;
|
|
16629
|
+
}
|
|
16630
|
+
return { states, rest: [...lines.slice(consumed), partial].join("\n") };
|
|
16631
|
+
}
|
|
16632
|
+
|
|
16459
16633
|
// ../../shared/all/helpers/terminal-ai/processes.ts
|
|
16460
16634
|
var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
|
|
16461
16635
|
var PS_FIELDS = 5;
|
|
@@ -16501,6 +16675,45 @@ function descendantsOf(rows, roots) {
|
|
|
16501
16675
|
}
|
|
16502
16676
|
return found.sort((a, b) => a.pid - b.pid);
|
|
16503
16677
|
}
|
|
16678
|
+
function panesByPid(raw) {
|
|
16679
|
+
const found = /* @__PURE__ */ new Map();
|
|
16680
|
+
for (const line of raw.split("\n")) {
|
|
16681
|
+
const trimmed = line.trim();
|
|
16682
|
+
const pid = Number(trimmed.split(/\s+/)[0]);
|
|
16683
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
16684
|
+
const match = /(?:^|\s)TMUX_PANE=(%\d+)(?:\s|$)/.exec(trimmed);
|
|
16685
|
+
if (match) found.set(pid, match[1]);
|
|
16686
|
+
}
|
|
16687
|
+
return found;
|
|
16688
|
+
}
|
|
16689
|
+
async function processesForCard(panes, table, lookupPanes) {
|
|
16690
|
+
const mine = /* @__PURE__ */ new Map();
|
|
16691
|
+
for (const pane of panes) {
|
|
16692
|
+
for (const row of processesForPane(table, pane.pid)) {
|
|
16693
|
+
mine.set(row.pid, { tabId: pane.tabId, row });
|
|
16694
|
+
}
|
|
16695
|
+
}
|
|
16696
|
+
const shells = new Set(panes.map((pane) => pane.pid));
|
|
16697
|
+
const rest = table.filter((row) => row.pid > 1 && !mine.has(row.pid) && !shells.has(row.pid)).map((row) => row.pid);
|
|
16698
|
+
if (rest.length === 0) return mine;
|
|
16699
|
+
const paneOf = await lookupPanes(rest);
|
|
16700
|
+
const tabByPane = new Map(panes.map((pane) => [pane.paneId, pane.tabId]));
|
|
16701
|
+
const adopted = /* @__PURE__ */ new Map();
|
|
16702
|
+
for (const row of table) {
|
|
16703
|
+
const tabId = tabByPane.get(paneOf.get(row.pid) ?? "");
|
|
16704
|
+
if (!tabId || mine.has(row.pid)) continue;
|
|
16705
|
+
mine.set(row.pid, { tabId, row });
|
|
16706
|
+
const roots = adopted.get(tabId);
|
|
16707
|
+
if (roots) roots.push(row.pid);
|
|
16708
|
+
else adopted.set(tabId, [row.pid]);
|
|
16709
|
+
}
|
|
16710
|
+
for (const [tabId, roots] of adopted) {
|
|
16711
|
+
for (const row of descendantsOf(table, roots)) {
|
|
16712
|
+
if (!mine.has(row.pid)) mine.set(row.pid, { tabId, row });
|
|
16713
|
+
}
|
|
16714
|
+
}
|
|
16715
|
+
return mine;
|
|
16716
|
+
}
|
|
16504
16717
|
function commandOf(args) {
|
|
16505
16718
|
const first = args.trim().split(/\s+/)[0] ?? "";
|
|
16506
16719
|
const base = first.split("/").pop() ?? first;
|
|
@@ -16625,12 +16838,12 @@ var FLUSH_MS = 120;
|
|
|
16625
16838
|
var SELF_WRITE_MS = 2e3;
|
|
16626
16839
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16627
16840
|
var importFs = new Function("s", "return import(s)");
|
|
16628
|
-
async function resolveReal(
|
|
16841
|
+
async function resolveReal(path20) {
|
|
16629
16842
|
try {
|
|
16630
16843
|
const { realpath } = await importFs("fs/promises");
|
|
16631
|
-
return await realpath(
|
|
16844
|
+
return await realpath(path20);
|
|
16632
16845
|
} catch {
|
|
16633
|
-
return
|
|
16846
|
+
return path20;
|
|
16634
16847
|
}
|
|
16635
16848
|
}
|
|
16636
16849
|
var loadSubscribe = null;
|
|
@@ -16744,19 +16957,19 @@ var WorkspaceWatcher = class {
|
|
|
16744
16957
|
}
|
|
16745
16958
|
record(kind, relPath) {
|
|
16746
16959
|
if (this.closed) return;
|
|
16747
|
-
const
|
|
16748
|
-
const expiry = this.selfWrites.get(
|
|
16960
|
+
const path20 = "/" + relPath;
|
|
16961
|
+
const expiry = this.selfWrites.get(path20);
|
|
16749
16962
|
if (expiry !== void 0) {
|
|
16750
16963
|
if (expiry > Date.now()) return;
|
|
16751
|
-
this.selfWrites.delete(
|
|
16964
|
+
this.selfWrites.delete(path20);
|
|
16752
16965
|
}
|
|
16753
16966
|
const { added, changed, removed } = this.pending;
|
|
16754
|
-
added.delete(
|
|
16755
|
-
changed.delete(
|
|
16756
|
-
removed.delete(
|
|
16757
|
-
if (kind === "added") added.add(
|
|
16758
|
-
else if (kind === "changed") changed.add(
|
|
16759
|
-
else removed.add(
|
|
16967
|
+
added.delete(path20);
|
|
16968
|
+
changed.delete(path20);
|
|
16969
|
+
removed.delete(path20);
|
|
16970
|
+
if (kind === "added") added.add(path20);
|
|
16971
|
+
else if (kind === "changed") changed.add(path20);
|
|
16972
|
+
else removed.add(path20);
|
|
16760
16973
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
16761
16974
|
this.pending = { ...emptyPending(), resync: true };
|
|
16762
16975
|
}
|
|
@@ -16775,8 +16988,8 @@ var WorkspaceWatcher = class {
|
|
|
16775
16988
|
this.pending = emptyPending();
|
|
16776
16989
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
16777
16990
|
const now = Date.now();
|
|
16778
|
-
for (const [
|
|
16779
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
16991
|
+
for (const [path20, expiry] of this.selfWrites) {
|
|
16992
|
+
if (expiry <= now) this.selfWrites.delete(path20);
|
|
16780
16993
|
}
|
|
16781
16994
|
this.emit({
|
|
16782
16995
|
added: resync ? [] : [...added],
|
|
@@ -16822,6 +17035,7 @@ async function writeBase64File(target, abs, b64, opts = {}) {
|
|
|
16822
17035
|
`Write ${abs}`
|
|
16823
17036
|
);
|
|
16824
17037
|
}
|
|
17038
|
+
var PANE_REREAD_MS = 1e3;
|
|
16825
17039
|
var TerminalModeEngine = class {
|
|
16826
17040
|
constructor(core, emitter) {
|
|
16827
17041
|
this.core = core;
|
|
@@ -16832,12 +17046,117 @@ var TerminalModeEngine = class {
|
|
|
16832
17046
|
/** Worktree name each session claimed, keyed by postId. Covers the window
|
|
16833
17047
|
* `claimWorkName`'s disk scan cannot: a name taken but not yet created. */
|
|
16834
17048
|
this.reservedNames = /* @__PURE__ */ new Map();
|
|
17049
|
+
// ── agent activity ──
|
|
17050
|
+
/** Long-lived `redis-cli subscribe` on the space's own socket. One for every
|
|
17051
|
+
* session: the channel is space-wide and the pane rides in the payload, so
|
|
17052
|
+
* a session starting does not mean re-subscribing. */
|
|
17053
|
+
this.stateSub = null;
|
|
17054
|
+
/** Applies states one at a time; see the comment at the push site. */
|
|
17055
|
+
this.stateQueue = Promise.resolve();
|
|
17056
|
+
/** pane id -> tmux session name, refreshed only when a pane we have not seen
|
|
17057
|
+
* turns up. Panes outlive individual messages, so re-reading the whole map
|
|
17058
|
+
* per message would be a tmux call per hook. */
|
|
17059
|
+
this.paneSessions = /* @__PURE__ */ new Map();
|
|
17060
|
+
this.paneReadAt = 0;
|
|
17061
|
+
}
|
|
17062
|
+
/** Idempotent, like startWatching: re-entering a session must not stack
|
|
17063
|
+
* subscribers. */
|
|
17064
|
+
async startStateSub() {
|
|
17065
|
+
if (this.stateSub) return;
|
|
17066
|
+
const target = this.core.target();
|
|
17067
|
+
const child = await target.spawn([
|
|
17068
|
+
"redis-cli",
|
|
17069
|
+
"-s",
|
|
17070
|
+
redisSocketPath(target),
|
|
17071
|
+
"subscribe",
|
|
17072
|
+
AGENT_STATE_CHANNEL
|
|
17073
|
+
]).catch(() => null);
|
|
17074
|
+
if (!child) return;
|
|
17075
|
+
this.stateSub = child;
|
|
17076
|
+
let buffer = "";
|
|
17077
|
+
child.stdout?.on("data", (chunk) => {
|
|
17078
|
+
buffer += String(chunk);
|
|
17079
|
+
const { states, rest } = drainStateMessages(buffer);
|
|
17080
|
+
buffer = rest;
|
|
17081
|
+
for (const { paneId, state } of states) {
|
|
17082
|
+
this.stateQueue = this.stateQueue.then(() => this.applyPaneState(paneId, state)).catch(() => {
|
|
17083
|
+
});
|
|
17084
|
+
}
|
|
17085
|
+
});
|
|
17086
|
+
child.once("exit", () => {
|
|
17087
|
+
if (this.stateSub === child) this.stateSub = null;
|
|
17088
|
+
});
|
|
17089
|
+
void this.hydrateAgentStates();
|
|
17090
|
+
}
|
|
17091
|
+
/** Read the last reported state for every live session's pane.
|
|
17092
|
+
*
|
|
17093
|
+
* Subscribing only catches what happens next. After a runner restart the
|
|
17094
|
+
* sessions come back but no hook has fired yet, so an idle session would
|
|
17095
|
+
* show as working until someone typed in it — which for an idle session may
|
|
17096
|
+
* be never. This is what the hook's SET alongside its PUBLISH is for. */
|
|
17097
|
+
async hydrateAgentStates() {
|
|
17098
|
+
if (this.sessions.size === 0) return;
|
|
17099
|
+
await this.refreshPanes();
|
|
17100
|
+
const target = this.core.target();
|
|
17101
|
+
for (const [paneId, name] of this.paneSessions) {
|
|
17102
|
+
const postId = postIdForSessionName(name, this.sessions.keys());
|
|
17103
|
+
const session = postId ? this.sessions.get(postId) : null;
|
|
17104
|
+
if (!session || session.agentState) continue;
|
|
17105
|
+
const raw = await target.sh(
|
|
17106
|
+
`redis-cli -s ${redisSocketPath(target)} get '${agentStateKey(paneId)}'`
|
|
17107
|
+
).catch(() => "");
|
|
17108
|
+
const state = parseAgentState(raw);
|
|
17109
|
+
if (!state) continue;
|
|
17110
|
+
session.agentState = state;
|
|
17111
|
+
this.emitter.emitSessionUpdate(session);
|
|
17112
|
+
}
|
|
17113
|
+
}
|
|
17114
|
+
/** Called after a session is removed, never before: every stopWatching call
|
|
17115
|
+
* site runs while the session is still in the map, so keying this off
|
|
17116
|
+
* stopWatching would leave the subscriber running for the life of the
|
|
17117
|
+
* process. */
|
|
17118
|
+
releaseStateSub() {
|
|
17119
|
+
if (this.sessions.size > 0) return;
|
|
17120
|
+
this.stateSub?.kill();
|
|
17121
|
+
this.stateSub = null;
|
|
17122
|
+
}
|
|
17123
|
+
async applyPaneState(paneId, state) {
|
|
17124
|
+
let name = this.paneSessions.get(paneId);
|
|
17125
|
+
if (!name) {
|
|
17126
|
+
await this.refreshPanes();
|
|
17127
|
+
name = this.paneSessions.get(paneId);
|
|
17128
|
+
}
|
|
17129
|
+
if (!name) return;
|
|
17130
|
+
const postId = postIdForSessionName(name, this.sessions.keys());
|
|
17131
|
+
if (!postId) return;
|
|
17132
|
+
const session = this.sessions.get(postId);
|
|
17133
|
+
if (!session || session.agentState === state) return;
|
|
17134
|
+
session.agentState = state;
|
|
17135
|
+
this.emitter.emitSessionUpdate(session);
|
|
17136
|
+
}
|
|
17137
|
+
/** Rate-limited: a pane that will never resolve — one whose session has
|
|
17138
|
+
* since closed — would otherwise re-list on every message it sent, which is
|
|
17139
|
+
* a tmux subprocess per hook. */
|
|
17140
|
+
async refreshPanes() {
|
|
17141
|
+
const now = Date.now();
|
|
17142
|
+
if (now - this.paneReadAt < PANE_REREAD_MS) return;
|
|
17143
|
+
this.paneReadAt = now;
|
|
17144
|
+
const target = this.core.target();
|
|
17145
|
+
const out = await target.sh(
|
|
17146
|
+
`${tmuxCli(target)} list-panes -a -F '#{pane_id}|#{session_name}' 2>/dev/null || true`
|
|
17147
|
+
).catch(() => "");
|
|
17148
|
+
this.paneSessions.clear();
|
|
17149
|
+
for (const raw of out.split("\n")) {
|
|
17150
|
+
const [pane, name] = raw.trim().split("|");
|
|
17151
|
+
if (pane && name) this.paneSessions.set(pane, name);
|
|
17152
|
+
}
|
|
16835
17153
|
}
|
|
16836
17154
|
// ── working-tree watching ──
|
|
16837
17155
|
/** Start watching a session's tree. Idempotent: re-entering a session (the
|
|
16838
17156
|
* modal reopening, a hydrate after restart) must not stack watchers. */
|
|
16839
17157
|
startWatching(session) {
|
|
16840
17158
|
const postId = session.postId;
|
|
17159
|
+
void this.startStateSub();
|
|
16841
17160
|
if (this.watchers.has(postId)) return;
|
|
16842
17161
|
const paths = this.core.target().paths;
|
|
16843
17162
|
const root = session.workspacePath || paths.root;
|
|
@@ -16874,8 +17193,8 @@ var TerminalModeEngine = class {
|
|
|
16874
17193
|
/** Suppress the change event our own write is about to produce, so a save
|
|
16875
17194
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
16876
17195
|
* the watcher emits, since callers spell the path either way. */
|
|
16877
|
-
expectWrite(postId,
|
|
16878
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17196
|
+
expectWrite(postId, path20) {
|
|
17197
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path20));
|
|
16879
17198
|
}
|
|
16880
17199
|
// ── Lifecycle / ModeEngine ──
|
|
16881
17200
|
start(base, args) {
|
|
@@ -16921,6 +17240,7 @@ var TerminalModeEngine = class {
|
|
|
16921
17240
|
this.stopWatching(postId);
|
|
16922
17241
|
this.reservedNames.delete(postId);
|
|
16923
17242
|
this.sessions.delete(postId);
|
|
17243
|
+
this.releaseStateSub();
|
|
16924
17244
|
}
|
|
16925
17245
|
async kill(postId) {
|
|
16926
17246
|
let session = this.sessions.get(postId);
|
|
@@ -16938,6 +17258,7 @@ var TerminalModeEngine = class {
|
|
|
16938
17258
|
const removedPostId = session.postId;
|
|
16939
17259
|
this.reservedNames.delete(removedPostId);
|
|
16940
17260
|
this.sessions.delete(removedPostId);
|
|
17261
|
+
this.releaseStateSub();
|
|
16941
17262
|
this.emitter.emitSessionRemoved(removedPostId);
|
|
16942
17263
|
}
|
|
16943
17264
|
destroy() {
|
|
@@ -17070,18 +17391,18 @@ var TerminalModeEngine = class {
|
|
|
17070
17391
|
this.reservedNames.values()
|
|
17071
17392
|
);
|
|
17072
17393
|
this.reservedNames.set(session.postId, workName);
|
|
17073
|
-
const { path:
|
|
17394
|
+
const { path: path20 } = await addWorktree(
|
|
17074
17395
|
this.core.target(),
|
|
17075
17396
|
workName,
|
|
17076
17397
|
branch,
|
|
17077
17398
|
"terminal"
|
|
17078
17399
|
);
|
|
17079
|
-
session.workspacePath =
|
|
17400
|
+
session.workspacePath = path20;
|
|
17080
17401
|
await setGitIdentity(
|
|
17081
17402
|
this.core.target(),
|
|
17082
17403
|
config.gitName,
|
|
17083
17404
|
config.gitEmail,
|
|
17084
|
-
|
|
17405
|
+
path20
|
|
17085
17406
|
);
|
|
17086
17407
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17087
17408
|
} else {
|
|
@@ -17143,11 +17464,11 @@ var TerminalModeEngine = class {
|
|
|
17143
17464
|
}
|
|
17144
17465
|
async bareFsList({
|
|
17145
17466
|
postId,
|
|
17146
|
-
path:
|
|
17467
|
+
path: path20
|
|
17147
17468
|
}) {
|
|
17148
17469
|
const target = this.getBareTarget(postId);
|
|
17149
17470
|
const root = this.getBareRoot(postId);
|
|
17150
|
-
const abs = this.resolveBarePath(
|
|
17471
|
+
const abs = this.resolveBarePath(path20, root);
|
|
17151
17472
|
const relRoot = abs.slice(root.length) || "/";
|
|
17152
17473
|
const out = await target.run([
|
|
17153
17474
|
"sh",
|
|
@@ -17197,30 +17518,30 @@ var TerminalModeEngine = class {
|
|
|
17197
17518
|
}
|
|
17198
17519
|
async bareFsRead({
|
|
17199
17520
|
postId,
|
|
17200
|
-
path:
|
|
17521
|
+
path: path20
|
|
17201
17522
|
}) {
|
|
17202
17523
|
const target = this.getBareTarget(postId);
|
|
17203
|
-
const abs = this.resolveBarePath(
|
|
17524
|
+
const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
|
|
17204
17525
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17205
17526
|
'`)).trim();
|
|
17206
17527
|
return { content: b64, encoding: "base64" };
|
|
17207
17528
|
}
|
|
17208
17529
|
async bareFsWrite({
|
|
17209
17530
|
postId,
|
|
17210
|
-
path:
|
|
17531
|
+
path: path20,
|
|
17211
17532
|
content,
|
|
17212
17533
|
encoding
|
|
17213
17534
|
}) {
|
|
17214
17535
|
const target = this.getBareTarget(postId);
|
|
17215
|
-
const abs = this.resolveBarePath(
|
|
17536
|
+
const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
|
|
17216
17537
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17217
17538
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17218
17539
|
throw new Error("Invalid base64 payload.");
|
|
17219
17540
|
}
|
|
17220
|
-
this.expectWrite(String(postId),
|
|
17541
|
+
this.expectWrite(String(postId), path20);
|
|
17221
17542
|
await writeBase64File(target, abs, b64);
|
|
17222
17543
|
const session = this.sessions.get(String(postId));
|
|
17223
|
-
if (session) delete session.buffers[
|
|
17544
|
+
if (session) delete session.buffers[path20];
|
|
17224
17545
|
}
|
|
17225
17546
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17226
17547
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17270,39 +17591,39 @@ var TerminalModeEngine = class {
|
|
|
17270
17591
|
}
|
|
17271
17592
|
bareOpenFile({
|
|
17272
17593
|
postId: rawPostId,
|
|
17273
|
-
path:
|
|
17594
|
+
path: path20
|
|
17274
17595
|
}) {
|
|
17275
17596
|
const session = this.sessions.get(String(rawPostId));
|
|
17276
17597
|
if (!session || session.mode !== "bare") return;
|
|
17277
|
-
if (!session.openedFiles.includes(
|
|
17278
|
-
session.openedFiles.push(
|
|
17598
|
+
if (!session.openedFiles.includes(path20)) {
|
|
17599
|
+
session.openedFiles.push(path20);
|
|
17279
17600
|
}
|
|
17280
17601
|
}
|
|
17281
17602
|
bareCloseFile({
|
|
17282
17603
|
postId: rawPostId,
|
|
17283
|
-
path:
|
|
17604
|
+
path: path20
|
|
17284
17605
|
}) {
|
|
17285
17606
|
const session = this.sessions.get(String(rawPostId));
|
|
17286
17607
|
if (!session || session.mode !== "bare") return;
|
|
17287
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17288
|
-
delete session.buffers[
|
|
17608
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path20);
|
|
17609
|
+
delete session.buffers[path20];
|
|
17289
17610
|
}
|
|
17290
17611
|
bareBufferSet({
|
|
17291
17612
|
postId: rawPostId,
|
|
17292
|
-
path:
|
|
17613
|
+
path: path20,
|
|
17293
17614
|
content
|
|
17294
17615
|
}) {
|
|
17295
17616
|
const session = this.sessions.get(String(rawPostId));
|
|
17296
17617
|
if (!session || session.mode !== "bare") return;
|
|
17297
|
-
session.buffers[
|
|
17618
|
+
session.buffers[path20] = content;
|
|
17298
17619
|
}
|
|
17299
17620
|
async bareFsDelete({
|
|
17300
17621
|
postId,
|
|
17301
|
-
path:
|
|
17622
|
+
path: path20
|
|
17302
17623
|
}) {
|
|
17303
17624
|
const target = this.getBareTarget(postId);
|
|
17304
17625
|
const root = this.getBareRoot(postId);
|
|
17305
|
-
const abs = this.resolveBarePath(
|
|
17626
|
+
const abs = this.resolveBarePath(path20, root);
|
|
17306
17627
|
if (abs === root) {
|
|
17307
17628
|
throw new Error("Refusing to delete the repo root.");
|
|
17308
17629
|
}
|
|
@@ -17310,10 +17631,10 @@ var TerminalModeEngine = class {
|
|
|
17310
17631
|
}
|
|
17311
17632
|
async bareFsMkdir({
|
|
17312
17633
|
postId,
|
|
17313
|
-
path:
|
|
17634
|
+
path: path20
|
|
17314
17635
|
}) {
|
|
17315
17636
|
const target = this.getBareTarget(postId);
|
|
17316
|
-
const abs = this.resolveBarePath(
|
|
17637
|
+
const abs = this.resolveBarePath(path20, this.getBareRoot(postId));
|
|
17317
17638
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17318
17639
|
}
|
|
17319
17640
|
async bareReview({
|
|
@@ -17456,53 +17777,63 @@ var TerminalModeEngine = class {
|
|
|
17456
17777
|
return [];
|
|
17457
17778
|
}
|
|
17458
17779
|
}
|
|
17459
|
-
/**
|
|
17460
|
-
*
|
|
17780
|
+
/** This card's panes: the pid its tree hangs off, and the pane id its
|
|
17781
|
+
* processes carry in their environment. */
|
|
17461
17782
|
async panePids(target, postId) {
|
|
17462
17783
|
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
17463
17784
|
const stdout = await target.sh(
|
|
17464
|
-
`${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}' 2>/dev/null || true`
|
|
17785
|
+
`${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}|#{pane_id}' 2>/dev/null || true`
|
|
17465
17786
|
).catch(() => "");
|
|
17466
17787
|
const panes = [];
|
|
17467
17788
|
for (const raw of stdout.split("\n")) {
|
|
17468
17789
|
const line = raw.trim();
|
|
17469
17790
|
if (!line.startsWith(prefix)) continue;
|
|
17470
|
-
const
|
|
17471
|
-
const
|
|
17472
|
-
if (
|
|
17473
|
-
|
|
17474
|
-
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
17475
|
-
panes.push({ tabId: rest.slice(0, sep), pid });
|
|
17791
|
+
const [tabId, rawPid, paneId] = line.slice(prefix.length).split("|");
|
|
17792
|
+
const pid = Number(rawPid);
|
|
17793
|
+
if (!tabId || !paneId || !Number.isInteger(pid) || pid <= 0) continue;
|
|
17794
|
+
panes.push({ tabId, pid, paneId });
|
|
17476
17795
|
}
|
|
17477
17796
|
return panes;
|
|
17478
17797
|
}
|
|
17798
|
+
/**
|
|
17799
|
+
* Every process this card is running, by pid. Shared with bareKillProcess,
|
|
17800
|
+
* so stopping a process accepts exactly the set the panel shows.
|
|
17801
|
+
*
|
|
17802
|
+
* The `-p <pids>` on the environment call is required, not a saving: macOS
|
|
17803
|
+
* omits environments from an unscoped `ps -axeww` — see panesByPid.
|
|
17804
|
+
*/
|
|
17805
|
+
async cardProcesses(target, postId) {
|
|
17806
|
+
const panes = await this.panePids(target, postId);
|
|
17807
|
+
if (panes.length === 0) return /* @__PURE__ */ new Map();
|
|
17808
|
+
const table = parsePsTable(
|
|
17809
|
+
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17810
|
+
);
|
|
17811
|
+
return processesForCard(
|
|
17812
|
+
panes,
|
|
17813
|
+
table,
|
|
17814
|
+
async (pids) => panesByPid(
|
|
17815
|
+
await target.run(["ps", "eww", "-o", "pid=,command=", "-p", pids.join(",")]).catch(() => "")
|
|
17816
|
+
)
|
|
17817
|
+
);
|
|
17818
|
+
}
|
|
17479
17819
|
async bareListProcesses({
|
|
17480
17820
|
postId: rawPostId
|
|
17481
17821
|
}) {
|
|
17482
17822
|
const postId = String(rawPostId);
|
|
17483
17823
|
const target = this.getBareTarget(postId);
|
|
17484
|
-
const
|
|
17485
|
-
if (
|
|
17486
|
-
const
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
cpu: row.cpu,
|
|
17498
|
-
memoryKb: row.rssKb,
|
|
17499
|
-
elapsed: row.elapsed,
|
|
17500
|
-
ports: [],
|
|
17501
|
-
connections: 0
|
|
17502
|
-
});
|
|
17503
|
-
}
|
|
17504
|
-
}
|
|
17505
|
-
if (found.length === 0) return [];
|
|
17824
|
+
const mine = await this.cardProcesses(target, postId);
|
|
17825
|
+
if (mine.size === 0) return [];
|
|
17826
|
+
const found = Array.from(mine.values(), ({ tabId, row }) => ({
|
|
17827
|
+
pid: row.pid,
|
|
17828
|
+
tabId,
|
|
17829
|
+
command: commandOf(row.args),
|
|
17830
|
+
args: row.args,
|
|
17831
|
+
cpu: row.cpu,
|
|
17832
|
+
memoryKb: row.rssKb,
|
|
17833
|
+
elapsed: row.elapsed,
|
|
17834
|
+
ports: [],
|
|
17835
|
+
connections: 0
|
|
17836
|
+
}));
|
|
17506
17837
|
const sockets = await socketsFor(
|
|
17507
17838
|
target,
|
|
17508
17839
|
found.map((p) => p.pid)
|
|
@@ -17525,14 +17856,8 @@ var TerminalModeEngine = class {
|
|
|
17525
17856
|
if (!Number.isInteger(pid) || pid <= 1) {
|
|
17526
17857
|
throw new Error("Not a process this card can stop.");
|
|
17527
17858
|
}
|
|
17528
|
-
const
|
|
17529
|
-
|
|
17530
|
-
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17531
|
-
);
|
|
17532
|
-
const mine = panes.some(
|
|
17533
|
-
(pane) => processesForPane(table, pane.pid).some((row) => row.pid === pid)
|
|
17534
|
-
);
|
|
17535
|
-
if (!mine) {
|
|
17859
|
+
const mine = await this.cardProcesses(target, postId);
|
|
17860
|
+
if (!mine.has(pid)) {
|
|
17536
17861
|
throw new Error("That process is not running in this card.");
|
|
17537
17862
|
}
|
|
17538
17863
|
await target.run(["kill", "-TERM", String(pid)]);
|
|
@@ -17991,8 +18316,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
17991
18316
|
};
|
|
17992
18317
|
}
|
|
17993
18318
|
}
|
|
17994
|
-
async function githubApi(token,
|
|
17995
|
-
const res = await fetch(`${API_BASE}${
|
|
18319
|
+
async function githubApi(token, path20) {
|
|
18320
|
+
const res = await fetch(`${API_BASE}${path20}`, {
|
|
17996
18321
|
headers: {
|
|
17997
18322
|
Authorization: `Bearer ${token}`,
|
|
17998
18323
|
Accept: "application/vnd.github+json",
|
|
@@ -18001,7 +18326,7 @@ async function githubApi(token, path17) {
|
|
|
18001
18326
|
});
|
|
18002
18327
|
if (!res.ok) {
|
|
18003
18328
|
throw new Error(
|
|
18004
|
-
`GitHub ${
|
|
18329
|
+
`GitHub ${path20} failed (${res.status}): ${await res.text()}`
|
|
18005
18330
|
);
|
|
18006
18331
|
}
|
|
18007
18332
|
return await res.json();
|
|
@@ -18046,13 +18371,13 @@ async function listGithubBranches(token, fullName) {
|
|
|
18046
18371
|
}
|
|
18047
18372
|
|
|
18048
18373
|
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
18049
|
-
var
|
|
18374
|
+
var import_path9 = __toESM(require("path"));
|
|
18050
18375
|
|
|
18051
18376
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
18052
18377
|
var import_child_process4 = require("child_process");
|
|
18053
18378
|
var import_crypto2 = __toESM(require("crypto"));
|
|
18054
|
-
var
|
|
18055
|
-
var
|
|
18379
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
18380
|
+
var import_path8 = __toESM(require("path"));
|
|
18056
18381
|
var import_util6 = require("util");
|
|
18057
18382
|
var execFileAsync3 = (0, import_util6.promisify)(import_child_process4.execFile);
|
|
18058
18383
|
var IDENTITY_FILE = "age-identity.enc";
|
|
@@ -18085,7 +18410,7 @@ function deriveKey(password, salt) {
|
|
|
18085
18410
|
}
|
|
18086
18411
|
async function secretsInitialized(configDir) {
|
|
18087
18412
|
try {
|
|
18088
|
-
await
|
|
18413
|
+
await import_promises6.default.access(import_path8.default.join(configDir, IDENTITY_FILE));
|
|
18089
18414
|
return true;
|
|
18090
18415
|
} catch {
|
|
18091
18416
|
return false;
|
|
@@ -18124,10 +18449,10 @@ async function initSecrets(configDir, password) {
|
|
|
18124
18449
|
tag: cipher.getAuthTag().toString("base64"),
|
|
18125
18450
|
data: data.toString("base64")
|
|
18126
18451
|
};
|
|
18127
|
-
await
|
|
18452
|
+
await import_promises6.default.writeFile(import_path8.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
|
|
18128
18453
|
mode: 384
|
|
18129
18454
|
});
|
|
18130
|
-
await
|
|
18455
|
+
await import_promises6.default.writeFile(import_path8.default.join(configDir, RECIPIENT_FILE), `${recipient}
|
|
18131
18456
|
`, {
|
|
18132
18457
|
mode: 384
|
|
18133
18458
|
});
|
|
@@ -18150,17 +18475,17 @@ function decryptIdentity(box, password) {
|
|
|
18150
18475
|
}
|
|
18151
18476
|
}
|
|
18152
18477
|
function envPath(spaceDir) {
|
|
18153
|
-
return
|
|
18478
|
+
return import_path8.default.join(spaceDir, "secrets", ENV_FILE);
|
|
18154
18479
|
}
|
|
18155
18480
|
async function identityFor(configDir, password) {
|
|
18156
18481
|
const box = JSON.parse(
|
|
18157
|
-
await
|
|
18482
|
+
await import_promises6.default.readFile(import_path8.default.join(configDir, IDENTITY_FILE), "utf-8")
|
|
18158
18483
|
);
|
|
18159
18484
|
return decryptIdentity(box, password);
|
|
18160
18485
|
}
|
|
18161
18486
|
async function readEnv(spaceDir, identity) {
|
|
18162
18487
|
try {
|
|
18163
|
-
await
|
|
18488
|
+
await import_promises6.default.access(envPath(spaceDir));
|
|
18164
18489
|
} catch {
|
|
18165
18490
|
return {};
|
|
18166
18491
|
}
|
|
@@ -18189,14 +18514,14 @@ async function readEnv(spaceDir, identity) {
|
|
|
18189
18514
|
return values;
|
|
18190
18515
|
}
|
|
18191
18516
|
async function writeEnv(configDir, spaceDir, identity, values) {
|
|
18192
|
-
const recipient = (await
|
|
18517
|
+
const recipient = (await import_promises6.default.readFile(import_path8.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
|
|
18193
18518
|
const plain = `${Object.entries(values).map(([k, v]) => `${k}=${v}`).join("\n")}
|
|
18194
18519
|
`;
|
|
18195
18520
|
const tmp = envPath(spaceDir).replace(
|
|
18196
18521
|
/\.enc\.env$/,
|
|
18197
18522
|
`.${process.pid}.tmp.env`
|
|
18198
18523
|
);
|
|
18199
|
-
await
|
|
18524
|
+
await import_promises6.default.writeFile(tmp, plain, { mode: 384 });
|
|
18200
18525
|
try {
|
|
18201
18526
|
const { stdout } = await execFileAsync3(
|
|
18202
18527
|
"sops",
|
|
@@ -18215,9 +18540,9 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
18215
18540
|
maxBuffer: 4 * 1024 * 1024
|
|
18216
18541
|
}
|
|
18217
18542
|
);
|
|
18218
|
-
await
|
|
18543
|
+
await import_promises6.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
|
|
18219
18544
|
} finally {
|
|
18220
|
-
await
|
|
18545
|
+
await import_promises6.default.rm(tmp, { force: true });
|
|
18221
18546
|
}
|
|
18222
18547
|
}
|
|
18223
18548
|
async function listSecretKeys(configDir, spaceDir, password) {
|
|
@@ -18255,16 +18580,6 @@ var FAILED_MARKER = "DEPLOY_FAILED:";
|
|
|
18255
18580
|
var MAX_MARKERLESS_TURNS = 3;
|
|
18256
18581
|
var COMMAND_TIMEOUT_MS = 45 * 6e4;
|
|
18257
18582
|
var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18258
|
-
function redactor(values) {
|
|
18259
|
-
const significant = values.filter((v) => v.length >= 4);
|
|
18260
|
-
return (text) => {
|
|
18261
|
-
let out = text;
|
|
18262
|
-
for (const value2 of significant) {
|
|
18263
|
-
out = out.split(value2).join("[REDACTED]");
|
|
18264
|
-
}
|
|
18265
|
-
return out;
|
|
18266
|
-
};
|
|
18267
|
-
}
|
|
18268
18583
|
function tailMarker(text) {
|
|
18269
18584
|
const zone = text.slice(-2e3);
|
|
18270
18585
|
let best = null;
|
|
@@ -18281,10 +18596,52 @@ function tailMarker(text) {
|
|
|
18281
18596
|
const value2 = zone.slice(best.at + best.marker.length).trim();
|
|
18282
18597
|
return value2 ? { marker: best.marker, value: value2 } : null;
|
|
18283
18598
|
}
|
|
18599
|
+
function containedCwd(cwd, spaceDir) {
|
|
18600
|
+
if (!cwd) return null;
|
|
18601
|
+
const resolved = import_path9.default.resolve(cwd);
|
|
18602
|
+
const root = import_path9.default.resolve(spaceDir);
|
|
18603
|
+
return resolved === root || resolved.startsWith(root + import_path9.default.sep) ? resolved : null;
|
|
18604
|
+
}
|
|
18605
|
+
async function authorizeAndRun(opts) {
|
|
18606
|
+
const { command, hooks } = opts;
|
|
18607
|
+
opts.status?.("waiting for the owner to authorize this command");
|
|
18608
|
+
let secrets = null;
|
|
18609
|
+
let authError = "";
|
|
18610
|
+
while (!secrets) {
|
|
18611
|
+
const auth = await hooks.requestAuth(command, authError);
|
|
18612
|
+
if ("denied" in auth) return { denied: true };
|
|
18613
|
+
try {
|
|
18614
|
+
secrets = await secretsForInjection(
|
|
18615
|
+
opts.configDir,
|
|
18616
|
+
opts.spaceDir,
|
|
18617
|
+
auth.password
|
|
18618
|
+
);
|
|
18619
|
+
} catch (err) {
|
|
18620
|
+
authError = err instanceof Error ? err.message : String(err);
|
|
18621
|
+
opts.status?.(`authorization failed: ${authError}`);
|
|
18622
|
+
}
|
|
18623
|
+
}
|
|
18624
|
+
const redact = redactor(Object.values(secrets));
|
|
18625
|
+
opts.status?.("authorized, running");
|
|
18626
|
+
hooks.sendLog({
|
|
18627
|
+
type: "system",
|
|
18628
|
+
content: "Authorized. Running the command with secrets injected..."
|
|
18629
|
+
});
|
|
18630
|
+
const res = await execLogin(command, {
|
|
18631
|
+
cwd: opts.cwd,
|
|
18632
|
+
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...secrets },
|
|
18633
|
+
timeout: COMMAND_TIMEOUT_MS,
|
|
18634
|
+
maxBuffer: 32 * 1024 * 1024
|
|
18635
|
+
});
|
|
18636
|
+
const output = redact(res.output);
|
|
18637
|
+
hooks.sendLog({ type: "tool", content: `$ ${command}
|
|
18638
|
+
${tail(output)}` });
|
|
18639
|
+
return { code: res.code, output };
|
|
18640
|
+
}
|
|
18284
18641
|
async function startDeployRun(opts) {
|
|
18285
18642
|
const { hooks } = opts;
|
|
18286
|
-
const workspace =
|
|
18287
|
-
const configDir =
|
|
18643
|
+
const workspace = import_path9.default.join(opts.spaceDir, "workspace");
|
|
18644
|
+
const configDir = import_path9.default.dirname(opts.spaceDir);
|
|
18288
18645
|
const status = await checkDeployStatus(opts);
|
|
18289
18646
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
18290
18647
|
throw new Error(
|
|
@@ -18305,7 +18662,7 @@ async function startDeployRun(opts) {
|
|
|
18305
18662
|
await hostGit(workspace, "clean", "-fd");
|
|
18306
18663
|
await ensureClaudeSkillLink(workspace);
|
|
18307
18664
|
const contract = prompts_default.deployRun.join("\n");
|
|
18308
|
-
const reminder = "Reminder: every declared deploy variable is intentionally unset in your shell. Never check for them or abort over them -
|
|
18665
|
+
const reminder = "Reminder: every declared deploy variable is intentionally unset in your shell. Never check for them or abort over them - prefix any command that needs one with `factiii-secrets run --` and the runner injects the values.";
|
|
18309
18666
|
let prompt2 = opts.provider === "codex" ? `Use the "deploy" skill: load it and execute it step by step.
|
|
18310
18667
|
|
|
18311
18668
|
${reminder}` : `/deploy
|
|
@@ -18314,109 +18671,105 @@ ${reminder}`;
|
|
|
18314
18671
|
let sessionId = "";
|
|
18315
18672
|
let markerless = 0;
|
|
18316
18673
|
let todos = [];
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
//
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18674
|
+
const bridge = await startSecretsBridge({
|
|
18675
|
+
spaceDir: opts.spaceDir,
|
|
18676
|
+
handlers: {
|
|
18677
|
+
run: ({ command, cwd, status: report }) => authorizeAndRun({
|
|
18678
|
+
configDir,
|
|
18679
|
+
spaceDir: opts.spaceDir,
|
|
18680
|
+
// A skill legitimately cds into subdirectories, so the CLI's own cwd
|
|
18681
|
+
// is the right place to run - but only while it is still inside the
|
|
18682
|
+
// space. Anywhere else is a confused caller, not a step, and the
|
|
18683
|
+
// workspace is the safe reading of what it meant.
|
|
18684
|
+
cwd: containedCwd(cwd, opts.spaceDir) ?? workspace,
|
|
18685
|
+
command,
|
|
18686
|
+
hooks,
|
|
18687
|
+
status: report
|
|
18688
|
+
}),
|
|
18689
|
+
// Names only. The contract on the default branch already declares them
|
|
18690
|
+
// in public, so this reveals nothing the repo does not.
|
|
18691
|
+
list: () => Promise.resolve(status.variables.map((v) => v.key))
|
|
18692
|
+
}
|
|
18693
|
+
});
|
|
18694
|
+
try {
|
|
18695
|
+
for (; ; ) {
|
|
18696
|
+
if (hooks.isCancelled()) throw new Error("Cancelled by user.");
|
|
18697
|
+
const { proc, result } = await spawnAgent(
|
|
18698
|
+
opts.provider,
|
|
18699
|
+
hostTarget(opts.spaceDir),
|
|
18700
|
+
{
|
|
18701
|
+
cwd: workspace,
|
|
18702
|
+
prompt: prompt2,
|
|
18703
|
+
allowedTools: "Bash,Read,Grep,Glob,TodoWrite,WebSearch,WebFetch,Skill",
|
|
18704
|
+
// The run's guardrails are structural (merge-gated skill, per-command
|
|
18705
|
+
// authorization, runner-side injection); the auto classifier fights
|
|
18706
|
+
// legitimate infrastructure commands, so the session itself runs open.
|
|
18707
|
+
permissionMode: "bypassPermissions",
|
|
18708
|
+
includePartialMessages: true,
|
|
18709
|
+
resumeSessionId: sessionId || void 0,
|
|
18710
|
+
appendSystemPrompt: sessionId ? void 0 : contract,
|
|
18711
|
+
env: spaceEnv(opts.spaceDir),
|
|
18712
|
+
callbacks: {
|
|
18713
|
+
initialTodos: todos,
|
|
18714
|
+
onLog: (entry) => hooks.sendLog(entry),
|
|
18715
|
+
onTodos: (updated) => {
|
|
18716
|
+
todos = updated;
|
|
18717
|
+
hooks.onTodos(updated);
|
|
18718
|
+
}
|
|
18341
18719
|
}
|
|
18342
18720
|
}
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18350
|
-
|
|
18351
|
-
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
try {
|
|
18363
|
-
secrets = await secretsForInjection(
|
|
18364
|
-
configDir,
|
|
18365
|
-
opts.spaceDir,
|
|
18366
|
-
auth.password
|
|
18367
|
-
);
|
|
18368
|
-
} catch (err) {
|
|
18369
|
-
authError = err instanceof Error ? err.message : String(err);
|
|
18721
|
+
);
|
|
18722
|
+
hooks.registerProc(proc);
|
|
18723
|
+
const { text, sessionId: sid } = await result;
|
|
18724
|
+
hooks.registerProc(null);
|
|
18725
|
+
sessionId = sid || sessionId;
|
|
18726
|
+
if (hooks.isCancelled()) throw new Error("Cancelled by user.");
|
|
18727
|
+
const found = tailMarker(text);
|
|
18728
|
+
if (found?.marker === SECRETS_MARKER) {
|
|
18729
|
+
const command = found.value.split("\n")[0].trim();
|
|
18730
|
+
const res = await authorizeAndRun({
|
|
18731
|
+
configDir,
|
|
18732
|
+
spaceDir: opts.spaceDir,
|
|
18733
|
+
cwd: workspace,
|
|
18734
|
+
command,
|
|
18735
|
+
hooks
|
|
18736
|
+
});
|
|
18737
|
+
if ("denied" in res) {
|
|
18738
|
+
prompt2 = "The owner declined to run that command. Adapt or stop and summarize where the release stands.";
|
|
18739
|
+
continue;
|
|
18370
18740
|
}
|
|
18741
|
+
markerless = 0;
|
|
18742
|
+
const clean = tail(res.output);
|
|
18743
|
+
prompt2 = `The authorized command finished (exit ${res.code}). Redacted output:
|
|
18744
|
+
|
|
18745
|
+
${clean}`;
|
|
18746
|
+
continue;
|
|
18371
18747
|
}
|
|
18372
|
-
if (
|
|
18373
|
-
|
|
18748
|
+
if (found?.marker === HUMAN_MARKER) {
|
|
18749
|
+
markerless = 0;
|
|
18750
|
+
prompt2 = await hooks.requestHuman(found.value);
|
|
18374
18751
|
continue;
|
|
18375
18752
|
}
|
|
18376
|
-
|
|
18753
|
+
if (found?.marker === COMPLETE_MARKER) {
|
|
18754
|
+
return { shipped: true, summary: found.value };
|
|
18755
|
+
}
|
|
18756
|
+
if (found?.marker === FAILED_MARKER) {
|
|
18757
|
+
return { shipped: false, summary: found.value };
|
|
18758
|
+
}
|
|
18759
|
+
markerless += 1;
|
|
18760
|
+
if (markerless >= MAX_MARKERLESS_TURNS) {
|
|
18761
|
+
throw new Error(
|
|
18762
|
+
"The run ended without declaring completion (no DEPLOY_COMPLETE after repeated turns)."
|
|
18763
|
+
);
|
|
18764
|
+
}
|
|
18377
18765
|
hooks.sendLog({
|
|
18378
18766
|
type: "system",
|
|
18379
|
-
content:
|
|
18380
|
-
});
|
|
18381
|
-
const res = await execLogin(command, {
|
|
18382
|
-
cwd: workspace,
|
|
18383
|
-
env: { ...process.env, ...spaceEnv(opts.spaceDir), ...secrets },
|
|
18384
|
-
timeout: COMMAND_TIMEOUT_MS,
|
|
18385
|
-
maxBuffer: 32 * 1024 * 1024
|
|
18767
|
+
content: "Turn ended without a protocol marker - resuming the run..."
|
|
18386
18768
|
});
|
|
18387
|
-
|
|
18388
|
-
const exitNote = `exit ${res.code}`;
|
|
18389
|
-
markerless = 0;
|
|
18390
|
-
const clean = redact(tail(output));
|
|
18391
|
-
hooks.sendLog({ type: "tool", content: `$ ${command}
|
|
18392
|
-
${clean}` });
|
|
18393
|
-
prompt2 = `The authorized command finished (${exitNote}). Redacted output:
|
|
18394
|
-
|
|
18395
|
-
${clean}`;
|
|
18396
|
-
continue;
|
|
18397
|
-
}
|
|
18398
|
-
if (found?.marker === HUMAN_MARKER) {
|
|
18399
|
-
markerless = 0;
|
|
18400
|
-
prompt2 = await hooks.requestHuman(found.value);
|
|
18401
|
-
continue;
|
|
18402
|
-
}
|
|
18403
|
-
if (found?.marker === COMPLETE_MARKER) {
|
|
18404
|
-
return { shipped: true, summary: found.value };
|
|
18405
|
-
}
|
|
18406
|
-
if (found?.marker === FAILED_MARKER) {
|
|
18407
|
-
return { shipped: false, summary: found.value };
|
|
18769
|
+
prompt2 = "Your turn ended with no protocol marker. If the release fully shipped, end your message with DEPLOY_COMPLETE: followed by your run report; if you are stopping without shipping, end with DEPLOY_FAILED: followed by the report. Otherwise CONTINUE executing the skill from where you left off. If you had background tasks running, they were killed when your turn ended - rerun them, and reap every background task before ending a turn.";
|
|
18408
18770
|
}
|
|
18409
|
-
|
|
18410
|
-
|
|
18411
|
-
throw new Error(
|
|
18412
|
-
"The run ended without declaring completion (no DEPLOY_COMPLETE after repeated turns)."
|
|
18413
|
-
);
|
|
18414
|
-
}
|
|
18415
|
-
hooks.sendLog({
|
|
18416
|
-
type: "system",
|
|
18417
|
-
content: "Turn ended without a protocol marker - resuming the run..."
|
|
18418
|
-
});
|
|
18419
|
-
prompt2 = "Your turn ended with no protocol marker. If the release fully shipped, end your message with DEPLOY_COMPLETE: followed by your run report; if you are stopping without shipping, end with DEPLOY_FAILED: followed by the report. Otherwise CONTINUE executing the skill from where you left off. If you had background tasks running, they were killed when your turn ended - rerun them, and reap every background task before ending a turn.";
|
|
18771
|
+
} finally {
|
|
18772
|
+
await bridge.close().catch(() => void 0);
|
|
18420
18773
|
}
|
|
18421
18774
|
}
|
|
18422
18775
|
|
|
@@ -18508,6 +18861,125 @@ async function getDriveRoot(accessToken) {
|
|
|
18508
18861
|
};
|
|
18509
18862
|
}
|
|
18510
18863
|
|
|
18864
|
+
// ../../shared/all/helpers/board-agent-core/space-core.ts
|
|
18865
|
+
var import_promises8 = require("fs/promises");
|
|
18866
|
+
var import_path11 = __toESM(require("path"));
|
|
18867
|
+
|
|
18868
|
+
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
18869
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
18870
|
+
var import_fs3 = require("fs");
|
|
18871
|
+
var import_promises7 = require("fs/promises");
|
|
18872
|
+
var import_os3 = __toESM(require("os"));
|
|
18873
|
+
var import_path10 = __toESM(require("path"));
|
|
18874
|
+
var MARKETPLACE_NAME = "factiii-runner";
|
|
18875
|
+
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
18876
|
+
var SECRETS_BIN = "factiii-secrets";
|
|
18877
|
+
function candidates() {
|
|
18878
|
+
return [
|
|
18879
|
+
import_path10.default.join(__dirname, "..", "plugin"),
|
|
18880
|
+
import_path10.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
18881
|
+
import_path10.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
18882
|
+
];
|
|
18883
|
+
}
|
|
18884
|
+
function pluginRoot() {
|
|
18885
|
+
for (const dir of candidates()) {
|
|
18886
|
+
if ((0, import_fs3.existsSync)(import_path10.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
18887
|
+
return dir;
|
|
18888
|
+
}
|
|
18889
|
+
}
|
|
18890
|
+
return null;
|
|
18891
|
+
}
|
|
18892
|
+
function renderedPluginRoot() {
|
|
18893
|
+
return import_path10.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
18894
|
+
}
|
|
18895
|
+
async function runnerVersion(source) {
|
|
18896
|
+
const pkg = import_path10.default.join(source, "..", "package.json");
|
|
18897
|
+
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
18898
|
+
if (!raw) return FALLBACK_VERSION;
|
|
18899
|
+
try {
|
|
18900
|
+
const { version } = JSON.parse(raw);
|
|
18901
|
+
return version || FALLBACK_VERSION;
|
|
18902
|
+
} catch {
|
|
18903
|
+
return FALLBACK_VERSION;
|
|
18904
|
+
}
|
|
18905
|
+
}
|
|
18906
|
+
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
18907
|
+
async function treeFiles(dir) {
|
|
18908
|
+
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
18909
|
+
const files = [];
|
|
18910
|
+
for (const entry of entries) {
|
|
18911
|
+
const full = import_path10.default.join(dir, entry.name);
|
|
18912
|
+
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
18913
|
+
else if (entry.isFile()) files.push(full);
|
|
18914
|
+
}
|
|
18915
|
+
return files.sort();
|
|
18916
|
+
}
|
|
18917
|
+
async function fingerprint(source, url2, version = "") {
|
|
18918
|
+
const hash = import_crypto3.default.createHash("sha256").update(url2).update(version);
|
|
18919
|
+
for (const file of await treeFiles(source)) {
|
|
18920
|
+
hash.update(import_path10.default.relative(source, file));
|
|
18921
|
+
hash.update(await (0, import_promises7.readFile)(file));
|
|
18922
|
+
}
|
|
18923
|
+
return hash.digest("hex");
|
|
18924
|
+
}
|
|
18925
|
+
function stampPath(dest) {
|
|
18926
|
+
return `${dest}.stamp`;
|
|
18927
|
+
}
|
|
18928
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
18929
|
+
function renderPlugin(boardMcpUrl) {
|
|
18930
|
+
const running = inFlight.get(boardMcpUrl);
|
|
18931
|
+
if (running) return running;
|
|
18932
|
+
const started = renderPluginImpl(boardMcpUrl).finally(
|
|
18933
|
+
() => inFlight.delete(boardMcpUrl)
|
|
18934
|
+
);
|
|
18935
|
+
inFlight.set(boardMcpUrl, started);
|
|
18936
|
+
return started;
|
|
18937
|
+
}
|
|
18938
|
+
async function renderPluginImpl(boardMcpUrl) {
|
|
18939
|
+
const source = pluginRoot();
|
|
18940
|
+
if (!source) return null;
|
|
18941
|
+
const dest = renderedPluginRoot();
|
|
18942
|
+
const version = await runnerVersion(source);
|
|
18943
|
+
const want = await fingerprint(source, boardMcpUrl, version);
|
|
18944
|
+
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
18945
|
+
if (have === want) return dest;
|
|
18946
|
+
await (0, import_promises7.mkdir)(import_path10.default.dirname(dest), { recursive: true });
|
|
18947
|
+
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
18948
|
+
await (0, import_promises7.writeFile)(
|
|
18949
|
+
import_path10.default.join(dest, "factiii", ".mcp.json"),
|
|
18950
|
+
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
18951
|
+
`
|
|
18952
|
+
);
|
|
18953
|
+
await stampVersion(dest, version);
|
|
18954
|
+
await (0, import_promises7.writeFile)(stampPath(dest), want);
|
|
18955
|
+
return dest;
|
|
18956
|
+
}
|
|
18957
|
+
async function stampVersion(dest, version) {
|
|
18958
|
+
const manifest = import_path10.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
18959
|
+
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
18960
|
+
if (!raw) return;
|
|
18961
|
+
const parsed = JSON.parse(raw);
|
|
18962
|
+
await (0, import_promises7.writeFile)(
|
|
18963
|
+
manifest,
|
|
18964
|
+
`${JSON.stringify({ ...parsed, version }, null, 2)}
|
|
18965
|
+
`
|
|
18966
|
+
);
|
|
18967
|
+
}
|
|
18968
|
+
function boardMcpConfig(url2) {
|
|
18969
|
+
return {
|
|
18970
|
+
mcpServers: {
|
|
18971
|
+
"factiii-board": {
|
|
18972
|
+
type: "http",
|
|
18973
|
+
url: url2,
|
|
18974
|
+
bearer_token_env_var: BOARD_MCP_TOKEN_ENV,
|
|
18975
|
+
// Written literally, for claude to expand at connect time. Not a
|
|
18976
|
+
// template string: the `${…}` has to survive into the file.
|
|
18977
|
+
headers: { Authorization: "Bearer ${" + BOARD_MCP_TOKEN_ENV + "}" }
|
|
18978
|
+
}
|
|
18979
|
+
}
|
|
18980
|
+
};
|
|
18981
|
+
}
|
|
18982
|
+
|
|
18511
18983
|
// ../../shared/all/helpers/board-agent-core/toolchain.ts
|
|
18512
18984
|
var TOOLS = [
|
|
18513
18985
|
{ bin: "git" },
|
|
@@ -18736,6 +19208,8 @@ var SpaceCore = class {
|
|
|
18736
19208
|
this.execTarget ??= hostTarget(this.spaceDir(), () => {
|
|
18737
19209
|
const env = {};
|
|
18738
19210
|
for (const lane of this.mcpLanes()) env[lane.tokenEnv] = lane.mcp.token;
|
|
19211
|
+
const board = this.boardMcp();
|
|
19212
|
+
if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
|
|
18739
19213
|
return env;
|
|
18740
19214
|
});
|
|
18741
19215
|
return this.execTarget;
|
|
@@ -18765,10 +19239,6 @@ var SpaceCore = class {
|
|
|
18765
19239
|
enabled: this.isOneDriveConnected()
|
|
18766
19240
|
});
|
|
18767
19241
|
}
|
|
18768
|
-
const board = this.boardMcp();
|
|
18769
|
-
if (board) {
|
|
18770
|
-
lanes.push({ mcp: board, tokenEnv: BOARD_MCP_TOKEN_ENV, enabled: true });
|
|
18771
|
-
}
|
|
18772
19242
|
return lanes;
|
|
18773
19243
|
}
|
|
18774
19244
|
isReady() {
|
|
@@ -18919,6 +19389,8 @@ var SpaceCore = class {
|
|
|
18919
19389
|
await Promise.all([
|
|
18920
19390
|
this.provisionGitLane(status),
|
|
18921
19391
|
this.provisionMcpLane(),
|
|
19392
|
+
this.provisionPluginLane(),
|
|
19393
|
+
this.provisionBinLane(),
|
|
18922
19394
|
// Covers a host signed in outside the one-click button, which would
|
|
18923
19395
|
// otherwise meet the wizard the first time it opens a terminal.
|
|
18924
19396
|
markOnboardingComplete(target).catch(() => {
|
|
@@ -18961,17 +19433,89 @@ var SpaceCore = class {
|
|
|
18961
19433
|
target.paths.repo
|
|
18962
19434
|
);
|
|
18963
19435
|
}
|
|
19436
|
+
/** Install the factiii plugin into both CLIs: the hooks behind the session
|
|
19437
|
+
* status dot, the skills this workspace provides, and the board MCP.
|
|
19438
|
+
*
|
|
19439
|
+
* A plugin rather than hand-written config because it is additive — unlike
|
|
19440
|
+
* pointing CLAUDE_CONFIG_DIR or CODEX_HOME at our own directory, which
|
|
19441
|
+
* would hide the user's own settings, skills, MCP entries and auth.
|
|
19442
|
+
*
|
|
19443
|
+
* Both CLIs read the same manifest, so one marketplace serves both, and
|
|
19444
|
+
* adding a marketplace or plugin that is already there is a no-op in each —
|
|
19445
|
+
* which is what makes this safe on every ensure. */
|
|
19446
|
+
async provisionPluginLane() {
|
|
19447
|
+
const board = this.boardMcp();
|
|
19448
|
+
const root = board ? await renderPlugin(board.url).catch(() => null) : null;
|
|
19449
|
+
if (!root) return;
|
|
19450
|
+
const q = `'${root}'`;
|
|
19451
|
+
const cmd = [
|
|
19452
|
+
`claude plugin marketplace add ${q} >/dev/null 2>&1 || true`,
|
|
19453
|
+
`claude plugin install ${PLUGIN_ID} --scope user >/dev/null 2>&1 || true`,
|
|
19454
|
+
// Both CLIs install by COPYING the plugin into a cache keyed by its
|
|
19455
|
+
// version, and that copy is what loads. claude's `install` refuses once
|
|
19456
|
+
// something is installed ("already installed"), so without these two an
|
|
19457
|
+
// upgrade renders new files into a directory nothing reads again. Codex
|
|
19458
|
+
// needs no equivalent: its `add` re-installs from a local marketplace.
|
|
19459
|
+
`claude plugin marketplace update ${MARKETPLACE_NAME} >/dev/null 2>&1 || true`,
|
|
19460
|
+
`claude plugin update ${PLUGIN_ID} >/dev/null 2>&1 || true`,
|
|
19461
|
+
`codex plugin marketplace add ${q} >/dev/null 2>&1 || true`,
|
|
19462
|
+
`codex plugin add ${PLUGIN_ID} >/dev/null 2>&1 || true`
|
|
19463
|
+
].join("; ");
|
|
19464
|
+
try {
|
|
19465
|
+
await this.target().sh(cmd);
|
|
19466
|
+
} catch {
|
|
19467
|
+
}
|
|
19468
|
+
}
|
|
19469
|
+
/** Stage the plugin's `factiii-secrets` onto the agent's PATH.
|
|
19470
|
+
*
|
|
19471
|
+
* A skill can only teach a command that exists. `paths.bin` is already the
|
|
19472
|
+
* PATH prefix `agentEnv()` hands every spawn (it is how `factiii-expose`
|
|
19473
|
+
* resolves), but nothing put anything there for a deploy run - so the
|
|
19474
|
+
* skill would name a command the shell could not find.
|
|
19475
|
+
*
|
|
19476
|
+
* Copied rather than symlinked: an `npm i -g @factiii/runner` upgrade
|
|
19477
|
+
* replaces the package directory, and a link into the old one dangles.
|
|
19478
|
+
* Re-copied whenever the sizes differ, which is what carries an upgrade
|
|
19479
|
+
* through without re-copying on every ensure. */
|
|
19480
|
+
async provisionBinLane() {
|
|
19481
|
+
const source = pluginRoot();
|
|
19482
|
+
if (!source) return;
|
|
19483
|
+
const { bin } = this.target().paths;
|
|
19484
|
+
const from = import_path11.default.join(source, "factiii", "bin", SECRETS_BIN);
|
|
19485
|
+
const to = import_path11.default.join(bin, SECRETS_BIN);
|
|
19486
|
+
try {
|
|
19487
|
+
const [src, staged] = await Promise.all([
|
|
19488
|
+
(0, import_promises8.stat)(from),
|
|
19489
|
+
(0, import_promises8.stat)(to).catch(() => null)
|
|
19490
|
+
]);
|
|
19491
|
+
if (staged?.size === src.size) return;
|
|
19492
|
+
await (0, import_promises8.mkdir)(bin, { recursive: true });
|
|
19493
|
+
await (0, import_promises8.copyFile)(from, to);
|
|
19494
|
+
await (0, import_promises8.chmod)(to, 493);
|
|
19495
|
+
} catch {
|
|
19496
|
+
}
|
|
19497
|
+
}
|
|
18964
19498
|
// Register this space's MCPs with both CLIs, user-scoped, and clear the ones
|
|
18965
19499
|
// it cannot currently offer. Registering regardless left every space holding
|
|
18966
19500
|
// an entry it never uses, which the CLI then fails to handshake with on every
|
|
18967
19501
|
// run. Removal is unconditional so a disconnect cleans itself up.
|
|
19502
|
+
//
|
|
19503
|
+
// No early return on an empty lane list: since the board lane moved into the
|
|
19504
|
+
// plugin, a host with no OneDrive has nothing to add and still has the old
|
|
19505
|
+
// board entries to drop.
|
|
18968
19506
|
async provisionMcpLane() {
|
|
18969
19507
|
const lanes = this.mcpLanes();
|
|
18970
|
-
|
|
19508
|
+
const board = this.boardMcp();
|
|
19509
|
+
const boardCodexTable = board?.name.replace(/-/g, "_");
|
|
18971
19510
|
const cmd = [
|
|
18972
19511
|
// Pre-rename entries, when the name was the same for every space.
|
|
18973
19512
|
`claude mcp remove factiii-onedrive --scope user >/dev/null 2>&1 || true`,
|
|
18974
19513
|
`codex mcp remove factiii_onedrive >/dev/null 2>&1 || true`,
|
|
19514
|
+
// The board lane moved into the plugin; drop what the old path wrote.
|
|
19515
|
+
...board ? [
|
|
19516
|
+
`claude mcp remove ${board.name} --scope user >/dev/null 2>&1 || true`,
|
|
19517
|
+
`codex mcp remove ${boardCodexTable} >/dev/null 2>&1 || true`
|
|
19518
|
+
] : [],
|
|
18975
19519
|
...lanes.flatMap(({ mcp, tokenEnv, enabled }) => {
|
|
18976
19520
|
const codexTable = mcp.name.replace(/-/g, "_");
|
|
18977
19521
|
const claudeJson = JSON.stringify({
|
|
@@ -19051,6 +19595,10 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19051
19595
|
// In-flight creator session, memory-only (the repo is the source of truth
|
|
19052
19596
|
// once merged). Survives modal reopen via deployCreatorState.
|
|
19053
19597
|
this.skillCreatorSessionId = null;
|
|
19598
|
+
// Which CLI produced that session. A resume id belongs to ONE provider, so
|
|
19599
|
+
// switching the picker between drafting and revising has to start fresh
|
|
19600
|
+
// rather than hand a claude session id to codex.
|
|
19601
|
+
this.skillCreatorProvider = null;
|
|
19054
19602
|
// Set when the draft was resumed from a pushed deploy-skill branch:
|
|
19055
19603
|
// approve commits onto that branch instead of cutting a new one.
|
|
19056
19604
|
this.skillDraftBranch = null;
|
|
@@ -19071,6 +19619,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19071
19619
|
// ── Deploy run (host executor; see host-run.ts + .specs/deploy.md) ──
|
|
19072
19620
|
// Memory-only: reopened clients reattach, a restart loses the run and says so.
|
|
19073
19621
|
this.runState = {
|
|
19622
|
+
provider: "claude",
|
|
19074
19623
|
phase: "idle",
|
|
19075
19624
|
todos: [],
|
|
19076
19625
|
logs: [],
|
|
@@ -19214,6 +19763,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19214
19763
|
});
|
|
19215
19764
|
const draft = await loadSkillDraft({ ...opts, branch: payload.branch });
|
|
19216
19765
|
this.skillCreatorSessionId = null;
|
|
19766
|
+
this.skillCreatorProvider = null;
|
|
19217
19767
|
this.skillDraftBranch = payload.branch;
|
|
19218
19768
|
this.creatorState = { phase: "review", logs: [], draft, error: "" };
|
|
19219
19769
|
return draft;
|
|
@@ -19259,8 +19809,8 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19259
19809
|
throw err;
|
|
19260
19810
|
}
|
|
19261
19811
|
}
|
|
19262
|
-
deployFixEnvironment(sendLog) {
|
|
19263
|
-
const opts = this.deployCreatorOpts(sendLog);
|
|
19812
|
+
deployFixEnvironment(sendLog, provider) {
|
|
19813
|
+
const opts = this.deployCreatorOpts(sendLog, provider);
|
|
19264
19814
|
return this.runEnvPass(
|
|
19265
19815
|
"fixing",
|
|
19266
19816
|
() => fixEnvironment({
|
|
@@ -19324,13 +19874,14 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19324
19874
|
return this.activity.clear("deploy-run");
|
|
19325
19875
|
}
|
|
19326
19876
|
}
|
|
19327
|
-
deployRunStart(sendLog) {
|
|
19877
|
+
deployRunStart(sendLog, provider) {
|
|
19328
19878
|
if (this.runState.phase === "running" || this.runState.phase === "awaiting-input" || this.runState.phase === "awaiting-auth") {
|
|
19329
19879
|
throw new Error("A deploy run is already in progress.");
|
|
19330
19880
|
}
|
|
19331
|
-
const opts = this.deployCreatorOpts(sendLog);
|
|
19881
|
+
const opts = this.deployCreatorOpts(sendLog, provider);
|
|
19332
19882
|
this.runCancelled = false;
|
|
19333
19883
|
this.runState = {
|
|
19884
|
+
provider: opts.provider,
|
|
19334
19885
|
phase: "running",
|
|
19335
19886
|
todos: [],
|
|
19336
19887
|
logs: [],
|
|
@@ -19451,6 +20002,9 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19451
20002
|
throw new Error("A deploy run is still in progress.");
|
|
19452
20003
|
}
|
|
19453
20004
|
this.runState = {
|
|
20005
|
+
// Unread while idle - the next run sets its own. Carried rather than
|
|
20006
|
+
// reset so nothing has to invent a provider that means nothing yet.
|
|
20007
|
+
provider: this.runState.provider,
|
|
19454
20008
|
phase: "idle",
|
|
19455
20009
|
todos: [],
|
|
19456
20010
|
logs: [],
|
|
@@ -19549,7 +20103,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19549
20103
|
throw err;
|
|
19550
20104
|
}
|
|
19551
20105
|
}
|
|
19552
|
-
deployCreatorOpts(sendLog) {
|
|
20106
|
+
deployCreatorOpts(sendLog, provider) {
|
|
19553
20107
|
const config = this.core.readConfig();
|
|
19554
20108
|
if (!config.repoUrl || !config.githubToken) {
|
|
19555
20109
|
throw new Error("Set the repository URL and GitHub token first.");
|
|
@@ -19559,36 +20113,51 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19559
20113
|
repoUrl: config.repoUrl,
|
|
19560
20114
|
mainBranch: config.mainBranch,
|
|
19561
20115
|
githubToken: config.githubToken,
|
|
19562
|
-
|
|
20116
|
+
// resolveProvider coerces anything unknown to claude, so it can only be
|
|
20117
|
+
// asked about a value that is actually there.
|
|
20118
|
+
provider: provider ? resolveProvider(provider) : this.core.agentProvider(),
|
|
19563
20119
|
sendLog
|
|
19564
20120
|
};
|
|
19565
20121
|
}
|
|
19566
20122
|
deployCreateSkill(payload, sendLog) {
|
|
19567
20123
|
this.skillDraftBranch = null;
|
|
19568
20124
|
return this.runCreatorPass(async () => {
|
|
20125
|
+
const opts = this.deployCreatorOpts(
|
|
20126
|
+
this.creatorLogger(sendLog),
|
|
20127
|
+
payload.provider
|
|
20128
|
+
);
|
|
19569
20129
|
const { sessionId, ...draft } = await generateDeploySkill({
|
|
19570
|
-
...
|
|
20130
|
+
...opts,
|
|
19571
20131
|
instructions: payload.instructions
|
|
19572
20132
|
});
|
|
19573
20133
|
this.skillCreatorSessionId = sessionId || null;
|
|
20134
|
+
this.skillCreatorProvider = opts.provider;
|
|
19574
20135
|
return draft;
|
|
19575
20136
|
});
|
|
19576
20137
|
}
|
|
19577
20138
|
deployReviseSkill(payload, sendLog) {
|
|
19578
20139
|
return this.runCreatorPass(async () => {
|
|
20140
|
+
const opts = this.deployCreatorOpts(
|
|
20141
|
+
this.creatorLogger(sendLog),
|
|
20142
|
+
payload.provider
|
|
20143
|
+
);
|
|
20144
|
+
const resumable = this.skillCreatorProvider === opts.provider;
|
|
19579
20145
|
const { sessionId, ...draft } = await reviseDeploySkill({
|
|
19580
|
-
...
|
|
20146
|
+
...opts,
|
|
19581
20147
|
feedback: payload.feedback,
|
|
19582
|
-
|
|
20148
|
+
// A different CLI than drafted this: its session id means nothing to
|
|
20149
|
+
// the new one, so the revision starts from the files on disk.
|
|
20150
|
+
resumeSessionId: resumable ? this.skillCreatorSessionId ?? void 0 : void 0
|
|
19583
20151
|
});
|
|
19584
20152
|
this.skillCreatorSessionId = sessionId || this.skillCreatorSessionId;
|
|
20153
|
+
this.skillCreatorProvider = opts.provider;
|
|
19585
20154
|
return draft;
|
|
19586
20155
|
});
|
|
19587
20156
|
}
|
|
19588
20157
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
19589
20158
|
secretsDirs() {
|
|
19590
20159
|
const spaceDir = this.core.spaceDir();
|
|
19591
|
-
return { configDir: (0,
|
|
20160
|
+
return { configDir: (0, import_path12.dirname)(spaceDir), spaceDir };
|
|
19592
20161
|
}
|
|
19593
20162
|
async deploySecretsStatus() {
|
|
19594
20163
|
return {
|
|
@@ -19622,6 +20191,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19622
20191
|
const config = this.core.readConfig();
|
|
19623
20192
|
const onBranch = this.skillDraftBranch ?? void 0;
|
|
19624
20193
|
this.skillCreatorSessionId = null;
|
|
20194
|
+
this.skillCreatorProvider = null;
|
|
19625
20195
|
this.skillDraftBranch = null;
|
|
19626
20196
|
this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
|
|
19627
20197
|
this.activity.clear("deploy-skill");
|
|
@@ -19634,6 +20204,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19634
20204
|
}
|
|
19635
20205
|
deployDiscardSkill() {
|
|
19636
20206
|
this.skillCreatorSessionId = null;
|
|
20207
|
+
this.skillCreatorProvider = null;
|
|
19637
20208
|
this.skillDraftBranch = null;
|
|
19638
20209
|
this.creatorState = { phase: "idle", logs: [], draft: null, error: "" };
|
|
19639
20210
|
this.activity.clear("deploy-skill");
|
|
@@ -20438,14 +21009,16 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20438
21009
|
case "deployFixEnvironment":
|
|
20439
21010
|
return await this.deployFixEnvironment(
|
|
20440
21011
|
askLogSender ?? (() => {
|
|
20441
|
-
})
|
|
21012
|
+
}),
|
|
21013
|
+
payload?.provider
|
|
20442
21014
|
);
|
|
20443
21015
|
case "deployEnvState":
|
|
20444
21016
|
return this.deployEnvState();
|
|
20445
21017
|
case "deployRunStart":
|
|
20446
21018
|
return this.deployRunStart(
|
|
20447
21019
|
askLogSender ?? (() => {
|
|
20448
|
-
})
|
|
21020
|
+
}),
|
|
21021
|
+
payload?.provider
|
|
20449
21022
|
);
|
|
20450
21023
|
case "deployRunState":
|
|
20451
21024
|
return this.deployRunState();
|
|
@@ -20645,11 +21218,11 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20645
21218
|
|
|
20646
21219
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
20647
21220
|
var import_child_process5 = require("child_process");
|
|
20648
|
-
var
|
|
20649
|
-
var
|
|
20650
|
-
var
|
|
20651
|
-
var
|
|
20652
|
-
var
|
|
21221
|
+
var import_crypto4 = require("crypto");
|
|
21222
|
+
var import_fs4 = require("fs");
|
|
21223
|
+
var import_promises9 = __toESM(require("fs/promises"));
|
|
21224
|
+
var import_net3 = require("net");
|
|
21225
|
+
var import_path13 = require("path");
|
|
20653
21226
|
var import_util7 = require("util");
|
|
20654
21227
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
20655
21228
|
var IndexService = class {
|
|
@@ -20663,7 +21236,7 @@ var IndexService = class {
|
|
|
20663
21236
|
/** Per-space bearer for MCP + file, derived from the control token (the
|
|
20664
21237
|
* service computes the same). */
|
|
20665
21238
|
bearerToken(spaceSlug) {
|
|
20666
|
-
return (0,
|
|
21239
|
+
return (0, import_crypto4.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
|
|
20667
21240
|
}
|
|
20668
21241
|
/** MCP bridge for a space. Null until the service is up (which it kicks
|
|
20669
21242
|
* off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
|
|
@@ -20708,7 +21281,7 @@ var IndexService = class {
|
|
|
20708
21281
|
const log = await this.openLog();
|
|
20709
21282
|
this.proc = (0, import_child_process5.spawn)(
|
|
20710
21283
|
process.execPath,
|
|
20711
|
-
[(0,
|
|
21284
|
+
[(0, import_path13.join)(this.opts.indexDir, "index.js")],
|
|
20712
21285
|
{
|
|
20713
21286
|
cwd: this.opts.indexDir,
|
|
20714
21287
|
env: {
|
|
@@ -20736,8 +21309,8 @@ var IndexService = class {
|
|
|
20736
21309
|
* opened (a missing log must never stop the service from starting). */
|
|
20737
21310
|
async openLog() {
|
|
20738
21311
|
try {
|
|
20739
|
-
await
|
|
20740
|
-
return (0,
|
|
21312
|
+
await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
21313
|
+
return (0, import_fs4.openSync)((0, import_path13.join)(this.opts.dataDir, "index.log"), "a");
|
|
20741
21314
|
} catch {
|
|
20742
21315
|
return null;
|
|
20743
21316
|
}
|
|
@@ -20746,10 +21319,10 @@ var IndexService = class {
|
|
|
20746
21319
|
* platform needing node-gyp fails here rather than at query time. */
|
|
20747
21320
|
async installDeps() {
|
|
20748
21321
|
const dir = this.opts.indexDir;
|
|
20749
|
-
if ((0,
|
|
20750
|
-
await
|
|
20751
|
-
(0,
|
|
20752
|
-
(0,
|
|
21322
|
+
if ((0, import_fs4.existsSync)((0, import_path13.join)(dir, "node_modules"))) return;
|
|
21323
|
+
await import_promises9.default.copyFile(
|
|
21324
|
+
(0, import_path13.join)(dir, "image-package.json"),
|
|
21325
|
+
(0, import_path13.join)(dir, "package.json")
|
|
20753
21326
|
);
|
|
20754
21327
|
await execFileAsync4(
|
|
20755
21328
|
"npm",
|
|
@@ -20761,15 +21334,15 @@ var IndexService = class {
|
|
|
20761
21334
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
20762
21335
|
* registration pointing at a dead one. */
|
|
20763
21336
|
async stablePort() {
|
|
20764
|
-
const file = (0,
|
|
20765
|
-
const saved = Number(await
|
|
21337
|
+
const file = (0, import_path13.join)(this.opts.dataDir, ".port");
|
|
21338
|
+
const saved = Number(await import_promises9.default.readFile(file, "utf-8").catch(() => ""));
|
|
20766
21339
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
20767
21340
|
return saved;
|
|
20768
21341
|
}
|
|
20769
21342
|
const port = await freePort();
|
|
20770
|
-
await
|
|
21343
|
+
await import_promises9.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
20771
21344
|
});
|
|
20772
|
-
await
|
|
21345
|
+
await import_promises9.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
20773
21346
|
});
|
|
20774
21347
|
return port;
|
|
20775
21348
|
}
|
|
@@ -20863,14 +21436,14 @@ var IndexService = class {
|
|
|
20863
21436
|
};
|
|
20864
21437
|
function isFree(port) {
|
|
20865
21438
|
return new Promise((resolve2) => {
|
|
20866
|
-
const srv = (0,
|
|
21439
|
+
const srv = (0, import_net3.createServer)();
|
|
20867
21440
|
srv.once("error", () => resolve2(false));
|
|
20868
21441
|
srv.listen(port, "127.0.0.1", () => srv.close(() => resolve2(true)));
|
|
20869
21442
|
});
|
|
20870
21443
|
}
|
|
20871
21444
|
function freePort() {
|
|
20872
21445
|
return new Promise((resolve2, reject) => {
|
|
20873
|
-
const srv = (0,
|
|
21446
|
+
const srv = (0, import_net3.createServer)();
|
|
20874
21447
|
srv.on("error", reject);
|
|
20875
21448
|
srv.listen(0, "127.0.0.1", () => {
|
|
20876
21449
|
const addr = srv.address();
|
|
@@ -20936,25 +21509,25 @@ async function pairWithBrowser(serverUrl) {
|
|
|
20936
21509
|
}
|
|
20937
21510
|
|
|
20938
21511
|
// src/config.ts
|
|
20939
|
-
var
|
|
20940
|
-
var
|
|
20941
|
-
var
|
|
21512
|
+
var import_fs8 = __toESM(require("fs"));
|
|
21513
|
+
var import_os6 = __toESM(require("os"));
|
|
21514
|
+
var import_path18 = __toESM(require("path"));
|
|
20942
21515
|
|
|
20943
21516
|
// src/secureFile.ts
|
|
20944
|
-
var
|
|
20945
|
-
var
|
|
20946
|
-
var
|
|
21517
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
21518
|
+
var import_fs7 = __toESM(require("fs"));
|
|
21519
|
+
var import_path17 = __toESM(require("path"));
|
|
20947
21520
|
|
|
20948
21521
|
// src/keychain.ts
|
|
20949
21522
|
var import_child_process7 = require("child_process");
|
|
20950
|
-
var
|
|
20951
|
-
var
|
|
20952
|
-
var
|
|
20953
|
-
var
|
|
21523
|
+
var import_crypto5 = __toESM(require("crypto"));
|
|
21524
|
+
var import_fs5 = __toESM(require("fs"));
|
|
21525
|
+
var import_os4 = __toESM(require("os"));
|
|
21526
|
+
var import_path14 = __toESM(require("path"));
|
|
20954
21527
|
var SERVICE = "factiii-runner";
|
|
20955
21528
|
var ACCOUNT = "config-encryption-key";
|
|
20956
|
-
var DPAPI_KEY_FILE =
|
|
20957
|
-
|
|
21529
|
+
var DPAPI_KEY_FILE = import_path14.default.join(
|
|
21530
|
+
import_os4.default.homedir(),
|
|
20958
21531
|
".factiii-runner",
|
|
20959
21532
|
"config-key.dpapi"
|
|
20960
21533
|
);
|
|
@@ -21034,7 +21607,7 @@ function linuxWrite(key) {
|
|
|
21034
21607
|
function winRead() {
|
|
21035
21608
|
let wrapped;
|
|
21036
21609
|
try {
|
|
21037
|
-
wrapped =
|
|
21610
|
+
wrapped = import_fs5.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
|
|
21038
21611
|
} catch {
|
|
21039
21612
|
return { state: "missing" };
|
|
21040
21613
|
}
|
|
@@ -21059,8 +21632,8 @@ function winWrite(key) {
|
|
|
21059
21632
|
"-Command",
|
|
21060
21633
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
21061
21634
|
]);
|
|
21062
|
-
|
|
21063
|
-
|
|
21635
|
+
import_fs5.default.mkdirSync(import_path14.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
21636
|
+
import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
21064
21637
|
`, { mode: 384 });
|
|
21065
21638
|
}
|
|
21066
21639
|
function keychainBackend() {
|
|
@@ -21199,7 +21772,7 @@ function resolve() {
|
|
|
21199
21772
|
cached = { key: null, status: diagnose() };
|
|
21200
21773
|
return cached;
|
|
21201
21774
|
}
|
|
21202
|
-
const fresh =
|
|
21775
|
+
const fresh = import_crypto5.default.randomBytes(32);
|
|
21203
21776
|
writeKey(fresh);
|
|
21204
21777
|
const verify = readKey();
|
|
21205
21778
|
if (verify.state !== "found" || !verify.key.equals(fresh)) {
|
|
@@ -21221,23 +21794,23 @@ function keychainStatus() {
|
|
|
21221
21794
|
}
|
|
21222
21795
|
|
|
21223
21796
|
// src/secureStore.ts
|
|
21224
|
-
var
|
|
21225
|
-
var
|
|
21226
|
-
var
|
|
21797
|
+
var import_crypto6 = __toESM(require("crypto"));
|
|
21798
|
+
var import_fs6 = __toESM(require("fs"));
|
|
21799
|
+
var import_path16 = __toESM(require("path"));
|
|
21227
21800
|
|
|
21228
21801
|
// src/paths.ts
|
|
21229
|
-
var
|
|
21230
|
-
var
|
|
21231
|
-
var CONFIG_DIR =
|
|
21802
|
+
var import_os5 = __toESM(require("os"));
|
|
21803
|
+
var import_path15 = __toESM(require("path"));
|
|
21804
|
+
var CONFIG_DIR = import_path15.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
21232
21805
|
function safeSlug(spaceSlug) {
|
|
21233
21806
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21234
21807
|
}
|
|
21235
21808
|
function spaceDirPath(spaceSlug) {
|
|
21236
|
-
return
|
|
21809
|
+
return import_path15.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
21237
21810
|
}
|
|
21238
21811
|
|
|
21239
21812
|
// src/secureStore.ts
|
|
21240
|
-
var VAULT_KEY_FILE =
|
|
21813
|
+
var VAULT_KEY_FILE = import_path16.default.join(CONFIG_DIR, "vault-key.json");
|
|
21241
21814
|
var SCRYPT_N2 = 1 << 15;
|
|
21242
21815
|
var MIN_PASSWORD_LENGTH = 8;
|
|
21243
21816
|
var SecureStoreError = class extends Error {
|
|
@@ -21264,7 +21837,7 @@ function requireKeychainKey() {
|
|
|
21264
21837
|
);
|
|
21265
21838
|
}
|
|
21266
21839
|
function passwordKey(password, salt) {
|
|
21267
|
-
return
|
|
21840
|
+
return import_crypto6.default.scryptSync(password, salt, 32, {
|
|
21268
21841
|
N: SCRYPT_N2,
|
|
21269
21842
|
maxmem: 64 * 1024 * 1024
|
|
21270
21843
|
});
|
|
@@ -21272,7 +21845,7 @@ function passwordKey(password, salt) {
|
|
|
21272
21845
|
function wrapKey(machineKey, pwKey) {
|
|
21273
21846
|
const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
|
|
21274
21847
|
return Buffer.from(
|
|
21275
|
-
|
|
21848
|
+
import_crypto6.default.hkdfSync(
|
|
21276
21849
|
"sha256",
|
|
21277
21850
|
ikm,
|
|
21278
21851
|
Buffer.alloc(0),
|
|
@@ -21282,8 +21855,8 @@ function wrapKey(machineKey, pwKey) {
|
|
|
21282
21855
|
);
|
|
21283
21856
|
}
|
|
21284
21857
|
function seal(plaintext, key) {
|
|
21285
|
-
const iv =
|
|
21286
|
-
const cipher =
|
|
21858
|
+
const iv = import_crypto6.default.randomBytes(12);
|
|
21859
|
+
const cipher = import_crypto6.default.createCipheriv("aes-256-gcm", key, iv);
|
|
21287
21860
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
21288
21861
|
return {
|
|
21289
21862
|
iv: iv.toString("base64"),
|
|
@@ -21293,7 +21866,7 @@ function seal(plaintext, key) {
|
|
|
21293
21866
|
}
|
|
21294
21867
|
function unseal(box, key) {
|
|
21295
21868
|
try {
|
|
21296
|
-
const decipher =
|
|
21869
|
+
const decipher = import_crypto6.default.createDecipheriv(
|
|
21297
21870
|
"aes-256-gcm",
|
|
21298
21871
|
key,
|
|
21299
21872
|
Buffer.from(box.iv, "base64")
|
|
@@ -21310,7 +21883,7 @@ function unseal(box, key) {
|
|
|
21310
21883
|
function readVaultFile() {
|
|
21311
21884
|
try {
|
|
21312
21885
|
const parsed = JSON.parse(
|
|
21313
|
-
|
|
21886
|
+
import_fs6.default.readFileSync(VAULT_KEY_FILE, "utf-8")
|
|
21314
21887
|
);
|
|
21315
21888
|
return parsed.v === 1 ? parsed : null;
|
|
21316
21889
|
} catch {
|
|
@@ -21318,15 +21891,15 @@ function readVaultFile() {
|
|
|
21318
21891
|
}
|
|
21319
21892
|
}
|
|
21320
21893
|
function writeVaultFile(file) {
|
|
21321
|
-
|
|
21894
|
+
import_fs6.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
21322
21895
|
const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
|
|
21323
|
-
|
|
21896
|
+
import_fs6.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
|
|
21324
21897
|
`, { mode: 384 });
|
|
21325
|
-
|
|
21898
|
+
import_fs6.default.renameSync(tmp, VAULT_KEY_FILE);
|
|
21326
21899
|
}
|
|
21327
21900
|
var unlockedDek = null;
|
|
21328
21901
|
function createVault(machineKey) {
|
|
21329
|
-
const dek =
|
|
21902
|
+
const dek = import_crypto6.default.randomBytes(32);
|
|
21330
21903
|
writeVaultFile({
|
|
21331
21904
|
v: 1,
|
|
21332
21905
|
passwordEnabled: false,
|
|
@@ -21421,7 +21994,7 @@ function enablePassword(password) {
|
|
|
21421
21994
|
);
|
|
21422
21995
|
}
|
|
21423
21996
|
const dek = dataKey();
|
|
21424
|
-
const salt =
|
|
21997
|
+
const salt = import_crypto6.default.randomBytes(16);
|
|
21425
21998
|
writeVaultFile({
|
|
21426
21999
|
v: 1,
|
|
21427
22000
|
passwordEnabled: true,
|
|
@@ -21449,7 +22022,7 @@ function changePassword(current, next) {
|
|
|
21449
22022
|
"The current password is not correct."
|
|
21450
22023
|
);
|
|
21451
22024
|
}
|
|
21452
|
-
const salt =
|
|
22025
|
+
const salt = import_crypto6.default.randomBytes(16);
|
|
21453
22026
|
writeVaultFile({
|
|
21454
22027
|
v: 1,
|
|
21455
22028
|
passwordEnabled: true,
|
|
@@ -21526,8 +22099,8 @@ function storeStatus() {
|
|
|
21526
22099
|
// src/secureFile.ts
|
|
21527
22100
|
var MAGIC = "FACTIII1";
|
|
21528
22101
|
function encrypt(plain, key, version) {
|
|
21529
|
-
const iv =
|
|
21530
|
-
const cipher =
|
|
22102
|
+
const iv = import_crypto7.default.randomBytes(12);
|
|
22103
|
+
const cipher = import_crypto7.default.createCipheriv("aes-256-gcm", key, iv);
|
|
21531
22104
|
const data = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
21532
22105
|
const envelope = {
|
|
21533
22106
|
v: version,
|
|
@@ -21538,7 +22111,7 @@ function encrypt(plain, key, version) {
|
|
|
21538
22111
|
return `${MAGIC}${JSON.stringify(envelope)}`;
|
|
21539
22112
|
}
|
|
21540
22113
|
function decrypt(envelope, key) {
|
|
21541
|
-
const decipher =
|
|
22114
|
+
const decipher = import_crypto7.default.createDecipheriv(
|
|
21542
22115
|
"aes-256-gcm",
|
|
21543
22116
|
key,
|
|
21544
22117
|
Buffer.from(envelope.iv, "base64")
|
|
@@ -21559,15 +22132,15 @@ function parseEnvelope(raw) {
|
|
|
21559
22132
|
}
|
|
21560
22133
|
}
|
|
21561
22134
|
function atomicWrite(filePath, body) {
|
|
21562
|
-
|
|
22135
|
+
import_fs7.default.mkdirSync(import_path17.default.dirname(filePath), { recursive: true });
|
|
21563
22136
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
21564
|
-
|
|
21565
|
-
|
|
22137
|
+
import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
|
|
22138
|
+
import_fs7.default.renameSync(tmp, filePath);
|
|
21566
22139
|
}
|
|
21567
22140
|
function readSecureBuffer(filePath) {
|
|
21568
22141
|
let raw;
|
|
21569
22142
|
try {
|
|
21570
|
-
raw =
|
|
22143
|
+
raw = import_fs7.default.readFileSync(filePath, "utf-8");
|
|
21571
22144
|
} catch {
|
|
21572
22145
|
return null;
|
|
21573
22146
|
}
|
|
@@ -21624,7 +22197,7 @@ function writeSecureJson(filePath, value2) {
|
|
|
21624
22197
|
function readBootJson(filePath) {
|
|
21625
22198
|
let raw;
|
|
21626
22199
|
try {
|
|
21627
|
-
raw =
|
|
22200
|
+
raw = import_fs7.default.readFileSync(filePath, "utf-8");
|
|
21628
22201
|
} catch {
|
|
21629
22202
|
return null;
|
|
21630
22203
|
}
|
|
@@ -21674,8 +22247,8 @@ function writeBootJson(filePath, value2) {
|
|
|
21674
22247
|
}
|
|
21675
22248
|
|
|
21676
22249
|
// src/config.ts
|
|
21677
|
-
var CONFIG_DIR2 =
|
|
21678
|
-
var CONFIG_FILE =
|
|
22250
|
+
var CONFIG_DIR2 = import_path18.default.join(import_os6.default.homedir(), ".factiii-runner");
|
|
22251
|
+
var CONFIG_FILE = import_path18.default.join(CONFIG_DIR2, "config.json");
|
|
21679
22252
|
function readRunnerConfig() {
|
|
21680
22253
|
return readBootJson(CONFIG_FILE);
|
|
21681
22254
|
}
|
|
@@ -21683,7 +22256,7 @@ function writeRunnerConfig(config) {
|
|
|
21683
22256
|
writeBootJson(CONFIG_FILE, config);
|
|
21684
22257
|
}
|
|
21685
22258
|
function configExists() {
|
|
21686
|
-
return
|
|
22259
|
+
return import_fs8.default.existsSync(CONFIG_FILE);
|
|
21687
22260
|
}
|
|
21688
22261
|
|
|
21689
22262
|
// src/promptPassword.ts
|
|
@@ -21844,10 +22417,10 @@ function makeChunkReassembler() {
|
|
|
21844
22417
|
}
|
|
21845
22418
|
|
|
21846
22419
|
// src/daemon.ts
|
|
21847
|
-
var
|
|
21848
|
-
var
|
|
22420
|
+
var import_crypto8 = require("crypto");
|
|
22421
|
+
var import_fs10 = __toESM(require("fs"));
|
|
21849
22422
|
var import_node_datachannel = require("node-datachannel");
|
|
21850
|
-
var
|
|
22423
|
+
var import_path20 = __toESM(require("path"));
|
|
21851
22424
|
|
|
21852
22425
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
21853
22426
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23027,12 +23600,12 @@ function parse2(str) {
|
|
|
23027
23600
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23028
23601
|
return uri;
|
|
23029
23602
|
}
|
|
23030
|
-
function pathNames(obj,
|
|
23031
|
-
const regx = /\/{2,9}/g, names =
|
|
23032
|
-
if (
|
|
23603
|
+
function pathNames(obj, path20) {
|
|
23604
|
+
const regx = /\/{2,9}/g, names = path20.replace(regx, "/").split("/");
|
|
23605
|
+
if (path20.slice(0, 1) == "/" || path20.length === 0) {
|
|
23033
23606
|
names.splice(0, 1);
|
|
23034
23607
|
}
|
|
23035
|
-
if (
|
|
23608
|
+
if (path20.slice(-1) == "/") {
|
|
23036
23609
|
names.splice(names.length - 1, 1);
|
|
23037
23610
|
}
|
|
23038
23611
|
return names;
|
|
@@ -23651,7 +24224,7 @@ var protocol2 = Socket.protocol;
|
|
|
23651
24224
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
23652
24225
|
var import_debug7 = __toESM(require_src(), 1);
|
|
23653
24226
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
23654
|
-
function url(uri,
|
|
24227
|
+
function url(uri, path20 = "", loc) {
|
|
23655
24228
|
let obj = uri;
|
|
23656
24229
|
loc = loc || typeof location !== "undefined" && location;
|
|
23657
24230
|
if (null == uri)
|
|
@@ -23685,7 +24258,7 @@ function url(uri, path17 = "", loc) {
|
|
|
23685
24258
|
obj.path = obj.path || "/";
|
|
23686
24259
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
23687
24260
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
23688
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
24261
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path20;
|
|
23689
24262
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
23690
24263
|
return obj;
|
|
23691
24264
|
}
|
|
@@ -25319,8 +25892,8 @@ function lookup(uri, opts) {
|
|
|
25319
25892
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
25320
25893
|
const source = parsed.source;
|
|
25321
25894
|
const id = parsed.id;
|
|
25322
|
-
const
|
|
25323
|
-
const sameNamespace = cache[id] &&
|
|
25895
|
+
const path20 = parsed.path;
|
|
25896
|
+
const sameNamespace = cache[id] && path20 in cache[id]["nsps"];
|
|
25324
25897
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
25325
25898
|
let io;
|
|
25326
25899
|
if (newConnection) {
|
|
@@ -25362,8 +25935,8 @@ function cardContextPath(target, postId) {
|
|
|
25362
25935
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
25363
25936
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
25364
25937
|
}
|
|
25365
|
-
function cardContextPrompt(
|
|
25366
|
-
return `Read ${
|
|
25938
|
+
function cardContextPrompt(path20) {
|
|
25939
|
+
return `Read ${path20} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
|
|
25367
25940
|
}
|
|
25368
25941
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
25369
25942
|
var RESUME_SEED = "You are now in an interactive terminal, not the board UI. Respond in plain text and use your normal tools; do not use openui-lang or component schemas anymore.";
|
|
@@ -25527,11 +26100,11 @@ var TerminalManager = class {
|
|
|
25527
26100
|
};
|
|
25528
26101
|
|
|
25529
26102
|
// src/agent-adapter.ts
|
|
25530
|
-
var
|
|
25531
|
-
var
|
|
25532
|
-
var RUNNER_CONFIG_PATH =
|
|
26103
|
+
var import_fs9 = __toESM(require("fs"));
|
|
26104
|
+
var import_path19 = __toESM(require("path"));
|
|
26105
|
+
var RUNNER_CONFIG_PATH = import_path19.default.join(CONFIG_DIR, "runner-config.json");
|
|
25533
26106
|
function connectionPath(spaceSlug) {
|
|
25534
|
-
return
|
|
26107
|
+
return import_path19.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
25535
26108
|
}
|
|
25536
26109
|
function writeJson(filePath, value2) {
|
|
25537
26110
|
writeSecureJson(filePath, value2);
|
|
@@ -25558,14 +26131,14 @@ function migrateLegacyConfigs() {
|
|
|
25558
26131
|
const file = { boards: {} };
|
|
25559
26132
|
let legacy = [];
|
|
25560
26133
|
try {
|
|
25561
|
-
legacy =
|
|
26134
|
+
legacy = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
|
|
25562
26135
|
} catch {
|
|
25563
26136
|
return file;
|
|
25564
26137
|
}
|
|
25565
26138
|
for (const name of legacy) {
|
|
25566
26139
|
const slug = name.slice("space-".length, -".json".length);
|
|
25567
26140
|
const parsed = readSecureJson(
|
|
25568
|
-
|
|
26141
|
+
import_path19.default.join(CONFIG_DIR, name)
|
|
25569
26142
|
);
|
|
25570
26143
|
if (!parsed) continue;
|
|
25571
26144
|
if (!file.defaultSlug) {
|
|
@@ -25585,14 +26158,14 @@ function encryptStrayPlaintextConfigs() {
|
|
|
25585
26158
|
const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
|
|
25586
26159
|
let names = [];
|
|
25587
26160
|
try {
|
|
25588
|
-
names =
|
|
26161
|
+
names = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
|
|
25589
26162
|
} catch {
|
|
25590
26163
|
return;
|
|
25591
26164
|
}
|
|
25592
26165
|
for (const name of names) {
|
|
25593
|
-
const filePath =
|
|
26166
|
+
const filePath = import_path19.default.join(CONFIG_DIR, name);
|
|
25594
26167
|
try {
|
|
25595
|
-
const raw =
|
|
26168
|
+
const raw = import_fs9.default.readFileSync(filePath, "utf-8");
|
|
25596
26169
|
if (!raw.startsWith("{")) continue;
|
|
25597
26170
|
writeSecureJson(filePath, JSON.parse(raw));
|
|
25598
26171
|
} catch {
|
|
@@ -25623,7 +26196,7 @@ async function getConfiguredConnections() {
|
|
|
25623
26196
|
}
|
|
25624
26197
|
if (await isSignedIn(hostTarget(CONFIG_DIR), "claude")) kinds.add("claude");
|
|
25625
26198
|
try {
|
|
25626
|
-
const hasOneDrive =
|
|
26199
|
+
const hasOneDrive = import_fs9.default.readdirSync(CONFIG_DIR).some((f) => f.endsWith(".onedrive.json"));
|
|
25627
26200
|
if (hasOneDrive) kinds.add("onedrive");
|
|
25628
26201
|
} catch {
|
|
25629
26202
|
}
|
|
@@ -25694,7 +26267,7 @@ var localConfigProvider = {
|
|
|
25694
26267
|
writeOneDriveConnection(spaceSlug, connection) {
|
|
25695
26268
|
const filePath = connectionPath(spaceSlug);
|
|
25696
26269
|
if (!connection) {
|
|
25697
|
-
|
|
26270
|
+
import_fs9.default.rmSync(filePath, { force: true });
|
|
25698
26271
|
return;
|
|
25699
26272
|
}
|
|
25700
26273
|
writeJson(filePath, connection);
|
|
@@ -25719,7 +26292,7 @@ var localConfigProvider = {
|
|
|
25719
26292
|
"claude",
|
|
25720
26293
|
"codex"
|
|
25721
26294
|
]) {
|
|
25722
|
-
|
|
26295
|
+
import_fs9.default.mkdirSync(import_path19.default.join(dir, sub), { recursive: true });
|
|
25723
26296
|
}
|
|
25724
26297
|
return dir;
|
|
25725
26298
|
}
|
|
@@ -25893,15 +26466,15 @@ async function startDaemon(config) {
|
|
|
25893
26466
|
}
|
|
25894
26467
|
);
|
|
25895
26468
|
const indexCandidates = [
|
|
25896
|
-
|
|
25897
|
-
|
|
26469
|
+
import_path20.default.join(__dirname, "..", "index"),
|
|
26470
|
+
import_path20.default.join(__dirname, "..", "..", "index", "image")
|
|
25898
26471
|
];
|
|
25899
|
-
const indexDir = indexCandidates.find((p) =>
|
|
26472
|
+
const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
|
|
25900
26473
|
const odIndex = new IndexService({
|
|
25901
|
-
controlToken: (0,
|
|
26474
|
+
controlToken: (0, import_crypto8.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
25902
26475
|
embedUrl: () => config.serverUrl,
|
|
25903
26476
|
indexDir,
|
|
25904
|
-
dataDir:
|
|
26477
|
+
dataDir: import_path20.default.join(CONFIG_DIR, "index-data")
|
|
25905
26478
|
});
|
|
25906
26479
|
void odIndex.ensureRunning().catch((err) => {
|
|
25907
26480
|
console.error(
|