@sma1lboy/kobe 0.7.21 → 0.7.22

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
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
90
90
  package_default = {
91
91
  $schema: "https://json.schemastore.org/package.json",
92
92
  name: "@sma1lboy/kobe",
93
- version: "0.7.21",
93
+ version: "0.7.22",
94
94
  description: "TUI orchestrator for Claude Code (codename)",
95
95
  type: "module",
96
96
  packageManager: "bun@1.3.13",
@@ -3891,6 +3891,18 @@ function isDaemonVersionStale(daemonVersion, clientVersion) {
3891
3891
  return false;
3892
3892
  return daemonVersion !== clientVersion;
3893
3893
  }
3894
+ function isChannelName(value) {
3895
+ return typeof value === "string" && CHANNEL_NAME_SET.has(value);
3896
+ }
3897
+ function normalizeChannelFilter(value) {
3898
+ if (!Array.isArray(value))
3899
+ return null;
3900
+ const set = new Set;
3901
+ for (const name of value)
3902
+ if (isChannelName(name))
3903
+ set.add(name);
3904
+ return set.size > 0 ? set : null;
3905
+ }
3894
3906
  function serializeTask(task) {
3895
3907
  return {
3896
3908
  id: task.id,
@@ -3912,7 +3924,7 @@ function frameToLine(frame) {
3912
3924
  return `${JSON.stringify(frame)}
3913
3925
  `;
3914
3926
  }
3915
- var DAEMON_PROTOCOL_VERSION = 3, MIN_COMPATIBLE_PROTOCOL_VERSION = 2, CHANNEL_NAMES;
3927
+ var DAEMON_PROTOCOL_VERSION = 3, MIN_COMPATIBLE_PROTOCOL_VERSION = 2, CHANNEL_NAMES, CHANNEL_NAME_SET;
3916
3928
  var init_protocol = __esm(() => {
3917
3929
  CHANNEL_NAMES = [
3918
3930
  "task.snapshot",
@@ -3924,6 +3936,7 @@ var init_protocol = __esm(() => {
3924
3936
  "task.jobs",
3925
3937
  "worktree.changes"
3926
3938
  ];
3939
+ CHANNEL_NAME_SET = new Set(CHANNEL_NAMES);
3927
3940
  });
3928
3941
 
3929
3942
  // ../kobe-daemon/src/daemon/paths.ts
@@ -4685,7 +4698,7 @@ async function probeBinary(probe2) {
4685
4698
  return { found: false, error: err instanceof Error ? err.message : String(err) };
4686
4699
  }
4687
4700
  }
4688
- async function detectAvailableVendors(deps = defaultDeps5) {
4701
+ async function probeAvailableVendors(deps) {
4689
4702
  const probes = [
4690
4703
  ["claude", () => deps.findClaudeBinary()],
4691
4704
  ["codex", () => deps.findCodexBinary()],
@@ -4694,6 +4707,18 @@ async function detectAvailableVendors(deps = defaultDeps5) {
4694
4707
  const detected = await Promise.all(probes.map(async ([vendor, probe2]) => (await probeBinary(probe2)).found ? vendor : null));
4695
4708
  return detected.filter((v) => v !== null);
4696
4709
  }
4710
+ function detectAvailableVendors(deps = defaultDeps5) {
4711
+ if (deps !== defaultDeps5)
4712
+ return probeAvailableVendors(deps);
4713
+ if (cachedDefaultVendors)
4714
+ return cachedDefaultVendors;
4715
+ const pending = probeAvailableVendors(deps).catch((err) => {
4716
+ cachedDefaultVendors = null;
4717
+ throw err;
4718
+ });
4719
+ cachedDefaultVendors = pending;
4720
+ return pending;
4721
+ }
4697
4722
  async function availableEngineIds(deps = defaultDeps5) {
4698
4723
  const builtins = await detectAvailableVendors(deps);
4699
4724
  return [...builtins, ...getCustomEngineIds()];
@@ -4850,7 +4875,7 @@ function hasStringDeep(value, interestingKeys, depth = 0) {
4850
4875
  }
4851
4876
  return false;
4852
4877
  }
4853
- var defaultDeps5;
4878
+ var defaultDeps5, cachedDefaultVendors = null;
4854
4879
  var init_account_detect = __esm(() => {
4855
4880
  init_repos();
4856
4881
  init_binary();
@@ -6291,6 +6316,28 @@ var init_auto_title = __esm(() => {
6291
6316
  init_registry();
6292
6317
  });
6293
6318
 
6319
+ // src/tui/panes/terminal/launch.ts
6320
+ function wrapEngineLaunch(engineCmd, remoteKey, remoteCwd) {
6321
+ if (!remoteKey)
6322
+ return engineCmd;
6323
+ const host = execHostForRepo(remoteKey);
6324
+ host.ensureReady();
6325
+ return host.wrapCommand(engineCmd, { tty: true, cwd: remoteCwd });
6326
+ }
6327
+ function inheritedEnvPrefix() {
6328
+ const parts = [];
6329
+ for (const key of ["KOBE_HOME_DIR", "KOBE_DAEMON_SOCKET_PATH", "KOBE_TMUX_SOCKET"]) {
6330
+ const value = process.env[key];
6331
+ if (value && value.length > 0)
6332
+ parts.push(`${key}=${shellQuote(value)}`);
6333
+ }
6334
+ return parts.length > 0 ? `${parts.join(" ")} ` : "";
6335
+ }
6336
+ var REMOTE_KEY_OPTION = "@kobe_remote";
6337
+ var init_launch = __esm(() => {
6338
+ init_resolve();
6339
+ });
6340
+
6294
6341
  // src/tmux/client.ts
6295
6342
  var exports_client = {};
6296
6343
  __export(exports_client, {
@@ -6590,7 +6637,7 @@ async function ensureFallbackSession() {
6590
6637
  "-P",
6591
6638
  "-F",
6592
6639
  "#{pane_id}",
6593
- keepAlive(tasksPaneCommand(kobeCliInvocation()))
6640
+ keepAlive(inheritedEnvPrefix() + tasksPaneCommand(kobeCliInvocation()))
6594
6641
  ]);
6595
6642
  const tasksPane = r1.stdout.trim();
6596
6643
  if (tasksPane) {
@@ -6617,6 +6664,7 @@ async function switchClientBeforeKill(killedName, nextSessionName) {
6617
6664
  var KOBE_TMUX_SOCKET, SAFE_SPAWN_CWD, PANE_ROLE_OPTION = "@kobe_role", CLAUDE_ROLE_OPTION, CLAUDE_ROLE_VALUE = "claude", CHAT_TAB_SESSION_ID_OPTION = "@kobe_session_id", KOBE_HOME_SESSION = "kobe-home", HOME_KIND_OPTION = "@kobe_home";
6618
6665
  var init_client2 = __esm(() => {
6619
6666
  init_invocation();
6667
+ init_launch();
6620
6668
  KOBE_TMUX_SOCKET = process.env.KOBE_TMUX_SOCKET?.trim() || "kobe";
6621
6669
  SAFE_SPAWN_CWD = homedir11() || "/";
6622
6670
  CLAUDE_ROLE_OPTION = PANE_ROLE_OPTION;
@@ -6770,11 +6818,13 @@ async function runAutoTitlePass(orch, derive = deriveTitleFromSession) {
6770
6818
  }
6771
6819
  return renamed;
6772
6820
  }
6773
- function startAutoTitlePoller(orch, intervalMs = DEFAULT_AUTO_TITLE_POLL_MS) {
6821
+ function startAutoTitlePoller(orch, intervalMs = DEFAULT_AUTO_TITLE_POLL_MS, hasSubscribers) {
6774
6822
  if (intervalMs <= 0)
6775
6823
  return () => {};
6776
6824
  let running = false;
6777
6825
  const tick = () => {
6826
+ if (hasSubscribers && !hasSubscribers())
6827
+ return;
6778
6828
  if (running)
6779
6829
  return;
6780
6830
  running = true;
@@ -7517,6 +7567,8 @@ class WorktreeChangesCollector {
7517
7567
  tick() {
7518
7568
  if (this.stopped)
7519
7569
  return;
7570
+ if (this.options.hasSubscribers && !this.options.hasSubscribers())
7571
+ return;
7520
7572
  try {
7521
7573
  const tracked = trackedWorktreePaths(this.orch.listTasks());
7522
7574
  let pruned = false;
@@ -7571,10 +7623,10 @@ class WorktreeChangesCollector {
7571
7623
  this.bus.publish("worktree.changes", { changes });
7572
7624
  }
7573
7625
  }
7574
- function startWorktreeChangesCollector(orch, bus, tickMs = DEFAULT_WORKTREE_CHANGES_TICK_MS) {
7626
+ function startWorktreeChangesCollector(orch, bus, tickMs = DEFAULT_WORKTREE_CHANGES_TICK_MS, hasSubscribers) {
7575
7627
  if (tickMs <= 0)
7576
7628
  return () => {};
7577
- const collector = new WorktreeChangesCollector(orch, bus);
7629
+ const collector = new WorktreeChangesCollector(orch, bus, { hasSubscribers });
7578
7630
  collector.tick();
7579
7631
  const timer = setInterval(() => collector.tick(), tickMs);
7580
7632
  timer.unref?.();
@@ -7617,6 +7669,12 @@ async function startDaemonServer(orch, options = {}) {
7617
7669
  n++;
7618
7670
  return n;
7619
7671
  }
7672
+ function hasSubscribers() {
7673
+ for (const c of clients)
7674
+ if (c.subscribed)
7675
+ return true;
7676
+ return false;
7677
+ }
7620
7678
  function cancelIdleTimer() {
7621
7679
  if (idleTimer) {
7622
7680
  clearTimeout(idleTimer);
@@ -7652,7 +7710,8 @@ async function startDaemonServer(orch, options = {}) {
7652
7710
  socket,
7653
7711
  buffer: "",
7654
7712
  subscribed: false,
7655
- holdsLifetime: false
7713
+ holdsLifetime: false,
7714
+ channels: null
7656
7715
  };
7657
7716
  clients.add(client);
7658
7717
  socket.on("data", (chunk) => {
@@ -7684,7 +7743,7 @@ async function startDaemonServer(orch, options = {}) {
7684
7743
  updateTimer.unref?.();
7685
7744
  }
7686
7745
  const autoTitlePollMs = options.autoTitlePollMs ?? DEFAULT_AUTO_TITLE_POLL_MS;
7687
- const stopAutoTitlePoller = startAutoTitlePoller(orch, autoTitlePollMs);
7746
+ const stopAutoTitlePoller = startAutoTitlePoller(orch, autoTitlePollMs, hasSubscribers);
7688
7747
  const stopUiPrefsWatcher = startUiPrefsWatcher(bus, {
7689
7748
  statePath: defaultUiPrefsStatePath(options.homeDir),
7690
7749
  debounceMs: options.uiPrefsDebounceMs ?? DEFAULT_UI_PREFS_DEBOUNCE_MS
@@ -7693,7 +7752,7 @@ async function startDaemonServer(orch, options = {}) {
7693
7752
  path: defaultKeybindingsPath(options.homeDir),
7694
7753
  debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
7695
7754
  });
7696
- const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS);
7755
+ const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS, hasSubscribers);
7697
7756
  const serverApi = {
7698
7757
  socketPath,
7699
7758
  pidPath,
@@ -7743,21 +7802,28 @@ async function startDaemonServer(orch, options = {}) {
7743
7802
  async function dispatch(req, client) {
7744
7803
  if (req.name === "subscribe") {
7745
7804
  const payload = objectPayload(req.payload);
7805
+ const wasSubscribed = client.subscribed;
7746
7806
  client.subscribed = true;
7747
7807
  const role = payload.role === "gui" ? "gui" : "pane";
7748
7808
  client.holdsLifetime = role === "gui";
7809
+ client.channels = normalizeChannelFilter(payload.channels);
7810
+ const firstSubscriber = !wasSubscribed;
7749
7811
  if (client.holdsLifetime)
7750
7812
  cancelIdleTimer();
7751
- logDaemonInfo("conn", `client #${client.id} subscribed as ${role} \u2014 ${clients.size} client(s), ${guiCount()} gui`);
7813
+ logDaemonInfo("conn", `client #${client.id} subscribed as ${role}${client.channels ? ` [${[...client.channels].join(",")}]` : ""} \u2014 ${clients.size} client(s), ${guiCount()} gui${firstSubscriber ? " (collectors resume)" : ""}`);
7752
7814
  for (const event of bus.snapshot()) {
7815
+ if (client.channels && !client.channels.has(event.channel))
7816
+ continue;
7753
7817
  writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
7754
7818
  }
7755
- for (const payload2 of activity.currentNonIdle()) {
7756
- writeFrame(client, {
7757
- type: "event",
7758
- name: "engine-state",
7759
- payload: payload2
7760
- });
7819
+ if (!client.channels || client.channels.has("engine-state")) {
7820
+ for (const payload2 of activity.currentNonIdle()) {
7821
+ writeFrame(client, {
7822
+ type: "event",
7823
+ name: "engine-state",
7824
+ payload: payload2
7825
+ });
7826
+ }
7761
7827
  }
7762
7828
  return {};
7763
7829
  }
@@ -7816,10 +7882,13 @@ function writeFrame(client, frame) {
7816
7882
  client.socket.write(frameToLine(frame));
7817
7883
  }
7818
7884
  function broadcast(clients, frame) {
7885
+ const channel = frame.type === "event" && frame.name !== "daemon.stopping" ? frame.name : null;
7819
7886
  let line = null;
7820
7887
  for (const client of clients) {
7821
7888
  if (!client.subscribed && frame.type === "event")
7822
7889
  continue;
7890
+ if (channel && client.channels && !client.channels.has(channel))
7891
+ continue;
7823
7892
  line ??= frameToLine(frame);
7824
7893
  client.socket.write(line);
7825
7894
  }
@@ -10436,28 +10505,6 @@ var init_tmux_border_theme = __esm(() => {
10436
10505
  FOCUS_ACCENT_SLOT_NAMES2 = ["primary", "success", "info"];
10437
10506
  });
10438
10507
 
10439
- // src/tui/panes/terminal/launch.ts
10440
- function wrapEngineLaunch(engineCmd, remoteKey, remoteCwd) {
10441
- if (!remoteKey)
10442
- return engineCmd;
10443
- const host = execHostForRepo(remoteKey);
10444
- host.ensureReady();
10445
- return host.wrapCommand(engineCmd, { tty: true, cwd: remoteCwd });
10446
- }
10447
- function inheritedEnvPrefix() {
10448
- const parts = [];
10449
- for (const key of ["KOBE_HOME_DIR", "KOBE_DAEMON_SOCKET_PATH", "KOBE_TMUX_SOCKET"]) {
10450
- const value = process.env[key];
10451
- if (value && value.length > 0)
10452
- parts.push(`${key}=${shellQuote(value)}`);
10453
- }
10454
- return parts.length > 0 ? `${parts.join(" ")} ` : "";
10455
- }
10456
- var REMOTE_KEY_OPTION = "@kobe_remote";
10457
- var init_launch = __esm(() => {
10458
- init_resolve();
10459
- });
10460
-
10461
10508
  // src/tui/panes/terminal/pane-heal.ts
10462
10509
  function parseKobePaneRows(stdout) {
10463
10510
  const rows = [];
@@ -10480,9 +10527,6 @@ function parseKobePaneRows(stdout) {
10480
10527
  }
10481
10528
  return rows;
10482
10529
  }
10483
- function paneIdsByRole(rows, role) {
10484
- return rows.filter((row) => row.role === role).map((row) => row.paneId);
10485
- }
10486
10530
  function planPaneHeals(rows, opts) {
10487
10531
  const byWindow = new Map;
10488
10532
  for (const row of rows) {
@@ -10498,7 +10542,8 @@ function planPaneHeals(rows, opts) {
10498
10542
  if (tasksPane && (opts.force || tasksPane.version !== opts.currentVersion)) {
10499
10543
  targets.push({ role: "tasks", paneId: tasksPane.paneId });
10500
10544
  }
10501
- if (opsPane && (opts.force ? true : claudePane && opsPane.version !== opts.currentVersion)) {
10545
+ const opsNeedsRespawn = opts.force ? true : claudePane && (opts.vendorChanged || opsPane?.version !== opts.currentVersion);
10546
+ if (opsPane && opsNeedsRespawn) {
10502
10547
  targets.push({ role: "ops", paneId: opsPane.paneId, claudePaneId: claudePane ?? null });
10503
10548
  }
10504
10549
  }
@@ -10533,15 +10578,22 @@ function respawnCommandsFor(targets, args) {
10533
10578
  }
10534
10579
  return commands;
10535
10580
  }
10536
- async function relaunchEngineInAllWindows(session, cwd, command, remoteKey) {
10581
+ async function relaunchEngineInAllWindows(session, cwd, command, remoteKey, vendor) {
10537
10582
  const rows = await listKobePanes(session);
10538
10583
  if (!rows)
10539
10584
  return false;
10540
- const enginePanes = paneIdsByRole(rows, "claude");
10585
+ const enginePanes = rows.filter((r) => r.role === "claude");
10541
10586
  if (enginePanes.length === 0)
10542
10587
  return false;
10543
- const cmd = keepAlive(wrapEngineLaunch(shellQuoteArgv(command), remoteKey, cwd));
10544
- await runTmuxSequence(enginePanes.map((pane) => ["respawn-pane", "-k", "-c", localSpawnCwd(cwd), "-t", pane, cmd]));
10588
+ const localCwd = localSpawnCwd(cwd);
10589
+ const commands = [];
10590
+ for (const pane of enginePanes) {
10591
+ const launch = withClaudeSessionId(command, vendor);
10592
+ const cmd = keepAlive(wrapEngineLaunch(shellQuoteArgv(launch.argv), remoteKey, cwd));
10593
+ commands.push(["respawn-pane", "-k", "-c", localCwd, "-t", pane.paneId, cmd]);
10594
+ commands.push(launch.sessionId ? ["set-window-option", "-t", pane.paneId, CHAT_TAB_SESSION_ID_OPTION, launch.sessionId] : ["set-window-option", "-u", "-t", pane.paneId, CHAT_TAB_SESSION_ID_OPTION]);
10595
+ }
10596
+ await runTmuxSequence(commands);
10545
10597
  return true;
10546
10598
  }
10547
10599
  async function globalLayoutPrefs() {
@@ -10583,7 +10635,11 @@ async function healWorkspaceLayout(session, versions) {
10583
10635
  }
10584
10636
  }
10585
10637
  if (versions) {
10586
- commands.push(...respawnCommandsFor(planPaneHeals(rows, { currentVersion: CURRENT_VERSION, force: false }), versions));
10638
+ commands.push(...respawnCommandsFor(planPaneHeals(rows, {
10639
+ currentVersion: CURRENT_VERSION,
10640
+ force: false,
10641
+ vendorChanged: versions.vendorChanged
10642
+ }), versions));
10587
10643
  }
10588
10644
  if (commands.length > 0)
10589
10645
  await runTmuxSequence(commands);
@@ -10650,6 +10706,7 @@ async function refreshKobeWorkspacePanes(session) {
10650
10706
  var PANE_VERSION_OPTION = "@kobe_pane_version", KOBE_PANE_LIST_FORMAT;
10651
10707
  var init_pane_heal = __esm(() => {
10652
10708
  init_invocation();
10709
+ init_interactive_command();
10653
10710
  init_resolve();
10654
10711
  init_client2();
10655
10712
  init_tmux_border_theme();
@@ -10988,10 +11045,15 @@ async function ensureSessionImpl(opts) {
10988
11045
  return true;
10989
11046
  }
10990
11047
  if (action.kind === "respawn-engine") {
10991
- if (await relaunchEngineInAllWindows(opts.name, opts.cwd, opts.command, remoteKey)) {
11048
+ if (await relaunchEngineInAllWindows(opts.name, opts.cwd, opts.command, remoteKey, opts.vendor)) {
10992
11049
  if (opts.vendor)
10993
11050
  await setSessionOption(opts.name, "@kobe_vendor", opts.vendor);
10994
- await healWorkspaceLayout(opts.name, { cwd: opts.cwd, taskId: opts.taskId, vendor: opts.vendor });
11051
+ await healWorkspaceLayout(opts.name, {
11052
+ cwd: opts.cwd,
11053
+ taskId: opts.taskId,
11054
+ vendor: opts.vendor,
11055
+ vendorChanged: true
11056
+ });
10995
11057
  return true;
10996
11058
  }
10997
11059
  }
@@ -11643,6 +11705,23 @@ async function setActive(ctx) {
11643
11705
  await daemon.request("task.setActive", { taskId });
11644
11706
  return { ok: true, activeTaskId: taskId };
11645
11707
  }
11708
+ async function archive(ctx) {
11709
+ const daemon = daemonOf(ctx);
11710
+ const taskId = ctx.args.require("task-id");
11711
+ const archived = ctx.args.bool("archived") ?? true;
11712
+ const res = await daemon.request("task.archive", { taskId, archived });
11713
+ if (archived)
11714
+ await ctx.runtime.tearDownSession(taskId);
11715
+ return res;
11716
+ }
11717
+ async function deleteTask(ctx) {
11718
+ const daemon = daemonOf(ctx);
11719
+ const taskId = ctx.args.require("task-id");
11720
+ const force = ctx.args.bool("force") ?? false;
11721
+ const res = await daemon.request("task.delete", { taskId, force });
11722
+ await ctx.runtime.tearDownSession(taskId);
11723
+ return res;
11724
+ }
11646
11725
  async function adopt(ctx) {
11647
11726
  const daemon = daemonOf(ctx);
11648
11727
  const { args } = ctx;
@@ -12006,10 +12085,7 @@ var init_api_cmd = __esm(() => {
12006
12085
  F.taskId(),
12007
12086
  { name: "archived", type: "bool", default: "true", description: "true to archive, false to unarchive." }
12008
12087
  ],
12009
- handler: (ctx) => simpleRpc(ctx, "task.archive", {
12010
- taskId: ctx.args.require("task-id"),
12011
- archived: ctx.args.bool("archived") ?? true
12012
- })
12088
+ handler: archive
12013
12089
  },
12014
12090
  {
12015
12091
  name: "pin",
@@ -12039,10 +12115,7 @@ var init_api_cmd = __esm(() => {
12039
12115
  name: "delete",
12040
12116
  summary: "Permanently remove a task (and its worktree). DESTRUCTIVE \u2014 prefer `archive`. Needs --force on a dirty worktree.",
12041
12117
  flags: [F.taskId(), { name: "force", type: "bool", description: "Delete even with uncommitted changes." }],
12042
- handler: (ctx) => simpleRpc(ctx, "task.delete", {
12043
- taskId: ctx.args.require("task-id"),
12044
- force: ctx.args.bool("force") ?? false
12045
- })
12118
+ handler: deleteTask
12046
12119
  },
12047
12120
  {
12048
12121
  name: "discover-adoptable",
@@ -12086,7 +12159,12 @@ var init_api_cmd = __esm(() => {
12086
12159
  isTaskRunning: (taskId) => sessionExists(tmuxSessionName(taskId)),
12087
12160
  deliverPrompt: (client, target, prompt) => deliverPrompt(client, target, prompt),
12088
12161
  resolveRepoRoot: async (absPath) => (await Promise.resolve().then(() => (init_repos(), exports_repos))).resolveRepoRoot(absPath),
12089
- readWorktreeChanges: async (worktreePath) => (await Promise.resolve().then(() => (init_worktree_changes(), exports_worktree_changes))).readWorktreeChanges(worktreePath)
12162
+ readWorktreeChanges: async (worktreePath) => (await Promise.resolve().then(() => (init_worktree_changes(), exports_worktree_changes))).readWorktreeChanges(worktreePath),
12163
+ tearDownSession: async (taskId) => {
12164
+ const session = tmuxSessionName(taskId);
12165
+ await switchClientBeforeKill(session).catch(() => {});
12166
+ await killSession(session).catch(() => {});
12167
+ }
12090
12168
  };
12091
12169
  });
12092
12170
 
@@ -13071,6 +13149,19 @@ function unquoteGitPath(p) {
13071
13149
  return inner.replace(/\\([\\"nt])/g, (_m, c) => c === "n" ? `
13072
13150
  ` : c === "t" ? "\t" : c);
13073
13151
  }
13152
+ async function mapPool(items, limit, fn) {
13153
+ const results = new Array(items.length);
13154
+ const width = Math.max(1, Math.min(limit, items.length));
13155
+ let next = 0;
13156
+ async function worker() {
13157
+ while (next < items.length) {
13158
+ const index = next++;
13159
+ results[index] = await fn(items[index], index);
13160
+ }
13161
+ }
13162
+ await Promise.all(Array.from({ length: width }, () => worker()));
13163
+ return results;
13164
+ }
13074
13165
  async function untrackedPatch(cwd, relPath) {
13075
13166
  const res = await runGit(cwd, ["diff", "--no-color", "--no-index", "--", "/dev/null", relPath]);
13076
13167
  if (res.stdout.trim())
@@ -13120,6 +13211,7 @@ async function handleDiffRequest(req, url) {
13120
13211
  const stagedByFile = splitDiffByFile(staged.stdout);
13121
13212
  const records = statusRes.stdout.split("\x00");
13122
13213
  const files = [];
13214
+ const untrackedJobs = [];
13123
13215
  for (let i = 0;i < records.length; i++) {
13124
13216
  const rec = records[i];
13125
13217
  if (!rec)
@@ -13136,7 +13228,7 @@ async function handleDiffRequest(req, url) {
13136
13228
  const staged2 = x !== " " && x !== "?";
13137
13229
  let patch = "";
13138
13230
  if (untracked) {
13139
- patch = await untrackedPatch(worktreePath, path11);
13231
+ untrackedJobs.push({ fileIndex: files.length, path: path11 });
13140
13232
  } else {
13141
13233
  patch = stagedByFile.get(path11) ?? unstagedByFile.get(path11) ?? "";
13142
13234
  if (stagedByFile.has(path11) && unstagedByFile.has(path11)) {
@@ -13150,13 +13242,19 @@ async function handleDiffRequest(req, url) {
13150
13242
  patch
13151
13243
  });
13152
13244
  }
13245
+ if (untrackedJobs.length > 0) {
13246
+ const patches = await mapPool(untrackedJobs, UNTRACKED_DIFF_CONCURRENCY, (job) => untrackedPatch(worktreePath, job.path));
13247
+ for (let j = 0;j < untrackedJobs.length; j++) {
13248
+ files[untrackedJobs[j].fileIndex].patch = patches[j];
13249
+ }
13250
+ }
13153
13251
  files.sort((a, b) => a.path.localeCompare(b.path));
13154
13252
  const raw = [staged.stdout, unstaged.stdout].filter(Boolean).join(`
13155
13253
  `);
13156
13254
  const result = { files, raw };
13157
13255
  return Response.json(result);
13158
13256
  }
13159
- var GIT_TIMEOUT_MS = 15000;
13257
+ var GIT_TIMEOUT_MS = 15000, UNTRACKED_DIFF_CONCURRENCY = 8;
13160
13258
 
13161
13259
  // src/web/notes.ts
13162
13260
  import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
@@ -13223,6 +13321,18 @@ var init_notes = __esm(() => {
13223
13321
  init_env();
13224
13322
  });
13225
13323
 
13324
+ // ../kobe-web/server/spa-channels.ts
13325
+ var SPA_CHANNELS, SPA_CHANNEL_SET;
13326
+ var init_spa_channels = __esm(() => {
13327
+ SPA_CHANNELS = [
13328
+ "task.snapshot",
13329
+ "active-task",
13330
+ "engine-state",
13331
+ "update"
13332
+ ];
13333
+ SPA_CHANNEL_SET = new Set(SPA_CHANNELS);
13334
+ });
13335
+
13226
13336
  // ../kobe-web/server/daemon-link.ts
13227
13337
  function sleep2(ms) {
13228
13338
  return new Promise((resolve6) => setTimeout(resolve6, ms));
@@ -13291,7 +13401,7 @@ class DaemonLink {
13291
13401
  this.engineStates = {};
13292
13402
  client.on("*", (frame) => this.onFrame(frame.name, frame.payload));
13293
13403
  client.onLifecycle("close", () => this.onDrop(client));
13294
- await client.subscribe({ role: "gui" });
13404
+ await client.subscribe({ role: "gui", channels: SPA_CHANNELS });
13295
13405
  } catch (err) {
13296
13406
  client.close();
13297
13407
  throw err;
@@ -13341,6 +13451,8 @@ class DaemonLink {
13341
13451
  default:
13342
13452
  break;
13343
13453
  }
13454
+ if (!SPA_CHANNEL_SET.has(name))
13455
+ return;
13344
13456
  const event = { channel: name, payload };
13345
13457
  for (const sink of this.eventSinks)
13346
13458
  sink(event);
@@ -13359,6 +13471,7 @@ var init_daemon_link = __esm(() => {
13359
13471
  init_daemon_process();
13360
13472
  init_paths2();
13361
13473
  init_protocol();
13474
+ init_spa_channels();
13362
13475
  });
13363
13476
 
13364
13477
  // ../kobe-web/server/session.ts
@@ -13890,17 +14003,29 @@ var init_skill_cmd = __esm(() => {
13890
14003
  var exports_hook_cmd = {};
13891
14004
  __export(exports_hook_cmd, {
13892
14005
  runHookSubcommand: () => runHookSubcommand,
14006
+ readTextWithTimeout: () => readTextWithTimeout,
13893
14007
  parseWorktreeAddPath: () => parseWorktreeAddPath,
13894
14008
  ensureGlobalKobeHooks: () => ensureGlobalKobeHooks
13895
14009
  });
13896
14010
  import { homedir as homedir17 } from "os";
13897
14011
  import { join as join13, resolve as resolve7 } from "path";
13898
- async function readStdinPayload() {
14012
+ async function readTextWithTimeout(read, timeoutMs = STDIN_READ_TIMEOUT_MS) {
14013
+ let raceTimer;
13899
14014
  try {
13900
- const text = await Promise.race([
13901
- Bun.stdin.text(),
13902
- new Promise((resolve8) => setTimeout(() => resolve8(""), 500))
14015
+ return await Promise.race([
14016
+ read(),
14017
+ new Promise((resolve8) => {
14018
+ raceTimer = setTimeout(() => resolve8(""), timeoutMs);
14019
+ })
13903
14020
  ]);
14021
+ } finally {
14022
+ if (raceTimer !== undefined)
14023
+ clearTimeout(raceTimer);
14024
+ }
14025
+ }
14026
+ async function readStdinPayload() {
14027
+ try {
14028
+ const text = await readTextWithTimeout(() => Bun.stdin.text());
13904
14029
  if (!text.trim())
13905
14030
  return {};
13906
14031
  const parsed = JSON.parse(text);
@@ -14066,7 +14191,7 @@ async function runHookSetup(_argv) {
14066
14191
  ].join(`
14067
14192
  `));
14068
14193
  }
14069
- var WORKTREE_CREATED_VERB = "worktree-created", SYNC_SETTING_KEY = "externalWorktreeSync";
14194
+ var STDIN_READ_TIMEOUT_MS = 500, WORKTREE_CREATED_VERB = "worktree-created", SYNC_SETTING_KEY = "externalWorktreeSync";
14070
14195
  var init_hook_cmd = __esm(() => {
14071
14196
  init_daemon_process();
14072
14197
  init_hook_adapter2();
@@ -15400,6 +15525,8 @@ class RemoteOrchestrator {
15400
15525
  setConnectionState;
15401
15526
  ensureReachable;
15402
15527
  role;
15528
+ channels;
15529
+ subscribesTasks;
15403
15530
  reconnecting = false;
15404
15531
  constructor(client, options = {}) {
15405
15532
  this.client = client;
@@ -15435,6 +15562,8 @@ class RemoteOrchestrator {
15435
15562
  this.setConnectionState = (next) => setConnectionState(() => next);
15436
15563
  this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
15437
15564
  this.role = options.role ?? "pane";
15565
+ this.channels = options.channels;
15566
+ this.subscribesTasks = !options.channels || options.channels.includes("task.snapshot");
15438
15567
  this.client.on("*", (frame) => this.handleEvent(frame.name, frame.payload));
15439
15568
  this.client.onLifecycle("close", () => {
15440
15569
  this.setConnectionState("disconnected");
@@ -15484,9 +15613,9 @@ class RemoteOrchestrator {
15484
15613
  throw new Error(`kobe daemon is protocol v${daemonVersion} (min v${daemonMin}); this client is v${DAEMON_PROTOCOL_VERSION} (min v${MIN_COMPATIBLE_PROTOCOL_VERSION}). Restart the daemon (\`kobe daemon restart\`) or upgrade kobe.`);
15485
15614
  }
15486
15615
  this.setDaemonVersionSig(typeof hello.kobeVersion === "string" ? hello.kobeVersion : null);
15487
- if (hello.tasks)
15616
+ if (hello.tasks && this.subscribesTasks)
15488
15617
  this.setTasks(hello.tasks.map(deserializeTask));
15489
- await this.client.subscribe({ role: this.role });
15618
+ await this.client.subscribe({ role: this.role, channels: this.channels });
15490
15619
  if (hello.capabilities?.includes("worktree.changes")) {
15491
15620
  if (this.worktreeChangesAcc() === null)
15492
15621
  this.setWorktreeChangesSig(new Map);
@@ -17190,97 +17319,104 @@ function NewTaskDialogView(props) {
17190
17319
  setCloneParent(joinDrill(cloneParent(), split.base, picked));
17191
17320
  setCloneParentCursor(absoluteIndex);
17192
17321
  }
17322
+ const adoptSelectAll = () => {
17323
+ const list2 = adoptList();
17324
+ if (list2.length === 0)
17325
+ return;
17326
+ const allSelected = list2.every((w) => adoptSelected().has(w.path));
17327
+ setAdoptSelected(allSelected ? new Set : new Set(list2.map((w) => w.path)));
17328
+ };
17193
17329
  useBindings(() => ({
17194
- bindings: [{
17195
- key: "tab",
17196
- cmd: () => setField((f) => nextField(f, tab()))
17197
- }, {
17198
- key: "ctrl+]",
17199
- cmd: () => switchToTab(nextDialogTab(tab()))
17200
- }, {
17201
- key: "ctrl+[",
17202
- cmd: () => switchToTab(nextDialogTab(tab()))
17203
- }, {
17204
- key: "ctrl+e",
17205
- cmd: () => setVendor((v) => nextVendorWithin(availableVendors(), v))
17206
- }, {
17207
- key: "up",
17208
- cmd: () => {
17209
- if (cloneInFlight())
17210
- return;
17211
- if (tab() === "existing" && field() === "repo") {
17212
- const list2 = activeList();
17213
- if (list2.length === 0)
17330
+ bindings: [
17331
+ {
17332
+ key: "tab",
17333
+ cmd: () => setField((f) => nextField(f, tab()))
17334
+ },
17335
+ {
17336
+ key: "ctrl+]",
17337
+ cmd: () => switchToTab(nextDialogTab(tab()))
17338
+ },
17339
+ {
17340
+ key: "ctrl+[",
17341
+ cmd: () => switchToTab(nextDialogTab(tab()))
17342
+ },
17343
+ {
17344
+ key: "ctrl+e",
17345
+ cmd: () => setVendor((v) => nextVendorWithin(availableVendors(), v))
17346
+ },
17347
+ {
17348
+ key: "up",
17349
+ cmd: () => {
17350
+ if (cloneInFlight())
17214
17351
  return;
17215
- setRepoCursor(clampCursor(repoCursor() - 1, list2.length));
17216
- return;
17217
- }
17218
- if (tab() === "existing" && field() === "baseRef") {
17219
- const list2 = branchFiltered();
17220
- if (list2.length === 0)
17352
+ if (tab() === "existing" && field() === "repo") {
17353
+ const list2 = activeList();
17354
+ if (list2.length === 0)
17355
+ return;
17356
+ setRepoCursor(clampCursor(repoCursor() - 1, list2.length));
17221
17357
  return;
17222
- setBranchCursor(clampCursor(branchCursor() - 1, list2.length));
17223
- return;
17224
- }
17225
- if (tab() === "clone" && field() === "cloneParent") {
17226
- const list2 = cloneParentFiltered();
17227
- if (list2.length === 0)
17358
+ }
17359
+ if (tab() === "existing" && field() === "baseRef") {
17360
+ const list2 = branchFiltered();
17361
+ if (list2.length === 0)
17362
+ return;
17363
+ setBranchCursor(clampCursor(branchCursor() - 1, list2.length));
17228
17364
  return;
17229
- setCloneParentCursor(clampCursor(cloneParentCursor() - 1, list2.length));
17230
- return;
17231
- }
17232
- if (tab() === "adopt") {
17233
- const list2 = adoptList();
17234
- if (list2.length === 0)
17365
+ }
17366
+ if (tab() === "clone" && field() === "cloneParent") {
17367
+ const list2 = cloneParentFiltered();
17368
+ if (list2.length === 0)
17369
+ return;
17370
+ setCloneParentCursor(clampCursor(cloneParentCursor() - 1, list2.length));
17235
17371
  return;
17236
- setAdoptCursor(clampCursor(adoptCursor() - 1, list2.length));
17372
+ }
17373
+ if (tab() === "adopt") {
17374
+ const list2 = adoptList();
17375
+ if (list2.length === 0)
17376
+ return;
17377
+ setAdoptCursor(clampCursor(adoptCursor() - 1, list2.length));
17378
+ }
17237
17379
  }
17238
- }
17239
- }, {
17240
- key: "down",
17241
- cmd: () => {
17242
- if (cloneInFlight())
17243
- return;
17244
- if (tab() === "existing" && field() === "repo") {
17245
- const list2 = activeList();
17246
- if (list2.length === 0)
17380
+ },
17381
+ {
17382
+ key: "down",
17383
+ cmd: () => {
17384
+ if (cloneInFlight())
17247
17385
  return;
17248
- setRepoCursor(clampCursor(repoCursor() + 1, list2.length));
17249
- return;
17250
- }
17251
- if (tab() === "existing" && field() === "baseRef") {
17252
- const list2 = branchFiltered();
17253
- if (list2.length === 0)
17386
+ if (tab() === "existing" && field() === "repo") {
17387
+ const list2 = activeList();
17388
+ if (list2.length === 0)
17389
+ return;
17390
+ setRepoCursor(clampCursor(repoCursor() + 1, list2.length));
17254
17391
  return;
17255
- setBranchCursor(clampCursor(branchCursor() + 1, list2.length));
17256
- return;
17257
- }
17258
- if (tab() === "clone" && field() === "cloneParent") {
17259
- const list2 = cloneParentFiltered();
17260
- if (list2.length === 0)
17392
+ }
17393
+ if (tab() === "existing" && field() === "baseRef") {
17394
+ const list2 = branchFiltered();
17395
+ if (list2.length === 0)
17396
+ return;
17397
+ setBranchCursor(clampCursor(branchCursor() + 1, list2.length));
17261
17398
  return;
17262
- setCloneParentCursor(clampCursor(cloneParentCursor() + 1, list2.length));
17263
- return;
17264
- }
17265
- if (tab() === "adopt") {
17266
- const list2 = adoptList();
17267
- if (list2.length === 0)
17399
+ }
17400
+ if (tab() === "clone" && field() === "cloneParent") {
17401
+ const list2 = cloneParentFiltered();
17402
+ if (list2.length === 0)
17403
+ return;
17404
+ setCloneParentCursor(clampCursor(cloneParentCursor() + 1, list2.length));
17268
17405
  return;
17269
- setAdoptCursor(clampCursor(adoptCursor() + 1, list2.length));
17406
+ }
17407
+ if (tab() === "adopt") {
17408
+ const list2 = adoptList();
17409
+ if (list2.length === 0)
17410
+ return;
17411
+ setAdoptCursor(clampCursor(adoptCursor() + 1, list2.length));
17412
+ }
17270
17413
  }
17271
- }
17272
- }, {
17273
- key: "ctrl+a",
17274
- cmd: () => {
17275
- if (tab() !== "adopt")
17276
- return;
17277
- const list2 = adoptList();
17278
- if (list2.length === 0)
17279
- return;
17280
- const allSelected = list2.every((w) => adoptSelected().has(w.path));
17281
- setAdoptSelected(allSelected ? new Set : new Set(list2.map((w) => w.path)));
17282
- }
17283
- }]
17414
+ },
17415
+ ...tab() === "adopt" ? [{
17416
+ key: "ctrl+a",
17417
+ cmd: adoptSelectAll
17418
+ }] : []
17419
+ ]
17284
17420
  }));
17285
17421
  useBindings(() => ({
17286
17422
  enabled: field() === "confirm" && !cloneInFlight(),
@@ -18193,6 +18329,35 @@ var init_quick_task_composer = __esm(() => {
18193
18329
  };
18194
18330
  });
18195
18331
 
18332
+ // src/client/connect-pane-orchestrator.ts
18333
+ async function connectPaneOrchestrator(options = {}) {
18334
+ const tag = options.logTag ?? "orch-connect";
18335
+ const connect2 = options.connect ?? connectIfRunning;
18336
+ let remote = null;
18337
+ try {
18338
+ const client = await connect2();
18339
+ if (!client) {
18340
+ logClient(tag, "no daemon running \u2014 caller degrades");
18341
+ return null;
18342
+ }
18343
+ remote = new RemoteOrchestrator(client, {
18344
+ ...options.orchestratorOptions,
18345
+ channels: options.channels
18346
+ });
18347
+ await remote.init();
18348
+ return remote;
18349
+ } catch (err) {
18350
+ logClientError(tag, err);
18351
+ remote?.dispose();
18352
+ return null;
18353
+ }
18354
+ }
18355
+ var init_connect_pane_orchestrator = __esm(() => {
18356
+ init_client_log();
18357
+ init_daemon_process();
18358
+ init_remote_orchestrator();
18359
+ });
18360
+
18196
18361
  // src/tui/context/focus.tsx
18197
18362
  function FocusProvider(props) {
18198
18363
  const [focused, setFocusedSignal] = createSignal(props.initial ?? "sidebar");
@@ -19195,20 +19360,12 @@ function UiPrefsSync(props) {
19195
19360
  let disposed = false;
19196
19361
  onMount(() => {
19197
19362
  (async () => {
19198
- let remote = null;
19199
- try {
19200
- const client = await connectIfRunning();
19201
- if (!client) {
19202
- logClient("ui-prefs", "no daemon \u2014 keeping boot-time visual prefs");
19203
- return;
19204
- }
19205
- remote = new RemoteOrchestrator(client);
19206
- await remote.init();
19207
- } catch (err) {
19208
- logClientError("ui-prefs", err);
19209
- remote?.dispose();
19363
+ const remote = await connectPaneOrchestrator({
19364
+ logTag: "ui-prefs",
19365
+ channels: ["ui-prefs", "keybindings"]
19366
+ });
19367
+ if (!remote)
19210
19368
  return;
19211
- }
19212
19369
  if (disposed) {
19213
19370
  remote.dispose();
19214
19371
  return;
@@ -19266,9 +19423,8 @@ var init_host_boot = __esm(() => {
19266
19423
  init_solid();
19267
19424
  init_solid();
19268
19425
  init_client_log();
19269
- init_daemon_process();
19270
19426
  init_dev();
19271
- init_remote_orchestrator();
19427
+ init_connect_pane_orchestrator();
19272
19428
  init_focus();
19273
19429
  init_keybindings_user();
19274
19430
  init_kv();
@@ -23860,7 +24016,9 @@ var init_Sidebar = __esm(() => {
23860
24016
  // src/tui/tasks-pane/host.tsx
23861
24017
  var exports_host3 = {};
23862
24018
  __export(exports_host3, {
23863
- startTasksPane: () => startTasksPane
24019
+ startTasksPane: () => startTasksPane,
24020
+ legendRowCap: () => legendRowCap,
24021
+ legendCap: () => legendCap
23864
24022
  });
23865
24023
  import { existsSync as existsSync16 } from "fs";
23866
24024
  import { stat as stat5 } from "fs/promises";
@@ -24002,7 +24160,7 @@ function TasksShell(props) {
24002
24160
  async function archiveTask(id) {
24003
24161
  await archiveTaskFlow(taskActions, id);
24004
24162
  }
24005
- async function deleteTask(id) {
24163
+ async function deleteTask2(id) {
24006
24164
  await deleteTaskFlow(taskActions, id);
24007
24165
  }
24008
24166
  async function renameTask(id) {
@@ -24117,13 +24275,16 @@ function TasksShell(props) {
24117
24275
  if (exists) {
24118
24276
  const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
24119
24277
  if (worktreeCwdUsable(cwd2)) {
24278
+ const init3 = task?.repo ? resolveRepoInit(task.repo, cwd2) : {};
24120
24279
  await ensureSession({
24121
24280
  name,
24122
24281
  cwd: cwd2,
24123
24282
  command: interactiveEngineCommand(task?.vendor),
24124
24283
  taskId: id,
24125
24284
  vendor: task?.vendor,
24126
- repo: task?.repo
24285
+ repo: task?.repo,
24286
+ initScript: init3.initScript,
24287
+ initPrompt: init3.initPrompt
24127
24288
  });
24128
24289
  }
24129
24290
  await runTmux(["switch-client", "-t", `=${name}`]);
@@ -24222,7 +24383,7 @@ function TasksShell(props) {
24222
24383
  } : undefined;
24223
24384
  },
24224
24385
  onRenameRequest: (id) => void renameTask(id),
24225
- onDeleteRequest: (id) => void deleteTask(id),
24386
+ onDeleteRequest: (id) => void deleteTask2(id),
24226
24387
  onArchiveRequest: (id) => void archiveTask(id),
24227
24388
  onLocalMergeRequest: (id) => {
24228
24389
  const task = props.tasks().find((t) => t.id === id);
@@ -24253,6 +24414,17 @@ function TasksShell(props) {
24253
24414
  return _el$;
24254
24415
  })();
24255
24416
  }
24417
+ function legendCap(id) {
24418
+ const row = findBinding(id);
24419
+ if (!row)
24420
+ return null;
24421
+ const cap = row.hint?.keys ?? row.keys[0];
24422
+ return cap && cap.length > 0 ? cap : null;
24423
+ }
24424
+ function legendRowCap(ids) {
24425
+ const caps = ids.map(legendCap).filter((c) => c !== null);
24426
+ return caps.length > 0 ? caps.join("/") : null;
24427
+ }
24256
24428
  function ShortcutHints(props) {
24257
24429
  const {
24258
24430
  theme
@@ -24340,55 +24512,70 @@ function ShortcutHints(props) {
24340
24512
  });
24341
24513
  return out;
24342
24514
  };
24343
- const defaultHints = () => [
24344
- {
24345
- k: "enter",
24346
- label: "open"
24347
- },
24348
- {
24349
- k: "right",
24350
- label: "focus engine"
24351
- },
24352
- {
24353
- k: "n",
24354
- label: "new task"
24355
- },
24356
- {
24357
- k: "s",
24358
- label: "settings"
24359
- },
24360
- {
24361
- k: "o",
24362
- label: "open wt"
24363
- },
24364
- {
24365
- k: "[/]",
24366
- label: "views"
24367
- },
24368
- {
24369
- k: "t",
24370
- label: "sort"
24371
- },
24372
- {
24373
- k: "M",
24374
- label: "move task",
24375
- dimWhenMain: true
24376
- },
24377
- {
24378
- k: "a/d",
24379
- label: "un/archive\xB7delete"
24380
- },
24381
- {
24382
- k: "r/b/v",
24383
- label: "name/branch/engine",
24384
- dimWhenMain: true
24385
- },
24386
- {
24387
- k: "f1",
24388
- label: "help"
24389
- },
24390
- ...tmuxHints()
24391
- ];
24515
+ const defaultHints = () => {
24516
+ keymapVersion();
24517
+ const rows = [
24518
+ {
24519
+ ids: ["sidebar.select"],
24520
+ label: "open"
24521
+ },
24522
+ {
24523
+ ids: ["tasks.focusEngine"],
24524
+ label: "focus engine"
24525
+ },
24526
+ {
24527
+ ids: ["task.new"],
24528
+ label: "new task"
24529
+ },
24530
+ {
24531
+ ids: ["settings.open.sidebar"],
24532
+ label: "settings"
24533
+ },
24534
+ {
24535
+ ids: ["tasks.openWorktree"],
24536
+ label: "open wt"
24537
+ },
24538
+ {
24539
+ ids: ["sidebar.view"],
24540
+ label: "views"
24541
+ },
24542
+ {
24543
+ ids: ["sidebar.sort"],
24544
+ label: "sort"
24545
+ },
24546
+ {
24547
+ ids: ["sidebar.localMerge"],
24548
+ label: "move task",
24549
+ dimWhenMain: true
24550
+ },
24551
+ {
24552
+ ids: ["sidebar.archive", "sidebar.delete"],
24553
+ label: "un/archive\xB7delete"
24554
+ },
24555
+ {
24556
+ ids: ["sidebar.rename", "tasks.renameBranch", "tasks.cycleEngine"],
24557
+ label: "name/branch/engine",
24558
+ dimWhenMain: true
24559
+ },
24560
+ {
24561
+ ids: ["help.open"],
24562
+ label: "help"
24563
+ }
24564
+ ];
24565
+ const out = [];
24566
+ for (const row of rows) {
24567
+ const k = legendRowCap(row.ids);
24568
+ if (k === null)
24569
+ continue;
24570
+ out.push({
24571
+ k,
24572
+ label: row.label,
24573
+ dimWhenMain: row.dimWhenMain
24574
+ });
24575
+ }
24576
+ out.push(...tmuxHints());
24577
+ return out;
24578
+ };
24392
24579
  const MOVE_HINTS = [{
24393
24580
  k: "j/k",
24394
24581
  label: "reorder"
@@ -24658,10 +24845,14 @@ async function startSettingsHost() {
24658
24845
  setup: async () => {
24659
24846
  let orch = null;
24660
24847
  try {
24661
- const client = await connectOrStartDaemon();
24662
- const remote = new RemoteOrchestrator(client);
24663
- await remote.init();
24664
- orch = remote;
24848
+ const client = await connectIfRunning();
24849
+ if (client) {
24850
+ const remote = new RemoteOrchestrator(client);
24851
+ await remote.init();
24852
+ orch = remote;
24853
+ } else {
24854
+ console.error("[kobe settings] no daemon running; Restart backend disabled");
24855
+ }
24665
24856
  } catch (err) {
24666
24857
  console.error("[kobe settings] daemon unavailable; Restart backend disabled:", err);
24667
24858
  }
@@ -26476,10 +26667,16 @@ var init_pr_prompt = __esm(() => {
26476
26667
  var exports_host7 = {};
26477
26668
  __export(exports_host7, {
26478
26669
  startOpsPreview: () => startOpsPreview,
26479
- startOpsHost: () => startOpsHost
26670
+ startOpsHost: () => startOpsHost,
26671
+ nextActivityPollDelay: () => nextActivityPollDelay
26480
26672
  });
26481
26673
  import { createHash as createHash5 } from "crypto";
26482
26674
  import { SyntaxStyle } from "@opentui/core";
26675
+ function nextActivityPollDelay(currentMs, idleStreak) {
26676
+ if (idleStreak < ACTIVITY_IDLE_RAMP_POLLS)
26677
+ return ACTIVITY_POLL_MIN_MS;
26678
+ return Math.min(currentMs * 2, ACTIVITY_POLL_MAX_MS);
26679
+ }
26483
26680
  function OpsShell(props) {
26484
26681
  const {
26485
26682
  theme
@@ -26489,6 +26686,10 @@ function OpsShell(props) {
26489
26686
  let primed = false;
26490
26687
  onMount(() => {
26491
26688
  let disposed = false;
26689
+ let timer;
26690
+ let delayMs = ACTIVITY_POLL_MIN_MS;
26691
+ let idleStreak = 0;
26692
+ let lastSeenMtime = 0;
26492
26693
  async function poll() {
26493
26694
  try {
26494
26695
  const mtime = await latestTranscriptMtime(props.vendor, props.worktree);
@@ -26497,15 +26698,29 @@ function OpsShell(props) {
26497
26698
  if (!primed) {
26498
26699
  primed = true;
26499
26700
  setBaseline(mtime);
26701
+ lastSeenMtime = mtime;
26702
+ }
26703
+ if (mtime > lastSeenMtime) {
26704
+ lastSeenMtime = mtime;
26705
+ idleStreak = 0;
26706
+ } else {
26707
+ idleStreak++;
26500
26708
  }
26501
26709
  setLatest(mtime);
26502
- } catch {}
26710
+ } catch {
26711
+ idleStreak++;
26712
+ } finally {
26713
+ if (!disposed) {
26714
+ delayMs = nextActivityPollDelay(delayMs, idleStreak);
26715
+ timer = setTimeout(() => void poll(), delayMs);
26716
+ }
26717
+ }
26503
26718
  }
26504
26719
  poll();
26505
- const timer = setInterval(() => void poll(), ACTIVITY_POLL_MS);
26506
26720
  onCleanup(() => {
26507
26721
  disposed = true;
26508
- clearInterval(timer);
26722
+ if (timer)
26723
+ clearTimeout(timer);
26509
26724
  });
26510
26725
  });
26511
26726
  const hasNewActivity = () => primed && latest() > baseline();
@@ -26867,7 +27082,7 @@ async function startOpsPreview(args2) {
26867
27082
  })
26868
27083
  });
26869
27084
  }
26870
- var ACTIVITY_POLL_MS = 2500, TURN_STATUS_POLL_MS = 1500, STABLE_POLLS_FOR_DONE = 2, CHAT_TAB_STATE_OPTION2 = "@kobe_tab_state";
27085
+ var ACTIVITY_POLL_MIN_MS = 2500, ACTIVITY_POLL_MAX_MS = 20000, ACTIVITY_IDLE_RAMP_POLLS = 3, TURN_STATUS_POLL_MS = 1500, STABLE_POLLS_FOR_DONE = 2, CHAT_TAB_STATE_OPTION2 = "@kobe_tab_state";
26871
27086
  var init_host7 = __esm(() => {
26872
27087
  init_solid();
26873
27088
  init_solid();