@sma1lboy/kobe 0.5.12 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -4494,11 +4494,11 @@ var init_theme = __esm(() => {
4494
4494
  });
4495
4495
 
4496
4496
  // src/tui/panes/filetree/git.ts
4497
- import { spawnSync as nodeSpawnSync } from "child_process";
4498
- function runGit(args, cwd) {
4497
+ import { spawn as nodeSpawn } from "child_process";
4498
+ async function runGit(args, cwd) {
4499
4499
  if (!cwd)
4500
4500
  throw new Error("git(): cwd is required");
4501
- const result = gitWrapper.spawnSync(args, cwd);
4501
+ const result = await gitWrapper.spawn(args, cwd);
4502
4502
  const exitCode = result.status ?? -1;
4503
4503
  if (exitCode !== 0) {
4504
4504
  const stderr = (result.stderr ?? "").trim();
@@ -4508,7 +4508,7 @@ function runGit(args, cwd) {
4508
4508
  return result.stdout ?? "";
4509
4509
  }
4510
4510
  async function listFiles(worktreePath) {
4511
- const out = runGit(["ls-files", "--cached", "--others", "--exclude-standard", "--full-name"], worktreePath);
4511
+ const out = await runGit(["ls-files", "--cached", "--others", "--exclude-standard", "--full-name"], worktreePath);
4512
4512
  const lines = out.split(`
4513
4513
  `).map((l) => l.replace(/\r$/, ""));
4514
4514
  const set = new Set;
@@ -4519,11 +4519,11 @@ async function listFiles(worktreePath) {
4519
4519
  return Array.from(set).sort();
4520
4520
  }
4521
4521
  async function statusFiles(worktreePath) {
4522
- const out = runGit(["status", "--porcelain"], worktreePath);
4522
+ const out = await runGit(["status", "--porcelain"], worktreePath);
4523
4523
  const entries = parsePorcelain(out);
4524
4524
  let stats = null;
4525
4525
  try {
4526
- const diffOut = runGit(["diff", "--no-color", "--numstat", "HEAD"], worktreePath);
4526
+ const diffOut = await runGit(["diff", "--no-color", "--numstat", "HEAD"], worktreePath);
4527
4527
  stats = new Map(parseNumstat(diffOut).map((n) => [n.path, { added: n.added, deleted: n.deleted }]));
4528
4528
  } catch {
4529
4529
  stats = new Map;
@@ -4642,11 +4642,27 @@ function parsePorcelain(raw) {
4642
4642
  var gitWrapper;
4643
4643
  var init_git = __esm(() => {
4644
4644
  gitWrapper = {
4645
- spawnSync(args, cwd) {
4646
- return nodeSpawnSync("git", [...args], {
4647
- cwd,
4648
- encoding: "utf8",
4649
- shell: false
4645
+ spawn(args, cwd) {
4646
+ return new Promise((resolve, reject) => {
4647
+ const child = nodeSpawn("git", [...args], {
4648
+ cwd,
4649
+ shell: false,
4650
+ stdio: ["ignore", "pipe", "pipe"]
4651
+ });
4652
+ let stdout = "";
4653
+ let stderr = "";
4654
+ child.stdout.setEncoding("utf8");
4655
+ child.stderr.setEncoding("utf8");
4656
+ child.stdout.on("data", (chunk) => {
4657
+ stdout += chunk;
4658
+ });
4659
+ child.stderr.on("data", (chunk) => {
4660
+ stderr += chunk;
4661
+ });
4662
+ child.on("error", reject);
4663
+ child.on("close", (status, signal) => {
4664
+ resolve({ stdout, stderr, status, signal });
4665
+ });
4650
4666
  });
4651
4667
  }
4652
4668
  };
@@ -5685,7 +5701,9 @@ function FileTree(props) {
5685
5701
  const [changes, setChanges] = createSignal(null);
5686
5702
  const [error, setError] = createSignal(null);
5687
5703
  const [expandedDirs, setExpandedDirs] = createSignal(new Set);
5704
+ let fetchSeq = 0;
5688
5705
  async function refetch(currentTab, path) {
5706
+ const seq = ++fetchSeq;
5689
5707
  if (path == null) {
5690
5708
  setAllFiles(null);
5691
5709
  setChanges(null);
@@ -5696,14 +5714,19 @@ function FileTree(props) {
5696
5714
  try {
5697
5715
  if (currentTab === "all") {
5698
5716
  const files = await listFiles(path);
5717
+ if (seq !== fetchSeq || props.worktreePath() !== path)
5718
+ return;
5699
5719
  setAllFiles(files);
5700
5720
  } else if (currentTab === "changes") {
5701
5721
  const entries = await statusFiles(path);
5722
+ if (seq !== fetchSeq || props.worktreePath() !== path)
5723
+ return;
5702
5724
  setChanges(entries);
5703
5725
  }
5704
5726
  } catch (err) {
5705
5727
  const message = err instanceof Error ? err.message : String(err);
5706
- setError(message);
5728
+ if (seq === fetchSeq && props.worktreePath() === path)
5729
+ setError(message);
5707
5730
  }
5708
5731
  }
5709
5732
  createEffect(on(props.worktreePath, async (path) => {
@@ -5717,6 +5740,8 @@ function FileTree(props) {
5717
5740
  createEffect(on(props.worktreePath, (path) => {
5718
5741
  if (path == null)
5719
5742
  return;
5743
+ if (process.env.KOBE_FILETREE_WATCH !== "1")
5744
+ return;
5720
5745
  let debounceTimer = null;
5721
5746
  let watcher = null;
5722
5747
  try {
@@ -5735,7 +5760,7 @@ function FileTree(props) {
5735
5760
  debounceTimer = setTimeout(() => {
5736
5761
  debounceTimer = null;
5737
5762
  setRefreshTick((n) => n + 1);
5738
- }, 200);
5763
+ }, 500);
5739
5764
  });
5740
5765
  watcher.on("error", () => {});
5741
5766
  } catch {}
@@ -6436,18 +6461,62 @@ var init_state = __esm(() => {
6436
6461
  });
6437
6462
 
6438
6463
  // src/tui/panes/preview/diff.ts
6439
- import { spawnSync as spawnSync2 } from "child_process";
6440
- function resolveGitToplevel(cwd) {
6441
- const r = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
6442
- cwd,
6443
- encoding: "utf8",
6444
- shell: false
6445
- });
6464
+ import { spawn } from "child_process";
6465
+ import { open } from "fs/promises";
6466
+ import path2 from "path";
6467
+ async function resolveGitToplevel(cwd) {
6468
+ const r = await runProcess("git", ["rev-parse", "--show-toplevel"], cwd, 64 * 1024);
6446
6469
  if (r.status !== 0)
6447
6470
  return cwd;
6448
- const top = (r.stdout ?? "").trim();
6471
+ const top = r.stdout.trim();
6449
6472
  return top || cwd;
6450
6473
  }
6474
+ function runProcess(command, args, cwd, maxBytes) {
6475
+ return new Promise((resolve, reject) => {
6476
+ const child = spawn(command, [...args], {
6477
+ cwd,
6478
+ shell: false,
6479
+ stdio: ["ignore", "pipe", "pipe"]
6480
+ });
6481
+ const stdoutChunks = [];
6482
+ const stderrChunks = [];
6483
+ let stdoutBytes = 0;
6484
+ let stderrBytes = 0;
6485
+ let truncated = false;
6486
+ function pushBounded(chunks, chunk, currentBytes) {
6487
+ const remaining = Math.max(0, maxBytes + 1 - currentBytes);
6488
+ if (remaining <= 0) {
6489
+ truncated = true;
6490
+ return currentBytes + chunk.length;
6491
+ }
6492
+ if (chunk.length > remaining) {
6493
+ chunks.push(chunk.subarray(0, remaining));
6494
+ truncated = true;
6495
+ } else {
6496
+ chunks.push(chunk);
6497
+ }
6498
+ return currentBytes + chunk.length;
6499
+ }
6500
+ child.stdout.on("data", (chunk) => {
6501
+ stdoutBytes = pushBounded(stdoutChunks, chunk, stdoutBytes);
6502
+ if (stdoutBytes > maxBytes)
6503
+ child.kill("SIGTERM");
6504
+ });
6505
+ child.stderr.on("data", (chunk) => {
6506
+ stderrBytes = pushBounded(stderrChunks, chunk, stderrBytes);
6507
+ });
6508
+ child.on("error", reject);
6509
+ child.on("close", (status, signal) => {
6510
+ resolve({
6511
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
6512
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
6513
+ status,
6514
+ signal,
6515
+ truncated
6516
+ });
6517
+ });
6518
+ });
6519
+ }
6451
6520
  async function readFile(worktreePath, relPath) {
6452
6521
  if (!worktreePath)
6453
6522
  return { ok: false, error: "no worktree path" };
@@ -6456,21 +6525,22 @@ async function readFile(worktreePath, relPath) {
6456
6525
  if (relPath.split("/").includes("..")) {
6457
6526
  return { ok: false, error: "path escapes worktree" };
6458
6527
  }
6459
- const proc = spawnSync2("cat", ["--", relPath], {
6460
- cwd: resolveGitToplevel(worktreePath),
6461
- encoding: "utf8",
6462
- shell: false,
6463
- maxBuffer: MAX_BYTES + 4096
6464
- });
6465
- if (proc.status !== 0) {
6466
- const err = (proc.stderr ?? "").trim() || `cat exited ${proc.status}`;
6467
- return { ok: false, error: err };
6468
- }
6469
- const raw = proc.stdout ?? "";
6470
- if (raw.length > MAX_BYTES) {
6471
- return { ok: true, text: raw.slice(0, MAX_BYTES) + TRUNCATED_BANNER, truncated: true };
6528
+ const top = await resolveGitToplevel(worktreePath);
6529
+ const absPath = path2.join(top, relPath);
6530
+ let handle = null;
6531
+ try {
6532
+ handle = await open(absPath, "r");
6533
+ const buf = Buffer.allocUnsafe(MAX_BYTES + 1);
6534
+ const { bytesRead } = await handle.read(buf, 0, MAX_BYTES + 1, 0);
6535
+ const truncated = bytesRead > MAX_BYTES;
6536
+ const raw = buf.subarray(0, Math.min(bytesRead, MAX_BYTES)).toString("utf8");
6537
+ return { ok: true, text: truncated ? raw + TRUNCATED_BANNER : raw, truncated };
6538
+ } catch (err) {
6539
+ const msg = err instanceof Error ? err.message : String(err);
6540
+ return { ok: false, error: msg };
6541
+ } finally {
6542
+ await handle?.close().catch(() => {});
6472
6543
  }
6473
- return { ok: true, text: raw, truncated: false };
6474
6544
  }
6475
6545
  async function readDiff(worktreePath, base, relPath) {
6476
6546
  if (!worktreePath)
@@ -6482,31 +6552,22 @@ async function readDiff(worktreePath, base, relPath) {
6482
6552
  if (relPath.split("/").includes("..")) {
6483
6553
  return { ok: false, error: "path escapes worktree" };
6484
6554
  }
6485
- const proc = spawnSync2("git", ["diff", "--no-color", base, "--", relPath], {
6486
- cwd: resolveGitToplevel(worktreePath),
6487
- encoding: "utf8",
6488
- shell: false,
6489
- maxBuffer: MAX_BYTES + 4096
6490
- });
6491
- if (proc.status !== 0) {
6492
- const err = (proc.stderr ?? "").trim() || `git diff exited ${proc.status}`;
6555
+ const proc = await runProcess("git", ["diff", "--no-color", base, "--", relPath], await resolveGitToplevel(worktreePath), MAX_BYTES);
6556
+ if (!proc.truncated && proc.status !== 0) {
6557
+ const err = (proc.stderr ?? "").trim() || `git diff exited ${proc.status ?? proc.signal ?? "unknown"}`;
6493
6558
  return { ok: false, error: err };
6494
6559
  }
6495
- const raw = proc.stdout ?? "";
6496
- if (raw.length > MAX_BYTES) {
6497
- return { ok: true, text: raw.slice(0, MAX_BYTES) + TRUNCATED_BANNER, truncated: true };
6498
- }
6499
- return { ok: true, text: raw, truncated: false };
6560
+ const raw = proc.stdout;
6561
+ return {
6562
+ ok: true,
6563
+ text: proc.truncated ? raw.slice(0, MAX_BYTES) + TRUNCATED_BANNER : raw,
6564
+ truncated: proc.truncated
6565
+ };
6500
6566
  }
6501
6567
  async function isPathChanged(worktreePath, relPath) {
6502
6568
  if (!worktreePath || !relPath)
6503
6569
  return false;
6504
- const proc = spawnSync2("git", ["status", "--porcelain", "--", relPath], {
6505
- cwd: resolveGitToplevel(worktreePath),
6506
- encoding: "utf8",
6507
- shell: false,
6508
- maxBuffer: 64 * 1024
6509
- });
6570
+ const proc = await runProcess("git", ["status", "--porcelain", "--", relPath], await resolveGitToplevel(worktreePath), 64 * 1024);
6510
6571
  if (proc.status !== 0)
6511
6572
  return false;
6512
6573
  return (proc.stdout ?? "").trim().length > 0;
@@ -7533,7 +7594,7 @@ var init_keys3 = __esm(() => {
7533
7594
  });
7534
7595
 
7535
7596
  // src/tui/panes/terminal/pty.ts
7536
- import { spawn, spawnSync as spawnSync3 } from "child_process";
7597
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
7537
7598
  function defaultShell() {
7538
7599
  return process.env.SHELL ?? "/bin/bash";
7539
7600
  }
@@ -7543,7 +7604,7 @@ function defaultTmuxBin() {
7543
7604
  function assertTmuxAvailable(tmuxBin2) {
7544
7605
  let result;
7545
7606
  try {
7546
- result = spawnSync3(tmuxBin2, ["-V"], { encoding: "utf8" });
7607
+ result = spawnSync2(tmuxBin2, ["-V"], { encoding: "utf8" });
7547
7608
  } catch (err) {
7548
7609
  throw new Error(`kobe terminal pane requires tmux on PATH. Failed to spawn '${tmuxBin2}': ${err.message}.
7549
7610
  Install tmux (e.g. 'brew install tmux') or set KOBE_TMUX_BIN to its path.`);
@@ -7561,7 +7622,7 @@ function sessionNameFor(taskId) {
7561
7622
  return `${SESSION_NAME_PREFIX}${safe}`;
7562
7623
  }
7563
7624
  function tmuxSync(tmuxBin2, args) {
7564
- const result = spawnSync3(tmuxBin2, args, { encoding: "utf8" });
7625
+ const result = spawnSync2(tmuxBin2, args, { encoding: "utf8" });
7565
7626
  if (result.error) {
7566
7627
  throw new Error(`${tmuxBin2} ${args.join(" ")}: ${result.error.message}`);
7567
7628
  }
@@ -7577,7 +7638,7 @@ class TmuxControlClient {
7577
7638
  buffer = "";
7578
7639
  _killed = false;
7579
7640
  constructor(tmuxBin2, sessionName) {
7580
- this.proc = spawn(tmuxBin2, ["-CC", "attach-session", "-t", sessionName], {
7641
+ this.proc = spawn2(tmuxBin2, ["-CC", "attach-session", "-t", sessionName], {
7581
7642
  stdio: ["pipe", "pipe", "ignore"]
7582
7643
  });
7583
7644
  this.proc.stdout?.setEncoding("utf8");
@@ -7677,7 +7738,7 @@ class TmuxTaskPty {
7677
7738
  this.sessionName = sessionNameFor(opts.taskId);
7678
7739
  assertTmuxAvailable(this.tmuxBin);
7679
7740
  const shell = opts.shell ?? defaultShell();
7680
- const has = spawnSync3(this.tmuxBin, ["has-session", "-t", this.sessionName], { encoding: "utf8" });
7741
+ const has = spawnSync2(this.tmuxBin, ["has-session", "-t", this.sessionName], { encoding: "utf8" });
7681
7742
  let existed = has.status === 0;
7682
7743
  if (existed) {
7683
7744
  try {
@@ -7800,7 +7861,7 @@ class TmuxTaskPty {
7800
7861
  for (const b of slice)
7801
7862
  hexArgs.push(b.toString(16).padStart(2, "0"));
7802
7863
  await new Promise((resolve) => {
7803
- const proc = spawn(this.tmuxBin, ["send-keys", "-t", this.sessionName, "-H", ...hexArgs], {
7864
+ const proc = spawn2(this.tmuxBin, ["send-keys", "-t", this.sessionName, "-H", ...hexArgs], {
7804
7865
  stdio: ["ignore", "ignore", "ignore"]
7805
7866
  });
7806
7867
  proc.on("error", () => resolve());
@@ -7884,7 +7945,7 @@ class TmuxTaskPty {
7884
7945
  return;
7885
7946
  this.markDead();
7886
7947
  try {
7887
- const result = spawnSync3(this.tmuxBin, ["kill-session", "-t", this.sessionName], { encoding: "utf8" });
7948
+ const result = spawnSync2(this.tmuxBin, ["kill-session", "-t", this.sessionName], { encoding: "utf8" });
7888
7949
  } catch {}
7889
7950
  }
7890
7951
  markDead() {
@@ -8901,10 +8962,10 @@ var init_terminal_host = __esm(() => {
8901
8962
  });
8902
8963
 
8903
8964
  // src/engine/claude-code-local/binary.ts
8904
- import { spawnSync as spawnSync4 } from "child_process";
8965
+ import { spawnSync as spawnSync3 } from "child_process";
8905
8966
  import { existsSync, statSync } from "fs";
8906
8967
  import { homedir as homedir2 } from "os";
8907
- import path2 from "path";
8968
+ import path3 from "path";
8908
8969
  async function findClaudeBinary(deps = defaultDeps) {
8909
8970
  const checked = [];
8910
8971
  const tryPath = (p2) => {
@@ -8920,19 +8981,19 @@ async function findClaudeBinary(deps = defaultDeps) {
8920
8981
  return whichResult;
8921
8982
  }
8922
8983
  const home = deps.home();
8923
- const localInstall = tryPath(path2.join(home, ".claude", "local", "claude"));
8984
+ const localInstall = tryPath(path3.join(home, ".claude", "local", "claude"));
8924
8985
  if (localInstall)
8925
8986
  return localInstall;
8926
8987
  const nvmBin = deps.env("NVM_BIN");
8927
8988
  if (nvmBin) {
8928
- const candidate = tryPath(path2.join(nvmBin, "claude"));
8989
+ const candidate = tryPath(path3.join(nvmBin, "claude"));
8929
8990
  if (candidate)
8930
8991
  return candidate;
8931
8992
  }
8932
- const nvmRoot = path2.join(home, ".nvm", "versions", "node");
8993
+ const nvmRoot = path3.join(home, ".nvm", "versions", "node");
8933
8994
  const nvmVersions = deps.readdir(nvmRoot).sort().reverse();
8934
8995
  for (const v2 of nvmVersions) {
8935
- const candidate = tryPath(path2.join(nvmRoot, v2, "bin", "claude"));
8996
+ const candidate = tryPath(path3.join(nvmRoot, v2, "bin", "claude"));
8936
8997
  if (candidate)
8937
8998
  return candidate;
8938
8999
  }
@@ -8948,7 +9009,7 @@ async function findClaudeBinary(deps = defaultDeps) {
8948
9009
  ".bun/bin/claude",
8949
9010
  "bin/claude"
8950
9011
  ]) {
8951
- const candidate = tryPath(path2.join(home, rel));
9012
+ const candidate = tryPath(path3.join(home, rel));
8952
9013
  if (candidate)
8953
9014
  return candidate;
8954
9015
  }
@@ -8980,7 +9041,7 @@ var init_binary = __esm(() => {
8980
9041
  },
8981
9042
  which(name) {
8982
9043
  const cmd = process.platform === "win32" ? "where" : "which";
8983
- const out = spawnSync4(cmd, [name], { encoding: "utf8" });
9044
+ const out = spawnSync3(cmd, [name], { encoding: "utf8" });
8984
9045
  if (out.status !== 0)
8985
9046
  return;
8986
9047
  const first = out.stdout.split(`
@@ -9074,7 +9135,7 @@ var init_ulid = __esm(() => {
9074
9135
  });
9075
9136
 
9076
9137
  // src/orchestrator/index/store.ts
9077
- import { mkdir, open, readFile as readFile2, rename, unlink } from "fs/promises";
9138
+ import { mkdir, open as open2, readFile as readFile2, rename, unlink } from "fs/promises";
9078
9139
  import { homedir as homedir3 } from "os";
9079
9140
  import { dirname as dirname2, join as join2 } from "path";
9080
9141
 
@@ -9152,7 +9213,7 @@ class TaskIndexStore {
9152
9213
  const payload = this.snapshot();
9153
9214
  const json = `${JSON.stringify(payload, null, 2)}
9154
9215
  `;
9155
- const handle = await open(this.tmpPath, "w", 420);
9216
+ const handle = await open2(this.tmpPath, "w", 420);
9156
9217
  try {
9157
9218
  await handle.writeFile(json, "utf8");
9158
9219
  await handle.sync();
@@ -9392,32 +9453,32 @@ var init_store = __esm(() => {
9392
9453
 
9393
9454
  // src/orchestrator/worktree/paths.ts
9394
9455
  import fs2 from "fs";
9395
- import path3 from "path";
9456
+ import path4 from "path";
9396
9457
  function worktreeRootFor(repo) {
9397
- if (!path3.isAbsolute(repo)) {
9458
+ if (!path4.isAbsolute(repo)) {
9398
9459
  throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
9399
9460
  }
9400
- return path3.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
9461
+ return path4.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
9401
9462
  }
9402
9463
  function worktreePathFor(repo, taskId) {
9403
9464
  if (!taskId || /[/\\\0]/.test(taskId)) {
9404
9465
  throw new Error(`worktreePathFor: invalid taskId: ${JSON.stringify(taskId)}`);
9405
9466
  }
9406
- return path3.join(worktreeRootFor(repo), taskId);
9467
+ return path4.join(worktreeRootFor(repo), taskId);
9407
9468
  }
9408
9469
  function isKobeManagedPath(repo, candidate) {
9409
- if (!path3.isAbsolute(repo) || !path3.isAbsolute(candidate))
9470
+ if (!path4.isAbsolute(repo) || !path4.isAbsolute(candidate))
9410
9471
  return false;
9411
9472
  const root = canonicalize(worktreeRootFor(repo));
9412
9473
  const target = canonicalize(candidate);
9413
- const rel = path3.relative(root, target);
9414
- return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
9474
+ const rel = path4.relative(root, target);
9475
+ return rel !== "" && !rel.startsWith("..") && !path4.isAbsolute(rel);
9415
9476
  }
9416
9477
  function canonicalize(p2) {
9417
9478
  try {
9418
9479
  return fs2.realpathSync(p2);
9419
9480
  } catch {
9420
- return path3.resolve(p2);
9481
+ return path4.resolve(p2);
9421
9482
  }
9422
9483
  }
9423
9484
  var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
@@ -9429,7 +9490,7 @@ var init_package = __esm(() => {
9429
9490
  package_default = {
9430
9491
  $schema: "https://json.schemastore.org/package.json",
9431
9492
  name: "@sma1lboy/kobe",
9432
- version: "0.5.12",
9493
+ version: "0.5.13",
9433
9494
  description: "TUI orchestrator for Claude Code (codename)",
9434
9495
  type: "module",
9435
9496
  packageManager: "bun@1.3.13",
@@ -9599,7 +9660,7 @@ __export(exports_diagnose, {
9599
9660
  formatBytes: () => formatBytes,
9600
9661
  buildDiagnoseReport: () => buildDiagnoseReport
9601
9662
  });
9602
- import { spawnSync as spawnSync5 } from "child_process";
9663
+ import { spawnSync as spawnSync4 } from "child_process";
9603
9664
  import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
9604
9665
  import { stat } from "fs/promises";
9605
9666
  import { homedir as homedir4, arch as osArch, platform as osPlatform, release as osRelease } from "os";
@@ -9670,12 +9731,12 @@ function reconcileWorktrees(tasks, onDiskByRepo) {
9670
9731
  }
9671
9732
  function probeVersion(bin) {
9672
9733
  try {
9673
- const out = spawnSync5(bin, ["--version"], {
9734
+ const out = spawnSync4(bin, ["--version"], {
9674
9735
  encoding: "utf8",
9675
9736
  timeout: 3000
9676
9737
  });
9677
9738
  if (out.status !== 0) {
9678
- const alt = spawnSync5(bin, ["-V"], { encoding: "utf8", timeout: 3000 });
9739
+ const alt = spawnSync4(bin, ["-V"], { encoding: "utf8", timeout: 3000 });
9679
9740
  if (alt.status !== 0)
9680
9741
  return null;
9681
9742
  return alt.stdout.trim() || alt.stderr.trim() || null;
@@ -9733,9 +9794,9 @@ function listWorktreeDirs(root) {
9733
9794
  return [];
9734
9795
  }
9735
9796
  }
9736
- function tailLines(path4, n2) {
9797
+ function tailLines(path5, n2) {
9737
9798
  try {
9738
- const text = __require("fs").readFileSync(path4, "utf8");
9799
+ const text = __require("fs").readFileSync(path5, "utf8");
9739
9800
  const lines = text.split(`
9740
9801
  `).filter((l2) => l2.length > 0);
9741
9802
  return lines.slice(-n2);
@@ -9786,7 +9847,7 @@ async function buildDiagnoseReport() {
9786
9847
  const tmux = tmuxBin();
9787
9848
  let tmuxPath = null;
9788
9849
  try {
9789
- const out = spawnSync5(process.platform === "win32" ? "where" : "which", [tmux], {
9850
+ const out = spawnSync4(process.platform === "win32" ? "where" : "which", [tmux], {
9790
9851
  encoding: "utf8",
9791
9852
  timeout: 3000
9792
9853
  });
@@ -9924,7 +9985,7 @@ __export(exports_update, {
9924
9985
  runUpdateSubcommand: () => runUpdateSubcommand,
9925
9986
  parseUpdateArgs: () => parseUpdateArgs
9926
9987
  });
9927
- import { spawnSync as spawnSync6 } from "child_process";
9988
+ import { spawnSync as spawnSync5 } from "child_process";
9928
9989
  function fail(message) {
9929
9990
  process.stderr.write(`kobe update: ${message}
9930
9991
  `);
@@ -9975,7 +10036,7 @@ function printUsage(out) {
9975
10036
  }
9976
10037
  async function runUpdateSubcommand(args, deps) {
9977
10038
  const io = {
9978
- spawn: deps?.spawn ?? spawnSync6,
10039
+ spawn: deps?.spawn ?? spawnSync5,
9979
10040
  stdout: deps?.stdout ?? process.stdout,
9980
10041
  stderr: deps?.stderr ?? process.stderr,
9981
10042
  exit: deps?.exit ?? ((code) => process.exit(code))
@@ -10072,19 +10133,19 @@ function loadUserThemes() {
10072
10133
  for (const file of entries) {
10073
10134
  if (!file.endsWith(".json"))
10074
10135
  continue;
10075
- const path4 = join4(dir, file);
10136
+ const path5 = join4(dir, file);
10076
10137
  let parsed;
10077
10138
  try {
10078
- const text = readFileSync2(path4, "utf8");
10139
+ const text = readFileSync2(path5, "utf8");
10079
10140
  parsed = JSON.parse(text);
10080
10141
  } catch (err) {
10081
10142
  const msg = err instanceof Error ? err.message : String(err);
10082
- console.warn(`[kobe] skipping user theme ${path4}: invalid JSON \u2014 ${msg}`);
10143
+ console.warn(`[kobe] skipping user theme ${path5}: invalid JSON \u2014 ${msg}`);
10083
10144
  continue;
10084
10145
  }
10085
10146
  const result = validateTheme(parsed);
10086
10147
  if (!result.ok) {
10087
- console.warn(`[kobe] skipping user theme ${path4}: ${result.reason}`);
10148
+ console.warn(`[kobe] skipping user theme ${path5}: ${result.reason}`);
10088
10149
  continue;
10089
10150
  }
10090
10151
  const name = file.slice(0, -".json".length);
@@ -10127,9 +10188,9 @@ function listThemes2() {
10127
10188
  } else {
10128
10189
  for (const f2 of userFiles) {
10129
10190
  const name = f2.slice(0, -".json".length);
10130
- const path4 = join5(dir, f2);
10191
+ const path5 = join5(dir, f2);
10131
10192
  const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
10132
- lines.push(` ${name}${overridesBundled} ${path4}`);
10193
+ lines.push(` ${name}${overridesBundled} ${path5}`);
10133
10194
  }
10134
10195
  }
10135
10196
  process.stdout.write(`${lines.join(`
@@ -10312,8 +10373,8 @@ class SocketClient {
10312
10373
  nextId = 1;
10313
10374
  pending = new Map;
10314
10375
  connected;
10315
- constructor(path4) {
10316
- this.socket = connect(path4);
10376
+ constructor(path5) {
10377
+ this.socket = connect(path5);
10317
10378
  this.connected = new Promise((resolve2, reject) => {
10318
10379
  this.socket.once("connect", resolve2);
10319
10380
  this.socket.once("error", reject);
@@ -10611,30 +10672,36 @@ class KobeDaemonClient {
10611
10672
  nextId = 1;
10612
10673
  pending = new Map;
10613
10674
  handlers = new Map;
10675
+ lifecycleHandlers = new Map;
10676
+ connecting = null;
10677
+ disposed = false;
10614
10678
  constructor(socketPath) {
10615
10679
  this.socketPath = socketPath;
10616
10680
  }
10617
10681
  connect() {
10618
10682
  if (this.socket)
10619
10683
  return Promise.resolve();
10620
- return new Promise((resolve2, reject) => {
10621
- const socket = connect2(this.socketPath);
10622
- this.socket = socket;
10623
- socket.once("connect", resolve2);
10624
- socket.once("error", reject);
10625
- socket.on("data", (chunk) => this.onData(chunk.toString("utf8")));
10626
- socket.on("close", () => {
10627
- this.socket = null;
10628
- for (const pending of this.pending.values())
10629
- pending.reject(new Error("daemon connection closed"));
10630
- this.pending.clear();
10631
- });
10632
- });
10684
+ if (this.disposed)
10685
+ return Promise.reject(new Error("daemon client disposed"));
10686
+ if (this.connecting)
10687
+ return this.connecting;
10688
+ const p2 = this.openSocket();
10689
+ this.connecting = p2;
10690
+ const cleanup = () => {
10691
+ if (this.connecting === p2)
10692
+ this.connecting = null;
10693
+ };
10694
+ p2.then(cleanup, cleanup);
10695
+ return p2;
10633
10696
  }
10634
10697
  close() {
10698
+ this.disposed = true;
10635
10699
  this.socket?.end();
10636
10700
  this.socket = null;
10637
10701
  }
10702
+ forceDisconnect() {
10703
+ this.socket?.destroy();
10704
+ }
10638
10705
  on(name, handler) {
10639
10706
  let set = this.handlers.get(name);
10640
10707
  if (!set) {
@@ -10648,6 +10715,19 @@ class KobeDaemonClient {
10648
10715
  this.handlers.delete(name);
10649
10716
  };
10650
10717
  }
10718
+ onLifecycle(name, handler) {
10719
+ let set = this.lifecycleHandlers.get(name);
10720
+ if (!set) {
10721
+ set = new Set;
10722
+ this.lifecycleHandlers.set(name, set);
10723
+ }
10724
+ set.add(handler);
10725
+ return () => {
10726
+ set?.delete(handler);
10727
+ if (set?.size === 0)
10728
+ this.lifecycleHandlers.delete(name);
10729
+ };
10730
+ }
10651
10731
  async request(name, payload) {
10652
10732
  await this.connect();
10653
10733
  const socket = this.socket;
@@ -10660,6 +10740,44 @@ class KobeDaemonClient {
10660
10740
  socket.write(frameToLine({ type: "request", id, name, payload }));
10661
10741
  return promise;
10662
10742
  }
10743
+ openSocket() {
10744
+ return new Promise((resolve2, reject) => {
10745
+ const socket = connect2(this.socketPath);
10746
+ this.socket = socket;
10747
+ const onConnect = () => {
10748
+ socket.off("error", onError);
10749
+ resolve2();
10750
+ };
10751
+ const onError = (err) => {
10752
+ socket.off("connect", onConnect);
10753
+ if (this.socket === socket)
10754
+ this.socket = null;
10755
+ reject(err);
10756
+ };
10757
+ socket.once("connect", onConnect);
10758
+ socket.once("error", onError);
10759
+ socket.on("data", (chunk) => this.onData(chunk.toString("utf8")));
10760
+ socket.on("close", () => this.onSocketClose(socket));
10761
+ });
10762
+ }
10763
+ onSocketClose(which) {
10764
+ if (this.socket !== which && this.socket !== null)
10765
+ return;
10766
+ this.socket = null;
10767
+ for (const pending of this.pending.values())
10768
+ pending.reject(new Error("daemon connection closed"));
10769
+ this.pending.clear();
10770
+ this.emitLifecycle("close");
10771
+ }
10772
+ emitLifecycle(name) {
10773
+ for (const handler of this.lifecycleHandlers.get(name) ?? []) {
10774
+ try {
10775
+ handler();
10776
+ } catch (err) {
10777
+ console.error(`[kobe] lifecycle handler for "${name}" threw:`, err);
10778
+ }
10779
+ }
10780
+ }
10663
10781
  onData(chunk) {
10664
10782
  this.buffer += chunk;
10665
10783
  let nl = this.buffer.indexOf(`
@@ -10700,21 +10818,20 @@ class KobeDaemonClient {
10700
10818
  var init_client = () => {};
10701
10819
 
10702
10820
  // src/client/daemon-process.ts
10703
- import { spawn as spawn2 } from "child_process";
10821
+ import { spawn as spawn3 } from "child_process";
10704
10822
  import { existsSync as existsSync4 } from "fs";
10705
10823
  import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
10706
10824
  import { fileURLToPath } from "url";
10707
- async function connectOrStartDaemon() {
10825
+ async function ensureDaemonReachable() {
10708
10826
  const socketPath = defaultDaemonSocketPath();
10709
- const client = new KobeDaemonClient(socketPath);
10710
- if (await canConnect(client))
10711
- return client;
10827
+ if (await testCanConnect(socketPath))
10828
+ return socketPath;
10712
10829
  const { entry, runWithBun } = resolveKobedEntry();
10713
- const child = runWithBun ? spawn2(process.execPath, [entry, "start"], {
10830
+ const child = runWithBun ? spawn3(process.execPath, [entry, "start"], {
10714
10831
  detached: true,
10715
10832
  stdio: "ignore",
10716
10833
  env: process.env
10717
- }) : spawn2(entry, ["start"], {
10834
+ }) : spawn3(entry, ["start"], {
10718
10835
  detached: true,
10719
10836
  stdio: "ignore",
10720
10837
  env: process.env
@@ -10723,24 +10840,26 @@ async function connectOrStartDaemon() {
10723
10840
  const deadline = Date.now() + 5000;
10724
10841
  let lastErr;
10725
10842
  while (Date.now() < deadline) {
10726
- const next = new KobeDaemonClient(socketPath);
10727
- try {
10728
- await next.connect();
10729
- return next;
10730
- } catch (err) {
10731
- lastErr = err;
10732
- next.close();
10733
- await new Promise((resolve3) => setTimeout(resolve3, 100));
10734
- }
10843
+ if (await testCanConnect(socketPath))
10844
+ return socketPath;
10845
+ await new Promise((resolveTimer) => setTimeout(resolveTimer, 100));
10735
10846
  }
10736
- throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
10847
+ throw new Error(`kobe: daemon did not start at ${socketPath}: ${lastErr instanceof Error ? lastErr.message : "timeout"}`);
10737
10848
  }
10738
- async function canConnect(client) {
10849
+ async function connectOrStartDaemon() {
10850
+ const socketPath = await ensureDaemonReachable();
10851
+ const client = new KobeDaemonClient(socketPath);
10852
+ await client.connect();
10853
+ return client;
10854
+ }
10855
+ async function testCanConnect(socketPath) {
10856
+ const probe = new KobeDaemonClient(socketPath);
10739
10857
  try {
10740
- await client.connect();
10858
+ await probe.connect();
10859
+ probe.close();
10741
10860
  return true;
10742
10861
  } catch {
10743
- client.close();
10862
+ probe.close();
10744
10863
  return false;
10745
10864
  }
10746
10865
  }
@@ -10884,7 +11003,7 @@ function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
10884
11003
  }
10885
11004
 
10886
11005
  // src/orchestrator/metadata-suggester.ts
10887
- import { spawn as spawn3 } from "child_process";
11006
+ import { spawn as spawn4 } from "child_process";
10888
11007
 
10889
11008
  class MetadataSuggester {
10890
11009
  binaryPromise = null;
@@ -10913,7 +11032,7 @@ class MetadataSuggester {
10913
11032
  return new Promise((resolve3) => {
10914
11033
  let proc;
10915
11034
  try {
10916
- proc = spawn3(binary, ["-p", builder(trimmed)], {
11035
+ proc = spawn4(binary, ["-p", builder(trimmed)], {
10917
11036
  stdio: ["ignore", "pipe", "ignore"],
10918
11037
  env: process.env
10919
11038
  });
@@ -11062,10 +11181,10 @@ class InMemoryPendingInputBroker {
11062
11181
  }
11063
11182
 
11064
11183
  // src/orchestrator/pr/build.ts
11065
- import { spawnSync as spawnSync7 } from "child_process";
11184
+ import { spawnSync as spawnSync6 } from "child_process";
11066
11185
  function git(cwd, args) {
11067
11186
  try {
11068
- const out = spawnSync7("git", args.slice(), {
11187
+ const out = spawnSync6("git", args.slice(), {
11069
11188
  cwd,
11070
11189
  encoding: "utf8",
11071
11190
  timeout: GIT_TIMEOUT_MS
@@ -11115,7 +11234,7 @@ var init_build = () => {};
11115
11234
 
11116
11235
  // src/orchestrator/pr/instructions.ts
11117
11236
  import { promises as fs3 } from "fs";
11118
- import path4 from "path";
11237
+ import path5 from "path";
11119
11238
  function dirtyCountSentence(n2) {
11120
11239
  if (n2 <= 0)
11121
11240
  return "There are no uncommitted changes.";
@@ -11143,7 +11262,7 @@ function renderPRInstructions(template, state) {
11143
11262
  async function loadPRInstructionsTemplate(worktreePath) {
11144
11263
  if (!worktreePath)
11145
11264
  return DEFAULT_PR_INSTRUCTIONS_TEMPLATE;
11146
- const file = path4.join(worktreePath, ".kobe", "pr-instructions.md");
11265
+ const file = path5.join(worktreePath, ".kobe", "pr-instructions.md");
11147
11266
  try {
11148
11267
  const text = await fs3.readFile(file, "utf8");
11149
11268
  if (text.length === 0)
@@ -12092,6 +12211,8 @@ class RemoteOrchestrator {
12092
12211
  setRunState;
12093
12212
  planUsageAcc;
12094
12213
  setPlanUsage;
12214
+ connectionStateAcc;
12215
+ setConnectionState;
12095
12216
  rcBridgeAcc;
12096
12217
  setRcBridge;
12097
12218
  subscribers = new Map;
@@ -12101,6 +12222,7 @@ class RemoteOrchestrator {
12101
12222
  const [tasks, setTasks] = createSignal([]);
12102
12223
  const [runState, setRunState] = createSignal(new Map);
12103
12224
  const [planUsage, setPlanUsage] = createSignal(null);
12225
+ const [connectionState, setConnectionState] = createSignal("online");
12104
12226
  const [rcBridge, setRcBridge] = createSignal({ state: "off" });
12105
12227
  this.tasksAcc = tasks;
12106
12228
  this.setTasks = (next) => setTasks(() => next);
@@ -12108,9 +12230,12 @@ class RemoteOrchestrator {
12108
12230
  this.setRunState = (next) => setRunState(() => next);
12109
12231
  this.planUsageAcc = planUsage;
12110
12232
  this.setPlanUsage = (next) => setPlanUsage(() => next);
12233
+ this.connectionStateAcc = connectionState;
12234
+ this.setConnectionState = (next) => setConnectionState(() => next);
12111
12235
  this.rcBridgeAcc = rcBridge;
12112
12236
  this.setRcBridge = (next) => setRcBridge(() => next);
12113
12237
  this.client.on("*", (frame) => this.handleEvent(frame.name, frame.payload));
12238
+ this.client.onLifecycle("close", () => this.setConnectionState("disconnected"));
12114
12239
  }
12115
12240
  async init() {
12116
12241
  const hello = await this.client.request("hello", { clientId: `tui-${process.pid}`, version: "1" });
@@ -12153,6 +12278,17 @@ class RemoteOrchestrator {
12153
12278
  } catch {}
12154
12279
  }));
12155
12280
  }
12281
+ async manualReconnect() {
12282
+ this.client.forceDisconnect();
12283
+ await ensureDaemonReachable();
12284
+ for (const task of this.tasksAcc())
12285
+ this.pendingInputBroker.clearForTask(task.id);
12286
+ await this.init();
12287
+ this.setConnectionState("online");
12288
+ }
12289
+ connectionStateSignal() {
12290
+ return this.connectionStateAcc;
12291
+ }
12156
12292
  dispose() {
12157
12293
  this.client.close();
12158
12294
  }
@@ -12405,15 +12541,16 @@ class RemoteOrchestrator {
12405
12541
  var init_remote_orchestrator = __esm(() => {
12406
12542
  init_dev();
12407
12543
  init_core();
12544
+ init_daemon_process();
12408
12545
  });
12409
12546
 
12410
12547
  // src/orchestrator/worktree/git.ts
12411
- import { spawnSync as spawnSync8 } from "child_process";
12548
+ import { spawnSync as spawnSync7 } from "child_process";
12412
12549
  function git2(args, opts) {
12413
12550
  if (!opts.cwd) {
12414
12551
  throw new Error("git(): cwd is required; refusing to inherit from process.cwd()");
12415
12552
  }
12416
- const proc = spawnSync8("git", [...args], {
12553
+ const proc = spawnSync7("git", [...args], {
12417
12554
  cwd: opts.cwd,
12418
12555
  env: opts.env ? { ...process.env, ...opts.env } : process.env,
12419
12556
  encoding: "utf8",
@@ -12451,7 +12588,7 @@ var init_git2 = __esm(() => {
12451
12588
 
12452
12589
  // src/orchestrator/worktree/manager.ts
12453
12590
  import fs4 from "fs";
12454
- import path5 from "path";
12591
+ import path6 from "path";
12455
12592
 
12456
12593
  class GitWorktreeManager {
12457
12594
  async create(repo, branch, worktreePath, baseRef) {
@@ -12469,7 +12606,7 @@ class GitWorktreeManager {
12469
12606
  }
12470
12607
  throw new Error(`create(): ${worktreePath} exists but is not a registered git worktree`);
12471
12608
  }
12472
- fs4.mkdirSync(path5.dirname(worktreePath), { recursive: true });
12609
+ fs4.mkdirSync(path6.dirname(worktreePath), { recursive: true });
12473
12610
  const branchExists = this.branchExists(repo, branch);
12474
12611
  const args = branchExists ? ["worktree", "add", worktreePath, branch] : baseRef ? ["worktree", "add", "-b", branch, worktreePath, baseRef] : ["worktree", "add", "-b", branch, worktreePath];
12475
12612
  git2(args, { cwd: repo });
@@ -12524,8 +12661,8 @@ class GitWorktreeManager {
12524
12661
  if (!entry.branch || entry.detached)
12525
12662
  continue;
12526
12663
  const canonEntry = canonicalize2(entry.path);
12527
- const rel = path5.relative(canonRoot, canonEntry);
12528
- const callerPath = path5.join(callerRoot, rel);
12664
+ const rel = path6.relative(canonRoot, canonEntry);
12665
+ const callerPath = path6.join(callerRoot, rel);
12529
12666
  const dirty = await this.isDirty(entry.path);
12530
12667
  infos.push({
12531
12668
  path: callerPath,
@@ -12586,9 +12723,9 @@ class GitWorktreeManager {
12586
12723
  const gitDir = out.stdout.trim();
12587
12724
  if (!gitDir)
12588
12725
  return null;
12589
- const absolute = path5.isAbsolute(gitDir) ? gitDir : path5.resolve(worktreePath, gitDir);
12590
- const base = path5.basename(absolute);
12591
- return base === ".git" ? path5.dirname(absolute) : absolute;
12726
+ const absolute = path6.isAbsolute(gitDir) ? gitDir : path6.resolve(worktreePath, gitDir);
12727
+ const base = path6.basename(absolute);
12728
+ return base === ".git" ? path6.dirname(absolute) : absolute;
12592
12729
  } catch (err) {
12593
12730
  if (err instanceof GitCommandError)
12594
12731
  return null;
@@ -12628,7 +12765,7 @@ function parsePorcelain2(out) {
12628
12765
  return records;
12629
12766
  }
12630
12767
  function requireAbsolute(name, value) {
12631
- if (!value || !path5.isAbsolute(value)) {
12768
+ if (!value || !path6.isAbsolute(value)) {
12632
12769
  throw new Error(`${name} must be an absolute path, got: ${JSON.stringify(value)}`);
12633
12770
  }
12634
12771
  }
@@ -12636,7 +12773,7 @@ function canonicalize2(p2) {
12636
12773
  try {
12637
12774
  return fs4.realpathSync(p2);
12638
12775
  } catch {
12639
- return path5.resolve(p2);
12776
+ return path6.resolve(p2);
12640
12777
  }
12641
12778
  }
12642
12779
  var init_manager = __esm(() => {
@@ -12711,7 +12848,7 @@ function DialogConfirm(props) {
12711
12848
  props.onCancel?.();
12712
12849
  dialog.clear();
12713
12850
  });
12714
- insert(_el$9, () => titlecase(key === "cancel" ? props.label ?? key : key));
12851
+ insert(_el$9, () => titlecase(key === "cancel" ? props.label ?? key : props.confirmLabel ?? key));
12715
12852
  effect((_p$) => {
12716
12853
  var _v$5 = key === store2.active ? theme.primary : undefined, _v$6 = key === store2.active ? theme.selectedListItemText : theme.textMuted;
12717
12854
  _v$5 !== _p$.e && (_p$.e = setProp(_el$8, "backgroundColor", _v$5, _p$.e));
@@ -12753,14 +12890,15 @@ var init_dialog_confirm = __esm(() => {
12753
12890
  init_theme();
12754
12891
  init_keymap();
12755
12892
  init_dialog();
12756
- DialogConfirm.show = (dialog, title, message, label) => {
12893
+ DialogConfirm.show = (dialog, title, message, label, confirmLabel) => {
12757
12894
  return new Promise((resolve3) => {
12758
12895
  dialog.replace(() => createComponent2(DialogConfirm, {
12759
12896
  title,
12760
12897
  message,
12761
12898
  onConfirm: () => resolve3(true),
12762
12899
  onCancel: () => resolve3(false),
12763
- label
12900
+ label,
12901
+ confirmLabel
12764
12902
  }), () => resolve3(undefined));
12765
12903
  dialog.setSize("small");
12766
12904
  });
@@ -13545,8 +13683,8 @@ function args(player, file, volume) {
13545
13683
  function pickPlayer() {
13546
13684
  if (cachedPlayer !== undefined)
13547
13685
  return cachedPlayer;
13548
- const path6 = process.env.PATH ?? "";
13549
- const segments = path6.split(":").filter(Boolean);
13686
+ const path7 = process.env.PATH ?? "";
13687
+ const segments = path7.split(":").filter(Boolean);
13550
13688
  cachedPlayer = PLAYERS.find((p2) => segments.some((dir) => existsSync5(join10(dir, p2)))) ?? null;
13551
13689
  return cachedPlayer;
13552
13690
  }
@@ -13566,9 +13704,9 @@ function pulse(volume = 0.4) {
13566
13704
  const player = pickPlayer();
13567
13705
  if (!player)
13568
13706
  return;
13569
- ensureAsset().then((path6) => {
13707
+ ensureAsset().then((path7) => {
13570
13708
  try {
13571
- const proc = Bun.spawn(args(player, path6, volume), {
13709
+ const proc = Bun.spawn(args(player, path7, volume), {
13572
13710
  stdin: "ignore",
13573
13711
  stdout: "ignore",
13574
13712
  stderr: "ignore"
@@ -15194,7 +15332,7 @@ var init_create_pr_button = __esm(() => {
15194
15332
  });
15195
15333
 
15196
15334
  // src/tui/lib/worktree-opener.ts
15197
- import { spawn as spawn4 } from "child_process";
15335
+ import { spawn as spawn5 } from "child_process";
15198
15336
  import { existsSync as existsSync6 } from "fs";
15199
15337
  import { basename as basename4, delimiter, isAbsolute as isAbsolute2, join as join12 } from "path";
15200
15338
  function executableOnPath(command, env, exists) {
@@ -15236,7 +15374,7 @@ function detectWorktreeOpener(deps = {}) {
15236
15374
  }
15237
15375
  if (platform === "darwin" && executableOnPath("open", env, exists)) {
15238
15376
  for (const app of MAC_APP_CANDIDATES) {
15239
- if (app.paths.some((path6) => exists(path6))) {
15377
+ if (app.paths.some((path7) => exists(path7))) {
15240
15378
  return { id: app.id, label: app.label, command: "open", args: ["-a", app.appName] };
15241
15379
  }
15242
15380
  }
@@ -15253,7 +15391,7 @@ function buildOpenWorktreeCommand(worktreePath, opener) {
15253
15391
  function openWorktree(worktreePath, opener, deps = {}) {
15254
15392
  if (!worktreePath)
15255
15393
  return false;
15256
- const spawnFn = deps.spawn ?? spawn4;
15394
+ const spawnFn = deps.spawn ?? spawn5;
15257
15395
  const [command, args2] = buildOpenWorktreeCommand(worktreePath, opener);
15258
15396
  try {
15259
15397
  const child = spawnFn(command, args2, { detached: true, stdio: "ignore" });
@@ -16304,6 +16442,10 @@ function TopBar(props) {
16304
16442
  theme
16305
16443
  } = useTheme();
16306
16444
  const dialog = useDialog();
16445
+ const connectionState = createMemo(() => {
16446
+ const orch = props.orchestrator;
16447
+ return orch instanceof RemoteOrchestrator ? orch.connectionStateSignal()() : "online";
16448
+ });
16307
16449
  const remoteOrch = props.orchestrator instanceof RemoteOrchestrator ? props.orchestrator : null;
16308
16450
  const rcBridge = props.orchestrator.rcBridgeSignal();
16309
16451
  const isBridgeOn = () => rcBridge().state === "running" || rcBridge().state === "starting";
@@ -16389,22 +16531,46 @@ function TopBar(props) {
16389
16531
  setProp(_el$11, "justifyContent", "center");
16390
16532
  insert(_el$11, createComponent2(Show, {
16391
16533
  get when() {
16392
- return props.activeTask() !== undefined;
16534
+ return connectionState() === "online";
16535
+ },
16536
+ get fallback() {
16537
+ return (() => {
16538
+ var _el$14 = createElement("text");
16539
+ insertNode(_el$14, createTextNode(`daemon disconnected`));
16540
+ setProp(_el$14, "wrapMode", "none");
16541
+ effect((_p$) => {
16542
+ var _v$0 = theme.error, _v$1 = TextAttributes18.BOLD;
16543
+ _v$0 !== _p$.e && (_p$.e = setProp(_el$14, "fg", _v$0, _p$.e));
16544
+ _v$1 !== _p$.t && (_p$.t = setProp(_el$14, "attributes", _v$1, _p$.t));
16545
+ return _p$;
16546
+ }, {
16547
+ e: undefined,
16548
+ t: undefined
16549
+ });
16550
+ return _el$14;
16551
+ })();
16393
16552
  },
16394
16553
  get children() {
16395
- var _el$12 = createElement("text");
16396
- setProp(_el$12, "wrapMode", "none");
16397
- insert(_el$12, () => props.activeTask()?.branch);
16398
- effect((_p$) => {
16399
- var _v$5 = theme.text, _v$6 = TextAttributes18.BOLD;
16400
- _v$5 !== _p$.e && (_p$.e = setProp(_el$12, "fg", _v$5, _p$.e));
16401
- _v$6 !== _p$.t && (_p$.t = setProp(_el$12, "attributes", _v$6, _p$.t));
16402
- return _p$;
16403
- }, {
16404
- e: undefined,
16405
- t: undefined
16554
+ return createComponent2(Show, {
16555
+ get when() {
16556
+ return props.activeTask() !== undefined;
16557
+ },
16558
+ get children() {
16559
+ var _el$12 = createElement("text");
16560
+ setProp(_el$12, "wrapMode", "none");
16561
+ insert(_el$12, () => props.activeTask()?.branch);
16562
+ effect((_p$) => {
16563
+ var _v$5 = theme.text, _v$6 = TextAttributes18.BOLD;
16564
+ _v$5 !== _p$.e && (_p$.e = setProp(_el$12, "fg", _v$5, _p$.e));
16565
+ _v$6 !== _p$.t && (_p$.t = setProp(_el$12, "attributes", _v$6, _p$.t));
16566
+ return _p$;
16567
+ }, {
16568
+ e: undefined,
16569
+ t: undefined
16570
+ });
16571
+ return _el$12;
16572
+ }
16406
16573
  });
16407
- return _el$12;
16408
16574
  }
16409
16575
  }));
16410
16576
  setProp(_el$13, "flexDirection", "row");
@@ -16511,7 +16677,7 @@ var init_sync = __esm(() => {
16511
16677
  import { randomUUID } from "crypto";
16512
16678
  import { appendFile, mkdir as mkdir2, readFile as readFile3, readdir, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
16513
16679
  import { homedir as homedir8 } from "os";
16514
- import path6 from "path";
16680
+ import path7 from "path";
16515
16681
  function encodeCwd(cwd) {
16516
16682
  return cwd.replace(/[/.]/g, "-");
16517
16683
  }
@@ -16519,7 +16685,7 @@ async function readHistory(sessionId, deps = defaultDeps2) {
16519
16685
  const root = deps.projectsDir();
16520
16686
  const projectDirs = await deps.readdir(root);
16521
16687
  for (const dir of projectDirs) {
16522
- const candidate = path6.join(root, dir, `${sessionId}.jsonl`);
16688
+ const candidate = path7.join(root, dir, `${sessionId}.jsonl`);
16523
16689
  let raw;
16524
16690
  try {
16525
16691
  raw = await deps.readFile(candidate);
@@ -16534,7 +16700,7 @@ async function deleteHistory(sessionId, deps = defaultDeps2) {
16534
16700
  const root = deps.projectsDir();
16535
16701
  const projectDirs = await deps.readdir(root);
16536
16702
  for (const dir of projectDirs) {
16537
- const candidate = path6.join(root, dir, `${sessionId}.jsonl`);
16703
+ const candidate = path7.join(root, dir, `${sessionId}.jsonl`);
16538
16704
  try {
16539
16705
  await unlink2(candidate);
16540
16706
  } catch (err) {
@@ -16609,8 +16775,8 @@ function isObject(v2) {
16609
16775
  async function appendInterruptedUserPrompt(sessionId, cwd, prompt, deps = defaultDeps2) {
16610
16776
  if (!prompt || prompt.trim().length === 0)
16611
16777
  return;
16612
- const projectDir = path6.join(deps.projectsDir(), encodeCwd(cwd));
16613
- const filePath = path6.join(projectDir, `${sessionId}.jsonl`);
16778
+ const projectDir = path7.join(deps.projectsDir(), encodeCwd(cwd));
16779
+ const filePath = path7.join(projectDir, `${sessionId}.jsonl`);
16614
16780
  let lines = [];
16615
16781
  try {
16616
16782
  const raw = await readFile3(filePath, "utf8");
@@ -16680,7 +16846,7 @@ var defaultDeps2;
16680
16846
  var init_history = __esm(() => {
16681
16847
  defaultDeps2 = {
16682
16848
  projectsDir() {
16683
- return path6.join(homedir8(), ".claude", "projects");
16849
+ return path7.join(homedir8(), ".claude", "projects");
16684
16850
  },
16685
16851
  async readdir(p2) {
16686
16852
  try {
@@ -16765,15 +16931,15 @@ function delay(ms) {
16765
16931
  // src/engine/claude-code-local/sessions.ts
16766
16932
  import { readFile as readFile4, readdir as readdir2, stat as stat2 } from "fs/promises";
16767
16933
  import { homedir as homedir9 } from "os";
16768
- import path7 from "path";
16934
+ import path8 from "path";
16769
16935
  async function listSessionsForCwd(cwd, deps = defaultDeps3) {
16770
- const projectDir = path7.join(deps.projectsDir(), encodeCwd(cwd));
16936
+ const projectDir = path8.join(deps.projectsDir(), encodeCwd(cwd));
16771
16937
  const entries = await deps.readdir(projectDir);
16772
16938
  const jsonlNames = entries.filter((n2) => n2.endsWith(".jsonl"));
16773
16939
  const out = [];
16774
16940
  for (const name of jsonlNames) {
16775
16941
  const sessionId = name.slice(0, -".jsonl".length);
16776
- const filePath = path7.join(projectDir, name);
16942
+ const filePath = path8.join(projectDir, name);
16777
16943
  try {
16778
16944
  const [meta, raw] = await Promise.all([deps.stat(filePath), deps.readFile(filePath)]);
16779
16945
  const lines = raw.split(`
@@ -16838,7 +17004,7 @@ var init_sessions = __esm(() => {
16838
17004
  init_history();
16839
17005
  defaultDeps3 = {
16840
17006
  projectsDir() {
16841
- return path7.join(homedir9(), ".claude", "projects");
17007
+ return path8.join(homedir9(), ".claude", "projects");
16842
17008
  },
16843
17009
  async readdir(p2) {
16844
17010
  try {
@@ -16858,10 +17024,10 @@ var init_sessions = __esm(() => {
16858
17024
  });
16859
17025
 
16860
17026
  // src/engine/claude-code-local/spawn.ts
16861
- import { spawn as spawn5 } from "child_process";
17027
+ import { spawn as spawn6 } from "child_process";
16862
17028
  function spawnClaudeProcess(opts) {
16863
17029
  const args2 = buildArgs(opts);
16864
- const proc = spawn5(opts.binaryPath, args2, {
17030
+ const proc = spawn6(opts.binaryPath, args2, {
16865
17031
  cwd: opts.cwd,
16866
17032
  env: { ...process.env, ...opts.env ?? {} },
16867
17033
  stdio: ["pipe", "pipe", "pipe"]
@@ -17811,8 +17977,8 @@ function validateRepoPath(repo) {
17811
17977
  if (!stat3.isDirectory())
17812
17978
  return `not a directory: ${trimmed}`;
17813
17979
  try {
17814
- const { spawnSync: spawnSync9 } = __require("child_process");
17815
- const out = spawnSync9("git", ["rev-parse", "--git-dir"], {
17980
+ const { spawnSync: spawnSync8 } = __require("child_process");
17981
+ const out = spawnSync8("git", ["rev-parse", "--git-dir"], {
17816
17982
  cwd: trimmed,
17817
17983
  encoding: "utf-8",
17818
17984
  timeout: 2000,
@@ -17829,8 +17995,8 @@ function getCurrentBranch(repo) {
17829
17995
  if (!repo)
17830
17996
  return null;
17831
17997
  try {
17832
- const { spawnSync: spawnSync9 } = __require("child_process");
17833
- const out = spawnSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
17998
+ const { spawnSync: spawnSync8 } = __require("child_process");
17999
+ const out = spawnSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
17834
18000
  cwd: repo,
17835
18001
  encoding: "utf-8",
17836
18002
  timeout: 2000,
@@ -17850,8 +18016,8 @@ function listLocalBranches(repo) {
17850
18016
  if (!repo)
17851
18017
  return [];
17852
18018
  try {
17853
- const { spawnSync: spawnSync9 } = __require("child_process");
17854
- const out = spawnSync9("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
18019
+ const { spawnSync: spawnSync8 } = __require("child_process");
18020
+ const out = spawnSync8("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
17855
18021
  cwd: repo,
17856
18022
  encoding: "utf-8",
17857
18023
  timeout: 2000
@@ -18970,7 +19136,7 @@ var init_resume_dialog = __esm(() => {
18970
19136
  });
18971
19137
 
18972
19138
  // src/tui/panes/chat/composer/clipboard-image.ts
18973
- import { spawnSync as spawnSync9 } from "child_process";
19139
+ import { spawnSync as spawnSync8 } from "child_process";
18974
19140
  import { statSync as statSync4 } from "fs";
18975
19141
  function clipboardImageSupported() {
18976
19142
  return process.platform === "darwin";
@@ -18995,7 +19161,7 @@ function readClipboardImageMacOS(destPath) {
18995
19161
  "end try"
18996
19162
  ].join(`
18997
19163
  `);
18998
- const result = spawnSync9("osascript", ["-e", script], {
19164
+ const result = spawnSync8("osascript", ["-e", script], {
18999
19165
  timeout: 5000,
19000
19166
  stdio: ["ignore", "ignore", "ignore"]
19001
19167
  });
@@ -19183,25 +19349,25 @@ function findMentionContext(text, cursor) {
19183
19349
  }
19184
19350
  return { atPos, query: text.slice(atPos + 1, cursor) };
19185
19351
  }
19186
- function formatDisplayPath(path8) {
19187
- if (path8.startsWith("packages/"))
19188
- return path8.slice("packages/".length);
19189
- return path8;
19352
+ function formatDisplayPath(path9) {
19353
+ if (path9.startsWith("packages/"))
19354
+ return path9.slice("packages/".length);
19355
+ return path9;
19190
19356
  }
19191
19357
  function filterMentionMatches(files, query, limit) {
19192
19358
  if (files.length === 0 || limit <= 0)
19193
19359
  return [];
19194
19360
  const q2 = query.toLowerCase();
19195
19361
  if (q2.length === 0) {
19196
- return files.slice(0, limit).map((path8) => ({
19197
- path: path8,
19198
- displayPath: formatDisplayPath(path8),
19362
+ return files.slice(0, limit).map((path9) => ({
19363
+ path: path9,
19364
+ displayPath: formatDisplayPath(path9),
19199
19365
  score: 0
19200
19366
  }));
19201
19367
  }
19202
19368
  const matches = [];
19203
- for (const path8 of files) {
19204
- const lower = path8.toLowerCase();
19369
+ for (const path9 of files) {
19370
+ const lower = path9.toLowerCase();
19205
19371
  const slash = lower.lastIndexOf("/");
19206
19372
  const filename = slash >= 0 ? lower.slice(slash + 1) : lower;
19207
19373
  let score = 0;
@@ -19215,8 +19381,8 @@ function filterMentionMatches(files, query, limit) {
19215
19381
  score = 40;
19216
19382
  else
19217
19383
  continue;
19218
- score -= path8.length * 0.5;
19219
- matches.push({ path: path8, displayPath: formatDisplayPath(path8), score });
19384
+ score -= path9.length * 0.5;
19385
+ matches.push({ path: path9, displayPath: formatDisplayPath(path9), score });
19220
19386
  }
19221
19387
  matches.sort((a2, b2) => b2.score - a2.score);
19222
19388
  return matches.slice(0, limit);
@@ -19363,7 +19529,7 @@ function Composer(props) {
19363
19529
  total
19364
19530
  };
19365
19531
  });
19366
- function insertMentionSelection(path8) {
19532
+ function insertMentionSelection(path9) {
19367
19533
  const ref = textareaRef;
19368
19534
  const ctx4 = mentionContext();
19369
19535
  if (!ref || !ctx4)
@@ -19371,7 +19537,7 @@ function Composer(props) {
19371
19537
  const cursor = ref.cursorOffset;
19372
19538
  ref.setSelection(ctx4.atPos, cursor);
19373
19539
  ref.deleteSelection();
19374
- const inserted = `@${path8} `;
19540
+ const inserted = `@${path9} `;
19375
19541
  ref.insertText(inserted);
19376
19542
  setMentionDismissedAt(null);
19377
19543
  setMentionCursor(0);
@@ -20731,7 +20897,7 @@ function readWriteInput(input) {
20731
20897
  };
20732
20898
  }
20733
20899
  function makeHeader(filePath, verb, adds, removes) {
20734
- const path8 = filePath || "(unknown file)";
20900
+ const path9 = filePath || "(unknown file)";
20735
20901
  const parts = [];
20736
20902
  if (adds > 0) {
20737
20903
  parts.push(`Added ${adds} ${adds === 1 ? "line" : "lines"}`);
@@ -20741,11 +20907,11 @@ function makeHeader(filePath, verb, adds, removes) {
20741
20907
  parts.push(`${word} ${removes} ${removes === 1 ? "line" : "lines"}`);
20742
20908
  }
20743
20909
  if (parts.length === 0)
20744
- return `${verb} ${path8}`;
20745
- return `${verb} ${path8} \xB7 ${parts.join(", ")}`;
20910
+ return `${verb} ${path9}`;
20911
+ return `${verb} ${path9} \xB7 ${parts.join(", ")}`;
20746
20912
  }
20747
20913
  function makeMultiEditHeader(filePath, count, adds, removes) {
20748
- const path8 = filePath || "(unknown file)";
20914
+ const path9 = filePath || "(unknown file)";
20749
20915
  const segments = [];
20750
20916
  if (count > 0) {
20751
20917
  segments.push(`${count} ${count === 1 ? "edit" : "edits"}`);
@@ -20761,8 +20927,8 @@ function makeMultiEditHeader(filePath, count, adds, removes) {
20761
20927
  if (lineParts.length > 0)
20762
20928
  segments.push(lineParts.join(", "));
20763
20929
  if (segments.length === 0)
20764
- return `Edited ${path8}`;
20765
- return `Edited ${path8} \xB7 ${segments.join(" \xB7 ")}`;
20930
+ return `Edited ${path9}`;
20931
+ return `Edited ${path9} \xB7 ${segments.join(" \xB7 ")}`;
20766
20932
  }
20767
20933
  var COLLAPSED_LINE_CAP = 10;
20768
20934
 
@@ -22742,24 +22908,24 @@ import { join as join14 } from "path";
22742
22908
  function resolveHome() {
22743
22909
  return process.env.HOME ?? homedir11();
22744
22910
  }
22745
- async function safeReaddir(path8) {
22911
+ async function safeReaddir(path9) {
22746
22912
  try {
22747
- return await readdir3(path8);
22913
+ return await readdir3(path9);
22748
22914
  } catch {
22749
22915
  return [];
22750
22916
  }
22751
22917
  }
22752
- async function isFile(path8) {
22918
+ async function isFile(path9) {
22753
22919
  try {
22754
- const s2 = await stat3(path8);
22920
+ const s2 = await stat3(path9);
22755
22921
  return s2.isFile();
22756
22922
  } catch {
22757
22923
  return false;
22758
22924
  }
22759
22925
  }
22760
- async function isDir(path8) {
22926
+ async function isDir(path9) {
22761
22927
  try {
22762
- const s2 = await stat3(path8);
22928
+ const s2 = await stat3(path9);
22763
22929
  return s2.isDirectory();
22764
22930
  } catch {
22765
22931
  return false;
@@ -22830,9 +22996,9 @@ function extractDescription(content) {
22830
22996
  }
22831
22997
  return null;
22832
22998
  }
22833
- async function tryReadDescription(path8) {
22999
+ async function tryReadDescription(path9) {
22834
23000
  try {
22835
- const content = await readFile5(path8, "utf-8");
23001
+ const content = await readFile5(path9, "utf-8");
22836
23002
  return extractDescription(content);
22837
23003
  } catch {
22838
23004
  return null;
@@ -24265,12 +24431,12 @@ var init_sidebar = __esm(() => {
24265
24431
  });
24266
24432
 
24267
24433
  // src/tui/panes/sidebar/git-head.ts
24268
- import { spawnSync as spawnSync10 } from "child_process";
24434
+ import { spawnSync as spawnSync9 } from "child_process";
24269
24435
  function readCurrentBranch(repo) {
24270
24436
  if (!repo)
24271
24437
  return "";
24272
24438
  try {
24273
- const out = spawnSync10("git", ["symbolic-ref", "--short", "HEAD"], {
24439
+ const out = spawnSync9("git", ["symbolic-ref", "--short", "HEAD"], {
24274
24440
  cwd: repo,
24275
24441
  encoding: "utf8",
24276
24442
  stdio: ["ignore", "pipe", "pipe"]
@@ -24280,7 +24446,7 @@ function readCurrentBranch(repo) {
24280
24446
  if (name && name !== "HEAD")
24281
24447
  return name;
24282
24448
  }
24283
- const head = spawnSync10("git", ["rev-parse", "--verify", "HEAD"], {
24449
+ const head = spawnSync9("git", ["rev-parse", "--verify", "HEAD"], {
24284
24450
  cwd: repo,
24285
24451
  encoding: "utf8",
24286
24452
  stdio: ["ignore", "pipe", "pipe"]
@@ -24941,8 +25107,8 @@ function Shell(props) {
24941
25107
  return pp.prompt;
24942
25108
  });
24943
25109
  const worktreePathAcc = createMemo(() => {
24944
- const path8 = activeTask()?.worktreePath;
24945
- return path8 ? path8 : null;
25110
+ const path9 = activeTask()?.worktreePath;
25111
+ return path9 ? path9 : null;
24946
25112
  });
24947
25113
  const taskIdNullAcc = createMemo(() => selectedId());
24948
25114
  const diffBaseAcc = createMemo(() => worktreePathAcc() ? "HEAD" : null);
@@ -24969,6 +25135,44 @@ function Shell(props) {
24969
25135
  const baseAcc = focus.is(pane);
24970
25136
  return () => baseAcc() && dialog.stack.length === 0;
24971
25137
  };
25138
+ let showingDisconnectDialog = false;
25139
+ async function showDisconnectDialog() {
25140
+ const orch = props.orchestrator;
25141
+ if (!(orch instanceof RemoteOrchestrator))
25142
+ return;
25143
+ let message = "kobed is no longer reachable. Restart it and reconnect, or quit kobe?";
25144
+ while (true) {
25145
+ const choice = await DialogConfirm.show(dialog, "daemon disconnected", message, "Quit", "Restart");
25146
+ if (choice !== true) {
25147
+ try {
25148
+ renderer?.destroy();
25149
+ } catch {}
25150
+ process.exit(0);
25151
+ }
25152
+ try {
25153
+ await orch.manualReconnect();
25154
+ return;
25155
+ } catch (err) {
25156
+ const errMsg = err instanceof Error ? err.message : String(err);
25157
+ message = `Restart failed: ${errMsg}
25158
+
25159
+ Try again or quit?`;
25160
+ }
25161
+ }
25162
+ }
25163
+ createEffect(() => {
25164
+ const orch = props.orchestrator;
25165
+ if (!(orch instanceof RemoteOrchestrator))
25166
+ return;
25167
+ if (orch.connectionStateSignal()() !== "disconnected")
25168
+ return;
25169
+ if (showingDisconnectDialog)
25170
+ return;
25171
+ showingDisconnectDialog = true;
25172
+ showDisconnectDialog().finally(() => {
25173
+ showingDisconnectDialog = false;
25174
+ });
25175
+ });
24972
25176
  const FOCUS_HJKL_TARGETS = {
24973
25177
  h: "sidebar",
24974
25178
  j: "workspace",
@@ -25467,6 +25671,7 @@ var init_app = __esm(() => {
25467
25671
  init_Sidebar();
25468
25672
  init_terminal();
25469
25673
  init_dialog();
25674
+ init_dialog_confirm();
25470
25675
  });
25471
25676
 
25472
25677
  // src/tui/index.tsx