@sma1lboy/kobe 0.7.3 → 0.7.4

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 +785 -276
  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.4",
74
74
  description: "TUI orchestrator for Claude Code (codename)",
75
75
  type: "module",
76
76
  packageManager: "bun@1.3.13",
@@ -2325,6 +2325,248 @@ 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
+ try {
2506
+ const proc = Bun.spawn(["git", "-C", worktreeDir, "rev-parse", "--git-common-dir"], {
2507
+ stdout: "pipe",
2508
+ stderr: "ignore"
2509
+ });
2510
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
2511
+ if (code !== 0)
2512
+ return;
2513
+ let commonDir = out.trim();
2514
+ if (!commonDir)
2515
+ return;
2516
+ if (!commonDir.startsWith("/"))
2517
+ commonDir = join3(worktreeDir, commonDir);
2518
+ const excludePath = join3(commonDir, "info", "exclude");
2519
+ const existing = existsSync(excludePath) ? await readFile2(excludePath, "utf8") : "";
2520
+ if (existing.split(`
2521
+ `).some((l) => l.trim() === relPath))
2522
+ return;
2523
+ await mkdir2(join3(commonDir, "info"), { recursive: true });
2524
+ await appendFile(excludePath, `${existing.endsWith(`
2525
+ `) || existing === "" ? "" : `
2526
+ `}${relPath}
2527
+ `);
2528
+ } catch {}
2529
+ }
2530
+ var EVENT_MAP, KOBE_HOOK_EVENTS, WORKTREE_SYNC_MARKER = "worktree-created";
2531
+ var init_hook_adapter = __esm(() => {
2532
+ init_invocation();
2533
+ EVENT_MAP = [
2534
+ { event: "SessionStart", verb: "session-start" },
2535
+ { event: "UserPromptSubmit", verb: "turn-start" },
2536
+ { event: "Stop", verb: "turn-complete" },
2537
+ { event: "StopFailure", verb: "turn-failed" },
2538
+ { event: "Notification", matcher: "permission_prompt", verb: "awaiting-input" },
2539
+ { event: "SessionEnd", verb: "session-end" }
2540
+ ];
2541
+ KOBE_HOOK_EVENTS = EVENT_MAP.map((e) => e.event);
2542
+ });
2543
+
2544
+ // src/engine/hook-adapter.ts
2545
+ function createEngineHookAdapter(vendor) {
2546
+ if (vendor === "claude")
2547
+ return new ClaudeHookAdapter;
2548
+ return new NoopHookAdapter(vendor);
2549
+ }
2550
+
2551
+ class NoopHookAdapter {
2552
+ vendor;
2553
+ constructor(vendor) {
2554
+ this.vendor = vendor;
2555
+ }
2556
+ supportsHooks() {
2557
+ return false;
2558
+ }
2559
+ async installTaskHooks() {}
2560
+ supportsWorktreeSync() {
2561
+ return false;
2562
+ }
2563
+ async installWorktreeSyncHook() {}
2564
+ async removeWorktreeSyncHook() {}
2565
+ }
2566
+ var init_hook_adapter2 = __esm(() => {
2567
+ init_hook_adapter();
2568
+ });
2569
+
2328
2570
  // src/orchestrator/errors.ts
2329
2571
  var IllegalTransitionError, TaskNotFoundError, CannotDeleteMainTaskError, DIRTY_WORKTREE_CODE = "DIRTY_WORKTREE", DirtyWorktreeError, WorktreeRemoveFailedError;
2330
2572
  var init_errors = __esm(() => {
@@ -3003,8 +3245,10 @@ class Orchestrator {
3003
3245
  const task = this.requireTask(id);
3004
3246
  if (task.kind === "main")
3005
3247
  return task.repo;
3006
- if (task.worktreePath)
3248
+ if (task.worktreePath) {
3249
+ await this.installEngineHooks(task);
3007
3250
  return task.worktreePath;
3251
+ }
3008
3252
  const inflight = this.worktreeLocks.get(task.id);
3009
3253
  if (inflight) {
3010
3254
  await inflight;
@@ -3028,6 +3272,7 @@ class Orchestrator {
3028
3272
  worktreePath: info.path,
3029
3273
  branch
3030
3274
  });
3275
+ await this.installEngineHooks(this.requireTask(task.id));
3031
3276
  } catch (err) {
3032
3277
  this.slugs.cancel(task.repo, slug);
3033
3278
  throw err;
@@ -3152,14 +3397,17 @@ class Orchestrator {
3152
3397
  if (!input.worktreePath)
3153
3398
  throw new Error("adoptWorktree: worktreePath is required");
3154
3399
  const target = canonPath(input.worktreePath);
3400
+ const existing = this.store.list().find((t) => t.worktreePath && canonPath(t.worktreePath) === target);
3401
+ if (existing) {
3402
+ if (input.ifExists === "return")
3403
+ return existing;
3404
+ throw new Error(`adoptWorktree: ${input.worktreePath} is already adopted as a task`);
3405
+ }
3155
3406
  const candidates = await this.worktrees.listAll(input.repo);
3156
3407
  const match = candidates.find((wt) => canonPath(wt.path) === target);
3157
3408
  if (!match) {
3158
3409
  throw new Error(`adoptWorktree: ${input.worktreePath} is not an adoptable git worktree of ${input.repo} (unknown, detached, or the main checkout)`);
3159
3410
  }
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
3411
  const branch = input.branch?.trim() || match.branch;
3164
3412
  const title = (input.title ?? basename2(match.path)).trim() || PLACEHOLDER_TASK_TITLE;
3165
3413
  return this.store.create({
@@ -3172,6 +3420,14 @@ class Orchestrator {
3172
3420
  vendor: input.vendor ?? DEFAULT_TASK_VENDOR
3173
3421
  });
3174
3422
  }
3423
+ async installEngineHooks(task) {
3424
+ if (task.kind === "main" || !task.worktreePath)
3425
+ return;
3426
+ await createEngineHookAdapter(task.vendor ?? DEFAULT_TASK_VENDOR).installTaskHooks({
3427
+ worktreeDir: task.worktreePath,
3428
+ taskId: task.id
3429
+ });
3430
+ }
3175
3431
  pendingBaseRefs = new Map;
3176
3432
  requireTask(id) {
3177
3433
  const task = this.store.get(id);
@@ -3194,6 +3450,7 @@ function canonPath(p) {
3194
3450
  var PLACEHOLDER_TASK_TITLE = "(new task)";
3195
3451
  var init_core = __esm(() => {
3196
3452
  init_dev();
3453
+ init_hook_adapter2();
3197
3454
  init_errors();
3198
3455
  init_slug_allocator();
3199
3456
  });
@@ -3225,13 +3482,13 @@ function frameToLine(frame) {
3225
3482
  }
3226
3483
  var DAEMON_PROTOCOL_VERSION = 2, MIN_COMPATIBLE_PROTOCOL_VERSION = 2, CHANNEL_NAMES;
3227
3484
  var init_protocol = __esm(() => {
3228
- CHANNEL_NAMES = ["task.snapshot", "active-task", "update"];
3485
+ CHANNEL_NAMES = ["task.snapshot", "active-task", "update", "engine-state"];
3229
3486
  });
3230
3487
 
3231
3488
  // src/daemon/paths.ts
3232
3489
  import { createHash as createHash2 } from "crypto";
3233
3490
  import { homedir as homedir3, tmpdir } from "os";
3234
- import { join as join3 } from "path";
3491
+ import { join as join4 } from "path";
3235
3492
  function shortHomeTag(homeDir2) {
3236
3493
  return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
3237
3494
  }
@@ -3240,7 +3497,7 @@ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
3240
3497
  return naturalPath;
3241
3498
  const tag = shortHomeTag(homeDir2);
3242
3499
  const suffix = pidTag === undefined ? "" : `-${pidTag}`;
3243
- const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
3500
+ const fallback = join4(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
3244
3501
  if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
3245
3502
  return fallback;
3246
3503
  throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
@@ -3251,33 +3508,33 @@ function defaultDaemonSocketPath(homeDir2) {
3251
3508
  return override;
3252
3509
  const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
3253
3510
  if (explicit && explicit.length > 0) {
3254
- return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
3511
+ return fitSocketPath(join4(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
3255
3512
  }
3256
3513
  const runtimeDir = process.env.XDG_RUNTIME_DIR;
3257
3514
  if (runtimeDir && runtimeDir.length > 0) {
3258
- return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
3515
+ return fitSocketPath(join4(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
3259
3516
  }
3260
3517
  const home = homedir3();
3261
- return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
3518
+ return fitSocketPath(join4(home, ".kobe", "daemon.sock"), home, "daemon");
3262
3519
  }
3263
3520
  function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3264
3521
  const override = process.env.KOBE_DAEMON_PID_PATH;
3265
3522
  if (override && override.length > 0)
3266
3523
  return override;
3267
- return join3(homeDir2, ".kobe", "daemon.pid");
3524
+ return join4(homeDir2, ".kobe", "daemon.pid");
3268
3525
  }
3269
3526
  function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3270
- return join3(homeDir2, ".kobe", "daemon.log");
3527
+ return join4(homeDir2, ".kobe", "daemon.log");
3271
3528
  }
3272
3529
  function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
3273
- return join3(homeDir2, ".kobe", "client.log");
3530
+ return join4(homeDir2, ".kobe", "client.log");
3274
3531
  }
3275
3532
  var SOCKET_PATH_SAFETY_LIMIT = 100;
3276
3533
  var init_paths2 = () => {};
3277
3534
 
3278
3535
  // src/client/client-log.ts
3279
- import { appendFile, mkdir as mkdir2 } from "fs/promises";
3280
- import { dirname as dirname3 } from "path";
3536
+ import { appendFile as appendFile2, mkdir as mkdir3 } from "fs/promises";
3537
+ import { dirname as dirname4 } from "path";
3281
3538
  function setClientLogContext(ctx) {
3282
3539
  context = ctx;
3283
3540
  }
@@ -3309,11 +3566,11 @@ function append(line) {
3309
3566
  const path3 = defaultClientLogPath();
3310
3567
  writeChain = writeChain.then(async () => {
3311
3568
  try {
3312
- await appendFile(path3, line);
3569
+ await appendFile2(path3, line);
3313
3570
  } catch (err) {
3314
3571
  if (err?.code === "ENOENT") {
3315
- await mkdir2(dirname3(path3), { recursive: true });
3316
- await appendFile(path3, line);
3572
+ await mkdir3(dirname4(path3), { recursive: true });
3573
+ await appendFile2(path3, line);
3317
3574
  } else {
3318
3575
  throw err;
3319
3576
  }
@@ -3515,6 +3772,37 @@ var init_client = __esm(() => {
3515
3772
  init_client_log();
3516
3773
  });
3517
3774
 
3775
+ // src/engine/hook-events.ts
3776
+ function isEngineActivityKind(v) {
3777
+ return ENGINE_ACTIVITY_KINDS.includes(v);
3778
+ }
3779
+ function reduceActivity(_prev, kind, detail) {
3780
+ switch (kind) {
3781
+ case "session-start":
3782
+ case "session-end":
3783
+ return "idle";
3784
+ case "turn-start":
3785
+ return "running";
3786
+ case "turn-complete":
3787
+ return "turn_complete";
3788
+ case "turn-failed":
3789
+ return detail?.failure === "rate_limit" || detail?.failure === "billing" ? "rate_limited" : "error";
3790
+ case "awaiting-input":
3791
+ return detail?.waiting === "permission" ? "permission_needed" : "running";
3792
+ }
3793
+ }
3794
+ var ENGINE_ACTIVITY_KINDS;
3795
+ var init_hook_events = __esm(() => {
3796
+ ENGINE_ACTIVITY_KINDS = [
3797
+ "session-start",
3798
+ "turn-start",
3799
+ "turn-complete",
3800
+ "turn-failed",
3801
+ "awaiting-input",
3802
+ "session-end"
3803
+ ];
3804
+ });
3805
+
3518
3806
  // src/engine/claude-code-local/normalize.ts
3519
3807
  function normalizeClaudeContent(content) {
3520
3808
  if (typeof content === "string") {
@@ -3562,7 +3850,7 @@ function normalizeClaudeContent(content) {
3562
3850
  }
3563
3851
 
3564
3852
  // 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";
3853
+ import { appendFile as appendFile3, mkdir as mkdir4, readFile as readFile3, readdir, stat, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
3566
3854
  import { homedir as homedir4 } from "os";
3567
3855
  import path3 from "path";
3568
3856
  function encodeCwd(cwd) {
@@ -3633,7 +3921,7 @@ function parseJsonl(raw, sessionId) {
3633
3921
  } catch {
3634
3922
  continue;
3635
3923
  }
3636
- if (!isObject(parsed))
3924
+ if (!isObject2(parsed))
3637
3925
  continue;
3638
3926
  const msg = extractMessage(parsed, sessionId);
3639
3927
  if (msg)
@@ -3642,7 +3930,7 @@ function parseJsonl(raw, sessionId) {
3642
3930
  return out;
3643
3931
  }
3644
3932
  function extractMessage(record, fallbackSessionId) {
3645
- const inner = isObject(record.message) ? record.message : record;
3933
+ const inner = isObject2(record.message) ? record.message : record;
3646
3934
  const role = inner.role;
3647
3935
  if (role !== "user" && role !== "assistant" && role !== "system")
3648
3936
  return null;
@@ -3655,7 +3943,7 @@ function extractMessage(record, fallbackSessionId) {
3655
3943
  return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
3656
3944
  }
3657
3945
  function extractUsage(v) {
3658
- if (!isObject(v))
3946
+ if (!isObject2(v))
3659
3947
  return;
3660
3948
  const inTok = typeof v.input_tokens === "number" ? v.input_tokens : undefined;
3661
3949
  const outTok = typeof v.output_tokens === "number" ? v.output_tokens : undefined;
@@ -3670,7 +3958,7 @@ function extractUsage(v) {
3670
3958
  ...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
3671
3959
  };
3672
3960
  }
3673
- function isObject(v) {
3961
+ function isObject2(v) {
3674
3962
  return typeof v === "object" && v !== null && !Array.isArray(v);
3675
3963
  }
3676
3964
  var defaultDeps;
@@ -3687,7 +3975,7 @@ var init_history = __esm(() => {
3687
3975
  }
3688
3976
  },
3689
3977
  async readFile(p) {
3690
- return await readFile2(p, "utf8");
3978
+ return await readFile3(p, "utf8");
3691
3979
  },
3692
3980
  async pathExists(p) {
3693
3981
  try {
@@ -3714,7 +4002,7 @@ function normalizeCodexContent(raw) {
3714
4002
  blocks.push({ type: "text", text: item });
3715
4003
  continue;
3716
4004
  }
3717
- if (!isObject2(item))
4005
+ if (!isObject3(item))
3718
4006
  continue;
3719
4007
  const t = typeof item.type === "string" ? item.type : undefined;
3720
4008
  if (t === "input_text" || t === "output_text") {
@@ -3728,7 +4016,7 @@ function normalizeCodexContent(raw) {
3728
4016
  }
3729
4017
  return blocks;
3730
4018
  }
3731
- function isObject2(v) {
4019
+ function isObject3(v) {
3732
4020
  return typeof v === "object" && v !== null && !Array.isArray(v);
3733
4021
  }
3734
4022
 
@@ -3778,7 +4066,7 @@ function validPositive(v) {
3778
4066
  }
3779
4067
 
3780
4068
  // src/engine/codex-local/history.ts
3781
- import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
4069
+ import { readFile as readFile4, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
3782
4070
  import { homedir as homedir5 } from "os";
3783
4071
  import path4 from "path";
3784
4072
  async function listRolloutFiles(deps = defaultDeps2) {
@@ -3913,11 +4201,11 @@ function parseJsonl2(raw, sessionId) {
3913
4201
  } catch {
3914
4202
  continue;
3915
4203
  }
3916
- if (!isObject3(parsed))
4204
+ if (!isObject4(parsed))
3917
4205
  continue;
3918
4206
  if (parsed.type !== "response_item")
3919
4207
  continue;
3920
- const payload = isObject3(parsed.payload) ? parsed.payload : undefined;
4208
+ const payload = isObject4(parsed.payload) ? parsed.payload : undefined;
3921
4209
  if (!payload)
3922
4210
  continue;
3923
4211
  const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
@@ -4034,7 +4322,7 @@ function textFromReasoningValue(value) {
4034
4322
  parts.push(entry);
4035
4323
  continue;
4036
4324
  }
4037
- if (!isObject3(entry))
4325
+ if (!isObject4(entry))
4038
4326
  continue;
4039
4327
  const text = typeof entry.text === "string" ? entry.text : "";
4040
4328
  if (text.length > 0)
@@ -4076,14 +4364,14 @@ function deriveCodexUsageMetrics(raw) {
4076
4364
  } catch {
4077
4365
  continue;
4078
4366
  }
4079
- if (!isObject3(parsed))
4367
+ if (!isObject4(parsed))
4080
4368
  continue;
4081
4369
  const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs(parsed.timestamp) : null;
4082
4370
  if (parsed.type === "response_item")
4083
4371
  continue;
4084
4372
  if (parsed.type !== "turn.completed")
4085
4373
  continue;
4086
- const usage = isObject3(parsed.usage) ? parsed.usage : undefined;
4374
+ const usage = isObject4(parsed.usage) ? parsed.usage : undefined;
4087
4375
  if (!usage)
4088
4376
  continue;
4089
4377
  const snapshot = codexUsageToSnapshot(usage);
@@ -4102,7 +4390,7 @@ function parseTimestampMs(value) {
4102
4390
  const ms = new Date(value).getTime();
4103
4391
  return Number.isFinite(ms) ? ms : null;
4104
4392
  }
4105
- function isObject3(v) {
4393
+ function isObject4(v) {
4106
4394
  return typeof v === "object" && v !== null && !Array.isArray(v);
4107
4395
  }
4108
4396
  var defaultDeps2, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
@@ -4120,7 +4408,7 @@ var init_history2 = __esm(() => {
4120
4408
  }
4121
4409
  },
4122
4410
  async readFile(p) {
4123
- return await readFile3(p, "utf8");
4411
+ return await readFile4(p, "utf8");
4124
4412
  },
4125
4413
  stat: stat2
4126
4414
  };
@@ -4129,16 +4417,16 @@ var init_history2 = __esm(() => {
4129
4417
 
4130
4418
  // src/engine/copilot-local/usage.ts
4131
4419
  function copilotUsageToSnapshot(value) {
4132
- if (!isObject4(value))
4420
+ if (!isObject5(value))
4133
4421
  return;
4134
- const modelMetrics = isObject4(value.modelMetrics) ? value.modelMetrics : undefined;
4422
+ const modelMetrics = isObject5(value.modelMetrics) ? value.modelMetrics : undefined;
4135
4423
  if (!modelMetrics)
4136
4424
  return;
4137
4425
  let input = 0;
4138
4426
  let output = 0;
4139
4427
  let cached = 0;
4140
4428
  for (const metrics of Object.values(modelMetrics)) {
4141
- if (!isObject4(metrics) || !isObject4(metrics.usage))
4429
+ if (!isObject5(metrics) || !isObject5(metrics.usage))
4142
4430
  continue;
4143
4431
  input += numberOr2(metrics.usage.inputTokens, 0);
4144
4432
  output += numberOr2(metrics.usage.outputTokens, 0);
@@ -4154,7 +4442,7 @@ function copilotUsageToSnapshot(value) {
4154
4442
  ...context2 > 0 ? { context_tokens: context2 } : {}
4155
4443
  };
4156
4444
  }
4157
- function isObject4(v) {
4445
+ function isObject5(v) {
4158
4446
  return typeof v === "object" && v !== null && !Array.isArray(v);
4159
4447
  }
4160
4448
  function numberOr2(value, fallback) {
@@ -4162,7 +4450,7 @@ function numberOr2(value, fallback) {
4162
4450
  }
4163
4451
 
4164
4452
  // src/engine/copilot-local/history.ts
4165
- import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
4453
+ import { readFile as readFile5, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
4166
4454
  import { homedir as homedir6 } from "os";
4167
4455
  import path5 from "path";
4168
4456
  async function listSessionDirs(deps = defaultDeps3) {
@@ -4259,10 +4547,10 @@ function parseEvents(raw, fallbackSessionId) {
4259
4547
  } catch {
4260
4548
  continue;
4261
4549
  }
4262
- if (!isObject5(record) || typeof record.type !== "string")
4550
+ if (!isObject6(record) || typeof record.type !== "string")
4263
4551
  continue;
4264
4552
  const timestamp = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
4265
- const data = isObject5(record.data) ? record.data : {};
4553
+ const data = isObject6(record.data) ? record.data : {};
4266
4554
  if (record.type === "session.start") {
4267
4555
  const sid = typeof data.sessionId === "string" ? data.sessionId : undefined;
4268
4556
  if (sid)
@@ -4285,7 +4573,7 @@ function parseEvents(raw, fallbackSessionId) {
4285
4573
  blocks.push({ type: "text", text });
4286
4574
  const toolRequests = Array.isArray(data.toolRequests) ? data.toolRequests : [];
4287
4575
  for (const req of toolRequests) {
4288
- if (!isObject5(req))
4576
+ if (!isObject6(req))
4289
4577
  continue;
4290
4578
  const callId = typeof req.id === "string" ? req.id : typeof req.toolCallId === "string" ? req.toolCallId : "tool";
4291
4579
  const name = typeof req.name === "string" ? req.name : typeof req.toolName === "string" ? req.toolName : "tool";
@@ -4322,7 +4610,7 @@ function parseEvents(raw, fallbackSessionId) {
4322
4610
  }
4323
4611
  return { messages, usageMetrics, firstUserMessage };
4324
4612
  }
4325
- function isObject5(v) {
4613
+ function isObject6(v) {
4326
4614
  return typeof v === "object" && v !== null && !Array.isArray(v);
4327
4615
  }
4328
4616
  var defaultDeps3, PREVIEW_CHAR_CAP = 200;
@@ -4342,7 +4630,7 @@ var init_history3 = __esm(() => {
4342
4630
  }
4343
4631
  },
4344
4632
  async readFile(p) {
4345
- return await readFile4(p, "utf8");
4633
+ return await readFile5(p, "utf8");
4346
4634
  },
4347
4635
  stat: stat3,
4348
4636
  async rm(p) {
@@ -4404,90 +4692,6 @@ var init_auto_title = __esm(() => {
4404
4692
  init_history3();
4405
4693
  });
4406
4694
 
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
4695
  // src/tmux/client.ts
4492
4696
  var exports_client = {};
4493
4697
  __export(exports_client, {
@@ -4967,9 +5171,9 @@ class DaemonEventBus {
4967
5171
  }
4968
5172
 
4969
5173
  // src/daemon/server.ts
4970
- import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
5174
+ import { mkdir as mkdir5, readFile as readFile6, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
4971
5175
  import { createServer } from "net";
4972
- import { dirname as dirname4 } from "path";
5176
+ import { dirname as dirname5 } from "path";
4973
5177
  function resolveIdleGraceMs() {
4974
5178
  const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
4975
5179
  if (raw === undefined)
@@ -4977,6 +5181,13 @@ function resolveIdleGraceMs() {
4977
5181
  const n = Number(raw);
4978
5182
  return Number.isFinite(n) && n >= 0 ? n : DEFAULT_IDLE_GRACE_MS;
4979
5183
  }
5184
+ function resolveEngineStateTtlMs() {
5185
+ const raw = process.env.KOBE_ENGINE_STATE_TTL_MS;
5186
+ if (raw === undefined)
5187
+ return DEFAULT_ENGINE_STATE_TTL_MS;
5188
+ const n = Number(raw);
5189
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_ENGINE_STATE_TTL_MS;
5190
+ }
4980
5191
  async function startDaemonServer(orch, options = {}) {
4981
5192
  const socketPath = options.socketPath ?? defaultDaemonSocketPath(options.homeDir);
4982
5193
  const pidPath = options.pidPath ?? defaultDaemonPidPath(options.homeDir);
@@ -5017,8 +5228,30 @@ async function startDaemonServer(orch, options = {}) {
5017
5228
  bus.onPublish((event) => {
5018
5229
  broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
5019
5230
  });
5020
- await mkdir4(dirname4(socketPath), { recursive: true });
5021
- await mkdir4(dirname4(pidPath), { recursive: true });
5231
+ const activity = new Map;
5232
+ const ACTIVITY_STALE_MS = resolveEngineStateTtlMs();
5233
+ function reportActivity(taskId, kind, detail) {
5234
+ const prev = activity.get(taskId);
5235
+ if (prev?.lapse)
5236
+ clearTimeout(prev.lapse);
5237
+ const state = reduceActivity(prev?.state, kind, detail);
5238
+ const at = Date.now();
5239
+ const entry = { state, detail, at };
5240
+ if (state !== "idle") {
5241
+ entry.lapse = setTimeout(() => {
5242
+ const cur = activity.get(taskId);
5243
+ if (cur && cur.at === at) {
5244
+ activity.set(taskId, { state: "idle", at: Date.now() });
5245
+ bus.publish("engine-state", { taskId, state: "idle", at: Date.now() });
5246
+ }
5247
+ }, ACTIVITY_STALE_MS);
5248
+ entry.lapse.unref?.();
5249
+ }
5250
+ activity.set(taskId, entry);
5251
+ bus.publish("engine-state", { taskId, state, ...detail ? { detail } : {}, at });
5252
+ }
5253
+ await mkdir5(dirname5(socketPath), { recursive: true });
5254
+ await mkdir5(dirname5(pidPath), { recursive: true });
5022
5255
  await unlink4(socketPath).catch(() => {});
5023
5256
  const server = createServer((socket) => {
5024
5257
  const client = {
@@ -5088,7 +5321,7 @@ async function startDaemonServer(orch, options = {}) {
5088
5321
  resolve2();
5089
5322
  });
5090
5323
  });
5091
- await writeFile3(pidPath, `${process.pid}
5324
+ await writeFile4(pidPath, `${process.pid}
5092
5325
  `, "utf8");
5093
5326
  async function stopSoon() {
5094
5327
  if (stopping)
@@ -5181,6 +5414,10 @@ async function startDaemonServer(orch, options = {}) {
5181
5414
  case "task.delete": {
5182
5415
  const taskId = requireString(payload, "taskId");
5183
5416
  await orch.deleteTask(taskId, { force: optionalBoolean(payload, "force") });
5417
+ const gone = activity.get(taskId);
5418
+ if (gone?.lapse)
5419
+ clearTimeout(gone.lapse);
5420
+ activity.delete(taskId);
5184
5421
  return {};
5185
5422
  }
5186
5423
  case "task.pin": {
@@ -5218,7 +5455,8 @@ async function startDaemonServer(orch, options = {}) {
5218
5455
  worktreePath: requireString(payload, "worktreePath"),
5219
5456
  branch: optionalString(payload, "branch"),
5220
5457
  vendor: optionalVendor(payload, "vendor"),
5221
- title: optionalString(payload, "title")
5458
+ title: optionalString(payload, "title"),
5459
+ ifExists: optionalString(payload, "ifExists") === "return" ? "return" : "error"
5222
5460
  });
5223
5461
  return { task: serializeTask(task) };
5224
5462
  }
@@ -5226,6 +5464,15 @@ async function startDaemonServer(orch, options = {}) {
5226
5464
  bus.publish("active-task", { taskId: optionalString(payload, "taskId") ?? null });
5227
5465
  return {};
5228
5466
  }
5467
+ case "engine.reportEvent": {
5468
+ const taskId = requireString(payload, "taskId");
5469
+ const kind = requireString(payload, "kind");
5470
+ if (!isEngineActivityKind(kind))
5471
+ throw new Error(`unknown engine event kind: ${kind}`);
5472
+ const detail = optionalActivityDetail(payload);
5473
+ reportActivity(taskId, kind, detail);
5474
+ return {};
5475
+ }
5229
5476
  case "subscribe": {
5230
5477
  client.subscribed = true;
5231
5478
  const role = payload.role === "gui" ? "gui" : "pane";
@@ -5236,6 +5483,15 @@ async function startDaemonServer(orch, options = {}) {
5236
5483
  for (const event of bus.snapshot()) {
5237
5484
  writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
5238
5485
  }
5486
+ for (const [taskId, entry] of activity) {
5487
+ if (entry.state === "idle")
5488
+ continue;
5489
+ writeFrame(client, {
5490
+ type: "event",
5491
+ name: "engine-state",
5492
+ payload: { taskId, state: entry.state, ...entry.detail ? { detail: entry.detail } : {}, at: entry.at }
5493
+ });
5494
+ }
5239
5495
  return {};
5240
5496
  }
5241
5497
  default:
@@ -5286,7 +5542,7 @@ async function startDaemonServer(orch, options = {}) {
5286
5542
  }
5287
5543
  async function readPidFile(pidPath) {
5288
5544
  try {
5289
- const raw = await readFile5(pidPath, "utf8");
5545
+ const raw = await readFile6(pidPath, "utf8");
5290
5546
  const pid = Number(raw.trim());
5291
5547
  return Number.isFinite(pid) ? pid : null;
5292
5548
  } catch {
@@ -5337,13 +5593,29 @@ function optionalVendor(payload, key) {
5337
5593
  }
5338
5594
  return value;
5339
5595
  }
5340
- var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000;
5596
+ function optionalActivityDetail(payload) {
5597
+ const raw = payload.detail;
5598
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
5599
+ return;
5600
+ const d = raw;
5601
+ const out = {};
5602
+ if (d.failure === "rate_limit" || d.failure === "billing" || d.failure === "other")
5603
+ out.failure = d.failure;
5604
+ if (d.waiting === "permission" || d.waiting === "input")
5605
+ out.waiting = d.waiting;
5606
+ if (typeof d.note === "string")
5607
+ out.note = d.note;
5608
+ return Object.keys(out).length > 0 ? out : undefined;
5609
+ }
5610
+ var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000, DEFAULT_ENGINE_STATE_TTL_MS;
5341
5611
  var init_server = __esm(() => {
5612
+ init_hook_events();
5342
5613
  init_version();
5343
5614
  init_auto_title_poller();
5344
5615
  init_paths2();
5345
5616
  init_protocol();
5346
5617
  DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
5618
+ DEFAULT_ENGINE_STATE_TTL_MS = 10 * 60 * 1000;
5347
5619
  });
5348
5620
 
5349
5621
  // src/daemon/lifecycle.ts
@@ -5411,14 +5683,14 @@ __export(exports_daemon_process, {
5411
5683
  connectIfRunning: () => connectIfRunning
5412
5684
  });
5413
5685
  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";
5686
+ import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync } from "fs";
5687
+ import { dirname as dirname6, resolve as resolve2 } from "path";
5416
5688
  import { fileURLToPath as fileURLToPath2 } from "url";
5417
5689
  function spawnDetachedDaemon(command, args, env, logPath) {
5418
5690
  let stdio = "ignore";
5419
5691
  let logFd;
5420
5692
  try {
5421
- mkdirSync2(dirname5(logPath), { recursive: true });
5693
+ mkdirSync2(dirname6(logPath), { recursive: true });
5422
5694
  logFd = openSync(logPath, "a");
5423
5695
  stdio = ["ignore", logFd, logFd];
5424
5696
  } catch {
@@ -5485,12 +5757,12 @@ function resolveKobeSpawn(subcommand) {
5485
5757
  if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
5486
5758
  return [process.execPath, ...subcommand];
5487
5759
  }
5488
- const dir = dirname5(here);
5760
+ const dir = dirname6(here);
5489
5761
  const sourceEntry = resolve2(dir, "../cli/index.ts");
5490
- if (existsSync(sourceEntry))
5762
+ if (existsSync2(sourceEntry))
5491
5763
  return [process.execPath, sourceEntry, ...subcommand];
5492
5764
  const distEntry = resolve2(dir, "../cli/index.js");
5493
- if (existsSync(distEntry))
5765
+ if (existsSync2(distEntry))
5494
5766
  return [process.execPath, distEntry, ...subcommand];
5495
5767
  throw new Error(`kobe: could not locate kobe entry near ${dir}; expected ../cli/index.{ts,js}`);
5496
5768
  }
@@ -5581,14 +5853,14 @@ async function runRepoSubcommand(args) {
5581
5853
  return;
5582
5854
  }
5583
5855
  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");
5856
+ const { existsSync: existsSync3 } = await import("fs");
5857
+ const { join: join5 } = await import("path");
5586
5858
  if (verb === "show") {
5587
5859
  const [pathArg] = rest.filter((a) => !a.startsWith("-"));
5588
5860
  const repo = resolveRepoRoot2(resolve3(process.cwd(), pathArg ?? "."));
5589
5861
  const override = getRepoInitOverride2(repo);
5590
- const hasFileScript = existsSync2(join4(repo, ".kobe", "init.sh"));
5591
- const hasFilePrompt = existsSync2(join4(repo, ".kobe", "init-prompt.md"));
5862
+ const hasFileScript = existsSync3(join5(repo, ".kobe", "init.sh"));
5863
+ const hasFilePrompt = existsSync3(join5(repo, ".kobe", "init-prompt.md"));
5592
5864
  console.log(`repo: ${repo}`);
5593
5865
  console.log(` .kobe/init.sh: ${hasFileScript ? "present (wins)" : "absent"}`);
5594
5866
  console.log(` .kobe/init-prompt.md: ${hasFilePrompt ? "present (wins)" : "absent"}`);
@@ -6247,14 +6519,14 @@ var exports_repo_init = {};
6247
6519
  __export(exports_repo_init, {
6248
6520
  resolveRepoInit: () => resolveRepoInit
6249
6521
  });
6250
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
6251
- import { join as join4 } from "path";
6522
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
6523
+ import { join as join5 } from "path";
6252
6524
  function repoFileScript(worktreePath) {
6253
- return existsSync2(join4(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
6525
+ return existsSync3(join5(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
6254
6526
  }
6255
6527
  function repoFilePrompt(worktreePath) {
6256
- const p = join4(worktreePath, INIT_PROMPT_REL);
6257
- if (!existsSync2(p))
6528
+ const p = join5(worktreePath, INIT_PROMPT_REL);
6529
+ if (!existsSync3(p))
6258
6530
  return;
6259
6531
  try {
6260
6532
  const text = readFileSync3(p, "utf8");
@@ -6275,8 +6547,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
6275
6547
  var INIT_SCRIPT_REL, INIT_PROMPT_REL;
6276
6548
  var init_repo_init = __esm(() => {
6277
6549
  init_repos();
6278
- INIT_SCRIPT_REL = join4(".kobe", "init.sh");
6279
- INIT_PROMPT_REL = join4(".kobe", "init-prompt.md");
6550
+ INIT_SCRIPT_REL = join5(".kobe", "init.sh");
6551
+ INIT_PROMPT_REL = join5(".kobe", "init-prompt.md");
6280
6552
  });
6281
6553
 
6282
6554
  // src/tui/panes/sidebar/worktree-changes.ts
@@ -7288,9 +7560,9 @@ var init_schema = () => {};
7288
7560
 
7289
7561
  // src/tui/context/theme/loader.ts
7290
7562
  import { readFileSync as readFileSync4, readdirSync } from "fs";
7291
- import { join as join5 } from "path";
7563
+ import { join as join6 } from "path";
7292
7564
  function userThemesDir() {
7293
- return join5(kobeStateDir(), "themes");
7565
+ return join6(kobeStateDir(), "themes");
7294
7566
  }
7295
7567
  function loadUserThemes() {
7296
7568
  const dir = userThemesDir();
@@ -7304,7 +7576,7 @@ function loadUserThemes() {
7304
7576
  for (const file of entries) {
7305
7577
  if (!file.endsWith(".json"))
7306
7578
  continue;
7307
- const path6 = join5(dir, file);
7579
+ const path6 = join6(dir, file);
7308
7580
  let parsed;
7309
7581
  try {
7310
7582
  const text = readFileSync4(path6, "utf8");
@@ -7334,8 +7606,8 @@ var exports_theme = {};
7334
7606
  __export(exports_theme, {
7335
7607
  runThemeSubcommand: () => runThemeSubcommand
7336
7608
  });
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";
7609
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
7610
+ import { basename as basename3, join as join7, resolve as resolve5 } from "path";
7339
7611
  function fail2(message) {
7340
7612
  process.stderr.write(`kobe theme: ${message}
7341
7613
  `);
@@ -7366,7 +7638,7 @@ function listThemes() {
7366
7638
  } else {
7367
7639
  for (const f of userFiles) {
7368
7640
  const name = f.slice(0, -".json".length);
7369
- const path6 = join6(dir, f);
7641
+ const path6 = join7(dir, f);
7370
7642
  const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
7371
7643
  lines.push(` ${name}${overridesBundled} ${path6}`);
7372
7644
  }
@@ -7456,8 +7728,8 @@ async function addTheme(args) {
7456
7728
  }
7457
7729
  const dir = userThemesDir();
7458
7730
  mkdirSync3(dir, { recursive: true });
7459
- const dest = join6(dir, `${name}.json`);
7460
- if (existsSync3(dest) && !opts.force) {
7731
+ const dest = join7(dir, `${name}.json`);
7732
+ if (existsSync4(dest) && !opts.force) {
7461
7733
  fail2(`${dest} already exists (pass --force to overwrite)`);
7462
7734
  }
7463
7735
  writeFileSync2(dest, `${JSON.stringify(result.theme, null, 2)}
@@ -7474,8 +7746,8 @@ function removeTheme(args) {
7474
7746
  if (BUNDLED_NAMES.includes(name)) {
7475
7747
  fail2(`"${name}" is a built-in theme and cannot be removed`);
7476
7748
  }
7477
- const dest = join6(userThemesDir(), `${name}.json`);
7478
- if (!existsSync3(dest)) {
7749
+ const dest = join7(userThemesDir(), `${name}.json`);
7750
+ if (!existsSync4(dest)) {
7479
7751
  fail2(`no user theme named "${name}" (looked for ${dest})`);
7480
7752
  }
7481
7753
  unlinkSync(dest);
@@ -7658,9 +7930,9 @@ var init_daemon_cmd = __esm(() => {
7658
7930
  });
7659
7931
 
7660
7932
  // src/lib/skill-install.ts
7661
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
7933
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
7662
7934
  import { homedir as homedir9 } from "os";
7663
- import { join as join7 } from "path";
7935
+ import { join as join8 } from "path";
7664
7936
  function npxSkillsArgv(opts = {}) {
7665
7937
  return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
7666
7938
  }
@@ -7670,14 +7942,14 @@ function npxSkillsCommand(opts = {}) {
7670
7942
  function kobeSkillPaths(opts = {}) {
7671
7943
  const home = opts.home ?? homedir9();
7672
7944
  const cwd = opts.cwd ?? process.cwd();
7673
- return [join7(home, SKILL_REL_PATH), join7(cwd, SKILL_REL_PATH)];
7945
+ return [join8(home, SKILL_REL_PATH), join8(cwd, SKILL_REL_PATH)];
7674
7946
  }
7675
7947
  function parseSkillVersion(content) {
7676
7948
  const m = content.match(/kobe-skill-version:\s*(\d+)/);
7677
7949
  return m ? Number.parseInt(m[1], 10) : null;
7678
7950
  }
7679
7951
  function kobeSkillState(opts) {
7680
- const path6 = kobeSkillPaths(opts).find((p) => existsSync4(p));
7952
+ const path6 = kobeSkillPaths(opts).find((p) => existsSync5(p));
7681
7953
  if (!path6) {
7682
7954
  return { installed: false, installedVersion: null, currentVersion: KOBE_SKILL_VERSION, stale: false };
7683
7955
  }
@@ -7729,9 +8001,9 @@ __export(exports_maintenance, {
7729
8001
  runReloadSubcommand: () => runReloadSubcommand,
7730
8002
  runDoctorSubcommand: () => runDoctorSubcommand
7731
8003
  });
7732
- import { existsSync as existsSync5, readFileSync as readFileSync7, statSync } from "fs";
8004
+ import { existsSync as existsSync6, readFileSync as readFileSync7, statSync } from "fs";
7733
8005
  import { unlink as unlink6 } from "fs/promises";
7734
- import { join as join8 } from "path";
8006
+ import { join as join9 } from "path";
7735
8007
  import { createInterface } from "readline";
7736
8008
  function isProcessAlive2(pid) {
7737
8009
  try {
@@ -7828,7 +8100,7 @@ async function runDoctorSubcommand(argv = []) {
7828
8100
  const socketPath = defaultDaemonSocketPath();
7829
8101
  const pidPath = defaultDaemonPidPath();
7830
8102
  const logPath = defaultDaemonLogPath();
7831
- const tasksPath = join8(kobeStateDir(), "tasks.json");
8103
+ const tasksPath = join9(kobeStateDir(), "tasks.json");
7832
8104
  const statePath2 = kvStatePath();
7833
8105
  const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
7834
8106
  const status = await probeDaemonStatus(socketPath);
@@ -7849,7 +8121,7 @@ async function runDoctorSubcommand(argv = []) {
7849
8121
  } else {
7850
8122
  out.push("daemon: \u2717 not running (no pidfile)");
7851
8123
  }
7852
- if (existsSync5(socketPath))
8124
+ if (existsSync6(socketPath))
7853
8125
  out.push(` orphan socket file present: ${socketPath}`);
7854
8126
  const tail = tailFile(logPath, 8);
7855
8127
  if (tail) {
@@ -7940,7 +8212,7 @@ async function runResetSubcommand(argv) {
7940
8212
  const yes = argv.includes("--yes") || argv.includes("-y");
7941
8213
  const socketPath = defaultDaemonSocketPath();
7942
8214
  const pidPath = defaultDaemonPidPath();
7943
- const tasksPath = join8(kobeStateDir(), "tasks.json");
8215
+ const tasksPath = join9(kobeStateDir(), "tasks.json");
7944
8216
  const statePath2 = kvStatePath();
7945
8217
  console.log("kobe reset will:");
7946
8218
  console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
@@ -8143,6 +8415,175 @@ var init_skill_cmd = __esm(() => {
8143
8415
  SKILL_VERBS = ["install", "status", "command"];
8144
8416
  });
8145
8417
 
8418
+ // src/cli/hook-cmd.ts
8419
+ var exports_hook_cmd = {};
8420
+ __export(exports_hook_cmd, {
8421
+ runHookSubcommand: () => runHookSubcommand
8422
+ });
8423
+ import { homedir as homedir10 } from "os";
8424
+ import { dirname as dirname7, join as join10, resolve as resolve6 } from "path";
8425
+ async function readStdinPayload() {
8426
+ try {
8427
+ const text = await Promise.race([
8428
+ Bun.stdin.text(),
8429
+ new Promise((resolve7) => setTimeout(() => resolve7(""), 500))
8430
+ ]);
8431
+ if (!text.trim())
8432
+ return {};
8433
+ const parsed = JSON.parse(text);
8434
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
8435
+ } catch {
8436
+ return {};
8437
+ }
8438
+ }
8439
+ function failureFromErrorType(errorType) {
8440
+ if (typeof errorType !== "string")
8441
+ return "other";
8442
+ if (errorType === "rate_limit" || errorType === "overloaded")
8443
+ return "rate_limit";
8444
+ if (errorType === "billing_error")
8445
+ return "billing";
8446
+ return "other";
8447
+ }
8448
+ function flagValue(argv, name) {
8449
+ for (let i = 0;i < argv.length; i++) {
8450
+ if (argv[i] === name)
8451
+ return argv[i + 1];
8452
+ if (argv[i].startsWith(`${name}=`))
8453
+ return argv[i].slice(name.length + 1);
8454
+ }
8455
+ return;
8456
+ }
8457
+ async function runHookSubcommand(argv) {
8458
+ const [verb, ...rest] = argv;
8459
+ if (verb === "setup") {
8460
+ await runHookSetup(rest);
8461
+ return;
8462
+ }
8463
+ try {
8464
+ if (verb === "worktree-created") {
8465
+ await reportWorktreeCreated();
8466
+ return;
8467
+ }
8468
+ if (!verb || !isEngineActivityKind(verb))
8469
+ return;
8470
+ const taskId = flagValue(rest, "--task-id");
8471
+ if (!taskId)
8472
+ return;
8473
+ const payload = await readStdinPayload();
8474
+ let detail;
8475
+ if (verb === "turn-failed") {
8476
+ detail = { failure: failureFromErrorType(payload.error_type) };
8477
+ } else if (verb === "awaiting-input") {
8478
+ detail = { waiting: "permission" };
8479
+ }
8480
+ const client = await connectIfRunning();
8481
+ if (!client)
8482
+ return;
8483
+ try {
8484
+ await client.request("engine.reportEvent", { taskId, kind: verb, ...detail ? { detail } : {} });
8485
+ } finally {
8486
+ client.close();
8487
+ }
8488
+ } catch {}
8489
+ }
8490
+ async function reportWorktreeCreated() {
8491
+ const payload = await readStdinPayload();
8492
+ const worktreePath = typeof payload.worktree_path === "string" ? payload.worktree_path : undefined;
8493
+ if (!worktreePath)
8494
+ return;
8495
+ const repo = await deriveRepoRoot(worktreePath);
8496
+ if (!repo)
8497
+ return;
8498
+ const client = await connectIfRunning();
8499
+ if (!client)
8500
+ return;
8501
+ try {
8502
+ await client.request("worktree.adopt", { repo, worktreePath, ifExists: "return" });
8503
+ } finally {
8504
+ client.close();
8505
+ }
8506
+ }
8507
+ async function deriveRepoRoot(worktreePath) {
8508
+ try {
8509
+ const proc = Bun.spawn(["git", "-C", worktreePath, "rev-parse", "--path-format=absolute", "--git-common-dir"], {
8510
+ stdout: "pipe",
8511
+ stderr: "ignore"
8512
+ });
8513
+ const [out, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
8514
+ if (code !== 0)
8515
+ return;
8516
+ const commonDir = out.trim();
8517
+ return commonDir ? dirname7(commonDir) : undefined;
8518
+ } catch {
8519
+ return;
8520
+ }
8521
+ }
8522
+ function syncSettingsPath(scope) {
8523
+ if (scope.kind === "repo")
8524
+ return join10(resolve6(scope.path), ".claude", "settings.json");
8525
+ return join10(homedir10(), ".claude", "settings.json");
8526
+ }
8527
+ function worktreeSyncAdapters() {
8528
+ return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsWorktreeSync());
8529
+ }
8530
+ async function runHookSetup(argv) {
8531
+ if (argv.includes("--help") || argv.includes("-h")) {
8532
+ process.stdout.write([
8533
+ "Usage: kobe hook setup [--global | --repo <path> | --off]",
8534
+ "",
8535
+ "Install (or remove with --off) the hook that syncs an external",
8536
+ "`claude --worktree` into kobe as a task. Default: --global (~/.claude).",
8537
+ ""
8538
+ ].join(`
8539
+ `));
8540
+ return;
8541
+ }
8542
+ const off = argv.includes("--off");
8543
+ const repoIdx = argv.indexOf("--repo");
8544
+ const repoPath = repoIdx !== -1 ? argv[repoIdx + 1] : undefined;
8545
+ if (repoIdx !== -1 && !repoPath) {
8546
+ process.stderr.write(`kobe hook setup: --repo requires a path
8547
+ `);
8548
+ process.exit(2);
8549
+ }
8550
+ const adapters = worktreeSyncAdapters();
8551
+ if (adapters.length === 0) {
8552
+ process.stdout.write(`kobe hook setup: no engine supports external worktree sync \u2014 nothing to do
8553
+ `);
8554
+ return;
8555
+ }
8556
+ if (off) {
8557
+ const prev = getPersistedString(SYNC_SETTING_KEY);
8558
+ const path7 = prev?.startsWith("repo:") ? syncSettingsPath({ kind: "repo", path: prev.slice(5) }) : syncSettingsPath({ kind: "global" });
8559
+ for (const a of adapters)
8560
+ await a.removeWorktreeSyncHook(path7);
8561
+ setPersistedString(SYNC_SETTING_KEY, "off");
8562
+ process.stdout.write(`kobe hook setup: external worktree sync disabled (removed from ${path7})
8563
+ `);
8564
+ return;
8565
+ }
8566
+ const scope = repoPath ? { kind: "repo", path: repoPath } : { kind: "global" };
8567
+ const path6 = syncSettingsPath(scope);
8568
+ for (const a of adapters)
8569
+ await a.installWorktreeSyncHook(path6);
8570
+ setPersistedString(SYNC_SETTING_KEY, scope.kind === "repo" ? `repo:${resolve6(scope.path)}` : "global");
8571
+ process.stdout.write([
8572
+ `kobe hook setup: external worktree sync enabled (${scope.kind}) \u2014 wrote ${path6}`,
8573
+ "New `claude --worktree` worktrees will appear as kobe tasks.",
8574
+ ""
8575
+ ].join(`
8576
+ `));
8577
+ }
8578
+ var SYNC_SETTING_KEY = "externalWorktreeSync";
8579
+ var init_hook_cmd = __esm(() => {
8580
+ init_daemon_process();
8581
+ init_hook_adapter2();
8582
+ init_hook_events();
8583
+ init_repos();
8584
+ init_vendor();
8585
+ });
8586
+
8146
8587
  // ../../node_modules/.bun/entities@7.0.1/node_modules/entities/dist/esm/decode-codepoint.js
8147
8588
  function replaceCodePoint(codePoint) {
8148
8589
  var _a2;
@@ -9428,6 +9869,8 @@ class RemoteOrchestrator {
9428
9869
  setActiveTaskSig;
9429
9870
  updateAcc;
9430
9871
  setUpdateSig;
9872
+ engineStateAcc;
9873
+ setEngineStateSig;
9431
9874
  connectionStateAcc;
9432
9875
  setConnectionState;
9433
9876
  ensureReachable;
@@ -9438,6 +9881,7 @@ class RemoteOrchestrator {
9438
9881
  const [tasks, setTasks] = createSignal([]);
9439
9882
  const [activeTask, setActiveTask] = createSignal(null);
9440
9883
  const [update, setUpdate] = createSignal(null);
9884
+ const [engineState, setEngineState] = createSignal(new Map);
9441
9885
  const [connectionState, setConnectionState] = createSignal("online");
9442
9886
  this.tasksAcc = tasks;
9443
9887
  this.setTasks = (next) => setTasks(() => next);
@@ -9445,6 +9889,8 @@ class RemoteOrchestrator {
9445
9889
  this.setActiveTaskSig = (next) => setActiveTask(() => next);
9446
9890
  this.updateAcc = update;
9447
9891
  this.setUpdateSig = (next) => setUpdate(() => next);
9892
+ this.engineStateAcc = engineState;
9893
+ this.setEngineStateSig = (next) => setEngineState(() => next);
9448
9894
  this.connectionStateAcc = connectionState;
9449
9895
  this.setConnectionState = (next) => setConnectionState(() => next);
9450
9896
  this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
@@ -9523,6 +9969,9 @@ class RemoteOrchestrator {
9523
9969
  updateSignal() {
9524
9970
  return this.updateAcc;
9525
9971
  }
9972
+ engineStateSignal() {
9973
+ return this.engineStateAcc;
9974
+ }
9526
9975
  listTasks() {
9527
9976
  return this.tasksAcc();
9528
9977
  }
@@ -9613,6 +10062,18 @@ class RemoteOrchestrator {
9613
10062
  this.setUpdateSig(info ?? null);
9614
10063
  return;
9615
10064
  }
10065
+ if (name === "engine-state") {
10066
+ const p = payload;
10067
+ if (typeof p?.taskId !== "string" || typeof p.state !== "string")
10068
+ return;
10069
+ const next = new Map(this.engineStateAcc());
10070
+ if (p.state === "idle")
10071
+ next.delete(p.taskId);
10072
+ else
10073
+ next.set(p.taskId, { state: p.state, detail: p.detail, at: typeof p.at === "number" ? p.at : 0 });
10074
+ this.setEngineStateSig(next);
10075
+ return;
10076
+ }
9616
10077
  }
9617
10078
  }
9618
10079
  function deserializeTask(s) {
@@ -11298,7 +11759,7 @@ function addTheme2(name, theme) {
11298
11759
  }
11299
11760
  function resolveTheme(theme, mode = "dark") {
11300
11761
  const defs = theme.defs ?? {};
11301
- function resolve6(c, chain = []) {
11762
+ function resolve7(c, chain = []) {
11302
11763
  if (typeof c === "string") {
11303
11764
  if (c === "transparent" || c === "none")
11304
11765
  return RGBA.fromInts(0, 0, 0, 0);
@@ -11310,13 +11771,13 @@ function resolveTheme(theme, mode = "dark") {
11310
11771
  const next = defs[c] ?? theme.theme[c];
11311
11772
  if (next === undefined)
11312
11773
  return RGBA.fromInts(0, 0, 0);
11313
- return resolve6(next, [...chain, c]);
11774
+ return resolve7(next, [...chain, c]);
11314
11775
  }
11315
- return resolve6(c[mode], chain);
11776
+ return resolve7(c[mode], chain);
11316
11777
  }
11317
11778
  const out = {};
11318
11779
  for (const [k, v] of Object.entries(theme.theme)) {
11319
- out[k] = resolve6(v);
11780
+ out[k] = resolve7(v);
11320
11781
  }
11321
11782
  const text = out.text ?? RGBA.fromHex("#ffffff");
11322
11783
  const background = out.background ?? RGBA.fromHex("#000000");
@@ -12095,7 +12556,7 @@ function findAvailableFolderName(parentDir, base) {
12095
12556
  return trimmed;
12096
12557
  }
12097
12558
  function cloneRepo(url, target, onProgress) {
12098
- return new Promise((resolve6) => {
12559
+ return new Promise((resolve7) => {
12099
12560
  let stderrBuf = "";
12100
12561
  try {
12101
12562
  const child = spawn2("git", ["clone", "--progress", url, target], {
@@ -12112,18 +12573,18 @@ function cloneRepo(url, target, onProgress) {
12112
12573
  }
12113
12574
  });
12114
12575
  child.on("error", (err) => {
12115
- resolve6({ ok: false, error: err.message });
12576
+ resolve7({ ok: false, error: err.message });
12116
12577
  });
12117
12578
  child.on("close", (code) => {
12118
12579
  if (code === 0) {
12119
- resolve6({ ok: true, path: target });
12580
+ resolve7({ ok: true, path: target });
12120
12581
  return;
12121
12582
  }
12122
12583
  const tail = stderrBuf.split(/[\r\n]+/).filter((s) => s.trim().length > 0).pop() ?? `git clone exited with ${code}`;
12123
- resolve6({ ok: false, error: tail });
12584
+ resolve7({ ok: false, error: tail });
12124
12585
  });
12125
12586
  } catch (err) {
12126
- resolve6({ ok: false, error: err instanceof Error ? err.message : String(err) });
12587
+ resolve7({ ok: false, error: err instanceof Error ? err.message : String(err) });
12127
12588
  }
12128
12589
  });
12129
12590
  }
@@ -13186,7 +13647,7 @@ var init_dialog2 = __esm(() => {
13186
13647
 
13187
13648
  // src/tui/component/new-task-dialog/index.tsx
13188
13649
  function show(dialog, defaultRepo, savedRepos, options) {
13189
- return new Promise((resolve6) => {
13650
+ return new Promise((resolve7) => {
13190
13651
  dialog.replace(() => createComponent2(NewTaskDialogView, {
13191
13652
  defaultRepo,
13192
13653
  savedRepos,
@@ -13199,9 +13660,9 @@ function show(dialog, defaultRepo, savedRepos, options) {
13199
13660
  get discoverAdoptable() {
13200
13661
  return options?.discoverAdoptable;
13201
13662
  },
13202
- onSubmit: (v) => resolve6(v),
13203
- onCancel: () => resolve6(undefined)
13204
- }), () => resolve6(undefined));
13663
+ onSubmit: (v) => resolve7(v),
13664
+ onCancel: () => resolve7(undefined)
13665
+ }), () => resolve7(undefined));
13205
13666
  dialog.setSize("medium");
13206
13667
  });
13207
13668
  }
@@ -13293,15 +13754,15 @@ var init_dialog3 = __esm(() => {
13293
13754
 
13294
13755
  // src/tui/component/rename-task-dialog/index.tsx
13295
13756
  function show2(dialog, currentTitle, opts = {}) {
13296
- return new Promise((resolve6) => {
13757
+ return new Promise((resolve7) => {
13297
13758
  dialog.replace(() => createComponent2(RenameTaskDialogView, {
13298
13759
  currentTitle,
13299
13760
  get dialogTitle() {
13300
13761
  return opts.dialogTitle;
13301
13762
  },
13302
- onSubmit: (v) => resolve6(v),
13303
- onCancel: () => resolve6(undefined)
13304
- }), () => resolve6(undefined));
13763
+ onSubmit: (v) => resolve7(v),
13764
+ onCancel: () => resolve7(undefined)
13765
+ }), () => resolve7(undefined));
13305
13766
  });
13306
13767
  }
13307
13768
  var RenameTaskDialog;
@@ -13315,8 +13776,8 @@ var init_rename_task_dialog = __esm(() => {
13315
13776
 
13316
13777
  // src/engine/claude-code-local/binary.ts
13317
13778
  import { spawnSync as spawnSync5 } from "child_process";
13318
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
13319
- import { homedir as homedir11 } from "os";
13779
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
13780
+ import { homedir as homedir12 } from "os";
13320
13781
  import path7 from "path";
13321
13782
  async function findClaudeBinary(deps = defaultDeps4) {
13322
13783
  const checked = [];
@@ -13389,7 +13850,7 @@ var init_binary = __esm(() => {
13389
13850
  return process.env[name];
13390
13851
  },
13391
13852
  home() {
13392
- return homedir11();
13853
+ return homedir12();
13393
13854
  },
13394
13855
  which(name) {
13395
13856
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -13402,7 +13863,7 @@ var init_binary = __esm(() => {
13402
13863
  return;
13403
13864
  if (first.startsWith("claude:") && first.includes("aliased to")) {
13404
13865
  const aliasTarget = first.split("aliased to")[1]?.trim();
13405
- return aliasTarget && existsSync7(aliasTarget) ? aliasTarget : undefined;
13866
+ return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
13406
13867
  }
13407
13868
  return first;
13408
13869
  },
@@ -13419,8 +13880,8 @@ var init_binary = __esm(() => {
13419
13880
 
13420
13881
  // src/engine/codex-local/binary.ts
13421
13882
  import { spawnSync as spawnSync6 } from "child_process";
13422
- import { existsSync as existsSync8, statSync as statSync4 } from "fs";
13423
- import { homedir as homedir12 } from "os";
13883
+ import { existsSync as existsSync9, statSync as statSync4 } from "fs";
13884
+ import { homedir as homedir13 } from "os";
13424
13885
  import path8 from "path";
13425
13886
  async function findCodexBinary(deps = defaultDeps5) {
13426
13887
  const checked = [];
@@ -13477,7 +13938,7 @@ var init_binary2 = __esm(() => {
13477
13938
  return process.env[name];
13478
13939
  },
13479
13940
  home() {
13480
- return homedir12();
13941
+ return homedir13();
13481
13942
  },
13482
13943
  which(name) {
13483
13944
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -13490,7 +13951,7 @@ var init_binary2 = __esm(() => {
13490
13951
  return;
13491
13952
  if (first.startsWith("codex:") && first.includes("aliased to")) {
13492
13953
  const aliasTarget = first.split("aliased to")[1]?.trim();
13493
- return aliasTarget && existsSync8(aliasTarget) ? aliasTarget : undefined;
13954
+ return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
13494
13955
  }
13495
13956
  return first;
13496
13957
  },
@@ -13507,8 +13968,8 @@ var init_binary2 = __esm(() => {
13507
13968
 
13508
13969
  // src/engine/copilot-local/binary.ts
13509
13970
  import { spawnSync as spawnSync7 } from "child_process";
13510
- import { existsSync as existsSync9, statSync as statSync5 } from "fs";
13511
- import { homedir as homedir13 } from "os";
13971
+ import { existsSync as existsSync10, statSync as statSync5 } from "fs";
13972
+ import { homedir as homedir14 } from "os";
13512
13973
  import path9 from "path";
13513
13974
  async function findCopilotBinary(deps = defaultDeps6) {
13514
13975
  const checked = [];
@@ -13589,7 +14050,7 @@ var init_binary3 = __esm(() => {
13589
14050
  return process.env[name];
13590
14051
  },
13591
14052
  home() {
13592
- return homedir13();
14053
+ return homedir14();
13593
14054
  },
13594
14055
  which(name) {
13595
14056
  const cmd = process.platform === "win32" ? "where" : "which";
@@ -13602,7 +14063,7 @@ var init_binary3 = __esm(() => {
13602
14063
  return;
13603
14064
  if (first.startsWith("copilot:") && first.includes("aliased to")) {
13604
14065
  const aliasTarget = first.split("aliased to")[1]?.trim();
13605
- return aliasTarget && existsSync9(aliasTarget) ? aliasTarget : undefined;
14066
+ return aliasTarget && existsSync10(aliasTarget) ? aliasTarget : undefined;
13606
14067
  }
13607
14068
  return first;
13608
14069
  },
@@ -13614,7 +14075,7 @@ var init_binary3 = __esm(() => {
13614
14075
 
13615
14076
  // src/engine/account-detect.ts
13616
14077
  import { readFileSync as readFileSync8, statSync as statSync6 } from "fs";
13617
- import { homedir as homedir14 } from "os";
14078
+ import { homedir as homedir15 } from "os";
13618
14079
  import path10 from "path";
13619
14080
  function claudeGlobalConfigPath(env, home) {
13620
14081
  const override = env("CLAUDE_CONFIG_DIR")?.trim();
@@ -13832,7 +14293,7 @@ var init_account_detect = __esm(() => {
13832
14293
  return process.env[name];
13833
14294
  },
13834
14295
  home() {
13835
- return homedir14();
14296
+ return homedir15();
13836
14297
  },
13837
14298
  findClaudeBinary() {
13838
14299
  return findClaudeBinary();
@@ -13971,18 +14432,18 @@ var init_dialog_confirm = __esm(() => {
13971
14432
  init_keymap();
13972
14433
  init_dialog();
13973
14434
  DialogConfirm.show = (dialog, title, message, label, confirmLabel, options) => {
13974
- return new Promise((resolve6) => {
14435
+ return new Promise((resolve7) => {
13975
14436
  dialog.replace(() => createComponent2(DialogConfirm, {
13976
14437
  title,
13977
14438
  message,
13978
- onConfirm: () => resolve6(true),
13979
- onCancel: () => resolve6(false),
14439
+ onConfirm: () => resolve7(true),
14440
+ onCancel: () => resolve7(false),
13980
14441
  label,
13981
14442
  confirmLabel,
13982
14443
  get initialActive() {
13983
14444
  return options?.initialActive;
13984
14445
  }
13985
- }), () => resolve6(undefined));
14446
+ }), () => resolve7(undefined));
13986
14447
  dialog.setSize("small");
13987
14448
  });
13988
14449
  };
@@ -13990,7 +14451,7 @@ var init_dialog_confirm = __esm(() => {
13990
14451
 
13991
14452
  // src/tui/component/settings-dialog/actions.ts
13992
14453
  import { unlinkSync as unlinkSync2 } from "fs";
13993
- import { join as join10 } from "path";
14454
+ import { join as join12 } from "path";
13994
14455
  function hasRestartableDaemon(orchestrator) {
13995
14456
  return orchestrator instanceof RemoteOrchestrator;
13996
14457
  }
@@ -14007,7 +14468,7 @@ async function confirmResetState(dialog, kv, renderer) {
14007
14468
  return;
14008
14469
  kv.clear();
14009
14470
  try {
14010
- unlinkSync2(join10(homeDir(), ".kobe", "tasks.json"));
14471
+ unlinkSync2(join12(homeDir(), ".kobe", "tasks.json"));
14011
14472
  } catch (err) {
14012
14473
  if (err.code !== "ENOENT") {
14013
14474
  console.error("kobe: failed to delete tasks.json during reset:", err);
@@ -15298,17 +15759,17 @@ var init_settings_dialog = __esm(() => {
15298
15759
  init_sections();
15299
15760
  SettingsDialog.show = (dialog, kv, orchestrator) => {
15300
15761
  let visualPrefsChanged = false;
15301
- return new Promise((resolve6) => {
15762
+ return new Promise((resolve7) => {
15302
15763
  dialog.replace(() => createComponent2(SettingsDialog, {
15303
15764
  kv,
15304
15765
  orchestrator,
15305
15766
  onVisualPrefsChange: () => {
15306
15767
  visualPrefsChanged = true;
15307
15768
  },
15308
- onClose: () => resolve6({
15769
+ onClose: () => resolve7({
15309
15770
  visualPrefsChanged
15310
15771
  })
15311
- }), () => resolve6({
15772
+ }), () => resolve7({
15312
15773
  visualPrefsChanged
15313
15774
  }));
15314
15775
  });
@@ -15378,7 +15839,7 @@ var init_focus = __esm(() => {
15378
15839
 
15379
15840
  // src/tui/context/kv.tsx
15380
15841
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
15381
- import { dirname as dirname6 } from "path";
15842
+ import { dirname as dirname8 } from "path";
15382
15843
  function loadInitial() {
15383
15844
  const statePath2 = kvStatePath();
15384
15845
  try {
@@ -15406,7 +15867,7 @@ var init_kv = __esm(() => {
15406
15867
  function writeNow(label) {
15407
15868
  const statePath2 = kvStatePath();
15408
15869
  try {
15409
- mkdirSync4(dirname6(statePath2), {
15870
+ mkdirSync4(dirname8(statePath2), {
15410
15871
  recursive: true
15411
15872
  });
15412
15873
  const tmp = `${statePath2}.tmp`;
@@ -15461,7 +15922,7 @@ var init_kv = __esm(() => {
15461
15922
  }
15462
15923
  const statePath2 = kvStatePath();
15463
15924
  try {
15464
- mkdirSync4(dirname6(statePath2), {
15925
+ mkdirSync4(dirname8(statePath2), {
15465
15926
  recursive: true
15466
15927
  });
15467
15928
  const tmp = `${statePath2}.tmp`;
@@ -15497,8 +15958,8 @@ var init_persisted_ui_prefs = __esm(() => {
15497
15958
 
15498
15959
  // src/tui/lib/worktree-opener.ts
15499
15960
  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";
15961
+ import { existsSync as existsSync11 } from "fs";
15962
+ import { basename as basename4, delimiter, isAbsolute, join as join13 } from "path";
15502
15963
  function executableOnPath(command, env, exists) {
15503
15964
  if (isAbsolute(command))
15504
15965
  return exists(command);
@@ -15506,7 +15967,7 @@ function executableOnPath(command, env, exists) {
15506
15967
  for (const dir of pathEnv.split(delimiter)) {
15507
15968
  if (!dir)
15508
15969
  continue;
15509
- if (exists(join11(dir, command)))
15970
+ if (exists(join13(dir, command)))
15510
15971
  return true;
15511
15972
  }
15512
15973
  return false;
@@ -15526,7 +15987,7 @@ function labelForOverride(command) {
15526
15987
  function detectWorktreeOpener(deps = {}) {
15527
15988
  const env = deps.env ?? process.env;
15528
15989
  const platform = deps.platform ?? process.platform;
15529
- const exists = deps.exists ?? existsSync10;
15990
+ const exists = deps.exists ?? existsSync11;
15530
15991
  const override = env.KOBE_OPEN_EDITOR?.trim();
15531
15992
  if (override) {
15532
15993
  return { id: "env", label: labelForOverride(override), command: override, args: [] };
@@ -16861,7 +17322,34 @@ function Sidebar(props) {
16861
17322
  return readWorktreeChanges(task.worktreePath);
16862
17323
  });
16863
17324
  const titleText = isMain ? repoBasename(task.repo) : task.title;
16864
- const loading = () => isLive() || !isMain && task.status === "in_progress";
17325
+ const activity = () => props.engineState?.().get(task.id)?.state;
17326
+ const loading = () => activity() === "running" || isLive() || !isMain && task.status === "in_progress";
17327
+ const activityChip = () => {
17328
+ switch (activity()) {
17329
+ case "rate_limited":
17330
+ return {
17331
+ text: "limited",
17332
+ tone: "warning"
17333
+ };
17334
+ case "permission_needed":
17335
+ return {
17336
+ text: "approve?",
17337
+ tone: "warning"
17338
+ };
17339
+ case "error":
17340
+ return {
17341
+ text: "error",
17342
+ tone: "error"
17343
+ };
17344
+ case "turn_complete":
17345
+ return {
17346
+ text: "done",
17347
+ tone: "primary"
17348
+ };
17349
+ default:
17350
+ return null;
17351
+ }
17352
+ };
16865
17353
  const subtitleText = createMemo(() => {
16866
17354
  if (task.branch.length > 0)
16867
17355
  return truncateBranchLabel(task.branch, subtitleBudget());
@@ -17024,6 +17512,18 @@ function Sidebar(props) {
17024
17512
  return _el$43;
17025
17513
  }
17026
17514
  }), null);
17515
+ insert(_el$40, createComponent2(Show, {
17516
+ get when() {
17517
+ return memo2(() => !!!loading())() && activityChip();
17518
+ },
17519
+ children: (chip) => (() => {
17520
+ var _el$55 = createElement("text");
17521
+ setProp(_el$55, "wrapMode", "none");
17522
+ insert(_el$55, () => chip().text);
17523
+ effect((_$p) => setProp(_el$55, "fg", chip().tone === "error" ? theme.error : chip().tone === "warning" ? theme.warning : theme.primary, _$p));
17524
+ return _el$55;
17525
+ })()
17526
+ }), null);
17027
17527
  effect((_p$) => {
17028
17528
  var _v$22 = barColor(), _v$23 = badgeColor(), _v$24 = TextAttributes8.BOLD, _v$25 = theme.text, _v$26 = isSelected() || isCursor() ? TextAttributes8.BOLD : undefined;
17029
17529
  _v$22 !== _p$.e && (_p$.e = setProp(_el$39, "fg", _v$22, _p$.e));
@@ -17169,43 +17669,43 @@ function Sidebar(props) {
17169
17669
  const left = createMemo(() => Math.max(0, Math.min(h().x + 2, dims().width - boxW() - 1)));
17170
17670
  const top = createMemo(() => Math.max(0, Math.min(h().y + 1, dims().height - boxH() - 1)));
17171
17671
  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, {
17672
+ var _el$56 = createElement("box");
17673
+ setProp(_el$56, "position", "absolute");
17674
+ setProp(_el$56, "zIndex", 2600);
17675
+ setProp(_el$56, "flexDirection", "column");
17676
+ setProp(_el$56, "border", true);
17677
+ setProp(_el$56, "paddingLeft", 1);
17678
+ setProp(_el$56, "paddingRight", 1);
17679
+ insert(_el$56, createComponent2(For, {
17180
17680
  get each() {
17181
17681
  return lines();
17182
17682
  },
17183
17683
  children: (l) => (() => {
17184
- var _el$56 = createElement("text");
17185
- setProp(_el$56, "wrapMode", "none");
17186
- insert(_el$56, (() => {
17684
+ var _el$57 = createElement("text");
17685
+ setProp(_el$57, "wrapMode", "none");
17686
+ insert(_el$57, (() => {
17187
17687
  var _c$4 = memo2(() => !!l.dim);
17188
17688
  return () => _c$4() ? truncatePathTail(l.text, innerW()) : truncateTitle(l.text, innerW());
17189
17689
  })());
17190
17690
  effect((_p$) => {
17191
17691
  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));
17692
+ _v$35 !== _p$.e && (_p$.e = setProp(_el$57, "fg", _v$35, _p$.e));
17693
+ _v$36 !== _p$.t && (_p$.t = setProp(_el$57, "attributes", _v$36, _p$.t));
17194
17694
  return _p$;
17195
17695
  }, {
17196
17696
  e: undefined,
17197
17697
  t: undefined
17198
17698
  });
17199
- return _el$56;
17699
+ return _el$57;
17200
17700
  })()
17201
17701
  }));
17202
17702
  effect((_p$) => {
17203
17703
  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));
17704
+ _v$30 !== _p$.e && (_p$.e = setProp(_el$56, "left", _v$30, _p$.e));
17705
+ _v$31 !== _p$.t && (_p$.t = setProp(_el$56, "top", _v$31, _p$.t));
17706
+ _v$32 !== _p$.a && (_p$.a = setProp(_el$56, "width", _v$32, _p$.a));
17707
+ _v$33 !== _p$.o && (_p$.o = setProp(_el$56, "borderColor", _v$33, _p$.o));
17708
+ _v$34 !== _p$.i && (_p$.i = setProp(_el$56, "backgroundColor", _v$34, _p$.i));
17209
17709
  return _p$;
17210
17710
  }, {
17211
17711
  e: undefined,
@@ -17214,7 +17714,7 @@ function Sidebar(props) {
17214
17714
  o: undefined,
17215
17715
  i: undefined
17216
17716
  });
17217
- return _el$55;
17717
+ return _el$56;
17218
17718
  })();
17219
17719
  }
17220
17720
  }), null);
@@ -17300,7 +17800,7 @@ var exports_host = {};
17300
17800
  __export(exports_host, {
17301
17801
  startTasksPane: () => startTasksPane
17302
17802
  });
17303
- import { existsSync as existsSync11 } from "fs";
17803
+ import { existsSync as existsSync12 } from "fs";
17304
17804
  import { TextAttributes as TextAttributes9 } from "@opentui/core";
17305
17805
  function TasksShell(props) {
17306
17806
  const themeCtx = useTheme();
@@ -17535,7 +18035,7 @@ function TasksShell(props) {
17535
18035
  async function openSelectedWorktree(id) {
17536
18036
  const task = props.tasks().find((t) => t.id === id);
17537
18037
  let worktree = task?.worktreePath;
17538
- if (!worktree || !existsSync11(worktree)) {
18038
+ if (!worktree || !existsSync12(worktree)) {
17539
18039
  if (!props.orch) {
17540
18040
  console.error("[kobe tasks] no daemon; cannot materialise worktree");
17541
18041
  return;
@@ -17548,7 +18048,7 @@ function TasksShell(props) {
17548
18048
  }
17549
18049
  await props.reload();
17550
18050
  }
17551
- if (!worktree || !existsSync11(worktree))
18051
+ if (!worktree || !existsSync12(worktree))
17552
18052
  return;
17553
18053
  const opener = detectWorktreeOpener();
17554
18054
  if (!opener) {
@@ -17599,7 +18099,7 @@ function TasksShell(props) {
17599
18099
  const exists = await sessionExists(name);
17600
18100
  if (exists) {
17601
18101
  const cwd2 = await getSessionOption(name, "@kobe_worktree") || task?.worktreePath || "";
17602
- if (cwd2 && existsSync11(cwd2)) {
18102
+ if (cwd2 && existsSync12(cwd2)) {
17603
18103
  await ensureSession({
17604
18104
  name,
17605
18105
  cwd: cwd2,
@@ -17613,7 +18113,7 @@ function TasksShell(props) {
17613
18113
  return;
17614
18114
  }
17615
18115
  let cwd = task?.worktreePath;
17616
- if (!cwd || !existsSync11(cwd)) {
18116
+ if (!cwd || !existsSync12(cwd)) {
17617
18117
  if (!props.orch) {
17618
18118
  console.error("[kobe tasks] no daemon; cannot materialise worktree");
17619
18119
  return;
@@ -17626,7 +18126,7 @@ function TasksShell(props) {
17626
18126
  }
17627
18127
  await props.reload();
17628
18128
  }
17629
- if (!cwd || !existsSync11(cwd))
18129
+ if (!cwd || !existsSync12(cwd))
17630
18130
  return;
17631
18131
  const init2 = task?.repo ? resolveRepoInit(task.repo, cwd) : {};
17632
18132
  const ready = await ensureSession({
@@ -17675,6 +18175,9 @@ function TasksShell(props) {
17675
18175
  headerStatus,
17676
18176
  onHeaderStatusClick: () => void openUpdate(),
17677
18177
  width: () => dimensions().width,
18178
+ get engineState() {
18179
+ return memo2(() => !!props.orch)() ? props.orch.engineStateSignal() : undefined;
18180
+ },
17678
18181
  onAddTask: () => void createTask(),
17679
18182
  onRenameRequest: (id) => void renameTask(id),
17680
18183
  onDeleteRequest: (id) => void deleteTask(id),
@@ -17894,6 +18397,7 @@ var init_host = __esm(() => {
17894
18397
  init_solid();
17895
18398
  init_solid();
17896
18399
  init_solid();
18400
+ init_solid();
17897
18401
  init_client2();
17898
18402
  init_solid();
17899
18403
  init_dev();
@@ -18244,13 +18748,13 @@ function releaseBodyLines(body) {
18244
18748
  function waitForKeypress() {
18245
18749
  if (!process.stdin.isTTY)
18246
18750
  return Promise.resolve();
18247
- return new Promise((resolve6) => {
18751
+ return new Promise((resolve7) => {
18248
18752
  const stdin = process.stdin;
18249
18753
  const done = () => {
18250
18754
  stdin.off("data", done);
18251
18755
  stdin.setRawMode?.(false);
18252
18756
  stdin.pause();
18253
- resolve6();
18757
+ resolve7();
18254
18758
  };
18255
18759
  stdin.setRawMode?.(true);
18256
18760
  stdin.resume();
@@ -18316,7 +18820,7 @@ function UpdatePage() {
18316
18820
  }
18317
18821
  async function runUpdater() {
18318
18822
  setStatus("Leaving the TUI page and running the updater in this tmux window...");
18319
- await new Promise((resolve6) => setTimeout(resolve6, 30));
18823
+ await new Promise((resolve7) => setTimeout(resolve7, 30));
18320
18824
  renderer?.destroy();
18321
18825
  process.stdout.write(`
18322
18826
  kobe ${CURRENT_VERSION} -> latest
@@ -18623,7 +19127,7 @@ var init_host4 = __esm(() => {
18623
19127
  });
18624
19128
 
18625
19129
  // src/engine/turn-detector.ts
18626
- import { readFile as readFile6 } from "fs/promises";
19130
+ import { readFile as readFile7 } from "fs/promises";
18627
19131
 
18628
19132
  class EngineTurnDetector {
18629
19133
  supportsCompletionMarkers() {
@@ -18646,7 +19150,7 @@ function latestClaudeCompletionMarkerFromJsonl(raw, sourceId = "claude", fallbac
18646
19150
  const record = parseJsonLine(line);
18647
19151
  if (!record)
18648
19152
  continue;
18649
- const inner = isObject6(record.message) ? record.message : record;
19153
+ const inner = isObject7(record.message) ? record.message : record;
18650
19154
  if (inner.role !== "assistant")
18651
19155
  continue;
18652
19156
  if (!("content" in inner))
@@ -18688,7 +19192,7 @@ function parseJsonLine(line) {
18688
19192
  return null;
18689
19193
  try {
18690
19194
  const parsed = JSON.parse(trimmed);
18691
- return isObject6(parsed) ? parsed : null;
19195
+ return isObject7(parsed) ? parsed : null;
18692
19196
  } catch {
18693
19197
  return null;
18694
19198
  }
@@ -18697,7 +19201,7 @@ function timestampFromRecord(record, fallback) {
18697
19201
  const ts = typeof record.timestamp === "string" ? Date.parse(record.timestamp) : Number.NaN;
18698
19202
  return Number.isFinite(ts) ? ts : fallback;
18699
19203
  }
18700
- function isObject6(v) {
19204
+ function isObject7(v) {
18701
19205
  return typeof v === "object" && v !== null && !Array.isArray(v);
18702
19206
  }
18703
19207
  var ClaudeTurnDetector, CodexTurnDetector, UnknownTurnDetector;
@@ -18710,7 +19214,7 @@ var init_turn_detector = __esm(() => {
18710
19214
  const files = await listSessionFilesForWorktree(worktree);
18711
19215
  let latest = null;
18712
19216
  for (const file of files.slice(0, 4)) {
18713
- const raw = await readFile6(file.path, "utf8").catch(() => "");
19217
+ const raw = await readFile7(file.path, "utf8").catch(() => "");
18714
19218
  const marker = latestClaudeCompletionMarkerFromJsonl(raw, file.path, file.mtimeMs);
18715
19219
  if (marker && (!latest || marker.timestampMs > latest.timestampMs))
18716
19220
  latest = marker;
@@ -18729,7 +19233,7 @@ var init_turn_detector = __esm(() => {
18729
19233
  if (scanned >= 12)
18730
19234
  break;
18731
19235
  scanned++;
18732
- const raw = await readFile6(file, "utf8").catch(() => "");
19236
+ const raw = await readFile7(file, "utf8").catch(() => "");
18733
19237
  if (!raw || rolloutCwd(raw) !== worktree)
18734
19238
  continue;
18735
19239
  return latestCodexCompletionMarkerFromJsonl(raw, file);
@@ -18978,7 +19482,7 @@ var gitWrapper;
18978
19482
  var init_git2 = __esm(() => {
18979
19483
  gitWrapper = {
18980
19484
  spawn(args, cwd) {
18981
- return new Promise((resolve6, reject) => {
19485
+ return new Promise((resolve7, reject) => {
18982
19486
  const child = nodeSpawn("git", [...args], {
18983
19487
  cwd,
18984
19488
  shell: false,
@@ -18997,7 +19501,7 @@ var init_git2 = __esm(() => {
18997
19501
  });
18998
19502
  child.on("error", reject);
18999
19503
  child.on("close", (status, signal) => {
19000
- resolve6({ stdout, stderr, status, signal });
19504
+ resolve7({ stdout, stderr, status, signal });
19001
19505
  });
19002
19506
  });
19003
19507
  }
@@ -19051,14 +19555,14 @@ var init_keys2 = __esm(() => {
19051
19555
 
19052
19556
  // src/tui/panes/filetree/open-external.ts
19053
19557
  import { spawn as spawn5 } from "child_process";
19054
- import { existsSync as existsSync12 } from "fs";
19558
+ import { existsSync as existsSync13 } from "fs";
19055
19559
  import { platform } from "os";
19056
19560
  function openExternally(absPath) {
19057
19561
  if (!absPath)
19058
19562
  return;
19059
19563
  const plat = platform();
19060
19564
  if (plat === "linux") {
19061
- if (existsSync12("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
19565
+ if (existsSync13("/proc/sys/fs/binfmt_misc/WSLInterop") || process.env.WSL_DISTRO_NAME) {
19062
19566
  spawnDetached("wslview", [absPath], () => {
19063
19567
  const child = spawn5("wslpath", ["-w", absPath], { stdio: ["ignore", "pipe", "ignore"] });
19064
19568
  let out = "";
@@ -20449,7 +20953,7 @@ __export(exports_direct, {
20449
20953
  startDirectTmux: () => startDirectTmux,
20450
20954
  chooseInitialTask: () => chooseInitialTask
20451
20955
  });
20452
- import { resolve as resolve6 } from "path";
20956
+ import { resolve as resolve7 } from "path";
20453
20957
  function chooseInitialTask(tasks, choice = {}) {
20454
20958
  const byId = (id) => id ? tasks.find((t) => t.id === id) : undefined;
20455
20959
  const active = byId(choice.activeTaskId);
@@ -20485,7 +20989,7 @@ async function ensureRepos(orchestrator) {
20485
20989
  normalizeSavedRepos();
20486
20990
  let repos = [...getSavedRepos()];
20487
20991
  if (repos.length === 0) {
20488
- const added = addSavedRepo(resolve6(process.cwd()));
20992
+ const added = addSavedRepo(resolve7(process.cwd()));
20489
20993
  repos = [added.path];
20490
20994
  }
20491
20995
  for (const repo of repos) {
@@ -20495,7 +20999,7 @@ async function ensureRepos(orchestrator) {
20495
20999
  console.error(`[kobe] ensureMainTask failed for ${repo}:`, err);
20496
21000
  }
20497
21001
  }
20498
- return repos[0] ?? resolve6(process.cwd());
21002
+ return repos[0] ?? resolve7(process.cwd());
20499
21003
  }
20500
21004
  async function startDirectTmux() {
20501
21005
  setClientLogContext("gui");
@@ -21092,9 +21596,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
21092
21596
  var init_pulse = () => {};
21093
21597
 
21094
21598
  // src/tui/lib/sound.ts
21095
- import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
21599
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "fs";
21096
21600
  import { tmpdir as tmpdir2 } from "os";
21097
- import { basename as basename6, isAbsolute as isAbsolute2, join as join12, resolve as resolve7 } from "path";
21601
+ import { basename as basename6, isAbsolute as isAbsolute2, join as join14, resolve as resolve8 } from "path";
21098
21602
  function args(player, file, volume) {
21099
21603
  if (player === "ffplay")
21100
21604
  return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
@@ -21117,13 +21621,13 @@ function pickPlayer() {
21117
21621
  return cachedPlayer;
21118
21622
  const path12 = process.env.PATH ?? "";
21119
21623
  const segments = path12.split(":").filter(Boolean);
21120
- cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync13(join12(dir, p)))) ?? null;
21624
+ cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join14(dir, p)))) ?? null;
21121
21625
  return cachedPlayer;
21122
21626
  }
21123
21627
  async function ensureAsset() {
21124
21628
  cachedPath ??= (async () => {
21125
21629
  mkdirSync5(DIR, { recursive: true });
21126
- const dest = join12(DIR, basename6(pulseAsset));
21630
+ const dest = join14(DIR, basename6(pulseAsset));
21127
21631
  const out = Bun.file(dest);
21128
21632
  if (await out.exists())
21129
21633
  return dest;
@@ -21152,8 +21656,8 @@ function pulse(volume = 0.4) {
21152
21656
  var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
21153
21657
  var init_sound = __esm(() => {
21154
21658
  init_pulse();
21155
- pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve7(import.meta.dir, pulse_default);
21156
- DIR = join12(tmpdir2(), "kobe-sfx");
21659
+ pulseAsset = isAbsolute2(pulse_default) ? pulse_default : resolve8(import.meta.dir, pulse_default);
21660
+ DIR = join14(tmpdir2(), "kobe-sfx");
21157
21661
  PLAYERS = [
21158
21662
  "ffplay",
21159
21663
  "mpv",
@@ -21679,7 +22183,7 @@ var init_use_theme_persistence = __esm(() => {
21679
22183
  });
21680
22184
 
21681
22185
  // src/monitor/cost.ts
21682
- import { readFile as readFile7 } from "fs/promises";
22186
+ import { readFile as readFile8 } from "fs/promises";
21683
22187
  async function summarizeTaskCost(opts) {
21684
22188
  const files = await listSessionFilesForWorktree(opts.worktree);
21685
22189
  const base = {
@@ -21701,7 +22205,7 @@ async function summarizeTaskCost(opts) {
21701
22205
  for (const file of files) {
21702
22206
  let raw;
21703
22207
  try {
21704
- raw = await readFile7(file.path, "utf8");
22208
+ raw = await readFile8(file.path, "utf8");
21705
22209
  } catch {
21706
22210
  continue;
21707
22211
  }
@@ -22212,7 +22716,7 @@ var exports_app = {};
22212
22716
  __export(exports_app, {
22213
22717
  startApp: () => startApp
22214
22718
  });
22215
- import { homedir as homedir15 } from "os";
22719
+ import { homedir as homedir16 } from "os";
22216
22720
  function Shell(props) {
22217
22721
  const themeCtx = useTheme();
22218
22722
  const {
@@ -22720,7 +23224,7 @@ async function startApp() {
22720
23224
  } of loadUserThemes()) {
22721
23225
  addTheme2(name, theme);
22722
23226
  }
22723
- const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir15();
23227
+ const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir16();
22724
23228
  let orchestrator;
22725
23229
  if (process.env.KOBE_NO_DAEMON === "1") {
22726
23230
  const store2 = new TaskIndexStore({
@@ -22833,7 +23337,7 @@ var init_tui = __esm(() => {
22833
23337
  // src/cli/index.ts
22834
23338
  init_path_glob();
22835
23339
  init_vendor();
22836
- import { resolve as resolve8 } from "path";
23340
+ import { resolve as resolve9 } from "path";
22837
23341
 
22838
23342
  // src/cli/usage.ts
22839
23343
  init_version();
@@ -22882,7 +23386,7 @@ Usage: kobe add [path]
22882
23386
  `);
22883
23387
  process.exit(2);
22884
23388
  }
22885
- const target = resolve8(process.cwd(), arg && arg.length > 0 ? arg : ".");
23389
+ const target = resolve9(process.cwd(), arg && arg.length > 0 ? arg : ".");
22886
23390
  const { addSavedRepo: addSavedRepo2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
22887
23391
  const result = addSavedRepo2(target);
22888
23392
  if (result.added) {
@@ -22981,7 +23485,7 @@ async function runAdoptSubcommand(args2) {
22981
23485
  }
22982
23486
  }
22983
23487
  const { resolveRepoRoot: resolveRepoRoot2 } = await Promise.resolve().then(() => (init_repos(), exports_repos));
22984
- const repo = resolveRepoRoot2(resolve8(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
23488
+ const repo = resolveRepoRoot2(resolve9(process.cwd(), repoArg && repoArg.length > 0 ? repoArg : "."));
22985
23489
  const vendor = coerceVendorId(vendorArg);
22986
23490
  const orch = await openLocalOrchestrator();
22987
23491
  const worktrees = await orch.discoverAdoptableWorktrees(repo);
@@ -23119,6 +23623,11 @@ async function main() {
23119
23623
  await runSkillSubcommand2(rest);
23120
23624
  return;
23121
23625
  }
23626
+ if (subcommand === "hook") {
23627
+ const { runHookSubcommand: runHookSubcommand2 } = await Promise.resolve().then(() => (init_hook_cmd(), exports_hook_cmd));
23628
+ await runHookSubcommand2(rest);
23629
+ return;
23630
+ }
23122
23631
  if (subcommand === "new-chattab") {
23123
23632
  const flags = parseOpsFlags(rest);
23124
23633
  const session = flags.session;