@factiii/runner 0.11.3 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1474 -929
- package/index/image-package.json +1 -1
- package/index/index.js +2 -2
- package/package.json +5 -3
- package/vendor/tmux-resurrect/COMMIT +1 -0
- package/vendor/tmux-resurrect/LICENSE.md +19 -0
- package/vendor/tmux-resurrect/README.factiii.md +13 -0
- package/vendor/tmux-resurrect/VERSION +1 -0
- package/vendor/tmux-resurrect/save_command_strategies/ps.sh +24 -0
- package/vendor/tmux-resurrect/scripts/check_tmux_version.sh +78 -0
- package/vendor/tmux-resurrect/scripts/helpers.sh +168 -0
- package/vendor/tmux-resurrect/scripts/process_restore_helpers.sh +198 -0
- package/vendor/tmux-resurrect/scripts/restore.sh +410 -0
- package/vendor/tmux-resurrect/scripts/save.sh +328 -0
- package/vendor/tmux-resurrect/scripts/spinner_helpers.sh +8 -0
- package/vendor/tmux-resurrect/scripts/tmux_spinner.sh +29 -0
- package/vendor/tmux-resurrect/scripts/variables.sh +51 -0
package/dist/cli.js
CHANGED
|
@@ -963,7 +963,7 @@ var require_command = __commonJS({
|
|
|
963
963
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
964
964
|
var EventEmitter = require("node:events").EventEmitter;
|
|
965
965
|
var childProcess = require("node:child_process");
|
|
966
|
-
var
|
|
966
|
+
var path23 = require("node:path");
|
|
967
967
|
var fs15 = require("node:fs");
|
|
968
968
|
var process2 = require("node:process");
|
|
969
969
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
@@ -1896,9 +1896,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1896
1896
|
let launchWithNode = false;
|
|
1897
1897
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1898
1898
|
function findFile(baseDir, baseName) {
|
|
1899
|
-
const localBin =
|
|
1899
|
+
const localBin = path23.resolve(baseDir, baseName);
|
|
1900
1900
|
if (fs15.existsSync(localBin)) return localBin;
|
|
1901
|
-
if (sourceExt.includes(
|
|
1901
|
+
if (sourceExt.includes(path23.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
1903
|
(ext) => fs15.existsSync(`${localBin}${ext}`)
|
|
1904
1904
|
);
|
|
@@ -1916,17 +1916,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1916
1916
|
} catch (err) {
|
|
1917
1917
|
resolvedScriptPath = this._scriptPath;
|
|
1918
1918
|
}
|
|
1919
|
-
executableDir =
|
|
1920
|
-
|
|
1919
|
+
executableDir = path23.resolve(
|
|
1920
|
+
path23.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 = path23.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path23.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(path23.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 = path23.basename(filename, path23.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(path24) {
|
|
2795
|
+
if (path24 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path24;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -6499,7 +6499,7 @@ var require_websocket = __commonJS({
|
|
|
6499
6499
|
var http = require("http");
|
|
6500
6500
|
var net3 = require("net");
|
|
6501
6501
|
var tls = require("tls");
|
|
6502
|
-
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
6502
|
+
var { randomBytes: randomBytes2, createHash: createHash2 } = require("crypto");
|
|
6503
6503
|
var { Duplex, Readable } = require("stream");
|
|
6504
6504
|
var { URL: URL2 } = require("url");
|
|
6505
6505
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -7037,7 +7037,7 @@ var require_websocket = __commonJS({
|
|
|
7037
7037
|
}
|
|
7038
7038
|
}
|
|
7039
7039
|
const defaultPort = isSecure ? 443 : 80;
|
|
7040
|
-
const key =
|
|
7040
|
+
const key = randomBytes2(16).toString("base64");
|
|
7041
7041
|
const request = isSecure ? https.request : http.request;
|
|
7042
7042
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
7043
7043
|
let perMessageDeflate;
|
|
@@ -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: path23, errorMaps, issueData } = params;
|
|
8432
|
+
const fullPath = [...path23, ...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, path23, key) {
|
|
8549
8549
|
this._cachedPath = [];
|
|
8550
8550
|
this.parent = parent;
|
|
8551
8551
|
this.data = value2;
|
|
8552
|
-
this._path =
|
|
8552
|
+
this._path = path23;
|
|
8553
8553
|
this._key = key;
|
|
8554
8554
|
}
|
|
8555
8555
|
get path() {
|
|
@@ -12910,7 +12910,7 @@ ${diff.slice(0, 8e3)}`,
|
|
|
12910
12910
|
}
|
|
12911
12911
|
|
|
12912
12912
|
// ../../shared/all/helpers/board-agent-core/engine.ts
|
|
12913
|
-
var
|
|
12913
|
+
var import_path15 = require("path");
|
|
12914
12914
|
|
|
12915
12915
|
// ../../shared/all/domains/electron.ts
|
|
12916
12916
|
var SETUP_LOG_PREFIX = "log ";
|
|
@@ -13019,7 +13019,7 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
|
|
|
13019
13019
|
await target.sh(`mkdir -p ${target.paths.worktrees}`);
|
|
13020
13020
|
}
|
|
13021
13021
|
async function addWorktree(target, workName, baseBranch, kind) {
|
|
13022
|
-
const
|
|
13022
|
+
const path23 = worktreeFor(target.paths, workName);
|
|
13023
13023
|
const branch = workBranch(workName, kind);
|
|
13024
13024
|
await gitInDir(
|
|
13025
13025
|
target,
|
|
@@ -13029,7 +13029,7 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13029
13029
|
baseBranch
|
|
13030
13030
|
).catch(() => {
|
|
13031
13031
|
});
|
|
13032
|
-
await removeWorktree(target,
|
|
13032
|
+
await removeWorktree(target, path23).catch(() => {
|
|
13033
13033
|
});
|
|
13034
13034
|
await gitInDir(
|
|
13035
13035
|
target,
|
|
@@ -13038,30 +13038,30 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13038
13038
|
"add",
|
|
13039
13039
|
"-B",
|
|
13040
13040
|
branch,
|
|
13041
|
-
|
|
13041
|
+
path23,
|
|
13042
13042
|
`origin/${baseBranch}`
|
|
13043
13043
|
);
|
|
13044
|
-
return { path:
|
|
13044
|
+
return { path: path23, branch };
|
|
13045
13045
|
}
|
|
13046
|
-
async function removeWorktree(target,
|
|
13046
|
+
async function removeWorktree(target, path23) {
|
|
13047
13047
|
await gitInDir(
|
|
13048
13048
|
target,
|
|
13049
13049
|
target.paths.repo,
|
|
13050
13050
|
"worktree",
|
|
13051
13051
|
"remove",
|
|
13052
13052
|
"--force",
|
|
13053
|
-
|
|
13053
|
+
path23
|
|
13054
13054
|
);
|
|
13055
13055
|
}
|
|
13056
|
-
async function removeWorktreeAndBranch(target,
|
|
13056
|
+
async function removeWorktreeAndBranch(target, path23) {
|
|
13057
13057
|
const branch = await gitInDir(
|
|
13058
13058
|
target,
|
|
13059
|
-
|
|
13059
|
+
path23,
|
|
13060
13060
|
"rev-parse",
|
|
13061
13061
|
"--abbrev-ref",
|
|
13062
13062
|
"HEAD"
|
|
13063
13063
|
).then((out) => out.trim()).catch(() => "");
|
|
13064
|
-
await removeWorktree(target,
|
|
13064
|
+
await removeWorktree(target, path23);
|
|
13065
13065
|
if (branch && branch !== "HEAD") {
|
|
13066
13066
|
await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
|
|
13067
13067
|
() => {
|
|
@@ -13075,8 +13075,8 @@ async function pruneWorktrees(target) {
|
|
|
13075
13075
|
}
|
|
13076
13076
|
);
|
|
13077
13077
|
}
|
|
13078
|
-
function isWorktreePath(target,
|
|
13079
|
-
return
|
|
13078
|
+
function isWorktreePath(target, path23) {
|
|
13079
|
+
return path23.startsWith(`${target.paths.worktrees}/`);
|
|
13080
13080
|
}
|
|
13081
13081
|
async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
|
|
13082
13082
|
if (name) await gitInDir(target, workdir, "config", "user.name", name);
|
|
@@ -13102,13 +13102,13 @@ function parseStatus(raw) {
|
|
|
13102
13102
|
const y = line[1];
|
|
13103
13103
|
const rest = line.slice(3);
|
|
13104
13104
|
const arrow = rest.indexOf(" -> ");
|
|
13105
|
-
const
|
|
13105
|
+
const path23 = arrow === -1 ? rest : rest.slice(arrow + 4);
|
|
13106
13106
|
if (x === "?" || y === "?") {
|
|
13107
|
-
unstaged.push({ path:
|
|
13107
|
+
unstaged.push({ path: path23, code: "A" });
|
|
13108
13108
|
continue;
|
|
13109
13109
|
}
|
|
13110
|
-
if (x !== " ") staged.push({ path:
|
|
13111
|
-
if (y !== " ") unstaged.push({ path:
|
|
13110
|
+
if (x !== " ") staged.push({ path: path23, code: x });
|
|
13111
|
+
if (y !== " ") unstaged.push({ path: path23, code: y });
|
|
13112
13112
|
}
|
|
13113
13113
|
return { staged, unstaged };
|
|
13114
13114
|
}
|
|
@@ -13337,6 +13337,13 @@ async function ensureRedis(target) {
|
|
|
13337
13337
|
}
|
|
13338
13338
|
throw new Error("redis-server did not come up.");
|
|
13339
13339
|
}
|
|
13340
|
+
var TERMINAL_ACK_KEY = "terminal:ack";
|
|
13341
|
+
async function getTerminalAck(target) {
|
|
13342
|
+
return await redisCli(target, `GET ${TERMINAL_ACK_KEY}`).catch(() => "");
|
|
13343
|
+
}
|
|
13344
|
+
async function setTerminalAck(target, digest) {
|
|
13345
|
+
await redisCliStdin(target, ["SET", TERMINAL_ACK_KEY], digest);
|
|
13346
|
+
}
|
|
13340
13347
|
async function putCard(target, record) {
|
|
13341
13348
|
const id = safeId(record.postId);
|
|
13342
13349
|
await redisCliStdin(target, ["SET", cardKey(id)], JSON.stringify(record));
|
|
@@ -13372,49 +13379,10 @@ async function deleteCard(target, postId) {
|
|
|
13372
13379
|
await redisCli(target, `SREM ${INDEX_KEY} ${id}`);
|
|
13373
13380
|
}
|
|
13374
13381
|
|
|
13375
|
-
// ../../shared/all/helpers/
|
|
13376
|
-
function isWorktreeSession(s, target) {
|
|
13377
|
-
return isWorktreePath(target, s.workspacePath);
|
|
13378
|
-
}
|
|
13379
|
-
function sessionToRecord(s, prompt2 = "") {
|
|
13380
|
-
return {
|
|
13381
|
-
postId: s.postId,
|
|
13382
|
-
taskId: s.taskId,
|
|
13383
|
-
mode: s.mode,
|
|
13384
|
-
phase: s.phase,
|
|
13385
|
-
provider: s.provider,
|
|
13386
|
-
worktreePath: s.workspacePath,
|
|
13387
|
-
agentSessionId: s.agentSessionId,
|
|
13388
|
-
taskTitle: s.taskTitle,
|
|
13389
|
-
targetBranch: s.targetBranch,
|
|
13390
|
-
startedAt: s.startedAt,
|
|
13391
|
-
prompt: prompt2,
|
|
13392
|
-
pendingQuestion: s.pendingQuestion
|
|
13393
|
-
};
|
|
13394
|
-
}
|
|
13395
|
-
async function putSessionCard(s, target, prompt2 = "") {
|
|
13396
|
-
try {
|
|
13397
|
-
await putCard(target, sessionToRecord(s, prompt2));
|
|
13398
|
-
} catch {
|
|
13399
|
-
}
|
|
13400
|
-
}
|
|
13401
|
-
async function teardownSession(s, target) {
|
|
13402
|
-
if (isWorktreeSession(s, target)) {
|
|
13403
|
-
const drop = s.mode === "bare" ? removeWorktreeAndBranch : removeWorktree;
|
|
13404
|
-
await drop(target, s.workspacePath).catch(() => {
|
|
13405
|
-
});
|
|
13406
|
-
}
|
|
13407
|
-
await deleteCard(target, s.postId).catch(() => {
|
|
13408
|
-
});
|
|
13409
|
-
}
|
|
13410
|
-
|
|
13411
|
-
// ../../shared/all/helpers/board-agent-core/exec.ts
|
|
13412
|
-
var import_child_process2 = require("child_process");
|
|
13382
|
+
// ../../shared/all/helpers/board-agent-core/tmux.ts
|
|
13413
13383
|
var import_fs = require("fs");
|
|
13414
|
-
var import_promises =
|
|
13415
|
-
var import_net = __toESM(require("net"));
|
|
13384
|
+
var import_promises = require("fs/promises");
|
|
13416
13385
|
var import_path3 = __toESM(require("path"));
|
|
13417
|
-
var import_util4 = require("util");
|
|
13418
13386
|
|
|
13419
13387
|
// ../../shared/all/helpers/board-agent-core/host-env.ts
|
|
13420
13388
|
var import_child_process = require("child_process");
|
|
@@ -13541,13 +13509,314 @@ async function hostEnv() {
|
|
|
13541
13509
|
return { ...process.env, PATH: await freshPath() };
|
|
13542
13510
|
}
|
|
13543
13511
|
|
|
13512
|
+
// ../../shared/all/helpers/board-agent-core/tmux.ts
|
|
13513
|
+
var TMUX_CONF = [
|
|
13514
|
+
// Wheel scrolls, drag selects in copy mode.
|
|
13515
|
+
"set -g mouse on",
|
|
13516
|
+
"set -g history-limit 50000",
|
|
13517
|
+
// These three are copy-on-select: tmux emits OSC 52, the client clips it.
|
|
13518
|
+
"set -g set-clipboard on",
|
|
13519
|
+
"set -ga terminal-features ,*:clipboard",
|
|
13520
|
+
"bind -T copy-mode MouseDragEnd1Pane send -X copy-selection-and-cancel",
|
|
13521
|
+
"bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel",
|
|
13522
|
+
// A pane whose process exits stays as a dead pane instead of taking the
|
|
13523
|
+
// session — and the tab — with it. `respawnTmuxPane` brings it back. Without
|
|
13524
|
+
// this a stray `exit` or Ctrl-D destroys the tab the user was working in.
|
|
13525
|
+
"set -g remain-on-exit on",
|
|
13526
|
+
""
|
|
13527
|
+
].join("\n");
|
|
13528
|
+
var DEFAULT_SIZE = { cols: 80, rows: 24 };
|
|
13529
|
+
var SOCKET = "factiii";
|
|
13530
|
+
function safeTmuxName(raw) {
|
|
13531
|
+
return raw.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
13532
|
+
}
|
|
13533
|
+
function tmuxConfPath(target) {
|
|
13534
|
+
return `${target.paths.root}/tmux.conf`;
|
|
13535
|
+
}
|
|
13536
|
+
function tmuxCli(target) {
|
|
13537
|
+
return `tmux -L ${SOCKET} -f ${tmuxConfPath(target)}`;
|
|
13538
|
+
}
|
|
13539
|
+
function shellQuote(argv) {
|
|
13540
|
+
return argv.map((arg) => `'${arg.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
13541
|
+
}
|
|
13542
|
+
function ptyArgv(argv, size) {
|
|
13543
|
+
const sized = [
|
|
13544
|
+
"sh",
|
|
13545
|
+
"-c",
|
|
13546
|
+
`stty rows ${size.rows} cols ${size.cols} 2>/dev/null; exec ${shellQuote(argv)}`
|
|
13547
|
+
];
|
|
13548
|
+
const script = process.platform === "darwin" ? ["script", "-q", "/dev/null", ...sized] : ["script", "-qec", shellQuote(sized), "/dev/null"];
|
|
13549
|
+
return ["bash", "-c", 'exec "$@" < <(cat)', "bash", ...script];
|
|
13550
|
+
}
|
|
13551
|
+
async function respawnTmuxPane(opts) {
|
|
13552
|
+
const { target, session, argv } = opts;
|
|
13553
|
+
const cmd = ["respawn-pane", "-k"];
|
|
13554
|
+
if (opts.cwd) cmd.push("-c", opts.cwd);
|
|
13555
|
+
for (const [k, v] of Object.entries(opts.env ?? {})) {
|
|
13556
|
+
cmd.push("-e", `${k}=${v}`);
|
|
13557
|
+
}
|
|
13558
|
+
cmd.push("-t", session, "--", ...argv);
|
|
13559
|
+
await target.sh(`${tmuxCli(target)} ${shellQuote(cmd)}`);
|
|
13560
|
+
}
|
|
13561
|
+
async function tmuxSessionExists(target, session) {
|
|
13562
|
+
return await target.sh(
|
|
13563
|
+
`${tmuxCli(target)} has-session -t ${shellQuote([session])} 2>/dev/null`
|
|
13564
|
+
).then(() => true).catch(() => false);
|
|
13565
|
+
}
|
|
13566
|
+
async function setTmuxSessionOption(target, session, name, value2) {
|
|
13567
|
+
await target.sh(
|
|
13568
|
+
`${tmuxCli(target)} set-option -t ${shellQuote([session])} ${shellQuote([name, value2])}`
|
|
13569
|
+
).catch(() => {
|
|
13570
|
+
});
|
|
13571
|
+
}
|
|
13572
|
+
async function sourceTmuxConf(target) {
|
|
13573
|
+
const conf = tmuxConfPath(target);
|
|
13574
|
+
await target.writeFile(conf, TMUX_CONF);
|
|
13575
|
+
await target.sh(`${tmuxCli(target)} source-file ${shellQuote([conf])}`).catch(() => {
|
|
13576
|
+
});
|
|
13577
|
+
}
|
|
13578
|
+
function resurrectSource() {
|
|
13579
|
+
const rel = ["vendor", "tmux-resurrect"];
|
|
13580
|
+
const candidates2 = [
|
|
13581
|
+
import_path3.default.join(__dirname, "..", ...rel),
|
|
13582
|
+
import_path3.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", ...rel),
|
|
13583
|
+
import_path3.default.join(process.cwd(), "apps", "runner", ...rel)
|
|
13584
|
+
];
|
|
13585
|
+
return candidates2.find(
|
|
13586
|
+
(dir) => (0, import_fs.existsSync)(import_path3.default.join(dir, "scripts", "save.sh"))
|
|
13587
|
+
) ?? null;
|
|
13588
|
+
}
|
|
13589
|
+
function hostRoot(target) {
|
|
13590
|
+
return import_path3.default.dirname(target.paths.root);
|
|
13591
|
+
}
|
|
13592
|
+
function resurrectRoot(target) {
|
|
13593
|
+
return `${hostRoot(target)}/tmux-plugins/tmux-resurrect`;
|
|
13594
|
+
}
|
|
13595
|
+
function resurrectStateDir(target) {
|
|
13596
|
+
return `${hostRoot(target)}/tmux-resurrect-state`;
|
|
13597
|
+
}
|
|
13598
|
+
async function ensureTmuxResurrect(target) {
|
|
13599
|
+
const source = resurrectSource();
|
|
13600
|
+
if (!source) return;
|
|
13601
|
+
const root = resurrectRoot(target);
|
|
13602
|
+
try {
|
|
13603
|
+
const want = await (0, import_promises.readFile)(import_path3.default.join(source, "VERSION"), "utf-8").then((v) => v.trim()).catch(() => "");
|
|
13604
|
+
const have = await target.sh(`cat ${shellQuote([`${root}/VERSION`])} 2>/dev/null || true`).catch(() => "");
|
|
13605
|
+
if (want && have.trim() === want) return;
|
|
13606
|
+
const files = (await resurrectFiles(source)).filter((f) => f !== "VERSION");
|
|
13607
|
+
for (const rel of files) {
|
|
13608
|
+
await target.writeFile(
|
|
13609
|
+
`${root}/${rel}`,
|
|
13610
|
+
await (0, import_promises.readFile)(import_path3.default.join(source, rel), "utf-8")
|
|
13611
|
+
);
|
|
13612
|
+
}
|
|
13613
|
+
await target.sh(
|
|
13614
|
+
`chmod +x ${shellQuote([`${root}/scripts`])}/*.sh ${shellQuote([`${root}/save_command_strategies`])}/*.sh`
|
|
13615
|
+
);
|
|
13616
|
+
await target.writeFile(`${root}/VERSION`, want);
|
|
13617
|
+
} catch {
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13620
|
+
async function resurrectFiles(source) {
|
|
13621
|
+
const out = [];
|
|
13622
|
+
const walk = async (rel) => {
|
|
13623
|
+
const entries = await (0, import_promises.readdir)(import_path3.default.join(source, rel), {
|
|
13624
|
+
withFileTypes: true
|
|
13625
|
+
});
|
|
13626
|
+
for (const entry of entries) {
|
|
13627
|
+
const next = rel ? `${rel}/${entry.name}` : entry.name;
|
|
13628
|
+
if (entry.isDirectory()) await walk(next);
|
|
13629
|
+
else out.push(next);
|
|
13630
|
+
}
|
|
13631
|
+
};
|
|
13632
|
+
await walk("");
|
|
13633
|
+
return out;
|
|
13634
|
+
}
|
|
13635
|
+
async function setResurrectOptions(target) {
|
|
13636
|
+
const opts = [
|
|
13637
|
+
["@resurrect-processes", "false"],
|
|
13638
|
+
["@resurrect-capture-pane-contents", "on"],
|
|
13639
|
+
["@resurrect-dir", resurrectStateDir(target)]
|
|
13640
|
+
];
|
|
13641
|
+
for (const [name, value2] of opts) {
|
|
13642
|
+
await target.sh(`${tmuxCli(target)} set-option -g ${shellQuote([name, value2])}`).catch(() => {
|
|
13643
|
+
});
|
|
13644
|
+
}
|
|
13645
|
+
}
|
|
13646
|
+
async function tmuxServerRunning(target) {
|
|
13647
|
+
return await target.sh(`${tmuxCli(target)} list-sessions -F '#{session_name}' 2>/dev/null`).then(() => true).catch(() => false);
|
|
13648
|
+
}
|
|
13649
|
+
async function resurrectSave(target) {
|
|
13650
|
+
if (!await tmuxServerRunning(target)) return;
|
|
13651
|
+
await setResurrectOptions(target);
|
|
13652
|
+
await target.sh(
|
|
13653
|
+
`${tmuxCli(target)} run-shell ${shellQuote([`${resurrectRoot(target)}/scripts/save.sh`])}`
|
|
13654
|
+
).catch(() => {
|
|
13655
|
+
});
|
|
13656
|
+
}
|
|
13657
|
+
async function resurrectRestore(target) {
|
|
13658
|
+
const scripts = resurrectRoot(target);
|
|
13659
|
+
const hasSave = await target.sh(`test -f ${shellQuote([`${resurrectStateDir(target)}/last`])}`).then(() => true).catch(() => false);
|
|
13660
|
+
if (!hasSave) return [];
|
|
13661
|
+
const placeholder = "factiii-restore";
|
|
13662
|
+
try {
|
|
13663
|
+
await target.sh(
|
|
13664
|
+
`${tmuxCli(target)} new-session -d -s ${placeholder} 'sleep 60' 2>/dev/null || true`
|
|
13665
|
+
);
|
|
13666
|
+
await setResurrectOptions(target);
|
|
13667
|
+
await target.sh(
|
|
13668
|
+
`${tmuxCli(target)} run-shell ${shellQuote([`${scripts}/scripts/restore.sh`])}`
|
|
13669
|
+
);
|
|
13670
|
+
const stdout = await target.sh(
|
|
13671
|
+
`${tmuxCli(target)} list-sessions -F '#{session_name}' 2>/dev/null || true`
|
|
13672
|
+
);
|
|
13673
|
+
return stdout.split("\n").map((line) => line.trim()).filter((name) => name.startsWith("terminal-"));
|
|
13674
|
+
} catch {
|
|
13675
|
+
return [];
|
|
13676
|
+
} finally {
|
|
13677
|
+
await target.sh(
|
|
13678
|
+
`${tmuxCli(target)} kill-session -t ${placeholder} 2>/dev/null || true`
|
|
13679
|
+
).catch(() => {
|
|
13680
|
+
});
|
|
13681
|
+
}
|
|
13682
|
+
}
|
|
13683
|
+
async function tmuxPanePaths(target) {
|
|
13684
|
+
const stdout = await target.sh(
|
|
13685
|
+
`${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_current_path}' 2>/dev/null || true`
|
|
13686
|
+
).catch(() => "");
|
|
13687
|
+
const byName = /* @__PURE__ */ new Map();
|
|
13688
|
+
for (const raw of stdout.split("\n")) {
|
|
13689
|
+
const [name, dir] = raw.trim().split("|");
|
|
13690
|
+
if (name && dir && !byName.has(name)) byName.set(name, dir);
|
|
13691
|
+
}
|
|
13692
|
+
return byName;
|
|
13693
|
+
}
|
|
13694
|
+
async function terminalSessionNames(target, postId) {
|
|
13695
|
+
const prefix = `terminal-${safeTmuxName(postId)}-`;
|
|
13696
|
+
const stdout = await target.sh(
|
|
13697
|
+
`${tmuxCli(target)} list-sessions -F '#{session_name}' 2>/dev/null || true`
|
|
13698
|
+
).catch(() => "");
|
|
13699
|
+
return stdout.split("\n").map((line) => line.trim()).filter((name) => name.startsWith(prefix));
|
|
13700
|
+
}
|
|
13701
|
+
async function killTerminalSessions(target, postId) {
|
|
13702
|
+
const names = await terminalSessionNames(target, postId);
|
|
13703
|
+
if (names.length === 0) return;
|
|
13704
|
+
const cli = tmuxCli(target);
|
|
13705
|
+
const script = names.map(
|
|
13706
|
+
(name) => (
|
|
13707
|
+
// -P walks children; the pane pid itself dies with the session below.
|
|
13708
|
+
`for p in $(${cli} list-panes -t ${name} -F '#{pane_pid}' 2>/dev/null); do pkill -TERM -P "$p" 2>/dev/null || true; done; ${cli} kill-session -t ${name} 2>/dev/null || true`
|
|
13709
|
+
)
|
|
13710
|
+
).join("; ");
|
|
13711
|
+
await target.sh(script).catch(() => {
|
|
13712
|
+
});
|
|
13713
|
+
}
|
|
13714
|
+
async function startTmuxSession(opts) {
|
|
13715
|
+
const { target, session } = opts;
|
|
13716
|
+
const conf = tmuxConfPath(target);
|
|
13717
|
+
await target.writeFile(conf, TMUX_CONF);
|
|
13718
|
+
let size = opts.size ?? DEFAULT_SIZE;
|
|
13719
|
+
const dataCbs = [];
|
|
13720
|
+
const closeCbs = [];
|
|
13721
|
+
const errorCbs = [];
|
|
13722
|
+
let proc;
|
|
13723
|
+
const attach = async () => {
|
|
13724
|
+
const argv = ["tmux", "-L", SOCKET, "-u", "-f", conf, "new-session"];
|
|
13725
|
+
if (opts.attachExisting) argv.push("-A");
|
|
13726
|
+
argv.push("-s", session, "-x", String(size.cols), "-y", String(size.rows));
|
|
13727
|
+
for (const [k, v] of Object.entries(opts.env ?? {})) {
|
|
13728
|
+
argv.push("-e", `${k}=${v}`);
|
|
13729
|
+
}
|
|
13730
|
+
argv.push("--", ...opts.argv);
|
|
13731
|
+
const mine = await target.spawn(ptyArgv(argv, size), {
|
|
13732
|
+
env: { TERM: "xterm-256color" }
|
|
13733
|
+
});
|
|
13734
|
+
proc = mine;
|
|
13735
|
+
mine.stdin?.on("error", () => {
|
|
13736
|
+
});
|
|
13737
|
+
const live = () => proc === mine;
|
|
13738
|
+
const emit = (chunk) => {
|
|
13739
|
+
if (live()) for (const cb of dataCbs) cb(chunk);
|
|
13740
|
+
};
|
|
13741
|
+
mine.stdout?.on("data", emit);
|
|
13742
|
+
mine.stderr?.on("data", emit);
|
|
13743
|
+
mine.on("error", (err) => {
|
|
13744
|
+
if (live()) for (const cb of errorCbs) cb(err);
|
|
13745
|
+
});
|
|
13746
|
+
mine.on("close", (code) => {
|
|
13747
|
+
if (live()) for (const cb of closeCbs) cb(code ?? null);
|
|
13748
|
+
});
|
|
13749
|
+
};
|
|
13750
|
+
await attach();
|
|
13751
|
+
return {
|
|
13752
|
+
session,
|
|
13753
|
+
onData: (cb) => dataCbs.push(cb),
|
|
13754
|
+
onClose: (cb) => closeCbs.push(cb),
|
|
13755
|
+
onError: (cb) => errorCbs.push(cb),
|
|
13756
|
+
write: (data) => proc.stdin?.write(data),
|
|
13757
|
+
resize: async (cols, rows) => {
|
|
13758
|
+
if (cols === size.cols && rows === size.rows) return;
|
|
13759
|
+
size = { cols, rows };
|
|
13760
|
+
const old = proc;
|
|
13761
|
+
await attach();
|
|
13762
|
+
killTree(old);
|
|
13763
|
+
},
|
|
13764
|
+
detach: () => killTree(proc)
|
|
13765
|
+
};
|
|
13766
|
+
}
|
|
13767
|
+
|
|
13768
|
+
// ../../shared/all/helpers/card-ai/card-session.ts
|
|
13769
|
+
function isWorktreeSession(s, target) {
|
|
13770
|
+
return isWorktreePath(target, s.workspacePath);
|
|
13771
|
+
}
|
|
13772
|
+
function sessionToRecord(s, prompt2 = "") {
|
|
13773
|
+
return {
|
|
13774
|
+
postId: s.postId,
|
|
13775
|
+
taskId: s.taskId,
|
|
13776
|
+
mode: s.mode,
|
|
13777
|
+
phase: s.phase,
|
|
13778
|
+
provider: s.provider,
|
|
13779
|
+
worktreePath: s.workspacePath,
|
|
13780
|
+
agentSessionId: s.agentSessionId,
|
|
13781
|
+
taskTitle: s.taskTitle,
|
|
13782
|
+
targetBranch: s.targetBranch,
|
|
13783
|
+
startedAt: s.startedAt,
|
|
13784
|
+
prompt: prompt2,
|
|
13785
|
+
pendingQuestion: s.pendingQuestion
|
|
13786
|
+
};
|
|
13787
|
+
}
|
|
13788
|
+
async function putSessionCard(s, target, prompt2 = "") {
|
|
13789
|
+
try {
|
|
13790
|
+
await putCard(target, sessionToRecord(s, prompt2));
|
|
13791
|
+
} catch {
|
|
13792
|
+
}
|
|
13793
|
+
}
|
|
13794
|
+
async function teardownSession(s, target) {
|
|
13795
|
+
await killTerminalSessions(target, s.postId).catch(() => {
|
|
13796
|
+
});
|
|
13797
|
+
if (isWorktreeSession(s, target)) {
|
|
13798
|
+
const drop = s.mode === "bare" ? removeWorktreeAndBranch : removeWorktree;
|
|
13799
|
+
await drop(target, s.workspacePath).catch(() => {
|
|
13800
|
+
});
|
|
13801
|
+
}
|
|
13802
|
+
await deleteCard(target, s.postId).catch(() => {
|
|
13803
|
+
});
|
|
13804
|
+
}
|
|
13805
|
+
|
|
13544
13806
|
// ../../shared/all/helpers/board-agent-core/exec.ts
|
|
13807
|
+
var import_child_process2 = require("child_process");
|
|
13808
|
+
var import_crypto = require("crypto");
|
|
13809
|
+
var import_fs2 = require("fs");
|
|
13810
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
13811
|
+
var import_net = __toESM(require("net"));
|
|
13812
|
+
var import_path4 = __toESM(require("path"));
|
|
13813
|
+
var import_util4 = require("util");
|
|
13545
13814
|
var execFileAsync = (0, import_util4.promisify)(import_child_process2.execFile);
|
|
13546
13815
|
var MAX_BUFFER = 10 * 1024 * 1024;
|
|
13547
13816
|
var PORTS_FILE = ".ports";
|
|
13548
13817
|
var NUM_PORTS = 6;
|
|
13549
13818
|
function portsFilePath(spaceDir) {
|
|
13550
|
-
return
|
|
13819
|
+
return import_path4.default.join(spaceDir, PORTS_FILE);
|
|
13551
13820
|
}
|
|
13552
13821
|
async function allocateSpacePorts(spaceDir) {
|
|
13553
13822
|
const existing = readSpacePorts(spaceDir);
|
|
@@ -13573,19 +13842,35 @@ async function allocateSpacePorts(spaceDir) {
|
|
|
13573
13842
|
seen.add(port);
|
|
13574
13843
|
ports.push(port);
|
|
13575
13844
|
}
|
|
13576
|
-
await
|
|
13577
|
-
await
|
|
13845
|
+
await import_promises2.default.mkdir(spaceDir, { recursive: true });
|
|
13846
|
+
await import_promises2.default.writeFile(portsFilePath(spaceDir), ports.join(","), "utf-8");
|
|
13578
13847
|
return ports;
|
|
13579
13848
|
}
|
|
13849
|
+
var SECRET_FILE = ".secret";
|
|
13850
|
+
function spaceSecret(spaceDir) {
|
|
13851
|
+
const file = import_path4.default.join(spaceDir, SECRET_FILE);
|
|
13852
|
+
try {
|
|
13853
|
+
const existing = (0, import_fs2.readFileSync)(file, "utf-8").trim();
|
|
13854
|
+
if (existing) return existing;
|
|
13855
|
+
} catch {
|
|
13856
|
+
}
|
|
13857
|
+
const secret = (0, import_crypto.randomBytes)(24).toString("hex");
|
|
13858
|
+
try {
|
|
13859
|
+
(0, import_fs2.mkdirSync)(spaceDir, { recursive: true });
|
|
13860
|
+
(0, import_fs2.writeFileSync)(file, secret, { encoding: "utf-8", mode: 384 });
|
|
13861
|
+
} catch {
|
|
13862
|
+
}
|
|
13863
|
+
return secret;
|
|
13864
|
+
}
|
|
13580
13865
|
function readSpacePorts(spaceDir) {
|
|
13581
13866
|
try {
|
|
13582
|
-
return (0,
|
|
13867
|
+
return (0, import_fs2.readFileSync)(portsFilePath(spaceDir), "utf-8").split(",").map((p) => Number(p.trim())).filter((p) => Number.isInteger(p) && p > 0);
|
|
13583
13868
|
} catch {
|
|
13584
13869
|
return [];
|
|
13585
13870
|
}
|
|
13586
13871
|
}
|
|
13587
13872
|
function usableCwd(dir) {
|
|
13588
|
-
return dir && (0,
|
|
13873
|
+
return dir && (0, import_fs2.existsSync)(dir) ? dir : void 0;
|
|
13589
13874
|
}
|
|
13590
13875
|
function hostTarget(spaceDir, extraEnv) {
|
|
13591
13876
|
const paths = hostSpacePaths(spaceDir);
|
|
@@ -13651,8 +13936,8 @@ function hostTarget(spaceDir, extraEnv) {
|
|
|
13651
13936
|
});
|
|
13652
13937
|
},
|
|
13653
13938
|
async writeFile(filePath, content) {
|
|
13654
|
-
await
|
|
13655
|
-
await
|
|
13939
|
+
await import_promises2.default.mkdir(import_path4.default.dirname(filePath), { recursive: true });
|
|
13940
|
+
await import_promises2.default.writeFile(filePath, content, "utf-8");
|
|
13656
13941
|
},
|
|
13657
13942
|
async step(cmd, opts) {
|
|
13658
13943
|
const res = await execLogin(cmd, {
|
|
@@ -13867,14 +14152,14 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13867
14152
|
}
|
|
13868
14153
|
|
|
13869
14154
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
13870
|
-
var
|
|
13871
|
-
var
|
|
13872
|
-
var
|
|
14155
|
+
var import_fs3 = require("fs");
|
|
14156
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
14157
|
+
var import_path8 = __toESM(require("path"));
|
|
13873
14158
|
|
|
13874
14159
|
// ../../shared/all/helpers/board-agent-core/host-skills.ts
|
|
13875
|
-
var
|
|
14160
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
13876
14161
|
var import_os2 = __toESM(require("os"));
|
|
13877
|
-
var
|
|
14162
|
+
var import_path7 = __toESM(require("path"));
|
|
13878
14163
|
|
|
13879
14164
|
// ../../shared/all/domains/posts.ts
|
|
13880
14165
|
var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
|
|
@@ -14384,17 +14669,17 @@ var skillScopeSchema = external_exports.enum(["global", "repo"]);
|
|
|
14384
14669
|
|
|
14385
14670
|
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14386
14671
|
var import_child_process3 = require("child_process");
|
|
14387
|
-
var
|
|
14388
|
-
var
|
|
14672
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
14673
|
+
var import_path6 = __toESM(require("path"));
|
|
14389
14674
|
var import_util5 = require("util");
|
|
14390
14675
|
|
|
14391
14676
|
// ../../shared/all/helpers/board-agent-core/secrets-bridge.ts
|
|
14392
|
-
var
|
|
14677
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
14393
14678
|
var import_net2 = __toESM(require("net"));
|
|
14394
|
-
var
|
|
14679
|
+
var import_path5 = __toESM(require("path"));
|
|
14395
14680
|
var SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14396
14681
|
function secretsSocketPath(spaceDir) {
|
|
14397
|
-
return
|
|
14682
|
+
return import_path5.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
|
|
14398
14683
|
}
|
|
14399
14684
|
function redactor(values) {
|
|
14400
14685
|
const significant = values.filter((v) => v.length >= 4);
|
|
@@ -14448,8 +14733,8 @@ async function handleRun(request, handlers, reply) {
|
|
|
14448
14733
|
}
|
|
14449
14734
|
async function startSecretsBridge(opts) {
|
|
14450
14735
|
const socketPath = secretsSocketPath(opts.spaceDir);
|
|
14451
|
-
await
|
|
14452
|
-
await
|
|
14736
|
+
await import_promises3.default.mkdir(import_path5.default.dirname(socketPath), { recursive: true });
|
|
14737
|
+
await import_promises3.default.rm(socketPath, { force: true });
|
|
14453
14738
|
const { handlers } = opts;
|
|
14454
14739
|
let queue = Promise.resolve();
|
|
14455
14740
|
let queueDepth = 0;
|
|
@@ -14514,13 +14799,13 @@ async function startSecretsBridge(opts) {
|
|
|
14514
14799
|
resolve2();
|
|
14515
14800
|
});
|
|
14516
14801
|
});
|
|
14517
|
-
await
|
|
14802
|
+
await import_promises3.default.chmod(socketPath, 384).catch(() => void 0);
|
|
14518
14803
|
return {
|
|
14519
14804
|
close: async () => {
|
|
14520
14805
|
for (const conn of live) conn.destroy();
|
|
14521
14806
|
live.clear();
|
|
14522
14807
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
14523
|
-
await
|
|
14808
|
+
await import_promises3.default.rm(socketPath, { force: true });
|
|
14524
14809
|
}
|
|
14525
14810
|
};
|
|
14526
14811
|
}
|
|
@@ -14537,14 +14822,14 @@ var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
|
|
|
14537
14822
|
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
14538
14823
|
var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
|
|
14539
14824
|
async function ensureClaudeSkillLink(workspace) {
|
|
14540
|
-
const linkPath =
|
|
14825
|
+
const linkPath = import_path6.default.join(workspace, CLAUDE_LINK);
|
|
14541
14826
|
try {
|
|
14542
|
-
await
|
|
14827
|
+
await import_promises4.default.lstat(linkPath);
|
|
14543
14828
|
return;
|
|
14544
14829
|
} catch {
|
|
14545
14830
|
}
|
|
14546
|
-
await
|
|
14547
|
-
await
|
|
14831
|
+
await import_promises4.default.mkdir(import_path6.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
|
|
14832
|
+
await import_promises4.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
|
|
14548
14833
|
}
|
|
14549
14834
|
var GIT_NO_HELPER = ["-c", "credential.helper="];
|
|
14550
14835
|
var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -14589,7 +14874,7 @@ async function gitCatFileBatch(cwd, shas) {
|
|
|
14589
14874
|
return out;
|
|
14590
14875
|
}
|
|
14591
14876
|
async function ensureWorkspaceClone(opts) {
|
|
14592
|
-
const workspace =
|
|
14877
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14593
14878
|
const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
14594
14879
|
const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
|
|
14595
14880
|
let cloned = false;
|
|
@@ -14597,14 +14882,14 @@ async function ensureWorkspaceClone(opts) {
|
|
|
14597
14882
|
await hostGit(workspace, "rev-parse", "--git-dir");
|
|
14598
14883
|
await hostGit(workspace, "remote", "set-url", "origin", url2);
|
|
14599
14884
|
} catch {
|
|
14600
|
-
const leftovers = await
|
|
14885
|
+
const leftovers = await import_promises4.default.readdir(workspace).catch(() => []);
|
|
14601
14886
|
if (leftovers.includes(".git")) {
|
|
14602
14887
|
throw new Error(
|
|
14603
14888
|
`${workspace} holds a git repository that cannot be read. Remove it and retry.`
|
|
14604
14889
|
);
|
|
14605
14890
|
}
|
|
14606
14891
|
for (const name of leftovers) {
|
|
14607
|
-
await
|
|
14892
|
+
await import_promises4.default.rm(import_path6.default.join(workspace, name), { recursive: true, force: true });
|
|
14608
14893
|
}
|
|
14609
14894
|
await execFileAsync2(
|
|
14610
14895
|
"git",
|
|
@@ -14756,15 +15041,15 @@ function spaceEnv(spaceDir) {
|
|
|
14756
15041
|
};
|
|
14757
15042
|
}
|
|
14758
15043
|
async function readContract(workspace) {
|
|
14759
|
-
const skill = await
|
|
15044
|
+
const skill = await import_promises4.default.readFile(import_path6.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
|
|
14760
15045
|
throw new Error("The creator wrote no SKILL.md.");
|
|
14761
15046
|
});
|
|
14762
|
-
const checker = await
|
|
15047
|
+
const checker = await import_promises4.default.readFile(import_path6.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
|
|
14763
15048
|
throw new Error("The creator wrote no check-environment.sh.");
|
|
14764
15049
|
});
|
|
14765
15050
|
try {
|
|
14766
15051
|
const raw = JSON.parse(
|
|
14767
|
-
await
|
|
15052
|
+
await import_promises4.default.readFile(import_path6.default.join(workspace, VARIABLES_PATH), "utf-8")
|
|
14768
15053
|
);
|
|
14769
15054
|
return {
|
|
14770
15055
|
skill,
|
|
@@ -14781,7 +15066,7 @@ async function generateDeploySkill(opts) {
|
|
|
14781
15066
|
throw new Error("Describe your deployment process first.");
|
|
14782
15067
|
}
|
|
14783
15068
|
const status = await checkDeployStatus(opts);
|
|
14784
|
-
const workspace =
|
|
15069
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14785
15070
|
sendLog({
|
|
14786
15071
|
type: "system",
|
|
14787
15072
|
content: `Resetting workspace to origin/${status.branch}...`
|
|
@@ -14824,7 +15109,7 @@ async function generateDeploySkill(opts) {
|
|
|
14824
15109
|
async function reviseDeploySkill(opts) {
|
|
14825
15110
|
const { feedback, sendLog } = opts;
|
|
14826
15111
|
if (!feedback.trim()) throw new Error("Corrections are empty.");
|
|
14827
|
-
const workspace =
|
|
15112
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14828
15113
|
sendLog({ type: "system", content: "Applying your corrections..." });
|
|
14829
15114
|
const { result } = await spawnAgent(
|
|
14830
15115
|
opts.provider,
|
|
@@ -14852,7 +15137,7 @@ async function loadSkillDraft(opts) {
|
|
|
14852
15137
|
throw new Error("Not a deploy-skill branch.");
|
|
14853
15138
|
}
|
|
14854
15139
|
await checkDeployStatus(opts);
|
|
14855
|
-
const workspace =
|
|
15140
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14856
15141
|
await hostGit(
|
|
14857
15142
|
workspace,
|
|
14858
15143
|
"fetch",
|
|
@@ -14864,7 +15149,7 @@ async function loadSkillDraft(opts) {
|
|
|
14864
15149
|
return readContract(workspace);
|
|
14865
15150
|
}
|
|
14866
15151
|
async function approveDeploySkill(opts) {
|
|
14867
|
-
const workspace =
|
|
15152
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14868
15153
|
await readContract(workspace);
|
|
14869
15154
|
await ensureClaudeSkillLink(workspace);
|
|
14870
15155
|
if (opts.gitName)
|
|
@@ -14907,19 +15192,19 @@ async function runEnvironmentCheck(opts) {
|
|
|
14907
15192
|
"No environment checker on the default branch. Generate the deploy skill first."
|
|
14908
15193
|
);
|
|
14909
15194
|
}
|
|
14910
|
-
const workspace =
|
|
15195
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14911
15196
|
const script = await hostGit(
|
|
14912
15197
|
workspace,
|
|
14913
15198
|
"cat-file",
|
|
14914
15199
|
"-p",
|
|
14915
15200
|
`origin/${status.branch}:${CHECKER_PATH}`
|
|
14916
15201
|
);
|
|
14917
|
-
const tmp =
|
|
14918
|
-
await
|
|
15202
|
+
const tmp = import_path6.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
|
|
15203
|
+
await import_promises4.default.writeFile(tmp, script, { mode: 448 });
|
|
14919
15204
|
let ok = true;
|
|
14920
15205
|
let output = "";
|
|
14921
15206
|
try {
|
|
14922
|
-
const scriptArg = tmp.split(
|
|
15207
|
+
const scriptArg = tmp.split(import_path6.default.sep).join("/");
|
|
14923
15208
|
const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
|
|
14924
15209
|
cwd: workspace,
|
|
14925
15210
|
env: { ...process.env, ...spaceEnv(opts.spaceDir) },
|
|
@@ -14928,7 +15213,7 @@ async function runEnvironmentCheck(opts) {
|
|
|
14928
15213
|
output = res.output;
|
|
14929
15214
|
if (res.code !== 0) ok = false;
|
|
14930
15215
|
} finally {
|
|
14931
|
-
await
|
|
15216
|
+
await import_promises4.default.rm(tmp, { force: true });
|
|
14932
15217
|
}
|
|
14933
15218
|
const checks = [];
|
|
14934
15219
|
for (const line of output.split("\n")) {
|
|
@@ -14950,7 +15235,7 @@ async function fixEnvironment(opts) {
|
|
|
14950
15235
|
const before = await runEnvironmentCheck(opts);
|
|
14951
15236
|
if (before.ok) return before;
|
|
14952
15237
|
const status = await checkDeployStatus(opts);
|
|
14953
|
-
const workspace =
|
|
15238
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14954
15239
|
const script = await hostGit(
|
|
14955
15240
|
workspace,
|
|
14956
15241
|
"cat-file",
|
|
@@ -14985,7 +15270,7 @@ async function fixEnvironment(opts) {
|
|
|
14985
15270
|
return runEnvironmentCheck(opts);
|
|
14986
15271
|
}
|
|
14987
15272
|
async function discardDeploySkill(opts) {
|
|
14988
|
-
const workspace =
|
|
15273
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14989
15274
|
await hostGit(workspace, "checkout", "--", ".").catch(() => null);
|
|
14990
15275
|
await hostGit(workspace, "clean", "-fd");
|
|
14991
15276
|
return { success: true };
|
|
@@ -15104,21 +15389,22 @@ var AGENTS_ROOT = ".agents/skills";
|
|
|
15104
15389
|
var CLAUDE_ROOT = ".claude/skills";
|
|
15105
15390
|
var CLI_ROOTS = [CLAUDE_ROOT, ".codex/skills"];
|
|
15106
15391
|
function globalAgentsRoot() {
|
|
15107
|
-
return
|
|
15392
|
+
return import_path7.default.join(import_os2.default.homedir(), ".agents", "skills");
|
|
15108
15393
|
}
|
|
15109
15394
|
var AGENTS_LINK_TARGET = "../.agents/skills";
|
|
15110
15395
|
var BRANCH_PREFIX = "skill/";
|
|
15396
|
+
var MIGRATE_BRANCH = `${BRANCH_PREFIX}migrate`;
|
|
15111
15397
|
var SYMLINK_MODE = "120000";
|
|
15112
15398
|
function summarize(raw, base) {
|
|
15113
15399
|
const { frontmatter, errors } = parseSkill(raw);
|
|
15114
15400
|
return { ...base, description: frontmatter.description, errors };
|
|
15115
15401
|
}
|
|
15116
15402
|
async function rootLinkState(root) {
|
|
15117
|
-
const linkPath =
|
|
15118
|
-
const stat = await
|
|
15403
|
+
const linkPath = import_path7.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15404
|
+
const stat = await import_promises5.default.lstat(linkPath).catch(() => null);
|
|
15119
15405
|
if (!stat) return "missing";
|
|
15120
15406
|
if (!stat.isSymbolicLink()) return "blocked";
|
|
15121
|
-
const target = await
|
|
15407
|
+
const target = await import_promises5.default.readlink(linkPath).catch(() => "");
|
|
15122
15408
|
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
15123
15409
|
}
|
|
15124
15410
|
async function globalLinkState() {
|
|
@@ -15128,12 +15414,12 @@ async function globalLinkState() {
|
|
|
15128
15414
|
}
|
|
15129
15415
|
async function listGlobal() {
|
|
15130
15416
|
const root = globalAgentsRoot();
|
|
15131
|
-
const entries = await
|
|
15417
|
+
const entries = await import_promises5.default.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
15132
15418
|
const out = [];
|
|
15133
15419
|
for (const entry of entries) {
|
|
15134
15420
|
if (entry.isFile()) continue;
|
|
15135
|
-
const dir =
|
|
15136
|
-
const raw = await
|
|
15421
|
+
const dir = import_path7.default.join(root, entry.name);
|
|
15422
|
+
const raw = await import_promises5.default.readFile(import_path7.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
|
|
15137
15423
|
if (!raw) continue;
|
|
15138
15424
|
out.push(
|
|
15139
15425
|
summarize(raw, {
|
|
@@ -15302,7 +15588,7 @@ async function ensureSkillsWorktree(spaceDir, branch) {
|
|
|
15302
15588
|
} catch {
|
|
15303
15589
|
await hostGit(repo, "worktree", "prune").catch(() => {
|
|
15304
15590
|
});
|
|
15305
|
-
await
|
|
15591
|
+
await import_promises5.default.rm(worktree, { recursive: true, force: true });
|
|
15306
15592
|
await hostGit(
|
|
15307
15593
|
repo,
|
|
15308
15594
|
"worktree",
|
|
@@ -15367,29 +15653,34 @@ async function listSkillsImpl(opts) {
|
|
|
15367
15653
|
globalLink,
|
|
15368
15654
|
repoLink: "linked",
|
|
15369
15655
|
repoBranch: "",
|
|
15370
|
-
repoAvailable: false
|
|
15656
|
+
repoAvailable: false,
|
|
15657
|
+
repoMigrationBranch: "",
|
|
15658
|
+
repoMigrationMissing: []
|
|
15371
15659
|
};
|
|
15372
15660
|
}
|
|
15373
15661
|
const ctx = await repoContext(opts);
|
|
15374
15662
|
const repo = await repoSkills(ctx);
|
|
15663
|
+
const migration = openMigration(ctx.liveBlobs, ctx.pending);
|
|
15375
15664
|
return {
|
|
15376
15665
|
global: global2,
|
|
15377
15666
|
repo,
|
|
15378
15667
|
globalLink,
|
|
15379
15668
|
repoLink: repoLinkState(ctx),
|
|
15380
15669
|
repoBranch: ctx.branch,
|
|
15381
|
-
repoAvailable: true
|
|
15670
|
+
repoAvailable: true,
|
|
15671
|
+
repoMigrationBranch: migration.branch,
|
|
15672
|
+
repoMigrationMissing: migration.missing
|
|
15382
15673
|
};
|
|
15383
15674
|
}
|
|
15384
15675
|
async function bundleFilesOnDisk(dir) {
|
|
15385
15676
|
const out = [];
|
|
15386
15677
|
const walk = async (current, prefix, depth) => {
|
|
15387
15678
|
if (depth > 3 || out.length >= 200) return;
|
|
15388
|
-
const entries = await
|
|
15679
|
+
const entries = await import_promises5.default.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
15389
15680
|
for (const entry of entries) {
|
|
15390
15681
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15391
15682
|
if (entry.isDirectory()) {
|
|
15392
|
-
await walk(
|
|
15683
|
+
await walk(import_path7.default.join(current, entry.name), rel, depth + 1);
|
|
15393
15684
|
} else if (rel !== "SKILL.md") {
|
|
15394
15685
|
out.push(rel);
|
|
15395
15686
|
}
|
|
@@ -15401,7 +15692,7 @@ async function bundleFilesOnDisk(dir) {
|
|
|
15401
15692
|
async function readSkillImpl(opts) {
|
|
15402
15693
|
if (opts.scope === "global") {
|
|
15403
15694
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15404
|
-
const content2 = await
|
|
15695
|
+
const content2 = await import_promises5.default.readFile(import_path7.default.join(dir, "SKILL.md"), "utf-8");
|
|
15405
15696
|
const { frontmatter: frontmatter2, errors: errors2 } = parseSkill(content2);
|
|
15406
15697
|
return {
|
|
15407
15698
|
name: opts.name,
|
|
@@ -15436,71 +15727,102 @@ async function readSkillImpl(opts) {
|
|
|
15436
15727
|
};
|
|
15437
15728
|
}
|
|
15438
15729
|
async function moveIntoAgents(from, to) {
|
|
15439
|
-
if (await
|
|
15730
|
+
if (await import_promises5.default.access(to).then(
|
|
15440
15731
|
() => true,
|
|
15441
15732
|
() => false
|
|
15442
15733
|
)) {
|
|
15443
15734
|
throw new Error(
|
|
15444
|
-
`${
|
|
15735
|
+
`${import_path7.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
|
|
15445
15736
|
);
|
|
15446
15737
|
}
|
|
15447
|
-
await
|
|
15738
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(to), { recursive: true });
|
|
15448
15739
|
try {
|
|
15449
|
-
await
|
|
15740
|
+
await import_promises5.default.rename(from, to);
|
|
15450
15741
|
} catch {
|
|
15451
|
-
await
|
|
15452
|
-
await
|
|
15742
|
+
await import_promises5.default.cp(from, to, { recursive: true });
|
|
15743
|
+
await import_promises5.default.rm(from, { recursive: true, force: true });
|
|
15453
15744
|
}
|
|
15454
15745
|
}
|
|
15455
15746
|
async function migrateGlobal() {
|
|
15456
15747
|
const moved = [];
|
|
15457
15748
|
for (const root of CLI_ROOTS) {
|
|
15458
|
-
const dir =
|
|
15459
|
-
const stat = await
|
|
15749
|
+
const dir = import_path7.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15750
|
+
const stat = await import_promises5.default.lstat(dir).catch(() => null);
|
|
15460
15751
|
if (!stat || stat.isSymbolicLink()) continue;
|
|
15461
|
-
const entries = await
|
|
15752
|
+
const entries = await import_promises5.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15462
15753
|
for (const entry of entries) {
|
|
15463
15754
|
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
15464
|
-
const src =
|
|
15465
|
-
if (!await
|
|
15755
|
+
const src = import_path7.default.join(dir, entry.name);
|
|
15756
|
+
if (!await import_promises5.default.access(import_path7.default.join(src, "SKILL.md")).then(
|
|
15466
15757
|
() => true,
|
|
15467
15758
|
() => false
|
|
15468
15759
|
)) {
|
|
15469
15760
|
continue;
|
|
15470
15761
|
}
|
|
15471
|
-
await moveIntoAgents(src,
|
|
15762
|
+
await moveIntoAgents(src, import_path7.default.join(globalAgentsRoot(), entry.name));
|
|
15472
15763
|
moved.push(entry.name);
|
|
15473
15764
|
}
|
|
15474
|
-
await
|
|
15765
|
+
await import_promises5.default.rmdir(dir).catch(() => {
|
|
15475
15766
|
});
|
|
15476
15767
|
}
|
|
15477
15768
|
for (const root of CLI_ROOTS) {
|
|
15478
|
-
const linkPath =
|
|
15479
|
-
const after = await
|
|
15480
|
-
if (after?.isSymbolicLink()) await
|
|
15769
|
+
const linkPath = import_path7.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15770
|
+
const after = await import_promises5.default.lstat(linkPath).catch(() => null);
|
|
15771
|
+
if (after?.isSymbolicLink()) await import_promises5.default.rm(linkPath, { force: true });
|
|
15481
15772
|
else if (after) {
|
|
15482
15773
|
throw new Error(
|
|
15483
15774
|
`~/${root} still has files that are not skills. Empty it, then migrate.`
|
|
15484
15775
|
);
|
|
15485
15776
|
}
|
|
15486
|
-
await
|
|
15487
|
-
await
|
|
15777
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(linkPath), { recursive: true });
|
|
15778
|
+
await import_promises5.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15488
15779
|
}
|
|
15489
15780
|
return moved;
|
|
15490
15781
|
}
|
|
15782
|
+
var MIGRATE_SUBJECT = /^Migrate \d+ skill\(s\) to /;
|
|
15783
|
+
function migrationCommitsOnly(log) {
|
|
15784
|
+
return log.split("\n").map((line) => line.trim()).filter(Boolean).every((subject) => MIGRATE_SUBJECT.test(subject));
|
|
15785
|
+
}
|
|
15786
|
+
function openMigration(liveBlobs, pending) {
|
|
15787
|
+
const onBranch = pending.find(
|
|
15788
|
+
(entry) => entry.branch === MIGRATE_BRANCH
|
|
15789
|
+
)?.blobs;
|
|
15790
|
+
if (!onBranch) return { branch: "", missing: [] };
|
|
15791
|
+
const missing = /* @__PURE__ */ new Set();
|
|
15792
|
+
for (const stray of strayRepoSkills(liveBlobs)) {
|
|
15793
|
+
const name = stray.slice(stray.lastIndexOf("/") + 1);
|
|
15794
|
+
if (!onBranch.has(`${AGENTS_ROOT}/${name}`)) missing.add(name);
|
|
15795
|
+
}
|
|
15796
|
+
return { branch: MIGRATE_BRANCH, missing: [...missing].sort() };
|
|
15797
|
+
}
|
|
15491
15798
|
async function migrateSkillsImpl(opts) {
|
|
15492
15799
|
if (opts.scope === "global") {
|
|
15493
|
-
return { branch: "", moved: await migrateGlobal() };
|
|
15800
|
+
return { branch: "", moved: await migrateGlobal(), pushed: false };
|
|
15494
15801
|
}
|
|
15495
15802
|
const ctx = await repoContext(opts);
|
|
15496
15803
|
const strays = strayRepoSkills(ctx.liveBlobs);
|
|
15497
15804
|
const linked = ctx.links.get(CLAUDE_ROOT);
|
|
15498
15805
|
if (!strays.length && linked === AGENTS_LINK_TARGET) {
|
|
15499
|
-
return { branch: "", moved: [] };
|
|
15806
|
+
return { branch: "", moved: [], pushed: false };
|
|
15500
15807
|
}
|
|
15501
15808
|
const { branch } = ctx;
|
|
15502
15809
|
const workspace = await ensureSkillsWorktree(opts.spaceDir, branch);
|
|
15503
|
-
const target =
|
|
15810
|
+
const target = MIGRATE_BRANCH;
|
|
15811
|
+
const existing = await hostGit(
|
|
15812
|
+
workspace,
|
|
15813
|
+
"ls-remote",
|
|
15814
|
+
"--heads",
|
|
15815
|
+
"origin",
|
|
15816
|
+
target
|
|
15817
|
+
).catch(() => "");
|
|
15818
|
+
if (existing.trim()) {
|
|
15819
|
+
await hostGit(
|
|
15820
|
+
workspace,
|
|
15821
|
+
"fetch",
|
|
15822
|
+
"origin",
|
|
15823
|
+
`+refs/heads/${target}:refs/remotes/origin/${target}`
|
|
15824
|
+
);
|
|
15825
|
+
}
|
|
15504
15826
|
await hostGit(workspace, "checkout", "-B", target, `origin/${branch}`);
|
|
15505
15827
|
await hostGit(workspace, "clean", "-fd");
|
|
15506
15828
|
if (opts.gitName) {
|
|
@@ -15518,24 +15840,46 @@ async function migrateSkillsImpl(opts) {
|
|
|
15518
15840
|
`${name} exists in both ${AGENTS_ROOT} and ${stray}. Remove one copy, then migrate.`
|
|
15519
15841
|
);
|
|
15520
15842
|
}
|
|
15521
|
-
await
|
|
15843
|
+
await import_promises5.default.mkdir(import_path7.default.join(workspace, AGENTS_ROOT), { recursive: true });
|
|
15522
15844
|
await hostGit(workspace, "mv", stray, dest);
|
|
15523
15845
|
moved.push(name);
|
|
15524
15846
|
}
|
|
15525
|
-
const linkPath =
|
|
15526
|
-
await
|
|
15527
|
-
await
|
|
15847
|
+
const linkPath = import_path7.default.join(workspace, CLAUDE_ROOT);
|
|
15848
|
+
await import_promises5.default.rm(linkPath, { recursive: true, force: true });
|
|
15849
|
+
await import_promises5.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15528
15850
|
await hostGit(workspace, "add", "-A");
|
|
15529
15851
|
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
15530
|
-
if (!dirty.trim()) return { branch: "", moved: [] };
|
|
15852
|
+
if (!dirty.trim()) return { branch: "", moved: [], pushed: false };
|
|
15531
15853
|
await hostGit(
|
|
15532
15854
|
workspace,
|
|
15533
15855
|
"commit",
|
|
15534
15856
|
"-m",
|
|
15535
15857
|
`Migrate ${moved.length} skill(s) to ${AGENTS_ROOT} and link ${CLAUDE_ROOT}`
|
|
15536
15858
|
);
|
|
15537
|
-
|
|
15538
|
-
|
|
15859
|
+
if (!existing.trim()) {
|
|
15860
|
+
await hostGit(workspace, "push", "origin", target);
|
|
15861
|
+
return { branch: target, moved, pushed: true };
|
|
15862
|
+
}
|
|
15863
|
+
const [rebuilt, onBranch] = await Promise.all([
|
|
15864
|
+
hostGit(workspace, "rev-parse", "HEAD^{tree}"),
|
|
15865
|
+
hostGit(workspace, "rev-parse", `origin/${target}^{tree}`)
|
|
15866
|
+
]);
|
|
15867
|
+
if (rebuilt.trim() === onBranch.trim()) {
|
|
15868
|
+
return { branch: target, moved, pushed: false };
|
|
15869
|
+
}
|
|
15870
|
+
const log = await hostGit(
|
|
15871
|
+
workspace,
|
|
15872
|
+
"log",
|
|
15873
|
+
"--format=%s",
|
|
15874
|
+
`origin/${branch}..origin/${target}`
|
|
15875
|
+
);
|
|
15876
|
+
if (!migrationCommitsOnly(log)) {
|
|
15877
|
+
throw new Error(
|
|
15878
|
+
`${target} carries commits that are not this migration. Merge or delete that branch, then migrate.`
|
|
15879
|
+
);
|
|
15880
|
+
}
|
|
15881
|
+
await hostGit(workspace, "push", "--force-with-lease", "origin", target);
|
|
15882
|
+
return { branch: target, moved, pushed: true };
|
|
15539
15883
|
}
|
|
15540
15884
|
function assertWritable(name, content) {
|
|
15541
15885
|
const parsed = parseSkill(content);
|
|
@@ -15550,16 +15894,16 @@ function assertWritable(name, content) {
|
|
|
15550
15894
|
}
|
|
15551
15895
|
async function writeSkillFile(opts) {
|
|
15552
15896
|
const dir = resolveSkillDir(opts.root, opts.name);
|
|
15553
|
-
const file =
|
|
15897
|
+
const file = import_path7.default.join(dir, "SKILL.md");
|
|
15554
15898
|
if (opts.keepExisting) {
|
|
15555
|
-
const exists = await
|
|
15899
|
+
const exists = await import_promises5.default.access(file).then(
|
|
15556
15900
|
() => true,
|
|
15557
15901
|
() => false
|
|
15558
15902
|
);
|
|
15559
15903
|
if (exists) return;
|
|
15560
15904
|
}
|
|
15561
|
-
await
|
|
15562
|
-
await
|
|
15905
|
+
await import_promises5.default.mkdir(dir, { recursive: true });
|
|
15906
|
+
await import_promises5.default.writeFile(file, opts.content, "utf-8");
|
|
15563
15907
|
}
|
|
15564
15908
|
async function writeGlobalSkill(opts) {
|
|
15565
15909
|
assertWritable(opts.name, opts.content);
|
|
@@ -15574,14 +15918,14 @@ async function installBundledSkill(opts) {
|
|
|
15574
15918
|
content: opts.content,
|
|
15575
15919
|
keepExisting: true
|
|
15576
15920
|
});
|
|
15577
|
-
await write(
|
|
15921
|
+
await write(import_path7.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15578
15922
|
for (const root of CLI_ROOTS) {
|
|
15579
|
-
const linkPath =
|
|
15580
|
-
const stat = await
|
|
15923
|
+
const linkPath = import_path7.default.join(home, ...root.split("/"));
|
|
15924
|
+
const stat = await import_promises5.default.lstat(linkPath).catch(() => null);
|
|
15581
15925
|
if (stat?.isSymbolicLink()) continue;
|
|
15582
15926
|
if (!stat) {
|
|
15583
|
-
await
|
|
15584
|
-
await
|
|
15927
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(linkPath), { recursive: true });
|
|
15928
|
+
await import_promises5.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15585
15929
|
continue;
|
|
15586
15930
|
}
|
|
15587
15931
|
await write(linkPath);
|
|
@@ -15589,7 +15933,7 @@ async function installBundledSkill(opts) {
|
|
|
15589
15933
|
}
|
|
15590
15934
|
async function deleteGlobalSkill(opts) {
|
|
15591
15935
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15592
|
-
await
|
|
15936
|
+
await import_promises5.default.rm(dir, { recursive: true, force: true });
|
|
15593
15937
|
}
|
|
15594
15938
|
async function proposeRepoSkillImpl(opts) {
|
|
15595
15939
|
assertWritable(opts.name, opts.content);
|
|
@@ -15627,9 +15971,9 @@ async function proposeRepoSkillImpl(opts) {
|
|
|
15627
15971
|
if (opts.gitEmail) {
|
|
15628
15972
|
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
15629
15973
|
}
|
|
15630
|
-
await
|
|
15631
|
-
await
|
|
15632
|
-
|
|
15974
|
+
await import_promises5.default.mkdir(import_path7.default.join(workspace, dir), { recursive: true });
|
|
15975
|
+
await import_promises5.default.writeFile(
|
|
15976
|
+
import_path7.default.join(workspace, dir, "SKILL.md"),
|
|
15633
15977
|
opts.content,
|
|
15634
15978
|
"utf-8"
|
|
15635
15979
|
);
|
|
@@ -15736,18 +16080,18 @@ function vendoredBinaryName() {
|
|
|
15736
16080
|
async function installFrpcBinary(target) {
|
|
15737
16081
|
const p = frpcPaths(target);
|
|
15738
16082
|
const binaryName = vendoredBinaryName();
|
|
15739
|
-
const src =
|
|
15740
|
-
if (!(0,
|
|
16083
|
+
const src = import_path8.default.join(__dirname, "..", "vendor", binaryName);
|
|
16084
|
+
if (!(0, import_fs3.existsSync)(src)) {
|
|
15741
16085
|
throw new Error(
|
|
15742
16086
|
`Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
|
|
15743
16087
|
);
|
|
15744
16088
|
}
|
|
15745
|
-
const staged = await
|
|
15746
|
-
const source = await
|
|
16089
|
+
const staged = await import_promises6.default.stat(p.binary).catch(() => null);
|
|
16090
|
+
const source = await import_promises6.default.stat(src);
|
|
15747
16091
|
if (staged?.size === source.size) return;
|
|
15748
|
-
await
|
|
15749
|
-
await
|
|
15750
|
-
await
|
|
16092
|
+
await import_promises6.default.mkdir(p.binDir, { recursive: true });
|
|
16093
|
+
await import_promises6.default.copyFile(src, p.binary);
|
|
16094
|
+
await import_promises6.default.chmod(p.binary, 493);
|
|
15751
16095
|
}
|
|
15752
16096
|
var EXPOSE_SKILL_NAME = "share-a-port";
|
|
15753
16097
|
var FENCE = "```";
|
|
@@ -16137,18 +16481,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16137
16481
|
await this.core.ensureSpaceWorkspace();
|
|
16138
16482
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16139
16483
|
if (hasGit) {
|
|
16140
|
-
const { path:
|
|
16484
|
+
const { path: path23 } = await addWorktree(
|
|
16141
16485
|
this.core.target(),
|
|
16142
16486
|
session.taskId,
|
|
16143
16487
|
baseBranch || config.mainBranch || "main",
|
|
16144
16488
|
"card"
|
|
16145
16489
|
);
|
|
16146
|
-
session.workspacePath =
|
|
16490
|
+
session.workspacePath = path23;
|
|
16147
16491
|
await setGitIdentity(
|
|
16148
16492
|
this.core.target(),
|
|
16149
16493
|
config.gitName,
|
|
16150
16494
|
config.gitEmail,
|
|
16151
|
-
|
|
16495
|
+
path23
|
|
16152
16496
|
);
|
|
16153
16497
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
16154
16498
|
} else {
|
|
@@ -16370,18 +16714,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16370
16714
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16371
16715
|
await this.core.ensureSpaceWorkspace();
|
|
16372
16716
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16373
|
-
const { path:
|
|
16717
|
+
const { path: path23 } = await addWorktree(
|
|
16374
16718
|
this.core.target(),
|
|
16375
16719
|
session.taskId,
|
|
16376
16720
|
config.mainBranch || "main",
|
|
16377
16721
|
"card"
|
|
16378
16722
|
);
|
|
16379
|
-
session.workspacePath =
|
|
16723
|
+
session.workspacePath = path23;
|
|
16380
16724
|
await setGitIdentity(
|
|
16381
16725
|
this.core.target(),
|
|
16382
16726
|
config.gitName,
|
|
16383
16727
|
config.gitEmail,
|
|
16384
|
-
|
|
16728
|
+
path23
|
|
16385
16729
|
);
|
|
16386
16730
|
await this.putCard(session);
|
|
16387
16731
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16576,7 +16920,7 @@ ${buildPrompt(prompts_default.implement)}`;
|
|
|
16576
16920
|
};
|
|
16577
16921
|
|
|
16578
16922
|
// ../../shared/all/helpers/terminal-ai/terminal-mode.ts
|
|
16579
|
-
var
|
|
16923
|
+
var import_crypto4 = require("crypto");
|
|
16580
16924
|
|
|
16581
16925
|
// ../../shared/all/helpers/board-agent-core/boardTerminal.ts
|
|
16582
16926
|
function boardTerminalKey(spaceSlug) {
|
|
@@ -16587,95 +16931,6 @@ function isCardPostId(id) {
|
|
|
16587
16931
|
return UUID_RE.test(id);
|
|
16588
16932
|
}
|
|
16589
16933
|
|
|
16590
|
-
// ../../shared/all/helpers/board-agent-core/tmux.ts
|
|
16591
|
-
var TMUX_CONF = [
|
|
16592
|
-
// Wheel scrolls, drag selects in copy mode.
|
|
16593
|
-
"set -g mouse on",
|
|
16594
|
-
"set -g history-limit 50000",
|
|
16595
|
-
// These three are copy-on-select: tmux emits OSC 52, the client clips it.
|
|
16596
|
-
"set -g set-clipboard on",
|
|
16597
|
-
"set -ga terminal-features ,*:clipboard",
|
|
16598
|
-
"bind -T copy-mode MouseDragEnd1Pane send -X copy-selection-and-cancel",
|
|
16599
|
-
"bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-selection-and-cancel",
|
|
16600
|
-
""
|
|
16601
|
-
].join("\n");
|
|
16602
|
-
var DEFAULT_SIZE = { cols: 80, rows: 24 };
|
|
16603
|
-
var SOCKET = "factiii";
|
|
16604
|
-
function safeTmuxName(raw) {
|
|
16605
|
-
return raw.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
|
|
16606
|
-
}
|
|
16607
|
-
function tmuxConfPath(target) {
|
|
16608
|
-
return `${target.paths.root}/tmux.conf`;
|
|
16609
|
-
}
|
|
16610
|
-
function tmuxCli(target) {
|
|
16611
|
-
return `tmux -L ${SOCKET} -f ${tmuxConfPath(target)}`;
|
|
16612
|
-
}
|
|
16613
|
-
function shellQuote(argv) {
|
|
16614
|
-
return argv.map((arg) => `'${arg.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
16615
|
-
}
|
|
16616
|
-
function ptyArgv(argv, size) {
|
|
16617
|
-
const sized = [
|
|
16618
|
-
"sh",
|
|
16619
|
-
"-c",
|
|
16620
|
-
`stty rows ${size.rows} cols ${size.cols} 2>/dev/null; exec ${shellQuote(argv)}`
|
|
16621
|
-
];
|
|
16622
|
-
const script = process.platform === "darwin" ? ["script", "-q", "/dev/null", ...sized] : ["script", "-qec", shellQuote(sized), "/dev/null"];
|
|
16623
|
-
return ["bash", "-c", 'exec "$@" < <(cat)', "bash", ...script];
|
|
16624
|
-
}
|
|
16625
|
-
async function startTmuxSession(opts) {
|
|
16626
|
-
const { target, session } = opts;
|
|
16627
|
-
const conf = tmuxConfPath(target);
|
|
16628
|
-
await target.writeFile(conf, TMUX_CONF);
|
|
16629
|
-
let size = opts.size ?? DEFAULT_SIZE;
|
|
16630
|
-
const dataCbs = [];
|
|
16631
|
-
const closeCbs = [];
|
|
16632
|
-
const errorCbs = [];
|
|
16633
|
-
let proc;
|
|
16634
|
-
const attach = async () => {
|
|
16635
|
-
const argv = ["tmux", "-L", SOCKET, "-u", "-f", conf, "new-session"];
|
|
16636
|
-
if (opts.attachExisting) argv.push("-A");
|
|
16637
|
-
argv.push("-s", session, "-x", String(size.cols), "-y", String(size.rows));
|
|
16638
|
-
for (const [k, v] of Object.entries(opts.env ?? {})) {
|
|
16639
|
-
argv.push("-e", `${k}=${v}`);
|
|
16640
|
-
}
|
|
16641
|
-
argv.push("--", ...opts.argv);
|
|
16642
|
-
const mine = await target.spawn(ptyArgv(argv, size), {
|
|
16643
|
-
env: { TERM: "xterm-256color" }
|
|
16644
|
-
});
|
|
16645
|
-
proc = mine;
|
|
16646
|
-
mine.stdin?.on("error", () => {
|
|
16647
|
-
});
|
|
16648
|
-
const live = () => proc === mine;
|
|
16649
|
-
const emit = (chunk) => {
|
|
16650
|
-
if (live()) for (const cb of dataCbs) cb(chunk);
|
|
16651
|
-
};
|
|
16652
|
-
mine.stdout?.on("data", emit);
|
|
16653
|
-
mine.stderr?.on("data", emit);
|
|
16654
|
-
mine.on("error", (err) => {
|
|
16655
|
-
if (live()) for (const cb of errorCbs) cb(err);
|
|
16656
|
-
});
|
|
16657
|
-
mine.on("close", (code) => {
|
|
16658
|
-
if (live()) for (const cb of closeCbs) cb(code ?? null);
|
|
16659
|
-
});
|
|
16660
|
-
};
|
|
16661
|
-
await attach();
|
|
16662
|
-
return {
|
|
16663
|
-
session,
|
|
16664
|
-
onData: (cb) => dataCbs.push(cb),
|
|
16665
|
-
onClose: (cb) => closeCbs.push(cb),
|
|
16666
|
-
onError: (cb) => errorCbs.push(cb),
|
|
16667
|
-
write: (data) => proc.stdin?.write(data),
|
|
16668
|
-
resize: async (cols, rows) => {
|
|
16669
|
-
if (cols === size.cols && rows === size.rows) return;
|
|
16670
|
-
size = { cols, rows };
|
|
16671
|
-
const old = proc;
|
|
16672
|
-
await attach();
|
|
16673
|
-
killTree(old);
|
|
16674
|
-
},
|
|
16675
|
-
detach: () => killTree(proc)
|
|
16676
|
-
};
|
|
16677
|
-
}
|
|
16678
|
-
|
|
16679
16934
|
// ../../shared/all/helpers/terminal-ai/agentState.ts
|
|
16680
16935
|
var STATES = /* @__PURE__ */ new Set(["working", "idle", "blocked"]);
|
|
16681
16936
|
var AGENT_STATE_CHANNEL = "agent-state";
|
|
@@ -16720,6 +16975,189 @@ function drainStateMessages(buffer) {
|
|
|
16720
16975
|
return { states, rest: [...lines.slice(consumed), partial].join("\n") };
|
|
16721
16976
|
}
|
|
16722
16977
|
|
|
16978
|
+
// ../../shared/all/helpers/terminal-ai/digest.ts
|
|
16979
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
16980
|
+
var import_promises8 = require("fs/promises");
|
|
16981
|
+
var import_path10 = __toESM(require("path"));
|
|
16982
|
+
|
|
16983
|
+
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
16984
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
16985
|
+
var import_fs4 = require("fs");
|
|
16986
|
+
var import_promises7 = require("fs/promises");
|
|
16987
|
+
var import_os3 = __toESM(require("os"));
|
|
16988
|
+
var import_path9 = __toESM(require("path"));
|
|
16989
|
+
var MARKETPLACE_NAME = "factiii-runner";
|
|
16990
|
+
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
16991
|
+
var SECRETS_BIN = "factiii-secrets";
|
|
16992
|
+
function candidates() {
|
|
16993
|
+
return [
|
|
16994
|
+
import_path9.default.join(__dirname, "..", "plugin"),
|
|
16995
|
+
import_path9.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
16996
|
+
import_path9.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
16997
|
+
];
|
|
16998
|
+
}
|
|
16999
|
+
function pluginRoot() {
|
|
17000
|
+
for (const dir of candidates()) {
|
|
17001
|
+
if ((0, import_fs4.existsSync)(import_path9.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
17002
|
+
return dir;
|
|
17003
|
+
}
|
|
17004
|
+
}
|
|
17005
|
+
return null;
|
|
17006
|
+
}
|
|
17007
|
+
function renderedPluginRoot() {
|
|
17008
|
+
return import_path9.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
17009
|
+
}
|
|
17010
|
+
async function runnerVersion(source) {
|
|
17011
|
+
const pkg = import_path9.default.join(source, "..", "package.json");
|
|
17012
|
+
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
17013
|
+
if (!raw) return FALLBACK_VERSION;
|
|
17014
|
+
try {
|
|
17015
|
+
const { version } = JSON.parse(raw);
|
|
17016
|
+
return version || FALLBACK_VERSION;
|
|
17017
|
+
} catch {
|
|
17018
|
+
return FALLBACK_VERSION;
|
|
17019
|
+
}
|
|
17020
|
+
}
|
|
17021
|
+
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
17022
|
+
async function treeFiles(dir) {
|
|
17023
|
+
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
17024
|
+
const files = [];
|
|
17025
|
+
for (const entry of entries) {
|
|
17026
|
+
const full = import_path9.default.join(dir, entry.name);
|
|
17027
|
+
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
17028
|
+
else if (entry.isFile()) files.push(full);
|
|
17029
|
+
}
|
|
17030
|
+
return files.sort();
|
|
17031
|
+
}
|
|
17032
|
+
async function fingerprint(source, url2, version = "") {
|
|
17033
|
+
const hash = import_crypto2.default.createHash("sha256").update(url2).update(version);
|
|
17034
|
+
for (const file of await treeFiles(source)) {
|
|
17035
|
+
hash.update(import_path9.default.relative(source, file));
|
|
17036
|
+
hash.update(await (0, import_promises7.readFile)(file));
|
|
17037
|
+
}
|
|
17038
|
+
return hash.digest("hex");
|
|
17039
|
+
}
|
|
17040
|
+
function stampPath(dest) {
|
|
17041
|
+
return `${dest}.stamp`;
|
|
17042
|
+
}
|
|
17043
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
17044
|
+
function renderPlugin(boardMcpUrl) {
|
|
17045
|
+
const running = inFlight.get(boardMcpUrl);
|
|
17046
|
+
if (running) return running;
|
|
17047
|
+
const started = renderPluginImpl(boardMcpUrl).finally(
|
|
17048
|
+
() => inFlight.delete(boardMcpUrl)
|
|
17049
|
+
);
|
|
17050
|
+
inFlight.set(boardMcpUrl, started);
|
|
17051
|
+
return started;
|
|
17052
|
+
}
|
|
17053
|
+
async function renderPluginImpl(boardMcpUrl) {
|
|
17054
|
+
const source = pluginRoot();
|
|
17055
|
+
if (!source) return null;
|
|
17056
|
+
const dest = renderedPluginRoot();
|
|
17057
|
+
const version = await runnerVersion(source);
|
|
17058
|
+
const want = await fingerprint(source, boardMcpUrl, version);
|
|
17059
|
+
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
17060
|
+
if (have === want) return dest;
|
|
17061
|
+
await (0, import_promises7.mkdir)(import_path9.default.dirname(dest), { recursive: true });
|
|
17062
|
+
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
17063
|
+
await (0, import_promises7.writeFile)(
|
|
17064
|
+
import_path9.default.join(dest, "factiii", ".mcp.json"),
|
|
17065
|
+
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
17066
|
+
`
|
|
17067
|
+
);
|
|
17068
|
+
await stampVersion(dest, version);
|
|
17069
|
+
await (0, import_promises7.writeFile)(stampPath(dest), want);
|
|
17070
|
+
return dest;
|
|
17071
|
+
}
|
|
17072
|
+
async function stampVersion(dest, version) {
|
|
17073
|
+
const manifest = import_path9.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
17074
|
+
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
17075
|
+
if (!raw) return;
|
|
17076
|
+
const parsed = JSON.parse(raw);
|
|
17077
|
+
await (0, import_promises7.writeFile)(
|
|
17078
|
+
manifest,
|
|
17079
|
+
`${JSON.stringify({ ...parsed, version }, null, 2)}
|
|
17080
|
+
`
|
|
17081
|
+
);
|
|
17082
|
+
}
|
|
17083
|
+
function boardMcpConfig(url2) {
|
|
17084
|
+
return {
|
|
17085
|
+
mcpServers: {
|
|
17086
|
+
"factiii-board": {
|
|
17087
|
+
type: "http",
|
|
17088
|
+
url: url2,
|
|
17089
|
+
bearer_token_env_var: BOARD_MCP_TOKEN_ENV,
|
|
17090
|
+
// Written literally, for claude to expand at connect time. Not a
|
|
17091
|
+
// template string: the `${…}` has to survive into the file.
|
|
17092
|
+
headers: { Authorization: "Bearer ${" + BOARD_MCP_TOKEN_ENV + "}" }
|
|
17093
|
+
}
|
|
17094
|
+
}
|
|
17095
|
+
};
|
|
17096
|
+
}
|
|
17097
|
+
|
|
17098
|
+
// ../../shared/all/helpers/terminal-ai/initScript.ts
|
|
17099
|
+
function terminalPrelude(cwd, binDir) {
|
|
17100
|
+
return [
|
|
17101
|
+
// cd in-script, falling back to $HOME. Must match the agent's worktree:
|
|
17102
|
+
// the CLIs key transcripts by cwd, so --resume needs it.
|
|
17103
|
+
`cd ${cwd} 2>/dev/null || cd "$HOME" 2>/dev/null || cd /`,
|
|
17104
|
+
// Prepended here rather than via the session env, so the login shell's
|
|
17105
|
+
// own PATH survives.
|
|
17106
|
+
`export PATH="${binDir}:$PATH"`,
|
|
17107
|
+
`export PS1='\\[\\e[1;32m\\]user@bare\\[\\e[0m\\]:\\w\\$ '`
|
|
17108
|
+
].join("; ");
|
|
17109
|
+
}
|
|
17110
|
+
var SAFE_SESSION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
17111
|
+
function cardBriefPrompt(postId) {
|
|
17112
|
+
return `You are working on a Factiii board card. Its postId is ${postId}. Pass that postId to the factiii-board MCP: get_card to read the card, then card_comments for its discussion. Then start working on it.`;
|
|
17113
|
+
}
|
|
17114
|
+
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
17115
|
+
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.";
|
|
17116
|
+
function terminalInitScript(cwd, binDir, resumeAgentSessionId, provider, cardPostId) {
|
|
17117
|
+
const prelude = terminalPrelude(cwd, binDir);
|
|
17118
|
+
const shell = `exec bash --norc -i`;
|
|
17119
|
+
if (!resumeAgentSessionId || !SAFE_SESSION_ID.test(resumeAgentSessionId)) {
|
|
17120
|
+
if (!cardPostId) return `${prelude}; ${shell}`;
|
|
17121
|
+
const prompt2 = cardBriefPrompt(cardPostId);
|
|
17122
|
+
const start = provider === "codex" ? `codex '${prompt2}'` : `claude ${CLAUDE_PERMISSION_MODE} '${prompt2}'`;
|
|
17123
|
+
return `${prelude}; ${start}; ${shell}`;
|
|
17124
|
+
}
|
|
17125
|
+
const resume = provider === "codex" ? `codex resume ${resumeAgentSessionId} '${RESUME_SEED}'` : `claude ${CLAUDE_PERMISSION_MODE} --resume ${resumeAgentSessionId} '${RESUME_SEED}'`;
|
|
17126
|
+
return `${prelude}; ${resume}; ${shell}`;
|
|
17127
|
+
}
|
|
17128
|
+
|
|
17129
|
+
// ../../shared/all/helpers/terminal-ai/digest.ts
|
|
17130
|
+
var TERMINAL_FP_OPTION = "@factiii-fp";
|
|
17131
|
+
var CWD_PLACEHOLDER = "/__cwd__";
|
|
17132
|
+
var VOLATILE_ENV_KEYS = /* @__PURE__ */ new Set(["FACTIII_PORTS"]);
|
|
17133
|
+
var PLUGIN_CACHE_MS = 3e4;
|
|
17134
|
+
var pluginCache = null;
|
|
17135
|
+
async function pluginContribution() {
|
|
17136
|
+
const now = Date.now();
|
|
17137
|
+
if (pluginCache && now - pluginCache.at < PLUGIN_CACHE_MS) {
|
|
17138
|
+
return pluginCache.value;
|
|
17139
|
+
}
|
|
17140
|
+
const source = pluginRoot();
|
|
17141
|
+
const shipped = source ? await fingerprint(source, "", "").catch(() => "") : "";
|
|
17142
|
+
const mcp = await (0, import_promises8.readFile)(
|
|
17143
|
+
import_path10.default.join(renderedPluginRoot(), "factiii", ".mcp.json"),
|
|
17144
|
+
"utf-8"
|
|
17145
|
+
).catch(() => "");
|
|
17146
|
+
const value2 = `${shipped}|${mcp}`;
|
|
17147
|
+
pluginCache = { at: now, value: value2 };
|
|
17148
|
+
return value2;
|
|
17149
|
+
}
|
|
17150
|
+
async function terminalDigest(target) {
|
|
17151
|
+
const hash = import_crypto3.default.createHash("sha256").update(TMUX_CONF).update(terminalPrelude(CWD_PLACEHOLDER, target.paths.bin));
|
|
17152
|
+
const env = target.stateEnv();
|
|
17153
|
+
for (const key of Object.keys(env).sort()) {
|
|
17154
|
+
if (VOLATILE_ENV_KEYS.has(key)) continue;
|
|
17155
|
+
hash.update(`${key}=${env[key]}`);
|
|
17156
|
+
}
|
|
17157
|
+
hash.update(await pluginContribution());
|
|
17158
|
+
return hash.digest("hex");
|
|
17159
|
+
}
|
|
17160
|
+
|
|
16723
17161
|
// ../../shared/all/helpers/terminal-ai/processes.ts
|
|
16724
17162
|
var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
|
|
16725
17163
|
var PS_FIELDS = 5;
|
|
@@ -16889,19 +17327,306 @@ function parseSsListen(raw) {
|
|
|
16889
17327
|
use.set(pid, found);
|
|
16890
17328
|
}
|
|
16891
17329
|
}
|
|
16892
|
-
return use;
|
|
16893
|
-
}
|
|
16894
|
-
async function socketsFor(target, pids) {
|
|
16895
|
-
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
16896
|
-
const lsof = await target.run(["lsof", "-a", "-p", pids.join(","), "-nP", "-iTCP", "-F", "pnT"]).catch((err) => String(err.stdout ?? ""));
|
|
16897
|
-
if (lsof.trim()) return parseLsofSockets(lsof);
|
|
16898
|
-
const ss = await target.run(["ss", "-ltnpH"]).catch(() => "");
|
|
16899
|
-
if (!ss.trim()) return /* @__PURE__ */ new Map();
|
|
16900
|
-
const all = parseSsListen(ss);
|
|
16901
|
-
const wanted = new Set(pids);
|
|
16902
|
-
for (const pid of all.keys()) if (!wanted.has(pid)) all.delete(pid);
|
|
16903
|
-
return all;
|
|
16904
|
-
}
|
|
17330
|
+
return use;
|
|
17331
|
+
}
|
|
17332
|
+
async function socketsFor(target, pids) {
|
|
17333
|
+
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
17334
|
+
const lsof = await target.run(["lsof", "-a", "-p", pids.join(","), "-nP", "-iTCP", "-F", "pnT"]).catch((err) => String(err.stdout ?? ""));
|
|
17335
|
+
if (lsof.trim()) return parseLsofSockets(lsof);
|
|
17336
|
+
const ss = await target.run(["ss", "-ltnpH"]).catch(() => "");
|
|
17337
|
+
if (!ss.trim()) return /* @__PURE__ */ new Map();
|
|
17338
|
+
const all = parseSsListen(ss);
|
|
17339
|
+
const wanted = new Set(pids);
|
|
17340
|
+
for (const pid of all.keys()) if (!wanted.has(pid)) all.delete(pid);
|
|
17341
|
+
return all;
|
|
17342
|
+
}
|
|
17343
|
+
|
|
17344
|
+
// ../../shared/all/helpers/terminal-ai/tabNames.ts
|
|
17345
|
+
var TAB_LIST_FORMAT = "#{session_name}|#{pane_current_command}|#{host}|#{@factiii-fp}|#{pane_dead}|#{pane_title}";
|
|
17346
|
+
var SHELL_COMMANDS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "fish"]);
|
|
17347
|
+
var tabName = (cmd, host, title) => {
|
|
17348
|
+
const command = cmd.replace(/\.exe$/, "") || "bash";
|
|
17349
|
+
if (!title || title === host || SHELL_COMMANDS.has(command)) return command;
|
|
17350
|
+
return title;
|
|
17351
|
+
};
|
|
17352
|
+
var parseTerminalTabs = (stdout, prefix) => {
|
|
17353
|
+
const byId = /* @__PURE__ */ new Map();
|
|
17354
|
+
for (const raw of stdout.split("\n")) {
|
|
17355
|
+
const line = raw.trim();
|
|
17356
|
+
if (!line.startsWith(prefix)) continue;
|
|
17357
|
+
const parts2 = line.slice(prefix.length).split("|");
|
|
17358
|
+
if (parts2.length < 6) continue;
|
|
17359
|
+
const id = parts2[0].trim();
|
|
17360
|
+
if (!id || byId.has(id)) continue;
|
|
17361
|
+
byId.set(id, {
|
|
17362
|
+
id,
|
|
17363
|
+
name: tabName(
|
|
17364
|
+
parts2[1].trim(),
|
|
17365
|
+
parts2[2].trim(),
|
|
17366
|
+
parts2.slice(5).join("|").trim()
|
|
17367
|
+
),
|
|
17368
|
+
fp: parts2[3].trim(),
|
|
17369
|
+
dead: parts2[4].trim() === "1"
|
|
17370
|
+
});
|
|
17371
|
+
}
|
|
17372
|
+
return Array.from(byId.values()).sort(
|
|
17373
|
+
(a, b) => (parseInt(a.id, 10) || 0) - (parseInt(b.id, 10) || 0)
|
|
17374
|
+
);
|
|
17375
|
+
};
|
|
17376
|
+
|
|
17377
|
+
// ../../shared/all/helpers/wireChunk.ts
|
|
17378
|
+
var MAX_INLINE_BYTES = 14e3;
|
|
17379
|
+
var chunkCounter = 0;
|
|
17380
|
+
function nextChunkId() {
|
|
17381
|
+
return `c-${++chunkCounter}-${Date.now()}`;
|
|
17382
|
+
}
|
|
17383
|
+
function chunkWire(wire) {
|
|
17384
|
+
if (wire.length <= MAX_INLINE_BYTES) return [wire];
|
|
17385
|
+
const id = nextChunkId();
|
|
17386
|
+
const total = Math.ceil(wire.length / MAX_INLINE_BYTES);
|
|
17387
|
+
const frames = [];
|
|
17388
|
+
for (let i = 0; i < total; i++) {
|
|
17389
|
+
const start = i * MAX_INLINE_BYTES;
|
|
17390
|
+
const slice = wire.slice(start, start + MAX_INLINE_BYTES);
|
|
17391
|
+
const frame = { __c: id, __i: i, __n: total, d: slice };
|
|
17392
|
+
frames.push(JSON.stringify(frame));
|
|
17393
|
+
}
|
|
17394
|
+
return frames;
|
|
17395
|
+
}
|
|
17396
|
+
function makeChunkReassembler() {
|
|
17397
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
17398
|
+
return {
|
|
17399
|
+
feed(raw) {
|
|
17400
|
+
if (!raw.startsWith('{"__c"')) return raw;
|
|
17401
|
+
let frame;
|
|
17402
|
+
try {
|
|
17403
|
+
frame = JSON.parse(raw);
|
|
17404
|
+
} catch {
|
|
17405
|
+
return raw;
|
|
17406
|
+
}
|
|
17407
|
+
if (typeof frame.__c !== "string" || typeof frame.__i !== "number" || typeof frame.__n !== "number" || typeof frame.d !== "string") {
|
|
17408
|
+
return raw;
|
|
17409
|
+
}
|
|
17410
|
+
let buf = buffers.get(frame.__c);
|
|
17411
|
+
if (!buf) {
|
|
17412
|
+
buf = { received: new Array(frame.__n), total: frame.__n };
|
|
17413
|
+
buffers.set(frame.__c, buf);
|
|
17414
|
+
}
|
|
17415
|
+
buf.received[frame.__i] = frame.d;
|
|
17416
|
+
for (let i = 0; i < buf.total; i++) {
|
|
17417
|
+
if (buf.received[i] === void 0) return null;
|
|
17418
|
+
}
|
|
17419
|
+
buffers.delete(frame.__c);
|
|
17420
|
+
return buf.received.join("");
|
|
17421
|
+
}
|
|
17422
|
+
};
|
|
17423
|
+
}
|
|
17424
|
+
|
|
17425
|
+
// ../../shared/all/helpers/terminal-ai/terminal.ts
|
|
17426
|
+
function tmuxName(terminalKey) {
|
|
17427
|
+
return `terminal-${safeTmuxName(terminalKey)}`;
|
|
17428
|
+
}
|
|
17429
|
+
async function restartTerminalSession(opts) {
|
|
17430
|
+
const { target, terminalKey, cwd } = opts;
|
|
17431
|
+
await respawnTmuxPane({
|
|
17432
|
+
target,
|
|
17433
|
+
session: tmuxName(terminalKey),
|
|
17434
|
+
argv: ["bash", "-c", terminalInitScript(cwd, target.paths.bin)],
|
|
17435
|
+
cwd,
|
|
17436
|
+
// Rebuilt rather than inherited: picking up an env the session was created
|
|
17437
|
+
// too early to see is half the reason to restart at all.
|
|
17438
|
+
env: target.stateEnv()
|
|
17439
|
+
}).catch(() => {
|
|
17440
|
+
});
|
|
17441
|
+
await terminalDigest(target).then(
|
|
17442
|
+
(digest) => setTmuxSessionOption(
|
|
17443
|
+
target,
|
|
17444
|
+
tmuxName(terminalKey),
|
|
17445
|
+
TERMINAL_FP_OPTION,
|
|
17446
|
+
digest
|
|
17447
|
+
)
|
|
17448
|
+
).catch(() => {
|
|
17449
|
+
});
|
|
17450
|
+
}
|
|
17451
|
+
var TerminalManager = class {
|
|
17452
|
+
constructor(send, getCardTarget, getCardWorkdir) {
|
|
17453
|
+
this.send = send;
|
|
17454
|
+
this.getCardTarget = getCardTarget;
|
|
17455
|
+
this.getCardWorkdir = getCardWorkdir;
|
|
17456
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
17457
|
+
}
|
|
17458
|
+
handle(msg) {
|
|
17459
|
+
switch (msg.op) {
|
|
17460
|
+
case "open":
|
|
17461
|
+
void this.open(msg);
|
|
17462
|
+
return;
|
|
17463
|
+
case "stdin":
|
|
17464
|
+
this.stdin(msg.sessionId, msg.data);
|
|
17465
|
+
return;
|
|
17466
|
+
case "resize":
|
|
17467
|
+
void this.resize(msg.sessionId, msg.cols, msg.rows);
|
|
17468
|
+
return;
|
|
17469
|
+
case "close":
|
|
17470
|
+
this.close(msg.sessionId);
|
|
17471
|
+
return;
|
|
17472
|
+
case "kill":
|
|
17473
|
+
void this.killTmuxSession(msg.postId, msg.terminalKey);
|
|
17474
|
+
return;
|
|
17475
|
+
case "restart":
|
|
17476
|
+
void this.restart(msg.postId, msg.terminalKey);
|
|
17477
|
+
return;
|
|
17478
|
+
}
|
|
17479
|
+
}
|
|
17480
|
+
async open(msg) {
|
|
17481
|
+
if (this.sessions.has(msg.sessionId)) {
|
|
17482
|
+
this.emitError(msg.sessionId, "Session already open.");
|
|
17483
|
+
return;
|
|
17484
|
+
}
|
|
17485
|
+
const target = this.getCardTarget(msg.postId);
|
|
17486
|
+
if (!target) {
|
|
17487
|
+
this.emitError(
|
|
17488
|
+
msg.sessionId,
|
|
17489
|
+
`No active card session for postId=${msg.postId}.`
|
|
17490
|
+
);
|
|
17491
|
+
return;
|
|
17492
|
+
}
|
|
17493
|
+
const tmuxSession = tmuxName(msg.terminalKey);
|
|
17494
|
+
const cardPostId = msg.briefCard && !msg.resumeAgentSessionId && isCardPostId(msg.postId) ? msg.postId : void 0;
|
|
17495
|
+
const init = this.buildInit(target, msg.postId, {
|
|
17496
|
+
resumeAgentSessionId: msg.resumeAgentSessionId,
|
|
17497
|
+
provider: msg.resumeProvider,
|
|
17498
|
+
cardPostId
|
|
17499
|
+
});
|
|
17500
|
+
const creating = !await tmuxSessionExists(target, tmuxSession);
|
|
17501
|
+
try {
|
|
17502
|
+
const session = await startTmuxSession({
|
|
17503
|
+
target,
|
|
17504
|
+
session: tmuxSession,
|
|
17505
|
+
// Attach if it exists, else create. This is what makes the shell
|
|
17506
|
+
// survive modal close/reopen: the server holds it, we just view.
|
|
17507
|
+
attachExisting: true,
|
|
17508
|
+
size: { cols: msg.cols, rows: msg.rows },
|
|
17509
|
+
// Without these, `claude` in the shell reads the default ~/.claude and
|
|
17510
|
+
// asks to sign in, while the agent spawns (which pass agentEnv) don't.
|
|
17511
|
+
env: target.stateEnv(),
|
|
17512
|
+
argv: init.argv
|
|
17513
|
+
});
|
|
17514
|
+
this.sessions.set(msg.sessionId, session);
|
|
17515
|
+
if (creating) {
|
|
17516
|
+
void resurrectSave(target);
|
|
17517
|
+
void terminalDigest(target).then(
|
|
17518
|
+
(digest) => setTmuxSessionOption(
|
|
17519
|
+
target,
|
|
17520
|
+
tmuxSession,
|
|
17521
|
+
TERMINAL_FP_OPTION,
|
|
17522
|
+
digest
|
|
17523
|
+
)
|
|
17524
|
+
).catch(() => {
|
|
17525
|
+
});
|
|
17526
|
+
}
|
|
17527
|
+
session.onData(
|
|
17528
|
+
(data) => this.emit({
|
|
17529
|
+
type: "terminal",
|
|
17530
|
+
op: "stdout",
|
|
17531
|
+
sessionId: msg.sessionId,
|
|
17532
|
+
data: data.toString("base64")
|
|
17533
|
+
})
|
|
17534
|
+
);
|
|
17535
|
+
session.onError((err) => {
|
|
17536
|
+
this.cleanup(msg.sessionId);
|
|
17537
|
+
this.emitError(msg.sessionId, err.message);
|
|
17538
|
+
});
|
|
17539
|
+
session.onClose((code) => {
|
|
17540
|
+
if (!this.sessions.has(msg.sessionId)) return;
|
|
17541
|
+
this.cleanup(msg.sessionId);
|
|
17542
|
+
this.emit({
|
|
17543
|
+
type: "terminal",
|
|
17544
|
+
op: "exit",
|
|
17545
|
+
sessionId: msg.sessionId,
|
|
17546
|
+
code
|
|
17547
|
+
});
|
|
17548
|
+
});
|
|
17549
|
+
this.emit({ type: "terminal", op: "opened", sessionId: msg.sessionId });
|
|
17550
|
+
} catch (err) {
|
|
17551
|
+
this.emitError(
|
|
17552
|
+
msg.sessionId,
|
|
17553
|
+
err instanceof Error ? err.message : String(err)
|
|
17554
|
+
);
|
|
17555
|
+
}
|
|
17556
|
+
}
|
|
17557
|
+
/** What tmux runs for a tab, and where. One builder so `open` and `restart`
|
|
17558
|
+
* cannot drift. */
|
|
17559
|
+
buildInit(target, postId, agent) {
|
|
17560
|
+
const cwd = this.getCardWorkdir(postId) || target.paths.workspace;
|
|
17561
|
+
return {
|
|
17562
|
+
cwd,
|
|
17563
|
+
argv: [
|
|
17564
|
+
"bash",
|
|
17565
|
+
"-c",
|
|
17566
|
+
terminalInitScript(
|
|
17567
|
+
cwd,
|
|
17568
|
+
target.paths.bin,
|
|
17569
|
+
agent?.resumeAgentSessionId,
|
|
17570
|
+
agent?.provider,
|
|
17571
|
+
agent?.cardPostId
|
|
17572
|
+
)
|
|
17573
|
+
]
|
|
17574
|
+
};
|
|
17575
|
+
}
|
|
17576
|
+
/** Restart the tab's process while keeping the tab. */
|
|
17577
|
+
async restart(postId, terminalKey) {
|
|
17578
|
+
const target = this.getCardTarget(postId);
|
|
17579
|
+
if (!target) return;
|
|
17580
|
+
await restartTerminalSession({
|
|
17581
|
+
target,
|
|
17582
|
+
terminalKey,
|
|
17583
|
+
cwd: this.getCardWorkdir(postId) || target.paths.workspace
|
|
17584
|
+
});
|
|
17585
|
+
}
|
|
17586
|
+
stdin(sessionId, b64) {
|
|
17587
|
+
this.sessions.get(sessionId)?.write(Buffer.from(b64, "base64"));
|
|
17588
|
+
}
|
|
17589
|
+
async resize(sessionId, cols, rows) {
|
|
17590
|
+
const session = this.sessions.get(sessionId);
|
|
17591
|
+
if (!session) return;
|
|
17592
|
+
if (!Number.isInteger(cols) || !Number.isInteger(rows)) return;
|
|
17593
|
+
if (cols < 1 || rows < 1) return;
|
|
17594
|
+
await session.resize(cols, rows).catch(() => {
|
|
17595
|
+
});
|
|
17596
|
+
}
|
|
17597
|
+
close(sessionId) {
|
|
17598
|
+
const session = this.sessions.get(sessionId);
|
|
17599
|
+
if (!session) return;
|
|
17600
|
+
this.sessions.delete(sessionId);
|
|
17601
|
+
session.detach();
|
|
17602
|
+
}
|
|
17603
|
+
/** Tear down the tmux session for a tab. Used when the user explicitly
|
|
17604
|
+
* removes a tab (the X button), not on transient detach. */
|
|
17605
|
+
async killTmuxSession(postId, terminalKey) {
|
|
17606
|
+
const target = this.getCardTarget(postId);
|
|
17607
|
+
if (!target) return;
|
|
17608
|
+
await target.sh(
|
|
17609
|
+
`${tmuxCli(target)} kill-session -t ${tmuxName(terminalKey)} 2>/dev/null || true`
|
|
17610
|
+
).catch(() => {
|
|
17611
|
+
});
|
|
17612
|
+
await resurrectSave(target);
|
|
17613
|
+
}
|
|
17614
|
+
destroyAll() {
|
|
17615
|
+
for (const sessionId of [...this.sessions.keys()]) {
|
|
17616
|
+
this.close(sessionId);
|
|
17617
|
+
}
|
|
17618
|
+
}
|
|
17619
|
+
cleanup(sessionId) {
|
|
17620
|
+
this.sessions.delete(sessionId);
|
|
17621
|
+
}
|
|
17622
|
+
emitError(sessionId, message) {
|
|
17623
|
+
this.emit({ type: "terminal", op: "error", sessionId, message });
|
|
17624
|
+
}
|
|
17625
|
+
emit(msg) {
|
|
17626
|
+
const wire = JSON.stringify(msg);
|
|
17627
|
+
for (const frame of chunkWire(wire)) this.send(frame);
|
|
17628
|
+
}
|
|
17629
|
+
};
|
|
16905
17630
|
|
|
16906
17631
|
// ../../shared/all/helpers/terminal-ai/watcher.ts
|
|
16907
17632
|
var PRUNED_DIRS = [
|
|
@@ -16928,12 +17653,12 @@ var FLUSH_MS = 120;
|
|
|
16928
17653
|
var SELF_WRITE_MS = 2e3;
|
|
16929
17654
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16930
17655
|
var importFs = new Function("s", "return import(s)");
|
|
16931
|
-
async function resolveReal(
|
|
17656
|
+
async function resolveReal(path23) {
|
|
16932
17657
|
try {
|
|
16933
17658
|
const { realpath } = await importFs("fs/promises");
|
|
16934
|
-
return await realpath(
|
|
17659
|
+
return await realpath(path23);
|
|
16935
17660
|
} catch {
|
|
16936
|
-
return
|
|
17661
|
+
return path23;
|
|
16937
17662
|
}
|
|
16938
17663
|
}
|
|
16939
17664
|
var loadSubscribe = null;
|
|
@@ -17047,19 +17772,19 @@ var WorkspaceWatcher = class {
|
|
|
17047
17772
|
}
|
|
17048
17773
|
record(kind, relPath) {
|
|
17049
17774
|
if (this.closed) return;
|
|
17050
|
-
const
|
|
17051
|
-
const expiry = this.selfWrites.get(
|
|
17775
|
+
const path23 = "/" + relPath;
|
|
17776
|
+
const expiry = this.selfWrites.get(path23);
|
|
17052
17777
|
if (expiry !== void 0) {
|
|
17053
17778
|
if (expiry > Date.now()) return;
|
|
17054
|
-
this.selfWrites.delete(
|
|
17779
|
+
this.selfWrites.delete(path23);
|
|
17055
17780
|
}
|
|
17056
17781
|
const { added, changed, removed } = this.pending;
|
|
17057
|
-
added.delete(
|
|
17058
|
-
changed.delete(
|
|
17059
|
-
removed.delete(
|
|
17060
|
-
if (kind === "added") added.add(
|
|
17061
|
-
else if (kind === "changed") changed.add(
|
|
17062
|
-
else removed.add(
|
|
17782
|
+
added.delete(path23);
|
|
17783
|
+
changed.delete(path23);
|
|
17784
|
+
removed.delete(path23);
|
|
17785
|
+
if (kind === "added") added.add(path23);
|
|
17786
|
+
else if (kind === "changed") changed.add(path23);
|
|
17787
|
+
else removed.add(path23);
|
|
17063
17788
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
17064
17789
|
this.pending = { ...emptyPending(), resync: true };
|
|
17065
17790
|
}
|
|
@@ -17078,8 +17803,8 @@ var WorkspaceWatcher = class {
|
|
|
17078
17803
|
this.pending = emptyPending();
|
|
17079
17804
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
17080
17805
|
const now = Date.now();
|
|
17081
|
-
for (const [
|
|
17082
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
17806
|
+
for (const [path23, expiry] of this.selfWrites) {
|
|
17807
|
+
if (expiry <= now) this.selfWrites.delete(path23);
|
|
17083
17808
|
}
|
|
17084
17809
|
this.emit({
|
|
17085
17810
|
added: resync ? [] : [...added],
|
|
@@ -17283,8 +18008,8 @@ var TerminalModeEngine = class {
|
|
|
17283
18008
|
/** Suppress the change event our own write is about to produce, so a save
|
|
17284
18009
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
17285
18010
|
* the watcher emits, since callers spell the path either way. */
|
|
17286
|
-
expectWrite(postId,
|
|
17287
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
18011
|
+
expectWrite(postId, path23) {
|
|
18012
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path23));
|
|
17288
18013
|
}
|
|
17289
18014
|
// ── Lifecycle / ModeEngine ──
|
|
17290
18015
|
start(base, args) {
|
|
@@ -17344,7 +18069,7 @@ var TerminalModeEngine = class {
|
|
|
17344
18069
|
}
|
|
17345
18070
|
if (!session) return;
|
|
17346
18071
|
this.stopWatching(session.postId);
|
|
17347
|
-
await this.
|
|
18072
|
+
await teardownSession(session, this.core.target());
|
|
17348
18073
|
const removedPostId = session.postId;
|
|
17349
18074
|
this.reservedNames.delete(removedPostId);
|
|
17350
18075
|
this.sessions.delete(removedPostId);
|
|
@@ -17357,33 +18082,6 @@ var TerminalModeEngine = class {
|
|
|
17357
18082
|
this.reservedNames.clear();
|
|
17358
18083
|
this.sessions.clear();
|
|
17359
18084
|
}
|
|
17360
|
-
/** Every tmux session this post owns, by the `terminal-<postId>-<tab>` name
|
|
17361
|
-
* tmuxName() builds. */
|
|
17362
|
-
async terminalSessionNames(target, postId) {
|
|
17363
|
-
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
17364
|
-
const stdout = await target.sh(
|
|
17365
|
-
`${tmuxCli(target)} list-sessions -F '#{session_name}' 2>/dev/null || true`
|
|
17366
|
-
).catch(() => "");
|
|
17367
|
-
return stdout.split("\n").map((line) => line.trim()).filter((name) => name.startsWith(prefix));
|
|
17368
|
-
}
|
|
17369
|
-
/** Kill the post's terminals and everything running inside them. Killing the
|
|
17370
|
-
* tmux session alone leaves a process that reparented away from the shell,
|
|
17371
|
-
* so each pane's process tree is killed first. */
|
|
17372
|
-
async killTerminals(postId) {
|
|
17373
|
-
const target = this.core.target();
|
|
17374
|
-
const names = await this.terminalSessionNames(target, postId);
|
|
17375
|
-
for (const name of names) {
|
|
17376
|
-
await target.sh(
|
|
17377
|
-
`for p in $(${tmuxCli(target)} list-panes -t ${name} -F '#{pane_pid}' 2>/dev/null); do pkill -TERM -P "$p" 2>/dev/null || true; done; ${tmuxCli(target)} kill-session -t ${name} 2>/dev/null || true`
|
|
17378
|
-
).catch(() => {
|
|
17379
|
-
});
|
|
17380
|
-
}
|
|
17381
|
-
}
|
|
17382
|
-
async teardown(session) {
|
|
17383
|
-
await this.killTerminals(session.postId).catch(() => {
|
|
17384
|
-
});
|
|
17385
|
-
await teardownSession(session, this.core.target());
|
|
17386
|
-
}
|
|
17387
18085
|
putCard(s) {
|
|
17388
18086
|
return putSessionCard(s, this.core.target());
|
|
17389
18087
|
}
|
|
@@ -17481,18 +18179,18 @@ var TerminalModeEngine = class {
|
|
|
17481
18179
|
this.reservedNames.values()
|
|
17482
18180
|
);
|
|
17483
18181
|
this.reservedNames.set(session.postId, workName);
|
|
17484
|
-
const { path:
|
|
18182
|
+
const { path: path23 } = await addWorktree(
|
|
17485
18183
|
this.core.target(),
|
|
17486
18184
|
workName,
|
|
17487
18185
|
branch,
|
|
17488
18186
|
"terminal"
|
|
17489
18187
|
);
|
|
17490
|
-
session.workspacePath =
|
|
18188
|
+
session.workspacePath = path23;
|
|
17491
18189
|
await setGitIdentity(
|
|
17492
18190
|
this.core.target(),
|
|
17493
18191
|
config.gitName,
|
|
17494
18192
|
config.gitEmail,
|
|
17495
|
-
|
|
18193
|
+
path23
|
|
17496
18194
|
);
|
|
17497
18195
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17498
18196
|
} else {
|
|
@@ -17554,11 +18252,11 @@ var TerminalModeEngine = class {
|
|
|
17554
18252
|
}
|
|
17555
18253
|
async bareFsList({
|
|
17556
18254
|
postId,
|
|
17557
|
-
path:
|
|
18255
|
+
path: path23
|
|
17558
18256
|
}) {
|
|
17559
18257
|
const target = this.getBareTarget(postId);
|
|
17560
18258
|
const root = this.getBareRoot(postId);
|
|
17561
|
-
const abs = this.resolveBarePath(
|
|
18259
|
+
const abs = this.resolveBarePath(path23, root);
|
|
17562
18260
|
const relRoot = abs.slice(root.length) || "/";
|
|
17563
18261
|
const out = await target.run([
|
|
17564
18262
|
"sh",
|
|
@@ -17608,30 +18306,30 @@ var TerminalModeEngine = class {
|
|
|
17608
18306
|
}
|
|
17609
18307
|
async bareFsRead({
|
|
17610
18308
|
postId,
|
|
17611
|
-
path:
|
|
18309
|
+
path: path23
|
|
17612
18310
|
}) {
|
|
17613
18311
|
const target = this.getBareTarget(postId);
|
|
17614
|
-
const abs = this.resolveBarePath(
|
|
18312
|
+
const abs = this.resolveBarePath(path23, this.getBareRoot(postId));
|
|
17615
18313
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17616
18314
|
'`)).trim();
|
|
17617
18315
|
return { content: b64, encoding: "base64" };
|
|
17618
18316
|
}
|
|
17619
18317
|
async bareFsWrite({
|
|
17620
18318
|
postId,
|
|
17621
|
-
path:
|
|
18319
|
+
path: path23,
|
|
17622
18320
|
content,
|
|
17623
18321
|
encoding
|
|
17624
18322
|
}) {
|
|
17625
18323
|
const target = this.getBareTarget(postId);
|
|
17626
|
-
const abs = this.resolveBarePath(
|
|
18324
|
+
const abs = this.resolveBarePath(path23, this.getBareRoot(postId));
|
|
17627
18325
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17628
18326
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17629
18327
|
throw new Error("Invalid base64 payload.");
|
|
17630
18328
|
}
|
|
17631
|
-
this.expectWrite(String(postId),
|
|
18329
|
+
this.expectWrite(String(postId), path23);
|
|
17632
18330
|
await writeBase64File(target, abs, b64);
|
|
17633
18331
|
const session = this.sessions.get(String(postId));
|
|
17634
|
-
if (session) delete session.buffers[
|
|
18332
|
+
if (session) delete session.buffers[path23];
|
|
17635
18333
|
}
|
|
17636
18334
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17637
18335
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17649,7 +18347,7 @@ var TerminalModeEngine = class {
|
|
|
17649
18347
|
}
|
|
17650
18348
|
const safeExt = (ext || "png").toLowerCase().replace(/[^a-z0-9]/g, "") || "png";
|
|
17651
18349
|
const oneShot = !dropId;
|
|
17652
|
-
const safeId2 = oneShot ? (0,
|
|
18350
|
+
const safeId2 = oneShot ? (0, import_crypto4.randomUUID)() : dropId.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
17653
18351
|
if (!safeId2) throw new Error("Invalid drop id.");
|
|
17654
18352
|
const dropsDir = `${target.paths.root}/drops`;
|
|
17655
18353
|
const abs = `${dropsDir}/${safeId2}.${safeExt}`;
|
|
@@ -17681,39 +18379,39 @@ var TerminalModeEngine = class {
|
|
|
17681
18379
|
}
|
|
17682
18380
|
bareOpenFile({
|
|
17683
18381
|
postId: rawPostId,
|
|
17684
|
-
path:
|
|
18382
|
+
path: path23
|
|
17685
18383
|
}) {
|
|
17686
18384
|
const session = this.sessions.get(String(rawPostId));
|
|
17687
18385
|
if (!session || session.mode !== "bare") return;
|
|
17688
|
-
if (!session.openedFiles.includes(
|
|
17689
|
-
session.openedFiles.push(
|
|
18386
|
+
if (!session.openedFiles.includes(path23)) {
|
|
18387
|
+
session.openedFiles.push(path23);
|
|
17690
18388
|
}
|
|
17691
18389
|
}
|
|
17692
18390
|
bareCloseFile({
|
|
17693
18391
|
postId: rawPostId,
|
|
17694
|
-
path:
|
|
18392
|
+
path: path23
|
|
17695
18393
|
}) {
|
|
17696
18394
|
const session = this.sessions.get(String(rawPostId));
|
|
17697
18395
|
if (!session || session.mode !== "bare") return;
|
|
17698
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17699
|
-
delete session.buffers[
|
|
18396
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path23);
|
|
18397
|
+
delete session.buffers[path23];
|
|
17700
18398
|
}
|
|
17701
18399
|
bareBufferSet({
|
|
17702
18400
|
postId: rawPostId,
|
|
17703
|
-
path:
|
|
18401
|
+
path: path23,
|
|
17704
18402
|
content
|
|
17705
18403
|
}) {
|
|
17706
18404
|
const session = this.sessions.get(String(rawPostId));
|
|
17707
18405
|
if (!session || session.mode !== "bare") return;
|
|
17708
|
-
session.buffers[
|
|
18406
|
+
session.buffers[path23] = content;
|
|
17709
18407
|
}
|
|
17710
18408
|
async bareFsDelete({
|
|
17711
18409
|
postId,
|
|
17712
|
-
path:
|
|
18410
|
+
path: path23
|
|
17713
18411
|
}) {
|
|
17714
18412
|
const target = this.getBareTarget(postId);
|
|
17715
18413
|
const root = this.getBareRoot(postId);
|
|
17716
|
-
const abs = this.resolveBarePath(
|
|
18414
|
+
const abs = this.resolveBarePath(path23, root);
|
|
17717
18415
|
if (abs === root) {
|
|
17718
18416
|
throw new Error("Refusing to delete the repo root.");
|
|
17719
18417
|
}
|
|
@@ -17721,10 +18419,10 @@ var TerminalModeEngine = class {
|
|
|
17721
18419
|
}
|
|
17722
18420
|
async bareFsMkdir({
|
|
17723
18421
|
postId,
|
|
17724
|
-
path:
|
|
18422
|
+
path: path23
|
|
17725
18423
|
}) {
|
|
17726
18424
|
const target = this.getBareTarget(postId);
|
|
17727
|
-
const abs = this.resolveBarePath(
|
|
18425
|
+
const abs = this.resolveBarePath(path23, this.getBareRoot(postId));
|
|
17728
18426
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17729
18427
|
}
|
|
17730
18428
|
async bareReview({
|
|
@@ -17847,26 +18545,68 @@ var TerminalModeEngine = class {
|
|
|
17847
18545
|
const prefix = `terminal-${sanitizedPostId}-`;
|
|
17848
18546
|
try {
|
|
17849
18547
|
const stdout = await target.sh(
|
|
17850
|
-
`${tmuxCli(target)} list-panes -a -F '
|
|
17851
|
-
);
|
|
17852
|
-
const byId = /* @__PURE__ */ new Map();
|
|
17853
|
-
for (const raw of stdout.split("\n")) {
|
|
17854
|
-
const line = raw.trim();
|
|
17855
|
-
if (!line.startsWith(prefix)) continue;
|
|
17856
|
-
const rest = line.slice(prefix.length);
|
|
17857
|
-
const sep = rest.indexOf("|");
|
|
17858
|
-
if (sep === -1) continue;
|
|
17859
|
-
const id = rest.slice(0, sep);
|
|
17860
|
-
const cmd = rest.slice(sep + 1).trim() || "bash";
|
|
17861
|
-
if (!byId.has(id)) byId.set(id, cmd);
|
|
17862
|
-
}
|
|
17863
|
-
return Array.from(byId, ([id, name]) => ({ id, name })).sort(
|
|
17864
|
-
(a, b) => (parseInt(a.id, 10) || 0) - (parseInt(b.id, 10) || 0)
|
|
18548
|
+
`${tmuxCli(target)} list-panes -a -F '${TAB_LIST_FORMAT}' 2>/dev/null || true`
|
|
17865
18549
|
);
|
|
18550
|
+
const rows = parseTerminalTabs(stdout, prefix);
|
|
18551
|
+
return rows.map(({ id, name, dead }) => ({ id, name, dead }));
|
|
17866
18552
|
} catch {
|
|
17867
18553
|
return [];
|
|
17868
18554
|
}
|
|
17869
18555
|
}
|
|
18556
|
+
/** Terminal tabs across several posts in one sweep, so the sessions view can
|
|
18557
|
+
* ask about a whole board without a round trip per card. */
|
|
18558
|
+
async staleTabsFor(target, postIds, current) {
|
|
18559
|
+
if (!current) return [];
|
|
18560
|
+
const stdout = await target.sh(
|
|
18561
|
+
`${tmuxCli(target)} list-panes -a -F '${TAB_LIST_FORMAT}' 2>/dev/null || true`
|
|
18562
|
+
).catch(() => "");
|
|
18563
|
+
const stale = [];
|
|
18564
|
+
for (const postId of postIds) {
|
|
18565
|
+
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
18566
|
+
for (const row of parseTerminalTabs(stdout, prefix)) {
|
|
18567
|
+
if (row.fp !== current) stale.push({ postId, tabId: row.id });
|
|
18568
|
+
}
|
|
18569
|
+
}
|
|
18570
|
+
return stale;
|
|
18571
|
+
}
|
|
18572
|
+
async bareTerminalHealth({
|
|
18573
|
+
postIds
|
|
18574
|
+
}) {
|
|
18575
|
+
const target = this.core.target();
|
|
18576
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
18577
|
+
const acked = await getTerminalAck(target);
|
|
18578
|
+
return {
|
|
18579
|
+
digest,
|
|
18580
|
+
acked: !!digest && acked === digest,
|
|
18581
|
+
staleTabs: await this.staleTabsFor(target, postIds, digest)
|
|
18582
|
+
};
|
|
18583
|
+
}
|
|
18584
|
+
async bareAckTerminalDigest() {
|
|
18585
|
+
const target = this.core.target();
|
|
18586
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
18587
|
+
if (!digest) return;
|
|
18588
|
+
await setTerminalAck(target, digest);
|
|
18589
|
+
}
|
|
18590
|
+
async bareRestartStaleTerminals({
|
|
18591
|
+
postIds
|
|
18592
|
+
}) {
|
|
18593
|
+
const target = this.core.target();
|
|
18594
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
18595
|
+
const stale = await this.staleTabsFor(target, postIds, digest);
|
|
18596
|
+
for (const { postId, tabId } of stale) {
|
|
18597
|
+
let cwd = target.paths.workspace;
|
|
18598
|
+
try {
|
|
18599
|
+
cwd = this.gitWorkdir(postId) || cwd;
|
|
18600
|
+
} catch {
|
|
18601
|
+
}
|
|
18602
|
+
await restartTerminalSession({
|
|
18603
|
+
target,
|
|
18604
|
+
terminalKey: `${postId}-${tabId}`,
|
|
18605
|
+
cwd
|
|
18606
|
+
});
|
|
18607
|
+
}
|
|
18608
|
+
return { restarted: stale.length };
|
|
18609
|
+
}
|
|
17870
18610
|
/** This card's panes: the pid its tree hangs off, and the pane id its
|
|
17871
18611
|
* processes carry in their environment. */
|
|
17872
18612
|
async panePids(target, postId) {
|
|
@@ -18406,8 +19146,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
18406
19146
|
};
|
|
18407
19147
|
}
|
|
18408
19148
|
}
|
|
18409
|
-
async function githubApi(token,
|
|
18410
|
-
const res = await fetch(`${API_BASE}${
|
|
19149
|
+
async function githubApi(token, path23) {
|
|
19150
|
+
const res = await fetch(`${API_BASE}${path23}`, {
|
|
18411
19151
|
headers: {
|
|
18412
19152
|
Authorization: `Bearer ${token}`,
|
|
18413
19153
|
Accept: "application/vnd.github+json",
|
|
@@ -18416,7 +19156,7 @@ async function githubApi(token, path21) {
|
|
|
18416
19156
|
});
|
|
18417
19157
|
if (!res.ok) {
|
|
18418
19158
|
throw new Error(
|
|
18419
|
-
`GitHub ${
|
|
19159
|
+
`GitHub ${path23} failed (${res.status}): ${await res.text()}`
|
|
18420
19160
|
);
|
|
18421
19161
|
}
|
|
18422
19162
|
return await res.json();
|
|
@@ -18461,10 +19201,10 @@ async function listGithubBranches(token, fullName) {
|
|
|
18461
19201
|
}
|
|
18462
19202
|
|
|
18463
19203
|
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
18464
|
-
var
|
|
19204
|
+
var import_path13 = __toESM(require("path"));
|
|
18465
19205
|
|
|
18466
19206
|
// ../../shared/all/helpers/board-agent-core/secrets-gate.ts
|
|
18467
|
-
var
|
|
19207
|
+
var import_crypto5 = __toESM(require("crypto"));
|
|
18468
19208
|
var DEPLOY_ORIGIN = "Deploy run";
|
|
18469
19209
|
var UNATTRIBUTED = "This runner did not start the session that asked, so the request was refused. `factiii-secrets` only works inside a session the runner spawned.";
|
|
18470
19210
|
var SecretsGate = class {
|
|
@@ -18489,7 +19229,14 @@ var SecretsGate = class {
|
|
|
18489
19229
|
/** Mint a token for one session, labelled with what the owner should see on
|
|
18490
19230
|
* the prompt - the label is most of the decision, so name the session. */
|
|
18491
19231
|
issueToken(origin) {
|
|
18492
|
-
const token =
|
|
19232
|
+
const token = import_crypto5.default.randomBytes(24).toString("hex");
|
|
19233
|
+
this.tokens.set(token, origin);
|
|
19234
|
+
return token;
|
|
19235
|
+
}
|
|
19236
|
+
/** Register a durable token the caller already holds, instead of minting a
|
|
19237
|
+
* fresh one. For the space token: terminals outlive this process, so a
|
|
19238
|
+
* minted one leaves them unattributed after a restart. Idempotent. */
|
|
19239
|
+
adoptToken(token, origin) {
|
|
18493
19240
|
this.tokens.set(token, origin);
|
|
18494
19241
|
return token;
|
|
18495
19242
|
}
|
|
@@ -18513,7 +19260,7 @@ var SecretsGate = class {
|
|
|
18513
19260
|
return new Promise((resolve2) => {
|
|
18514
19261
|
this.waiting = {
|
|
18515
19262
|
request: {
|
|
18516
|
-
id:
|
|
19263
|
+
id: import_crypto5.default.randomUUID(),
|
|
18517
19264
|
command: opts.command,
|
|
18518
19265
|
keys: opts.keys,
|
|
18519
19266
|
origin: opts.origin,
|
|
@@ -18553,13 +19300,13 @@ var SecretsGate = class {
|
|
|
18553
19300
|
};
|
|
18554
19301
|
|
|
18555
19302
|
// ../../shared/all/helpers/board-agent-core/secrets-run.ts
|
|
18556
|
-
var
|
|
19303
|
+
var import_path12 = __toESM(require("path"));
|
|
18557
19304
|
|
|
18558
19305
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
18559
19306
|
var import_child_process4 = require("child_process");
|
|
18560
|
-
var
|
|
18561
|
-
var
|
|
18562
|
-
var
|
|
19307
|
+
var import_crypto6 = __toESM(require("crypto"));
|
|
19308
|
+
var import_promises9 = __toESM(require("fs/promises"));
|
|
19309
|
+
var import_path11 = __toESM(require("path"));
|
|
18563
19310
|
var import_util6 = require("util");
|
|
18564
19311
|
var execFileAsync3 = (0, import_util6.promisify)(import_child_process4.execFile);
|
|
18565
19312
|
var IDENTITY_FILE = "age-identity.enc";
|
|
@@ -18585,14 +19332,14 @@ async function assertBinaries() {
|
|
|
18585
19332
|
}
|
|
18586
19333
|
}
|
|
18587
19334
|
function deriveKey(password, salt) {
|
|
18588
|
-
return
|
|
19335
|
+
return import_crypto6.default.scryptSync(password, salt, 32, {
|
|
18589
19336
|
N: SCRYPT_N,
|
|
18590
19337
|
maxmem: 64 * 1024 * 1024
|
|
18591
19338
|
});
|
|
18592
19339
|
}
|
|
18593
19340
|
async function secretsInitialized(configDir) {
|
|
18594
19341
|
try {
|
|
18595
|
-
await
|
|
19342
|
+
await import_promises9.default.access(import_path11.default.join(configDir, IDENTITY_FILE));
|
|
18596
19343
|
return true;
|
|
18597
19344
|
} catch {
|
|
18598
19345
|
return false;
|
|
@@ -18614,9 +19361,9 @@ async function initSecrets(configDir, password) {
|
|
|
18614
19361
|
if (!identity || !recipient) {
|
|
18615
19362
|
throw new Error("age-keygen returned no usable keypair.");
|
|
18616
19363
|
}
|
|
18617
|
-
const salt =
|
|
18618
|
-
const iv =
|
|
18619
|
-
const cipher =
|
|
19364
|
+
const salt = import_crypto6.default.randomBytes(16);
|
|
19365
|
+
const iv = import_crypto6.default.randomBytes(12);
|
|
19366
|
+
const cipher = import_crypto6.default.createCipheriv(
|
|
18620
19367
|
"aes-256-gcm",
|
|
18621
19368
|
deriveKey(password, salt),
|
|
18622
19369
|
iv
|
|
@@ -18631,17 +19378,17 @@ async function initSecrets(configDir, password) {
|
|
|
18631
19378
|
tag: cipher.getAuthTag().toString("base64"),
|
|
18632
19379
|
data: data.toString("base64")
|
|
18633
19380
|
};
|
|
18634
|
-
await
|
|
19381
|
+
await import_promises9.default.writeFile(import_path11.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
|
|
18635
19382
|
mode: 384
|
|
18636
19383
|
});
|
|
18637
|
-
await
|
|
19384
|
+
await import_promises9.default.writeFile(import_path11.default.join(configDir, RECIPIENT_FILE), `${recipient}
|
|
18638
19385
|
`, {
|
|
18639
19386
|
mode: 384
|
|
18640
19387
|
});
|
|
18641
19388
|
return { success: true };
|
|
18642
19389
|
}
|
|
18643
19390
|
function decryptIdentity(box, password) {
|
|
18644
|
-
const decipher =
|
|
19391
|
+
const decipher = import_crypto6.default.createDecipheriv(
|
|
18645
19392
|
"aes-256-gcm",
|
|
18646
19393
|
deriveKey(password, Buffer.from(box.salt, "base64")),
|
|
18647
19394
|
Buffer.from(box.iv, "base64")
|
|
@@ -18657,17 +19404,17 @@ function decryptIdentity(box, password) {
|
|
|
18657
19404
|
}
|
|
18658
19405
|
}
|
|
18659
19406
|
function envPath(spaceDir) {
|
|
18660
|
-
return
|
|
19407
|
+
return import_path11.default.join(spaceDir, "secrets", ENV_FILE);
|
|
18661
19408
|
}
|
|
18662
19409
|
async function identityFor(configDir, password) {
|
|
18663
19410
|
const box = JSON.parse(
|
|
18664
|
-
await
|
|
19411
|
+
await import_promises9.default.readFile(import_path11.default.join(configDir, IDENTITY_FILE), "utf-8")
|
|
18665
19412
|
);
|
|
18666
19413
|
return decryptIdentity(box, password);
|
|
18667
19414
|
}
|
|
18668
19415
|
async function readEnv(spaceDir, identity) {
|
|
18669
19416
|
try {
|
|
18670
|
-
await
|
|
19417
|
+
await import_promises9.default.access(envPath(spaceDir));
|
|
18671
19418
|
} catch {
|
|
18672
19419
|
return {};
|
|
18673
19420
|
}
|
|
@@ -18696,14 +19443,14 @@ async function readEnv(spaceDir, identity) {
|
|
|
18696
19443
|
return values;
|
|
18697
19444
|
}
|
|
18698
19445
|
async function writeEnv(configDir, spaceDir, identity, values) {
|
|
18699
|
-
const recipient = (await
|
|
19446
|
+
const recipient = (await import_promises9.default.readFile(import_path11.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
|
|
18700
19447
|
const plain = `${Object.entries(values).map(([k, v]) => `${k}=${v}`).join("\n")}
|
|
18701
19448
|
`;
|
|
18702
19449
|
const tmp = envPath(spaceDir).replace(
|
|
18703
19450
|
/\.enc\.env$/,
|
|
18704
19451
|
`.${process.pid}.tmp.env`
|
|
18705
19452
|
);
|
|
18706
|
-
await
|
|
19453
|
+
await import_promises9.default.writeFile(tmp, plain, { mode: 384 });
|
|
18707
19454
|
try {
|
|
18708
19455
|
const { stdout } = await execFileAsync3(
|
|
18709
19456
|
"sops",
|
|
@@ -18722,13 +19469,13 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
18722
19469
|
maxBuffer: 4 * 1024 * 1024
|
|
18723
19470
|
}
|
|
18724
19471
|
);
|
|
18725
|
-
await
|
|
19472
|
+
await import_promises9.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
|
|
18726
19473
|
} finally {
|
|
18727
|
-
await
|
|
19474
|
+
await import_promises9.default.rm(tmp, { force: true });
|
|
18728
19475
|
}
|
|
18729
19476
|
}
|
|
18730
19477
|
async function storedSecretKeys(spaceDir) {
|
|
18731
|
-
const raw = await
|
|
19478
|
+
const raw = await import_promises9.default.readFile(envPath(spaceDir), "utf-8").catch(() => "");
|
|
18732
19479
|
const keys = [];
|
|
18733
19480
|
for (const line of raw.split("\n")) {
|
|
18734
19481
|
const eq = line.indexOf("=");
|
|
@@ -18772,9 +19519,9 @@ var COMMAND_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
|
18772
19519
|
var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18773
19520
|
function containedCwd(cwd, spaceDir) {
|
|
18774
19521
|
if (!cwd) return null;
|
|
18775
|
-
const resolved =
|
|
18776
|
-
const root =
|
|
18777
|
-
return resolved === root || resolved.startsWith(root +
|
|
19522
|
+
const resolved = import_path12.default.resolve(cwd);
|
|
19523
|
+
const root = import_path12.default.resolve(spaceDir);
|
|
19524
|
+
return resolved === root || resolved.startsWith(root + import_path12.default.sep) ? resolved : null;
|
|
18778
19525
|
}
|
|
18779
19526
|
async function authorizeAndRun(opts) {
|
|
18780
19527
|
const { command, gate } = opts;
|
|
@@ -18859,8 +19606,8 @@ function tailMarker(text) {
|
|
|
18859
19606
|
}
|
|
18860
19607
|
async function startDeployRun(opts) {
|
|
18861
19608
|
const { hooks } = opts;
|
|
18862
|
-
const workspace =
|
|
18863
|
-
const configDir =
|
|
19609
|
+
const workspace = import_path13.default.join(opts.spaceDir, "workspace");
|
|
19610
|
+
const configDir = import_path13.default.dirname(opts.spaceDir);
|
|
18864
19611
|
const status = await checkDeployStatus(opts);
|
|
18865
19612
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
18866
19613
|
throw new Error(
|
|
@@ -19079,123 +19826,8 @@ async function getDriveRoot(accessToken) {
|
|
|
19079
19826
|
}
|
|
19080
19827
|
|
|
19081
19828
|
// ../../shared/all/helpers/board-agent-core/space-core.ts
|
|
19082
|
-
var
|
|
19083
|
-
var
|
|
19084
|
-
|
|
19085
|
-
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
19086
|
-
var import_crypto4 = __toESM(require("crypto"));
|
|
19087
|
-
var import_fs3 = require("fs");
|
|
19088
|
-
var import_promises7 = require("fs/promises");
|
|
19089
|
-
var import_os3 = __toESM(require("os"));
|
|
19090
|
-
var import_path11 = __toESM(require("path"));
|
|
19091
|
-
var MARKETPLACE_NAME = "factiii-runner";
|
|
19092
|
-
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
19093
|
-
var SECRETS_BIN = "factiii-secrets";
|
|
19094
|
-
function candidates() {
|
|
19095
|
-
return [
|
|
19096
|
-
import_path11.default.join(__dirname, "..", "plugin"),
|
|
19097
|
-
import_path11.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
19098
|
-
import_path11.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
19099
|
-
];
|
|
19100
|
-
}
|
|
19101
|
-
function pluginRoot() {
|
|
19102
|
-
for (const dir of candidates()) {
|
|
19103
|
-
if ((0, import_fs3.existsSync)(import_path11.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
19104
|
-
return dir;
|
|
19105
|
-
}
|
|
19106
|
-
}
|
|
19107
|
-
return null;
|
|
19108
|
-
}
|
|
19109
|
-
function renderedPluginRoot() {
|
|
19110
|
-
return import_path11.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
19111
|
-
}
|
|
19112
|
-
async function runnerVersion(source) {
|
|
19113
|
-
const pkg = import_path11.default.join(source, "..", "package.json");
|
|
19114
|
-
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
19115
|
-
if (!raw) return FALLBACK_VERSION;
|
|
19116
|
-
try {
|
|
19117
|
-
const { version } = JSON.parse(raw);
|
|
19118
|
-
return version || FALLBACK_VERSION;
|
|
19119
|
-
} catch {
|
|
19120
|
-
return FALLBACK_VERSION;
|
|
19121
|
-
}
|
|
19122
|
-
}
|
|
19123
|
-
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
19124
|
-
async function treeFiles(dir) {
|
|
19125
|
-
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
19126
|
-
const files = [];
|
|
19127
|
-
for (const entry of entries) {
|
|
19128
|
-
const full = import_path11.default.join(dir, entry.name);
|
|
19129
|
-
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
19130
|
-
else if (entry.isFile()) files.push(full);
|
|
19131
|
-
}
|
|
19132
|
-
return files.sort();
|
|
19133
|
-
}
|
|
19134
|
-
async function fingerprint(source, url2, version = "") {
|
|
19135
|
-
const hash = import_crypto4.default.createHash("sha256").update(url2).update(version);
|
|
19136
|
-
for (const file of await treeFiles(source)) {
|
|
19137
|
-
hash.update(import_path11.default.relative(source, file));
|
|
19138
|
-
hash.update(await (0, import_promises7.readFile)(file));
|
|
19139
|
-
}
|
|
19140
|
-
return hash.digest("hex");
|
|
19141
|
-
}
|
|
19142
|
-
function stampPath(dest) {
|
|
19143
|
-
return `${dest}.stamp`;
|
|
19144
|
-
}
|
|
19145
|
-
var inFlight = /* @__PURE__ */ new Map();
|
|
19146
|
-
function renderPlugin(boardMcpUrl) {
|
|
19147
|
-
const running = inFlight.get(boardMcpUrl);
|
|
19148
|
-
if (running) return running;
|
|
19149
|
-
const started = renderPluginImpl(boardMcpUrl).finally(
|
|
19150
|
-
() => inFlight.delete(boardMcpUrl)
|
|
19151
|
-
);
|
|
19152
|
-
inFlight.set(boardMcpUrl, started);
|
|
19153
|
-
return started;
|
|
19154
|
-
}
|
|
19155
|
-
async function renderPluginImpl(boardMcpUrl) {
|
|
19156
|
-
const source = pluginRoot();
|
|
19157
|
-
if (!source) return null;
|
|
19158
|
-
const dest = renderedPluginRoot();
|
|
19159
|
-
const version = await runnerVersion(source);
|
|
19160
|
-
const want = await fingerprint(source, boardMcpUrl, version);
|
|
19161
|
-
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
19162
|
-
if (have === want) return dest;
|
|
19163
|
-
await (0, import_promises7.mkdir)(import_path11.default.dirname(dest), { recursive: true });
|
|
19164
|
-
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
19165
|
-
await (0, import_promises7.writeFile)(
|
|
19166
|
-
import_path11.default.join(dest, "factiii", ".mcp.json"),
|
|
19167
|
-
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
19168
|
-
`
|
|
19169
|
-
);
|
|
19170
|
-
await stampVersion(dest, version);
|
|
19171
|
-
await (0, import_promises7.writeFile)(stampPath(dest), want);
|
|
19172
|
-
return dest;
|
|
19173
|
-
}
|
|
19174
|
-
async function stampVersion(dest, version) {
|
|
19175
|
-
const manifest = import_path11.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
19176
|
-
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
19177
|
-
if (!raw) return;
|
|
19178
|
-
const parsed = JSON.parse(raw);
|
|
19179
|
-
await (0, import_promises7.writeFile)(
|
|
19180
|
-
manifest,
|
|
19181
|
-
`${JSON.stringify({ ...parsed, version }, null, 2)}
|
|
19182
|
-
`
|
|
19183
|
-
);
|
|
19184
|
-
}
|
|
19185
|
-
function boardMcpConfig(url2) {
|
|
19186
|
-
return {
|
|
19187
|
-
mcpServers: {
|
|
19188
|
-
"factiii-board": {
|
|
19189
|
-
type: "http",
|
|
19190
|
-
url: url2,
|
|
19191
|
-
bearer_token_env_var: BOARD_MCP_TOKEN_ENV,
|
|
19192
|
-
// Written literally, for claude to expand at connect time. Not a
|
|
19193
|
-
// template string: the `${…}` has to survive into the file.
|
|
19194
|
-
headers: { Authorization: "Bearer ${" + BOARD_MCP_TOKEN_ENV + "}" }
|
|
19195
|
-
}
|
|
19196
|
-
}
|
|
19197
|
-
};
|
|
19198
|
-
}
|
|
19829
|
+
var import_promises10 = require("fs/promises");
|
|
19830
|
+
var import_path14 = __toESM(require("path"));
|
|
19199
19831
|
|
|
19200
19832
|
// ../../shared/all/helpers/board-agent-core/toolchain.ts
|
|
19201
19833
|
var TOOLS = [
|
|
@@ -19421,6 +20053,7 @@ var SpaceCore = class {
|
|
|
19421
20053
|
this.runnerToken = options.runnerToken;
|
|
19422
20054
|
this.serverUrl = options.serverUrl;
|
|
19423
20055
|
this.mcp = options.mcp;
|
|
20056
|
+
this.mcpToken = options.mcpToken;
|
|
19424
20057
|
this.requestSudo = options.requestSudo;
|
|
19425
20058
|
this.activity = new ActivityTracker(this.emitter);
|
|
19426
20059
|
this.gate.subscribe(() => {
|
|
@@ -19464,7 +20097,7 @@ var SpaceCore = class {
|
|
|
19464
20097
|
return { code: 3, output: "" };
|
|
19465
20098
|
}
|
|
19466
20099
|
return authorizeAndRun({
|
|
19467
|
-
configDir:
|
|
20100
|
+
configDir: import_path14.default.dirname(spaceDir),
|
|
19468
20101
|
spaceDir,
|
|
19469
20102
|
// A step legitimately cds into subdirectories, so the caller's own
|
|
19470
20103
|
// cwd is right - but only inside the space. Anywhere else is a
|
|
@@ -19515,7 +20148,11 @@ var SpaceCore = class {
|
|
|
19515
20148
|
* that DOES know itself - the deploy run - mints its own and overrides this.
|
|
19516
20149
|
*/
|
|
19517
20150
|
secretsToken() {
|
|
19518
|
-
this.spaceSecretsToken
|
|
20151
|
+
if (this.spaceSecretsToken) return this.spaceSecretsToken;
|
|
20152
|
+
this.spaceSecretsToken = this.gate.adoptToken(
|
|
20153
|
+
spaceSecret(this.spaceDir()),
|
|
20154
|
+
"A session on this board"
|
|
20155
|
+
);
|
|
19519
20156
|
return this.spaceSecretsToken;
|
|
19520
20157
|
}
|
|
19521
20158
|
readConfig() {
|
|
@@ -19526,7 +20163,8 @@ var SpaceCore = class {
|
|
|
19526
20163
|
target() {
|
|
19527
20164
|
this.execTarget ??= hostTarget(this.spaceDir(), () => {
|
|
19528
20165
|
const env = {};
|
|
19529
|
-
|
|
20166
|
+
const onedrive = this.mcpToken?.();
|
|
20167
|
+
if (onedrive) env[MCP_TOKEN_ENV] = onedrive;
|
|
19530
20168
|
const board = this.boardMcp();
|
|
19531
20169
|
if (board) env[BOARD_MCP_TOKEN_ENV] = board.token;
|
|
19532
20170
|
env.FACTIII_SECRETS_TOKEN = this.secretsToken();
|
|
@@ -19717,6 +20355,8 @@ var SpaceCore = class {
|
|
|
19717
20355
|
markOnboardingComplete(target).catch(() => {
|
|
19718
20356
|
})
|
|
19719
20357
|
]);
|
|
20358
|
+
await this.restoreTerminals();
|
|
20359
|
+
await this.applyTerminalConfig();
|
|
19720
20360
|
status("");
|
|
19721
20361
|
this.ready = true;
|
|
19722
20362
|
} catch (err) {
|
|
@@ -19802,17 +20442,17 @@ var SpaceCore = class {
|
|
|
19802
20442
|
const source = pluginRoot();
|
|
19803
20443
|
if (!source) return;
|
|
19804
20444
|
const { bin } = this.target().paths;
|
|
19805
|
-
const from =
|
|
19806
|
-
const to =
|
|
20445
|
+
const from = import_path14.default.join(source, "factiii", "bin", SECRETS_BIN);
|
|
20446
|
+
const to = import_path14.default.join(bin, SECRETS_BIN);
|
|
19807
20447
|
try {
|
|
19808
20448
|
const [src, staged] = await Promise.all([
|
|
19809
|
-
(0,
|
|
19810
|
-
(0,
|
|
20449
|
+
(0, import_promises10.readFile)(from),
|
|
20450
|
+
(0, import_promises10.readFile)(to).catch(() => null)
|
|
19811
20451
|
]);
|
|
19812
20452
|
if (staged?.equals(src)) return;
|
|
19813
|
-
await (0,
|
|
19814
|
-
await (0,
|
|
19815
|
-
await (0,
|
|
20453
|
+
await (0, import_promises10.mkdir)(bin, { recursive: true });
|
|
20454
|
+
await (0, import_promises10.copyFile)(from, to);
|
|
20455
|
+
await (0, import_promises10.chmod)(to, 493);
|
|
19816
20456
|
} catch {
|
|
19817
20457
|
}
|
|
19818
20458
|
}
|
|
@@ -19875,9 +20515,79 @@ var SpaceCore = class {
|
|
|
19875
20515
|
});
|
|
19876
20516
|
await Promise.all([this.provisionPluginLane(), this.provisionBinLane()]);
|
|
19877
20517
|
await this.provisionMcpLane();
|
|
20518
|
+
await this.restoreTerminals();
|
|
20519
|
+
await this.applyTerminalConfig();
|
|
19878
20520
|
this.ready = true;
|
|
19879
20521
|
return true;
|
|
19880
20522
|
}
|
|
20523
|
+
/** Push a changed tmux config into the server that is already running.
|
|
20524
|
+
*
|
|
20525
|
+
* tmux reads `-f` only when it STARTS, so a config a runner update changed
|
|
20526
|
+
* would otherwise sit unread until every session on the host died. Sourcing
|
|
20527
|
+
* it reaches the live sessions and costs no process its life, which is why
|
|
20528
|
+
* this half never has to bother the user - unlike the env half, where a
|
|
20529
|
+
* restart is the only way in.
|
|
20530
|
+
*
|
|
20531
|
+
* Runs AFTER the plugin and helper lanes above, deliberately: those are two
|
|
20532
|
+
* of the inputs the digest covers, and recording one before they finish
|
|
20533
|
+
* would stamp a state the runner is about to invalidate. */
|
|
20534
|
+
/** Bring this space's tabs back after the tmux server died (a reboot, or a
|
|
20535
|
+
* kill). A runner restart needs none of this — tmux outlives us.
|
|
20536
|
+
*
|
|
20537
|
+
* Structure only: every tab returns as a fresh shell.
|
|
20538
|
+
*
|
|
20539
|
+
* Each restored pane is respawned, because resurrect rebuilds panes on the
|
|
20540
|
+
* DEFAULT shell with no bin dir on PATH, and stamped, because it saves no
|
|
20541
|
+
* user options and an unstamped session would read as stale on every boot. */
|
|
20542
|
+
async restoreTerminals() {
|
|
20543
|
+
try {
|
|
20544
|
+
const target = this.target();
|
|
20545
|
+
await ensureTmuxResurrect(target);
|
|
20546
|
+
const root = target.paths.root;
|
|
20547
|
+
const before = await tmuxPanePaths(target);
|
|
20548
|
+
const mineUp = [...before].some(
|
|
20549
|
+
([name, dir]) => name.startsWith("terminal-") && dir.startsWith(root)
|
|
20550
|
+
);
|
|
20551
|
+
if (mineUp) return;
|
|
20552
|
+
const restored = await resurrectRestore(target);
|
|
20553
|
+
const paths = await tmuxPanePaths(target);
|
|
20554
|
+
const revived = restored.filter(
|
|
20555
|
+
(name) => !before.has(name) && (paths.get(name) ?? "").startsWith(root)
|
|
20556
|
+
);
|
|
20557
|
+
if (revived.length === 0) return;
|
|
20558
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
20559
|
+
for (const session of revived) {
|
|
20560
|
+
await respawnTmuxPane({
|
|
20561
|
+
target,
|
|
20562
|
+
session,
|
|
20563
|
+
argv: [
|
|
20564
|
+
"bash",
|
|
20565
|
+
"-c",
|
|
20566
|
+
terminalInitScript(
|
|
20567
|
+
paths.get(session) || target.paths.workspace,
|
|
20568
|
+
target.paths.bin
|
|
20569
|
+
)
|
|
20570
|
+
],
|
|
20571
|
+
cwd: paths.get(session) || target.paths.workspace,
|
|
20572
|
+
env: target.stateEnv()
|
|
20573
|
+
}).catch(() => {
|
|
20574
|
+
});
|
|
20575
|
+
if (digest) {
|
|
20576
|
+
await setTmuxSessionOption(
|
|
20577
|
+
target,
|
|
20578
|
+
session,
|
|
20579
|
+
TERMINAL_FP_OPTION,
|
|
20580
|
+
digest
|
|
20581
|
+
);
|
|
20582
|
+
}
|
|
20583
|
+
}
|
|
20584
|
+
} catch {
|
|
20585
|
+
}
|
|
20586
|
+
}
|
|
20587
|
+
async applyTerminalConfig() {
|
|
20588
|
+
await sourceTmuxConf(this.target()).catch(() => {
|
|
20589
|
+
});
|
|
20590
|
+
}
|
|
19881
20591
|
async ensureSetup() {
|
|
19882
20592
|
await this.ensureSpaceWorkspace();
|
|
19883
20593
|
return { ready: true };
|
|
@@ -20528,7 +21238,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20528
21238
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
20529
21239
|
secretsDirs() {
|
|
20530
21240
|
const spaceDir = this.core.spaceDir();
|
|
20531
|
-
return { configDir: (0,
|
|
21241
|
+
return { configDir: (0, import_path15.dirname)(spaceDir), spaceDir };
|
|
20532
21242
|
}
|
|
20533
21243
|
async deploySecretsStatus() {
|
|
20534
21244
|
return {
|
|
@@ -20919,6 +21629,15 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20919
21629
|
this.getCardTarget(String(p.postId)) ?? void 0
|
|
20920
21630
|
);
|
|
20921
21631
|
}
|
|
21632
|
+
bareTerminalHealth(p) {
|
|
21633
|
+
return this.terminal.bareTerminalHealth(p);
|
|
21634
|
+
}
|
|
21635
|
+
bareAckTerminalDigest() {
|
|
21636
|
+
return this.terminal.bareAckTerminalDigest();
|
|
21637
|
+
}
|
|
21638
|
+
bareRestartStaleTerminals(p) {
|
|
21639
|
+
return this.terminal.bareRestartStaleTerminals(p);
|
|
21640
|
+
}
|
|
20922
21641
|
bareEditorState(p) {
|
|
20923
21642
|
return this.terminal.bareEditorState(p);
|
|
20924
21643
|
}
|
|
@@ -21556,6 +22275,17 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21556
22275
|
return await this.bareListTerminals(
|
|
21557
22276
|
payload
|
|
21558
22277
|
);
|
|
22278
|
+
case "bareTerminalHealth":
|
|
22279
|
+
return await this.bareTerminalHealth(
|
|
22280
|
+
payload
|
|
22281
|
+
);
|
|
22282
|
+
case "bareAckTerminalDigest":
|
|
22283
|
+
await this.bareAckTerminalDigest();
|
|
22284
|
+
return void 0;
|
|
22285
|
+
case "bareRestartStaleTerminals":
|
|
22286
|
+
return await this.bareRestartStaleTerminals(
|
|
22287
|
+
payload
|
|
22288
|
+
);
|
|
21559
22289
|
case "bareEditorState":
|
|
21560
22290
|
return this.bareEditorState(
|
|
21561
22291
|
payload
|
|
@@ -21598,11 +22328,11 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21598
22328
|
|
|
21599
22329
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
21600
22330
|
var import_child_process5 = require("child_process");
|
|
21601
|
-
var
|
|
21602
|
-
var
|
|
21603
|
-
var
|
|
22331
|
+
var import_crypto7 = require("crypto");
|
|
22332
|
+
var import_fs5 = require("fs");
|
|
22333
|
+
var import_promises11 = __toESM(require("fs/promises"));
|
|
21604
22334
|
var import_net3 = require("net");
|
|
21605
|
-
var
|
|
22335
|
+
var import_path16 = require("path");
|
|
21606
22336
|
var import_util7 = require("util");
|
|
21607
22337
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
21608
22338
|
var IndexService = class {
|
|
@@ -21616,7 +22346,7 @@ var IndexService = class {
|
|
|
21616
22346
|
/** Per-space bearer for MCP + file, derived from the control token (the
|
|
21617
22347
|
* service computes the same). */
|
|
21618
22348
|
bearerToken(spaceSlug) {
|
|
21619
|
-
return (0,
|
|
22349
|
+
return (0, import_crypto7.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
|
|
21620
22350
|
}
|
|
21621
22351
|
/** MCP bridge for a space. Null until the service is up (which it kicks
|
|
21622
22352
|
* off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
|
|
@@ -21661,7 +22391,7 @@ var IndexService = class {
|
|
|
21661
22391
|
const log = await this.openLog();
|
|
21662
22392
|
this.proc = (0, import_child_process5.spawn)(
|
|
21663
22393
|
process.execPath,
|
|
21664
|
-
[(0,
|
|
22394
|
+
[(0, import_path16.join)(this.opts.indexDir, "index.js")],
|
|
21665
22395
|
{
|
|
21666
22396
|
cwd: this.opts.indexDir,
|
|
21667
22397
|
env: {
|
|
@@ -21689,8 +22419,8 @@ var IndexService = class {
|
|
|
21689
22419
|
* opened (a missing log must never stop the service from starting). */
|
|
21690
22420
|
async openLog() {
|
|
21691
22421
|
try {
|
|
21692
|
-
await
|
|
21693
|
-
return (0,
|
|
22422
|
+
await import_promises11.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
22423
|
+
return (0, import_fs5.openSync)((0, import_path16.join)(this.opts.dataDir, "index.log"), "a");
|
|
21694
22424
|
} catch {
|
|
21695
22425
|
return null;
|
|
21696
22426
|
}
|
|
@@ -21699,10 +22429,10 @@ var IndexService = class {
|
|
|
21699
22429
|
* platform needing node-gyp fails here rather than at query time. */
|
|
21700
22430
|
async installDeps() {
|
|
21701
22431
|
const dir = this.opts.indexDir;
|
|
21702
|
-
if ((0,
|
|
21703
|
-
await
|
|
21704
|
-
(0,
|
|
21705
|
-
(0,
|
|
22432
|
+
if ((0, import_fs5.existsSync)((0, import_path16.join)(dir, "node_modules"))) return;
|
|
22433
|
+
await import_promises11.default.copyFile(
|
|
22434
|
+
(0, import_path16.join)(dir, "image-package.json"),
|
|
22435
|
+
(0, import_path16.join)(dir, "package.json")
|
|
21706
22436
|
);
|
|
21707
22437
|
await execFileAsync4(
|
|
21708
22438
|
"npm",
|
|
@@ -21714,15 +22444,15 @@ var IndexService = class {
|
|
|
21714
22444
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
21715
22445
|
* registration pointing at a dead one. */
|
|
21716
22446
|
async stablePort() {
|
|
21717
|
-
const file = (0,
|
|
21718
|
-
const saved = Number(await
|
|
22447
|
+
const file = (0, import_path16.join)(this.opts.dataDir, ".port");
|
|
22448
|
+
const saved = Number(await import_promises11.default.readFile(file, "utf-8").catch(() => ""));
|
|
21719
22449
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
21720
22450
|
return saved;
|
|
21721
22451
|
}
|
|
21722
22452
|
const port = await freePort();
|
|
21723
|
-
await
|
|
22453
|
+
await import_promises11.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
21724
22454
|
});
|
|
21725
|
-
await
|
|
22455
|
+
await import_promises11.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
21726
22456
|
});
|
|
21727
22457
|
return port;
|
|
21728
22458
|
}
|
|
@@ -21889,24 +22619,24 @@ async function pairWithBrowser(serverUrl) {
|
|
|
21889
22619
|
}
|
|
21890
22620
|
|
|
21891
22621
|
// src/config.ts
|
|
21892
|
-
var
|
|
22622
|
+
var import_fs9 = __toESM(require("fs"));
|
|
21893
22623
|
var import_os6 = __toESM(require("os"));
|
|
21894
|
-
var
|
|
22624
|
+
var import_path21 = __toESM(require("path"));
|
|
21895
22625
|
|
|
21896
22626
|
// src/secureFile.ts
|
|
21897
|
-
var
|
|
21898
|
-
var
|
|
21899
|
-
var
|
|
22627
|
+
var import_crypto10 = __toESM(require("crypto"));
|
|
22628
|
+
var import_fs8 = __toESM(require("fs"));
|
|
22629
|
+
var import_path20 = __toESM(require("path"));
|
|
21900
22630
|
|
|
21901
22631
|
// src/keychain.ts
|
|
21902
22632
|
var import_child_process7 = require("child_process");
|
|
21903
|
-
var
|
|
21904
|
-
var
|
|
22633
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
22634
|
+
var import_fs6 = __toESM(require("fs"));
|
|
21905
22635
|
var import_os4 = __toESM(require("os"));
|
|
21906
|
-
var
|
|
22636
|
+
var import_path17 = __toESM(require("path"));
|
|
21907
22637
|
var SERVICE = "factiii-runner";
|
|
21908
22638
|
var ACCOUNT = "config-encryption-key";
|
|
21909
|
-
var DPAPI_KEY_FILE =
|
|
22639
|
+
var DPAPI_KEY_FILE = import_path17.default.join(
|
|
21910
22640
|
import_os4.default.homedir(),
|
|
21911
22641
|
".factiii-runner",
|
|
21912
22642
|
"config-key.dpapi"
|
|
@@ -21987,7 +22717,7 @@ function linuxWrite(key) {
|
|
|
21987
22717
|
function winRead() {
|
|
21988
22718
|
let wrapped;
|
|
21989
22719
|
try {
|
|
21990
|
-
wrapped =
|
|
22720
|
+
wrapped = import_fs6.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
|
|
21991
22721
|
} catch {
|
|
21992
22722
|
return { state: "missing" };
|
|
21993
22723
|
}
|
|
@@ -22012,8 +22742,8 @@ function winWrite(key) {
|
|
|
22012
22742
|
"-Command",
|
|
22013
22743
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
22014
22744
|
]);
|
|
22015
|
-
|
|
22016
|
-
|
|
22745
|
+
import_fs6.default.mkdirSync(import_path17.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
22746
|
+
import_fs6.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
22017
22747
|
`, { mode: 384 });
|
|
22018
22748
|
}
|
|
22019
22749
|
function keychainBackend() {
|
|
@@ -22152,7 +22882,7 @@ function resolve() {
|
|
|
22152
22882
|
cached = { key: null, status: diagnose() };
|
|
22153
22883
|
return cached;
|
|
22154
22884
|
}
|
|
22155
|
-
const fresh =
|
|
22885
|
+
const fresh = import_crypto8.default.randomBytes(32);
|
|
22156
22886
|
writeKey(fresh);
|
|
22157
22887
|
const verify = readKey();
|
|
22158
22888
|
if (verify.state !== "found" || !verify.key.equals(fresh)) {
|
|
@@ -22174,23 +22904,23 @@ function keychainStatus() {
|
|
|
22174
22904
|
}
|
|
22175
22905
|
|
|
22176
22906
|
// src/secureStore.ts
|
|
22177
|
-
var
|
|
22178
|
-
var
|
|
22179
|
-
var
|
|
22907
|
+
var import_crypto9 = __toESM(require("crypto"));
|
|
22908
|
+
var import_fs7 = __toESM(require("fs"));
|
|
22909
|
+
var import_path19 = __toESM(require("path"));
|
|
22180
22910
|
|
|
22181
22911
|
// src/paths.ts
|
|
22182
22912
|
var import_os5 = __toESM(require("os"));
|
|
22183
|
-
var
|
|
22184
|
-
var CONFIG_DIR =
|
|
22913
|
+
var import_path18 = __toESM(require("path"));
|
|
22914
|
+
var CONFIG_DIR = import_path18.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
22185
22915
|
function safeSlug(spaceSlug) {
|
|
22186
22916
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
22187
22917
|
}
|
|
22188
22918
|
function spaceDirPath(spaceSlug) {
|
|
22189
|
-
return
|
|
22919
|
+
return import_path18.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
22190
22920
|
}
|
|
22191
22921
|
|
|
22192
22922
|
// src/secureStore.ts
|
|
22193
|
-
var VAULT_KEY_FILE =
|
|
22923
|
+
var VAULT_KEY_FILE = import_path19.default.join(CONFIG_DIR, "vault-key.json");
|
|
22194
22924
|
var SCRYPT_N2 = 1 << 15;
|
|
22195
22925
|
var MIN_PASSWORD_LENGTH = 8;
|
|
22196
22926
|
var SecureStoreError = class extends Error {
|
|
@@ -22217,7 +22947,7 @@ function requireKeychainKey() {
|
|
|
22217
22947
|
);
|
|
22218
22948
|
}
|
|
22219
22949
|
function passwordKey(password, salt) {
|
|
22220
|
-
return
|
|
22950
|
+
return import_crypto9.default.scryptSync(password, salt, 32, {
|
|
22221
22951
|
N: SCRYPT_N2,
|
|
22222
22952
|
maxmem: 64 * 1024 * 1024
|
|
22223
22953
|
});
|
|
@@ -22225,7 +22955,7 @@ function passwordKey(password, salt) {
|
|
|
22225
22955
|
function wrapKey(machineKey, pwKey) {
|
|
22226
22956
|
const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
|
|
22227
22957
|
return Buffer.from(
|
|
22228
|
-
|
|
22958
|
+
import_crypto9.default.hkdfSync(
|
|
22229
22959
|
"sha256",
|
|
22230
22960
|
ikm,
|
|
22231
22961
|
Buffer.alloc(0),
|
|
@@ -22235,8 +22965,8 @@ function wrapKey(machineKey, pwKey) {
|
|
|
22235
22965
|
);
|
|
22236
22966
|
}
|
|
22237
22967
|
function seal(plaintext, key) {
|
|
22238
|
-
const iv =
|
|
22239
|
-
const cipher =
|
|
22968
|
+
const iv = import_crypto9.default.randomBytes(12);
|
|
22969
|
+
const cipher = import_crypto9.default.createCipheriv("aes-256-gcm", key, iv);
|
|
22240
22970
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
22241
22971
|
return {
|
|
22242
22972
|
iv: iv.toString("base64"),
|
|
@@ -22246,7 +22976,7 @@ function seal(plaintext, key) {
|
|
|
22246
22976
|
}
|
|
22247
22977
|
function unseal(box, key) {
|
|
22248
22978
|
try {
|
|
22249
|
-
const decipher =
|
|
22979
|
+
const decipher = import_crypto9.default.createDecipheriv(
|
|
22250
22980
|
"aes-256-gcm",
|
|
22251
22981
|
key,
|
|
22252
22982
|
Buffer.from(box.iv, "base64")
|
|
@@ -22263,7 +22993,7 @@ function unseal(box, key) {
|
|
|
22263
22993
|
function readVaultFile() {
|
|
22264
22994
|
try {
|
|
22265
22995
|
const parsed = JSON.parse(
|
|
22266
|
-
|
|
22996
|
+
import_fs7.default.readFileSync(VAULT_KEY_FILE, "utf-8")
|
|
22267
22997
|
);
|
|
22268
22998
|
return parsed.v === 1 ? parsed : null;
|
|
22269
22999
|
} catch {
|
|
@@ -22271,15 +23001,15 @@ function readVaultFile() {
|
|
|
22271
23001
|
}
|
|
22272
23002
|
}
|
|
22273
23003
|
function writeVaultFile(file) {
|
|
22274
|
-
|
|
23004
|
+
import_fs7.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22275
23005
|
const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
|
|
22276
|
-
|
|
23006
|
+
import_fs7.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
|
|
22277
23007
|
`, { mode: 384 });
|
|
22278
|
-
|
|
23008
|
+
import_fs7.default.renameSync(tmp, VAULT_KEY_FILE);
|
|
22279
23009
|
}
|
|
22280
23010
|
var unlockedDek = null;
|
|
22281
23011
|
function createVault(machineKey) {
|
|
22282
|
-
const dek =
|
|
23012
|
+
const dek = import_crypto9.default.randomBytes(32);
|
|
22283
23013
|
writeVaultFile({
|
|
22284
23014
|
v: 1,
|
|
22285
23015
|
passwordEnabled: false,
|
|
@@ -22374,7 +23104,7 @@ function enablePassword(password) {
|
|
|
22374
23104
|
);
|
|
22375
23105
|
}
|
|
22376
23106
|
const dek = dataKey();
|
|
22377
|
-
const salt =
|
|
23107
|
+
const salt = import_crypto9.default.randomBytes(16);
|
|
22378
23108
|
writeVaultFile({
|
|
22379
23109
|
v: 1,
|
|
22380
23110
|
passwordEnabled: true,
|
|
@@ -22402,7 +23132,7 @@ function changePassword(current, next) {
|
|
|
22402
23132
|
"The current password is not correct."
|
|
22403
23133
|
);
|
|
22404
23134
|
}
|
|
22405
|
-
const salt =
|
|
23135
|
+
const salt = import_crypto9.default.randomBytes(16);
|
|
22406
23136
|
writeVaultFile({
|
|
22407
23137
|
v: 1,
|
|
22408
23138
|
passwordEnabled: true,
|
|
@@ -22479,8 +23209,8 @@ function storeStatus() {
|
|
|
22479
23209
|
// src/secureFile.ts
|
|
22480
23210
|
var MAGIC = "FACTIII1";
|
|
22481
23211
|
function encrypt(plain, key, version) {
|
|
22482
|
-
const iv =
|
|
22483
|
-
const cipher =
|
|
23212
|
+
const iv = import_crypto10.default.randomBytes(12);
|
|
23213
|
+
const cipher = import_crypto10.default.createCipheriv("aes-256-gcm", key, iv);
|
|
22484
23214
|
const data = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
22485
23215
|
const envelope = {
|
|
22486
23216
|
v: version,
|
|
@@ -22491,7 +23221,7 @@ function encrypt(plain, key, version) {
|
|
|
22491
23221
|
return `${MAGIC}${JSON.stringify(envelope)}`;
|
|
22492
23222
|
}
|
|
22493
23223
|
function decrypt(envelope, key) {
|
|
22494
|
-
const decipher =
|
|
23224
|
+
const decipher = import_crypto10.default.createDecipheriv(
|
|
22495
23225
|
"aes-256-gcm",
|
|
22496
23226
|
key,
|
|
22497
23227
|
Buffer.from(envelope.iv, "base64")
|
|
@@ -22512,15 +23242,15 @@ function parseEnvelope(raw) {
|
|
|
22512
23242
|
}
|
|
22513
23243
|
}
|
|
22514
23244
|
function atomicWrite(filePath, body) {
|
|
22515
|
-
|
|
23245
|
+
import_fs8.default.mkdirSync(import_path20.default.dirname(filePath), { recursive: true });
|
|
22516
23246
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
22517
|
-
|
|
22518
|
-
|
|
23247
|
+
import_fs8.default.writeFileSync(tmp, body, { mode: 384 });
|
|
23248
|
+
import_fs8.default.renameSync(tmp, filePath);
|
|
22519
23249
|
}
|
|
22520
23250
|
function readSecureBuffer(filePath) {
|
|
22521
23251
|
let raw;
|
|
22522
23252
|
try {
|
|
22523
|
-
raw =
|
|
23253
|
+
raw = import_fs8.default.readFileSync(filePath, "utf-8");
|
|
22524
23254
|
} catch {
|
|
22525
23255
|
return null;
|
|
22526
23256
|
}
|
|
@@ -22577,7 +23307,7 @@ function writeSecureJson(filePath, value2) {
|
|
|
22577
23307
|
function readBootJson(filePath) {
|
|
22578
23308
|
let raw;
|
|
22579
23309
|
try {
|
|
22580
|
-
raw =
|
|
23310
|
+
raw = import_fs8.default.readFileSync(filePath, "utf-8");
|
|
22581
23311
|
} catch {
|
|
22582
23312
|
return null;
|
|
22583
23313
|
}
|
|
@@ -22627,8 +23357,8 @@ function writeBootJson(filePath, value2) {
|
|
|
22627
23357
|
}
|
|
22628
23358
|
|
|
22629
23359
|
// src/config.ts
|
|
22630
|
-
var CONFIG_DIR2 =
|
|
22631
|
-
var CONFIG_FILE =
|
|
23360
|
+
var CONFIG_DIR2 = import_path21.default.join(import_os6.default.homedir(), ".factiii-runner");
|
|
23361
|
+
var CONFIG_FILE = import_path21.default.join(CONFIG_DIR2, "config.json");
|
|
22632
23362
|
function readRunnerConfig() {
|
|
22633
23363
|
return readBootJson(CONFIG_FILE);
|
|
22634
23364
|
}
|
|
@@ -22636,7 +23366,7 @@ function writeRunnerConfig(config) {
|
|
|
22636
23366
|
writeBootJson(CONFIG_FILE, config);
|
|
22637
23367
|
}
|
|
22638
23368
|
function configExists() {
|
|
22639
|
-
return
|
|
23369
|
+
return import_fs9.default.existsSync(CONFIG_FILE);
|
|
22640
23370
|
}
|
|
22641
23371
|
|
|
22642
23372
|
// src/promptPassword.ts
|
|
@@ -22735,72 +23465,24 @@ function installLatest() {
|
|
|
22735
23465
|
stdio: "inherit",
|
|
22736
23466
|
// On Windows npm is npm.cmd, which spawnSync can't exec without a shell.
|
|
22737
23467
|
shell: process.platform === "win32"
|
|
22738
|
-
});
|
|
22739
|
-
if (res.status !== 0) {
|
|
22740
|
-
console.error(
|
|
22741
|
-
`[update] npm install failed (exit ${res.status ?? "signal"}). Staying on ${currentVersion()}.`
|
|
22742
|
-
);
|
|
22743
|
-
return false;
|
|
22744
|
-
}
|
|
22745
|
-
return true;
|
|
22746
|
-
}
|
|
22747
|
-
|
|
22748
|
-
// src/supervisor.ts
|
|
22749
|
-
var import_node_child_process2 = require("node:child_process");
|
|
22750
|
-
|
|
22751
|
-
// ../../shared/all/helpers/wireChunk.ts
|
|
22752
|
-
var MAX_INLINE_BYTES = 14e3;
|
|
22753
|
-
var chunkCounter = 0;
|
|
22754
|
-
function nextChunkId() {
|
|
22755
|
-
return `c-${++chunkCounter}-${Date.now()}`;
|
|
22756
|
-
}
|
|
22757
|
-
function chunkWire(wire) {
|
|
22758
|
-
if (wire.length <= MAX_INLINE_BYTES) return [wire];
|
|
22759
|
-
const id = nextChunkId();
|
|
22760
|
-
const total = Math.ceil(wire.length / MAX_INLINE_BYTES);
|
|
22761
|
-
const frames = [];
|
|
22762
|
-
for (let i = 0; i < total; i++) {
|
|
22763
|
-
const start = i * MAX_INLINE_BYTES;
|
|
22764
|
-
const slice = wire.slice(start, start + MAX_INLINE_BYTES);
|
|
22765
|
-
const frame = { __c: id, __i: i, __n: total, d: slice };
|
|
22766
|
-
frames.push(JSON.stringify(frame));
|
|
23468
|
+
});
|
|
23469
|
+
if (res.status !== 0) {
|
|
23470
|
+
console.error(
|
|
23471
|
+
`[update] npm install failed (exit ${res.status ?? "signal"}). Staying on ${currentVersion()}.`
|
|
23472
|
+
);
|
|
23473
|
+
return false;
|
|
22767
23474
|
}
|
|
22768
|
-
return
|
|
22769
|
-
}
|
|
22770
|
-
function makeChunkReassembler() {
|
|
22771
|
-
const buffers = /* @__PURE__ */ new Map();
|
|
22772
|
-
return {
|
|
22773
|
-
feed(raw) {
|
|
22774
|
-
if (!raw.startsWith('{"__c"')) return raw;
|
|
22775
|
-
let frame;
|
|
22776
|
-
try {
|
|
22777
|
-
frame = JSON.parse(raw);
|
|
22778
|
-
} catch {
|
|
22779
|
-
return raw;
|
|
22780
|
-
}
|
|
22781
|
-
if (typeof frame.__c !== "string" || typeof frame.__i !== "number" || typeof frame.__n !== "number" || typeof frame.d !== "string") {
|
|
22782
|
-
return raw;
|
|
22783
|
-
}
|
|
22784
|
-
let buf = buffers.get(frame.__c);
|
|
22785
|
-
if (!buf) {
|
|
22786
|
-
buf = { received: new Array(frame.__n), total: frame.__n };
|
|
22787
|
-
buffers.set(frame.__c, buf);
|
|
22788
|
-
}
|
|
22789
|
-
buf.received[frame.__i] = frame.d;
|
|
22790
|
-
for (let i = 0; i < buf.total; i++) {
|
|
22791
|
-
if (buf.received[i] === void 0) return null;
|
|
22792
|
-
}
|
|
22793
|
-
buffers.delete(frame.__c);
|
|
22794
|
-
return buf.received.join("");
|
|
22795
|
-
}
|
|
22796
|
-
};
|
|
23475
|
+
return true;
|
|
22797
23476
|
}
|
|
22798
23477
|
|
|
23478
|
+
// src/supervisor.ts
|
|
23479
|
+
var import_node_child_process2 = require("node:child_process");
|
|
23480
|
+
|
|
22799
23481
|
// src/daemon.ts
|
|
22800
|
-
var
|
|
22801
|
-
var
|
|
23482
|
+
var import_crypto11 = require("crypto");
|
|
23483
|
+
var import_fs11 = __toESM(require("fs"));
|
|
22802
23484
|
var import_node_datachannel = require("node-datachannel");
|
|
22803
|
-
var
|
|
23485
|
+
var import_path23 = __toESM(require("path"));
|
|
22804
23486
|
|
|
22805
23487
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
22806
23488
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23980,12 +24662,12 @@ function parse2(str) {
|
|
|
23980
24662
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23981
24663
|
return uri;
|
|
23982
24664
|
}
|
|
23983
|
-
function pathNames(obj,
|
|
23984
|
-
const regx = /\/{2,9}/g, names =
|
|
23985
|
-
if (
|
|
24665
|
+
function pathNames(obj, path23) {
|
|
24666
|
+
const regx = /\/{2,9}/g, names = path23.replace(regx, "/").split("/");
|
|
24667
|
+
if (path23.slice(0, 1) == "/" || path23.length === 0) {
|
|
23986
24668
|
names.splice(0, 1);
|
|
23987
24669
|
}
|
|
23988
|
-
if (
|
|
24670
|
+
if (path23.slice(-1) == "/") {
|
|
23989
24671
|
names.splice(names.length - 1, 1);
|
|
23990
24672
|
}
|
|
23991
24673
|
return names;
|
|
@@ -24604,7 +25286,7 @@ var protocol2 = Socket.protocol;
|
|
|
24604
25286
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
24605
25287
|
var import_debug7 = __toESM(require_src(), 1);
|
|
24606
25288
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
24607
|
-
function url(uri,
|
|
25289
|
+
function url(uri, path23 = "", loc) {
|
|
24608
25290
|
let obj = uri;
|
|
24609
25291
|
loc = loc || typeof location !== "undefined" && location;
|
|
24610
25292
|
if (null == uri)
|
|
@@ -24638,7 +25320,7 @@ function url(uri, path21 = "", loc) {
|
|
|
24638
25320
|
obj.path = obj.path || "/";
|
|
24639
25321
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
24640
25322
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
24641
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
25323
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path23;
|
|
24642
25324
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
24643
25325
|
return obj;
|
|
24644
25326
|
}
|
|
@@ -26272,8 +26954,8 @@ function lookup(uri, opts) {
|
|
|
26272
26954
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
26273
26955
|
const source = parsed.source;
|
|
26274
26956
|
const id = parsed.id;
|
|
26275
|
-
const
|
|
26276
|
-
const sameNamespace = cache[id] &&
|
|
26957
|
+
const path23 = parsed.path;
|
|
26958
|
+
const sameNamespace = cache[id] && path23 in cache[id]["nsps"];
|
|
26277
26959
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
26278
26960
|
let io;
|
|
26279
26961
|
if (newConnection) {
|
|
@@ -26298,205 +26980,22 @@ Object.assign(lookup, {
|
|
|
26298
26980
|
connect: lookup
|
|
26299
26981
|
});
|
|
26300
26982
|
|
|
26301
|
-
//
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
|
|
26306
|
-
`cd ${cwd} 2>/dev/null || cd "$HOME" 2>/dev/null || cd /`,
|
|
26307
|
-
// Prepended here rather than via the session env, so the login shell's
|
|
26308
|
-
// own PATH survives.
|
|
26309
|
-
`export PATH="${binDir}:$PATH"`,
|
|
26310
|
-
`export PS1='\\[\\e[1;32m\\]user@bare\\[\\e[0m\\]:\\w\\$ '`
|
|
26311
|
-
].join("; ");
|
|
26312
|
-
}
|
|
26313
|
-
var SAFE_SESSION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
26314
|
-
function cardContextPath(target, postId) {
|
|
26315
|
-
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
26316
|
-
return `${target.paths.root}/.cards/${safe}.md`;
|
|
26317
|
-
}
|
|
26318
|
-
function cardContextPrompt(path21) {
|
|
26319
|
-
return `Read ${path21} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
|
|
26320
|
-
}
|
|
26321
|
-
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
26322
|
-
var RESUME_SEED = "You are now in an interactive terminal, not the board UI. Respond in plain text and use your normal tools; do not use openui-lang or component schemas anymore.";
|
|
26323
|
-
function terminalInitScript(cwd, binDir, resumeAgentSessionId, provider, cardFile) {
|
|
26324
|
-
const prelude = terminalPrelude(cwd, binDir);
|
|
26325
|
-
const shell = `exec bash --norc -i`;
|
|
26326
|
-
if (!resumeAgentSessionId || !SAFE_SESSION_ID.test(resumeAgentSessionId)) {
|
|
26327
|
-
if (!cardFile) return `${prelude}; ${shell}`;
|
|
26328
|
-
const prompt2 = cardContextPrompt(cardFile);
|
|
26329
|
-
const start = provider === "codex" ? `codex '${prompt2}'` : `claude ${CLAUDE_PERMISSION_MODE} '${prompt2}'`;
|
|
26330
|
-
return `${prelude}; ${start}; ${shell}`;
|
|
26331
|
-
}
|
|
26332
|
-
const resume = provider === "codex" ? `codex resume ${resumeAgentSessionId} '${RESUME_SEED}'` : `claude ${CLAUDE_PERMISSION_MODE} --resume ${resumeAgentSessionId} '${RESUME_SEED}'`;
|
|
26333
|
-
return `${prelude}; ${resume}; ${shell}`;
|
|
26983
|
+
// src/agent-adapter.ts
|
|
26984
|
+
var import_fs10 = __toESM(require("fs"));
|
|
26985
|
+
var import_path22 = __toESM(require("path"));
|
|
26986
|
+
function credsDir(spaceSlug) {
|
|
26987
|
+
return import_path22.default.join(spaceDirPath(spaceSlug), ".creds");
|
|
26334
26988
|
}
|
|
26335
|
-
function
|
|
26336
|
-
return
|
|
26989
|
+
function githubPath(spaceSlug) {
|
|
26990
|
+
return import_path22.default.join(credsDir(spaceSlug), "github.json");
|
|
26337
26991
|
}
|
|
26338
|
-
var TerminalManager = class {
|
|
26339
|
-
constructor(send, getCardTarget, getCardWorkdir) {
|
|
26340
|
-
this.send = send;
|
|
26341
|
-
this.getCardTarget = getCardTarget;
|
|
26342
|
-
this.getCardWorkdir = getCardWorkdir;
|
|
26343
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
26344
|
-
}
|
|
26345
|
-
handle(msg) {
|
|
26346
|
-
switch (msg.op) {
|
|
26347
|
-
case "open":
|
|
26348
|
-
void this.open(msg);
|
|
26349
|
-
return;
|
|
26350
|
-
case "stdin":
|
|
26351
|
-
this.stdin(msg.sessionId, msg.data);
|
|
26352
|
-
return;
|
|
26353
|
-
case "resize":
|
|
26354
|
-
void this.resize(msg.sessionId, msg.cols, msg.rows);
|
|
26355
|
-
return;
|
|
26356
|
-
case "close":
|
|
26357
|
-
this.close(msg.sessionId);
|
|
26358
|
-
return;
|
|
26359
|
-
case "kill":
|
|
26360
|
-
void this.killTmuxSession(msg.postId, msg.terminalKey);
|
|
26361
|
-
return;
|
|
26362
|
-
}
|
|
26363
|
-
}
|
|
26364
|
-
async open(msg) {
|
|
26365
|
-
if (this.sessions.has(msg.sessionId)) {
|
|
26366
|
-
this.emitError(msg.sessionId, "Session already open.");
|
|
26367
|
-
return;
|
|
26368
|
-
}
|
|
26369
|
-
const target = this.getCardTarget(msg.postId);
|
|
26370
|
-
if (!target) {
|
|
26371
|
-
this.emitError(
|
|
26372
|
-
msg.sessionId,
|
|
26373
|
-
`No active card session for postId=${msg.postId}.`
|
|
26374
|
-
);
|
|
26375
|
-
return;
|
|
26376
|
-
}
|
|
26377
|
-
const tmuxSession = tmuxName(msg.terminalKey);
|
|
26378
|
-
const wantsCardContext = !msg.resumeAgentSessionId && !!msg.cardContext;
|
|
26379
|
-
const cardFile = wantsCardContext ? cardContextPath(target, msg.postId) : void 0;
|
|
26380
|
-
let wroteCardFile = false;
|
|
26381
|
-
if (cardFile) {
|
|
26382
|
-
wroteCardFile = await target.writeFile(cardFile, msg.cardContext).then(() => true).catch(() => false);
|
|
26383
|
-
}
|
|
26384
|
-
try {
|
|
26385
|
-
const session = await startTmuxSession({
|
|
26386
|
-
target,
|
|
26387
|
-
session: tmuxSession,
|
|
26388
|
-
// Attach if it exists, else create. This is what makes the shell
|
|
26389
|
-
// survive modal close/reopen: the server holds it, we just view.
|
|
26390
|
-
attachExisting: true,
|
|
26391
|
-
size: { cols: msg.cols, rows: msg.rows },
|
|
26392
|
-
// Without these, `claude` in the shell reads the default ~/.claude and
|
|
26393
|
-
// asks to sign in, while the agent spawns (which pass agentEnv) don't.
|
|
26394
|
-
env: target.stateEnv(),
|
|
26395
|
-
argv: [
|
|
26396
|
-
"bash",
|
|
26397
|
-
"-c",
|
|
26398
|
-
terminalInitScript(
|
|
26399
|
-
this.getCardWorkdir(msg.postId) || target.paths.workspace,
|
|
26400
|
-
target.paths.bin,
|
|
26401
|
-
msg.resumeAgentSessionId,
|
|
26402
|
-
msg.resumeProvider,
|
|
26403
|
-
wroteCardFile ? cardFile : void 0
|
|
26404
|
-
)
|
|
26405
|
-
]
|
|
26406
|
-
});
|
|
26407
|
-
this.sessions.set(msg.sessionId, session);
|
|
26408
|
-
session.onData(
|
|
26409
|
-
(data) => this.emit({
|
|
26410
|
-
type: "terminal",
|
|
26411
|
-
op: "stdout",
|
|
26412
|
-
sessionId: msg.sessionId,
|
|
26413
|
-
data: data.toString("base64")
|
|
26414
|
-
})
|
|
26415
|
-
);
|
|
26416
|
-
session.onError((err) => {
|
|
26417
|
-
this.cleanup(msg.sessionId);
|
|
26418
|
-
this.emitError(msg.sessionId, err.message);
|
|
26419
|
-
});
|
|
26420
|
-
session.onClose((code) => {
|
|
26421
|
-
if (!this.sessions.has(msg.sessionId)) return;
|
|
26422
|
-
this.cleanup(msg.sessionId);
|
|
26423
|
-
this.emit({
|
|
26424
|
-
type: "terminal",
|
|
26425
|
-
op: "exit",
|
|
26426
|
-
sessionId: msg.sessionId,
|
|
26427
|
-
code
|
|
26428
|
-
});
|
|
26429
|
-
});
|
|
26430
|
-
this.emit({ type: "terminal", op: "opened", sessionId: msg.sessionId });
|
|
26431
|
-
} catch (err) {
|
|
26432
|
-
this.emitError(
|
|
26433
|
-
msg.sessionId,
|
|
26434
|
-
err instanceof Error ? err.message : String(err)
|
|
26435
|
-
);
|
|
26436
|
-
}
|
|
26437
|
-
}
|
|
26438
|
-
stdin(sessionId, b64) {
|
|
26439
|
-
this.sessions.get(sessionId)?.write(Buffer.from(b64, "base64"));
|
|
26440
|
-
}
|
|
26441
|
-
async resize(sessionId, cols, rows) {
|
|
26442
|
-
const session = this.sessions.get(sessionId);
|
|
26443
|
-
if (!session) return;
|
|
26444
|
-
if (!Number.isInteger(cols) || !Number.isInteger(rows)) return;
|
|
26445
|
-
if (cols < 1 || rows < 1) return;
|
|
26446
|
-
await session.resize(cols, rows).catch(() => {
|
|
26447
|
-
});
|
|
26448
|
-
}
|
|
26449
|
-
close(sessionId) {
|
|
26450
|
-
const session = this.sessions.get(sessionId);
|
|
26451
|
-
if (!session) return;
|
|
26452
|
-
this.sessions.delete(sessionId);
|
|
26453
|
-
session.detach();
|
|
26454
|
-
}
|
|
26455
|
-
/** Tear down the tmux session for a tab. Used when the user explicitly
|
|
26456
|
-
* removes a tab (the X button), not on transient detach. */
|
|
26457
|
-
async killTmuxSession(postId, terminalKey) {
|
|
26458
|
-
const target = this.getCardTarget(postId);
|
|
26459
|
-
if (!target) return;
|
|
26460
|
-
await target.sh(
|
|
26461
|
-
`${tmuxCli(target)} kill-session -t ${tmuxName(terminalKey)} 2>/dev/null || true`
|
|
26462
|
-
).catch(() => {
|
|
26463
|
-
});
|
|
26464
|
-
}
|
|
26465
|
-
destroyAll() {
|
|
26466
|
-
for (const sessionId of [...this.sessions.keys()]) {
|
|
26467
|
-
this.close(sessionId);
|
|
26468
|
-
}
|
|
26469
|
-
}
|
|
26470
|
-
cleanup(sessionId) {
|
|
26471
|
-
this.sessions.delete(sessionId);
|
|
26472
|
-
}
|
|
26473
|
-
emitError(sessionId, message) {
|
|
26474
|
-
this.emit({ type: "terminal", op: "error", sessionId, message });
|
|
26475
|
-
}
|
|
26476
|
-
emit(msg) {
|
|
26477
|
-
const wire = JSON.stringify(msg);
|
|
26478
|
-
for (const frame of chunkWire(wire)) this.send(frame);
|
|
26479
|
-
}
|
|
26480
|
-
};
|
|
26481
|
-
|
|
26482
|
-
// src/agent-adapter.ts
|
|
26483
|
-
var import_fs9 = __toESM(require("fs"));
|
|
26484
|
-
var import_path20 = __toESM(require("path"));
|
|
26485
|
-
var RUNNER_CONFIG_PATH = import_path20.default.join(CONFIG_DIR, "runner-config.json");
|
|
26486
26992
|
function connectionPath(spaceSlug) {
|
|
26487
|
-
return
|
|
26993
|
+
return import_path22.default.join(credsDir(spaceSlug), "onedrive.json");
|
|
26488
26994
|
}
|
|
26995
|
+
var RUNNER_CONFIG_PATH = import_path22.default.join(CONFIG_DIR, "runner-config.json");
|
|
26489
26996
|
function writeJson(filePath, value2) {
|
|
26490
26997
|
writeSecureJson(filePath, value2);
|
|
26491
26998
|
}
|
|
26492
|
-
var CONFIG_FIELDS = [
|
|
26493
|
-
"repoUrl",
|
|
26494
|
-
"mainBranch",
|
|
26495
|
-
"gitName",
|
|
26496
|
-
"gitEmail",
|
|
26497
|
-
"githubToken",
|
|
26498
|
-
"provider"
|
|
26499
|
-
];
|
|
26500
26999
|
function fillDefaults(config) {
|
|
26501
27000
|
return {
|
|
26502
27001
|
repoUrl: config.repoUrl || "",
|
|
@@ -26507,29 +27006,107 @@ function fillDefaults(config) {
|
|
|
26507
27006
|
provider: config.provider || "claude"
|
|
26508
27007
|
};
|
|
26509
27008
|
}
|
|
26510
|
-
function
|
|
26511
|
-
|
|
26512
|
-
|
|
27009
|
+
function configDirNames() {
|
|
27010
|
+
try {
|
|
27011
|
+
return import_fs10.default.readdirSync(CONFIG_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
27012
|
+
} catch {
|
|
27013
|
+
return [];
|
|
27014
|
+
}
|
|
27015
|
+
}
|
|
27016
|
+
function provisionedSpaceSlugs() {
|
|
27017
|
+
return configDirNames().filter(
|
|
27018
|
+
(name) => import_fs10.default.existsSync(import_path22.default.join(CONFIG_DIR, name, "bin"))
|
|
27019
|
+
);
|
|
27020
|
+
}
|
|
27021
|
+
var migrated = false;
|
|
27022
|
+
function migrateToSpaceFolders() {
|
|
27023
|
+
if (migrated) return;
|
|
27024
|
+
migrated = true;
|
|
27025
|
+
try {
|
|
27026
|
+
migrateOnce();
|
|
27027
|
+
} catch {
|
|
27028
|
+
migrated = false;
|
|
27029
|
+
}
|
|
27030
|
+
}
|
|
27031
|
+
function readLegacy(filePath) {
|
|
27032
|
+
if (!import_fs10.default.existsSync(filePath)) return { readable: true, value: null };
|
|
27033
|
+
const value2 = readSecureJson(filePath);
|
|
27034
|
+
return { readable: value2 !== null, value: value2 };
|
|
27035
|
+
}
|
|
27036
|
+
function migrateOnce() {
|
|
27037
|
+
const legacy = readLegacy(RUNNER_CONFIG_PATH);
|
|
27038
|
+
if (!legacy.readable) {
|
|
27039
|
+
throw new Error(
|
|
27040
|
+
`Cannot read ${RUNNER_CONFIG_PATH} \u2014 leaving the legacy stores in place.`
|
|
27041
|
+
);
|
|
27042
|
+
}
|
|
27043
|
+
const store = legacy.value ?? {};
|
|
27044
|
+
const base = store.default ?? {};
|
|
27045
|
+
const boards = store.boards ?? {};
|
|
27046
|
+
const strays = {};
|
|
27047
|
+
const consumed = [];
|
|
27048
|
+
let strayFiles = [];
|
|
26513
27049
|
try {
|
|
26514
|
-
|
|
27050
|
+
strayFiles = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
|
|
26515
27051
|
} catch {
|
|
26516
|
-
|
|
27052
|
+
strayFiles = [];
|
|
26517
27053
|
}
|
|
26518
|
-
for (const name of
|
|
27054
|
+
for (const name of strayFiles) {
|
|
26519
27055
|
const slug = name.slice("space-".length, -".json".length);
|
|
26520
|
-
const parsed =
|
|
26521
|
-
|
|
27056
|
+
const parsed = readLegacy(
|
|
27057
|
+
import_path22.default.join(CONFIG_DIR, name)
|
|
26522
27058
|
);
|
|
26523
|
-
if (!parsed) continue;
|
|
26524
|
-
if (
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
27059
|
+
if (!parsed.readable) continue;
|
|
27060
|
+
if (parsed.value) strays[slug] = parsed.value;
|
|
27061
|
+
consumed.push(name);
|
|
27062
|
+
}
|
|
27063
|
+
const slugs = /* @__PURE__ */ new Set([
|
|
27064
|
+
...Object.keys(boards),
|
|
27065
|
+
...Object.keys(strays),
|
|
27066
|
+
...store.defaultSlug ? [store.defaultSlug] : [],
|
|
27067
|
+
...provisionedSpaceSlugs()
|
|
27068
|
+
]);
|
|
27069
|
+
for (const slug of slugs) {
|
|
27070
|
+
if (import_fs10.default.existsSync(githubPath(slug))) continue;
|
|
27071
|
+
const over = boards[slug] ?? strays[slug] ?? {};
|
|
27072
|
+
if (!Object.keys(over).length && !Object.keys(base).length) continue;
|
|
27073
|
+
const merged = fillDefaults({
|
|
27074
|
+
repoUrl: over.repoUrl || base.repoUrl,
|
|
27075
|
+
mainBranch: over.mainBranch || base.mainBranch,
|
|
27076
|
+
gitName: over.gitName || base.gitName,
|
|
27077
|
+
gitEmail: over.gitEmail || base.gitEmail,
|
|
27078
|
+
githubToken: over.githubToken || base.githubToken,
|
|
27079
|
+
provider: over.provider || base.provider
|
|
27080
|
+
});
|
|
27081
|
+
import_fs10.default.mkdirSync(credsDir(slug), { recursive: true });
|
|
27082
|
+
writeJson(githubPath(slug), merged);
|
|
27083
|
+
}
|
|
27084
|
+
let odFiles = [];
|
|
27085
|
+
try {
|
|
27086
|
+
odFiles = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".onedrive.json"));
|
|
27087
|
+
} catch {
|
|
27088
|
+
odFiles = [];
|
|
27089
|
+
}
|
|
27090
|
+
for (const name of odFiles) {
|
|
27091
|
+
const slug = name.slice("space-".length, -".onedrive.json".length);
|
|
27092
|
+
if (import_fs10.default.existsSync(connectionPath(slug))) {
|
|
27093
|
+
consumed.push(name);
|
|
27094
|
+
continue;
|
|
27095
|
+
}
|
|
27096
|
+
const conn = readLegacy(import_path22.default.join(CONFIG_DIR, name));
|
|
27097
|
+
if (!conn.readable) continue;
|
|
27098
|
+
if (conn.value) {
|
|
27099
|
+
import_fs10.default.mkdirSync(credsDir(slug), { recursive: true });
|
|
27100
|
+
writeJson(connectionPath(slug), conn.value);
|
|
27101
|
+
}
|
|
27102
|
+
consumed.push(name);
|
|
27103
|
+
}
|
|
27104
|
+
for (const name of [...consumed, "runner-config.json"]) {
|
|
27105
|
+
try {
|
|
27106
|
+
import_fs10.default.rmSync(import_path22.default.join(CONFIG_DIR, name), { force: true });
|
|
27107
|
+
} catch {
|
|
26529
27108
|
}
|
|
26530
27109
|
}
|
|
26531
|
-
if (file.defaultSlug) writeJson(RUNNER_CONFIG_PATH, file);
|
|
26532
|
-
return file;
|
|
26533
27110
|
}
|
|
26534
27111
|
var sweptStrays = false;
|
|
26535
27112
|
function encryptStrayPlaintextConfigs() {
|
|
@@ -26538,14 +27115,14 @@ function encryptStrayPlaintextConfigs() {
|
|
|
26538
27115
|
const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
|
|
26539
27116
|
let names = [];
|
|
26540
27117
|
try {
|
|
26541
|
-
names =
|
|
27118
|
+
names = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
|
|
26542
27119
|
} catch {
|
|
26543
27120
|
return;
|
|
26544
27121
|
}
|
|
26545
27122
|
for (const name of names) {
|
|
26546
|
-
const filePath =
|
|
27123
|
+
const filePath = import_path22.default.join(CONFIG_DIR, name);
|
|
26547
27124
|
try {
|
|
26548
|
-
const raw =
|
|
27125
|
+
const raw = import_fs10.default.readFileSync(filePath, "utf-8");
|
|
26549
27126
|
if (!raw.startsWith("{")) continue;
|
|
26550
27127
|
writeSecureJson(filePath, JSON.parse(raw));
|
|
26551
27128
|
} catch {
|
|
@@ -26557,84 +27134,50 @@ function encryptStrayPlaintextConfigs() {
|
|
|
26557
27134
|
function resetStraySweep() {
|
|
26558
27135
|
sweptStrays = false;
|
|
26559
27136
|
}
|
|
26560
|
-
function
|
|
27137
|
+
function readBoard(spaceSlug) {
|
|
26561
27138
|
encryptStrayPlaintextConfigs();
|
|
26562
|
-
|
|
26563
|
-
return
|
|
27139
|
+
migrateToSpaceFolders();
|
|
27140
|
+
return readSecureJson(githubPath(spaceSlug)) ?? {};
|
|
26564
27141
|
}
|
|
26565
27142
|
async function getConfiguredConnections() {
|
|
26566
27143
|
const kinds = /* @__PURE__ */ new Set();
|
|
26567
|
-
let file;
|
|
26568
27144
|
try {
|
|
26569
|
-
|
|
27145
|
+
for (const slug of listConfiguredSlugs()) {
|
|
27146
|
+
if (kinds.has("git") && kinds.has("onedrive")) break;
|
|
27147
|
+
if (readBoard(slug).githubToken) kinds.add("git");
|
|
27148
|
+
if (import_fs10.default.existsSync(connectionPath(slug))) kinds.add("onedrive");
|
|
27149
|
+
}
|
|
26570
27150
|
} catch {
|
|
26571
27151
|
return [];
|
|
26572
27152
|
}
|
|
26573
|
-
const configs = [file.default ?? {}, ...Object.values(file.boards ?? {})];
|
|
26574
|
-
for (const c of configs) {
|
|
26575
|
-
if (c.githubToken) kinds.add("git");
|
|
26576
|
-
}
|
|
26577
27153
|
if (await isSignedIn(hostTarget(CONFIG_DIR), "claude")) kinds.add("claude");
|
|
26578
|
-
try {
|
|
26579
|
-
const hasOneDrive = import_fs9.default.readdirSync(CONFIG_DIR).some((f) => f.endsWith(".onedrive.json"));
|
|
26580
|
-
if (hasOneDrive) kinds.add("onedrive");
|
|
26581
|
-
} catch {
|
|
26582
|
-
}
|
|
26583
27154
|
return [...kinds];
|
|
26584
27155
|
}
|
|
26585
27156
|
function listConfiguredSlugs() {
|
|
26586
27157
|
try {
|
|
26587
|
-
|
|
26588
|
-
return
|
|
26589
|
-
|
|
26590
|
-
|
|
26591
|
-
...file.defaultSlug ? [file.defaultSlug] : []
|
|
26592
|
-
])
|
|
26593
|
-
];
|
|
27158
|
+
migrateToSpaceFolders();
|
|
27159
|
+
return configDirNames().filter(
|
|
27160
|
+
(slug) => import_fs10.default.existsSync(githubPath(slug)) || import_fs10.default.existsSync(connectionPath(slug))
|
|
27161
|
+
);
|
|
26594
27162
|
} catch {
|
|
26595
27163
|
return [];
|
|
26596
27164
|
}
|
|
26597
27165
|
}
|
|
26598
27166
|
var localConfigProvider = {
|
|
26599
27167
|
readConfig(spaceSlug) {
|
|
26600
|
-
let file;
|
|
26601
27168
|
try {
|
|
26602
|
-
|
|
27169
|
+
return fillDefaults(readBoard(spaceSlug));
|
|
26603
27170
|
} catch (err) {
|
|
26604
27171
|
if (!isSecureStoreError(err)) throw err;
|
|
26605
27172
|
return fillDefaults({});
|
|
26606
27173
|
}
|
|
26607
|
-
const base = file.default ?? {};
|
|
26608
|
-
const over = file.boards?.[spaceSlug] ?? {};
|
|
26609
|
-
return fillDefaults({
|
|
26610
|
-
repoUrl: over.repoUrl || base.repoUrl,
|
|
26611
|
-
mainBranch: over.mainBranch || base.mainBranch,
|
|
26612
|
-
gitName: over.gitName || base.gitName,
|
|
26613
|
-
gitEmail: over.gitEmail || base.gitEmail,
|
|
26614
|
-
githubToken: over.githubToken || base.githubToken,
|
|
26615
|
-
provider: over.provider || base.provider
|
|
26616
|
-
});
|
|
26617
27174
|
},
|
|
27175
|
+
// The whole config, every time: a cleared field has to persist as cleared,
|
|
27176
|
+
// which is what makes disconnect stick.
|
|
26618
27177
|
writeConfig(spaceSlug, config) {
|
|
26619
|
-
|
|
26620
|
-
|
|
26621
|
-
|
|
26622
|
-
file.defaultSlug = spaceSlug;
|
|
26623
|
-
file.default = config;
|
|
26624
|
-
} else if (spaceSlug === file.defaultSlug) {
|
|
26625
|
-
file.default = config;
|
|
26626
|
-
} else {
|
|
26627
|
-
const base = file.default ?? {};
|
|
26628
|
-
const diff = {};
|
|
26629
|
-
for (const f of CONFIG_FIELDS) {
|
|
26630
|
-
if (config[f] && config[f] !== base[f]) {
|
|
26631
|
-
diff[f] = config[f];
|
|
26632
|
-
}
|
|
26633
|
-
}
|
|
26634
|
-
if (Object.keys(diff).length) file.boards[spaceSlug] = diff;
|
|
26635
|
-
else delete file.boards[spaceSlug];
|
|
26636
|
-
}
|
|
26637
|
-
writeJson(RUNNER_CONFIG_PATH, file);
|
|
27178
|
+
migrateToSpaceFolders();
|
|
27179
|
+
import_fs10.default.mkdirSync(credsDir(spaceSlug), { recursive: true });
|
|
27180
|
+
writeJson(githubPath(spaceSlug), config);
|
|
26638
27181
|
},
|
|
26639
27182
|
readOneDriveConnection(spaceSlug) {
|
|
26640
27183
|
try {
|
|
@@ -26647,7 +27190,7 @@ var localConfigProvider = {
|
|
|
26647
27190
|
writeOneDriveConnection(spaceSlug, connection) {
|
|
26648
27191
|
const filePath = connectionPath(spaceSlug);
|
|
26649
27192
|
if (!connection) {
|
|
26650
|
-
|
|
27193
|
+
import_fs10.default.rmSync(filePath, { force: true });
|
|
26651
27194
|
return;
|
|
26652
27195
|
}
|
|
26653
27196
|
writeJson(filePath, connection);
|
|
@@ -26672,7 +27215,7 @@ var localConfigProvider = {
|
|
|
26672
27215
|
"claude",
|
|
26673
27216
|
"codex"
|
|
26674
27217
|
]) {
|
|
26675
|
-
|
|
27218
|
+
import_fs10.default.mkdirSync(import_path22.default.join(dir, sub), { recursive: true });
|
|
26676
27219
|
}
|
|
26677
27220
|
return dir;
|
|
26678
27221
|
}
|
|
@@ -26846,15 +27389,15 @@ async function startDaemon(config) {
|
|
|
26846
27389
|
}
|
|
26847
27390
|
);
|
|
26848
27391
|
const indexCandidates = [
|
|
26849
|
-
|
|
26850
|
-
|
|
27392
|
+
import_path23.default.join(__dirname, "..", "index"),
|
|
27393
|
+
import_path23.default.join(__dirname, "..", "..", "index", "image")
|
|
26851
27394
|
];
|
|
26852
|
-
const indexDir = indexCandidates.find((p) =>
|
|
27395
|
+
const indexDir = indexCandidates.find((p) => import_fs11.default.existsSync(p)) ?? indexCandidates[0];
|
|
26853
27396
|
const odIndex = new IndexService({
|
|
26854
|
-
controlToken: (0,
|
|
27397
|
+
controlToken: (0, import_crypto11.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
26855
27398
|
embedUrl: () => config.serverUrl,
|
|
26856
27399
|
indexDir,
|
|
26857
|
-
dataDir:
|
|
27400
|
+
dataDir: import_path23.default.join(CONFIG_DIR, "index-data")
|
|
26858
27401
|
});
|
|
26859
27402
|
void odIndex.ensureRunning().catch((err) => {
|
|
26860
27403
|
console.error(
|
|
@@ -26951,7 +27494,9 @@ async function startDaemon(config) {
|
|
|
26951
27494
|
// Board MCP is server-hosted, so the base URL is all it needs.
|
|
26952
27495
|
serverUrl: config.serverUrl,
|
|
26953
27496
|
// Point the agent at the index MCP endpoint (lazy for the port).
|
|
26954
|
-
mcp: () => odIndex.mcpBridge(spaceSlug)
|
|
27497
|
+
mcp: () => odIndex.mcpBridge(spaceSlug),
|
|
27498
|
+
// The bearer alone, available before the service has a port for a URL.
|
|
27499
|
+
mcpToken: () => odIndex.bearerToken(spaceSlug)
|
|
26955
27500
|
});
|
|
26956
27501
|
engines.set(spaceSlug, engine);
|
|
26957
27502
|
void engine.reattachAndHydrate();
|