seshmux 0.1.4 → 0.1.6

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 (27) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +6 -6
  3. package/.next/standalone/.next/build-manifest.json +5 -5
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/page.js +3 -3
  7. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  9. package/.next/standalone/.next/server/pages/500.html +1 -1
  10. package/.next/standalone/.next/server/pages-manifest.json +1 -1
  11. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  12. package/.next/standalone/.next/static/chunks/app/page-6e6c6148c4d1c925.js +1 -0
  13. package/.next/standalone/.next/static/chunks/{webpack-f60e08036f4baea4.js → webpack-452b158709859445.js} +1 -1
  14. package/.next/standalone/.next/static/css/{87ea7bb93e0b1326.css → a41d278d539911dd.css} +1 -1
  15. package/.next/standalone/.next/static/css/{bc53104caace13e7.css → e4d6e5dc25191e2a.css} +1 -1
  16. package/.next/standalone/package.json +1 -1
  17. package/.next/standalone/seshmux-server.js +394 -109
  18. package/README.md +16 -0
  19. package/bin/seshmux.js +295 -36
  20. package/daemon/ensure.js +55 -0
  21. package/daemon/holder.js +248 -0
  22. package/daemon/index.js +4 -1
  23. package/daemon/pty-manager.js +341 -29
  24. package/package.json +1 -1
  25. package/.next/standalone/.next/static/chunks/app/page-cd707a4bc18d9497.js +0 -1
  26. /package/.next/standalone/.next/static/{Xjuy0Fy4Y1goqBeK0eRol → z5Dh_tm8C81dOurk7OuAw}/_buildManifest.js +0 -0
  27. /package/.next/standalone/.next/static/{Xjuy0Fy4Y1goqBeK0eRol → z5Dh_tm8C81dOurk7OuAw}/_ssgManifest.js +0 -0
@@ -372,7 +372,9 @@ var init_needs_input = __esm({
372
372
  var workspaces_exports = {};
373
373
  __export(workspaces_exports, {
374
374
  create: () => create,
375
+ defaultBranch: () => defaultBranch,
375
376
  dirtyCount: () => dirtyCount,
377
+ git: () => git,
376
378
  list: () => list,
377
379
  listAll: () => listAll,
378
380
  reconcile: () => reconcile,
@@ -479,13 +481,24 @@ async function isGitRepo(repoPath) {
479
481
  }
480
482
  }
481
483
  async function defaultBranch(repoPath) {
484
+ const localExists = async (name) => {
485
+ try {
486
+ await git(repoPath, ["show-ref", "--verify", "--quiet", `refs/heads/${name}`]);
487
+ return true;
488
+ } catch {
489
+ return false;
490
+ }
491
+ };
482
492
  try {
483
493
  const out2 = await git(repoPath, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
484
494
  const ref = out2.trim().replace(/^origin\//, "");
485
- if (ref) return ref;
495
+ if (ref) return await localExists(ref) ? ref : `origin/${ref}`;
486
496
  } catch {
487
497
  }
488
- const out = await git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"]);
498
+ for (const name of ["main", "master"]) {
499
+ if (await localExists(name)) return name;
500
+ }
501
+ const out = await git(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "");
489
502
  return out.trim() || "main";
490
503
  }
491
504
  function create(repoPath) {
@@ -753,10 +766,10 @@ function encodeProjectId(cwd) {
753
766
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
754
767
  }
755
768
  function decodeProjectDir(dir) {
756
- const path9 = dir.replace(/-/g, "/");
757
- const segments = path9.split("/").filter(Boolean);
769
+ const path10 = dir.replace(/-/g, "/");
770
+ const segments = path10.split("/").filter(Boolean);
758
771
  const name = segments[segments.length - 1] ?? dir;
759
- return { path: path9, name };
772
+ return { path: path10, name };
760
773
  }
761
774
  function firstUserText(content) {
762
775
  if (typeof content === "string") return content;
@@ -908,28 +921,28 @@ async function computeRootScan(root, provider) {
908
921
  if (!newestFile || s.st.mtimeMs > newestFile.mtime) newestFile = { path: s.fp, mtime: s.st.mtimeMs };
909
922
  }
910
923
  if (createdAt === Number.MAX_SAFE_INTEGER) createdAt = updatedAt;
911
- let path9 = decoded.path;
924
+ let path10 = decoded.path;
912
925
  let name = decoded.name;
913
926
  if (newestFile) {
914
927
  const head = await readHead(newestFile.path, Math.floor(newestFile.mtime));
915
928
  if (head.cwd) {
916
- path9 = head.cwd;
929
+ path10 = head.cwd;
917
930
  name = head.cwd.split("/").filter(Boolean).pop() || decoded.name;
918
931
  dirCwds.push({ dirPath, cwd: head.cwd });
919
932
  }
920
933
  }
921
934
  let missing = false;
922
935
  try {
923
- missing = !(await (0, import_promises2.stat)(path9)).isDirectory();
936
+ missing = !(await (0, import_promises2.stat)(path10)).isDirectory();
924
937
  } catch {
925
938
  missing = true;
926
939
  }
927
- const parentPath = parentOf.get(path9);
940
+ const parentPath = parentOf.get(path10);
928
941
  return {
929
942
  id: d.name,
930
943
  provider,
931
944
  name,
932
- path: parentPath ?? path9,
945
+ path: parentPath ?? path10,
933
946
  sessionCount: files.length,
934
947
  createdAt,
935
948
  updatedAt,
@@ -1128,8 +1141,8 @@ function rgSearch(root, q, limit) {
1128
1141
  continue;
1129
1142
  }
1130
1143
  if (ev.type !== "match") continue;
1131
- const path9 = ev.data?.path?.text ?? "";
1132
- const m = path9.match(/([^/]+)\.jsonl$/);
1144
+ const path10 = ev.data?.path?.text ?? "";
1145
+ const m = path10.match(/([^/]+)\.jsonl$/);
1133
1146
  if (!m) continue;
1134
1147
  out.push({ sessionId: m[1], line: ev.data?.lines?.text ?? "" });
1135
1148
  if (out.length >= limit) break;
@@ -1600,9 +1613,9 @@ function nodeFromSources(agentId, s) {
1600
1613
  jsonlPath
1601
1614
  };
1602
1615
  }
1603
- async function readJsonSafe(path9) {
1616
+ async function readJsonSafe(path10) {
1604
1617
  try {
1605
- return JSON.parse(await (0, import_promises6.readFile)(path9, "utf8"));
1618
+ return JSON.parse(await (0, import_promises6.readFile)(path10, "utf8"));
1606
1619
  } catch {
1607
1620
  return null;
1608
1621
  }
@@ -1985,20 +1998,20 @@ function ourVersion(command) {
1985
1998
  const m = command.match(new RegExp(`${MARKER}_V(\\d+)=1`));
1986
1999
  return m ? Number(m[1]) : null;
1987
2000
  }
1988
- async function readJson2(path9) {
2001
+ async function readJson2(path10) {
1989
2002
  try {
1990
- const raw = await (0, import_promises8.readFile)(path9, "utf8");
2003
+ const raw = await (0, import_promises8.readFile)(path10, "utf8");
1991
2004
  return JSON.parse(raw);
1992
2005
  } catch (err) {
1993
2006
  if (err instanceof Error && err.code === "ENOENT") return {};
1994
2007
  throw new Error(`settings.json is not valid JSON: ${err.message}`);
1995
2008
  }
1996
2009
  }
1997
- async function atomicWrite(path9, content) {
1998
- await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(path9), { recursive: true });
1999
- const tmp = (0, import_node_path10.join)((0, import_node_path10.dirname)(path9), `.${(0, import_node_crypto3.randomBytes)(6).toString("hex")}.tmp`);
2010
+ async function atomicWrite(path10, content) {
2011
+ await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(path10), { recursive: true });
2012
+ const tmp = (0, import_node_path10.join)((0, import_node_path10.dirname)(path10), `.${(0, import_node_crypto3.randomBytes)(6).toString("hex")}.tmp`);
2000
2013
  await (0, import_promises8.writeFile)(tmp, content, "utf8");
2001
- await (0, import_promises8.rename)(tmp, path9);
2014
+ await (0, import_promises8.rename)(tmp, path10);
2002
2015
  }
2003
2016
  function asHooksObject(cfg) {
2004
2017
  const h = cfg.hooks;
@@ -2687,15 +2700,15 @@ var init_codex2 = __esm({
2687
2700
  const projects = [];
2688
2701
  for (const [id, { cwd, count, createdAt, updatedAt }] of byProject) {
2689
2702
  const { name } = decodeProjectDir(id);
2690
- const path9 = cwd || id;
2703
+ const path10 = cwd || id;
2691
2704
  const displayName = cwd ? cwd.split("/").filter(Boolean).pop() || name : name;
2692
2705
  let missing = false;
2693
2706
  try {
2694
- missing = !(await (0, import_promises11.stat)(path9)).isDirectory();
2707
+ missing = !(await (0, import_promises11.stat)(path10)).isDirectory();
2695
2708
  } catch {
2696
2709
  missing = true;
2697
2710
  }
2698
- projects.push({ id, provider: this.id, name: displayName, path: path9, sessionCount: count, createdAt, updatedAt, missing });
2711
+ projects.push({ id, provider: this.id, name: displayName, path: path10, sessionCount: count, createdAt, updatedAt, missing });
2699
2712
  }
2700
2713
  projects.sort((a, b) => a.name.localeCompare(b.name));
2701
2714
  return projects;
@@ -3032,8 +3045,8 @@ function startWatching(deps) {
3032
3045
  awaitWriteFinish: { stabilityThreshold: 250 },
3033
3046
  ignored: (p, stats) => !!stats?.isFile() && !p.endsWith(".jsonl")
3034
3047
  });
3035
- w.on("add", (path9) => schedule(path9, provider, config, "add"));
3036
- w.on("change", (path9) => schedule(path9, provider, config, "change"));
3048
+ w.on("add", (path10) => schedule(path10, provider, config, "add"));
3049
+ w.on("change", (path10) => schedule(path10, provider, config, "change"));
3037
3050
  w.on("error", (err) => console.error(`[watch] ${provider} watcher error:`, err));
3038
3051
  return w;
3039
3052
  });
@@ -3486,8 +3499,8 @@ function defaultRun(cmd, args) {
3486
3499
  async function which(run, bin) {
3487
3500
  try {
3488
3501
  const { stdout } = await run("which", [bin]);
3489
- const path9 = stdout.trim().split("\n")[0];
3490
- return path9 || void 0;
3502
+ const path10 = stdout.trim().split("\n")[0];
3503
+ return path10 || void 0;
3491
3504
  } catch {
3492
3505
  return void 0;
3493
3506
  }
@@ -3501,10 +3514,10 @@ async function version(run, bin, flag = "--version") {
3501
3514
  }
3502
3515
  }
3503
3516
  async function detectTool(run, bin, versionFlag = "--version") {
3504
- const path9 = await which(run, bin);
3505
- if (!path9) return { found: false };
3517
+ const path10 = await which(run, bin);
3518
+ if (!path10) return { found: false };
3506
3519
  const v = await version(run, bin, versionFlag);
3507
- return { found: true, path: path9, ...v ? { version: v } : {} };
3520
+ return { found: true, path: path10, ...v ? { version: v } : {} };
3508
3521
  }
3509
3522
  async function detectEnv(deps) {
3510
3523
  const run = deps?.run ?? defaultRun;
@@ -3517,8 +3530,8 @@ async function detectEnv(deps) {
3517
3530
  async function agentEnv(id) {
3518
3531
  const provider = byId.get(id);
3519
3532
  const bin = provider.commands.fresh("")[0];
3520
- const path9 = await which(run, bin);
3521
- const v = path9 ? await version(run, bin) : void 0;
3533
+ const path10 = await which(run, bin);
3534
+ const v = path10 ? await version(run, bin) : void 0;
3522
3535
  let store = { found: false, projects: 0, bytes: 0 };
3523
3536
  try {
3524
3537
  const d = await provider.detect();
@@ -3526,7 +3539,7 @@ async function detectEnv(deps) {
3526
3539
  store = { found: projects > 0, projects, bytes: d.store?.bytes ?? 0 };
3527
3540
  } catch {
3528
3541
  }
3529
- return { found: !!path9, ...path9 ? { path: path9 } : {}, ...v ? { version: v } : {}, store };
3542
+ return { found: !!path10, ...path10 ? { path: path10 } : {}, ...v ? { version: v } : {}, store };
3530
3543
  }
3531
3544
  const [claude, codex, tmux, rg] = await Promise.all([
3532
3545
  agentEnv("claude"),
@@ -4125,24 +4138,24 @@ function bridgeServerConfig() {
4125
4138
  if (process.env.SESHMUX_CONFIG_DIR) cfg.env = { SESHMUX_CONFIG_DIR: process.env.SESHMUX_CONFIG_DIR };
4126
4139
  return cfg;
4127
4140
  }
4128
- async function atomicWrite2(path9, content) {
4129
- await (0, import_promises12.mkdir)((0, import_node_path16.dirname)(path9), { recursive: true });
4130
- const tmp = (0, import_node_path16.join)((0, import_node_path16.dirname)(path9), `.${(0, import_node_crypto5.randomBytes)(6).toString("hex")}.tmp`);
4141
+ async function atomicWrite2(path10, content) {
4142
+ await (0, import_promises12.mkdir)((0, import_node_path16.dirname)(path10), { recursive: true });
4143
+ const tmp = (0, import_node_path16.join)((0, import_node_path16.dirname)(path10), `.${(0, import_node_crypto5.randomBytes)(6).toString("hex")}.tmp`);
4131
4144
  await (0, import_promises12.writeFile)(tmp, content, "utf8");
4132
- await (0, import_promises12.rename)(tmp, path9);
4145
+ await (0, import_promises12.rename)(tmp, path10);
4133
4146
  }
4134
- async function readJson3(path9) {
4147
+ async function readJson3(path10) {
4135
4148
  try {
4136
- const raw = await (0, import_promises12.readFile)(path9, "utf8");
4149
+ const raw = await (0, import_promises12.readFile)(path10, "utf8");
4137
4150
  return JSON.parse(raw);
4138
4151
  } catch {
4139
4152
  return {};
4140
4153
  }
4141
4154
  }
4142
- async function registerClaude(path9) {
4155
+ async function registerClaude(path10) {
4143
4156
  let cfg = {};
4144
4157
  try {
4145
- const parsed = JSON.parse(await (0, import_promises12.readFile)(path9, "utf8"));
4158
+ const parsed = JSON.parse(await (0, import_promises12.readFile)(path10, "utf8"));
4146
4159
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
4147
4160
  throw new Error("top-level JSON is not an object");
4148
4161
  }
@@ -4150,17 +4163,17 @@ async function registerClaude(path9) {
4150
4163
  } catch (e) {
4151
4164
  if (e.code !== "ENOENT") {
4152
4165
  throw new Error(
4153
- `refusing to modify ${path9}: it exists but is not valid JSON (${e.message}) \u2014 fix or move it, then re-register (nothing was written)`
4166
+ `refusing to modify ${path10}: it exists but is not valid JSON (${e.message}) \u2014 fix or move it, then re-register (nothing was written)`
4154
4167
  );
4155
4168
  }
4156
4169
  }
4157
4170
  const mcpServers = cfg.mcpServers && typeof cfg.mcpServers === "object" ? cfg.mcpServers : {};
4158
4171
  mcpServers["seshmux-bridge"] = bridgeServerConfig();
4159
4172
  cfg.mcpServers = mcpServers;
4160
- await atomicWrite2(path9, JSON.stringify(cfg, null, 2) + "\n");
4173
+ await atomicWrite2(path10, JSON.stringify(cfg, null, 2) + "\n");
4161
4174
  }
4162
- async function isClaudeRegistered(path9) {
4163
- const cfg = await readJson3(path9);
4175
+ async function isClaudeRegistered(path10) {
4176
+ const cfg = await readJson3(path10);
4164
4177
  const mcpServers = cfg.mcpServers;
4165
4178
  return !!(mcpServers && typeof mcpServers === "object" && "seshmux-bridge" in mcpServers);
4166
4179
  }
@@ -4180,10 +4193,10 @@ function codexTomlBlock() {
4180
4193
  function codexHasBridgeBlock(raw) {
4181
4194
  return /^\[mcp_servers\.seshmux-bridge\]/m.test(raw);
4182
4195
  }
4183
- async function registerCodex(path9) {
4196
+ async function registerCodex(path10) {
4184
4197
  let raw = "";
4185
4198
  try {
4186
- raw = await (0, import_promises12.readFile)(path9, "utf8");
4199
+ raw = await (0, import_promises12.readFile)(path10, "utf8");
4187
4200
  } catch {
4188
4201
  raw = "";
4189
4202
  }
@@ -4192,17 +4205,17 @@ async function registerCodex(path9) {
4192
4205
  /\[mcp_servers\.seshmux-bridge\][\s\S]*?(?=\n\[|$)/,
4193
4206
  codexTomlBlock().trimEnd() + "\n"
4194
4207
  );
4195
- if (replaced !== raw) await atomicWrite2(path9, replaced);
4208
+ if (replaced !== raw) await atomicWrite2(path10, replaced);
4196
4209
  return;
4197
4210
  }
4198
4211
  const needsSep = raw.length > 0 && !raw.endsWith("\n\n");
4199
4212
  const sep = raw.length === 0 ? "" : needsSep ? raw.endsWith("\n") ? "\n" : "\n\n" : "";
4200
4213
  const next2 = raw + sep + codexTomlBlock();
4201
- await atomicWrite2(path9, next2);
4214
+ await atomicWrite2(path10, next2);
4202
4215
  }
4203
- async function isCodexRegistered(path9) {
4216
+ async function isCodexRegistered(path10) {
4204
4217
  try {
4205
- const raw = await (0, import_promises12.readFile)(path9, "utf8");
4218
+ const raw = await (0, import_promises12.readFile)(path10, "utf8");
4206
4219
  return codexHasBridgeBlock(raw);
4207
4220
  } catch {
4208
4221
  return false;
@@ -4273,28 +4286,28 @@ __export(bridge_exports, {
4273
4286
  function providerName2(p) {
4274
4287
  return p === "codex" ? "Codex" : "Claude";
4275
4288
  }
4276
- async function isDir(path9) {
4289
+ async function isDir(path10) {
4277
4290
  try {
4278
- return (await (0, import_promises13.stat)(path9)).isDirectory();
4291
+ return (await (0, import_promises13.stat)(path10)).isDirectory();
4279
4292
  } catch {
4280
4293
  return false;
4281
4294
  }
4282
4295
  }
4283
- async function atomicWrite3(path9, content) {
4284
- const dir = (0, import_node_path17.join)(path9, "..");
4296
+ async function atomicWrite3(path10, content) {
4297
+ const dir = (0, import_node_path17.join)(path10, "..");
4285
4298
  await (0, import_promises13.mkdir)(dir, { recursive: true });
4286
4299
  const tmp = (0, import_node_path17.join)(dir, `.${(0, import_node_crypto6.randomBytes)(6).toString("hex")}.tmp`);
4287
4300
  await (0, import_promises13.writeFile)(tmp, content, "utf8");
4288
- await (0, import_promises13.rename)(tmp, path9);
4301
+ await (0, import_promises13.rename)(tmp, path10);
4289
4302
  }
4290
4303
  async function appendScratchpad(repo, text) {
4291
- const path9 = (0, import_node_path17.join)(repo, ".seshmux", "handoff.md");
4304
+ const path10 = (0, import_node_path17.join)(repo, ".seshmux", "handoff.md");
4292
4305
  let existing = "";
4293
4306
  try {
4294
- existing = await (0, import_promises13.readFile)(path9, "utf8");
4307
+ existing = await (0, import_promises13.readFile)(path10, "utf8");
4295
4308
  } catch {
4296
4309
  }
4297
- await atomicWrite3(path9, existing + (existing && !existing.endsWith("\n") ? "\n" : "") + text + "\n");
4310
+ await atomicWrite3(path10, existing + (existing && !existing.endsWith("\n") ? "\n" : "") + text + "\n");
4298
4311
  }
4299
4312
  async function defaultResolveProvider(projectId, sessionId) {
4300
4313
  const providers = await getProviders();
@@ -4341,7 +4354,7 @@ async function bridgeRoutes(f, deps) {
4341
4354
  const resolveProvider = deps.resolveSessionProvider ?? defaultResolveProvider;
4342
4355
  const brief = deps.composeBrief ? (p, s) => deps.composeBrief(p, s) : (p, s) => composeBrief(p, s);
4343
4356
  const review = deps.composeDiffReview ? (p, s) => deps.composeDiffReview(p, s) : (p, s, repo) => composeDiffReview(p, s, {}, repo);
4344
- const planoff = deps.runPlanoff ?? ((path9, task) => runPlanoff(path9, task));
4357
+ const planoff = deps.runPlanoff ?? ((path10, task) => runPlanoff(path10, task));
4345
4358
  const registerBridge2 = deps.registerBridge ?? (() => registerBridge());
4346
4359
  const bridgeStatus2 = deps.bridgeStatus ?? (() => bridgeStatus());
4347
4360
  const listLivePtys = deps.listLivePtys ?? defaultListLivePtys;
@@ -4535,8 +4548,8 @@ var projects_exports = {};
4535
4548
  __export(projects_exports, {
4536
4549
  default: () => projectsRoutes
4537
4550
  });
4538
- function isTmpProject(path9) {
4539
- const p = path9.endsWith("/") ? path9 : path9 + "/";
4551
+ function isTmpProject(path10) {
4552
+ const p = path10.endsWith("/") ? path10 : path10 + "/";
4540
4553
  return TMP_ROOTS.some((root) => p.startsWith(root));
4541
4554
  }
4542
4555
  async function projectsRoutes(f) {
@@ -4835,14 +4848,17 @@ var env_exports = {};
4835
4848
  __export(env_exports, {
4836
4849
  default: () => envRoutes
4837
4850
  });
4838
- async function realDaemonVersion() {
4851
+ async function realDaemonInfo() {
4839
4852
  let conn = null;
4840
4853
  try {
4841
4854
  conn = await dial();
4842
- const { version: version2 } = await conn.hello();
4843
- return version2 || null;
4855
+ const [{ version: version2 }, { ptys }] = await Promise.all([conn.hello(), conn.list()]);
4856
+ return {
4857
+ version: version2 || null,
4858
+ plainPtys: ptys.filter((p) => p.alive !== false && !p.tmuxName).length
4859
+ };
4844
4860
  } catch {
4845
- return null;
4861
+ return { version: null, plainPtys: 0 };
4846
4862
  } finally {
4847
4863
  conn?.close();
4848
4864
  }
@@ -4857,15 +4873,16 @@ function commandPreview(commands) {
4857
4873
  }
4858
4874
  async function envRoutes(f, deps = {}) {
4859
4875
  const bridgeStatus2 = deps.bridgeStatus ?? (() => bridgeStatus());
4860
- const daemonVersion = deps.daemonVersion ?? realDaemonVersion;
4876
+ const daemonInfo = deps.daemonInfo ?? realDaemonInfo;
4861
4877
  const serverVersion = deps.serverVersion ?? (() => process.env.SESHMUX_VERSION ?? "");
4862
4878
  f.get("/api/env", async () => {
4863
- const [env, bridge, providers, dVersion] = await Promise.all([
4879
+ const [env, bridge, providers, dInfo] = await Promise.all([
4864
4880
  detectEnv(),
4865
4881
  bridgeStatus2().catch(() => ({ claude: false, codex: false })),
4866
4882
  getProviders(),
4867
- daemonVersion().catch(() => null)
4883
+ daemonInfo().catch(() => ({ version: null, plainPtys: 0 }))
4868
4884
  ]);
4885
+ const dVersion = dInfo.version;
4869
4886
  const sVersion = serverVersion();
4870
4887
  const commands = {};
4871
4888
  const teams = {};
@@ -4881,12 +4898,14 @@ async function envRoutes(f, deps = {}) {
4881
4898
  },
4882
4899
  commands,
4883
4900
  teams,
4884
- // The daemon outlives server updates by design tell the UI when it's older than us
4885
- // so the user can be nudged to fully restart. Never auto-restart: that kills live PTYs.
4901
+ // The daemon outlives server updates by design, so it can be older than us. The NEXT update
4902
+ // upgrades it automatically unless plain (non-tmux) PTYs are live, which a restart would
4903
+ // kill. plainPtys is what lets the UI tell those two states apart.
4886
4904
  daemon: {
4887
4905
  version: dVersion,
4888
4906
  serverVersion: sVersion || null,
4889
- stale: isDaemonStale(dVersion, sVersion)
4907
+ stale: isDaemonStale(dVersion, sVersion),
4908
+ plainPtys: dInfo.plainPtys
4890
4909
  }
4891
4910
  };
4892
4911
  });
@@ -5250,10 +5269,10 @@ var workspaces_exports2 = {};
5250
5269
  __export(workspaces_exports2, {
5251
5270
  default: () => workspaceRoutes
5252
5271
  });
5253
- async function isDir2(path9) {
5272
+ async function isDir2(path10) {
5254
5273
  try {
5255
- const { stat: stat10 } = await import("node:fs/promises");
5256
- return (await stat10(path9)).isDirectory();
5274
+ const { stat: stat11 } = await import("node:fs/promises");
5275
+ return (await stat11(path10)).isDirectory();
5257
5276
  } catch {
5258
5277
  return false;
5259
5278
  }
@@ -5367,6 +5386,271 @@ var init_workspaces2 = __esm({
5367
5386
  }
5368
5387
  });
5369
5388
 
5389
+ // server/lib/git-stats.ts
5390
+ function parseNumstat(out) {
5391
+ if (out.includes("\0")) return parseNumstatZ(out);
5392
+ const rows = [];
5393
+ for (const line of out.split("\n")) {
5394
+ if (!line.trim()) continue;
5395
+ const [a, r, ...rest] = line.split(" ");
5396
+ if (rest.length === 0) continue;
5397
+ rows.push({ path: rest.join(" "), added: num(a), removed: num(r) });
5398
+ }
5399
+ return rows;
5400
+ }
5401
+ function parseNumstatZ(out) {
5402
+ const tokens = out.split("\0");
5403
+ const rows = [];
5404
+ for (let i = 0; i < tokens.length; i++) {
5405
+ const t = tokens[i];
5406
+ if (!t) continue;
5407
+ const tab1 = t.indexOf(" ");
5408
+ const tab2 = t.indexOf(" ", tab1 + 1);
5409
+ if (tab1 === -1 || tab2 === -1) continue;
5410
+ const added = num(t.slice(0, tab1));
5411
+ const removed = num(t.slice(tab1 + 1, tab2));
5412
+ const rest = t.slice(tab2 + 1);
5413
+ if (rest === "") {
5414
+ const oldPath = tokens[++i];
5415
+ const newPath = tokens[++i];
5416
+ if (oldPath && newPath) rows.push({ path: newPath, added, removed, oldPath });
5417
+ } else {
5418
+ rows.push({ path: rest, added, removed });
5419
+ }
5420
+ }
5421
+ return rows;
5422
+ }
5423
+ async function resolveMergeBase(dir, baseRef) {
5424
+ if (!baseRef) return "HEAD";
5425
+ try {
5426
+ return (await git(dir, ["merge-base", baseRef, "HEAD"])).trim() || "HEAD";
5427
+ } catch {
5428
+ return "HEAD";
5429
+ }
5430
+ }
5431
+ async function untrackedLines(abs) {
5432
+ try {
5433
+ const st = await (0, import_promises16.stat)(abs);
5434
+ const hit = lineCache.get(abs);
5435
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) return hit;
5436
+ let lines = 0;
5437
+ let read = 0;
5438
+ let lastByte = 10;
5439
+ let binary = false;
5440
+ if (st.size > 0) {
5441
+ const fh = await (0, import_promises16.open)(abs, "r");
5442
+ try {
5443
+ const buf = Buffer.alloc(64 * 1024);
5444
+ const limit = Math.min(st.size, LINE_COUNT_CAP);
5445
+ while (read < limit) {
5446
+ const { bytesRead } = await fh.read(buf, 0, buf.length, read);
5447
+ if (bytesRead <= 0) break;
5448
+ if (read === 0 && buf.subarray(0, Math.min(bytesRead, 8192)).includes(0)) {
5449
+ binary = true;
5450
+ break;
5451
+ }
5452
+ for (let i = 0; i < bytesRead; i++) if (buf[i] === 10) lines++;
5453
+ lastByte = buf[bytesRead - 1];
5454
+ read += bytesRead;
5455
+ }
5456
+ } finally {
5457
+ await fh.close();
5458
+ }
5459
+ }
5460
+ const result = {
5461
+ mtimeMs: st.mtimeMs,
5462
+ size: st.size,
5463
+ lines: binary || st.size === 0 ? 0 : lines + (lastByte !== 10 && read >= st.size ? 1 : 0),
5464
+ approx: !binary && st.size > LINE_COUNT_CAP
5465
+ };
5466
+ if (lineCache.size > 1e4) lineCache.clear();
5467
+ lineCache.set(abs, result);
5468
+ return result;
5469
+ } catch {
5470
+ return { lines: 0, approx: false };
5471
+ }
5472
+ }
5473
+ async function fileDiff(dir, baseRef, relPath) {
5474
+ const empty = { diff: "", truncated: false };
5475
+ try {
5476
+ const abs = import_node_path20.default.resolve(dir, relPath);
5477
+ if (!abs.startsWith(import_node_path20.default.resolve(dir) + import_node_path20.default.sep)) return empty;
5478
+ const base = await resolveMergeBase(dir, baseRef);
5479
+ const tracked = await git(dir, ["diff", "--no-renames", base, "--", relPath]);
5480
+ if (tracked) return truncateDiff(tracked);
5481
+ const untracked = splitZ(await git(dir, ["ls-files", "-o", "--exclude-standard", "-z", "--", relPath]));
5482
+ if (untracked.length === 0) return empty;
5483
+ try {
5484
+ return truncateDiff(await git(dir, ["diff", "--no-index", "--", "/dev/null", abs]));
5485
+ } catch (e) {
5486
+ const out = e.stdout;
5487
+ return typeof out === "string" ? truncateDiff(out) : empty;
5488
+ }
5489
+ } catch {
5490
+ return empty;
5491
+ }
5492
+ }
5493
+ function truncateDiff(diff) {
5494
+ const lines = diff.split("\n");
5495
+ if (lines.length <= MAX_DIFF_LINES) return { diff, truncated: false };
5496
+ return { diff: lines.slice(0, MAX_DIFF_LINES).join("\n"), truncated: true };
5497
+ }
5498
+ function changes(dir, baseRef, wantTree) {
5499
+ const key = `${dir}\0${baseRef ?? ""}\0${wantTree}`;
5500
+ const hit = changesMemo.get(key);
5501
+ if (hit && Date.now() - hit.at < CHANGES_TTL_MS) return hit.promise;
5502
+ const promise = computeChanges(dir, baseRef, wantTree);
5503
+ if (changesMemo.size > 200) changesMemo.clear();
5504
+ changesMemo.set(key, { at: Date.now(), promise });
5505
+ void promise.then((res) => {
5506
+ if (res.degraded && changesMemo.get(key)?.promise === promise) changesMemo.delete(key);
5507
+ });
5508
+ return promise;
5509
+ }
5510
+ async function computeChanges(dir, baseRef, wantTree) {
5511
+ try {
5512
+ const base = await resolveMergeBase(dir, baseRef);
5513
+ const [numstatOut, nameStatusOut, untrackedOut] = await Promise.all([
5514
+ git(dir, ["diff", "--numstat", "-z", base]),
5515
+ git(dir, ["diff", "--name-status", "-z", base]),
5516
+ git(dir, ["ls-files", "-o", "--exclude-standard", "-z"])
5517
+ ]);
5518
+ const statusTokens = splitZ(nameStatusOut);
5519
+ const statusByPath = /* @__PURE__ */ new Map();
5520
+ for (let i = 0; i < statusTokens.length; ) {
5521
+ const code = statusTokens[i++]?.charAt(0);
5522
+ if (!code) continue;
5523
+ if (code === "R" || code === "C") {
5524
+ i++;
5525
+ const newPath = statusTokens[i++];
5526
+ if (newPath) statusByPath.set(newPath, code);
5527
+ } else {
5528
+ const p = statusTokens[i++];
5529
+ if (p) statusByPath.set(p, code);
5530
+ }
5531
+ }
5532
+ const files = [];
5533
+ for (const f of parseNumstat(numstatOut)) {
5534
+ if (f.oldPath) {
5535
+ const code = statusByPath.get(f.path) ?? "R";
5536
+ files.push({ path: f.path, added: f.added, removed: f.removed, status: code });
5537
+ if (code !== "C") files.push({ path: f.oldPath, added: 0, removed: 0, status: "D" });
5538
+ } else {
5539
+ files.push({ path: f.path, added: f.added, removed: f.removed, status: statusByPath.get(f.path) ?? "M" });
5540
+ }
5541
+ }
5542
+ const untracked = splitZ(untrackedOut);
5543
+ for (let i = 0; i < untracked.length; i += 16) {
5544
+ const batch = await Promise.all(
5545
+ untracked.slice(i, i + 16).map(async (rel) => {
5546
+ const { lines, approx } = await untrackedLines(import_node_path20.default.join(dir, rel));
5547
+ return { path: rel, added: lines, removed: 0, status: "A", ...approx ? { approx: true } : {} };
5548
+ })
5549
+ );
5550
+ files.push(...batch);
5551
+ }
5552
+ const result = {
5553
+ added: files.reduce((n, f) => n + f.added, 0),
5554
+ removed: files.reduce((n, f) => n + f.removed, 0),
5555
+ files
5556
+ };
5557
+ if (wantTree) {
5558
+ const tracked = splitZ(await git(dir, ["ls-files", "-z"]));
5559
+ result.tree = [.../* @__PURE__ */ new Set([...tracked, ...untracked])].sort();
5560
+ }
5561
+ return result;
5562
+ } catch {
5563
+ return { added: 0, removed: 0, files: [], degraded: true };
5564
+ }
5565
+ }
5566
+ var import_promises16, import_node_path20, num, LINE_COUNT_CAP, lineCache, splitZ, MAX_DIFF_LINES, changesMemo, CHANGES_TTL_MS;
5567
+ var init_git_stats = __esm({
5568
+ "server/lib/git-stats.ts"() {
5569
+ "use strict";
5570
+ import_promises16 = require("node:fs/promises");
5571
+ import_node_path20 = __toESM(require("node:path"));
5572
+ init_workspaces();
5573
+ num = (s) => s === "-" ? 0 : Number(s) || 0;
5574
+ LINE_COUNT_CAP = 10 * 1024 * 1024;
5575
+ lineCache = /* @__PURE__ */ new Map();
5576
+ splitZ = (out) => out.split("\0").filter(Boolean);
5577
+ MAX_DIFF_LINES = 5e3;
5578
+ changesMemo = /* @__PURE__ */ new Map();
5579
+ CHANGES_TTL_MS = 5e3;
5580
+ }
5581
+ });
5582
+
5583
+ // server/routes/git.ts
5584
+ var git_exports = {};
5585
+ __export(git_exports, {
5586
+ default: () => gitRoutes
5587
+ });
5588
+ async function gitRoutes(f, deps = {}) {
5589
+ const resolveRepo = deps.resolveRepo ?? defaultResolveRepo;
5590
+ const listWorkspaces = deps.listWorkspaces ?? list;
5591
+ const repoMemo = /* @__PURE__ */ new Map();
5592
+ const REPO_TTL_MS = 6e4;
5593
+ async function repoFor(projectId) {
5594
+ const hit = repoMemo.get(projectId);
5595
+ if (hit && Date.now() - hit.at < REPO_TTL_MS) return hit.path;
5596
+ const path10 = await resolveRepo(projectId);
5597
+ if (repoMemo.size > 500) repoMemo.clear();
5598
+ if (path10) repoMemo.set(projectId, { at: Date.now(), path: path10 });
5599
+ return path10;
5600
+ }
5601
+ async function resolveTarget(projectId, branch) {
5602
+ const repo = await repoFor(projectId);
5603
+ if (!repo) return null;
5604
+ let dir = repo;
5605
+ if (branch?.startsWith("agent/")) {
5606
+ const rec = (await listWorkspaces(repo).catch(() => [])).find((r) => r.branch === branch);
5607
+ if (rec) dir = rec.dir;
5608
+ }
5609
+ const base = await defaultBranch(repo).catch(() => null);
5610
+ return { dir, base };
5611
+ }
5612
+ f.get(
5613
+ "/api/git/changes",
5614
+ async (req, reply) => {
5615
+ const { project, branch, tree } = req.query;
5616
+ if (!project) {
5617
+ reply.code(400);
5618
+ return { error: "project is required" };
5619
+ }
5620
+ const target = await resolveTarget(project, branch);
5621
+ if (!target) {
5622
+ reply.code(404);
5623
+ return { error: "project not found" };
5624
+ }
5625
+ return changes(target.dir, target.base, tree === "1");
5626
+ }
5627
+ );
5628
+ f.get(
5629
+ "/api/git/changes/file",
5630
+ async (req, reply) => {
5631
+ const { project, branch, path: relPath } = req.query;
5632
+ if (!project || !relPath) {
5633
+ reply.code(400);
5634
+ return { error: "project and path are required" };
5635
+ }
5636
+ const target = await resolveTarget(project, branch);
5637
+ if (!target) {
5638
+ reply.code(404);
5639
+ return { error: "project not found" };
5640
+ }
5641
+ return fileDiff(target.dir, target.base, relPath);
5642
+ }
5643
+ );
5644
+ }
5645
+ var init_git = __esm({
5646
+ "server/routes/git.ts"() {
5647
+ "use strict";
5648
+ init_git_stats();
5649
+ init_workspaces();
5650
+ init_bridge();
5651
+ }
5652
+ });
5653
+
5370
5654
  // server/lib/teams.ts
5371
5655
  function composeTeamPrompt(t, task) {
5372
5656
  const roster = t.members.map((m) => ` - ${oneLine(m.name)} \u2014 ${oneLine(m.role)}${m.model ? ` (${m.model})` : ""}`).join("\n");
@@ -5380,16 +5664,16 @@ function composeTeamPrompt(t, task) {
5380
5664
  }
5381
5665
  async function readAll2() {
5382
5666
  try {
5383
- return JSON.parse(await (0, import_promises16.readFile)(file(), "utf8"));
5667
+ return JSON.parse(await (0, import_promises17.readFile)(file(), "utf8"));
5384
5668
  } catch {
5385
5669
  return [];
5386
5670
  }
5387
5671
  }
5388
5672
  async function writeAll2(list2) {
5389
- await (0, import_promises16.mkdir)(configDir(), { recursive: true });
5390
- const tmp = import_node_path20.default.join(configDir(), `.${(0, import_node_crypto8.randomBytes)(6).toString("hex")}.tmp`);
5391
- await (0, import_promises16.writeFile)(tmp, JSON.stringify(list2, null, 2), "utf8");
5392
- await (0, import_promises16.rename)(tmp, file());
5673
+ await (0, import_promises17.mkdir)(configDir(), { recursive: true });
5674
+ const tmp = import_node_path21.default.join(configDir(), `.${(0, import_node_crypto8.randomBytes)(6).toString("hex")}.tmp`);
5675
+ await (0, import_promises17.writeFile)(tmp, JSON.stringify(list2, null, 2), "utf8");
5676
+ await (0, import_promises17.rename)(tmp, file());
5393
5677
  }
5394
5678
  function update2(mutate) {
5395
5679
  const run = writeChain2.then(async () => {
@@ -5407,15 +5691,15 @@ async function saveTemplate(t) {
5407
5691
  async function deleteTemplate(name) {
5408
5692
  await update2((list2) => list2.filter((x) => x.name !== name));
5409
5693
  }
5410
- var import_promises16, import_node_crypto8, import_node_path20, file, oneLine, writeChain2, listTemplates;
5694
+ var import_promises17, import_node_crypto8, import_node_path21, file, oneLine, writeChain2, listTemplates;
5411
5695
  var init_teams = __esm({
5412
5696
  "server/lib/teams.ts"() {
5413
5697
  "use strict";
5414
- import_promises16 = require("node:fs/promises");
5698
+ import_promises17 = require("node:fs/promises");
5415
5699
  import_node_crypto8 = require("node:crypto");
5416
- import_node_path20 = __toESM(require("node:path"));
5700
+ import_node_path21 = __toESM(require("node:path"));
5417
5701
  init_daemon_client();
5418
- file = () => import_node_path20.default.join(configDir(), "teams.json");
5702
+ file = () => import_node_path21.default.join(configDir(), "teams.json");
5419
5703
  oneLine = (s) => s.replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
5420
5704
  writeChain2 = Promise.resolve();
5421
5705
  listTemplates = readAll2;
@@ -5427,10 +5711,10 @@ var teams_exports = {};
5427
5711
  __export(teams_exports, {
5428
5712
  default: () => teamRoutes
5429
5713
  });
5430
- async function isDir3(path9) {
5714
+ async function isDir3(path10) {
5431
5715
  try {
5432
- const { stat: stat10 } = await import("node:fs/promises");
5433
- return (await stat10(path9)).isDirectory();
5716
+ const { stat: stat11 } = await import("node:fs/promises");
5717
+ return (await stat11(path10)).isDirectory();
5434
5718
  } catch {
5435
5719
  return false;
5436
5720
  }
@@ -5587,22 +5871,22 @@ var scratchpad_exports = {};
5587
5871
  __export(scratchpad_exports, {
5588
5872
  default: () => scratchpadRoutes
5589
5873
  });
5590
- async function isDir4(path9) {
5874
+ async function isDir4(path10) {
5591
5875
  try {
5592
- return (await (0, import_promises17.stat)(path9)).isDirectory();
5876
+ return (await (0, import_promises18.stat)(path10)).isDirectory();
5593
5877
  } catch {
5594
5878
  return false;
5595
5879
  }
5596
5880
  }
5597
5881
  function scratchpadPath(repo) {
5598
- return (0, import_node_path21.join)(repo, ".seshmux", "handoff.md");
5882
+ return (0, import_node_path22.join)(repo, ".seshmux", "handoff.md");
5599
5883
  }
5600
- async function atomicWrite4(path9, content) {
5601
- const dir = (0, import_node_path21.join)(path9, "..");
5602
- await (0, import_promises17.mkdir)(dir, { recursive: true });
5603
- const tmp = (0, import_node_path21.join)(dir, `.${(0, import_node_crypto9.randomBytes)(6).toString("hex")}.tmp`);
5604
- await (0, import_promises17.writeFile)(tmp, content, "utf8");
5605
- await (0, import_promises17.rename)(tmp, path9);
5884
+ async function atomicWrite4(path10, content) {
5885
+ const dir = (0, import_node_path22.join)(path10, "..");
5886
+ await (0, import_promises18.mkdir)(dir, { recursive: true });
5887
+ const tmp = (0, import_node_path22.join)(dir, `.${(0, import_node_crypto9.randomBytes)(6).toString("hex")}.tmp`);
5888
+ await (0, import_promises18.writeFile)(tmp, content, "utf8");
5889
+ await (0, import_promises18.rename)(tmp, path10);
5606
5890
  }
5607
5891
  async function scratchpadRoutes(f, deps = {}) {
5608
5892
  const defaultResolveRepo2 = async (id) => {
@@ -5628,7 +5912,7 @@ async function scratchpadRoutes(f, deps = {}) {
5628
5912
  }
5629
5913
  deps.onOpen?.(req.params.projectId, repo);
5630
5914
  try {
5631
- const content = await (0, import_promises17.readFile)(scratchpadPath(repo), "utf8");
5915
+ const content = await (0, import_promises18.readFile)(scratchpadPath(repo), "utf8");
5632
5916
  return { content };
5633
5917
  } catch {
5634
5918
  return { content: "" };
@@ -5642,25 +5926,25 @@ async function scratchpadRoutes(f, deps = {}) {
5642
5926
  reply.code(404);
5643
5927
  return { error: "project not found" };
5644
5928
  }
5645
- const path9 = scratchpadPath(repo);
5929
+ const path10 = scratchpadPath(repo);
5646
5930
  let content = typeof req.body?.content === "string" ? req.body.content : "";
5647
5931
  try {
5648
- await (0, import_promises17.stat)(path9);
5932
+ await (0, import_promises18.stat)(path10);
5649
5933
  } catch {
5650
5934
  if (!content.startsWith("# ")) content = TEMPLATE + content;
5651
5935
  }
5652
- await atomicWrite4(path9, content);
5936
+ await atomicWrite4(path10, content);
5653
5937
  return { ok: true, content };
5654
5938
  }
5655
5939
  );
5656
5940
  }
5657
- var import_node_crypto9, import_promises17, import_node_path21, TEMPLATE;
5941
+ var import_node_crypto9, import_promises18, import_node_path22, TEMPLATE;
5658
5942
  var init_scratchpad = __esm({
5659
5943
  "server/routes/scratchpad.ts"() {
5660
5944
  "use strict";
5661
5945
  import_node_crypto9 = require("node:crypto");
5662
- import_promises17 = require("node:fs/promises");
5663
- import_node_path21 = require("node:path");
5946
+ import_promises18 = require("node:fs/promises");
5947
+ import_node_path22 = require("node:path");
5664
5948
  init_scan();
5665
5949
  init_types();
5666
5950
  TEMPLATE = `# Shared scratchpad
@@ -5725,12 +6009,12 @@ __export(notify_exports, {
5725
6009
  default: () => notifyRoutes
5726
6010
  });
5727
6011
  function configPath2() {
5728
- const dir = process.env.SESHMUX_CONFIG_DIR || import_node_path22.default.join(import_node_os10.default.homedir(), ".config", "seshmux");
5729
- return import_node_path22.default.join(dir, "config.json");
6012
+ const dir = process.env.SESHMUX_CONFIG_DIR || import_node_path23.default.join(import_node_os10.default.homedir(), ".config", "seshmux");
6013
+ return import_node_path23.default.join(dir, "config.json");
5730
6014
  }
5731
6015
  async function macNotificationsEnabled() {
5732
6016
  try {
5733
- const raw = await (0, import_promises18.readFile)(configPath2(), "utf8");
6017
+ const raw = await (0, import_promises19.readFile)(configPath2(), "utf8");
5734
6018
  const cfg = JSON.parse(raw);
5735
6019
  return cfg?.settings?.macNotifications !== false;
5736
6020
  } catch {
@@ -5766,13 +6050,13 @@ async function notifyRoutes(f) {
5766
6050
  return { ok: true, delivered: true };
5767
6051
  });
5768
6052
  }
5769
- var import_node_child_process8, import_promises18, import_node_path22, import_node_os10;
6053
+ var import_node_child_process8, import_promises19, import_node_path23, import_node_os10;
5770
6054
  var init_notify = __esm({
5771
6055
  "server/routes/notify.ts"() {
5772
6056
  "use strict";
5773
6057
  import_node_child_process8 = require("node:child_process");
5774
- import_promises18 = require("node:fs/promises");
5775
- import_node_path22 = __toESM(require("node:path"));
6058
+ import_promises19 = require("node:fs/promises");
6059
+ import_node_path23 = __toESM(require("node:path"));
5776
6060
  import_node_os10 = __toESM(require("node:os"));
5777
6061
  }
5778
6062
  });
@@ -5986,6 +6270,7 @@ async function startServer({ port: port2 = 4700, dev = false } = {}) {
5986
6270
  await (await Promise.resolve().then(() => (init_workspaces(), workspaces_exports))).reconcile().catch(() => {
5987
6271
  });
5988
6272
  await f.register((await Promise.resolve().then(() => (init_workspaces2(), workspaces_exports2))).default);
6273
+ await f.register((await Promise.resolve().then(() => (init_git(), git_exports))).default);
5989
6274
  await f.register((await Promise.resolve().then(() => (init_teams2(), teams_exports))).default, {
5990
6275
  onTeamWatch: (teamName, leadSessionId, configPath3) => hub.watchTeam(teamName, leadSessionId, configPath3)
5991
6276
  });