@factiii/runner 0.10.1 → 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 +494 -210
- package/index/index.js +10 -4
- package/package.json +2 -1
- package/plugin/.claude-plugin/marketplace.json +12 -0
- package/plugin/factiii/.claude-plugin/plugin.json +7 -0
- package/plugin/factiii/bin/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 {
|
|
@@ -13688,6 +13690,7 @@ function toSummary(session) {
|
|
|
13688
13690
|
pendingQuestion: session.pendingQuestion,
|
|
13689
13691
|
error: session.error,
|
|
13690
13692
|
startedAt: session.startedAt,
|
|
13693
|
+
agentState: session.agentState,
|
|
13691
13694
|
targetBranch: session.targetBranch,
|
|
13692
13695
|
taskTitle: session.taskTitle,
|
|
13693
13696
|
todos: session.todos,
|
|
@@ -15917,18 +15920,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
15917
15920
|
await this.core.ensureSpaceWorkspace();
|
|
15918
15921
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
15919
15922
|
if (hasGit) {
|
|
15920
|
-
const { path:
|
|
15923
|
+
const { path: path18 } = await addWorktree(
|
|
15921
15924
|
this.core.target(),
|
|
15922
15925
|
session.taskId,
|
|
15923
15926
|
baseBranch || config.mainBranch || "main",
|
|
15924
15927
|
"card"
|
|
15925
15928
|
);
|
|
15926
|
-
session.workspacePath =
|
|
15929
|
+
session.workspacePath = path18;
|
|
15927
15930
|
await setGitIdentity(
|
|
15928
15931
|
this.core.target(),
|
|
15929
15932
|
config.gitName,
|
|
15930
15933
|
config.gitEmail,
|
|
15931
|
-
|
|
15934
|
+
path18
|
|
15932
15935
|
);
|
|
15933
15936
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
15934
15937
|
} else {
|
|
@@ -16150,18 +16153,18 @@ var ClaudeModeEngine = class _ClaudeModeEngine {
|
|
|
16150
16153
|
this.emitter.addLog(session, "system", "Preparing environment...");
|
|
16151
16154
|
await this.core.ensureSpaceWorkspace();
|
|
16152
16155
|
await assertAgentSignedIn(session.provider, this.core.target());
|
|
16153
|
-
const { path:
|
|
16156
|
+
const { path: path18 } = await addWorktree(
|
|
16154
16157
|
this.core.target(),
|
|
16155
16158
|
session.taskId,
|
|
16156
16159
|
config.mainBranch || "main",
|
|
16157
16160
|
"card"
|
|
16158
16161
|
);
|
|
16159
|
-
session.workspacePath =
|
|
16162
|
+
session.workspacePath = path18;
|
|
16160
16163
|
await setGitIdentity(
|
|
16161
16164
|
this.core.target(),
|
|
16162
16165
|
config.gitName,
|
|
16163
16166
|
config.gitEmail,
|
|
16164
|
-
|
|
16167
|
+
path18
|
|
16165
16168
|
);
|
|
16166
16169
|
await this.putCard(session);
|
|
16167
16170
|
const patchDir = `${this.core.target().paths.root}/patches/${session.taskId}`;
|
|
@@ -16456,6 +16459,50 @@ async function startTmuxSession(opts) {
|
|
|
16456
16459
|
};
|
|
16457
16460
|
}
|
|
16458
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
|
+
|
|
16459
16506
|
// ../../shared/all/helpers/terminal-ai/processes.ts
|
|
16460
16507
|
var PS_FORMAT = "pid=,ppid=,pcpu=,rss=,etime=,args=";
|
|
16461
16508
|
var PS_FIELDS = 5;
|
|
@@ -16501,6 +16548,45 @@ function descendantsOf(rows, roots) {
|
|
|
16501
16548
|
}
|
|
16502
16549
|
return found.sort((a, b) => a.pid - b.pid);
|
|
16503
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
|
+
}
|
|
16504
16590
|
function commandOf(args) {
|
|
16505
16591
|
const first = args.trim().split(/\s+/)[0] ?? "";
|
|
16506
16592
|
const base = first.split("/").pop() ?? first;
|
|
@@ -16625,12 +16711,12 @@ var FLUSH_MS = 120;
|
|
|
16625
16711
|
var SELF_WRITE_MS = 2e3;
|
|
16626
16712
|
var dynamicImport = new Function("s", "return import(s)");
|
|
16627
16713
|
var importFs = new Function("s", "return import(s)");
|
|
16628
|
-
async function resolveReal(
|
|
16714
|
+
async function resolveReal(path18) {
|
|
16629
16715
|
try {
|
|
16630
16716
|
const { realpath } = await importFs("fs/promises");
|
|
16631
|
-
return await realpath(
|
|
16717
|
+
return await realpath(path18);
|
|
16632
16718
|
} catch {
|
|
16633
|
-
return
|
|
16719
|
+
return path18;
|
|
16634
16720
|
}
|
|
16635
16721
|
}
|
|
16636
16722
|
var loadSubscribe = null;
|
|
@@ -16744,19 +16830,19 @@ var WorkspaceWatcher = class {
|
|
|
16744
16830
|
}
|
|
16745
16831
|
record(kind, relPath) {
|
|
16746
16832
|
if (this.closed) return;
|
|
16747
|
-
const
|
|
16748
|
-
const expiry = this.selfWrites.get(
|
|
16833
|
+
const path18 = "/" + relPath;
|
|
16834
|
+
const expiry = this.selfWrites.get(path18);
|
|
16749
16835
|
if (expiry !== void 0) {
|
|
16750
16836
|
if (expiry > Date.now()) return;
|
|
16751
|
-
this.selfWrites.delete(
|
|
16837
|
+
this.selfWrites.delete(path18);
|
|
16752
16838
|
}
|
|
16753
16839
|
const { added, changed, removed } = this.pending;
|
|
16754
|
-
added.delete(
|
|
16755
|
-
changed.delete(
|
|
16756
|
-
removed.delete(
|
|
16757
|
-
if (kind === "added") added.add(
|
|
16758
|
-
else if (kind === "changed") changed.add(
|
|
16759
|
-
else removed.add(
|
|
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);
|
|
16760
16846
|
if (added.size + changed.size + removed.size > MAX_BATCH) {
|
|
16761
16847
|
this.pending = { ...emptyPending(), resync: true };
|
|
16762
16848
|
}
|
|
@@ -16775,8 +16861,8 @@ var WorkspaceWatcher = class {
|
|
|
16775
16861
|
this.pending = emptyPending();
|
|
16776
16862
|
if (!resync && !added.size && !changed.size && !removed.size) return;
|
|
16777
16863
|
const now = Date.now();
|
|
16778
|
-
for (const [
|
|
16779
|
-
if (expiry <= now) this.selfWrites.delete(
|
|
16864
|
+
for (const [path18, expiry] of this.selfWrites) {
|
|
16865
|
+
if (expiry <= now) this.selfWrites.delete(path18);
|
|
16780
16866
|
}
|
|
16781
16867
|
this.emit({
|
|
16782
16868
|
added: resync ? [] : [...added],
|
|
@@ -16822,6 +16908,7 @@ async function writeBase64File(target, abs, b64, opts = {}) {
|
|
|
16822
16908
|
`Write ${abs}`
|
|
16823
16909
|
);
|
|
16824
16910
|
}
|
|
16911
|
+
var PANE_REREAD_MS = 1e3;
|
|
16825
16912
|
var TerminalModeEngine = class {
|
|
16826
16913
|
constructor(core, emitter) {
|
|
16827
16914
|
this.core = core;
|
|
@@ -16832,12 +16919,117 @@ var TerminalModeEngine = class {
|
|
|
16832
16919
|
/** Worktree name each session claimed, keyed by postId. Covers the window
|
|
16833
16920
|
* `claimWorkName`'s disk scan cannot: a name taken but not yet created. */
|
|
16834
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
|
+
}
|
|
16835
17026
|
}
|
|
16836
17027
|
// ── working-tree watching ──
|
|
16837
17028
|
/** Start watching a session's tree. Idempotent: re-entering a session (the
|
|
16838
17029
|
* modal reopening, a hydrate after restart) must not stack watchers. */
|
|
16839
17030
|
startWatching(session) {
|
|
16840
17031
|
const postId = session.postId;
|
|
17032
|
+
void this.startStateSub();
|
|
16841
17033
|
if (this.watchers.has(postId)) return;
|
|
16842
17034
|
const paths = this.core.target().paths;
|
|
16843
17035
|
const root = session.workspacePath || paths.root;
|
|
@@ -16874,8 +17066,8 @@ var TerminalModeEngine = class {
|
|
|
16874
17066
|
/** Suppress the change event our own write is about to produce, so a save
|
|
16875
17067
|
* never comes back to the editor as an outside edit. Normalized to the form
|
|
16876
17068
|
* the watcher emits, since callers spell the path either way. */
|
|
16877
|
-
expectWrite(postId,
|
|
16878
|
-
this.watchers.get(postId)?.expectWrite(posixNormalize(
|
|
17069
|
+
expectWrite(postId, path18) {
|
|
17070
|
+
this.watchers.get(postId)?.expectWrite(posixNormalize(path18));
|
|
16879
17071
|
}
|
|
16880
17072
|
// ── Lifecycle / ModeEngine ──
|
|
16881
17073
|
start(base, args) {
|
|
@@ -16921,6 +17113,7 @@ var TerminalModeEngine = class {
|
|
|
16921
17113
|
this.stopWatching(postId);
|
|
16922
17114
|
this.reservedNames.delete(postId);
|
|
16923
17115
|
this.sessions.delete(postId);
|
|
17116
|
+
this.releaseStateSub();
|
|
16924
17117
|
}
|
|
16925
17118
|
async kill(postId) {
|
|
16926
17119
|
let session = this.sessions.get(postId);
|
|
@@ -16938,6 +17131,7 @@ var TerminalModeEngine = class {
|
|
|
16938
17131
|
const removedPostId = session.postId;
|
|
16939
17132
|
this.reservedNames.delete(removedPostId);
|
|
16940
17133
|
this.sessions.delete(removedPostId);
|
|
17134
|
+
this.releaseStateSub();
|
|
16941
17135
|
this.emitter.emitSessionRemoved(removedPostId);
|
|
16942
17136
|
}
|
|
16943
17137
|
destroy() {
|
|
@@ -17070,18 +17264,18 @@ var TerminalModeEngine = class {
|
|
|
17070
17264
|
this.reservedNames.values()
|
|
17071
17265
|
);
|
|
17072
17266
|
this.reservedNames.set(session.postId, workName);
|
|
17073
|
-
const { path:
|
|
17267
|
+
const { path: path18 } = await addWorktree(
|
|
17074
17268
|
this.core.target(),
|
|
17075
17269
|
workName,
|
|
17076
17270
|
branch,
|
|
17077
17271
|
"terminal"
|
|
17078
17272
|
);
|
|
17079
|
-
session.workspacePath =
|
|
17273
|
+
session.workspacePath = path18;
|
|
17080
17274
|
await setGitIdentity(
|
|
17081
17275
|
this.core.target(),
|
|
17082
17276
|
config.gitName,
|
|
17083
17277
|
config.gitEmail,
|
|
17084
|
-
|
|
17278
|
+
path18
|
|
17085
17279
|
);
|
|
17086
17280
|
this.emitter.addLog(session, "system", "Worktree ready.");
|
|
17087
17281
|
} else {
|
|
@@ -17143,11 +17337,11 @@ var TerminalModeEngine = class {
|
|
|
17143
17337
|
}
|
|
17144
17338
|
async bareFsList({
|
|
17145
17339
|
postId,
|
|
17146
|
-
path:
|
|
17340
|
+
path: path18
|
|
17147
17341
|
}) {
|
|
17148
17342
|
const target = this.getBareTarget(postId);
|
|
17149
17343
|
const root = this.getBareRoot(postId);
|
|
17150
|
-
const abs = this.resolveBarePath(
|
|
17344
|
+
const abs = this.resolveBarePath(path18, root);
|
|
17151
17345
|
const relRoot = abs.slice(root.length) || "/";
|
|
17152
17346
|
const out = await target.run([
|
|
17153
17347
|
"sh",
|
|
@@ -17197,30 +17391,30 @@ var TerminalModeEngine = class {
|
|
|
17197
17391
|
}
|
|
17198
17392
|
async bareFsRead({
|
|
17199
17393
|
postId,
|
|
17200
|
-
path:
|
|
17394
|
+
path: path18
|
|
17201
17395
|
}) {
|
|
17202
17396
|
const target = this.getBareTarget(postId);
|
|
17203
|
-
const abs = this.resolveBarePath(
|
|
17397
|
+
const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
|
|
17204
17398
|
const b64 = (await target.sh(`base64 < ${shEscape(abs)} | tr -d '
|
|
17205
17399
|
'`)).trim();
|
|
17206
17400
|
return { content: b64, encoding: "base64" };
|
|
17207
17401
|
}
|
|
17208
17402
|
async bareFsWrite({
|
|
17209
17403
|
postId,
|
|
17210
|
-
path:
|
|
17404
|
+
path: path18,
|
|
17211
17405
|
content,
|
|
17212
17406
|
encoding
|
|
17213
17407
|
}) {
|
|
17214
17408
|
const target = this.getBareTarget(postId);
|
|
17215
|
-
const abs = this.resolveBarePath(
|
|
17409
|
+
const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
|
|
17216
17410
|
const b64 = encoding === "base64" ? content : Buffer.from(content, "utf-8").toString("base64");
|
|
17217
17411
|
if (!/^[A-Za-z0-9+/=\n\r]*$/.test(b64)) {
|
|
17218
17412
|
throw new Error("Invalid base64 payload.");
|
|
17219
17413
|
}
|
|
17220
|
-
this.expectWrite(String(postId),
|
|
17414
|
+
this.expectWrite(String(postId), path18);
|
|
17221
17415
|
await writeBase64File(target, abs, b64);
|
|
17222
17416
|
const session = this.sessions.get(String(postId));
|
|
17223
|
-
if (session) delete session.buffers[
|
|
17417
|
+
if (session) delete session.buffers[path18];
|
|
17224
17418
|
}
|
|
17225
17419
|
/** Append one chunk of a dropped image to the per-space drops dir
|
|
17226
17420
|
* (`paths.root`, OUTSIDE the card worktree so it never touches the repo).
|
|
@@ -17270,39 +17464,39 @@ var TerminalModeEngine = class {
|
|
|
17270
17464
|
}
|
|
17271
17465
|
bareOpenFile({
|
|
17272
17466
|
postId: rawPostId,
|
|
17273
|
-
path:
|
|
17467
|
+
path: path18
|
|
17274
17468
|
}) {
|
|
17275
17469
|
const session = this.sessions.get(String(rawPostId));
|
|
17276
17470
|
if (!session || session.mode !== "bare") return;
|
|
17277
|
-
if (!session.openedFiles.includes(
|
|
17278
|
-
session.openedFiles.push(
|
|
17471
|
+
if (!session.openedFiles.includes(path18)) {
|
|
17472
|
+
session.openedFiles.push(path18);
|
|
17279
17473
|
}
|
|
17280
17474
|
}
|
|
17281
17475
|
bareCloseFile({
|
|
17282
17476
|
postId: rawPostId,
|
|
17283
|
-
path:
|
|
17477
|
+
path: path18
|
|
17284
17478
|
}) {
|
|
17285
17479
|
const session = this.sessions.get(String(rawPostId));
|
|
17286
17480
|
if (!session || session.mode !== "bare") return;
|
|
17287
|
-
session.openedFiles = session.openedFiles.filter((p) => p !==
|
|
17288
|
-
delete session.buffers[
|
|
17481
|
+
session.openedFiles = session.openedFiles.filter((p) => p !== path18);
|
|
17482
|
+
delete session.buffers[path18];
|
|
17289
17483
|
}
|
|
17290
17484
|
bareBufferSet({
|
|
17291
17485
|
postId: rawPostId,
|
|
17292
|
-
path:
|
|
17486
|
+
path: path18,
|
|
17293
17487
|
content
|
|
17294
17488
|
}) {
|
|
17295
17489
|
const session = this.sessions.get(String(rawPostId));
|
|
17296
17490
|
if (!session || session.mode !== "bare") return;
|
|
17297
|
-
session.buffers[
|
|
17491
|
+
session.buffers[path18] = content;
|
|
17298
17492
|
}
|
|
17299
17493
|
async bareFsDelete({
|
|
17300
17494
|
postId,
|
|
17301
|
-
path:
|
|
17495
|
+
path: path18
|
|
17302
17496
|
}) {
|
|
17303
17497
|
const target = this.getBareTarget(postId);
|
|
17304
17498
|
const root = this.getBareRoot(postId);
|
|
17305
|
-
const abs = this.resolveBarePath(
|
|
17499
|
+
const abs = this.resolveBarePath(path18, root);
|
|
17306
17500
|
if (abs === root) {
|
|
17307
17501
|
throw new Error("Refusing to delete the repo root.");
|
|
17308
17502
|
}
|
|
@@ -17310,10 +17504,10 @@ var TerminalModeEngine = class {
|
|
|
17310
17504
|
}
|
|
17311
17505
|
async bareFsMkdir({
|
|
17312
17506
|
postId,
|
|
17313
|
-
path:
|
|
17507
|
+
path: path18
|
|
17314
17508
|
}) {
|
|
17315
17509
|
const target = this.getBareTarget(postId);
|
|
17316
|
-
const abs = this.resolveBarePath(
|
|
17510
|
+
const abs = this.resolveBarePath(path18, this.getBareRoot(postId));
|
|
17317
17511
|
await target.sh(`mkdir -p ${shEscape(abs)}`);
|
|
17318
17512
|
}
|
|
17319
17513
|
async bareReview({
|
|
@@ -17456,53 +17650,63 @@ var TerminalModeEngine = class {
|
|
|
17456
17650
|
return [];
|
|
17457
17651
|
}
|
|
17458
17652
|
}
|
|
17459
|
-
/**
|
|
17460
|
-
*
|
|
17653
|
+
/** This card's panes: the pid its tree hangs off, and the pane id its
|
|
17654
|
+
* processes carry in their environment. */
|
|
17461
17655
|
async panePids(target, postId) {
|
|
17462
17656
|
const prefix = `terminal-${postId.replace(/[^a-zA-Z0-9._-]/g, "_")}-`;
|
|
17463
17657
|
const stdout = await target.sh(
|
|
17464
|
-
`${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`
|
|
17465
17659
|
).catch(() => "");
|
|
17466
17660
|
const panes = [];
|
|
17467
17661
|
for (const raw of stdout.split("\n")) {
|
|
17468
17662
|
const line = raw.trim();
|
|
17469
17663
|
if (!line.startsWith(prefix)) continue;
|
|
17470
|
-
const
|
|
17471
|
-
const
|
|
17472
|
-
if (
|
|
17473
|
-
|
|
17474
|
-
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
17475
|
-
panes.push({ tabId: rest.slice(0, sep), pid });
|
|
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 });
|
|
17476
17668
|
}
|
|
17477
17669
|
return panes;
|
|
17478
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
|
+
}
|
|
17479
17692
|
async bareListProcesses({
|
|
17480
17693
|
postId: rawPostId
|
|
17481
17694
|
}) {
|
|
17482
17695
|
const postId = String(rawPostId);
|
|
17483
17696
|
const target = this.getBareTarget(postId);
|
|
17484
|
-
const
|
|
17485
|
-
if (
|
|
17486
|
-
const
|
|
17487
|
-
|
|
17488
|
-
|
|
17489
|
-
|
|
17490
|
-
|
|
17491
|
-
|
|
17492
|
-
|
|
17493
|
-
|
|
17494
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
cpu: row.cpu,
|
|
17498
|
-
memoryKb: row.rssKb,
|
|
17499
|
-
elapsed: row.elapsed,
|
|
17500
|
-
ports: [],
|
|
17501
|
-
connections: 0
|
|
17502
|
-
});
|
|
17503
|
-
}
|
|
17504
|
-
}
|
|
17505
|
-
if (found.length === 0) return [];
|
|
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
|
+
}));
|
|
17506
17710
|
const sockets = await socketsFor(
|
|
17507
17711
|
target,
|
|
17508
17712
|
found.map((p) => p.pid)
|
|
@@ -17525,14 +17729,8 @@ var TerminalModeEngine = class {
|
|
|
17525
17729
|
if (!Number.isInteger(pid) || pid <= 1) {
|
|
17526
17730
|
throw new Error("Not a process this card can stop.");
|
|
17527
17731
|
}
|
|
17528
|
-
const
|
|
17529
|
-
|
|
17530
|
-
await target.run(["ps", "-axo", PS_FORMAT]).catch(() => "")
|
|
17531
|
-
);
|
|
17532
|
-
const mine = panes.some(
|
|
17533
|
-
(pane) => processesForPane(table, pane.pid).some((row) => row.pid === pid)
|
|
17534
|
-
);
|
|
17535
|
-
if (!mine) {
|
|
17732
|
+
const mine = await this.cardProcesses(target, postId);
|
|
17733
|
+
if (!mine.has(pid)) {
|
|
17536
17734
|
throw new Error("That process is not running in this card.");
|
|
17537
17735
|
}
|
|
17538
17736
|
await target.run(["kill", "-TERM", String(pid)]);
|
|
@@ -17991,8 +18189,8 @@ async function pollGithubDeviceCode(clientId, deviceCode) {
|
|
|
17991
18189
|
};
|
|
17992
18190
|
}
|
|
17993
18191
|
}
|
|
17994
|
-
async function githubApi(token,
|
|
17995
|
-
const res = await fetch(`${API_BASE}${
|
|
18192
|
+
async function githubApi(token, path18) {
|
|
18193
|
+
const res = await fetch(`${API_BASE}${path18}`, {
|
|
17996
18194
|
headers: {
|
|
17997
18195
|
Authorization: `Bearer ${token}`,
|
|
17998
18196
|
Accept: "application/vnd.github+json",
|
|
@@ -18001,7 +18199,7 @@ async function githubApi(token, path17) {
|
|
|
18001
18199
|
});
|
|
18002
18200
|
if (!res.ok) {
|
|
18003
18201
|
throw new Error(
|
|
18004
|
-
`GitHub ${
|
|
18202
|
+
`GitHub ${path18} failed (${res.status}): ${await res.text()}`
|
|
18005
18203
|
);
|
|
18006
18204
|
}
|
|
18007
18205
|
return await res.json();
|
|
@@ -18508,6 +18706,59 @@ async function getDriveRoot(accessToken) {
|
|
|
18508
18706
|
};
|
|
18509
18707
|
}
|
|
18510
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
|
+
|
|
18511
18762
|
// ../../shared/all/helpers/board-agent-core/toolchain.ts
|
|
18512
18763
|
var TOOLS = [
|
|
18513
18764
|
{ bin: "git" },
|
|
@@ -18765,10 +19016,6 @@ var SpaceCore = class {
|
|
|
18765
19016
|
enabled: this.isOneDriveConnected()
|
|
18766
19017
|
});
|
|
18767
19018
|
}
|
|
18768
|
-
const board = this.boardMcp();
|
|
18769
|
-
if (board) {
|
|
18770
|
-
lanes.push({ mcp: board, tokenEnv: BOARD_MCP_TOKEN_ENV, enabled: true });
|
|
18771
|
-
}
|
|
18772
19019
|
return lanes;
|
|
18773
19020
|
}
|
|
18774
19021
|
isReady() {
|
|
@@ -18919,6 +19166,7 @@ var SpaceCore = class {
|
|
|
18919
19166
|
await Promise.all([
|
|
18920
19167
|
this.provisionGitLane(status),
|
|
18921
19168
|
this.provisionMcpLane(),
|
|
19169
|
+
this.provisionPluginLane(),
|
|
18922
19170
|
// Covers a host signed in outside the one-click button, which would
|
|
18923
19171
|
// otherwise meet the wizard the first time it opens a terminal.
|
|
18924
19172
|
markOnboardingComplete(target).catch(() => {
|
|
@@ -18961,17 +19209,53 @@ var SpaceCore = class {
|
|
|
18961
19209
|
target.paths.repo
|
|
18962
19210
|
);
|
|
18963
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
|
+
}
|
|
18964
19238
|
// Register this space's MCPs with both CLIs, user-scoped, and clear the ones
|
|
18965
19239
|
// it cannot currently offer. Registering regardless left every space holding
|
|
18966
19240
|
// an entry it never uses, which the CLI then fails to handshake with on every
|
|
18967
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.
|
|
18968
19246
|
async provisionMcpLane() {
|
|
18969
19247
|
const lanes = this.mcpLanes();
|
|
18970
|
-
|
|
19248
|
+
const board = this.boardMcp();
|
|
19249
|
+
const boardCodexTable = board?.name.replace(/-/g, "_");
|
|
18971
19250
|
const cmd = [
|
|
18972
19251
|
// Pre-rename entries, when the name was the same for every space.
|
|
18973
19252
|
`claude mcp remove factiii-onedrive --scope user >/dev/null 2>&1 || true`,
|
|
18974
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
|
+
] : [],
|
|
18975
19259
|
...lanes.flatMap(({ mcp, tokenEnv, enabled }) => {
|
|
18976
19260
|
const codexTable = mcp.name.replace(/-/g, "_");
|
|
18977
19261
|
const claudeJson = JSON.stringify({
|
|
@@ -19588,7 +19872,7 @@ var BoardAgentEngine = class _BoardAgentEngine {
|
|
|
19588
19872
|
// ── Runner secrets store (secrets.sh contract; see host-secrets.ts) ──
|
|
19589
19873
|
secretsDirs() {
|
|
19590
19874
|
const spaceDir = this.core.spaceDir();
|
|
19591
|
-
return { configDir: (0,
|
|
19875
|
+
return { configDir: (0, import_path10.dirname)(spaceDir), spaceDir };
|
|
19592
19876
|
}
|
|
19593
19877
|
async deploySecretsStatus() {
|
|
19594
19878
|
return {
|
|
@@ -20646,10 +20930,10 @@ ${c.content.trim() || "(no description)"}`
|
|
|
20646
20930
|
// ../../shared/all/helpers/board-agent-core/index-service.ts
|
|
20647
20931
|
var import_child_process5 = require("child_process");
|
|
20648
20932
|
var import_crypto3 = require("crypto");
|
|
20649
|
-
var
|
|
20650
|
-
var
|
|
20933
|
+
var import_fs4 = require("fs");
|
|
20934
|
+
var import_promises7 = __toESM(require("fs/promises"));
|
|
20651
20935
|
var import_net2 = require("net");
|
|
20652
|
-
var
|
|
20936
|
+
var import_path11 = require("path");
|
|
20653
20937
|
var import_util7 = require("util");
|
|
20654
20938
|
var execFileAsync4 = (0, import_util7.promisify)(import_child_process5.execFile);
|
|
20655
20939
|
var IndexService = class {
|
|
@@ -20708,7 +20992,7 @@ var IndexService = class {
|
|
|
20708
20992
|
const log = await this.openLog();
|
|
20709
20993
|
this.proc = (0, import_child_process5.spawn)(
|
|
20710
20994
|
process.execPath,
|
|
20711
|
-
[(0,
|
|
20995
|
+
[(0, import_path11.join)(this.opts.indexDir, "index.js")],
|
|
20712
20996
|
{
|
|
20713
20997
|
cwd: this.opts.indexDir,
|
|
20714
20998
|
env: {
|
|
@@ -20736,8 +21020,8 @@ var IndexService = class {
|
|
|
20736
21020
|
* opened (a missing log must never stop the service from starting). */
|
|
20737
21021
|
async openLog() {
|
|
20738
21022
|
try {
|
|
20739
|
-
await
|
|
20740
|
-
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");
|
|
20741
21025
|
} catch {
|
|
20742
21026
|
return null;
|
|
20743
21027
|
}
|
|
@@ -20746,10 +21030,10 @@ var IndexService = class {
|
|
|
20746
21030
|
* platform needing node-gyp fails here rather than at query time. */
|
|
20747
21031
|
async installDeps() {
|
|
20748
21032
|
const dir = this.opts.indexDir;
|
|
20749
|
-
if ((0,
|
|
20750
|
-
await
|
|
20751
|
-
(0,
|
|
20752
|
-
(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")
|
|
20753
21037
|
);
|
|
20754
21038
|
await execFileAsync4(
|
|
20755
21039
|
"npm",
|
|
@@ -20761,15 +21045,15 @@ var IndexService = class {
|
|
|
20761
21045
|
* CLIs' persistent MCP config, so a fresh port each start would leave every
|
|
20762
21046
|
* registration pointing at a dead one. */
|
|
20763
21047
|
async stablePort() {
|
|
20764
|
-
const file = (0,
|
|
20765
|
-
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(() => ""));
|
|
20766
21050
|
if (Number.isInteger(saved) && saved > 0 && await isFree(saved)) {
|
|
20767
21051
|
return saved;
|
|
20768
21052
|
}
|
|
20769
21053
|
const port = await freePort();
|
|
20770
|
-
await
|
|
21054
|
+
await import_promises7.default.mkdir(this.opts.dataDir, { recursive: true }).catch(() => {
|
|
20771
21055
|
});
|
|
20772
|
-
await
|
|
21056
|
+
await import_promises7.default.writeFile(file, String(port), "utf-8").catch(() => {
|
|
20773
21057
|
});
|
|
20774
21058
|
return port;
|
|
20775
21059
|
}
|
|
@@ -20936,25 +21220,25 @@ async function pairWithBrowser(serverUrl) {
|
|
|
20936
21220
|
}
|
|
20937
21221
|
|
|
20938
21222
|
// src/config.ts
|
|
20939
|
-
var
|
|
20940
|
-
var
|
|
20941
|
-
var
|
|
21223
|
+
var import_fs8 = __toESM(require("fs"));
|
|
21224
|
+
var import_os6 = __toESM(require("os"));
|
|
21225
|
+
var import_path16 = __toESM(require("path"));
|
|
20942
21226
|
|
|
20943
21227
|
// src/secureFile.ts
|
|
20944
21228
|
var import_crypto6 = __toESM(require("crypto"));
|
|
20945
|
-
var
|
|
20946
|
-
var
|
|
21229
|
+
var import_fs7 = __toESM(require("fs"));
|
|
21230
|
+
var import_path15 = __toESM(require("path"));
|
|
20947
21231
|
|
|
20948
21232
|
// src/keychain.ts
|
|
20949
21233
|
var import_child_process7 = require("child_process");
|
|
20950
21234
|
var import_crypto4 = __toESM(require("crypto"));
|
|
20951
|
-
var
|
|
20952
|
-
var
|
|
20953
|
-
var
|
|
21235
|
+
var import_fs5 = __toESM(require("fs"));
|
|
21236
|
+
var import_os4 = __toESM(require("os"));
|
|
21237
|
+
var import_path12 = __toESM(require("path"));
|
|
20954
21238
|
var SERVICE = "factiii-runner";
|
|
20955
21239
|
var ACCOUNT = "config-encryption-key";
|
|
20956
|
-
var DPAPI_KEY_FILE =
|
|
20957
|
-
|
|
21240
|
+
var DPAPI_KEY_FILE = import_path12.default.join(
|
|
21241
|
+
import_os4.default.homedir(),
|
|
20958
21242
|
".factiii-runner",
|
|
20959
21243
|
"config-key.dpapi"
|
|
20960
21244
|
);
|
|
@@ -21034,7 +21318,7 @@ function linuxWrite(key) {
|
|
|
21034
21318
|
function winRead() {
|
|
21035
21319
|
let wrapped;
|
|
21036
21320
|
try {
|
|
21037
|
-
wrapped =
|
|
21321
|
+
wrapped = import_fs5.default.readFileSync(DPAPI_KEY_FILE, "utf-8").trim();
|
|
21038
21322
|
} catch {
|
|
21039
21323
|
return { state: "missing" };
|
|
21040
21324
|
}
|
|
@@ -21059,8 +21343,8 @@ function winWrite(key) {
|
|
|
21059
21343
|
"-Command",
|
|
21060
21344
|
`ConvertTo-SecureString -String '${key.toString("base64")}' -AsPlainText -Force | ConvertFrom-SecureString`
|
|
21061
21345
|
]);
|
|
21062
|
-
|
|
21063
|
-
|
|
21346
|
+
import_fs5.default.mkdirSync(import_path12.default.dirname(DPAPI_KEY_FILE), { recursive: true });
|
|
21347
|
+
import_fs5.default.writeFileSync(DPAPI_KEY_FILE, `${out.trim()}
|
|
21064
21348
|
`, { mode: 384 });
|
|
21065
21349
|
}
|
|
21066
21350
|
function keychainBackend() {
|
|
@@ -21222,22 +21506,22 @@ function keychainStatus() {
|
|
|
21222
21506
|
|
|
21223
21507
|
// src/secureStore.ts
|
|
21224
21508
|
var import_crypto5 = __toESM(require("crypto"));
|
|
21225
|
-
var
|
|
21226
|
-
var
|
|
21509
|
+
var import_fs6 = __toESM(require("fs"));
|
|
21510
|
+
var import_path14 = __toESM(require("path"));
|
|
21227
21511
|
|
|
21228
21512
|
// src/paths.ts
|
|
21229
|
-
var
|
|
21230
|
-
var
|
|
21231
|
-
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");
|
|
21232
21516
|
function safeSlug(spaceSlug) {
|
|
21233
21517
|
return spaceSlug.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
21234
21518
|
}
|
|
21235
21519
|
function spaceDirPath(spaceSlug) {
|
|
21236
|
-
return
|
|
21520
|
+
return import_path13.default.join(CONFIG_DIR, safeSlug(spaceSlug));
|
|
21237
21521
|
}
|
|
21238
21522
|
|
|
21239
21523
|
// src/secureStore.ts
|
|
21240
|
-
var VAULT_KEY_FILE =
|
|
21524
|
+
var VAULT_KEY_FILE = import_path14.default.join(CONFIG_DIR, "vault-key.json");
|
|
21241
21525
|
var SCRYPT_N2 = 1 << 15;
|
|
21242
21526
|
var MIN_PASSWORD_LENGTH = 8;
|
|
21243
21527
|
var SecureStoreError = class extends Error {
|
|
@@ -21310,7 +21594,7 @@ function unseal(box, key) {
|
|
|
21310
21594
|
function readVaultFile() {
|
|
21311
21595
|
try {
|
|
21312
21596
|
const parsed = JSON.parse(
|
|
21313
|
-
|
|
21597
|
+
import_fs6.default.readFileSync(VAULT_KEY_FILE, "utf-8")
|
|
21314
21598
|
);
|
|
21315
21599
|
return parsed.v === 1 ? parsed : null;
|
|
21316
21600
|
} catch {
|
|
@@ -21318,11 +21602,11 @@ function readVaultFile() {
|
|
|
21318
21602
|
}
|
|
21319
21603
|
}
|
|
21320
21604
|
function writeVaultFile(file) {
|
|
21321
|
-
|
|
21605
|
+
import_fs6.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
21322
21606
|
const tmp = `${VAULT_KEY_FILE}.${process.pid}.tmp`;
|
|
21323
|
-
|
|
21607
|
+
import_fs6.default.writeFileSync(tmp, `${JSON.stringify(file, null, 2)}
|
|
21324
21608
|
`, { mode: 384 });
|
|
21325
|
-
|
|
21609
|
+
import_fs6.default.renameSync(tmp, VAULT_KEY_FILE);
|
|
21326
21610
|
}
|
|
21327
21611
|
var unlockedDek = null;
|
|
21328
21612
|
function createVault(machineKey) {
|
|
@@ -21559,15 +21843,15 @@ function parseEnvelope(raw) {
|
|
|
21559
21843
|
}
|
|
21560
21844
|
}
|
|
21561
21845
|
function atomicWrite(filePath, body) {
|
|
21562
|
-
|
|
21846
|
+
import_fs7.default.mkdirSync(import_path15.default.dirname(filePath), { recursive: true });
|
|
21563
21847
|
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
21564
|
-
|
|
21565
|
-
|
|
21848
|
+
import_fs7.default.writeFileSync(tmp, body, { mode: 384 });
|
|
21849
|
+
import_fs7.default.renameSync(tmp, filePath);
|
|
21566
21850
|
}
|
|
21567
21851
|
function readSecureBuffer(filePath) {
|
|
21568
21852
|
let raw;
|
|
21569
21853
|
try {
|
|
21570
|
-
raw =
|
|
21854
|
+
raw = import_fs7.default.readFileSync(filePath, "utf-8");
|
|
21571
21855
|
} catch {
|
|
21572
21856
|
return null;
|
|
21573
21857
|
}
|
|
@@ -21624,7 +21908,7 @@ function writeSecureJson(filePath, value2) {
|
|
|
21624
21908
|
function readBootJson(filePath) {
|
|
21625
21909
|
let raw;
|
|
21626
21910
|
try {
|
|
21627
|
-
raw =
|
|
21911
|
+
raw = import_fs7.default.readFileSync(filePath, "utf-8");
|
|
21628
21912
|
} catch {
|
|
21629
21913
|
return null;
|
|
21630
21914
|
}
|
|
@@ -21674,8 +21958,8 @@ function writeBootJson(filePath, value2) {
|
|
|
21674
21958
|
}
|
|
21675
21959
|
|
|
21676
21960
|
// src/config.ts
|
|
21677
|
-
var CONFIG_DIR2 =
|
|
21678
|
-
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");
|
|
21679
21963
|
function readRunnerConfig() {
|
|
21680
21964
|
return readBootJson(CONFIG_FILE);
|
|
21681
21965
|
}
|
|
@@ -21683,7 +21967,7 @@ function writeRunnerConfig(config) {
|
|
|
21683
21967
|
writeBootJson(CONFIG_FILE, config);
|
|
21684
21968
|
}
|
|
21685
21969
|
function configExists() {
|
|
21686
|
-
return
|
|
21970
|
+
return import_fs8.default.existsSync(CONFIG_FILE);
|
|
21687
21971
|
}
|
|
21688
21972
|
|
|
21689
21973
|
// src/promptPassword.ts
|
|
@@ -21845,9 +22129,9 @@ function makeChunkReassembler() {
|
|
|
21845
22129
|
|
|
21846
22130
|
// src/daemon.ts
|
|
21847
22131
|
var import_crypto7 = require("crypto");
|
|
21848
|
-
var
|
|
22132
|
+
var import_fs10 = __toESM(require("fs"));
|
|
21849
22133
|
var import_node_datachannel = require("node-datachannel");
|
|
21850
|
-
var
|
|
22134
|
+
var import_path18 = __toESM(require("path"));
|
|
21851
22135
|
|
|
21852
22136
|
// ../../node_modules/engine.io-client/build/esm-debug/transports/polling-xhr.node.js
|
|
21853
22137
|
var XMLHttpRequestModule = __toESM(require_XMLHttpRequest(), 1);
|
|
@@ -23027,12 +23311,12 @@ function parse2(str) {
|
|
|
23027
23311
|
uri.queryKey = queryKey(uri, uri["query"]);
|
|
23028
23312
|
return uri;
|
|
23029
23313
|
}
|
|
23030
|
-
function pathNames(obj,
|
|
23031
|
-
const regx = /\/{2,9}/g, names =
|
|
23032
|
-
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) {
|
|
23033
23317
|
names.splice(0, 1);
|
|
23034
23318
|
}
|
|
23035
|
-
if (
|
|
23319
|
+
if (path18.slice(-1) == "/") {
|
|
23036
23320
|
names.splice(names.length - 1, 1);
|
|
23037
23321
|
}
|
|
23038
23322
|
return names;
|
|
@@ -23651,7 +23935,7 @@ var protocol2 = Socket.protocol;
|
|
|
23651
23935
|
// ../../node_modules/socket.io-client/build/esm-debug/url.js
|
|
23652
23936
|
var import_debug7 = __toESM(require_src(), 1);
|
|
23653
23937
|
var debug7 = (0, import_debug7.default)("socket.io-client:url");
|
|
23654
|
-
function url(uri,
|
|
23938
|
+
function url(uri, path18 = "", loc) {
|
|
23655
23939
|
let obj = uri;
|
|
23656
23940
|
loc = loc || typeof location !== "undefined" && location;
|
|
23657
23941
|
if (null == uri)
|
|
@@ -23685,7 +23969,7 @@ function url(uri, path17 = "", loc) {
|
|
|
23685
23969
|
obj.path = obj.path || "/";
|
|
23686
23970
|
const ipv6 = obj.host.indexOf(":") !== -1;
|
|
23687
23971
|
const host = ipv6 ? "[" + obj.host + "]" : obj.host;
|
|
23688
|
-
obj.id = obj.protocol + "://" + host + ":" + obj.port +
|
|
23972
|
+
obj.id = obj.protocol + "://" + host + ":" + obj.port + path18;
|
|
23689
23973
|
obj.href = obj.protocol + "://" + host + (loc && loc.port === obj.port ? "" : ":" + obj.port);
|
|
23690
23974
|
return obj;
|
|
23691
23975
|
}
|
|
@@ -25319,8 +25603,8 @@ function lookup(uri, opts) {
|
|
|
25319
25603
|
const parsed = url(uri, opts.path || "/socket.io");
|
|
25320
25604
|
const source = parsed.source;
|
|
25321
25605
|
const id = parsed.id;
|
|
25322
|
-
const
|
|
25323
|
-
const sameNamespace = cache[id] &&
|
|
25606
|
+
const path18 = parsed.path;
|
|
25607
|
+
const sameNamespace = cache[id] && path18 in cache[id]["nsps"];
|
|
25324
25608
|
const newConnection = opts.forceNew || opts["force new connection"] || false === opts.multiplex || sameNamespace;
|
|
25325
25609
|
let io;
|
|
25326
25610
|
if (newConnection) {
|
|
@@ -25362,8 +25646,8 @@ function cardContextPath(target, postId) {
|
|
|
25362
25646
|
const safe = postId.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80) || "card";
|
|
25363
25647
|
return `${target.paths.root}/.cards/${safe}.md`;
|
|
25364
25648
|
}
|
|
25365
|
-
function cardContextPrompt(
|
|
25366
|
-
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.`;
|
|
25367
25651
|
}
|
|
25368
25652
|
var CLAUDE_PERMISSION_MODE = `--permission-mode auto`;
|
|
25369
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.";
|
|
@@ -25527,11 +25811,11 @@ var TerminalManager = class {
|
|
|
25527
25811
|
};
|
|
25528
25812
|
|
|
25529
25813
|
// src/agent-adapter.ts
|
|
25530
|
-
var
|
|
25531
|
-
var
|
|
25532
|
-
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");
|
|
25533
25817
|
function connectionPath(spaceSlug) {
|
|
25534
|
-
return
|
|
25818
|
+
return import_path17.default.join(CONFIG_DIR, `space-${safeSlug(spaceSlug)}.onedrive.json`);
|
|
25535
25819
|
}
|
|
25536
25820
|
function writeJson(filePath, value2) {
|
|
25537
25821
|
writeSecureJson(filePath, value2);
|
|
@@ -25558,14 +25842,14 @@ function migrateLegacyConfigs() {
|
|
|
25558
25842
|
const file = { boards: {} };
|
|
25559
25843
|
let legacy = [];
|
|
25560
25844
|
try {
|
|
25561
|
-
legacy =
|
|
25845
|
+
legacy = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => f.startsWith("space-") && f.endsWith(".json")).filter((f) => !f.endsWith(".onedrive.json"));
|
|
25562
25846
|
} catch {
|
|
25563
25847
|
return file;
|
|
25564
25848
|
}
|
|
25565
25849
|
for (const name of legacy) {
|
|
25566
25850
|
const slug = name.slice("space-".length, -".json".length);
|
|
25567
25851
|
const parsed = readSecureJson(
|
|
25568
|
-
|
|
25852
|
+
import_path17.default.join(CONFIG_DIR, name)
|
|
25569
25853
|
);
|
|
25570
25854
|
if (!parsed) continue;
|
|
25571
25855
|
if (!file.defaultSlug) {
|
|
@@ -25585,14 +25869,14 @@ function encryptStrayPlaintextConfigs() {
|
|
|
25585
25869
|
const OURS = /^(config|runner-config|space-.*)\.json(\.bak-\d+)?$/;
|
|
25586
25870
|
let names = [];
|
|
25587
25871
|
try {
|
|
25588
|
-
names =
|
|
25872
|
+
names = import_fs9.default.readdirSync(CONFIG_DIR).filter((f) => OURS.test(f));
|
|
25589
25873
|
} catch {
|
|
25590
25874
|
return;
|
|
25591
25875
|
}
|
|
25592
25876
|
for (const name of names) {
|
|
25593
|
-
const filePath =
|
|
25877
|
+
const filePath = import_path17.default.join(CONFIG_DIR, name);
|
|
25594
25878
|
try {
|
|
25595
|
-
const raw =
|
|
25879
|
+
const raw = import_fs9.default.readFileSync(filePath, "utf-8");
|
|
25596
25880
|
if (!raw.startsWith("{")) continue;
|
|
25597
25881
|
writeSecureJson(filePath, JSON.parse(raw));
|
|
25598
25882
|
} catch {
|
|
@@ -25623,7 +25907,7 @@ async function getConfiguredConnections() {
|
|
|
25623
25907
|
}
|
|
25624
25908
|
if (await isSignedIn(hostTarget(CONFIG_DIR), "claude")) kinds.add("claude");
|
|
25625
25909
|
try {
|
|
25626
|
-
const hasOneDrive =
|
|
25910
|
+
const hasOneDrive = import_fs9.default.readdirSync(CONFIG_DIR).some((f) => f.endsWith(".onedrive.json"));
|
|
25627
25911
|
if (hasOneDrive) kinds.add("onedrive");
|
|
25628
25912
|
} catch {
|
|
25629
25913
|
}
|
|
@@ -25694,7 +25978,7 @@ var localConfigProvider = {
|
|
|
25694
25978
|
writeOneDriveConnection(spaceSlug, connection) {
|
|
25695
25979
|
const filePath = connectionPath(spaceSlug);
|
|
25696
25980
|
if (!connection) {
|
|
25697
|
-
|
|
25981
|
+
import_fs9.default.rmSync(filePath, { force: true });
|
|
25698
25982
|
return;
|
|
25699
25983
|
}
|
|
25700
25984
|
writeJson(filePath, connection);
|
|
@@ -25719,7 +26003,7 @@ var localConfigProvider = {
|
|
|
25719
26003
|
"claude",
|
|
25720
26004
|
"codex"
|
|
25721
26005
|
]) {
|
|
25722
|
-
|
|
26006
|
+
import_fs9.default.mkdirSync(import_path17.default.join(dir, sub), { recursive: true });
|
|
25723
26007
|
}
|
|
25724
26008
|
return dir;
|
|
25725
26009
|
}
|
|
@@ -25893,15 +26177,15 @@ async function startDaemon(config) {
|
|
|
25893
26177
|
}
|
|
25894
26178
|
);
|
|
25895
26179
|
const indexCandidates = [
|
|
25896
|
-
|
|
25897
|
-
|
|
26180
|
+
import_path18.default.join(__dirname, "..", "index"),
|
|
26181
|
+
import_path18.default.join(__dirname, "..", "..", "index", "image")
|
|
25898
26182
|
];
|
|
25899
|
-
const indexDir = indexCandidates.find((p) =>
|
|
26183
|
+
const indexDir = indexCandidates.find((p) => import_fs10.default.existsSync(p)) ?? indexCandidates[0];
|
|
25900
26184
|
const odIndex = new IndexService({
|
|
25901
26185
|
controlToken: (0, import_crypto7.createHash)("sha256").update(config.authToken).digest("hex"),
|
|
25902
26186
|
embedUrl: () => config.serverUrl,
|
|
25903
26187
|
indexDir,
|
|
25904
|
-
dataDir:
|
|
26188
|
+
dataDir: import_path18.default.join(CONFIG_DIR, "index-data")
|
|
25905
26189
|
});
|
|
25906
26190
|
void odIndex.ensureRunning().catch((err) => {
|
|
25907
26191
|
console.error(
|