@sma1lboy/kobe 0.7.3 → 0.7.5

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 +965 -318
  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.3",
73
+ version: "0.7.5",
74
74
  description: "TUI orchestrator for Claude Code (codename)",
75
75
  type: "module",
76
76
  packageManager: "bun@1.3.13",
@@ -2325,6 +2325,254 @@ var init_dev = __esm(() => {
2325
2325
  }
2326
2326
  });
2327
2327
 
2328
+ // src/cli/invocation.ts
2329
+ import { fileURLToPath } from "url";
2330
+ function kobeCliInvocation() {
2331
+ const isBuilt = import.meta.url.endsWith(".js");
2332
+ if (isBuilt)
2333
+ return ["kobe"];
2334
+ const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
2335
+ const preload = fileURLToPath(import.meta.resolve("@opentui/solid/preload"));
2336
+ return [process.execPath, "--preload", preload, "--conditions=browser", entry];
2337
+ }
2338
+ var init_invocation = () => {};
2339
+
2340
+ // src/tmux/session-layout.ts
2341
+ function shellQuote(s) {
2342
+ return `'${s.replace(/'/g, "'\\''")}'`;
2343
+ }
2344
+ function shellQuoteArgv(argv) {
2345
+ return argv.map(shellQuote).join(" ");
2346
+ }
2347
+ function keepAlive(cmd) {
2348
+ return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
2349
+ }
2350
+ function shQuote(s) {
2351
+ return `'${s.replace(/'/g, "'\\''")}'`;
2352
+ }
2353
+ function homeWelcomeCommand() {
2354
+ const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
2355
+ return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
2356
+ }
2357
+ function engineLaunchLine(engineCmd, init) {
2358
+ const tail = keepAlive(engineCmd);
2359
+ const script = init?.initScript?.trim();
2360
+ if (!script)
2361
+ return tail;
2362
+ const group = ["{", script, "}"].join(`
2363
+ `);
2364
+ if (init?.markerPath) {
2365
+ const marker = shQuote(init.markerPath);
2366
+ const markerDir = shQuote(markerDirOf(init.markerPath));
2367
+ return [
2368
+ `if [ ! -f ${marker} ]; then`,
2369
+ group,
2370
+ `if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
2371
+ "fi",
2372
+ tail
2373
+ ].join(`
2374
+ `);
2375
+ }
2376
+ return [group, tail].join(`
2377
+ `);
2378
+ }
2379
+ function markerDirOf(p) {
2380
+ const i = p.lastIndexOf("/");
2381
+ return i <= 0 ? "." : p.slice(0, i);
2382
+ }
2383
+ function fallbackOpsScript(cwd) {
2384
+ return `cd ${shellQuote(cwd)} && while :; do clear; printf "\\033[1m# %s\\033[0m\\n\\n" ${shellQuote(cwd)}; git status --short --branch 2>/dev/null | sed 's/^/ /' || true; printf "\\n"; if command -v lsd >/dev/null 2>&1; then lsd --tree --git -I node_modules -I .git --depth 2 .; elif command -v eza >/dev/null 2>&1; then eza --tree --git -L 2 -I 'node_modules|.git' .; elif command -v tree >/dev/null 2>&1; then tree -L 2 -I 'node_modules|.git'; else ls -la; fi; sleep 2; done`;
2385
+ }
2386
+ function previewWindowCommand(args) {
2387
+ const wt = shellQuote(args.worktree);
2388
+ const file = shellQuote(args.relPath);
2389
+ const inv = args.cliInvocation.map(shellQuote).join(" ");
2390
+ const fallback = `cd ${wt} && if ! git diff --quiet HEAD -- ${file} 2>/dev/null; then ` + `git diff HEAD -- ${file} | { delta --paging=always 2>/dev/null || less -R; }; ` + `else bat --style=plain --paging=always ${file} 2>/dev/null || \${PAGER:-less} ${file} 2>/dev/null || cat ${file}; fi`;
2391
+ return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
2392
+ }
2393
+ function updatePageCommand(args) {
2394
+ return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
2395
+ }
2396
+ function tasksPaneCommand(cliInvocation, opts = {}) {
2397
+ const argv = [...cliInvocation, "tasks"];
2398
+ if (opts.initialTaskId)
2399
+ argv.push("--initial-task-id", opts.initialTaskId);
2400
+ return shellQuoteArgv(argv);
2401
+ }
2402
+ function opsPaneCommand(args) {
2403
+ if (args.taskId && args.claudePaneId) {
2404
+ const inv = args.cliInvocation.map(shellQuote).join(" ");
2405
+ const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
2406
+ return `KOBE_FILETREE_WATCH=1 ${inv} ops --task-id ${shellQuote(args.taskId)} --worktree ${shellQuote(args.cwd)} ` + `--target-pane ${shellQuote(args.claudePaneId)}${vendorFlag} || { ${fallbackOpsScript(args.cwd)}; }`;
2407
+ }
2408
+ return fallbackOpsScript(args.cwd);
2409
+ }
2410
+ var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
2411
+
2412
+ // src/engine/claude-code-local/hook-adapter.ts
2413
+ import { existsSync } from "fs";
2414
+ import { appendFile, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
2415
+ import { dirname as dirname3, join as join3 } from "path";
2416
+ function isObject(v) {
2417
+ return !!v && typeof v === "object" && !Array.isArray(v);
2418
+ }
2419
+ async function readJsonObject(path3) {
2420
+ try {
2421
+ const parsed = JSON.parse(await readFile2(path3, "utf8"));
2422
+ return isObject(parsed) ? parsed : {};
2423
+ } catch {
2424
+ return {};
2425
+ }
2426
+ }
2427
+ function isKobeWorktreeSyncGroup(group) {
2428
+ if (!isObject(group) || !Array.isArray(group.hooks))
2429
+ return false;
2430
+ return group.hooks.some((h) => isObject(h) && typeof h.command === "string" && h.command.includes(WORKTREE_SYNC_MARKER));
2431
+ }
2432
+ function mergeWorktreeSyncHook(current, command) {
2433
+ const { hooks: rawHooks, ...restSettings } = current;
2434
+ const { WorktreeCreate, ...otherHooks } = isObject(rawHooks) ? rawHooks : {};
2435
+ const prior = Array.isArray(WorktreeCreate) ? WorktreeCreate : [];
2436
+ const kept = prior.filter((g) => !isKobeWorktreeSyncGroup(g));
2437
+ if (command !== null)
2438
+ kept.push({ hooks: [{ type: "command", command }] });
2439
+ const nextHooks = { ...otherHooks };
2440
+ if (kept.length > 0)
2441
+ nextHooks.WorktreeCreate = kept;
2442
+ return Object.keys(nextHooks).length > 0 ? { ...restSettings, hooks: nextHooks } : { ...restSettings };
2443
+ }
2444
+ function buildClaudeHooks(taskId, inv = kobeCliInvocation()) {
2445
+ const out = {};
2446
+ for (const { event, matcher, verb } of EVENT_MAP) {
2447
+ const command = shellQuoteArgv([...inv, "hook", verb, "--task-id", taskId]);
2448
+ const group = { hooks: [{ type: "command", command }] };
2449
+ if (matcher)
2450
+ group.matcher = matcher;
2451
+ out[event] = [group];
2452
+ }
2453
+ return out;
2454
+ }
2455
+ function mergeClaudeHooks(existing, kobeHooks) {
2456
+ const merged = { ...existing };
2457
+ for (const event of KOBE_HOOK_EVENTS)
2458
+ merged[event] = kobeHooks[event];
2459
+ return merged;
2460
+ }
2461
+
2462
+ class ClaudeHookAdapter {
2463
+ vendor = "claude";
2464
+ supportsHooks() {
2465
+ return true;
2466
+ }
2467
+ supportsWorktreeSync() {
2468
+ return true;
2469
+ }
2470
+ async installWorktreeSyncHook(settingsFilePath) {
2471
+ await this.editWorktreeSyncHook(settingsFilePath, true);
2472
+ }
2473
+ async removeWorktreeSyncHook(settingsFilePath) {
2474
+ await this.editWorktreeSyncHook(settingsFilePath, false);
2475
+ }
2476
+ async editWorktreeSyncHook(settingsFilePath, install) {
2477
+ const current = await readJsonObject(settingsFilePath);
2478
+ const command = install ? shellQuoteArgv([...kobeCliInvocation(), "hook", "worktree-created"]) : null;
2479
+ const next = mergeWorktreeSyncHook(current, command);
2480
+ await mkdir2(dirname3(settingsFilePath), { recursive: true });
2481
+ await writeFile2(settingsFilePath, `${JSON.stringify(next, null, 2)}
2482
+ `);
2483
+ }
2484
+ async installTaskHooks(ctx) {
2485
+ try {
2486
+ const claudeDir = join3(ctx.worktreeDir, ".claude");
2487
+ const settingsPath = join3(claudeDir, "settings.local.json");
2488
+ await mkdir2(claudeDir, { recursive: true });
2489
+ let current = {};
2490
+ try {
2491
+ const raw = await readFile2(settingsPath, "utf8");
2492
+ const parsed = JSON.parse(raw);
2493
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
2494
+ current = parsed;
2495
+ } catch {}
2496
+ const existingHooks = current.hooks && typeof current.hooks === "object" && !Array.isArray(current.hooks) ? current.hooks : {};
2497
+ current.hooks = mergeClaudeHooks(existingHooks, buildClaudeHooks(ctx.taskId));
2498
+ await writeFile2(settingsPath, `${JSON.stringify(current, null, 2)}
2499
+ `);
2500
+ await hideFromGit(ctx.worktreeDir, ".claude/settings.local.json");
2501
+ } catch {}
2502
+ }
2503
+ }
2504
+ async function hideFromGit(worktreeDir, relPath) {
2505
+ if (hiddenWorktrees.has(worktreeDir))
2506
+ return;
2507
+ try {
2508
+ const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
2509
+ stdout: "pipe",
2510
+ stderr: "ignore"
2511
+ });
2512
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
2513
+ if (code !== 0)
2514
+ return;
2515
+ let commonDir = out.trim();
2516
+ if (!commonDir)
2517
+ return;
2518
+ if (!commonDir.startsWith("/"))
2519
+ commonDir = join3(worktreeDir, commonDir);
2520
+ const excludePath = join3(commonDir, "info", "exclude");
2521
+ const existing = existsSync(excludePath) ? await readFile2(excludePath, "utf8") : "";
2522
+ if (existing.split(`
2523
+ `).some((l) => l.trim() === relPath)) {
2524
+ hiddenWorktrees.add(worktreeDir);
2525
+ return;
2526
+ }
2527
+ await mkdir2(join3(commonDir, "info"), { recursive: true });
2528
+ await appendFile(excludePath, `${existing.endsWith(`
2529
+ `) || existing === "" ? "" : `
2530
+ `}${relPath}
2531
+ `);
2532
+ hiddenWorktrees.add(worktreeDir);
2533
+ } catch {}
2534
+ }
2535
+ var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created", hiddenWorktrees;
2536
+ var init_hook_adapter = __esm(() => {
2537
+ init_invocation();
2538
+ EVENT_MAP = [
2539
+ { event: "SessionStart", verb: "session-start" },
2540
+ { event: "UserPromptSubmit", verb: "turn-start" },
2541
+ { event: "Stop", verb: "turn-complete" },
2542
+ { event: "StopFailure", verb: "turn-failed" },
2543
+ { event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
2544
+ { event: "SessionEnd", verb: "session-end" }
2545
+ ];
2546
+ KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
2547
+ hiddenWorktrees = new Set;
2548
+ });
2549
+
2550
+ // src/engine/hook-adapter.ts
2551
+ function createEngineHookAdapter(vendor) {
2552
+ if (vendor === "claude")
2553
+ return new ClaudeHookAdapter;
2554
+ return new NoopHookAdapter(vendor);
2555
+ }
2556
+
2557
+ class NoopHookAdapter {
2558
+ vendor;
2559
+ constructor(vendor) {
2560
+ this.vendor = vendor;
2561
+ }
2562
+ supportsHooks() {
2563
+ return false;
2564
+ }
2565
+ async installTaskHooks() {}
2566
+ supportsWorktreeSync() {
2567
+ return false;
2568
+ }
2569
+ async installWorktreeSyncHook() {}
2570
+ async removeWorktreeSyncHook() {}
2571
+ }
2572
+ var init_hook_adapter2 = __esm(() => {
2573
+ init_hook_adapter();
2574
+ });
2575
+
2328
2576
  // src/orchestrator/errors.ts
2329
2577
  var IllegalTransitionError, TaskNotFoundError, CannotDeleteMainTaskError, DIRTY_WORKTREE_CODE = "DIRTY_WORKTREE", DirtyWorktreeError, WorktreeRemoveFailedError;
2330
2578
  var init_errors = __esm(() => {
@@ -3003,8 +3251,10 @@ class Orchestrator {
3003
3251
  const task = this.requireTask(id);
3004
3252
  if (task.kind === "main")
3005
3253
  return task.repo;
3006
- if (task.worktreePath)
3254
+ if (task.worktreePath) {
3255
+ await this.installEngineHooks(task);
3007
3256
  return task.worktreePath;
3257
+ }
3008
3258
  const inflight = this.worktreeLocks.get(task.id);
3009
3259
  if (inflight) {
3010
3260
  await inflight;
@@ -3028,6 +3278,7 @@ class Orchestrator {
3028
3278
  worktreePath: info.path,
3029
3279
  branch
3030
3280
  });
3281
+ await this.installEngineHooks(this.requireTask(task.id));
3031
3282
  } catch (err) {
3032
3283
  this.slugs.cancel(task.repo, slug);
3033
3284
  throw err;
@@ -3152,14 +3403,29 @@ class Orchestrator {
3152
3403
  if (!input.worktreePath)
3153
3404
  throw new Error("adoptWorktree: worktreePath is required");
3154
3405
  const target = canonPath(input.worktreePath);
3406
+ const inflight = this.adoptLocks.get(target);
3407
+ if (inflight)
3408
+ return inflight;
3409
+ const work = this.adoptWorktreeLocked(input, target);
3410
+ this.adoptLocks.set(target, work);
3411
+ try {
3412
+ return await work;
3413
+ } finally {
3414
+ this.adoptLocks.delete(target);
3415
+ }
3416
+ }
3417
+ async adoptWorktreeLocked(input, target) {
3418
+ const existing = this.store.list().find((t) => t.worktreePath && canonPath(t.worktreePath) === target);
3419
+ if (existing) {
3420
+ if (input.ifExists === "return")
3421
+ return existing;
3422
+ throw new Error(`adoptWorktree: ${input.worktreePath} is already adopted as a task`);
3423
+ }
3155
3424
  const candidates = await this.worktrees.listAll(input.repo);
3156
3425
  const match = candidates.find((wt) => canonPath(wt.path) === target);
3157
3426
  if (!match) {
3158
3427
  throw new Error(`adoptWorktree: ${input.worktreePath} is not an adoptable git worktree of ${input.repo} (unknown, detached, or the main checkout)`);
3159
3428
  }
3160
- const alreadyLinked = this.store.list().some((t) => t.worktreePath && canonPath(t.worktreePath) === target);
3161
- if (alreadyLinked)
3162
- throw new Error(`adoptWorktree: ${input.worktreePath} is already adopted as a task`);
3163
3429
  const branch = input.branch?.trim() || match.branch;
3164
3430
  const title = (input.title ?? basename2(match.path)).trim() || PLACEHOLDER_TASK_TITLE;
3165
3431
  return this.store.create({
@@ -3172,7 +3438,16 @@ class Orchestrator {
3172
3438
  vendor: input.vendor ?? DEFAULT_TASK_VENDOR
3173
3439
  });
3174
3440
  }
3441
+ async installEngineHooks(task) {
3442
+ if (task.kind === "main" || !task.worktreePath)
3443
+ return;
3444
+ await createEngineHookAdapter(task.vendor ?? DEFAULT_TASK_VENDOR).installTaskHooks({
3445
+ worktreeDir: task.worktreePath,
3446
+ taskId: task.id
3447
+ });
3448
+ }
3175
3449
  pendingBaseRefs = new Map;
3450
+ adoptLocks = new Map;
3176
3451
  requireTask(id) {
3177
3452
  const task = this.store.get(id);
3178
3453
  if (!task)
@@ -3194,6 +3469,7 @@ function canonPath(p) {
3194
3469
  var PLACEHOLDER_TASK_TITLE = "(new task)";
3195
3470
  var init_core = __esm(() => {
3196
3471
  init_dev();
3472
+ init_hook_adapter2();
3197
3473
  init_errors();
3198
3474
  init_slug_allocator();
3199
3475
  });
@@ -3202,6 +3478,11 @@ var init_core = __esm(() => {
3202
3478
  function isProtocolCompatible(args) {
3203
3479
  return args.remoteVersion >= args.localMin && args.localVersion >= args.remoteMin;
3204
3480
  }
3481
+ function isDaemonVersionStale(daemonVersion, clientVersion) {
3482
+ if (!daemonVersion)
3483
+ return false;
3484
+ return daemonVersion !== clientVersion;
3485
+ }
3205
3486
  function serializeTask(task) {
3206
3487
  return {
3207
3488
  id: task.id,
@@ -3225,13 +3506,13 @@ function frameToLine(frame) {
3225
3506
  }
3226
3507
  var DAEMON_PROTOCOL_VERSION = 2, MIN_COMPATIBLE_PROTOCOL_VERSION = 2, CHANNEL_NAMES;
3227
3508
  var init_protocol = __esm(() => {
3228
- CHANNEL_NAMES = ["task.snapshot", "active-task", "update"];
3509
+ CHANNEL_NAMES = ["task.snapshot", "active-task", "update", "engine-state"];
3229
3510
  });
3230
3511
 
3231
3512
  // src/daemon/paths.ts
3232
3513
  import { createHash as createHash2 } from "crypto";
3233
3514
  import { homedir as homedir3, tmpdir } from "os";
3234
- import { join as join3 } from "path";
3515
+ import { join as join4 } from "path";
3235
3516
  function shortHomeTag(homeDir2) {
3236
3517
  return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
3237
3518
  }
@@ -3240,7 +3521,7 @@ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
3240
3521
  return naturalPath;
3241
3522
  const tag = shortHomeTag(homeDir2);
3242
3523
  const suffix = pidTag === undefined ? "" : `-${pidTag}`;
3243
- const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
3524
+ const fallback = join4(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
3244
3525
  if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
3245
3526
  return fallback;
3246
3527
  throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
@@ -3251,33 +3532,33 @@ function defaultDaemonSocketPath(homeDir2) {
3251
3532
  return override;
3252
3533
  const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
3253
3534
  if (explicit && explicit.length > 0) {
3254
- return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
3535
+ return fitSocketPath(join4(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
3255
3536
  }
3256
3537
  const runtimeDir = process.env.XDG_RUNTIME_DIR;
3257
3538
  if (runtimeDir && runtimeDir.length > 0) {
3258
- return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
3539
+ return fitSocketPath(join4(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
3259
3540
  }
3260
3541
  const home = homedir3();
3261
- return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
3542
+ return fitSocketPath(join4(home, ".kobe", "daemon.sock"), home, "daemon");
3262
3543
  }
3263
3544
  function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3264
3545
  const override = process.env.KOBE_DAEMON_PID_PATH;
3265
3546
  if (override && override.length > 0)
3266
3547
  return override;
3267
- return join3(homeDir2, ".kobe", "daemon.pid");
3548
+ return join4(homeDir2, ".kobe", "daemon.pid");
3268
3549
  }
3269
3550
  function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3270
- return join3(homeDir2, ".kobe", "daemon.log");
3551
+ return join4(homeDir2, ".kobe", "daemon.log");
3271
3552
  }
3272
3553
  function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3273
- return join3(homeDir2, ".kobe", "client.log");
3554
+ return join4(homeDir2, ".kobe", "client.log");
3274
3555
  }
3275
3556
  var SOCKET_PATH_SAFETY_LIMIT = 100;
3276
3557
  var init_paths2 = () => {};
3277
3558
 
3278
3559
  // src/client/client-log.ts
3279
- import { appendFile, mkdir as mkdir2 } from "fs/promises";
3280
- import { dirname as dirname3 } from "path";
3560
+ import { appendFile as appendFile2, mkdir as mkdir3 } from "fs/promises";
3561
+ import { dirname as dirname4 } from "path";
3281
3562
  function setClientLogContext(ctx) {
3282
3563
  context = ctx;
3283
3564
  }
@@ -3309,11 +3590,11 @@ function append(line) {
3309
3590
  const path3 = defaultClientLogPath();
3310
3591
  writeChain = writeChain.then(async () => {
3311
3592
  try {
3312
- await appendFile(path3, line);
3593
+ await appendFile2(path3, line);
3313
3594
  } catch (err) {
3314
3595
  if (err?.code === "ENOENT") {
3315
- await mkdir2(dirname3(path3), { recursive: true });
3316
- await appendFile(path3, line);
3596
+ await mkdir3(dirname4(path3), { recursive: true });
3597
+ await appendFile2(path3, line);
3317
3598
  } else {
3318
3599
  throw err;
3319
3600
  }
@@ -3515,6 +3796,37 @@ var init_client = __esm(() => {
3515
3796
  init_client_log();
3516
3797
  });
3517
3798
 
3799
+ // src/engine/hook-events.ts
3800
+ function isEngineActivityKind(v) {
3801
+ return ENGINE_ACTIVITY_KINDS.includes(v);
3802
+ }
3803
+ function reduceActivity(_prev, kind, detail) {
3804
+ switch (kind) {
3805
+ case "session-start":
3806
+ case "session-end":
3807
+ return "idle";
3808
+ case "turn-start":
3809
+ return "running";
3810
+ case "turn-complete":
3811
+ return "turn_complete";
3812
+ case "turn-failed":
3813
+ return detail?.failure === "rate_limit" || detail?.failure === "billing" ? "rate_limited" : "error";
3814
+ case "awaiting-input":
3815
+ return detail?.waiting === "permission" ? "permission_needed" : "running";
3816
+ }
3817
+ }
3818
+ var ENGINE_ACTIVITY_KINDS;
3819
+ var init_hook_events = __esm(() => {
3820
+ ENGINE_ACTIVITY_KINDS = [
3821
+ "session-start",
3822
+ "turn-start",
3823
+ "turn-complete",
3824
+ "turn-failed",
3825
+ "awaiting-input",
3826
+ "session-end"
3827
+ ];
3828
+ });
3829
+
3518
3830
  // src/engine/claude-code-local/normalize.ts
3519
3831
  function normalizeClaudeContent(content) {
3520
3832
  if (typeof content === "string") {
@@ -3562,7 +3874,7 @@ function normalizeClaudeContent(content) {
3562
3874
  }
3563
3875
 
3564
3876
  // src/engine/claude-code-local/history.ts
3565
- import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
3877
+ import { appendFile as appendFile3, mkdir as mkdir4, readFile as readFile3, readdir, stat, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
3566
3878
  import { homedir as homedir4 } from "os";
3567
3879
  import path3 from "path";
3568
3880
  function encodeCwd(cwd) {
@@ -3633,7 +3945,7 @@ function parseJsonl(raw, sessionId) {
3633
3945
  } catch {
3634
3946
  continue;
3635
3947
  }
3636
- if (!isObject(parsed))
3948
+ if (!isObject2(parsed))
3637
3949
  continue;
3638
3950
  const msg = extractMessage(parsed, sessionId);
3639
3951
  if (msg)
@@ -3642,7 +3954,7 @@ function parseJsonl(raw, sessionId) {
3642
3954
  return out;
3643
3955
  }
3644
3956
  function extractMessage(record, fallbackSessionId) {
3645
- const inner = isObject(record.message) ? record.message : record;
3957
+ const inner = isObject2(record.message) ? record.message : record;
3646
3958
  const role = inner.role;
3647
3959
  if (role !== "user" && role !== "assistant" && role !== "system")
3648
3960
  return null;
@@ -3655,7 +3967,7 @@ function extractMessage(record, fallbackSessionId) {
3655
3967
  return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
3656
3968
  }
3657
3969
  function extractUsage(v) {
3658
- if (!isObject(v))
3970
+ if (!isObject2(v))
3659
3971
  return;
3660
3972
  const inTok = typeof v.input_tokens === "number" ? v.input_tokens : undefined;
3661
3973
  const outTok = typeof v.output_tokens === "number" ? v.output_tokens : undefined;
@@ -3670,7 +3982,7 @@ function extractUsage(v) {
3670
3982
  ...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
3671
3983
  };
3672
3984
  }
3673
- function isObject(v) {
3985
+ function isObject2(v) {
3674
3986
  return typeof v === "object" && v !== null && !Array.isArray(v);
3675
3987
  }
3676
3988
  var defaultDeps;
@@ -3687,7 +3999,7 @@ var init_history = __esm(() => {
3687
3999
  }
3688
4000
  },
3689
4001
  async readFile(p) {
3690
- return await readFile2(p, "utf8");
4002
+ return await readFile3(p, "utf8");
3691
4003
  },
3692
4004
  async pathExists(p) {
3693
4005
  try {
@@ -3714,7 +4026,7 @@ function normalizeCodexContent(raw) {
3714
4026
  blocks.push({ type: "text", text: item });
3715
4027
  continue;
3716
4028
  }
3717
- if (!isObject2(item))
4029
+ if (!isObject3(item))
3718
4030
  continue;
3719
4031
  const t = typeof item.type === "string" ? item.type : undefined;
3720
4032
  if (t === "input_text" || t === "output_text") {
@@ -3728,7 +4040,7 @@ function normalizeCodexContent(raw) {
3728
4040
  }
3729
4041
  return blocks;
3730
4042
  }
3731
- function isObject2(v) {
4043
+ function isObject3(v) {
3732
4044
  return typeof v === "object" && v !== null && !Array.isArray(v);
3733
4045
  }
3734
4046
 
@@ -3778,7 +4090,7 @@ function validPositive(v) {
3778
4090
  }
3779
4091
 
3780
4092
  // src/engine/codex-local/history.ts
3781
- import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
4093
+ import { readFile as readFile4, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
3782
4094
  import { homedir as homedir5 } from "os";
3783
4095
  import path4 from "path";
3784
4096
  async function listRolloutFiles(deps = defaultDeps2) {
@@ -3913,11 +4225,11 @@ function parseJsonl2(raw, sessionId) {
3913
4225
  } catch {
3914
4226
  continue;
3915
4227
  }
3916
- if (!isObject3(parsed))
4228
+ if (!isObject4(parsed))
3917
4229
  continue;
3918
4230
  if (parsed.type !== "response_item")
3919
4231
  continue;
3920
- const payload = isObject3(parsed.payload) ? parsed.payload : undefined;
4232
+ const payload = isObject4(parsed.payload) ? parsed.payload : undefined;
3921
4233
  if (!payload)
3922
4234
  continue;
3923
4235
  const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
@@ -4034,7 +4346,7 @@ function textFromReasoningValue(value) {
4034
4346
  parts.push(entry);
4035
4347
  continue;
4036
4348
  }
4037
- if (!isObject3(entry))
4349
+ if (!isObject4(entry))
4038
4350
  continue;
4039
4351
  const text = typeof entry.text === "string" ? entry.text : "";
4040
4352
  if (text.length > 0)
@@ -4076,14 +4388,14 @@ function deriveCodexUsageMetrics(raw) {
4076
4388
  } catch {
4077
4389
  continue;
4078
4390
  }
4079
- if (!isObject3(parsed))
4391
+ if (!isObject4(parsed))
4080
4392
  continue;
4081
4393
  const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs(parsed.timestamp) : null;
4082
4394
  if (parsed.type === "response_item")
4083
4395
  continue;
4084
4396
  if (parsed.type !== "turn.completed")
4085
4397
  continue;
4086
- const usage = isObject3(parsed.usage) ? parsed.usage : undefined;
4398
+ const usage = isObject4(parsed.usage) ? parsed.usage : undefined;
4087
4399
  if (!usage)
4088
4400
  continue;
4089
4401
  const snapshot = codexUsageToSnapshot(usage);
@@ -4102,7 +4414,7 @@ function parseTimestampMs(value) {
4102
4414
  const ms = new Date(value).getTime();
4103
4415
  return Number.isFinite(ms) ? ms : null;
4104
4416
  }
4105
- function isObject3(v) {
4417
+ function isObject4(v) {
4106
4418
  return typeof v === "object" && v !== null && !Array.isArray(v);
4107
4419
  }
4108
4420
  var defaultDeps2, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
@@ -4120,7 +4432,7 @@ var init_history2 = __esm(() => {
4120
4432
  }
4121
4433
  },
4122
4434
  async readFile(p) {
4123
- return await readFile3(p, "utf8");
4435
+ return await readFile4(p, "utf8");
4124
4436
  },
4125
4437
  stat: stat2
4126
4438
  };
@@ -4129,16 +4441,16 @@ var init_history2 = __esm(() => {
4129
4441
 
4130
4442
  // src/engine/copilot-local/usage.ts
4131
4443
  function copilotUsageToSnapshot(value) {
4132
- if (!isObject4(value))
4444
+ if (!isObject5(value))
4133
4445
  return;
4134
- const modelMetrics = isObject4(value.modelMetrics) ? value.modelMetrics : undefined;
4446
+ const modelMetrics = isObject5(value.modelMetrics) ? value.modelMetrics : undefined;
4135
4447
  if (!modelMetrics)
4136
4448
  return;
4137
4449
  let input = 0;
4138
4450
  let output = 0;
4139
4451
  let cached = 0;
4140
4452
  for (const metrics of Object.values(modelMetrics)) {
4141
- if (!isObject4(metrics) || !isObject4(metrics.usage))
4453
+ if (!isObject5(metrics) || !isObject5(metrics.usage))
4142
4454
  continue;
4143
4455
  input += numberOr2(metrics.usage.inputTokens, 0);
4144
4456
  output += numberOr2(metrics.usage.outputTokens, 0);
@@ -4154,7 +4466,7 @@ function copilotUsageToSnapshot(value) {
4154
4466
  ...context2 > 0 ? { context_tokens: context2 } : {}
4155
4467
  };
4156
4468
  }
4157
- function isObject4(v) {
4469
+ function isObject5(v) {
4158
4470
  return typeof v === "object" && v !== null && !Array.isArray(v);
4159
4471
  }
4160
4472
  function numberOr2(value, fallback) {
@@ -4162,7 +4474,7 @@ function numberOr2(value, fallback) {
4162
4474
  }
4163
4475
 
4164
4476
  // src/engine/copilot-local/history.ts
4165
- import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
4477
+ import { readFile as readFile5, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
4166
4478
  import { homedir as homedir6 } from "os";
4167
4479
  import path5 from "path";
4168
4480
  async function listSessionDirs(deps = defaultDeps3) {
@@ -4259,10 +4571,10 @@ function parseEvents(raw, fallbackSessionId) {
4259
4571
  } catch {
4260
4572
  continue;
4261
4573
  }
4262
- if (!isObject5(record) || typeof record.type !== "string")
4574
+ if (!isObject6(record) || typeof record.type !== "string")
4263
4575
  continue;
4264
4576
  const timestamp = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
4265
- const data = isObject5(record.data) ? record.data : {};
4577
+ const data = isObject6(record.data) ? record.data : {};
4266
4578
  if (record.type === "session.start") {
4267
4579
  const sid = typeof data.sessionId === "string" ? data.sessionId : undefined;
4268
4580
  if (sid)
@@ -4285,7 +4597,7 @@ function parseEvents(raw, fallbackSessionId) {
4285
4597
  blocks.push({ type: "text", text });
4286
4598
  const toolRequests = Array.isArray(data.toolRequests) ? data.toolRequests : [];
4287
4599
  for (const req of toolRequests) {
4288
- if (!isObject5(req))
4600
+ if (!isObject6(req))
4289
4601
  continue;
4290
4602
  const callId = typeof req.id === "string" ? req.id : typeof req.toolCallId === "string" ? req.toolCallId : "tool";
4291
4603
  const name = typeof req.name === "string" ? req.name : typeof req.toolName === "string" ? req.toolName : "tool";
@@ -4322,7 +4634,7 @@ function parseEvents(raw, fallbackSessionId) {
4322
4634
  }
4323
4635
  return { messages, usageMetrics, firstUserMessage };
4324
4636
  }
4325
- function isObject5(v) {
4637
+ function isObject6(v) {
4326
4638
  return typeof v === "object" && v !== null && !Array.isArray(v);
4327
4639
  }
4328
4640
  var defaultDeps3, PREVIEW_CHAR_CAP = 200;
@@ -4342,7 +4654,7 @@ var init_history3 = __esm(() => {
4342
4654
  }
4343
4655
  },
4344
4656
  async readFile(p) {
4345
- return await readFile4(p, "utf8");
4657
+ return await readFile5(p, "utf8");
4346
4658
  },
4347
4659
  stat: stat3,
4348
4660
  async rm(p) {
@@ -4404,90 +4716,6 @@ var init_auto_title = __esm(() => {
4404
4716
  init_history3();
4405
4717
  });
4406
4718
 
4407
- // src/cli/invocation.ts
4408
- import { fileURLToPath } from "url";
4409
- function kobeCliInvocation() {
4410
- const isBuilt = import.meta.url.endsWith(".js");
4411
- if (isBuilt)
4412
- return ["kobe"];
4413
- const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
4414
- const preload = fileURLToPath(import.meta.resolve("@opentui/solid/preload"));
4415
- return [process.execPath, "--preload", preload, "--conditions=browser", entry];
4416
- }
4417
- var init_invocation = () => {};
4418
-
4419
- // src/tmux/session-layout.ts
4420
- function shellQuote(s) {
4421
- return `'${s.replace(/'/g, "'\\''")}'`;
4422
- }
4423
- function shellQuoteArgv(argv) {
4424
- return argv.map(shellQuote).join(" ");
4425
- }
4426
- function keepAlive(cmd) {
4427
- return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
4428
- }
4429
- function shQuote(s) {
4430
- return `'${s.replace(/'/g, "'\\''")}'`;
4431
- }
4432
- function homeWelcomeCommand() {
4433
- const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
4434
- return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
4435
- }
4436
- function engineLaunchLine(engineCmd, init) {
4437
- const tail = keepAlive(engineCmd);
4438
- const script = init?.initScript?.trim();
4439
- if (!script)
4440
- return tail;
4441
- const group = ["{", script, "}"].join(`
4442
- `);
4443
- if (init?.markerPath) {
4444
- const marker = shQuote(init.markerPath);
4445
- const markerDir = shQuote(markerDirOf(init.markerPath));
4446
- return [
4447
- `if [ ! -f ${marker} ]; then`,
4448
- group,
4449
- `if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
4450
- "fi",
4451
- tail
4452
- ].join(`
4453
- `);
4454
- }
4455
- return [group, tail].join(`
4456
- `);
4457
- }
4458
- function markerDirOf(p) {
4459
- const i = p.lastIndexOf("/");
4460
- return i <= 0 ? "." : p.slice(0, i);
4461
- }
4462
- function fallbackOpsScript(cwd) {
4463
- return `cd ${shellQuote(cwd)} && while :; do clear; printf "\\033[1m# %s\\033[0m\\n\\n" ${shellQuote(cwd)}; git status --short --branch 2>/dev/null | sed 's/^/ /' || true; printf "\\n"; if command -v lsd >/dev/null 2>&1; then lsd --tree --git -I node_modules -I .git --depth 2 .; elif command -v eza >/dev/null 2>&1; then eza --tree --git -L 2 -I 'node_modules|.git' .; elif command -v tree >/dev/null 2>&1; then tree -L 2 -I 'node_modules|.git'; else ls -la; fi; sleep 2; done`;
4464
- }
4465
- function previewWindowCommand(args) {
4466
- const wt = shellQuote(args.worktree);
4467
- const file = shellQuote(args.relPath);
4468
- const inv = args.cliInvocation.map(shellQuote).join(" ");
4469
- const fallback = `cd ${wt} && if ! git diff --quiet HEAD -- ${file} 2>/dev/null; then ` + `git diff HEAD -- ${file} | { delta --paging=always 2>/dev/null || less -R; }; ` + `else bat --style=plain --paging=always ${file} 2>/dev/null || \${PAGER:-less} ${file} 2>/dev/null || cat ${file}; fi`;
4470
- return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
4471
- }
4472
- function updatePageCommand(args) {
4473
- return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
4474
- }
4475
- function tasksPaneCommand(cliInvocation, opts = {}) {
4476
- const argv = [...cliInvocation, "tasks"];
4477
- if (opts.initialTaskId)
4478
- argv.push("--initial-task-id", opts.initialTaskId);
4479
- return shellQuoteArgv(argv);
4480
- }
4481
- function opsPaneCommand(args) {
4482
- if (args.taskId && args.claudePaneId) {
4483
- const inv = args.cliInvocation.map(shellQuote).join(" ");
4484
- const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
4485
- return `KOBE_FILETREE_WATCH=1 ${inv} ops --task-id ${shellQuote(args.taskId)} --worktree ${shellQuote(args.cwd)} ` + `--target-pane ${shellQuote(args.claudePaneId)}${vendorFlag} || { ${fallbackOpsScript(args.cwd)}; }`;
4486
- }
4487
- return fallbackOpsScript(args.cwd);
4488
- }
4489
- var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
4490
-
4491
4719
  // src/tmux/client.ts
4492
4720
  var exports_client = {};
4493
4721
  __export(exports_client, {
@@ -4967,9 +5195,9 @@ class DaemonEventBus {
4967
5195
  }
4968
5196
 
4969
5197
  // src/daemon/server.ts
4970
- import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
5198
+ import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
4971
5199
  import { createServer } from "net";
4972
- import { dirname as dirname4 } from "path";
5200
+ import { dirname as dirname5 } from "path";
4973
5201
  function resolveIdleGraceMs() {
4974
5202
  const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
4975
5203
  if (raw === undefined)
@@ -4977,6 +5205,13 @@ function resolveIdleGraceMs() {
4977
5205
  const n = Number(raw);
4978
5206
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_IDLE_GRACE_MS;
4979
5207
  }
5208
+ function resolveEngineStateTtlMs() {
5209
+ const raw = process.env.KOBE_ENGINE_STATE_TTL_MS;
5210
+ if (raw === undefined)
5211
+ return DEFAULT_ENGINE_STATE_TTL_MS;
5212
+ const n = Number(raw);
5213
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_ENGINE_STATE_TTL_MS;
5214
+ }
4980
5215
  async function startDaemonServer(orch, options = {}) {
4981
5216
  const socketPath = options.socketPath ?? defaultDaemonSocketPath(options.homeDir);
4982
5217
  const pidPath = options.pidPath ?? defaultDaemonPidPath(options.homeDir);
@@ -5017,8 +5252,30 @@ async function startDaemonServer(orch, options = {}) {
5017
5252
  bus.onPublish((event) => {
5018
5253
  broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
5019
5254
  });
5020
- await mkdir4(dirname4(socketPath), { recursive: true });
5021
- await mkdir4(dirname4(pidPath), { recursive: true });
5255
+ const activity = new Map;
5256
+ const ACTIVITY_STALE_MS = resolveEngineStateTtlMs();
5257
+ function reportActivity(taskId, kind, detail) {
5258
+ const prev = activity.get(taskId);
5259
+ if (prev?.lapse)
5260
+ clearTimeout(prev.lapse);
5261
+ const state = reduceActivity(prev?.state, kind, detail);
5262
+ const at = Date.now();
5263
+ const entry = { state, detail, at };
5264
+ if (state !== "idle") {
5265
+ entry.lapse = setTimeout(() => {
5266
+ const cur = activity.get(taskId);
5267
+ if (cur && cur.at === at) {
5268
+ activity.set(taskId, { state: "idle", at: Date.now() });
5269
+ bus.publish("engine-state", { taskId, state: "idle", at: Date.now() });
5270
+ }
5271
+ }, ACTIVITY_STALE_MS);
5272
+ entry.lapse.unref?.();
5273
+ }
5274
+ activity.set(taskId, entry);
5275
+ bus.publish("engine-state", { taskId, state, ...detail ? { detail } : {}, at });
5276
+ }
5277
+ await mkdir5(dirname5(socketPath), { recursive: true });
5278
+ await mkdir5(dirname5(pidPath), { recursive: true });
5022
5279
  await unlink4(socketPath).catch(() => {});
5023
5280
  const server = createServer((socket) => {
5024
5281
  const client = {
@@ -5088,7 +5345,7 @@ async function startDaemonServer(orch, options = {}) {
5088
5345
  resolve2();
5089
5346
  });
5090
5347
  });
5091
- await writeFile3(pidPath, `${process.pid}
5348
+ await writeFile4(pidPath, `${process.pid}
5092
5349
  `, "utf8");
5093
5350
  async function stopSoon() {
5094
5351
  if (stopping)
@@ -5117,6 +5374,7 @@ async function startDaemonServer(orch, options = {}) {
5117
5374
  return {
5118
5375
  protocolVersion: DAEMON_PROTOCOL_VERSION,
5119
5376
  minProtocolVersion: MIN_COMPATIBLE_PROTOCOL_VERSION,
5377
+ kobeVersion: CURRENT_VERSION,
5120
5378
  capabilities: [...CHANNEL_NAMES],
5121
5379
  daemonPid: process.pid,
5122
5380
  clientId: client.id,
@@ -5126,6 +5384,7 @@ async function startDaemonServer(orch, options = {}) {
5126
5384
  case "daemon.status":
5127
5385
  return {
5128
5386
  daemonPid: process.pid,
5387
+ kobeVersion: CURRENT_VERSION,
5129
5388
  uptimeMs: Date.now() - startedAt.getTime(),
5130
5389
  startedAt: startedAt.toISOString(),
5131
5390
  attachedClients: guiCount(),
@@ -5181,6 +5440,12 @@ async function startDaemonServer(orch, options = {}) {
5181
5440
  case "task.delete": {
5182
5441
  const taskId = requireString(payload, "taskId");
5183
5442
  await orch.deleteTask(taskId, { force: optionalBoolean(payload, "force") });
5443
+ const gone = activity.get(taskId);
5444
+ if (gone?.lapse)
5445
+ clearTimeout(gone.lapse);
5446
+ activity.delete(taskId);
5447
+ if (gone)
5448
+ bus.publish("engine-state", { taskId, state: "idle", at: Date.now() });
5184
5449
  return {};
5185
5450
  }
5186
5451
  case "task.pin": {
@@ -5218,7 +5483,8 @@ async function startDaemonServer(orch, options = {}) {
5218
5483
  worktreePath: requireString(payload, "worktreePath"),
5219
5484
  branch: optionalString(payload, "branch"),
5220
5485
  vendor: optionalVendor(payload, "vendor"),
5221
- title: optionalString(payload, "title")
5486
+ title: optionalString(payload, "title"),
5487
+ ifExists: optionalString(payload, "ifExists") === "return" ? "return" : "error"
5222
5488
  });
5223
5489
  return { task: serializeTask(task) };
5224
5490
  }
@@ -5226,6 +5492,15 @@ async function startDaemonServer(orch, options = {}) {
5226
5492
  bus.publish("active-task", { taskId: optionalString(payload, "taskId") ?? null });
5227
5493
  return {};
5228
5494
  }
5495
+ case "engine.reportEvent": {
5496
+ const taskId = requireString(payload, "taskId");
5497
+ const kind = requireString(payload, "kind");
5498
+ if (!isEngineActivityKind(kind))
5499
+ throw new Error(`unknown engine event kind: ${kind}`);
5500
+ const detail = optionalActivityDetail(payload);
5501
+ reportActivity(taskId, kind, detail);
5502
+ return {};
5503
+ }
5229
5504
  case "subscribe": {
5230
5505
  client.subscribed = true;
5231
5506
  const role = payload.role === "gui" ? "gui" : "pane";
@@ -5236,6 +5511,15 @@ async function startDaemonServer(orch, options = {}) {
5236
5511
  for (const event of bus.snapshot()) {
5237
5512
  writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
5238
5513
  }
5514
+ for (const [taskId, entry] of activity) {
5515
+ if (entry.state === "idle")
5516
+ continue;
5517
+ writeFrame(client, {
5518
+ type: "event",
5519
+ name: "engine-state",
5520
+ payload: { taskId, state: entry.state, ...entry.detail ? { detail: entry.detail } : {}, at: entry.at }
5521
+ });
5522
+ }
5239
5523
  return {};
5240
5524
  }
5241
5525
  default:
@@ -5286,7 +5570,7 @@ async function startDaemonServer(orch, options = {}) {
5286
5570
  }
5287
5571
  async function readPidFile(pidPath) {
5288
5572
  try {
5289
- const raw = await readFile5(pidPath, "utf8");
5573
+ const raw = await readFile6(pidPath, "utf8");
5290
5574
  const pid = Number(raw.trim());
5291
5575
  return Number.isFinite(pid) ? pid : null;
5292
5576
  } catch {
@@ -5337,13 +5621,29 @@ function optionalVendor(payload, key) {
5337
5621
  }
5338
5622
  return value;
5339
5623
  }
5340
- var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000;
5624
+ function optionalActivityDetail(payload) {
5625
+ const raw = payload.detail;
5626
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
5627
+ return;
5628
+ const d = raw;
5629
+ const out = {};
5630
+ if (d.failure === "rate_limit" || d.failure === "billing" || d.failure === "other")
5631
+ out.failure = d.failure;
5632
+ if (d.waiting === "permission" || d.waiting === "input")
5633
+ out.waiting = d.waiting;
5634
+ if (typeof d.note === "string")
5635
+ out.note = d.note;
5636
+ return Object.keys(out).length > 0 ? out : undefined;
5637
+ }
5638
+ var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000, DEFAULT_ENGINE_STATE_TTL_MS;
5341
5639
  var init_server = __esm(() => {
5640
+ init_hook_events();
5342
5641
  init_version();
5343
5642
  init_auto_title_poller();
5344
5643
  init_paths2();
5345
5644
  init_protocol();
5346
5645
  DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
5646
+ DEFAULT_ENGINE_STATE_TTL_MS = 10 * 60 * 1000;
5347
5647
  });
5348
5648
 
5349
5649
  // src/daemon/lifecycle.ts
@@ -5411,14 +5711,14 @@ __export(exports_daemon_process, {
5411
5711
  connectIfRunning: () => connectIfRunning
5412
5712
  });
5413
5713
  import { spawn } from "child_process";
5414
- import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
5415
- import { dirname as dirname5, resolve as resolve2 } from "path";
5714
+ import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync } from "fs";
5715
+ import { dirname as dirname6, resolve as resolve2 } from "path";
5416
5716
  import { fileURLToPath as fileURLToPath2 } from "url";
5417
5717
  function spawnDetachedDaemon(command, args, env, logPath) {
5418
5718
  let stdio = "ignore";
5419
5719
  let logFd;
5420
5720
  try {
5421
- mkdirSync2(dirname5(logPath), { recursive: true });
5721
+ mkdirSync2(dirname6(logPath), { recursive: true });
5422
5722
  logFd = openSync(logPath, "a");
5423
5723
  stdio = ["ignore", logFd, logFd];
5424
5724
  } catch {
@@ -5485,12 +5785,12 @@ function resolveKobeSpawn(subcommand) {
5485
5785
  if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
5486
5786
  return [process.execPath, ...subcommand];
5487
5787
  }
5488
- const dir = dirname5(here);
5788
+ const dir = dirname6(here);
5489
5789
  const sourceEntry = resolve2(dir, "../cli/index.ts");
5490
- if (existsSync(sourceEntry))
5790
+ if (existsSync2(sourceEntry))
5491
5791
  return [process.execPath, sourceEntry, ...subcommand];
5492
5792
  const distEntry = resolve2(dir, "../cli/index.js");
5493
- if (existsSync(distEntry))
5793
+ if (existsSync2(distEntry))
5494
5794
  return [process.execPath, distEntry, ...subcommand];
5495
5795
  throw new Error(`kobe: could not locate kobe entry near ${dir}; expected ../cli/index.{ts,js}`);
5496
5796
  }
@@ -5581,14 +5881,14 @@ async function runRepoSubcommand(args) {
5581
5881
  return;
5582
5882
  }
5583
5883
  const { getRepoInitOverride: getRepoInitOverride2, setRepoInitOverride: setRepoInitOverride2, resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
5584
- const { existsSync: existsSync2 } = await import("fs");
5585
- const { join: join4 } = await import("path");
5884
+ const { existsSync: existsSync3 } = await import("fs");
5885
+ const { join: join5 } = await import("path");
5586
5886
  if (verb === "show") {
5587
5887
  const [pathArg] = rest.filter((a) => !a.startsWith("-"));
5588
5888
  const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
5589
5889
  const override = getRepoInitOverride2(repo);
5590
- const hasFileScript = existsSync2(join4(repo, ".kobe", "init.sh"));
5591
- const hasFilePrompt = existsSync2(join4(repo, ".kobe", "init-prompt.md"));
5890
+ const hasFileScript = existsSync3(join5(repo, ".kobe", "init.sh"));
5891
+ const hasFilePrompt = existsSync3(join5(repo, ".kobe", "init-prompt.md"));
5592
5892
  console.log(`repo: ${repo}`);
5593
5893
  console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
5594
5894
  console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
@@ -6247,14 +6547,14 @@ var exports_repo_init = {};
6247
6547
  __export(exports_repo_init, {
6248
6548
  resolveRepoInit: () => resolveRepoInit
6249
6549
  });
6250
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
6251
- import { join as join4 } from "path";
6550
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
6551
+ import { join as join5 } from "path";
6252
6552
  function repoFileScript(worktreePath) {
6253
- return existsSync2(join4(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
6553
+ return existsSync3(join5(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
6254
6554
  }
6255
6555
  function repoFilePrompt(worktreePath) {
6256
- const p = join4(worktreePath, INIT_PROMPT_REL);
6257
- if (!existsSync2(p))
6556
+ const p = join5(worktreePath, INIT_PROMPT_REL);
6557
+ if (!existsSync3(p))
6258
6558
  return;
6259
6559
  try {
6260
6560
  const text = readFileSync3(p, "utf8");
@@ -6275,8 +6575,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
6275
6575
  var INIT_SCRIPT_REL, INIT_PROMPT_REL;
6276
6576
  var init_repo_init = __esm(() => {
6277
6577
  init_repos();
6278
- INIT_SCRIPT_REL = join4(".kobe", "init.sh");
6279
- INIT_PROMPT_REL = join4(".kobe", "init-prompt.md");
6578
+ INIT_SCRIPT_REL = join5(".kobe", "init.sh");
6579
+ INIT_PROMPT_REL = join5(".kobe", "init-prompt.md");
6280
6580
  });
6281
6581
 
6282
6582
  // src/tui/panes/sidebar/worktree-changes.ts
@@ -7288,9 +7588,9 @@ var init_schema = () => {};
7288
7588
 
7289
7589
  // src/tui/context/theme/loader.ts
7290
7590
  import { readFileSync as readFileSync4, readdirSync } from "fs";
7291
- import { join as join5 } from "path";
7591
+ import { join as join6 } from "path";
7292
7592
  function userThemesDir() {
7293
- return join5(kobeStateDir(), "themes");
7593
+ return join6(kobeStateDir(), "themes");
7294
7594
  }
7295
7595
  function loadUserThemes() {
7296
7596
  const dir = userThemesDir();
@@ -7304,7 +7604,7 @@ function loadUserThemes() {
7304
7604
  for (const file of entries) {
7305
7605
  if (!file.endsWith(".json"))
7306
7606
  continue;
7307
- const path6 = join5(dir, file);
7607
+ const path6 = join6(dir, file);
7308
7608
  let parsed;
7309
7609
  try {
7310
7610
  const text = readFileSync4(path6, "utf8");
@@ -7334,8 +7634,8 @@ var exports_theme = {};
7334
7634
  __export(exports_theme, {
7335
7635
  runThemeSubcommand: () => runThemeSubcommand
7336
7636
  });
7337
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
7338
- import { basename as basename3, join as join6, resolve as resolve5 } from "path";
7637
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
7638
+ import { basename as basename3, join as join7, resolve as resolve5 } from "path";
7339
7639
  function fail2(message) {
7340
7640
  process.stderr.write(`kobe theme: ${message}
7341
7641
  `);
@@ -7366,7 +7666,7 @@ function listThemes() {
7366
7666
  } else {
7367
7667
  for (const f of userFiles) {
7368
7668
  const name = f.slice(0, -".json".length);
7369
- const path6 = join6(dir, f);
7669
+ const path6 = join7(dir, f);
7370
7670
  const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
7371
7671
  lines.push(` ${name}${overridesBundled} ${path6}`);
7372
7672
  }
@@ -7456,8 +7756,8 @@ async function addTheme(args) {
7456
7756
  }
7457
7757
  const dir = userThemesDir();
7458
7758
  mkdirSync3(dir, { recursive: true });
7459
- const dest = join6(dir, `${name}.json`);
7460
- if (existsSync3(dest) && !opts.force) {
7759
+ const dest = join7(dir, `${name}.json`);
7760
+ if (existsSync4(dest) && !opts.force) {
7461
7761
  fail2(`${dest} already exists (pass --force to overwrite)`);
7462
7762
  }
7463
7763
  writeFileSync2(dest, `${JSON.stringify(result.theme, null, 2)}
@@ -7474,8 +7774,8 @@ function removeTheme(args) {
7474
7774
  if (BUNDLED_NAMES.includes(name)) {
7475
7775
  fail2(`"${name}" is a built-in theme and cannot be removed`);
7476
7776
  }
7477
- const dest = join6(userThemesDir(), `${name}.json`);
7478
- if (!existsSync3(dest)) {
7777
+ const dest = join7(userThemesDir(), `${name}.json`);
7778
+ if (!existsSync4(dest)) {
7479
7779
  fail2(`no user theme named "${name}" (looked for ${dest})`);
7480
7780
  }
7481
7781
  unlinkSync(dest);
@@ -7658,9 +7958,9 @@ var init_daemon_cmd = __esm(() => {
7658
7958
  });
7659
7959
 
7660
7960
  // src/lib/skill-install.ts
7661
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
7961
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
7662
7962
  import { homedir as homedir9 } from "os";
7663
- import { join as join7 } from "path";
7963
+ import { join as join8 } from "path";
7664
7964
  function npxSkillsArgv(opts = {}) {
7665
7965
  return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
7666
7966
  }
@@ -7670,14 +7970,14 @@ function npxSkillsCommand(opts = {}) {
7670
7970
  function kobeSkillPaths(opts = {}) {
7671
7971
  const home = opts.home ?? homedir9();
7672
7972
  const cwd = opts.cwd ?? process.cwd();
7673
- return [join7(home, SKILL_REL_PATH), join7(cwd, SKILL_REL_PATH)];
7973
+ return [join8(home, SKILL_REL_PATH), join8(cwd, SKILL_REL_PATH)];
7674
7974
  }
7675
7975
  function parseSkillVersion(content) {
7676
7976
  const m = content.match(/kobe-skill-version:\s*(\d+)/);
7677
7977
  return m ? Number.parseInt(m[1], 10) : null;
7678
7978
  }
7679
7979
  function kobeSkillState(opts) {
7680
- const path6 = kobeSkillPaths(opts).find((p) => existsSync4(p));
7980
+ const path6 = kobeSkillPaths(opts).find((p) => existsSync5(p));
7681
7981
  if (!path6) {
7682
7982
  return { installed: false, installedVersion: null, currentVersion: KOBE_SKILL_VERSION, stale: false };
7683
7983
  }
@@ -7729,9 +8029,9 @@ __export(exports_maintenance, {
7729
8029
  runReloadSubcommand: () => runReloadSubcommand,
7730
8030
  runDoctorSubcommand: () => runDoctorSubcommand
7731
8031
  });
7732
- import { existsSync as existsSync5, readFileSync as readFileSync7, statSync } from "fs";
8032
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync } from "fs";
7733
8033
  import { unlink as unlink6 } from "fs/promises";
7734
- import { join as join8 } from "path";
8034
+ import { join as join9 } from "path";
7735
8035
  import { createInterface } from "readline";
7736
8036
  function isProcessAlive2(pid) {
7737
8037
  try {
@@ -7828,7 +8128,7 @@ async function runDoctorSubcommand(argv = []) {
7828
8128
  const socketPath = defaultDaemonSocketPath();
7829
8129
  const pidPath = defaultDaemonPidPath();
7830
8130
  const logPath = defaultDaemonLogPath();
7831
- const tasksPath = join8(kobeStateDir(), "tasks.json");
8131
+ const tasksPath = join9(kobeStateDir(), "tasks.json");
7832
8132
  const statePath2 = kvStatePath();
7833
8133
  const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
7834
8134
  const status = await probeDaemonStatus(socketPath);
@@ -7838,6 +8138,13 @@ async function runDoctorSubcommand(argv = []) {
7838
8138
  const tasks = typeof status.taskCount === "number" ? status.taskCount : "?";
7839
8139
  const clients = typeof status.attachedClients === "number" ? status.attachedClients : "?";
7840
8140
  out.push(`daemon: \u2713 running (pid ${pid}, up ${up}, ${tasks} task(s), ${clients} client(s))`);
8141
+ const daemonVersion = typeof status.kobeVersion === "string" ? status.kobeVersion : undefined;
8142
+ if (daemonVersion && daemonVersion !== CURRENT_VERSION) {
8143
+ out.push(` \u26A0 stale build: daemon is v${daemonVersion}, you launched v${CURRENT_VERSION}`);
8144
+ out.push(" \u2192 run `kobe daemon restart`, then `kobe reload` in any open kobe sessions");
8145
+ } else if (daemonVersion) {
8146
+ out.push(` build: v${daemonVersion}`);
8147
+ }
7841
8148
  } else {
7842
8149
  const pid = await readPidFile(pidPath);
7843
8150
  if (pid && isProcessAlive2(pid)) {
@@ -7849,7 +8156,7 @@ async function runDoctorSubcommand(argv = []) {
7849
8156
  } else {
7850
8157
  out.push("daemon: \u2717 not running (no pidfile)");
7851
8158
  }
7852
- if (existsSync5(socketPath))
8159
+ if (existsSync6(socketPath))
7853
8160
  out.push(` orphan socket file present: ${socketPath}`);
7854
8161
  const tail = tailFile(logPath, 8);
7855
8162
  if (tail) {
@@ -7940,7 +8247,7 @@ async function runResetSubcommand(argv) {
7940
8247
  const yes = argv.includes("--yes") || argv.includes("-y");
7941
8248
  const socketPath = defaultDaemonSocketPath();
7942
8249
  const pidPath = defaultDaemonPidPath();
7943
- const tasksPath = join8(kobeStateDir(), "tasks.json");
8250
+ const tasksPath = join9(kobeStateDir(), "tasks.json");
7944
8251
  const statePath2 = kvStatePath();
7945
8252
  console.log("kobe reset will:");
7946
8253
  console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
@@ -8041,6 +8348,7 @@ var init_maintenance = __esm(() => {
8041
8348
  init_env();
8042
8349
  init_skill_install();
8043
8350
  init_client2();
8351
+ init_version();
8044
8352
  });
8045
8353
 
8046
8354
  // src/cli/skill-cmd.ts
@@ -8143,6 +8451,189 @@ var init_skill_cmd = __esm(() => {
8143
8451
  SKILL_VERBS = ["install", "status", "command"];
8144
8452
  });
8145
8453
 
8454
+ // src/cli/hook-cmd.ts
8455
+ var exports_hook_cmd = {};
8456
+ __export(exports_hook_cmd, {
8457
+ runHookSubcommand: () => runHookSubcommand
8458
+ });
8459
+ import { homedir as homedir10 } from "os";
8460
+ import { dirname as dirname7, join as join10, resolve as resolve6 } from "path";
8461
+ async function readStdinPayload() {
8462
+ try {
8463
+ const text = await Promise.race([
8464
+ Bun.stdin.text(),
8465
+ new Promise((resolve7) => setTimeout(() => resolve7(""), 500))
8466
+ ]);
8467
+ if (!text.trim())
8468
+ return {};
8469
+ const parsed = JSON.parse(text);
8470
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
8471
+ } catch {
8472
+ return {};
8473
+ }
8474
+ }
8475
+ function failureFromErrorType(errorType) {
8476
+ if (typeof errorType !== "string")
8477
+ return "other";
8478
+ if (errorType === "rate_limit" || errorType === "overloaded")
8479
+ return "rate_limit";
8480
+ if (errorType === "billing_error")
8481
+ return "billing";
8482
+ return "other";
8483
+ }
8484
+ function flagValue(argv, name) {
8485
+ for (let i = 0;i < argv.length; i++) {
8486
+ if (argv[i] === name)
8487
+ return argv[i + 1];
8488
+ if (argv[i].startsWith(`${name}=`))
8489
+ return argv[i].slice(name.length + 1);
8490
+ }
8491
+ return;
8492
+ }
8493
+ async function runHookSubcommand(argv) {
8494
+ const [verb, ...rest] = argv;
8495
+ if (verb === "setup") {
8496
+ await runHookSetup(rest);
8497
+ return;
8498
+ }
8499
+ try {
8500
+ if (verb === "worktree-created") {
8501
+ await reportWorktreeCreated();
8502
+ return;
8503
+ }
8504
+ if (!verb || !isEngineActivityKind(verb))
8505
+ return;
8506
+ const taskId = flagValue(rest, "--task-id");
8507
+ if (!taskId)
8508
+ return;
8509
+ const payload = await readStdinPayload();
8510
+ let detail;
8511
+ if (verb === "turn-failed") {
8512
+ detail = { failure: failureFromErrorType(payload.error_type) };
8513
+ } else if (verb === "awaiting-input") {
8514
+ detail = { waiting: "permission" };
8515
+ }
8516
+ const client = await connectIfRunning();
8517
+ if (!client)
8518
+ return;
8519
+ try {
8520
+ await client.request("engine.reportEvent", { taskId, kind: verb, ...detail ? { detail } : {} });
8521
+ } finally {
8522
+ client.close();
8523
+ }
8524
+ } catch {}
8525
+ }
8526
+ async function reportWorktreeCreated() {
8527
+ const payload = await readStdinPayload();
8528
+ const worktreePath = typeof payload.worktree_path === "string" ? payload.worktree_path : undefined;
8529
+ if (!worktreePath)
8530
+ return;
8531
+ const repo = await deriveRepoRoot(worktreePath);
8532
+ if (!repo)
8533
+ return;
8534
+ const client = await connectIfRunning();
8535
+ if (!client)
8536
+ return;
8537
+ try {
8538
+ await client.request("worktree.adopt", { repo, worktreePath, ifExists: "return" });
8539
+ } finally {
8540
+ client.close();
8541
+ }
8542
+ }
8543
+ async function deriveRepoRoot(worktreePath) {
8544
+ try {
8545
+ const proc = Bun.spawn(["git", "-C", worktreePath, "rev-parse", "--path-format=absolute", "--git-common-dir"], {
8546
+ stdout: "pipe",
8547
+ stderr: "ignore"
8548
+ });
8549
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
8550
+ if (code !== 0)
8551
+ return;
8552
+ const commonDir = out.trim();
8553
+ return commonDir ? dirname7(commonDir) : undefined;
8554
+ } catch {
8555
+ return;
8556
+ }
8557
+ }
8558
+ function syncSettingsPath(scope) {
8559
+ if (scope.kind === "repo")
8560
+ return join10(resolve6(scope.path), ".claude", "settings.json");
8561
+ return join10(homedir10(), ".claude", "settings.json");
8562
+ }
8563
+ function worktreeSyncAdapters() {
8564
+ return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsWorktreeSync());
8565
+ }
8566
+ function persistedSyncPath(stored) {
8567
+ if (!stored || stored === "off")
8568
+ return;
8569
+ if (stored === "global")
8570
+ return syncSettingsPath({ kind: "global" });
8571
+ if (stored.startsWith("repo:"))
8572
+ return syncSettingsPath({ kind: "repo", path: stored.slice(5) });
8573
+ return stored;
8574
+ }
8575
+ async function runHookSetup(argv) {
8576
+ if (argv.includes("--help") || argv.includes("-h")) {
8577
+ process.stdout.write([
8578
+ "Usage: kobe hook setup [--global | --repo <path> | --off]",
8579
+ "",
8580
+ "Install (or remove with --off) the hook that syncs an external",
8581
+ "`claude --worktree` into kobe as a task. Default: --global (~/.claude).",
8582
+ ""
8583
+ ].join(`
8584
+ `));
8585
+ return;
8586
+ }
8587
+ const off = argv.includes("--off");
8588
+ const repoIdx = argv.indexOf("--repo");
8589
+ const repoPath = repoIdx !== -1 ? argv[repoIdx + 1] : undefined;
8590
+ if (repoIdx !== -1 && !repoPath) {
8591
+ process.stderr.write(`kobe hook setup: --repo requires a path
8592
+ `);
8593
+ process.exit(2);
8594
+ }
8595
+ const adapters = worktreeSyncAdapters();
8596
+ if (adapters.length === 0) {
8597
+ process.stdout.write(`kobe hook setup: no engine supports external worktree sync \u2014 nothing to do
8598
+ `);
8599
+ return;
8600
+ }
8601
+ const prevPath = persistedSyncPath(getPersistedString(SYNC_SETTING_KEY));
8602
+ if (off) {
8603
+ if (prevPath) {
8604
+ for (const a of adapters)
8605
+ await a.removeWorktreeSyncHook(prevPath);
8606
+ }
8607
+ setPersistedString(SYNC_SETTING_KEY, "off");
8608
+ process.stdout.write(`kobe hook setup: external worktree sync disabled${prevPath ? ` (removed from ${prevPath})` : ""}
8609
+ `);
8610
+ return;
8611
+ }
8612
+ const scope = repoPath ? { kind: "repo", path: repoPath } : { kind: "global" };
8613
+ const path6 = syncSettingsPath(scope);
8614
+ if (prevPath && prevPath !== path6) {
8615
+ for (const a of adapters)
8616
+ await a.removeWorktreeSyncHook(prevPath);
8617
+ }
8618
+ for (const a of adapters)
8619
+ await a.installWorktreeSyncHook(path6);
8620
+ setPersistedString(SYNC_SETTING_KEY, path6);
8621
+ process.stdout.write([
8622
+ `kobe hook setup: external worktree sync enabled (${scope.kind}) \u2014 wrote ${path6}`,
8623
+ "New `claude --worktree` worktrees will appear as kobe tasks.",
8624
+ ""
8625
+ ].join(`
8626
+ `));
8627
+ }
8628
+ var SYNC_SETTING_KEY = "externalWorktreeSync";
8629
+ var init_hook_cmd = __esm(() => {
8630
+ init_daemon_process();
8631
+ init_hook_adapter2();
8632
+ init_hook_events();
8633
+ init_repos();
8634
+ init_vendor();
8635
+ });
8636
+
8146
8637
  // ../../node_modules/.bun/entities@7.0.1/node_modules/entities/dist/esm/decode-codepoint.js
8147
8638
  function replaceCodePoint(codePoint) {
8148
8639
  var _a2;
@@ -9428,6 +9919,10 @@ class RemoteOrchestrator {
9428
9919
  setActiveTaskSig;
9429
9920
  updateAcc;
9430
9921
  setUpdateSig;
9922
+ daemonVersionAcc;
9923
+ setDaemonVersionSig;
9924
+ engineStateAcc;
9925
+ setEngineStateSig;
9431
9926
  connectionStateAcc;
9432
9927
  setConnectionState;
9433
9928
  ensureReachable;
@@ -9438,6 +9933,8 @@ class RemoteOrchestrator {
9438
9933
  const [tasks, setTasks] = createSignal([]);
9439
9934
  const [activeTask, setActiveTask] = createSignal(null);
9440
9935
  const [update, setUpdate] = createSignal(null);
9936
+ const [daemonVersion, setDaemonVersion] = createSignal(null);
9937
+ const [engineState, setEngineState] = createSignal(new Map);
9441
9938
  const [connectionState, setConnectionState] = createSignal("online");
9442
9939
  this.tasksAcc = tasks;
9443
9940
  this.setTasks = (next) => setTasks(() => next);
@@ -9445,6 +9942,10 @@ class RemoteOrchestrator {
9445
9942
  this.setActiveTaskSig = (next) => setActiveTask(() => next);
9446
9943
  this.updateAcc = update;
9447
9944
  this.setUpdateSig = (next) => setUpdate(() => next);
9945
+ this.daemonVersionAcc = daemonVersion;
9946
+ this.setDaemonVersionSig = (next) => setDaemonVersion(() => next);
9947
+ this.engineStateAcc = engineState;
9948
+ this.setEngineStateSig = (next) => setEngineState(() => next);
9448
9949
  this.connectionStateAcc = connectionState;
9449
9950
  this.setConnectionState = (next) => setConnectionState(() => next);
9450
9951
  this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
@@ -9497,6 +9998,7 @@ class RemoteOrchestrator {
9497
9998
  })) {
9498
9999
  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.`);
9499
10000
  }
10001
+ this.setDaemonVersionSig(typeof hello.kobeVersion === "string" ? hello.kobeVersion : null);
9500
10002
  if (hello.tasks)
9501
10003
  this.setTasks(hello.tasks.map(deserializeTask));
9502
10004
  await this.client.subscribe({ role: this.role });
@@ -9523,6 +10025,15 @@ class RemoteOrchestrator {
9523
10025
  updateSignal() {
9524
10026
  return this.updateAcc;
9525
10027
  }
10028
+ daemonVersionSignal() {
10029
+ return this.daemonVersionAcc;
10030
+ }
10031
+ daemonStaleSignal() {
10032
+ return () => isDaemonVersionStale(this.daemonVersionAcc() ?? undefined, CURRENT_VERSION);
10033
+ }
10034
+ engineStateSignal() {
10035
+ return this.engineStateAcc;
10036
+ }
9526
10037
  listTasks() {
9527
10038
  return this.tasksAcc();
9528
10039
  }
@@ -9613,6 +10124,18 @@ class RemoteOrchestrator {
9613
10124
  this.setUpdateSig(info ?? null);
9614
10125
  return;
9615
10126
  }
10127
+ if (name === "engine-state") {
10128
+ const p = payload;
10129
+ if (typeof p?.taskId !== "string" || typeof p.state !== "string")
10130
+ return;
10131
+ const next = new Map(this.engineStateAcc());
10132
+ if (p.state === "idle")
10133
+ next.delete(p.taskId);
10134
+ else
10135
+ next.set(p.taskId, { state: p.state, detail: p.detail, at: typeof p.at === "number" ? p.at : 0 });
10136
+ this.setEngineStateSig(next);
10137
+ return;
10138
+ }
9616
10139
  }
9617
10140
  }
9618
10141
  function deserializeTask(s) {
@@ -9635,6 +10158,7 @@ function deserializeTask(s) {
9635
10158
  var init_remote_orchestrator = __esm(() => {
9636
10159
  init_dev();
9637
10160
  init_protocol();
10161
+ init_version();
9638
10162
  init_client_log();
9639
10163
  init_daemon_process();
9640
10164
  });
@@ -11298,7 +11822,7 @@ function addTheme2(name, theme) {
11298
11822
  }
11299
11823
  function resolveTheme(theme, mode = "dark") {
11300
11824
  const defs = theme.defs ?? {};
11301
- function resolve6(c, chain = []) {
11825
+ function resolve7(c, chain = []) {
11302
11826
  if (typeof c === "string") {
11303
11827
  if (c === "transparent" || c === "none")
11304
11828
  return RGBA.fromInts(0, 0, 0, 0);
@@ -11310,13 +11834,13 @@ function resolveTheme(theme, mode = "dark") {
11310
11834
  const next = defs[c] ?? theme.theme[c];
11311
11835
  if (next === undefined)
11312
11836
  return RGBA.fromInts(0, 0, 0);
11313
- return resolve6(next, [...chain, c]);
11837
+ return resolve7(next, [...chain, c]);
11314
11838
  }
11315
- return resolve6(c[mode], chain);
11839
+ return resolve7(c[mode], chain);
11316
11840
  }
11317
11841
  const out = {};
11318
11842
  for (const [k, v] of Object.entries(theme.theme)) {
11319
- out[k] = resolve6(v);
11843
+ out[k] = resolve7(v);
11320
11844
  }
11321
11845
  const text = out.text ?? RGBA.fromHex("#ffffff");
11322
11846
  const background = out.background ?? RGBA.fromHex("#000000");
@@ -12095,7 +12619,7 @@ function findAvailableFolderName(parentDir, base) {
12095
12619
  return trimmed;
12096
12620
  }
12097
12621
  function cloneRepo(url, target, onProgress) {
12098
- return new Promise((resolve6) => {
12622
+ return new Promise((resolve7) => {
12099
12623
  let stderrBuf = "";
12100
12624
  try {
12101
12625
  const child = spawn2("git", ["clone", "--progress", url, target], {
@@ -12112,18 +12636,18 @@ function cloneRepo(url, target, onProgress) {
12112
12636
  }
12113
12637
  });
12114
12638
  child.on("error", (err) => {
12115
- resolve6({ ok: false, error: err.message });
12639
+ resolve7({ ok: false, error: err.message });
12116
12640
  });
12117
12641
  child.on("close", (code) => {
12118
12642
  if (code === 0) {
12119
- resolve6({ ok: true, path: target });
12643
+ resolve7({ ok: true, path: target });
12120
12644
  return;
12121
12645
  }
12122
12646
  const tail = stderrBuf.split(/[\r\n]+/).filter((s) => s.trim().length > 0).pop() ?? `git clone exited with ${code}`;
12123
- resolve6({ ok: false, error: tail });
12647
+ resolve7({ ok: false, error: tail });
12124
12648
  });
12125
12649
  } catch (err) {
12126
- resolve6({ ok: false, error: err instanceof Error ? err.message : String(err) });
12650
+ resolve7({ ok: false, error: err instanceof Error ? err.message : String(err) });
12127
12651
  }
12128
12652
  });
12129
12653
  }
@@ -13186,7 +13710,7 @@ var init_dialog2 = __esm(() => {
13186
13710
 
13187
13711
  // src/tui/component/new-task-dialog/index.tsx
13188
13712
  function show(dialog, defaultRepo, savedRepos, options) {
13189
- return new Promise((resolve6) => {
13713
+ return new Promise((resolve7) => {
13190
13714
  dialog.replace(() => createComponent2(NewTaskDialogView, {
13191
13715
  defaultRepo,
13192
13716
  savedRepos,
@@ -13199,9 +13723,9 @@ function show(dialog, defaultRepo, savedRepos, options) {
13199
13723
  get discoverAdoptable() {
13200
13724
  return options?.discoverAdoptable;
13201
13725
  },
13202
- onSubmit: (v) => resolve6(v),
13203
- onCancel: () => resolve6(undefined)
13204
- }), () => resolve6(undefined));
13726
+ onSubmit: (v) => resolve7(v),
13727
+ onCancel: () => resolve7(undefined)
13728
+ }), () => resolve7(undefined));
13205
13729
  dialog.setSize("medium");
13206
13730
  });
13207
13731
  }
@@ -13293,15 +13817,15 @@ var init_dialog3 = __esm(() => {
13293
13817
 
13294
13818
  // src/tui/component/rename-task-dialog/index.tsx
13295
13819
  function show2(dialog, currentTitle, opts = {}) {
13296
- return new Promise((resolve6) => {
13820
+ return new Promise((resolve7) => {
13297
13821
  dialog.replace(() => createComponent2(RenameTaskDialogView, {
13298
13822
  currentTitle,
13299
13823
  get dialogTitle() {
13300
13824
  return opts.dialogTitle;
13301
13825
  },
13302
- onSubmit: (v) => resolve6(v),
13303
- onCancel: () => resolve6(undefined)
13304
- }), () => resolve6(undefined));
13826
+ onSubmit: (v) => resolve7(v),
13827
+ onCancel: () => resolve7(undefined)
13828
+ }), () => resolve7(undefined));
13305
13829
  });
13306
13830
  }
13307
13831
  var RenameTaskDialog;
@@ -13315,8 +13839,8 @@ var init_rename_task_dialog = __esm(() => {
13315
13839
 
13316
13840
  // src/engine/claude-code-local/binary.ts
13317
13841
  import { spawnSync as spawnSync5 } from "child_process";
13318
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
13319
- import { homedir as homedir11 } from "os";
13842
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
13843
+ import { homedir as homedir12 } from "os";
13320
13844
  import path7 from "path";
13321
13845
  async function findClaudeBinary(deps = defaultDeps4) {
13322
13846
  const checked = [];
@@ -13389,7 +13913,7 @@ var init_binary = __esm(() => {
13389
13913
  return process.env[name];
13390
13914
  },
13391
13915
  home() {
13392
- return homedir11();
13916
+ return homedir12();
13393
13917
  },
13394
13918
  which(name) {
13395
13919
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -13402,7 +13926,7 @@ var init_binary = __esm(() => {
13402
13926
  return;
13403
13927
  if (first.startsWith("claude:") && first.includes("aliased to")) {
13404
13928
  const aliasTarget = first.split("aliased to")[1]?.trim();
13405
- return aliasTarget && existsSync7(aliasTarget) ? aliasTarget : undefined;
13929
+ return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
13406
13930
  }
13407
13931
  return first;
13408
13932
  },
@@ -13419,8 +13943,8 @@ var init_binary = __esm(() => {
13419
13943
 
13420
13944
  // src/engine/codex-local/binary.ts
13421
13945
  import { spawnSync as spawnSync6 } from "child_process";
13422
- import { existsSync as existsSync8, statSync as statSync4 } from "fs";
13423
- import { homedir as homedir12 } from "os";
13946
+ import { existsSync as existsSync9, statSync as statSync4 } from "fs";
13947
+ import { homedir as homedir13 } from "os";
13424
13948
  import path8 from "path";
13425
13949
  async function findCodexBinary(deps = defaultDeps5) {
13426
13950
  const checked = [];
@@ -13477,7 +14001,7 @@ var init_binary2 = __esm(() => {
13477
14001
  return process.env[name];
13478
14002
  },
13479
14003
  home() {
13480
- return homedir12();
14004
+ return homedir13();
13481
14005
  },
13482
14006
  which(name) {
13483
14007
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -13490,7 +14014,7 @@ var init_binary2 = __esm(() => {
13490
14014
  return;
13491
14015
  if (first.startsWith("codex:") && first.includes("aliased to")) {
13492
14016
  const aliasTarget = first.split("aliased to")[1]?.trim();
13493
- return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
14017
+ return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
13494
14018
  }
13495
14019
  return first;
13496
14020
  },
@@ -13507,8 +14031,8 @@ var init_binary2 = __esm(() => {
13507
14031
 
13508
14032
  // src/engine/copilot-local/binary.ts
13509
14033
  import { spawnSync as spawnSync7 } from "child_process";
13510
- import { existsSync as existsSync9, statSync as statSync5 } from "fs";
13511
- import { homedir as homedir13 } from "os";
14034
+ import { existsSync as existsSync10, statSync as statSync5 } from "fs";
14035
+ import { homedir as homedir14 } from "os";
13512
14036
  import path9 from "path";
13513
14037
  async function findCopilotBinary(deps = defaultDeps6) {
13514
14038
  const checked = [];
@@ -13589,7 +14113,7 @@ var init_binary3 = __esm(() => {
13589
14113
  return process.env[name];
13590
14114
  },
13591
14115
  home() {
13592
- return homedir13();
14116
+ return homedir14();
13593
14117
  },
13594
14118
  which(name) {
13595
14119
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -13602,7 +14126,7 @@ var init_binary3 = __esm(() => {
13602
14126
  return;
13603
14127
  if (first.startsWith("copilot:") && first.includes("aliased to")) {
13604
14128
  const aliasTarget = first.split("aliased to")[1]?.trim();
13605
- return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
14129
+ return aliasTarget && existsSync10(aliasTarget) ? aliasTarget : undefined;
13606
14130
  }
13607
14131
  return first;
13608
14132
  },
@@ -13614,7 +14138,7 @@ var init_binary3 = __esm(() => {
13614
14138
 
13615
14139
  // src/engine/account-detect.ts
13616
14140
  import { readFileSync as readFileSync8, statSync as statSync6 } from "fs";
13617
- import { homedir as homedir14 } from "os";
14141
+ import { homedir as homedir15 } from "os";
13618
14142
  import path10 from "path";
13619
14143
  function claudeGlobalConfigPath(env, home) {
13620
14144
  const override = env("CLAUDE_CONFIG_DIR")?.trim();
@@ -13832,7 +14356,7 @@ var init_account_detect = __esm(() => {
13832
14356
  return process.env[name];
13833
14357
  },
13834
14358
  home() {
13835
- return homedir14();
14359
+ return homedir15();
13836
14360
  },
13837
14361
  findClaudeBinary() {
13838
14362
  return findClaudeBinary();
@@ -13971,18 +14495,18 @@ var init_dialog_confirm = __esm(() => {
13971
14495
  init_keymap();
13972
14496
  init_dialog();
13973
14497
  DialogConfirm.show = (dialog, title, message, label, confirmLabel, options) => {
13974
- return new Promise((resolve6) => {
14498
+ return new Promise((resolve7) => {
13975
14499
  dialog.replace(() => createComponent2(DialogConfirm, {
13976
14500
  title,
13977
14501
  message,
13978
- onConfirm: () => resolve6(true),
13979
- onCancel: () => resolve6(false),
14502
+ onConfirm: () => resolve7(true),
14503
+ onCancel: () => resolve7(false),
13980
14504
  label,
13981
14505
  confirmLabel,
13982
14506
  get initialActive() {
13983
14507
  return options?.initialActive;
13984
14508
  }
13985
- }), () => resolve6(undefined));
14509
+ }), () => resolve7(undefined));
13986
14510
  dialog.setSize("small");
13987
14511
  });
13988
14512
  };
@@ -13990,7 +14514,7 @@ var init_dialog_confirm = __esm(() => {
13990
14514
 
13991
14515
  // src/tui/component/settings-dialog/actions.ts
13992
14516
  import { unlinkSync as unlinkSync2 } from "fs";
13993
- import { join as join10 } from "path";
14517
+ import { join as join12 } from "path";
13994
14518
  function hasRestartableDaemon(orchestrator) {
13995
14519
  return orchestrator instanceof RemoteOrchestrator;
13996
14520
  }
@@ -14007,7 +14531,7 @@ async function confirmResetState(dialog, kv, renderer) {
14007
14531
  return;
14008
14532
  kv.clear();
14009
14533
  try {
14010
- unlinkSync2(join10(homeDir(), ".kobe", "tasks.json"));
14534
+ unlinkSync2(join12(homeDir(), ".kobe", "tasks.json"));
14011
14535
  } catch (err) {
14012
14536
  if (err.code !== "ENOENT") {
14013
14537
  console.error("kobe: failed to delete tasks.json during reset:", err);
@@ -15298,23 +15822,89 @@ var init_settings_dialog = __esm(() => {
15298
15822
  init_sections();
15299
15823
  SettingsDialog.show = (dialog, kv, orchestrator) => {
15300
15824
  let visualPrefsChanged = false;
15301
- return new Promise((resolve6) => {
15825
+ return new Promise((resolve7) => {
15302
15826
  dialog.replace(() => createComponent2(SettingsDialog, {
15303
15827
  kv,
15304
15828
  orchestrator,
15305
15829
  onVisualPrefsChange: () => {
15306
15830
  visualPrefsChanged = true;
15307
15831
  },
15308
- onClose: () => resolve6({
15832
+ onClose: () => resolve7({
15309
15833
  visualPrefsChanged
15310
15834
  })
15311
- }), () => resolve6({
15835
+ }), () => resolve7({
15312
15836
  visualPrefsChanged
15313
15837
  }));
15314
15838
  });
15315
15839
  };
15316
15840
  });
15317
15841
 
15842
+ // src/tui/component/version-skew-banner.tsx
15843
+ import { TextAttributes as TextAttributes7 } from "@opentui/core";
15844
+ function versionSkewHint(daemonVersion, clientVersion) {
15845
+ const daemon = daemonVersion ? `v${daemonVersion}` : "an older build";
15846
+ return `daemon is ${daemon} \u2014 you launched v${clientVersion}. Run \`kobe daemon restart\` then \`kobe reload\``;
15847
+ }
15848
+ function VersionSkewBanner(props) {
15849
+ const {
15850
+ theme
15851
+ } = useTheme();
15852
+ const ruleWidth = () => Math.max(4, props.width() - 2);
15853
+ return createComponent2(Show, {
15854
+ get when() {
15855
+ return props.stale();
15856
+ },
15857
+ get children() {
15858
+ var _el$ = createElement("box"), _el$2 = createElement("text"), _el$3 = createElement("box"), _el$4 = createElement("text"), _el$6 = createElement("box"), _el$7 = createElement("text");
15859
+ insertNode(_el$, _el$2);
15860
+ insertNode(_el$, _el$3);
15861
+ insertNode(_el$, _el$6);
15862
+ setProp(_el$, "flexDirection", "column");
15863
+ setProp(_el$, "flexShrink", 0);
15864
+ setProp(_el$, "paddingLeft", 1);
15865
+ setProp(_el$, "paddingRight", 1);
15866
+ setProp(_el$, "paddingBottom", 1);
15867
+ setProp(_el$2, "wrapMode", "none");
15868
+ insert(_el$2, () => "\u2594".repeat(ruleWidth()));
15869
+ insertNode(_el$3, _el$4);
15870
+ setProp(_el$3, "flexDirection", "row");
15871
+ setProp(_el$3, "gap", 1);
15872
+ insertNode(_el$4, createTextNode(`\u26A0 DAEMON OUT OF DATE`));
15873
+ setProp(_el$4, "wrapMode", "none");
15874
+ insertNode(_el$6, _el$7);
15875
+ setProp(_el$6, "flexDirection", "row");
15876
+ setProp(_el$6, "gap", 1);
15877
+ setProp(_el$7, "wrapMode", "word");
15878
+ insert(_el$7, () => versionSkewHint(props.daemonVersion(), props.clientVersion));
15879
+ effect((_p$) => {
15880
+ var { warning: _v$, warning: _v$2 } = theme, _v$3 = TextAttributes7.BOLD, _v$4 = theme.text;
15881
+ _v$ !== _p$.e && (_p$.e = setProp(_el$2, "fg", _v$, _p$.e));
15882
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "fg", _v$2, _p$.t));
15883
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$4, "attributes", _v$3, _p$.a));
15884
+ _v$4 !== _p$.o && (_p$.o = setProp(_el$7, "fg", _v$4, _p$.o));
15885
+ return _p$;
15886
+ }, {
15887
+ e: undefined,
15888
+ t: undefined,
15889
+ a: undefined,
15890
+ o: undefined
15891
+ });
15892
+ return _el$;
15893
+ }
15894
+ });
15895
+ }
15896
+ var init_version_skew_banner = __esm(() => {
15897
+ init_solid();
15898
+ init_solid();
15899
+ init_solid();
15900
+ init_solid();
15901
+ init_solid();
15902
+ init_solid();
15903
+ init_solid();
15904
+ init_dev();
15905
+ init_theme2();
15906
+ });
15907
+
15318
15908
  // src/tui/context/focus.tsx
15319
15909
  function FocusProvider(props) {
15320
15910
  const [focused, setFocusedSignal] = createSignal(props.initial ?? "sidebar");
@@ -15378,7 +15968,7 @@ var init_focus = __esm(() => {
15378
15968
 
15379
15969
  // src/tui/context/kv.tsx
15380
15970
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
15381
- import { dirname as dirname6 } from "path";
15971
+ import { dirname as dirname8 } from "path";
15382
15972
  function loadInitial() {
15383
15973
  const statePath2 = kvStatePath();
15384
15974
  try {
@@ -15406,7 +15996,7 @@ var init_kv = __esm(() => {
15406
15996
  function writeNow(label) {
15407
15997
  const statePath2 = kvStatePath();
15408
15998
  try {
15409
- mkdirSync4(dirname6(statePath2), {
15999
+ mkdirSync4(dirname8(statePath2), {
15410
16000
  recursive: true
15411
16001
  });
15412
16002
  const tmp = `${statePath2}.tmp`;
@@ -15461,7 +16051,7 @@ var init_kv = __esm(() => {
15461
16051
  }
15462
16052
  const statePath2 = kvStatePath();
15463
16053
  try {
15464
- mkdirSync4(dirname6(statePath2), {
16054
+ mkdirSync4(dirname8(statePath2), {
15465
16055
  recursive: true
15466
16056
  });
15467
16057
  const tmp = `${statePath2}.tmp`;
@@ -15497,8 +16087,8 @@ var init_persisted_ui_prefs = __esm(() => {
15497
16087
 
15498
16088
  // src/tui/lib/worktree-opener.ts
15499
16089
  import { spawn as spawn3 } from "child_process";
15500
- import { existsSync as existsSync10 } from "fs";
15501
- import { basename as basename4, delimiter, isAbsolute, join as join11 } from "path";
16090
+ import { existsSync as existsSync11 } from "fs";
16091
+ import { basename as basename4, delimiter, isAbsolute, join as join13 } from "path";
15502
16092
  function executableOnPath(command, env, exists) {
15503
16093
  if (isAbsolute(command))
15504
16094
  return exists(command);
@@ -15506,7 +16096,7 @@ function executableOnPath(command, env, exists) {
15506
16096
  for (const dir of pathEnv.split(delimiter)) {
15507
16097
  if (!dir)
15508
16098
  continue;
15509
- if (exists(join11(dir, command)))
16099
+ if (exists(join13(dir, command)))
15510
16100
  return true;
15511
16101
  }
15512
16102
  return false;
@@ -15526,7 +16116,7 @@ function labelForOverride(command) {
15526
16116
  function detectWorktreeOpener(deps = {}) {
15527
16117
  const env = deps.env ?? process.env;
15528
16118
  const platform = deps.platform ?? process.platform;
15529
- const exists = deps.exists ?? existsSync10;
16119
+ const exists = deps.exists ?? existsSync11;
15530
16120
  const override = env.KOBE_OPEN_EDITOR?.trim();
15531
16121
  if (override) {
15532
16122
  return { id: "env", label: labelForOverride(override), command: override, args: [] };
@@ -15664,7 +16254,7 @@ function flattenIds(rows) {
15664
16254
  var init_groups = () => {};
15665
16255
 
15666
16256
  // src/tui/context/command-palette.tsx
15667
- import { TextAttributes as TextAttributes7 } from "@opentui/core";
16257
+ import { TextAttributes as TextAttributes8 } from "@opentui/core";
15668
16258
  function CommandPaletteProvider(props) {
15669
16259
  const dialog = useDialog();
15670
16260
  const [commands, setCommands] = createSignal([]);
@@ -15813,7 +16403,7 @@ function CommandPaletteDialog(props) {
15813
16403
  }
15814
16404
  }), null);
15815
16405
  effect((_p$) => {
15816
- var _v$ = TextAttributes7.BOLD, _v$2 = theme.text, _v$3 = theme.textMuted;
16406
+ var _v$ = TextAttributes8.BOLD, _v$2 = theme.text, _v$3 = theme.textMuted;
15817
16407
  _v$ !== _p$.e && (_p$.e = setProp(_el$3, "attributes", _v$, _p$.e));
15818
16408
  _v$2 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$2, _p$.t));
15819
16409
  _v$3 !== _p$.a && (_p$.a = setProp(_el$5, "fg", _v$3, _p$.a));
@@ -16497,7 +17087,7 @@ var init_keys = __esm(() => {
16497
17087
  });
16498
17088
 
16499
17089
  // src/tui/panes/sidebar/Sidebar.tsx
16500
- import { TextAttributes as TextAttributes8 } from "@opentui/core";
17090
+ import { TextAttributes as TextAttributes9 } from "@opentui/core";
16501
17091
  function truncateBranchLabel(branch, max = BRANCH_LABEL_MAX) {
16502
17092
  if (branch.length <= max)
16503
17093
  return branch;
@@ -16662,7 +17252,7 @@ function Sidebar(props) {
16662
17252
  setProp(_el$3, "wrapMode", "none");
16663
17253
  insert(_el$3, () => "\u2500".repeat(Math.max(2, effectiveWidth() - 9 - p.label.length)));
16664
17254
  effect((_p$) => {
16665
- var _v$ = p.topPad ? 1 : 0, _v$2 = theme.textMuted, _v$3 = TextAttributes8.BOLD, _v$4 = theme.border;
17255
+ var _v$ = p.topPad ? 1 : 0, _v$2 = theme.textMuted, _v$3 = TextAttributes9.BOLD, _v$4 = theme.border;
16666
17256
  _v$ !== _p$.e && (_p$.e = setProp(_el$, "paddingTop", _v$, _p$.e));
16667
17257
  _v$2 !== _p$.t && (_p$.t = setProp(_el$2, "fg", _v$2, _p$.t));
16668
17258
  _v$3 !== _p$.a && (_p$.a = setProp(_el$2, "attributes", _v$3, _p$.a));
@@ -16707,7 +17297,7 @@ function Sidebar(props) {
16707
17297
  setProp(_el$26, "onMouseUp", () => props.onHeaderStatusClick?.());
16708
17298
  insert(_el$26, () => status().label);
16709
17299
  effect((_p$) => {
16710
- var _v$11 = status().emphasize ? theme.warning : theme.textMuted, _v$12 = status().emphasize ? TextAttributes8.BOLD : TextAttributes8.DIM;
17300
+ var _v$11 = status().emphasize ? theme.warning : theme.textMuted, _v$12 = status().emphasize ? TextAttributes9.BOLD : TextAttributes9.DIM;
16711
17301
  _v$11 !== _p$.e && (_p$.e = setProp(_el$26, "fg", _v$11, _p$.e));
16712
17302
  _v$12 !== _p$.t && (_p$.t = setProp(_el$26, "attributes", _v$12, _p$.t));
16713
17303
  return _p$;
@@ -16765,7 +17355,7 @@ function Sidebar(props) {
16765
17355
  }
16766
17356
  }), null);
16767
17357
  effect((_p$) => {
16768
- var { info: _v$5, text: _v$6, info: _v$7 } = theme, _v$8 = TextAttributes8.BLINK;
17358
+ var { info: _v$5, text: _v$6, info: _v$7 } = theme, _v$8 = TextAttributes9.BLINK;
16769
17359
  _v$5 !== _p$.e && (_p$.e = setProp(_el$9, "fg", _v$5, _p$.e));
16770
17360
  _v$6 !== _p$.t && (_p$.t = setProp(_el$1, "fg", _v$6, _p$.t));
16771
17361
  _v$7 !== _p$.a && (_p$.a = setProp(_el$10, "fg", _v$7, _p$.a));
@@ -16794,7 +17384,7 @@ function Sidebar(props) {
16794
17384
  setProp(_el$27, "onMouseUp", () => setView(tab.view));
16795
17385
  insert(_el$27, () => tab.label);
16796
17386
  effect((_p$) => {
16797
- var _v$13 = active() ? theme.primary : theme.textMuted, _v$14 = active() ? TextAttributes8.BOLD : undefined;
17387
+ var _v$13 = active() ? theme.primary : theme.textMuted, _v$14 = active() ? TextAttributes9.BOLD : undefined;
16798
17388
  _v$13 !== _p$.e && (_p$.e = setProp(_el$27, "fg", _v$13, _p$.e));
16799
17389
  _v$14 !== _p$.t && (_p$.t = setProp(_el$27, "attributes", _v$14, _p$.t));
16800
17390
  return _p$;
@@ -16861,7 +17451,34 @@ function Sidebar(props) {
16861
17451
  return readWorktreeChanges(task.worktreePath);
16862
17452
  });
16863
17453
  const titleText = isMain ? repoBasename(task.repo) : task.title;
16864
- const loading = () => isLive() || !isMain && task.status === "in_progress";
17454
+ const activity = () => props.engineState?.().get(task.id)?.state;
17455
+ const loading = () => activity() === "running" || isLive() || !isMain && task.status === "in_progress";
17456
+ const activityChip = () => {
17457
+ switch (activity()) {
17458
+ case "rate_limited":
17459
+ return {
17460
+ text: "limited",
17461
+ tone: "warning"
17462
+ };
17463
+ case "permission_needed":
17464
+ return {
17465
+ text: "approve?",
17466
+ tone: "warning"
17467
+ };
17468
+ case "error":
17469
+ return {
17470
+ text: "error",
17471
+ tone: "error"
17472
+ };
17473
+ case "turn_complete":
17474
+ return {
17475
+ text: "done",
17476
+ tone: "primary"
17477
+ };
17478
+ default:
17479
+ return null;
17480
+ }
17481
+ };
16865
17482
  const subtitleText = createMemo(() => {
16866
17483
  if (task.branch.length > 0)
16867
17484
  return truncateBranchLabel(task.branch, subtitleBudget());
@@ -16958,7 +17575,7 @@ function Sidebar(props) {
16958
17575
  setProp(_el$37, "wrapMode", "none");
16959
17576
  insert(_el$37, () => truncatePathTail(abbrevHome(task.repo), subtitleBudget()));
16960
17577
  effect((_p$) => {
16961
- var _v$15 = theme.textMuted, _v$16 = TextAttributes8.DIM;
17578
+ var _v$15 = theme.textMuted, _v$16 = TextAttributes9.DIM;
16962
17579
  _v$15 !== _p$.e && (_p$.e = setProp(_el$37, "fg", _v$15, _p$.e));
16963
17580
  _v$16 !== _p$.t && (_p$.t = setProp(_el$37, "attributes", _v$16, _p$.t));
16964
17581
  return _p$;
@@ -16970,7 +17587,7 @@ function Sidebar(props) {
16970
17587
  }
16971
17588
  }), null);
16972
17589
  effect((_p$) => {
16973
- var _v$17 = barColor(), _v$18 = theme.primary, _v$19 = TextAttributes8.BOLD, _v$20 = theme.text, _v$21 = TextAttributes8.BOLD;
17590
+ var _v$17 = barColor(), _v$18 = theme.primary, _v$19 = TextAttributes9.BOLD, _v$20 = theme.text, _v$21 = TextAttributes9.BOLD;
16974
17591
  _v$17 !== _p$.e && (_p$.e = setProp(_el$31, "fg", _v$17, _p$.e));
16975
17592
  _v$18 !== _p$.t && (_p$.t = setProp(_el$33, "fg", _v$18, _p$.t));
16976
17593
  _v$19 !== _p$.a && (_p$.a = setProp(_el$33, "attributes", _v$19, _p$.a));
@@ -17024,8 +17641,20 @@ function Sidebar(props) {
17024
17641
  return _el$43;
17025
17642
  }
17026
17643
  }), null);
17644
+ insert(_el$40, createComponent2(Show, {
17645
+ get when() {
17646
+ return memo2(() => !!!loading())() && activityChip();
17647
+ },
17648
+ children: (chip) => (() => {
17649
+ var _el$55 = createElement("text");
17650
+ setProp(_el$55, "wrapMode", "none");
17651
+ insert(_el$55, () => chip().text);
17652
+ effect((_$p) => setProp(_el$55, "fg", chip().tone === "error" ? theme.error : chip().tone === "warning" ? theme.warning : theme.primary, _$p));
17653
+ return _el$55;
17654
+ })()
17655
+ }), null);
17027
17656
  effect((_p$) => {
17028
- var _v$22 = barColor(), _v$23 = badgeColor(), _v$24 = TextAttributes8.BOLD, _v$25 = theme.text, _v$26 = isSelected() || isCursor() ? TextAttributes8.BOLD : undefined;
17657
+ var _v$22 = barColor(), _v$23 = badgeColor(), _v$24 = TextAttributes9.BOLD, _v$25 = theme.text, _v$26 = isSelected() || isCursor() ? TextAttributes9.BOLD : undefined;
17029
17658
  _v$22 !== _p$.e && (_p$.e = setProp(_el$39, "fg", _v$22, _p$.e));
17030
17659
  _v$23 !== _p$.t && (_p$.t = setProp(_el$41, "fg", _v$23, _p$.t));
17031
17660
  _v$24 !== _p$.a && (_p$.a = setProp(_el$41, "attributes", _v$24, _p$.a));
@@ -17096,7 +17725,7 @@ function Sidebar(props) {
17096
17725
  }
17097
17726
  }), null);
17098
17727
  effect((_p$) => {
17099
- var _v$27 = barColor(), _v$28 = theme.textMuted, _v$29 = TextAttributes8.DIM;
17728
+ var _v$27 = barColor(), _v$28 = theme.textMuted, _v$29 = TextAttributes9.DIM;
17100
17729
  _v$27 !== _p$.e && (_p$.e = setProp(_el$46, "fg", _v$27, _p$.e));
17101
17730
  _v$28 !== _p$.t && (_p$.t = setProp(_el$48, "fg", _v$28, _p$.t));
17102
17731
  _v$29 !== _p$.a && (_p$.a = setProp(_el$48, "attributes", _v$29, _p$.a));
@@ -17169,43 +17798,43 @@ function Sidebar(props) {
17169
17798
  const left = createMemo(() => Math.max(0, Math.min(h().x + 2, dims().width - boxW() - 1)));
17170
17799
  const top = createMemo(() => Math.max(0, Math.min(h().y + 1, dims().height - boxH() - 1)));
17171
17800
  return (() => {
17172
- var _el$55 = createElement("box");
17173
- setProp(_el$55, "position", "absolute");
17174
- setProp(_el$55, "zIndex", 2600);
17175
- setProp(_el$55, "flexDirection", "column");
17176
- setProp(_el$55, "border", true);
17177
- setProp(_el$55, "paddingLeft", 1);
17178
- setProp(_el$55, "paddingRight", 1);
17179
- insert(_el$55, createComponent2(For, {
17801
+ var _el$56 = createElement("box");
17802
+ setProp(_el$56, "position", "absolute");
17803
+ setProp(_el$56, "zIndex", 2600);
17804
+ setProp(_el$56, "flexDirection", "column");
17805
+ setProp(_el$56, "border", true);
17806
+ setProp(_el$56, "paddingLeft", 1);
17807
+ setProp(_el$56, "paddingRight", 1);
17808
+ insert(_el$56, createComponent2(For, {
17180
17809
  get each() {
17181
17810
  return lines();
17182
17811
  },
17183
17812
  children: (l) => (() => {
17184
- var _el$56 = createElement("text");
17185
- setProp(_el$56, "wrapMode", "none");
17186
- insert(_el$56, (() => {
17813
+ var _el$57 = createElement("text");
17814
+ setProp(_el$57, "wrapMode", "none");
17815
+ insert(_el$57, (() => {
17187
17816
  var _c$4 = memo2(() => !!l.dim);
17188
17817
  return () => _c$4() ? truncatePathTail(l.text, innerW()) : truncateTitle(l.text, innerW());
17189
17818
  })());
17190
17819
  effect((_p$) => {
17191
- var _v$35 = l.dim ? theme.textMuted : theme.text, _v$36 = l.bold ? TextAttributes8.BOLD : l.dim ? TextAttributes8.DIM : undefined;
17192
- _v$35 !== _p$.e && (_p$.e = setProp(_el$56, "fg", _v$35, _p$.e));
17193
- _v$36 !== _p$.t && (_p$.t = setProp(_el$56, "attributes", _v$36, _p$.t));
17820
+ var _v$35 = l.dim ? theme.textMuted : theme.text, _v$36 = l.bold ? TextAttributes9.BOLD : l.dim ? TextAttributes9.DIM : undefined;
17821
+ _v$35 !== _p$.e && (_p$.e = setProp(_el$57, "fg", _v$35, _p$.e));
17822
+ _v$36 !== _p$.t && (_p$.t = setProp(_el$57, "attributes", _v$36, _p$.t));
17194
17823
  return _p$;
17195
17824
  }, {
17196
17825
  e: undefined,
17197
17826
  t: undefined
17198
17827
  });
17199
- return _el$56;
17828
+ return _el$57;
17200
17829
  })()
17201
17830
  }));
17202
17831
  effect((_p$) => {
17203
17832
  var _v$30 = left(), _v$31 = top(), _v$32 = boxW(), _v$33 = theme.focusAccent, _v$34 = theme.backgroundElement;
17204
- _v$30 !== _p$.e && (_p$.e = setProp(_el$55, "left", _v$30, _p$.e));
17205
- _v$31 !== _p$.t && (_p$.t = setProp(_el$55, "top", _v$31, _p$.t));
17206
- _v$32 !== _p$.a && (_p$.a = setProp(_el$55, "width", _v$32, _p$.a));
17207
- _v$33 !== _p$.o && (_p$.o = setProp(_el$55, "borderColor", _v$33, _p$.o));
17208
- _v$34 !== _p$.i && (_p$.i = setProp(_el$55, "backgroundColor", _v$34, _p$.i));
17833
+ _v$30 !== _p$.e && (_p$.e = setProp(_el$56, "left", _v$30, _p$.e));
17834
+ _v$31 !== _p$.t && (_p$.t = setProp(_el$56, "top", _v$31, _p$.t));
17835
+ _v$32 !== _p$.a && (_p$.a = setProp(_el$56, "width", _v$32, _p$.a));
17836
+ _v$33 !== _p$.o && (_p$.o = setProp(_el$56, "borderColor", _v$33, _p$.o));
17837
+ _v$34 !== _p$.i && (_p$.i = setProp(_el$56, "backgroundColor", _v$34, _p$.i));
17209
17838
  return _p$;
17210
17839
  }, {
17211
17840
  e: undefined,
@@ -17214,12 +17843,12 @@ function Sidebar(props) {
17214
17843
  o: undefined,
17215
17844
  i: undefined
17216
17845
  });
17217
- return _el$55;
17846
+ return _el$56;
17218
17847
  })();
17219
17848
  }
17220
17849
  }), null);
17221
17850
  effect((_p$) => {
17222
- var _v$9 = props.width ? props.width() : SIDEBAR_WIDTH, _v$0 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$1 = TextAttributes8.BOLD, _v$10 = theme.textMuted;
17851
+ var _v$9 = props.width ? props.width() : SIDEBAR_WIDTH, _v$0 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$1 = TextAttributes9.BOLD, _v$10 = theme.textMuted;
17223
17852
  _v$9 !== _p$.e && (_p$.e = setProp(_el$4, "width", _v$9, _p$.e));
17224
17853
  _v$0 !== _p$.t && (_p$.t = setProp(_el$6, "fg", _v$0, _p$.t));
17225
17854
  _v$1 !== _p$.a && (_p$.a = setProp(_el$6, "attributes", _v$1, _p$.a));
@@ -17300,8 +17929,8 @@ var exports_host = {};
17300
17929
  __export(exports_host, {
17301
17930
  startTasksPane: () => startTasksPane
17302
17931
  });
17303
- import { existsSync as existsSync11 } from "fs";
17304
- import { TextAttributes as TextAttributes9 } from "@opentui/core";
17932
+ import { existsSync as existsSync12 } from "fs";
17933
+ import { TextAttributes as TextAttributes10 } from "@opentui/core";
17305
17934
  function TasksShell(props) {
17306
17935
  const themeCtx = useTheme();
17307
17936
  const {
@@ -17535,7 +18164,7 @@ function TasksShell(props) {
17535
18164
  async function openSelectedWorktree(id) {
17536
18165
  const task = props.tasks().find((t) => t.id === id);
17537
18166
  let worktree = task?.worktreePath;
17538
- if (!worktree || !existsSync11(worktree)) {
18167
+ if (!worktree || !existsSync12(worktree)) {
17539
18168
  if (!props.orch) {
17540
18169
  console.error("[kobe tasks] no daemon; cannot materialise worktree");
17541
18170
  return;
@@ -17548,7 +18177,7 @@ function TasksShell(props) {
17548
18177
  }
17549
18178
  await props.reload();
17550
18179
  }
17551
- if (!worktree || !existsSync11(worktree))
18180
+ if (!worktree || !existsSync12(worktree))
17552
18181
  return;
17553
18182
  const opener = detectWorktreeOpener();
17554
18183
  if (!opener) {
@@ -17599,7 +18228,7 @@ function TasksShell(props) {
17599
18228
  const exists = await sessionExists(name);
17600
18229
  if (exists) {
17601
18230
  const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
17602
- if (cwd2 && existsSync11(cwd2)) {
18231
+ if (cwd2 && existsSync12(cwd2)) {
17603
18232
  await ensureSession({
17604
18233
  name,
17605
18234
  cwd: cwd2,
@@ -17613,7 +18242,7 @@ function TasksShell(props) {
17613
18242
  return;
17614
18243
  }
17615
18244
  let cwd = task?.worktreePath;
17616
- if (!cwd || !existsSync11(cwd)) {
18245
+ if (!cwd || !existsSync12(cwd)) {
17617
18246
  if (!props.orch) {
17618
18247
  console.error("[kobe tasks] no daemon; cannot materialise worktree");
17619
18248
  return;
@@ -17626,7 +18255,7 @@ function TasksShell(props) {
17626
18255
  }
17627
18256
  await props.reload();
17628
18257
  }
17629
- if (!cwd || !existsSync11(cwd))
18258
+ if (!cwd || !existsSync12(cwd))
17630
18259
  return;
17631
18260
  const init2 = task?.repo ? resolveRepoInit(task.repo, cwd) : {};
17632
18261
  const ready = await ensureSession({
@@ -17657,11 +18286,19 @@ function TasksShell(props) {
17657
18286
  emphasize: false
17658
18287
  };
17659
18288
  });
18289
+ const daemonStale = () => props.orch?.daemonStaleSignal()() ?? false;
18290
+ const daemonVersion = () => props.orch?.daemonVersionSignal()() ?? null;
17660
18291
  return (() => {
17661
18292
  var _el$ = createElement("box"), _el$2 = createElement("box");
17662
18293
  insertNode(_el$, _el$2);
17663
18294
  setProp(_el$, "flexDirection", "column");
17664
18295
  setProp(_el$, "flexGrow", 1);
18296
+ insert(_el$, createComponent2(VersionSkewBanner, {
18297
+ stale: daemonStale,
18298
+ daemonVersion,
18299
+ clientVersion: CURRENT_VERSION,
18300
+ width: () => dimensions().width
18301
+ }), _el$2);
17665
18302
  setProp(_el$2, "flexGrow", 1);
17666
18303
  setProp(_el$2, "flexShrink", 1);
17667
18304
  insert(_el$2, createComponent2(Sidebar, {
@@ -17675,6 +18312,9 @@ function TasksShell(props) {
17675
18312
  headerStatus,
17676
18313
  onHeaderStatusClick: () => void openUpdate(),
17677
18314
  width: () => dimensions().width,
18315
+ get engineState() {
18316
+ return memo2(() => !!props.orch)() ? props.orch.engineStateSignal() : undefined;
18317
+ },
17678
18318
  onAddTask: () => void createTask(),
17679
18319
  onRenameRequest: (id) => void renameTask(id),
17680
18320
  onDeleteRequest: (id) => void deleteTask(id),
@@ -17775,7 +18415,7 @@ function ShortcutHints() {
17775
18415
  setProp(_el$10, "wrapMode", "none");
17776
18416
  insert(_el$10, () => clipLabel(h.label));
17777
18417
  effect((_p$) => {
17778
- var _v$3 = theme.accent, _v$4 = TextAttributes9.BOLD, _v$5 = theme.textMuted;
18418
+ var _v$3 = theme.accent, _v$4 = TextAttributes10.BOLD, _v$5 = theme.textMuted;
17779
18419
  _v$3 !== _p$.e && (_p$.e = setProp(_el$8, "fg", _v$3, _p$.e));
17780
18420
  _v$4 !== _p$.t && (_p$.t = setProp(_el$8, "attributes", _v$4, _p$.t));
17781
18421
  _v$5 !== _p$.a && (_p$.a = setProp(_el$10, "fg", _v$5, _p$.a));
@@ -17789,7 +18429,7 @@ function ShortcutHints() {
17789
18429
  })()
17790
18430
  }), null);
17791
18431
  effect((_p$) => {
17792
- var _v$ = theme.textMuted, _v$2 = TextAttributes9.DIM;
18432
+ var _v$ = theme.textMuted, _v$2 = TextAttributes10.DIM;
17793
18433
  _v$ !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$, _p$.e));
17794
18434
  _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "attributes", _v$2, _p$.t));
17795
18435
  return _p$;
@@ -17894,6 +18534,7 @@ var init_host = __esm(() => {
17894
18534
  init_solid();
17895
18535
  init_solid();
17896
18536
  init_solid();
18537
+ init_solid();
17897
18538
  init_client2();
17898
18539
  init_solid();
17899
18540
  init_dev();
@@ -17911,6 +18552,7 @@ var init_host = __esm(() => {
17911
18552
  init_new_task_dialog();
17912
18553
  init_rename_task_dialog();
17913
18554
  init_settings_dialog();
18555
+ init_version_skew_banner();
17914
18556
  init_focus();
17915
18557
  init_kv();
17916
18558
  init_theme2();
@@ -18219,7 +18861,7 @@ __export(exports_host4, {
18219
18861
  startUpdateHost: () => startUpdateHost
18220
18862
  });
18221
18863
  import { spawn as spawn4, spawnSync as spawnSync8 } from "child_process";
18222
- import { TextAttributes as TextAttributes10 } from "@opentui/core";
18864
+ import { TextAttributes as TextAttributes11 } from "@opentui/core";
18223
18865
  function openExternalUrl(url) {
18224
18866
  if (!url)
18225
18867
  return false;
@@ -18244,13 +18886,13 @@ function releaseBodyLines(body) {
18244
18886
  function waitForKeypress() {
18245
18887
  if (!process.stdin.isTTY)
18246
18888
  return Promise.resolve();
18247
- return new Promise((resolve6) => {
18889
+ return new Promise((resolve7) => {
18248
18890
  const stdin = process.stdin;
18249
18891
  const done = () => {
18250
18892
  stdin.off("data", done);
18251
18893
  stdin.setRawMode?.(false);
18252
18894
  stdin.pause();
18253
- resolve6();
18895
+ resolve7();
18254
18896
  };
18255
18897
  stdin.setRawMode?.(true);
18256
18898
  stdin.resume();
@@ -18316,7 +18958,7 @@ function UpdatePage() {
18316
18958
  }
18317
18959
  async function runUpdater() {
18318
18960
  setStatus("Leaving the TUI page and running the updater in this tmux window...");
18319
- await new Promise((resolve6) => setTimeout(resolve6, 30));
18961
+ await new Promise((resolve7) => setTimeout(resolve7, 30));
18320
18962
  renderer?.destroy();
18321
18963
  process.stdout.write(`
18322
18964
  kobe ${CURRENT_VERSION} -> latest
@@ -18447,7 +19089,7 @@ kobe update failed with exit code ${code}.
18447
19089
  setProp(_el$32, "wrapMode", "word");
18448
19090
  insert(_el$32, () => action.detail);
18449
19091
  effect((_p$) => {
18450
- var _v$12 = selected() === action.id ? theme.primary : undefined, _v$13 = selected() === action.id ? theme.selectedListItemText : theme.accent, _v$14 = TextAttributes10.BOLD, _v$15 = selected() === action.id ? theme.selectedListItemText : theme.text, _v$16 = selected() === action.id ? theme.selectedListItemText : theme.textMuted;
19092
+ var _v$12 = selected() === action.id ? theme.primary : undefined, _v$13 = selected() === action.id ? theme.selectedListItemText : theme.accent, _v$14 = TextAttributes11.BOLD, _v$15 = selected() === action.id ? theme.selectedListItemText : theme.text, _v$16 = selected() === action.id ? theme.selectedListItemText : theme.textMuted;
18451
19093
  _v$12 !== _p$.e && (_p$.e = setProp(_el$25, "backgroundColor", _v$12, _p$.e));
18452
19094
  _v$13 !== _p$.t && (_p$.t = setProp(_el$27, "fg", _v$13, _p$.t));
18453
19095
  _v$14 !== _p$.a && (_p$.a = setProp(_el$27, "attributes", _v$14, _p$.a));
@@ -18525,7 +19167,7 @@ kobe update failed with exit code ${code}.
18525
19167
  })()
18526
19168
  }), null);
18527
19169
  effect((_p$) => {
18528
- var { background: _v$, text: _v$2 } = theme, _v$3 = TextAttributes10.BOLD, _v$4 = theme.textMuted, _v$5 = theme.textMuted, _v$6 = theme.text, _v$7 = TextAttributes10.BOLD, _v$8 = theme.textMuted, _v$9 = info()?.hasUpdate ? theme.warning : theme.success, _v$0 = TextAttributes10.BOLD, _v$1 = theme.textMuted, _v$10 = TextAttributes10.DIM, _v$11 = {
19170
+ var { background: _v$, text: _v$2 } = theme, _v$3 = TextAttributes11.BOLD, _v$4 = theme.textMuted, _v$5 = theme.textMuted, _v$6 = theme.text, _v$7 = TextAttributes11.BOLD, _v$8 = theme.textMuted, _v$9 = info()?.hasUpdate ? theme.warning : theme.success, _v$0 = TextAttributes11.BOLD, _v$1 = theme.textMuted, _v$10 = TextAttributes11.DIM, _v$11 = {
18529
19171
  trackOptions: {
18530
19172
  backgroundColor: theme.background,
18531
19173
  foregroundColor: theme.borderActive
@@ -18623,7 +19265,7 @@ var init_host4 = __esm(() => {
18623
19265
  });
18624
19266
 
18625
19267
  // src/engine/turn-detector.ts
18626
- import { readFile as readFile6 } from "fs/promises";
19268
+ import { readFile as readFile7 } from "fs/promises";
18627
19269
 
18628
19270
  class EngineTurnDetector {
18629
19271
  supportsCompletionMarkers() {
@@ -18646,7 +19288,7 @@ function latestClaudeCompletionMarkerFromJsonl(raw, sourceId = "claude", fallbac
18646
19288
  const record = parseJsonLine(line);
18647
19289
  if (!record)
18648
19290
  continue;
18649
- const inner = isObject6(record.message) ? record.message : record;
19291
+ const inner = isObject7(record.message) ? record.message : record;
18650
19292
  if (inner.role !== "assistant")
18651
19293
  continue;
18652
19294
  if (!("content" in inner))
@@ -18688,7 +19330,7 @@ function parseJsonLine(line) {
18688
19330
  return null;
18689
19331
  try {
18690
19332
  const parsed = JSON.parse(trimmed);
18691
- return isObject6(parsed) ? parsed : null;
19333
+ return isObject7(parsed) ? parsed : null;
18692
19334
  } catch {
18693
19335
  return null;
18694
19336
  }
@@ -18697,7 +19339,7 @@ function timestampFromRecord(record, fallback) {
18697
19339
  const ts = typeof record.timestamp === "string" ? Date.parse(record.timestamp) : Number.NaN;
18698
19340
  return Number.isFinite(ts) ? ts : fallback;
18699
19341
  }
18700
- function isObject6(v) {
19342
+ function isObject7(v) {
18701
19343
  return typeof v === "object" && v !== null && !Array.isArray(v);
18702
19344
  }
18703
19345
  var ClaudeTurnDetector, CodexTurnDetector, UnknownTurnDetector;
@@ -18710,7 +19352,7 @@ var init_turn_detector = __esm(() => {
18710
19352
  const files = await listSessionFilesForWorktree(worktree);
18711
19353
  let latest = null;
18712
19354
  for (const file of files.slice(0, 4)) {
18713
- const raw = await readFile6(file.path, "utf8").catch(() => "");
19355
+ const raw = await readFile7(file.path, "utf8").catch(() => "");
18714
19356
  const marker = latestClaudeCompletionMarkerFromJsonl(raw, file.path, file.mtimeMs);
18715
19357
  if (marker && (!latest || marker.timestampMs > latest.timestampMs))
18716
19358
  latest = marker;
@@ -18729,7 +19371,7 @@ var init_turn_detector = __esm(() => {
18729
19371
  if (scanned >= 12)
18730
19372
  break;
18731
19373
  scanned++;
18732
- const raw = await readFile6(file, "utf8").catch(() => "");
19374
+ const raw = await readFile7(file, "utf8").catch(() => "");
18733
19375
  if (!raw || rolloutCwd(raw) !== worktree)
18734
19376
  continue;
18735
19377
  return latestCodexCompletionMarkerFromJsonl(raw, file);
@@ -18978,7 +19620,7 @@ var gitWrapper;
18978
19620
  var init_git2 = __esm(() => {
18979
19621
  gitWrapper = {
18980
19622
  spawn(args, cwd) {
18981
- return new Promise((resolve6, reject) => {
19623
+ return new Promise((resolve7, reject) => {
18982
19624
  const child = nodeSpawn("git", [...args], {
18983
19625
  cwd,
18984
19626
  shell: false,
@@ -18997,7 +19639,7 @@ var init_git2 = __esm(() => {
18997
19639
  });
18998
19640
  child.on("error", reject);
18999
19641
  child.on("close", (status, signal) => {
19000
- resolve6({ stdout, stderr, status, signal });
19642
+ resolve7({ stdout, stderr, status, signal });
19001
19643
  });
19002
19644
  });
19003
19645
  }
@@ -19051,14 +19693,14 @@ var init_keys2 = __esm(() => {
19051
19693
 
19052
19694
  // src/tui/panes/filetree/open-external.ts
19053
19695
  import { spawn as spawn5 } from "child_process";
19054
- import { existsSync as existsSync12 } from "fs";
19696
+ import { existsSync as existsSync13 } from "fs";
19055
19697
  import { platform } from "os";
19056
19698
  function openExternally(absPath) {
19057
19699
  if (!absPath)
19058
19700
  return;
19059
19701
  const plat = platform();
19060
19702
  if (plat === "linux") {
19061
- if (existsSync12("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
19703
+ if (existsSync13("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
19062
19704
  spawnDetached("wslview", [absPath], () => {
19063
19705
  const child = spawn5("wslpath", ["-w", absPath], { stdio: ["ignore", "pipe", "ignore"] });
19064
19706
  let out = "";
@@ -19097,7 +19739,7 @@ var init_open_external = () => {};
19097
19739
 
19098
19740
  // src/tui/panes/filetree/FileTree.tsx
19099
19741
  import { watch } from "fs";
19100
- import { TextAttributes as TextAttributes11 } from "@opentui/core";
19742
+ import { TextAttributes as TextAttributes12 } from "@opentui/core";
19101
19743
  function statusToken(s) {
19102
19744
  switch (s) {
19103
19745
  case "M":
@@ -19496,7 +20138,7 @@ function FileTree(props) {
19496
20138
  insertNode(_el$6, createTextNode(`create PR`));
19497
20139
  setProp(_el$6, "wrapMode", "none");
19498
20140
  effect((_p$) => {
19499
- var _v$ = theme.accent, _v$2 = TextAttributes11.BOLD, _v$3 = theme.text;
20141
+ var _v$ = theme.accent, _v$2 = TextAttributes12.BOLD, _v$3 = theme.text;
19500
20142
  _v$ !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$, _p$.e));
19501
20143
  _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "attributes", _v$2, _p$.t));
19502
20144
  _v$3 !== _p$.a && (_p$.a = setProp(_el$6, "fg", _v$3, _p$.a));
@@ -19526,7 +20168,7 @@ function FileTree(props) {
19526
20168
  setProp(_el$26, "onMouseUp", () => setTab(t));
19527
20169
  insert(_el$26, () => TAB_LABEL[t]);
19528
20170
  effect((_p$) => {
19529
- var _v$6 = isActive() ? theme.primary : theme.textMuted, _v$7 = isActive() ? TextAttributes11.BOLD : undefined;
20171
+ var _v$6 = isActive() ? theme.primary : theme.textMuted, _v$7 = isActive() ? TextAttributes12.BOLD : undefined;
19530
20172
  _v$6 !== _p$.e && (_p$.e = setProp(_el$26, "fg", _v$6, _p$.e));
19531
20173
  _v$7 !== _p$.t && (_p$.t = setProp(_el$26, "attributes", _v$7, _p$.t));
19532
20174
  return _p$;
@@ -19547,7 +20189,7 @@ function FileTree(props) {
19547
20189
  setProp(_el$27, "wrapMode", "none");
19548
20190
  insert(_el$27, () => badge().text);
19549
20191
  effect((_p$) => {
19550
- var _v$8 = badge().active ? theme.accent : theme.textMuted, _v$9 = badge().active ? TextAttributes11.BOLD : undefined;
20192
+ var _v$8 = badge().active ? theme.accent : theme.textMuted, _v$9 = badge().active ? TextAttributes12.BOLD : undefined;
19551
20193
  _v$8 !== _p$.e && (_p$.e = setProp(_el$27, "fg", _v$8, _p$.e));
19552
20194
  _v$9 !== _p$.t && (_p$.t = setProp(_el$27, "attributes", _v$9, _p$.t));
19553
20195
  return _p$;
@@ -19701,7 +20343,7 @@ function FileTree(props) {
19701
20343
  setProp(_el$31, "wrapMode", "none");
19702
20344
  insert(_el$31, () => `${indent}${marker} ${row.name}/`);
19703
20345
  effect((_p$) => {
19704
- var _v$0 = rowBg(), _v$1 = theme.textMuted, _v$10 = TextAttributes11.BOLD;
20346
+ var _v$0 = rowBg(), _v$1 = theme.textMuted, _v$10 = TextAttributes12.BOLD;
19705
20347
  _v$0 !== _p$.e && (_p$.e = setProp(_el$29, "backgroundColor", _v$0, _p$.e));
19706
20348
  _v$1 !== _p$.t && (_p$.t = setProp(_el$31, "fg", _v$1, _p$.t));
19707
20349
  _v$10 !== _p$.a && (_p$.a = setProp(_el$31, "attributes", _v$10, _p$.a));
@@ -20449,7 +21091,7 @@ __export(exports_direct, {
20449
21091
  startDirectTmux: () => startDirectTmux,
20450
21092
  chooseInitialTask: () => chooseInitialTask
20451
21093
  });
20452
- import { resolve as resolve6 } from "path";
21094
+ import { resolve as resolve7 } from "path";
20453
21095
  function chooseInitialTask(tasks, choice = {}) {
20454
21096
  const byId = (id) => id ? tasks.find((t) => t.id === id) : undefined;
20455
21097
  const active = byId(choice.activeTaskId);
@@ -20485,7 +21127,7 @@ async function ensureRepos(orchestrator) {
20485
21127
  normalizeSavedRepos();
20486
21128
  let repos = [...getSavedRepos()];
20487
21129
  if (repos.length === 0) {
20488
- const added = addSavedRepo(resolve6(process.cwd()));
21130
+ const added = addSavedRepo(resolve7(process.cwd()));
20489
21131
  repos = [added.path];
20490
21132
  }
20491
21133
  for (const repo of repos) {
@@ -20495,7 +21137,7 @@ async function ensureRepos(orchestrator) {
20495
21137
  console.error(`[kobe] ensureMainTask failed for ${repo}:`, err);
20496
21138
  }
20497
21139
  }
20498
- return repos[0] ?? resolve6(process.cwd());
21140
+ return repos[0] ?? resolve7(process.cwd());
20499
21141
  }
20500
21142
  async function startDirectTmux() {
20501
21143
  setClientLogContext("gui");
@@ -20581,7 +21223,7 @@ var init_direct = __esm(() => {
20581
21223
  });
20582
21224
 
20583
21225
  // src/tui/component/help-dialog.tsx
20584
- import { TextAttributes as TextAttributes12 } from "@opentui/core";
21226
+ import { TextAttributes as TextAttributes13 } from "@opentui/core";
20585
21227
  function groupBindings(keymap) {
20586
21228
  const groups = new Map;
20587
21229
  const order = [];
@@ -20685,7 +21327,7 @@ function HelpDialog() {
20685
21327
  }
20686
21328
  }), null);
20687
21329
  effect((_p$) => {
20688
- var _v$5 = theme.accent, _v$6 = TextAttributes12.BOLD;
21330
+ var _v$5 = theme.accent, _v$6 = TextAttributes13.BOLD;
20689
21331
  _v$5 !== _p$.e && (_p$.e = setProp(_el$0, "fg", _v$5, _p$.e));
20690
21332
  _v$6 !== _p$.t && (_p$.t = setProp(_el$0, "attributes", _v$6, _p$.t));
20691
21333
  return _p$;
@@ -20697,7 +21339,7 @@ function HelpDialog() {
20697
21339
  })()
20698
21340
  }));
20699
21341
  effect((_p$) => {
20700
- var _v$ = TextAttributes12.BOLD, _v$2 = theme.text, _v$3 = theme.textMuted, _v$4 = {
21342
+ var _v$ = TextAttributes13.BOLD, _v$2 = theme.text, _v$3 = theme.textMuted, _v$4 = {
20701
21343
  trackOptions: {
20702
21344
  backgroundColor: theme.backgroundDialog,
20703
21345
  foregroundColor: theme.borderActive
@@ -20737,7 +21379,7 @@ var init_help_dialog = __esm(() => {
20737
21379
  });
20738
21380
 
20739
21381
  // src/tui/component/pane-header.tsx
20740
- import { TextAttributes as TextAttributes13 } from "@opentui/core";
21382
+ import { TextAttributes as TextAttributes14 } from "@opentui/core";
20741
21383
  function PaneHeader(props) {
20742
21384
  const {
20743
21385
  theme
@@ -20767,7 +21409,7 @@ function PaneHeader(props) {
20767
21409
  setProp(_el$3, "wrapMode", "none");
20768
21410
  insert(_el$3, () => props.ordinal);
20769
21411
  effect((_p$) => {
20770
- var _v$ = titleColor(), _v$2 = TextAttributes13.BOLD;
21412
+ var _v$ = titleColor(), _v$2 = TextAttributes14.BOLD;
20771
21413
  _v$ !== _p$.e && (_p$.e = setProp(_el$3, "fg", _v$, _p$.e));
20772
21414
  _v$2 !== _p$.t && (_p$.t = setProp(_el$3, "attributes", _v$2, _p$.t));
20773
21415
  return _p$;
@@ -20818,7 +21460,7 @@ function PaneHeader(props) {
20818
21460
  }
20819
21461
  }), null);
20820
21462
  effect((_p$) => {
20821
- var _v$3 = titleColor(), _v$4 = TextAttributes13.BOLD;
21463
+ var _v$3 = titleColor(), _v$4 = TextAttributes14.BOLD;
20822
21464
  _v$3 !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$3, _p$.e));
20823
21465
  _v$4 !== _p$.t && (_p$.t = setProp(_el$4, "attributes", _v$4, _p$.t));
20824
21466
  return _p$;
@@ -20943,7 +21585,7 @@ var init_resizable_edge = __esm(() => {
20943
21585
  });
20944
21586
 
20945
21587
  // src/tui/component/status-bar.tsx
20946
- import { TextAttributes as TextAttributes14 } from "@opentui/core";
21588
+ import { TextAttributes as TextAttributes15 } from "@opentui/core";
20947
21589
  function Hotkey(props) {
20948
21590
  const {
20949
21591
  theme
@@ -20962,7 +21604,7 @@ function Hotkey(props) {
20962
21604
  setProp(_el$5, "wrapMode", "none");
20963
21605
  insert(_el$5, () => props.label);
20964
21606
  effect((_p$) => {
20965
- var _v$ = theme.accent, _v$2 = TextAttributes14.BOLD, _v$3 = theme.textMuted;
21607
+ var _v$ = theme.accent, _v$2 = TextAttributes15.BOLD, _v$3 = theme.textMuted;
20966
21608
  _v$ !== _p$.e && (_p$.e = setProp(_el$2, "fg", _v$, _p$.e));
20967
21609
  _v$2 !== _p$.t && (_p$.t = setProp(_el$2, "attributes", _v$2, _p$.t));
20968
21610
  _v$3 !== _p$.a && (_p$.a = setProp(_el$5, "fg", _v$3, _p$.a));
@@ -21050,7 +21692,7 @@ function StatusBar() {
21050
21692
  insertNode(_el$0, createTextNode(`Press Ctrl+C again to exit`));
21051
21693
  setProp(_el$0, "wrapMode", "none");
21052
21694
  effect((_p$) => {
21053
- var _v$4 = theme.warning, _v$5 = TextAttributes14.BOLD;
21695
+ var _v$4 = theme.warning, _v$5 = TextAttributes15.BOLD;
21054
21696
  _v$4 !== _p$.e && (_p$.e = setProp(_el$0, "fg", _v$4, _p$.e));
21055
21697
  _v$5 !== _p$.t && (_p$.t = setProp(_el$0, "attributes", _v$5, _p$.t));
21056
21698
  return _p$;
@@ -21062,7 +21704,7 @@ function StatusBar() {
21062
21704
  }
21063
21705
  }), null);
21064
21706
  effect((_p$) => {
21065
- var _v$6 = theme.primary, _v$7 = TextAttributes14.BOLD;
21707
+ var _v$6 = theme.primary, _v$7 = TextAttributes15.BOLD;
21066
21708
  _v$6 !== _p$.e && (_p$.e = setProp(_el$8, "fg", _v$6, _p$.e));
21067
21709
  _v$7 !== _p$.t && (_p$.t = setProp(_el$8, "attributes", _v$7, _p$.t));
21068
21710
  return _p$;
@@ -21092,9 +21734,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
21092
21734
  var init_pulse = () => {};
21093
21735
 
21094
21736
  // src/tui/lib/sound.ts
21095
- import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
21737
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "fs";
21096
21738
  import { tmpdir as tmpdir2 } from "os";
21097
- import { basename as basename6, isAbsolute as isAbsolute2, join as join12, resolve as resolve7 } from "path";
21739
+ import { basename as basename6, isAbsolute as isAbsolute2, join as join14, resolve as resolve8 } from "path";
21098
21740
  function args(player, file, volume) {
21099
21741
  if (player === "ffplay")
21100
21742
  return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
@@ -21117,13 +21759,13 @@ function pickPlayer() {
21117
21759
  return cachedPlayer;
21118
21760
  const path12 = process.env.PATH ?? "";
21119
21761
  const segments = path12.split(":").filter(Boolean);
21120
- cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync13(join12(dir, p)))) ?? null;
21762
+ cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join14(dir, p)))) ?? null;
21121
21763
  return cachedPlayer;
21122
21764
  }
21123
21765
  async function ensureAsset() {
21124
21766
  cachedPath ??= (async () => {
21125
21767
  mkdirSync5(DIR, { recursive: true });
21126
- const dest = join12(DIR, basename6(pulseAsset));
21768
+ const dest = join14(DIR, basename6(pulseAsset));
21127
21769
  const out = Bun.file(dest);
21128
21770
  if (await out.exists())
21129
21771
  return dest;
@@ -21152,8 +21794,8 @@ function pulse(volume = 0.4) {
21152
21794
  var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
21153
21795
  var init_sound = __esm(() => {
21154
21796
  init_pulse();
21155
- pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve7(import.meta.dir, pulse_default);
21156
- DIR = join12(tmpdir2(), "kobe-sfx");
21797
+ pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve8(import.meta.dir, pulse_default);
21798
+ DIR = join14(tmpdir2(), "kobe-sfx");
21157
21799
  PLAYERS = [
21158
21800
  "ffplay",
21159
21801
  "mpv",
@@ -21251,7 +21893,7 @@ var init_notifications = __esm(() => {
21251
21893
  });
21252
21894
 
21253
21895
  // src/tui/component/toast-overlay.tsx
21254
- import { TextAttributes as TextAttributes15 } from "@opentui/core";
21896
+ import { TextAttributes as TextAttributes16 } from "@opentui/core";
21255
21897
  function ToastOverlay() {
21256
21898
  const {
21257
21899
  theme
@@ -21294,7 +21936,7 @@ function ToastOverlay() {
21294
21936
  setProp(_el$4, "wrapMode", "none");
21295
21937
  insert(_el$4, () => toast.title);
21296
21938
  effect((_p$) => {
21297
- var _v$3 = bg(), _v$4 = fg(), _v$5 = TextAttributes15.BOLD, _v$6 = fg();
21939
+ var _v$3 = bg(), _v$4 = fg(), _v$5 = TextAttributes16.BOLD, _v$6 = fg();
21298
21940
  _v$3 !== _p$.e && (_p$.e = setProp(_el$2, "backgroundColor", _v$3, _p$.e));
21299
21941
  _v$4 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$4, _p$.t));
21300
21942
  _v$5 !== _p$.a && (_p$.a = setProp(_el$3, "attributes", _v$5, _p$.a));
@@ -21354,7 +21996,7 @@ function repoBasename2(repo) {
21354
21996
 
21355
21997
  // src/tui/component/top-bar.tsx
21356
21998
  import { spawnSync as spawnSync10 } from "child_process";
21357
- import { TextAttributes as TextAttributes16 } from "@opentui/core";
21999
+ import { TextAttributes as TextAttributes17 } from "@opentui/core";
21358
22000
  function TopBar(props) {
21359
22001
  const {
21360
22002
  theme
@@ -21429,7 +22071,7 @@ function TopBar(props) {
21429
22071
  insertNode(_el$7, createTextNode(`[Update]`));
21430
22072
  setProp(_el$7, "onMouseUp", () => void confirmUpdate());
21431
22073
  effect((_p$) => {
21432
- var _v$ = theme.warning, _v$2 = TextAttributes16.BOLD;
22074
+ var _v$ = theme.warning, _v$2 = TextAttributes17.BOLD;
21433
22075
  _v$ !== _p$.e && (_p$.e = setProp(_el$7, "fg", _v$, _p$.e));
21434
22076
  _v$2 !== _p$.t && (_p$.t = setProp(_el$7, "attributes", _v$2, _p$.t));
21435
22077
  return _p$;
@@ -21465,7 +22107,7 @@ function TopBar(props) {
21465
22107
  insertNode(_el$12, createTextNode(`daemon disconnected`));
21466
22108
  setProp(_el$12, "wrapMode", "none");
21467
22109
  effect((_p$) => {
21468
- var _v$6 = theme.error, _v$7 = TextAttributes16.BOLD;
22110
+ var _v$6 = theme.error, _v$7 = TextAttributes17.BOLD;
21469
22111
  _v$6 !== _p$.e && (_p$.e = setProp(_el$12, "fg", _v$6, _p$.e));
21470
22112
  _v$7 !== _p$.t && (_p$.t = setProp(_el$12, "attributes", _v$7, _p$.t));
21471
22113
  return _p$;
@@ -21495,7 +22137,7 @@ function TopBar(props) {
21495
22137
  setProp(_el$15, "wrapMode", "none");
21496
22138
  insert(_el$15, () => label().repoName);
21497
22139
  effect((_p$) => {
21498
- var _v$8 = label().branch ? theme.textMuted : theme.text, _v$9 = label().branch ? undefined : TextAttributes16.BOLD;
22140
+ var _v$8 = label().branch ? theme.textMuted : theme.text, _v$9 = label().branch ? undefined : TextAttributes17.BOLD;
21499
22141
  _v$8 !== _p$.e && (_p$.e = setProp(_el$15, "fg", _v$8, _p$.e));
21500
22142
  _v$9 !== _p$.t && (_p$.t = setProp(_el$15, "attributes", _v$9, _p$.t));
21501
22143
  return _p$;
@@ -21527,7 +22169,7 @@ function TopBar(props) {
21527
22169
  setProp(_el$18, "wrapMode", "none");
21528
22170
  insert(_el$18, () => label().branch);
21529
22171
  effect((_p$) => {
21530
- var _v$0 = theme.text, _v$1 = TextAttributes16.BOLD;
22172
+ var _v$0 = theme.text, _v$1 = TextAttributes17.BOLD;
21531
22173
  _v$0 !== _p$.e && (_p$.e = setProp(_el$18, "fg", _v$0, _p$.e));
21532
22174
  _v$1 !== _p$.t && (_p$.t = setProp(_el$18, "attributes", _v$1, _p$.t));
21533
22175
  return _p$;
@@ -21550,7 +22192,7 @@ function TopBar(props) {
21550
22192
  setProp(_el$11, "gap", 2);
21551
22193
  setProp(_el$11, "justifyContent", "flex-end");
21552
22194
  effect((_p$) => {
21553
- var _v$3 = theme.primary, _v$4 = TextAttributes16.BOLD, _v$5 = theme.textMuted;
22195
+ var _v$3 = theme.primary, _v$4 = TextAttributes17.BOLD, _v$5 = theme.textMuted;
21554
22196
  _v$3 !== _p$.e && (_p$.e = setProp(_el$3, "fg", _v$3, _p$.e));
21555
22197
  _v$4 !== _p$.t && (_p$.t = setProp(_el$3, "attributes", _v$4, _p$.t));
21556
22198
  _v$5 !== _p$.a && (_p$.a = setProp(_el$5, "fg", _v$5, _p$.a));
@@ -21679,7 +22321,7 @@ var init_use_theme_persistence = __esm(() => {
21679
22321
  });
21680
22322
 
21681
22323
  // src/monitor/cost.ts
21682
- import { readFile as readFile7 } from "fs/promises";
22324
+ import { readFile as readFile8 } from "fs/promises";
21683
22325
  async function summarizeTaskCost(opts) {
21684
22326
  const files = await listSessionFilesForWorktree(opts.worktree);
21685
22327
  const base = {
@@ -21701,7 +22343,7 @@ async function summarizeTaskCost(opts) {
21701
22343
  for (const file of files) {
21702
22344
  let raw;
21703
22345
  try {
21704
- raw = await readFile7(file.path, "utf8");
22346
+ raw = await readFile8(file.path, "utf8");
21705
22347
  } catch {
21706
22348
  continue;
21707
22349
  }
@@ -22212,7 +22854,7 @@ var exports_app = {};
22212
22854
  __export(exports_app, {
22213
22855
  startApp: () => startApp
22214
22856
  });
22215
- import { homedir as homedir15 } from "os";
22857
+ import { homedir as homedir16 } from "os";
22216
22858
  function Shell(props) {
22217
22859
  const themeCtx = useTheme();
22218
22860
  const {
@@ -22720,7 +23362,7 @@ async function startApp() {
22720
23362
  } of loadUserThemes()) {
22721
23363
  addTheme2(name, theme);
22722
23364
  }
22723
- const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir15();
23365
+ const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir16();
22724
23366
  let orchestrator;
22725
23367
  if (process.env.KOBE_NO_DAEMON === "1") {
22726
23368
  const store2 = new TaskIndexStore({
@@ -22833,7 +23475,7 @@ var init_tui = __esm(() => {
22833
23475
  // src/cli/index.ts
22834
23476
  init_path_glob();
22835
23477
  init_vendor();
22836
- import { resolve as resolve8 } from "path";
23478
+ import { resolve as resolve9 } from "path";
22837
23479
 
22838
23480
  // src/cli/usage.ts
22839
23481
  init_version();
@@ -22882,7 +23524,7 @@ Usage: kobe add [path]
22882
23524
  `);
22883
23525
  process.exit(2);
22884
23526
  }
22885
- const target = resolve8(process.cwd(), arg && arg.length > 0 ? arg : ".");
23527
+ const target = resolve9(process.cwd(), arg && arg.length > 0 ? arg : ".");
22886
23528
  const { addSavedRepo: addSavedRepo2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
22887
23529
  const result = addSavedRepo2(target);
22888
23530
  if (result.added) {
@@ -22981,7 +23623,7 @@ async function runAdoptSubcommand(args2) {
22981
23623
  }
22982
23624
  }
22983
23625
  const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
22984
- const repo = resolveRepoRoot2(resolve8(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
23626
+ const repo = resolveRepoRoot2(resolve9(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
22985
23627
  const vendor = coerceVendorId(vendorArg);
22986
23628
  const orch = await openLocalOrchestrator();
22987
23629
  const worktrees = await orch.discoverAdoptableWorktrees(repo);
@@ -23119,6 +23761,11 @@ async function main() {
23119
23761
  await runSkillSubcommand2(rest);
23120
23762
  return;
23121
23763
  }
23764
+ if (subcommand === "hook") {
23765
+ const { runHookSubcommand: runHookSubcommand2 } = await Promise.resolve().then(() => (init_hook_cmd(), exports_hook_cmd));
23766
+ await runHookSubcommand2(rest);
23767
+ return;
23768
+ }
23122
23769
  if (subcommand === "new-chattab") {
23123
23770
  const flags = parseOpsFlags(rest);
23124
23771
  const session = flags.session;