mexus-cli 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/cli.mjs +632 -149
  3. package/packages/server/dist/cli.mjs.map +1 -1
  4. package/packages/web/dist/assets/{_basePickBy-DQhOWiJk.js → _basePickBy-Co7zHyMx.js} +1 -1
  5. package/packages/web/dist/assets/{_baseUniq-DXTx6DDJ.js → _baseUniq-Dxx1kUxk.js} +1 -1
  6. package/packages/web/dist/assets/{arc-D98xlvrt.js → arc-BvdbVAwY.js} +1 -1
  7. package/packages/web/dist/assets/{architectureDiagram-2XIMDMQ5-BFC9xn2H.js → architectureDiagram-2XIMDMQ5-NJEtmrih.js} +1 -1
  8. package/packages/web/dist/assets/{blockDiagram-WCTKOSBZ-DJH8Soam.js → blockDiagram-WCTKOSBZ-NMdKrHRR.js} +1 -1
  9. package/packages/web/dist/assets/{c4Diagram-IC4MRINW-w36rnsiL.js → c4Diagram-IC4MRINW-Cid8liHK.js} +1 -1
  10. package/packages/web/dist/assets/channel-ggzcU6fx.js +1 -0
  11. package/packages/web/dist/assets/{chunk-4BX2VUAB-SF13Eulk.js → chunk-4BX2VUAB-C0Cpcxjt.js} +1 -1
  12. package/packages/web/dist/assets/{chunk-55IACEB6-wMI9Klww.js → chunk-55IACEB6-uCQWWVUa.js} +1 -1
  13. package/packages/web/dist/assets/{chunk-FMBD7UC4-WwcC59gR.js → chunk-FMBD7UC4-j69k_AXR.js} +1 -1
  14. package/packages/web/dist/assets/{chunk-JSJVCQXG-DvXmNU5-.js → chunk-JSJVCQXG-BRb3U-tF.js} +1 -1
  15. package/packages/web/dist/assets/{chunk-KX2RTZJC-BdEEznCp.js → chunk-KX2RTZJC-CMLyC_k7.js} +1 -1
  16. package/packages/web/dist/assets/{chunk-NQ4KR5QH-BwNAeHYs.js → chunk-NQ4KR5QH-DUQxdvVp.js} +1 -1
  17. package/packages/web/dist/assets/{chunk-QZHKN3VN-C2xATrOG.js → chunk-QZHKN3VN-I_0dQXDN.js} +1 -1
  18. package/packages/web/dist/assets/{chunk-WL4C6EOR-CEAL3q8O.js → chunk-WL4C6EOR-BR8lxAey.js} +1 -1
  19. package/packages/web/dist/assets/classDiagram-VBA2DB6C-qeSRjRBc.js +1 -0
  20. package/packages/web/dist/assets/classDiagram-v2-RAHNMMFH-qeSRjRBc.js +1 -0
  21. package/packages/web/dist/assets/clone-CGAMRvPs.js +1 -0
  22. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DMB9iF_s.js → cose-bilkent-S5V4N54A-BF1cADHY.js} +1 -1
  23. package/packages/web/dist/assets/{dagre-KLK3FWXG-B3CBp1UR.js → dagre-KLK3FWXG-DDt0p4vG.js} +1 -1
  24. package/packages/web/dist/assets/{diagram-E7M64L7V-BP3gXbyt.js → diagram-E7M64L7V-CTkR6JTl.js} +1 -1
  25. package/packages/web/dist/assets/{diagram-IFDJBPK2-DiWKPyS1.js → diagram-IFDJBPK2-DKPHzwe0.js} +1 -1
  26. package/packages/web/dist/assets/{diagram-P4PSJMXO-BRwIWc-T.js → diagram-P4PSJMXO-k5sUOXg6.js} +1 -1
  27. package/packages/web/dist/assets/{erDiagram-INFDFZHY-CwNQ0bqL.js → erDiagram-INFDFZHY-HQ6gUchT.js} +1 -1
  28. package/packages/web/dist/assets/{flowDiagram-PKNHOUZH-CGwKqUfG.js → flowDiagram-PKNHOUZH-zQ6z0FfA.js} +1 -1
  29. package/packages/web/dist/assets/{ganttDiagram-A5KZAMGK-CwfwEk-O.js → ganttDiagram-A5KZAMGK-7OanXR1G.js} +1 -1
  30. package/packages/web/dist/assets/{gitGraphDiagram-K3NZZRJ6-BU-TCNoX.js → gitGraphDiagram-K3NZZRJ6-CWHj7ZAf.js} +1 -1
  31. package/packages/web/dist/assets/{graph-BpGuAB34.js → graph-DGZqBZjC.js} +1 -1
  32. package/packages/web/dist/assets/index-DWMolj1f.css +32 -0
  33. package/packages/web/dist/assets/{index-BQZEjwBD.js → index-ORmXz3Zu.js} +256 -204
  34. package/packages/web/dist/assets/{infoDiagram-LFFYTUFH-OgqnEPLD.js → infoDiagram-LFFYTUFH-yRz9H7FV.js} +1 -1
  35. package/packages/web/dist/assets/{ishikawaDiagram-PHBUUO56-C7q1YvZf.js → ishikawaDiagram-PHBUUO56-D9KVAEH1.js} +1 -1
  36. package/packages/web/dist/assets/{journeyDiagram-4ABVD52K-BFTnwlYT.js → journeyDiagram-4ABVD52K-xYc2g_2E.js} +1 -1
  37. package/packages/web/dist/assets/{kanban-definition-K7BYSVSG-BPNSFbTq.js → kanban-definition-K7BYSVSG-C9ctSGeG.js} +1 -1
  38. package/packages/web/dist/assets/{layout-DiHSY-T6.js → layout-dR6Z9QBB.js} +1 -1
  39. package/packages/web/dist/assets/{linear-DcbEPsvj.js → linear-C41sKDUx.js} +1 -1
  40. package/packages/web/dist/assets/{mindmap-definition-YRQLILUH-CalG0npn.js → mindmap-definition-YRQLILUH-DnEvOt89.js} +1 -1
  41. package/packages/web/dist/assets/{pieDiagram-SKSYHLDU-D1PyVe3u.js → pieDiagram-SKSYHLDU-Dwygi596.js} +1 -1
  42. package/packages/web/dist/assets/{quadrantDiagram-337W2JSQ-bTKx0-2j.js → quadrantDiagram-337W2JSQ-CGsDpSZv.js} +1 -1
  43. package/packages/web/dist/assets/{requirementDiagram-Z7DCOOCP-DHyIVcFu.js → requirementDiagram-Z7DCOOCP-DrFNwpYL.js} +1 -1
  44. package/packages/web/dist/assets/{sankeyDiagram-WA2Y5GQK-CifZqZcJ.js → sankeyDiagram-WA2Y5GQK-CKWlgXXF.js} +1 -1
  45. package/packages/web/dist/assets/{sequenceDiagram-2WXFIKYE-DK8qO73H.js → sequenceDiagram-2WXFIKYE-DpbgNxFF.js} +1 -1
  46. package/packages/web/dist/assets/{stateDiagram-RAJIS63D-BoK_s4xU.js → stateDiagram-RAJIS63D-UQV1zJr3.js} +1 -1
  47. package/packages/web/dist/assets/stateDiagram-v2-FVOUBMTO-CmZylZ6s.js +1 -0
  48. package/packages/web/dist/assets/{timeline-definition-YZTLITO2-GwB8m3Pz.js → timeline-definition-YZTLITO2-CBNnAMDu.js} +1 -1
  49. package/packages/web/dist/assets/{treemap-KZPCXAKY-RvN0JaqD.js → treemap-KZPCXAKY-DQg0JG5Z.js} +1 -1
  50. package/packages/web/dist/assets/{vennDiagram-LZ73GAT5-OnlpPkDW.js → vennDiagram-LZ73GAT5-6EhbiWUV.js} +1 -1
  51. package/packages/web/dist/assets/{xychartDiagram-JWTSCODW-Dv-KEW3d.js → xychartDiagram-JWTSCODW-DrTSQ6rp.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-SBtQaWyn.css +0 -32
  58. 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,7 +891,19 @@ 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
  });
@@ -1049,6 +1076,10 @@ var PtyManager = class {
1049
1076
  entry.shellDetector?.dispose();
1050
1077
  entry.agentDetector?.dispose();
1051
1078
  entry.parser.reset();
1079
+ entry.onDataCallbacks.length = 0;
1080
+ entry.onStatusCallbacks.length = 0;
1081
+ entry.onMetaCallbacks.length = 0;
1082
+ entry.onActivityCallbacks.length = 0;
1052
1083
  try {
1053
1084
  entry.pty.kill();
1054
1085
  } catch {
@@ -1104,9 +1135,368 @@ var PtyManager = class {
1104
1135
  }
1105
1136
  };
1106
1137
 
1107
- // src/git/WorktreeManager.ts
1108
- import path3 from "path";
1138
+ // src/runtime/AcpRuntime.ts
1139
+ import { spawn as spawn2 } from "child_process";
1109
1140
  import fs3 from "fs";
1141
+ import os3 from "os";
1142
+ import path3 from "path";
1143
+ var MAX_SCROLLBACK_BYTES2 = 512 * 1024;
1144
+ function resolveAgentEnv(agentDef) {
1145
+ const env = {};
1146
+ for (const [key, value] of Object.entries(process.env)) {
1147
+ if (value !== void 0 && !key.startsWith("CLAUDE") && key !== "CLAUDECODE") {
1148
+ env[key] = value;
1149
+ }
1150
+ }
1151
+ if (agentDef?.env) {
1152
+ const blocked = /* @__PURE__ */ new Set([
1153
+ "PATH",
1154
+ "LD_PRELOAD",
1155
+ "LD_LIBRARY_PATH",
1156
+ "DYLD_INSERT_LIBRARIES",
1157
+ "DYLD_LIBRARY_PATH",
1158
+ "DYLD_FRAMEWORK_PATH"
1159
+ ]);
1160
+ for (const [key, value] of Object.entries(agentDef.env)) {
1161
+ if (blocked.has(key)) continue;
1162
+ env[key] = value.replace(/\$\{(\w+)\}/g, (_, name) => process.env[name] || "");
1163
+ }
1164
+ }
1165
+ if (!env.PATH) {
1166
+ env.PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
1167
+ if (process.platform === "darwin") {
1168
+ env.PATH = `/opt/homebrew/bin:/opt/homebrew/sbin:${env.PATH}`;
1169
+ }
1170
+ }
1171
+ return env;
1172
+ }
1173
+ var AcpRuntime = class {
1174
+ entries = /* @__PURE__ */ new Map();
1175
+ configManager;
1176
+ constructor(configManager) {
1177
+ this.configManager = configManager;
1178
+ }
1179
+ spawn(paneId, config) {
1180
+ if (this.entries.has(paneId)) {
1181
+ this.kill(paneId);
1182
+ }
1183
+ const projectDir = this.configManager.getProjectDir();
1184
+ const basePath = config.isolation === "worktree" && config.worktreePath ? config.worktreePath : projectDir;
1185
+ let cwd = config.workdir ? path3.resolve(basePath, config.workdir) : basePath;
1186
+ if (!fs3.existsSync(cwd)) {
1187
+ cwd = fs3.existsSync(projectDir) ? projectDir : os3.homedir();
1188
+ }
1189
+ const agentDef = this.configManager.getAgentDefinition(config.agent);
1190
+ if (!agentDef) {
1191
+ throw new Error(`Missing agent definition for ${config.agent}`);
1192
+ }
1193
+ const env = resolveAgentEnv(agentDef);
1194
+ const proc = spawn2(agentDef.bin, ["acp"], {
1195
+ cwd,
1196
+ env,
1197
+ stdio: ["pipe", "pipe", "pipe"]
1198
+ });
1199
+ const entry = {
1200
+ proc,
1201
+ config,
1202
+ status: "running",
1203
+ meta: { cwd },
1204
+ nextRequestId: 1,
1205
+ nextMessageId: 1,
1206
+ nextToolId: 1,
1207
+ pending: /* @__PURE__ */ new Map(),
1208
+ stdoutBuffer: "",
1209
+ scrollback: [],
1210
+ scrollbackBytes: 0,
1211
+ onDataCallbacks: [],
1212
+ onStatusCallbacks: [],
1213
+ onMetaCallbacks: [],
1214
+ onConversationCallbacks: [],
1215
+ onActivityCallbacks: []
1216
+ };
1217
+ this.entries.set(paneId, entry);
1218
+ proc.stdout.setEncoding("utf8");
1219
+ proc.stderr.setEncoding("utf8");
1220
+ proc.stdout.on("data", (chunk) => this.handleStdout(paneId, chunk));
1221
+ proc.stderr.on("data", (chunk) => {
1222
+ this.emitTerminal(paneId, `[acp stderr] ${chunk}`);
1223
+ });
1224
+ proc.on("exit", (code, signal) => {
1225
+ const e = this.entries.get(paneId);
1226
+ if (!e) return;
1227
+ for (const pending of e.pending.values()) {
1228
+ pending.reject(new Error(`ACP process exited (${code ?? "null"}${signal ? `, ${signal}` : ""})`));
1229
+ }
1230
+ e.pending.clear();
1231
+ this.setStatus(paneId, code === 0 ? "stopped" : "error");
1232
+ });
1233
+ this.bootstrap(paneId, cwd, config).catch((err) => {
1234
+ this.emitTerminal(paneId, `[acp error] ${err.message}
1235
+ `);
1236
+ this.setStatus(paneId, "error");
1237
+ });
1238
+ return proc.pid;
1239
+ }
1240
+ async bootstrap(paneId, cwd, config) {
1241
+ const initResult = await this.request(paneId, "initialize", {
1242
+ protocolVersion: 1,
1243
+ clientInfo: { name: "nexus", version: "0.1.0" },
1244
+ clientCapabilities: {}
1245
+ });
1246
+ const loadedSessionId = config.restore === "resume" && config.sessionId ? await this.tryLoadSession(paneId, config.sessionId) : null;
1247
+ const sessionResult = loadedSessionId ? { sessionId: loadedSessionId } : await this.request(paneId, "session/new", {
1248
+ cwd
1249
+ });
1250
+ const sessionId = this.extractSessionId(sessionResult) || this.extractSessionId(initResult) || config.sessionId;
1251
+ if (sessionId) {
1252
+ this.updateMeta(paneId, { sessionId, cwd });
1253
+ } else {
1254
+ this.updateMeta(paneId, { cwd });
1255
+ }
1256
+ this.emitConversation(paneId, { type: "status", status: "idle" });
1257
+ this.setStatus(paneId, "idle");
1258
+ if (config.task && config.restore !== "manual") {
1259
+ await this.sendPrompt(paneId, config.task);
1260
+ }
1261
+ }
1262
+ async tryLoadSession(paneId, sessionId) {
1263
+ try {
1264
+ const result = await this.request(paneId, "session/load", { sessionId });
1265
+ return this.extractSessionId(result) || sessionId;
1266
+ } catch {
1267
+ return null;
1268
+ }
1269
+ }
1270
+ async sendPrompt(paneId, text) {
1271
+ const entry = this.entries.get(paneId);
1272
+ if (!entry) return;
1273
+ const sessionId = entry.meta.sessionId || entry.config.sessionId;
1274
+ const messageId = `user-${entry.nextMessageId++}`;
1275
+ this.emitConversation(paneId, { type: "message", messageId, role: "user", text });
1276
+ this.emitTerminal(paneId, `
1277
+ > ${text}
1278
+
1279
+ `);
1280
+ this.setStatus(paneId, "running");
1281
+ this.emitConversation(paneId, { type: "status", status: "running" });
1282
+ const params = {
1283
+ prompt: [{ type: "text", text }]
1284
+ };
1285
+ if (sessionId) params.sessionId = sessionId;
1286
+ await this.request(paneId, "session/prompt", params);
1287
+ }
1288
+ onData(paneId, cb) {
1289
+ const entry = this.entries.get(paneId);
1290
+ if (entry) entry.onDataCallbacks.push(cb);
1291
+ }
1292
+ onStatus(paneId, cb) {
1293
+ const entry = this.entries.get(paneId);
1294
+ if (entry) entry.onStatusCallbacks.push(cb);
1295
+ }
1296
+ onMeta(paneId, cb) {
1297
+ const entry = this.entries.get(paneId);
1298
+ if (entry) entry.onMetaCallbacks.push(cb);
1299
+ }
1300
+ onConversation(paneId, cb) {
1301
+ const entry = this.entries.get(paneId);
1302
+ if (entry) entry.onConversationCallbacks.push(cb);
1303
+ }
1304
+ onActivity(paneId, cb) {
1305
+ const entry = this.entries.get(paneId);
1306
+ if (entry) entry.onActivityCallbacks.push(cb);
1307
+ }
1308
+ write(_paneId, _data) {
1309
+ }
1310
+ resize(_paneId, _cols, _rows) {
1311
+ }
1312
+ getScrollback(paneId) {
1313
+ const entry = this.entries.get(paneId);
1314
+ return entry ? entry.scrollback.join("") : "";
1315
+ }
1316
+ kill(paneId) {
1317
+ const entry = this.entries.get(paneId);
1318
+ if (!entry) return;
1319
+ entry.proc.kill();
1320
+ this.entries.delete(paneId);
1321
+ }
1322
+ killAll() {
1323
+ for (const paneId of this.entries.keys()) {
1324
+ this.kill(paneId);
1325
+ }
1326
+ }
1327
+ handleStdout(paneId, chunk) {
1328
+ const entry = this.entries.get(paneId);
1329
+ if (!entry) return;
1330
+ entry.stdoutBuffer += chunk;
1331
+ while (true) {
1332
+ const newline = entry.stdoutBuffer.indexOf("\n");
1333
+ if (newline === -1) break;
1334
+ const line = entry.stdoutBuffer.slice(0, newline).trim();
1335
+ entry.stdoutBuffer = entry.stdoutBuffer.slice(newline + 1);
1336
+ if (!line) continue;
1337
+ try {
1338
+ const message = JSON.parse(line);
1339
+ this.handleMessage(paneId, message);
1340
+ } catch {
1341
+ this.emitTerminal(paneId, line + "\n");
1342
+ }
1343
+ }
1344
+ }
1345
+ handleMessage(paneId, message) {
1346
+ const entry = this.entries.get(paneId);
1347
+ if (!entry) return;
1348
+ if (typeof message.id === "number") {
1349
+ const pending = entry.pending.get(message.id);
1350
+ if (pending) {
1351
+ entry.pending.delete(message.id);
1352
+ if ("error" in message && message.error) {
1353
+ const err = message.error;
1354
+ pending.reject(new Error(err?.message || "ACP request failed"));
1355
+ } else {
1356
+ pending.resolve(message.result);
1357
+ }
1358
+ }
1359
+ return;
1360
+ }
1361
+ if (message.method === "session/update") {
1362
+ const params = message.params || {};
1363
+ this.handleSessionUpdate(paneId, params);
1364
+ return;
1365
+ }
1366
+ }
1367
+ handleSessionUpdate(paneId, params) {
1368
+ const sessionId = this.extractSessionId(params);
1369
+ if (sessionId) {
1370
+ this.updateMeta(paneId, { sessionId });
1371
+ }
1372
+ const rawUpdate = params.update || params.delta || params.event || params;
1373
+ const updates = Array.isArray(rawUpdate) ? rawUpdate : [rawUpdate];
1374
+ for (const update of updates) {
1375
+ if (!update || typeof update !== "object") continue;
1376
+ const record = update;
1377
+ const kind = String(record.type || record.kind || "");
1378
+ const content = this.extractText(record);
1379
+ if (kind.includes("agent_message")) {
1380
+ const messageId = `assistant-${this.entries.get(paneId)?.nextMessageId ?? 1}`;
1381
+ this.emitConversation(paneId, {
1382
+ type: "message",
1383
+ messageId,
1384
+ role: "assistant",
1385
+ text: content,
1386
+ append: true
1387
+ });
1388
+ if (content) {
1389
+ this.emitTerminal(paneId, content);
1390
+ }
1391
+ this.setStatus(paneId, "running");
1392
+ } else if (kind.includes("tool_call")) {
1393
+ const toolCallId = `tool-${this.entries.get(paneId)?.nextToolId ?? 1}`;
1394
+ this.emitConversation(paneId, {
1395
+ type: "tool",
1396
+ toolCallId,
1397
+ title: String(record.title || record.name || "tool"),
1398
+ status: kind.includes("update") ? "in_progress" : "pending",
1399
+ text: content || void 0
1400
+ });
1401
+ if (content) {
1402
+ this.emitTerminal(paneId, `
1403
+ [tool] ${content}
1404
+ `);
1405
+ }
1406
+ } else if (kind.includes("turn") || kind.includes("done") || kind.includes("completed")) {
1407
+ this.setStatus(paneId, "idle");
1408
+ this.emitConversation(paneId, { type: "status", status: "idle" });
1409
+ }
1410
+ }
1411
+ }
1412
+ extractText(record) {
1413
+ const direct = record.text || record.delta || record.content;
1414
+ if (typeof direct === "string") return direct;
1415
+ if (Array.isArray(direct)) {
1416
+ return direct.map((item) => {
1417
+ if (typeof item === "string") return item;
1418
+ if (item && typeof item === "object" && typeof item.text === "string") {
1419
+ return String(item.text);
1420
+ }
1421
+ return "";
1422
+ }).join("");
1423
+ }
1424
+ if (direct && typeof direct === "object" && typeof direct.text === "string") {
1425
+ return String(direct.text);
1426
+ }
1427
+ return "";
1428
+ }
1429
+ request(paneId, method, params) {
1430
+ const entry = this.entries.get(paneId);
1431
+ if (!entry) {
1432
+ return Promise.reject(new Error(`Missing ACP entry for ${paneId}`));
1433
+ }
1434
+ const id = entry.nextRequestId++;
1435
+ const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
1436
+ entry.proc.stdin.write(payload);
1437
+ return new Promise((resolve, reject) => {
1438
+ entry.pending.set(id, { resolve, reject });
1439
+ });
1440
+ }
1441
+ extractSessionId(result) {
1442
+ if (!result || typeof result !== "object") return void 0;
1443
+ const record = result;
1444
+ if (typeof record.sessionId === "string") return record.sessionId;
1445
+ if (typeof record.session_id === "string") return record.session_id;
1446
+ if (record.session && typeof record.session === "object") {
1447
+ const nested = record.session;
1448
+ if (typeof nested.id === "string") return nested.id;
1449
+ if (typeof nested.sessionId === "string") return nested.sessionId;
1450
+ }
1451
+ return void 0;
1452
+ }
1453
+ emitTerminal(paneId, data) {
1454
+ const entry = this.entries.get(paneId);
1455
+ if (!entry || !data) return;
1456
+ entry.scrollback.push(data);
1457
+ entry.scrollbackBytes += data.length;
1458
+ if (entry.scrollbackBytes > MAX_SCROLLBACK_BYTES2) {
1459
+ let bytesToRemove = entry.scrollbackBytes - MAX_SCROLLBACK_BYTES2;
1460
+ let removeCount = 0;
1461
+ while (removeCount < entry.scrollback.length - 1 && bytesToRemove > 0) {
1462
+ bytesToRemove -= entry.scrollback[removeCount].length;
1463
+ entry.scrollbackBytes -= entry.scrollback[removeCount].length;
1464
+ removeCount++;
1465
+ }
1466
+ if (removeCount > 0) entry.scrollback.splice(0, removeCount);
1467
+ }
1468
+ for (const cb of entry.onDataCallbacks) {
1469
+ cb(data);
1470
+ }
1471
+ }
1472
+ setStatus(paneId, status) {
1473
+ const entry = this.entries.get(paneId);
1474
+ if (!entry || entry.status === status) return;
1475
+ entry.status = status;
1476
+ for (const cb of entry.onStatusCallbacks) {
1477
+ cb(status);
1478
+ }
1479
+ }
1480
+ updateMeta(paneId, meta) {
1481
+ const entry = this.entries.get(paneId);
1482
+ if (!entry) return;
1483
+ entry.meta = { ...entry.meta, ...meta };
1484
+ for (const cb of entry.onMetaCallbacks) {
1485
+ cb(entry.meta);
1486
+ }
1487
+ }
1488
+ emitConversation(paneId, event) {
1489
+ const entry = this.entries.get(paneId);
1490
+ if (!entry) return;
1491
+ for (const cb of entry.onConversationCallbacks) {
1492
+ cb(event);
1493
+ }
1494
+ }
1495
+ };
1496
+
1497
+ // src/git/WorktreeManager.ts
1498
+ import path4 from "path";
1499
+ import fs4 from "fs";
1110
1500
  import { simpleGit } from "simple-git";
1111
1501
  var WorktreeManager = class {
1112
1502
  projectDir;
@@ -1124,15 +1514,15 @@ var WorktreeManager = class {
1124
1514
  const baseBranch = await this.getCurrentBranch();
1125
1515
  const slug = this.slugify(paneName);
1126
1516
  const branch = `nexus/${paneId}-${slug}`;
1127
- const worktreePath = path3.join(this.projectDir, ".nexus", "worktrees", paneId);
1128
- if (fs3.existsSync(worktreePath)) {
1517
+ const worktreePath = path4.join(this.projectDir, ".nexus", "worktrees", paneId);
1518
+ if (fs4.existsSync(worktreePath)) {
1129
1519
  await this.forceRemoveWorktree(worktreePath);
1130
1520
  }
1131
1521
  try {
1132
1522
  await this.git.raw(["branch", "-D", branch]);
1133
1523
  } catch {
1134
1524
  }
1135
- fs3.mkdirSync(path3.dirname(worktreePath), { recursive: true });
1525
+ fs4.mkdirSync(path4.dirname(worktreePath), { recursive: true });
1136
1526
  await this.git.raw(["worktree", "add", "-b", branch, worktreePath, baseBranch]);
1137
1527
  const entry = { path: worktreePath, branch, baseBranch };
1138
1528
  this.worktrees.set(paneId, entry);
@@ -1151,14 +1541,14 @@ var WorktreeManager = class {
1151
1541
  } catch {
1152
1542
  return false;
1153
1543
  }
1154
- if (fs3.existsSync(worktreePath)) {
1544
+ if (fs4.existsSync(worktreePath)) {
1155
1545
  this.worktrees.set(paneId, { path: worktreePath, branch, baseBranch });
1156
1546
  return true;
1157
1547
  }
1158
1548
  try {
1159
1549
  await this.git.raw(["worktree", "prune"]).catch(() => {
1160
1550
  });
1161
- fs3.mkdirSync(path3.dirname(worktreePath), { recursive: true });
1551
+ fs4.mkdirSync(path4.dirname(worktreePath), { recursive: true });
1162
1552
  await this.git.raw(["worktree", "add", worktreePath, branch]);
1163
1553
  this.worktrees.set(paneId, { path: worktreePath, branch, baseBranch });
1164
1554
  return true;
@@ -1337,7 +1727,7 @@ var WorktreeManager = class {
1337
1727
  await this.git.raw(["worktree", "remove", "--force", wtPath]);
1338
1728
  } catch {
1339
1729
  try {
1340
- fs3.rmSync(wtPath, { recursive: true, force: true });
1730
+ fs4.rmSync(wtPath, { recursive: true, force: true });
1341
1731
  await this.git.raw(["worktree", "prune"]);
1342
1732
  } catch {
1343
1733
  }
@@ -1357,7 +1747,7 @@ var WorktreeManager = class {
1357
1747
  };
1358
1748
 
1359
1749
  // src/git/GitService.ts
1360
- import path4 from "path";
1750
+ import path5 from "path";
1361
1751
  import { simpleGit as simpleGit2 } from "simple-git";
1362
1752
  import { watch } from "chokidar";
1363
1753
  var GitService = class {
@@ -1383,11 +1773,11 @@ var GitService = class {
1383
1773
  this.refresh();
1384
1774
  }, 1e3);
1385
1775
  };
1386
- const gitDir = path4.join(this.projectDir, ".git");
1776
+ const gitDir = path5.join(this.projectDir, ".git");
1387
1777
  this.gitWatcher = watch([
1388
- path4.join(gitDir, "index"),
1389
- path4.join(gitDir, "HEAD"),
1390
- path4.join(gitDir, "refs")
1778
+ path5.join(gitDir, "index"),
1779
+ path5.join(gitDir, "HEAD"),
1780
+ path5.join(gitDir, "refs")
1391
1781
  ], {
1392
1782
  persistent: true,
1393
1783
  ignoreInitial: true
@@ -1401,7 +1791,7 @@ var GitService = class {
1401
1791
  };
1402
1792
  this.workWatcher = watch(this.projectDir, {
1403
1793
  ignored: (filePath) => {
1404
- const basename = path4.basename(filePath);
1794
+ const basename = path5.basename(filePath);
1405
1795
  return basename === ".git" || basename === "node_modules" || basename === ".nexus" || basename === "dist";
1406
1796
  },
1407
1797
  persistent: true,
@@ -1412,10 +1802,18 @@ var GitService = class {
1412
1802
  }
1413
1803
  async refresh() {
1414
1804
  try {
1415
- const result = await this.getDiffs();
1805
+ const result = await Promise.race([
1806
+ this.getDiffs(),
1807
+ new Promise(
1808
+ (_, reject) => setTimeout(() => reject(new Error("git diff timeout")), 15e3)
1809
+ )
1810
+ ]);
1416
1811
  this.currentResult = result;
1417
1812
  this.notifyListeners();
1418
- } catch {
1813
+ } catch (err) {
1814
+ if (err.message === "git diff timeout") {
1815
+ console.warn("[GitService] git diff timed out (15s), using cached result");
1816
+ }
1419
1817
  }
1420
1818
  }
1421
1819
  getCurrentDiffs() {
@@ -1447,10 +1845,10 @@ var GitService = class {
1447
1845
  const status = await this.git.status();
1448
1846
  const isUntracked = status.not_added.includes(file) || status.created.includes(file);
1449
1847
  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);
1848
+ const fullPath = path5.join(this.projectDir, file);
1849
+ const fs11 = await import("fs");
1850
+ if (fs11.existsSync(fullPath)) {
1851
+ fs11.unlinkSync(fullPath);
1454
1852
  }
1455
1853
  } else {
1456
1854
  await this.git.checkout(["--", file]);
@@ -1591,15 +1989,19 @@ function nextPaneId() {
1591
1989
  var WorkspaceManager = class {
1592
1990
  panes = /* @__PURE__ */ new Map();
1593
1991
  ptyManager;
1992
+ acpRuntime;
1594
1993
  configManager;
1595
1994
  worktreeManager;
1596
1995
  perPaneGitServices = /* @__PURE__ */ new Map();
1597
1996
  wsName = "";
1598
1997
  wsDescription = "";
1998
+ // Serialize config writes to prevent race conditions when closing multiple panes
1999
+ configWriteLock = Promise.resolve();
1599
2000
  // Multi-client event listener sets
1600
2001
  listeners = {
1601
2002
  onPaneAdded: /* @__PURE__ */ new Set(),
1602
2003
  onPaneRemoved: /* @__PURE__ */ new Set(),
2004
+ onConversationEvent: /* @__PURE__ */ new Set(),
1603
2005
  onPaneStatus: /* @__PURE__ */ new Set(),
1604
2006
  onPaneMeta: /* @__PURE__ */ new Set(),
1605
2007
  onTerminalData: /* @__PURE__ */ new Set(),
@@ -1612,6 +2014,7 @@ var WorkspaceManager = class {
1612
2014
  constructor(configManager) {
1613
2015
  this.configManager = configManager;
1614
2016
  this.ptyManager = new PtyManager(configManager);
2017
+ this.acpRuntime = new AcpRuntime(configManager);
1615
2018
  this.worktreeManager = new WorktreeManager(configManager.getProjectDir());
1616
2019
  }
1617
2020
  async init() {
@@ -1712,7 +2115,11 @@ var WorkspaceManager = class {
1712
2115
  }
1713
2116
  async closePane(paneId) {
1714
2117
  const pane = this.panes.get(paneId);
1715
- this.ptyManager.kill(paneId);
2118
+ if (pane?.runtime === "acp") {
2119
+ this.acpRuntime.kill(paneId);
2120
+ } else {
2121
+ this.ptyManager.kill(paneId);
2122
+ }
1716
2123
  if (pane?.isolation === "worktree") {
1717
2124
  this.stopPaneGitService(paneId);
1718
2125
  await this.worktreeManager.remove(paneId);
@@ -1724,7 +2131,11 @@ var WorkspaceManager = class {
1724
2131
  restartPane(paneId, mode, sessionId) {
1725
2132
  const existingState = this.panes.get(paneId);
1726
2133
  if (!existingState) return;
1727
- this.ptyManager.kill(paneId);
2134
+ if (existingState.runtime === "acp") {
2135
+ this.acpRuntime.kill(paneId);
2136
+ } else {
2137
+ this.ptyManager.kill(paneId);
2138
+ }
1728
2139
  const resolvedSessionId = mode === "resume" ? sessionId || existingState.sessionId || existingState.meta.sessionId : void 0;
1729
2140
  const config = {
1730
2141
  id: paneId,
@@ -1765,13 +2176,36 @@ var WorkspaceManager = class {
1765
2176
  return result;
1766
2177
  }
1767
2178
  writeToPane(paneId, data) {
2179
+ const pane = this.panes.get(paneId);
2180
+ if (!pane) return;
2181
+ if (pane.runtime === "acp") {
2182
+ this.acpRuntime.write(paneId, data);
2183
+ return;
2184
+ }
1768
2185
  this.ptyManager.write(paneId, data);
1769
2186
  }
2187
+ sendConversationToPane(paneId, text) {
2188
+ const pane = this.panes.get(paneId);
2189
+ if (!pane) return Promise.resolve();
2190
+ if (pane.runtime === "acp") {
2191
+ return this.acpRuntime.sendPrompt(paneId, text);
2192
+ }
2193
+ this.ptyManager.write(paneId, text + "\r");
2194
+ return Promise.resolve();
2195
+ }
1770
2196
  resizePane(paneId, cols, rows) {
2197
+ const pane = this.panes.get(paneId);
2198
+ if (!pane) return;
2199
+ if (pane.runtime === "acp") {
2200
+ this.acpRuntime.resize(paneId, cols, rows);
2201
+ return;
2202
+ }
1771
2203
  this.ptyManager.resize(paneId, cols, rows);
1772
2204
  }
1773
2205
  getScrollback(paneId) {
1774
- return this.ptyManager.getScrollback(paneId);
2206
+ const pane = this.panes.get(paneId);
2207
+ if (!pane) return "";
2208
+ return pane.runtime === "acp" ? this.acpRuntime.getScrollback(paneId) : this.ptyManager.getScrollback(paneId);
1775
2209
  }
1776
2210
  // ─── Event Registration (multi-client safe) ────────────────
1777
2211
  /**
@@ -1828,7 +2262,8 @@ var WorkspaceManager = class {
1828
2262
  }
1829
2263
  }
1830
2264
  spawnPane(config, cols, rows) {
1831
- const pid = this.ptyManager.spawn(config.id, config, cols || 80, rows || 24);
2265
+ const runtime = this.resolveRuntime(config.agent);
2266
+ const pid = runtime === "acp" ? this.acpRuntime.spawn(config.id, config) : this.ptyManager.spawn(config.id, config, cols || 80, rows || 24);
1832
2267
  const pane = {
1833
2268
  id: config.id,
1834
2269
  name: config.name,
@@ -1841,6 +2276,7 @@ var WorkspaceManager = class {
1841
2276
  branch: config.branch,
1842
2277
  worktreePath: config.worktreePath,
1843
2278
  sessionId: config.sessionId,
2279
+ runtime,
1844
2280
  status: "running",
1845
2281
  pid,
1846
2282
  meta: {},
@@ -1848,17 +2284,18 @@ var WorkspaceManager = class {
1848
2284
  };
1849
2285
  this.panes.set(config.id, pane);
1850
2286
  this.emit("onPaneAdded", pane);
1851
- this.ptyManager.onData(config.id, (data) => {
2287
+ const runtimeAdapter = runtime === "acp" ? this.acpRuntime : this.ptyManager;
2288
+ runtimeAdapter.onData(config.id, (data) => {
1852
2289
  this.emit("onTerminalData", config.id, data);
1853
2290
  });
1854
- this.ptyManager.onStatus(config.id, (status) => {
2291
+ runtimeAdapter.onStatus(config.id, (status) => {
1855
2292
  const p = this.panes.get(config.id);
1856
2293
  if (p) {
1857
2294
  p.status = status;
1858
2295
  this.emit("onPaneStatus", config.id, status);
1859
2296
  }
1860
2297
  });
1861
- this.ptyManager.onMeta(config.id, (meta) => {
2298
+ runtimeAdapter.onMeta(config.id, (meta) => {
1862
2299
  const p = this.panes.get(config.id);
1863
2300
  if (p) {
1864
2301
  p.meta = meta;
@@ -1869,11 +2306,21 @@ var WorkspaceManager = class {
1869
2306
  this.emit("onPaneMeta", config.id, meta);
1870
2307
  }
1871
2308
  });
1872
- this.ptyManager.onActivity(config.id, (activity) => {
2309
+ runtimeAdapter.onActivity(config.id, (activity) => {
1873
2310
  this.emit("onPaneActivity", config.id, activity);
1874
2311
  });
2312
+ if (runtime === "acp") {
2313
+ this.acpRuntime.onConversation(config.id, (event) => {
2314
+ this.emit("onConversationEvent", config.id, event);
2315
+ });
2316
+ }
1875
2317
  return pane;
1876
2318
  }
2319
+ resolveRuntime(agentType) {
2320
+ if (agentType === "__shell__") return "pty";
2321
+ const def = this.configManager.getAgentDefinition(agentType);
2322
+ return def?.transport || "pty";
2323
+ }
1877
2324
  async startPaneGitService(paneId, worktreePath) {
1878
2325
  const gitService = new GitService(worktreePath);
1879
2326
  gitService.onDiffChange((result) => {
@@ -1891,21 +2338,25 @@ var WorkspaceManager = class {
1891
2338
  }
1892
2339
  }
1893
2340
  persistPaneConfig(config) {
1894
- const wsConfig = this.configManager.loadWorkspaceConfig();
1895
- if (wsConfig) {
1896
- wsConfig.panes.push(config);
1897
- this.configManager.saveWorkspaceConfig(wsConfig);
1898
- }
2341
+ this.serializedConfigWrite(() => {
2342
+ const wsConfig = this.configManager.loadWorkspaceConfig();
2343
+ if (wsConfig) {
2344
+ wsConfig.panes.push(config);
2345
+ this.configManager.saveWorkspaceConfig(wsConfig);
2346
+ }
2347
+ });
1899
2348
  }
1900
2349
  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);
2350
+ this.serializedConfigWrite(() => {
2351
+ const wsConfig = this.configManager.loadWorkspaceConfig();
2352
+ if (wsConfig) {
2353
+ const paneConfig = wsConfig.panes.find((p) => p.id === paneId);
2354
+ if (paneConfig) {
2355
+ paneConfig.sessionId = sessionId;
2356
+ this.configManager.saveWorkspaceConfig(wsConfig);
2357
+ }
1907
2358
  }
1908
- }
2359
+ });
1909
2360
  }
1910
2361
  getSessionList(paneId) {
1911
2362
  const sessions = [];
@@ -1943,14 +2394,30 @@ var WorkspaceManager = class {
1943
2394
  return sessions;
1944
2395
  }
1945
2396
  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
- }
2397
+ this.serializedConfigWrite(() => {
2398
+ const wsConfig = this.configManager.loadWorkspaceConfig();
2399
+ if (wsConfig) {
2400
+ wsConfig.panes = wsConfig.panes.filter((p) => p.id !== paneId);
2401
+ this.configManager.saveWorkspaceConfig(wsConfig);
2402
+ }
2403
+ });
2404
+ }
2405
+ /**
2406
+ * Serialize config file writes to prevent race conditions
2407
+ * when multiple panes are created/closed simultaneously.
2408
+ */
2409
+ serializedConfigWrite(fn) {
2410
+ this.configWriteLock = this.configWriteLock.then(() => {
2411
+ try {
2412
+ fn();
2413
+ } catch (err) {
2414
+ console.error("[WorkspaceManager] Config write failed:", err);
2415
+ }
2416
+ });
1951
2417
  }
1952
2418
  async shutdown() {
1953
2419
  this.ptyManager.killAll();
2420
+ this.acpRuntime.killAll();
1954
2421
  for (const [paneId] of this.perPaneGitServices) {
1955
2422
  this.stopPaneGitService(paneId);
1956
2423
  }
@@ -1958,8 +2425,8 @@ var WorkspaceManager = class {
1958
2425
  };
1959
2426
 
1960
2427
  // src/workspace/AgentsYamlWriter.ts
1961
- import fs4 from "fs";
1962
- import path5 from "path";
2428
+ import fs5 from "fs";
2429
+ import path6 from "path";
1963
2430
  import yaml2 from "js-yaml";
1964
2431
  var AgentsYamlWriter = class {
1965
2432
  projectDir;
@@ -1984,8 +2451,8 @@ var AgentsYamlWriter = class {
1984
2451
  this.writeFile(panes);
1985
2452
  }
1986
2453
  writeFile(panes) {
1987
- const nexusDir = path5.join(this.projectDir, ".nexus");
1988
- fs4.mkdirSync(nexusDir, { recursive: true });
2454
+ const nexusDir = path6.join(this.projectDir, ".nexus");
2455
+ fs5.mkdirSync(nexusDir, { recursive: true });
1989
2456
  const visible = panes.filter((p) => p.agent !== "__shell__");
1990
2457
  const data = {
1991
2458
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1995,11 +2462,12 @@ var AgentsYamlWriter = class {
1995
2462
  id: p.id,
1996
2463
  name: p.name,
1997
2464
  agent: p.agent,
2465
+ runtime: p.runtime,
1998
2466
  pid: p.pid,
1999
2467
  status: p.status,
2000
2468
  isolation: p.isolation || "shared",
2001
2469
  branch: p.branch || void 0,
2002
- workdir: p.workdir ? path5.resolve(basePath, p.workdir) : basePath,
2470
+ workdir: p.workdir ? path6.resolve(basePath, p.workdir) : basePath,
2003
2471
  task: p.task || void 0,
2004
2472
  model: p.meta.model || void 0,
2005
2473
  context_used_pct: p.meta.contextUsedPct ?? void 0,
@@ -2009,18 +2477,19 @@ var AgentsYamlWriter = class {
2009
2477
  };
2010
2478
  })
2011
2479
  };
2012
- const filePath = path5.join(nexusDir, "agents.yaml");
2013
- fs4.writeFileSync(filePath, yaml2.dump(data, { lineWidth: -1 }));
2480
+ const filePath = path6.join(nexusDir, "agents.yaml");
2481
+ fs5.writeFileSync(filePath, yaml2.dump(data, { lineWidth: -1 }));
2014
2482
  }
2015
2483
  };
2016
2484
 
2017
2485
  // src/fs/FsWatcher.ts
2018
- import fs5 from "fs";
2019
- import path6 from "path";
2486
+ import fs6 from "fs";
2487
+ import path7 from "path";
2020
2488
  import { watch as watch2 } from "chokidar";
2021
2489
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
2022
2490
  "node_modules",
2023
2491
  ".git",
2492
+ ".nexus",
2024
2493
  "dist",
2025
2494
  ".cache",
2026
2495
  ".turbo",
@@ -2047,7 +2516,7 @@ var FsWatcher = class {
2047
2516
  this.notifyListeners();
2048
2517
  this.watcher = watch2(this.projectDir, {
2049
2518
  ignored: (filePath) => {
2050
- const basename = path6.basename(filePath);
2519
+ const basename = path7.basename(filePath);
2051
2520
  return IGNORED_DIRS.has(basename) || IGNORED_FILES.has(basename);
2052
2521
  },
2053
2522
  persistent: true,
@@ -2064,9 +2533,9 @@ var FsWatcher = class {
2064
2533
  }, 300);
2065
2534
  };
2066
2535
  const emitFileChange = (eventType, filePath) => {
2067
- const relativePath = path6.relative(this.projectDir, filePath);
2536
+ const relativePath = path7.relative(this.projectDir, filePath);
2068
2537
  if (!relativePath || relativePath.startsWith("..")) return;
2069
- if (!/\.\w{1,10}$/.test(path6.basename(filePath))) return;
2538
+ if (!/\.\w{1,10}$/.test(path7.basename(filePath))) return;
2070
2539
  const now = Date.now();
2071
2540
  const lastChange = this.recentChanges.get(relativePath);
2072
2541
  if (lastChange && now - lastChange < 1e3) return;
@@ -2135,14 +2604,14 @@ var FsWatcher = class {
2135
2604
  return parts.join("\n");
2136
2605
  }
2137
2606
  buildTree(dirPath, depth) {
2138
- if (depth > 8) return [];
2607
+ if (depth > 5) return [];
2139
2608
  try {
2140
- const entries = fs5.readdirSync(dirPath, { withFileTypes: true });
2609
+ const entries = fs6.readdirSync(dirPath, { withFileTypes: true });
2141
2610
  const nodes = [];
2142
2611
  for (const entry of entries) {
2143
2612
  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);
2613
+ const fullPath = path7.join(dirPath, entry.name);
2614
+ const relativePath = path7.relative(this.projectDir, fullPath);
2146
2615
  if (entry.isDirectory()) {
2147
2616
  nodes.push({
2148
2617
  name: entry.name,
@@ -2181,7 +2650,7 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
2181
2650
  type: "workspace.state",
2182
2651
  state
2183
2652
  });
2184
- const SCROLLBACK_CHUNK_SIZE = 64 * 1024;
2653
+ const SCROLLBACK_CHUNK_SIZE = 512 * 1024;
2185
2654
  const replayScrollback = async () => {
2186
2655
  for (const pane of state.panes) {
2187
2656
  const scrollback = workspaceManager.getScrollback(pane.id);
@@ -2211,6 +2680,9 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
2211
2680
  onTerminalData: (paneId, data) => {
2212
2681
  send({ type: "terminal.output", paneId, data });
2213
2682
  },
2683
+ onConversationEvent: (paneId, event) => {
2684
+ send({ type: "conversation.event", paneId, event });
2685
+ },
2214
2686
  onPaneStatus: (paneId, status) => {
2215
2687
  send({ type: "pane.status", paneId, status });
2216
2688
  },
@@ -2248,10 +2720,21 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
2248
2720
  }
2249
2721
  switch (event.type) {
2250
2722
  case "terminal.input":
2251
- workspaceManager.writeToPane(event.paneId, event.data);
2723
+ try {
2724
+ workspaceManager.writeToPane(event.paneId, event.data);
2725
+ } catch {
2726
+ }
2252
2727
  break;
2253
2728
  case "terminal.resize":
2254
- workspaceManager.resizePane(event.paneId, event.cols, event.rows);
2729
+ try {
2730
+ workspaceManager.resizePane(event.paneId, event.cols, event.rows);
2731
+ } catch {
2732
+ }
2733
+ break;
2734
+ case "conversation.send":
2735
+ workspaceManager.sendConversationToPane(event.paneId, event.text).catch((err) => {
2736
+ console.error("conversation.send failed:", err);
2737
+ });
2255
2738
  break;
2256
2739
  case "pane.create":
2257
2740
  workspaceManager.createPane(event.config).catch((err) => {
@@ -2372,8 +2855,8 @@ function setupWsHandlers(socket, workspaceManager, gitService) {
2372
2855
  }
2373
2856
 
2374
2857
  // src/deps/DependencyAnalyzer.ts
2375
- import fs6 from "fs";
2376
- import path7 from "path";
2858
+ import fs7 from "fs";
2859
+ import path8 from "path";
2377
2860
  var IMPORT_FROM_RE = /import\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
2378
2861
  var EXPORT_FROM_RE = /export\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
2379
2862
  var REQUIRE_RE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
@@ -2394,7 +2877,7 @@ var DependencyAnalyzer = class {
2394
2877
  const files = this.collectFiles(this.projectDir);
2395
2878
  const nodes = [];
2396
2879
  for (const absPath of files) {
2397
- const relPath = path7.relative(this.projectDir, absPath);
2880
+ const relPath = path8.relative(this.projectDir, absPath);
2398
2881
  const imports = this.extractImports(absPath, relPath);
2399
2882
  nodes.push({ id: relPath, imports });
2400
2883
  }
@@ -2405,18 +2888,18 @@ var DependencyAnalyzer = class {
2405
2888
  const files = [];
2406
2889
  let entries;
2407
2890
  try {
2408
- entries = fs6.readdirSync(dir, { withFileTypes: true });
2891
+ entries = fs7.readdirSync(dir, { withFileTypes: true });
2409
2892
  } catch {
2410
2893
  return files;
2411
2894
  }
2412
2895
  for (const entry of entries) {
2413
2896
  if (entry.name.startsWith(".") && entry.name !== ".") continue;
2414
- const fullPath = path7.join(dir, entry.name);
2897
+ const fullPath = path8.join(dir, entry.name);
2415
2898
  if (entry.isDirectory()) {
2416
2899
  if (SKIP_DIRS.has(entry.name)) continue;
2417
2900
  files.push(...this.collectFiles(fullPath, depth + 1));
2418
2901
  } else if (entry.isFile()) {
2419
- const ext = path7.extname(entry.name);
2902
+ const ext = path8.extname(entry.name);
2420
2903
  if (JS_TS_EXTENSIONS.has(ext)) {
2421
2904
  files.push(fullPath);
2422
2905
  }
@@ -2427,7 +2910,7 @@ var DependencyAnalyzer = class {
2427
2910
  extractImports(absPath, relPath) {
2428
2911
  let content;
2429
2912
  try {
2430
- content = fs6.readFileSync(absPath, "utf-8");
2913
+ content = fs7.readFileSync(absPath, "utf-8");
2431
2914
  } catch {
2432
2915
  return [];
2433
2916
  }
@@ -2444,22 +2927,22 @@ var DependencyAnalyzer = class {
2444
2927
  }
2445
2928
  }
2446
2929
  const imports = [];
2447
- const fileDir = path7.dirname(absPath);
2930
+ const fileDir = path8.dirname(absPath);
2448
2931
  for (const spec of specifiers) {
2449
2932
  const resolved = this.resolveSpecifier(fileDir, spec);
2450
2933
  if (resolved) {
2451
- const resolvedRel = path7.relative(this.projectDir, resolved);
2934
+ const resolvedRel = path8.relative(this.projectDir, resolved);
2452
2935
  imports.push(resolvedRel);
2453
2936
  }
2454
2937
  }
2455
2938
  return imports;
2456
2939
  }
2457
2940
  resolveSpecifier(fromDir, specifier) {
2458
- const base = path7.resolve(fromDir, specifier);
2941
+ const base = path8.resolve(fromDir, specifier);
2459
2942
  for (const ext of RESOLVE_EXTENSIONS) {
2460
2943
  const candidate = base + ext;
2461
2944
  try {
2462
- if (fs6.statSync(candidate).isFile()) {
2945
+ if (fs7.statSync(candidate).isFile()) {
2463
2946
  return candidate;
2464
2947
  }
2465
2948
  } catch {
@@ -2470,8 +2953,8 @@ var DependencyAnalyzer = class {
2470
2953
  };
2471
2954
 
2472
2955
  // src/history/SessionRecorder.ts
2473
- import fs7 from "fs";
2474
- import path8 from "path";
2956
+ import fs8 from "fs";
2957
+ import path9 from "path";
2475
2958
  import { execFile as execFile2 } from "child_process";
2476
2959
  var HISTORY_DIR = ".nexus/history";
2477
2960
  var SESSIONS_INDEX = "sessions.json";
@@ -2489,8 +2972,8 @@ var SessionRecorder = class _SessionRecorder {
2489
2972
  constructor(projectDir, projectName, retentionDays = 30) {
2490
2973
  this.projectDir = projectDir;
2491
2974
  const sessionId = `s-${Date.now()}`;
2492
- this.sessionDir = path8.join(projectDir, HISTORY_DIR, sessionId);
2493
- fs7.mkdirSync(this.sessionDir, { recursive: true });
2975
+ this.sessionDir = path9.join(projectDir, HISTORY_DIR, sessionId);
2976
+ fs8.mkdirSync(this.sessionDir, { recursive: true });
2494
2977
  this.retentionDays = retentionDays;
2495
2978
  this.session = {
2496
2979
  id: sessionId,
@@ -2630,47 +3113,47 @@ var SessionRecorder = class _SessionRecorder {
2630
3113
  }
2631
3114
  // ─── Query API ────────────────────────────────────────────
2632
3115
  static listSessions(projectDir) {
2633
- const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2634
- if (!fs7.existsSync(indexPath)) return [];
3116
+ const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
3117
+ if (!fs8.existsSync(indexPath)) return [];
2635
3118
  try {
2636
- const data = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
3119
+ const data = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
2637
3120
  return Array.isArray(data) ? data : [];
2638
3121
  } catch {
2639
3122
  return [];
2640
3123
  }
2641
3124
  }
2642
3125
  static getSession(projectDir, sessionId) {
2643
- const sessionPath = path8.join(projectDir, HISTORY_DIR, sessionId, "session.json");
2644
- if (!fs7.existsSync(sessionPath)) return null;
3126
+ const sessionPath = path9.join(projectDir, HISTORY_DIR, sessionId, "session.json");
3127
+ if (!fs8.existsSync(sessionPath)) return null;
2645
3128
  try {
2646
- return JSON.parse(fs7.readFileSync(sessionPath, "utf-8"));
3129
+ return JSON.parse(fs8.readFileSync(sessionPath, "utf-8"));
2647
3130
  } catch {
2648
3131
  return null;
2649
3132
  }
2650
3133
  }
2651
3134
  static getTurn(projectDir, sessionId, turnId) {
2652
- const turnPath = path8.join(projectDir, HISTORY_DIR, sessionId, `${turnId}.json`);
2653
- if (!fs7.existsSync(turnPath)) return null;
3135
+ const turnPath = path9.join(projectDir, HISTORY_DIR, sessionId, `${turnId}.json`);
3136
+ if (!fs8.existsSync(turnPath)) return null;
2654
3137
  try {
2655
- return JSON.parse(fs7.readFileSync(turnPath, "utf-8"));
3138
+ return JSON.parse(fs8.readFileSync(turnPath, "utf-8"));
2656
3139
  } catch {
2657
3140
  return null;
2658
3141
  }
2659
3142
  }
2660
3143
  /** Delete a single session and its directory. Returns true if deleted. */
2661
3144
  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 });
3145
+ const sessionDir = path9.join(projectDir, HISTORY_DIR, sessionId);
3146
+ if (fs8.existsSync(sessionDir)) {
3147
+ fs8.rmSync(sessionDir, { recursive: true, force: true });
2665
3148
  }
2666
- const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2667
- if (fs7.existsSync(indexPath)) {
3149
+ const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
3150
+ if (fs8.existsSync(indexPath)) {
2668
3151
  try {
2669
- let sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
3152
+ let sessions = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
2670
3153
  const before = sessions.length;
2671
3154
  sessions = sessions.filter((s) => s.id !== sessionId);
2672
3155
  if (sessions.length < before) {
2673
- fs7.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
3156
+ fs8.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
2674
3157
  return true;
2675
3158
  }
2676
3159
  } catch {
@@ -2683,14 +3166,14 @@ var SessionRecorder = class _SessionRecorder {
2683
3166
  const sessions = _SessionRecorder.listSessions(projectDir);
2684
3167
  let count = 0;
2685
3168
  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 });
3169
+ const sessionDir = path9.join(projectDir, HISTORY_DIR, session.id);
3170
+ if (fs8.existsSync(sessionDir)) {
3171
+ fs8.rmSync(sessionDir, { recursive: true, force: true });
2689
3172
  count++;
2690
3173
  }
2691
3174
  }
2692
- const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2693
- fs7.writeFileSync(indexPath, JSON.stringify([], null, 2));
3175
+ const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
3176
+ fs8.writeFileSync(indexPath, JSON.stringify([], null, 2));
2694
3177
  return count;
2695
3178
  }
2696
3179
  /**
@@ -2698,11 +3181,11 @@ var SessionRecorder = class _SessionRecorder {
2698
3181
  * Called automatically when saving a new session.
2699
3182
  */
2700
3183
  static pruneOldSessions(projectDir, retentionDays) {
2701
- const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2702
- if (!fs7.existsSync(indexPath)) return 0;
3184
+ const indexPath = path9.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
3185
+ if (!fs8.existsSync(indexPath)) return 0;
2703
3186
  let sessions;
2704
3187
  try {
2705
- sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
3188
+ sessions = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
2706
3189
  } catch {
2707
3190
  return 0;
2708
3191
  }
@@ -2718,12 +3201,12 @@ var SessionRecorder = class _SessionRecorder {
2718
3201
  }
2719
3202
  if (remove.length === 0) return 0;
2720
3203
  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 });
3204
+ const sessionDir = path9.join(projectDir, HISTORY_DIR, s.id);
3205
+ if (fs8.existsSync(sessionDir)) {
3206
+ fs8.rmSync(sessionDir, { recursive: true, force: true });
2724
3207
  }
2725
3208
  }
2726
- fs7.writeFileSync(indexPath, JSON.stringify(keep, null, 2));
3209
+ fs8.writeFileSync(indexPath, JSON.stringify(keep, null, 2));
2727
3210
  return remove.length;
2728
3211
  }
2729
3212
  // ─── Internal ─────────────────────────────────────────────
@@ -2830,19 +3313,19 @@ var SessionRecorder = class _SessionRecorder {
2830
3313
  });
2831
3314
  }
2832
3315
  writeTurnFile(turn) {
2833
- const turnPath = path8.join(this.sessionDir, `${turn.id}.json`);
2834
- fs7.writeFileSync(turnPath, JSON.stringify(turn));
3316
+ const turnPath = path9.join(this.sessionDir, `${turn.id}.json`);
3317
+ fs8.writeFileSync(turnPath, JSON.stringify(turn));
2835
3318
  }
2836
3319
  writeSessionFile() {
2837
- const sessionPath = path8.join(this.sessionDir, "session.json");
2838
- fs7.writeFileSync(sessionPath, JSON.stringify(this.session, null, 2));
3320
+ const sessionPath = path9.join(this.sessionDir, "session.json");
3321
+ fs8.writeFileSync(sessionPath, JSON.stringify(this.session, null, 2));
2839
3322
  }
2840
3323
  updateSessionsIndex() {
2841
- const indexPath = path8.join(this.projectDir, HISTORY_DIR, SESSIONS_INDEX);
3324
+ const indexPath = path9.join(this.projectDir, HISTORY_DIR, SESSIONS_INDEX);
2842
3325
  let sessions = [];
2843
- if (fs7.existsSync(indexPath)) {
3326
+ if (fs8.existsSync(indexPath)) {
2844
3327
  try {
2845
- sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
3328
+ sessions = JSON.parse(fs8.readFileSync(indexPath, "utf-8"));
2846
3329
  } catch {
2847
3330
  }
2848
3331
  }
@@ -2856,7 +3339,7 @@ var SessionRecorder = class _SessionRecorder {
2856
3339
  paneCount: this.session.panes.length,
2857
3340
  totalDurationMs
2858
3341
  });
2859
- fs7.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
3342
+ fs8.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
2860
3343
  const pruned = _SessionRecorder.pruneOldSessions(this.projectDir, this.retentionDays);
2861
3344
  if (pruned > 0) {
2862
3345
  console.log(`[SessionRecorder] Pruned ${pruned} old session(s)`);
@@ -2915,7 +3398,7 @@ var SessionDiscovery = class {
2915
3398
  };
2916
3399
 
2917
3400
  // src/index.ts
2918
- var __dirname = path9.dirname(fileURLToPath(import.meta.url));
3401
+ var __dirname = path10.dirname(fileURLToPath(import.meta.url));
2919
3402
  async function startServer(port, projectDir) {
2920
3403
  const fastify = Fastify({ logger: false });
2921
3404
  const configManager = new ConfigManager(projectDir);
@@ -3079,17 +3562,17 @@ async function startServer(port, projectDir) {
3079
3562
  reply.code(400);
3080
3563
  return { error: "Missing path parameter" };
3081
3564
  }
3082
- if (filePath.includes("..") || path9.isAbsolute(filePath)) {
3565
+ if (filePath.includes("..") || path10.isAbsolute(filePath)) {
3083
3566
  reply.code(403);
3084
3567
  return { error: "Invalid path" };
3085
3568
  }
3086
- const fullPath = path9.resolve(projectDir, filePath);
3569
+ const fullPath = path10.resolve(projectDir, filePath);
3087
3570
  if (!fullPath.startsWith(projectDir)) {
3088
3571
  reply.code(403);
3089
3572
  return { error: "Path traversal not allowed" };
3090
3573
  }
3091
3574
  try {
3092
- const content = fs8.readFileSync(fullPath, "utf-8");
3575
+ const content = fs9.readFileSync(fullPath, "utf-8");
3093
3576
  return { content, path: filePath };
3094
3577
  } catch {
3095
3578
  reply.code(404);
@@ -3102,20 +3585,20 @@ async function startServer(port, projectDir) {
3102
3585
  reply.code(400);
3103
3586
  return { error: "Missing path parameter" };
3104
3587
  }
3105
- if (filePath.includes("..") || path9.isAbsolute(filePath)) {
3588
+ if (filePath.includes("..") || path10.isAbsolute(filePath)) {
3106
3589
  reply.code(403);
3107
3590
  return { error: "Invalid path" };
3108
3591
  }
3109
- const fullPath = path9.resolve(projectDir, filePath);
3592
+ const fullPath = path10.resolve(projectDir, filePath);
3110
3593
  if (!fullPath.startsWith(projectDir)) {
3111
3594
  reply.code(403);
3112
3595
  return { error: "Path traversal not allowed" };
3113
3596
  }
3114
- if (!fs8.existsSync(fullPath)) {
3597
+ if (!fs9.existsSync(fullPath)) {
3115
3598
  reply.code(404);
3116
3599
  return { error: "File not found" };
3117
3600
  }
3118
- const ext = path9.extname(fullPath).toLowerCase();
3601
+ const ext = path10.extname(fullPath).toLowerCase();
3119
3602
  const mimeMap = {
3120
3603
  ".png": "image/png",
3121
3604
  ".jpg": "image/jpeg",
@@ -3129,14 +3612,14 @@ async function startServer(port, projectDir) {
3129
3612
  ".svg": "image/svg+xml"
3130
3613
  };
3131
3614
  const mime = mimeMap[ext] || "application/octet-stream";
3132
- const stream = fs8.createReadStream(fullPath);
3615
+ const stream = fs9.createReadStream(fullPath);
3133
3616
  reply.type(mime);
3134
3617
  return reply.send(stream);
3135
3618
  });
3136
- const notesPath = path9.join(projectDir, ".nexus", "notes.yaml");
3619
+ const notesPath = path10.join(projectDir, ".nexus", "notes.yaml");
3137
3620
  fastify.get("/api/notes", async () => {
3138
3621
  try {
3139
- const raw = fs8.readFileSync(notesPath, "utf-8");
3622
+ const raw = fs9.readFileSync(notesPath, "utf-8");
3140
3623
  const data = yaml3.load(raw);
3141
3624
  return { notes: data?.notes || [] };
3142
3625
  } catch {
@@ -3146,20 +3629,20 @@ async function startServer(port, projectDir) {
3146
3629
  fastify.put("/api/notes", async (request, reply) => {
3147
3630
  try {
3148
3631
  const { notes } = request.body;
3149
- fs8.mkdirSync(path9.dirname(notesPath), { recursive: true });
3150
- fs8.writeFileSync(notesPath, yaml3.dump({ notes }, { lineWidth: -1 }), "utf-8");
3632
+ fs9.mkdirSync(path10.dirname(notesPath), { recursive: true });
3633
+ fs9.writeFileSync(notesPath, yaml3.dump({ notes }, { lineWidth: -1 }), "utf-8");
3151
3634
  return { success: true };
3152
3635
  } catch (err) {
3153
3636
  reply.code(400);
3154
3637
  return { error: "Failed to save notes" };
3155
3638
  }
3156
3639
  });
3157
- const webDistPath = path9.resolve(__dirname, "../../web/dist");
3158
- if (!fs8.existsSync(webDistPath)) {
3640
+ const webDistPath = path10.resolve(__dirname, "../../web/dist");
3641
+ if (!fs9.existsSync(webDistPath)) {
3159
3642
  console.warn(` [Warning] Frontend not found at ${webDistPath}`);
3160
3643
  console.warn(` Run 'pnpm run build:web' to build the frontend, or use dev mode.`);
3161
3644
  }
3162
- if (fs8.existsSync(webDistPath)) {
3645
+ if (fs9.existsSync(webDistPath)) {
3163
3646
  await fastify.register(fastifyStatic, {
3164
3647
  root: webDistPath,
3165
3648
  prefix: "/"
@@ -3237,14 +3720,14 @@ function findProjectRoot(startDir) {
3237
3720
  const home = process.env.HOME || process.env.USERPROFILE || "";
3238
3721
  let dir = startDir;
3239
3722
  let bestMatch = startDir;
3240
- while (dir !== path10.dirname(dir)) {
3241
- if (fs9.existsSync(path10.join(dir, "pnpm-workspace.yaml"))) {
3723
+ while (dir !== path11.dirname(dir)) {
3724
+ if (fs10.existsSync(path11.join(dir, "pnpm-workspace.yaml"))) {
3242
3725
  return dir;
3243
3726
  }
3244
- if (fs9.existsSync(path10.join(dir, ".git")) || fs9.existsSync(path10.join(dir, ".nexus"))) {
3727
+ if (fs10.existsSync(path11.join(dir, ".git")) || fs10.existsSync(path11.join(dir, ".nexus"))) {
3245
3728
  bestMatch = dir;
3246
3729
  }
3247
- const parent = path10.dirname(dir);
3730
+ const parent = path11.dirname(dir);
3248
3731
  if (home && parent === home && dir !== startDir) break;
3249
3732
  dir = parent;
3250
3733
  }
@@ -3256,15 +3739,15 @@ function findProjectRoot(startDir) {
3256
3739
  }
3257
3740
  function resolveProjectDir(dirArg) {
3258
3741
  if (process.env.NEXUS_PROJECT_DIR) {
3259
- return path10.resolve(process.env.NEXUS_PROJECT_DIR);
3742
+ return path11.resolve(process.env.NEXUS_PROJECT_DIR);
3260
3743
  }
3261
3744
  if (dirArg) {
3262
- const resolved = path10.resolve(dirArg);
3263
- if (!fs9.existsSync(resolved)) {
3745
+ const resolved = path11.resolve(dirArg);
3746
+ if (!fs10.existsSync(resolved)) {
3264
3747
  console.error(`Error: directory does not exist: ${resolved}`);
3265
3748
  process.exit(1);
3266
3749
  }
3267
- if (!fs9.statSync(resolved).isDirectory()) {
3750
+ if (!fs10.statSync(resolved).isDirectory()) {
3268
3751
  console.error(`Error: not a directory: ${resolved}`);
3269
3752
  process.exit(1);
3270
3753
  }