mexus-cli 1.0.1 → 1.0.3
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/package.json +1 -1
- package/packages/server/dist/cli.mjs +675 -153
- package/packages/server/dist/cli.mjs.map +1 -1
- package/packages/web/dist/assets/{_basePickBy-DQhOWiJk.js → _basePickBy-DXIN2jRI.js} +1 -1
- package/packages/web/dist/assets/{_baseUniq-DXTx6DDJ.js → _baseUniq-BDywcd6l.js} +1 -1
- package/packages/web/dist/assets/{arc-D98xlvrt.js → arc-DyeF6zm4.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-2XIMDMQ5-BFC9xn2H.js → architectureDiagram-2XIMDMQ5-DJLWO4Pj.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-WCTKOSBZ-DJH8Soam.js → blockDiagram-WCTKOSBZ-BV82tfhi.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-IC4MRINW-w36rnsiL.js → c4Diagram-IC4MRINW-fu-JF9Ds.js} +1 -1
- package/packages/web/dist/assets/channel-DMMUT-ui.js +1 -0
- package/packages/web/dist/assets/{chunk-4BX2VUAB-SF13Eulk.js → chunk-4BX2VUAB-CviYIUxF.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-wMI9Klww.js → chunk-55IACEB6-BPPHvGS8.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-WwcC59gR.js → chunk-FMBD7UC4-CUHRhjBX.js} +1 -1
- package/packages/web/dist/assets/{chunk-JSJVCQXG-DvXmNU5-.js → chunk-JSJVCQXG-C4BN-QRR.js} +1 -1
- package/packages/web/dist/assets/{chunk-KX2RTZJC-BdEEznCp.js → chunk-KX2RTZJC-DbvSNohi.js} +1 -1
- package/packages/web/dist/assets/{chunk-NQ4KR5QH-BwNAeHYs.js → chunk-NQ4KR5QH-Dmi6UmQw.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-C2xATrOG.js → chunk-QZHKN3VN-cq0CYVai.js} +1 -1
- package/packages/web/dist/assets/{chunk-WL4C6EOR-CEAL3q8O.js → chunk-WL4C6EOR-DU7g3-p8.js} +1 -1
- package/packages/web/dist/assets/classDiagram-VBA2DB6C-VAN2n8PZ.js +1 -0
- package/packages/web/dist/assets/classDiagram-v2-RAHNMMFH-VAN2n8PZ.js +1 -0
- package/packages/web/dist/assets/clone-CvILe-_6.js +1 -0
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DMB9iF_s.js → cose-bilkent-S5V4N54A-DcB6mhJx.js} +1 -1
- package/packages/web/dist/assets/{dagre-KLK3FWXG-B3CBp1UR.js → dagre-KLK3FWXG-CKsicAAq.js} +1 -1
- package/packages/web/dist/assets/{diagram-E7M64L7V-BP3gXbyt.js → diagram-E7M64L7V-DGe3_R9s.js} +1 -1
- package/packages/web/dist/assets/{diagram-IFDJBPK2-DiWKPyS1.js → diagram-IFDJBPK2-BZq50qti.js} +1 -1
- package/packages/web/dist/assets/{diagram-P4PSJMXO-BRwIWc-T.js → diagram-P4PSJMXO-nwlLLk4m.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-INFDFZHY-CwNQ0bqL.js → erDiagram-INFDFZHY-BMh-qsJC.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-PKNHOUZH-CGwKqUfG.js → flowDiagram-PKNHOUZH-DR8q3sxn.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-A5KZAMGK-CwfwEk-O.js → ganttDiagram-A5KZAMGK-B27SJapO.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-K3NZZRJ6-BU-TCNoX.js → gitGraphDiagram-K3NZZRJ6-q-tTdScO.js} +1 -1
- package/packages/web/dist/assets/{graph-BpGuAB34.js → graph-CMugmIWl.js} +1 -1
- package/packages/web/dist/assets/index-Diaj_XhW.js +954 -0
- package/packages/web/dist/assets/index-Dmv7dA85.css +32 -0
- package/packages/web/dist/assets/{infoDiagram-LFFYTUFH-OgqnEPLD.js → infoDiagram-LFFYTUFH-CK_zFXvT.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-PHBUUO56-C7q1YvZf.js → ishikawaDiagram-PHBUUO56-BltT2ON5.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-4ABVD52K-BFTnwlYT.js → journeyDiagram-4ABVD52K-Dy5sDS4Z.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-K7BYSVSG-BPNSFbTq.js → kanban-definition-K7BYSVSG-D5Ls_L8y.js} +1 -1
- package/packages/web/dist/assets/{layout-DiHSY-T6.js → layout-DmCA190Q.js} +1 -1
- package/packages/web/dist/assets/{linear-DcbEPsvj.js → linear-Xq2b2hkZ.js} +1 -1
- package/packages/web/dist/assets/{mindmap-definition-YRQLILUH-CalG0npn.js → mindmap-definition-YRQLILUH-Bb9IeFFk.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-SKSYHLDU-D1PyVe3u.js → pieDiagram-SKSYHLDU-voM4tR2p.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-337W2JSQ-bTKx0-2j.js → quadrantDiagram-337W2JSQ-CWsXyyMa.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-Z7DCOOCP-DHyIVcFu.js → requirementDiagram-Z7DCOOCP-CsOwpcMM.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-WA2Y5GQK-CifZqZcJ.js → sankeyDiagram-WA2Y5GQK-B6cIdL6Z.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-2WXFIKYE-DK8qO73H.js → sequenceDiagram-2WXFIKYE-xbTir98k.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-RAJIS63D-BoK_s4xU.js → stateDiagram-RAJIS63D-BYDksMZW.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-FVOUBMTO-B4eba8Bu.js +1 -0
- package/packages/web/dist/assets/{timeline-definition-YZTLITO2-GwB8m3Pz.js → timeline-definition-YZTLITO2-D6_c0hm9.js} +1 -1
- package/packages/web/dist/assets/{treemap-KZPCXAKY-RvN0JaqD.js → treemap-KZPCXAKY-D0Xw90T6.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-LZ73GAT5-OnlpPkDW.js → vennDiagram-LZ73GAT5-D_T_iazZ.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-JWTSCODW-Dv-KEW3d.js → xychartDiagram-JWTSCODW-CBckvVCD.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/channel-s9cXVaHr.js +0 -1
- package/packages/web/dist/assets/classDiagram-VBA2DB6C-quXqjdGh.js +0 -1
- package/packages/web/dist/assets/classDiagram-v2-RAHNMMFH-quXqjdGh.js +0 -1
- package/packages/web/dist/assets/clone-BwNC5Rz2.js +0 -1
- package/packages/web/dist/assets/index-BQZEjwBD.js +0 -890
- package/packages/web/dist/assets/index-SBtQaWyn.css +0 -32
- package/packages/web/dist/assets/stateDiagram-v2-FVOUBMTO-BKcMBcxU.js +0 -1
|
@@ -43,6 +43,7 @@ var init_ConfigManager = __esm({
|
|
|
43
43
|
resume_flag: "--resume",
|
|
44
44
|
yolo_flag: "--dangerously-skip-permissions",
|
|
45
45
|
statusline: true,
|
|
46
|
+
transport: "pty",
|
|
46
47
|
env: {}
|
|
47
48
|
},
|
|
48
49
|
codex: {
|
|
@@ -51,6 +52,7 @@ var init_ConfigManager = __esm({
|
|
|
51
52
|
resume_flag: "",
|
|
52
53
|
yolo_flag: "",
|
|
53
54
|
statusline: false,
|
|
55
|
+
transport: "pty",
|
|
54
56
|
env: {}
|
|
55
57
|
},
|
|
56
58
|
opencode: {
|
|
@@ -59,6 +61,7 @@ var init_ConfigManager = __esm({
|
|
|
59
61
|
resume_flag: "",
|
|
60
62
|
yolo_flag: "--yolo",
|
|
61
63
|
statusline: false,
|
|
64
|
+
transport: "acp",
|
|
62
65
|
env: {}
|
|
63
66
|
},
|
|
64
67
|
"kimi-cli": {
|
|
@@ -67,6 +70,7 @@ var init_ConfigManager = __esm({
|
|
|
67
70
|
resume_flag: "",
|
|
68
71
|
yolo_flag: "",
|
|
69
72
|
statusline: false,
|
|
73
|
+
transport: "pty",
|
|
70
74
|
env: {}
|
|
71
75
|
},
|
|
72
76
|
qodercli: {
|
|
@@ -75,6 +79,7 @@ var init_ConfigManager = __esm({
|
|
|
75
79
|
resume_flag: "-r",
|
|
76
80
|
yolo_flag: "--yolo",
|
|
77
81
|
statusline: false,
|
|
82
|
+
transport: "pty",
|
|
78
83
|
env: {}
|
|
79
84
|
}
|
|
80
85
|
}
|
|
@@ -168,11 +173,11 @@ var init_ConfigManager = __esm({
|
|
|
168
173
|
async detectAgentsAsync() {
|
|
169
174
|
const agents = {};
|
|
170
175
|
const agentBins = [
|
|
171
|
-
{ key: "claudecode", bin: "claude", flag: "--continue", statusline: true },
|
|
172
|
-
{ key: "codex", bin: "codex", flag: "", statusline: false },
|
|
173
|
-
{ key: "opencode", bin: "opencode", flag: "--continue", statusline: false },
|
|
174
|
-
{ key: "kimi-cli", bin: "kimi", flag: "--continue", statusline: false },
|
|
175
|
-
{ key: "qodercli", bin: "qodercli", flag: "-c", statusline: false }
|
|
176
|
+
{ key: "claudecode", bin: "claude", flag: "--continue", statusline: true, transport: "pty" },
|
|
177
|
+
{ key: "codex", bin: "codex", flag: "", statusline: false, transport: "pty" },
|
|
178
|
+
{ key: "opencode", bin: "opencode", flag: "--continue", statusline: false, transport: "acp" },
|
|
179
|
+
{ key: "kimi-cli", bin: "kimi", flag: "--continue", statusline: false, transport: "pty" },
|
|
180
|
+
{ key: "qodercli", bin: "qodercli", flag: "-c", statusline: false, transport: "pty" }
|
|
176
181
|
];
|
|
177
182
|
const results = await Promise.allSettled(
|
|
178
183
|
agentBins.map(async (agent) => {
|
|
@@ -187,6 +192,7 @@ var init_ConfigManager = __esm({
|
|
|
187
192
|
bin: agent.bin,
|
|
188
193
|
continue_flag: agent.flag,
|
|
189
194
|
statusline: agent.statusline,
|
|
195
|
+
transport: agent.transport,
|
|
190
196
|
env: {}
|
|
191
197
|
};
|
|
192
198
|
}
|
|
@@ -270,16 +276,16 @@ var init_ConfigManager = __esm({
|
|
|
270
276
|
});
|
|
271
277
|
|
|
272
278
|
// src/cli.ts
|
|
273
|
-
import
|
|
274
|
-
import
|
|
279
|
+
import path11 from "path";
|
|
280
|
+
import fs10 from "fs";
|
|
275
281
|
|
|
276
282
|
// src/index.ts
|
|
277
283
|
init_ConfigManager();
|
|
278
284
|
import Fastify from "fastify";
|
|
279
285
|
import fastifyWebsocket from "@fastify/websocket";
|
|
280
286
|
import fastifyStatic from "@fastify/static";
|
|
281
|
-
import
|
|
282
|
-
import
|
|
287
|
+
import path10 from "path";
|
|
288
|
+
import fs9 from "fs";
|
|
283
289
|
import yaml3 from "js-yaml";
|
|
284
290
|
import { fileURLToPath } from "url";
|
|
285
291
|
|
|
@@ -710,7 +716,8 @@ var OutputStateAnalyzer = class {
|
|
|
710
716
|
function stripAnsi2(str) {
|
|
711
717
|
return str.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b[()][AB012]/g, "").replace(/\x1b[>=<]/g, "").replace(/\x0f|\x0e/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
|
|
712
718
|
}
|
|
713
|
-
var
|
|
719
|
+
var MAX_BUFFER_SIZE2 = 64 * 1024;
|
|
720
|
+
var ActivityParser = class _ActivityParser {
|
|
714
721
|
buffer = "";
|
|
715
722
|
lastFile = "";
|
|
716
723
|
lastTime = 0;
|
|
@@ -719,6 +726,9 @@ var ActivityParser = class {
|
|
|
719
726
|
parse(data) {
|
|
720
727
|
if (!data.includes("\n")) {
|
|
721
728
|
this.buffer += data;
|
|
729
|
+
if (this.buffer.length > MAX_BUFFER_SIZE2) {
|
|
730
|
+
this.buffer = "";
|
|
731
|
+
}
|
|
722
732
|
return null;
|
|
723
733
|
}
|
|
724
734
|
const lines = (this.buffer + data).split("\n");
|
|
@@ -825,10 +835,15 @@ var ActivityParser = class {
|
|
|
825
835
|
cleanPath(raw) {
|
|
826
836
|
return raw.trim().replace(/^['"`]+|['"`]+$/g, "").replace(/^\.\//, "").replace(/\s+.*$/, "").replace(/[,;:)]+$/, "").replace(/^\(/, "");
|
|
827
837
|
}
|
|
838
|
+
// Paths under these prefixes are Nexus internals or noise — ignore them
|
|
839
|
+
static IGNORED_PREFIXES = [".nexus/", "node_modules/", ".git/"];
|
|
828
840
|
isValidPath(file) {
|
|
829
841
|
if (!file || file.length < 3) return false;
|
|
830
842
|
if (!/\.\w{1,10}$/.test(file)) return false;
|
|
831
843
|
if (file.startsWith("/") || file.includes("://")) return false;
|
|
844
|
+
for (const prefix of _ActivityParser.IGNORED_PREFIXES) {
|
|
845
|
+
if (file.startsWith(prefix)) return false;
|
|
846
|
+
}
|
|
832
847
|
if (/[<>|&$`\\{}[\]]/.test(file)) return false;
|
|
833
848
|
const parts = file.split("/");
|
|
834
849
|
if (parts.length > 15) return false;
|
|
@@ -876,13 +891,28 @@ var PtyManager = class {
|
|
|
876
891
|
}
|
|
877
892
|
}
|
|
878
893
|
if (agentDef?.env) {
|
|
894
|
+
const BLOCKED_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
895
|
+
"PATH",
|
|
896
|
+
"LD_PRELOAD",
|
|
897
|
+
"LD_LIBRARY_PATH",
|
|
898
|
+
"DYLD_INSERT_LIBRARIES",
|
|
899
|
+
"DYLD_LIBRARY_PATH",
|
|
900
|
+
"DYLD_FRAMEWORK_PATH"
|
|
901
|
+
]);
|
|
879
902
|
for (const [key, value] of Object.entries(agentDef.env)) {
|
|
903
|
+
if (BLOCKED_ENV_KEYS.has(key)) {
|
|
904
|
+
console.warn(`[PTY] Ignoring blocked env var from agent config: ${key}`);
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
880
907
|
const resolved = value.replace(/\$\{(\w+)\}/g, (_, varName) => {
|
|
881
908
|
return process.env[varName] || "";
|
|
882
909
|
});
|
|
883
910
|
if (resolved) env[key] = resolved;
|
|
884
911
|
}
|
|
885
912
|
}
|
|
913
|
+
if (agentDef?.bin === "claude" && !env.CLAUDE_CODE_NO_FLICKER) {
|
|
914
|
+
env.CLAUDE_CODE_NO_FLICKER = "1";
|
|
915
|
+
}
|
|
886
916
|
if (!env.PATH) {
|
|
887
917
|
env.PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
888
918
|
if (process.platform === "darwin") {
|
|
@@ -1049,6 +1079,10 @@ var PtyManager = class {
|
|
|
1049
1079
|
entry.shellDetector?.dispose();
|
|
1050
1080
|
entry.agentDetector?.dispose();
|
|
1051
1081
|
entry.parser.reset();
|
|
1082
|
+
entry.onDataCallbacks.length = 0;
|
|
1083
|
+
entry.onStatusCallbacks.length = 0;
|
|
1084
|
+
entry.onMetaCallbacks.length = 0;
|
|
1085
|
+
entry.onActivityCallbacks.length = 0;
|
|
1052
1086
|
try {
|
|
1053
1087
|
entry.pty.kill();
|
|
1054
1088
|
} catch {
|
|
@@ -1104,9 +1138,368 @@ var PtyManager = class {
|
|
|
1104
1138
|
}
|
|
1105
1139
|
};
|
|
1106
1140
|
|
|
1107
|
-
// src/
|
|
1108
|
-
import
|
|
1141
|
+
// src/runtime/AcpRuntime.ts
|
|
1142
|
+
import { spawn as spawn2 } from "child_process";
|
|
1109
1143
|
import fs3 from "fs";
|
|
1144
|
+
import os3 from "os";
|
|
1145
|
+
import path3 from "path";
|
|
1146
|
+
var MAX_SCROLLBACK_BYTES2 = 512 * 1024;
|
|
1147
|
+
function resolveAgentEnv(agentDef) {
|
|
1148
|
+
const env = {};
|
|
1149
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1150
|
+
if (value !== void 0 && !key.startsWith("CLAUDE") && key !== "CLAUDECODE") {
|
|
1151
|
+
env[key] = value;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (agentDef?.env) {
|
|
1155
|
+
const blocked = /* @__PURE__ */ new Set([
|
|
1156
|
+
"PATH",
|
|
1157
|
+
"LD_PRELOAD",
|
|
1158
|
+
"LD_LIBRARY_PATH",
|
|
1159
|
+
"DYLD_INSERT_LIBRARIES",
|
|
1160
|
+
"DYLD_LIBRARY_PATH",
|
|
1161
|
+
"DYLD_FRAMEWORK_PATH"
|
|
1162
|
+
]);
|
|
1163
|
+
for (const [key, value] of Object.entries(agentDef.env)) {
|
|
1164
|
+
if (blocked.has(key)) continue;
|
|
1165
|
+
env[key] = value.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] || "");
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (!env.PATH) {
|
|
1169
|
+
env.PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
1170
|
+
if (process.platform === "darwin") {
|
|
1171
|
+
env.PATH = `/opt/homebrew/bin:/opt/homebrew/sbin:${env.PATH}`;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return env;
|
|
1175
|
+
}
|
|
1176
|
+
var AcpRuntime = class {
|
|
1177
|
+
entries = /* @__PURE__ */ new Map();
|
|
1178
|
+
configManager;
|
|
1179
|
+
constructor(configManager) {
|
|
1180
|
+
this.configManager = configManager;
|
|
1181
|
+
}
|
|
1182
|
+
spawn(paneId, config) {
|
|
1183
|
+
if (this.entries.has(paneId)) {
|
|
1184
|
+
this.kill(paneId);
|
|
1185
|
+
}
|
|
1186
|
+
const projectDir = this.configManager.getProjectDir();
|
|
1187
|
+
const basePath = config.isolation === "worktree" && config.worktreePath ? config.worktreePath : projectDir;
|
|
1188
|
+
let cwd = config.workdir ? path3.resolve(basePath, config.workdir) : basePath;
|
|
1189
|
+
if (!fs3.existsSync(cwd)) {
|
|
1190
|
+
cwd = fs3.existsSync(projectDir) ? projectDir : os3.homedir();
|
|
1191
|
+
}
|
|
1192
|
+
const agentDef = this.configManager.getAgentDefinition(config.agent);
|
|
1193
|
+
if (!agentDef) {
|
|
1194
|
+
throw new Error(`Missing agent definition for ${config.agent}`);
|
|
1195
|
+
}
|
|
1196
|
+
const env = resolveAgentEnv(agentDef);
|
|
1197
|
+
const proc = spawn2(agentDef.bin, ["acp"], {
|
|
1198
|
+
cwd,
|
|
1199
|
+
env,
|
|
1200
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1201
|
+
});
|
|
1202
|
+
const entry = {
|
|
1203
|
+
proc,
|
|
1204
|
+
config,
|
|
1205
|
+
status: "running",
|
|
1206
|
+
meta: { cwd },
|
|
1207
|
+
nextRequestId: 1,
|
|
1208
|
+
nextMessageId: 1,
|
|
1209
|
+
nextToolId: 1,
|
|
1210
|
+
pending: /* @__PURE__ */ new Map(),
|
|
1211
|
+
stdoutBuffer: "",
|
|
1212
|
+
scrollback: [],
|
|
1213
|
+
scrollbackBytes: 0,
|
|
1214
|
+
onDataCallbacks: [],
|
|
1215
|
+
onStatusCallbacks: [],
|
|
1216
|
+
onMetaCallbacks: [],
|
|
1217
|
+
onConversationCallbacks: [],
|
|
1218
|
+
onActivityCallbacks: []
|
|
1219
|
+
};
|
|
1220
|
+
this.entries.set(paneId, entry);
|
|
1221
|
+
proc.stdout.setEncoding("utf8");
|
|
1222
|
+
proc.stderr.setEncoding("utf8");
|
|
1223
|
+
proc.stdout.on("data", (chunk) => this.handleStdout(paneId, chunk));
|
|
1224
|
+
proc.stderr.on("data", (chunk) => {
|
|
1225
|
+
this.emitTerminal(paneId, `[acp stderr] ${chunk}`);
|
|
1226
|
+
});
|
|
1227
|
+
proc.on("exit", (code, signal) => {
|
|
1228
|
+
const e = this.entries.get(paneId);
|
|
1229
|
+
if (!e) return;
|
|
1230
|
+
for (const pending of e.pending.values()) {
|
|
1231
|
+
pending.reject(new Error(`ACP process exited (${code ?? "null"}${signal ? `, ${signal}` : ""})`));
|
|
1232
|
+
}
|
|
1233
|
+
e.pending.clear();
|
|
1234
|
+
this.setStatus(paneId, code === 0 ? "stopped" : "error");
|
|
1235
|
+
});
|
|
1236
|
+
this.bootstrap(paneId, cwd, config).catch((err) => {
|
|
1237
|
+
this.emitTerminal(paneId, `[acp error] ${err.message}
|
|
1238
|
+
`);
|
|
1239
|
+
this.setStatus(paneId, "error");
|
|
1240
|
+
});
|
|
1241
|
+
return proc.pid;
|
|
1242
|
+
}
|
|
1243
|
+
async bootstrap(paneId, cwd, config) {
|
|
1244
|
+
const initResult = await this.request(paneId, "initialize", {
|
|
1245
|
+
protocolVersion: 1,
|
|
1246
|
+
clientInfo: { name: "nexus", version: "0.1.0" },
|
|
1247
|
+
clientCapabilities: {}
|
|
1248
|
+
});
|
|
1249
|
+
const loadedSessionId = config.restore === "resume" && config.sessionId ? await this.tryLoadSession(paneId, config.sessionId) : null;
|
|
1250
|
+
const sessionResult = loadedSessionId ? { sessionId: loadedSessionId } : await this.request(paneId, "session/new", {
|
|
1251
|
+
cwd
|
|
1252
|
+
});
|
|
1253
|
+
const sessionId = this.extractSessionId(sessionResult) || this.extractSessionId(initResult) || config.sessionId;
|
|
1254
|
+
if (sessionId) {
|
|
1255
|
+
this.updateMeta(paneId, { sessionId, cwd });
|
|
1256
|
+
} else {
|
|
1257
|
+
this.updateMeta(paneId, { cwd });
|
|
1258
|
+
}
|
|
1259
|
+
this.emitConversation(paneId, { type: "status", status: "idle" });
|
|
1260
|
+
this.setStatus(paneId, "idle");
|
|
1261
|
+
if (config.task && config.restore !== "manual") {
|
|
1262
|
+
await this.sendPrompt(paneId, config.task);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
async tryLoadSession(paneId, sessionId) {
|
|
1266
|
+
try {
|
|
1267
|
+
const result = await this.request(paneId, "session/load", { sessionId });
|
|
1268
|
+
return this.extractSessionId(result) || sessionId;
|
|
1269
|
+
} catch {
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
async sendPrompt(paneId, text) {
|
|
1274
|
+
const entry = this.entries.get(paneId);
|
|
1275
|
+
if (!entry) return;
|
|
1276
|
+
const sessionId = entry.meta.sessionId || entry.config.sessionId;
|
|
1277
|
+
const messageId = `user-${entry.nextMessageId++}`;
|
|
1278
|
+
this.emitConversation(paneId, { type: "message", messageId, role: "user", text });
|
|
1279
|
+
this.emitTerminal(paneId, `
|
|
1280
|
+
> ${text}
|
|
1281
|
+
|
|
1282
|
+
`);
|
|
1283
|
+
this.setStatus(paneId, "running");
|
|
1284
|
+
this.emitConversation(paneId, { type: "status", status: "running" });
|
|
1285
|
+
const params = {
|
|
1286
|
+
prompt: [{ type: "text", text }]
|
|
1287
|
+
};
|
|
1288
|
+
if (sessionId) params.sessionId = sessionId;
|
|
1289
|
+
await this.request(paneId, "session/prompt", params);
|
|
1290
|
+
}
|
|
1291
|
+
onData(paneId, cb) {
|
|
1292
|
+
const entry = this.entries.get(paneId);
|
|
1293
|
+
if (entry) entry.onDataCallbacks.push(cb);
|
|
1294
|
+
}
|
|
1295
|
+
onStatus(paneId, cb) {
|
|
1296
|
+
const entry = this.entries.get(paneId);
|
|
1297
|
+
if (entry) entry.onStatusCallbacks.push(cb);
|
|
1298
|
+
}
|
|
1299
|
+
onMeta(paneId, cb) {
|
|
1300
|
+
const entry = this.entries.get(paneId);
|
|
1301
|
+
if (entry) entry.onMetaCallbacks.push(cb);
|
|
1302
|
+
}
|
|
1303
|
+
onConversation(paneId, cb) {
|
|
1304
|
+
const entry = this.entries.get(paneId);
|
|
1305
|
+
if (entry) entry.onConversationCallbacks.push(cb);
|
|
1306
|
+
}
|
|
1307
|
+
onActivity(paneId, cb) {
|
|
1308
|
+
const entry = this.entries.get(paneId);
|
|
1309
|
+
if (entry) entry.onActivityCallbacks.push(cb);
|
|
1310
|
+
}
|
|
1311
|
+
write(_paneId, _data) {
|
|
1312
|
+
}
|
|
1313
|
+
resize(_paneId, _cols, _rows) {
|
|
1314
|
+
}
|
|
1315
|
+
getScrollback(paneId) {
|
|
1316
|
+
const entry = this.entries.get(paneId);
|
|
1317
|
+
return entry ? entry.scrollback.join("") : "";
|
|
1318
|
+
}
|
|
1319
|
+
kill(paneId) {
|
|
1320
|
+
const entry = this.entries.get(paneId);
|
|
1321
|
+
if (!entry) return;
|
|
1322
|
+
entry.proc.kill();
|
|
1323
|
+
this.entries.delete(paneId);
|
|
1324
|
+
}
|
|
1325
|
+
killAll() {
|
|
1326
|
+
for (const paneId of this.entries.keys()) {
|
|
1327
|
+
this.kill(paneId);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
handleStdout(paneId, chunk) {
|
|
1331
|
+
const entry = this.entries.get(paneId);
|
|
1332
|
+
if (!entry) return;
|
|
1333
|
+
entry.stdoutBuffer += chunk;
|
|
1334
|
+
while (true) {
|
|
1335
|
+
const newline = entry.stdoutBuffer.indexOf("\n");
|
|
1336
|
+
if (newline === -1) break;
|
|
1337
|
+
const line = entry.stdoutBuffer.slice(0, newline).trim();
|
|
1338
|
+
entry.stdoutBuffer = entry.stdoutBuffer.slice(newline + 1);
|
|
1339
|
+
if (!line) continue;
|
|
1340
|
+
try {
|
|
1341
|
+
const message = JSON.parse(line);
|
|
1342
|
+
this.handleMessage(paneId, message);
|
|
1343
|
+
} catch {
|
|
1344
|
+
this.emitTerminal(paneId, line + "\n");
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
handleMessage(paneId, message) {
|
|
1349
|
+
const entry = this.entries.get(paneId);
|
|
1350
|
+
if (!entry) return;
|
|
1351
|
+
if (typeof message.id === "number") {
|
|
1352
|
+
const pending = entry.pending.get(message.id);
|
|
1353
|
+
if (pending) {
|
|
1354
|
+
entry.pending.delete(message.id);
|
|
1355
|
+
if ("error" in message && message.error) {
|
|
1356
|
+
const err = message.error;
|
|
1357
|
+
pending.reject(new Error(err?.message || "ACP request failed"));
|
|
1358
|
+
} else {
|
|
1359
|
+
pending.resolve(message.result);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
if (message.method === "session/update") {
|
|
1365
|
+
const params = message.params || {};
|
|
1366
|
+
this.handleSessionUpdate(paneId, params);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
handleSessionUpdate(paneId, params) {
|
|
1371
|
+
const sessionId = this.extractSessionId(params);
|
|
1372
|
+
if (sessionId) {
|
|
1373
|
+
this.updateMeta(paneId, { sessionId });
|
|
1374
|
+
}
|
|
1375
|
+
const rawUpdate = params.update || params.delta || params.event || params;
|
|
1376
|
+
const updates = Array.isArray(rawUpdate) ? rawUpdate : [rawUpdate];
|
|
1377
|
+
for (const update of updates) {
|
|
1378
|
+
if (!update || typeof update !== "object") continue;
|
|
1379
|
+
const record = update;
|
|
1380
|
+
const kind = String(record.type || record.kind || "");
|
|
1381
|
+
const content = this.extractText(record);
|
|
1382
|
+
if (kind.includes("agent_message")) {
|
|
1383
|
+
const messageId = `assistant-${this.entries.get(paneId)?.nextMessageId ?? 1}`;
|
|
1384
|
+
this.emitConversation(paneId, {
|
|
1385
|
+
type: "message",
|
|
1386
|
+
messageId,
|
|
1387
|
+
role: "assistant",
|
|
1388
|
+
text: content,
|
|
1389
|
+
append: true
|
|
1390
|
+
});
|
|
1391
|
+
if (content) {
|
|
1392
|
+
this.emitTerminal(paneId, content);
|
|
1393
|
+
}
|
|
1394
|
+
this.setStatus(paneId, "running");
|
|
1395
|
+
} else if (kind.includes("tool_call")) {
|
|
1396
|
+
const toolCallId = `tool-${this.entries.get(paneId)?.nextToolId ?? 1}`;
|
|
1397
|
+
this.emitConversation(paneId, {
|
|
1398
|
+
type: "tool",
|
|
1399
|
+
toolCallId,
|
|
1400
|
+
title: String(record.title || record.name || "tool"),
|
|
1401
|
+
status: kind.includes("update") ? "in_progress" : "pending",
|
|
1402
|
+
text: content || void 0
|
|
1403
|
+
});
|
|
1404
|
+
if (content) {
|
|
1405
|
+
this.emitTerminal(paneId, `
|
|
1406
|
+
[tool] ${content}
|
|
1407
|
+
`);
|
|
1408
|
+
}
|
|
1409
|
+
} else if (kind.includes("turn") || kind.includes("done") || kind.includes("completed")) {
|
|
1410
|
+
this.setStatus(paneId, "idle");
|
|
1411
|
+
this.emitConversation(paneId, { type: "status", status: "idle" });
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
extractText(record) {
|
|
1416
|
+
const direct = record.text || record.delta || record.content;
|
|
1417
|
+
if (typeof direct === "string") return direct;
|
|
1418
|
+
if (Array.isArray(direct)) {
|
|
1419
|
+
return direct.map((item) => {
|
|
1420
|
+
if (typeof item === "string") return item;
|
|
1421
|
+
if (item && typeof item === "object" && typeof item.text === "string") {
|
|
1422
|
+
return String(item.text);
|
|
1423
|
+
}
|
|
1424
|
+
return "";
|
|
1425
|
+
}).join("");
|
|
1426
|
+
}
|
|
1427
|
+
if (direct && typeof direct === "object" && typeof direct.text === "string") {
|
|
1428
|
+
return String(direct.text);
|
|
1429
|
+
}
|
|
1430
|
+
return "";
|
|
1431
|
+
}
|
|
1432
|
+
request(paneId, method, params) {
|
|
1433
|
+
const entry = this.entries.get(paneId);
|
|
1434
|
+
if (!entry) {
|
|
1435
|
+
return Promise.reject(new Error(`Missing ACP entry for ${paneId}`));
|
|
1436
|
+
}
|
|
1437
|
+
const id = entry.nextRequestId++;
|
|
1438
|
+
const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
1439
|
+
entry.proc.stdin.write(payload);
|
|
1440
|
+
return new Promise((resolve, reject) => {
|
|
1441
|
+
entry.pending.set(id, { resolve, reject });
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
extractSessionId(result) {
|
|
1445
|
+
if (!result || typeof result !== "object") return void 0;
|
|
1446
|
+
const record = result;
|
|
1447
|
+
if (typeof record.sessionId === "string") return record.sessionId;
|
|
1448
|
+
if (typeof record.session_id === "string") return record.session_id;
|
|
1449
|
+
if (record.session && typeof record.session === "object") {
|
|
1450
|
+
const nested = record.session;
|
|
1451
|
+
if (typeof nested.id === "string") return nested.id;
|
|
1452
|
+
if (typeof nested.sessionId === "string") return nested.sessionId;
|
|
1453
|
+
}
|
|
1454
|
+
return void 0;
|
|
1455
|
+
}
|
|
1456
|
+
emitTerminal(paneId, data) {
|
|
1457
|
+
const entry = this.entries.get(paneId);
|
|
1458
|
+
if (!entry || !data) return;
|
|
1459
|
+
entry.scrollback.push(data);
|
|
1460
|
+
entry.scrollbackBytes += data.length;
|
|
1461
|
+
if (entry.scrollbackBytes > MAX_SCROLLBACK_BYTES2) {
|
|
1462
|
+
let bytesToRemove = entry.scrollbackBytes - MAX_SCROLLBACK_BYTES2;
|
|
1463
|
+
let removeCount = 0;
|
|
1464
|
+
while (removeCount < entry.scrollback.length - 1 && bytesToRemove > 0) {
|
|
1465
|
+
bytesToRemove -= entry.scrollback[removeCount].length;
|
|
1466
|
+
entry.scrollbackBytes -= entry.scrollback[removeCount].length;
|
|
1467
|
+
removeCount++;
|
|
1468
|
+
}
|
|
1469
|
+
if (removeCount > 0) entry.scrollback.splice(0, removeCount);
|
|
1470
|
+
}
|
|
1471
|
+
for (const cb of entry.onDataCallbacks) {
|
|
1472
|
+
cb(data);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
setStatus(paneId, status) {
|
|
1476
|
+
const entry = this.entries.get(paneId);
|
|
1477
|
+
if (!entry || entry.status === status) return;
|
|
1478
|
+
entry.status = status;
|
|
1479
|
+
for (const cb of entry.onStatusCallbacks) {
|
|
1480
|
+
cb(status);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
updateMeta(paneId, meta) {
|
|
1484
|
+
const entry = this.entries.get(paneId);
|
|
1485
|
+
if (!entry) return;
|
|
1486
|
+
entry.meta = { ...entry.meta, ...meta };
|
|
1487
|
+
for (const cb of entry.onMetaCallbacks) {
|
|
1488
|
+
cb(entry.meta);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
emitConversation(paneId, event) {
|
|
1492
|
+
const entry = this.entries.get(paneId);
|
|
1493
|
+
if (!entry) return;
|
|
1494
|
+
for (const cb of entry.onConversationCallbacks) {
|
|
1495
|
+
cb(event);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
// src/git/WorktreeManager.ts
|
|
1501
|
+
import path4 from "path";
|
|
1502
|
+
import fs4 from "fs";
|
|
1110
1503
|
import { simpleGit } from "simple-git";
|
|
1111
1504
|
var WorktreeManager = class {
|
|
1112
1505
|
projectDir;
|
|
@@ -1124,15 +1517,15 @@ var WorktreeManager = class {
|
|
|
1124
1517
|
const baseBranch = await this.getCurrentBranch();
|
|
1125
1518
|
const slug = this.slugify(paneName);
|
|
1126
1519
|
const branch = `nexus/${paneId}-${slug}`;
|
|
1127
|
-
const worktreePath =
|
|
1128
|
-
if (
|
|
1520
|
+
const worktreePath = path4.join(this.projectDir, ".nexus", "worktrees", paneId);
|
|
1521
|
+
if (fs4.existsSync(worktreePath)) {
|
|
1129
1522
|
await this.forceRemoveWorktree(worktreePath);
|
|
1130
1523
|
}
|
|
1131
1524
|
try {
|
|
1132
1525
|
await this.git.raw(["branch", "-D", branch]);
|
|
1133
1526
|
} catch {
|
|
1134
1527
|
}
|
|
1135
|
-
|
|
1528
|
+
fs4.mkdirSync(path4.dirname(worktreePath), { recursive: true });
|
|
1136
1529
|
await this.git.raw(["worktree", "add", "-b", branch, worktreePath, baseBranch]);
|
|
1137
1530
|
const entry = { path: worktreePath, branch, baseBranch };
|
|
1138
1531
|
this.worktrees.set(paneId, entry);
|
|
@@ -1151,14 +1544,14 @@ var WorktreeManager = class {
|
|
|
1151
1544
|
} catch {
|
|
1152
1545
|
return false;
|
|
1153
1546
|
}
|
|
1154
|
-
if (
|
|
1547
|
+
if (fs4.existsSync(worktreePath)) {
|
|
1155
1548
|
this.worktrees.set(paneId, { path: worktreePath, branch, baseBranch });
|
|
1156
1549
|
return true;
|
|
1157
1550
|
}
|
|
1158
1551
|
try {
|
|
1159
1552
|
await this.git.raw(["worktree", "prune"]).catch(() => {
|
|
1160
1553
|
});
|
|
1161
|
-
|
|
1554
|
+
fs4.mkdirSync(path4.dirname(worktreePath), { recursive: true });
|
|
1162
1555
|
await this.git.raw(["worktree", "add", worktreePath, branch]);
|
|
1163
1556
|
this.worktrees.set(paneId, { path: worktreePath, branch, baseBranch });
|
|
1164
1557
|
return true;
|
|
@@ -1242,6 +1635,31 @@ var WorktreeManager = class {
|
|
|
1242
1635
|
hunks: hunkMap.get(file) || ""
|
|
1243
1636
|
});
|
|
1244
1637
|
}
|
|
1638
|
+
const fs11 = await import("fs/promises");
|
|
1639
|
+
for (const diff of diffs) {
|
|
1640
|
+
if (diff.status === "added" && !diff.hunks) {
|
|
1641
|
+
try {
|
|
1642
|
+
const fullPath = path4.join(entry.path, diff.file);
|
|
1643
|
+
const stat = await fs11.stat(fullPath).catch(() => null);
|
|
1644
|
+
if (!stat || !stat.isFile()) continue;
|
|
1645
|
+
if (stat.size > 256 * 1024) {
|
|
1646
|
+
diff.hunks = `--- /dev/null
|
|
1647
|
+
+++ b/${diff.file}
|
|
1648
|
+
@@ -0,0 +0,0 @@
|
|
1649
|
+
Binary or large file (${Math.round(stat.size / 1024)}KB)`;
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
const content = await fs11.readFile(fullPath, "utf-8");
|
|
1653
|
+
const lines = content.split("\n");
|
|
1654
|
+
const plusLines = lines.map((line) => `+${line}`).join("\n");
|
|
1655
|
+
diff.hunks = `--- /dev/null
|
|
1656
|
+
+++ b/${diff.file}
|
|
1657
|
+
@@ -0,0 +1,${lines.length} @@
|
|
1658
|
+
${plusLines}`;
|
|
1659
|
+
} catch {
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1245
1663
|
} catch (err) {
|
|
1246
1664
|
console.warn(`[WorktreeManager] getDiffs failed for ${paneId}:`, err.message);
|
|
1247
1665
|
}
|
|
@@ -1337,7 +1755,7 @@ var WorktreeManager = class {
|
|
|
1337
1755
|
await this.git.raw(["worktree", "remove", "--force", wtPath]);
|
|
1338
1756
|
} catch {
|
|
1339
1757
|
try {
|
|
1340
|
-
|
|
1758
|
+
fs4.rmSync(wtPath, { recursive: true, force: true });
|
|
1341
1759
|
await this.git.raw(["worktree", "prune"]);
|
|
1342
1760
|
} catch {
|
|
1343
1761
|
}
|
|
@@ -1357,7 +1775,7 @@ var WorktreeManager = class {
|
|
|
1357
1775
|
};
|
|
1358
1776
|
|
|
1359
1777
|
// src/git/GitService.ts
|
|
1360
|
-
import
|
|
1778
|
+
import path5 from "path";
|
|
1361
1779
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1362
1780
|
import { watch } from "chokidar";
|
|
1363
1781
|
var GitService = class {
|
|
@@ -1383,11 +1801,11 @@ var GitService = class {
|
|
|
1383
1801
|
this.refresh();
|
|
1384
1802
|
}, 1e3);
|
|
1385
1803
|
};
|
|
1386
|
-
const gitDir =
|
|
1804
|
+
const gitDir = path5.join(this.projectDir, ".git");
|
|
1387
1805
|
this.gitWatcher = watch([
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1806
|
+
path5.join(gitDir, "index"),
|
|
1807
|
+
path5.join(gitDir, "HEAD"),
|
|
1808
|
+
path5.join(gitDir, "refs")
|
|
1391
1809
|
], {
|
|
1392
1810
|
persistent: true,
|
|
1393
1811
|
ignoreInitial: true
|
|
@@ -1401,7 +1819,7 @@ var GitService = class {
|
|
|
1401
1819
|
};
|
|
1402
1820
|
this.workWatcher = watch(this.projectDir, {
|
|
1403
1821
|
ignored: (filePath) => {
|
|
1404
|
-
const basename =
|
|
1822
|
+
const basename = path5.basename(filePath);
|
|
1405
1823
|
return basename === ".git" || basename === "node_modules" || basename === ".nexus" || basename === "dist";
|
|
1406
1824
|
},
|
|
1407
1825
|
persistent: true,
|
|
@@ -1412,10 +1830,18 @@ var GitService = class {
|
|
|
1412
1830
|
}
|
|
1413
1831
|
async refresh() {
|
|
1414
1832
|
try {
|
|
1415
|
-
const result = await
|
|
1833
|
+
const result = await Promise.race([
|
|
1834
|
+
this.getDiffs(),
|
|
1835
|
+
new Promise(
|
|
1836
|
+
(_, reject) => setTimeout(() => reject(new Error("git diff timeout")), 15e3)
|
|
1837
|
+
)
|
|
1838
|
+
]);
|
|
1416
1839
|
this.currentResult = result;
|
|
1417
1840
|
this.notifyListeners();
|
|
1418
|
-
} catch {
|
|
1841
|
+
} catch (err) {
|
|
1842
|
+
if (err.message === "git diff timeout") {
|
|
1843
|
+
console.warn("[GitService] git diff timed out (15s), using cached result");
|
|
1844
|
+
}
|
|
1419
1845
|
}
|
|
1420
1846
|
}
|
|
1421
1847
|
getCurrentDiffs() {
|
|
@@ -1447,10 +1873,10 @@ var GitService = class {
|
|
|
1447
1873
|
const status = await this.git.status();
|
|
1448
1874
|
const isUntracked = status.not_added.includes(file) || status.created.includes(file);
|
|
1449
1875
|
if (isUntracked) {
|
|
1450
|
-
const fullPath =
|
|
1451
|
-
const
|
|
1452
|
-
if (
|
|
1453
|
-
|
|
1876
|
+
const fullPath = path5.join(this.projectDir, file);
|
|
1877
|
+
const fs11 = await import("fs");
|
|
1878
|
+
if (fs11.existsSync(fullPath)) {
|
|
1879
|
+
fs11.unlinkSync(fullPath);
|
|
1454
1880
|
}
|
|
1455
1881
|
} else {
|
|
1456
1882
|
await this.git.checkout(["--", file]);
|
|
@@ -1537,13 +1963,24 @@ var GitService = class {
|
|
|
1537
1963
|
for (const diff of unstaged) {
|
|
1538
1964
|
if (diff.status === "added" && !diff.hunks) {
|
|
1539
1965
|
try {
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1966
|
+
const fs11 = await import("fs/promises");
|
|
1967
|
+
const fullPath = path5.join(this.projectDir, diff.file);
|
|
1968
|
+
const stat = await fs11.stat(fullPath).catch(() => null);
|
|
1969
|
+
if (!stat || !stat.isFile()) continue;
|
|
1970
|
+
if (stat.size > 256 * 1024) {
|
|
1542
1971
|
diff.hunks = `--- /dev/null
|
|
1543
1972
|
+++ b/${diff.file}
|
|
1544
|
-
@@ -0,0 +
|
|
1545
|
-
|
|
1973
|
+
@@ -0,0 +0,0 @@
|
|
1974
|
+
Binary or large file (${Math.round(stat.size / 1024)}KB)`;
|
|
1975
|
+
continue;
|
|
1546
1976
|
}
|
|
1977
|
+
const content = await fs11.readFile(fullPath, "utf-8");
|
|
1978
|
+
const lines = content.split("\n");
|
|
1979
|
+
const plusLines = lines.map((line) => `+${line}`).join("\n");
|
|
1980
|
+
diff.hunks = `--- /dev/null
|
|
1981
|
+
+++ b/${diff.file}
|
|
1982
|
+
@@ -0,0 +1,${lines.length} @@
|
|
1983
|
+
${plusLines}`;
|
|
1547
1984
|
} catch {
|
|
1548
1985
|
}
|
|
1549
1986
|
}
|
|
@@ -1591,15 +2028,19 @@ function nextPaneId() {
|
|
|
1591
2028
|
var WorkspaceManager = class {
|
|
1592
2029
|
panes = /* @__PURE__ */ new Map();
|
|
1593
2030
|
ptyManager;
|
|
2031
|
+
acpRuntime;
|
|
1594
2032
|
configManager;
|
|
1595
2033
|
worktreeManager;
|
|
1596
2034
|
perPaneGitServices = /* @__PURE__ */ new Map();
|
|
1597
2035
|
wsName = "";
|
|
1598
2036
|
wsDescription = "";
|
|
2037
|
+
// Serialize config writes to prevent race conditions when closing multiple panes
|
|
2038
|
+
configWriteLock = Promise.resolve();
|
|
1599
2039
|
// Multi-client event listener sets
|
|
1600
2040
|
listeners = {
|
|
1601
2041
|
onPaneAdded: /* @__PURE__ */ new Set(),
|
|
1602
2042
|
onPaneRemoved: /* @__PURE__ */ new Set(),
|
|
2043
|
+
onConversationEvent: /* @__PURE__ */ new Set(),
|
|
1603
2044
|
onPaneStatus: /* @__PURE__ */ new Set(),
|
|
1604
2045
|
onPaneMeta: /* @__PURE__ */ new Set(),
|
|
1605
2046
|
onTerminalData: /* @__PURE__ */ new Set(),
|
|
@@ -1612,6 +2053,7 @@ var WorkspaceManager = class {
|
|
|
1612
2053
|
constructor(configManager) {
|
|
1613
2054
|
this.configManager = configManager;
|
|
1614
2055
|
this.ptyManager = new PtyManager(configManager);
|
|
2056
|
+
this.acpRuntime = new AcpRuntime(configManager);
|
|
1615
2057
|
this.worktreeManager = new WorktreeManager(configManager.getProjectDir());
|
|
1616
2058
|
}
|
|
1617
2059
|
async init() {
|
|
@@ -1712,7 +2154,11 @@ var WorkspaceManager = class {
|
|
|
1712
2154
|
}
|
|
1713
2155
|
async closePane(paneId) {
|
|
1714
2156
|
const pane = this.panes.get(paneId);
|
|
1715
|
-
|
|
2157
|
+
if (pane?.runtime === "acp") {
|
|
2158
|
+
this.acpRuntime.kill(paneId);
|
|
2159
|
+
} else {
|
|
2160
|
+
this.ptyManager.kill(paneId);
|
|
2161
|
+
}
|
|
1716
2162
|
if (pane?.isolation === "worktree") {
|
|
1717
2163
|
this.stopPaneGitService(paneId);
|
|
1718
2164
|
await this.worktreeManager.remove(paneId);
|
|
@@ -1724,7 +2170,11 @@ var WorkspaceManager = class {
|
|
|
1724
2170
|
restartPane(paneId, mode, sessionId) {
|
|
1725
2171
|
const existingState = this.panes.get(paneId);
|
|
1726
2172
|
if (!existingState) return;
|
|
1727
|
-
|
|
2173
|
+
if (existingState.runtime === "acp") {
|
|
2174
|
+
this.acpRuntime.kill(paneId);
|
|
2175
|
+
} else {
|
|
2176
|
+
this.ptyManager.kill(paneId);
|
|
2177
|
+
}
|
|
1728
2178
|
const resolvedSessionId = mode === "resume" ? sessionId || existingState.sessionId || existingState.meta.sessionId : void 0;
|
|
1729
2179
|
const config = {
|
|
1730
2180
|
id: paneId,
|
|
@@ -1765,13 +2215,36 @@ var WorkspaceManager = class {
|
|
|
1765
2215
|
return result;
|
|
1766
2216
|
}
|
|
1767
2217
|
writeToPane(paneId, data) {
|
|
2218
|
+
const pane = this.panes.get(paneId);
|
|
2219
|
+
if (!pane) return;
|
|
2220
|
+
if (pane.runtime === "acp") {
|
|
2221
|
+
this.acpRuntime.write(paneId, data);
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
1768
2224
|
this.ptyManager.write(paneId, data);
|
|
1769
2225
|
}
|
|
2226
|
+
sendConversationToPane(paneId, text) {
|
|
2227
|
+
const pane = this.panes.get(paneId);
|
|
2228
|
+
if (!pane) return Promise.resolve();
|
|
2229
|
+
if (pane.runtime === "acp") {
|
|
2230
|
+
return this.acpRuntime.sendPrompt(paneId, text);
|
|
2231
|
+
}
|
|
2232
|
+
this.ptyManager.write(paneId, text + "\r");
|
|
2233
|
+
return Promise.resolve();
|
|
2234
|
+
}
|
|
1770
2235
|
resizePane(paneId, cols, rows) {
|
|
2236
|
+
const pane = this.panes.get(paneId);
|
|
2237
|
+
if (!pane) return;
|
|
2238
|
+
if (pane.runtime === "acp") {
|
|
2239
|
+
this.acpRuntime.resize(paneId, cols, rows);
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
1771
2242
|
this.ptyManager.resize(paneId, cols, rows);
|
|
1772
2243
|
}
|
|
1773
2244
|
getScrollback(paneId) {
|
|
1774
|
-
|
|
2245
|
+
const pane = this.panes.get(paneId);
|
|
2246
|
+
if (!pane) return "";
|
|
2247
|
+
return pane.runtime === "acp" ? this.acpRuntime.getScrollback(paneId) : this.ptyManager.getScrollback(paneId);
|
|
1775
2248
|
}
|
|
1776
2249
|
// ─── Event Registration (multi-client safe) ────────────────
|
|
1777
2250
|
/**
|
|
@@ -1828,7 +2301,8 @@ var WorkspaceManager = class {
|
|
|
1828
2301
|
}
|
|
1829
2302
|
}
|
|
1830
2303
|
spawnPane(config, cols, rows) {
|
|
1831
|
-
const
|
|
2304
|
+
const runtime = this.resolveRuntime(config.agent);
|
|
2305
|
+
const pid = runtime === "acp" ? this.acpRuntime.spawn(config.id, config) : this.ptyManager.spawn(config.id, config, cols || 80, rows || 24);
|
|
1832
2306
|
const pane = {
|
|
1833
2307
|
id: config.id,
|
|
1834
2308
|
name: config.name,
|
|
@@ -1841,6 +2315,7 @@ var WorkspaceManager = class {
|
|
|
1841
2315
|
branch: config.branch,
|
|
1842
2316
|
worktreePath: config.worktreePath,
|
|
1843
2317
|
sessionId: config.sessionId,
|
|
2318
|
+
runtime,
|
|
1844
2319
|
status: "running",
|
|
1845
2320
|
pid,
|
|
1846
2321
|
meta: {},
|
|
@@ -1848,17 +2323,18 @@ var WorkspaceManager = class {
|
|
|
1848
2323
|
};
|
|
1849
2324
|
this.panes.set(config.id, pane);
|
|
1850
2325
|
this.emit("onPaneAdded", pane);
|
|
1851
|
-
this.
|
|
2326
|
+
const runtimeAdapter = runtime === "acp" ? this.acpRuntime : this.ptyManager;
|
|
2327
|
+
runtimeAdapter.onData(config.id, (data) => {
|
|
1852
2328
|
this.emit("onTerminalData", config.id, data);
|
|
1853
2329
|
});
|
|
1854
|
-
|
|
2330
|
+
runtimeAdapter.onStatus(config.id, (status) => {
|
|
1855
2331
|
const p = this.panes.get(config.id);
|
|
1856
2332
|
if (p) {
|
|
1857
2333
|
p.status = status;
|
|
1858
2334
|
this.emit("onPaneStatus", config.id, status);
|
|
1859
2335
|
}
|
|
1860
2336
|
});
|
|
1861
|
-
|
|
2337
|
+
runtimeAdapter.onMeta(config.id, (meta) => {
|
|
1862
2338
|
const p = this.panes.get(config.id);
|
|
1863
2339
|
if (p) {
|
|
1864
2340
|
p.meta = meta;
|
|
@@ -1869,11 +2345,21 @@ var WorkspaceManager = class {
|
|
|
1869
2345
|
this.emit("onPaneMeta", config.id, meta);
|
|
1870
2346
|
}
|
|
1871
2347
|
});
|
|
1872
|
-
|
|
2348
|
+
runtimeAdapter.onActivity(config.id, (activity) => {
|
|
1873
2349
|
this.emit("onPaneActivity", config.id, activity);
|
|
1874
2350
|
});
|
|
2351
|
+
if (runtime === "acp") {
|
|
2352
|
+
this.acpRuntime.onConversation(config.id, (event) => {
|
|
2353
|
+
this.emit("onConversationEvent", config.id, event);
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
1875
2356
|
return pane;
|
|
1876
2357
|
}
|
|
2358
|
+
resolveRuntime(agentType) {
|
|
2359
|
+
if (agentType === "__shell__") return "pty";
|
|
2360
|
+
const def = this.configManager.getAgentDefinition(agentType);
|
|
2361
|
+
return def?.transport || "pty";
|
|
2362
|
+
}
|
|
1877
2363
|
async startPaneGitService(paneId, worktreePath) {
|
|
1878
2364
|
const gitService = new GitService(worktreePath);
|
|
1879
2365
|
gitService.onDiffChange((result) => {
|
|
@@ -1891,21 +2377,25 @@ var WorkspaceManager = class {
|
|
|
1891
2377
|
}
|
|
1892
2378
|
}
|
|
1893
2379
|
persistPaneConfig(config) {
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
wsConfig
|
|
1897
|
-
|
|
1898
|
-
|
|
2380
|
+
this.serializedConfigWrite(() => {
|
|
2381
|
+
const wsConfig = this.configManager.loadWorkspaceConfig();
|
|
2382
|
+
if (wsConfig) {
|
|
2383
|
+
wsConfig.panes.push(config);
|
|
2384
|
+
this.configManager.saveWorkspaceConfig(wsConfig);
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
1899
2387
|
}
|
|
1900
2388
|
updatePaneConfigSessionId(paneId, sessionId) {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
paneConfig
|
|
1906
|
-
|
|
2389
|
+
this.serializedConfigWrite(() => {
|
|
2390
|
+
const wsConfig = this.configManager.loadWorkspaceConfig();
|
|
2391
|
+
if (wsConfig) {
|
|
2392
|
+
const paneConfig = wsConfig.panes.find((p) => p.id === paneId);
|
|
2393
|
+
if (paneConfig) {
|
|
2394
|
+
paneConfig.sessionId = sessionId;
|
|
2395
|
+
this.configManager.saveWorkspaceConfig(wsConfig);
|
|
2396
|
+
}
|
|
1907
2397
|
}
|
|
1908
|
-
}
|
|
2398
|
+
});
|
|
1909
2399
|
}
|
|
1910
2400
|
getSessionList(paneId) {
|
|
1911
2401
|
const sessions = [];
|
|
@@ -1943,14 +2433,30 @@ var WorkspaceManager = class {
|
|
|
1943
2433
|
return sessions;
|
|
1944
2434
|
}
|
|
1945
2435
|
removePaneFromConfig(paneId) {
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2436
|
+
this.serializedConfigWrite(() => {
|
|
2437
|
+
const wsConfig = this.configManager.loadWorkspaceConfig();
|
|
2438
|
+
if (wsConfig) {
|
|
2439
|
+
wsConfig.panes = wsConfig.panes.filter((p) => p.id !== paneId);
|
|
2440
|
+
this.configManager.saveWorkspaceConfig(wsConfig);
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Serialize config file writes to prevent race conditions
|
|
2446
|
+
* when multiple panes are created/closed simultaneously.
|
|
2447
|
+
*/
|
|
2448
|
+
serializedConfigWrite(fn) {
|
|
2449
|
+
this.configWriteLock = this.configWriteLock.then(() => {
|
|
2450
|
+
try {
|
|
2451
|
+
fn();
|
|
2452
|
+
} catch (err) {
|
|
2453
|
+
console.error("[WorkspaceManager] Config write failed:", err);
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
1951
2456
|
}
|
|
1952
2457
|
async shutdown() {
|
|
1953
2458
|
this.ptyManager.killAll();
|
|
2459
|
+
this.acpRuntime.killAll();
|
|
1954
2460
|
for (const [paneId] of this.perPaneGitServices) {
|
|
1955
2461
|
this.stopPaneGitService(paneId);
|
|
1956
2462
|
}
|
|
@@ -1958,8 +2464,8 @@ var WorkspaceManager = class {
|
|
|
1958
2464
|
};
|
|
1959
2465
|
|
|
1960
2466
|
// src/workspace/AgentsYamlWriter.ts
|
|
1961
|
-
import
|
|
1962
|
-
import
|
|
2467
|
+
import fs5 from "fs";
|
|
2468
|
+
import path6 from "path";
|
|
1963
2469
|
import yaml2 from "js-yaml";
|
|
1964
2470
|
var AgentsYamlWriter = class {
|
|
1965
2471
|
projectDir;
|
|
@@ -1984,8 +2490,8 @@ var AgentsYamlWriter = class {
|
|
|
1984
2490
|
this.writeFile(panes);
|
|
1985
2491
|
}
|
|
1986
2492
|
writeFile(panes) {
|
|
1987
|
-
const nexusDir =
|
|
1988
|
-
|
|
2493
|
+
const nexusDir = path6.join(this.projectDir, ".nexus");
|
|
2494
|
+
fs5.mkdirSync(nexusDir, { recursive: true });
|
|
1989
2495
|
const visible = panes.filter((p) => p.agent !== "__shell__");
|
|
1990
2496
|
const data = {
|
|
1991
2497
|
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1995,11 +2501,12 @@ var AgentsYamlWriter = class {
|
|
|
1995
2501
|
id: p.id,
|
|
1996
2502
|
name: p.name,
|
|
1997
2503
|
agent: p.agent,
|
|
2504
|
+
runtime: p.runtime,
|
|
1998
2505
|
pid: p.pid,
|
|
1999
2506
|
status: p.status,
|
|
2000
2507
|
isolation: p.isolation || "shared",
|
|
2001
2508
|
branch: p.branch || void 0,
|
|
2002
|
-
workdir: p.workdir ?
|
|
2509
|
+
workdir: p.workdir ? path6.resolve(basePath, p.workdir) : basePath,
|
|
2003
2510
|
task: p.task || void 0,
|
|
2004
2511
|
model: p.meta.model || void 0,
|
|
2005
2512
|
context_used_pct: p.meta.contextUsedPct ?? void 0,
|
|
@@ -2009,18 +2516,19 @@ var AgentsYamlWriter = class {
|
|
|
2009
2516
|
};
|
|
2010
2517
|
})
|
|
2011
2518
|
};
|
|
2012
|
-
const filePath =
|
|
2013
|
-
|
|
2519
|
+
const filePath = path6.join(nexusDir, "agents.yaml");
|
|
2520
|
+
fs5.writeFileSync(filePath, yaml2.dump(data, { lineWidth: -1 }));
|
|
2014
2521
|
}
|
|
2015
2522
|
};
|
|
2016
2523
|
|
|
2017
2524
|
// src/fs/FsWatcher.ts
|
|
2018
|
-
import
|
|
2019
|
-
import
|
|
2525
|
+
import fs6 from "fs";
|
|
2526
|
+
import path7 from "path";
|
|
2020
2527
|
import { watch as watch2 } from "chokidar";
|
|
2021
2528
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2022
2529
|
"node_modules",
|
|
2023
2530
|
".git",
|
|
2531
|
+
".nexus",
|
|
2024
2532
|
"dist",
|
|
2025
2533
|
".cache",
|
|
2026
2534
|
".turbo",
|
|
@@ -2047,7 +2555,7 @@ var FsWatcher = class {
|
|
|
2047
2555
|
this.notifyListeners();
|
|
2048
2556
|
this.watcher = watch2(this.projectDir, {
|
|
2049
2557
|
ignored: (filePath) => {
|
|
2050
|
-
const basename =
|
|
2558
|
+
const basename = path7.basename(filePath);
|
|
2051
2559
|
return IGNORED_DIRS.has(basename) || IGNORED_FILES.has(basename);
|
|
2052
2560
|
},
|
|
2053
2561
|
persistent: true,
|
|
@@ -2064,9 +2572,9 @@ var FsWatcher = class {
|
|
|
2064
2572
|
}, 300);
|
|
2065
2573
|
};
|
|
2066
2574
|
const emitFileChange = (eventType, filePath) => {
|
|
2067
|
-
const relativePath =
|
|
2575
|
+
const relativePath = path7.relative(this.projectDir, filePath);
|
|
2068
2576
|
if (!relativePath || relativePath.startsWith("..")) return;
|
|
2069
|
-
if (!/\.\w{1,10}$/.test(
|
|
2577
|
+
if (!/\.\w{1,10}$/.test(path7.basename(filePath))) return;
|
|
2070
2578
|
const now = Date.now();
|
|
2071
2579
|
const lastChange = this.recentChanges.get(relativePath);
|
|
2072
2580
|
if (lastChange && now - lastChange < 1e3) return;
|
|
@@ -2135,14 +2643,14 @@ var FsWatcher = class {
|
|
|
2135
2643
|
return parts.join("\n");
|
|
2136
2644
|
}
|
|
2137
2645
|
buildTree(dirPath, depth) {
|
|
2138
|
-
if (depth >
|
|
2646
|
+
if (depth > 5) return [];
|
|
2139
2647
|
try {
|
|
2140
|
-
const entries =
|
|
2648
|
+
const entries = fs6.readdirSync(dirPath, { withFileTypes: true });
|
|
2141
2649
|
const nodes = [];
|
|
2142
2650
|
for (const entry of entries) {
|
|
2143
2651
|
if (IGNORED_DIRS.has(entry.name) || IGNORED_FILES.has(entry.name)) continue;
|
|
2144
|
-
const fullPath =
|
|
2145
|
-
const relativePath =
|
|
2652
|
+
const fullPath = path7.join(dirPath, entry.name);
|
|
2653
|
+
const relativePath = path7.relative(this.projectDir, fullPath);
|
|
2146
2654
|
if (entry.isDirectory()) {
|
|
2147
2655
|
nodes.push({
|
|
2148
2656
|
name: entry.name,
|
|
@@ -2181,7 +2689,7 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
|
|
|
2181
2689
|
type: "workspace.state",
|
|
2182
2690
|
state
|
|
2183
2691
|
});
|
|
2184
|
-
const SCROLLBACK_CHUNK_SIZE =
|
|
2692
|
+
const SCROLLBACK_CHUNK_SIZE = 512 * 1024;
|
|
2185
2693
|
const replayScrollback = async () => {
|
|
2186
2694
|
for (const pane of state.panes) {
|
|
2187
2695
|
const scrollback = workspaceManager.getScrollback(pane.id);
|
|
@@ -2211,6 +2719,9 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
|
|
|
2211
2719
|
onTerminalData: (paneId, data) => {
|
|
2212
2720
|
send({ type: "terminal.output", paneId, data });
|
|
2213
2721
|
},
|
|
2722
|
+
onConversationEvent: (paneId, event) => {
|
|
2723
|
+
send({ type: "conversation.event", paneId, event });
|
|
2724
|
+
},
|
|
2214
2725
|
onPaneStatus: (paneId, status) => {
|
|
2215
2726
|
send({ type: "pane.status", paneId, status });
|
|
2216
2727
|
},
|
|
@@ -2248,10 +2759,21 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
|
|
|
2248
2759
|
}
|
|
2249
2760
|
switch (event.type) {
|
|
2250
2761
|
case "terminal.input":
|
|
2251
|
-
|
|
2762
|
+
try {
|
|
2763
|
+
workspaceManager.writeToPane(event.paneId, event.data);
|
|
2764
|
+
} catch {
|
|
2765
|
+
}
|
|
2252
2766
|
break;
|
|
2253
2767
|
case "terminal.resize":
|
|
2254
|
-
|
|
2768
|
+
try {
|
|
2769
|
+
workspaceManager.resizePane(event.paneId, event.cols, event.rows);
|
|
2770
|
+
} catch {
|
|
2771
|
+
}
|
|
2772
|
+
break;
|
|
2773
|
+
case "conversation.send":
|
|
2774
|
+
workspaceManager.sendConversationToPane(event.paneId, event.text).catch((err) => {
|
|
2775
|
+
console.error("conversation.send failed:", err);
|
|
2776
|
+
});
|
|
2255
2777
|
break;
|
|
2256
2778
|
case "pane.create":
|
|
2257
2779
|
workspaceManager.createPane(event.config).catch((err) => {
|
|
@@ -2372,8 +2894,8 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
|
|
|
2372
2894
|
}
|
|
2373
2895
|
|
|
2374
2896
|
// src/deps/DependencyAnalyzer.ts
|
|
2375
|
-
import
|
|
2376
|
-
import
|
|
2897
|
+
import fs7 from "fs";
|
|
2898
|
+
import path8 from "path";
|
|
2377
2899
|
var IMPORT_FROM_RE = /import\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
2378
2900
|
var EXPORT_FROM_RE = /export\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
2379
2901
|
var REQUIRE_RE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
@@ -2394,7 +2916,7 @@ var DependencyAnalyzer = class {
|
|
|
2394
2916
|
const files = this.collectFiles(this.projectDir);
|
|
2395
2917
|
const nodes = [];
|
|
2396
2918
|
for (const absPath of files) {
|
|
2397
|
-
const relPath =
|
|
2919
|
+
const relPath = path8.relative(this.projectDir, absPath);
|
|
2398
2920
|
const imports = this.extractImports(absPath, relPath);
|
|
2399
2921
|
nodes.push({ id: relPath, imports });
|
|
2400
2922
|
}
|
|
@@ -2405,18 +2927,18 @@ var DependencyAnalyzer = class {
|
|
|
2405
2927
|
const files = [];
|
|
2406
2928
|
let entries;
|
|
2407
2929
|
try {
|
|
2408
|
-
entries =
|
|
2930
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
2409
2931
|
} catch {
|
|
2410
2932
|
return files;
|
|
2411
2933
|
}
|
|
2412
2934
|
for (const entry of entries) {
|
|
2413
2935
|
if (entry.name.startsWith(".") && entry.name !== ".") continue;
|
|
2414
|
-
const fullPath =
|
|
2936
|
+
const fullPath = path8.join(dir, entry.name);
|
|
2415
2937
|
if (entry.isDirectory()) {
|
|
2416
2938
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
2417
2939
|
files.push(...this.collectFiles(fullPath, depth + 1));
|
|
2418
2940
|
} else if (entry.isFile()) {
|
|
2419
|
-
const ext =
|
|
2941
|
+
const ext = path8.extname(entry.name);
|
|
2420
2942
|
if (JS_TS_EXTENSIONS.has(ext)) {
|
|
2421
2943
|
files.push(fullPath);
|
|
2422
2944
|
}
|
|
@@ -2427,7 +2949,7 @@ var DependencyAnalyzer = class {
|
|
|
2427
2949
|
extractImports(absPath, relPath) {
|
|
2428
2950
|
let content;
|
|
2429
2951
|
try {
|
|
2430
|
-
content =
|
|
2952
|
+
content = fs7.readFileSync(absPath, "utf-8");
|
|
2431
2953
|
} catch {
|
|
2432
2954
|
return [];
|
|
2433
2955
|
}
|
|
@@ -2444,22 +2966,22 @@ var DependencyAnalyzer = class {
|
|
|
2444
2966
|
}
|
|
2445
2967
|
}
|
|
2446
2968
|
const imports = [];
|
|
2447
|
-
const fileDir =
|
|
2969
|
+
const fileDir = path8.dirname(absPath);
|
|
2448
2970
|
for (const spec of specifiers) {
|
|
2449
2971
|
const resolved = this.resolveSpecifier(fileDir, spec);
|
|
2450
2972
|
if (resolved) {
|
|
2451
|
-
const resolvedRel =
|
|
2973
|
+
const resolvedRel = path8.relative(this.projectDir, resolved);
|
|
2452
2974
|
imports.push(resolvedRel);
|
|
2453
2975
|
}
|
|
2454
2976
|
}
|
|
2455
2977
|
return imports;
|
|
2456
2978
|
}
|
|
2457
2979
|
resolveSpecifier(fromDir, specifier) {
|
|
2458
|
-
const base =
|
|
2980
|
+
const base = path8.resolve(fromDir, specifier);
|
|
2459
2981
|
for (const ext of RESOLVE_EXTENSIONS) {
|
|
2460
2982
|
const candidate = base + ext;
|
|
2461
2983
|
try {
|
|
2462
|
-
if (
|
|
2984
|
+
if (fs7.statSync(candidate).isFile()) {
|
|
2463
2985
|
return candidate;
|
|
2464
2986
|
}
|
|
2465
2987
|
} catch {
|
|
@@ -2470,8 +2992,8 @@ var DependencyAnalyzer = class {
|
|
|
2470
2992
|
};
|
|
2471
2993
|
|
|
2472
2994
|
// src/history/SessionRecorder.ts
|
|
2473
|
-
import
|
|
2474
|
-
import
|
|
2995
|
+
import fs8 from "fs";
|
|
2996
|
+
import path9 from "path";
|
|
2475
2997
|
import { execFile as execFile2 } from "child_process";
|
|
2476
2998
|
var HISTORY_DIR = ".nexus/history";
|
|
2477
2999
|
var SESSIONS_INDEX = "sessions.json";
|
|
@@ -2489,8 +3011,8 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2489
3011
|
constructor(projectDir, projectName, retentionDays = 30) {
|
|
2490
3012
|
this.projectDir = projectDir;
|
|
2491
3013
|
const sessionId = `s-${Date.now()}`;
|
|
2492
|
-
this.sessionDir =
|
|
2493
|
-
|
|
3014
|
+
this.sessionDir = path9.join(projectDir, HISTORY_DIR, sessionId);
|
|
3015
|
+
fs8.mkdirSync(this.sessionDir, { recursive: true });
|
|
2494
3016
|
this.retentionDays = retentionDays;
|
|
2495
3017
|
this.session = {
|
|
2496
3018
|
id: sessionId,
|
|
@@ -2630,47 +3152,47 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2630
3152
|
}
|
|
2631
3153
|
// ─── Query API ────────────────────────────────────────────
|
|
2632
3154
|
static listSessions(projectDir) {
|
|
2633
|
-
const indexPath =
|
|
2634
|
-
if (!
|
|
3155
|
+
const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
|
|
3156
|
+
if (!fs8.existsSync(indexPath)) return [];
|
|
2635
3157
|
try {
|
|
2636
|
-
const data = JSON.parse(
|
|
3158
|
+
const data = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
|
|
2637
3159
|
return Array.isArray(data) ? data : [];
|
|
2638
3160
|
} catch {
|
|
2639
3161
|
return [];
|
|
2640
3162
|
}
|
|
2641
3163
|
}
|
|
2642
3164
|
static getSession(projectDir, sessionId) {
|
|
2643
|
-
const sessionPath =
|
|
2644
|
-
if (!
|
|
3165
|
+
const sessionPath = path9.join(projectDir, HISTORY_DIR, sessionId, "session.json");
|
|
3166
|
+
if (!fs8.existsSync(sessionPath)) return null;
|
|
2645
3167
|
try {
|
|
2646
|
-
return JSON.parse(
|
|
3168
|
+
return JSON.parse(fs8.readFileSync(sessionPath, "utf-8"));
|
|
2647
3169
|
} catch {
|
|
2648
3170
|
return null;
|
|
2649
3171
|
}
|
|
2650
3172
|
}
|
|
2651
3173
|
static getTurn(projectDir, sessionId, turnId) {
|
|
2652
|
-
const turnPath =
|
|
2653
|
-
if (!
|
|
3174
|
+
const turnPath = path9.join(projectDir, HISTORY_DIR, sessionId, `${turnId}.json`);
|
|
3175
|
+
if (!fs8.existsSync(turnPath)) return null;
|
|
2654
3176
|
try {
|
|
2655
|
-
return JSON.parse(
|
|
3177
|
+
return JSON.parse(fs8.readFileSync(turnPath, "utf-8"));
|
|
2656
3178
|
} catch {
|
|
2657
3179
|
return null;
|
|
2658
3180
|
}
|
|
2659
3181
|
}
|
|
2660
3182
|
/** Delete a single session and its directory. Returns true if deleted. */
|
|
2661
3183
|
static deleteSession(projectDir, sessionId) {
|
|
2662
|
-
const sessionDir =
|
|
2663
|
-
if (
|
|
2664
|
-
|
|
3184
|
+
const sessionDir = path9.join(projectDir, HISTORY_DIR, sessionId);
|
|
3185
|
+
if (fs8.existsSync(sessionDir)) {
|
|
3186
|
+
fs8.rmSync(sessionDir, { recursive: true, force: true });
|
|
2665
3187
|
}
|
|
2666
|
-
const indexPath =
|
|
2667
|
-
if (
|
|
3188
|
+
const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
|
|
3189
|
+
if (fs8.existsSync(indexPath)) {
|
|
2668
3190
|
try {
|
|
2669
|
-
let sessions = JSON.parse(
|
|
3191
|
+
let sessions = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
|
|
2670
3192
|
const before = sessions.length;
|
|
2671
3193
|
sessions = sessions.filter((s) => s.id !== sessionId);
|
|
2672
3194
|
if (sessions.length < before) {
|
|
2673
|
-
|
|
3195
|
+
fs8.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
|
|
2674
3196
|
return true;
|
|
2675
3197
|
}
|
|
2676
3198
|
} catch {
|
|
@@ -2683,14 +3205,14 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2683
3205
|
const sessions = _SessionRecorder.listSessions(projectDir);
|
|
2684
3206
|
let count = 0;
|
|
2685
3207
|
for (const session of sessions) {
|
|
2686
|
-
const sessionDir =
|
|
2687
|
-
if (
|
|
2688
|
-
|
|
3208
|
+
const sessionDir = path9.join(projectDir, HISTORY_DIR, session.id);
|
|
3209
|
+
if (fs8.existsSync(sessionDir)) {
|
|
3210
|
+
fs8.rmSync(sessionDir, { recursive: true, force: true });
|
|
2689
3211
|
count++;
|
|
2690
3212
|
}
|
|
2691
3213
|
}
|
|
2692
|
-
const indexPath =
|
|
2693
|
-
|
|
3214
|
+
const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
|
|
3215
|
+
fs8.writeFileSync(indexPath, JSON.stringify([], null, 2));
|
|
2694
3216
|
return count;
|
|
2695
3217
|
}
|
|
2696
3218
|
/**
|
|
@@ -2698,11 +3220,11 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2698
3220
|
* Called automatically when saving a new session.
|
|
2699
3221
|
*/
|
|
2700
3222
|
static pruneOldSessions(projectDir, retentionDays) {
|
|
2701
|
-
const indexPath =
|
|
2702
|
-
if (!
|
|
3223
|
+
const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
|
|
3224
|
+
if (!fs8.existsSync(indexPath)) return 0;
|
|
2703
3225
|
let sessions;
|
|
2704
3226
|
try {
|
|
2705
|
-
sessions = JSON.parse(
|
|
3227
|
+
sessions = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
|
|
2706
3228
|
} catch {
|
|
2707
3229
|
return 0;
|
|
2708
3230
|
}
|
|
@@ -2718,12 +3240,12 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2718
3240
|
}
|
|
2719
3241
|
if (remove.length === 0) return 0;
|
|
2720
3242
|
for (const s of remove) {
|
|
2721
|
-
const sessionDir =
|
|
2722
|
-
if (
|
|
2723
|
-
|
|
3243
|
+
const sessionDir = path9.join(projectDir, HISTORY_DIR, s.id);
|
|
3244
|
+
if (fs8.existsSync(sessionDir)) {
|
|
3245
|
+
fs8.rmSync(sessionDir, { recursive: true, force: true });
|
|
2724
3246
|
}
|
|
2725
3247
|
}
|
|
2726
|
-
|
|
3248
|
+
fs8.writeFileSync(indexPath, JSON.stringify(keep, null, 2));
|
|
2727
3249
|
return remove.length;
|
|
2728
3250
|
}
|
|
2729
3251
|
// ─── Internal ─────────────────────────────────────────────
|
|
@@ -2830,19 +3352,19 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2830
3352
|
});
|
|
2831
3353
|
}
|
|
2832
3354
|
writeTurnFile(turn) {
|
|
2833
|
-
const turnPath =
|
|
2834
|
-
|
|
3355
|
+
const turnPath = path9.join(this.sessionDir, `${turn.id}.json`);
|
|
3356
|
+
fs8.writeFileSync(turnPath, JSON.stringify(turn));
|
|
2835
3357
|
}
|
|
2836
3358
|
writeSessionFile() {
|
|
2837
|
-
const sessionPath =
|
|
2838
|
-
|
|
3359
|
+
const sessionPath = path9.join(this.sessionDir, "session.json");
|
|
3360
|
+
fs8.writeFileSync(sessionPath, JSON.stringify(this.session, null, 2));
|
|
2839
3361
|
}
|
|
2840
3362
|
updateSessionsIndex() {
|
|
2841
|
-
const indexPath =
|
|
3363
|
+
const indexPath = path9.join(this.projectDir, HISTORY_DIR, SESSIONS_INDEX);
|
|
2842
3364
|
let sessions = [];
|
|
2843
|
-
if (
|
|
3365
|
+
if (fs8.existsSync(indexPath)) {
|
|
2844
3366
|
try {
|
|
2845
|
-
sessions = JSON.parse(
|
|
3367
|
+
sessions = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
|
|
2846
3368
|
} catch {
|
|
2847
3369
|
}
|
|
2848
3370
|
}
|
|
@@ -2856,7 +3378,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2856
3378
|
paneCount: this.session.panes.length,
|
|
2857
3379
|
totalDurationMs
|
|
2858
3380
|
});
|
|
2859
|
-
|
|
3381
|
+
fs8.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
|
|
2860
3382
|
const pruned = _SessionRecorder.pruneOldSessions(this.projectDir, this.retentionDays);
|
|
2861
3383
|
if (pruned > 0) {
|
|
2862
3384
|
console.log(`[SessionRecorder] Pruned ${pruned} old session(s)`);
|
|
@@ -2915,7 +3437,7 @@ var SessionDiscovery = class {
|
|
|
2915
3437
|
};
|
|
2916
3438
|
|
|
2917
3439
|
// src/index.ts
|
|
2918
|
-
var __dirname =
|
|
3440
|
+
var __dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
2919
3441
|
async function startServer(port, projectDir) {
|
|
2920
3442
|
const fastify = Fastify({ logger: false });
|
|
2921
3443
|
const configManager = new ConfigManager(projectDir);
|
|
@@ -3079,17 +3601,17 @@ async function startServer(port, projectDir) {
|
|
|
3079
3601
|
reply.code(400);
|
|
3080
3602
|
return { error: "Missing path parameter" };
|
|
3081
3603
|
}
|
|
3082
|
-
if (filePath.includes("..") ||
|
|
3604
|
+
if (filePath.includes("..") || path10.isAbsolute(filePath)) {
|
|
3083
3605
|
reply.code(403);
|
|
3084
3606
|
return { error: "Invalid path" };
|
|
3085
3607
|
}
|
|
3086
|
-
const fullPath =
|
|
3608
|
+
const fullPath = path10.resolve(projectDir, filePath);
|
|
3087
3609
|
if (!fullPath.startsWith(projectDir)) {
|
|
3088
3610
|
reply.code(403);
|
|
3089
3611
|
return { error: "Path traversal not allowed" };
|
|
3090
3612
|
}
|
|
3091
3613
|
try {
|
|
3092
|
-
const content =
|
|
3614
|
+
const content = fs9.readFileSync(fullPath, "utf-8");
|
|
3093
3615
|
return { content, path: filePath };
|
|
3094
3616
|
} catch {
|
|
3095
3617
|
reply.code(404);
|
|
@@ -3102,20 +3624,20 @@ async function startServer(port, projectDir) {
|
|
|
3102
3624
|
reply.code(400);
|
|
3103
3625
|
return { error: "Missing path parameter" };
|
|
3104
3626
|
}
|
|
3105
|
-
if (filePath.includes("..") ||
|
|
3627
|
+
if (filePath.includes("..") || path10.isAbsolute(filePath)) {
|
|
3106
3628
|
reply.code(403);
|
|
3107
3629
|
return { error: "Invalid path" };
|
|
3108
3630
|
}
|
|
3109
|
-
const fullPath =
|
|
3631
|
+
const fullPath = path10.resolve(projectDir, filePath);
|
|
3110
3632
|
if (!fullPath.startsWith(projectDir)) {
|
|
3111
3633
|
reply.code(403);
|
|
3112
3634
|
return { error: "Path traversal not allowed" };
|
|
3113
3635
|
}
|
|
3114
|
-
if (!
|
|
3636
|
+
if (!fs9.existsSync(fullPath)) {
|
|
3115
3637
|
reply.code(404);
|
|
3116
3638
|
return { error: "File not found" };
|
|
3117
3639
|
}
|
|
3118
|
-
const ext =
|
|
3640
|
+
const ext = path10.extname(fullPath).toLowerCase();
|
|
3119
3641
|
const mimeMap = {
|
|
3120
3642
|
".png": "image/png",
|
|
3121
3643
|
".jpg": "image/jpeg",
|
|
@@ -3129,14 +3651,14 @@ async function startServer(port, projectDir) {
|
|
|
3129
3651
|
".svg": "image/svg+xml"
|
|
3130
3652
|
};
|
|
3131
3653
|
const mime = mimeMap[ext] || "application/octet-stream";
|
|
3132
|
-
const stream =
|
|
3654
|
+
const stream = fs9.createReadStream(fullPath);
|
|
3133
3655
|
reply.type(mime);
|
|
3134
3656
|
return reply.send(stream);
|
|
3135
3657
|
});
|
|
3136
|
-
const notesPath =
|
|
3658
|
+
const notesPath = path10.join(projectDir, ".nexus", "notes.yaml");
|
|
3137
3659
|
fastify.get("/api/notes", async () => {
|
|
3138
3660
|
try {
|
|
3139
|
-
const raw =
|
|
3661
|
+
const raw = fs9.readFileSync(notesPath, "utf-8");
|
|
3140
3662
|
const data = yaml3.load(raw);
|
|
3141
3663
|
return { notes: data?.notes || [] };
|
|
3142
3664
|
} catch {
|
|
@@ -3146,20 +3668,20 @@ async function startServer(port, projectDir) {
|
|
|
3146
3668
|
fastify.put("/api/notes", async (request, reply) => {
|
|
3147
3669
|
try {
|
|
3148
3670
|
const { notes } = request.body;
|
|
3149
|
-
|
|
3150
|
-
|
|
3671
|
+
fs9.mkdirSync(path10.dirname(notesPath), { recursive: true });
|
|
3672
|
+
fs9.writeFileSync(notesPath, yaml3.dump({ notes }, { lineWidth: -1 }), "utf-8");
|
|
3151
3673
|
return { success: true };
|
|
3152
3674
|
} catch (err) {
|
|
3153
3675
|
reply.code(400);
|
|
3154
3676
|
return { error: "Failed to save notes" };
|
|
3155
3677
|
}
|
|
3156
3678
|
});
|
|
3157
|
-
const webDistPath =
|
|
3158
|
-
if (!
|
|
3679
|
+
const webDistPath = path10.resolve(__dirname, "../../web/dist");
|
|
3680
|
+
if (!fs9.existsSync(webDistPath)) {
|
|
3159
3681
|
console.warn(` [Warning] Frontend not found at ${webDistPath}`);
|
|
3160
3682
|
console.warn(` Run 'pnpm run build:web' to build the frontend, or use dev mode.`);
|
|
3161
3683
|
}
|
|
3162
|
-
if (
|
|
3684
|
+
if (fs9.existsSync(webDistPath)) {
|
|
3163
3685
|
await fastify.register(fastifyStatic, {
|
|
3164
3686
|
root: webDistPath,
|
|
3165
3687
|
prefix: "/"
|
|
@@ -3237,14 +3759,14 @@ function findProjectRoot(startDir) {
|
|
|
3237
3759
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
3238
3760
|
let dir = startDir;
|
|
3239
3761
|
let bestMatch = startDir;
|
|
3240
|
-
while (dir !==
|
|
3241
|
-
if (
|
|
3762
|
+
while (dir !== path11.dirname(dir)) {
|
|
3763
|
+
if (fs10.existsSync(path11.join(dir, "pnpm-workspace.yaml"))) {
|
|
3242
3764
|
return dir;
|
|
3243
3765
|
}
|
|
3244
|
-
if (
|
|
3766
|
+
if (fs10.existsSync(path11.join(dir, ".git")) || fs10.existsSync(path11.join(dir, ".nexus"))) {
|
|
3245
3767
|
bestMatch = dir;
|
|
3246
3768
|
}
|
|
3247
|
-
const parent =
|
|
3769
|
+
const parent = path11.dirname(dir);
|
|
3248
3770
|
if (home && parent === home && dir !== startDir) break;
|
|
3249
3771
|
dir = parent;
|
|
3250
3772
|
}
|
|
@@ -3256,15 +3778,15 @@ function findProjectRoot(startDir) {
|
|
|
3256
3778
|
}
|
|
3257
3779
|
function resolveProjectDir(dirArg) {
|
|
3258
3780
|
if (process.env.NEXUS_PROJECT_DIR) {
|
|
3259
|
-
return
|
|
3781
|
+
return path11.resolve(process.env.NEXUS_PROJECT_DIR);
|
|
3260
3782
|
}
|
|
3261
3783
|
if (dirArg) {
|
|
3262
|
-
const resolved =
|
|
3263
|
-
if (!
|
|
3784
|
+
const resolved = path11.resolve(dirArg);
|
|
3785
|
+
if (!fs10.existsSync(resolved)) {
|
|
3264
3786
|
console.error(`Error: directory does not exist: ${resolved}`);
|
|
3265
3787
|
process.exit(1);
|
|
3266
3788
|
}
|
|
3267
|
-
if (!
|
|
3789
|
+
if (!fs10.statSync(resolved).isDirectory()) {
|
|
3268
3790
|
console.error(`Error: not a directory: ${resolved}`);
|
|
3269
3791
|
process.exit(1);
|
|
3270
3792
|
}
|