@factiii/runner 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1459 -918
- package/index/image-package.json +1 -1
- package/package.json +6 -4
- 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
|
/**
|
|
@@ -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,313 @@ 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_fs2 = require("fs");
|
|
13809
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
13810
|
+
var import_net = __toESM(require("net"));
|
|
13811
|
+
var import_path4 = __toESM(require("path"));
|
|
13812
|
+
var import_util4 = require("util");
|
|
13545
13813
|
var execFileAsync = (0, import_util4.promisify)(import_child_process2.execFile);
|
|
13546
13814
|
var MAX_BUFFER = 10 * 1024 * 1024;
|
|
13547
13815
|
var PORTS_FILE = ".ports";
|
|
13548
13816
|
var NUM_PORTS = 6;
|
|
13549
13817
|
function portsFilePath(spaceDir) {
|
|
13550
|
-
return
|
|
13818
|
+
return import_path4.default.join(spaceDir, PORTS_FILE);
|
|
13551
13819
|
}
|
|
13552
13820
|
async function allocateSpacePorts(spaceDir) {
|
|
13553
13821
|
const existing = readSpacePorts(spaceDir);
|
|
@@ -13573,19 +13841,19 @@ async function allocateSpacePorts(spaceDir) {
|
|
|
13573
13841
|
seen.add(port);
|
|
13574
13842
|
ports.push(port);
|
|
13575
13843
|
}
|
|
13576
|
-
await
|
|
13577
|
-
await
|
|
13844
|
+
await import_promises2.default.mkdir(spaceDir, { recursive: true });
|
|
13845
|
+
await import_promises2.default.writeFile(portsFilePath(spaceDir), ports.join(","), "utf-8");
|
|
13578
13846
|
return ports;
|
|
13579
13847
|
}
|
|
13580
13848
|
function readSpacePorts(spaceDir) {
|
|
13581
13849
|
try {
|
|
13582
|
-
return (0,
|
|
13850
|
+
return (0, import_fs2.readFileSync)(portsFilePath(spaceDir), "utf-8").split(",").map((p) => Number(p.trim())).filter((p) => Number.isInteger(p) && p > 0);
|
|
13583
13851
|
} catch {
|
|
13584
13852
|
return [];
|
|
13585
13853
|
}
|
|
13586
13854
|
}
|
|
13587
13855
|
function usableCwd(dir) {
|
|
13588
|
-
return dir && (0,
|
|
13856
|
+
return dir && (0, import_fs2.existsSync)(dir) ? dir : void 0;
|
|
13589
13857
|
}
|
|
13590
13858
|
function hostTarget(spaceDir, extraEnv) {
|
|
13591
13859
|
const paths = hostSpacePaths(spaceDir);
|
|
@@ -13651,8 +13919,8 @@ function hostTarget(spaceDir, extraEnv) {
|
|
|
13651
13919
|
});
|
|
13652
13920
|
},
|
|
13653
13921
|
async writeFile(filePath, content) {
|
|
13654
|
-
await
|
|
13655
|
-
await
|
|
13922
|
+
await import_promises2.default.mkdir(import_path4.default.dirname(filePath), { recursive: true });
|
|
13923
|
+
await import_promises2.default.writeFile(filePath, content, "utf-8");
|
|
13656
13924
|
},
|
|
13657
13925
|
async step(cmd, opts) {
|
|
13658
13926
|
const res = await execLogin(cmd, {
|
|
@@ -13867,14 +14135,14 @@ function createEventSink(send, spaceSlug, notify) {
|
|
|
13867
14135
|
}
|
|
13868
14136
|
|
|
13869
14137
|
// ../../shared/all/helpers/card-ai/frpc.ts
|
|
13870
|
-
var
|
|
13871
|
-
var
|
|
13872
|
-
var
|
|
14138
|
+
var import_fs3 = require("fs");
|
|
14139
|
+
var import_promises6 = __toESM(require("fs/promises"));
|
|
14140
|
+
var import_path8 = __toESM(require("path"));
|
|
13873
14141
|
|
|
13874
14142
|
// ../../shared/all/helpers/board-agent-core/host-skills.ts
|
|
13875
|
-
var
|
|
14143
|
+
var import_promises5 = __toESM(require("fs/promises"));
|
|
13876
14144
|
var import_os2 = __toESM(require("os"));
|
|
13877
|
-
var
|
|
14145
|
+
var import_path7 = __toESM(require("path"));
|
|
13878
14146
|
|
|
13879
14147
|
// ../../shared/all/domains/posts.ts
|
|
13880
14148
|
var postDataSort = /* @__PURE__ */ ((postDataSort2) => {
|
|
@@ -14384,17 +14652,17 @@ var skillScopeSchema = external_exports.enum(["global", "repo"]);
|
|
|
14384
14652
|
|
|
14385
14653
|
// ../../shared/all/helpers/board-agent-core/host-deploy.ts
|
|
14386
14654
|
var import_child_process3 = require("child_process");
|
|
14387
|
-
var
|
|
14388
|
-
var
|
|
14655
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
14656
|
+
var import_path6 = __toESM(require("path"));
|
|
14389
14657
|
var import_util5 = require("util");
|
|
14390
14658
|
|
|
14391
14659
|
// ../../shared/all/helpers/board-agent-core/secrets-bridge.ts
|
|
14392
|
-
var
|
|
14660
|
+
var import_promises3 = __toESM(require("fs/promises"));
|
|
14393
14661
|
var import_net2 = __toESM(require("net"));
|
|
14394
|
-
var
|
|
14662
|
+
var import_path5 = __toESM(require("path"));
|
|
14395
14663
|
var SECRET_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14396
14664
|
function secretsSocketPath(spaceDir) {
|
|
14397
|
-
return
|
|
14665
|
+
return import_path5.default.join(hostSpacePaths(spaceDir).state, "secrets.sock");
|
|
14398
14666
|
}
|
|
14399
14667
|
function redactor(values) {
|
|
14400
14668
|
const significant = values.filter((v) => v.length >= 4);
|
|
@@ -14448,8 +14716,8 @@ async function handleRun(request, handlers, reply) {
|
|
|
14448
14716
|
}
|
|
14449
14717
|
async function startSecretsBridge(opts) {
|
|
14450
14718
|
const socketPath = secretsSocketPath(opts.spaceDir);
|
|
14451
|
-
await
|
|
14452
|
-
await
|
|
14719
|
+
await import_promises3.default.mkdir(import_path5.default.dirname(socketPath), { recursive: true });
|
|
14720
|
+
await import_promises3.default.rm(socketPath, { force: true });
|
|
14453
14721
|
const { handlers } = opts;
|
|
14454
14722
|
let queue = Promise.resolve();
|
|
14455
14723
|
let queueDepth = 0;
|
|
@@ -14514,13 +14782,13 @@ async function startSecretsBridge(opts) {
|
|
|
14514
14782
|
resolve2();
|
|
14515
14783
|
});
|
|
14516
14784
|
});
|
|
14517
|
-
await
|
|
14785
|
+
await import_promises3.default.chmod(socketPath, 384).catch(() => void 0);
|
|
14518
14786
|
return {
|
|
14519
14787
|
close: async () => {
|
|
14520
14788
|
for (const conn of live) conn.destroy();
|
|
14521
14789
|
live.clear();
|
|
14522
14790
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
14523
|
-
await
|
|
14791
|
+
await import_promises3.default.rm(socketPath, { force: true });
|
|
14524
14792
|
}
|
|
14525
14793
|
};
|
|
14526
14794
|
}
|
|
@@ -14537,14 +14805,14 @@ var CHECKER_PATH = `${SKILL_DIR}/check-environment.sh`;
|
|
|
14537
14805
|
var CLAUDE_SKILLS_DIR = ".claude/skills";
|
|
14538
14806
|
var CLAUDE_LINK = `${CLAUDE_SKILLS_DIR}/deploy`;
|
|
14539
14807
|
async function ensureClaudeSkillLink(workspace) {
|
|
14540
|
-
const linkPath =
|
|
14808
|
+
const linkPath = import_path6.default.join(workspace, CLAUDE_LINK);
|
|
14541
14809
|
try {
|
|
14542
|
-
await
|
|
14810
|
+
await import_promises4.default.lstat(linkPath);
|
|
14543
14811
|
return;
|
|
14544
14812
|
} catch {
|
|
14545
14813
|
}
|
|
14546
|
-
await
|
|
14547
|
-
await
|
|
14814
|
+
await import_promises4.default.mkdir(import_path6.default.join(workspace, CLAUDE_SKILLS_DIR), { recursive: true });
|
|
14815
|
+
await import_promises4.default.symlink("../../.agents/skills/deploy", linkPath, "dir");
|
|
14548
14816
|
}
|
|
14549
14817
|
var GIT_NO_HELPER = ["-c", "credential.helper="];
|
|
14550
14818
|
var GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
@@ -14589,7 +14857,7 @@ async function gitCatFileBatch(cwd, shas) {
|
|
|
14589
14857
|
return out;
|
|
14590
14858
|
}
|
|
14591
14859
|
async function ensureWorkspaceClone(opts) {
|
|
14592
|
-
const workspace =
|
|
14860
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14593
14861
|
const branch = (opts.mainBranch || "main").replace(/[^\w./-]/g, "");
|
|
14594
14862
|
const url2 = buildCloneUrl(opts.repoUrl, opts.githubToken);
|
|
14595
14863
|
let cloned = false;
|
|
@@ -14597,14 +14865,14 @@ async function ensureWorkspaceClone(opts) {
|
|
|
14597
14865
|
await hostGit(workspace, "rev-parse", "--git-dir");
|
|
14598
14866
|
await hostGit(workspace, "remote", "set-url", "origin", url2);
|
|
14599
14867
|
} catch {
|
|
14600
|
-
const leftovers = await
|
|
14868
|
+
const leftovers = await import_promises4.default.readdir(workspace).catch(() => []);
|
|
14601
14869
|
if (leftovers.includes(".git")) {
|
|
14602
14870
|
throw new Error(
|
|
14603
14871
|
`${workspace} holds a git repository that cannot be read. Remove it and retry.`
|
|
14604
14872
|
);
|
|
14605
14873
|
}
|
|
14606
14874
|
for (const name of leftovers) {
|
|
14607
|
-
await
|
|
14875
|
+
await import_promises4.default.rm(import_path6.default.join(workspace, name), { recursive: true, force: true });
|
|
14608
14876
|
}
|
|
14609
14877
|
await execFileAsync2(
|
|
14610
14878
|
"git",
|
|
@@ -14756,15 +15024,15 @@ function spaceEnv(spaceDir) {
|
|
|
14756
15024
|
};
|
|
14757
15025
|
}
|
|
14758
15026
|
async function readContract(workspace) {
|
|
14759
|
-
const skill = await
|
|
15027
|
+
const skill = await import_promises4.default.readFile(import_path6.default.join(workspace, SKILL_PATH), "utf-8").catch(() => {
|
|
14760
15028
|
throw new Error("The creator wrote no SKILL.md.");
|
|
14761
15029
|
});
|
|
14762
|
-
const checker = await
|
|
15030
|
+
const checker = await import_promises4.default.readFile(import_path6.default.join(workspace, CHECKER_PATH), "utf-8").catch(() => {
|
|
14763
15031
|
throw new Error("The creator wrote no check-environment.sh.");
|
|
14764
15032
|
});
|
|
14765
15033
|
try {
|
|
14766
15034
|
const raw = JSON.parse(
|
|
14767
|
-
await
|
|
15035
|
+
await import_promises4.default.readFile(import_path6.default.join(workspace, VARIABLES_PATH), "utf-8")
|
|
14768
15036
|
);
|
|
14769
15037
|
return {
|
|
14770
15038
|
skill,
|
|
@@ -14781,7 +15049,7 @@ async function generateDeploySkill(opts) {
|
|
|
14781
15049
|
throw new Error("Describe your deployment process first.");
|
|
14782
15050
|
}
|
|
14783
15051
|
const status = await checkDeployStatus(opts);
|
|
14784
|
-
const workspace =
|
|
15052
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14785
15053
|
sendLog({
|
|
14786
15054
|
type: "system",
|
|
14787
15055
|
content: `Resetting workspace to origin/${status.branch}...`
|
|
@@ -14824,7 +15092,7 @@ async function generateDeploySkill(opts) {
|
|
|
14824
15092
|
async function reviseDeploySkill(opts) {
|
|
14825
15093
|
const { feedback, sendLog } = opts;
|
|
14826
15094
|
if (!feedback.trim()) throw new Error("Corrections are empty.");
|
|
14827
|
-
const workspace =
|
|
15095
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14828
15096
|
sendLog({ type: "system", content: "Applying your corrections..." });
|
|
14829
15097
|
const { result } = await spawnAgent(
|
|
14830
15098
|
opts.provider,
|
|
@@ -14852,7 +15120,7 @@ async function loadSkillDraft(opts) {
|
|
|
14852
15120
|
throw new Error("Not a deploy-skill branch.");
|
|
14853
15121
|
}
|
|
14854
15122
|
await checkDeployStatus(opts);
|
|
14855
|
-
const workspace =
|
|
15123
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14856
15124
|
await hostGit(
|
|
14857
15125
|
workspace,
|
|
14858
15126
|
"fetch",
|
|
@@ -14864,7 +15132,7 @@ async function loadSkillDraft(opts) {
|
|
|
14864
15132
|
return readContract(workspace);
|
|
14865
15133
|
}
|
|
14866
15134
|
async function approveDeploySkill(opts) {
|
|
14867
|
-
const workspace =
|
|
15135
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14868
15136
|
await readContract(workspace);
|
|
14869
15137
|
await ensureClaudeSkillLink(workspace);
|
|
14870
15138
|
if (opts.gitName)
|
|
@@ -14907,19 +15175,19 @@ async function runEnvironmentCheck(opts) {
|
|
|
14907
15175
|
"No environment checker on the default branch. Generate the deploy skill first."
|
|
14908
15176
|
);
|
|
14909
15177
|
}
|
|
14910
|
-
const workspace =
|
|
15178
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14911
15179
|
const script = await hostGit(
|
|
14912
15180
|
workspace,
|
|
14913
15181
|
"cat-file",
|
|
14914
15182
|
"-p",
|
|
14915
15183
|
`origin/${status.branch}:${CHECKER_PATH}`
|
|
14916
15184
|
);
|
|
14917
|
-
const tmp =
|
|
14918
|
-
await
|
|
15185
|
+
const tmp = import_path6.default.join(opts.spaceDir, `.env-check.${process.pid}.sh`);
|
|
15186
|
+
await import_promises4.default.writeFile(tmp, script, { mode: 448 });
|
|
14919
15187
|
let ok = true;
|
|
14920
15188
|
let output = "";
|
|
14921
15189
|
try {
|
|
14922
|
-
const scriptArg = tmp.split(
|
|
15190
|
+
const scriptArg = tmp.split(import_path6.default.sep).join("/");
|
|
14923
15191
|
const res = await execLogin(`bash ${JSON.stringify(scriptArg)}`, {
|
|
14924
15192
|
cwd: workspace,
|
|
14925
15193
|
env: { ...process.env, ...spaceEnv(opts.spaceDir) },
|
|
@@ -14928,7 +15196,7 @@ async function runEnvironmentCheck(opts) {
|
|
|
14928
15196
|
output = res.output;
|
|
14929
15197
|
if (res.code !== 0) ok = false;
|
|
14930
15198
|
} finally {
|
|
14931
|
-
await
|
|
15199
|
+
await import_promises4.default.rm(tmp, { force: true });
|
|
14932
15200
|
}
|
|
14933
15201
|
const checks = [];
|
|
14934
15202
|
for (const line of output.split("\n")) {
|
|
@@ -14950,7 +15218,7 @@ async function fixEnvironment(opts) {
|
|
|
14950
15218
|
const before = await runEnvironmentCheck(opts);
|
|
14951
15219
|
if (before.ok) return before;
|
|
14952
15220
|
const status = await checkDeployStatus(opts);
|
|
14953
|
-
const workspace =
|
|
15221
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14954
15222
|
const script = await hostGit(
|
|
14955
15223
|
workspace,
|
|
14956
15224
|
"cat-file",
|
|
@@ -14985,7 +15253,7 @@ async function fixEnvironment(opts) {
|
|
|
14985
15253
|
return runEnvironmentCheck(opts);
|
|
14986
15254
|
}
|
|
14987
15255
|
async function discardDeploySkill(opts) {
|
|
14988
|
-
const workspace =
|
|
15256
|
+
const workspace = import_path6.default.join(opts.spaceDir, "workspace");
|
|
14989
15257
|
await hostGit(workspace, "checkout", "--", ".").catch(() => null);
|
|
14990
15258
|
await hostGit(workspace, "clean", "-fd");
|
|
14991
15259
|
return { success: true };
|
|
@@ -15104,21 +15372,22 @@ var AGENTS_ROOT = ".agents/skills";
|
|
|
15104
15372
|
var CLAUDE_ROOT = ".claude/skills";
|
|
15105
15373
|
var CLI_ROOTS = [CLAUDE_ROOT, ".codex/skills"];
|
|
15106
15374
|
function globalAgentsRoot() {
|
|
15107
|
-
return
|
|
15375
|
+
return import_path7.default.join(import_os2.default.homedir(), ".agents", "skills");
|
|
15108
15376
|
}
|
|
15109
15377
|
var AGENTS_LINK_TARGET = "../.agents/skills";
|
|
15110
15378
|
var BRANCH_PREFIX = "skill/";
|
|
15379
|
+
var MIGRATE_BRANCH = `${BRANCH_PREFIX}migrate`;
|
|
15111
15380
|
var SYMLINK_MODE = "120000";
|
|
15112
15381
|
function summarize(raw, base) {
|
|
15113
15382
|
const { frontmatter, errors } = parseSkill(raw);
|
|
15114
15383
|
return { ...base, description: frontmatter.description, errors };
|
|
15115
15384
|
}
|
|
15116
15385
|
async function rootLinkState(root) {
|
|
15117
|
-
const linkPath =
|
|
15118
|
-
const stat = await
|
|
15386
|
+
const linkPath = import_path7.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15387
|
+
const stat = await import_promises5.default.lstat(linkPath).catch(() => null);
|
|
15119
15388
|
if (!stat) return "missing";
|
|
15120
15389
|
if (!stat.isSymbolicLink()) return "blocked";
|
|
15121
|
-
const target = await
|
|
15390
|
+
const target = await import_promises5.default.readlink(linkPath).catch(() => "");
|
|
15122
15391
|
return target.replace(/\/+$/, "") === AGENTS_LINK_TARGET ? "linked" : "missing";
|
|
15123
15392
|
}
|
|
15124
15393
|
async function globalLinkState() {
|
|
@@ -15128,12 +15397,12 @@ async function globalLinkState() {
|
|
|
15128
15397
|
}
|
|
15129
15398
|
async function listGlobal() {
|
|
15130
15399
|
const root = globalAgentsRoot();
|
|
15131
|
-
const entries = await
|
|
15400
|
+
const entries = await import_promises5.default.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
15132
15401
|
const out = [];
|
|
15133
15402
|
for (const entry of entries) {
|
|
15134
15403
|
if (entry.isFile()) continue;
|
|
15135
|
-
const dir =
|
|
15136
|
-
const raw = await
|
|
15404
|
+
const dir = import_path7.default.join(root, entry.name);
|
|
15405
|
+
const raw = await import_promises5.default.readFile(import_path7.default.join(dir, "SKILL.md"), "utf-8").catch(() => "");
|
|
15137
15406
|
if (!raw) continue;
|
|
15138
15407
|
out.push(
|
|
15139
15408
|
summarize(raw, {
|
|
@@ -15302,7 +15571,7 @@ async function ensureSkillsWorktree(spaceDir, branch) {
|
|
|
15302
15571
|
} catch {
|
|
15303
15572
|
await hostGit(repo, "worktree", "prune").catch(() => {
|
|
15304
15573
|
});
|
|
15305
|
-
await
|
|
15574
|
+
await import_promises5.default.rm(worktree, { recursive: true, force: true });
|
|
15306
15575
|
await hostGit(
|
|
15307
15576
|
repo,
|
|
15308
15577
|
"worktree",
|
|
@@ -15367,29 +15636,34 @@ async function listSkillsImpl(opts) {
|
|
|
15367
15636
|
globalLink,
|
|
15368
15637
|
repoLink: "linked",
|
|
15369
15638
|
repoBranch: "",
|
|
15370
|
-
repoAvailable: false
|
|
15639
|
+
repoAvailable: false,
|
|
15640
|
+
repoMigrationBranch: "",
|
|
15641
|
+
repoMigrationMissing: []
|
|
15371
15642
|
};
|
|
15372
15643
|
}
|
|
15373
15644
|
const ctx = await repoContext(opts);
|
|
15374
15645
|
const repo = await repoSkills(ctx);
|
|
15646
|
+
const migration = openMigration(ctx.liveBlobs, ctx.pending);
|
|
15375
15647
|
return {
|
|
15376
15648
|
global: global2,
|
|
15377
15649
|
repo,
|
|
15378
15650
|
globalLink,
|
|
15379
15651
|
repoLink: repoLinkState(ctx),
|
|
15380
15652
|
repoBranch: ctx.branch,
|
|
15381
|
-
repoAvailable: true
|
|
15653
|
+
repoAvailable: true,
|
|
15654
|
+
repoMigrationBranch: migration.branch,
|
|
15655
|
+
repoMigrationMissing: migration.missing
|
|
15382
15656
|
};
|
|
15383
15657
|
}
|
|
15384
15658
|
async function bundleFilesOnDisk(dir) {
|
|
15385
15659
|
const out = [];
|
|
15386
15660
|
const walk = async (current, prefix, depth) => {
|
|
15387
15661
|
if (depth > 3 || out.length >= 200) return;
|
|
15388
|
-
const entries = await
|
|
15662
|
+
const entries = await import_promises5.default.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
15389
15663
|
for (const entry of entries) {
|
|
15390
15664
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15391
15665
|
if (entry.isDirectory()) {
|
|
15392
|
-
await walk(
|
|
15666
|
+
await walk(import_path7.default.join(current, entry.name), rel, depth + 1);
|
|
15393
15667
|
} else if (rel !== "SKILL.md") {
|
|
15394
15668
|
out.push(rel);
|
|
15395
15669
|
}
|
|
@@ -15401,7 +15675,7 @@ async function bundleFilesOnDisk(dir) {
|
|
|
15401
15675
|
async function readSkillImpl(opts) {
|
|
15402
15676
|
if (opts.scope === "global") {
|
|
15403
15677
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15404
|
-
const content2 = await
|
|
15678
|
+
const content2 = await import_promises5.default.readFile(import_path7.default.join(dir, "SKILL.md"), "utf-8");
|
|
15405
15679
|
const { frontmatter: frontmatter2, errors: errors2 } = parseSkill(content2);
|
|
15406
15680
|
return {
|
|
15407
15681
|
name: opts.name,
|
|
@@ -15436,71 +15710,102 @@ async function readSkillImpl(opts) {
|
|
|
15436
15710
|
};
|
|
15437
15711
|
}
|
|
15438
15712
|
async function moveIntoAgents(from, to) {
|
|
15439
|
-
if (await
|
|
15713
|
+
if (await import_promises5.default.access(to).then(
|
|
15440
15714
|
() => true,
|
|
15441
15715
|
() => false
|
|
15442
15716
|
)) {
|
|
15443
15717
|
throw new Error(
|
|
15444
|
-
`${
|
|
15718
|
+
`${import_path7.default.basename(to)} exists in both roots. Remove one copy, then migrate.`
|
|
15445
15719
|
);
|
|
15446
15720
|
}
|
|
15447
|
-
await
|
|
15721
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(to), { recursive: true });
|
|
15448
15722
|
try {
|
|
15449
|
-
await
|
|
15723
|
+
await import_promises5.default.rename(from, to);
|
|
15450
15724
|
} catch {
|
|
15451
|
-
await
|
|
15452
|
-
await
|
|
15725
|
+
await import_promises5.default.cp(from, to, { recursive: true });
|
|
15726
|
+
await import_promises5.default.rm(from, { recursive: true, force: true });
|
|
15453
15727
|
}
|
|
15454
15728
|
}
|
|
15455
15729
|
async function migrateGlobal() {
|
|
15456
15730
|
const moved = [];
|
|
15457
15731
|
for (const root of CLI_ROOTS) {
|
|
15458
|
-
const dir =
|
|
15459
|
-
const stat = await
|
|
15732
|
+
const dir = import_path7.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15733
|
+
const stat = await import_promises5.default.lstat(dir).catch(() => null);
|
|
15460
15734
|
if (!stat || stat.isSymbolicLink()) continue;
|
|
15461
|
-
const entries = await
|
|
15735
|
+
const entries = await import_promises5.default.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
15462
15736
|
for (const entry of entries) {
|
|
15463
15737
|
if (entry.isFile() || entry.isSymbolicLink()) continue;
|
|
15464
|
-
const src =
|
|
15465
|
-
if (!await
|
|
15738
|
+
const src = import_path7.default.join(dir, entry.name);
|
|
15739
|
+
if (!await import_promises5.default.access(import_path7.default.join(src, "SKILL.md")).then(
|
|
15466
15740
|
() => true,
|
|
15467
15741
|
() => false
|
|
15468
15742
|
)) {
|
|
15469
15743
|
continue;
|
|
15470
15744
|
}
|
|
15471
|
-
await moveIntoAgents(src,
|
|
15745
|
+
await moveIntoAgents(src, import_path7.default.join(globalAgentsRoot(), entry.name));
|
|
15472
15746
|
moved.push(entry.name);
|
|
15473
15747
|
}
|
|
15474
|
-
await
|
|
15748
|
+
await import_promises5.default.rmdir(dir).catch(() => {
|
|
15475
15749
|
});
|
|
15476
15750
|
}
|
|
15477
15751
|
for (const root of CLI_ROOTS) {
|
|
15478
|
-
const linkPath =
|
|
15479
|
-
const after = await
|
|
15480
|
-
if (after?.isSymbolicLink()) await
|
|
15752
|
+
const linkPath = import_path7.default.join(import_os2.default.homedir(), ...root.split("/"));
|
|
15753
|
+
const after = await import_promises5.default.lstat(linkPath).catch(() => null);
|
|
15754
|
+
if (after?.isSymbolicLink()) await import_promises5.default.rm(linkPath, { force: true });
|
|
15481
15755
|
else if (after) {
|
|
15482
15756
|
throw new Error(
|
|
15483
15757
|
`~/${root} still has files that are not skills. Empty it, then migrate.`
|
|
15484
15758
|
);
|
|
15485
15759
|
}
|
|
15486
|
-
await
|
|
15487
|
-
await
|
|
15760
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(linkPath), { recursive: true });
|
|
15761
|
+
await import_promises5.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15488
15762
|
}
|
|
15489
15763
|
return moved;
|
|
15490
15764
|
}
|
|
15765
|
+
var MIGRATE_SUBJECT = /^Migrate \d+ skill\(s\) to /;
|
|
15766
|
+
function migrationCommitsOnly(log) {
|
|
15767
|
+
return log.split("\n").map((line) => line.trim()).filter(Boolean).every((subject) => MIGRATE_SUBJECT.test(subject));
|
|
15768
|
+
}
|
|
15769
|
+
function openMigration(liveBlobs, pending) {
|
|
15770
|
+
const onBranch = pending.find(
|
|
15771
|
+
(entry) => entry.branch === MIGRATE_BRANCH
|
|
15772
|
+
)?.blobs;
|
|
15773
|
+
if (!onBranch) return { branch: "", missing: [] };
|
|
15774
|
+
const missing = /* @__PURE__ */ new Set();
|
|
15775
|
+
for (const stray of strayRepoSkills(liveBlobs)) {
|
|
15776
|
+
const name = stray.slice(stray.lastIndexOf("/") + 1);
|
|
15777
|
+
if (!onBranch.has(`${AGENTS_ROOT}/${name}`)) missing.add(name);
|
|
15778
|
+
}
|
|
15779
|
+
return { branch: MIGRATE_BRANCH, missing: [...missing].sort() };
|
|
15780
|
+
}
|
|
15491
15781
|
async function migrateSkillsImpl(opts) {
|
|
15492
15782
|
if (opts.scope === "global") {
|
|
15493
|
-
return { branch: "", moved: await migrateGlobal() };
|
|
15783
|
+
return { branch: "", moved: await migrateGlobal(), pushed: false };
|
|
15494
15784
|
}
|
|
15495
15785
|
const ctx = await repoContext(opts);
|
|
15496
15786
|
const strays = strayRepoSkills(ctx.liveBlobs);
|
|
15497
15787
|
const linked = ctx.links.get(CLAUDE_ROOT);
|
|
15498
15788
|
if (!strays.length && linked === AGENTS_LINK_TARGET) {
|
|
15499
|
-
return { branch: "", moved: [] };
|
|
15789
|
+
return { branch: "", moved: [], pushed: false };
|
|
15500
15790
|
}
|
|
15501
15791
|
const { branch } = ctx;
|
|
15502
15792
|
const workspace = await ensureSkillsWorktree(opts.spaceDir, branch);
|
|
15503
|
-
const target =
|
|
15793
|
+
const target = MIGRATE_BRANCH;
|
|
15794
|
+
const existing = await hostGit(
|
|
15795
|
+
workspace,
|
|
15796
|
+
"ls-remote",
|
|
15797
|
+
"--heads",
|
|
15798
|
+
"origin",
|
|
15799
|
+
target
|
|
15800
|
+
).catch(() => "");
|
|
15801
|
+
if (existing.trim()) {
|
|
15802
|
+
await hostGit(
|
|
15803
|
+
workspace,
|
|
15804
|
+
"fetch",
|
|
15805
|
+
"origin",
|
|
15806
|
+
`+refs/heads/${target}:refs/remotes/origin/${target}`
|
|
15807
|
+
);
|
|
15808
|
+
}
|
|
15504
15809
|
await hostGit(workspace, "checkout", "-B", target, `origin/${branch}`);
|
|
15505
15810
|
await hostGit(workspace, "clean", "-fd");
|
|
15506
15811
|
if (opts.gitName) {
|
|
@@ -15518,24 +15823,46 @@ async function migrateSkillsImpl(opts) {
|
|
|
15518
15823
|
`${name} exists in both ${AGENTS_ROOT} and ${stray}. Remove one copy, then migrate.`
|
|
15519
15824
|
);
|
|
15520
15825
|
}
|
|
15521
|
-
await
|
|
15826
|
+
await import_promises5.default.mkdir(import_path7.default.join(workspace, AGENTS_ROOT), { recursive: true });
|
|
15522
15827
|
await hostGit(workspace, "mv", stray, dest);
|
|
15523
15828
|
moved.push(name);
|
|
15524
15829
|
}
|
|
15525
|
-
const linkPath =
|
|
15526
|
-
await
|
|
15527
|
-
await
|
|
15830
|
+
const linkPath = import_path7.default.join(workspace, CLAUDE_ROOT);
|
|
15831
|
+
await import_promises5.default.rm(linkPath, { recursive: true, force: true });
|
|
15832
|
+
await import_promises5.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir");
|
|
15528
15833
|
await hostGit(workspace, "add", "-A");
|
|
15529
15834
|
const dirty = await hostGit(workspace, "status", "--porcelain");
|
|
15530
|
-
if (!dirty.trim()) return { branch: "", moved: [] };
|
|
15835
|
+
if (!dirty.trim()) return { branch: "", moved: [], pushed: false };
|
|
15531
15836
|
await hostGit(
|
|
15532
15837
|
workspace,
|
|
15533
15838
|
"commit",
|
|
15534
15839
|
"-m",
|
|
15535
15840
|
`Migrate ${moved.length} skill(s) to ${AGENTS_ROOT} and link ${CLAUDE_ROOT}`
|
|
15536
15841
|
);
|
|
15537
|
-
|
|
15538
|
-
|
|
15842
|
+
if (!existing.trim()) {
|
|
15843
|
+
await hostGit(workspace, "push", "origin", target);
|
|
15844
|
+
return { branch: target, moved, pushed: true };
|
|
15845
|
+
}
|
|
15846
|
+
const [rebuilt, onBranch] = await Promise.all([
|
|
15847
|
+
hostGit(workspace, "rev-parse", "HEAD^{tree}"),
|
|
15848
|
+
hostGit(workspace, "rev-parse", `origin/${target}^{tree}`)
|
|
15849
|
+
]);
|
|
15850
|
+
if (rebuilt.trim() === onBranch.trim()) {
|
|
15851
|
+
return { branch: target, moved, pushed: false };
|
|
15852
|
+
}
|
|
15853
|
+
const log = await hostGit(
|
|
15854
|
+
workspace,
|
|
15855
|
+
"log",
|
|
15856
|
+
"--format=%s",
|
|
15857
|
+
`origin/${branch}..origin/${target}`
|
|
15858
|
+
);
|
|
15859
|
+
if (!migrationCommitsOnly(log)) {
|
|
15860
|
+
throw new Error(
|
|
15861
|
+
`${target} carries commits that are not this migration. Merge or delete that branch, then migrate.`
|
|
15862
|
+
);
|
|
15863
|
+
}
|
|
15864
|
+
await hostGit(workspace, "push", "--force-with-lease", "origin", target);
|
|
15865
|
+
return { branch: target, moved, pushed: true };
|
|
15539
15866
|
}
|
|
15540
15867
|
function assertWritable(name, content) {
|
|
15541
15868
|
const parsed = parseSkill(content);
|
|
@@ -15550,16 +15877,16 @@ function assertWritable(name, content) {
|
|
|
15550
15877
|
}
|
|
15551
15878
|
async function writeSkillFile(opts) {
|
|
15552
15879
|
const dir = resolveSkillDir(opts.root, opts.name);
|
|
15553
|
-
const file =
|
|
15880
|
+
const file = import_path7.default.join(dir, "SKILL.md");
|
|
15554
15881
|
if (opts.keepExisting) {
|
|
15555
|
-
const exists = await
|
|
15882
|
+
const exists = await import_promises5.default.access(file).then(
|
|
15556
15883
|
() => true,
|
|
15557
15884
|
() => false
|
|
15558
15885
|
);
|
|
15559
15886
|
if (exists) return;
|
|
15560
15887
|
}
|
|
15561
|
-
await
|
|
15562
|
-
await
|
|
15888
|
+
await import_promises5.default.mkdir(dir, { recursive: true });
|
|
15889
|
+
await import_promises5.default.writeFile(file, opts.content, "utf-8");
|
|
15563
15890
|
}
|
|
15564
15891
|
async function writeGlobalSkill(opts) {
|
|
15565
15892
|
assertWritable(opts.name, opts.content);
|
|
@@ -15574,14 +15901,14 @@ async function installBundledSkill(opts) {
|
|
|
15574
15901
|
content: opts.content,
|
|
15575
15902
|
keepExisting: true
|
|
15576
15903
|
});
|
|
15577
|
-
await write(
|
|
15904
|
+
await write(import_path7.default.join(home, ...AGENTS_ROOT.split("/")));
|
|
15578
15905
|
for (const root of CLI_ROOTS) {
|
|
15579
|
-
const linkPath =
|
|
15580
|
-
const stat = await
|
|
15906
|
+
const linkPath = import_path7.default.join(home, ...root.split("/"));
|
|
15907
|
+
const stat = await import_promises5.default.lstat(linkPath).catch(() => null);
|
|
15581
15908
|
if (stat?.isSymbolicLink()) continue;
|
|
15582
15909
|
if (!stat) {
|
|
15583
|
-
await
|
|
15584
|
-
await
|
|
15910
|
+
await import_promises5.default.mkdir(import_path7.default.dirname(linkPath), { recursive: true });
|
|
15911
|
+
await import_promises5.default.symlink(AGENTS_LINK_TARGET, linkPath, "dir").catch(() => void 0);
|
|
15585
15912
|
continue;
|
|
15586
15913
|
}
|
|
15587
15914
|
await write(linkPath);
|
|
@@ -15589,7 +15916,7 @@ async function installBundledSkill(opts) {
|
|
|
15589
15916
|
}
|
|
15590
15917
|
async function deleteGlobalSkill(opts) {
|
|
15591
15918
|
const dir = resolveSkillDir(globalAgentsRoot(), opts.name);
|
|
15592
|
-
await
|
|
15919
|
+
await import_promises5.default.rm(dir, { recursive: true, force: true });
|
|
15593
15920
|
}
|
|
15594
15921
|
async function proposeRepoSkillImpl(opts) {
|
|
15595
15922
|
assertWritable(opts.name, opts.content);
|
|
@@ -15627,9 +15954,9 @@ async function proposeRepoSkillImpl(opts) {
|
|
|
15627
15954
|
if (opts.gitEmail) {
|
|
15628
15955
|
await hostGit(workspace, "config", "user.email", opts.gitEmail);
|
|
15629
15956
|
}
|
|
15630
|
-
await
|
|
15631
|
-
await
|
|
15632
|
-
|
|
15957
|
+
await import_promises5.default.mkdir(import_path7.default.join(workspace, dir), { recursive: true });
|
|
15958
|
+
await import_promises5.default.writeFile(
|
|
15959
|
+
import_path7.default.join(workspace, dir, "SKILL.md"),
|
|
15633
15960
|
opts.content,
|
|
15634
15961
|
"utf-8"
|
|
15635
15962
|
);
|
|
@@ -15736,18 +16063,18 @@ function vendoredBinaryName() {
|
|
|
15736
16063
|
async function installFrpcBinary(target) {
|
|
15737
16064
|
const p = frpcPaths(target);
|
|
15738
16065
|
const binaryName = vendoredBinaryName();
|
|
15739
|
-
const src =
|
|
15740
|
-
if (!(0,
|
|
16066
|
+
const src = import_path8.default.join(__dirname, "..", "vendor", binaryName);
|
|
16067
|
+
if (!(0, import_fs3.existsSync)(src)) {
|
|
15741
16068
|
throw new Error(
|
|
15742
16069
|
`Vendored frpc missing (${src}). Run apps/runner/scripts/fetch-frpc.sh.`
|
|
15743
16070
|
);
|
|
15744
16071
|
}
|
|
15745
|
-
const staged = await
|
|
15746
|
-
const source = await
|
|
16072
|
+
const staged = await import_promises6.default.stat(p.binary).catch(() => null);
|
|
16073
|
+
const source = await import_promises6.default.stat(src);
|
|
15747
16074
|
if (staged?.size === source.size) return;
|
|
15748
|
-
await
|
|
15749
|
-
await
|
|
15750
|
-
await
|
|
16075
|
+
await import_promises6.default.mkdir(p.binDir, { recursive: true });
|
|
16076
|
+
await import_promises6.default.copyFile(src, p.binary);
|
|
16077
|
+
await import_promises6.default.chmod(p.binary, 493);
|
|
15751
16078
|
}
|
|
15752
16079
|
var EXPOSE_SKILL_NAME = "share-a-port";
|
|
15753
16080
|
var FENCE = "```";
|
|
@@ -16137,18 +16464,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16137
16464
|
await this.core.ensureSpaceWorkspace();
|
|
16138
16465
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16139
16466
|
if (hasGit) {
|
|
16140
|
-
const { path:
|
|
16467
|
+
const { path: path23 } = await addWorktree(
|
|
16141
16468
|
this.core.target(),
|
|
16142
16469
|
session.taskId,
|
|
16143
16470
|
baseBranch || config.mainBranch || "main",
|
|
16144
16471
|
"card"
|
|
16145
16472
|
);
|
|
16146
|
-
session.workspacePath =
|
|
16473
|
+
session.workspacePath = path23;
|
|
16147
16474
|
await setGitIdentity(
|
|
16148
16475
|
this.core.target(),
|
|
16149
16476
|
config.gitName,
|
|
16150
16477
|
config.gitEmail,
|
|
16151
|
-
|
|
16478
|
+
path23
|
|
16152
16479
|
);
|
|
16153
16480
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
16154
16481
|
} else {
|
|
@@ -16370,18 +16697,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16370
16697
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16371
16698
|
await this.core.ensureSpaceWorkspace();
|
|
16372
16699
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16373
|
-
const { path:
|
|
16700
|
+
const { path: path23 } = await addWorktree(
|
|
16374
16701
|
this.core.target(),
|
|
16375
16702
|
session.taskId,
|
|
16376
16703
|
config.mainBranch || "main",
|
|
16377
16704
|
"card"
|
|
16378
16705
|
);
|
|
16379
|
-
session.workspacePath =
|
|
16706
|
+
session.workspacePath = path23;
|
|
16380
16707
|
await setGitIdentity(
|
|
16381
16708
|
this.core.target(),
|
|
16382
16709
|
config.gitName,
|
|
16383
16710
|
config.gitEmail,
|
|
16384
|
-
|
|
16711
|
+
path23
|
|
16385
16712
|
);
|
|
16386
16713
|
await this.putCard(session);
|
|
16387
16714
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16576,7 +16903,7 @@ ${buildPrompt(prompts_default.implement)}`;
|
|
|
16576
16903
|
};
|
|
16577
16904
|
|
|
16578
16905
|
// ../../shared/all/helpers/terminal-ai/terminal-mode.ts
|
|
16579
|
-
var
|
|
16906
|
+
var import_crypto3 = require("crypto");
|
|
16580
16907
|
|
|
16581
16908
|
// ../../shared/all/helpers/board-agent-core/boardTerminal.ts
|
|
16582
16909
|
function boardTerminalKey(spaceSlug) {
|
|
@@ -16587,95 +16914,6 @@ function isCardPostId(id) {
|
|
|
16587
16914
|
return UUID_RE.test(id);
|
|
16588
16915
|
}
|
|
16589
16916
|
|
|
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
16917
|
// ../../shared/all/helpers/terminal-ai/agentState.ts
|
|
16680
16918
|
var STATES = /* @__PURE__ */ new Set(["working", "idle", "blocked"]);
|
|
16681
16919
|
var AGENT_STATE_CHANNEL = "agent-state";
|
|
@@ -16720,6 +16958,189 @@ function drainStateMessages(buffer) {
|
|
|
16720
16958
|
return { states, rest: [...lines.slice(consumed), partial].join("\n") };
|
|
16721
16959
|
}
|
|
16722
16960
|
|
|
16961
|
+
// ../../shared/all/helpers/terminal-ai/digest.ts
|
|
16962
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
16963
|
+
var import_promises8 = require("fs/promises");
|
|
16964
|
+
var import_path10 = __toESM(require("path"));
|
|
16965
|
+
|
|
16966
|
+
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
16967
|
+
var import_crypto = __toESM(require("crypto"));
|
|
16968
|
+
var import_fs4 = require("fs");
|
|
16969
|
+
var import_promises7 = require("fs/promises");
|
|
16970
|
+
var import_os3 = __toESM(require("os"));
|
|
16971
|
+
var import_path9 = __toESM(require("path"));
|
|
16972
|
+
var MARKETPLACE_NAME = "factiii-runner";
|
|
16973
|
+
var PLUGIN_ID = `factiii@${MARKETPLACE_NAME}`;
|
|
16974
|
+
var SECRETS_BIN = "factiii-secrets";
|
|
16975
|
+
function candidates() {
|
|
16976
|
+
return [
|
|
16977
|
+
import_path9.default.join(__dirname, "..", "plugin"),
|
|
16978
|
+
import_path9.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
16979
|
+
import_path9.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
16980
|
+
];
|
|
16981
|
+
}
|
|
16982
|
+
function pluginRoot() {
|
|
16983
|
+
for (const dir of candidates()) {
|
|
16984
|
+
if ((0, import_fs4.existsSync)(import_path9.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
16985
|
+
return dir;
|
|
16986
|
+
}
|
|
16987
|
+
}
|
|
16988
|
+
return null;
|
|
16989
|
+
}
|
|
16990
|
+
function renderedPluginRoot() {
|
|
16991
|
+
return import_path9.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
16992
|
+
}
|
|
16993
|
+
async function runnerVersion(source) {
|
|
16994
|
+
const pkg = import_path9.default.join(source, "..", "package.json");
|
|
16995
|
+
const raw = await (0, import_promises7.readFile)(pkg, "utf8").catch(() => null);
|
|
16996
|
+
if (!raw) return FALLBACK_VERSION;
|
|
16997
|
+
try {
|
|
16998
|
+
const { version } = JSON.parse(raw);
|
|
16999
|
+
return version || FALLBACK_VERSION;
|
|
17000
|
+
} catch {
|
|
17001
|
+
return FALLBACK_VERSION;
|
|
17002
|
+
}
|
|
17003
|
+
}
|
|
17004
|
+
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
17005
|
+
async function treeFiles(dir) {
|
|
17006
|
+
const entries = await (0, import_promises7.readdir)(dir, { withFileTypes: true });
|
|
17007
|
+
const files = [];
|
|
17008
|
+
for (const entry of entries) {
|
|
17009
|
+
const full = import_path9.default.join(dir, entry.name);
|
|
17010
|
+
if (entry.isDirectory()) files.push(...await treeFiles(full));
|
|
17011
|
+
else if (entry.isFile()) files.push(full);
|
|
17012
|
+
}
|
|
17013
|
+
return files.sort();
|
|
17014
|
+
}
|
|
17015
|
+
async function fingerprint(source, url2, version = "") {
|
|
17016
|
+
const hash = import_crypto.default.createHash("sha256").update(url2).update(version);
|
|
17017
|
+
for (const file of await treeFiles(source)) {
|
|
17018
|
+
hash.update(import_path9.default.relative(source, file));
|
|
17019
|
+
hash.update(await (0, import_promises7.readFile)(file));
|
|
17020
|
+
}
|
|
17021
|
+
return hash.digest("hex");
|
|
17022
|
+
}
|
|
17023
|
+
function stampPath(dest) {
|
|
17024
|
+
return `${dest}.stamp`;
|
|
17025
|
+
}
|
|
17026
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
17027
|
+
function renderPlugin(boardMcpUrl) {
|
|
17028
|
+
const running = inFlight.get(boardMcpUrl);
|
|
17029
|
+
if (running) return running;
|
|
17030
|
+
const started = renderPluginImpl(boardMcpUrl).finally(
|
|
17031
|
+
() => inFlight.delete(boardMcpUrl)
|
|
17032
|
+
);
|
|
17033
|
+
inFlight.set(boardMcpUrl, started);
|
|
17034
|
+
return started;
|
|
17035
|
+
}
|
|
17036
|
+
async function renderPluginImpl(boardMcpUrl) {
|
|
17037
|
+
const source = pluginRoot();
|
|
17038
|
+
if (!source) return null;
|
|
17039
|
+
const dest = renderedPluginRoot();
|
|
17040
|
+
const version = await runnerVersion(source);
|
|
17041
|
+
const want = await fingerprint(source, boardMcpUrl, version);
|
|
17042
|
+
const have = await (0, import_promises7.readFile)(stampPath(dest), "utf8").catch(() => null);
|
|
17043
|
+
if (have === want) return dest;
|
|
17044
|
+
await (0, import_promises7.mkdir)(import_path9.default.dirname(dest), { recursive: true });
|
|
17045
|
+
await (0, import_promises7.cp)(source, dest, { recursive: true, force: true });
|
|
17046
|
+
await (0, import_promises7.writeFile)(
|
|
17047
|
+
import_path9.default.join(dest, "factiii", ".mcp.json"),
|
|
17048
|
+
`${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
17049
|
+
`
|
|
17050
|
+
);
|
|
17051
|
+
await stampVersion(dest, version);
|
|
17052
|
+
await (0, import_promises7.writeFile)(stampPath(dest), want);
|
|
17053
|
+
return dest;
|
|
17054
|
+
}
|
|
17055
|
+
async function stampVersion(dest, version) {
|
|
17056
|
+
const manifest = import_path9.default.join(dest, "factiii", ".claude-plugin", "plugin.json");
|
|
17057
|
+
const raw = await (0, import_promises7.readFile)(manifest, "utf8").catch(() => null);
|
|
17058
|
+
if (!raw) return;
|
|
17059
|
+
const parsed = JSON.parse(raw);
|
|
17060
|
+
await (0, import_promises7.writeFile)(
|
|
17061
|
+
manifest,
|
|
17062
|
+
`${JSON.stringify({ ...parsed, version }, null, 2)}
|
|
17063
|
+
`
|
|
17064
|
+
);
|
|
17065
|
+
}
|
|
17066
|
+
function boardMcpConfig(url2) {
|
|
17067
|
+
return {
|
|
17068
|
+
mcpServers: {
|
|
17069
|
+
"factiii-board": {
|
|
17070
|
+
type: "http",
|
|
17071
|
+
url: url2,
|
|
17072
|
+
bearer_token_env_var: BOARD_MCP_TOKEN_ENV,
|
|
17073
|
+
// Written literally, for claude to expand at connect time. Not a
|
|
17074
|
+
// template string: the `${…}` has to survive into the file.
|
|
17075
|
+
headers: { Authorization: "Bearer ${" + BOARD_MCP_TOKEN_ENV + "}" }
|
|
17076
|
+
}
|
|
17077
|
+
}
|
|
17078
|
+
};
|
|
17079
|
+
}
|
|
17080
|
+
|
|
17081
|
+
// ../../shared/all/helpers/terminal-ai/initScript.ts
|
|
17082
|
+
function terminalPrelude(cwd, binDir) {
|
|
17083
|
+
return [
|
|
17084
|
+
// cd in-script, falling back to $HOME. Must match the agent's worktree:
|
|
17085
|
+
// the CLIs key transcripts by cwd, so --resume needs it.
|
|
17086
|
+
`cd ${cwd} 2>/dev/null || cd "$HOME" 2>/dev/null || cd /`,
|
|
17087
|
+
// Prepended here rather than via the session env, so the login shell's
|
|
17088
|
+
// own PATH survives.
|
|
17089
|
+
`export PATH="${binDir}:$PATH"`,
|
|
17090
|
+
`export PS1='\\[\\e[1;32m\\]user@bare\\[\\e[0m\\]:\\w\\$ '`
|
|
17091
|
+
].join("; ");
|
|
17092
|
+
}
|
|
17093
|
+
var SAFE_SESSION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
17094
|
+
function cardBriefPrompt(postId) {
|
|
17095
|
+
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.`;
|
|
17096
|
+
}
|
|
17097
|
+
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
17098
|
+
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.";
|
|
17099
|
+
function terminalInitScript(cwd, binDir, resumeAgentSessionId, provider, cardPostId) {
|
|
17100
|
+
const prelude = terminalPrelude(cwd, binDir);
|
|
17101
|
+
const shell = `exec bash --norc -i`;
|
|
17102
|
+
if (!resumeAgentSessionId || !SAFE_SESSION_ID.test(resumeAgentSessionId)) {
|
|
17103
|
+
if (!cardPostId) return `${prelude}; ${shell}`;
|
|
17104
|
+
const prompt2 = cardBriefPrompt(cardPostId);
|
|
17105
|
+
const start = provider === "codex" ? `codex '${prompt2}'` : `claude ${CLAUDE_PERMISSION_MODE} '${prompt2}'`;
|
|
17106
|
+
return `${prelude}; ${start}; ${shell}`;
|
|
17107
|
+
}
|
|
17108
|
+
const resume = provider === "codex" ? `codex resume ${resumeAgentSessionId} '${RESUME_SEED}'` : `claude ${CLAUDE_PERMISSION_MODE} --resume ${resumeAgentSessionId} '${RESUME_SEED}'`;
|
|
17109
|
+
return `${prelude}; ${resume}; ${shell}`;
|
|
17110
|
+
}
|
|
17111
|
+
|
|
17112
|
+
// ../../shared/all/helpers/terminal-ai/digest.ts
|
|
17113
|
+
var TERMINAL_FP_OPTION = "@factiii-fp";
|
|
17114
|
+
var CWD_PLACEHOLDER = "/__cwd__";
|
|
17115
|
+
var VOLATILE_ENV_KEYS = /* @__PURE__ */ new Set(["FACTIII_PORTS"]);
|
|
17116
|
+
var PLUGIN_CACHE_MS = 3e4;
|
|
17117
|
+
var pluginCache = null;
|
|
17118
|
+
async function pluginContribution() {
|
|
17119
|
+
const now = Date.now();
|
|
17120
|
+
if (pluginCache && now - pluginCache.at < PLUGIN_CACHE_MS) {
|
|
17121
|
+
return pluginCache.value;
|
|
17122
|
+
}
|
|
17123
|
+
const source = pluginRoot();
|
|
17124
|
+
const shipped = source ? await fingerprint(source, "", "").catch(() => "") : "";
|
|
17125
|
+
const mcp = await (0, import_promises8.readFile)(
|
|
17126
|
+
import_path10.default.join(renderedPluginRoot(), "factiii", ".mcp.json"),
|
|
17127
|
+
"utf-8"
|
|
17128
|
+
).catch(() => "");
|
|
17129
|
+
const value2 = `${shipped}|${mcp}`;
|
|
17130
|
+
pluginCache = { at: now, value: value2 };
|
|
17131
|
+
return value2;
|
|
17132
|
+
}
|
|
17133
|
+
async function terminalDigest(target) {
|
|
17134
|
+
const hash = import_crypto2.default.createHash("sha256").update(TMUX_CONF).update(terminalPrelude(CWD_PLACEHOLDER, target.paths.bin));
|
|
17135
|
+
const env = target.stateEnv();
|
|
17136
|
+
for (const key of Object.keys(env).sort()) {
|
|
17137
|
+
if (VOLATILE_ENV_KEYS.has(key)) continue;
|
|
17138
|
+
hash.update(`${key}=${env[key]}`);
|
|
17139
|
+
}
|
|
17140
|
+
hash.update(await pluginContribution());
|
|
17141
|
+
return hash.digest("hex");
|
|
17142
|
+
}
|
|
17143
|
+
|
|
16723
17144
|
// ../../shared/all/helpers/terminal-ai/processes.ts
|
|
16724
17145
|
var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
|
|
16725
17146
|
var PS_FIELDS = 5;
|
|
@@ -16889,19 +17310,306 @@ function parseSsListen(raw) {
|
|
|
16889
17310
|
use.set(pid, found);
|
|
16890
17311
|
}
|
|
16891
17312
|
}
|
|
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
|
-
}
|
|
17313
|
+
return use;
|
|
17314
|
+
}
|
|
17315
|
+
async function socketsFor(target, pids) {
|
|
17316
|
+
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
17317
|
+
const lsof = await target.run(["lsof", "-a", "-p", pids.join(","), "-nP", "-iTCP", "-F", "pnT"]).catch((err) => String(err.stdout ?? ""));
|
|
17318
|
+
if (lsof.trim()) return parseLsofSockets(lsof);
|
|
17319
|
+
const ss = await target.run(["ss", "-ltnpH"]).catch(() => "");
|
|
17320
|
+
if (!ss.trim()) return /* @__PURE__ */ new Map();
|
|
17321
|
+
const all = parseSsListen(ss);
|
|
17322
|
+
const wanted = new Set(pids);
|
|
17323
|
+
for (const pid of all.keys()) if (!wanted.has(pid)) all.delete(pid);
|
|
17324
|
+
return all;
|
|
17325
|
+
}
|
|
17326
|
+
|
|
17327
|
+
// ../../shared/all/helpers/terminal-ai/tabNames.ts
|
|
17328
|
+
var TAB_LIST_FORMAT = "#{session_name}|#{pane_current_command}|#{host}|#{@factiii-fp}|#{pane_dead}|#{pane_title}";
|
|
17329
|
+
var SHELL_COMMANDS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "fish"]);
|
|
17330
|
+
var tabName = (cmd, host, title) => {
|
|
17331
|
+
const command = cmd.replace(/\.exe$/, "") || "bash";
|
|
17332
|
+
if (!title || title === host || SHELL_COMMANDS.has(command)) return command;
|
|
17333
|
+
return title;
|
|
17334
|
+
};
|
|
17335
|
+
var parseTerminalTabs = (stdout, prefix) => {
|
|
17336
|
+
const byId = /* @__PURE__ */ new Map();
|
|
17337
|
+
for (const raw of stdout.split("\n")) {
|
|
17338
|
+
const line = raw.trim();
|
|
17339
|
+
if (!line.startsWith(prefix)) continue;
|
|
17340
|
+
const parts2 = line.slice(prefix.length).split("|");
|
|
17341
|
+
if (parts2.length < 6) continue;
|
|
17342
|
+
const id = parts2[0].trim();
|
|
17343
|
+
if (!id || byId.has(id)) continue;
|
|
17344
|
+
byId.set(id, {
|
|
17345
|
+
id,
|
|
17346
|
+
name: tabName(
|
|
17347
|
+
parts2[1].trim(),
|
|
17348
|
+
parts2[2].trim(),
|
|
17349
|
+
parts2.slice(5).join("|").trim()
|
|
17350
|
+
),
|
|
17351
|
+
fp: parts2[3].trim(),
|
|
17352
|
+
dead: parts2[4].trim() === "1"
|
|
17353
|
+
});
|
|
17354
|
+
}
|
|
17355
|
+
return Array.from(byId.values()).sort(
|
|
17356
|
+
(a, b) => (parseInt(a.id, 10) || 0) - (parseInt(b.id, 10) || 0)
|
|
17357
|
+
);
|
|
17358
|
+
};
|
|
17359
|
+
|
|
17360
|
+
// ../../shared/all/helpers/wireChunk.ts
|
|
17361
|
+
var MAX_INLINE_BYTES = 14e3;
|
|
17362
|
+
var chunkCounter = 0;
|
|
17363
|
+
function nextChunkId() {
|
|
17364
|
+
return `c-${++chunkCounter}-${Date.now()}`;
|
|
17365
|
+
}
|
|
17366
|
+
function chunkWire(wire) {
|
|
17367
|
+
if (wire.length <= MAX_INLINE_BYTES) return [wire];
|
|
17368
|
+
const id = nextChunkId();
|
|
17369
|
+
const total = Math.ceil(wire.length / MAX_INLINE_BYTES);
|
|
17370
|
+
const frames = [];
|
|
17371
|
+
for (let i = 0; i < total; i++) {
|
|
17372
|
+
const start = i * MAX_INLINE_BYTES;
|
|
17373
|
+
const slice = wire.slice(start, start + MAX_INLINE_BYTES);
|
|
17374
|
+
const frame = { __c: id, __i: i, __n: total, d: slice };
|
|
17375
|
+
frames.push(JSON.stringify(frame));
|
|
17376
|
+
}
|
|
17377
|
+
return frames;
|
|
17378
|
+
}
|
|
17379
|
+
function makeChunkReassembler() {
|
|
17380
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
17381
|
+
return {
|
|
17382
|
+
feed(raw) {
|
|
17383
|
+
if (!raw.startsWith('{"__c"')) return raw;
|
|
17384
|
+
let frame;
|
|
17385
|
+
try {
|
|
17386
|
+
frame = JSON.parse(raw);
|
|
17387
|
+
} catch {
|
|
17388
|
+
return raw;
|
|
17389
|
+
}
|
|
17390
|
+
if (typeof frame.__c !== "string" || typeof frame.__i !== "number" || typeof frame.__n !== "number" || typeof frame.d !== "string") {
|
|
17391
|
+
return raw;
|
|
17392
|
+
}
|
|
17393
|
+
let buf = buffers.get(frame.__c);
|
|
17394
|
+
if (!buf) {
|
|
17395
|
+
buf = { received: new Array(frame.__n), total: frame.__n };
|
|
17396
|
+
buffers.set(frame.__c, buf);
|
|
17397
|
+
}
|
|
17398
|
+
buf.received[frame.__i] = frame.d;
|
|
17399
|
+
for (let i = 0; i < buf.total; i++) {
|
|
17400
|
+
if (buf.received[i] === void 0) return null;
|
|
17401
|
+
}
|
|
17402
|
+
buffers.delete(frame.__c);
|
|
17403
|
+
return buf.received.join("");
|
|
17404
|
+
}
|
|
17405
|
+
};
|
|
17406
|
+
}
|
|
17407
|
+
|
|
17408
|
+
// ../../shared/all/helpers/terminal-ai/terminal.ts
|
|
17409
|
+
function tmuxName(terminalKey) {
|
|
17410
|
+
return `terminal-${safeTmuxName(terminalKey)}`;
|
|
17411
|
+
}
|
|
17412
|
+
async function restartTerminalSession(opts) {
|
|
17413
|
+
const { target, terminalKey, cwd } = opts;
|
|
17414
|
+
await respawnTmuxPane({
|
|
17415
|
+
target,
|
|
17416
|
+
session: tmuxName(terminalKey),
|
|
17417
|
+
argv: ["bash", "-c", terminalInitScript(cwd, target.paths.bin)],
|
|
17418
|
+
cwd,
|
|
17419
|
+
// Rebuilt rather than inherited: picking up an env the session was created
|
|
17420
|
+
// too early to see is half the reason to restart at all.
|
|
17421
|
+
env: target.stateEnv()
|
|
17422
|
+
}).catch(() => {
|
|
17423
|
+
});
|
|
17424
|
+
await terminalDigest(target).then(
|
|
17425
|
+
(digest) => setTmuxSessionOption(
|
|
17426
|
+
target,
|
|
17427
|
+
tmuxName(terminalKey),
|
|
17428
|
+
TERMINAL_FP_OPTION,
|
|
17429
|
+
digest
|
|
17430
|
+
)
|
|
17431
|
+
).catch(() => {
|
|
17432
|
+
});
|
|
17433
|
+
}
|
|
17434
|
+
var TerminalManager = class {
|
|
17435
|
+
constructor(send, getCardTarget, getCardWorkdir) {
|
|
17436
|
+
this.send = send;
|
|
17437
|
+
this.getCardTarget = getCardTarget;
|
|
17438
|
+
this.getCardWorkdir = getCardWorkdir;
|
|
17439
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
17440
|
+
}
|
|
17441
|
+
handle(msg) {
|
|
17442
|
+
switch (msg.op) {
|
|
17443
|
+
case "open":
|
|
17444
|
+
void this.open(msg);
|
|
17445
|
+
return;
|
|
17446
|
+
case "stdin":
|
|
17447
|
+
this.stdin(msg.sessionId, msg.data);
|
|
17448
|
+
return;
|
|
17449
|
+
case "resize":
|
|
17450
|
+
void this.resize(msg.sessionId, msg.cols, msg.rows);
|
|
17451
|
+
return;
|
|
17452
|
+
case "close":
|
|
17453
|
+
this.close(msg.sessionId);
|
|
17454
|
+
return;
|
|
17455
|
+
case "kill":
|
|
17456
|
+
void this.killTmuxSession(msg.postId, msg.terminalKey);
|
|
17457
|
+
return;
|
|
17458
|
+
case "restart":
|
|
17459
|
+
void this.restart(msg.postId, msg.terminalKey);
|
|
17460
|
+
return;
|
|
17461
|
+
}
|
|
17462
|
+
}
|
|
17463
|
+
async open(msg) {
|
|
17464
|
+
if (this.sessions.has(msg.sessionId)) {
|
|
17465
|
+
this.emitError(msg.sessionId, "Session already open.");
|
|
17466
|
+
return;
|
|
17467
|
+
}
|
|
17468
|
+
const target = this.getCardTarget(msg.postId);
|
|
17469
|
+
if (!target) {
|
|
17470
|
+
this.emitError(
|
|
17471
|
+
msg.sessionId,
|
|
17472
|
+
`No active card session for postId=${msg.postId}.`
|
|
17473
|
+
);
|
|
17474
|
+
return;
|
|
17475
|
+
}
|
|
17476
|
+
const tmuxSession = tmuxName(msg.terminalKey);
|
|
17477
|
+
const cardPostId = msg.briefCard && !msg.resumeAgentSessionId && isCardPostId(msg.postId) ? msg.postId : void 0;
|
|
17478
|
+
const init = this.buildInit(target, msg.postId, {
|
|
17479
|
+
resumeAgentSessionId: msg.resumeAgentSessionId,
|
|
17480
|
+
provider: msg.resumeProvider,
|
|
17481
|
+
cardPostId
|
|
17482
|
+
});
|
|
17483
|
+
const creating = !await tmuxSessionExists(target, tmuxSession);
|
|
17484
|
+
try {
|
|
17485
|
+
const session = await startTmuxSession({
|
|
17486
|
+
target,
|
|
17487
|
+
session: tmuxSession,
|
|
17488
|
+
// Attach if it exists, else create. This is what makes the shell
|
|
17489
|
+
// survive modal close/reopen: the server holds it, we just view.
|
|
17490
|
+
attachExisting: true,
|
|
17491
|
+
size: { cols: msg.cols, rows: msg.rows },
|
|
17492
|
+
// Without these, `claude` in the shell reads the default ~/.claude and
|
|
17493
|
+
// asks to sign in, while the agent spawns (which pass agentEnv) don't.
|
|
17494
|
+
env: target.stateEnv(),
|
|
17495
|
+
argv: init.argv
|
|
17496
|
+
});
|
|
17497
|
+
this.sessions.set(msg.sessionId, session);
|
|
17498
|
+
if (creating) {
|
|
17499
|
+
void resurrectSave(target);
|
|
17500
|
+
void terminalDigest(target).then(
|
|
17501
|
+
(digest) => setTmuxSessionOption(
|
|
17502
|
+
target,
|
|
17503
|
+
tmuxSession,
|
|
17504
|
+
TERMINAL_FP_OPTION,
|
|
17505
|
+
digest
|
|
17506
|
+
)
|
|
17507
|
+
).catch(() => {
|
|
17508
|
+
});
|
|
17509
|
+
}
|
|
17510
|
+
session.onData(
|
|
17511
|
+
(data) => this.emit({
|
|
17512
|
+
type: "terminal",
|
|
17513
|
+
op: "stdout",
|
|
17514
|
+
sessionId: msg.sessionId,
|
|
17515
|
+
data: data.toString("base64")
|
|
17516
|
+
})
|
|
17517
|
+
);
|
|
17518
|
+
session.onError((err) => {
|
|
17519
|
+
this.cleanup(msg.sessionId);
|
|
17520
|
+
this.emitError(msg.sessionId, err.message);
|
|
17521
|
+
});
|
|
17522
|
+
session.onClose((code) => {
|
|
17523
|
+
if (!this.sessions.has(msg.sessionId)) return;
|
|
17524
|
+
this.cleanup(msg.sessionId);
|
|
17525
|
+
this.emit({
|
|
17526
|
+
type: "terminal",
|
|
17527
|
+
op: "exit",
|
|
17528
|
+
sessionId: msg.sessionId,
|
|
17529
|
+
code
|
|
17530
|
+
});
|
|
17531
|
+
});
|
|
17532
|
+
this.emit({ type: "terminal", op: "opened", sessionId: msg.sessionId });
|
|
17533
|
+
} catch (err) {
|
|
17534
|
+
this.emitError(
|
|
17535
|
+
msg.sessionId,
|
|
17536
|
+
err instanceof Error ? err.message : String(err)
|
|
17537
|
+
);
|
|
17538
|
+
}
|
|
17539
|
+
}
|
|
17540
|
+
/** What tmux runs for a tab, and where. One builder so `open` and `restart`
|
|
17541
|
+
* cannot drift. */
|
|
17542
|
+
buildInit(target, postId, agent) {
|
|
17543
|
+
const cwd = this.getCardWorkdir(postId) || target.paths.workspace;
|
|
17544
|
+
return {
|
|
17545
|
+
cwd,
|
|
17546
|
+
argv: [
|
|
17547
|
+
"bash",
|
|
17548
|
+
"-c",
|
|
17549
|
+
terminalInitScript(
|
|
17550
|
+
cwd,
|
|
17551
|
+
target.paths.bin,
|
|
17552
|
+
agent?.resumeAgentSessionId,
|
|
17553
|
+
agent?.provider,
|
|
17554
|
+
agent?.cardPostId
|
|
17555
|
+
)
|
|
17556
|
+
]
|
|
17557
|
+
};
|
|
17558
|
+
}
|
|
17559
|
+
/** Restart the tab's process while keeping the tab. */
|
|
17560
|
+
async restart(postId, terminalKey) {
|
|
17561
|
+
const target = this.getCardTarget(postId);
|
|
17562
|
+
if (!target) return;
|
|
17563
|
+
await restartTerminalSession({
|
|
17564
|
+
target,
|
|
17565
|
+
terminalKey,
|
|
17566
|
+
cwd: this.getCardWorkdir(postId) || target.paths.workspace
|
|
17567
|
+
});
|
|
17568
|
+
}
|
|
17569
|
+
stdin(sessionId, b64) {
|
|
17570
|
+
this.sessions.get(sessionId)?.write(Buffer.from(b64, "base64"));
|
|
17571
|
+
}
|
|
17572
|
+
async resize(sessionId, cols, rows) {
|
|
17573
|
+
const session = this.sessions.get(sessionId);
|
|
17574
|
+
if (!session) return;
|
|
17575
|
+
if (!Number.isInteger(cols) || !Number.isInteger(rows)) return;
|
|
17576
|
+
if (cols < 1 || rows < 1) return;
|
|
17577
|
+
await session.resize(cols, rows).catch(() => {
|
|
17578
|
+
});
|
|
17579
|
+
}
|
|
17580
|
+
close(sessionId) {
|
|
17581
|
+
const session = this.sessions.get(sessionId);
|
|
17582
|
+
if (!session) return;
|
|
17583
|
+
this.sessions.delete(sessionId);
|
|
17584
|
+
session.detach();
|
|
17585
|
+
}
|
|
17586
|
+
/** Tear down the tmux session for a tab. Used when the user explicitly
|
|
17587
|
+
* removes a tab (the X button), not on transient detach. */
|
|
17588
|
+
async killTmuxSession(postId, terminalKey) {
|
|
17589
|
+
const target = this.getCardTarget(postId);
|
|
17590
|
+
if (!target) return;
|
|
17591
|
+
await target.sh(
|
|
17592
|
+
`${tmuxCli(target)} kill-session -t ${tmuxName(terminalKey)} 2>/dev/null || true`
|
|
17593
|
+
).catch(() => {
|
|
17594
|
+
});
|
|
17595
|
+
await resurrectSave(target);
|
|
17596
|
+
}
|
|
17597
|
+
destroyAll() {
|
|
17598
|
+
for (const sessionId of [...this.sessions.keys()]) {
|
|
17599
|
+
this.close(sessionId);
|
|
17600
|
+
}
|
|
17601
|
+
}
|
|
17602
|
+
cleanup(sessionId) {
|
|
17603
|
+
this.sessions.delete(sessionId);
|
|
17604
|
+
}
|
|
17605
|
+
emitError(sessionId, message) {
|
|
17606
|
+
this.emit({ type: "terminal", op: "error", sessionId, message });
|
|
17607
|
+
}
|
|
17608
|
+
emit(msg) {
|
|
17609
|
+
const wire = JSON.stringify(msg);
|
|
17610
|
+
for (const frame of chunkWire(wire)) this.send(frame);
|
|
17611
|
+
}
|
|
17612
|
+
};
|
|
16905
17613
|
|
|
16906
17614
|
// ../../shared/all/helpers/terminal-ai/watcher.ts
|
|
16907
17615
|
var PRUNED_DIRS = [
|
|
@@ -16928,12 +17636,12 @@ var FLUSH_MS = 120;
|
|
|
16928
17636
|
var SELF_WRITE_MS = 2e3;
|
|
16929
17637
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16930
17638
|
var importFs = new Function("s", "return import(s)");
|
|
16931
|
-
async function resolveReal(
|
|
17639
|
+
async function resolveReal(path23) {
|
|
16932
17640
|
try {
|
|
16933
17641
|
const { realpath } = await importFs("fs/promises");
|
|
16934
|
-
return await realpath(
|
|
17642
|
+
return await realpath(path23);
|
|
16935
17643
|
} catch {
|
|
16936
|
-
return
|
|
17644
|
+
return path23;
|
|
16937
17645
|
}
|
|
16938
17646
|
}
|
|
16939
17647
|
var loadSubscribe = null;
|
|
@@ -17047,19 +17755,19 @@ var WorkspaceWatcher = class {
|
|
|
17047
17755
|
}
|
|
17048
17756
|
record(kind, relPath) {
|
|
17049
17757
|
if (this.closed) return;
|
|
17050
|
-
const
|
|
17051
|
-
const expiry = this.selfWrites.get(
|
|
17758
|
+
const path23 = "/" + relPath;
|
|
17759
|
+
const expiry = this.selfWrites.get(path23);
|
|
17052
17760
|
if (expiry !== void 0) {
|
|
17053
17761
|
if (expiry > Date.now()) return;
|
|
17054
|
-
this.selfWrites.delete(
|
|
17762
|
+
this.selfWrites.delete(path23);
|
|
17055
17763
|
}
|
|
17056
17764
|
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(
|
|
17765
|
+
added.delete(path23);
|
|
17766
|
+
changed.delete(path23);
|
|
17767
|
+
removed.delete(path23);
|
|
17768
|
+
if (kind === "added") added.add(path23);
|
|
17769
|
+
else if (kind === "changed") changed.add(path23);
|
|
17770
|
+
else removed.add(path23);
|
|
17063
17771
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
17064
17772
|
this.pending = { ...emptyPending(), resync: true };
|
|
17065
17773
|
}
|
|
@@ -17078,8 +17786,8 @@ var WorkspaceWatcher = class {
|
|
|
17078
17786
|
this.pending = emptyPending();
|
|
17079
17787
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
17080
17788
|
const now = Date.now();
|
|
17081
|
-
for (const [
|
|
17082
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
17789
|
+
for (const [path23, expiry] of this.selfWrites) {
|
|
17790
|
+
if (expiry <= now) this.selfWrites.delete(path23);
|
|
17083
17791
|
}
|
|
17084
17792
|
this.emit({
|
|
17085
17793
|
added: resync ? [] : [...added],
|
|
@@ -17283,8 +17991,8 @@ var TerminalModeEngine = class {
|
|
|
17283
17991
|
/** Suppress the change event our own write is about to produce, so a save
|
|
17284
17992
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
17285
17993
|
* the watcher emits, since callers spell the path either way. */
|
|
17286
|
-
expectWrite(postId,
|
|
17287
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17994
|
+
expectWrite(postId, path23) {
|
|
17995
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path23));
|
|
17288
17996
|
}
|
|
17289
17997
|
// ── Lifecycle / ModeEngine ──
|
|
17290
17998
|
start(base, args) {
|
|
@@ -17344,7 +18052,7 @@ var TerminalModeEngine = class {
|
|
|
17344
18052
|
}
|
|
17345
18053
|
if (!session) return;
|
|
17346
18054
|
this.stopWatching(session.postId);
|
|
17347
|
-
await this.
|
|
18055
|
+
await teardownSession(session, this.core.target());
|
|
17348
18056
|
const removedPostId = session.postId;
|
|
17349
18057
|
this.reservedNames.delete(removedPostId);
|
|
17350
18058
|
this.sessions.delete(removedPostId);
|
|
@@ -17357,33 +18065,6 @@ var TerminalModeEngine = class {
|
|
|
17357
18065
|
this.reservedNames.clear();
|
|
17358
18066
|
this.sessions.clear();
|
|
17359
18067
|
}
|
|
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
18068
|
putCard(s) {
|
|
17388
18069
|
return putSessionCard(s, this.core.target());
|
|
17389
18070
|
}
|
|
@@ -17481,18 +18162,18 @@ var TerminalModeEngine = class {
|
|
|
17481
18162
|
this.reservedNames.values()
|
|
17482
18163
|
);
|
|
17483
18164
|
this.reservedNames.set(session.postId, workName);
|
|
17484
|
-
const { path:
|
|
18165
|
+
const { path: path23 } = await addWorktree(
|
|
17485
18166
|
this.core.target(),
|
|
17486
18167
|
workName,
|
|
17487
18168
|
branch,
|
|
17488
18169
|
"terminal"
|
|
17489
18170
|
);
|
|
17490
|
-
session.workspacePath =
|
|
18171
|
+
session.workspacePath = path23;
|
|
17491
18172
|
await setGitIdentity(
|
|
17492
18173
|
this.core.target(),
|
|
17493
18174
|
config.gitName,
|
|
17494
18175
|
config.gitEmail,
|
|
17495
|
-
|
|
18176
|
+
path23
|
|
17496
18177
|
);
|
|
17497
18178
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17498
18179
|
} else {
|
|
@@ -17554,11 +18235,11 @@ var TerminalModeEngine = class {
|
|
|
17554
18235
|
}
|
|
17555
18236
|
async bareFsList({
|
|
17556
18237
|
postId,
|
|
17557
|
-
path:
|
|
18238
|
+
path: path23
|
|
17558
18239
|
}) {
|
|
17559
18240
|
const target = this.getBareTarget(postId);
|
|
17560
18241
|
const root = this.getBareRoot(postId);
|
|
17561
|
-
const abs = this.resolveBarePath(
|
|
18242
|
+
const abs = this.resolveBarePath(path23, root);
|
|
17562
18243
|
const relRoot = abs.slice(root.length) || "/";
|
|
17563
18244
|
const out = await target.run([
|
|
17564
18245
|
"sh",
|
|
@@ -17608,30 +18289,30 @@ var TerminalModeEngine = class {
|
|
|
17608
18289
|
}
|
|
17609
18290
|
async bareFsRead({
|
|
17610
18291
|
postId,
|
|
17611
|
-
path:
|
|
18292
|
+
path: path23
|
|
17612
18293
|
}) {
|
|
17613
18294
|
const target = this.getBareTarget(postId);
|
|
17614
|
-
const abs = this.resolveBarePath(
|
|
18295
|
+
const abs = this.resolveBarePath(path23, this.getBareRoot(postId));
|
|
17615
18296
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17616
18297
|
'`)).trim();
|
|
17617
18298
|
return { content: b64, encoding: "base64" };
|
|
17618
18299
|
}
|
|
17619
18300
|
async bareFsWrite({
|
|
17620
18301
|
postId,
|
|
17621
|
-
path:
|
|
18302
|
+
path: path23,
|
|
17622
18303
|
content,
|
|
17623
18304
|
encoding
|
|
17624
18305
|
}) {
|
|
17625
18306
|
const target = this.getBareTarget(postId);
|
|
17626
|
-
const abs = this.resolveBarePath(
|
|
18307
|
+
const abs = this.resolveBarePath(path23, this.getBareRoot(postId));
|
|
17627
18308
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17628
18309
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17629
18310
|
throw new Error("Invalid base64 payload.");
|
|
17630
18311
|
}
|
|
17631
|
-
this.expectWrite(String(postId),
|
|
18312
|
+
this.expectWrite(String(postId), path23);
|
|
17632
18313
|
await writeBase64File(target, abs, b64);
|
|
17633
18314
|
const session = this.sessions.get(String(postId));
|
|
17634
|
-
if (session) delete session.buffers[
|
|
18315
|
+
if (session) delete session.buffers[path23];
|
|
17635
18316
|
}
|
|
17636
18317
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17637
18318
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17649,7 +18330,7 @@ var TerminalModeEngine = class {
|
|
|
17649
18330
|
}
|
|
17650
18331
|
const safeExt = (ext || "png").toLowerCase().replace(/[^a-z0-9]/g, "") || "png";
|
|
17651
18332
|
const oneShot = !dropId;
|
|
17652
|
-
const safeId2 = oneShot ? (0,
|
|
18333
|
+
const safeId2 = oneShot ? (0, import_crypto3.randomUUID)() : dropId.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
17653
18334
|
if (!safeId2) throw new Error("Invalid drop id.");
|
|
17654
18335
|
const dropsDir = `${target.paths.root}/drops`;
|
|
17655
18336
|
const abs = `${dropsDir}/${safeId2}.${safeExt}`;
|
|
@@ -17681,39 +18362,39 @@ var TerminalModeEngine = class {
|
|
|
17681
18362
|
}
|
|
17682
18363
|
bareOpenFile({
|
|
17683
18364
|
postId: rawPostId,
|
|
17684
|
-
path:
|
|
18365
|
+
path: path23
|
|
17685
18366
|
}) {
|
|
17686
18367
|
const session = this.sessions.get(String(rawPostId));
|
|
17687
18368
|
if (!session || session.mode !== "bare") return;
|
|
17688
|
-
if (!session.openedFiles.includes(
|
|
17689
|
-
session.openedFiles.push(
|
|
18369
|
+
if (!session.openedFiles.includes(path23)) {
|
|
18370
|
+
session.openedFiles.push(path23);
|
|
17690
18371
|
}
|
|
17691
18372
|
}
|
|
17692
18373
|
bareCloseFile({
|
|
17693
18374
|
postId: rawPostId,
|
|
17694
|
-
path:
|
|
18375
|
+
path: path23
|
|
17695
18376
|
}) {
|
|
17696
18377
|
const session = this.sessions.get(String(rawPostId));
|
|
17697
18378
|
if (!session || session.mode !== "bare") return;
|
|
17698
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17699
|
-
delete session.buffers[
|
|
18379
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path23);
|
|
18380
|
+
delete session.buffers[path23];
|
|
17700
18381
|
}
|
|
17701
18382
|
bareBufferSet({
|
|
17702
18383
|
postId: rawPostId,
|
|
17703
|
-
path:
|
|
18384
|
+
path: path23,
|
|
17704
18385
|
content
|
|
17705
18386
|
}) {
|
|
17706
18387
|
const session = this.sessions.get(String(rawPostId));
|
|
17707
18388
|
if (!session || session.mode !== "bare") return;
|
|
17708
|
-
session.buffers[
|
|
18389
|
+
session.buffers[path23] = content;
|
|
17709
18390
|
}
|
|
17710
18391
|
async bareFsDelete({
|
|
17711
18392
|
postId,
|
|
17712
|
-
path:
|
|
18393
|
+
path: path23
|
|
17713
18394
|
}) {
|
|
17714
18395
|
const target = this.getBareTarget(postId);
|
|
17715
18396
|
const root = this.getBareRoot(postId);
|
|
17716
|
-
const abs = this.resolveBarePath(
|
|
18397
|
+
const abs = this.resolveBarePath(path23, root);
|
|
17717
18398
|
if (abs === root) {
|
|
17718
18399
|
throw new Error("Refusing to delete the repo root.");
|
|
17719
18400
|
}
|
|
@@ -17721,10 +18402,10 @@ var TerminalModeEngine = class {
|
|
|
17721
18402
|
}
|
|
17722
18403
|
async bareFsMkdir({
|
|
17723
18404
|
postId,
|
|
17724
|
-
path:
|
|
18405
|
+
path: path23
|
|
17725
18406
|
}) {
|
|
17726
18407
|
const target = this.getBareTarget(postId);
|
|
17727
|
-
const abs = this.resolveBarePath(
|
|
18408
|
+
const abs = this.resolveBarePath(path23, this.getBareRoot(postId));
|
|
17728
18409
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17729
18410
|
}
|
|
17730
18411
|
async bareReview({
|
|
@@ -17847,26 +18528,76 @@ var TerminalModeEngine = class {
|
|
|
17847
18528
|
const prefix = `terminal-${sanitizedPostId}-`;
|
|
17848
18529
|
try {
|
|
17849
18530
|
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)
|
|
18531
|
+
`${tmuxCli(target)} list-panes -a -F '${TAB_LIST_FORMAT}' 2>/dev/null || true`
|
|
17865
18532
|
);
|
|
18533
|
+
const rows = parseTerminalTabs(stdout, prefix);
|
|
18534
|
+
const current = await terminalDigest(target).catch(() => "");
|
|
18535
|
+
return rows.map(({ id, name, fp, dead }) => ({
|
|
18536
|
+
id,
|
|
18537
|
+
name,
|
|
18538
|
+
// No stamp means a session older than the stamp, as blind to the
|
|
18539
|
+
// current build as a mismatched one.
|
|
18540
|
+
stale: !!current && fp !== current,
|
|
18541
|
+
dead
|
|
18542
|
+
}));
|
|
17866
18543
|
} catch {
|
|
17867
18544
|
return [];
|
|
17868
18545
|
}
|
|
17869
18546
|
}
|
|
18547
|
+
/** Terminal tabs across several posts in one sweep, so the sessions view can
|
|
18548
|
+
* ask about a whole board without a round trip per card. */
|
|
18549
|
+
async staleTabsFor(target, postIds, current) {
|
|
18550
|
+
if (!current) return [];
|
|
18551
|
+
const stdout = await target.sh(
|
|
18552
|
+
`${tmuxCli(target)} list-panes -a -F '${TAB_LIST_FORMAT}' 2>/dev/null || true`
|
|
18553
|
+
).catch(() => "");
|
|
18554
|
+
const stale = [];
|
|
18555
|
+
for (const postId of postIds) {
|
|
18556
|
+
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
18557
|
+
for (const row of parseTerminalTabs(stdout, prefix)) {
|
|
18558
|
+
if (row.fp !== current) stale.push({ postId, tabId: row.id });
|
|
18559
|
+
}
|
|
18560
|
+
}
|
|
18561
|
+
return stale;
|
|
18562
|
+
}
|
|
18563
|
+
async bareTerminalHealth({
|
|
18564
|
+
postIds
|
|
18565
|
+
}) {
|
|
18566
|
+
const target = this.core.target();
|
|
18567
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
18568
|
+
const acked = await getTerminalAck(target);
|
|
18569
|
+
return {
|
|
18570
|
+
digest,
|
|
18571
|
+
acked: !!digest && acked === digest,
|
|
18572
|
+
staleTabs: await this.staleTabsFor(target, postIds, digest)
|
|
18573
|
+
};
|
|
18574
|
+
}
|
|
18575
|
+
async bareAckTerminalDigest() {
|
|
18576
|
+
const target = this.core.target();
|
|
18577
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
18578
|
+
if (!digest) return;
|
|
18579
|
+
await setTerminalAck(target, digest);
|
|
18580
|
+
}
|
|
18581
|
+
async bareRestartStaleTerminals({
|
|
18582
|
+
postIds
|
|
18583
|
+
}) {
|
|
18584
|
+
const target = this.core.target();
|
|
18585
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
18586
|
+
const stale = await this.staleTabsFor(target, postIds, digest);
|
|
18587
|
+
for (const { postId, tabId } of stale) {
|
|
18588
|
+
let cwd = target.paths.workspace;
|
|
18589
|
+
try {
|
|
18590
|
+
cwd = this.gitWorkdir(postId) || cwd;
|
|
18591
|
+
} catch {
|
|
18592
|
+
}
|
|
18593
|
+
await restartTerminalSession({
|
|
18594
|
+
target,
|
|
18595
|
+
terminalKey: `${postId}-${tabId}`,
|
|
18596
|
+
cwd
|
|
18597
|
+
});
|
|
18598
|
+
}
|
|
18599
|
+
return { restarted: stale.length };
|
|
18600
|
+
}
|
|
17870
18601
|
/** This card's panes: the pid its tree hangs off, and the pane id its
|
|
17871
18602
|
* processes carry in their environment. */
|
|
17872
18603
|
async panePids(target, postId) {
|
|
@@ -18406,8 +19137,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
18406
19137
|
};
|
|
18407
19138
|
}
|
|
18408
19139
|
}
|
|
18409
|
-
async function githubApi(token,
|
|
18410
|
-
const res = await fetch(`${API_BASE}${
|
|
19140
|
+
async function githubApi(token, path23) {
|
|
19141
|
+
const res = await fetch(`${API_BASE}${path23}`, {
|
|
18411
19142
|
headers: {
|
|
18412
19143
|
Authorization: `Bearer ${token}`,
|
|
18413
19144
|
Accept: "application/vnd.github+json",
|
|
@@ -18416,7 +19147,7 @@ async function githubApi(token, path21) {
|
|
|
18416
19147
|
});
|
|
18417
19148
|
if (!res.ok) {
|
|
18418
19149
|
throw new Error(
|
|
18419
|
-
`GitHub ${
|
|
19150
|
+
`GitHub ${path23} failed (${res.status}): ${await res.text()}`
|
|
18420
19151
|
);
|
|
18421
19152
|
}
|
|
18422
19153
|
return await res.json();
|
|
@@ -18461,10 +19192,10 @@ async function listGithubBranches(token, fullName) {
|
|
|
18461
19192
|
}
|
|
18462
19193
|
|
|
18463
19194
|
// ../../shared/all/helpers/board-agent-core/host-run.ts
|
|
18464
|
-
var
|
|
19195
|
+
var import_path13 = __toESM(require("path"));
|
|
18465
19196
|
|
|
18466
19197
|
// ../../shared/all/helpers/board-agent-core/secrets-gate.ts
|
|
18467
|
-
var
|
|
19198
|
+
var import_crypto4 = __toESM(require("crypto"));
|
|
18468
19199
|
var DEPLOY_ORIGIN = "Deploy run";
|
|
18469
19200
|
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
19201
|
var SecretsGate = class {
|
|
@@ -18489,7 +19220,7 @@ var SecretsGate = class {
|
|
|
18489
19220
|
/** Mint a token for one session, labelled with what the owner should see on
|
|
18490
19221
|
* the prompt - the label is most of the decision, so name the session. */
|
|
18491
19222
|
issueToken(origin) {
|
|
18492
|
-
const token =
|
|
19223
|
+
const token = import_crypto4.default.randomBytes(24).toString("hex");
|
|
18493
19224
|
this.tokens.set(token, origin);
|
|
18494
19225
|
return token;
|
|
18495
19226
|
}
|
|
@@ -18513,7 +19244,7 @@ var SecretsGate = class {
|
|
|
18513
19244
|
return new Promise((resolve2) => {
|
|
18514
19245
|
this.waiting = {
|
|
18515
19246
|
request: {
|
|
18516
|
-
id:
|
|
19247
|
+
id: import_crypto4.default.randomUUID(),
|
|
18517
19248
|
command: opts.command,
|
|
18518
19249
|
keys: opts.keys,
|
|
18519
19250
|
origin: opts.origin,
|
|
@@ -18553,13 +19284,13 @@ var SecretsGate = class {
|
|
|
18553
19284
|
};
|
|
18554
19285
|
|
|
18555
19286
|
// ../../shared/all/helpers/board-agent-core/secrets-run.ts
|
|
18556
|
-
var
|
|
19287
|
+
var import_path12 = __toESM(require("path"));
|
|
18557
19288
|
|
|
18558
19289
|
// ../../shared/all/helpers/board-agent-core/host-secrets.ts
|
|
18559
19290
|
var import_child_process4 = require("child_process");
|
|
18560
|
-
var
|
|
18561
|
-
var
|
|
18562
|
-
var
|
|
19291
|
+
var import_crypto5 = __toESM(require("crypto"));
|
|
19292
|
+
var import_promises9 = __toESM(require("fs/promises"));
|
|
19293
|
+
var import_path11 = __toESM(require("path"));
|
|
18563
19294
|
var import_util6 = require("util");
|
|
18564
19295
|
var execFileAsync3 = (0, import_util6.promisify)(import_child_process4.execFile);
|
|
18565
19296
|
var IDENTITY_FILE = "age-identity.enc";
|
|
@@ -18585,14 +19316,14 @@ async function assertBinaries() {
|
|
|
18585
19316
|
}
|
|
18586
19317
|
}
|
|
18587
19318
|
function deriveKey(password, salt) {
|
|
18588
|
-
return
|
|
19319
|
+
return import_crypto5.default.scryptSync(password, salt, 32, {
|
|
18589
19320
|
N: SCRYPT_N,
|
|
18590
19321
|
maxmem: 64 * 1024 * 1024
|
|
18591
19322
|
});
|
|
18592
19323
|
}
|
|
18593
19324
|
async function secretsInitialized(configDir) {
|
|
18594
19325
|
try {
|
|
18595
|
-
await
|
|
19326
|
+
await import_promises9.default.access(import_path11.default.join(configDir, IDENTITY_FILE));
|
|
18596
19327
|
return true;
|
|
18597
19328
|
} catch {
|
|
18598
19329
|
return false;
|
|
@@ -18614,9 +19345,9 @@ async function initSecrets(configDir, password) {
|
|
|
18614
19345
|
if (!identity || !recipient) {
|
|
18615
19346
|
throw new Error("age-keygen returned no usable keypair.");
|
|
18616
19347
|
}
|
|
18617
|
-
const salt =
|
|
18618
|
-
const iv =
|
|
18619
|
-
const cipher =
|
|
19348
|
+
const salt = import_crypto5.default.randomBytes(16);
|
|
19349
|
+
const iv = import_crypto5.default.randomBytes(12);
|
|
19350
|
+
const cipher = import_crypto5.default.createCipheriv(
|
|
18620
19351
|
"aes-256-gcm",
|
|
18621
19352
|
deriveKey(password, salt),
|
|
18622
19353
|
iv
|
|
@@ -18631,17 +19362,17 @@ async function initSecrets(configDir, password) {
|
|
|
18631
19362
|
tag: cipher.getAuthTag().toString("base64"),
|
|
18632
19363
|
data: data.toString("base64")
|
|
18633
19364
|
};
|
|
18634
|
-
await
|
|
19365
|
+
await import_promises9.default.writeFile(import_path11.default.join(configDir, IDENTITY_FILE), JSON.stringify(box), {
|
|
18635
19366
|
mode: 384
|
|
18636
19367
|
});
|
|
18637
|
-
await
|
|
19368
|
+
await import_promises9.default.writeFile(import_path11.default.join(configDir, RECIPIENT_FILE), `${recipient}
|
|
18638
19369
|
`, {
|
|
18639
19370
|
mode: 384
|
|
18640
19371
|
});
|
|
18641
19372
|
return { success: true };
|
|
18642
19373
|
}
|
|
18643
19374
|
function decryptIdentity(box, password) {
|
|
18644
|
-
const decipher =
|
|
19375
|
+
const decipher = import_crypto5.default.createDecipheriv(
|
|
18645
19376
|
"aes-256-gcm",
|
|
18646
19377
|
deriveKey(password, Buffer.from(box.salt, "base64")),
|
|
18647
19378
|
Buffer.from(box.iv, "base64")
|
|
@@ -18657,17 +19388,17 @@ function decryptIdentity(box, password) {
|
|
|
18657
19388
|
}
|
|
18658
19389
|
}
|
|
18659
19390
|
function envPath(spaceDir) {
|
|
18660
|
-
return
|
|
19391
|
+
return import_path11.default.join(spaceDir, "secrets", ENV_FILE);
|
|
18661
19392
|
}
|
|
18662
19393
|
async function identityFor(configDir, password) {
|
|
18663
19394
|
const box = JSON.parse(
|
|
18664
|
-
await
|
|
19395
|
+
await import_promises9.default.readFile(import_path11.default.join(configDir, IDENTITY_FILE), "utf-8")
|
|
18665
19396
|
);
|
|
18666
19397
|
return decryptIdentity(box, password);
|
|
18667
19398
|
}
|
|
18668
19399
|
async function readEnv(spaceDir, identity) {
|
|
18669
19400
|
try {
|
|
18670
|
-
await
|
|
19401
|
+
await import_promises9.default.access(envPath(spaceDir));
|
|
18671
19402
|
} catch {
|
|
18672
19403
|
return {};
|
|
18673
19404
|
}
|
|
@@ -18696,14 +19427,14 @@ async function readEnv(spaceDir, identity) {
|
|
|
18696
19427
|
return values;
|
|
18697
19428
|
}
|
|
18698
19429
|
async function writeEnv(configDir, spaceDir, identity, values) {
|
|
18699
|
-
const recipient = (await
|
|
19430
|
+
const recipient = (await import_promises9.default.readFile(import_path11.default.join(configDir, RECIPIENT_FILE), "utf-8")).trim();
|
|
18700
19431
|
const plain = `${Object.entries(values).map(([k, v]) => `${k}=${v}`).join("\n")}
|
|
18701
19432
|
`;
|
|
18702
19433
|
const tmp = envPath(spaceDir).replace(
|
|
18703
19434
|
/\.enc\.env$/,
|
|
18704
19435
|
`.${process.pid}.tmp.env`
|
|
18705
19436
|
);
|
|
18706
|
-
await
|
|
19437
|
+
await import_promises9.default.writeFile(tmp, plain, { mode: 384 });
|
|
18707
19438
|
try {
|
|
18708
19439
|
const { stdout } = await execFileAsync3(
|
|
18709
19440
|
"sops",
|
|
@@ -18722,13 +19453,13 @@ async function writeEnv(configDir, spaceDir, identity, values) {
|
|
|
18722
19453
|
maxBuffer: 4 * 1024 * 1024
|
|
18723
19454
|
}
|
|
18724
19455
|
);
|
|
18725
|
-
await
|
|
19456
|
+
await import_promises9.default.writeFile(envPath(spaceDir), stdout, { mode: 384 });
|
|
18726
19457
|
} finally {
|
|
18727
|
-
await
|
|
19458
|
+
await import_promises9.default.rm(tmp, { force: true });
|
|
18728
19459
|
}
|
|
18729
19460
|
}
|
|
18730
19461
|
async function storedSecretKeys(spaceDir) {
|
|
18731
|
-
const raw = await
|
|
19462
|
+
const raw = await import_promises9.default.readFile(envPath(spaceDir), "utf-8").catch(() => "");
|
|
18732
19463
|
const keys = [];
|
|
18733
19464
|
for (const line of raw.split("\n")) {
|
|
18734
19465
|
const eq = line.indexOf("=");
|
|
@@ -18772,9 +19503,9 @@ var COMMAND_TIMEOUT_MS = 6 * 60 * 6e4;
|
|
|
18772
19503
|
var tail = (s, max = 8e3) => s.length > max ? s.slice(-max) : s;
|
|
18773
19504
|
function containedCwd(cwd, spaceDir) {
|
|
18774
19505
|
if (!cwd) return null;
|
|
18775
|
-
const resolved =
|
|
18776
|
-
const root =
|
|
18777
|
-
return resolved === root || resolved.startsWith(root +
|
|
19506
|
+
const resolved = import_path12.default.resolve(cwd);
|
|
19507
|
+
const root = import_path12.default.resolve(spaceDir);
|
|
19508
|
+
return resolved === root || resolved.startsWith(root + import_path12.default.sep) ? resolved : null;
|
|
18778
19509
|
}
|
|
18779
19510
|
async function authorizeAndRun(opts) {
|
|
18780
19511
|
const { command, gate } = opts;
|
|
@@ -18859,8 +19590,8 @@ function tailMarker(text) {
|
|
|
18859
19590
|
}
|
|
18860
19591
|
async function startDeployRun(opts) {
|
|
18861
19592
|
const { hooks } = opts;
|
|
18862
|
-
const workspace =
|
|
18863
|
-
const configDir =
|
|
19593
|
+
const workspace = import_path13.default.join(opts.spaceDir, "workspace");
|
|
19594
|
+
const configDir = import_path13.default.dirname(opts.spaceDir);
|
|
18864
19595
|
const status = await checkDeployStatus(opts);
|
|
18865
19596
|
if (!status.hasSkill || !status.hasVariables || !status.hasChecker) {
|
|
18866
19597
|
throw new Error(
|
|
@@ -19079,123 +19810,8 @@ async function getDriveRoot(accessToken) {
|
|
|
19079
19810
|
}
|
|
19080
19811
|
|
|
19081
19812
|
// ../../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
|
-
}
|
|
19813
|
+
var import_promises10 = require("fs/promises");
|
|
19814
|
+
var import_path14 = __toESM(require("path"));
|
|
19199
19815
|
|
|
19200
19816
|
// ../../shared/all/helpers/board-agent-core/toolchain.ts
|
|
19201
19817
|
var TOOLS = [
|
|
@@ -19464,7 +20080,7 @@ var SpaceCore = class {
|
|
|
19464
20080
|
return { code: 3, output: "" };
|
|
19465
20081
|
}
|
|
19466
20082
|
return authorizeAndRun({
|
|
19467
|
-
configDir:
|
|
20083
|
+
configDir: import_path14.default.dirname(spaceDir),
|
|
19468
20084
|
spaceDir,
|
|
19469
20085
|
// A step legitimately cds into subdirectories, so the caller's own
|
|
19470
20086
|
// cwd is right - but only inside the space. Anywhere else is a
|
|
@@ -19717,6 +20333,8 @@ var SpaceCore = class {
|
|
|
19717
20333
|
markOnboardingComplete(target).catch(() => {
|
|
19718
20334
|
})
|
|
19719
20335
|
]);
|
|
20336
|
+
await this.restoreTerminals();
|
|
20337
|
+
await this.applyTerminalConfig();
|
|
19720
20338
|
status("");
|
|
19721
20339
|
this.ready = true;
|
|
19722
20340
|
} catch (err) {
|
|
@@ -19802,17 +20420,17 @@ var SpaceCore = class {
|
|
|
19802
20420
|
const source = pluginRoot();
|
|
19803
20421
|
if (!source) return;
|
|
19804
20422
|
const { bin } = this.target().paths;
|
|
19805
|
-
const from =
|
|
19806
|
-
const to =
|
|
20423
|
+
const from = import_path14.default.join(source, "factiii", "bin", SECRETS_BIN);
|
|
20424
|
+
const to = import_path14.default.join(bin, SECRETS_BIN);
|
|
19807
20425
|
try {
|
|
19808
20426
|
const [src, staged] = await Promise.all([
|
|
19809
|
-
(0,
|
|
19810
|
-
(0,
|
|
20427
|
+
(0, import_promises10.readFile)(from),
|
|
20428
|
+
(0, import_promises10.readFile)(to).catch(() => null)
|
|
19811
20429
|
]);
|
|
19812
20430
|
if (staged?.equals(src)) return;
|
|
19813
|
-
await (0,
|
|
19814
|
-
await (0,
|
|
19815
|
-
await (0,
|
|
20431
|
+
await (0, import_promises10.mkdir)(bin, { recursive: true });
|
|
20432
|
+
await (0, import_promises10.copyFile)(from, to);
|
|
20433
|
+
await (0, import_promises10.chmod)(to, 493);
|
|
19816
20434
|
} catch {
|
|
19817
20435
|
}
|
|
19818
20436
|
}
|
|
@@ -19875,9 +20493,79 @@ var SpaceCore = class {
|
|
|
19875
20493
|
});
|
|
19876
20494
|
await Promise.all([this.provisionPluginLane(), this.provisionBinLane()]);
|
|
19877
20495
|
await this.provisionMcpLane();
|
|
20496
|
+
await this.restoreTerminals();
|
|
20497
|
+
await this.applyTerminalConfig();
|
|
19878
20498
|
this.ready = true;
|
|
19879
20499
|
return true;
|
|
19880
20500
|
}
|
|
20501
|
+
/** Push a changed tmux config into the server that is already running.
|
|
20502
|
+
*
|
|
20503
|
+
* tmux reads `-f` only when it STARTS, so a config a runner update changed
|
|
20504
|
+
* would otherwise sit unread until every session on the host died. Sourcing
|
|
20505
|
+
* it reaches the live sessions and costs no process its life, which is why
|
|
20506
|
+
* this half never has to bother the user - unlike the env half, where a
|
|
20507
|
+
* restart is the only way in.
|
|
20508
|
+
*
|
|
20509
|
+
* Runs AFTER the plugin and helper lanes above, deliberately: those are two
|
|
20510
|
+
* of the inputs the digest covers, and recording one before they finish
|
|
20511
|
+
* would stamp a state the runner is about to invalidate. */
|
|
20512
|
+
/** Bring this space's tabs back after the tmux server died (a reboot, or a
|
|
20513
|
+
* kill). A runner restart needs none of this — tmux outlives us.
|
|
20514
|
+
*
|
|
20515
|
+
* Structure only: every tab returns as a fresh shell.
|
|
20516
|
+
*
|
|
20517
|
+
* Each restored pane is respawned, because resurrect rebuilds panes on the
|
|
20518
|
+
* DEFAULT shell with no bin dir on PATH, and stamped, because it saves no
|
|
20519
|
+
* user options and an unstamped session would read as stale on every boot. */
|
|
20520
|
+
async restoreTerminals() {
|
|
20521
|
+
try {
|
|
20522
|
+
const target = this.target();
|
|
20523
|
+
await ensureTmuxResurrect(target);
|
|
20524
|
+
const root = target.paths.root;
|
|
20525
|
+
const before = await tmuxPanePaths(target);
|
|
20526
|
+
const mineUp = [...before].some(
|
|
20527
|
+
([name, dir]) => name.startsWith("terminal-") && dir.startsWith(root)
|
|
20528
|
+
);
|
|
20529
|
+
if (mineUp) return;
|
|
20530
|
+
const restored = await resurrectRestore(target);
|
|
20531
|
+
const paths = await tmuxPanePaths(target);
|
|
20532
|
+
const revived = restored.filter(
|
|
20533
|
+
(name) => !before.has(name) && (paths.get(name) ?? "").startsWith(root)
|
|
20534
|
+
);
|
|
20535
|
+
if (revived.length === 0) return;
|
|
20536
|
+
const digest = await terminalDigest(target).catch(() => "");
|
|
20537
|
+
for (const session of revived) {
|
|
20538
|
+
await respawnTmuxPane({
|
|
20539
|
+
target,
|
|
20540
|
+
session,
|
|
20541
|
+
argv: [
|
|
20542
|
+
"bash",
|
|
20543
|
+
"-c",
|
|
20544
|
+
terminalInitScript(
|
|
20545
|
+
paths.get(session) || target.paths.workspace,
|
|
20546
|
+
target.paths.bin
|
|
20547
|
+
)
|
|
20548
|
+
],
|
|
20549
|
+
cwd: paths.get(session) || target.paths.workspace,
|
|
20550
|
+
env: target.stateEnv()
|
|
20551
|
+
}).catch(() => {
|
|
20552
|
+
});
|
|
20553
|
+
if (digest) {
|
|
20554
|
+
await setTmuxSessionOption(
|
|
20555
|
+
target,
|
|
20556
|
+
session,
|
|
20557
|
+
TERMINAL_FP_OPTION,
|
|
20558
|
+
digest
|
|
20559
|
+
);
|
|
20560
|
+
}
|
|
20561
|
+
}
|
|
20562
|
+
} catch {
|
|
20563
|
+
}
|
|
20564
|
+
}
|
|
20565
|
+
async applyTerminalConfig() {
|
|
20566
|
+
await sourceTmuxConf(this.target()).catch(() => {
|
|
20567
|
+
});
|
|
20568
|
+
}
|
|
19881
20569
|
async ensureSetup() {
|
|
19882
20570
|
await this.ensureSpaceWorkspace();
|
|
19883
20571
|
return { ready: true };
|
|
@@ -20528,7 +21216,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20528
21216
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
20529
21217
|
secretsDirs() {
|
|
20530
21218
|
const spaceDir = this.core.spaceDir();
|
|
20531
|
-
return { configDir: (0,
|
|
21219
|
+
return { configDir: (0, import_path15.dirname)(spaceDir), spaceDir };
|
|
20532
21220
|
}
|
|
20533
21221
|
async deploySecretsStatus() {
|
|
20534
21222
|
return {
|
|
@@ -20919,6 +21607,15 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
20919
21607
|
this.getCardTarget(String(p.postId)) ?? void 0
|
|
20920
21608
|
);
|
|
20921
21609
|
}
|
|
21610
|
+
bareTerminalHealth(p) {
|
|
21611
|
+
return this.terminal.bareTerminalHealth(p);
|
|
21612
|
+
}
|
|
21613
|
+
bareAckTerminalDigest() {
|
|
21614
|
+
return this.terminal.bareAckTerminalDigest();
|
|
21615
|
+
}
|
|
21616
|
+
bareRestartStaleTerminals(p) {
|
|
21617
|
+
return this.terminal.bareRestartStaleTerminals(p);
|
|
21618
|
+
}
|
|
20922
21619
|
bareEditorState(p) {
|
|
20923
21620
|
return this.terminal.bareEditorState(p);
|
|
20924
21621
|
}
|
|
@@ -21556,6 +22253,17 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21556
22253
|
return await this.bareListTerminals(
|
|
21557
22254
|
payload
|
|
21558
22255
|
);
|
|
22256
|
+
case "bareTerminalHealth":
|
|
22257
|
+
return await this.bareTerminalHealth(
|
|
22258
|
+
payload
|
|
22259
|
+
);
|
|
22260
|
+
case "bareAckTerminalDigest":
|
|
22261
|
+
await this.bareAckTerminalDigest();
|
|
22262
|
+
return void 0;
|
|
22263
|
+
case "bareRestartStaleTerminals":
|
|
22264
|
+
return await this.bareRestartStaleTerminals(
|
|
22265
|
+
payload
|
|
22266
|
+
);
|
|
21559
22267
|
case "bareEditorState":
|
|
21560
22268
|
return this.bareEditorState(
|
|
21561
22269
|
payload
|
|
@@ -21598,11 +22306,11 @@ ${c.content.trim() || "(no description)"}`
|
|
|
21598
22306
|
|
|
21599
22307
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
21600
22308
|
var import_child_process5 = require("child_process");
|
|
21601
|
-
var
|
|
21602
|
-
var
|
|
21603
|
-
var
|
|
22309
|
+
var import_crypto6 = require("crypto");
|
|
22310
|
+
var import_fs5 = require("fs");
|
|
22311
|
+
var import_promises11 = __toESM(require("fs/promises"));
|
|
21604
22312
|
var import_net3 = require("net");
|
|
21605
|
-
var
|
|
22313
|
+
var import_path16 = require("path");
|
|
21606
22314
|
var import_util7 = require("util");
|
|
21607
22315
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
21608
22316
|
var IndexService = class {
|
|
@@ -21616,7 +22324,7 @@ var IndexService = class {
|
|
|
21616
22324
|
/** Per-space bearer for MCP + file, derived from the control token (the
|
|
21617
22325
|
* service computes the same). */
|
|
21618
22326
|
bearerToken(spaceSlug) {
|
|
21619
|
-
return (0,
|
|
22327
|
+
return (0, import_crypto6.createHmac)("sha256", this.opts.controlToken).update(`mcp:${spaceSlug}`).digest("hex");
|
|
21620
22328
|
}
|
|
21621
22329
|
/** MCP bridge for a space. Null until the service is up (which it kicks
|
|
21622
22330
|
* off), so a bad :0 URL is never baked in; provisionMcpLane re-runs. */
|
|
@@ -21661,7 +22369,7 @@ var IndexService = class {
|
|
|
21661
22369
|
const log = await this.openLog();
|
|
21662
22370
|
this.proc = (0, import_child_process5.spawn)(
|
|
21663
22371
|
process.execPath,
|
|
21664
|
-
[(0,
|
|
22372
|
+
[(0, import_path16.join)(this.opts.indexDir, "index.js")],
|
|
21665
22373
|
{
|
|
21666
22374
|
cwd: this.opts.indexDir,
|
|
21667
22375
|
env: {
|
|
@@ -21689,8 +22397,8 @@ var IndexService = class {
|
|
|
21689
22397
|
* opened (a missing log must never stop the service from starting). */
|
|
21690
22398
|
async openLog() {
|
|
21691
22399
|
try {
|
|
21692
|
-
await
|
|
21693
|
-
return (0,
|
|
22400
|
+
await import_promises11.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
22401
|
+
return (0, import_fs5.openSync)((0, import_path16.join)(this.opts.dataDir, "index.log"), "a");
|
|
21694
22402
|
} catch {
|
|
21695
22403
|
return null;
|
|
21696
22404
|
}
|
|
@@ -21699,10 +22407,10 @@ var IndexService = class {
|
|
|
21699
22407
|
* platform needing node-gyp fails here rather than at query time. */
|
|
21700
22408
|
async installDeps() {
|
|
21701
22409
|
const dir = this.opts.indexDir;
|
|
21702
|
-
if ((0,
|
|
21703
|
-
await
|
|
21704
|
-
(0,
|
|
21705
|
-
(0,
|
|
22410
|
+
if ((0, import_fs5.existsSync)((0, import_path16.join)(dir, "node_modules"))) return;
|
|
22411
|
+
await import_promises11.default.copyFile(
|
|
22412
|
+
(0, import_path16.join)(dir, "image-package.json"),
|
|
22413
|
+
(0, import_path16.join)(dir, "package.json")
|
|
21706
22414
|
);
|
|
21707
22415
|
await execFileAsync4(
|
|
21708
22416
|
"npm",
|
|
@@ -21714,15 +22422,15 @@ var IndexService = class {
|
|
|
21714
22422
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
21715
22423
|
* registration pointing at a dead one. */
|
|
21716
22424
|
async stablePort() {
|
|
21717
|
-
const file = (0,
|
|
21718
|
-
const saved = Number(await
|
|
22425
|
+
const file = (0, import_path16.join)(this.opts.dataDir, ".port");
|
|
22426
|
+
const saved = Number(await import_promises11.default.readFile(file, "utf-8").catch(() => ""));
|
|
21719
22427
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
21720
22428
|
return saved;
|
|
21721
22429
|
}
|
|
21722
22430
|
const port = await freePort();
|
|
21723
|
-
await
|
|
22431
|
+
await import_promises11.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
21724
22432
|
});
|
|
21725
|
-
await
|
|
22433
|
+
await import_promises11.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
21726
22434
|
});
|
|
21727
22435
|
return port;
|
|
21728
22436
|
}
|
|
@@ -21889,24 +22597,24 @@ async function pairWithBrowser(serverUrl) {
|
|
|
21889
22597
|
}
|
|
21890
22598
|
|
|
21891
22599
|
// src/config.ts
|
|
21892
|
-
var
|
|
22600
|
+
var import_fs9 = __toESM(require("fs"));
|
|
21893
22601
|
var import_os6 = __toESM(require("os"));
|
|
21894
|
-
var
|
|
22602
|
+
var import_path21 = __toESM(require("path"));
|
|
21895
22603
|
|
|
21896
22604
|
// src/secureFile.ts
|
|
21897
|
-
var
|
|
21898
|
-
var
|
|
21899
|
-
var
|
|
22605
|
+
var import_crypto9 = __toESM(require("crypto"));
|
|
22606
|
+
var import_fs8 = __toESM(require("fs"));
|
|
22607
|
+
var import_path20 = __toESM(require("path"));
|
|
21900
22608
|
|
|
21901
22609
|
// src/keychain.ts
|
|
21902
22610
|
var import_child_process7 = require("child_process");
|
|
21903
|
-
var
|
|
21904
|
-
var
|
|
22611
|
+
var import_crypto7 = __toESM(require("crypto"));
|
|
22612
|
+
var import_fs6 = __toESM(require("fs"));
|
|
21905
22613
|
var import_os4 = __toESM(require("os"));
|
|
21906
|
-
var
|
|
22614
|
+
var import_path17 = __toESM(require("path"));
|
|
21907
22615
|
var SERVICE = "factiii-runner";
|
|
21908
22616
|
var ACCOUNT = "config-encryption-key";
|
|
21909
|
-
var DPAPI_KEY_FILE =
|
|
22617
|
+
var DPAPI_KEY_FILE = import_path17.default.join(
|
|
21910
22618
|
import_os4.default.homedir(),
|
|
21911
22619
|
".factiii-runner",
|
|
21912
22620
|
"config-key.dpapi"
|
|
@@ -21987,7 +22695,7 @@ function linuxWrite(key) {
|
|
|
21987
22695
|
function winRead() {
|
|
21988
22696
|
let wrapped;
|
|
21989
22697
|
try {
|
|
21990
|
-
wrapped =
|
|
22698
|
+
wrapped = import_fs6.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
|
|
21991
22699
|
} catch {
|
|
21992
22700
|
return { state: "missing" };
|
|
21993
22701
|
}
|
|
@@ -22012,8 +22720,8 @@ function winWrite(key) {
|
|
|
22012
22720
|
"-Command",
|
|
22013
22721
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
22014
22722
|
]);
|
|
22015
|
-
|
|
22016
|
-
|
|
22723
|
+
import_fs6.default.mkdirSync(import_path17.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
22724
|
+
import_fs6.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
22017
22725
|
`, { mode: 384 });
|
|
22018
22726
|
}
|
|
22019
22727
|
function keychainBackend() {
|
|
@@ -22152,7 +22860,7 @@ function resolve() {
|
|
|
22152
22860
|
cached = { key: null, status: diagnose() };
|
|
22153
22861
|
return cached;
|
|
22154
22862
|
}
|
|
22155
|
-
const fresh =
|
|
22863
|
+
const fresh = import_crypto7.default.randomBytes(32);
|
|
22156
22864
|
writeKey(fresh);
|
|
22157
22865
|
const verify = readKey();
|
|
22158
22866
|
if (verify.state !== "found" || !verify.key.equals(fresh)) {
|
|
@@ -22174,23 +22882,23 @@ function keychainStatus() {
|
|
|
22174
22882
|
}
|
|
22175
22883
|
|
|
22176
22884
|
// src/secureStore.ts
|
|
22177
|
-
var
|
|
22178
|
-
var
|
|
22179
|
-
var
|
|
22885
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
22886
|
+
var import_fs7 = __toESM(require("fs"));
|
|
22887
|
+
var import_path19 = __toESM(require("path"));
|
|
22180
22888
|
|
|
22181
22889
|
// src/paths.ts
|
|
22182
22890
|
var import_os5 = __toESM(require("os"));
|
|
22183
|
-
var
|
|
22184
|
-
var CONFIG_DIR =
|
|
22891
|
+
var import_path18 = __toESM(require("path"));
|
|
22892
|
+
var CONFIG_DIR = import_path18.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
22185
22893
|
function safeSlug(spaceSlug) {
|
|
22186
22894
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
22187
22895
|
}
|
|
22188
22896
|
function spaceDirPath(spaceSlug) {
|
|
22189
|
-
return
|
|
22897
|
+
return import_path18.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
22190
22898
|
}
|
|
22191
22899
|
|
|
22192
22900
|
// src/secureStore.ts
|
|
22193
|
-
var VAULT_KEY_FILE =
|
|
22901
|
+
var VAULT_KEY_FILE = import_path19.default.join(CONFIG_DIR, "vault-key.json");
|
|
22194
22902
|
var SCRYPT_N2 = 1 << 15;
|
|
22195
22903
|
var MIN_PASSWORD_LENGTH = 8;
|
|
22196
22904
|
var SecureStoreError = class extends Error {
|
|
@@ -22217,7 +22925,7 @@ function requireKeychainKey() {
|
|
|
22217
22925
|
);
|
|
22218
22926
|
}
|
|
22219
22927
|
function passwordKey(password, salt) {
|
|
22220
|
-
return
|
|
22928
|
+
return import_crypto8.default.scryptSync(password, salt, 32, {
|
|
22221
22929
|
N: SCRYPT_N2,
|
|
22222
22930
|
maxmem: 64 * 1024 * 1024
|
|
22223
22931
|
});
|
|
@@ -22225,7 +22933,7 @@ function passwordKey(password, salt) {
|
|
|
22225
22933
|
function wrapKey(machineKey, pwKey) {
|
|
22226
22934
|
const ikm = pwKey ? Buffer.concat([machineKey, pwKey]) : machineKey;
|
|
22227
22935
|
return Buffer.from(
|
|
22228
|
-
|
|
22936
|
+
import_crypto8.default.hkdfSync(
|
|
22229
22937
|
"sha256",
|
|
22230
22938
|
ikm,
|
|
22231
22939
|
Buffer.alloc(0),
|
|
@@ -22235,8 +22943,8 @@ function wrapKey(machineKey, pwKey) {
|
|
|
22235
22943
|
);
|
|
22236
22944
|
}
|
|
22237
22945
|
function seal(plaintext, key) {
|
|
22238
|
-
const iv =
|
|
22239
|
-
const cipher =
|
|
22946
|
+
const iv = import_crypto8.default.randomBytes(12);
|
|
22947
|
+
const cipher = import_crypto8.default.createCipheriv("aes-256-gcm", key, iv);
|
|
22240
22948
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
22241
22949
|
return {
|
|
22242
22950
|
iv: iv.toString("base64"),
|
|
@@ -22246,7 +22954,7 @@ function seal(plaintext, key) {
|
|
|
22246
22954
|
}
|
|
22247
22955
|
function unseal(box, key) {
|
|
22248
22956
|
try {
|
|
22249
|
-
const decipher =
|
|
22957
|
+
const decipher = import_crypto8.default.createDecipheriv(
|
|
22250
22958
|
"aes-256-gcm",
|
|
22251
22959
|
key,
|
|
22252
22960
|
Buffer.from(box.iv, "base64")
|
|
@@ -22263,7 +22971,7 @@ function unseal(box, key) {
|
|
|
22263
22971
|
function readVaultFile() {
|
|
22264
22972
|
try {
|
|
22265
22973
|
const parsed = JSON.parse(
|
|
22266
|
-
|
|
22974
|
+
import_fs7.default.readFileSync(VAULT_KEY_FILE, "utf-8")
|
|
22267
22975
|
);
|
|
22268
22976
|
return parsed.v === 1 ? parsed : null;
|
|
22269
22977
|
} catch {
|
|
@@ -22271,15 +22979,15 @@ function readVaultFile() {
|
|
|
22271
22979
|
}
|
|
22272
22980
|
}
|
|
22273
22981
|
function writeVaultFile(file) {
|
|
22274
|
-
|
|
22982
|
+
import_fs7.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
22275
22983
|
const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
|
|
22276
|
-
|
|
22984
|
+
import_fs7.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
|
|
22277
22985
|
`, { mode: 384 });
|
|
22278
|
-
|
|
22986
|
+
import_fs7.default.renameSync(tmp, VAULT_KEY_FILE);
|
|
22279
22987
|
}
|
|
22280
22988
|
var unlockedDek = null;
|
|
22281
22989
|
function createVault(machineKey) {
|
|
22282
|
-
const dek =
|
|
22990
|
+
const dek = import_crypto8.default.randomBytes(32);
|
|
22283
22991
|
writeVaultFile({
|
|
22284
22992
|
v: 1,
|
|
22285
22993
|
passwordEnabled: false,
|
|
@@ -22374,7 +23082,7 @@ function enablePassword(password) {
|
|
|
22374
23082
|
);
|
|
22375
23083
|
}
|
|
22376
23084
|
const dek = dataKey();
|
|
22377
|
-
const salt =
|
|
23085
|
+
const salt = import_crypto8.default.randomBytes(16);
|
|
22378
23086
|
writeVaultFile({
|
|
22379
23087
|
v: 1,
|
|
22380
23088
|
passwordEnabled: true,
|
|
@@ -22402,7 +23110,7 @@ function changePassword(current, next) {
|
|
|
22402
23110
|
"The current password is not correct."
|
|
22403
23111
|
);
|
|
22404
23112
|
}
|
|
22405
|
-
const salt =
|
|
23113
|
+
const salt = import_crypto8.default.randomBytes(16);
|
|
22406
23114
|
writeVaultFile({
|
|
22407
23115
|
v: 1,
|
|
22408
23116
|
passwordEnabled: true,
|
|
@@ -22479,8 +23187,8 @@ function storeStatus() {
|
|
|
22479
23187
|
// src/secureFile.ts
|
|
22480
23188
|
var MAGIC = "FACTIII1";
|
|
22481
23189
|
function encrypt(plain, key, version) {
|
|
22482
|
-
const iv =
|
|
22483
|
-
const cipher =
|
|
23190
|
+
const iv = import_crypto9.default.randomBytes(12);
|
|
23191
|
+
const cipher = import_crypto9.default.createCipheriv("aes-256-gcm", key, iv);
|
|
22484
23192
|
const data = Buffer.concat([cipher.update(plain), cipher.final()]);
|
|
22485
23193
|
const envelope = {
|
|
22486
23194
|
v: version,
|
|
@@ -22491,7 +23199,7 @@ function encrypt(plain, key, version) {
|
|
|
22491
23199
|
return `${MAGIC}${JSON.stringify(envelope)}`;
|
|
22492
23200
|
}
|
|
22493
23201
|
function decrypt(envelope, key) {
|
|
22494
|
-
const decipher =
|
|
23202
|
+
const decipher = import_crypto9.default.createDecipheriv(
|
|
22495
23203
|
"aes-256-gcm",
|
|
22496
23204
|
key,
|
|
22497
23205
|
Buffer.from(envelope.iv, "base64")
|
|
@@ -22512,15 +23220,15 @@ function parseEnvelope(raw) {
|
|
|
22512
23220
|
}
|
|
22513
23221
|
}
|
|
22514
23222
|
function atomicWrite(filePath, body) {
|
|
22515
|
-
|
|
23223
|
+
import_fs8.default.mkdirSync(import_path20.default.dirname(filePath), { recursive: true });
|
|
22516
23224
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
22517
|
-
|
|
22518
|
-
|
|
23225
|
+
import_fs8.default.writeFileSync(tmp, body, { mode: 384 });
|
|
23226
|
+
import_fs8.default.renameSync(tmp, filePath);
|
|
22519
23227
|
}
|
|
22520
23228
|
function readSecureBuffer(filePath) {
|
|
22521
23229
|
let raw;
|
|
22522
23230
|
try {
|
|
22523
|
-
raw =
|
|
23231
|
+
raw = import_fs8.default.readFileSync(filePath, "utf-8");
|
|
22524
23232
|
} catch {
|
|
22525
23233
|
return null;
|
|
22526
23234
|
}
|
|
@@ -22577,7 +23285,7 @@ function writeSecureJson(filePath, value2) {
|
|
|
22577
23285
|
function readBootJson(filePath) {
|
|
22578
23286
|
let raw;
|
|
22579
23287
|
try {
|
|
22580
|
-
raw =
|
|
23288
|
+
raw = import_fs8.default.readFileSync(filePath, "utf-8");
|
|
22581
23289
|
} catch {
|
|
22582
23290
|
return null;
|
|
22583
23291
|
}
|
|
@@ -22627,8 +23335,8 @@ function writeBootJson(filePath, value2) {
|
|
|
22627
23335
|
}
|
|
22628
23336
|
|
|
22629
23337
|
// src/config.ts
|
|
22630
|
-
var CONFIG_DIR2 =
|
|
22631
|
-
var CONFIG_FILE =
|
|
23338
|
+
var CONFIG_DIR2 = import_path21.default.join(import_os6.default.homedir(), ".factiii-runner");
|
|
23339
|
+
var CONFIG_FILE = import_path21.default.join(CONFIG_DIR2, "config.json");
|
|
22632
23340
|
function readRunnerConfig() {
|
|
22633
23341
|
return readBootJson(CONFIG_FILE);
|
|
22634
23342
|
}
|
|
@@ -22636,7 +23344,7 @@ function writeRunnerConfig(config) {
|
|
|
22636
23344
|
writeBootJson(CONFIG_FILE, config);
|
|
22637
23345
|
}
|
|
22638
23346
|
function configExists() {
|
|
22639
|
-
return
|
|
23347
|
+
return import_fs9.default.existsSync(CONFIG_FILE);
|
|
22640
23348
|
}
|
|
22641
23349
|
|
|
22642
23350
|
// src/promptPassword.ts
|
|
@@ -22742,65 +23450,17 @@ function installLatest() {
|
|
|
22742
23450
|
);
|
|
22743
23451
|
return false;
|
|
22744
23452
|
}
|
|
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));
|
|
22767
|
-
}
|
|
22768
|
-
return frames;
|
|
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
|
-
};
|
|
23453
|
+
return true;
|
|
22797
23454
|
}
|
|
22798
23455
|
|
|
23456
|
+
// src/supervisor.ts
|
|
23457
|
+
var import_node_child_process2 = require("node:child_process");
|
|
23458
|
+
|
|
22799
23459
|
// src/daemon.ts
|
|
22800
|
-
var
|
|
22801
|
-
var
|
|
23460
|
+
var import_crypto10 = require("crypto");
|
|
23461
|
+
var import_fs11 = __toESM(require("fs"));
|
|
22802
23462
|
var import_node_datachannel = require("node-datachannel");
|
|
22803
|
-
var
|
|
23463
|
+
var import_path23 = __toESM(require("path"));
|
|
22804
23464
|
|
|
22805
23465
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
22806
23466
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23980,12 +24640,12 @@ function parse2(str) {
|
|
|
23980
24640
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23981
24641
|
return uri;
|
|
23982
24642
|
}
|
|
23983
|
-
function pathNames(obj,
|
|
23984
|
-
const regx = /\/{2,9}/g, names =
|
|
23985
|
-
if (
|
|
24643
|
+
function pathNames(obj, path23) {
|
|
24644
|
+
const regx = /\/{2,9}/g, names = path23.replace(regx, "/").split("/");
|
|
24645
|
+
if (path23.slice(0, 1) == "/" || path23.length === 0) {
|
|
23986
24646
|
names.splice(0, 1);
|
|
23987
24647
|
}
|
|
23988
|
-
if (
|
|
24648
|
+
if (path23.slice(-1) == "/") {
|
|
23989
24649
|
names.splice(names.length - 1, 1);
|
|
23990
24650
|
}
|
|
23991
24651
|
return names;
|
|
@@ -24604,7 +25264,7 @@ var protocol2 = Socket.protocol;
|
|
|
24604
25264
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
24605
25265
|
var import_debug7 = __toESM(require_src(), 1);
|
|
24606
25266
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
24607
|
-
function url(uri,
|
|
25267
|
+
function url(uri, path23 = "", loc) {
|
|
24608
25268
|
let obj = uri;
|
|
24609
25269
|
loc = loc || typeof location !== "undefined" && location;
|
|
24610
25270
|
if (null == uri)
|
|
@@ -24638,7 +25298,7 @@ function url(uri, path21 = "", loc) {
|
|
|
24638
25298
|
obj.path = obj.path || "/";
|
|
24639
25299
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
24640
25300
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
24641
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
25301
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path23;
|
|
24642
25302
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
24643
25303
|
return obj;
|
|
24644
25304
|
}
|
|
@@ -26272,8 +26932,8 @@ function lookup(uri, opts) {
|
|
|
26272
26932
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
26273
26933
|
const source = parsed.source;
|
|
26274
26934
|
const id = parsed.id;
|
|
26275
|
-
const
|
|
26276
|
-
const sameNamespace = cache[id] &&
|
|
26935
|
+
const path23 = parsed.path;
|
|
26936
|
+
const sameNamespace = cache[id] && path23 in cache[id]["nsps"];
|
|
26277
26937
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
26278
26938
|
let io;
|
|
26279
26939
|
if (newConnection) {
|
|
@@ -26298,205 +26958,22 @@ Object.assign(lookup, {
|
|
|
26298
26958
|
connect: lookup
|
|
26299
26959
|
});
|
|
26300
26960
|
|
|
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}`;
|
|
26961
|
+
// src/agent-adapter.ts
|
|
26962
|
+
var import_fs10 = __toESM(require("fs"));
|
|
26963
|
+
var import_path22 = __toESM(require("path"));
|
|
26964
|
+
function credsDir(spaceSlug) {
|
|
26965
|
+
return import_path22.default.join(spaceDirPath(spaceSlug), ".creds");
|
|
26334
26966
|
}
|
|
26335
|
-
function
|
|
26336
|
-
return
|
|
26967
|
+
function githubPath(spaceSlug) {
|
|
26968
|
+
return import_path22.default.join(credsDir(spaceSlug), "github.json");
|
|
26337
26969
|
}
|
|
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
26970
|
function connectionPath(spaceSlug) {
|
|
26487
|
-
return
|
|
26971
|
+
return import_path22.default.join(credsDir(spaceSlug), "onedrive.json");
|
|
26488
26972
|
}
|
|
26973
|
+
var RUNNER_CONFIG_PATH = import_path22.default.join(CONFIG_DIR, "runner-config.json");
|
|
26489
26974
|
function writeJson(filePath, value2) {
|
|
26490
26975
|
writeSecureJson(filePath, value2);
|
|
26491
26976
|
}
|
|
26492
|
-
var CONFIG_FIELDS = [
|
|
26493
|
-
"repoUrl",
|
|
26494
|
-
"mainBranch",
|
|
26495
|
-
"gitName",
|
|
26496
|
-
"gitEmail",
|
|
26497
|
-
"githubToken",
|
|
26498
|
-
"provider"
|
|
26499
|
-
];
|
|
26500
26977
|
function fillDefaults(config) {
|
|
26501
26978
|
return {
|
|
26502
26979
|
repoUrl: config.repoUrl || "",
|
|
@@ -26507,29 +26984,107 @@ function fillDefaults(config) {
|
|
|
26507
26984
|
provider: config.provider || "claude"
|
|
26508
26985
|
};
|
|
26509
26986
|
}
|
|
26510
|
-
function
|
|
26511
|
-
|
|
26512
|
-
|
|
26987
|
+
function configDirNames() {
|
|
26988
|
+
try {
|
|
26989
|
+
return import_fs10.default.readdirSync(CONFIG_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
26990
|
+
} catch {
|
|
26991
|
+
return [];
|
|
26992
|
+
}
|
|
26993
|
+
}
|
|
26994
|
+
function provisionedSpaceSlugs() {
|
|
26995
|
+
return configDirNames().filter(
|
|
26996
|
+
(name) => import_fs10.default.existsSync(import_path22.default.join(CONFIG_DIR, name, "bin"))
|
|
26997
|
+
);
|
|
26998
|
+
}
|
|
26999
|
+
var migrated = false;
|
|
27000
|
+
function migrateToSpaceFolders() {
|
|
27001
|
+
if (migrated) return;
|
|
27002
|
+
migrated = true;
|
|
27003
|
+
try {
|
|
27004
|
+
migrateOnce();
|
|
27005
|
+
} catch {
|
|
27006
|
+
migrated = false;
|
|
27007
|
+
}
|
|
27008
|
+
}
|
|
27009
|
+
function readLegacy(filePath) {
|
|
27010
|
+
if (!import_fs10.default.existsSync(filePath)) return { readable: true, value: null };
|
|
27011
|
+
const value2 = readSecureJson(filePath);
|
|
27012
|
+
return { readable: value2 !== null, value: value2 };
|
|
27013
|
+
}
|
|
27014
|
+
function migrateOnce() {
|
|
27015
|
+
const legacy = readLegacy(RUNNER_CONFIG_PATH);
|
|
27016
|
+
if (!legacy.readable) {
|
|
27017
|
+
throw new Error(
|
|
27018
|
+
`Cannot read ${RUNNER_CONFIG_PATH} \u2014 leaving the legacy stores in place.`
|
|
27019
|
+
);
|
|
27020
|
+
}
|
|
27021
|
+
const store = legacy.value ?? {};
|
|
27022
|
+
const base = store.default ?? {};
|
|
27023
|
+
const boards = store.boards ?? {};
|
|
27024
|
+
const strays = {};
|
|
27025
|
+
const consumed = [];
|
|
27026
|
+
let strayFiles = [];
|
|
26513
27027
|
try {
|
|
26514
|
-
|
|
27028
|
+
strayFiles = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
|
|
26515
27029
|
} catch {
|
|
26516
|
-
|
|
27030
|
+
strayFiles = [];
|
|
26517
27031
|
}
|
|
26518
|
-
for (const name of
|
|
27032
|
+
for (const name of strayFiles) {
|
|
26519
27033
|
const slug = name.slice("space-".length, -".json".length);
|
|
26520
|
-
const parsed =
|
|
26521
|
-
|
|
27034
|
+
const parsed = readLegacy(
|
|
27035
|
+
import_path22.default.join(CONFIG_DIR, name)
|
|
26522
27036
|
);
|
|
26523
|
-
if (!parsed) continue;
|
|
26524
|
-
if (
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
27037
|
+
if (!parsed.readable) continue;
|
|
27038
|
+
if (parsed.value) strays[slug] = parsed.value;
|
|
27039
|
+
consumed.push(name);
|
|
27040
|
+
}
|
|
27041
|
+
const slugs = /* @__PURE__ */ new Set([
|
|
27042
|
+
...Object.keys(boards),
|
|
27043
|
+
...Object.keys(strays),
|
|
27044
|
+
...store.defaultSlug ? [store.defaultSlug] : [],
|
|
27045
|
+
...provisionedSpaceSlugs()
|
|
27046
|
+
]);
|
|
27047
|
+
for (const slug of slugs) {
|
|
27048
|
+
if (import_fs10.default.existsSync(githubPath(slug))) continue;
|
|
27049
|
+
const over = boards[slug] ?? strays[slug] ?? {};
|
|
27050
|
+
if (!Object.keys(over).length && !Object.keys(base).length) continue;
|
|
27051
|
+
const merged = fillDefaults({
|
|
27052
|
+
repoUrl: over.repoUrl || base.repoUrl,
|
|
27053
|
+
mainBranch: over.mainBranch || base.mainBranch,
|
|
27054
|
+
gitName: over.gitName || base.gitName,
|
|
27055
|
+
gitEmail: over.gitEmail || base.gitEmail,
|
|
27056
|
+
githubToken: over.githubToken || base.githubToken,
|
|
27057
|
+
provider: over.provider || base.provider
|
|
27058
|
+
});
|
|
27059
|
+
import_fs10.default.mkdirSync(credsDir(slug), { recursive: true });
|
|
27060
|
+
writeJson(githubPath(slug), merged);
|
|
27061
|
+
}
|
|
27062
|
+
let odFiles = [];
|
|
27063
|
+
try {
|
|
27064
|
+
odFiles = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".onedrive.json"));
|
|
27065
|
+
} catch {
|
|
27066
|
+
odFiles = [];
|
|
27067
|
+
}
|
|
27068
|
+
for (const name of odFiles) {
|
|
27069
|
+
const slug = name.slice("space-".length, -".onedrive.json".length);
|
|
27070
|
+
if (import_fs10.default.existsSync(connectionPath(slug))) {
|
|
27071
|
+
consumed.push(name);
|
|
27072
|
+
continue;
|
|
27073
|
+
}
|
|
27074
|
+
const conn = readLegacy(import_path22.default.join(CONFIG_DIR, name));
|
|
27075
|
+
if (!conn.readable) continue;
|
|
27076
|
+
if (conn.value) {
|
|
27077
|
+
import_fs10.default.mkdirSync(credsDir(slug), { recursive: true });
|
|
27078
|
+
writeJson(connectionPath(slug), conn.value);
|
|
27079
|
+
}
|
|
27080
|
+
consumed.push(name);
|
|
27081
|
+
}
|
|
27082
|
+
for (const name of [...consumed, "runner-config.json"]) {
|
|
27083
|
+
try {
|
|
27084
|
+
import_fs10.default.rmSync(import_path22.default.join(CONFIG_DIR, name), { force: true });
|
|
27085
|
+
} catch {
|
|
26529
27086
|
}
|
|
26530
27087
|
}
|
|
26531
|
-
if (file.defaultSlug) writeJson(RUNNER_CONFIG_PATH, file);
|
|
26532
|
-
return file;
|
|
26533
27088
|
}
|
|
26534
27089
|
var sweptStrays = false;
|
|
26535
27090
|
function encryptStrayPlaintextConfigs() {
|
|
@@ -26538,14 +27093,14 @@ function encryptStrayPlaintextConfigs() {
|
|
|
26538
27093
|
const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
|
|
26539
27094
|
let names = [];
|
|
26540
27095
|
try {
|
|
26541
|
-
names =
|
|
27096
|
+
names = import_fs10.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
|
|
26542
27097
|
} catch {
|
|
26543
27098
|
return;
|
|
26544
27099
|
}
|
|
26545
27100
|
for (const name of names) {
|
|
26546
|
-
const filePath =
|
|
27101
|
+
const filePath = import_path22.default.join(CONFIG_DIR, name);
|
|
26547
27102
|
try {
|
|
26548
|
-
const raw =
|
|
27103
|
+
const raw = import_fs10.default.readFileSync(filePath, "utf-8");
|
|
26549
27104
|
if (!raw.startsWith("{")) continue;
|
|
26550
27105
|
writeSecureJson(filePath, JSON.parse(raw));
|
|
26551
27106
|
} catch {
|
|
@@ -26557,84 +27112,50 @@ function encryptStrayPlaintextConfigs() {
|
|
|
26557
27112
|
function resetStraySweep() {
|
|
26558
27113
|
sweptStrays = false;
|
|
26559
27114
|
}
|
|
26560
|
-
function
|
|
27115
|
+
function readBoard(spaceSlug) {
|
|
26561
27116
|
encryptStrayPlaintextConfigs();
|
|
26562
|
-
|
|
26563
|
-
return
|
|
27117
|
+
migrateToSpaceFolders();
|
|
27118
|
+
return readSecureJson(githubPath(spaceSlug)) ?? {};
|
|
26564
27119
|
}
|
|
26565
27120
|
async function getConfiguredConnections() {
|
|
26566
27121
|
const kinds = /* @__PURE__ */ new Set();
|
|
26567
|
-
let file;
|
|
26568
27122
|
try {
|
|
26569
|
-
|
|
27123
|
+
for (const slug of listConfiguredSlugs()) {
|
|
27124
|
+
if (kinds.has("git") && kinds.has("onedrive")) break;
|
|
27125
|
+
if (readBoard(slug).githubToken) kinds.add("git");
|
|
27126
|
+
if (import_fs10.default.existsSync(connectionPath(slug))) kinds.add("onedrive");
|
|
27127
|
+
}
|
|
26570
27128
|
} catch {
|
|
26571
27129
|
return [];
|
|
26572
27130
|
}
|
|
26573
|
-
const configs = [file.default ?? {}, ...Object.values(file.boards ?? {})];
|
|
26574
|
-
for (const c of configs) {
|
|
26575
|
-
if (c.githubToken) kinds.add("git");
|
|
26576
|
-
}
|
|
26577
27131
|
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
27132
|
return [...kinds];
|
|
26584
27133
|
}
|
|
26585
27134
|
function listConfiguredSlugs() {
|
|
26586
27135
|
try {
|
|
26587
|
-
|
|
26588
|
-
return
|
|
26589
|
-
|
|
26590
|
-
|
|
26591
|
-
...file.defaultSlug ? [file.defaultSlug] : []
|
|
26592
|
-
])
|
|
26593
|
-
];
|
|
27136
|
+
migrateToSpaceFolders();
|
|
27137
|
+
return configDirNames().filter(
|
|
27138
|
+
(slug) => import_fs10.default.existsSync(githubPath(slug)) || import_fs10.default.existsSync(connectionPath(slug))
|
|
27139
|
+
);
|
|
26594
27140
|
} catch {
|
|
26595
27141
|
return [];
|
|
26596
27142
|
}
|
|
26597
27143
|
}
|
|
26598
27144
|
var localConfigProvider = {
|
|
26599
27145
|
readConfig(spaceSlug) {
|
|
26600
|
-
let file;
|
|
26601
27146
|
try {
|
|
26602
|
-
|
|
27147
|
+
return fillDefaults(readBoard(spaceSlug));
|
|
26603
27148
|
} catch (err) {
|
|
26604
27149
|
if (!isSecureStoreError(err)) throw err;
|
|
26605
27150
|
return fillDefaults({});
|
|
26606
27151
|
}
|
|
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
27152
|
},
|
|
27153
|
+
// The whole config, every time: a cleared field has to persist as cleared,
|
|
27154
|
+
// which is what makes disconnect stick.
|
|
26618
27155
|
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);
|
|
27156
|
+
migrateToSpaceFolders();
|
|
27157
|
+
import_fs10.default.mkdirSync(credsDir(spaceSlug), { recursive: true });
|
|
27158
|
+
writeJson(githubPath(spaceSlug), config);
|
|
26638
27159
|
},
|
|
26639
27160
|
readOneDriveConnection(spaceSlug) {
|
|
26640
27161
|
try {
|
|
@@ -26647,7 +27168,7 @@ var localConfigProvider = {
|
|
|
26647
27168
|
writeOneDriveConnection(spaceSlug, connection) {
|
|
26648
27169
|
const filePath = connectionPath(spaceSlug);
|
|
26649
27170
|
if (!connection) {
|
|
26650
|
-
|
|
27171
|
+
import_fs10.default.rmSync(filePath, { force: true });
|
|
26651
27172
|
return;
|
|
26652
27173
|
}
|
|
26653
27174
|
writeJson(filePath, connection);
|
|
@@ -26672,7 +27193,7 @@ var localConfigProvider = {
|
|
|
26672
27193
|
"claude",
|
|
26673
27194
|
"codex"
|
|
26674
27195
|
]) {
|
|
26675
|
-
|
|
27196
|
+
import_fs10.default.mkdirSync(import_path22.default.join(dir, sub), { recursive: true });
|
|
26676
27197
|
}
|
|
26677
27198
|
return dir;
|
|
26678
27199
|
}
|
|
@@ -26846,15 +27367,15 @@ async function startDaemon(config) {
|
|
|
26846
27367
|
}
|
|
26847
27368
|
);
|
|
26848
27369
|
const indexCandidates = [
|
|
26849
|
-
|
|
26850
|
-
|
|
27370
|
+
import_path23.default.join(__dirname, "..", "index"),
|
|
27371
|
+
import_path23.default.join(__dirname, "..", "..", "index", "image")
|
|
26851
27372
|
];
|
|
26852
|
-
const indexDir = indexCandidates.find((p) =>
|
|
27373
|
+
const indexDir = indexCandidates.find((p) => import_fs11.default.existsSync(p)) ?? indexCandidates[0];
|
|
26853
27374
|
const odIndex = new IndexService({
|
|
26854
|
-
controlToken: (0,
|
|
27375
|
+
controlToken: (0, import_crypto10.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
26855
27376
|
embedUrl: () => config.serverUrl,
|
|
26856
27377
|
indexDir,
|
|
26857
|
-
dataDir:
|
|
27378
|
+
dataDir: import_path23.default.join(CONFIG_DIR, "index-data")
|
|
26858
27379
|
});
|
|
26859
27380
|
void odIndex.ensureRunning().catch((err) => {
|
|
26860
27381
|
console.error(
|
|
@@ -27468,11 +27989,27 @@ ${err.fix}`);
|
|
|
27468
27989
|
console.error("");
|
|
27469
27990
|
process.exit(1);
|
|
27470
27991
|
}
|
|
27992
|
+
var MAX_SUPPORTED_NODE_MAJOR = 24;
|
|
27993
|
+
function assertSupportedNode() {
|
|
27994
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
27995
|
+
if (Number.isNaN(major) || major <= MAX_SUPPORTED_NODE_MAJOR) return;
|
|
27996
|
+
console.error(
|
|
27997
|
+
`
|
|
27998
|
+
factiii-runner needs Node 20\u2013${MAX_SUPPORTED_NODE_MAJOR} (pinned 24.11.1); you are on Node ${process.versions.node}.
|
|
27999
|
+
The bundled OneDrive index compiles better-sqlite3 from source, and that build fails on Node 25+.
|
|
28000
|
+
|
|
28001
|
+
Switch with nvm: nvm install 24.11.1 && nvm use 24.11.1
|
|
28002
|
+
then relaunch: factiii-runner start
|
|
28003
|
+
`
|
|
28004
|
+
);
|
|
28005
|
+
process.exit(1);
|
|
28006
|
+
}
|
|
27471
28007
|
import_node_dns.default.setDefaultResultOrder("ipv4first");
|
|
27472
28008
|
var program2 = new Command();
|
|
27473
28009
|
program2.name("factiii-runner").description("Factiii Runner - run Board AI agents on remote servers").version(currentVersion());
|
|
27474
28010
|
program2.command("setup").description("Verify the host toolchain and pair this runner").option("-s, --server <url>", "API server URL", "https://api.factiii.com").action(async (opts) => {
|
|
27475
28011
|
try {
|
|
28012
|
+
assertSupportedNode();
|
|
27476
28013
|
await setup(opts.server);
|
|
27477
28014
|
} catch (err) {
|
|
27478
28015
|
console.error(
|
|
@@ -27501,6 +28038,7 @@ program2.command("connect").description("Pair this runner with a Factiii account
|
|
|
27501
28038
|
}
|
|
27502
28039
|
});
|
|
27503
28040
|
program2.command("start").description("Start the runner daemon").action(async () => {
|
|
28041
|
+
assertSupportedNode();
|
|
27504
28042
|
if (!configExists()) {
|
|
27505
28043
|
console.error('No config found. Run "factiii-runner setup" first.');
|
|
27506
28044
|
process.exit(1);
|
|
@@ -27546,6 +28084,9 @@ program2.command("status").description("Show current runner configuration").acti
|
|
|
27546
28084
|
console.log(`Token: \xB7\xB7\xB7\xB7${config.authToken.slice(-4)}`);
|
|
27547
28085
|
});
|
|
27548
28086
|
program2.command("doctor").description("Show where each host tool resolves, for a failed environment").action(async () => {
|
|
28087
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
28088
|
+
const nodeNote = nodeMajor > MAX_SUPPORTED_NODE_MAJOR ? " (UNSUPPORTED \u2014 use 24.11.1)" : "";
|
|
28089
|
+
console.log(`${"node".padEnd(14)} ${process.execPath} v${process.versions.node}${nodeNote}`);
|
|
27549
28090
|
const report = await reportToolchain(hostTarget(CONFIG_DIR));
|
|
27550
28091
|
for (const tool of report.tools) {
|
|
27551
28092
|
console.log(`${tool.bin.padEnd(14)} ${tool.location || "NOT FOUND"}`);
|