@factiii/runner 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +526 -225
- package/index/index.js +8 -2
- package/package.json +2 -1
- package/plugin/.claude-plugin/marketplace.json +12 -0
- package/plugin/factiii/.claude-plugin/plugin.json +7 -0
- package/plugin/factiii/bin/report-state.sh +43 -0
- package/plugin/factiii/hooks/hooks.json +59 -0
- package/plugin/factiii/skills/share-a-port/SKILL.md +41 -0
package/dist/cli.js
CHANGED
|
@@ -915,18 +915,18 @@ var require_suggestSimilar = __commonJS({
|
|
|
915
915
|
}
|
|
916
916
|
return d[a.length][b.length];
|
|
917
917
|
}
|
|
918
|
-
function suggestSimilar(word,
|
|
919
|
-
if (!
|
|
920
|
-
|
|
918
|
+
function suggestSimilar(word, candidates2) {
|
|
919
|
+
if (!candidates2 || candidates2.length === 0) return "";
|
|
920
|
+
candidates2 = Array.from(new Set(candidates2));
|
|
921
921
|
const searchingOptions = word.startsWith("--");
|
|
922
922
|
if (searchingOptions) {
|
|
923
923
|
word = word.slice(2);
|
|
924
|
-
|
|
924
|
+
candidates2 = candidates2.map((candidate) => candidate.slice(2));
|
|
925
925
|
}
|
|
926
926
|
let similar = [];
|
|
927
927
|
let bestDistance = maxDistance;
|
|
928
928
|
const minSimilarity = 0.4;
|
|
929
|
-
|
|
929
|
+
candidates2.forEach((candidate) => {
|
|
930
930
|
if (candidate.length <= 1) return;
|
|
931
931
|
const distance = editDistance(word, candidate);
|
|
932
932
|
const length = Math.max(word.length, candidate.length);
|
|
@@ -963,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 path18 = require("node:path");
|
|
967
967
|
var fs14 = 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 = path18.resolve(baseDir, baseName);
|
|
1900
1900
|
if (fs14.existsSync(localBin)) return localBin;
|
|
1901
|
-
if (sourceExt.includes(
|
|
1901
|
+
if (sourceExt.includes(path18.extname(baseName))) return void 0;
|
|
1902
1902
|
const foundExt = sourceExt.find(
|
|
1903
1903
|
(ext) => fs14.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 = path18.resolve(
|
|
1920
|
+
path18.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 = path18.basename(
|
|
1928
1928
|
this._scriptPath,
|
|
1929
|
-
|
|
1929
|
+
path18.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(path18.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 = path18.basename(filename, path18.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(path19) {
|
|
2795
|
+
if (path19 === void 0) return this._executableDir;
|
|
2796
|
+
this._executableDir = path19;
|
|
2797
2797
|
return this;
|
|
2798
2798
|
}
|
|
2799
2799
|
/**
|
|
@@ -3984,7 +3984,7 @@ var require_has_flag = __commonJS({
|
|
|
3984
3984
|
var require_supports_color = __commonJS({
|
|
3985
3985
|
"../../node_modules/debug/node_modules/supports-color/index.js"(exports2, module2) {
|
|
3986
3986
|
"use strict";
|
|
3987
|
-
var
|
|
3987
|
+
var os7 = require("os");
|
|
3988
3988
|
var hasFlag = require_has_flag();
|
|
3989
3989
|
var env = process.env;
|
|
3990
3990
|
var forceColor;
|
|
@@ -4022,7 +4022,7 @@ var require_supports_color = __commonJS({
|
|
|
4022
4022
|
}
|
|
4023
4023
|
const min = forceColor ? 1 : 0;
|
|
4024
4024
|
if (process.platform === "win32") {
|
|
4025
|
-
const osRelease =
|
|
4025
|
+
const osRelease = os7.release().split(".");
|
|
4026
4026
|
if (Number(process.versions.node.split(".")[0]) >= 8 && Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
4027
4027
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
4028
4028
|
}
|
|
@@ -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: path18, errorMaps, issueData } = params;
|
|
8432
|
+
const fullPath = [...path18, ...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, path18, key) {
|
|
8549
8549
|
this._cachedPath = [];
|
|
8550
8550
|
this.parent = parent;
|
|
8551
8551
|
this.data = value2;
|
|
8552
|
-
this._path =
|
|
8552
|
+
this._path = path18;
|
|
8553
8553
|
this._key = key;
|
|
8554
8554
|
}
|
|
8555
8555
|
get path() {
|
|
@@ -12908,7 +12908,7 @@ ${diff.slice(0, 8e3)}`,
|
|
|
12908
12908
|
}
|
|
12909
12909
|
|
|
12910
12910
|
// ../../shared/all/helpers/board-agent-core/engine.ts
|
|
12911
|
-
var
|
|
12911
|
+
var import_path10 = require("path");
|
|
12912
12912
|
|
|
12913
12913
|
// ../../shared/all/domains/electron.ts
|
|
12914
12914
|
var SETUP_LOG_PREFIX = "log ";
|
|
@@ -13017,7 +13017,7 @@ async function ensurePrimaryClone(target, cloneUrl, branch) {
|
|
|
13017
13017
|
await target.sh(`mkdir -p ${target.paths.worktrees}`);
|
|
13018
13018
|
}
|
|
13019
13019
|
async function addWorktree(target, workName, baseBranch, kind) {
|
|
13020
|
-
const
|
|
13020
|
+
const path18 = worktreeFor(target.paths, workName);
|
|
13021
13021
|
const branch = workBranch(workName, kind);
|
|
13022
13022
|
await gitInDir(
|
|
13023
13023
|
target,
|
|
@@ -13027,7 +13027,7 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13027
13027
|
baseBranch
|
|
13028
13028
|
).catch(() => {
|
|
13029
13029
|
});
|
|
13030
|
-
await removeWorktree(target,
|
|
13030
|
+
await removeWorktree(target, path18).catch(() => {
|
|
13031
13031
|
});
|
|
13032
13032
|
await gitInDir(
|
|
13033
13033
|
target,
|
|
@@ -13036,30 +13036,30 @@ async function addWorktree(target, workName, baseBranch, kind) {
|
|
|
13036
13036
|
"add",
|
|
13037
13037
|
"-B",
|
|
13038
13038
|
branch,
|
|
13039
|
-
|
|
13039
|
+
path18,
|
|
13040
13040
|
`origin/${baseBranch}`
|
|
13041
13041
|
);
|
|
13042
|
-
return { path:
|
|
13042
|
+
return { path: path18, branch };
|
|
13043
13043
|
}
|
|
13044
|
-
async function removeWorktree(target,
|
|
13044
|
+
async function removeWorktree(target, path18) {
|
|
13045
13045
|
await gitInDir(
|
|
13046
13046
|
target,
|
|
13047
13047
|
target.paths.repo,
|
|
13048
13048
|
"worktree",
|
|
13049
13049
|
"remove",
|
|
13050
13050
|
"--force",
|
|
13051
|
-
|
|
13051
|
+
path18
|
|
13052
13052
|
);
|
|
13053
13053
|
}
|
|
13054
|
-
async function removeWorktreeAndBranch(target,
|
|
13054
|
+
async function removeWorktreeAndBranch(target, path18) {
|
|
13055
13055
|
const branch = await gitInDir(
|
|
13056
13056
|
target,
|
|
13057
|
-
|
|
13057
|
+
path18,
|
|
13058
13058
|
"rev-parse",
|
|
13059
13059
|
"--abbrev-ref",
|
|
13060
13060
|
"HEAD"
|
|
13061
13061
|
).then((out) => out.trim()).catch(() => "");
|
|
13062
|
-
await removeWorktree(target,
|
|
13062
|
+
await removeWorktree(target, path18);
|
|
13063
13063
|
if (branch && branch !== "HEAD") {
|
|
13064
13064
|
await gitInDir(target, target.paths.repo, "branch", "-D", branch).catch(
|
|
13065
13065
|
() => {
|
|
@@ -13073,8 +13073,8 @@ async function pruneWorktrees(target) {
|
|
|
13073
13073
|
}
|
|
13074
13074
|
);
|
|
13075
13075
|
}
|
|
13076
|
-
function isWorktreePath(target,
|
|
13077
|
-
return
|
|
13076
|
+
function isWorktreePath(target, path18) {
|
|
13077
|
+
return path18.startsWith(`${target.paths.worktrees}/`);
|
|
13078
13078
|
}
|
|
13079
13079
|
async function setGitIdentity(target, name, email, workdir = target.paths.workspace) {
|
|
13080
13080
|
if (name) await gitInDir(target, workdir, "config", "user.name", name);
|
|
@@ -13100,13 +13100,13 @@ function parseStatus(raw) {
|
|
|
13100
13100
|
const y = line[1];
|
|
13101
13101
|
const rest = line.slice(3);
|
|
13102
13102
|
const arrow = rest.indexOf(" -> ");
|
|
13103
|
-
const
|
|
13103
|
+
const path18 = arrow === -1 ? rest : rest.slice(arrow + 4);
|
|
13104
13104
|
if (x === "?" || y === "?") {
|
|
13105
|
-
unstaged.push({ path:
|
|
13105
|
+
unstaged.push({ path: path18, code: "A" });
|
|
13106
13106
|
continue;
|
|
13107
13107
|
}
|
|
13108
|
-
if (x !== " ") staged.push({ path:
|
|
13109
|
-
if (y !== " ") unstaged.push({ path:
|
|
13108
|
+
if (x !== " ") staged.push({ path: path18, code: x });
|
|
13109
|
+
if (y !== " ") unstaged.push({ path: path18, code: y });
|
|
13110
13110
|
}
|
|
13111
13111
|
return { staged, unstaged };
|
|
13112
13112
|
}
|
|
@@ -13300,16 +13300,18 @@ function cardKey(postId) {
|
|
|
13300
13300
|
return `card:${safeId(postId)}`;
|
|
13301
13301
|
}
|
|
13302
13302
|
var INDEX_KEY = "cards";
|
|
13303
|
-
function
|
|
13303
|
+
function redisSocketPath(target) {
|
|
13304
13304
|
return `${target.paths.state}/redis.sock`;
|
|
13305
13305
|
}
|
|
13306
13306
|
async function redisCli(target, args) {
|
|
13307
|
-
const out = await target.sh(
|
|
13307
|
+
const out = await target.sh(
|
|
13308
|
+
`redis-cli -s ${redisSocketPath(target)} ${args}`
|
|
13309
|
+
);
|
|
13308
13310
|
return out.replace(/\n$/, "");
|
|
13309
13311
|
}
|
|
13310
13312
|
function redisCliStdin(target, args, stdin) {
|
|
13311
13313
|
return target.runWithStdin(
|
|
13312
|
-
["redis-cli", "-s",
|
|
13314
|
+
["redis-cli", "-s", redisSocketPath(target), "-x", ...args],
|
|
13313
13315
|
stdin,
|
|
13314
13316
|
`redis-cli ${args.join(" ")}`
|
|
13315
13317
|
);
|
|
@@ -13322,7 +13324,7 @@ async function ensureRedis(target) {
|
|
|
13322
13324
|
}
|
|
13323
13325
|
const stateDir = target.paths.state;
|
|
13324
13326
|
await target.sh(
|
|
13325
|
-
`mkdir -p ${stateDir} && redis-server --daemonize yes --appendonly yes --save '' --dir ${stateDir} --port 0 --unixsocket ${
|
|
13327
|
+
`mkdir -p ${stateDir} && redis-server --daemonize yes --appendonly yes --save '' --dir ${stateDir} --port 0 --unixsocket ${redisSocketPath(target)} --unixsocketperm 700`
|
|
13326
13328
|
);
|
|
13327
13329
|
for (let i = 0; i < 20; i += 1) {
|
|
13328
13330
|
try {
|
|
@@ -13461,10 +13463,11 @@ function execLogin(command, opts) {
|
|
|
13461
13463
|
return new Promise((resolve2) => {
|
|
13462
13464
|
let output = "";
|
|
13463
13465
|
let settled = false;
|
|
13466
|
+
let timer;
|
|
13464
13467
|
const done = (code, extra = "") => {
|
|
13465
13468
|
if (settled) return;
|
|
13466
13469
|
settled = true;
|
|
13467
|
-
clearTimeout(timer);
|
|
13470
|
+
if (timer) clearTimeout(timer);
|
|
13468
13471
|
resolve2({ code, output: (output + extra).slice(-cap) });
|
|
13469
13472
|
};
|
|
13470
13473
|
let proc;
|
|
@@ -13481,7 +13484,7 @@ function execLogin(command, opts) {
|
|
|
13481
13484
|
done(-1, err instanceof Error ? err.message : String(err));
|
|
13482
13485
|
return;
|
|
13483
13486
|
}
|
|
13484
|
-
|
|
13487
|
+
timer = setTimeout(() => {
|
|
13485
13488
|
killTree(proc);
|
|
13486
13489
|
done(-1, `
|
|
13487
13490
|
[timed out after ${Math.round(opts.timeout / 1e3)}s]`);
|
|
@@ -13687,6 +13690,7 @@ function toSummary(session) {
|
|
|
13687
13690
|
pendingQuestion: session.pendingQuestion,
|
|
13688
13691
|
error: session.error,
|
|
13689
13692
|
startedAt: session.startedAt,
|
|
13693
|
+
agentState: session.agentState,
|
|
13690
13694
|
targetBranch: session.targetBranch,
|
|
13691
13695
|
taskTitle: session.taskTitle,
|
|
13692
13696
|
todos: session.todos,
|
|
@@ -15916,18 +15920,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
15916
15920
|
await this.core.ensureSpaceWorkspace();
|
|
15917
15921
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
15918
15922
|
if (hasGit) {
|
|
15919
|
-
const { path:
|
|
15923
|
+
const { path: path18 } = await addWorktree(
|
|
15920
15924
|
this.core.target(),
|
|
15921
15925
|
session.taskId,
|
|
15922
15926
|
baseBranch || config.mainBranch || "main",
|
|
15923
15927
|
"card"
|
|
15924
15928
|
);
|
|
15925
|
-
session.workspacePath =
|
|
15929
|
+
session.workspacePath = path18;
|
|
15926
15930
|
await setGitIdentity(
|
|
15927
15931
|
this.core.target(),
|
|
15928
15932
|
config.gitName,
|
|
15929
15933
|
config.gitEmail,
|
|
15930
|
-
|
|
15934
|
+
path18
|
|
15931
15935
|
);
|
|
15932
15936
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
15933
15937
|
} else {
|
|
@@ -16149,18 +16153,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16149
16153
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16150
16154
|
await this.core.ensureSpaceWorkspace();
|
|
16151
16155
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16152
|
-
const { path:
|
|
16156
|
+
const { path: path18 } = await addWorktree(
|
|
16153
16157
|
this.core.target(),
|
|
16154
16158
|
session.taskId,
|
|
16155
16159
|
config.mainBranch || "main",
|
|
16156
16160
|
"card"
|
|
16157
16161
|
);
|
|
16158
|
-
session.workspacePath =
|
|
16162
|
+
session.workspacePath = path18;
|
|
16159
16163
|
await setGitIdentity(
|
|
16160
16164
|
this.core.target(),
|
|
16161
16165
|
config.gitName,
|
|
16162
16166
|
config.gitEmail,
|
|
16163
|
-
|
|
16167
|
+
path18
|
|
16164
16168
|
);
|
|
16165
16169
|
await this.putCard(session);
|
|
16166
16170
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16455,6 +16459,50 @@ async function startTmuxSession(opts) {
|
|
|
16455
16459
|
};
|
|
16456
16460
|
}
|
|
16457
16461
|
|
|
16462
|
+
// ../../shared/all/helpers/terminal-ai/agentState.ts
|
|
16463
|
+
var STATES = /* @__PURE__ */ new Set(["working", "idle", "blocked"]);
|
|
16464
|
+
var AGENT_STATE_CHANNEL = "agent-state";
|
|
16465
|
+
function agentStateKey(paneId) {
|
|
16466
|
+
return `agent-state:${paneId}`;
|
|
16467
|
+
}
|
|
16468
|
+
function parseAgentMessage(payload) {
|
|
16469
|
+
const at = payload.indexOf(" ");
|
|
16470
|
+
if (at <= 0) return null;
|
|
16471
|
+
const state = parseAgentState(payload.slice(at + 1));
|
|
16472
|
+
return state ? { paneId: payload.slice(0, at), state } : null;
|
|
16473
|
+
}
|
|
16474
|
+
function postIdForSessionName(sessionName, postIds) {
|
|
16475
|
+
for (const postId of postIds) {
|
|
16476
|
+
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
16477
|
+
if (sessionName.startsWith(prefix)) return postId;
|
|
16478
|
+
}
|
|
16479
|
+
return null;
|
|
16480
|
+
}
|
|
16481
|
+
function parseAgentState(text) {
|
|
16482
|
+
const word = text.trim();
|
|
16483
|
+
return STATES.has(word) ? word : null;
|
|
16484
|
+
}
|
|
16485
|
+
function drainStateMessages(buffer) {
|
|
16486
|
+
const lines = buffer.split("\n");
|
|
16487
|
+
const partial = lines.pop() ?? "";
|
|
16488
|
+
const states = [];
|
|
16489
|
+
let i = 0;
|
|
16490
|
+
let consumed = 0;
|
|
16491
|
+
while (i < lines.length) {
|
|
16492
|
+
if (lines[i] !== "message") {
|
|
16493
|
+
i += 1;
|
|
16494
|
+
consumed = i;
|
|
16495
|
+
continue;
|
|
16496
|
+
}
|
|
16497
|
+
if (i + 2 >= lines.length) break;
|
|
16498
|
+
const parsed = parseAgentMessage(lines[i + 2]);
|
|
16499
|
+
if (parsed) states.push(parsed);
|
|
16500
|
+
i += 3;
|
|
16501
|
+
consumed = i;
|
|
16502
|
+
}
|
|
16503
|
+
return { states, rest: [...lines.slice(consumed), partial].join("\n") };
|
|
16504
|
+
}
|
|
16505
|
+
|
|
16458
16506
|
// ../../shared/all/helpers/terminal-ai/processes.ts
|
|
16459
16507
|
var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
|
|
16460
16508
|
var PS_FIELDS = 5;
|
|
@@ -16500,6 +16548,45 @@ function descendantsOf(rows, roots) {
|
|
|
16500
16548
|
}
|
|
16501
16549
|
return found.sort((a, b) => a.pid - b.pid);
|
|
16502
16550
|
}
|
|
16551
|
+
function panesByPid(raw) {
|
|
16552
|
+
const found = /* @__PURE__ */ new Map();
|
|
16553
|
+
for (const line of raw.split("\n")) {
|
|
16554
|
+
const trimmed = line.trim();
|
|
16555
|
+
const pid = Number(trimmed.split(/\s+/)[0]);
|
|
16556
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
16557
|
+
const match = /(?:^|\s)TMUX_PANE=(%\d+)(?:\s|$)/.exec(trimmed);
|
|
16558
|
+
if (match) found.set(pid, match[1]);
|
|
16559
|
+
}
|
|
16560
|
+
return found;
|
|
16561
|
+
}
|
|
16562
|
+
async function processesForCard(panes, table, lookupPanes) {
|
|
16563
|
+
const mine = /* @__PURE__ */ new Map();
|
|
16564
|
+
for (const pane of panes) {
|
|
16565
|
+
for (const row of processesForPane(table, pane.pid)) {
|
|
16566
|
+
mine.set(row.pid, { tabId: pane.tabId, row });
|
|
16567
|
+
}
|
|
16568
|
+
}
|
|
16569
|
+
const shells = new Set(panes.map((pane) => pane.pid));
|
|
16570
|
+
const rest = table.filter((row) => row.pid > 1 && !mine.has(row.pid) && !shells.has(row.pid)).map((row) => row.pid);
|
|
16571
|
+
if (rest.length === 0) return mine;
|
|
16572
|
+
const paneOf = await lookupPanes(rest);
|
|
16573
|
+
const tabByPane = new Map(panes.map((pane) => [pane.paneId, pane.tabId]));
|
|
16574
|
+
const adopted = /* @__PURE__ */ new Map();
|
|
16575
|
+
for (const row of table) {
|
|
16576
|
+
const tabId = tabByPane.get(paneOf.get(row.pid) ?? "");
|
|
16577
|
+
if (!tabId || mine.has(row.pid)) continue;
|
|
16578
|
+
mine.set(row.pid, { tabId, row });
|
|
16579
|
+
const roots = adopted.get(tabId);
|
|
16580
|
+
if (roots) roots.push(row.pid);
|
|
16581
|
+
else adopted.set(tabId, [row.pid]);
|
|
16582
|
+
}
|
|
16583
|
+
for (const [tabId, roots] of adopted) {
|
|
16584
|
+
for (const row of descendantsOf(table, roots)) {
|
|
16585
|
+
if (!mine.has(row.pid)) mine.set(row.pid, { tabId, row });
|
|
16586
|
+
}
|
|
16587
|
+
}
|
|
16588
|
+
return mine;
|
|
16589
|
+
}
|
|
16503
16590
|
function commandOf(args) {
|
|
16504
16591
|
const first = args.trim().split(/\s+/)[0] ?? "";
|
|
16505
16592
|
const base = first.split("/").pop() ?? first;
|
|
@@ -16624,12 +16711,12 @@ var FLUSH_MS = 120;
|
|
|
16624
16711
|
var SELF_WRITE_MS = 2e3;
|
|
16625
16712
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16626
16713
|
var importFs = new Function("s", "return import(s)");
|
|
16627
|
-
async function resolveReal(
|
|
16714
|
+
async function resolveReal(path18) {
|
|
16628
16715
|
try {
|
|
16629
16716
|
const { realpath } = await importFs("fs/promises");
|
|
16630
|
-
return await realpath(
|
|
16717
|
+
return await realpath(path18);
|
|
16631
16718
|
} catch {
|
|
16632
|
-
return
|
|
16719
|
+
return path18;
|
|
16633
16720
|
}
|
|
16634
16721
|
}
|
|
16635
16722
|
var loadSubscribe = null;
|
|
@@ -16743,19 +16830,19 @@ var WorkspaceWatcher = class {
|
|
|
16743
16830
|
}
|
|
16744
16831
|
record(kind, relPath) {
|
|
16745
16832
|
if (this.closed) return;
|
|
16746
|
-
const
|
|
16747
|
-
const expiry = this.selfWrites.get(
|
|
16833
|
+
const path18 = "/" + relPath;
|
|
16834
|
+
const expiry = this.selfWrites.get(path18);
|
|
16748
16835
|
if (expiry !== void 0) {
|
|
16749
16836
|
if (expiry > Date.now()) return;
|
|
16750
|
-
this.selfWrites.delete(
|
|
16837
|
+
this.selfWrites.delete(path18);
|
|
16751
16838
|
}
|
|
16752
16839
|
const { added, changed, removed } = this.pending;
|
|
16753
|
-
added.delete(
|
|
16754
|
-
changed.delete(
|
|
16755
|
-
removed.delete(
|
|
16756
|
-
if (kind === "added") added.add(
|
|
16757
|
-
else if (kind === "changed") changed.add(
|
|
16758
|
-
else removed.add(
|
|
16840
|
+
added.delete(path18);
|
|
16841
|
+
changed.delete(path18);
|
|
16842
|
+
removed.delete(path18);
|
|
16843
|
+
if (kind === "added") added.add(path18);
|
|
16844
|
+
else if (kind === "changed") changed.add(path18);
|
|
16845
|
+
else removed.add(path18);
|
|
16759
16846
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
16760
16847
|
this.pending = { ...emptyPending(), resync: true };
|
|
16761
16848
|
}
|
|
@@ -16774,8 +16861,8 @@ var WorkspaceWatcher = class {
|
|
|
16774
16861
|
this.pending = emptyPending();
|
|
16775
16862
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
16776
16863
|
const now = Date.now();
|
|
16777
|
-
for (const [
|
|
16778
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
16864
|
+
for (const [path18, expiry] of this.selfWrites) {
|
|
16865
|
+
if (expiry <= now) this.selfWrites.delete(path18);
|
|
16779
16866
|
}
|
|
16780
16867
|
this.emit({
|
|
16781
16868
|
added: resync ? [] : [...added],
|
|
@@ -16808,6 +16895,20 @@ function posixDirname(p) {
|
|
|
16808
16895
|
function shEscape(s) {
|
|
16809
16896
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
16810
16897
|
}
|
|
16898
|
+
async function writeBase64File(target, abs, b64, opts = {}) {
|
|
16899
|
+
const file = shEscape(abs);
|
|
16900
|
+
const cap = opts.maxBytes ? ` && if [ $(wc -c < ${file}) -gt ${opts.maxBytes} ]; then rm -f ${file}; echo 'file too large' >&2; exit 1; fi` : "";
|
|
16901
|
+
await target.runWithStdin(
|
|
16902
|
+
[
|
|
16903
|
+
"bash",
|
|
16904
|
+
"-c",
|
|
16905
|
+
`mkdir -p ${shEscape(posixDirname(abs))} && base64 -d ${opts.append ? ">>" : ">"} ${file}${cap}`
|
|
16906
|
+
],
|
|
16907
|
+
b64,
|
|
16908
|
+
`Write ${abs}`
|
|
16909
|
+
);
|
|
16910
|
+
}
|
|
16911
|
+
var PANE_REREAD_MS = 1e3;
|
|
16811
16912
|
var TerminalModeEngine = class {
|
|
16812
16913
|
constructor(core, emitter) {
|
|
16813
16914
|
this.core = core;
|
|
@@ -16818,12 +16919,117 @@ var TerminalModeEngine = class {
|
|
|
16818
16919
|
/** Worktree name each session claimed, keyed by postId. Covers the window
|
|
16819
16920
|
* `claimWorkName`'s disk scan cannot: a name taken but not yet created. */
|
|
16820
16921
|
this.reservedNames = /* @__PURE__ */ new Map();
|
|
16922
|
+
// ── agent activity ──
|
|
16923
|
+
/** Long-lived `redis-cli subscribe` on the space's own socket. One for every
|
|
16924
|
+
* session: the channel is space-wide and the pane rides in the payload, so
|
|
16925
|
+
* a session starting does not mean re-subscribing. */
|
|
16926
|
+
this.stateSub = null;
|
|
16927
|
+
/** Applies states one at a time; see the comment at the push site. */
|
|
16928
|
+
this.stateQueue = Promise.resolve();
|
|
16929
|
+
/** pane id -> tmux session name, refreshed only when a pane we have not seen
|
|
16930
|
+
* turns up. Panes outlive individual messages, so re-reading the whole map
|
|
16931
|
+
* per message would be a tmux call per hook. */
|
|
16932
|
+
this.paneSessions = /* @__PURE__ */ new Map();
|
|
16933
|
+
this.paneReadAt = 0;
|
|
16934
|
+
}
|
|
16935
|
+
/** Idempotent, like startWatching: re-entering a session must not stack
|
|
16936
|
+
* subscribers. */
|
|
16937
|
+
async startStateSub() {
|
|
16938
|
+
if (this.stateSub) return;
|
|
16939
|
+
const target = this.core.target();
|
|
16940
|
+
const child = await target.spawn([
|
|
16941
|
+
"redis-cli",
|
|
16942
|
+
"-s",
|
|
16943
|
+
redisSocketPath(target),
|
|
16944
|
+
"subscribe",
|
|
16945
|
+
AGENT_STATE_CHANNEL
|
|
16946
|
+
]).catch(() => null);
|
|
16947
|
+
if (!child) return;
|
|
16948
|
+
this.stateSub = child;
|
|
16949
|
+
let buffer = "";
|
|
16950
|
+
child.stdout?.on("data", (chunk) => {
|
|
16951
|
+
buffer += String(chunk);
|
|
16952
|
+
const { states, rest } = drainStateMessages(buffer);
|
|
16953
|
+
buffer = rest;
|
|
16954
|
+
for (const { paneId, state } of states) {
|
|
16955
|
+
this.stateQueue = this.stateQueue.then(() => this.applyPaneState(paneId, state)).catch(() => {
|
|
16956
|
+
});
|
|
16957
|
+
}
|
|
16958
|
+
});
|
|
16959
|
+
child.once("exit", () => {
|
|
16960
|
+
if (this.stateSub === child) this.stateSub = null;
|
|
16961
|
+
});
|
|
16962
|
+
void this.hydrateAgentStates();
|
|
16963
|
+
}
|
|
16964
|
+
/** Read the last reported state for every live session's pane.
|
|
16965
|
+
*
|
|
16966
|
+
* Subscribing only catches what happens next. After a runner restart the
|
|
16967
|
+
* sessions come back but no hook has fired yet, so an idle session would
|
|
16968
|
+
* show as working until someone typed in it — which for an idle session may
|
|
16969
|
+
* be never. This is what the hook's SET alongside its PUBLISH is for. */
|
|
16970
|
+
async hydrateAgentStates() {
|
|
16971
|
+
if (this.sessions.size === 0) return;
|
|
16972
|
+
await this.refreshPanes();
|
|
16973
|
+
const target = this.core.target();
|
|
16974
|
+
for (const [paneId, name] of this.paneSessions) {
|
|
16975
|
+
const postId = postIdForSessionName(name, this.sessions.keys());
|
|
16976
|
+
const session = postId ? this.sessions.get(postId) : null;
|
|
16977
|
+
if (!session || session.agentState) continue;
|
|
16978
|
+
const raw = await target.sh(
|
|
16979
|
+
`redis-cli -s ${redisSocketPath(target)} get '${agentStateKey(paneId)}'`
|
|
16980
|
+
).catch(() => "");
|
|
16981
|
+
const state = parseAgentState(raw);
|
|
16982
|
+
if (!state) continue;
|
|
16983
|
+
session.agentState = state;
|
|
16984
|
+
this.emitter.emitSessionUpdate(session);
|
|
16985
|
+
}
|
|
16986
|
+
}
|
|
16987
|
+
/** Called after a session is removed, never before: every stopWatching call
|
|
16988
|
+
* site runs while the session is still in the map, so keying this off
|
|
16989
|
+
* stopWatching would leave the subscriber running for the life of the
|
|
16990
|
+
* process. */
|
|
16991
|
+
releaseStateSub() {
|
|
16992
|
+
if (this.sessions.size > 0) return;
|
|
16993
|
+
this.stateSub?.kill();
|
|
16994
|
+
this.stateSub = null;
|
|
16995
|
+
}
|
|
16996
|
+
async applyPaneState(paneId, state) {
|
|
16997
|
+
let name = this.paneSessions.get(paneId);
|
|
16998
|
+
if (!name) {
|
|
16999
|
+
await this.refreshPanes();
|
|
17000
|
+
name = this.paneSessions.get(paneId);
|
|
17001
|
+
}
|
|
17002
|
+
if (!name) return;
|
|
17003
|
+
const postId = postIdForSessionName(name, this.sessions.keys());
|
|
17004
|
+
if (!postId) return;
|
|
17005
|
+
const session = this.sessions.get(postId);
|
|
17006
|
+
if (!session || session.agentState === state) return;
|
|
17007
|
+
session.agentState = state;
|
|
17008
|
+
this.emitter.emitSessionUpdate(session);
|
|
17009
|
+
}
|
|
17010
|
+
/** Rate-limited: a pane that will never resolve — one whose session has
|
|
17011
|
+
* since closed — would otherwise re-list on every message it sent, which is
|
|
17012
|
+
* a tmux subprocess per hook. */
|
|
17013
|
+
async refreshPanes() {
|
|
17014
|
+
const now = Date.now();
|
|
17015
|
+
if (now - this.paneReadAt < PANE_REREAD_MS) return;
|
|
17016
|
+
this.paneReadAt = now;
|
|
17017
|
+
const target = this.core.target();
|
|
17018
|
+
const out = await target.sh(
|
|
17019
|
+
`${tmuxCli(target)} list-panes -a -F '#{pane_id}|#{session_name}' 2>/dev/null || true`
|
|
17020
|
+
).catch(() => "");
|
|
17021
|
+
this.paneSessions.clear();
|
|
17022
|
+
for (const raw of out.split("\n")) {
|
|
17023
|
+
const [pane, name] = raw.trim().split("|");
|
|
17024
|
+
if (pane && name) this.paneSessions.set(pane, name);
|
|
17025
|
+
}
|
|
16821
17026
|
}
|
|
16822
17027
|
// ── working-tree watching ──
|
|
16823
17028
|
/** Start watching a session's tree. Idempotent: re-entering a session (the
|
|
16824
17029
|
* modal reopening, a hydrate after restart) must not stack watchers. */
|
|
16825
17030
|
startWatching(session) {
|
|
16826
17031
|
const postId = session.postId;
|
|
17032
|
+
void this.startStateSub();
|
|
16827
17033
|
if (this.watchers.has(postId)) return;
|
|
16828
17034
|
const paths = this.core.target().paths;
|
|
16829
17035
|
const root = session.workspacePath || paths.root;
|
|
@@ -16860,8 +17066,8 @@ var TerminalModeEngine = class {
|
|
|
16860
17066
|
/** Suppress the change event our own write is about to produce, so a save
|
|
16861
17067
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
16862
17068
|
* the watcher emits, since callers spell the path either way. */
|
|
16863
|
-
expectWrite(postId,
|
|
16864
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17069
|
+
expectWrite(postId, path18) {
|
|
17070
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path18));
|
|
16865
17071
|
}
|
|
16866
17072
|
// ── Lifecycle / ModeEngine ──
|
|
16867
17073
|
start(base, args) {
|
|
@@ -16907,6 +17113,7 @@ var TerminalModeEngine = class {
|
|
|
16907
17113
|
this.stopWatching(postId);
|
|
16908
17114
|
this.reservedNames.delete(postId);
|
|
16909
17115
|
this.sessions.delete(postId);
|
|
17116
|
+
this.releaseStateSub();
|
|
16910
17117
|
}
|
|
16911
17118
|
async kill(postId) {
|
|
16912
17119
|
let session = this.sessions.get(postId);
|
|
@@ -16924,6 +17131,7 @@ var TerminalModeEngine = class {
|
|
|
16924
17131
|
const removedPostId = session.postId;
|
|
16925
17132
|
this.reservedNames.delete(removedPostId);
|
|
16926
17133
|
this.sessions.delete(removedPostId);
|
|
17134
|
+
this.releaseStateSub();
|
|
16927
17135
|
this.emitter.emitSessionRemoved(removedPostId);
|
|
16928
17136
|
}
|
|
16929
17137
|
destroy() {
|
|
@@ -17056,18 +17264,18 @@ var TerminalModeEngine = class {
|
|
|
17056
17264
|
this.reservedNames.values()
|
|
17057
17265
|
);
|
|
17058
17266
|
this.reservedNames.set(session.postId, workName);
|
|
17059
|
-
const { path:
|
|
17267
|
+
const { path: path18 } = await addWorktree(
|
|
17060
17268
|
this.core.target(),
|
|
17061
17269
|
workName,
|
|
17062
17270
|
branch,
|
|
17063
17271
|
"terminal"
|
|
17064
17272
|
);
|
|
17065
|
-
session.workspacePath =
|
|
17273
|
+
session.workspacePath = path18;
|
|
17066
17274
|
await setGitIdentity(
|
|
17067
17275
|
this.core.target(),
|
|
17068
17276
|
config.gitName,
|
|
17069
17277
|
config.gitEmail,
|
|
17070
|
-
|
|
17278
|
+
path18
|
|
17071
17279
|
);
|
|
17072
17280
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17073
17281
|
} else {
|
|
@@ -17129,11 +17337,11 @@ var TerminalModeEngine = class {
|
|
|
17129
17337
|
}
|
|
17130
17338
|
async bareFsList({
|
|
17131
17339
|
postId,
|
|
17132
|
-
path:
|
|
17340
|
+
path: path18
|
|
17133
17341
|
}) {
|
|
17134
17342
|
const target = this.getBareTarget(postId);
|
|
17135
17343
|
const root = this.getBareRoot(postId);
|
|
17136
|
-
const abs = this.resolveBarePath(
|
|
17344
|
+
const abs = this.resolveBarePath(path18, root);
|
|
17137
17345
|
const relRoot = abs.slice(root.length) || "/";
|
|
17138
17346
|
const out = await target.run([
|
|
17139
17347
|
"sh",
|
|
@@ -17183,53 +17391,56 @@ var TerminalModeEngine = class {
|
|
|
17183
17391
|
}
|
|
17184
17392
|
async bareFsRead({
|
|
17185
17393
|
postId,
|
|
17186
|
-
path:
|
|
17394
|
+
path: path18
|
|
17187
17395
|
}) {
|
|
17188
17396
|
const target = this.getBareTarget(postId);
|
|
17189
|
-
const abs = this.resolveBarePath(
|
|
17397
|
+
const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
|
|
17190
17398
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17191
17399
|
'`)).trim();
|
|
17192
17400
|
return { content: b64, encoding: "base64" };
|
|
17193
17401
|
}
|
|
17194
17402
|
async bareFsWrite({
|
|
17195
17403
|
postId,
|
|
17196
|
-
path:
|
|
17404
|
+
path: path18,
|
|
17197
17405
|
content,
|
|
17198
17406
|
encoding
|
|
17199
17407
|
}) {
|
|
17200
17408
|
const target = this.getBareTarget(postId);
|
|
17201
|
-
const abs = this.resolveBarePath(
|
|
17409
|
+
const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
|
|
17202
17410
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17203
17411
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17204
17412
|
throw new Error("Invalid base64 payload.");
|
|
17205
17413
|
}
|
|
17206
|
-
this.expectWrite(String(postId),
|
|
17207
|
-
await target
|
|
17208
|
-
`mkdir -p ${shEscape(posixDirname(abs))} && printf %s ${shEscape(b64)} | base64 -d > ${shEscape(abs)}`
|
|
17209
|
-
);
|
|
17414
|
+
this.expectWrite(String(postId), path18);
|
|
17415
|
+
await writeBase64File(target, abs, b64);
|
|
17210
17416
|
const session = this.sessions.get(String(postId));
|
|
17211
|
-
if (session) delete session.buffers[
|
|
17417
|
+
if (session) delete session.buffers[path18];
|
|
17212
17418
|
}
|
|
17213
|
-
/**
|
|
17214
|
-
* card worktree so it never touches the repo)
|
|
17419
|
+
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17420
|
+
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
17421
|
+
* Returns the absolute path on the last chunk, '' before that. */
|
|
17215
17422
|
async terminalDropImage({
|
|
17216
17423
|
postId,
|
|
17217
17424
|
content,
|
|
17218
|
-
ext
|
|
17425
|
+
ext,
|
|
17426
|
+
dropId,
|
|
17427
|
+
last
|
|
17219
17428
|
}) {
|
|
17220
17429
|
const target = this.getBareTarget(postId);
|
|
17221
17430
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(content)) {
|
|
17222
17431
|
throw new Error("Invalid base64 payload.");
|
|
17223
17432
|
}
|
|
17224
|
-
if (content.length > 14e6) {
|
|
17225
|
-
throw new Error("Image too large (max ~10MB).");
|
|
17226
|
-
}
|
|
17227
17433
|
const safeExt = (ext || "png").toLowerCase().replace(/[^a-z0-9]/g, "") || "png";
|
|
17434
|
+
const oneShot = !dropId;
|
|
17435
|
+
const safeId2 = oneShot ? (0, import_crypto.randomUUID)() : dropId.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
17436
|
+
if (!safeId2) throw new Error("Invalid drop id.");
|
|
17228
17437
|
const dropsDir = `${target.paths.root}/drops`;
|
|
17229
|
-
const abs = `${dropsDir}/${
|
|
17230
|
-
await target
|
|
17231
|
-
|
|
17232
|
-
|
|
17438
|
+
const abs = `${dropsDir}/${safeId2}.${safeExt}`;
|
|
17439
|
+
await writeBase64File(target, abs, content, {
|
|
17440
|
+
append: !oneShot,
|
|
17441
|
+
maxBytes: 1e7
|
|
17442
|
+
});
|
|
17443
|
+
if (!last && !oneShot) return "";
|
|
17233
17444
|
try {
|
|
17234
17445
|
await target.sh(
|
|
17235
17446
|
`find ${shEscape(dropsDir)} -type f -mtime +1 -delete 2>/dev/null || true`
|
|
@@ -17253,39 +17464,39 @@ var TerminalModeEngine = class {
|
|
|
17253
17464
|
}
|
|
17254
17465
|
bareOpenFile({
|
|
17255
17466
|
postId: rawPostId,
|
|
17256
|
-
path:
|
|
17467
|
+
path: path18
|
|
17257
17468
|
}) {
|
|
17258
17469
|
const session = this.sessions.get(String(rawPostId));
|
|
17259
17470
|
if (!session || session.mode !== "bare") return;
|
|
17260
|
-
if (!session.openedFiles.includes(
|
|
17261
|
-
session.openedFiles.push(
|
|
17471
|
+
if (!session.openedFiles.includes(path18)) {
|
|
17472
|
+
session.openedFiles.push(path18);
|
|
17262
17473
|
}
|
|
17263
17474
|
}
|
|
17264
17475
|
bareCloseFile({
|
|
17265
17476
|
postId: rawPostId,
|
|
17266
|
-
path:
|
|
17477
|
+
path: path18
|
|
17267
17478
|
}) {
|
|
17268
17479
|
const session = this.sessions.get(String(rawPostId));
|
|
17269
17480
|
if (!session || session.mode !== "bare") return;
|
|
17270
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17271
|
-
delete session.buffers[
|
|
17481
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path18);
|
|
17482
|
+
delete session.buffers[path18];
|
|
17272
17483
|
}
|
|
17273
17484
|
bareBufferSet({
|
|
17274
17485
|
postId: rawPostId,
|
|
17275
|
-
path:
|
|
17486
|
+
path: path18,
|
|
17276
17487
|
content
|
|
17277
17488
|
}) {
|
|
17278
17489
|
const session = this.sessions.get(String(rawPostId));
|
|
17279
17490
|
if (!session || session.mode !== "bare") return;
|
|
17280
|
-
session.buffers[
|
|
17491
|
+
session.buffers[path18] = content;
|
|
17281
17492
|
}
|
|
17282
17493
|
async bareFsDelete({
|
|
17283
17494
|
postId,
|
|
17284
|
-
path:
|
|
17495
|
+
path: path18
|
|
17285
17496
|
}) {
|
|
17286
17497
|
const target = this.getBareTarget(postId);
|
|
17287
17498
|
const root = this.getBareRoot(postId);
|
|
17288
|
-
const abs = this.resolveBarePath(
|
|
17499
|
+
const abs = this.resolveBarePath(path18, root);
|
|
17289
17500
|
if (abs === root) {
|
|
17290
17501
|
throw new Error("Refusing to delete the repo root.");
|
|
17291
17502
|
}
|
|
@@ -17293,10 +17504,10 @@ var TerminalModeEngine = class {
|
|
|
17293
17504
|
}
|
|
17294
17505
|
async bareFsMkdir({
|
|
17295
17506
|
postId,
|
|
17296
|
-
path:
|
|
17507
|
+
path: path18
|
|
17297
17508
|
}) {
|
|
17298
17509
|
const target = this.getBareTarget(postId);
|
|
17299
|
-
const abs = this.resolveBarePath(
|
|
17510
|
+
const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
|
|
17300
17511
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17301
17512
|
}
|
|
17302
17513
|
async bareReview({
|
|
@@ -17439,53 +17650,63 @@ var TerminalModeEngine = class {
|
|
|
17439
17650
|
return [];
|
|
17440
17651
|
}
|
|
17441
17652
|
}
|
|
17442
|
-
/**
|
|
17443
|
-
*
|
|
17653
|
+
/** This card's panes: the pid its tree hangs off, and the pane id its
|
|
17654
|
+
* processes carry in their environment. */
|
|
17444
17655
|
async panePids(target, postId) {
|
|
17445
17656
|
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
17446
17657
|
const stdout = await target.sh(
|
|
17447
|
-
`${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}' 2>/dev/null || true`
|
|
17658
|
+
`${tmuxCli(target)} list-panes -a -F '#{session_name}|#{pane_pid}|#{pane_id}' 2>/dev/null || true`
|
|
17448
17659
|
).catch(() => "");
|
|
17449
17660
|
const panes = [];
|
|
17450
17661
|
for (const raw of stdout.split("\n")) {
|
|
17451
17662
|
const line = raw.trim();
|
|
17452
17663
|
if (!line.startsWith(prefix)) continue;
|
|
17453
|
-
const
|
|
17454
|
-
const
|
|
17455
|
-
if (
|
|
17456
|
-
|
|
17457
|
-
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
17458
|
-
panes.push({ tabId: rest.slice(0, sep), pid });
|
|
17664
|
+
const [tabId, rawPid, paneId] = line.slice(prefix.length).split("|");
|
|
17665
|
+
const pid = Number(rawPid);
|
|
17666
|
+
if (!tabId || !paneId || !Number.isInteger(pid) || pid <= 0) continue;
|
|
17667
|
+
panes.push({ tabId, pid, paneId });
|
|
17459
17668
|
}
|
|
17460
17669
|
return panes;
|
|
17461
17670
|
}
|
|
17671
|
+
/**
|
|
17672
|
+
* Every process this card is running, by pid. Shared with bareKillProcess,
|
|
17673
|
+
* so stopping a process accepts exactly the set the panel shows.
|
|
17674
|
+
*
|
|
17675
|
+
* The `-p <pids>` on the environment call is required, not a saving: macOS
|
|
17676
|
+
* omits environments from an unscoped `ps -axeww` — see panesByPid.
|
|
17677
|
+
*/
|
|
17678
|
+
async cardProcesses(target, postId) {
|
|
17679
|
+
const panes = await this.panePids(target, postId);
|
|
17680
|
+
if (panes.length === 0) return /* @__PURE__ */ new Map();
|
|
17681
|
+
const table = parsePsTable(
|
|
17682
|
+
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17683
|
+
);
|
|
17684
|
+
return processesForCard(
|
|
17685
|
+
panes,
|
|
17686
|
+
table,
|
|
17687
|
+
async (pids) => panesByPid(
|
|
17688
|
+
await target.run(["ps", "eww", "-o", "pid=,command=", "-p", pids.join(",")]).catch(() => "")
|
|
17689
|
+
)
|
|
17690
|
+
);
|
|
17691
|
+
}
|
|
17462
17692
|
async bareListProcesses({
|
|
17463
17693
|
postId: rawPostId
|
|
17464
17694
|
}) {
|
|
17465
17695
|
const postId = String(rawPostId);
|
|
17466
17696
|
const target = this.getBareTarget(postId);
|
|
17467
|
-
const
|
|
17468
|
-
if (
|
|
17469
|
-
const
|
|
17470
|
-
|
|
17471
|
-
|
|
17472
|
-
|
|
17473
|
-
|
|
17474
|
-
|
|
17475
|
-
|
|
17476
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
cpu: row.cpu,
|
|
17481
|
-
memoryKb: row.rssKb,
|
|
17482
|
-
elapsed: row.elapsed,
|
|
17483
|
-
ports: [],
|
|
17484
|
-
connections: 0
|
|
17485
|
-
});
|
|
17486
|
-
}
|
|
17487
|
-
}
|
|
17488
|
-
if (found.length === 0) return [];
|
|
17697
|
+
const mine = await this.cardProcesses(target, postId);
|
|
17698
|
+
if (mine.size === 0) return [];
|
|
17699
|
+
const found = Array.from(mine.values(), ({ tabId, row }) => ({
|
|
17700
|
+
pid: row.pid,
|
|
17701
|
+
tabId,
|
|
17702
|
+
command: commandOf(row.args),
|
|
17703
|
+
args: row.args,
|
|
17704
|
+
cpu: row.cpu,
|
|
17705
|
+
memoryKb: row.rssKb,
|
|
17706
|
+
elapsed: row.elapsed,
|
|
17707
|
+
ports: [],
|
|
17708
|
+
connections: 0
|
|
17709
|
+
}));
|
|
17489
17710
|
const sockets = await socketsFor(
|
|
17490
17711
|
target,
|
|
17491
17712
|
found.map((p) => p.pid)
|
|
@@ -17508,14 +17729,8 @@ var TerminalModeEngine = class {
|
|
|
17508
17729
|
if (!Number.isInteger(pid) || pid <= 1) {
|
|
17509
17730
|
throw new Error("Not a process this card can stop.");
|
|
17510
17731
|
}
|
|
17511
|
-
const
|
|
17512
|
-
|
|
17513
|
-
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17514
|
-
);
|
|
17515
|
-
const mine = panes.some(
|
|
17516
|
-
(pane) => processesForPane(table, pane.pid).some((row) => row.pid === pid)
|
|
17517
|
-
);
|
|
17518
|
-
if (!mine) {
|
|
17732
|
+
const mine = await this.cardProcesses(target, postId);
|
|
17733
|
+
if (!mine.has(pid)) {
|
|
17519
17734
|
throw new Error("That process is not running in this card.");
|
|
17520
17735
|
}
|
|
17521
17736
|
await target.run(["kill", "-TERM", String(pid)]);
|
|
@@ -17974,8 +18189,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
17974
18189
|
};
|
|
17975
18190
|
}
|
|
17976
18191
|
}
|
|
17977
|
-
async function githubApi(token,
|
|
17978
|
-
const res = await fetch(`${API_BASE}${
|
|
18192
|
+
async function githubApi(token, path18) {
|
|
18193
|
+
const res = await fetch(`${API_BASE}${path18}`, {
|
|
17979
18194
|
headers: {
|
|
17980
18195
|
Authorization: `Bearer ${token}`,
|
|
17981
18196
|
Accept: "application/vnd.github+json",
|
|
@@ -17984,7 +18199,7 @@ async function githubApi(token, path17) {
|
|
|
17984
18199
|
});
|
|
17985
18200
|
if (!res.ok) {
|
|
17986
18201
|
throw new Error(
|
|
17987
|
-
`GitHub ${
|
|
18202
|
+
`GitHub ${path18} failed (${res.status}): ${await res.text()}`
|
|
17988
18203
|
);
|
|
17989
18204
|
}
|
|
17990
18205
|
return await res.json();
|
|
@@ -18491,6 +18706,59 @@ async function getDriveRoot(accessToken) {
|
|
|
18491
18706
|
};
|
|
18492
18707
|
}
|
|
18493
18708
|
|
|
18709
|
+
// ../../shared/all/helpers/board-agent-core/plugin.ts
|
|
18710
|
+
var import_fs3 = require("fs");
|
|
18711
|
+
var import_promises6 = require("fs/promises");
|
|
18712
|
+
var import_os3 = __toESM(require("os"));
|
|
18713
|
+
var import_path9 = __toESM(require("path"));
|
|
18714
|
+
var PLUGIN_ID = "factiii@factiii-runner";
|
|
18715
|
+
function candidates() {
|
|
18716
|
+
return [
|
|
18717
|
+
import_path9.default.join(__dirname, "..", "plugin"),
|
|
18718
|
+
import_path9.default.join(__dirname, "..", "..", "..", "..", "apps", "runner", "plugin"),
|
|
18719
|
+
import_path9.default.join(process.cwd(), "apps", "runner", "plugin")
|
|
18720
|
+
];
|
|
18721
|
+
}
|
|
18722
|
+
function pluginRoot() {
|
|
18723
|
+
for (const dir of candidates()) {
|
|
18724
|
+
if ((0, import_fs3.existsSync)(import_path9.default.join(dir, ".claude-plugin", "marketplace.json"))) {
|
|
18725
|
+
return dir;
|
|
18726
|
+
}
|
|
18727
|
+
}
|
|
18728
|
+
return null;
|
|
18729
|
+
}
|
|
18730
|
+
function renderedPluginRoot() {
|
|
18731
|
+
return import_path9.default.join(import_os3.default.homedir(), ".factiii-runner", "plugin");
|
|
18732
|
+
}
|
|
18733
|
+
async function renderPlugin(boardMcpUrl) {
|
|
18734
|
+
const source = pluginRoot();
|
|
18735
|
+
if (!source) return null;
|
|
18736
|
+
const dest = renderedPluginRoot();
|
|
18737
|
+
const manifest = import_path9.default.join(dest, "factiii", ".mcp.json");
|
|
18738
|
+
const want = `${JSON.stringify(boardMcpConfig(boardMcpUrl), null, 2)}
|
|
18739
|
+
`;
|
|
18740
|
+
const have = await (0, import_promises6.readFile)(manifest, "utf8").catch(() => null);
|
|
18741
|
+
if (have === want) return dest;
|
|
18742
|
+
await (0, import_promises6.mkdir)(import_path9.default.dirname(dest), { recursive: true });
|
|
18743
|
+
await (0, import_promises6.cp)(source, dest, { recursive: true, force: true });
|
|
18744
|
+
await (0, import_promises6.writeFile)(manifest, want);
|
|
18745
|
+
return dest;
|
|
18746
|
+
}
|
|
18747
|
+
function boardMcpConfig(url2) {
|
|
18748
|
+
return {
|
|
18749
|
+
mcpServers: {
|
|
18750
|
+
"factiii-board": {
|
|
18751
|
+
type: "http",
|
|
18752
|
+
url: url2,
|
|
18753
|
+
bearer_token_env_var: BOARD_MCP_TOKEN_ENV,
|
|
18754
|
+
// Written literally, for claude to expand at connect time. Not a
|
|
18755
|
+
// template string: the `${…}` has to survive into the file.
|
|
18756
|
+
headers: { Authorization: "Bearer ${" + BOARD_MCP_TOKEN_ENV + "}" }
|
|
18757
|
+
}
|
|
18758
|
+
}
|
|
18759
|
+
};
|
|
18760
|
+
}
|
|
18761
|
+
|
|
18494
18762
|
// ../../shared/all/helpers/board-agent-core/toolchain.ts
|
|
18495
18763
|
var TOOLS = [
|
|
18496
18764
|
{ bin: "git" },
|
|
@@ -18748,10 +19016,6 @@ var SpaceCore = class {
|
|
|
18748
19016
|
enabled: this.isOneDriveConnected()
|
|
18749
19017
|
});
|
|
18750
19018
|
}
|
|
18751
|
-
const board = this.boardMcp();
|
|
18752
|
-
if (board) {
|
|
18753
|
-
lanes.push({ mcp: board, tokenEnv: BOARD_MCP_TOKEN_ENV, enabled: true });
|
|
18754
|
-
}
|
|
18755
19019
|
return lanes;
|
|
18756
19020
|
}
|
|
18757
19021
|
isReady() {
|
|
@@ -18902,6 +19166,7 @@ var SpaceCore = class {
|
|
|
18902
19166
|
await Promise.all([
|
|
18903
19167
|
this.provisionGitLane(status),
|
|
18904
19168
|
this.provisionMcpLane(),
|
|
19169
|
+
this.provisionPluginLane(),
|
|
18905
19170
|
// Covers a host signed in outside the one-click button, which would
|
|
18906
19171
|
// otherwise meet the wizard the first time it opens a terminal.
|
|
18907
19172
|
markOnboardingComplete(target).catch(() => {
|
|
@@ -18944,17 +19209,53 @@ var SpaceCore = class {
|
|
|
18944
19209
|
target.paths.repo
|
|
18945
19210
|
);
|
|
18946
19211
|
}
|
|
19212
|
+
/** Install the factiii plugin into both CLIs: the hooks behind the session
|
|
19213
|
+
* status dot, the skills this workspace provides, and the board MCP.
|
|
19214
|
+
*
|
|
19215
|
+
* A plugin rather than hand-written config because it is additive — unlike
|
|
19216
|
+
* pointing CLAUDE_CONFIG_DIR or CODEX_HOME at our own directory, which
|
|
19217
|
+
* would hide the user's own settings, skills, MCP entries and auth.
|
|
19218
|
+
*
|
|
19219
|
+
* Both CLIs read the same manifest, so one marketplace serves both, and
|
|
19220
|
+
* adding a marketplace or plugin that is already there is a no-op in each —
|
|
19221
|
+
* which is what makes this safe on every ensure. */
|
|
19222
|
+
async provisionPluginLane() {
|
|
19223
|
+
const board = this.boardMcp();
|
|
19224
|
+
const root = board ? await renderPlugin(board.url).catch(() => null) : null;
|
|
19225
|
+
if (!root) return;
|
|
19226
|
+
const q = `'${root}'`;
|
|
19227
|
+
const cmd = [
|
|
19228
|
+
`claude plugin marketplace add ${q} >/dev/null 2>&1 || true`,
|
|
19229
|
+
`claude plugin install ${PLUGIN_ID} --scope user >/dev/null 2>&1 || true`,
|
|
19230
|
+
`codex plugin marketplace add ${q} >/dev/null 2>&1 || true`,
|
|
19231
|
+
`codex plugin add ${PLUGIN_ID} >/dev/null 2>&1 || true`
|
|
19232
|
+
].join("; ");
|
|
19233
|
+
try {
|
|
19234
|
+
await this.target().sh(cmd);
|
|
19235
|
+
} catch {
|
|
19236
|
+
}
|
|
19237
|
+
}
|
|
18947
19238
|
// Register this space's MCPs with both CLIs, user-scoped, and clear the ones
|
|
18948
19239
|
// it cannot currently offer. Registering regardless left every space holding
|
|
18949
19240
|
// an entry it never uses, which the CLI then fails to handshake with on every
|
|
18950
19241
|
// run. Removal is unconditional so a disconnect cleans itself up.
|
|
19242
|
+
//
|
|
19243
|
+
// No early return on an empty lane list: since the board lane moved into the
|
|
19244
|
+
// plugin, a host with no OneDrive has nothing to add and still has the old
|
|
19245
|
+
// board entries to drop.
|
|
18951
19246
|
async provisionMcpLane() {
|
|
18952
19247
|
const lanes = this.mcpLanes();
|
|
18953
|
-
|
|
19248
|
+
const board = this.boardMcp();
|
|
19249
|
+
const boardCodexTable = board?.name.replace(/-/g, "_");
|
|
18954
19250
|
const cmd = [
|
|
18955
19251
|
// Pre-rename entries, when the name was the same for every space.
|
|
18956
19252
|
`claude mcp remove factiii-onedrive --scope user >/dev/null 2>&1 || true`,
|
|
18957
19253
|
`codex mcp remove factiii_onedrive >/dev/null 2>&1 || true`,
|
|
19254
|
+
// The board lane moved into the plugin; drop what the old path wrote.
|
|
19255
|
+
...board ? [
|
|
19256
|
+
`claude mcp remove ${board.name} --scope user >/dev/null 2>&1 || true`,
|
|
19257
|
+
`codex mcp remove ${boardCodexTable} >/dev/null 2>&1 || true`
|
|
19258
|
+
] : [],
|
|
18958
19259
|
...lanes.flatMap(({ mcp, tokenEnv, enabled }) => {
|
|
18959
19260
|
const codexTable = mcp.name.replace(/-/g, "_");
|
|
18960
19261
|
const claudeJson = JSON.stringify({
|
|
@@ -19571,7 +19872,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19571
19872
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
19572
19873
|
secretsDirs() {
|
|
19573
19874
|
const spaceDir = this.core.spaceDir();
|
|
19574
|
-
return { configDir: (0,
|
|
19875
|
+
return { configDir: (0, import_path10.dirname)(spaceDir), spaceDir };
|
|
19575
19876
|
}
|
|
19576
19877
|
async deploySecretsStatus() {
|
|
19577
19878
|
return {
|
|
@@ -20629,10 +20930,10 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20629
20930
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
20630
20931
|
var import_child_process5 = require("child_process");
|
|
20631
20932
|
var import_crypto3 = require("crypto");
|
|
20632
|
-
var
|
|
20633
|
-
var
|
|
20933
|
+
var import_fs4 = require("fs");
|
|
20934
|
+
var import_promises7 = __toESM(require("fs/promises"));
|
|
20634
20935
|
var import_net2 = require("net");
|
|
20635
|
-
var
|
|
20936
|
+
var import_path11 = require("path");
|
|
20636
20937
|
var import_util7 = require("util");
|
|
20637
20938
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
20638
20939
|
var IndexService = class {
|
|
@@ -20691,7 +20992,7 @@ var IndexService = class {
|
|
|
20691
20992
|
const log = await this.openLog();
|
|
20692
20993
|
this.proc = (0, import_child_process5.spawn)(
|
|
20693
20994
|
process.execPath,
|
|
20694
|
-
[(0,
|
|
20995
|
+
[(0, import_path11.join)(this.opts.indexDir, "index.js")],
|
|
20695
20996
|
{
|
|
20696
20997
|
cwd: this.opts.indexDir,
|
|
20697
20998
|
env: {
|
|
@@ -20719,8 +21020,8 @@ var IndexService = class {
|
|
|
20719
21020
|
* opened (a missing log must never stop the service from starting). */
|
|
20720
21021
|
async openLog() {
|
|
20721
21022
|
try {
|
|
20722
|
-
await
|
|
20723
|
-
return (0,
|
|
21023
|
+
await import_promises7.default.mkdir(this.opts.dataDir, { recursive: true });
|
|
21024
|
+
return (0, import_fs4.openSync)((0, import_path11.join)(this.opts.dataDir, "index.log"), "a");
|
|
20724
21025
|
} catch {
|
|
20725
21026
|
return null;
|
|
20726
21027
|
}
|
|
@@ -20729,10 +21030,10 @@ var IndexService = class {
|
|
|
20729
21030
|
* platform needing node-gyp fails here rather than at query time. */
|
|
20730
21031
|
async installDeps() {
|
|
20731
21032
|
const dir = this.opts.indexDir;
|
|
20732
|
-
if ((0,
|
|
20733
|
-
await
|
|
20734
|
-
(0,
|
|
20735
|
-
(0,
|
|
21033
|
+
if ((0, import_fs4.existsSync)((0, import_path11.join)(dir, "node_modules"))) return;
|
|
21034
|
+
await import_promises7.default.copyFile(
|
|
21035
|
+
(0, import_path11.join)(dir, "image-package.json"),
|
|
21036
|
+
(0, import_path11.join)(dir, "package.json")
|
|
20736
21037
|
);
|
|
20737
21038
|
await execFileAsync4(
|
|
20738
21039
|
"npm",
|
|
@@ -20744,15 +21045,15 @@ var IndexService = class {
|
|
|
20744
21045
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
20745
21046
|
* registration pointing at a dead one. */
|
|
20746
21047
|
async stablePort() {
|
|
20747
|
-
const file = (0,
|
|
20748
|
-
const saved = Number(await
|
|
21048
|
+
const file = (0, import_path11.join)(this.opts.dataDir, ".port");
|
|
21049
|
+
const saved = Number(await import_promises7.default.readFile(file, "utf-8").catch(() => ""));
|
|
20749
21050
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
20750
21051
|
return saved;
|
|
20751
21052
|
}
|
|
20752
21053
|
const port = await freePort();
|
|
20753
|
-
await
|
|
21054
|
+
await import_promises7.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
20754
21055
|
});
|
|
20755
|
-
await
|
|
21056
|
+
await import_promises7.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
20756
21057
|
});
|
|
20757
21058
|
return port;
|
|
20758
21059
|
}
|
|
@@ -20919,25 +21220,25 @@ async function pairWithBrowser(serverUrl) {
|
|
|
20919
21220
|
}
|
|
20920
21221
|
|
|
20921
21222
|
// src/config.ts
|
|
20922
|
-
var
|
|
20923
|
-
var
|
|
20924
|
-
var
|
|
21223
|
+
var import_fs8 = __toESM(require("fs"));
|
|
21224
|
+
var import_os6 = __toESM(require("os"));
|
|
21225
|
+
var import_path16 = __toESM(require("path"));
|
|
20925
21226
|
|
|
20926
21227
|
// src/secureFile.ts
|
|
20927
21228
|
var import_crypto6 = __toESM(require("crypto"));
|
|
20928
|
-
var
|
|
20929
|
-
var
|
|
21229
|
+
var import_fs7 = __toESM(require("fs"));
|
|
21230
|
+
var import_path15 = __toESM(require("path"));
|
|
20930
21231
|
|
|
20931
21232
|
// src/keychain.ts
|
|
20932
21233
|
var import_child_process7 = require("child_process");
|
|
20933
21234
|
var import_crypto4 = __toESM(require("crypto"));
|
|
20934
|
-
var
|
|
20935
|
-
var
|
|
20936
|
-
var
|
|
21235
|
+
var import_fs5 = __toESM(require("fs"));
|
|
21236
|
+
var import_os4 = __toESM(require("os"));
|
|
21237
|
+
var import_path12 = __toESM(require("path"));
|
|
20937
21238
|
var SERVICE = "factiii-runner";
|
|
20938
21239
|
var ACCOUNT = "config-encryption-key";
|
|
20939
|
-
var DPAPI_KEY_FILE =
|
|
20940
|
-
|
|
21240
|
+
var DPAPI_KEY_FILE = import_path12.default.join(
|
|
21241
|
+
import_os4.default.homedir(),
|
|
20941
21242
|
".factiii-runner",
|
|
20942
21243
|
"config-key.dpapi"
|
|
20943
21244
|
);
|
|
@@ -21017,7 +21318,7 @@ function linuxWrite(key) {
|
|
|
21017
21318
|
function winRead() {
|
|
21018
21319
|
let wrapped;
|
|
21019
21320
|
try {
|
|
21020
|
-
wrapped =
|
|
21321
|
+
wrapped = import_fs5.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
|
|
21021
21322
|
} catch {
|
|
21022
21323
|
return { state: "missing" };
|
|
21023
21324
|
}
|
|
@@ -21042,8 +21343,8 @@ function winWrite(key) {
|
|
|
21042
21343
|
"-Command",
|
|
21043
21344
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
21044
21345
|
]);
|
|
21045
|
-
|
|
21046
|
-
|
|
21346
|
+
import_fs5.default.mkdirSync(import_path12.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
21347
|
+
import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
21047
21348
|
`, { mode: 384 });
|
|
21048
21349
|
}
|
|
21049
21350
|
function keychainBackend() {
|
|
@@ -21205,22 +21506,22 @@ function keychainStatus() {
|
|
|
21205
21506
|
|
|
21206
21507
|
// src/secureStore.ts
|
|
21207
21508
|
var import_crypto5 = __toESM(require("crypto"));
|
|
21208
|
-
var
|
|
21209
|
-
var
|
|
21509
|
+
var import_fs6 = __toESM(require("fs"));
|
|
21510
|
+
var import_path14 = __toESM(require("path"));
|
|
21210
21511
|
|
|
21211
21512
|
// src/paths.ts
|
|
21212
|
-
var
|
|
21213
|
-
var
|
|
21214
|
-
var CONFIG_DIR =
|
|
21513
|
+
var import_os5 = __toESM(require("os"));
|
|
21514
|
+
var import_path13 = __toESM(require("path"));
|
|
21515
|
+
var CONFIG_DIR = import_path13.default.join(import_os5.default.homedir(), ".factiii-runner");
|
|
21215
21516
|
function safeSlug(spaceSlug) {
|
|
21216
21517
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21217
21518
|
}
|
|
21218
21519
|
function spaceDirPath(spaceSlug) {
|
|
21219
|
-
return
|
|
21520
|
+
return import_path13.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
21220
21521
|
}
|
|
21221
21522
|
|
|
21222
21523
|
// src/secureStore.ts
|
|
21223
|
-
var VAULT_KEY_FILE =
|
|
21524
|
+
var VAULT_KEY_FILE = import_path14.default.join(CONFIG_DIR, "vault-key.json");
|
|
21224
21525
|
var SCRYPT_N2 = 1 << 15;
|
|
21225
21526
|
var MIN_PASSWORD_LENGTH = 8;
|
|
21226
21527
|
var SecureStoreError = class extends Error {
|
|
@@ -21293,7 +21594,7 @@ function unseal(box, key) {
|
|
|
21293
21594
|
function readVaultFile() {
|
|
21294
21595
|
try {
|
|
21295
21596
|
const parsed = JSON.parse(
|
|
21296
|
-
|
|
21597
|
+
import_fs6.default.readFileSync(VAULT_KEY_FILE, "utf-8")
|
|
21297
21598
|
);
|
|
21298
21599
|
return parsed.v === 1 ? parsed : null;
|
|
21299
21600
|
} catch {
|
|
@@ -21301,11 +21602,11 @@ function readVaultFile() {
|
|
|
21301
21602
|
}
|
|
21302
21603
|
}
|
|
21303
21604
|
function writeVaultFile(file) {
|
|
21304
|
-
|
|
21605
|
+
import_fs6.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
21305
21606
|
const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
|
|
21306
|
-
|
|
21607
|
+
import_fs6.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
|
|
21307
21608
|
`, { mode: 384 });
|
|
21308
|
-
|
|
21609
|
+
import_fs6.default.renameSync(tmp, VAULT_KEY_FILE);
|
|
21309
21610
|
}
|
|
21310
21611
|
var unlockedDek = null;
|
|
21311
21612
|
function createVault(machineKey) {
|
|
@@ -21542,15 +21843,15 @@ function parseEnvelope(raw) {
|
|
|
21542
21843
|
}
|
|
21543
21844
|
}
|
|
21544
21845
|
function atomicWrite(filePath, body) {
|
|
21545
|
-
|
|
21846
|
+
import_fs7.default.mkdirSync(import_path15.default.dirname(filePath), { recursive: true });
|
|
21546
21847
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
21547
|
-
|
|
21548
|
-
|
|
21848
|
+
import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
|
|
21849
|
+
import_fs7.default.renameSync(tmp, filePath);
|
|
21549
21850
|
}
|
|
21550
21851
|
function readSecureBuffer(filePath) {
|
|
21551
21852
|
let raw;
|
|
21552
21853
|
try {
|
|
21553
|
-
raw =
|
|
21854
|
+
raw = import_fs7.default.readFileSync(filePath, "utf-8");
|
|
21554
21855
|
} catch {
|
|
21555
21856
|
return null;
|
|
21556
21857
|
}
|
|
@@ -21607,7 +21908,7 @@ function writeSecureJson(filePath, value2) {
|
|
|
21607
21908
|
function readBootJson(filePath) {
|
|
21608
21909
|
let raw;
|
|
21609
21910
|
try {
|
|
21610
|
-
raw =
|
|
21911
|
+
raw = import_fs7.default.readFileSync(filePath, "utf-8");
|
|
21611
21912
|
} catch {
|
|
21612
21913
|
return null;
|
|
21613
21914
|
}
|
|
@@ -21657,8 +21958,8 @@ function writeBootJson(filePath, value2) {
|
|
|
21657
21958
|
}
|
|
21658
21959
|
|
|
21659
21960
|
// src/config.ts
|
|
21660
|
-
var CONFIG_DIR2 =
|
|
21661
|
-
var CONFIG_FILE =
|
|
21961
|
+
var CONFIG_DIR2 = import_path16.default.join(import_os6.default.homedir(), ".factiii-runner");
|
|
21962
|
+
var CONFIG_FILE = import_path16.default.join(CONFIG_DIR2, "config.json");
|
|
21662
21963
|
function readRunnerConfig() {
|
|
21663
21964
|
return readBootJson(CONFIG_FILE);
|
|
21664
21965
|
}
|
|
@@ -21666,7 +21967,7 @@ function writeRunnerConfig(config) {
|
|
|
21666
21967
|
writeBootJson(CONFIG_FILE, config);
|
|
21667
21968
|
}
|
|
21668
21969
|
function configExists() {
|
|
21669
|
-
return
|
|
21970
|
+
return import_fs8.default.existsSync(CONFIG_FILE);
|
|
21670
21971
|
}
|
|
21671
21972
|
|
|
21672
21973
|
// src/promptPassword.ts
|
|
@@ -21828,9 +22129,9 @@ function makeChunkReassembler() {
|
|
|
21828
22129
|
|
|
21829
22130
|
// src/daemon.ts
|
|
21830
22131
|
var import_crypto7 = require("crypto");
|
|
21831
|
-
var
|
|
22132
|
+
var import_fs10 = __toESM(require("fs"));
|
|
21832
22133
|
var import_node_datachannel = require("node-datachannel");
|
|
21833
|
-
var
|
|
22134
|
+
var import_path18 = __toESM(require("path"));
|
|
21834
22135
|
|
|
21835
22136
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
21836
22137
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23010,12 +23311,12 @@ function parse2(str) {
|
|
|
23010
23311
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23011
23312
|
return uri;
|
|
23012
23313
|
}
|
|
23013
|
-
function pathNames(obj,
|
|
23014
|
-
const regx = /\/{2,9}/g, names =
|
|
23015
|
-
if (
|
|
23314
|
+
function pathNames(obj, path18) {
|
|
23315
|
+
const regx = /\/{2,9}/g, names = path18.replace(regx, "/").split("/");
|
|
23316
|
+
if (path18.slice(0, 1) == "/" || path18.length === 0) {
|
|
23016
23317
|
names.splice(0, 1);
|
|
23017
23318
|
}
|
|
23018
|
-
if (
|
|
23319
|
+
if (path18.slice(-1) == "/") {
|
|
23019
23320
|
names.splice(names.length - 1, 1);
|
|
23020
23321
|
}
|
|
23021
23322
|
return names;
|
|
@@ -23634,7 +23935,7 @@ var protocol2 = Socket.protocol;
|
|
|
23634
23935
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
23635
23936
|
var import_debug7 = __toESM(require_src(), 1);
|
|
23636
23937
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
23637
|
-
function url(uri,
|
|
23938
|
+
function url(uri, path18 = "", loc) {
|
|
23638
23939
|
let obj = uri;
|
|
23639
23940
|
loc = loc || typeof location !== "undefined" && location;
|
|
23640
23941
|
if (null == uri)
|
|
@@ -23668,7 +23969,7 @@ function url(uri, path17 = "", loc) {
|
|
|
23668
23969
|
obj.path = obj.path || "/";
|
|
23669
23970
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
23670
23971
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
23671
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
23972
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path18;
|
|
23672
23973
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
23673
23974
|
return obj;
|
|
23674
23975
|
}
|
|
@@ -25302,8 +25603,8 @@ function lookup(uri, opts) {
|
|
|
25302
25603
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
25303
25604
|
const source = parsed.source;
|
|
25304
25605
|
const id = parsed.id;
|
|
25305
|
-
const
|
|
25306
|
-
const sameNamespace = cache[id] &&
|
|
25606
|
+
const path18 = parsed.path;
|
|
25607
|
+
const sameNamespace = cache[id] && path18 in cache[id]["nsps"];
|
|
25307
25608
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
25308
25609
|
let io;
|
|
25309
25610
|
if (newConnection) {
|
|
@@ -25345,8 +25646,8 @@ function cardContextPath(target, postId) {
|
|
|
25345
25646
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
25346
25647
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
25347
25648
|
}
|
|
25348
|
-
function cardContextPrompt(
|
|
25349
|
-
return `Read ${
|
|
25649
|
+
function cardContextPrompt(path18) {
|
|
25650
|
+
return `Read ${path18} - it is the Factiii card this session was opened from. Use it as the context and start working on it.`;
|
|
25350
25651
|
}
|
|
25351
25652
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
25352
25653
|
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.";
|
|
@@ -25510,11 +25811,11 @@ var TerminalManager = class {
|
|
|
25510
25811
|
};
|
|
25511
25812
|
|
|
25512
25813
|
// src/agent-adapter.ts
|
|
25513
|
-
var
|
|
25514
|
-
var
|
|
25515
|
-
var RUNNER_CONFIG_PATH =
|
|
25814
|
+
var import_fs9 = __toESM(require("fs"));
|
|
25815
|
+
var import_path17 = __toESM(require("path"));
|
|
25816
|
+
var RUNNER_CONFIG_PATH = import_path17.default.join(CONFIG_DIR, "runner-config.json");
|
|
25516
25817
|
function connectionPath(spaceSlug) {
|
|
25517
|
-
return
|
|
25818
|
+
return import_path17.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
25518
25819
|
}
|
|
25519
25820
|
function writeJson(filePath, value2) {
|
|
25520
25821
|
writeSecureJson(filePath, value2);
|
|
@@ -25541,14 +25842,14 @@ function migrateLegacyConfigs() {
|
|
|
25541
25842
|
const file = { boards: {} };
|
|
25542
25843
|
let legacy = [];
|
|
25543
25844
|
try {
|
|
25544
|
-
legacy =
|
|
25845
|
+
legacy = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
|
|
25545
25846
|
} catch {
|
|
25546
25847
|
return file;
|
|
25547
25848
|
}
|
|
25548
25849
|
for (const name of legacy) {
|
|
25549
25850
|
const slug = name.slice("space-".length, -".json".length);
|
|
25550
25851
|
const parsed = readSecureJson(
|
|
25551
|
-
|
|
25852
|
+
import_path17.default.join(CONFIG_DIR, name)
|
|
25552
25853
|
);
|
|
25553
25854
|
if (!parsed) continue;
|
|
25554
25855
|
if (!file.defaultSlug) {
|
|
@@ -25568,14 +25869,14 @@ function encryptStrayPlaintextConfigs() {
|
|
|
25568
25869
|
const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
|
|
25569
25870
|
let names = [];
|
|
25570
25871
|
try {
|
|
25571
|
-
names =
|
|
25872
|
+
names = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
|
|
25572
25873
|
} catch {
|
|
25573
25874
|
return;
|
|
25574
25875
|
}
|
|
25575
25876
|
for (const name of names) {
|
|
25576
|
-
const filePath =
|
|
25877
|
+
const filePath = import_path17.default.join(CONFIG_DIR, name);
|
|
25577
25878
|
try {
|
|
25578
|
-
const raw =
|
|
25879
|
+
const raw = import_fs9.default.readFileSync(filePath, "utf-8");
|
|
25579
25880
|
if (!raw.startsWith("{")) continue;
|
|
25580
25881
|
writeSecureJson(filePath, JSON.parse(raw));
|
|
25581
25882
|
} catch {
|
|
@@ -25606,7 +25907,7 @@ async function getConfiguredConnections() {
|
|
|
25606
25907
|
}
|
|
25607
25908
|
if (await isSignedIn(hostTarget(CONFIG_DIR), "claude")) kinds.add("claude");
|
|
25608
25909
|
try {
|
|
25609
|
-
const hasOneDrive =
|
|
25910
|
+
const hasOneDrive = import_fs9.default.readdirSync(CONFIG_DIR).some((f) => f.endsWith(".onedrive.json"));
|
|
25610
25911
|
if (hasOneDrive) kinds.add("onedrive");
|
|
25611
25912
|
} catch {
|
|
25612
25913
|
}
|
|
@@ -25677,7 +25978,7 @@ var localConfigProvider = {
|
|
|
25677
25978
|
writeOneDriveConnection(spaceSlug, connection) {
|
|
25678
25979
|
const filePath = connectionPath(spaceSlug);
|
|
25679
25980
|
if (!connection) {
|
|
25680
|
-
|
|
25981
|
+
import_fs9.default.rmSync(filePath, { force: true });
|
|
25681
25982
|
return;
|
|
25682
25983
|
}
|
|
25683
25984
|
writeJson(filePath, connection);
|
|
@@ -25702,7 +26003,7 @@ var localConfigProvider = {
|
|
|
25702
26003
|
"claude",
|
|
25703
26004
|
"codex"
|
|
25704
26005
|
]) {
|
|
25705
|
-
|
|
26006
|
+
import_fs9.default.mkdirSync(import_path17.default.join(dir, sub), { recursive: true });
|
|
25706
26007
|
}
|
|
25707
26008
|
return dir;
|
|
25708
26009
|
}
|
|
@@ -25876,15 +26177,15 @@ async function startDaemon(config) {
|
|
|
25876
26177
|
}
|
|
25877
26178
|
);
|
|
25878
26179
|
const indexCandidates = [
|
|
25879
|
-
|
|
25880
|
-
|
|
26180
|
+
import_path18.default.join(__dirname, "..", "index"),
|
|
26181
|
+
import_path18.default.join(__dirname, "..", "..", "index", "image")
|
|
25881
26182
|
];
|
|
25882
|
-
const indexDir = indexCandidates.find((p) =>
|
|
26183
|
+
const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
|
|
25883
26184
|
const odIndex = new IndexService({
|
|
25884
26185
|
controlToken: (0, import_crypto7.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
25885
26186
|
embedUrl: () => config.serverUrl,
|
|
25886
26187
|
indexDir,
|
|
25887
|
-
dataDir:
|
|
26188
|
+
dataDir: import_path18.default.join(CONFIG_DIR, "index-data")
|
|
25888
26189
|
});
|
|
25889
26190
|
void odIndex.ensureRunning().catch((err) => {
|
|
25890
26191
|
console.error(
|