@sma1lboy/kobe 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +270 -84
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -70,7 +70,7 @@ var init_package = __esm(() => {
70
70
  package_default = {
71
71
  $schema: "https://json.schemastore.org/package.json",
72
72
  name: "@sma1lboy/kobe",
73
- version: "0.7.1",
73
+ version: "0.7.2",
74
74
  description: "TUI orchestrator for Claude Code (codename)",
75
75
  type: "module",
76
76
  packageManager: "bun@1.3.13",
@@ -748,7 +748,8 @@ function coerceTask(value) {
748
748
  if (!isTaskStatus(v.status))
749
749
  return null;
750
750
  const archived = typeof v.archived === "boolean" ? v.archived : false;
751
- const healedStatus = v.status === "done" && !archived ? "in_progress" : v.status;
751
+ const kind = v.kind === "main" ? "main" : "task";
752
+ const healedStatus = kind === "main" ? v.status === "in_progress" || v.status === "done" ? "backlog" : v.status : v.status === "done" && !archived ? "in_progress" : v.status;
752
753
  return {
753
754
  id: toTaskId(v.id),
754
755
  title: v.title,
@@ -758,7 +759,7 @@ function coerceTask(value) {
758
759
  status: healedStatus,
759
760
  archived,
760
761
  pinned: typeof v.pinned === "boolean" ? v.pinned : false,
761
- kind: v.kind === "main" ? "main" : "task",
762
+ kind,
762
763
  vendor: isVendorId(v.vendor) ? v.vendor : DEFAULT_TASK_VENDOR,
763
764
  prStatus: coercePRStatus(v.prStatus),
764
765
  createdAt: v.createdAt,
@@ -3227,6 +3228,111 @@ var init_protocol = __esm(() => {
3227
3228
  CHANNEL_NAMES = ["task.snapshot", "active-task", "update"];
3228
3229
  });
3229
3230
 
3231
+ // src/daemon/paths.ts
3232
+ import { createHash as createHash2 } from "crypto";
3233
+ import { homedir as homedir3, tmpdir } from "os";
3234
+ import { join as join3 } from "path";
3235
+ function shortHomeTag(homeDir2) {
3236
+ return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
3237
+ }
3238
+ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
3239
+ if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
3240
+ return naturalPath;
3241
+ const tag = shortHomeTag(homeDir2);
3242
+ const suffix = pidTag === undefined ? "" : `-${pidTag}`;
3243
+ const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
3244
+ if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
3245
+ return fallback;
3246
+ throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
3247
+ }
3248
+ function defaultDaemonSocketPath(homeDir2) {
3249
+ const override = process.env.KOBE_DAEMON_SOCKET_PATH;
3250
+ if (override && override.length > 0)
3251
+ return override;
3252
+ const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
3253
+ if (explicit && explicit.length > 0) {
3254
+ return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
3255
+ }
3256
+ const runtimeDir = process.env.XDG_RUNTIME_DIR;
3257
+ if (runtimeDir && runtimeDir.length > 0) {
3258
+ return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
3259
+ }
3260
+ const home = homedir3();
3261
+ return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
3262
+ }
3263
+ function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3264
+ const override = process.env.KOBE_DAEMON_PID_PATH;
3265
+ if (override && override.length > 0)
3266
+ return override;
3267
+ return join3(homeDir2, ".kobe", "daemon.pid");
3268
+ }
3269
+ function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3270
+ return join3(homeDir2, ".kobe", "daemon.log");
3271
+ }
3272
+ function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3273
+ return join3(homeDir2, ".kobe", "client.log");
3274
+ }
3275
+ var SOCKET_PATH_SAFETY_LIMIT = 100;
3276
+ var init_paths2 = () => {};
3277
+
3278
+ // src/client/client-log.ts
3279
+ import { appendFile, mkdir as mkdir2 } from "fs/promises";
3280
+ import { dirname as dirname3 } from "path";
3281
+ function setClientLogContext(ctx) {
3282
+ context = ctx;
3283
+ }
3284
+ function coerceError(err) {
3285
+ if (err instanceof Error)
3286
+ return { message: err.message, stack: err.stack };
3287
+ let text;
3288
+ try {
3289
+ text = typeof err === "string" ? err : JSON.stringify(err);
3290
+ } catch {
3291
+ text = String(err);
3292
+ }
3293
+ return { message: text };
3294
+ }
3295
+ function formatClientEntry(subsystem, message, now = new Date) {
3296
+ return `[${now.toISOString()}] client ${context} [${subsystem}] pid=${process.pid}: ${message}
3297
+ `;
3298
+ }
3299
+ function warnOnce() {
3300
+ if (warnedOnce)
3301
+ return;
3302
+ warnedOnce = true;
3303
+ try {
3304
+ process.stderr.write(`[kobe] client log write failed; continuing without it
3305
+ `);
3306
+ } catch {}
3307
+ }
3308
+ function append(line) {
3309
+ const path3 = defaultClientLogPath();
3310
+ writeChain = writeChain.then(async () => {
3311
+ try {
3312
+ await appendFile(path3, line);
3313
+ } catch (err) {
3314
+ if (err?.code === "ENOENT") {
3315
+ await mkdir2(dirname3(path3), { recursive: true });
3316
+ await appendFile(path3, line);
3317
+ } else {
3318
+ throw err;
3319
+ }
3320
+ }
3321
+ }).catch(() => warnOnce());
3322
+ }
3323
+ function logClient(subsystem, message) {
3324
+ append(formatClientEntry(subsystem, message));
3325
+ }
3326
+ function logClientError(subsystem, err) {
3327
+ const e = coerceError(err);
3328
+ append(formatClientEntry(subsystem, e.stack ?? e.message));
3329
+ }
3330
+ var context = "client", warnedOnce = false, writeChain;
3331
+ var init_client_log = __esm(() => {
3332
+ init_paths2();
3333
+ writeChain = Promise.resolve();
3334
+ });
3335
+
3230
3336
  // src/client/index.ts
3231
3337
  import { connect } from "net";
3232
3338
 
@@ -3259,6 +3365,9 @@ class KobeDaemonClient {
3259
3365
  p.then(cleanup, cleanup);
3260
3366
  return p;
3261
3367
  }
3368
+ get isDisposed() {
3369
+ return this.disposed;
3370
+ }
3262
3371
  close() {
3263
3372
  this.disposed = true;
3264
3373
  this.socket?.end();
@@ -3372,7 +3481,13 @@ class KobeDaemonClient {
3372
3481
  }
3373
3482
  }
3374
3483
  onLine(line) {
3375
- const frame = JSON.parse(line);
3484
+ let frame;
3485
+ try {
3486
+ frame = JSON.parse(line);
3487
+ } catch (err) {
3488
+ logClientError("client-frame", err);
3489
+ return;
3490
+ }
3376
3491
  if (frame.type === "event") {
3377
3492
  this.emit(frame);
3378
3493
  return;
@@ -3397,6 +3512,7 @@ class KobeDaemonClient {
3397
3512
  }
3398
3513
  var init_client = __esm(() => {
3399
3514
  init_protocol();
3515
+ init_client_log();
3400
3516
  });
3401
3517
 
3402
3518
  // src/engine/claude-code-local/normalize.ts
@@ -3446,8 +3562,8 @@ function normalizeClaudeContent(content) {
3446
3562
  }
3447
3563
 
3448
3564
  // src/engine/claude-code-local/history.ts
3449
- import { appendFile, mkdir as mkdir2, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
3450
- import { homedir as homedir3 } from "os";
3565
+ import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
3566
+ import { homedir as homedir4 } from "os";
3451
3567
  import path3 from "path";
3452
3568
  function encodeCwd(cwd) {
3453
3569
  return cwd.replace(/[/.]/g, "-");
@@ -3455,7 +3571,7 @@ function encodeCwd(cwd) {
3455
3571
  async function listSessionFilesForWorktree(worktree) {
3456
3572
  if (!worktree)
3457
3573
  return [];
3458
- const dir = path3.join(homedir3(), ".claude", "projects", encodeCwd(worktree));
3574
+ const dir = path3.join(homedir4(), ".claude", "projects", encodeCwd(worktree));
3459
3575
  let entries;
3460
3576
  try {
3461
3577
  entries = await readdir(dir);
@@ -3561,7 +3677,7 @@ var defaultDeps;
3561
3677
  var init_history = __esm(() => {
3562
3678
  defaultDeps = {
3563
3679
  projectsDir() {
3564
- return path3.join(homedir3(), ".claude", "projects");
3680
+ return path3.join(homedir4(), ".claude", "projects");
3565
3681
  },
3566
3682
  async readdir(p) {
3567
3683
  try {
@@ -3663,7 +3779,7 @@ function validPositive(v) {
3663
3779
 
3664
3780
  // src/engine/codex-local/history.ts
3665
3781
  import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
3666
- import { homedir as homedir4 } from "os";
3782
+ import { homedir as homedir5 } from "os";
3667
3783
  import path4 from "path";
3668
3784
  async function listRolloutFiles(deps = defaultDeps2) {
3669
3785
  const root = deps.sessionsDir();
@@ -3994,7 +4110,7 @@ var init_history2 = __esm(() => {
3994
4110
  init_synthetic();
3995
4111
  defaultDeps2 = {
3996
4112
  sessionsDir() {
3997
- return path4.join(homedir4(), ".codex", "sessions");
4113
+ return path4.join(homedir5(), ".codex", "sessions");
3998
4114
  },
3999
4115
  async readdir(p) {
4000
4116
  try {
@@ -4028,14 +4144,14 @@ function copilotUsageToSnapshot(value) {
4028
4144
  output += numberOr2(metrics.usage.outputTokens, 0);
4029
4145
  cached += numberOr2(metrics.usage.cacheReadTokens, 0);
4030
4146
  }
4031
- const context = numberOr2(value.currentTokens, 0);
4032
- if (input === 0 && output === 0 && cached === 0 && context === 0)
4147
+ const context2 = numberOr2(value.currentTokens, 0);
4148
+ if (input === 0 && output === 0 && cached === 0 && context2 === 0)
4033
4149
  return;
4034
4150
  return {
4035
4151
  input_tokens: input,
4036
4152
  output_tokens: output,
4037
4153
  ...cached > 0 ? { cache_read_input_tokens: cached } : {},
4038
- ...context > 0 ? { context_tokens: context } : {}
4154
+ ...context2 > 0 ? { context_tokens: context2 } : {}
4039
4155
  };
4040
4156
  }
4041
4157
  function isObject4(v) {
@@ -4047,7 +4163,7 @@ function numberOr2(value, fallback) {
4047
4163
 
4048
4164
  // src/engine/copilot-local/history.ts
4049
4165
  import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
4050
- import { homedir as homedir5 } from "os";
4166
+ import { homedir as homedir6 } from "os";
4051
4167
  import path5 from "path";
4052
4168
  async function listSessionDirs(deps = defaultDeps3) {
4053
4169
  const root = path5.join(deps.copilotDir(), "session-state");
@@ -4216,7 +4332,7 @@ var init_history3 = __esm(() => {
4216
4332
  const override = process.env.COPILOT_HOME?.trim();
4217
4333
  if (override)
4218
4334
  return override;
4219
- return path5.join(homedir5(), ".copilot");
4335
+ return path5.join(homedir6(), ".copilot");
4220
4336
  },
4221
4337
  async readdir(p) {
4222
4338
  try {
@@ -4409,7 +4525,7 @@ __export(exports_client, {
4409
4525
  CLAUDE_ROLE_OPTION: () => CLAUDE_ROLE_OPTION,
4410
4526
  CHAT_TAB_SESSION_ID_OPTION: () => CHAT_TAB_SESSION_ID_OPTION
4411
4527
  });
4412
- import { homedir as homedir6 } from "os";
4528
+ import { homedir as homedir7 } from "os";
4413
4529
  function tmuxArgs(...args) {
4414
4530
  return ["tmux", "-L", KOBE_TMUX_SOCKET, ...args];
4415
4531
  }
@@ -4670,7 +4786,7 @@ var KOBE_TMUX_SOCKET, SAFE_SPAWN_CWD, PANE_ROLE_OPTION = "@kobe_role", CLAUDE_RO
4670
4786
  var init_client2 = __esm(() => {
4671
4787
  init_invocation();
4672
4788
  KOBE_TMUX_SOCKET = process.env.KOBE_TMUX_SOCKET?.trim() || "kobe";
4673
- SAFE_SPAWN_CWD = homedir6() || "/";
4789
+ SAFE_SPAWN_CWD = homedir7() || "/";
4674
4790
  CLAUDE_ROLE_OPTION = PANE_ROLE_OPTION;
4675
4791
  });
4676
4792
 
@@ -4768,6 +4884,13 @@ function formatDaemonError(subsystem, err, now = new Date) {
4768
4884
  function logDaemonError(subsystem, err) {
4769
4885
  process.stderr.write(formatDaemonError(subsystem, err));
4770
4886
  }
4887
+ function formatDaemonInfo(subsystem, message, now = new Date) {
4888
+ return `[${now.toISOString()}] daemon [${subsystem}]: ${message}
4889
+ `;
4890
+ }
4891
+ function logDaemonInfo(subsystem, message) {
4892
+ process.stderr.write(formatDaemonInfo(subsystem, message));
4893
+ }
4771
4894
  function installDaemonCrashHandlers(log = (l) => process.stderr.write(l)) {
4772
4895
  if (onRejection || onException)
4773
4896
  return;
@@ -4843,54 +4966,10 @@ class DaemonEventBus {
4843
4966
  }
4844
4967
  }
4845
4968
 
4846
- // src/daemon/paths.ts
4847
- import { createHash as createHash2 } from "crypto";
4848
- import { homedir as homedir7, tmpdir } from "os";
4849
- import { join as join3 } from "path";
4850
- function shortHomeTag(homeDir2) {
4851
- return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
4852
- }
4853
- function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
4854
- if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
4855
- return naturalPath;
4856
- const tag = shortHomeTag(homeDir2);
4857
- const suffix = pidTag === undefined ? "" : `-${pidTag}`;
4858
- const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
4859
- if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
4860
- return fallback;
4861
- throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
4862
- }
4863
- function defaultDaemonSocketPath(homeDir2) {
4864
- const override = process.env.KOBE_DAEMON_SOCKET_PATH;
4865
- if (override && override.length > 0)
4866
- return override;
4867
- const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
4868
- if (explicit && explicit.length > 0) {
4869
- return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
4870
- }
4871
- const runtimeDir = process.env.XDG_RUNTIME_DIR;
4872
- if (runtimeDir && runtimeDir.length > 0) {
4873
- return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
4874
- }
4875
- const home = homedir7();
4876
- return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
4877
- }
4878
- function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
4879
- const override = process.env.KOBE_DAEMON_PID_PATH;
4880
- if (override && override.length > 0)
4881
- return override;
4882
- return join3(homeDir2, ".kobe", "daemon.pid");
4883
- }
4884
- function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
4885
- return join3(homeDir2, ".kobe", "daemon.log");
4886
- }
4887
- var SOCKET_PATH_SAFETY_LIMIT = 100;
4888
- var init_paths2 = () => {};
4889
-
4890
4969
  // src/daemon/server.ts
4891
- import { mkdir as mkdir3, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
4970
+ import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
4892
4971
  import { createServer } from "net";
4893
- import { dirname as dirname3 } from "path";
4972
+ import { dirname as dirname4 } from "path";
4894
4973
  function resolveIdleGraceMs() {
4895
4974
  const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
4896
4975
  if (raw === undefined)
@@ -4924,10 +5003,12 @@ async function startDaemonServer(orch, options = {}) {
4924
5003
  if (stopping || guiCount() > 0)
4925
5004
  return;
4926
5005
  cancelIdleTimer();
5006
+ logDaemonInfo("idle", `last gui gone \u2014 arming ${idleGraceMs}ms idle-stop grace`);
4927
5007
  idleTimer = setTimeout(() => {
4928
5008
  idleTimer = null;
4929
5009
  if (stopping || guiCount() > 0)
4930
5010
  return;
5011
+ logDaemonInfo("idle", "grace elapsed with no gui \u2014 self-stopping");
4931
5012
  stopSoon().catch((err) => logDaemonError("daemon-idle-shutdown", err));
4932
5013
  }, idleGraceMs);
4933
5014
  idleTimer.unref?.();
@@ -4936,8 +5017,8 @@ async function startDaemonServer(orch, options = {}) {
4936
5017
  bus.onPublish((event) => {
4937
5018
  broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
4938
5019
  });
4939
- await mkdir3(dirname3(socketPath), { recursive: true });
4940
- await mkdir3(dirname3(pidPath), { recursive: true });
5020
+ await mkdir4(dirname4(socketPath), { recursive: true });
5021
+ await mkdir4(dirname4(pidPath), { recursive: true });
4941
5022
  await unlink4(socketPath).catch(() => {});
4942
5023
  const server = createServer((socket) => {
4943
5024
  const client = {
@@ -4956,6 +5037,9 @@ async function startDaemonServer(orch, options = {}) {
4956
5037
  socket.on("error", () => {});
4957
5038
  socket.on("close", () => {
4958
5039
  clients.delete(client);
5040
+ if (client.subscribed) {
5041
+ logDaemonInfo("conn", `client #${client.id} (${client.holdsLifetime ? "gui" : "pane"}) disconnected \u2014 ${clients.size} client(s), ${guiCount()} gui left`);
5042
+ }
4959
5043
  if (client.holdsLifetime)
4960
5044
  maybeArmIdleShutdown();
4961
5045
  });
@@ -5148,6 +5232,7 @@ async function startDaemonServer(orch, options = {}) {
5148
5232
  client.holdsLifetime = role === "gui";
5149
5233
  if (client.holdsLifetime)
5150
5234
  cancelIdleTimer();
5235
+ logDaemonInfo("conn", `client #${client.id} subscribed as ${role} \u2014 ${clients.size} client(s), ${guiCount()} gui`);
5151
5236
  for (const event of bus.snapshot()) {
5152
5237
  writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
5153
5238
  }
@@ -5327,13 +5412,13 @@ __export(exports_daemon_process, {
5327
5412
  });
5328
5413
  import { spawn } from "child_process";
5329
5414
  import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
5330
- import { dirname as dirname4, resolve as resolve2 } from "path";
5415
+ import { dirname as dirname5, resolve as resolve2 } from "path";
5331
5416
  import { fileURLToPath as fileURLToPath2 } from "url";
5332
5417
  function spawnDetachedDaemon(command, args, env, logPath) {
5333
5418
  let stdio = "ignore";
5334
5419
  let logFd;
5335
5420
  try {
5336
- mkdirSync2(dirname4(logPath), { recursive: true });
5421
+ mkdirSync2(dirname5(logPath), { recursive: true });
5337
5422
  logFd = openSync(logPath, "a");
5338
5423
  stdio = ["ignore", logFd, logFd];
5339
5424
  } catch {
@@ -5400,7 +5485,7 @@ function resolveKobeSpawn(subcommand) {
5400
5485
  if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
5401
5486
  return [process.execPath, ...subcommand];
5402
5487
  }
5403
- const dir = dirname4(here);
5488
+ const dir = dirname5(here);
5404
5489
  const sourceEntry = resolve2(dir, "../cli/index.ts");
5405
5490
  if (existsSync(sourceEntry))
5406
5491
  return [process.execPath, sourceEntry, ...subcommand];
@@ -5946,7 +6031,7 @@ async function refreshKobeWorkspacePanes(session) {
5946
6031
  keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
5947
6032
  ], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
5948
6033
  }
5949
- if (opsPane && claudePane) {
6034
+ if (opsPane) {
5950
6035
  commands.push([
5951
6036
  "respawn-pane",
5952
6037
  "-k",
@@ -5957,7 +6042,7 @@ async function refreshKobeWorkspacePanes(session) {
5957
6042
  keepAlive(envPrefix + opsPaneCommand({
5958
6043
  cwd,
5959
6044
  taskId,
5960
- claudePaneId: claudePane,
6045
+ claudePaneId: claudePane ?? null,
5961
6046
  cliInvocation: inv,
5962
6047
  vendor
5963
6048
  }))
@@ -7154,6 +7239,7 @@ var init_skill_install = __esm(() => {
7154
7239
  var exports_maintenance = {};
7155
7240
  __export(exports_maintenance, {
7156
7241
  runResetSubcommand: () => runResetSubcommand,
7242
+ runReloadSubcommand: () => runReloadSubcommand,
7157
7243
  runDoctorSubcommand: () => runDoctorSubcommand
7158
7244
  });
7159
7245
  import { existsSync as existsSync5, readFileSync as readFileSync6, statSync } from "fs";
@@ -7407,6 +7493,54 @@ Stop daemon and kill kobe sessions? [y/N] `);
7407
7493
  console.log(`
7408
7494
  kobe: reset complete. Relaunch kobe to start fresh.`);
7409
7495
  }
7496
+ async function runReloadSubcommand(argv = []) {
7497
+ if (argv.includes("--help") || argv.includes("-h") || argv.includes("help")) {
7498
+ process.stdout.write([
7499
+ "Usage: kobe reload",
7500
+ "",
7501
+ "Restart kobe's Tasks + Ops panes in every live session, in place.",
7502
+ "Picks up new kobe code without `kobe reset` \u2014 the engine (claude)",
7503
+ "panes and your running turns are never touched. Takes no options.",
7504
+ ""
7505
+ ].join(`
7506
+ `));
7507
+ return;
7508
+ }
7509
+ const unknown = argv.find((a) => a.length > 0);
7510
+ if (unknown !== undefined) {
7511
+ process.stderr.write(`kobe reload: unexpected argument "${unknown}"
7512
+
7513
+ Usage: kobe reload (takes no options)
7514
+ `);
7515
+ process.exit(2);
7516
+ }
7517
+ if (!await tmuxAvailable()) {
7518
+ console.log("kobe reload: tmux is not installed \u2014 no panes to reload");
7519
+ return;
7520
+ }
7521
+ const { code, stdout } = await tmuxQuiet(["list-sessions", "-F", "#{session_name}"]);
7522
+ if (code !== 0) {
7523
+ console.log(`kobe reload: no kobe tmux sessions on the \`${KOBE_TMUX_SOCKET}\` socket`);
7524
+ return;
7525
+ }
7526
+ const sessions = stdout.split(`
7527
+ `).map((l) => l.trim()).filter((l) => l.length > 0);
7528
+ if (sessions.length === 0) {
7529
+ console.log(`kobe reload: no kobe tmux sessions on the \`${KOBE_TMUX_SOCKET}\` socket`);
7530
+ return;
7531
+ }
7532
+ const { refreshKobeWorkspacePanes: refreshKobeWorkspacePanes2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
7533
+ let reloaded = 0;
7534
+ for (const session of sessions) {
7535
+ try {
7536
+ await refreshKobeWorkspacePanes2(session);
7537
+ reloaded++;
7538
+ } catch (err) {
7539
+ console.error(` failed to reload session "${session}": ${err instanceof Error ? err.message : String(err)}`);
7540
+ }
7541
+ }
7542
+ console.log(`kobe: reloaded Tasks/Ops panes in ${reloaded}/${sessions.length} session(s) \u2014 engine panes untouched`);
7543
+ }
7410
7544
  var init_maintenance = __esm(() => {
7411
7545
  init_client();
7412
7546
  init_lifecycle();
@@ -8706,6 +8840,7 @@ class RemoteOrchestrator {
8706
8840
  setConnectionState;
8707
8841
  ensureReachable;
8708
8842
  role;
8843
+ reconnecting = false;
8709
8844
  constructor(client, options = {}) {
8710
8845
  this.client = client;
8711
8846
  const [tasks, setTasks] = createSignal([]);
@@ -8723,7 +8858,37 @@ class RemoteOrchestrator {
8723
8858
  this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
8724
8859
  this.role = options.role ?? "pane";
8725
8860
  this.client.on("*", (frame) => this.handleEvent(frame.name, frame.payload));
8726
- this.client.onLifecycle("close", () => this.setConnectionState("disconnected"));
8861
+ this.client.onLifecycle("close", () => {
8862
+ this.setConnectionState("disconnected");
8863
+ if (this.role === "pane") {
8864
+ logClient("orch", "daemon socket closed \u2014 starting non-spawning reconnect loop");
8865
+ this.reconnectLoop();
8866
+ }
8867
+ });
8868
+ }
8869
+ async reconnectLoop() {
8870
+ if (this.reconnecting)
8871
+ return;
8872
+ this.reconnecting = true;
8873
+ let delayMs = 500;
8874
+ let attempt = 0;
8875
+ while (!this.client.isDisposed) {
8876
+ await new Promise((r) => setTimeout(r, delayMs));
8877
+ if (this.client.isDisposed)
8878
+ break;
8879
+ attempt++;
8880
+ try {
8881
+ await this.init();
8882
+ logClient("orch", `reconnected and re-subscribed after ${attempt} attempt(s) \u2014 task list re-synced`);
8883
+ this.reconnecting = false;
8884
+ return;
8885
+ } catch (err) {
8886
+ if (attempt === 1 || attempt % 10 === 0)
8887
+ logClientError("orch-reconnect", err);
8888
+ delayMs = Math.min(delayMs * 2, 3000);
8889
+ }
8890
+ }
8891
+ this.reconnecting = false;
8727
8892
  }
8728
8893
  async init() {
8729
8894
  const hello = await this.client.request("hello", {
@@ -8744,6 +8909,7 @@ class RemoteOrchestrator {
8744
8909
  this.setTasks(hello.tasks.map(deserializeTask));
8745
8910
  await this.client.subscribe({ role: this.role });
8746
8911
  this.setConnectionState("online");
8912
+ logClient("orch", `subscribed as ${this.role} (${this.tasksAcc().length} tasks)`);
8747
8913
  }
8748
8914
  connectionStateSignal() {
8749
8915
  return this.connectionStateAcc;
@@ -8877,6 +9043,7 @@ function deserializeTask(s) {
8877
9043
  var init_remote_orchestrator = __esm(() => {
8878
9044
  init_dev();
8879
9045
  init_protocol();
9046
+ init_client_log();
8880
9047
  init_daemon_process();
8881
9048
  });
8882
9049
 
@@ -14619,7 +14786,7 @@ var init_focus = __esm(() => {
14619
14786
 
14620
14787
  // src/tui/context/kv.tsx
14621
14788
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
14622
- import { dirname as dirname5 } from "path";
14789
+ import { dirname as dirname6 } from "path";
14623
14790
  function loadInitial() {
14624
14791
  const statePath2 = kvStatePath();
14625
14792
  try {
@@ -14647,7 +14814,7 @@ var init_kv = __esm(() => {
14647
14814
  function writeNow(label) {
14648
14815
  const statePath2 = kvStatePath();
14649
14816
  try {
14650
- mkdirSync4(dirname5(statePath2), {
14817
+ mkdirSync4(dirname6(statePath2), {
14651
14818
  recursive: true
14652
14819
  });
14653
14820
  const tmp = `${statePath2}.tmp`;
@@ -14702,7 +14869,7 @@ var init_kv = __esm(() => {
14702
14869
  }
14703
14870
  const statePath2 = kvStatePath();
14704
14871
  try {
14705
- mkdirSync4(dirname5(statePath2), {
14872
+ mkdirSync4(dirname6(statePath2), {
14706
14873
  recursive: true
14707
14874
  });
14708
14875
  const tmp = `${statePath2}.tmp`;
@@ -16102,7 +16269,7 @@ function Sidebar(props) {
16102
16269
  return readWorktreeChanges(task.worktreePath);
16103
16270
  });
16104
16271
  const titleText = isMain ? repoBasename(task.repo) : task.title;
16105
- const loading = () => isLive() || task.status === "in_progress";
16272
+ const loading = () => isLive() || !isMain && task.status === "in_progress";
16106
16273
  const subtitleText = createMemo(() => {
16107
16274
  if (task.branch.length > 0)
16108
16275
  return truncateBranchLabel(task.branch, subtitleBudget());
@@ -17042,6 +17209,7 @@ function ShortcutHints() {
17042
17209
  })();
17043
17210
  }
17044
17211
  async function startTasksPane(opts = {}) {
17212
+ setClientLogContext("tasks");
17045
17213
  for (const {
17046
17214
  name,
17047
17215
  theme
@@ -17056,21 +17224,28 @@ async function startTasksPane(opts = {}) {
17056
17224
  const [fileTasks, setFileTasks] = createSignal(store2.list());
17057
17225
  let orch = null;
17058
17226
  try {
17059
- const client = await connectOrStartDaemon();
17060
- const remote = new RemoteOrchestrator(client);
17061
- await remote.init();
17062
- orch = remote;
17227
+ const client = await connectIfRunning();
17228
+ if (client) {
17229
+ const remote = new RemoteOrchestrator(client);
17230
+ await remote.init();
17231
+ orch = remote;
17232
+ } else {
17233
+ logClient("tasks-boot", "no daemon running \u2014 polling tasks.json (a gui owns daemon lifecycle)");
17234
+ }
17063
17235
  } catch (err) {
17064
- console.error("[kobe tasks] daemon subscribe unavailable, polling tasks.json:", err);
17236
+ logClientError("tasks-boot", err);
17237
+ logClient("tasks-boot", "daemon subscribe failed \u2014 polling tasks.json");
17065
17238
  }
17066
- const tasks = orch ? orch.tasksSignal() : fileTasks;
17239
+ const tasks = () => orch && orch.connectionStateSignal()() === "online" ? orch.tasksSignal()() : fileTasks();
17067
17240
  const reload = async () => {
17068
- if (orch)
17069
- return;
17070
17241
  await store2.load();
17071
17242
  setFileTasks(store2.list());
17072
17243
  };
17073
- const timer = orch ? undefined : setInterval(() => void reload(), RELOAD_MS);
17244
+ const timer = setInterval(() => {
17245
+ if (orch && orch.connectionStateSignal()() === "online")
17246
+ return;
17247
+ reload();
17248
+ }, RELOAD_MS);
17074
17249
  await render(() => createComponent2(ThemeProvider, {
17075
17250
  mode: "dark",
17076
17251
  get theme() {
@@ -17130,6 +17305,7 @@ var init_host = __esm(() => {
17130
17305
  init_client2();
17131
17306
  init_solid();
17132
17307
  init_dev();
17308
+ init_client_log();
17133
17309
  init_daemon_process();
17134
17310
  init_remote_orchestrator();
17135
17311
  init_interactive_command();
@@ -19387,6 +19563,7 @@ function OpsApp(props) {
19387
19563
  });
19388
19564
  }
19389
19565
  async function startOpsHost(args) {
19566
+ setClientLogContext("ops");
19390
19567
  for (const {
19391
19568
  name,
19392
19569
  theme
@@ -19658,6 +19835,7 @@ var init_host5 = __esm(() => {
19658
19835
  init_solid();
19659
19836
  init_solid();
19660
19837
  init_invocation();
19838
+ init_client_log();
19661
19839
  init_turn_detector();
19662
19840
  init_activity();
19663
19841
  init_client2();
@@ -19728,6 +19906,7 @@ async function ensureRepos(orchestrator) {
19728
19906
  return repos[0] ?? resolve6(process.cwd());
19729
19907
  }
19730
19908
  async function startDirectTmux() {
19909
+ setClientLogContext("gui");
19731
19910
  if (!await tmuxAvailable()) {
19732
19911
  console.error("kobe: tmux not found on PATH \u2014 install tmux to use kobe 0.6 direct mode");
19733
19912
  process.exitCode = 1;
@@ -19797,6 +19976,7 @@ async function startDirectTmux() {
19797
19976
  }
19798
19977
  }
19799
19978
  var init_direct = __esm(() => {
19979
+ init_client_log();
19800
19980
  init_daemon_process();
19801
19981
  init_remote_orchestrator();
19802
19982
  init_interactive_command();
@@ -22083,6 +22263,7 @@ function topLevelUsage() {
22083
22263
  " update [target] Self-update kobe",
22084
22264
  " doctor Diagnose daemon / tmux / state (read-only)",
22085
22265
  " reset [--hard] Recover a wedged install",
22266
+ " reload Restart Tasks/Ops panes in place (engine untouched)",
22086
22267
  " kill-sessions Tear down kobe's tmux server (dev reset)",
22087
22268
  "",
22088
22269
  "Options:",
@@ -22335,6 +22516,11 @@ async function main() {
22335
22516
  await runResetSubcommand2(rest);
22336
22517
  return;
22337
22518
  }
22519
+ if (subcommand === "reload") {
22520
+ const { runReloadSubcommand: runReloadSubcommand2 } = await Promise.resolve().then(() => (init_maintenance(), exports_maintenance));
22521
+ await runReloadSubcommand2(rest);
22522
+ return;
22523
+ }
22338
22524
  if (subcommand === "new-chattab") {
22339
22525
  const flags = parseOpsFlags(rest);
22340
22526
  const session = flags.session;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@sma1lboy/kobe",
4
- "version": "0.7.1",
4
+ "version": "0.7.2",
5
5
  "description": "TUI orchestrator for Claude Code (codename)",
6
6
  "type": "module",
7
7
  "packageManager": "bun@1.3.13",