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.
Files changed (59) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/cli.mjs +675 -153
  3. package/packages/server/dist/cli.mjs.map +1 -1
  4. package/packages/web/dist/assets/{_basePickBy-DQhOWiJk.js → _basePickBy-DXIN2jRI.js} +1 -1
  5. package/packages/web/dist/assets/{_baseUniq-DXTx6DDJ.js → _baseUniq-BDywcd6l.js} +1 -1
  6. package/packages/web/dist/assets/{arc-D98xlvrt.js → arc-DyeF6zm4.js} +1 -1
  7. package/packages/web/dist/assets/{architectureDiagram-2XIMDMQ5-BFC9xn2H.js → architectureDiagram-2XIMDMQ5-DJLWO4Pj.js} +1 -1
  8. package/packages/web/dist/assets/{blockDiagram-WCTKOSBZ-DJH8Soam.js → blockDiagram-WCTKOSBZ-BV82tfhi.js} +1 -1
  9. package/packages/web/dist/assets/{c4Diagram-IC4MRINW-w36rnsiL.js → c4Diagram-IC4MRINW-fu-JF9Ds.js} +1 -1
  10. package/packages/web/dist/assets/channel-DMMUT-ui.js +1 -0
  11. package/packages/web/dist/assets/{chunk-4BX2VUAB-SF13Eulk.js → chunk-4BX2VUAB-CviYIUxF.js} +1 -1
  12. package/packages/web/dist/assets/{chunk-55IACEB6-wMI9Klww.js → chunk-55IACEB6-BPPHvGS8.js} +1 -1
  13. package/packages/web/dist/assets/{chunk-FMBD7UC4-WwcC59gR.js → chunk-FMBD7UC4-CUHRhjBX.js} +1 -1
  14. package/packages/web/dist/assets/{chunk-JSJVCQXG-DvXmNU5-.js → chunk-JSJVCQXG-C4BN-QRR.js} +1 -1
  15. package/packages/web/dist/assets/{chunk-KX2RTZJC-BdEEznCp.js → chunk-KX2RTZJC-DbvSNohi.js} +1 -1
  16. package/packages/web/dist/assets/{chunk-NQ4KR5QH-BwNAeHYs.js → chunk-NQ4KR5QH-Dmi6UmQw.js} +1 -1
  17. package/packages/web/dist/assets/{chunk-QZHKN3VN-C2xATrOG.js → chunk-QZHKN3VN-cq0CYVai.js} +1 -1
  18. package/packages/web/dist/assets/{chunk-WL4C6EOR-CEAL3q8O.js → chunk-WL4C6EOR-DU7g3-p8.js} +1 -1
  19. package/packages/web/dist/assets/classDiagram-VBA2DB6C-VAN2n8PZ.js +1 -0
  20. package/packages/web/dist/assets/classDiagram-v2-RAHNMMFH-VAN2n8PZ.js +1 -0
  21. package/packages/web/dist/assets/clone-CvILe-_6.js +1 -0
  22. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DMB9iF_s.js → cose-bilkent-S5V4N54A-DcB6mhJx.js} +1 -1
  23. package/packages/web/dist/assets/{dagre-KLK3FWXG-B3CBp1UR.js → dagre-KLK3FWXG-CKsicAAq.js} +1 -1
  24. package/packages/web/dist/assets/{diagram-E7M64L7V-BP3gXbyt.js → diagram-E7M64L7V-DGe3_R9s.js} +1 -1
  25. package/packages/web/dist/assets/{diagram-IFDJBPK2-DiWKPyS1.js → diagram-IFDJBPK2-BZq50qti.js} +1 -1
  26. package/packages/web/dist/assets/{diagram-P4PSJMXO-BRwIWc-T.js → diagram-P4PSJMXO-nwlLLk4m.js} +1 -1
  27. package/packages/web/dist/assets/{erDiagram-INFDFZHY-CwNQ0bqL.js → erDiagram-INFDFZHY-BMh-qsJC.js} +1 -1
  28. package/packages/web/dist/assets/{flowDiagram-PKNHOUZH-CGwKqUfG.js → flowDiagram-PKNHOUZH-DR8q3sxn.js} +1 -1
  29. package/packages/web/dist/assets/{ganttDiagram-A5KZAMGK-CwfwEk-O.js → ganttDiagram-A5KZAMGK-B27SJapO.js} +1 -1
  30. package/packages/web/dist/assets/{gitGraphDiagram-K3NZZRJ6-BU-TCNoX.js → gitGraphDiagram-K3NZZRJ6-q-tTdScO.js} +1 -1
  31. package/packages/web/dist/assets/{graph-BpGuAB34.js → graph-CMugmIWl.js} +1 -1
  32. package/packages/web/dist/assets/index-Diaj_XhW.js +954 -0
  33. package/packages/web/dist/assets/index-Dmv7dA85.css +32 -0
  34. package/packages/web/dist/assets/{infoDiagram-LFFYTUFH-OgqnEPLD.js → infoDiagram-LFFYTUFH-CK_zFXvT.js} +1 -1
  35. package/packages/web/dist/assets/{ishikawaDiagram-PHBUUO56-C7q1YvZf.js → ishikawaDiagram-PHBUUO56-BltT2ON5.js} +1 -1
  36. package/packages/web/dist/assets/{journeyDiagram-4ABVD52K-BFTnwlYT.js → journeyDiagram-4ABVD52K-Dy5sDS4Z.js} +1 -1
  37. package/packages/web/dist/assets/{kanban-definition-K7BYSVSG-BPNSFbTq.js → kanban-definition-K7BYSVSG-D5Ls_L8y.js} +1 -1
  38. package/packages/web/dist/assets/{layout-DiHSY-T6.js → layout-DmCA190Q.js} +1 -1
  39. package/packages/web/dist/assets/{linear-DcbEPsvj.js → linear-Xq2b2hkZ.js} +1 -1
  40. package/packages/web/dist/assets/{mindmap-definition-YRQLILUH-CalG0npn.js → mindmap-definition-YRQLILUH-Bb9IeFFk.js} +1 -1
  41. package/packages/web/dist/assets/{pieDiagram-SKSYHLDU-D1PyVe3u.js → pieDiagram-SKSYHLDU-voM4tR2p.js} +1 -1
  42. package/packages/web/dist/assets/{quadrantDiagram-337W2JSQ-bTKx0-2j.js → quadrantDiagram-337W2JSQ-CWsXyyMa.js} +1 -1
  43. package/packages/web/dist/assets/{requirementDiagram-Z7DCOOCP-DHyIVcFu.js → requirementDiagram-Z7DCOOCP-CsOwpcMM.js} +1 -1
  44. package/packages/web/dist/assets/{sankeyDiagram-WA2Y5GQK-CifZqZcJ.js → sankeyDiagram-WA2Y5GQK-B6cIdL6Z.js} +1 -1
  45. package/packages/web/dist/assets/{sequenceDiagram-2WXFIKYE-DK8qO73H.js → sequenceDiagram-2WXFIKYE-xbTir98k.js} +1 -1
  46. package/packages/web/dist/assets/{stateDiagram-RAJIS63D-BoK_s4xU.js → stateDiagram-RAJIS63D-BYDksMZW.js} +1 -1
  47. package/packages/web/dist/assets/stateDiagram-v2-FVOUBMTO-B4eba8Bu.js +1 -0
  48. package/packages/web/dist/assets/{timeline-definition-YZTLITO2-GwB8m3Pz.js → timeline-definition-YZTLITO2-D6_c0hm9.js} +1 -1
  49. package/packages/web/dist/assets/{treemap-KZPCXAKY-RvN0JaqD.js → treemap-KZPCXAKY-D0Xw90T6.js} +1 -1
  50. package/packages/web/dist/assets/{vennDiagram-LZ73GAT5-OnlpPkDW.js → vennDiagram-LZ73GAT5-D_T_iazZ.js} +1 -1
  51. package/packages/web/dist/assets/{xychartDiagram-JWTSCODW-Dv-KEW3d.js → xychartDiagram-JWTSCODW-CBckvVCD.js} +1 -1
  52. package/packages/web/dist/index.html +2 -2
  53. package/packages/web/dist/assets/channel-s9cXVaHr.js +0 -1
  54. package/packages/web/dist/assets/classDiagram-VBA2DB6C-quXqjdGh.js +0 -1
  55. package/packages/web/dist/assets/classDiagram-v2-RAHNMMFH-quXqjdGh.js +0 -1
  56. package/packages/web/dist/assets/clone-BwNC5Rz2.js +0 -1
  57. package/packages/web/dist/assets/index-BQZEjwBD.js +0 -890
  58. package/packages/web/dist/assets/index-SBtQaWyn.css +0 -32
  59. 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 path10 from "path";
274
- import fs9 from "fs";
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 path9 from "path";
282
- import fs8 from "fs";
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 ActivityParser = class {
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/git/WorktreeManager.ts
1108
- import path3 from "path";
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 = path3.join(this.projectDir, ".nexus", "worktrees", paneId);
1128
- if (fs3.existsSync(worktreePath)) {
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
- fs3.mkdirSync(path3.dirname(worktreePath), { recursive: true });
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 (fs3.existsSync(worktreePath)) {
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
- fs3.mkdirSync(path3.dirname(worktreePath), { recursive: true });
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
- fs3.rmSync(wtPath, { recursive: true, force: true });
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 path4 from "path";
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 = path4.join(this.projectDir, ".git");
1804
+ const gitDir = path5.join(this.projectDir, ".git");
1387
1805
  this.gitWatcher = watch([
1388
- path4.join(gitDir, "index"),
1389
- path4.join(gitDir, "HEAD"),
1390
- path4.join(gitDir, "refs")
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 = path4.basename(filePath);
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 this.getDiffs();
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 = path4.join(this.projectDir, file);
1451
- const fs10 = await import("fs");
1452
- if (fs10.existsSync(fullPath)) {
1453
- fs10.unlinkSync(fullPath);
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 content = await this.git.show([`:${diff.file}`]).catch(() => null);
1541
- if (content) {
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 +1 @@
1545
- +${content}`;
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
- this.ptyManager.kill(paneId);
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
- this.ptyManager.kill(paneId);
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
- return this.ptyManager.getScrollback(paneId);
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 pid = this.ptyManager.spawn(config.id, config, cols || 80, rows || 24);
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.ptyManager.onData(config.id, (data) => {
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
- this.ptyManager.onStatus(config.id, (status) => {
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
- this.ptyManager.onMeta(config.id, (meta) => {
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
- this.ptyManager.onActivity(config.id, (activity) => {
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
- const wsConfig = this.configManager.loadWorkspaceConfig();
1895
- if (wsConfig) {
1896
- wsConfig.panes.push(config);
1897
- this.configManager.saveWorkspaceConfig(wsConfig);
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
- const wsConfig = this.configManager.loadWorkspaceConfig();
1902
- if (wsConfig) {
1903
- const paneConfig = wsConfig.panes.find((p) => p.id === paneId);
1904
- if (paneConfig) {
1905
- paneConfig.sessionId = sessionId;
1906
- this.configManager.saveWorkspaceConfig(wsConfig);
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
- const wsConfig = this.configManager.loadWorkspaceConfig();
1947
- if (wsConfig) {
1948
- wsConfig.panes = wsConfig.panes.filter((p) => p.id !== paneId);
1949
- this.configManager.saveWorkspaceConfig(wsConfig);
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 fs4 from "fs";
1962
- import path5 from "path";
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 = path5.join(this.projectDir, ".nexus");
1988
- fs4.mkdirSync(nexusDir, { recursive: true });
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 ? path5.resolve(basePath, p.workdir) : basePath,
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 = path5.join(nexusDir, "agents.yaml");
2013
- fs4.writeFileSync(filePath, yaml2.dump(data, { lineWidth: -1 }));
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 fs5 from "fs";
2019
- import path6 from "path";
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 = path6.basename(filePath);
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 = path6.relative(this.projectDir, filePath);
2575
+ const relativePath = path7.relative(this.projectDir, filePath);
2068
2576
  if (!relativePath || relativePath.startsWith("..")) return;
2069
- if (!/\.\w{1,10}$/.test(path6.basename(filePath))) return;
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 > 8) return [];
2646
+ if (depth > 5) return [];
2139
2647
  try {
2140
- const entries = fs5.readdirSync(dirPath, { withFileTypes: true });
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 = path6.join(dirPath, entry.name);
2145
- const relativePath = path6.relative(this.projectDir, fullPath);
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 = 64 * 1024;
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
- workspaceManager.writeToPane(event.paneId, event.data);
2762
+ try {
2763
+ workspaceManager.writeToPane(event.paneId, event.data);
2764
+ } catch {
2765
+ }
2252
2766
  break;
2253
2767
  case "terminal.resize":
2254
- workspaceManager.resizePane(event.paneId, event.cols, event.rows);
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 fs6 from "fs";
2376
- import path7 from "path";
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 = path7.relative(this.projectDir, absPath);
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 = fs6.readdirSync(dir, { withFileTypes: true });
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 = path7.join(dir, entry.name);
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 = path7.extname(entry.name);
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 = fs6.readFileSync(absPath, "utf-8");
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 = path7.dirname(absPath);
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 = path7.relative(this.projectDir, resolved);
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 = path7.resolve(fromDir, specifier);
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 (fs6.statSync(candidate).isFile()) {
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 fs7 from "fs";
2474
- import path8 from "path";
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 = path8.join(projectDir, HISTORY_DIR, sessionId);
2493
- fs7.mkdirSync(this.sessionDir, { recursive: true });
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 = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2634
- if (!fs7.existsSync(indexPath)) return [];
3155
+ const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
3156
+ if (!fs8.existsSync(indexPath)) return [];
2635
3157
  try {
2636
- const data = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
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 = path8.join(projectDir, HISTORY_DIR, sessionId, "session.json");
2644
- if (!fs7.existsSync(sessionPath)) return null;
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(fs7.readFileSync(sessionPath, "utf-8"));
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 = path8.join(projectDir, HISTORY_DIR, sessionId, `${turnId}.json`);
2653
- if (!fs7.existsSync(turnPath)) return null;
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(fs7.readFileSync(turnPath, "utf-8"));
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 = path8.join(projectDir, HISTORY_DIR, sessionId);
2663
- if (fs7.existsSync(sessionDir)) {
2664
- fs7.rmSync(sessionDir, { recursive: true, force: true });
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 = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2667
- if (fs7.existsSync(indexPath)) {
3188
+ const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
3189
+ if (fs8.existsSync(indexPath)) {
2668
3190
  try {
2669
- let sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
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
- fs7.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
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 = path8.join(projectDir, HISTORY_DIR, session.id);
2687
- if (fs7.existsSync(sessionDir)) {
2688
- fs7.rmSync(sessionDir, { recursive: true, force: true });
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 = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2693
- fs7.writeFileSync(indexPath, JSON.stringify([], null, 2));
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 = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2702
- if (!fs7.existsSync(indexPath)) return 0;
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(fs7.readFileSync(indexPath, "utf-8"));
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 = path8.join(projectDir, HISTORY_DIR, s.id);
2722
- if (fs7.existsSync(sessionDir)) {
2723
- fs7.rmSync(sessionDir, { recursive: true, force: true });
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
- fs7.writeFileSync(indexPath, JSON.stringify(keep, null, 2));
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 = path8.join(this.sessionDir, `${turn.id}.json`);
2834
- fs7.writeFileSync(turnPath, JSON.stringify(turn));
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 = path8.join(this.sessionDir, "session.json");
2838
- fs7.writeFileSync(sessionPath, JSON.stringify(this.session, null, 2));
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 = path8.join(this.projectDir, HISTORY_DIR, SESSIONS_INDEX);
3363
+ const indexPath = path9.join(this.projectDir, HISTORY_DIR, SESSIONS_INDEX);
2842
3364
  let sessions = [];
2843
- if (fs7.existsSync(indexPath)) {
3365
+ if (fs8.existsSync(indexPath)) {
2844
3366
  try {
2845
- sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
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
- fs7.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
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 = path9.dirname(fileURLToPath(import.meta.url));
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("..") || path9.isAbsolute(filePath)) {
3604
+ if (filePath.includes("..") || path10.isAbsolute(filePath)) {
3083
3605
  reply.code(403);
3084
3606
  return { error: "Invalid path" };
3085
3607
  }
3086
- const fullPath = path9.resolve(projectDir, filePath);
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 = fs8.readFileSync(fullPath, "utf-8");
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("..") || path9.isAbsolute(filePath)) {
3627
+ if (filePath.includes("..") || path10.isAbsolute(filePath)) {
3106
3628
  reply.code(403);
3107
3629
  return { error: "Invalid path" };
3108
3630
  }
3109
- const fullPath = path9.resolve(projectDir, filePath);
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 (!fs8.existsSync(fullPath)) {
3636
+ if (!fs9.existsSync(fullPath)) {
3115
3637
  reply.code(404);
3116
3638
  return { error: "File not found" };
3117
3639
  }
3118
- const ext = path9.extname(fullPath).toLowerCase();
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 = fs8.createReadStream(fullPath);
3654
+ const stream = fs9.createReadStream(fullPath);
3133
3655
  reply.type(mime);
3134
3656
  return reply.send(stream);
3135
3657
  });
3136
- const notesPath = path9.join(projectDir, ".nexus", "notes.yaml");
3658
+ const notesPath = path10.join(projectDir, ".nexus", "notes.yaml");
3137
3659
  fastify.get("/api/notes", async () => {
3138
3660
  try {
3139
- const raw = fs8.readFileSync(notesPath, "utf-8");
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
- fs8.mkdirSync(path9.dirname(notesPath), { recursive: true });
3150
- fs8.writeFileSync(notesPath, yaml3.dump({ notes }, { lineWidth: -1 }), "utf-8");
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 = path9.resolve(__dirname, "../../web/dist");
3158
- if (!fs8.existsSync(webDistPath)) {
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 (fs8.existsSync(webDistPath)) {
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 !== path10.dirname(dir)) {
3241
- if (fs9.existsSync(path10.join(dir, "pnpm-workspace.yaml"))) {
3762
+ while (dir !== path11.dirname(dir)) {
3763
+ if (fs10.existsSync(path11.join(dir, "pnpm-workspace.yaml"))) {
3242
3764
  return dir;
3243
3765
  }
3244
- if (fs9.existsSync(path10.join(dir, ".git")) || fs9.existsSync(path10.join(dir, ".nexus"))) {
3766
+ if (fs10.existsSync(path11.join(dir, ".git")) || fs10.existsSync(path11.join(dir, ".nexus"))) {
3245
3767
  bestMatch = dir;
3246
3768
  }
3247
- const parent = path10.dirname(dir);
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 path10.resolve(process.env.NEXUS_PROJECT_DIR);
3781
+ return path11.resolve(process.env.NEXUS_PROJECT_DIR);
3260
3782
  }
3261
3783
  if (dirArg) {
3262
- const resolved = path10.resolve(dirArg);
3263
- if (!fs9.existsSync(resolved)) {
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 (!fs9.statSync(resolved).isDirectory()) {
3789
+ if (!fs10.statSync(resolved).isDirectory()) {
3268
3790
  console.error(`Error: not a directory: ${resolved}`);
3269
3791
  process.exit(1);
3270
3792
  }