@sma1lboy/kobe 0.7.32 → 0.7.33

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 +986 -141
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
90
90
  package_default = {
91
91
  $schema: "https://json.schemastore.org/package.json",
92
92
  name: "@sma1lboy/kobe",
93
- version: "0.7.32",
93
+ version: "0.7.33",
94
94
  description: "TUI orchestrator for Claude Code (codename)",
95
95
  type: "module",
96
96
  packageManager: "bun@1.3.13",
@@ -5259,6 +5259,14 @@ function kobeCliInvocation() {
5259
5259
  var init_invocation = () => {};
5260
5260
 
5261
5261
  // src/tmux/session-layout.ts
5262
+ function hiddenTerminalSessionName(session) {
5263
+ const safe = session.replace(/[^A-Za-z0-9_-]/g, "");
5264
+ return `kobe-hidden-${safe || "session"}`;
5265
+ }
5266
+ function hiddenTerminalWindowIndex(windowId) {
5267
+ const n = Number.parseInt(windowId.replace(/^@/, ""), 10);
5268
+ return Number.isFinite(n) && n >= 0 ? 1000 + n : 1000;
5269
+ }
5262
5270
  function clampTasksPaneWidth(width) {
5263
5271
  if (!Number.isFinite(width))
5264
5272
  return TASKS_PANE_WIDTH;
@@ -5339,7 +5347,7 @@ function opsPaneCommand(args) {
5339
5347
  }
5340
5348
  return fallbackOpsScript(args.cwd);
5341
5349
  }
5342
- var TASKS_PANE_WIDTH = 32, TASKS_WIDTH_OPTION = "@kobe_tasks_width", TASKS_PANE_WIDTH_MIN = 16, TASKS_PANE_WIDTH_MAX = 120, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50, RIGHT_COLUMN_WIDTH_OPTION = "@kobe_right_width_pct", OPS_HEIGHT_OPTION = "@kobe_ops_height_pct", PANE_PERCENT_MIN = 10, PANE_PERCENT_MAX = 90;
5350
+ var TASKS_PANE_WIDTH = 32, TASKS_PANE_ROLE = "tasks", ENGINE_PANE_ROLE = "claude", OPS_PANE_ROLE = "ops", SHELL_PANE_ROLE = "shell", WORKSPACE_AUX_PANE_ROLE = "workspace_aux", WORKSPACE_SPLIT_MAX_PANES = 4, HIDDEN_TERMINAL_PANE_OPTION = "@kobe_hidden_shell_pane", HIDDEN_TASKS_PANE_OPTION = "@kobe_hidden_tasks_pane", TASKS_WIDTH_OPTION = "@kobe_tasks_width", TASKS_PANE_WIDTH_MIN = 16, TASKS_PANE_WIDTH_MAX = 120, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50, RIGHT_COLUMN_WIDTH_OPTION = "@kobe_right_width_pct", OPS_HEIGHT_OPTION = "@kobe_ops_height_pct", PANE_PERCENT_MIN = 10, PANE_PERCENT_MAX = 90;
5343
5351
 
5344
5352
  // src/engine/claude-code-local/hook-adapter.ts
5345
5353
  import { mkdir as mkdir5, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
@@ -6751,6 +6759,11 @@ async function currentSessionName() {
6751
6759
  return code === 0 && name.length > 0 ? name : null;
6752
6760
  }
6753
6761
  async function killSession(name) {
6762
+ if (!name.startsWith("kobe-hidden-")) {
6763
+ const hidden = hiddenTerminalSessionName(name);
6764
+ if (await sessionExists(hidden))
6765
+ await runTmux(["kill-session", "-t", `=${hidden}`]);
6766
+ }
6754
6767
  if (await sessionExists(name))
6755
6768
  await runTmux(["kill-session", "-t", `=${name}`]);
6756
6769
  }
@@ -9407,7 +9420,10 @@ var init_keymap_overrides = __esm(() => {
9407
9420
  });
9408
9421
 
9409
9422
  // src/tmux/keybindings.ts
9410
- function chordToTmuxKey(chord) {
9423
+ function isTmuxPrefixBindingId(id) {
9424
+ return id in TMUX_PREFIX_BINDING_DEFAULTS;
9425
+ }
9426
+ function chordToTmuxKey(chord, opts) {
9411
9427
  const parts = chord.split("+");
9412
9428
  let key = parts.pop() ?? "";
9413
9429
  if (key === "" && parts.length > 0) {
@@ -9429,7 +9445,7 @@ function chordToTmuxKey(chord) {
9429
9445
  tmuxKey = named;
9430
9446
  }
9431
9447
  const isFKey = /^f\d+$/.test(key);
9432
- if (mods.size === 0 && !isFKey) {
9448
+ if (mods.size === 0 && !isFKey && !opts?.allowBare) {
9433
9449
  return {
9434
9450
  error: `"${chord}": tmux session keys are no-prefix root bindings live in every pane \u2014 a bare key would shadow typing (add a modifier, or use an F-key)`
9435
9451
  };
@@ -9446,7 +9462,7 @@ function chordToTmuxKey(chord) {
9446
9462
  function defaultResolution() {
9447
9463
  const binds = {};
9448
9464
  for (const [id, chord] of Object.entries(TMUX_SINGLE_BINDING_DEFAULTS)) {
9449
- const t = chordToTmuxKey(chord);
9465
+ const t = chordToTmuxKey(chord, { allowBare: isTmuxPrefixBindingId(id) });
9450
9466
  if ("error" in t)
9451
9467
  throw new Error(`default tmux chord for ${id} failed to translate: ${t.error}`);
9452
9468
  binds[id] = { chord, key: t.key };
@@ -9506,7 +9522,7 @@ function resolveTmuxKeyEntries(entries) {
9506
9522
  res.warnings.push(`${id}: tmux bindings take ONE chord \u2014 using "${entry.keys[0]}", ignoring the rest`);
9507
9523
  }
9508
9524
  const chord = entry.keys[0];
9509
- const t = chordToTmuxKey(chord);
9525
+ const t = chordToTmuxKey(chord, { allowBare: isTmuxPrefixBindingId(id) });
9510
9526
  if ("error" in t) {
9511
9527
  res.warnings.push(`${id}: ${t.error} \u2014 keeping the default`);
9512
9528
  continue;
@@ -9541,11 +9557,11 @@ function resolveUserTmuxKeys() {
9541
9557
  cached2 = res;
9542
9558
  return cached2;
9543
9559
  }
9544
- var TMUX_SINGLE_BINDING_DEFAULTS, TMUX_FOCUS_ID = "tmux.focus", TMUX_FOCUS_DEFAULTS, TMUX_NAMED_KEYS, cached2 = null;
9560
+ var TMUX_ROOT_BINDING_DEFAULTS, TMUX_PREFIX_BINDING_DEFAULTS, TMUX_LEGACY_LAYOUT_ROOT_KEYS, TMUX_SINGLE_BINDING_DEFAULTS, TMUX_FOCUS_ID = "tmux.focus", TMUX_FOCUS_DEFAULTS, TMUX_NAMED_KEYS, cached2 = null;
9545
9561
  var init_keybindings = __esm(() => {
9546
9562
  init_keybindings_file();
9547
9563
  init_keymap_overrides();
9548
- TMUX_SINGLE_BINDING_DEFAULTS = {
9564
+ TMUX_ROOT_BINDING_DEFAULTS = {
9549
9565
  "tmux.detach": "ctrl+q",
9550
9566
  "tmux.tab.new": "ctrl+t",
9551
9567
  "tmux.tab.chooseEngine": "ctrl+shift+t",
@@ -9554,6 +9570,19 @@ var init_keybindings = __esm(() => {
9554
9570
  "tmux.tab.close": "ctrl+w",
9555
9571
  "tmux.tab.rename": "f2"
9556
9572
  };
9573
+ TMUX_PREFIX_BINDING_DEFAULTS = {
9574
+ "tmux.layout.workspaceSplit": "s",
9575
+ "tmux.layout.workspaceClose": "x",
9576
+ "tmux.layout.workspaceReset": "r",
9577
+ "tmux.layout.tasksToggle": "a",
9578
+ "tmux.layout.opsToggle": "o",
9579
+ "tmux.layout.terminalToggle": "z"
9580
+ };
9581
+ TMUX_LEGACY_LAYOUT_ROOT_KEYS = ["F6", "F7", "F8", "F9", "F10", "F11"];
9582
+ TMUX_SINGLE_BINDING_DEFAULTS = {
9583
+ ...TMUX_ROOT_BINDING_DEFAULTS,
9584
+ ...TMUX_PREFIX_BINDING_DEFAULTS
9585
+ };
9557
9586
  TMUX_FOCUS_DEFAULTS = ["ctrl+h", "ctrl+j", "ctrl+k", "ctrl+l"];
9558
9587
  TMUX_NAMED_KEYS = {
9559
9588
  up: "Up",
@@ -11400,7 +11429,7 @@ async function captureGlobalLayout(session) {
11400
11429
  "-t",
11401
11430
  `=${session}`,
11402
11431
  "-F",
11403
- "#{@kobe_role}\t#{pane_width}\t#{pane_height}\t#{window_width}\t#{window_height}\t#{window_zoomed_flag}"
11432
+ `#{@kobe_role} #{pane_width} #{pane_height} #{window_width} #{window_height} #{window_zoomed_flag} #{${HIDDEN_TERMINAL_PANE_OPTION}} #{${HIDDEN_TASKS_PANE_OPTION}}`
11404
11433
  ]);
11405
11434
  if (code !== 0)
11406
11435
  return;
@@ -11410,6 +11439,10 @@ async function captureGlobalLayout(session) {
11410
11439
  return;
11411
11440
  if (rows.some((cols) => cols[5]?.trim() === "1"))
11412
11441
  return;
11442
+ if (rows.some((cols) => (cols[6]?.trim() ?? "") !== ""))
11443
+ return;
11444
+ if (rows.some((cols) => (cols[7]?.trim() ?? "") !== ""))
11445
+ return;
11413
11446
  const winW = Number.parseInt(rows[0][3]?.trim() ?? "", 10);
11414
11447
  const winH = Number.parseInt(rows[0][4]?.trim() ?? "", 10);
11415
11448
  const sets = [];
@@ -11440,6 +11473,10 @@ function shouldCaptureDrag(stdout) {
11440
11473
  return false;
11441
11474
  if (rows.some((cols) => cols[1]?.trim() === "1"))
11442
11475
  return false;
11476
+ if (rows.some((cols) => (cols[2]?.trim() ?? "") !== ""))
11477
+ return false;
11478
+ if (rows.some((cols) => (cols[3]?.trim() ?? "") !== ""))
11479
+ return false;
11443
11480
  const roles = new Set(rows.map((cols) => cols[0]?.trim()));
11444
11481
  return roles.has("tasks") && roles.has("ops");
11445
11482
  }
@@ -11449,7 +11486,7 @@ async function captureGlobalLayoutOnDrag(session) {
11449
11486
  "-t",
11450
11487
  `=${session}`,
11451
11488
  "-F",
11452
- "#{@kobe_role}\t#{window_zoomed_flag}"
11489
+ `#{@kobe_role} #{window_zoomed_flag} #{${HIDDEN_TERMINAL_PANE_OPTION}} #{${HIDDEN_TASKS_PANE_OPTION}}`
11453
11490
  ]);
11454
11491
  if (code !== 0 || !shouldCaptureDrag(stdout))
11455
11492
  return;
@@ -11492,7 +11529,7 @@ function chatTabSwitchBindings(prevKey, nextKey) {
11492
11529
  ["bind-key", "-n", nextKey, "next-window"]
11493
11530
  ];
11494
11531
  }
11495
- function chatTabCloseBinding(key) {
11532
+ function chatTabCloseBinding(key, closeCommand = "kill-window") {
11496
11533
  return [
11497
11534
  "bind-key",
11498
11535
  "-n",
@@ -11500,7 +11537,7 @@ function chatTabCloseBinding(key) {
11500
11537
  "if-shell",
11501
11538
  "-F",
11502
11539
  "#{>:#{session_windows},1}",
11503
- "kill-window",
11540
+ closeCommand,
11504
11541
  "display-message 'Cannot close the only ChatTab'"
11505
11542
  ];
11506
11543
  }
@@ -11520,7 +11557,9 @@ function kobeStatusRight(keys) {
11520
11557
  const segments = [
11521
11558
  keys.focusLeft ? `${tmuxKeyCap(keys.focusLeft)} tasks` : null,
11522
11559
  keys.detach ? `${tmuxKeyCap(keys.detach)} detach` : null,
11523
- keys.newTab ? `${tmuxKeyCap(keys.newTab)} tab` : null
11560
+ keys.newTab ? `${tmuxKeyCap(keys.newTab)} tab` : null,
11561
+ keys.layoutSplits ? `prefix ${keys.layoutSplits} splits` : null,
11562
+ keys.layoutPanes ? `prefix ${keys.layoutPanes} panes` : null
11524
11563
  ].filter((s) => s !== null);
11525
11564
  return `#[fg=brightblack]${segments.join(" ")} `;
11526
11565
  }
@@ -11566,16 +11605,27 @@ async function buildPanesAround(claudePane, args) {
11566
11605
  "ops=#{pane_id}",
11567
11606
  opsCmd
11568
11607
  ],
11569
- ["split-window", "-v", "-l", `${100 - OPS_PANE_PERCENT}%`, "-c", localSpawnCwd(args.cwd)],
11608
+ [
11609
+ "split-window",
11610
+ "-v",
11611
+ "-l",
11612
+ `${100 - OPS_PANE_PERCENT}%`,
11613
+ "-c",
11614
+ localSpawnCwd(args.cwd),
11615
+ "-P",
11616
+ "-F",
11617
+ "shell=#{pane_id}"
11618
+ ],
11570
11619
  ["select-pane", "-t", claudePane]
11571
11620
  ]);
11572
11621
  const ids = Object.fromEntries(stdout.split(`
11573
11622
  `).map((line) => line.trim()).filter(Boolean).map((line) => line.split("=", 2)));
11574
11623
  await runTmuxSequence([
11575
- ...ids.tasks ? [["set-option", "-p", "-t", ids.tasks, "@kobe_role", "tasks"]] : [],
11624
+ ...ids.tasks ? [["set-option", "-p", "-t", ids.tasks, "@kobe_role", TASKS_PANE_ROLE]] : [],
11576
11625
  ...ids.tasks ? [["set-option", "-p", "-t", ids.tasks, PANE_VERSION_OPTION, CURRENT_VERSION]] : [],
11577
- ...ids.ops ? [["set-option", "-p", "-t", ids.ops, "@kobe_role", "ops"]] : [],
11578
- ...ids.ops ? [["set-option", "-p", "-t", ids.ops, PANE_VERSION_OPTION, CURRENT_VERSION]] : []
11626
+ ...ids.ops ? [["set-option", "-p", "-t", ids.ops, "@kobe_role", OPS_PANE_ROLE]] : [],
11627
+ ...ids.ops ? [["set-option", "-p", "-t", ids.ops, PANE_VERSION_OPTION, CURRENT_VERSION]] : [],
11628
+ ...ids.shell ? [["set-option", "-p", "-t", ids.shell, "@kobe_role", SHELL_PANE_ROLE]] : []
11579
11629
  ]);
11580
11630
  if (ids.ops) {
11581
11631
  const rcArgs = await globalRightColumnResizeArgs();
@@ -11700,6 +11750,644 @@ var init_chattab = __esm(() => {
11700
11750
  CHAT_TAB_STATUS_CURRENT_FORMAT = CHAT_TAB_STATUS_FORMAT;
11701
11751
  });
11702
11752
 
11753
+ // src/tui/panes/terminal/layout-actions.ts
11754
+ function parseLayoutPaneRows(stdout) {
11755
+ const rows = [];
11756
+ for (const raw of stdout.split(`
11757
+ `)) {
11758
+ const line = raw.trim();
11759
+ if (!line)
11760
+ continue;
11761
+ const [paneId, role, active, paneWidth, paneHeight, windowWidth, windowHeight] = line.split("\t");
11762
+ if (!paneId)
11763
+ continue;
11764
+ const width = Number.parseInt(paneWidth ?? "", 10);
11765
+ const height = Number.parseInt(paneHeight ?? "", 10);
11766
+ const winW = Number.parseInt(windowWidth ?? "", 10);
11767
+ const winH = Number.parseInt(windowHeight ?? "", 10);
11768
+ rows.push({
11769
+ paneId: paneId.trim(),
11770
+ role: role?.trim() ?? "",
11771
+ active: active?.trim() === "1",
11772
+ paneWidth: Number.isFinite(width) ? width : 0,
11773
+ paneHeight: Number.isFinite(height) ? height : 0,
11774
+ windowWidth: Number.isFinite(winW) ? winW : 0,
11775
+ windowHeight: Number.isFinite(winH) ? winH : 0
11776
+ });
11777
+ }
11778
+ return rows;
11779
+ }
11780
+ function planWorkspaceSplit(rows) {
11781
+ const engine = rows.find((row) => row.role === ENGINE_PANE_ROLE);
11782
+ if (!engine)
11783
+ return { kind: "missing-engine" };
11784
+ const aux = rows.filter((row) => row.role === WORKSPACE_AUX_PANE_ROLE);
11785
+ if (aux.length + 1 >= WORKSPACE_SPLIT_MAX_PANES)
11786
+ return { kind: "maxed" };
11787
+ if (aux.length === 0)
11788
+ return { kind: "split", targetPane: engine.paneId, direction: "-h" };
11789
+ if (aux.length === 1)
11790
+ return { kind: "split", targetPane: aux[0]?.paneId ?? engine.paneId, direction: "-v" };
11791
+ return { kind: "split", targetPane: engine.paneId, direction: "-v" };
11792
+ }
11793
+ function resolveShellPane(rows) {
11794
+ return rows.find((row) => row.role === SHELL_PANE_ROLE) ?? rows.find((row) => row.role === "");
11795
+ }
11796
+ function expandedTerminalHeightPercent(rawOpsHeightPercent) {
11797
+ const opsPct = clampPanePercent(rawOpsHeightPercent ?? Number.NaN) ?? OPS_PANE_PERCENT;
11798
+ return 100 - opsPct;
11799
+ }
11800
+ function parsePositiveInt(raw) {
11801
+ const n = Number.parseInt(raw ?? "", 10);
11802
+ return Number.isFinite(n) && n > 0 ? n : undefined;
11803
+ }
11804
+ async function windowPanes(session, windowId) {
11805
+ const target = windowId?.trim() || `=${session}`;
11806
+ const { code, stdout } = await runTmuxCapturing(["list-panes", "-t", target, "-F", ACTIVE_WINDOW_LAYOUT_FORMAT]);
11807
+ return code === 0 ? parseLayoutPaneRows(stdout) : null;
11808
+ }
11809
+ async function activeWindowId(session) {
11810
+ const { code, stdout } = await runTmuxCapturing([
11811
+ "list-windows",
11812
+ "-t",
11813
+ `=${session}`,
11814
+ "-F",
11815
+ "#{window_active}\t#{window_id}"
11816
+ ]);
11817
+ if (code !== 0)
11818
+ return "";
11819
+ for (const line of stdout.split(`
11820
+ `)) {
11821
+ const [active, windowId] = line.split("\t");
11822
+ if (active?.trim() === "1" && windowId?.trim())
11823
+ return windowId.trim();
11824
+ }
11825
+ return "";
11826
+ }
11827
+ async function windowOption(target, option) {
11828
+ const { code, stdout } = await runTmuxCapturing(["show-options", "-wqv", "-t", target, option]);
11829
+ return code === 0 ? stdout.trim() : "";
11830
+ }
11831
+ async function setActiveWindowOption(windowId, option, value) {
11832
+ await runTmux(["set-window-option", "-t", windowId, option, value]);
11833
+ }
11834
+ async function clearActiveWindowOption(windowId, option) {
11835
+ await runTmux(["set-window-option", "-u", "-t", windowId, option]);
11836
+ }
11837
+ async function paneExists(paneId) {
11838
+ const { code, stdout } = await runTmuxCapturing(["display-message", "-p", "-t", paneId, "#{pane_id}"]);
11839
+ return code === 0 && stdout.trim() === paneId;
11840
+ }
11841
+ async function display(session, message) {
11842
+ await runTmux(["display-message", "-t", session, message]);
11843
+ }
11844
+ async function sessionWorktree(session) {
11845
+ const opts = await getSessionOptions(session, ["@kobe_worktree", "@kobe_task", "@kobe_vendor"]);
11846
+ return {
11847
+ cwd: opts["@kobe_worktree"] || process.cwd(),
11848
+ taskId: opts["@kobe_task"] || undefined,
11849
+ vendor: opts["@kobe_vendor"] || undefined
11850
+ };
11851
+ }
11852
+ function opsPaneLaunchCommand(args) {
11853
+ const inv = kobeCliInvocation();
11854
+ const envPrefix = inheritedEnvPrefix();
11855
+ return keepAlive(envPrefix + opsPaneCommand({
11856
+ cwd: args.cwd,
11857
+ taskId: args.taskId,
11858
+ claudePaneId: args.enginePaneId,
11859
+ cliInvocation: inv,
11860
+ vendor: args.vendor
11861
+ }));
11862
+ }
11863
+ async function resolveActionWindowId(session, windowId) {
11864
+ return windowId?.trim() || await activeWindowId(session);
11865
+ }
11866
+ async function addWorkspaceSplit(session, windowId) {
11867
+ const rows = await windowPanes(session, windowId);
11868
+ if (!rows)
11869
+ return;
11870
+ const plan = planWorkspaceSplit(rows);
11871
+ if (plan.kind === "missing-engine") {
11872
+ await display(windowId, "kobe: no engine pane in this window");
11873
+ return;
11874
+ }
11875
+ if (plan.kind === "maxed") {
11876
+ await display(windowId, `kobe: workspace split limit is ${WORKSPACE_SPLIT_MAX_PANES} panes`);
11877
+ return;
11878
+ }
11879
+ const { cwd } = await sessionWorktree(session);
11880
+ const { code, stdout } = await runTmuxCapturing([
11881
+ "split-window",
11882
+ plan.direction,
11883
+ "-t",
11884
+ plan.targetPane,
11885
+ "-l",
11886
+ "50%",
11887
+ "-c",
11888
+ localSpawnCwd(cwd),
11889
+ "-P",
11890
+ "-F",
11891
+ "#{pane_id}",
11892
+ keepAlive("true")
11893
+ ]);
11894
+ const paneId = stdout.trim();
11895
+ if (code !== 0 || !paneId)
11896
+ return;
11897
+ await runTmuxSequence([
11898
+ ["set-option", "-p", "-t", paneId, "@kobe_role", WORKSPACE_AUX_PANE_ROLE],
11899
+ ["select-pane", "-t", paneId]
11900
+ ]);
11901
+ }
11902
+ async function closeWorkspaceSplit(session, windowId) {
11903
+ const rows = await windowPanes(session, windowId);
11904
+ if (!rows)
11905
+ return;
11906
+ const aux = rows.filter((row) => row.role === WORKSPACE_AUX_PANE_ROLE);
11907
+ if (aux.length === 0) {
11908
+ await display(windowId, "kobe: no workspace split to close");
11909
+ return;
11910
+ }
11911
+ const activeAux = aux.find((row) => row.active);
11912
+ const target = activeAux ?? aux[aux.length - 1];
11913
+ if (!target)
11914
+ return;
11915
+ await runTmux(["kill-pane", "-t", target.paneId]);
11916
+ }
11917
+ async function resetWorkspaceSplits(session, windowId) {
11918
+ const rows = await windowPanes(session, windowId);
11919
+ if (!rows)
11920
+ return;
11921
+ const aux = rows.filter((row) => row.role === WORKSPACE_AUX_PANE_ROLE);
11922
+ if (aux.length === 0) {
11923
+ await display(windowId, "kobe: no workspace splits to reset");
11924
+ return;
11925
+ }
11926
+ await runTmuxSequence(aux.map((row) => ["kill-pane", "-t", row.paneId]));
11927
+ }
11928
+ async function preferredTasksWidth() {
11929
+ const opts = await getServerOptions([TASKS_WIDTH_OPTION]);
11930
+ return clampTasksPaneWidth(parsePositiveInt(opts[TASKS_WIDTH_OPTION]) ?? TASKS_PANE_WIDTH);
11931
+ }
11932
+ async function tasksPaneLaunchCommand(session) {
11933
+ const { cwd, taskId } = await sessionWorktree(session);
11934
+ const inv = kobeCliInvocation();
11935
+ const envPrefix = inheritedEnvPrefix();
11936
+ return {
11937
+ cwd,
11938
+ command: keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
11939
+ };
11940
+ }
11941
+ async function toggleOpsPane(session, windowId) {
11942
+ const rows = await windowPanes(session, windowId);
11943
+ if (!rows)
11944
+ return;
11945
+ const ops = rows.find((row) => row.role === OPS_PANE_ROLE);
11946
+ if (ops) {
11947
+ await runTmux(["kill-pane", "-t", ops.paneId]);
11948
+ return;
11949
+ }
11950
+ const shell = resolveShellPane(rows);
11951
+ const engine = rows.find((row) => row.role === ENGINE_PANE_ROLE);
11952
+ if (!engine) {
11953
+ await display(windowId, "kobe: cannot restore file pane in this layout");
11954
+ return;
11955
+ }
11956
+ const { cwd, taskId, vendor } = await sessionWorktree(session);
11957
+ const opsCmd = opsPaneLaunchCommand({ cwd, taskId, vendor, enginePaneId: engine.paneId });
11958
+ const splitArgs = shell ? [
11959
+ "split-window",
11960
+ "-v",
11961
+ "-b",
11962
+ "-t",
11963
+ shell.paneId,
11964
+ "-l",
11965
+ `${await preferredOpsHeightPercent()}%`,
11966
+ "-c",
11967
+ localSpawnCwd(cwd),
11968
+ "-P",
11969
+ "-F",
11970
+ "#{pane_id}",
11971
+ opsCmd
11972
+ ] : [
11973
+ "split-window",
11974
+ "-h",
11975
+ "-t",
11976
+ engine.paneId,
11977
+ "-l",
11978
+ `${await preferredRightColumnWidthPercent()}%`,
11979
+ "-c",
11980
+ localSpawnCwd(cwd),
11981
+ "-P",
11982
+ "-F",
11983
+ "#{pane_id}",
11984
+ opsCmd
11985
+ ];
11986
+ const { code, stdout } = await runTmuxCapturing(splitArgs);
11987
+ const paneId = stdout.trim();
11988
+ if (code !== 0 || !paneId)
11989
+ return;
11990
+ const commands = [
11991
+ ["set-option", "-p", "-t", paneId, "@kobe_role", OPS_PANE_ROLE],
11992
+ ["set-option", "-p", "-t", paneId, PANE_VERSION_OPTION, CURRENT_VERSION]
11993
+ ];
11994
+ if (shell?.role === "")
11995
+ commands.push(["set-option", "-p", "-t", shell.paneId, "@kobe_role", SHELL_PANE_ROLE]);
11996
+ await runTmuxSequence(commands);
11997
+ }
11998
+ async function preferredOpsHeightPercent() {
11999
+ const opts = await getServerOptions([OPS_HEIGHT_OPTION]);
12000
+ return clampPanePercent(Number.parseInt(opts[OPS_HEIGHT_OPTION] ?? "", 10)) ?? OPS_PANE_PERCENT;
12001
+ }
12002
+ async function preferredRightColumnWidthPercent() {
12003
+ const opts = await getServerOptions([RIGHT_COLUMN_WIDTH_OPTION]);
12004
+ return clampPanePercent(Number.parseInt(opts[RIGHT_COLUMN_WIDTH_OPTION] ?? "", 10)) ?? 100 - CLAUDE_PANE_PERCENT;
12005
+ }
12006
+ async function preferredTerminalHeightPercent() {
12007
+ const opts = await getServerOptions([OPS_HEIGHT_OPTION]);
12008
+ return expandedTerminalHeightPercent(Number.parseInt(opts[OPS_HEIGHT_OPTION] ?? "", 10));
12009
+ }
12010
+ async function hiddenWindowIndices(hiddenSession) {
12011
+ const { code, stdout } = await runTmuxCapturing(["list-windows", "-t", `=${hiddenSession}`, "-F", "#{window_index}"]);
12012
+ if (code !== 0)
12013
+ return new Set;
12014
+ return new Set(stdout.split(`
12015
+ `).map((line) => Number.parseInt(line.trim(), 10)).filter((n) => Number.isFinite(n)));
12016
+ }
12017
+ async function nextHiddenWindowIndex(hiddenSession, windowId) {
12018
+ const used = await hiddenWindowIndices(hiddenSession);
12019
+ let idx = hiddenTerminalWindowIndex(windowId);
12020
+ while (used.has(idx))
12021
+ idx++;
12022
+ return idx;
12023
+ }
12024
+ async function ensureHiddenPaneSession(session) {
12025
+ const hidden = hiddenTerminalSessionName(session);
12026
+ if (await sessionExists(hidden))
12027
+ return hidden;
12028
+ await runTmux([
12029
+ "new-session",
12030
+ "-d",
12031
+ "-s",
12032
+ hidden,
12033
+ "-n",
12034
+ "hidden-panes",
12035
+ "-c",
12036
+ localSpawnCwd(process.cwd()),
12037
+ "while :; do sleep 3600; done"
12038
+ ]);
12039
+ return hidden;
12040
+ }
12041
+ async function cleanupHiddenPaneSessionIfEmpty(session) {
12042
+ const hidden = hiddenTerminalSessionName(session);
12043
+ if (!await sessionExists(hidden))
12044
+ return;
12045
+ const { code, stdout } = await runTmuxCapturing(["list-panes", "-s", "-t", `=${hidden}`, "-F", "#{@kobe_role}"]);
12046
+ if (code !== 0)
12047
+ return;
12048
+ const hasHiddenPane = stdout.split(`
12049
+ `).some((line) => {
12050
+ const role = line.trim();
12051
+ return role === SHELL_PANE_ROLE || role === TASKS_PANE_ROLE;
12052
+ });
12053
+ if (!hasHiddenPane)
12054
+ await runTmux(["kill-session", "-t", `=${hidden}`]);
12055
+ }
12056
+ async function cleanupHiddenTerminalForWindow(session, windowId) {
12057
+ await cleanupHiddenPaneForWindow(session, windowId, HIDDEN_TERMINAL_PANE_OPTION);
12058
+ }
12059
+ async function cleanupHiddenTasksForWindow(session, windowId) {
12060
+ await cleanupHiddenPaneForWindow(session, windowId, HIDDEN_TASKS_PANE_OPTION);
12061
+ }
12062
+ async function cleanupHiddenPaneForWindow(session, windowId, option) {
12063
+ const hiddenPane = await windowOption(windowId, option);
12064
+ if (!hiddenPane)
12065
+ return;
12066
+ if (await paneExists(hiddenPane)) {
12067
+ await runTmux(["kill-pane", "-t", hiddenPane]);
12068
+ }
12069
+ await clearActiveWindowOption(windowId, option);
12070
+ }
12071
+ async function cleanupHiddenPanesForWindow(session, windowId) {
12072
+ await cleanupHiddenTerminalForWindow(session, windowId);
12073
+ await cleanupHiddenTasksForWindow(session, windowId);
12074
+ await cleanupHiddenPaneSessionIfEmpty(session);
12075
+ }
12076
+ function restoreTasksTarget(rows) {
12077
+ return rows.find((row) => row.role === ENGINE_PANE_ROLE) ?? rows[0];
12078
+ }
12079
+ async function createTasksPane(session, windowId, rows) {
12080
+ const target = restoreTasksTarget(rows);
12081
+ if (!target) {
12082
+ await display(windowId, "kobe: cannot restore Tasks pane in this layout");
12083
+ return false;
12084
+ }
12085
+ const width = await preferredTasksWidth();
12086
+ const { cwd, command } = await tasksPaneLaunchCommand(session);
12087
+ const { code, stdout } = await runTmuxCapturing([
12088
+ "split-window",
12089
+ "-h",
12090
+ "-b",
12091
+ "-t",
12092
+ target.paneId,
12093
+ "-l",
12094
+ `${width}`,
12095
+ "-c",
12096
+ localSpawnCwd(cwd),
12097
+ "-P",
12098
+ "-F",
12099
+ "#{pane_id}",
12100
+ command
12101
+ ]);
12102
+ const paneId = stdout.trim();
12103
+ if (code !== 0 || !paneId)
12104
+ return false;
12105
+ await runTmuxSequence([
12106
+ ["set-option", "-p", "-t", paneId, "@kobe_role", TASKS_PANE_ROLE],
12107
+ ["set-option", "-p", "-t", paneId, PANE_VERSION_OPTION, CURRENT_VERSION],
12108
+ ["select-pane", "-t", paneId]
12109
+ ]);
12110
+ await clearActiveWindowOption(windowId, HIDDEN_TASKS_PANE_OPTION);
12111
+ await display(windowId, "kobe: Tasks pane restored");
12112
+ return true;
12113
+ }
12114
+ async function hideTasksPane(session, windowId, tasks) {
12115
+ const hidden = await ensureHiddenPaneSession(session);
12116
+ const hiddenIndex = await nextHiddenWindowIndex(hidden, windowId);
12117
+ const { code, stdout } = await runTmuxCapturing([
12118
+ "break-pane",
12119
+ "-d",
12120
+ "-s",
12121
+ tasks.paneId,
12122
+ "-t",
12123
+ `${hidden}:${hiddenIndex}`,
12124
+ "-P",
12125
+ "-F",
12126
+ "#{pane_id}"
12127
+ ]);
12128
+ const hiddenPane = stdout.trim() || tasks.paneId;
12129
+ if (code !== 0 || !hiddenPane) {
12130
+ await display(windowId, "kobe: could not hide Tasks pane");
12131
+ return;
12132
+ }
12133
+ await setActiveWindowOption(windowId, HIDDEN_TASKS_PANE_OPTION, hiddenPane);
12134
+ await display(windowId, "kobe: Tasks pane hidden");
12135
+ }
12136
+ async function restoreHiddenTasksPane(session, windowId, hiddenPane) {
12137
+ const rows = await windowPanes(session, windowId);
12138
+ if (!rows)
12139
+ return;
12140
+ if (!await paneExists(hiddenPane)) {
12141
+ await clearActiveWindowOption(windowId, HIDDEN_TASKS_PANE_OPTION);
12142
+ const visibleTasks = rows.find((row) => row.role === TASKS_PANE_ROLE);
12143
+ if (visibleTasks) {
12144
+ await runTmux(["select-pane", "-t", visibleTasks.paneId]);
12145
+ return;
12146
+ }
12147
+ await createTasksPane(session, windowId, rows);
12148
+ return;
12149
+ }
12150
+ const target = restoreTasksTarget(rows);
12151
+ if (!target) {
12152
+ await display(windowId, "kobe: cannot restore Tasks pane in this layout");
12153
+ return;
12154
+ }
12155
+ const code = await runTmux([
12156
+ "join-pane",
12157
+ "-h",
12158
+ "-b",
12159
+ "-s",
12160
+ hiddenPane,
12161
+ "-t",
12162
+ target.paneId,
12163
+ "-l",
12164
+ `${await preferredTasksWidth()}`
12165
+ ]);
12166
+ if (code !== 0) {
12167
+ await display(windowId, "kobe: could not restore Tasks pane");
12168
+ return;
12169
+ }
12170
+ await runTmuxSequence([
12171
+ ["set-option", "-p", "-t", hiddenPane, "@kobe_role", TASKS_PANE_ROLE],
12172
+ ["set-option", "-p", "-t", hiddenPane, PANE_VERSION_OPTION, CURRENT_VERSION],
12173
+ ["select-pane", "-t", hiddenPane]
12174
+ ]);
12175
+ await clearActiveWindowOption(windowId, HIDDEN_TASKS_PANE_OPTION);
12176
+ await cleanupHiddenPaneSessionIfEmpty(session);
12177
+ await display(windowId, "kobe: Tasks pane restored");
12178
+ }
12179
+ async function toggleTasksPane(session, windowId) {
12180
+ const hiddenPane = await windowOption(windowId, HIDDEN_TASKS_PANE_OPTION);
12181
+ if (hiddenPane) {
12182
+ await restoreHiddenTasksPane(session, windowId, hiddenPane);
12183
+ return;
12184
+ }
12185
+ const rows = await windowPanes(session, windowId);
12186
+ if (!rows)
12187
+ return;
12188
+ const tasks = rows.find((row) => row.role === TASKS_PANE_ROLE);
12189
+ if (tasks) {
12190
+ await hideTasksPane(session, windowId, tasks);
12191
+ return;
12192
+ }
12193
+ await createTasksPane(session, windowId, rows);
12194
+ }
12195
+ async function restoreTasksPane(session, windowId) {
12196
+ const hiddenPane = await windowOption(windowId, HIDDEN_TASKS_PANE_OPTION);
12197
+ if (hiddenPane) {
12198
+ await restoreHiddenTasksPane(session, windowId, hiddenPane);
12199
+ return;
12200
+ }
12201
+ const rows = await windowPanes(session, windowId);
12202
+ if (!rows)
12203
+ return;
12204
+ const tasks = rows.find((row) => row.role === TASKS_PANE_ROLE);
12205
+ if (tasks) {
12206
+ await runTmux(["select-pane", "-t", tasks.paneId]);
12207
+ return;
12208
+ }
12209
+ await createTasksPane(session, windowId, rows);
12210
+ }
12211
+ async function hideTerminalPane(session, windowId, shell) {
12212
+ if (shell.role === "") {
12213
+ await runTmux(["set-option", "-p", "-t", shell.paneId, "@kobe_role", SHELL_PANE_ROLE]);
12214
+ }
12215
+ const hidden = await ensureHiddenPaneSession(session);
12216
+ const hiddenIndex = await nextHiddenWindowIndex(hidden, windowId);
12217
+ const { code, stdout } = await runTmuxCapturing([
12218
+ "break-pane",
12219
+ "-d",
12220
+ "-s",
12221
+ shell.paneId,
12222
+ "-t",
12223
+ `${hidden}:${hiddenIndex}`,
12224
+ "-P",
12225
+ "-F",
12226
+ "#{pane_id}"
12227
+ ]);
12228
+ const hiddenPane = stdout.trim() || shell.paneId;
12229
+ if (code !== 0 || !hiddenPane) {
12230
+ await display(windowId, "kobe: could not hide terminal pane");
12231
+ return;
12232
+ }
12233
+ await setActiveWindowOption(windowId, HIDDEN_TERMINAL_PANE_OPTION, hiddenPane);
12234
+ await display(windowId, "kobe: Terminal pane hidden");
12235
+ }
12236
+ async function createTerminalPane(session, windowId, rows) {
12237
+ const ops = rows.find((row) => row.role === OPS_PANE_ROLE);
12238
+ const engine = rows.find((row) => row.role === ENGINE_PANE_ROLE);
12239
+ if (!ops && !engine) {
12240
+ await display(windowId, "kobe: cannot restore terminal pane in this layout");
12241
+ return false;
12242
+ }
12243
+ const { cwd } = await sessionWorktree(session);
12244
+ const splitArgs = ops ? [
12245
+ "split-window",
12246
+ "-v",
12247
+ "-t",
12248
+ ops.paneId,
12249
+ "-l",
12250
+ `${await preferredTerminalHeightPercent()}%`,
12251
+ "-c",
12252
+ localSpawnCwd(cwd),
12253
+ "-P",
12254
+ "-F",
12255
+ "#{pane_id}"
12256
+ ] : [
12257
+ "split-window",
12258
+ "-h",
12259
+ "-t",
12260
+ engine?.paneId ?? "",
12261
+ "-l",
12262
+ `${await preferredRightColumnWidthPercent()}%`,
12263
+ "-c",
12264
+ localSpawnCwd(cwd),
12265
+ "-P",
12266
+ "-F",
12267
+ "#{pane_id}"
12268
+ ];
12269
+ const { code, stdout } = await runTmuxCapturing(splitArgs);
12270
+ const paneId = stdout.trim();
12271
+ if (code !== 0 || !paneId)
12272
+ return false;
12273
+ await runTmux(["set-option", "-p", "-t", paneId, "@kobe_role", SHELL_PANE_ROLE]);
12274
+ await clearActiveWindowOption(windowId, HIDDEN_TERMINAL_PANE_OPTION);
12275
+ await display(windowId, "kobe: Terminal pane restored");
12276
+ return true;
12277
+ }
12278
+ async function restoreHiddenTerminalPane(session, windowId, hiddenPane) {
12279
+ let rows = await windowPanes(session, windowId);
12280
+ if (!rows)
12281
+ return;
12282
+ if (!await paneExists(hiddenPane)) {
12283
+ await clearActiveWindowOption(windowId, HIDDEN_TERMINAL_PANE_OPTION);
12284
+ await createTerminalPane(session, windowId, rows);
12285
+ return;
12286
+ }
12287
+ const ops = rows.find((row) => row.role === OPS_PANE_ROLE);
12288
+ const engine = rows.find((row) => row.role === ENGINE_PANE_ROLE);
12289
+ if (!ops && !engine) {
12290
+ await display(windowId, "kobe: cannot restore terminal pane in this layout");
12291
+ return;
12292
+ }
12293
+ const joinArgs = ops ? ["join-pane", "-v", "-s", hiddenPane, "-t", ops.paneId, "-l", `${await preferredTerminalHeightPercent()}%`] : [
12294
+ "join-pane",
12295
+ "-h",
12296
+ "-s",
12297
+ hiddenPane,
12298
+ "-t",
12299
+ engine?.paneId ?? "",
12300
+ "-l",
12301
+ `${await preferredRightColumnWidthPercent()}%`
12302
+ ];
12303
+ const code = await runTmux(joinArgs);
12304
+ if (code !== 0) {
12305
+ await display(windowId, "kobe: could not restore terminal pane");
12306
+ return;
12307
+ }
12308
+ await runTmux(["set-option", "-p", "-t", hiddenPane, "@kobe_role", SHELL_PANE_ROLE]);
12309
+ await clearActiveWindowOption(windowId, HIDDEN_TERMINAL_PANE_OPTION);
12310
+ await cleanupHiddenPaneSessionIfEmpty(session);
12311
+ rows = await windowPanes(session, windowId);
12312
+ const restored = rows?.find((row) => row.paneId === hiddenPane);
12313
+ if (restored)
12314
+ await runTmux(["select-pane", "-t", restored.paneId]);
12315
+ await display(windowId, "kobe: Terminal pane restored");
12316
+ }
12317
+ async function toggleTerminalPane(session, windowId) {
12318
+ const hiddenPane = await windowOption(windowId, HIDDEN_TERMINAL_PANE_OPTION);
12319
+ if (hiddenPane) {
12320
+ await restoreHiddenTerminalPane(session, windowId, hiddenPane);
12321
+ return;
12322
+ }
12323
+ const rows = await windowPanes(session, windowId);
12324
+ if (!rows)
12325
+ return;
12326
+ const shell = resolveShellPane(rows);
12327
+ if (shell) {
12328
+ await hideTerminalPane(session, windowId, shell);
12329
+ return;
12330
+ }
12331
+ await createTerminalPane(session, windowId, rows);
12332
+ }
12333
+ async function activeSessionWindowCount(session) {
12334
+ const { code, stdout } = await runTmuxCapturing(["list-windows", "-t", `=${session}`, "-F", "#{window_id}"]);
12335
+ if (code !== 0)
12336
+ return 0;
12337
+ return stdout.split(`
12338
+ `).filter((line) => line.trim().length > 0).length;
12339
+ }
12340
+ async function closeChatTab(session, windowId) {
12341
+ if (await activeSessionWindowCount(session) <= 1) {
12342
+ await display(windowId, "Cannot close the only ChatTab");
12343
+ return;
12344
+ }
12345
+ await cleanupHiddenPanesForWindow(session, windowId);
12346
+ await runTmux(["kill-window", "-t", windowId]);
12347
+ }
12348
+ async function runLayoutAction(session, action, opts = {}) {
12349
+ if (!await sessionExists(session))
12350
+ return;
12351
+ const windowId = await resolveActionWindowId(session, opts.windowId);
12352
+ if (!windowId)
12353
+ return;
12354
+ switch (action) {
12355
+ case "workspace-split":
12356
+ await addWorkspaceSplit(session, windowId);
12357
+ return;
12358
+ case "workspace-close":
12359
+ await closeWorkspaceSplit(session, windowId);
12360
+ return;
12361
+ case "workspace-reset":
12362
+ await resetWorkspaceSplits(session, windowId);
12363
+ return;
12364
+ case "tasks-toggle":
12365
+ await toggleTasksPane(session, windowId);
12366
+ return;
12367
+ case "tasks-restore":
12368
+ await restoreTasksPane(session, windowId);
12369
+ return;
12370
+ case "ops-toggle":
12371
+ await toggleOpsPane(session, windowId);
12372
+ return;
12373
+ case "terminal-toggle":
12374
+ await toggleTerminalPane(session, windowId);
12375
+ return;
12376
+ case "chat-tab-close":
12377
+ await closeChatTab(session, windowId);
12378
+ return;
12379
+ }
12380
+ }
12381
+ var ACTIVE_WINDOW_LAYOUT_FORMAT = "#{pane_id}\t#{@kobe_role}\t#{pane_active}\t#{pane_width}\t#{pane_height}\t#{window_width}\t#{window_height}";
12382
+ var init_layout_actions = __esm(() => {
12383
+ init_invocation();
12384
+ init_resolve();
12385
+ init_client2();
12386
+ init_version();
12387
+ init_launch();
12388
+ init_pane_heal();
12389
+ });
12390
+
11703
12391
  // src/tui/panes/terminal/tmux.ts
11704
12392
  var exports_tmux = {};
11705
12393
  __export(exports_tmux, {
@@ -11709,6 +12397,7 @@ __export(exports_tmux, {
11709
12397
  switchClientBeforeKill: () => switchClientBeforeKill,
11710
12398
  sessionExists: () => sessionExists,
11711
12399
  selectTasksPane: () => selectTasksPane,
12400
+ runLayoutAction: () => runLayoutAction,
11712
12401
  refreshKobeWorkspacePanes: () => refreshKobeWorkspacePanes,
11713
12402
  quickCreate: () => quickCreate,
11714
12403
  prepareWindowForSwitch: () => prepareWindowForSwitch,
@@ -11768,16 +12457,12 @@ async function prepareWindowForSwitch(session) {
11768
12457
  await runTmux(["resize-window", "-t", `=${session}`, ...sizeArgs]);
11769
12458
  await healWorkspaceLayout(session);
11770
12459
  }
11771
- function focusBindCommand(key, dir) {
11772
- return [
11773
- "bind-key",
11774
- "-n",
11775
- key,
11776
- "if-shell",
11777
- "-F",
11778
- `#{?window_zoomed_flag,1,#{?${FOCUS_EDGE_VARS[dir]},,1}}`,
11779
- `select-pane ${dir}`
11780
- ];
12460
+ function focusBindCommand(key, dir, edgeCommand) {
12461
+ const condition = `#{?window_zoomed_flag,1,#{?${FOCUS_EDGE_VARS[dir]},,1}}`;
12462
+ if (edgeCommand) {
12463
+ return ["bind-key", "-n", key, "if-shell", "-F", condition, `select-pane ${dir}`, edgeCommand];
12464
+ }
12465
+ return ["bind-key", "-n", key, "if-shell", "-F", condition, `select-pane ${dir}`];
11781
12466
  }
11782
12467
  async function ensureSession(opts) {
11783
12468
  const inflight = ensureSessionLocks.get(opts.name);
@@ -11845,7 +12530,7 @@ async function ensureSessionImpl(opts) {
11845
12530
  }
11846
12531
  }
11847
12532
  if (action.kind === "rebuild" || action.kind === "respawn-engine") {
11848
- await runTmux(["kill-session", "-t", `=${opts.name}`]);
12533
+ await killSession(opts.name);
11849
12534
  }
11850
12535
  const inv = kobeCliInvocation();
11851
12536
  const launch = withClaudeSessionId(opts.command, opts.vendor);
@@ -11895,14 +12580,19 @@ async function ensureSessionImpl(opts) {
11895
12580
  const newChatTabCommand = `${envStr}${invStr} new-chattab --session '#{session_name}'`;
11896
12581
  const chooseEngineCommand = `${newChatTabCommand} --vendor '%%'`;
11897
12582
  const chooseEngineTmuxCommand = `run-shell ${shellQuote(chooseEngineCommand)}`;
11898
- const focusTasksCommand = `${envStr}${invStr} focus-tasks --session '#{session_name}'`;
12583
+ const focusTasksCommand = `${envStr}${invStr} focus-tasks --session '#{session_name}' --window '#{window_id}'`;
11899
12584
  const focusTasksTmuxCommand = `run-shell ${shellQuote(focusTasksCommand)}`;
12585
+ const layoutCommand = (action2) => `${envStr}${invStr} layout --session '#{session_name}' --window '#{window_id}' --action ${action2}`;
12586
+ const restoreTasksCommand = layoutCommand("tasks-restore");
12587
+ const restoreTasksTmuxCommand = `run-shell ${shellQuote(restoreTasksCommand)}`;
12588
+ const closeChatTabCommand = layoutCommand("chat-tab-close");
12589
+ const closeChatTabTmuxCommand = `run-shell ${shellQuote(closeChatTabCommand)}`;
11900
12590
  const healLayoutCommand = `${envStr}${invStr} heal-layout --session '#{session_name}'`;
11901
12591
  const healLayoutTmuxCommand = `run-shell -b ${shellQuote(healLayoutCommand)}`;
11902
12592
  const captureLayoutCommand = `${envStr}${invStr} capture-layout --session '#{session_name}'`;
11903
12593
  const captureLayoutTmuxCommand = `run-shell -b ${shellQuote(captureLayoutCommand)}`;
11904
12594
  const userKeys = resolveUserTmuxKeys();
11905
- const unbinds = [];
12595
+ const unbinds = TMUX_LEGACY_LAYOUT_ROOT_KEYS.map((key) => ["unbind-key", "-n", key]);
11906
12596
  if (userKeys.overridden.has(TMUX_FOCUS_ID)) {
11907
12597
  for (const chord of TMUX_FOCUS_DEFAULTS) {
11908
12598
  const t = chordToTmuxKey(chord);
@@ -11914,16 +12604,26 @@ async function ensureSessionImpl(opts) {
11914
12604
  if (id === TMUX_FOCUS_ID)
11915
12605
  continue;
11916
12606
  const def = TMUX_SINGLE_BINDING_DEFAULTS[id];
11917
- const t = chordToTmuxKey(def);
12607
+ const isPrefix = isTmuxPrefixBindingId(id);
12608
+ const t = chordToTmuxKey(def, { allowBare: isPrefix });
11918
12609
  if ("key" in t)
11919
- unbinds.push(["unbind-key", "-n", t.key]);
12610
+ unbinds.push(isPrefix ? ["unbind-key", t.key] : ["unbind-key", "-n", t.key]);
11920
12611
  }
11921
12612
  const focusDirections = ["-L", "-D", "-U", "-R"];
11922
12613
  const focusBinds = userKeys.focus.flatMap((bind, i) => {
11923
12614
  const dir = focusDirections[i];
11924
- return bind && dir ? [focusBindCommand(bind.key, dir)] : [];
12615
+ const edgeCommand = dir === "-L" ? restoreTasksTmuxCommand : undefined;
12616
+ return bind && dir ? [focusBindCommand(bind.key, dir, edgeCommand)] : [];
11925
12617
  });
11926
12618
  const b = userKeys.binds;
12619
+ const layoutBind = (id, action2) => {
12620
+ const bind = b[id];
12621
+ return bind ? [["bind-key", bind.key, "run-shell", layoutCommand(action2)]] : [];
12622
+ };
12623
+ const layoutChordGroup = (...ids) => {
12624
+ const chords = ids.map((id) => b[id]?.chord).filter((chord) => !!chord);
12625
+ return chords.length > 0 ? chords.join("/") : null;
12626
+ };
11927
12627
  await runTmuxSequence([
11928
12628
  ["set-option", "-g", "status", "on"],
11929
12629
  ["set-window-option", "-g", "aggressive-resize", "on"],
@@ -11938,7 +12638,9 @@ async function ensureSessionImpl(opts) {
11938
12638
  kobeStatusRight({
11939
12639
  focusLeft: userKeys.focus[0]?.key ?? null,
11940
12640
  detach: b["tmux.detach"]?.key ?? null,
11941
- newTab: b["tmux.tab.new"]?.key ?? null
12641
+ newTab: b["tmux.tab.new"]?.key ?? null,
12642
+ layoutSplits: layoutChordGroup("tmux.layout.workspaceSplit", "tmux.layout.workspaceClose", "tmux.layout.workspaceReset"),
12643
+ layoutPanes: layoutChordGroup("tmux.layout.tasksToggle", "tmux.layout.opsToggle", "tmux.layout.terminalToggle")
11942
12644
  })
11943
12645
  ],
11944
12646
  ["set-option", "-g", "mouse", "on"],
@@ -11952,7 +12654,7 @@ async function ensureSessionImpl(opts) {
11952
12654
  b["tmux.detach"].key,
11953
12655
  "if-shell",
11954
12656
  "-F",
11955
- "#{==:#{@kobe_role},tasks}",
12657
+ `#{?#{${HIDDEN_TASKS_PANE_OPTION}},1,#{==:#{@kobe_role},tasks}}`,
11956
12658
  "detach-client",
11957
12659
  focusTasksTmuxCommand
11958
12660
  ]
@@ -11961,8 +12663,14 @@ async function ensureSessionImpl(opts) {
11961
12663
  ...b["tmux.tab.new"] ? [["bind-key", "-n", b["tmux.tab.new"].key, "run-shell", newChatTabCommand]] : [],
11962
12664
  ...b["tmux.tab.chooseEngine"] ? chatTabChooseEngineBindings(b["tmux.tab.chooseEngine"].key).map((binding) => [...binding, chooseEngineTmuxCommand]) : [],
11963
12665
  ...b["tmux.tab.prev"] && b["tmux.tab.next"] ? chatTabSwitchBindings(b["tmux.tab.prev"].key, b["tmux.tab.next"].key) : b["tmux.tab.prev"] ? [["bind-key", "-n", b["tmux.tab.prev"].key, "previous-window"]] : b["tmux.tab.next"] ? [["bind-key", "-n", b["tmux.tab.next"].key, "next-window"]] : [],
11964
- ...b["tmux.tab.close"] ? [chatTabCloseBinding(b["tmux.tab.close"].key)] : [],
12666
+ ...b["tmux.tab.close"] ? [chatTabCloseBinding(b["tmux.tab.close"].key, closeChatTabTmuxCommand)] : [],
11965
12667
  ...b["tmux.tab.rename"] ? [chatTabRenameBinding(b["tmux.tab.rename"].key)] : [],
12668
+ ...layoutBind("tmux.layout.workspaceSplit", "workspace-split"),
12669
+ ...layoutBind("tmux.layout.workspaceClose", "workspace-close"),
12670
+ ...layoutBind("tmux.layout.workspaceReset", "workspace-reset"),
12671
+ ...layoutBind("tmux.layout.tasksToggle", "tasks-toggle"),
12672
+ ...layoutBind("tmux.layout.opsToggle", "ops-toggle"),
12673
+ ...layoutBind("tmux.layout.terminalToggle", "terminal-toggle"),
11966
12674
  ["bind-key", "f", "run-shell", `${envStr}${invStr} quick-create --session '#{session_name}'`]
11967
12675
  ]);
11968
12676
  await applyTmuxPaneBorderTheme();
@@ -11973,10 +12681,27 @@ async function ensureSessionImpl(opts) {
11973
12681
  }
11974
12682
  return true;
11975
12683
  }
11976
- async function selectTasksPane(session) {
12684
+ async function paneIdByRoleInWindow(session, role, windowId) {
12685
+ const target = windowId?.trim() || `=${session}`;
12686
+ const { code, stdout } = await runTmuxCapturing(["list-panes", "-t", target, "-F", "#{pane_id}\t#{@kobe_role}"]);
12687
+ if (code !== 0)
12688
+ return "";
12689
+ for (const line of stdout.split(`
12690
+ `)) {
12691
+ const [paneId, paneRole] = line.split("\t");
12692
+ if (paneId?.trim() && paneRole?.trim() === role)
12693
+ return paneId.trim();
12694
+ }
12695
+ return "";
12696
+ }
12697
+ async function selectTasksPane(session, opts = {}) {
11977
12698
  if (!await sessionExists(session))
11978
12699
  return "";
11979
- const tasksPane = await paneIdByRole(session, "tasks");
12700
+ let tasksPane = await paneIdByRoleInWindow(session, "tasks", opts.windowId);
12701
+ if (!tasksPane) {
12702
+ await runLayoutAction(session, "tasks-restore", { windowId: opts.windowId });
12703
+ tasksPane = await paneIdByRoleInWindow(session, "tasks", opts.windowId);
12704
+ }
11980
12705
  if (!tasksPane)
11981
12706
  return "";
11982
12707
  await runTmux(["select-pane", "-t", tasksPane]);
@@ -11994,11 +12719,13 @@ var init_tmux = __esm(() => {
11994
12719
  init_tmux_border_theme();
11995
12720
  init_chattab();
11996
12721
  init_launch();
12722
+ init_layout_actions();
11997
12723
  init_layout_coord();
11998
12724
  init_pane_heal();
11999
12725
  init_client2();
12000
12726
  init_chattab();
12001
12727
  init_pane_heal();
12728
+ init_layout_actions();
12002
12729
  FOCUS_EDGE_VARS = {
12003
12730
  "-L": "pane_at_left",
12004
12731
  "-D": "pane_at_bottom",
@@ -20263,6 +20990,54 @@ var init_keybindings2 = __esm(() => {
20263
20990
  description: "Open the engine in a new tab (tmux prefix, then t)",
20264
20991
  hint: { keys: "prefix t", label: "engine tab", status: false }
20265
20992
  },
20993
+ {
20994
+ id: "tmux.layout.workspaceSplit",
20995
+ scope: "global",
20996
+ keys: [],
20997
+ category: "Workspace (tmux)",
20998
+ description: "Add a temporary workspace split (tmux prefix, then s)",
20999
+ hint: { keys: "prefix s", label: "split", status: false }
21000
+ },
21001
+ {
21002
+ id: "tmux.layout.workspaceClose",
21003
+ scope: "global",
21004
+ keys: [],
21005
+ category: "Workspace (tmux)",
21006
+ description: "Close the focused temporary workspace split (tmux prefix, then x)",
21007
+ hint: { keys: "prefix x", label: "close split", status: false }
21008
+ },
21009
+ {
21010
+ id: "tmux.layout.workspaceReset",
21011
+ scope: "global",
21012
+ keys: [],
21013
+ category: "Workspace (tmux)",
21014
+ description: "Close all temporary workspace splits (tmux prefix, then r)",
21015
+ hint: { keys: "prefix r", label: "reset splits", status: false }
21016
+ },
21017
+ {
21018
+ id: "tmux.layout.tasksToggle",
21019
+ scope: "global",
21020
+ keys: [],
21021
+ category: "Workspace (tmux)",
21022
+ description: "Hide / restore the Tasks pane (tmux prefix, then a)",
21023
+ hint: { keys: "prefix a", label: "tasks pane", status: false }
21024
+ },
21025
+ {
21026
+ id: "tmux.layout.opsToggle",
21027
+ scope: "global",
21028
+ keys: [],
21029
+ category: "Workspace (tmux)",
21030
+ description: "Toggle the file/Ops pane (tmux prefix, then o)",
21031
+ hint: { keys: "prefix o", label: "file pane", status: false }
21032
+ },
21033
+ {
21034
+ id: "tmux.layout.terminalToggle",
21035
+ scope: "global",
21036
+ keys: [],
21037
+ category: "Workspace (tmux)",
21038
+ description: "Hide / restore the terminal pane (tmux prefix, then z)",
21039
+ hint: { keys: "prefix z", label: "terminal", status: false }
21040
+ },
20266
21041
  {
20267
21042
  id: "chat.send",
20268
21043
  scope: "workspace",
@@ -20526,6 +21301,10 @@ function applyUserKeybindings() {
20526
21301
  keys: bind ? [bind.chord] : [],
20527
21302
  defaultKeys: [TMUX_SINGLE_BINDING_DEFAULTS[id]]
20528
21303
  });
21304
+ const displayRow = KobeKeymap.find((row) => row.id === id);
21305
+ if (displayRow?.hint) {
21306
+ displayRow.hint.keys = bind ? `${isTmuxPrefixBindingId(id) ? "prefix " : ""}${bind.chord}` : "\u2014";
21307
+ }
20529
21308
  }
20530
21309
  }
20531
21310
  for (const w of warnings)
@@ -21007,6 +21786,45 @@ var init_host_boot = __esm(() => {
21007
21786
  init_persisted_ui_prefs();
21008
21787
  });
21009
21788
 
21789
+ // src/tui/lib/task-enter.ts
21790
+ async function ensureTaskSession2(orch, task, repo, vendor, opts = {}) {
21791
+ const session = tmuxSessionName(task.id);
21792
+ if (await sessionExists(session))
21793
+ return true;
21794
+ let worktree = task.worktreePath;
21795
+ if (!worktree)
21796
+ worktree = await orch.ensureWorktree(task.id);
21797
+ if (!worktree)
21798
+ throw new Error(`task ${task.id} has no worktree`);
21799
+ const { ensureSession: ensureSession2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
21800
+ const { resolveRepoInit: resolveRepoInit2 } = await Promise.resolve().then(() => (init_repo_init(), exports_repo_init));
21801
+ const init2 = resolveRepoInit2(repo, worktree);
21802
+ const ok = await ensureSession2({
21803
+ name: session,
21804
+ cwd: worktree,
21805
+ command: interactiveEngineCommand(vendor),
21806
+ taskId: task.id,
21807
+ vendor,
21808
+ repo,
21809
+ initScript: init2.initScript,
21810
+ initPrompt: opts.includeInitPrompt ? init2.initPrompt : undefined
21811
+ });
21812
+ if (!ok)
21813
+ throw new Error(`failed to start tmux session for ${task.id}`);
21814
+ return false;
21815
+ }
21816
+ async function jumpToTask(orch, task, repo, vendor, opts = {}) {
21817
+ await ensureTaskSession2(orch, task, repo, vendor, opts);
21818
+ await orch.setActiveTask(task.id).catch(() => {});
21819
+ const { prepareWindowForSwitch: prepareWindowForSwitch2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
21820
+ await prepareWindowForSwitch2(tmuxSessionName(task.id));
21821
+ await runTmux(["switch-client", "-t", `=${tmuxSessionName(task.id)}`]);
21822
+ }
21823
+ var init_task_enter = __esm(() => {
21824
+ init_interactive_command();
21825
+ init_client2();
21826
+ });
21827
+
21010
21828
  // src/tui/new-task/host.tsx
21011
21829
  var exports_host = {};
21012
21830
  __export(exports_host, {
@@ -21041,10 +21859,11 @@ function NewTaskPage(props) {
21041
21859
  console.error("[kobe new-task] no daemon; cannot create task");
21042
21860
  process.exit(1);
21043
21861
  }
21862
+ let entered;
21044
21863
  try {
21045
21864
  if (result.mode === "adopt") {
21046
21865
  for (const w of result.adopt) {
21047
- await orch.adoptWorktree({
21866
+ entered = await orch.adoptWorktree({
21048
21867
  repo: result.repo,
21049
21868
  worktreePath: w.worktreePath,
21050
21869
  branch: w.branch,
@@ -21052,7 +21871,7 @@ function NewTaskPage(props) {
21052
21871
  });
21053
21872
  }
21054
21873
  } else {
21055
- await orch.createTask({
21874
+ entered = await orch.createTask({
21056
21875
  repo: result.repo,
21057
21876
  baseRef: result.baseRef,
21058
21877
  vendor: result.vendor
@@ -21062,6 +21881,15 @@ function NewTaskPage(props) {
21062
21881
  console.error("[kobe new-task] task.create/adopt failed:", err);
21063
21882
  process.exit(1);
21064
21883
  }
21884
+ if (entered) {
21885
+ try {
21886
+ await jumpToTask(orch, entered, result.repo, result.vendor, {
21887
+ includeInitPrompt: result.mode !== "adopt"
21888
+ });
21889
+ } catch (err) {
21890
+ console.error("[kobe new-task] auto-enter failed:", err);
21891
+ }
21892
+ }
21065
21893
  process.exit(0);
21066
21894
  }
21067
21895
  return (() => {
@@ -21112,6 +21940,7 @@ var init_host = __esm(() => {
21112
21940
  init_new_task_dialog();
21113
21941
  init_theme2();
21114
21942
  init_host_boot();
21943
+ init_task_enter();
21115
21944
  init_dialog();
21116
21945
  });
21117
21946
 
@@ -21249,35 +22078,6 @@ async function resolveQuickTaskContext(orch, session) {
21249
22078
  fallbackRepo
21250
22079
  };
21251
22080
  }
21252
- async function ensureTaskSession2(orch, task, repo, vendor) {
21253
- const session = tmuxSessionName(task.id);
21254
- if (await sessionExists(session))
21255
- return true;
21256
- let worktree = task.worktreePath;
21257
- if (!worktree)
21258
- worktree = await orch.ensureWorktree(task.id);
21259
- if (!worktree)
21260
- throw new Error(`task ${task.id} has no worktree`);
21261
- const {
21262
- ensureSession: ensureSession2
21263
- } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
21264
- const {
21265
- resolveRepoInit: resolveRepoInit2
21266
- } = await Promise.resolve().then(() => (init_repo_init(), exports_repo_init));
21267
- const init2 = resolveRepoInit2(repo, worktree);
21268
- const ok = await ensureSession2({
21269
- name: session,
21270
- cwd: worktree,
21271
- command: interactiveEngineCommand(vendor),
21272
- taskId: task.id,
21273
- vendor,
21274
- repo,
21275
- initScript: init2.initScript
21276
- });
21277
- if (!ok)
21278
- throw new Error(`failed to start tmux session for ${task.id}`);
21279
- return false;
21280
- }
21281
22081
  async function deliverFirstPromptToTask(orch, task, repo, vendor, prompt) {
21282
22082
  const existed = await ensureTaskSession2(orch, task, repo, vendor);
21283
22083
  const session = tmuxSessionName(task.id);
@@ -21287,15 +22087,6 @@ async function deliverFirstPromptToTask(orch, task, repo, vendor, prompt) {
21287
22087
  if (pane)
21288
22088
  await pasteAndSubmit(pane, prompt);
21289
22089
  }
21290
- async function jumpToTask(orch, task, repo, vendor) {
21291
- await ensureTaskSession2(orch, task, repo, vendor);
21292
- await orch.setActiveTask(task.id).catch(() => {});
21293
- const {
21294
- prepareWindowForSwitch: prepareWindowForSwitch2
21295
- } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
21296
- await prepareWindowForSwitch2(tmuxSessionName(task.id));
21297
- await runTmux(["switch-client", "-t", `=${tmuxSessionName(task.id)}`]);
21298
- }
21299
22090
  function QuickTaskPage(props) {
21300
22091
  const {
21301
22092
  theme
@@ -21394,6 +22185,7 @@ var init_host2 = __esm(() => {
21394
22185
  init_git_snapshot();
21395
22186
  init_host_boot();
21396
22187
  init_path_helpers();
22188
+ init_task_enter();
21397
22189
  init_host();
21398
22190
  init_groups();
21399
22191
  init_dialog();
@@ -21432,8 +22224,10 @@ function formatChord(chord, prefixGlyph = "\u2303B") {
21432
22224
  if (!s)
21433
22225
  return s;
21434
22226
  const pm = /^prefix\s+(.+)$/i.exec(s);
21435
- if (pm)
21436
- return `${prefixGlyph} ${formatKey(pm[1] ?? "", true)}`;
22227
+ if (pm) {
22228
+ const suffix = pm[1] ?? "";
22229
+ return `${prefixGlyph} ${suffix.includes("+") ? formatChord(suffix, prefixGlyph) : formatKey(suffix, true)}`;
22230
+ }
21437
22231
  const parts = s.split("+");
21438
22232
  if (parts.length === 1)
21439
22233
  return formatKey(parts[0] ?? "", false);
@@ -23022,12 +23816,12 @@ function KeybindingsSettingsSection() {
23022
23816
  const report = userKeybindingsReport();
23023
23817
  const fixedIds = Object.keys(FIXED_BINDING_IDS).sort();
23024
23818
  return (() => {
23025
- var _el$161 = createElement("box"), _el$162 = createElement("text"), _el$164 = createElement("text"), _el$166 = createElement("box"), _el$167 = createElement("text"), _el$169 = createElement("text"), _el$197 = createElement("text"), _el$199 = createElement("text");
23819
+ var _el$161 = createElement("box"), _el$162 = createElement("text"), _el$164 = createElement("text"), _el$166 = createElement("box"), _el$167 = createElement("text"), _el$169 = createElement("text"), _el$199 = createElement("text"), _el$201 = createElement("text");
23026
23820
  insertNode(_el$161, _el$162);
23027
23821
  insertNode(_el$161, _el$164);
23028
23822
  insertNode(_el$161, _el$166);
23029
- insertNode(_el$161, _el$197);
23030
23823
  insertNode(_el$161, _el$199);
23824
+ insertNode(_el$161, _el$201);
23031
23825
  setProp(_el$161, "flexDirection", "column");
23032
23826
  setProp(_el$161, "gap", 1);
23033
23827
  insertNode(_el$162, createTextNode(`Keybindings`));
@@ -23046,7 +23840,7 @@ function KeybindingsSettingsSection() {
23046
23840
  return !report.exists;
23047
23841
  },
23048
23842
  get children() {
23049
- var _el$170 = createElement("box"), _el$171 = createElement("text"), _el$173 = createElement("text"), _el$175 = createElement("text"), _el$177 = createElement("text"), _el$179 = createElement("text"), _el$181 = createElement("text"), _el$183 = createElement("text"), _el$185 = createElement("text"), _el$187 = createElement("text");
23843
+ var _el$170 = createElement("box"), _el$171 = createElement("text"), _el$173 = createElement("text"), _el$175 = createElement("text"), _el$177 = createElement("text"), _el$179 = createElement("text"), _el$181 = createElement("text"), _el$183 = createElement("text"), _el$185 = createElement("text"), _el$187 = createElement("text"), _el$189 = createElement("text");
23050
23844
  insertNode(_el$170, _el$171);
23051
23845
  insertNode(_el$170, _el$173);
23052
23846
  insertNode(_el$170, _el$175);
@@ -23056,6 +23850,7 @@ function KeybindingsSettingsSection() {
23056
23850
  insertNode(_el$170, _el$183);
23057
23851
  insertNode(_el$170, _el$185);
23058
23852
  insertNode(_el$170, _el$187);
23853
+ insertNode(_el$170, _el$189);
23059
23854
  setProp(_el$170, "flexDirection", "column");
23060
23855
  setProp(_el$170, "gap", 0);
23061
23856
  insertNode(_el$171, createTextNode(`Example`));
@@ -23064,11 +23859,12 @@ function KeybindingsSettingsSection() {
23064
23859
  insertNode(_el$177, createTextNode(` sidebar.select: [enter] # list = several chords`));
23065
23860
  insertNode(_el$179, createTextNode(` files.createPR: null # null = unbind`));
23066
23861
  insertNode(_el$181, createTextNode(` tmux.tab.new: ctrl+y # tmux session key (see below)`));
23067
- insertNode(_el$183, createTextNode(`darwin: # platform overlay (also: linux)`));
23068
- insertNode(_el$185, createTextNode(` bindings:`));
23069
- insertNode(_el$187, createTextNode(` palette.open: [cmd+p, ctrl+p]`));
23862
+ insertNode(_el$183, createTextNode(` tmux.layout.workspaceSplit: g # prefix g`));
23863
+ insertNode(_el$185, createTextNode(`darwin: # platform overlay (also: linux)`));
23864
+ insertNode(_el$187, createTextNode(` bindings:`));
23865
+ insertNode(_el$189, createTextNode(` palette.open: [cmd+p, ctrl+p]`));
23070
23866
  effect((_p$) => {
23071
- var _v$107 = theme.text, _v$108 = TextAttributes7.BOLD, _v$109 = theme.textMuted, _v$110 = theme.textMuted, _v$111 = theme.textMuted, _v$112 = theme.textMuted, _v$113 = theme.textMuted, _v$114 = theme.textMuted, _v$115 = theme.textMuted, _v$116 = theme.textMuted;
23867
+ var _v$107 = theme.text, _v$108 = TextAttributes7.BOLD, _v$109 = theme.textMuted, _v$110 = theme.textMuted, _v$111 = theme.textMuted, _v$112 = theme.textMuted, _v$113 = theme.textMuted, _v$114 = theme.textMuted, _v$115 = theme.textMuted, _v$116 = theme.textMuted, _v$117 = theme.textMuted;
23072
23868
  _v$107 !== _p$.e && (_p$.e = setProp(_el$171, "fg", _v$107, _p$.e));
23073
23869
  _v$108 !== _p$.t && (_p$.t = setProp(_el$171, "attributes", _v$108, _p$.t));
23074
23870
  _v$109 !== _p$.a && (_p$.a = setProp(_el$173, "fg", _v$109, _p$.a));
@@ -23079,6 +23875,7 @@ function KeybindingsSettingsSection() {
23079
23875
  _v$114 !== _p$.h && (_p$.h = setProp(_el$183, "fg", _v$114, _p$.h));
23080
23876
  _v$115 !== _p$.r && (_p$.r = setProp(_el$185, "fg", _v$115, _p$.r));
23081
23877
  _v$116 !== _p$.d && (_p$.d = setProp(_el$187, "fg", _v$116, _p$.d));
23878
+ _v$117 !== _p$.l && (_p$.l = setProp(_el$189, "fg", _v$117, _p$.l));
23082
23879
  return _p$;
23083
23880
  }, {
23084
23881
  e: undefined,
@@ -23090,104 +23887,105 @@ function KeybindingsSettingsSection() {
23090
23887
  s: undefined,
23091
23888
  h: undefined,
23092
23889
  r: undefined,
23093
- d: undefined
23890
+ d: undefined,
23891
+ l: undefined
23094
23892
  });
23095
23893
  return _el$170;
23096
23894
  }
23097
- }), _el$197);
23895
+ }), _el$199);
23098
23896
  insert(_el$161, createComponent2(Show, {
23099
23897
  get when() {
23100
23898
  return report.exists;
23101
23899
  },
23102
23900
  get children() {
23103
- var _el$189 = createElement("box"), _el$190 = createElement("text");
23104
- insertNode(_el$189, _el$190);
23105
- setProp(_el$189, "flexDirection", "column");
23106
- setProp(_el$189, "gap", 0);
23107
- insertNode(_el$190, createTextNode(`Overrides applied`));
23108
- insert(_el$189, createComponent2(Show, {
23901
+ var _el$191 = createElement("box"), _el$192 = createElement("text");
23902
+ insertNode(_el$191, _el$192);
23903
+ setProp(_el$191, "flexDirection", "column");
23904
+ setProp(_el$191, "gap", 0);
23905
+ insertNode(_el$192, createTextNode(`Overrides applied`));
23906
+ insert(_el$191, createComponent2(Show, {
23109
23907
  get when() {
23110
23908
  return report.applied.length === 0;
23111
23909
  },
23112
23910
  get children() {
23113
- var _el$192 = createElement("text");
23114
- insertNode(_el$192, createTextNode(`none`));
23115
- effect((_$p) => setProp(_el$192, "fg", theme.textMuted, _$p));
23116
- return _el$192;
23911
+ var _el$194 = createElement("text");
23912
+ insertNode(_el$194, createTextNode(`none`));
23913
+ effect((_$p) => setProp(_el$194, "fg", theme.textMuted, _$p));
23914
+ return _el$194;
23117
23915
  }
23118
23916
  }), null);
23119
- insert(_el$189, createComponent2(For, {
23917
+ insert(_el$191, createComponent2(For, {
23120
23918
  get each() {
23121
23919
  return report.applied;
23122
23920
  },
23123
23921
  children: (o) => (() => {
23124
- var _el$200 = createElement("text");
23125
- setProp(_el$200, "wrapMode", "word");
23126
- insert(_el$200, () => `${o.id} \u2192 ${o.keys.length > 0 ? o.keys.join(" / ") : "(unbound)"} (default: ${o.defaultKeys.join(" / ")})`);
23127
- effect((_$p) => setProp(_el$200, "fg", theme.text, _$p));
23128
- return _el$200;
23922
+ var _el$202 = createElement("text");
23923
+ setProp(_el$202, "wrapMode", "word");
23924
+ insert(_el$202, () => `${o.id} \u2192 ${o.keys.length > 0 ? o.keys.join(" / ") : "(unbound)"} (default: ${o.defaultKeys.join(" / ")})`);
23925
+ effect((_$p) => setProp(_el$202, "fg", theme.text, _$p));
23926
+ return _el$202;
23129
23927
  })()
23130
23928
  }), null);
23131
23929
  effect((_p$) => {
23132
- var _v$117 = theme.text, _v$118 = TextAttributes7.BOLD;
23133
- _v$117 !== _p$.e && (_p$.e = setProp(_el$190, "fg", _v$117, _p$.e));
23134
- _v$118 !== _p$.t && (_p$.t = setProp(_el$190, "attributes", _v$118, _p$.t));
23930
+ var _v$118 = theme.text, _v$119 = TextAttributes7.BOLD;
23931
+ _v$118 !== _p$.e && (_p$.e = setProp(_el$192, "fg", _v$118, _p$.e));
23932
+ _v$119 !== _p$.t && (_p$.t = setProp(_el$192, "attributes", _v$119, _p$.t));
23135
23933
  return _p$;
23136
23934
  }, {
23137
23935
  e: undefined,
23138
23936
  t: undefined
23139
23937
  });
23140
- return _el$189;
23938
+ return _el$191;
23141
23939
  }
23142
- }), _el$197);
23940
+ }), _el$199);
23143
23941
  insert(_el$161, createComponent2(Show, {
23144
23942
  get when() {
23145
23943
  return report.warnings.length > 0;
23146
23944
  },
23147
23945
  get children() {
23148
- var _el$194 = createElement("box"), _el$195 = createElement("text");
23149
- insertNode(_el$194, _el$195);
23150
- setProp(_el$194, "flexDirection", "column");
23151
- setProp(_el$194, "gap", 0);
23152
- insertNode(_el$195, createTextNode(`Warnings`));
23153
- insert(_el$194, createComponent2(For, {
23946
+ var _el$196 = createElement("box"), _el$197 = createElement("text");
23947
+ insertNode(_el$196, _el$197);
23948
+ setProp(_el$196, "flexDirection", "column");
23949
+ setProp(_el$196, "gap", 0);
23950
+ insertNode(_el$197, createTextNode(`Warnings`));
23951
+ insert(_el$196, createComponent2(For, {
23154
23952
  get each() {
23155
23953
  return report.warnings;
23156
23954
  },
23157
23955
  children: (w) => (() => {
23158
- var _el$201 = createElement("text");
23159
- setProp(_el$201, "wrapMode", "word");
23160
- insert(_el$201, `! ${w}`);
23161
- effect((_$p) => setProp(_el$201, "fg", theme.warning, _$p));
23162
- return _el$201;
23956
+ var _el$203 = createElement("text");
23957
+ setProp(_el$203, "wrapMode", "word");
23958
+ insert(_el$203, `! ${w}`);
23959
+ effect((_$p) => setProp(_el$203, "fg", theme.warning, _$p));
23960
+ return _el$203;
23163
23961
  })()
23164
23962
  }), null);
23165
23963
  effect((_p$) => {
23166
- var _v$119 = theme.warning, _v$120 = TextAttributes7.BOLD;
23167
- _v$119 !== _p$.e && (_p$.e = setProp(_el$195, "fg", _v$119, _p$.e));
23168
- _v$120 !== _p$.t && (_p$.t = setProp(_el$195, "attributes", _v$120, _p$.t));
23964
+ var _v$120 = theme.warning, _v$121 = TextAttributes7.BOLD;
23965
+ _v$120 !== _p$.e && (_p$.e = setProp(_el$197, "fg", _v$120, _p$.e));
23966
+ _v$121 !== _p$.t && (_p$.t = setProp(_el$197, "attributes", _v$121, _p$.t));
23169
23967
  return _p$;
23170
23968
  }, {
23171
23969
  e: undefined,
23172
23970
  t: undefined
23173
23971
  });
23174
- return _el$194;
23972
+ return _el$196;
23175
23973
  }
23176
- }), _el$197);
23177
- insertNode(_el$197, createTextNode(`tmux session keys use the same file: tmux.tab.new (ctrl+t), tmux.tab.prev/next (ctrl+[/]), tmux.tab.close (ctrl+w), tmux.tab.rename (f2), tmux.tab.chooseEngine (ctrl+shift+t), tmux.detach (ctrl+q), tmux.focus (4 chords, left/down/up/right). They apply when a session is (re)built.`));
23178
- setProp(_el$197, "wrapMode", "word");
23974
+ }), _el$199);
23975
+ insertNode(_el$199, createTextNode(`tmux session keys use the same file: tmux.tab.new (ctrl+t), tmux.tab.prev/next (ctrl+[/]), tmux.tab.close (ctrl+w), tmux.tab.rename (f2), tmux.tab.chooseEngine (ctrl+shift+t), tmux.detach (ctrl+q), tmux.focus (4 chords, left/down/up/right), and prefix layout keys: workspaceSplit (s), workspaceClose (x), workspaceReset (r), tasksToggle (a), opsToggle (o), terminalToggle (z). They apply when a session is (re)built.`));
23179
23976
  setProp(_el$199, "wrapMode", "word");
23180
- insert(_el$199, () => `Fixed (not rebindable): ${fixedIds.join(", ")}.`);
23977
+ setProp(_el$201, "wrapMode", "word");
23978
+ insert(_el$201, () => `Fixed (not rebindable): ${fixedIds.join(", ")}.`);
23181
23979
  effect((_p$) => {
23182
- var _v$121 = theme.text, _v$122 = TextAttributes7.BOLD, _v$123 = theme.textMuted, _v$124 = theme.text, _v$125 = TextAttributes7.BOLD, _v$126 = theme.textMuted, _v$127 = theme.textMuted, _v$128 = theme.textMuted;
23183
- _v$121 !== _p$.e && (_p$.e = setProp(_el$162, "fg", _v$121, _p$.e));
23184
- _v$122 !== _p$.t && (_p$.t = setProp(_el$162, "attributes", _v$122, _p$.t));
23185
- _v$123 !== _p$.a && (_p$.a = setProp(_el$164, "fg", _v$123, _p$.a));
23186
- _v$124 !== _p$.o && (_p$.o = setProp(_el$167, "fg", _v$124, _p$.o));
23187
- _v$125 !== _p$.i && (_p$.i = setProp(_el$167, "attributes", _v$125, _p$.i));
23188
- _v$126 !== _p$.n && (_p$.n = setProp(_el$169, "fg", _v$126, _p$.n));
23189
- _v$127 !== _p$.s && (_p$.s = setProp(_el$197, "fg", _v$127, _p$.s));
23190
- _v$128 !== _p$.h && (_p$.h = setProp(_el$199, "fg", _v$128, _p$.h));
23980
+ var _v$122 = theme.text, _v$123 = TextAttributes7.BOLD, _v$124 = theme.textMuted, _v$125 = theme.text, _v$126 = TextAttributes7.BOLD, _v$127 = theme.textMuted, _v$128 = theme.textMuted, _v$129 = theme.textMuted;
23981
+ _v$122 !== _p$.e && (_p$.e = setProp(_el$162, "fg", _v$122, _p$.e));
23982
+ _v$123 !== _p$.t && (_p$.t = setProp(_el$162, "attributes", _v$123, _p$.t));
23983
+ _v$124 !== _p$.a && (_p$.a = setProp(_el$164, "fg", _v$124, _p$.a));
23984
+ _v$125 !== _p$.o && (_p$.o = setProp(_el$167, "fg", _v$125, _p$.o));
23985
+ _v$126 !== _p$.i && (_p$.i = setProp(_el$167, "attributes", _v$126, _p$.i));
23986
+ _v$127 !== _p$.n && (_p$.n = setProp(_el$169, "fg", _v$127, _p$.n));
23987
+ _v$128 !== _p$.s && (_p$.s = setProp(_el$199, "fg", _v$128, _p$.s));
23988
+ _v$129 !== _p$.h && (_p$.h = setProp(_el$201, "fg", _v$129, _p$.h));
23191
23989
  return _p$;
23192
23990
  }, {
23193
23991
  e: undefined,
@@ -24201,8 +24999,10 @@ async function createTaskFlow(ctx3) {
24201
24999
  return;
24202
25000
  }
24203
25001
  await ctx3.reload?.();
24204
- if (createdId)
25002
+ if (createdId) {
24205
25003
  ctx3.selectTask?.(createdId);
25004
+ await ctx3.enterTask?.(createdId);
25005
+ }
24206
25006
  }
24207
25007
  var init_task_actions = __esm(() => {
24208
25008
  init_account_detect();
@@ -25783,7 +26583,8 @@ function TasksShell(props) {
25783
26583
  await openNewTaskTab(session, defaultRepo);
25784
26584
  return true;
25785
26585
  },
25786
- selectTask: (id) => setSelectedId(id)
26586
+ selectTask: (id) => setSelectedId(id),
26587
+ enterTask: (id) => switchTo(id)
25787
26588
  };
25788
26589
  async function createTask() {
25789
26590
  await createTaskFlow(taskActions);
@@ -26132,11 +26933,21 @@ function ShortcutHints(props) {
26132
26933
  label: "move panes"
26133
26934
  });
26134
26935
  }
26936
+ const layoutGroup = (label, ids) => {
26937
+ const chords = ids.map((id) => b[id]?.chord).filter((chord) => !!chord);
26938
+ if (chords.length > 0)
26939
+ out.push({
26940
+ k: `prefix ${chords.join("/")}`,
26941
+ label
26942
+ });
26943
+ };
26135
26944
  if (b["tmux.detach"])
26136
26945
  out.push({
26137
26946
  k: b["tmux.detach"].chord,
26138
26947
  label: "tasks\u2192detach"
26139
26948
  });
26949
+ layoutGroup("splits", ["tmux.layout.workspaceSplit", "tmux.layout.workspaceClose", "tmux.layout.workspaceReset"]);
26950
+ layoutGroup("panes", ["tmux.layout.tasksToggle", "tmux.layout.opsToggle", "tmux.layout.terminalToggle"]);
26140
26951
  return out;
26141
26952
  };
26142
26953
  const defaultHints = () => {
@@ -29139,6 +29950,12 @@ function parseOpsFlags(argv) {
29139
29950
  } else if (flag === "--initial-task-id") {
29140
29951
  flags.initialTaskId = value;
29141
29952
  i++;
29953
+ } else if (flag === "--action") {
29954
+ flags.action = value;
29955
+ i++;
29956
+ } else if (flag === "--window") {
29957
+ flags.windowId = value;
29958
+ i++;
29142
29959
  }
29143
29960
  }
29144
29961
  return flags;
@@ -29277,7 +30094,7 @@ async function main() {
29277
30094
  process.exit(2);
29278
30095
  }
29279
30096
  const { selectTasksPane: selectTasksPane2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
29280
- await selectTasksPane2(session);
30097
+ await selectTasksPane2(session, { windowId: flags.windowId });
29281
30098
  return;
29282
30099
  }
29283
30100
  if (subcommand === "heal-layout") {
@@ -29308,6 +30125,34 @@ async function main() {
29308
30125
  });
29309
30126
  return;
29310
30127
  }
30128
+ if (subcommand === "layout") {
30129
+ const flags = parseOpsFlags(rest);
30130
+ const session = flags.session;
30131
+ if (!session) {
30132
+ console.error("kobe layout: --session <name> is required");
30133
+ process.exit(2);
30134
+ }
30135
+ const action = flags.action;
30136
+ const valid = new Set([
30137
+ "workspace-split",
30138
+ "workspace-close",
30139
+ "workspace-reset",
30140
+ "tasks-toggle",
30141
+ "tasks-restore",
30142
+ "ops-toggle",
30143
+ "terminal-toggle",
30144
+ "chat-tab-close"
30145
+ ]);
30146
+ if (!action || !valid.has(action)) {
30147
+ console.error("kobe layout: --action must be one of workspace-split, workspace-close, workspace-reset, tasks-toggle, tasks-restore, ops-toggle, terminal-toggle, chat-tab-close");
30148
+ process.exit(2);
30149
+ }
30150
+ const { runLayoutAction: runLayoutAction2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
30151
+ await runLayoutAction2(session, action, {
30152
+ windowId: flags.windowId
30153
+ });
30154
+ return;
30155
+ }
29311
30156
  if (subcommand === "quick-task") {
29312
30157
  const flags = parseOpsFlags(rest);
29313
30158
  const { startQuickTaskHost: startQuickTaskHost2 } = await Promise.resolve().then(() => (init_host2(), exports_host2));