@sma1lboy/kobe 0.5.15 → 0.5.16

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 (3) hide show
  1. package/dist/bin/kobed.js +1554 -310
  2. package/dist/cli/index.js +2404 -762
  3. package/package.json +3 -1
package/dist/cli/index.js CHANGED
@@ -14755,6 +14755,234 @@ var init_binary = __esm(() => {
14755
14755
  };
14756
14756
  });
14757
14757
 
14758
+ // src/engine/claude-code-local/models.ts
14759
+ function parseContextWindowSize(modelIdentifier) {
14760
+ const delimitedMatch = /(?:\(|\[)\s*(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])\s*(?:\)|\])/i.exec(modelIdentifier);
14761
+ if (delimitedMatch?.[1] && delimitedMatch[2]) {
14762
+ const parsed2 = Number.parseFloat(delimitedMatch[1].replace(/[,_]/g, ""));
14763
+ if (Number.isFinite(parsed2) && parsed2 > 0) {
14764
+ return Math.round(parsed2 * (delimitedMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
14765
+ }
14766
+ }
14767
+ const contextMatch = /\b(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])(?:\s*(?:token\s*)?context)?\b/i.exec(modelIdentifier);
14768
+ if (!contextMatch?.[1] || !contextMatch[2])
14769
+ return null;
14770
+ const parsed = Number.parseFloat(contextMatch[1].replace(/[,_]/g, ""));
14771
+ if (!Number.isFinite(parsed) || parsed <= 0)
14772
+ return null;
14773
+ return Math.round(parsed * (contextMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
14774
+ }
14775
+ function claudeContextWindowFor(modelId) {
14776
+ const parsed = parseContextWindowSize(modelId);
14777
+ if (parsed !== null)
14778
+ return parsed;
14779
+ if (modelId.includes("[1m]"))
14780
+ return LONG_CTX;
14781
+ const inCatalog = CLAUDE_MODELS.some((m2) => m2.id === modelId);
14782
+ if (inCatalog)
14783
+ return STD_CTX;
14784
+ if (modelId.includes("1m") || modelId.includes("[1M]"))
14785
+ return LONG_CTX;
14786
+ return STD_CTX;
14787
+ }
14788
+ var CLAUDE_MODELS, LONG_CTX = 1e6, STD_CTX = 200000;
14789
+ var init_models = __esm(() => {
14790
+ CLAUDE_MODELS = [
14791
+ { vendor: "claude", id: "claude-opus-4-7[1m]", label: "Opus 4.7 1M", hint: "long context, default" },
14792
+ { vendor: "claude", id: "claude-opus-4-7", label: "Opus 4.7", hint: "most capable, slowest" },
14793
+ { vendor: "claude", id: "claude-sonnet-4-6[1m]", label: "sonnet 4.6 (1M)", hint: "long context" },
14794
+ { vendor: "claude", id: "claude-sonnet-4-6", label: "sonnet 4.6" },
14795
+ { vendor: "claude", id: "claude-haiku-4-5-20251001", label: "haiku 4.5", hint: "fastest, cheapest" }
14796
+ ];
14797
+ });
14798
+
14799
+ // src/engine/claude-code-local/settings.ts
14800
+ import { readFileSync as readFileSync2 } from "fs";
14801
+ import { homedir as homedir3 } from "os";
14802
+ import { join as join2 } from "path";
14803
+ function readClaudeSettings() {
14804
+ if (cached !== undefined)
14805
+ return cached;
14806
+ try {
14807
+ const raw = readFileSync2(SETTINGS_PATH, "utf8");
14808
+ const parsed = JSON.parse(raw);
14809
+ if (!parsed || typeof parsed !== "object") {
14810
+ cached = null;
14811
+ return null;
14812
+ }
14813
+ const obj = parsed;
14814
+ const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
14815
+ cached = { model };
14816
+ return cached;
14817
+ } catch {
14818
+ cached = null;
14819
+ return null;
14820
+ }
14821
+ }
14822
+ function resolveClaudeDefaultModelId() {
14823
+ const settings = readClaudeSettings();
14824
+ if (settings?.model && settings.model.length > 0)
14825
+ return settings.model;
14826
+ return CLAUDE_FALLBACK_DEFAULT_MODEL_ID;
14827
+ }
14828
+ var SETTINGS_PATH, cached, CLAUDE_FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
14829
+ var init_settings = __esm(() => {
14830
+ SETTINGS_PATH = join2(homedir3(), ".claude", "settings.json");
14831
+ });
14832
+
14833
+ // src/engine/claude-code-local/capabilities.ts
14834
+ var claudeCapabilities, claudeIdentity;
14835
+ var init_capabilities = __esm(() => {
14836
+ init_models();
14837
+ init_settings();
14838
+ claudeCapabilities = {
14839
+ vendorId: "claude",
14840
+ label: "Claude Code",
14841
+ models: CLAUDE_MODELS,
14842
+ defaultModelId: resolveClaudeDefaultModelId,
14843
+ contextWindowFor: claudeContextWindowFor
14844
+ };
14845
+ claudeIdentity = {
14846
+ vendorId: "claude",
14847
+ productName: "Claude Code",
14848
+ shortName: "Claude",
14849
+ assistantName: "Claude",
14850
+ inputPlaceholder: "Ask Claude\u2026"
14851
+ };
14852
+ });
14853
+
14854
+ // src/engine/codex-local/models.ts
14855
+ function codexContextWindowFor(_modelId) {
14856
+ return DEFAULT_CTX;
14857
+ }
14858
+ var CODEX_MODELS, CODEX_FALLBACK_DEFAULT_MODEL_ID = "gpt-5.4-mini", DEFAULT_CTX = 400000;
14859
+ var init_models2 = __esm(() => {
14860
+ CODEX_MODELS = [
14861
+ { vendor: "codex", id: "gpt-5.5", label: "GPT-5.5", hint: "latest, ChatGPT-account compatible" },
14862
+ { vendor: "codex", id: "gpt-5.4", label: "GPT-5.4", hint: "stable" },
14863
+ { vendor: "codex", id: "gpt-5.4-mini", label: "GPT-5.4 mini", hint: "fastest, always supported" }
14864
+ ];
14865
+ });
14866
+
14867
+ // src/engine/codex-local/settings.ts
14868
+ import { readFileSync as readFileSync3 } from "fs";
14869
+ import { homedir as homedir4 } from "os";
14870
+ import { join as join3 } from "path";
14871
+ function readModelFromConfig() {
14872
+ try {
14873
+ const raw = readFileSync3(CONFIG_PATH, "utf8");
14874
+ let inTable = false;
14875
+ for (const line of raw.split(`
14876
+ `)) {
14877
+ const t2 = line.trim();
14878
+ if (t2.startsWith("[") && t2.endsWith("]")) {
14879
+ inTable = true;
14880
+ continue;
14881
+ }
14882
+ if (inTable)
14883
+ continue;
14884
+ const m2 = /^model\s*=\s*"([^"]+)"/.exec(t2);
14885
+ if (m2)
14886
+ return m2[1] ?? null;
14887
+ }
14888
+ return null;
14889
+ } catch {
14890
+ return null;
14891
+ }
14892
+ }
14893
+ function resolveCodexDefaultModelId() {
14894
+ if (cached2 === undefined)
14895
+ cached2 = readModelFromConfig();
14896
+ return cached2 ?? CODEX_FALLBACK_DEFAULT_MODEL_ID;
14897
+ }
14898
+ var CONFIG_PATH, cached2;
14899
+ var init_settings2 = __esm(() => {
14900
+ init_models2();
14901
+ CONFIG_PATH = join3(homedir4(), ".codex", "config.toml");
14902
+ });
14903
+
14904
+ // src/engine/codex-local/capabilities.ts
14905
+ var codexCapabilities, codexIdentity;
14906
+ var init_capabilities2 = __esm(() => {
14907
+ init_models2();
14908
+ init_settings2();
14909
+ codexCapabilities = {
14910
+ vendorId: "codex",
14911
+ label: "Codex",
14912
+ models: CODEX_MODELS,
14913
+ defaultModelId: resolveCodexDefaultModelId,
14914
+ contextWindowFor: codexContextWindowFor
14915
+ };
14916
+ codexIdentity = {
14917
+ vendorId: "codex",
14918
+ productName: "Codex",
14919
+ shortName: "Codex",
14920
+ assistantName: "Codex",
14921
+ inputPlaceholder: "Ask Codex\u2026"
14922
+ };
14923
+ });
14924
+
14925
+ // src/engine/registry.ts
14926
+ function getCapabilities(vendor) {
14927
+ return ENGINE_REGISTRY[vendor] ?? defaultCapabilities;
14928
+ }
14929
+ function getIdentity(vendor) {
14930
+ return ENGINE_IDENTITIES[vendor] ?? defaultIdentity;
14931
+ }
14932
+ function capabilitiesForModelId(modelId) {
14933
+ if (!modelId)
14934
+ return defaultCapabilities;
14935
+ for (const caps of Object.values(ENGINE_REGISTRY)) {
14936
+ if (!caps)
14937
+ continue;
14938
+ if (caps.models.some((m2) => m2.id === modelId))
14939
+ return caps;
14940
+ }
14941
+ return defaultCapabilities;
14942
+ }
14943
+ function allModels() {
14944
+ const seen = new Set;
14945
+ const out = [];
14946
+ for (const caps of Object.values(ENGINE_REGISTRY)) {
14947
+ if (!caps)
14948
+ continue;
14949
+ for (const m2 of caps.models) {
14950
+ const key = `${m2.vendor}:${m2.id}`;
14951
+ if (seen.has(key))
14952
+ continue;
14953
+ seen.add(key);
14954
+ out.push(m2);
14955
+ }
14956
+ }
14957
+ return out;
14958
+ }
14959
+ function modelLabelFor(modelId) {
14960
+ const resolved = modelId ?? defaultCapabilities.defaultModelId();
14961
+ for (const caps of Object.values(ENGINE_REGISTRY)) {
14962
+ if (!caps)
14963
+ continue;
14964
+ const match = caps.models.find((m2) => m2.id === resolved);
14965
+ if (match)
14966
+ return match.label;
14967
+ }
14968
+ return resolved;
14969
+ }
14970
+ var ENGINE_REGISTRY, ENGINE_IDENTITIES, defaultCapabilities, defaultIdentity;
14971
+ var init_registry2 = __esm(() => {
14972
+ init_capabilities();
14973
+ init_capabilities2();
14974
+ ENGINE_REGISTRY = {
14975
+ claude: claudeCapabilities,
14976
+ codex: codexCapabilities
14977
+ };
14978
+ ENGINE_IDENTITIES = {
14979
+ claude: claudeIdentity,
14980
+ codex: codexIdentity
14981
+ };
14982
+ defaultCapabilities = claudeCapabilities;
14983
+ defaultIdentity = claudeIdentity;
14984
+ });
14985
+
14758
14986
  // src/types/task.ts
14759
14987
  function nextChatTabSeq(tabs) {
14760
14988
  let max = 0;
@@ -14763,7 +14991,7 @@ function nextChatTabSeq(tabs) {
14763
14991
  max = t2.seq;
14764
14992
  return max + 1;
14765
14993
  }
14766
- var toTaskId = (id) => id;
14994
+ var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude";
14767
14995
 
14768
14996
  // src/orchestrator/index/ulid.ts
14769
14997
  function encodeTime(now, len) {
@@ -14826,8 +15054,8 @@ var init_ulid = __esm(() => {
14826
15054
 
14827
15055
  // src/orchestrator/index/store.ts
14828
15056
  import { mkdir, open as open2, readFile as readFile2, rename, unlink } from "fs/promises";
14829
- import { homedir as homedir3 } from "os";
14830
- import { dirname as dirname2, join as join2 } from "path";
15057
+ import { homedir as homedir5 } from "os";
15058
+ import { dirname as dirname2, join as join4 } from "path";
14831
15059
 
14832
15060
  class TaskIndexStore {
14833
15061
  homeDir;
@@ -14839,9 +15067,9 @@ class TaskIndexStore {
14839
15067
  listeners = new Set;
14840
15068
  saveChain = Promise.resolve();
14841
15069
  constructor(options = {}) {
14842
- this.homeDir = options.homeDir ?? homedir3();
14843
- this.kobeDir = join2(this.homeDir, ".kobe");
14844
- this.path = join2(this.kobeDir, "tasks.json");
15070
+ this.homeDir = options.homeDir ?? homedir5();
15071
+ this.kobeDir = join4(this.homeDir, ".kobe");
15072
+ this.path = join4(this.kobeDir, "tasks.json");
14845
15073
  this.tmpPath = `${this.path}.tmp`;
14846
15074
  }
14847
15075
  subscribe(listener2) {
@@ -14931,7 +15159,16 @@ class TaskIndexStore {
14931
15159
  activeTabId = activeIn && tabsIn.some((t2) => t2.id === activeIn) ? activeIn : tabsIn[0]?.id ?? "";
14932
15160
  } else {
14933
15161
  const tabId = ulid();
14934
- tabs = [{ id: tabId, sessionId: sessionId ?? null, seq: 1, createdAt: now }];
15162
+ tabs = [
15163
+ {
15164
+ id: tabId,
15165
+ sessionId: sessionId ?? null,
15166
+ seq: 1,
15167
+ createdAt: now,
15168
+ ...rest.model ? { model: rest.model } : {},
15169
+ vendor: rest.vendor ?? DEFAULT_TASK_VENDOR
15170
+ }
15171
+ ];
14935
15172
  activeTabId = tabId;
14936
15173
  }
14937
15174
  const firstSession = tabs[0]?.sessionId ?? null;
@@ -15094,7 +15331,9 @@ function coerceTask(value) {
15094
15331
  sessionId: tt.sessionId ?? null,
15095
15332
  seq,
15096
15333
  createdAt: tt.createdAt,
15097
- ...typeof tt.title === "string" ? { title: tt.title } : {}
15334
+ ...typeof tt.title === "string" ? { title: tt.title } : {},
15335
+ ...typeof tt.model === "string" ? { model: tt.model } : {},
15336
+ ...isVendorId(tt.vendor) ? { vendor: tt.vendor } : {}
15098
15337
  };
15099
15338
  tabs.push(tab);
15100
15339
  }
@@ -15127,6 +15366,7 @@ function coerceTask(value) {
15127
15366
  kind: v2.kind === "main" ? "main" : "task",
15128
15367
  permissionMode: isPermissionMode(v2.permissionMode) ? v2.permissionMode : undefined,
15129
15368
  model: typeof v2.model === "string" ? v2.model : undefined,
15369
+ vendor: resolveTaskVendor(v2.vendor, typeof v2.model === "string" ? v2.model : undefined),
15130
15370
  createdAt: v2.createdAt,
15131
15371
  updatedAt: v2.updatedAt
15132
15372
  };
@@ -15134,10 +15374,23 @@ function coerceTask(value) {
15134
15374
  function isPermissionMode(v2) {
15135
15375
  return v2 === "default" || v2 === "plan";
15136
15376
  }
15377
+ function isVendorId(v2) {
15378
+ return typeof v2 === "string" && v2 in ENGINE_REGISTRY;
15379
+ }
15380
+ function resolveTaskVendor(rawVendor, modelId) {
15381
+ const stored = isVendorId(rawVendor) ? rawVendor : DEFAULT_TASK_VENDOR;
15382
+ if (!modelId)
15383
+ return stored;
15384
+ const matched = Object.values(ENGINE_REGISTRY).some((caps) => caps?.models.some((m2) => m2.id === modelId));
15385
+ if (!matched)
15386
+ return stored;
15387
+ return capabilitiesForModelId(modelId).vendorId;
15388
+ }
15137
15389
  function isTaskStatus(s2) {
15138
15390
  return s2 === "backlog" || s2 === "in_progress" || s2 === "in_review" || s2 === "done" || s2 === "canceled" || s2 === "error";
15139
15391
  }
15140
15392
  var init_store = __esm(() => {
15393
+ init_registry2();
15141
15394
  init_ulid();
15142
15395
  });
15143
15396
 
@@ -15180,7 +15433,7 @@ var init_package = __esm(() => {
15180
15433
  package_default = {
15181
15434
  $schema: "https://json.schemastore.org/package.json",
15182
15435
  name: "@sma1lboy/kobe",
15183
- version: "0.5.15",
15436
+ version: "0.5.16",
15184
15437
  description: "TUI orchestrator for Claude Code (codename)",
15185
15438
  type: "module",
15186
15439
  packageManager: "bun@1.3.13",
@@ -15207,6 +15460,8 @@ var init_package = __esm(() => {
15207
15460
  dev: "KOBE_DEV=1 bun --preload @opentui/solid/preload --conditions=browser ./src/cli/index.ts",
15208
15461
  "dev:test": "bun run scripts/dev-fixture.ts && KOBE_DEV=1 KOBE_TEST_ENGINE=dev-fake KOBE_HOME_DIR=$PWD/.dev-fixture/home bun --preload @opentui/solid/preload --conditions=browser ./src/cli/index.ts",
15209
15462
  "dev:test:reset": "bun run scripts/dev-fixture.ts --reset",
15463
+ "dev:sandbox": "mkdir -p .dev-sandbox/home && KOBE_DEV=1 KOBE_HOME_DIR=$PWD/.dev-sandbox/home bun --preload @opentui/solid/preload --conditions=browser ./src/cli/index.ts",
15464
+ "dev:sandbox:reset": "bun run scripts/dev-sandbox.ts --reset",
15210
15465
  build: "bun run scripts/build.ts",
15211
15466
  compile: "bun run scripts/compile.ts",
15212
15467
  typecheck: "tsc --noEmit",
@@ -15354,8 +15609,8 @@ __export(exports_diagnose, {
15354
15609
  import { spawnSync as spawnSync3 } from "child_process";
15355
15610
  import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
15356
15611
  import { stat } from "fs/promises";
15357
- import { homedir as homedir4, arch as osArch, platform as osPlatform, release as osRelease } from "os";
15358
- import { join as join3 } from "path";
15612
+ import { homedir as homedir6, arch as osArch, platform as osPlatform, release as osRelease } from "os";
15613
+ import { join as join5 } from "path";
15359
15614
  function formatKv(key, value) {
15360
15615
  const k2 = key.length >= KEY_PAD ? key : key.padEnd(KEY_PAD, " ");
15361
15616
  return `${k2}${value}`;
@@ -15398,7 +15653,7 @@ function reconcileWorktrees(tasks, onDiskByRepo) {
15398
15653
  for (const [repo, names] of onDiskByRepo) {
15399
15654
  const root = worktreeRootFor(repo);
15400
15655
  for (const n2 of names)
15401
- onDiskAbs.add(join3(root, n2));
15656
+ onDiskAbs.add(join5(root, n2));
15402
15657
  }
15403
15658
  const referenced = new Set;
15404
15659
  for (const t2 of tasks)
@@ -15454,7 +15709,7 @@ function dirSize(root) {
15454
15709
  continue;
15455
15710
  }
15456
15711
  for (const name of entries) {
15457
- const full = join3(dir, name);
15712
+ const full = join5(dir, name);
15458
15713
  try {
15459
15714
  const st = statSync2(full);
15460
15715
  if (st.isSymbolicLink())
@@ -15472,7 +15727,7 @@ function listWorktreeDirs(root) {
15472
15727
  try {
15473
15728
  return readdirSync(root).filter((n2) => {
15474
15729
  try {
15475
- return statSync2(join3(root, n2)).isDirectory();
15730
+ return statSync2(join5(root, n2)).isDirectory();
15476
15731
  } catch {
15477
15732
  return false;
15478
15733
  }
@@ -15543,7 +15798,7 @@ async function buildDiagnoseReport() {
15543
15798
  let tasks = [];
15544
15799
  let taskLoadError = null;
15545
15800
  try {
15546
- const store2 = new TaskIndexStore({ homeDir: homedir4() });
15801
+ const store2 = new TaskIndexStore({ homeDir: homedir6() });
15547
15802
  await store2.load();
15548
15803
  tasks = store2.list();
15549
15804
  } catch (err) {
@@ -15555,8 +15810,8 @@ async function buildDiagnoseReport() {
15555
15810
  lines.push(formatKv("tasks.json:", formatTaskBreakdown(tasks)));
15556
15811
  }
15557
15812
  section("config dir (~/.config/kobe/)");
15558
- const configDir = join3(homedir4(), ".config", "kobe");
15559
- const stateJsonPath = join3(configDir, "state.json");
15813
+ const configDir = join5(homedir6(), ".config", "kobe");
15814
+ const stateJsonPath = join5(configDir, "state.json");
15560
15815
  lines.push(formatKv("path:", configDir));
15561
15816
  if (existsSync2(configDir)) {
15562
15817
  lines.push(formatKv("exists:", "yes"));
@@ -15613,7 +15868,7 @@ async function buildDiagnoseReport() {
15613
15868
  }
15614
15869
  }
15615
15870
  section("recent errors");
15616
- const errorLogPath = join3(stateDir, "crash-logs", "latest.log");
15871
+ const errorLogPath = join5(stateDir, "crash-logs", "latest.log");
15617
15872
  if (existsSync2(errorLogPath)) {
15618
15873
  const tail = tailLines(errorLogPath, 5);
15619
15874
  if (tail.length === 0) {
@@ -15782,10 +16037,10 @@ function validateTheme(value) {
15782
16037
  var init_schema = () => {};
15783
16038
 
15784
16039
  // src/tui/context/theme/loader.ts
15785
- import { readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
15786
- import { join as join4 } from "path";
16040
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
16041
+ import { join as join6 } from "path";
15787
16042
  function userThemesDir() {
15788
- return join4(kobeStateDir(), "themes");
16043
+ return join6(kobeStateDir(), "themes");
15789
16044
  }
15790
16045
  function loadUserThemes() {
15791
16046
  const dir = userThemesDir();
@@ -15799,10 +16054,10 @@ function loadUserThemes() {
15799
16054
  for (const file of entries) {
15800
16055
  if (!file.endsWith(".json"))
15801
16056
  continue;
15802
- const path5 = join4(dir, file);
16057
+ const path5 = join6(dir, file);
15803
16058
  let parsed;
15804
16059
  try {
15805
- const text = readFileSync2(path5, "utf8");
16060
+ const text = readFileSync4(path5, "utf8");
15806
16061
  parsed = JSON.parse(text);
15807
16062
  } catch (err) {
15808
16063
  const msg = err instanceof Error ? err.message : String(err);
@@ -15829,8 +16084,8 @@ var exports_theme = {};
15829
16084
  __export(exports_theme, {
15830
16085
  runThemeSubcommand: () => runThemeSubcommand
15831
16086
  });
15832
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
15833
- import { basename, join as join5, resolve } from "path";
16087
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, readdirSync as readdirSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
16088
+ import { basename, join as join7, resolve } from "path";
15834
16089
  function fail2(message) {
15835
16090
  process.stderr.write(`kobe theme: ${message}
15836
16091
  `);
@@ -15854,7 +16109,7 @@ function listThemes2() {
15854
16109
  } else {
15855
16110
  for (const f2 of userFiles) {
15856
16111
  const name = f2.slice(0, -".json".length);
15857
- const path5 = join5(dir, f2);
16112
+ const path5 = join7(dir, f2);
15858
16113
  const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
15859
16114
  lines.push(` ${name}${overridesBundled} ${path5}`);
15860
16115
  }
@@ -15883,7 +16138,7 @@ async function readSource(source) {
15883
16138
  const abs = resolve(process.cwd(), source);
15884
16139
  let text;
15885
16140
  try {
15886
- text = readFileSync3(abs, "utf8");
16141
+ text = readFileSync5(abs, "utf8");
15887
16142
  } catch (err) {
15888
16143
  fail2(`failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}`);
15889
16144
  }
@@ -15944,7 +16199,7 @@ async function addTheme2(args) {
15944
16199
  }
15945
16200
  const dir = userThemesDir();
15946
16201
  mkdirSync2(dir, { recursive: true });
15947
- const dest = join5(dir, `${name}.json`);
16202
+ const dest = join7(dir, `${name}.json`);
15948
16203
  if (existsSync3(dest) && !opts.force) {
15949
16204
  fail2(`${dest} already exists (pass --force to overwrite)`);
15950
16205
  }
@@ -15962,7 +16217,7 @@ function removeTheme(args) {
15962
16217
  if (BUNDLED_NAMES.includes(name)) {
15963
16218
  fail2(`"${name}" is a built-in theme and cannot be removed`);
15964
16219
  }
15965
- const dest = join5(userThemesDir(), `${name}.json`);
16220
+ const dest = join7(userThemesDir(), `${name}.json`);
15966
16221
  if (!existsSync3(dest)) {
15967
16222
  fail2(`no user theme named "${name}" (looked for ${dest})`);
15968
16223
  }
@@ -16257,17 +16512,38 @@ var init_mcp_bridge = __esm(() => {
16257
16512
  });
16258
16513
 
16259
16514
  // src/daemon/paths.ts
16260
- import { homedir as homedir5, tmpdir } from "os";
16261
- import { join as join6 } from "path";
16262
- function defaultDaemonSocketPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir5()) {
16515
+ import { createHash } from "crypto";
16516
+ import { homedir as homedir7, tmpdir } from "os";
16517
+ import { join as join8 } from "path";
16518
+ function shortHomeTag(homeDir2) {
16519
+ return createHash("sha1").update(homeDir2).digest("hex").slice(0, 8);
16520
+ }
16521
+ function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
16522
+ if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
16523
+ return naturalPath;
16524
+ const tag = shortHomeTag(homeDir2);
16525
+ const suffix = pidTag === undefined ? "" : `-${pidTag}`;
16526
+ const fallback = join8(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
16527
+ if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
16528
+ return fallback;
16529
+ throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
16530
+ }
16531
+ function defaultDaemonSocketPath(homeDir2) {
16532
+ const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
16533
+ if (explicit && explicit.length > 0) {
16534
+ return fitSocketPath(join8(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
16535
+ }
16263
16536
  const runtimeDir = process.env.XDG_RUNTIME_DIR;
16264
- if (runtimeDir && runtimeDir.length > 0)
16265
- return join6(runtimeDir, "kobe.sock");
16266
- return join6(homeDir2, ".kobe", "daemon.sock");
16537
+ if (runtimeDir && runtimeDir.length > 0) {
16538
+ return fitSocketPath(join8(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
16539
+ }
16540
+ const home = homedir7();
16541
+ return fitSocketPath(join8(home, ".kobe", "daemon.sock"), home, "daemon");
16267
16542
  }
16268
- function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir5()) {
16269
- return join6(homeDir2, ".kobe", "daemon.pid");
16543
+ function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
16544
+ return join8(homeDir2, ".kobe", "daemon.pid");
16270
16545
  }
16546
+ var SOCKET_PATH_SAFETY_LIMIT = 100;
16271
16547
  var init_paths2 = () => {};
16272
16548
 
16273
16549
  // src/daemon/protocol.ts
@@ -16287,6 +16563,7 @@ function serializeTask(task) {
16287
16563
  pinned: task.pinned ?? false,
16288
16564
  permissionMode: task.permissionMode,
16289
16565
  model: task.model,
16566
+ vendor: task.vendor,
16290
16567
  createdAt: task.createdAt,
16291
16568
  updatedAt: task.updatedAt
16292
16569
  };
@@ -16486,7 +16763,7 @@ var init_client = () => {};
16486
16763
  // src/client/daemon-process.ts
16487
16764
  import { spawn as spawn3 } from "child_process";
16488
16765
  import { existsSync as existsSync4 } from "fs";
16489
- import { dirname as dirname3, join as join7, resolve as resolve2 } from "path";
16766
+ import { dirname as dirname3, join as join9, resolve as resolve2 } from "path";
16490
16767
  import { fileURLToPath } from "url";
16491
16768
  async function ensureDaemonReachable() {
16492
16769
  const socketPath = defaultDaemonSocketPath();
@@ -16534,7 +16811,7 @@ function resolveKobedEntry() {
16534
16811
  if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
16535
16812
  const exeDir = dirname3(process.execPath);
16536
16813
  const ext = process.platform === "win32" ? ".exe" : "";
16537
- const sibling = join7(exeDir, `kobed${ext}`);
16814
+ const sibling = join9(exeDir, `kobed${ext}`);
16538
16815
  if (!existsSync4(sibling)) {
16539
16816
  throw new Error(`kobe: standalone build expected sibling kobed binary at ${sibling}; extract the full release tarball.`);
16540
16817
  }
@@ -16554,120 +16831,6 @@ var init_daemon_process = __esm(() => {
16554
16831
  init_client();
16555
16832
  });
16556
16833
 
16557
- // src/engine/claude-settings.ts
16558
- import { readFileSync as readFileSync4 } from "fs";
16559
- import { homedir as homedir6 } from "os";
16560
- import { join as join8 } from "path";
16561
- function readClaudeSettings() {
16562
- if (cached !== undefined)
16563
- return cached;
16564
- try {
16565
- const raw = readFileSync4(SETTINGS_PATH, "utf8");
16566
- const parsed = JSON.parse(raw);
16567
- if (!parsed || typeof parsed !== "object") {
16568
- cached = null;
16569
- return null;
16570
- }
16571
- const obj = parsed;
16572
- const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
16573
- cached = { model };
16574
- return cached;
16575
- } catch {
16576
- cached = null;
16577
- return null;
16578
- }
16579
- }
16580
- function resolveDefaultModelId() {
16581
- const settings = readClaudeSettings();
16582
- if (settings?.model && settings.model.length > 0)
16583
- return settings.model;
16584
- return FALLBACK_DEFAULT_MODEL_ID;
16585
- }
16586
- var SETTINGS_PATH, cached, FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
16587
- var init_claude_settings = __esm(() => {
16588
- SETTINGS_PATH = join8(homedir6(), ".claude", "settings.json");
16589
- });
16590
-
16591
- // src/session/usage-metrics.ts
16592
- function totalContextTokens(u2) {
16593
- return u2.input_tokens + (u2.cache_read_input_tokens ?? 0) + (u2.cache_creation_input_tokens ?? 0);
16594
- }
16595
- function parseTimestampMs(value) {
16596
- const ms = new Date(value).getTime();
16597
- return Number.isFinite(ms) ? ms : null;
16598
- }
16599
- function mergeIntervals(intervals) {
16600
- if (intervals.length === 0)
16601
- return [];
16602
- const sorted = [...intervals].sort((a2, b2) => a2.startMs - b2.startMs);
16603
- const first = sorted[0];
16604
- if (!first)
16605
- return [];
16606
- const merged = [{ startMs: first.startMs, endMs: first.endMs }];
16607
- for (let i2 = 1;i2 < sorted.length; i2++) {
16608
- const current = sorted[i2];
16609
- const last = merged[merged.length - 1];
16610
- if (!current || !last)
16611
- continue;
16612
- if (current.startMs <= last.endMs) {
16613
- last.endMs = Math.max(last.endMs, current.endMs);
16614
- } else {
16615
- merged.push({ startMs: current.startMs, endMs: current.endMs });
16616
- }
16617
- }
16618
- return merged;
16619
- }
16620
- function durationMs(intervals) {
16621
- return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
16622
- }
16623
- function deriveSessionUsageMetrics(past) {
16624
- let latestUsage;
16625
- let latestUsageTimestampMs = null;
16626
- let lastUserTimestampMs = null;
16627
- let inputTokens = 0;
16628
- let outputTokens = 0;
16629
- const intervals = [];
16630
- for (const message of past) {
16631
- const timestampMs = parseTimestampMs(message.timestamp);
16632
- if (message.role === "user" && timestampMs !== null) {
16633
- lastUserTimestampMs = timestampMs;
16634
- continue;
16635
- }
16636
- if (message.role !== "assistant" || !message.usage)
16637
- continue;
16638
- if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
16639
- latestUsageTimestampMs = timestampMs;
16640
- latestUsage = message.usage;
16641
- } else if (latestUsage === undefined) {
16642
- latestUsage = message.usage;
16643
- }
16644
- inputTokens += message.usage.input_tokens;
16645
- outputTokens += message.usage.output_tokens;
16646
- if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
16647
- intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
16648
- }
16649
- }
16650
- if (!latestUsage)
16651
- return;
16652
- const totalDurationMs = durationMs(mergeIntervals(intervals));
16653
- if (totalDurationMs <= 0)
16654
- return latestUsage;
16655
- return {
16656
- ...latestUsage,
16657
- total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
16658
- };
16659
- }
16660
- function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
16661
- const startMs = startedAtIso ? parseTimestampMs(startedAtIso) : null;
16662
- const endMs = parseTimestampMs(endedAtIso);
16663
- if (startMs === null || endMs === null || endMs <= startMs)
16664
- return usage;
16665
- return {
16666
- ...usage,
16667
- total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
16668
- };
16669
- }
16670
-
16671
16834
  // src/orchestrator/metadata-suggester.ts
16672
16835
  import { spawn as spawn4 } from "child_process";
16673
16836
 
@@ -16979,10 +17142,11 @@ class SessionPump {
16979
17142
  }
16980
17143
  async run(taskId, tabId, handle) {
16981
17144
  const tabKey = chatRunStateKey(taskId, tabId);
17145
+ const engine3 = this.env.engineFor(taskId, tabId);
16982
17146
  let killedForInput = false;
16983
17147
  let terminalEvent = null;
16984
17148
  try {
16985
- for await (const ev of this.env.engine.stream(handle)) {
17149
+ for await (const ev of engine3.stream(handle)) {
16986
17150
  const inputReq = detectUserInputFromEngineEvent(ev);
16987
17151
  if (inputReq) {
16988
17152
  this.env.dispatch(taskId, tabId, ev);
@@ -16996,7 +17160,7 @@ class SessionPump {
16996
17160
  });
16997
17161
  killedForInput = true;
16998
17162
  try {
16999
- await this.env.engine.stop(handle);
17163
+ await engine3.stop(handle);
17000
17164
  } catch {}
17001
17165
  break;
17002
17166
  }
@@ -17009,7 +17173,7 @@ class SessionPump {
17009
17173
  } finally {
17010
17174
  if (!killedForInput) {
17011
17175
  try {
17012
- await this.env.engine.stop(handle);
17176
+ await engine3.stop(handle);
17013
17177
  } catch {}
17014
17178
  }
17015
17179
  }
@@ -17069,7 +17233,8 @@ function summarizeWorktreeError(raw, repo, baseRef) {
17069
17233
  }
17070
17234
 
17071
17235
  class Orchestrator {
17072
- engine;
17236
+ engines;
17237
+ fallbackEngine;
17073
17238
  store;
17074
17239
  worktrees;
17075
17240
  metadataSuggester;
@@ -17087,7 +17252,26 @@ class Orchestrator {
17087
17252
  runStateAcc;
17088
17253
  setRunState;
17089
17254
  constructor(deps) {
17090
- this.engine = deps.engine;
17255
+ const built = {};
17256
+ let fallback;
17257
+ if (deps.engines) {
17258
+ for (const [vendor, eng] of Object.entries(deps.engines)) {
17259
+ if (!eng)
17260
+ continue;
17261
+ built[vendor] = eng;
17262
+ fallback ??= eng;
17263
+ }
17264
+ }
17265
+ if (deps.engine) {
17266
+ const v2 = deps.engine.capabilities.vendorId;
17267
+ built[v2] ??= deps.engine;
17268
+ fallback ??= deps.engine;
17269
+ }
17270
+ if (!fallback) {
17271
+ throw new Error("Orchestrator: no usable engine found; both deps.engine and deps.engines were examined but contained no valid engines.");
17272
+ }
17273
+ this.engines = built;
17274
+ this.fallbackEngine = fallback;
17091
17275
  this.store = deps.store;
17092
17276
  this.worktrees = deps.worktrees;
17093
17277
  this.metadataSuggester = deps.metadataSuggester ?? new MetadataSuggester;
@@ -17101,13 +17285,86 @@ class Orchestrator {
17101
17285
  this.runStateAcc = runState;
17102
17286
  this.setRunState = (next) => setRunState(() => next);
17103
17287
  this.sessionPump = new SessionPump({
17104
- engine: this.engine,
17288
+ engineFor: (taskId, tabId) => this.engineForTaskTabId(taskId, tabId),
17105
17289
  broker: this.pendingInputBroker,
17106
17290
  dispatch: (taskId, tabId, ev) => this.dispatchEvent(taskId, tabId, ev),
17107
17291
  nextRequestId: () => `req-${++this.requestIdCounter}`,
17108
17292
  onPendingInputChange: () => this.bumpRunState()
17109
17293
  });
17110
17294
  }
17295
+ engineForVendor(vendor) {
17296
+ const v2 = vendor ?? DEFAULT_TASK_VENDOR;
17297
+ return this.engines[v2] ?? this.fallbackEngine;
17298
+ }
17299
+ engineForTask(task) {
17300
+ return this.engineForVendor(task.vendor);
17301
+ }
17302
+ vendorForTab(task, tab) {
17303
+ return tab.vendor ?? task.vendor ?? "claude";
17304
+ }
17305
+ modelForTab(task, tab, engine3) {
17306
+ return tab.model ?? task.model ?? engine3.capabilities.defaultModelId();
17307
+ }
17308
+ engineForTab(task, tab) {
17309
+ return this.engineForVendor(this.vendorForTab(task, tab));
17310
+ }
17311
+ async engineForTabRun(task, tab) {
17312
+ if (!tab.sessionId || tab.vendor)
17313
+ return this.engineForTab(task, tab);
17314
+ const resolved = await this.findEngineWithHistory(tab.sessionId, this.vendorForTab(task, tab));
17315
+ if (resolved.vendor && resolved.vendor !== tab.vendor) {
17316
+ await this.updateTab(task.id, tab.id, { vendor: resolved.vendor });
17317
+ }
17318
+ return resolved.engine;
17319
+ }
17320
+ async findEngineWithHistory(sessionId, preferredVendor) {
17321
+ const candidates = [];
17322
+ if (preferredVendor)
17323
+ candidates.push([preferredVendor, this.engineForVendor(preferredVendor)]);
17324
+ for (const [vendor, engine3] of Object.entries(this.engines)) {
17325
+ if (!engine3 || vendor === preferredVendor)
17326
+ continue;
17327
+ candidates.push([vendor, engine3]);
17328
+ }
17329
+ if (candidates.length === 0)
17330
+ candidates.push([undefined, this.fallbackEngine]);
17331
+ let fallback = candidates[0] ?? [undefined, this.fallbackEngine];
17332
+ let fallbackHistory;
17333
+ for (const [vendor, engine3] of candidates) {
17334
+ try {
17335
+ const history = await engine3.readHistory(sessionId);
17336
+ if (!fallbackHistory) {
17337
+ fallback = [vendor, engine3];
17338
+ fallbackHistory = history;
17339
+ }
17340
+ if (history.messages.length > 0 || history.usageMetrics)
17341
+ return { engine: engine3, vendor, history };
17342
+ } catch {}
17343
+ }
17344
+ return { engine: fallback[1], vendor: fallback[0], history: fallbackHistory };
17345
+ }
17346
+ engineForTaskId(taskId) {
17347
+ const task = this.store.get(taskId);
17348
+ return task ? this.engineForTask(task) : this.fallbackEngine;
17349
+ }
17350
+ engineForTaskTabId(taskId, tabId) {
17351
+ const task = this.store.get(taskId);
17352
+ if (!task)
17353
+ return this.fallbackEngine;
17354
+ const tab = task.tabs.find((t2) => t2.id === tabId);
17355
+ return tab ? this.engineForTab(task, tab) : this.engineForTask(task);
17356
+ }
17357
+ engineForSessionId(sessionId) {
17358
+ for (const task of this.store.list()) {
17359
+ for (const tab of task.tabs) {
17360
+ if (tab.sessionId === sessionId)
17361
+ return this.engineForTab(task, tab);
17362
+ }
17363
+ if (task.sessionId === sessionId)
17364
+ return this.engineForTask(task);
17365
+ }
17366
+ return this.fallbackEngine;
17367
+ }
17111
17368
  bumpRunState() {
17112
17369
  const next = new Map;
17113
17370
  for (const tabKey2 of this.pendingInputBroker.awaitingTabKeys()) {
@@ -17327,10 +17584,11 @@ class Orchestrator {
17327
17584
  if (prompt && prompt.trim().length > 0) {
17328
17585
  this.dispatchEvent(task.id, targetTab.id, { type: "user.inject", text: prompt });
17329
17586
  }
17330
- const modelToUse = task.model ?? resolveDefaultModelId();
17587
+ const engine3 = targetTab.sessionId ? await this.engineForTabRun(task, targetTab) : this.engineForTab(task, targetTab);
17588
+ const modelToUse = this.modelForTab(task, targetTab, engine3);
17331
17589
  let handle;
17332
17590
  if (targetTab.sessionId) {
17333
- handle = await this.engine.resume(targetTab.sessionId, promptToSend, {
17591
+ handle = await engine3.resume(targetTab.sessionId, promptToSend, {
17334
17592
  cwd: task.worktreePath,
17335
17593
  env: { KOBE_RESUME_CWD: task.worktreePath },
17336
17594
  permissionMode: task.permissionMode,
@@ -17343,7 +17601,7 @@ class Orchestrator {
17343
17601
  });
17344
17602
  this.firstSpawnLatches.set(key, latch);
17345
17603
  try {
17346
- handle = await this.engine.spawn(task.worktreePath, promptToSend, {
17604
+ handle = await engine3.spawn(task.worktreePath, promptToSend, {
17347
17605
  permissionMode: task.permissionMode,
17348
17606
  model: modelToUse
17349
17607
  });
@@ -17418,7 +17676,7 @@ class Orchestrator {
17418
17676
  text: "(turn interrupted \u2014 sending new prompt)"
17419
17677
  });
17420
17678
  try {
17421
- await this.engine.stop(handle);
17679
+ await this.engineForTask(task).stop(handle);
17422
17680
  } finally {
17423
17681
  this.handles.delete(key);
17424
17682
  this.bumpRunState();
@@ -17460,11 +17718,13 @@ class Orchestrator {
17460
17718
  return;
17461
17719
  await this.store.update(task.id, { permissionMode: mode });
17462
17720
  }
17463
- async setModel(id, model) {
17721
+ async setModel(id, model, tabId) {
17464
17722
  const task = this.requireTask(id);
17465
- if (task.model === model)
17723
+ const tab = this.resolveTab(task, tabId);
17724
+ const vendor = model ? capabilitiesForModelId(model).vendorId : this.vendorForTab(task, tab);
17725
+ if (tab.model === model && this.vendorForTab(task, tab) === vendor)
17466
17726
  return;
17467
- await this.store.update(task.id, { model });
17727
+ await this.updateTab(task.id, tab.id, { model, vendor });
17468
17728
  }
17469
17729
  async setTitle(id, title) {
17470
17730
  const task = this.requireTask(id);
@@ -17528,7 +17788,7 @@ class Orchestrator {
17528
17788
  }
17529
17789
  if (task.sessionId) {
17530
17790
  try {
17531
- await this.engine.deleteHistory(task.sessionId);
17791
+ await this.engineForTask(task).deleteHistory(task.sessionId);
17532
17792
  } catch (err) {
17533
17793
  console.error(`[kobe orchestrator] deleteTask: deleteHistory failed for ${task.id}:`, err);
17534
17794
  }
@@ -17537,15 +17797,15 @@ class Orchestrator {
17537
17797
  await this.store.remove(task.id);
17538
17798
  }
17539
17799
  async readHistory(sessionId) {
17540
- try {
17541
- return await this.engine.readHistory(sessionId);
17542
- } catch {
17543
- return [];
17544
- }
17800
+ return (await this.readHistoryWithMetrics(sessionId)).messages;
17545
17801
  }
17546
17802
  async readHistoryWithMetrics(sessionId) {
17547
- const messages = await this.readHistory(sessionId);
17548
- const usageMetrics = deriveSessionUsageMetrics(messages);
17803
+ const preferred = this.engineForSessionId(sessionId);
17804
+ const { history } = await this.findEngineWithHistory(sessionId, preferred.capabilities.vendorId);
17805
+ if (!history)
17806
+ return { messages: [] };
17807
+ const messages = [...history.messages];
17808
+ const usageMetrics = history.usageMetrics;
17549
17809
  return {
17550
17810
  messages,
17551
17811
  ...usageMetrics ? { usageMetrics } : {}
@@ -17555,14 +17815,16 @@ class Orchestrator {
17555
17815
  const task = this.requireTask(id);
17556
17816
  if (!task.worktreePath)
17557
17817
  return [];
17818
+ const tab = this.resolveTab(task);
17558
17819
  try {
17559
- return await this.engine.listSessions(task.worktreePath);
17820
+ return await this.engineForTab(task, tab).listSessions(task.worktreePath);
17560
17821
  } catch {
17561
17822
  return [];
17562
17823
  }
17563
17824
  }
17564
17825
  async openSessionInTab(id, sessionId, opts = {}) {
17565
17826
  const task = this.requireTask(id);
17827
+ const active = this.resolveTab(task);
17566
17828
  const existing = task.tabs.find((t2) => t2.sessionId === sessionId);
17567
17829
  if (existing) {
17568
17830
  await this.setActiveTab(task.id, existing.id);
@@ -17573,6 +17835,8 @@ class Orchestrator {
17573
17835
  sessionId,
17574
17836
  seq: nextChatTabSeq(task.tabs),
17575
17837
  createdAt: new Date().toISOString(),
17838
+ model: active.model ?? task.model,
17839
+ vendor: this.vendorForTab(task, active),
17576
17840
  ...opts.title ? { title: opts.title } : {}
17577
17841
  };
17578
17842
  await this.store.update(task.id, { tabs: [...task.tabs, tab], activeTabId: tab.id });
@@ -17600,11 +17864,14 @@ class Orchestrator {
17600
17864
  }
17601
17865
  async createTab(id, opts = {}) {
17602
17866
  const task = this.requireTask(id);
17867
+ const active = this.resolveTab(task);
17603
17868
  const tab = {
17604
17869
  id: ulid(),
17605
17870
  sessionId: null,
17606
17871
  seq: nextChatTabSeq(task.tabs),
17607
17872
  createdAt: new Date().toISOString(),
17873
+ model: active.model ?? task.model,
17874
+ vendor: this.vendorForTab(task, active),
17608
17875
  ...opts.title ? { title: opts.title } : {}
17609
17876
  };
17610
17877
  const tabs = [...task.tabs, tab];
@@ -17684,7 +17951,7 @@ class Orchestrator {
17684
17951
  if (!handle)
17685
17952
  return;
17686
17953
  try {
17687
- await this.engine.stop(handle);
17954
+ await this.engineForTaskId(taskId).stop(handle);
17688
17955
  } finally {
17689
17956
  this.handles.delete(key);
17690
17957
  this.bumpRunState();
@@ -17693,12 +17960,13 @@ class Orchestrator {
17693
17960
  async stopAllTabsForTask(taskId) {
17694
17961
  const prefix = `${taskId}:`;
17695
17962
  const keys = Array.from(this.handles.keys()).filter((k2) => k2.startsWith(prefix));
17963
+ const engine3 = this.engineForTaskId(taskId);
17696
17964
  for (const key of keys) {
17697
17965
  const handle = this.handles.get(key);
17698
17966
  if (!handle)
17699
17967
  continue;
17700
17968
  try {
17701
- await this.engine.stop(handle);
17969
+ await engine3.stop(handle);
17702
17970
  } catch {}
17703
17971
  this.handles.delete(key);
17704
17972
  }
@@ -17824,7 +18092,7 @@ function deriveTitleFromPrompt(prompt) {
17824
18092
  var CONCURRENCY_CAP = 20, PLACEHOLDER_TASK_TITLE = "(new task)", IllegalTransitionError, ConcurrencyCapError, PRPreconditionError, TaskNotFoundError, CannotDeleteMainTaskError, TITLE_CHAR_CAP = 40;
17825
18093
  var init_core = __esm(() => {
17826
18094
  init_dev();
17827
- init_claude_settings();
18095
+ init_registry2();
17828
18096
  init_repos();
17829
18097
  init_ulid();
17830
18098
  init_metadata_suggester();
@@ -18025,8 +18293,8 @@ class RemoteOrchestrator {
18025
18293
  async setPermissionMode(taskId, mode) {
18026
18294
  await this.client.request("task.permissionMode", { taskId, mode });
18027
18295
  }
18028
- async setModel(taskId, model) {
18029
- await this.client.request("task.model", { taskId, model });
18296
+ async setModel(taskId, model, tabId) {
18297
+ await this.client.request("task.model", { taskId, model, tabId });
18030
18298
  }
18031
18299
  async createTab(taskId, opts = {}) {
18032
18300
  const res = await this.client.request("chat.tab.create", { taskId, title: opts.title });
@@ -18447,21 +18715,276 @@ var init_manager = __esm(() => {
18447
18715
  init_paths();
18448
18716
  });
18449
18717
 
18450
- // src/tui/ui/dialog-confirm.tsx
18451
- import { TextAttributes as TextAttributes6 } from "@opentui/core";
18452
- function titlecase(s2) {
18453
- if (!s2)
18454
- return s2;
18455
- return s2.charAt(0).toUpperCase() + s2.slice(1);
18456
- }
18457
- function DialogConfirm(props) {
18458
- const dialog = useDialog();
18459
- const {
18460
- theme
18461
- } = useTheme();
18462
- const [store2, setStore2] = createStore({
18463
- active: "confirm"
18464
- });
18718
+ // src/engine/codex-local/binary.ts
18719
+ import { spawnSync as spawnSync7 } from "child_process";
18720
+ import { existsSync as existsSync5, statSync as statSync3 } from "fs";
18721
+ import { homedir as homedir8 } from "os";
18722
+ import path7 from "path";
18723
+ async function findCodexBinary(deps = defaultDeps2) {
18724
+ const checked = [];
18725
+ const tryPath = (p2) => {
18726
+ if (!p2)
18727
+ return;
18728
+ checked.push(p2);
18729
+ return deps.fileExists(p2) ? p2 : undefined;
18730
+ };
18731
+ const whichResult = deps.which("codex");
18732
+ if (whichResult) {
18733
+ checked.push(`which:${whichResult}`);
18734
+ if (deps.fileExists(whichResult))
18735
+ return whichResult;
18736
+ }
18737
+ const home = deps.home();
18738
+ for (const p2 of ["/opt/homebrew/bin/codex", "/usr/local/bin/codex", "/usr/bin/codex", "/bin/codex"]) {
18739
+ const candidate = tryPath(p2);
18740
+ if (candidate)
18741
+ return candidate;
18742
+ }
18743
+ const nvmBin = deps.env("NVM_BIN");
18744
+ if (nvmBin) {
18745
+ const candidate = tryPath(path7.join(nvmBin, "codex"));
18746
+ if (candidate)
18747
+ return candidate;
18748
+ }
18749
+ for (const rel of [".local/bin/codex", ".bun/bin/codex", "bin/codex"]) {
18750
+ const candidate = tryPath(path7.join(home, rel));
18751
+ if (candidate)
18752
+ return candidate;
18753
+ }
18754
+ throw new CodexBinaryNotFoundError(checked);
18755
+ }
18756
+ var CodexBinaryNotFoundError, defaultDeps2;
18757
+ var init_binary2 = __esm(() => {
18758
+ CodexBinaryNotFoundError = class CodexBinaryNotFoundError extends Error {
18759
+ checkedPaths;
18760
+ constructor(checkedPaths) {
18761
+ super(`Codex CLI binary not found. Checked: ${checkedPaths.join(", ")}. Ensure 'codex' is on PATH (e.g. \`brew install codex\` or the official installer).`);
18762
+ this.name = "CodexBinaryNotFoundError";
18763
+ this.checkedPaths = checkedPaths;
18764
+ }
18765
+ };
18766
+ defaultDeps2 = {
18767
+ fileExists(p2) {
18768
+ try {
18769
+ return statSync3(p2).isFile();
18770
+ } catch {
18771
+ return false;
18772
+ }
18773
+ },
18774
+ env(name) {
18775
+ return process.env[name];
18776
+ },
18777
+ home() {
18778
+ return homedir8();
18779
+ },
18780
+ which(name) {
18781
+ const cmd = process.platform === "win32" ? "where" : "which";
18782
+ const out = spawnSync7(cmd, [name], { encoding: "utf8" });
18783
+ if (out.status !== 0)
18784
+ return;
18785
+ const first = out.stdout.split(`
18786
+ `).map((l2) => l2.trim()).filter(Boolean)[0];
18787
+ if (!first)
18788
+ return;
18789
+ if (first.startsWith("codex:") && first.includes("aliased to")) {
18790
+ const aliasTarget = first.split("aliased to")[1]?.trim();
18791
+ return aliasTarget && existsSync5(aliasTarget) ? aliasTarget : undefined;
18792
+ }
18793
+ return first;
18794
+ },
18795
+ readdir(p2) {
18796
+ try {
18797
+ const fs5 = __require("fs");
18798
+ return fs5.readdirSync(p2);
18799
+ } catch {
18800
+ return [];
18801
+ }
18802
+ }
18803
+ };
18804
+ });
18805
+
18806
+ // src/engine/account-detect.ts
18807
+ import { readFileSync as readFileSync6, statSync as statSync4 } from "fs";
18808
+ import { homedir as homedir9 } from "os";
18809
+ import path8 from "path";
18810
+ function claudeGlobalConfigPath(env, home) {
18811
+ const override = env("CLAUDE_CONFIG_DIR")?.trim();
18812
+ if (override)
18813
+ return path8.join(override, ".claude.json");
18814
+ return path8.join(home, ".claude.json");
18815
+ }
18816
+ function codexAuthPath(env, home) {
18817
+ const override = env("CODEX_HOME")?.trim();
18818
+ const dir = override ?? path8.join(home, ".codex");
18819
+ return path8.join(dir, "auth.json");
18820
+ }
18821
+ function decodeJwtPayload(jwt) {
18822
+ const parts = jwt.split(".");
18823
+ if (parts.length !== 3)
18824
+ return null;
18825
+ const payload = parts[1];
18826
+ if (!payload)
18827
+ return null;
18828
+ const b64 = payload.replace(/-/g, "+").replace(/_/g, "/");
18829
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
18830
+ try {
18831
+ const json = Buffer.from(padded, "base64").toString("utf8");
18832
+ const obj = JSON.parse(json);
18833
+ return typeof obj === "object" && obj !== null ? obj : null;
18834
+ } catch {
18835
+ return null;
18836
+ }
18837
+ }
18838
+ async function probeBinary(probe) {
18839
+ try {
18840
+ const p2 = await probe();
18841
+ return { found: true, path: p2 };
18842
+ } catch (err) {
18843
+ if (err instanceof ClaudeBinaryNotFoundError || err instanceof CodexBinaryNotFoundError) {
18844
+ return { found: false, error: "not found on PATH" };
18845
+ }
18846
+ return { found: false, error: err instanceof Error ? err.message : String(err) };
18847
+ }
18848
+ }
18849
+ async function detectClaudeAccount(deps = defaultDeps3) {
18850
+ const binary = await probeBinary(() => deps.findClaudeBinary());
18851
+ const configPath = claudeGlobalConfigPath(deps.env, deps.home());
18852
+ let raw;
18853
+ try {
18854
+ raw = deps.readFile(configPath);
18855
+ } catch (err) {
18856
+ return {
18857
+ binary,
18858
+ account: { kind: "none" },
18859
+ accountError: `read ${configPath}: ${err instanceof Error ? err.message : String(err)}`
18860
+ };
18861
+ }
18862
+ if (raw === null)
18863
+ return { binary, account: { kind: "none" } };
18864
+ let parsed;
18865
+ try {
18866
+ parsed = JSON.parse(raw);
18867
+ } catch (err) {
18868
+ return {
18869
+ binary,
18870
+ account: { kind: "none" },
18871
+ accountError: `parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`
18872
+ };
18873
+ }
18874
+ const oauth = parsed?.oauthAccount;
18875
+ if (!oauth || typeof oauth !== "object")
18876
+ return { binary, account: { kind: "none" } };
18877
+ const o2 = oauth;
18878
+ const email = typeof o2.emailAddress === "string" ? o2.emailAddress : undefined;
18879
+ if (!email)
18880
+ return { binary, account: { kind: "none" } };
18881
+ return {
18882
+ binary,
18883
+ account: {
18884
+ kind: "oauth",
18885
+ email,
18886
+ organization: typeof o2.organizationName === "string" ? o2.organizationName : undefined,
18887
+ displayName: typeof o2.displayName === "string" ? o2.displayName : undefined,
18888
+ billingType: typeof o2.billingType === "string" ? o2.billingType : undefined
18889
+ }
18890
+ };
18891
+ }
18892
+ async function detectCodexAccount(deps = defaultDeps3) {
18893
+ const binary = await probeBinary(() => deps.findCodexBinary());
18894
+ const authPath = codexAuthPath(deps.env, deps.home());
18895
+ let raw;
18896
+ try {
18897
+ raw = deps.readFile(authPath);
18898
+ } catch (err) {
18899
+ return {
18900
+ binary,
18901
+ account: { kind: "none" },
18902
+ accountError: `read ${authPath}: ${err instanceof Error ? err.message : String(err)}`
18903
+ };
18904
+ }
18905
+ if (raw === null)
18906
+ return { binary, account: { kind: "none" } };
18907
+ let parsed;
18908
+ try {
18909
+ parsed = JSON.parse(raw);
18910
+ } catch (err) {
18911
+ return {
18912
+ binary,
18913
+ account: { kind: "none" },
18914
+ accountError: `parse ${authPath}: ${err instanceof Error ? err.message : String(err)}`
18915
+ };
18916
+ }
18917
+ const obj = parsed ?? {};
18918
+ const tokens = obj.tokens;
18919
+ const idToken = typeof tokens?.id_token === "string" ? tokens.id_token : undefined;
18920
+ if (idToken) {
18921
+ const payload = decodeJwtPayload(idToken);
18922
+ if (!payload) {
18923
+ return {
18924
+ binary,
18925
+ account: { kind: "none" },
18926
+ accountError: "codex id_token: malformed JWT"
18927
+ };
18928
+ }
18929
+ const email = typeof payload.email === "string" ? payload.email : undefined;
18930
+ const authClaimRaw = payload["https://api.openai.com/auth"];
18931
+ const authClaim = typeof authClaimRaw === "object" && authClaimRaw !== null && !Array.isArray(authClaimRaw) ? authClaimRaw : undefined;
18932
+ const plan = typeof authClaim?.chatgpt_plan_type === "string" ? authClaim.chatgpt_plan_type : undefined;
18933
+ if (email)
18934
+ return { binary, account: { kind: "chatgpt", email, plan } };
18935
+ return { binary, account: { kind: "none" }, accountError: "codex id_token: no email claim" };
18936
+ }
18937
+ const apiKey = obj.OPENAI_API_KEY;
18938
+ if (typeof apiKey === "string" && apiKey.length > 0) {
18939
+ return { binary, account: { kind: "apikey" } };
18940
+ }
18941
+ return { binary, account: { kind: "none" } };
18942
+ }
18943
+ var defaultDeps3;
18944
+ var init_account_detect = __esm(() => {
18945
+ init_binary();
18946
+ init_binary2();
18947
+ defaultDeps3 = {
18948
+ readFile(p2) {
18949
+ try {
18950
+ statSync4(p2);
18951
+ } catch (err) {
18952
+ if (err.code === "ENOENT")
18953
+ return null;
18954
+ throw err;
18955
+ }
18956
+ return readFileSync6(p2, "utf8");
18957
+ },
18958
+ env(name) {
18959
+ return process.env[name];
18960
+ },
18961
+ home() {
18962
+ return homedir9();
18963
+ },
18964
+ findClaudeBinary() {
18965
+ return findClaudeBinary();
18966
+ },
18967
+ findCodexBinary() {
18968
+ return findCodexBinary();
18969
+ }
18970
+ };
18971
+ });
18972
+
18973
+ // src/tui/ui/dialog-confirm.tsx
18974
+ import { TextAttributes as TextAttributes6 } from "@opentui/core";
18975
+ function titlecase(s2) {
18976
+ if (!s2)
18977
+ return s2;
18978
+ return s2.charAt(0).toUpperCase() + s2.slice(1);
18979
+ }
18980
+ function DialogConfirm(props) {
18981
+ const dialog = useDialog();
18982
+ const {
18983
+ theme
18984
+ } = useTheme();
18985
+ const [store2, setStore2] = createStore({
18986
+ active: "confirm"
18987
+ });
18465
18988
  useBindings(() => ({
18466
18989
  bindings: [{
18467
18990
  key: "return",
@@ -18573,7 +19096,7 @@ var init_dialog_confirm = __esm(() => {
18573
19096
 
18574
19097
  // src/tui/component/settings-dialog.tsx
18575
19098
  import { unlinkSync as unlinkSync2 } from "fs";
18576
- import { join as join9 } from "path";
19099
+ import { join as join10 } from "path";
18577
19100
  import { TextAttributes as TextAttributes7 } from "@opentui/core";
18578
19101
  function SettingsDialog(props) {
18579
19102
  const dialog = useDialog();
@@ -18589,6 +19112,34 @@ function SettingsDialog(props) {
18589
19112
  const themeNames = createMemo(() => themeCtx.all().slice().sort());
18590
19113
  const [themeCursor, setThemeCursor] = createSignal(Math.max(0, themeNames().findIndex((n2) => n2 === themeCtx.selected)));
18591
19114
  const hasDaemon = props.orchestrator instanceof RemoteOrchestrator;
19115
+ const [claudeStatus, setClaudeStatus] = createSignal(null);
19116
+ const [codexStatus, setCodexStatus] = createSignal(null);
19117
+ onMount(() => {
19118
+ detectClaudeAccount().then(setClaudeStatus).catch((err) => {
19119
+ console.error("kobe: detectClaudeAccount threw:", err);
19120
+ setClaudeStatus({
19121
+ binary: {
19122
+ found: false,
19123
+ error: String(err)
19124
+ },
19125
+ account: {
19126
+ kind: "none"
19127
+ }
19128
+ });
19129
+ });
19130
+ detectCodexAccount().then(setCodexStatus).catch((err) => {
19131
+ console.error("kobe: detectCodexAccount threw:", err);
19132
+ setCodexStatus({
19133
+ binary: {
19134
+ found: false,
19135
+ error: String(err)
19136
+ },
19137
+ account: {
19138
+ kind: "none"
19139
+ }
19140
+ });
19141
+ });
19142
+ });
18592
19143
  function devRowCount() {
18593
19144
  return hasDaemon ? 2 : 1;
18594
19145
  }
@@ -18597,6 +19148,8 @@ function SettingsDialog(props) {
18597
19148
  return themeNames().length + 1 + FOCUS_ACCENT_SLOTS.length + 2;
18598
19149
  if (section() === "dev")
18599
19150
  return devRowCount();
19151
+ if (section() === "accounts")
19152
+ return 0;
18600
19153
  return 0;
18601
19154
  }
18602
19155
  function isTransparentRow() {
@@ -18665,7 +19218,7 @@ function SettingsDialog(props) {
18665
19218
  return;
18666
19219
  props.kv.clear();
18667
19220
  try {
18668
- unlinkSync2(join9(homeDir(), ".kobe", "tasks.json"));
19221
+ unlinkSync2(join10(homeDir(), ".kobe", "tasks.json"));
18669
19222
  } catch (err) {
18670
19223
  if (err.code !== "ENOENT") {
18671
19224
  console.error("kobe: failed to delete tasks.json during reset:", err);
@@ -18805,10 +19358,10 @@ function SettingsDialog(props) {
18805
19358
  ]
18806
19359
  }));
18807
19360
  return (() => {
18808
- var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text"), _el$5 = createElement("text"), _el$7 = createElement("box"), _el$8 = createElement("box"), _el$9 = createElement("box"), _el$47 = createElement("box"), _el$48 = createElement("text");
19361
+ var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text"), _el$5 = createElement("text"), _el$7 = createElement("box"), _el$8 = createElement("box"), _el$9 = createElement("box"), _el$62 = createElement("box"), _el$63 = createElement("text");
18809
19362
  insertNode(_el$, _el$2);
18810
19363
  insertNode(_el$, _el$7);
18811
- insertNode(_el$, _el$47);
19364
+ insertNode(_el$, _el$62);
18812
19365
  setProp(_el$, "paddingLeft", 2);
18813
19366
  setProp(_el$, "paddingRight", 2);
18814
19367
  setProp(_el$, "paddingBottom", 1);
@@ -18834,28 +19387,28 @@ function SettingsDialog(props) {
18834
19387
  const isSection = () => i2() === cursor();
18835
19388
  const isSidebarFocused = () => isSection() && level() === "sidebar";
18836
19389
  return (() => {
18837
- var _el$50 = createElement("box"), _el$51 = createElement("text");
18838
- insertNode(_el$50, _el$51);
18839
- setProp(_el$50, "paddingLeft", 1);
18840
- setProp(_el$50, "paddingRight", 1);
18841
- setProp(_el$50, "onMouseUp", () => {
19390
+ var _el$65 = createElement("box"), _el$66 = createElement("text");
19391
+ insertNode(_el$65, _el$66);
19392
+ setProp(_el$65, "paddingLeft", 1);
19393
+ setProp(_el$65, "paddingRight", 1);
19394
+ setProp(_el$65, "onMouseUp", () => {
18842
19395
  switchSection(s2.id);
18843
19396
  setLevel("sidebar");
18844
19397
  });
18845
- setProp(_el$51, "wrapMode", "none");
18846
- insert(_el$51, () => s2.label);
19398
+ setProp(_el$66, "wrapMode", "none");
19399
+ insert(_el$66, () => s2.label);
18847
19400
  effect((_p$) => {
18848
- var _v$30 = isSidebarFocused() ? theme.primary : undefined, _v$31 = isSidebarFocused() ? theme.selectedListItemText : isSection() ? theme.accent : theme.textMuted, _v$32 = isSection() ? TextAttributes7.BOLD : undefined;
18849
- _v$30 !== _p$.e && (_p$.e = setProp(_el$50, "backgroundColor", _v$30, _p$.e));
18850
- _v$31 !== _p$.t && (_p$.t = setProp(_el$51, "fg", _v$31, _p$.t));
18851
- _v$32 !== _p$.a && (_p$.a = setProp(_el$51, "attributes", _v$32, _p$.a));
19401
+ var _v$37 = isSidebarFocused() ? theme.primary : undefined, _v$38 = isSidebarFocused() ? theme.selectedListItemText : isSection() ? theme.accent : theme.textMuted, _v$39 = isSection() ? TextAttributes7.BOLD : undefined;
19402
+ _v$37 !== _p$.e && (_p$.e = setProp(_el$65, "backgroundColor", _v$37, _p$.e));
19403
+ _v$38 !== _p$.t && (_p$.t = setProp(_el$66, "fg", _v$38, _p$.t));
19404
+ _v$39 !== _p$.a && (_p$.a = setProp(_el$66, "attributes", _v$39, _p$.a));
18852
19405
  return _p$;
18853
19406
  }, {
18854
19407
  e: undefined,
18855
19408
  t: undefined,
18856
19409
  a: undefined
18857
19410
  });
18858
- return _el$50;
19411
+ return _el$65;
18859
19412
  })();
18860
19413
  }
18861
19414
  }));
@@ -18889,33 +19442,33 @@ function SettingsDialog(props) {
18889
19442
  const isCursor = () => level() === "body" && bodyRow() === i2();
18890
19443
  const isSelected = () => name === themeCtx.selected;
18891
19444
  return (() => {
18892
- var _el$52 = createElement("box"), _el$53 = createElement("text");
18893
- insertNode(_el$52, _el$53);
18894
- setProp(_el$52, "flexDirection", "row");
18895
- setProp(_el$52, "gap", 1);
18896
- setProp(_el$52, "paddingLeft", 1);
18897
- setProp(_el$52, "paddingRight", 1);
18898
- setProp(_el$52, "onMouseUp", () => {
19445
+ var _el$67 = createElement("box"), _el$68 = createElement("text");
19446
+ insertNode(_el$67, _el$68);
19447
+ setProp(_el$67, "flexDirection", "row");
19448
+ setProp(_el$67, "gap", 1);
19449
+ setProp(_el$67, "paddingLeft", 1);
19450
+ setProp(_el$67, "paddingRight", 1);
19451
+ setProp(_el$67, "onMouseUp", () => {
18899
19452
  setLevel("body");
18900
19453
  setBodyRow(i2());
18901
19454
  setThemeCursor(i2());
18902
19455
  themeCtx.set(name);
18903
19456
  });
18904
- setProp(_el$53, "wrapMode", "none");
18905
- insert(_el$53, () => isSelected() ? "\u25CF " : " ", null);
18906
- insert(_el$53, name, null);
19457
+ setProp(_el$68, "wrapMode", "none");
19458
+ insert(_el$68, () => isSelected() ? "\u25CF " : " ", null);
19459
+ insert(_el$68, name, null);
18907
19460
  effect((_p$) => {
18908
- var _v$33 = isCursor() ? theme.primary : undefined, _v$34 = isCursor() ? theme.selectedListItemText : isSelected() ? theme.accent : theme.text, _v$35 = isCursor() || isSelected() ? TextAttributes7.BOLD : undefined;
18909
- _v$33 !== _p$.e && (_p$.e = setProp(_el$52, "backgroundColor", _v$33, _p$.e));
18910
- _v$34 !== _p$.t && (_p$.t = setProp(_el$53, "fg", _v$34, _p$.t));
18911
- _v$35 !== _p$.a && (_p$.a = setProp(_el$53, "attributes", _v$35, _p$.a));
19461
+ var _v$40 = isCursor() ? theme.primary : undefined, _v$41 = isCursor() ? theme.selectedListItemText : isSelected() ? theme.accent : theme.text, _v$42 = isCursor() || isSelected() ? TextAttributes7.BOLD : undefined;
19462
+ _v$40 !== _p$.e && (_p$.e = setProp(_el$67, "backgroundColor", _v$40, _p$.e));
19463
+ _v$41 !== _p$.t && (_p$.t = setProp(_el$68, "fg", _v$41, _p$.t));
19464
+ _v$42 !== _p$.a && (_p$.a = setProp(_el$68, "attributes", _v$42, _p$.a));
18912
19465
  return _p$;
18913
19466
  }, {
18914
19467
  e: undefined,
18915
19468
  t: undefined,
18916
19469
  a: undefined
18917
19470
  });
18918
- return _el$52;
19471
+ return _el$67;
18919
19472
  })();
18920
19473
  }
18921
19474
  }));
@@ -18954,32 +19507,32 @@ function SettingsDialog(props) {
18954
19507
  const isCursor = () => level() === "body" && bodyRow() === rowIndex();
18955
19508
  const isSelected = () => themeCtx.focusAccent === slot;
18956
19509
  return (() => {
18957
- var _el$54 = createElement("box"), _el$55 = createElement("text");
18958
- insertNode(_el$54, _el$55);
18959
- setProp(_el$54, "flexDirection", "row");
18960
- setProp(_el$54, "gap", 1);
18961
- setProp(_el$54, "paddingLeft", 1);
18962
- setProp(_el$54, "paddingRight", 1);
18963
- setProp(_el$54, "onMouseUp", () => {
19510
+ var _el$69 = createElement("box"), _el$70 = createElement("text");
19511
+ insertNode(_el$69, _el$70);
19512
+ setProp(_el$69, "flexDirection", "row");
19513
+ setProp(_el$69, "gap", 1);
19514
+ setProp(_el$69, "paddingLeft", 1);
19515
+ setProp(_el$69, "paddingRight", 1);
19516
+ setProp(_el$69, "onMouseUp", () => {
18964
19517
  setLevel("body");
18965
19518
  setBodyRow(rowIndex());
18966
19519
  themeCtx.setFocusAccent(slot);
18967
19520
  });
18968
- setProp(_el$55, "wrapMode", "none");
18969
- insert(_el$55, () => isSelected() ? "\u25CF " : " ", null);
18970
- insert(_el$55, () => FOCUS_ACCENT_LABEL[slot], null);
19521
+ setProp(_el$70, "wrapMode", "none");
19522
+ insert(_el$70, () => isSelected() ? "\u25CF " : " ", null);
19523
+ insert(_el$70, () => FOCUS_ACCENT_LABEL[slot], null);
18971
19524
  effect((_p$) => {
18972
- var _v$36 = isCursor() ? theme.primary : undefined, _v$37 = isCursor() ? theme.selectedListItemText : isSelected() ? theme.focusAccent : theme.text, _v$38 = isCursor() || isSelected() ? TextAttributes7.BOLD : undefined;
18973
- _v$36 !== _p$.e && (_p$.e = setProp(_el$54, "backgroundColor", _v$36, _p$.e));
18974
- _v$37 !== _p$.t && (_p$.t = setProp(_el$55, "fg", _v$37, _p$.t));
18975
- _v$38 !== _p$.a && (_p$.a = setProp(_el$55, "attributes", _v$38, _p$.a));
19525
+ var _v$43 = isCursor() ? theme.primary : undefined, _v$44 = isCursor() ? theme.selectedListItemText : isSelected() ? theme.focusAccent : theme.text, _v$45 = isCursor() || isSelected() ? TextAttributes7.BOLD : undefined;
19526
+ _v$43 !== _p$.e && (_p$.e = setProp(_el$69, "backgroundColor", _v$43, _p$.e));
19527
+ _v$44 !== _p$.t && (_p$.t = setProp(_el$70, "fg", _v$44, _p$.t));
19528
+ _v$45 !== _p$.a && (_p$.a = setProp(_el$70, "attributes", _v$45, _p$.a));
18976
19529
  return _p$;
18977
19530
  }, {
18978
19531
  e: undefined,
18979
19532
  t: undefined,
18980
19533
  a: undefined
18981
19534
  });
18982
- return _el$54;
19535
+ return _el$69;
18983
19536
  })();
18984
19537
  }
18985
19538
  }), null);
@@ -19071,122 +19624,294 @@ function SettingsDialog(props) {
19071
19624
  }), null);
19072
19625
  insert(_el$9, createComponent2(Show, {
19073
19626
  get when() {
19074
- return section() === "dev";
19627
+ return section() === "accounts";
19075
19628
  },
19076
19629
  get children() {
19077
- var _el$37 = createElement("box"), _el$38 = createElement("text"), _el$40 = createElement("text");
19630
+ var _el$37 = createElement("box"), _el$38 = createElement("text"), _el$40 = createElement("text"), _el$42 = createElement("box"), _el$43 = createElement("text"), _el$47 = createElement("box"), _el$48 = createElement("text");
19078
19631
  insertNode(_el$37, _el$38);
19079
19632
  insertNode(_el$37, _el$40);
19633
+ insertNode(_el$37, _el$42);
19634
+ insertNode(_el$37, _el$47);
19080
19635
  setProp(_el$37, "flexDirection", "column");
19081
19636
  setProp(_el$37, "gap", 1);
19082
- insertNode(_el$38, createTextNode(`Reset UI state`));
19083
- insertNode(_el$40, createTextNode(`Clears ~/.config/kobe/state.json and ~/.kobe/tasks.json, then quits kobe \u2014 relaunch to start fresh. Working session / Archive lists, pane sizes, theme, model picks all reset. Worktrees on disk and Claude Code session history are not touched.`));
19637
+ insertNode(_el$38, createTextNode(`Accounts`));
19638
+ insertNode(_el$40, createTextNode(`Read-only view of locally-detected engine accounts. Login flows land here later.`));
19084
19639
  setProp(_el$40, "wrapMode", "word");
19085
- insert(_el$37, () => {
19640
+ insertNode(_el$42, _el$43);
19641
+ setProp(_el$42, "flexDirection", "column");
19642
+ setProp(_el$42, "gap", 0);
19643
+ insertNode(_el$43, createTextNode(`claude-code`));
19644
+ insert(_el$42, createComponent2(Show, {
19645
+ get when() {
19646
+ return claudeStatus() === null;
19647
+ },
19648
+ get children() {
19649
+ var _el$45 = createElement("text");
19650
+ insertNode(_el$45, createTextNode(`Checking\u2026`));
19651
+ effect((_$p) => setProp(_el$45, "fg", theme.textMuted, _$p));
19652
+ return _el$45;
19653
+ }
19654
+ }), null);
19655
+ insert(_el$42, createComponent2(Show, {
19656
+ get when() {
19657
+ return claudeStatus();
19658
+ },
19659
+ children: (s2) => (() => {
19660
+ var _el$71 = createElement("box"), _el$72 = createElement("text");
19661
+ insertNode(_el$71, _el$72);
19662
+ setProp(_el$71, "flexDirection", "column");
19663
+ setProp(_el$71, "gap", 0);
19664
+ setProp(_el$72, "wrapMode", "word");
19665
+ insert(_el$72, (() => {
19666
+ var _c$ = memo2(() => !!s2().binary.found);
19667
+ return () => _c$() ? `Binary: ${s2().binary.path}` : `Binary: ${s2().binary.error}`;
19668
+ })());
19669
+ insert(_el$71, () => {
19670
+ const a2 = s2().account;
19671
+ if (a2.kind === "oauth") {
19672
+ const tail = [a2.organization, a2.billingType].filter((x2) => !!x2).join(" \xB7 ");
19673
+ return (() => {
19674
+ var _el$73 = createElement("text");
19675
+ setProp(_el$73, "wrapMode", "word");
19676
+ insert(_el$73, () => `\u25CF Logged in: ${a2.email}${tail ? ` (${tail})` : ""}`);
19677
+ effect((_$p) => setProp(_el$73, "fg", theme.success, _$p));
19678
+ return _el$73;
19679
+ })();
19680
+ }
19681
+ return (() => {
19682
+ var _el$74 = createElement("text");
19683
+ insertNode(_el$74, createTextNode(`\u25CB Not logged in`));
19684
+ effect((_$p) => setProp(_el$74, "fg", theme.textMuted, _$p));
19685
+ return _el$74;
19686
+ })();
19687
+ }, null);
19688
+ insert(_el$71, createComponent2(Show, {
19689
+ get when() {
19690
+ return s2().accountError;
19691
+ },
19692
+ children: (err) => (() => {
19693
+ var _el$76 = createElement("text");
19694
+ setProp(_el$76, "wrapMode", "word");
19695
+ insert(_el$76, () => `! ${err()}`);
19696
+ effect((_$p) => setProp(_el$76, "fg", theme.warning, _$p));
19697
+ return _el$76;
19698
+ })()
19699
+ }), null);
19700
+ effect((_$p) => setProp(_el$72, "fg", s2().binary.found ? theme.textMuted : theme.warning, _$p));
19701
+ return _el$71;
19702
+ })()
19703
+ }), null);
19704
+ insertNode(_el$47, _el$48);
19705
+ setProp(_el$47, "flexDirection", "column");
19706
+ setProp(_el$47, "gap", 0);
19707
+ insertNode(_el$48, createTextNode(`codex`));
19708
+ insert(_el$47, createComponent2(Show, {
19709
+ get when() {
19710
+ return codexStatus() === null;
19711
+ },
19712
+ get children() {
19713
+ var _el$50 = createElement("text");
19714
+ insertNode(_el$50, createTextNode(`Checking\u2026`));
19715
+ effect((_$p) => setProp(_el$50, "fg", theme.textMuted, _$p));
19716
+ return _el$50;
19717
+ }
19718
+ }), null);
19719
+ insert(_el$47, createComponent2(Show, {
19720
+ get when() {
19721
+ return codexStatus();
19722
+ },
19723
+ children: (s2) => (() => {
19724
+ var _el$77 = createElement("box"), _el$78 = createElement("text");
19725
+ insertNode(_el$77, _el$78);
19726
+ setProp(_el$77, "flexDirection", "column");
19727
+ setProp(_el$77, "gap", 0);
19728
+ setProp(_el$78, "wrapMode", "word");
19729
+ insert(_el$78, (() => {
19730
+ var _c$2 = memo2(() => !!s2().binary.found);
19731
+ return () => _c$2() ? `Binary: ${s2().binary.path}` : `Binary: ${s2().binary.error}`;
19732
+ })());
19733
+ insert(_el$77, () => {
19734
+ const a2 = s2().account;
19735
+ if (a2.kind === "chatgpt") {
19736
+ return (() => {
19737
+ var _el$79 = createElement("text");
19738
+ setProp(_el$79, "wrapMode", "word");
19739
+ insert(_el$79, () => `\u25CF ChatGPT login: ${a2.email}${a2.plan ? ` (${a2.plan})` : ""}`);
19740
+ effect((_$p) => setProp(_el$79, "fg", theme.success, _$p));
19741
+ return _el$79;
19742
+ })();
19743
+ }
19744
+ if (a2.kind === "apikey") {
19745
+ return (() => {
19746
+ var _el$80 = createElement("text");
19747
+ insertNode(_el$80, createTextNode(`\u25CF API key configured`));
19748
+ effect((_$p) => setProp(_el$80, "fg", theme.success, _$p));
19749
+ return _el$80;
19750
+ })();
19751
+ }
19752
+ return (() => {
19753
+ var _el$82 = createElement("text");
19754
+ insertNode(_el$82, createTextNode(`\u25CB Not logged in`));
19755
+ effect((_$p) => setProp(_el$82, "fg", theme.textMuted, _$p));
19756
+ return _el$82;
19757
+ })();
19758
+ }, null);
19759
+ insert(_el$77, createComponent2(Show, {
19760
+ get when() {
19761
+ return s2().accountError;
19762
+ },
19763
+ children: (err) => (() => {
19764
+ var _el$84 = createElement("text");
19765
+ setProp(_el$84, "wrapMode", "word");
19766
+ insert(_el$84, () => `! ${err()}`);
19767
+ effect((_$p) => setProp(_el$84, "fg", theme.warning, _$p));
19768
+ return _el$84;
19769
+ })()
19770
+ }), null);
19771
+ effect((_$p) => setProp(_el$78, "fg", s2().binary.found ? theme.textMuted : theme.warning, _$p));
19772
+ return _el$77;
19773
+ })()
19774
+ }), null);
19775
+ effect((_p$) => {
19776
+ var _v$20 = theme.text, _v$21 = TextAttributes7.BOLD, _v$22 = theme.textMuted, _v$23 = theme.text, _v$24 = TextAttributes7.BOLD, _v$25 = theme.text, _v$26 = TextAttributes7.BOLD;
19777
+ _v$20 !== _p$.e && (_p$.e = setProp(_el$38, "fg", _v$20, _p$.e));
19778
+ _v$21 !== _p$.t && (_p$.t = setProp(_el$38, "attributes", _v$21, _p$.t));
19779
+ _v$22 !== _p$.a && (_p$.a = setProp(_el$40, "fg", _v$22, _p$.a));
19780
+ _v$23 !== _p$.o && (_p$.o = setProp(_el$43, "fg", _v$23, _p$.o));
19781
+ _v$24 !== _p$.i && (_p$.i = setProp(_el$43, "attributes", _v$24, _p$.i));
19782
+ _v$25 !== _p$.n && (_p$.n = setProp(_el$48, "fg", _v$25, _p$.n));
19783
+ _v$26 !== _p$.s && (_p$.s = setProp(_el$48, "attributes", _v$26, _p$.s));
19784
+ return _p$;
19785
+ }, {
19786
+ e: undefined,
19787
+ t: undefined,
19788
+ a: undefined,
19789
+ o: undefined,
19790
+ i: undefined,
19791
+ n: undefined,
19792
+ s: undefined
19793
+ });
19794
+ return _el$37;
19795
+ }
19796
+ }), null);
19797
+ insert(_el$9, createComponent2(Show, {
19798
+ get when() {
19799
+ return section() === "dev";
19800
+ },
19801
+ get children() {
19802
+ var _el$52 = createElement("box"), _el$53 = createElement("text"), _el$55 = createElement("text");
19803
+ insertNode(_el$52, _el$53);
19804
+ insertNode(_el$52, _el$55);
19805
+ setProp(_el$52, "flexDirection", "column");
19806
+ setProp(_el$52, "gap", 1);
19807
+ insertNode(_el$53, createTextNode(`Reset UI state`));
19808
+ insertNode(_el$55, createTextNode(`Clears ~/.config/kobe/state.json and ~/.kobe/tasks.json, then quits kobe \u2014 relaunch to start fresh. Working session / Archive lists, pane sizes, theme, model picks all reset. Worktrees on disk and Claude Code session history are not touched.`));
19809
+ setProp(_el$55, "wrapMode", "word");
19810
+ insert(_el$52, () => {
19086
19811
  const isCursor = () => level() === "body" && bodyRow() === 0;
19087
19812
  return (() => {
19088
- var _el$56 = createElement("box"), _el$57 = createElement("text");
19089
- insertNode(_el$56, _el$57);
19090
- setProp(_el$56, "flexDirection", "row");
19091
- setProp(_el$56, "paddingLeft", 1);
19092
- setProp(_el$56, "paddingRight", 1);
19093
- setProp(_el$56, "onMouseUp", () => {
19813
+ var _el$85 = createElement("box"), _el$86 = createElement("text");
19814
+ insertNode(_el$85, _el$86);
19815
+ setProp(_el$85, "flexDirection", "row");
19816
+ setProp(_el$85, "paddingLeft", 1);
19817
+ setProp(_el$85, "paddingRight", 1);
19818
+ setProp(_el$85, "onMouseUp", () => {
19094
19819
  setLevel("body");
19095
19820
  setBodyRow(0);
19096
19821
  confirmReset();
19097
19822
  });
19098
- insertNode(_el$57, createTextNode(`[enter] Reset`));
19823
+ insertNode(_el$86, createTextNode(`[enter] Reset`));
19099
19824
  effect((_p$) => {
19100
- var _v$39 = isCursor() ? theme.primary : theme.backgroundElement, _v$40 = isCursor() ? theme.selectedListItemText : theme.warning, _v$41 = TextAttributes7.BOLD;
19101
- _v$39 !== _p$.e && (_p$.e = setProp(_el$56, "backgroundColor", _v$39, _p$.e));
19102
- _v$40 !== _p$.t && (_p$.t = setProp(_el$57, "fg", _v$40, _p$.t));
19103
- _v$41 !== _p$.a && (_p$.a = setProp(_el$57, "attributes", _v$41, _p$.a));
19825
+ var _v$46 = isCursor() ? theme.primary : theme.backgroundElement, _v$47 = isCursor() ? theme.selectedListItemText : theme.warning, _v$48 = TextAttributes7.BOLD;
19826
+ _v$46 !== _p$.e && (_p$.e = setProp(_el$85, "backgroundColor", _v$46, _p$.e));
19827
+ _v$47 !== _p$.t && (_p$.t = setProp(_el$86, "fg", _v$47, _p$.t));
19828
+ _v$48 !== _p$.a && (_p$.a = setProp(_el$86, "attributes", _v$48, _p$.a));
19104
19829
  return _p$;
19105
19830
  }, {
19106
19831
  e: undefined,
19107
19832
  t: undefined,
19108
19833
  a: undefined
19109
19834
  });
19110
- return _el$56;
19835
+ return _el$85;
19111
19836
  })();
19112
19837
  }, null);
19113
- insert(_el$37, createComponent2(Show, {
19838
+ insert(_el$52, createComponent2(Show, {
19114
19839
  when: hasDaemon,
19115
19840
  get children() {
19116
- var _el$42 = createElement("box"), _el$43 = createElement("text"), _el$45 = createElement("text");
19117
- insertNode(_el$42, _el$43);
19118
- insertNode(_el$42, _el$45);
19119
- setProp(_el$42, "flexDirection", "column");
19120
- setProp(_el$42, "gap", 0);
19121
- setProp(_el$42, "paddingTop", 1);
19122
- insertNode(_el$43, createTextNode(`Restart backend`));
19123
- insertNode(_el$45, createTextNode(`Stops the kobed daemon and quits this kobe window so the next launch spawns a fresh daemon \u2014 picks up daemon / orchestrator / engine edits without a process kill. Other attached kobe windows will lose their connection too.`));
19124
- setProp(_el$45, "wrapMode", "word");
19125
- insert(_el$42, () => {
19841
+ var _el$57 = createElement("box"), _el$58 = createElement("text"), _el$60 = createElement("text");
19842
+ insertNode(_el$57, _el$58);
19843
+ insertNode(_el$57, _el$60);
19844
+ setProp(_el$57, "flexDirection", "column");
19845
+ setProp(_el$57, "gap", 0);
19846
+ setProp(_el$57, "paddingTop", 1);
19847
+ insertNode(_el$58, createTextNode(`Restart backend`));
19848
+ insertNode(_el$60, createTextNode(`Stops the kobed daemon and quits this kobe window so the next launch spawns a fresh daemon \u2014 picks up daemon / orchestrator / engine edits without a process kill. Other attached kobe windows will lose their connection too.`));
19849
+ setProp(_el$60, "wrapMode", "word");
19850
+ insert(_el$57, () => {
19126
19851
  const isCursor = () => level() === "body" && bodyRow() === 1;
19127
19852
  return (() => {
19128
- var _el$59 = createElement("box"), _el$60 = createElement("text");
19129
- insertNode(_el$59, _el$60);
19130
- setProp(_el$59, "flexDirection", "row");
19131
- setProp(_el$59, "paddingLeft", 1);
19132
- setProp(_el$59, "paddingRight", 1);
19133
- setProp(_el$59, "onMouseUp", () => {
19853
+ var _el$88 = createElement("box"), _el$89 = createElement("text");
19854
+ insertNode(_el$88, _el$89);
19855
+ setProp(_el$88, "flexDirection", "row");
19856
+ setProp(_el$88, "paddingLeft", 1);
19857
+ setProp(_el$88, "paddingRight", 1);
19858
+ setProp(_el$88, "onMouseUp", () => {
19134
19859
  setLevel("body");
19135
19860
  setBodyRow(1);
19136
19861
  confirmRestartDaemon();
19137
19862
  });
19138
- insertNode(_el$60, createTextNode(`[enter] Restart`));
19863
+ insertNode(_el$89, createTextNode(`[enter] Restart`));
19139
19864
  effect((_p$) => {
19140
- var _v$42 = isCursor() ? theme.primary : theme.backgroundElement, _v$43 = isCursor() ? theme.selectedListItemText : theme.accent, _v$44 = TextAttributes7.BOLD;
19141
- _v$42 !== _p$.e && (_p$.e = setProp(_el$59, "backgroundColor", _v$42, _p$.e));
19142
- _v$43 !== _p$.t && (_p$.t = setProp(_el$60, "fg", _v$43, _p$.t));
19143
- _v$44 !== _p$.a && (_p$.a = setProp(_el$60, "attributes", _v$44, _p$.a));
19865
+ var _v$49 = isCursor() ? theme.primary : theme.backgroundElement, _v$50 = isCursor() ? theme.selectedListItemText : theme.accent, _v$51 = TextAttributes7.BOLD;
19866
+ _v$49 !== _p$.e && (_p$.e = setProp(_el$88, "backgroundColor", _v$49, _p$.e));
19867
+ _v$50 !== _p$.t && (_p$.t = setProp(_el$89, "fg", _v$50, _p$.t));
19868
+ _v$51 !== _p$.a && (_p$.a = setProp(_el$89, "attributes", _v$51, _p$.a));
19144
19869
  return _p$;
19145
19870
  }, {
19146
19871
  e: undefined,
19147
19872
  t: undefined,
19148
19873
  a: undefined
19149
19874
  });
19150
- return _el$59;
19875
+ return _el$88;
19151
19876
  })();
19152
19877
  }, null);
19153
19878
  effect((_p$) => {
19154
- var _v$20 = theme.text, _v$21 = TextAttributes7.BOLD, _v$22 = theme.textMuted;
19155
- _v$20 !== _p$.e && (_p$.e = setProp(_el$43, "fg", _v$20, _p$.e));
19156
- _v$21 !== _p$.t && (_p$.t = setProp(_el$43, "attributes", _v$21, _p$.t));
19157
- _v$22 !== _p$.a && (_p$.a = setProp(_el$45, "fg", _v$22, _p$.a));
19879
+ var _v$27 = theme.text, _v$28 = TextAttributes7.BOLD, _v$29 = theme.textMuted;
19880
+ _v$27 !== _p$.e && (_p$.e = setProp(_el$58, "fg", _v$27, _p$.e));
19881
+ _v$28 !== _p$.t && (_p$.t = setProp(_el$58, "attributes", _v$28, _p$.t));
19882
+ _v$29 !== _p$.a && (_p$.a = setProp(_el$60, "fg", _v$29, _p$.a));
19158
19883
  return _p$;
19159
19884
  }, {
19160
19885
  e: undefined,
19161
19886
  t: undefined,
19162
19887
  a: undefined
19163
19888
  });
19164
- return _el$42;
19889
+ return _el$57;
19165
19890
  }
19166
19891
  }), null);
19167
19892
  effect((_p$) => {
19168
- var _v$23 = theme.text, _v$24 = TextAttributes7.BOLD, _v$25 = theme.textMuted;
19169
- _v$23 !== _p$.e && (_p$.e = setProp(_el$38, "fg", _v$23, _p$.e));
19170
- _v$24 !== _p$.t && (_p$.t = setProp(_el$38, "attributes", _v$24, _p$.t));
19171
- _v$25 !== _p$.a && (_p$.a = setProp(_el$40, "fg", _v$25, _p$.a));
19893
+ var _v$30 = theme.text, _v$31 = TextAttributes7.BOLD, _v$32 = theme.textMuted;
19894
+ _v$30 !== _p$.e && (_p$.e = setProp(_el$53, "fg", _v$30, _p$.e));
19895
+ _v$31 !== _p$.t && (_p$.t = setProp(_el$53, "attributes", _v$31, _p$.t));
19896
+ _v$32 !== _p$.a && (_p$.a = setProp(_el$55, "fg", _v$32, _p$.a));
19172
19897
  return _p$;
19173
19898
  }, {
19174
19899
  e: undefined,
19175
19900
  t: undefined,
19176
19901
  a: undefined
19177
19902
  });
19178
- return _el$37;
19903
+ return _el$52;
19179
19904
  }
19180
19905
  }), null);
19181
- insertNode(_el$47, _el$48);
19182
- setProp(_el$47, "paddingTop", 0);
19183
- insertNode(_el$48, createTextNode(`j/k pick \xB7 h/l switch level \xB7 enter activate \xB7 esc close`));
19906
+ insertNode(_el$62, _el$63);
19907
+ setProp(_el$62, "paddingTop", 0);
19908
+ insertNode(_el$63, createTextNode(`j/k pick \xB7 h/l switch level \xB7 enter activate \xB7 esc close`));
19184
19909
  effect((_p$) => {
19185
- var _v$26 = TextAttributes7.BOLD, _v$27 = theme.text, _v$28 = theme.textMuted, _v$29 = theme.textMuted;
19186
- _v$26 !== _p$.e && (_p$.e = setProp(_el$3, "attributes", _v$26, _p$.e));
19187
- _v$27 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$27, _p$.t));
19188
- _v$28 !== _p$.a && (_p$.a = setProp(_el$5, "fg", _v$28, _p$.a));
19189
- _v$29 !== _p$.o && (_p$.o = setProp(_el$48, "fg", _v$29, _p$.o));
19910
+ var _v$33 = TextAttributes7.BOLD, _v$34 = theme.text, _v$35 = theme.textMuted, _v$36 = theme.textMuted;
19911
+ _v$33 !== _p$.e && (_p$.e = setProp(_el$3, "attributes", _v$33, _p$.e));
19912
+ _v$34 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$34, _p$.t));
19913
+ _v$35 !== _p$.a && (_p$.a = setProp(_el$5, "fg", _v$35, _p$.a));
19914
+ _v$36 !== _p$.o && (_p$.o = setProp(_el$63, "fg", _v$36, _p$.o));
19190
19915
  return _p$;
19191
19916
  }, {
19192
19917
  e: undefined,
@@ -19210,6 +19935,7 @@ var init_settings_dialog = __esm(() => {
19210
19935
  init_solid();
19211
19936
  init_dev();
19212
19937
  init_remote_orchestrator();
19938
+ init_account_detect();
19213
19939
  init_env();
19214
19940
  init_theme();
19215
19941
  init_keymap();
@@ -19223,6 +19949,9 @@ var init_settings_dialog = __esm(() => {
19223
19949
  SECTIONS = [{
19224
19950
  id: "general",
19225
19951
  label: "General"
19952
+ }, {
19953
+ id: "accounts",
19954
+ label: "Accounts"
19226
19955
  }, {
19227
19956
  id: "dev",
19228
19957
  label: "Dev"
@@ -19326,9 +20055,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
19326
20055
  var init_pulse = () => {};
19327
20056
 
19328
20057
  // src/tui/lib/sound.ts
19329
- import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
20058
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
19330
20059
  import { tmpdir as tmpdir2 } from "os";
19331
- import { basename as basename2, isAbsolute, join as join10, resolve as resolve3 } from "path";
20060
+ import { basename as basename2, isAbsolute, join as join11, resolve as resolve3 } from "path";
19332
20061
  function args(player, file, volume) {
19333
20062
  if (player === "ffplay")
19334
20063
  return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
@@ -19349,15 +20078,15 @@ function args(player, file, volume) {
19349
20078
  function pickPlayer() {
19350
20079
  if (cachedPlayer !== undefined)
19351
20080
  return cachedPlayer;
19352
- const path7 = process.env.PATH ?? "";
19353
- const segments = path7.split(":").filter(Boolean);
19354
- cachedPlayer = PLAYERS.find((p2) => segments.some((dir) => existsSync5(join10(dir, p2)))) ?? null;
20081
+ const path9 = process.env.PATH ?? "";
20082
+ const segments = path9.split(":").filter(Boolean);
20083
+ cachedPlayer = PLAYERS.find((p2) => segments.some((dir) => existsSync6(join11(dir, p2)))) ?? null;
19355
20084
  return cachedPlayer;
19356
20085
  }
19357
20086
  async function ensureAsset() {
19358
20087
  cachedPath ??= (async () => {
19359
20088
  mkdirSync3(DIR, { recursive: true });
19360
- const dest = join10(DIR, basename2(pulseAsset));
20089
+ const dest = join11(DIR, basename2(pulseAsset));
19361
20090
  const out = Bun.file(dest);
19362
20091
  if (await out.exists())
19363
20092
  return dest;
@@ -19370,9 +20099,9 @@ function pulse(volume = 0.4) {
19370
20099
  const player = pickPlayer();
19371
20100
  if (!player)
19372
20101
  return;
19373
- ensureAsset().then((path7) => {
20102
+ ensureAsset().then((path9) => {
19374
20103
  try {
19375
- const proc = Bun.spawn(args(player, path7, volume), {
20104
+ const proc = Bun.spawn(args(player, path9, volume), {
19376
20105
  stdin: "ignore",
19377
20106
  stdout: "ignore",
19378
20107
  stderr: "ignore"
@@ -19387,7 +20116,7 @@ var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
19387
20116
  var init_sound = __esm(() => {
19388
20117
  init_pulse();
19389
20118
  pulseAsset = isAbsolute(pulse_default) ? pulse_default : resolve3(import.meta.dir, pulse_default);
19390
- DIR = join10(tmpdir2(), "kobe-sfx");
20119
+ DIR = join11(tmpdir2(), "kobe-sfx");
19391
20120
  PLAYERS = [
19392
20121
  "ffplay",
19393
20122
  "mpv",
@@ -19405,12 +20134,12 @@ var init_sound = __esm(() => {
19405
20134
  });
19406
20135
 
19407
20136
  // src/tui/context/kv.tsx
19408
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
19409
- import { homedir as homedir7 } from "os";
19410
- import { dirname as dirname4, join as join11 } from "path";
20137
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
20138
+ import { homedir as homedir10 } from "os";
20139
+ import { dirname as dirname4, join as join12 } from "path";
19411
20140
  function loadInitial() {
19412
20141
  try {
19413
- const text = readFileSync5(STATE_PATH, "utf8");
20142
+ const text = readFileSync7(STATE_PATH, "utf8");
19414
20143
  const parsed = JSON.parse(text);
19415
20144
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
19416
20145
  return parsed;
@@ -19422,7 +20151,7 @@ var STATE_PATH, WRITE_DEBOUNCE_MS = 250, useKV, KVProvider;
19422
20151
  var init_kv = __esm(() => {
19423
20152
  init_dev2();
19424
20153
  init_helper();
19425
- STATE_PATH = join11(homedir7(), ".config", "kobe", "state.json");
20154
+ STATE_PATH = join12(homedir10(), ".config", "kobe", "state.json");
19426
20155
  ({
19427
20156
  use: useKV,
19428
20157
  provider: KVProvider
@@ -20999,8 +21728,8 @@ var init_create_pr_button = __esm(() => {
20999
21728
 
21000
21729
  // src/tui/lib/worktree-opener.ts
21001
21730
  import { spawn as spawn5 } from "child_process";
21002
- import { existsSync as existsSync6 } from "fs";
21003
- import { basename as basename4, delimiter, isAbsolute as isAbsolute2, join as join12 } from "path";
21731
+ import { existsSync as existsSync7 } from "fs";
21732
+ import { basename as basename4, delimiter, isAbsolute as isAbsolute2, join as join13 } from "path";
21004
21733
  function executableOnPath(command, env, exists) {
21005
21734
  if (isAbsolute2(command))
21006
21735
  return exists(command);
@@ -21008,7 +21737,7 @@ function executableOnPath(command, env, exists) {
21008
21737
  for (const dir of pathEnv.split(delimiter)) {
21009
21738
  if (!dir)
21010
21739
  continue;
21011
- if (exists(join12(dir, command)))
21740
+ if (exists(join13(dir, command)))
21012
21741
  return true;
21013
21742
  }
21014
21743
  return false;
@@ -21028,7 +21757,7 @@ function labelForOverride(command) {
21028
21757
  function detectWorktreeOpener(deps = {}) {
21029
21758
  const env = deps.env ?? process.env;
21030
21759
  const platform = deps.platform ?? process.platform;
21031
- const exists = deps.exists ?? existsSync6;
21760
+ const exists = deps.exists ?? existsSync7;
21032
21761
  const override = env.KOBE_OPEN_EDITOR?.trim();
21033
21762
  if (override) {
21034
21763
  return { id: "env", label: labelForOverride(override), command: override, args: [] };
@@ -21040,7 +21769,7 @@ function detectWorktreeOpener(deps = {}) {
21040
21769
  }
21041
21770
  if (platform === "darwin" && executableOnPath("open", env, exists)) {
21042
21771
  for (const app of MAC_APP_CANDIDATES) {
21043
- if (app.paths.some((path7) => exists(path7))) {
21772
+ if (app.paths.some((path9) => exists(path9))) {
21044
21773
  return { id: app.id, label: app.label, command: "open", args: ["-a", app.appName] };
21045
21774
  }
21046
21775
  }
@@ -22102,7 +22831,7 @@ var init_update_dialog = __esm(() => {
22102
22831
  });
22103
22832
 
22104
22833
  // src/tui/component/top-bar.tsx
22105
- import { spawnSync as spawnSync7 } from "child_process";
22834
+ import { spawnSync as spawnSync8 } from "child_process";
22106
22835
  import { TextAttributes as TextAttributes18 } from "@opentui/core";
22107
22836
  function TopBar(props) {
22108
22837
  const {
@@ -22133,7 +22862,7 @@ function TopBar(props) {
22133
22862
  `);
22134
22863
  process.stderr.write(`running: ${UPDATE_COMMAND}
22135
22864
  `);
22136
- const result = spawnSync7("sh", ["-c", UPDATE_COMMAND], {
22865
+ const result = spawnSync8("sh", ["-c", UPDATE_COMMAND], {
22137
22866
  stdio: "inherit"
22138
22867
  });
22139
22868
  if (result.error) {
@@ -22387,34 +23116,160 @@ var init_sync = __esm(() => {
22387
23116
  }));
22388
23117
  });
22389
23118
 
22390
- // src/engine/claude-code-local/history.ts
22391
- import { randomUUID } from "crypto";
22392
- import { appendFile, mkdir as mkdir2, readFile as readFile3, readdir, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
22393
- import { homedir as homedir8 } from "os";
22394
- import path7 from "path";
22395
- function encodeCwd(cwd) {
22396
- return cwd.replace(/[/.]/g, "-");
22397
- }
22398
- async function readHistory(sessionId, deps = defaultDeps2) {
22399
- const root = deps.projectsDir();
22400
- const projectDirs = await deps.readdir(root);
22401
- for (const dir of projectDirs) {
22402
- const candidate = path7.join(root, dir, `${sessionId}.jsonl`);
22403
- let raw;
22404
- try {
22405
- raw = await deps.readFile(candidate);
22406
- } catch {
22407
- continue;
22408
- }
22409
- return sortByTimestamp(parseJsonl(raw, sessionId));
22410
- }
22411
- return [];
23119
+ // src/session/usage-metrics.ts
23120
+ function totalContextTokens(u2) {
23121
+ return u2.input_tokens + (u2.cache_read_input_tokens ?? 0) + (u2.cache_creation_input_tokens ?? 0);
22412
23122
  }
22413
- async function deleteHistory(sessionId, deps = defaultDeps2) {
22414
- const root = deps.projectsDir();
23123
+ function parseTimestampMs(value) {
23124
+ const ms = new Date(value).getTime();
23125
+ return Number.isFinite(ms) ? ms : null;
23126
+ }
23127
+ function mergeIntervals(intervals) {
23128
+ if (intervals.length === 0)
23129
+ return [];
23130
+ const sorted = [...intervals].sort((a2, b2) => a2.startMs - b2.startMs);
23131
+ const first = sorted[0];
23132
+ if (!first)
23133
+ return [];
23134
+ const merged = [{ startMs: first.startMs, endMs: first.endMs }];
23135
+ for (let i2 = 1;i2 < sorted.length; i2++) {
23136
+ const current = sorted[i2];
23137
+ const last = merged[merged.length - 1];
23138
+ if (!current || !last)
23139
+ continue;
23140
+ if (current.startMs <= last.endMs) {
23141
+ last.endMs = Math.max(last.endMs, current.endMs);
23142
+ } else {
23143
+ merged.push({ startMs: current.startMs, endMs: current.endMs });
23144
+ }
23145
+ }
23146
+ return merged;
23147
+ }
23148
+ function durationMs(intervals) {
23149
+ return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
23150
+ }
23151
+ function deriveSessionUsageMetrics(past) {
23152
+ let latestUsage;
23153
+ let latestUsageTimestampMs = null;
23154
+ let lastUserTimestampMs = null;
23155
+ let inputTokens = 0;
23156
+ let outputTokens = 0;
23157
+ const intervals = [];
23158
+ for (const message of past) {
23159
+ const timestampMs = parseTimestampMs(message.timestamp);
23160
+ if (message.role === "user" && timestampMs !== null) {
23161
+ lastUserTimestampMs = timestampMs;
23162
+ continue;
23163
+ }
23164
+ if (message.role !== "assistant" || !message.usage)
23165
+ continue;
23166
+ if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
23167
+ latestUsageTimestampMs = timestampMs;
23168
+ latestUsage = message.usage;
23169
+ } else if (latestUsage === undefined) {
23170
+ latestUsage = message.usage;
23171
+ }
23172
+ inputTokens += message.usage.input_tokens;
23173
+ outputTokens += message.usage.output_tokens;
23174
+ if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
23175
+ intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
23176
+ }
23177
+ }
23178
+ if (!latestUsage)
23179
+ return;
23180
+ const totalDurationMs = durationMs(mergeIntervals(intervals));
23181
+ if (totalDurationMs <= 0)
23182
+ return latestUsage;
23183
+ return {
23184
+ ...latestUsage,
23185
+ total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
23186
+ };
23187
+ }
23188
+ function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
23189
+ const startMs = startedAtIso ? parseTimestampMs(startedAtIso) : null;
23190
+ const endMs = parseTimestampMs(endedAtIso);
23191
+ if (startMs === null || endMs === null || endMs <= startMs)
23192
+ return usage;
23193
+ return {
23194
+ ...usage,
23195
+ total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
23196
+ };
23197
+ }
23198
+
23199
+ // src/engine/claude-code-local/normalize.ts
23200
+ function normalizeClaudeContent(content) {
23201
+ if (typeof content === "string") {
23202
+ return content.length > 0 ? [{ type: "text", text: content }] : [];
23203
+ }
23204
+ if (!Array.isArray(content))
23205
+ return [];
23206
+ const out = [];
23207
+ for (const block of content) {
23208
+ if (typeof block === "string") {
23209
+ if (block.length > 0)
23210
+ out.push({ type: "text", text: block });
23211
+ continue;
23212
+ }
23213
+ if (!block || typeof block !== "object")
23214
+ continue;
23215
+ const b2 = block;
23216
+ if (b2.type === "text" && typeof b2.text === "string") {
23217
+ out.push({ type: "text", text: b2.text });
23218
+ continue;
23219
+ }
23220
+ if (b2.type === "tool_use") {
23221
+ out.push({
23222
+ type: "tool_call",
23223
+ callId: typeof b2.id === "string" ? b2.id : "",
23224
+ name: typeof b2.name === "string" ? b2.name : "",
23225
+ input: b2.input
23226
+ });
23227
+ continue;
23228
+ }
23229
+ if (b2.type === "tool_result") {
23230
+ out.push({
23231
+ type: "tool_result",
23232
+ callId: typeof b2.tool_use_id === "string" ? b2.tool_use_id : "",
23233
+ output: b2.content,
23234
+ isError: b2.is_error === true
23235
+ });
23236
+ continue;
23237
+ }
23238
+ if (b2.type === "thinking" && typeof b2.thinking === "string") {
23239
+ out.push({ type: "thinking", text: b2.thinking });
23240
+ }
23241
+ }
23242
+ return out;
23243
+ }
23244
+
23245
+ // src/engine/claude-code-local/history.ts
23246
+ import { randomUUID } from "crypto";
23247
+ import { appendFile, mkdir as mkdir2, readFile as readFile3, readdir, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
23248
+ import { homedir as homedir11 } from "os";
23249
+ import path9 from "path";
23250
+ function encodeCwd(cwd) {
23251
+ return cwd.replace(/[/.]/g, "-");
23252
+ }
23253
+ async function readHistory(sessionId, deps = defaultDeps4) {
23254
+ const root = deps.projectsDir();
23255
+ const projectDirs = await deps.readdir(root);
23256
+ for (const dir of projectDirs) {
23257
+ const candidate = path9.join(root, dir, `${sessionId}.jsonl`);
23258
+ let raw;
23259
+ try {
23260
+ raw = await deps.readFile(candidate);
23261
+ } catch {
23262
+ continue;
23263
+ }
23264
+ return sortByTimestamp(parseJsonl(raw, sessionId));
23265
+ }
23266
+ return [];
23267
+ }
23268
+ async function deleteHistory(sessionId, deps = defaultDeps4) {
23269
+ const root = deps.projectsDir();
22415
23270
  const projectDirs = await deps.readdir(root);
22416
23271
  for (const dir of projectDirs) {
22417
- const candidate = path7.join(root, dir, `${sessionId}.jsonl`);
23272
+ const candidate = path9.join(root, dir, `${sessionId}.jsonl`);
22418
23273
  try {
22419
23274
  await unlink2(candidate);
22420
23275
  } catch (err) {
@@ -22461,11 +23316,11 @@ function extractMessage(record, fallbackSessionId) {
22461
23316
  return null;
22462
23317
  if (!("content" in inner))
22463
23318
  return null;
22464
- const content = inner.content;
23319
+ const blocks = normalizeClaudeContent(inner.content);
22465
23320
  const ts = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
22466
23321
  const sid = typeof record.sessionId === "string" ? record.sessionId : fallbackSessionId;
22467
23322
  const usage = extractUsage(inner.usage);
22468
- return usage ? { role, content, timestamp: ts, sessionId: sid, usage } : { role, content, timestamp: ts, sessionId: sid };
23323
+ return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
22469
23324
  }
22470
23325
  function extractUsage(v2) {
22471
23326
  if (!isObject(v2))
@@ -22486,11 +23341,11 @@ function extractUsage(v2) {
22486
23341
  function isObject(v2) {
22487
23342
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
22488
23343
  }
22489
- async function appendInterruptedUserPrompt(sessionId, cwd, prompt, deps = defaultDeps2) {
23344
+ async function appendInterruptedUserPrompt(sessionId, cwd, prompt, deps = defaultDeps4) {
22490
23345
  if (!prompt || prompt.trim().length === 0)
22491
23346
  return;
22492
- const projectDir = path7.join(deps.projectsDir(), encodeCwd(cwd));
22493
- const filePath = path7.join(projectDir, `${sessionId}.jsonl`);
23347
+ const projectDir = path9.join(deps.projectsDir(), encodeCwd(cwd));
23348
+ const filePath = path9.join(projectDir, `${sessionId}.jsonl`);
22494
23349
  let lines = [];
22495
23350
  try {
22496
23351
  const raw = await readFile3(filePath, "utf8");
@@ -22556,11 +23411,11 @@ ${prompt}` : prompt;
22556
23411
  await appendFile(filePath, `${JSON.stringify(record)}
22557
23412
  `);
22558
23413
  }
22559
- var defaultDeps2;
23414
+ var defaultDeps4;
22560
23415
  var init_history = __esm(() => {
22561
- defaultDeps2 = {
23416
+ defaultDeps4 = {
22562
23417
  projectsDir() {
22563
- return path7.join(homedir8(), ".claude", "projects");
23418
+ return path9.join(homedir11(), ".claude", "projects");
22564
23419
  },
22565
23420
  async readdir(p2) {
22566
23421
  try {
@@ -22644,16 +23499,16 @@ function delay(ms) {
22644
23499
 
22645
23500
  // src/engine/claude-code-local/sessions.ts
22646
23501
  import { readFile as readFile4, readdir as readdir2, stat as stat2 } from "fs/promises";
22647
- import { homedir as homedir9 } from "os";
22648
- import path8 from "path";
22649
- async function listSessionsForCwd(cwd, deps = defaultDeps3) {
22650
- const projectDir = path8.join(deps.projectsDir(), encodeCwd(cwd));
23502
+ import { homedir as homedir12 } from "os";
23503
+ import path10 from "path";
23504
+ async function listSessionsForCwd(cwd, deps = defaultDeps5) {
23505
+ const projectDir = path10.join(deps.projectsDir(), encodeCwd(cwd));
22651
23506
  const entries = await deps.readdir(projectDir);
22652
23507
  const jsonlNames = entries.filter((n2) => n2.endsWith(".jsonl"));
22653
23508
  const out = [];
22654
23509
  for (const name of jsonlNames) {
22655
23510
  const sessionId = name.slice(0, -".jsonl".length);
22656
- const filePath = path8.join(projectDir, name);
23511
+ const filePath = path10.join(projectDir, name);
22657
23512
  try {
22658
23513
  const [meta, raw] = await Promise.all([deps.stat(filePath), deps.readFile(filePath)]);
22659
23514
  const lines = raw.split(`
@@ -22696,29 +23551,23 @@ function extractFirstUserMessage(lines) {
22696
23551
  return null;
22697
23552
  }
22698
23553
  function stringifyContent(content) {
22699
- if (typeof content === "string")
22700
- return content.trim();
22701
- if (!Array.isArray(content))
22702
- return "";
23554
+ const blocks = normalizeClaudeContent(content);
22703
23555
  const parts = [];
22704
- for (const block of content) {
22705
- if (!isObject2(block))
22706
- continue;
22707
- if (block.type === "text" && typeof block.text === "string") {
22708
- parts.push(block.text);
22709
- }
23556
+ for (const b2 of blocks) {
23557
+ if (b2.type === "text")
23558
+ parts.push(b2.text);
22710
23559
  }
22711
23560
  return parts.join(" ").trim();
22712
23561
  }
22713
23562
  function isObject2(v2) {
22714
23563
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
22715
23564
  }
22716
- var defaultDeps3, PREVIEW_MAX_CHARS = 200;
23565
+ var defaultDeps5, PREVIEW_MAX_CHARS = 200;
22717
23566
  var init_sessions = __esm(() => {
22718
23567
  init_history();
22719
- defaultDeps3 = {
23568
+ defaultDeps5 = {
22720
23569
  projectsDir() {
22721
- return path8.join(homedir9(), ".claude", "projects");
23570
+ return path10.join(homedir12(), ".claude", "projects");
22722
23571
  },
22723
23572
  async readdir(p2) {
22724
23573
  try {
@@ -22765,22 +23614,804 @@ function buildArgs(opts) {
22765
23614
  if (opts.permissionMode) {
22766
23615
  args2.push("--permission-mode", opts.permissionMode);
22767
23616
  }
22768
- args2.push("--output-format", "stream-json", "--verbose");
22769
- const mcpConfig = process.env.KOBE_MCP_CONFIG;
22770
- if (mcpConfig && mcpConfig.length > 0) {
22771
- args2.push("--mcp-config", mcpConfig);
23617
+ args2.push("--output-format", "stream-json", "--verbose");
23618
+ const mcpConfig = process.env.KOBE_MCP_CONFIG;
23619
+ if (mcpConfig && mcpConfig.length > 0) {
23620
+ args2.push("--mcp-config", mcpConfig);
23621
+ }
23622
+ if (opts.extraArgs && opts.extraArgs.length > 0) {
23623
+ args2.push(...opts.extraArgs);
23624
+ }
23625
+ return args2;
23626
+ }
23627
+ var init_spawn = () => {};
23628
+
23629
+ // src/engine/claude-code-local/stream.ts
23630
+ async function* parseStreamJson(lines, opts = {}) {
23631
+ let sessionIdEmitted = false;
23632
+ const toolNameById = new Map;
23633
+ for await (const rawLine of lines) {
23634
+ const line = rawLine.trim();
23635
+ if (!line)
23636
+ continue;
23637
+ let msg;
23638
+ try {
23639
+ msg = JSON.parse(line);
23640
+ } catch (err) {
23641
+ yield { type: "error", message: `stream-json parse failed: ${stringifyErr(err)}` };
23642
+ continue;
23643
+ }
23644
+ if (!isObject3(msg))
23645
+ continue;
23646
+ const type = typeof msg.type === "string" ? msg.type : undefined;
23647
+ if (!type)
23648
+ continue;
23649
+ if ("parent_tool_use_id" in msg && msg.parent_tool_use_id != null)
23650
+ continue;
23651
+ if (type === "system") {
23652
+ const subtype = typeof msg.subtype === "string" ? msg.subtype : undefined;
23653
+ if (subtype === "init" && !sessionIdEmitted) {
23654
+ const sid = typeof msg.session_id === "string" ? msg.session_id : undefined;
23655
+ if (sid) {
23656
+ sessionIdEmitted = true;
23657
+ opts.onSessionId?.(sid);
23658
+ }
23659
+ }
23660
+ continue;
23661
+ }
23662
+ if (type === "assistant") {
23663
+ const content = extractContentBlocks(msg);
23664
+ for (const block of content) {
23665
+ if (!isObject3(block))
23666
+ continue;
23667
+ const blockType = typeof block.type === "string" ? block.type : undefined;
23668
+ if (blockType === "text") {
23669
+ const text = typeof block.text === "string" ? block.text : "";
23670
+ if (text)
23671
+ yield { type: "assistant.delta", text };
23672
+ } else if (blockType === "tool_use") {
23673
+ const name = typeof block.name === "string" ? block.name : "tool";
23674
+ const id = typeof block.id === "string" ? block.id : undefined;
23675
+ if (id)
23676
+ toolNameById.set(id, name);
23677
+ const input = "input" in block ? block.input : undefined;
23678
+ yield { type: "tool.start", name, input };
23679
+ }
23680
+ }
23681
+ continue;
23682
+ }
23683
+ if (type === "user") {
23684
+ const content = extractContentBlocks(msg);
23685
+ for (const block of content) {
23686
+ if (!isObject3(block))
23687
+ continue;
23688
+ const blockType = typeof block.type === "string" ? block.type : undefined;
23689
+ if (blockType === "tool_result") {
23690
+ const id = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
23691
+ const name = id && toolNameById.get(id) || "tool";
23692
+ const output = "content" in block ? block.content : undefined;
23693
+ yield { type: "tool.result", name, output };
23694
+ }
23695
+ }
23696
+ continue;
23697
+ }
23698
+ if (type === "result") {
23699
+ const usage = isObject3(msg.usage) ? msg.usage : undefined;
23700
+ if (usage) {
23701
+ const inTok = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
23702
+ const outTok = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
23703
+ const cacheRead = typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : undefined;
23704
+ const cacheCreate = typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : undefined;
23705
+ yield {
23706
+ type: "usage",
23707
+ input_tokens: inTok,
23708
+ output_tokens: outTok,
23709
+ ...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {},
23710
+ ...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
23711
+ };
23712
+ }
23713
+ const subtype = typeof msg.subtype === "string" ? msg.subtype : "success";
23714
+ const isApiError = msg.is_error === true || typeof msg.api_error_status === "number";
23715
+ if (isApiError) {
23716
+ const status = typeof msg.api_error_status === "number" ? ` ${msg.api_error_status}` : "";
23717
+ const result = typeof msg.result === "string" ? msg.result.trim() : "";
23718
+ yield { type: "error", message: result ? `claude API error${status}: ${result}` : `claude API error${status}` };
23719
+ return;
23720
+ }
23721
+ if (subtype === "success") {
23722
+ yield { type: "done" };
23723
+ } else {
23724
+ yield { type: "error", message: `claude session ended: ${subtype}` };
23725
+ }
23726
+ return;
23727
+ }
23728
+ }
23729
+ }
23730
+ async function* readLines(stream) {
23731
+ let buf = "";
23732
+ for await (const chunk of stream) {
23733
+ const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
23734
+ buf += text;
23735
+ let nl = buf.indexOf(`
23736
+ `);
23737
+ while (nl !== -1) {
23738
+ yield buf.slice(0, nl);
23739
+ buf = buf.slice(nl + 1);
23740
+ nl = buf.indexOf(`
23741
+ `);
23742
+ }
23743
+ }
23744
+ if (buf.length > 0)
23745
+ yield buf;
23746
+ }
23747
+ function isObject3(v2) {
23748
+ return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
23749
+ }
23750
+ function extractContentBlocks(msg) {
23751
+ if (Array.isArray(msg.content))
23752
+ return msg.content;
23753
+ const inner = msg.message;
23754
+ if (isObject3(inner) && Array.isArray(inner.content))
23755
+ return inner.content;
23756
+ return [];
23757
+ }
23758
+ function stringifyErr(err) {
23759
+ if (err instanceof Error)
23760
+ return err.message;
23761
+ try {
23762
+ return JSON.stringify(err);
23763
+ } catch {
23764
+ return String(err);
23765
+ }
23766
+ }
23767
+
23768
+ // src/engine/claude-code-local/index.ts
23769
+ class ClaudeCodeLocal {
23770
+ identity = claudeIdentity;
23771
+ capabilities = claudeCapabilities;
23772
+ registry = new SessionRegistry;
23773
+ running = new Map;
23774
+ binaryPathResolver;
23775
+ stopGraceMs;
23776
+ constructor(opts = {}) {
23777
+ this.binaryPathResolver = opts.binaryPathResolver ?? findClaudeBinary;
23778
+ this.stopGraceMs = opts.stopGraceMs ?? 5000;
23779
+ }
23780
+ async spawn(cwd, prompt, opts) {
23781
+ return this.start({ cwd, prompt, opts });
23782
+ }
23783
+ async resume(sessionId, prompt, opts) {
23784
+ const cwd = opts?.cwd ?? opts?.env?.KOBE_RESUME_CWD ?? process.cwd();
23785
+ return this.start({ cwd, prompt, opts, resumeSessionId: sessionId });
23786
+ }
23787
+ stream(handle) {
23788
+ const sid = handle.sessionId;
23789
+ const self = this;
23790
+ return {
23791
+ async* [Symbol.asyncIterator]() {
23792
+ const session = self.running.get(sid);
23793
+ if (!session)
23794
+ return;
23795
+ let idx = 0;
23796
+ while (true) {
23797
+ if (idx < session.queue.length) {
23798
+ const ev = session.queue[idx++];
23799
+ if (!ev)
23800
+ continue;
23801
+ yield ev;
23802
+ if (ev.type === "done" || ev.type === "error")
23803
+ return;
23804
+ continue;
23805
+ }
23806
+ if (session.closed)
23807
+ return;
23808
+ await new Promise((resolve4) => session.waiters.push(resolve4));
23809
+ }
23810
+ }
23811
+ };
23812
+ }
23813
+ async readHistory(sessionId) {
23814
+ const messages = await readHistory(sessionId);
23815
+ const usageMetrics = deriveSessionUsageMetrics(messages);
23816
+ return { messages, ...usageMetrics ? { usageMetrics } : {} };
23817
+ }
23818
+ async deleteHistory(sessionId) {
23819
+ return deleteHistory(sessionId);
23820
+ }
23821
+ async listSessions(cwd) {
23822
+ return listSessionsForCwd(cwd);
23823
+ }
23824
+ async stop(handle) {
23825
+ const sid = handle.sessionId;
23826
+ const session = this.running.get(sid);
23827
+ const shouldRescue = !!session && !session.completedNaturally && session.prompt.trim().length > 0;
23828
+ const rescuePrompt = session?.prompt ?? "";
23829
+ const rescueCwd = session?.cwd ?? handle.cwd;
23830
+ await this.registry.kill(sid, this.stopGraceMs);
23831
+ if (session) {
23832
+ session.closed = true;
23833
+ this.notify(session);
23834
+ this.running.delete(sid);
23835
+ }
23836
+ if (shouldRescue) {
23837
+ try {
23838
+ await appendInterruptedUserPrompt(sid, rescueCwd, rescuePrompt);
23839
+ } catch {}
23840
+ }
23841
+ }
23842
+ async start(args2) {
23843
+ const binaryPath = await this.binaryPathResolver();
23844
+ const cliPermissionMode = args2.opts?.permissionMode === "plan" ? "plan" : "bypassPermissions";
23845
+ const spawned = spawnClaudeProcess({
23846
+ binaryPath,
23847
+ cwd: args2.cwd,
23848
+ prompt: args2.prompt,
23849
+ model: args2.opts?.model,
23850
+ permissionMode: cliPermissionMode,
23851
+ env: args2.opts?.env,
23852
+ resumeSessionId: args2.resumeSessionId
23853
+ });
23854
+ let resolveHandle = () => {};
23855
+ let rejectHandle = () => {};
23856
+ const handlePromise = new Promise((res, rej) => {
23857
+ resolveHandle = res;
23858
+ rejectHandle = rej;
23859
+ });
23860
+ const queue = [];
23861
+ let session;
23862
+ let bound = false;
23863
+ const bind = (sessionId) => {
23864
+ if (bound)
23865
+ return;
23866
+ bound = true;
23867
+ session = {
23868
+ sessionId,
23869
+ cwd: args2.cwd,
23870
+ spawned,
23871
+ queue,
23872
+ waiters: [],
23873
+ closed: false,
23874
+ completedNaturally: false,
23875
+ prompt: args2.prompt,
23876
+ spawnedAtIso: new Date().toISOString()
23877
+ };
23878
+ this.running.set(sessionId, session);
23879
+ this.registry.register({
23880
+ sessionId,
23881
+ cwd: args2.cwd,
23882
+ proc: spawned.proc,
23883
+ startedAt: Date.now(),
23884
+ prompt: args2.prompt
23885
+ });
23886
+ resolveHandle({ sessionId, cwd: args2.cwd });
23887
+ };
23888
+ if (args2.resumeSessionId) {
23889
+ try {
23890
+ bind(args2.resumeSessionId);
23891
+ } catch (err) {
23892
+ try {
23893
+ spawned.proc.kill("SIGKILL");
23894
+ } catch {}
23895
+ rejectHandle(err);
23896
+ throw err;
23897
+ }
23898
+ }
23899
+ (async () => {
23900
+ const events = parseStreamJson(readLines(spawned.stdout), {
23901
+ onSessionId: (sid) => bind(sid)
23902
+ });
23903
+ try {
23904
+ for await (const ev of events) {
23905
+ const enriched = enrichUsageEvent(ev, session?.spawnedAtIso);
23906
+ queue.push(enriched);
23907
+ if (ev.type === "done" && session) {
23908
+ session.completedNaturally = true;
23909
+ }
23910
+ if (session)
23911
+ this.notify(session);
23912
+ }
23913
+ } catch (err) {
23914
+ const ev = {
23915
+ type: "error",
23916
+ message: `parser failure: ${err instanceof Error ? err.message : String(err)}`
23917
+ };
23918
+ queue.push(ev);
23919
+ if (session)
23920
+ this.notify(session);
23921
+ } finally {
23922
+ if (session) {
23923
+ session.closed = true;
23924
+ this.notify(session);
23925
+ this.registry.unregister(session.sessionId);
23926
+ }
23927
+ if (!bound) {
23928
+ rejectHandle(new Error("claude exited without emitting a session id"));
23929
+ }
23930
+ }
23931
+ })();
23932
+ drainStream(spawned.stderr);
23933
+ spawned.proc.once("error", (err) => {
23934
+ if (!bound)
23935
+ rejectHandle(err);
23936
+ });
23937
+ spawned.proc.once("exit", () => {
23938
+ if (!bound) {
23939
+ rejectHandle(new Error("claude exited before session id was captured"));
23940
+ }
23941
+ });
23942
+ return handlePromise;
23943
+ }
23944
+ notify(session) {
23945
+ const waiters = session.waiters;
23946
+ session.waiters = [];
23947
+ for (const w2 of waiters)
23948
+ w2();
23949
+ }
23950
+ }
23951
+ function drainStream(stream) {
23952
+ const s2 = stream;
23953
+ s2.on("data", () => {});
23954
+ s2.on("error", () => {});
23955
+ }
23956
+ function enrichUsageEvent(ev, startedAtIso) {
23957
+ if (ev.type !== "usage")
23958
+ return ev;
23959
+ return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
23960
+ }
23961
+ var init_claude_code_local = __esm(() => {
23962
+ init_binary();
23963
+ init_capabilities();
23964
+ init_history();
23965
+ init_sessions();
23966
+ init_spawn();
23967
+ });
23968
+
23969
+ // src/engine/codex-local/normalize.ts
23970
+ function normalizeCodexContent(raw) {
23971
+ if (typeof raw === "string") {
23972
+ return raw.length > 0 ? [{ type: "text", text: raw }] : [];
23973
+ }
23974
+ if (!Array.isArray(raw))
23975
+ return [];
23976
+ const blocks = [];
23977
+ for (const item of raw) {
23978
+ if (typeof item === "string") {
23979
+ if (item.length > 0)
23980
+ blocks.push({ type: "text", text: item });
23981
+ continue;
23982
+ }
23983
+ if (!isObject4(item))
23984
+ continue;
23985
+ const t2 = typeof item.type === "string" ? item.type : undefined;
23986
+ if (t2 === "input_text" || t2 === "output_text") {
23987
+ const text = typeof item.text === "string" ? item.text : "";
23988
+ if (text.length > 0)
23989
+ blocks.push({ type: "text", text });
23990
+ continue;
23991
+ }
23992
+ if (t2)
23993
+ blocks.push({ type: "text", text: `[codex: ${t2}]` });
23994
+ }
23995
+ return blocks;
23996
+ }
23997
+ function isObject4(v2) {
23998
+ return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
23999
+ }
24000
+
24001
+ // src/engine/codex-local/history.ts
24002
+ import { readFile as readFile5, readdir as readdir3, unlink as unlink3 } from "fs/promises";
24003
+ import { homedir as homedir13 } from "os";
24004
+ import path11 from "path";
24005
+ async function listRolloutFiles(deps = defaultDeps6) {
24006
+ const root = deps.sessionsDir();
24007
+ const years = (await deps.readdir(root)).sort().reverse();
24008
+ const out = [];
24009
+ for (const y2 of years) {
24010
+ const yp = path11.join(root, y2);
24011
+ const months = (await deps.readdir(yp)).sort().reverse();
24012
+ for (const m2 of months) {
24013
+ const mp = path11.join(yp, m2);
24014
+ const days = (await deps.readdir(mp)).sort().reverse();
24015
+ for (const d2 of days) {
24016
+ const dp = path11.join(mp, d2);
24017
+ const files = (await deps.readdir(dp)).filter((f2) => f2.startsWith("rollout-") && f2.endsWith(".jsonl"));
24018
+ files.sort().reverse();
24019
+ for (const f2 of files)
24020
+ out.push(path11.join(dp, f2));
24021
+ }
24022
+ }
24023
+ }
24024
+ return out;
24025
+ }
24026
+ async function findRolloutFile(sessionId, deps = defaultDeps6) {
24027
+ const all = await listRolloutFiles(deps);
24028
+ for (const p2 of all) {
24029
+ if (path11.basename(p2).endsWith(`-${sessionId}.jsonl`))
24030
+ return p2;
24031
+ }
24032
+ return;
24033
+ }
24034
+ async function readHistoryWithMetrics(sessionId, deps = defaultDeps6) {
24035
+ const file = await findRolloutFile(sessionId, deps);
24036
+ if (!file)
24037
+ return { messages: [] };
24038
+ let raw;
24039
+ try {
24040
+ raw = await deps.readFile(file);
24041
+ } catch {
24042
+ return { messages: [] };
24043
+ }
24044
+ const messages = sortByTimestamp2(parseJsonl2(raw, sessionId));
24045
+ const usageMetrics = deriveCodexUsageMetrics(raw);
24046
+ return { messages, ...usageMetrics ? { usageMetrics } : {} };
24047
+ }
24048
+ async function deleteHistory2(sessionId, deps = defaultDeps6) {
24049
+ const file = await findRolloutFile(sessionId, deps);
24050
+ if (!file)
24051
+ return;
24052
+ try {
24053
+ await unlink3(file);
24054
+ } catch (err) {
24055
+ if (err.code === "ENOENT")
24056
+ return;
24057
+ throw err;
24058
+ }
24059
+ }
24060
+ function sortByTimestamp2(messages) {
24061
+ return messages.map((msg, idx) => ({ msg, idx })).sort((a2, b2) => {
24062
+ if (a2.msg.timestamp < b2.msg.timestamp)
24063
+ return -1;
24064
+ if (a2.msg.timestamp > b2.msg.timestamp)
24065
+ return 1;
24066
+ return a2.idx - b2.idx;
24067
+ }).map((entry) => entry.msg);
24068
+ }
24069
+ function parseJsonl2(raw, sessionId) {
24070
+ const out = [];
24071
+ for (const line of raw.split(`
24072
+ `)) {
24073
+ const trimmed = line.trim();
24074
+ if (!trimmed)
24075
+ continue;
24076
+ let parsed;
24077
+ try {
24078
+ parsed = JSON.parse(trimmed);
24079
+ } catch {
24080
+ continue;
24081
+ }
24082
+ if (!isObject5(parsed))
24083
+ continue;
24084
+ if (parsed.type !== "response_item")
24085
+ continue;
24086
+ const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
24087
+ if (!payload)
24088
+ continue;
24089
+ if (payload.type !== "message")
24090
+ continue;
24091
+ const role = payload.role;
24092
+ if (role !== "user" && role !== "assistant" && role !== "system")
24093
+ continue;
24094
+ const blocks = normalizeCodexContent(payload.content);
24095
+ if (role === "user" && isEnvironmentContextEnvelope(blocks))
24096
+ continue;
24097
+ const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
24098
+ out.push({ role, blocks, timestamp: ts, sessionId });
24099
+ }
24100
+ return out;
24101
+ }
24102
+ function deriveCodexUsageMetrics(raw) {
24103
+ let latestUsage;
24104
+ let latestUsageTimestampMs = null;
24105
+ let lastUserTimestampMs = null;
24106
+ let inputTokens = 0;
24107
+ let outputTokens = 0;
24108
+ const intervals = [];
24109
+ for (const line of raw.split(`
24110
+ `)) {
24111
+ const trimmed = line.trim();
24112
+ if (!trimmed)
24113
+ continue;
24114
+ let parsed;
24115
+ try {
24116
+ parsed = JSON.parse(trimmed);
24117
+ } catch {
24118
+ continue;
24119
+ }
24120
+ if (!isObject5(parsed))
24121
+ continue;
24122
+ const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs2(parsed.timestamp) : null;
24123
+ if (parsed.type === "response_item") {
24124
+ const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
24125
+ if (payload?.type === "message" && payload.role === "user" && timestampMs !== null) {
24126
+ const blocks = normalizeCodexContent(payload.content);
24127
+ if (!isEnvironmentContextEnvelope(blocks))
24128
+ lastUserTimestampMs = timestampMs;
24129
+ }
24130
+ continue;
24131
+ }
24132
+ if (parsed.type !== "turn.completed")
24133
+ continue;
24134
+ const usage = isObject5(parsed.usage) ? parsed.usage : undefined;
24135
+ if (!usage)
24136
+ continue;
24137
+ const snapshot = codexUsageToSnapshot(usage);
24138
+ if (!snapshot)
24139
+ continue;
24140
+ if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
24141
+ latestUsageTimestampMs = timestampMs;
24142
+ latestUsage = snapshot;
24143
+ } else if (latestUsage === undefined) {
24144
+ latestUsage = snapshot;
24145
+ }
24146
+ inputTokens += snapshot.input_tokens;
24147
+ outputTokens += snapshot.output_tokens;
24148
+ if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
24149
+ intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
24150
+ }
24151
+ }
24152
+ if (!latestUsage)
24153
+ return;
24154
+ const durationMs2 = mergedDurationMs(intervals);
24155
+ if (durationMs2 <= 0)
24156
+ return latestUsage;
24157
+ return {
24158
+ ...latestUsage,
24159
+ total_speed_tokens_per_second: (inputTokens + outputTokens) / (durationMs2 / 1000)
24160
+ };
24161
+ }
24162
+ function codexUsageToSnapshot(usage) {
24163
+ const input = numberOr(usage.input_tokens, 0);
24164
+ const output = numberOr(usage.output_tokens, 0) + numberOr(usage.reasoning_output_tokens, 0);
24165
+ const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
24166
+ if (input <= 0 && output <= 0 && cacheRead === undefined)
24167
+ return;
24168
+ return {
24169
+ input_tokens: input,
24170
+ output_tokens: output,
24171
+ ...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
24172
+ };
24173
+ }
24174
+ function parseTimestampMs2(value) {
24175
+ const ms = new Date(value).getTime();
24176
+ return Number.isFinite(ms) ? ms : null;
24177
+ }
24178
+ function mergedDurationMs(intervals) {
24179
+ if (intervals.length === 0)
24180
+ return 0;
24181
+ const sorted = [...intervals].sort((a2, b2) => a2.startMs - b2.startMs);
24182
+ let total = 0;
24183
+ let current = sorted[0];
24184
+ if (!current)
24185
+ return 0;
24186
+ for (let i2 = 1;i2 < sorted.length; i2++) {
24187
+ const next = sorted[i2];
24188
+ if (!next)
24189
+ continue;
24190
+ if (next.startMs <= current.endMs) {
24191
+ current = { startMs: current.startMs, endMs: Math.max(current.endMs, next.endMs) };
24192
+ } else {
24193
+ total += current.endMs - current.startMs;
24194
+ current = next;
24195
+ }
24196
+ }
24197
+ total += current.endMs - current.startMs;
24198
+ return total;
24199
+ }
24200
+ function numberOr(v2, fallback) {
24201
+ return typeof v2 === "number" && Number.isFinite(v2) ? v2 : fallback;
24202
+ }
24203
+ function isEnvironmentContextEnvelope(blocks) {
24204
+ if (blocks.length === 0)
24205
+ return false;
24206
+ for (const b2 of blocks) {
24207
+ if (b2.type !== "text")
24208
+ return false;
24209
+ const t2 = (b2.text ?? "").trim();
24210
+ if (!t2.startsWith("<environment_context>") || !t2.endsWith("</environment_context>"))
24211
+ return false;
24212
+ }
24213
+ return true;
24214
+ }
24215
+ function isObject5(v2) {
24216
+ return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
24217
+ }
24218
+ var defaultDeps6;
24219
+ var init_history2 = __esm(() => {
24220
+ defaultDeps6 = {
24221
+ sessionsDir() {
24222
+ return path11.join(homedir13(), ".codex", "sessions");
24223
+ },
24224
+ async readdir(p2) {
24225
+ try {
24226
+ return await readdir3(p2);
24227
+ } catch {
24228
+ return [];
24229
+ }
24230
+ },
24231
+ async readFile(p2) {
24232
+ return await readFile5(p2, "utf8");
24233
+ }
24234
+ };
24235
+ });
24236
+
24237
+ // src/engine/codex-local/sessions.ts
24238
+ import { open as open3, stat as stat3 } from "fs/promises";
24239
+ async function listSessionsForCwd2(cwd, deps) {
24240
+ const files = await listRolloutFiles(deps);
24241
+ const out = [];
24242
+ for (const file of files) {
24243
+ const meta = await tryReadMeta(file);
24244
+ if (!meta)
24245
+ continue;
24246
+ if (meta.cwd !== cwd)
24247
+ continue;
24248
+ out.push({
24249
+ sessionId: meta.sessionId,
24250
+ mtimeMs: meta.mtimeMs,
24251
+ firstUserMessage: meta.firstUserMessage,
24252
+ messageCount: meta.messageCount
24253
+ });
24254
+ }
24255
+ out.sort((a2, b2) => b2.mtimeMs - a2.mtimeMs);
24256
+ return out;
24257
+ }
24258
+ async function tryReadMeta(file) {
24259
+ let st;
24260
+ try {
24261
+ st = await stat3(file);
24262
+ } catch {
24263
+ return null;
24264
+ }
24265
+ let sessionId;
24266
+ let cwd;
24267
+ let firstUser = null;
24268
+ let messageCount = 0;
24269
+ const handle = await open3(file, "r").catch(() => null);
24270
+ if (!handle)
24271
+ return null;
24272
+ try {
24273
+ let buf = "";
24274
+ let lineCount = 0;
24275
+ const processLine = (line) => {
24276
+ lineCount++;
24277
+ const parsed = safeParse(line);
24278
+ if (parsed) {
24279
+ if (parsed.type === "session_meta") {
24280
+ const payload = parsed.payload;
24281
+ if (isObject6(payload)) {
24282
+ if (typeof payload.id === "string")
24283
+ sessionId = payload.id;
24284
+ if (typeof payload.cwd === "string")
24285
+ cwd = payload.cwd;
24286
+ }
24287
+ } else if (parsed.type === "response_item" && isObject6(parsed.payload)) {
24288
+ const p2 = parsed.payload;
24289
+ if (p2.type === "message") {
24290
+ messageCount++;
24291
+ if (!firstUser && p2.role === "user") {
24292
+ firstUser = extractText(p2.content)?.slice(0, PREVIEW_CHAR_CAP) ?? null;
24293
+ }
24294
+ }
24295
+ }
24296
+ }
24297
+ return lineCount >= PREVIEW_HEAD_LINES;
24298
+ };
24299
+ const reader = handle.createReadStream({ encoding: "utf8" });
24300
+ outer:
24301
+ for await (const chunk of reader) {
24302
+ buf += chunk;
24303
+ let nl = buf.indexOf(`
24304
+ `);
24305
+ while (nl !== -1) {
24306
+ const line = buf.slice(0, nl);
24307
+ buf = buf.slice(nl + 1);
24308
+ nl = buf.indexOf(`
24309
+ `);
24310
+ if (processLine(line))
24311
+ break outer;
24312
+ }
24313
+ }
24314
+ if (lineCount < PREVIEW_HEAD_LINES && buf.trim())
24315
+ processLine(buf);
24316
+ } finally {
24317
+ await handle.close().catch(() => {});
24318
+ }
24319
+ if (!sessionId || !cwd)
24320
+ return null;
24321
+ return {
24322
+ sessionId,
24323
+ cwd,
24324
+ mtimeMs: st.mtimeMs,
24325
+ firstUserMessage: firstUser,
24326
+ messageCount
24327
+ };
24328
+ }
24329
+ function safeParse(line) {
24330
+ const t2 = line.trim();
24331
+ if (!t2)
24332
+ return null;
24333
+ try {
24334
+ const v2 = JSON.parse(t2);
24335
+ return isObject6(v2) ? v2 : null;
24336
+ } catch {
24337
+ return null;
24338
+ }
24339
+ }
24340
+ function extractText(content) {
24341
+ if (typeof content === "string")
24342
+ return content;
24343
+ if (!Array.isArray(content))
24344
+ return null;
24345
+ for (const item of content) {
24346
+ if (typeof item === "string" && item.length > 0)
24347
+ return item;
24348
+ if (isObject6(item)) {
24349
+ const t2 = item.type;
24350
+ if ((t2 === "input_text" || t2 === "output_text" || t2 === "text") && typeof item.text === "string") {
24351
+ return item.text;
24352
+ }
24353
+ }
24354
+ }
24355
+ return null;
24356
+ }
24357
+ function isObject6(v2) {
24358
+ return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
24359
+ }
24360
+ var PREVIEW_HEAD_LINES = 40, PREVIEW_CHAR_CAP = 200;
24361
+ var init_sessions2 = __esm(() => {
24362
+ init_history2();
24363
+ });
24364
+
24365
+ // src/engine/codex-local/spawn.ts
24366
+ import { spawn as spawn7 } from "child_process";
24367
+ function spawnCodexProcess(opts) {
24368
+ const args2 = buildArgs2(opts);
24369
+ const proc = spawn7(opts.binaryPath, args2, {
24370
+ cwd: opts.cwd,
24371
+ env: { ...process.env, ...opts.env ?? {} },
24372
+ stdio: ["pipe", "pipe", "pipe"]
24373
+ });
24374
+ try {
24375
+ proc.stdin.end();
24376
+ } catch {}
24377
+ return {
24378
+ proc,
24379
+ stdout: proc.stdout,
24380
+ stderr: proc.stderr,
24381
+ args: args2
24382
+ };
24383
+ }
24384
+ function buildArgs2(opts) {
24385
+ const isResume = !!opts.resumeSessionId;
24386
+ const args2 = ["exec"];
24387
+ if (isResume) {
24388
+ args2.push("resume", opts.resumeSessionId);
24389
+ }
24390
+ args2.push("--json", "--skip-git-repo-check");
24391
+ if (opts.model) {
24392
+ args2.push("-m", opts.model);
24393
+ }
24394
+ if (!isResume) {
24395
+ args2.push("-C", opts.cwd);
24396
+ if (opts.permissionMode === "plan")
24397
+ args2.push("-s", "read-only");
24398
+ }
24399
+ if (opts.permissionMode !== "plan") {
24400
+ args2.push("--dangerously-bypass-approvals-and-sandbox");
22772
24401
  }
22773
24402
  if (opts.extraArgs && opts.extraArgs.length > 0) {
22774
24403
  args2.push(...opts.extraArgs);
22775
24404
  }
24405
+ args2.push(opts.prompt);
22776
24406
  return args2;
22777
24407
  }
22778
- var init_spawn = () => {};
24408
+ var init_spawn2 = () => {};
22779
24409
 
22780
- // src/engine/claude-code-local/stream.ts
22781
- async function* parseStreamJson(lines, opts = {}) {
24410
+ // src/engine/codex-local/stream.ts
24411
+ async function* parseStreamJson2(lines, opts = {}) {
22782
24412
  let sessionIdEmitted = false;
22783
24413
  const toolNameById = new Map;
24414
+ const startedByItemId = new Set;
22784
24415
  for await (const rawLine of lines) {
22785
24416
  const line = rawLine.trim();
22786
24417
  if (!line)
@@ -22789,89 +24420,82 @@ async function* parseStreamJson(lines, opts = {}) {
22789
24420
  try {
22790
24421
  msg = JSON.parse(line);
22791
24422
  } catch (err) {
22792
- yield { type: "error", message: `stream-json parse failed: ${stringifyErr(err)}` };
24423
+ yield { type: "error", message: `codex stream-json parse failed: ${stringifyErr2(err)}` };
22793
24424
  continue;
22794
24425
  }
22795
- if (!isObject3(msg))
24426
+ if (!isObject7(msg))
22796
24427
  continue;
22797
24428
  const type = typeof msg.type === "string" ? msg.type : undefined;
22798
24429
  if (!type)
22799
24430
  continue;
22800
- if ("parent_tool_use_id" in msg && msg.parent_tool_use_id != null)
22801
- continue;
22802
- if (type === "system") {
22803
- const subtype = typeof msg.subtype === "string" ? msg.subtype : undefined;
22804
- if (subtype === "init" && !sessionIdEmitted) {
22805
- const sid = typeof msg.session_id === "string" ? msg.session_id : undefined;
22806
- if (sid) {
22807
- sessionIdEmitted = true;
22808
- opts.onSessionId?.(sid);
22809
- }
24431
+ if (type === "thread.started") {
24432
+ const sid = typeof msg.thread_id === "string" ? msg.thread_id : undefined;
24433
+ if (sid && !sessionIdEmitted) {
24434
+ sessionIdEmitted = true;
24435
+ opts.onSessionId?.(sid);
22810
24436
  }
22811
24437
  continue;
22812
24438
  }
22813
- if (type === "assistant") {
22814
- const content = extractContentBlocks(msg);
22815
- for (const block of content) {
22816
- if (!isObject3(block))
22817
- continue;
22818
- const blockType = typeof block.type === "string" ? block.type : undefined;
22819
- if (blockType === "text") {
22820
- const text = typeof block.text === "string" ? block.text : "";
22821
- if (text)
22822
- yield { type: "assistant.delta", text };
22823
- } else if (blockType === "tool_use") {
22824
- const name = typeof block.name === "string" ? block.name : "tool";
22825
- const id = typeof block.id === "string" ? block.id : undefined;
22826
- if (id)
22827
- toolNameById.set(id, name);
22828
- const input = "input" in block ? block.input : undefined;
22829
- yield { type: "tool.start", name, input };
22830
- }
22831
- }
24439
+ if (type === "turn.started")
22832
24440
  continue;
22833
- }
22834
- if (type === "user") {
22835
- const content = extractContentBlocks(msg);
22836
- for (const block of content) {
22837
- if (!isObject3(block))
24441
+ if (type === "item.started" || type === "item.completed") {
24442
+ const item = isObject7(msg.item) ? msg.item : undefined;
24443
+ if (!item)
24444
+ continue;
24445
+ const itemId = typeof item.id === "string" ? item.id : undefined;
24446
+ const itemType = typeof item.type === "string" ? item.type : "tool";
24447
+ if (itemType === "agent_message") {
24448
+ if (type !== "item.completed")
22838
24449
  continue;
22839
- const blockType = typeof block.type === "string" ? block.type : undefined;
22840
- if (blockType === "tool_result") {
22841
- const id = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
22842
- const name = id && toolNameById.get(id) || "tool";
22843
- const output = "content" in block ? block.content : undefined;
22844
- yield { type: "tool.result", name, output };
22845
- }
24450
+ const text = typeof item.text === "string" ? item.text : "";
24451
+ if (text)
24452
+ yield { type: "assistant.delta", text };
24453
+ continue;
24454
+ }
24455
+ if (itemId) {
24456
+ toolNameById.set(itemId, itemType);
24457
+ }
24458
+ if (type === "item.started") {
24459
+ if (itemId)
24460
+ startedByItemId.add(itemId);
24461
+ const input = stripIdAndType(item);
24462
+ yield { type: "tool.start", name: itemType, input };
24463
+ continue;
22846
24464
  }
24465
+ if (itemId && !startedByItemId.has(itemId)) {
24466
+ const input = stripIdAndType(item);
24467
+ yield { type: "tool.start", name: itemType, input };
24468
+ }
24469
+ if (itemId)
24470
+ startedByItemId.delete(itemId);
24471
+ const output = stripIdAndType(item);
24472
+ yield { type: "tool.result", name: itemType, output };
22847
24473
  continue;
22848
24474
  }
22849
- if (type === "result") {
22850
- const usage = isObject3(msg.usage) ? msg.usage : undefined;
24475
+ if (type === "turn.completed") {
24476
+ const usage = isObject7(msg.usage) ? msg.usage : undefined;
22851
24477
  if (usage) {
22852
- const inTok = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
22853
- const outTok = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
22854
- const cacheRead = typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : undefined;
22855
- const cacheCreate = typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : undefined;
24478
+ const inTok = numberOr2(usage.input_tokens, 0);
24479
+ const outTok = numberOr2(usage.output_tokens, 0) + numberOr2(usage.reasoning_output_tokens, 0);
24480
+ const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
22856
24481
  yield {
22857
24482
  type: "usage",
22858
24483
  input_tokens: inTok,
22859
24484
  output_tokens: outTok,
22860
- ...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {},
22861
- ...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
24485
+ ...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
22862
24486
  };
22863
24487
  }
22864
- const subtype = typeof msg.subtype === "string" ? msg.subtype : "success";
22865
- if (subtype === "success") {
22866
- yield { type: "done" };
22867
- } else {
22868
- yield { type: "error", message: `claude session ended: ${subtype}` };
22869
- }
24488
+ yield { type: "done" };
24489
+ return;
24490
+ }
24491
+ if (type === "error") {
24492
+ const message = typeof msg.message === "string" ? msg.message : "codex emitted an error";
24493
+ yield { type: "error", message };
22870
24494
  return;
22871
24495
  }
22872
24496
  }
22873
24497
  }
22874
- async function* readLines(stream) {
24498
+ async function* readLines2(stream) {
22875
24499
  let buf = "";
22876
24500
  for await (const chunk of stream) {
22877
24501
  const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
@@ -22888,18 +24512,17 @@ async function* readLines(stream) {
22888
24512
  if (buf.length > 0)
22889
24513
  yield buf;
22890
24514
  }
22891
- function isObject3(v2) {
24515
+ function isObject7(v2) {
22892
24516
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
22893
24517
  }
22894
- function extractContentBlocks(msg) {
22895
- if (Array.isArray(msg.content))
22896
- return msg.content;
22897
- const inner = msg.message;
22898
- if (isObject3(inner) && Array.isArray(inner.content))
22899
- return inner.content;
22900
- return [];
24518
+ function numberOr2(v2, fallback) {
24519
+ return typeof v2 === "number" && Number.isFinite(v2) ? v2 : fallback;
22901
24520
  }
22902
- function stringifyErr(err) {
24521
+ function stripIdAndType(item) {
24522
+ const { id: _id, type: _type, ...rest } = item;
24523
+ return rest;
24524
+ }
24525
+ function stringifyErr2(err) {
22903
24526
  if (err instanceof Error)
22904
24527
  return err.message;
22905
24528
  try {
@@ -22909,14 +24532,16 @@ function stringifyErr(err) {
22909
24532
  }
22910
24533
  }
22911
24534
 
22912
- // src/engine/claude-code-local/index.ts
22913
- class ClaudeCodeLocal {
24535
+ // src/engine/codex-local/index.ts
24536
+ class CodexLocal {
24537
+ identity = codexIdentity;
24538
+ capabilities = codexCapabilities;
22914
24539
  registry = new SessionRegistry;
22915
24540
  running = new Map;
22916
24541
  binaryPathResolver;
22917
24542
  stopGraceMs;
22918
24543
  constructor(opts = {}) {
22919
- this.binaryPathResolver = opts.binaryPathResolver ?? findClaudeBinary;
24544
+ this.binaryPathResolver = opts.binaryPathResolver ?? findCodexBinary;
22920
24545
  this.stopGraceMs = opts.stopGraceMs ?? 5000;
22921
24546
  }
22922
24547
  async spawn(cwd, prompt, opts) {
@@ -22953,41 +24578,32 @@ class ClaudeCodeLocal {
22953
24578
  };
22954
24579
  }
22955
24580
  async readHistory(sessionId) {
22956
- return readHistory(sessionId);
24581
+ return readHistoryWithMetrics(sessionId);
22957
24582
  }
22958
24583
  async deleteHistory(sessionId) {
22959
- return deleteHistory(sessionId);
24584
+ return deleteHistory2(sessionId);
22960
24585
  }
22961
24586
  async listSessions(cwd) {
22962
- return listSessionsForCwd(cwd);
24587
+ return listSessionsForCwd2(cwd);
22963
24588
  }
22964
24589
  async stop(handle) {
22965
24590
  const sid = handle.sessionId;
22966
- const session = this.running.get(sid);
22967
- const shouldRescue = !!session && !session.completedNaturally && session.prompt.trim().length > 0;
22968
- const rescuePrompt = session?.prompt ?? "";
22969
- const rescueCwd = session?.cwd ?? handle.cwd;
22970
24591
  await this.registry.kill(sid, this.stopGraceMs);
24592
+ const session = this.running.get(sid);
22971
24593
  if (session) {
22972
24594
  session.closed = true;
22973
24595
  this.notify(session);
22974
24596
  this.running.delete(sid);
22975
24597
  }
22976
- if (shouldRescue) {
22977
- try {
22978
- await appendInterruptedUserPrompt(sid, rescueCwd, rescuePrompt);
22979
- } catch {}
22980
- }
22981
24598
  }
22982
24599
  async start(args2) {
22983
24600
  const binaryPath = await this.binaryPathResolver();
22984
- const cliPermissionMode = args2.opts?.permissionMode === "plan" ? "plan" : "bypassPermissions";
22985
- const spawned = spawnClaudeProcess({
24601
+ const spawned = spawnCodexProcess({
22986
24602
  binaryPath,
22987
24603
  cwd: args2.cwd,
22988
24604
  prompt: args2.prompt,
22989
24605
  model: args2.opts?.model,
22990
- permissionMode: cliPermissionMode,
24606
+ permissionMode: args2.opts?.permissionMode,
22991
24607
  env: args2.opts?.env,
22992
24608
  resumeSessionId: args2.resumeSessionId
22993
24609
  });
@@ -23011,8 +24627,7 @@ class ClaudeCodeLocal {
23011
24627
  queue,
23012
24628
  waiters: [],
23013
24629
  closed: false,
23014
- completedNaturally: false,
23015
- prompt: args2.prompt
24630
+ spawnedAtIso: new Date().toISOString()
23016
24631
  };
23017
24632
  this.running.set(sessionId, session);
23018
24633
  this.registry.register({
@@ -23036,22 +24651,20 @@ class ClaudeCodeLocal {
23036
24651
  }
23037
24652
  }
23038
24653
  (async () => {
23039
- const events = parseStreamJson(readLines(spawned.stdout), {
24654
+ const events = parseStreamJson2(readLines2(spawned.stdout), {
23040
24655
  onSessionId: (sid) => bind(sid)
23041
24656
  });
23042
24657
  try {
23043
24658
  for await (const ev of events) {
23044
- queue.push(ev);
23045
- if (ev.type === "done" && session) {
23046
- session.completedNaturally = true;
23047
- }
24659
+ const enriched = enrichUsageEvent2(ev, session?.spawnedAtIso);
24660
+ queue.push(enriched);
23048
24661
  if (session)
23049
24662
  this.notify(session);
23050
24663
  }
23051
24664
  } catch (err) {
23052
24665
  const ev = {
23053
24666
  type: "error",
23054
- message: `parser failure: ${err instanceof Error ? err.message : String(err)}`
24667
+ message: `codex parser failure: ${err instanceof Error ? err.message : String(err)}`
23055
24668
  };
23056
24669
  queue.push(ev);
23057
24670
  if (session)
@@ -23061,20 +24674,21 @@ class ClaudeCodeLocal {
23061
24674
  session.closed = true;
23062
24675
  this.notify(session);
23063
24676
  this.registry.unregister(session.sessionId);
24677
+ this.running.delete(session.sessionId);
23064
24678
  }
23065
24679
  if (!bound) {
23066
- rejectHandle(new Error("claude exited without emitting a session id"));
24680
+ rejectHandle(new Error("codex exited without emitting a session id"));
23067
24681
  }
23068
24682
  }
23069
24683
  })();
23070
- drainStream(spawned.stderr);
24684
+ drainStream2(spawned.stderr);
23071
24685
  spawned.proc.once("error", (err) => {
23072
24686
  if (!bound)
23073
24687
  rejectHandle(err);
23074
24688
  });
23075
24689
  spawned.proc.once("exit", () => {
23076
24690
  if (!bound) {
23077
- rejectHandle(new Error("claude exited before session id was captured"));
24691
+ rejectHandle(new Error("codex exited before session id was captured"));
23078
24692
  }
23079
24693
  });
23080
24694
  return handlePromise;
@@ -23086,16 +24700,22 @@ class ClaudeCodeLocal {
23086
24700
  w2();
23087
24701
  }
23088
24702
  }
23089
- function drainStream(stream) {
24703
+ function drainStream2(stream) {
23090
24704
  const s2 = stream;
23091
24705
  s2.on("data", () => {});
23092
24706
  s2.on("error", () => {});
23093
24707
  }
23094
- var init_claude_code_local = __esm(() => {
23095
- init_binary();
23096
- init_history();
23097
- init_sessions();
23098
- init_spawn();
24708
+ function enrichUsageEvent2(ev, startedAtIso) {
24709
+ if (ev.type !== "usage")
24710
+ return ev;
24711
+ return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
24712
+ }
24713
+ var init_codex_local = __esm(() => {
24714
+ init_binary2();
24715
+ init_capabilities2();
24716
+ init_history2();
24717
+ init_sessions2();
24718
+ init_spawn2();
23099
24719
  });
23100
24720
 
23101
24721
  // test/behavior/fake-engine.ts
@@ -23103,8 +24723,17 @@ var exports_fake_engine = {};
23103
24723
  __export(exports_fake_engine, {
23104
24724
  FakeAIEngine: () => FakeAIEngine
23105
24725
  });
24726
+ function firstUserText(blocks) {
24727
+ for (const b2 of blocks) {
24728
+ if (b2.type === "text")
24729
+ return b2.text;
24730
+ }
24731
+ return null;
24732
+ }
23106
24733
 
23107
24734
  class FakeAIEngine {
24735
+ identity = claudeIdentity;
24736
+ capabilities = claudeCapabilities;
23108
24737
  nextId = 1;
23109
24738
  queues = new Map;
23110
24739
  historyBySession = new Map;
@@ -23162,7 +24791,9 @@ class FakeAIEngine {
23162
24791
  };
23163
24792
  }
23164
24793
  async readHistory(sessionId) {
23165
- return this.historyBySession.get(sessionId) ?? [];
24794
+ const messages = this.historyBySession.get(sessionId) ?? [];
24795
+ const usageMetrics = deriveSessionUsageMetrics(messages);
24796
+ return { messages, ...usageMetrics ? { usageMetrics } : {} };
23166
24797
  }
23167
24798
  async deleteHistory(sessionId) {
23168
24799
  this.historyBySession.delete(sessionId);
@@ -23171,7 +24802,7 @@ class FakeAIEngine {
23171
24802
  const out = [];
23172
24803
  for (const [sessionId, msgs] of this.historyBySession) {
23173
24804
  const firstUser = msgs.find((m2) => m2.role === "user");
23174
- const preview = firstUser && typeof firstUser.content === "string" ? firstUser.content : null;
24805
+ const preview = firstUser ? firstUserText(firstUser.blocks) : null;
23175
24806
  out.push({
23176
24807
  sessionId,
23177
24808
  mtimeMs: 0,
@@ -23214,6 +24845,10 @@ class FakeAIEngine {
23214
24845
  w2();
23215
24846
  }
23216
24847
  }
24848
+ var init_fake_engine = __esm(() => {
24849
+ init_capabilities();
24850
+ init_capabilities();
24851
+ });
23217
24852
 
23218
24853
  // src/engine/dev-fake.ts
23219
24854
  var exports_dev_fake = {};
@@ -23222,6 +24857,8 @@ __export(exports_dev_fake, {
23222
24857
  });
23223
24858
 
23224
24859
  class DevAIEngine {
24860
+ identity = claudeIdentity;
24861
+ capabilities = claudeCapabilities;
23225
24862
  inner = new FakeAIEngine;
23226
24863
  tickMs;
23227
24864
  constructor(opts = {}) {
@@ -23354,12 +24991,15 @@ function buildPlanModeReply(prompt) {
23354
24991
  { type: "done" }
23355
24992
  ];
23356
24993
  }
23357
- var init_dev_fake = () => {};
24994
+ var init_dev_fake = __esm(() => {
24995
+ init_fake_engine();
24996
+ init_capabilities();
24997
+ });
23358
24998
 
23359
24999
  // src/tui/engine-bootstrap.ts
23360
25000
  async function buildEngine() {
23361
25001
  if (process.env.KOBE_TEST_ENGINE === "fake") {
23362
- const { FakeAIEngine: FakeAIEngine2 } = await Promise.resolve().then(() => exports_fake_engine);
25002
+ const { FakeAIEngine: FakeAIEngine2 } = await Promise.resolve().then(() => (init_fake_engine(), exports_fake_engine));
23363
25003
  const fake = new FakeAIEngine2;
23364
25004
  await mountFakeEngineServer(fake);
23365
25005
  return fake;
@@ -23370,6 +25010,16 @@ async function buildEngine() {
23370
25010
  }
23371
25011
  return new ClaudeCodeLocal;
23372
25012
  }
25013
+ async function buildEngines() {
25014
+ if (process.env.KOBE_TEST_ENGINE) {
25015
+ const engine3 = await buildEngine();
25016
+ return { [engine3.capabilities.vendorId]: engine3 };
25017
+ }
25018
+ return {
25019
+ claude: new ClaudeCodeLocal,
25020
+ codex: new CodexLocal
25021
+ };
25022
+ }
23373
25023
  async function mountFakeEngineServer(fake) {
23374
25024
  const portStr = process.env.KOBE_TEST_FAKE_PORT;
23375
25025
  if (!portStr)
@@ -23457,6 +25107,7 @@ async function mountFakeEngineServer(fake) {
23457
25107
  }
23458
25108
  var init_engine_bootstrap = __esm(() => {
23459
25109
  init_claude_code_local();
25110
+ init_codex_local();
23460
25111
  });
23461
25112
 
23462
25113
  // src/tui/lib/format-plan-usage.ts
@@ -23682,17 +25333,17 @@ function validateRepoPath(repo) {
23682
25333
  const trimmed = repo.trim();
23683
25334
  if (!trimmed)
23684
25335
  return "repo path is required";
23685
- let stat3;
25336
+ let stat4;
23686
25337
  try {
23687
- stat3 = fs5.statSync(trimmed);
25338
+ stat4 = fs5.statSync(trimmed);
23688
25339
  } catch {
23689
25340
  return `path does not exist: ${trimmed}`;
23690
25341
  }
23691
- if (!stat3.isDirectory())
25342
+ if (!stat4.isDirectory())
23692
25343
  return `not a directory: ${trimmed}`;
23693
25344
  try {
23694
- const { spawnSync: spawnSync8 } = __require("child_process");
23695
- const out = spawnSync8("git", ["rev-parse", "--git-dir"], {
25345
+ const { spawnSync: spawnSync9 } = __require("child_process");
25346
+ const out = spawnSync9("git", ["rev-parse", "--git-dir"], {
23696
25347
  cwd: trimmed,
23697
25348
  encoding: "utf-8",
23698
25349
  timeout: 2000,
@@ -23709,8 +25360,8 @@ function getCurrentBranch(repo) {
23709
25360
  if (!repo)
23710
25361
  return null;
23711
25362
  try {
23712
- const { spawnSync: spawnSync8 } = __require("child_process");
23713
- const out = spawnSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
25363
+ const { spawnSync: spawnSync9 } = __require("child_process");
25364
+ const out = spawnSync9("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
23714
25365
  cwd: repo,
23715
25366
  encoding: "utf-8",
23716
25367
  timeout: 2000,
@@ -23730,8 +25381,8 @@ function listLocalBranches(repo) {
23730
25381
  if (!repo)
23731
25382
  return [];
23732
25383
  try {
23733
- const { spawnSync: spawnSync8 } = __require("child_process");
23734
- const out = spawnSync8("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
25384
+ const { spawnSync: spawnSync9 } = __require("child_process");
25385
+ const out = spawnSync9("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
23735
25386
  cwd: repo,
23736
25387
  encoding: "utf-8",
23737
25388
  timeout: 2000
@@ -24850,8 +26501,8 @@ var init_resume_dialog = __esm(() => {
24850
26501
  });
24851
26502
 
24852
26503
  // src/tui/panes/chat/composer/clipboard-image.ts
24853
- import { spawnSync as spawnSync8 } from "child_process";
24854
- import { statSync as statSync4 } from "fs";
26504
+ import { spawnSync as spawnSync9 } from "child_process";
26505
+ import { statSync as statSync6 } from "fs";
24855
26506
  function clipboardImageSupported() {
24856
26507
  return process.platform === "darwin";
24857
26508
  }
@@ -24875,14 +26526,14 @@ function readClipboardImageMacOS(destPath) {
24875
26526
  "end try"
24876
26527
  ].join(`
24877
26528
  `);
24878
- const result = spawnSync8("osascript", ["-e", script], {
26529
+ const result = spawnSync9("osascript", ["-e", script], {
24879
26530
  timeout: 5000,
24880
26531
  stdio: ["ignore", "ignore", "ignore"]
24881
26532
  });
24882
26533
  if (result.status !== 0)
24883
26534
  return null;
24884
26535
  try {
24885
- const st = statSync4(destPath);
26536
+ const st = statSync6(destPath);
24886
26537
  if (st.size === 0)
24887
26538
  return null;
24888
26539
  } catch {
@@ -24916,16 +26567,16 @@ function getHistory(key) {
24916
26567
  return ring.slice();
24917
26568
  }
24918
26569
  var HISTORY_LIMIT = 200, STORE;
24919
- var init_history2 = __esm(() => {
26570
+ var init_history3 = __esm(() => {
24920
26571
  STORE = new Map;
24921
26572
  });
24922
26573
 
24923
26574
  // src/tui/panes/chat/composer/image-paste.ts
24924
26575
  import { randomUUID as randomUUID2 } from "crypto";
24925
26576
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
24926
- import { join as join13 } from "path";
26577
+ import { join as join14 } from "path";
24927
26578
  function pastedImagesDir() {
24928
- return join13(kobeStateDir(), "pasted-images");
26579
+ return join14(kobeStateDir(), "pasted-images");
24929
26580
  }
24930
26581
 
24931
26582
  class ImagePasteRegistry {
@@ -25005,7 +26656,7 @@ function mimeTypeToExt(mimeType) {
25005
26656
  return ".png";
25006
26657
  }
25007
26658
  function mintPath(ext) {
25008
- return join13(pastedImagesDir(), `${randomUUID2()}${ext}`);
26659
+ return join14(pastedImagesDir(), `${randomUUID2()}${ext}`);
25009
26660
  }
25010
26661
  var IMAGE_TOKEN_RE;
25011
26662
  var init_image_paste = __esm(() => {
@@ -25026,10 +26677,10 @@ var init_keybindings2 = __esm(() => {
25026
26677
 
25027
26678
  // src/tui/panes/chat/composer/mention.ts
25028
26679
  async function getWorktreeFiles(worktreePath) {
25029
- const cached2 = fileListCache.get(worktreePath);
26680
+ const cached3 = fileListCache.get(worktreePath);
25030
26681
  const now = Date.now();
25031
- if (cached2 && now - cached2.ts < CACHE_TTL_MS)
25032
- return cached2.files;
26682
+ if (cached3 && now - cached3.ts < CACHE_TTL_MS)
26683
+ return cached3.files;
25033
26684
  try {
25034
26685
  const files = await listFiles(worktreePath);
25035
26686
  fileListCache.set(worktreePath, { files, ts: now });
@@ -25063,25 +26714,25 @@ function findMentionContext(text, cursor) {
25063
26714
  }
25064
26715
  return { atPos, query: text.slice(atPos + 1, cursor) };
25065
26716
  }
25066
- function formatDisplayPath(path9) {
25067
- if (path9.startsWith("packages/"))
25068
- return path9.slice("packages/".length);
25069
- return path9;
26717
+ function formatDisplayPath(path12) {
26718
+ if (path12.startsWith("packages/"))
26719
+ return path12.slice("packages/".length);
26720
+ return path12;
25070
26721
  }
25071
26722
  function filterMentionMatches(files, query, limit) {
25072
26723
  if (files.length === 0 || limit <= 0)
25073
26724
  return [];
25074
26725
  const q2 = query.toLowerCase();
25075
26726
  if (q2.length === 0) {
25076
- return files.slice(0, limit).map((path9) => ({
25077
- path: path9,
25078
- displayPath: formatDisplayPath(path9),
26727
+ return files.slice(0, limit).map((path12) => ({
26728
+ path: path12,
26729
+ displayPath: formatDisplayPath(path12),
25079
26730
  score: 0
25080
26731
  }));
25081
26732
  }
25082
26733
  const matches = [];
25083
- for (const path9 of files) {
25084
- const lower = path9.toLowerCase();
26734
+ for (const path12 of files) {
26735
+ const lower = path12.toLowerCase();
25085
26736
  const slash = lower.lastIndexOf("/");
25086
26737
  const filename = slash >= 0 ? lower.slice(slash + 1) : lower;
25087
26738
  let score = 0;
@@ -25095,8 +26746,8 @@ function filterMentionMatches(files, query, limit) {
25095
26746
  score = 40;
25096
26747
  else
25097
26748
  continue;
25098
- score -= path9.length * 0.5;
25099
- matches.push({ path: path9, displayPath: formatDisplayPath(path9), score });
26749
+ score -= path12.length * 0.5;
26750
+ matches.push({ path: path12, displayPath: formatDisplayPath(path12), score });
25100
26751
  }
25101
26752
  matches.sort((a2, b2) => b2.score - a2.score);
25102
26753
  return matches.slice(0, limit);
@@ -25114,7 +26765,7 @@ function resolvePlaceholder(opts) {
25114
26765
  return opts.noTaskMessage ?? "(no task \u2014 press n to create)";
25115
26766
  if (opts.isStreaming)
25116
26767
  return "(streaming \u2014 enter to queue, ctrl+enter to steer)";
25117
- return "Ask Claude\u2026";
26768
+ return opts.inputPlaceholder ?? "Ask Claude\u2026";
25118
26769
  }
25119
26770
  function Composer(props) {
25120
26771
  const {
@@ -25243,7 +26894,7 @@ function Composer(props) {
25243
26894
  total
25244
26895
  };
25245
26896
  });
25246
- function insertMentionSelection(path9) {
26897
+ function insertMentionSelection(path12) {
25247
26898
  const ref = textareaRef;
25248
26899
  const ctx4 = mentionContext();
25249
26900
  if (!ref || !ctx4)
@@ -25251,7 +26902,7 @@ function Composer(props) {
25251
26902
  const cursor = ref.cursorOffset;
25252
26903
  ref.setSelection(ctx4.atPos, cursor);
25253
26904
  ref.deleteSelection();
25254
- const inserted = `@${path9} `;
26905
+ const inserted = `@${path12} `;
25255
26906
  ref.insertText(inserted);
25256
26907
  setMentionDismissedAt(null);
25257
26908
  setMentionCursor(0);
@@ -25961,7 +27612,8 @@ function Composer(props) {
25961
27612
  var _v$0 = resolvePlaceholder({
25962
27613
  isStreaming: props.isStreaming,
25963
27614
  hasTask: props.hasTask,
25964
- noTaskMessage: props.noTaskMessage
27615
+ noTaskMessage: props.noTaskMessage,
27616
+ inputPlaceholder: props.inputPlaceholder?.()
25965
27617
  }), _v$1 = theme.textMuted, _v$10 = theme.text, _v$11 = theme.backgroundElement, _v$12 = theme.backgroundElement;
25966
27618
  _v$0 !== _p$.e && (_p$.e = setProp(_el$24, "placeholder", _v$0, _p$.e));
25967
27619
  _v$1 !== _p$.t && (_p$.t = setProp(_el$24, "placeholderColor", _v$1, _p$.t));
@@ -26082,7 +27734,7 @@ var init_Composer = __esm(() => {
26082
27734
  init_focus();
26083
27735
  init_theme();
26084
27736
  init_clipboard_image();
26085
- init_history2();
27737
+ init_history3();
26086
27738
  init_image_paste();
26087
27739
  init_keybindings2();
26088
27740
  init_mention();
@@ -26611,7 +28263,7 @@ function readWriteInput(input) {
26611
28263
  };
26612
28264
  }
26613
28265
  function makeHeader(filePath, verb, adds, removes) {
26614
- const path9 = filePath || "(unknown file)";
28266
+ const path12 = filePath || "(unknown file)";
26615
28267
  const parts = [];
26616
28268
  if (adds > 0) {
26617
28269
  parts.push(`Added ${adds} ${adds === 1 ? "line" : "lines"}`);
@@ -26621,11 +28273,11 @@ function makeHeader(filePath, verb, adds, removes) {
26621
28273
  parts.push(`${word} ${removes} ${removes === 1 ? "line" : "lines"}`);
26622
28274
  }
26623
28275
  if (parts.length === 0)
26624
- return `${verb} ${path9}`;
26625
- return `${verb} ${path9} \xB7 ${parts.join(", ")}`;
28276
+ return `${verb} ${path12}`;
28277
+ return `${verb} ${path12} \xB7 ${parts.join(", ")}`;
26626
28278
  }
26627
28279
  function makeMultiEditHeader(filePath, count, adds, removes) {
26628
- const path9 = filePath || "(unknown file)";
28280
+ const path12 = filePath || "(unknown file)";
26629
28281
  const segments = [];
26630
28282
  if (count > 0) {
26631
28283
  segments.push(`${count} ${count === 1 ? "edit" : "edits"}`);
@@ -26641,8 +28293,8 @@ function makeMultiEditHeader(filePath, count, adds, removes) {
26641
28293
  if (lineParts.length > 0)
26642
28294
  segments.push(lineParts.join(", "));
26643
28295
  if (segments.length === 0)
26644
- return `Edited ${path9}`;
26645
- return `Edited ${path9} \xB7 ${segments.join(" \xB7 ")}`;
28296
+ return `Edited ${path12}`;
28297
+ return `Edited ${path12} \xB7 ${segments.join(" \xB7 ")}`;
26646
28298
  }
26647
28299
  var COLLAPSED_LINE_CAP = 10;
26648
28300
 
@@ -26705,6 +28357,39 @@ function summarizeGlob(input, output, done) {
26705
28357
  return `${head} \xB7 ${lines.length} ${lines.length === 1 ? "file" : "files"}`;
26706
28358
  }
26707
28359
 
28360
+ // src/tui/panes/chat/tool-registry.ts
28361
+ function lookupToolMeta(name, vendor = "claude") {
28362
+ return registries[vendor][name] ?? DEFAULT_META;
28363
+ }
28364
+ function classifyTool(name, vendor = "claude") {
28365
+ return lookupToolMeta(name, vendor).bucket;
28366
+ }
28367
+ var DEFAULT_META, claudeRegistry, registries;
28368
+ var init_tool_registry = __esm(() => {
28369
+ DEFAULT_META = {
28370
+ bucket: "other",
28371
+ banner: "default",
28372
+ body: "default"
28373
+ };
28374
+ claudeRegistry = {
28375
+ Edit: { bucket: "other", banner: "default", body: "edit-diff" },
28376
+ Write: { bucket: "other", banner: "default", body: "edit-diff" },
28377
+ MultiEdit: { bucket: "other", banner: "default", body: "multi-edit-diff" },
28378
+ Bash: { bucket: "bash", banner: "bash", body: "bash-output" },
28379
+ BashOutput: { bucket: "bash", banner: "default", body: "default" },
28380
+ KillShell: { bucket: "bash", banner: "default", body: "default" },
28381
+ Read: { bucket: "read", banner: "read-grep-glob", body: "read-grep-glob" },
28382
+ NotebookRead: { bucket: "read", banner: "default", body: "default" },
28383
+ Grep: { bucket: "search", banner: "read-grep-glob", body: "read-grep-glob" },
28384
+ Glob: { bucket: "list", banner: "read-grep-glob", body: "read-grep-glob" },
28385
+ LS: { bucket: "list", banner: "default", body: "default" }
28386
+ };
28387
+ registries = {
28388
+ claude: claudeRegistry,
28389
+ codex: {}
28390
+ };
28391
+ });
28392
+
26708
28393
  // src/tui/panes/chat/MessageList.tsx
26709
28394
  import { TextAttributes as TextAttributes23 } from "@opentui/core";
26710
28395
  function previewToolInput(input) {
@@ -26936,12 +28621,13 @@ function ToolRow(props) {
26936
28621
  const r2 = () => props.row;
26937
28622
  const prefixGlyph = () => r2().done ? BLACK_CIRCLE : "\u273B";
26938
28623
  const prefixColor = () => r2().done ? theme.success : theme.warning;
26939
- const isDiffTool = () => r2().name === "Edit" || r2().name === "Write";
26940
- const isMultiEdit = () => r2().name === "MultiEdit";
26941
- const isBash = () => r2().name === "Bash";
26942
- const isReadGrepGlob = () => r2().name === "Read" || r2().name === "Grep" || r2().name === "Glob";
26943
- const usesCustomBanner = () => isDiffTool() || isMultiEdit() || isBash() || isReadGrepGlob();
26944
- const usesCustomBody = () => isDiffTool() || isMultiEdit() || isBash();
28624
+ const meta = () => lookupToolMeta(r2().name);
28625
+ const isDiffTool = () => meta().body === "edit-diff";
28626
+ const isMultiEdit = () => meta().body === "multi-edit-diff";
28627
+ const isBash = () => meta().banner === "bash";
28628
+ const isReadGrepGlob = () => meta().banner === "read-grep-glob";
28629
+ const usesCustomBanner = () => meta().banner !== "default" || meta().body !== "default";
28630
+ const usesCustomBody = () => meta().body === "edit-diff" || meta().body === "multi-edit-diff" || meta().body === "bash-output";
26945
28631
  const diff = () => {
26946
28632
  if (r2().name === "Edit")
26947
28633
  return formatEditDiff(r2().input);
@@ -28241,34 +29927,6 @@ function MessageList(props) {
28241
29927
  });
28242
29928
  }
28243
29929
  }), null);
28244
- insert(_el$153, createComponent2(Show, {
28245
- get when() {
28246
- return props.error;
28247
- },
28248
- get children() {
28249
- var _el$157 = createElement("box"), _el$158 = createElement("text"), _el$160 = createElement("text"), _el$161 = createTextNode(`error: `);
28250
- insertNode(_el$157, _el$158);
28251
- insertNode(_el$157, _el$160);
28252
- setProp(_el$157, "paddingTop", 1);
28253
- setProp(_el$157, "flexDirection", "row");
28254
- setProp(_el$157, "gap", 1);
28255
- insertNode(_el$158, createTextNode(`\u203B`));
28256
- insertNode(_el$160, _el$161);
28257
- insert(_el$160, () => props.error, null);
28258
- effect((_p$) => {
28259
- var _v$71 = theme.error, _v$72 = TextAttributes23.BOLD, _v$73 = theme.error;
28260
- _v$71 !== _p$.e && (_p$.e = setProp(_el$158, "fg", _v$71, _p$.e));
28261
- _v$72 !== _p$.t && (_p$.t = setProp(_el$158, "attributes", _v$72, _p$.t));
28262
- _v$73 !== _p$.a && (_p$.a = setProp(_el$160, "fg", _v$73, _p$.a));
28263
- return _p$;
28264
- }, {
28265
- e: undefined,
28266
- t: undefined,
28267
- a: undefined
28268
- });
28269
- return _el$157;
28270
- }
28271
- }), null);
28272
29930
  return _el$153;
28273
29931
  })();
28274
29932
  }
@@ -28339,17 +29997,6 @@ function groupRenderItems(messages, expandedFoldStartIndex = null) {
28339
29997
  }
28340
29998
  return items;
28341
29999
  }
28342
- function classifyTool(name) {
28343
- if (name === "Grep")
28344
- return "search";
28345
- if (name === "Read" || name === "NotebookRead")
28346
- return "read";
28347
- if (name === "Glob" || name === "LS")
28348
- return "list";
28349
- if (name === "Bash" || name === "BashOutput" || name === "KillShell")
28350
- return "bash";
28351
- return "other";
28352
- }
28353
30000
  function summarizeToolRun(c2, inFlight = false) {
28354
30001
  const verbs = inFlight ? {
28355
30002
  search: ["Searching", "searching"],
@@ -28400,29 +30047,29 @@ function ToolFoldRow(props) {
28400
30047
  const glyph = () => props.inFlight ? "\u273B" : props.expanded ? "\u25BC" : "\u25B6";
28401
30048
  const fg = () => props.inFlight ? theme.warning : theme.textMuted;
28402
30049
  return (() => {
28403
- var _el$162 = createElement("box"), _el$163 = createElement("text"), _el$164 = createElement("box"), _el$165 = createElement("text");
28404
- insertNode(_el$162, _el$163);
28405
- insertNode(_el$162, _el$164);
28406
- setProp(_el$162, "paddingTop", 1);
28407
- setProp(_el$162, "flexDirection", "row");
28408
- setProp(_el$162, "gap", 1);
28409
- setProp(_el$162, "onMouseUp", () => props.onToggle());
28410
- insert(_el$163, glyph);
28411
- insertNode(_el$164, _el$165);
28412
- setProp(_el$164, "flexGrow", 1);
28413
- insert(_el$165, () => props.summary);
30050
+ var _el$157 = createElement("box"), _el$158 = createElement("text"), _el$159 = createElement("box"), _el$160 = createElement("text");
30051
+ insertNode(_el$157, _el$158);
30052
+ insertNode(_el$157, _el$159);
30053
+ setProp(_el$157, "paddingTop", 1);
30054
+ setProp(_el$157, "flexDirection", "row");
30055
+ setProp(_el$157, "gap", 1);
30056
+ setProp(_el$157, "onMouseUp", () => props.onToggle());
30057
+ insert(_el$158, glyph);
30058
+ insertNode(_el$159, _el$160);
30059
+ setProp(_el$159, "flexGrow", 1);
30060
+ insert(_el$160, () => props.summary);
28414
30061
  effect((_p$) => {
28415
- var _v$74 = fg(), _v$75 = TextAttributes23.DIM, _v$76 = theme.textMuted;
28416
- _v$74 !== _p$.e && (_p$.e = setProp(_el$163, "fg", _v$74, _p$.e));
28417
- _v$75 !== _p$.t && (_p$.t = setProp(_el$163, "attributes", _v$75, _p$.t));
28418
- _v$76 !== _p$.a && (_p$.a = setProp(_el$165, "fg", _v$76, _p$.a));
30062
+ var _v$71 = fg(), _v$72 = TextAttributes23.DIM, _v$73 = theme.textMuted;
30063
+ _v$71 !== _p$.e && (_p$.e = setProp(_el$158, "fg", _v$71, _p$.e));
30064
+ _v$72 !== _p$.t && (_p$.t = setProp(_el$158, "attributes", _v$72, _p$.t));
30065
+ _v$73 !== _p$.a && (_p$.a = setProp(_el$160, "fg", _v$73, _p$.a));
28419
30066
  return _p$;
28420
30067
  }, {
28421
30068
  e: undefined,
28422
30069
  t: undefined,
28423
30070
  a: undefined
28424
30071
  });
28425
- return _el$162;
30072
+ return _el$157;
28426
30073
  })();
28427
30074
  }
28428
30075
  var BLACK_CIRCLE, OTHER_SENTINEL = "__kobe_other__", TOOL_FOLD_THRESHOLD = 3;
@@ -28441,41 +30088,10 @@ var init_MessageList = __esm(() => {
28441
30088
  init_keymap();
28442
30089
  init_Markdown();
28443
30090
  init_image_paste();
30091
+ init_tool_registry();
28444
30092
  BLACK_CIRCLE = process.platform === "darwin" ? "\u23FA" : "\u25CF";
28445
30093
  });
28446
30094
 
28447
- // src/tui/panes/chat/composer/claude-settings.ts
28448
- var init_claude_settings2 = __esm(() => {
28449
- init_claude_settings();
28450
- });
28451
-
28452
- // src/tui/panes/chat/composer/models.ts
28453
- function modelLabelFor(id) {
28454
- const resolved = id ?? resolveDefaultModelId();
28455
- const canonical = ALIAS_TO_ID[resolved] ?? resolved;
28456
- const match = MODEL_CHOICES.find((m2) => m2.id === canonical);
28457
- return match?.label ?? resolved;
28458
- }
28459
- var DEFAULT_MODEL_ID = "claude-opus-4-7[1m]", MODEL_CHOICES, ALIAS_TO_ID;
28460
- var init_models = __esm(() => {
28461
- init_claude_settings2();
28462
- init_claude_settings2();
28463
- MODEL_CHOICES = [
28464
- { id: "claude-opus-4-7[1m]", label: "Opus 4.7 1M", hint: "long context, default" },
28465
- { id: "claude-opus-4-7", label: "Opus 4.7", hint: "most capable, slowest" },
28466
- { id: "claude-sonnet-4-6[1m]", label: "sonnet 4.6 (1M)", hint: "long context" },
28467
- { id: "claude-sonnet-4-6", label: "sonnet 4.6" },
28468
- { id: "claude-haiku-4-5-20251001", label: "haiku 4.5", hint: "fastest, cheapest" }
28469
- ];
28470
- ALIAS_TO_ID = {
28471
- opus: "claude-opus-4-7",
28472
- "opus[1m]": "claude-opus-4-7[1m]",
28473
- sonnet: "claude-sonnet-4-6",
28474
- "sonnet[1m]": "claude-sonnet-4-6[1m]",
28475
- haiku: "claude-haiku-4-5-20251001"
28476
- };
28477
- });
28478
-
28479
30095
  // src/tui/panes/chat/composer/ModelPicker.tsx
28480
30096
  import { TextAttributes as TextAttributes24 } from "@opentui/core";
28481
30097
  function ModelPicker(props) {
@@ -28483,11 +30099,12 @@ function ModelPicker(props) {
28483
30099
  const {
28484
30100
  theme
28485
30101
  } = useTheme();
28486
- const seed = props.current ?? DEFAULT_MODEL_ID;
28487
- const initial = MODEL_CHOICES.findIndex((m2) => m2.id === seed);
30102
+ const choices = createMemo(() => allModels());
30103
+ const seed = props.current ?? defaultCapabilities.defaultModelId();
30104
+ const initial = choices().findIndex((m2) => m2.id === seed);
28488
30105
  const [cursor, setCursor] = createSignal(initial >= 0 ? initial : 0);
28489
30106
  function commit() {
28490
- const choice = MODEL_CHOICES[cursor()];
30107
+ const choice = choices()[cursor()];
28491
30108
  if (!choice)
28492
30109
  return;
28493
30110
  props.onPick(choice.id);
@@ -28496,16 +30113,36 @@ function ModelPicker(props) {
28496
30113
  useBindings(() => ({
28497
30114
  bindings: [{
28498
30115
  key: "up",
28499
- cmd: () => setCursor((c2) => (c2 - 1 + MODEL_CHOICES.length) % MODEL_CHOICES.length)
30116
+ cmd: () => {
30117
+ const n2 = choices().length;
30118
+ if (n2 === 0)
30119
+ return;
30120
+ setCursor((c2) => (c2 - 1 + n2) % n2);
30121
+ }
28500
30122
  }, {
28501
30123
  key: "down",
28502
- cmd: () => setCursor((c2) => (c2 + 1) % MODEL_CHOICES.length)
30124
+ cmd: () => {
30125
+ const n2 = choices().length;
30126
+ if (n2 === 0)
30127
+ return;
30128
+ setCursor((c2) => (c2 + 1) % n2);
30129
+ }
28503
30130
  }, {
28504
30131
  key: "k",
28505
- cmd: () => setCursor((c2) => (c2 - 1 + MODEL_CHOICES.length) % MODEL_CHOICES.length)
30132
+ cmd: () => {
30133
+ const n2 = choices().length;
30134
+ if (n2 === 0)
30135
+ return;
30136
+ setCursor((c2) => (c2 - 1 + n2) % n2);
30137
+ }
28506
30138
  }, {
28507
30139
  key: "j",
28508
- cmd: () => setCursor((c2) => (c2 + 1) % MODEL_CHOICES.length)
30140
+ cmd: () => {
30141
+ const n2 = choices().length;
30142
+ if (n2 === 0)
30143
+ return;
30144
+ setCursor((c2) => (c2 + 1) % n2);
30145
+ }
28509
30146
  }, {
28510
30147
  key: "return",
28511
30148
  cmd: commit
@@ -28529,7 +30166,9 @@ function ModelPicker(props) {
28529
30166
  setProp(_el$7, "flexDirection", "column");
28530
30167
  setProp(_el$7, "paddingBottom", 1);
28531
30168
  insert(_el$7, createComponent2(For, {
28532
- each: MODEL_CHOICES,
30169
+ get each() {
30170
+ return choices();
30171
+ },
28533
30172
  children: (choice, i2) => {
28534
30173
  const active = () => i2() === cursor();
28535
30174
  return (() => {
@@ -28599,11 +30238,11 @@ var init_ModelPicker = __esm(() => {
28599
30238
  init_solid();
28600
30239
  init_solid();
28601
30240
  init_solid();
30241
+ init_registry2();
28602
30242
  init_dev();
28603
30243
  init_theme();
28604
30244
  init_keymap();
28605
30245
  init_dialog();
28606
- init_models();
28607
30246
  ModelPicker.show = (dialog, current) => {
28608
30247
  return new Promise((resolve4) => {
28609
30248
  dialog.replace(() => createComponent2(ModelPicker, {
@@ -28616,30 +30255,30 @@ var init_ModelPicker = __esm(() => {
28616
30255
  });
28617
30256
 
28618
30257
  // src/tui/panes/chat/composer/user-slashes.ts
28619
- import { readFile as readFile5, readdir as readdir3, stat as stat3 } from "fs/promises";
28620
- import { homedir as homedir11 } from "os";
28621
- import { join as join14 } from "path";
30258
+ import { readFile as readFile6, readdir as readdir4, stat as stat4 } from "fs/promises";
30259
+ import { homedir as homedir15 } from "os";
30260
+ import { join as join15 } from "path";
28622
30261
  function resolveHome() {
28623
- return process.env.HOME ?? homedir11();
30262
+ return process.env.HOME ?? homedir15();
28624
30263
  }
28625
- async function safeReaddir(path9) {
30264
+ async function safeReaddir(path12) {
28626
30265
  try {
28627
- return await readdir3(path9);
30266
+ return await readdir4(path12);
28628
30267
  } catch {
28629
30268
  return [];
28630
30269
  }
28631
30270
  }
28632
- async function isFile(path9) {
30271
+ async function isFile(path12) {
28633
30272
  try {
28634
- const s2 = await stat3(path9);
30273
+ const s2 = await stat4(path12);
28635
30274
  return s2.isFile();
28636
30275
  } catch {
28637
30276
  return false;
28638
30277
  }
28639
30278
  }
28640
- async function isDir(path9) {
30279
+ async function isDir(path12) {
28641
30280
  try {
28642
- const s2 = await stat3(path9);
30281
+ const s2 = await stat4(path12);
28643
30282
  return s2.isDirectory();
28644
30283
  } catch {
28645
30284
  return false;
@@ -28710,9 +30349,9 @@ function extractDescription(content) {
28710
30349
  }
28711
30350
  return null;
28712
30351
  }
28713
- async function tryReadDescription(path9) {
30352
+ async function tryReadDescription(path12) {
28714
30353
  try {
28715
- const content = await readFile5(path9, "utf-8");
30354
+ const content = await readFile6(path12, "utf-8");
28716
30355
  return extractDescription(content);
28717
30356
  } catch {
28718
30357
  return null;
@@ -28724,7 +30363,7 @@ async function scanCommandsDir(dir) {
28724
30363
  for (const entry of entries) {
28725
30364
  if (!entry.endsWith(".md"))
28726
30365
  continue;
28727
- const full = join14(dir, entry);
30366
+ const full = join15(dir, entry);
28728
30367
  if (!await isFile(full))
28729
30368
  continue;
28730
30369
  const name = entry.slice(0, -3);
@@ -28737,10 +30376,10 @@ async function scanSkillsDir(dir) {
28737
30376
  const entries = await safeReaddir(dir);
28738
30377
  const out = [];
28739
30378
  for (const entry of entries) {
28740
- const sub = join14(dir, entry);
30379
+ const sub = join15(dir, entry);
28741
30380
  if (!await isDir(sub))
28742
30381
  continue;
28743
- const skillMd = join14(sub, "SKILL.md");
30382
+ const skillMd = join15(sub, "SKILL.md");
28744
30383
  if (!await isFile(skillMd))
28745
30384
  continue;
28746
30385
  const description = await tryReadDescription(skillMd) ?? "";
@@ -28750,8 +30389,8 @@ async function scanSkillsDir(dir) {
28750
30389
  }
28751
30390
  async function scanBasePath(claudeDir) {
28752
30391
  const [skills, commands] = await Promise.all([
28753
- scanSkillsDir(join14(claudeDir, "skills")),
28754
- scanCommandsDir(join14(claudeDir, "commands"))
30392
+ scanSkillsDir(join15(claudeDir, "skills")),
30393
+ scanCommandsDir(join15(claudeDir, "commands"))
28755
30394
  ]);
28756
30395
  const map = new Map;
28757
30396
  for (const e2 of skills)
@@ -28761,8 +30400,8 @@ async function scanBasePath(claudeDir) {
28761
30400
  return [...map.values()];
28762
30401
  }
28763
30402
  async function loadUserSlashes(worktreePath) {
28764
- const projectScan = worktreePath ? scanBasePath(join14(worktreePath, ".claude")) : Promise.resolve([]);
28765
- const globalScan = scanBasePath(join14(resolveHome(), ".claude"));
30403
+ const projectScan = worktreePath ? scanBasePath(join15(worktreePath, ".claude")) : Promise.resolve([]);
30404
+ const globalScan = scanBasePath(join15(resolveHome(), ".claude"));
28766
30405
  const [project, global] = await Promise.all([projectScan, globalScan]);
28767
30406
  const map = new Map;
28768
30407
  for (const e2 of global)
@@ -28774,32 +30413,6 @@ async function loadUserSlashes(worktreePath) {
28774
30413
  var init_user_slashes = () => {};
28775
30414
 
28776
30415
  // src/tui/panes/chat/context-meter.ts
28777
- function parseContextWindowSize(modelIdentifier) {
28778
- const delimitedMatch = /(?:\(|\[)\s*(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])\s*(?:\)|\])/i.exec(modelIdentifier);
28779
- if (delimitedMatch?.[1] && delimitedMatch[2]) {
28780
- const parsed2 = Number.parseFloat(delimitedMatch[1].replace(/[,_]/g, ""));
28781
- if (Number.isFinite(parsed2) && parsed2 > 0) {
28782
- return Math.round(parsed2 * (delimitedMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
28783
- }
28784
- }
28785
- const contextMatch = /\b(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])(?:\s*(?:token\s*)?context)?\b/i.exec(modelIdentifier);
28786
- if (!contextMatch?.[1] || !contextMatch[2])
28787
- return null;
28788
- const parsed = Number.parseFloat(contextMatch[1].replace(/[,_]/g, ""));
28789
- if (!Number.isFinite(parsed) || parsed <= 0)
28790
- return null;
28791
- return Math.round(parsed * (contextMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
28792
- }
28793
- function contextWindowTokensForModel(modelId) {
28794
- const id = modelId ?? resolveDefaultModelId();
28795
- const parsedWindow = parseContextWindowSize(id);
28796
- if (parsedWindow !== null)
28797
- return parsedWindow;
28798
- const inPicker = MODEL_CHOICES.some((m2) => m2.id === id);
28799
- if (inPicker)
28800
- return STD_CTX;
28801
- return STD_CTX;
28802
- }
28803
30416
  function formatTokShort(n2) {
28804
30417
  if (n2 >= 1e6) {
28805
30418
  const m2 = n2 / 1e6;
@@ -28818,8 +30431,10 @@ function formatTotalSpeed(tokensPerSecond) {
28818
30431
  return `${(tokensPerSecond / 1000).toFixed(1)}k t/s`;
28819
30432
  return `${tokensPerSecond.toFixed(1)} t/s`;
28820
30433
  }
28821
- function formatContextUsageCompact(u2, modelId) {
28822
- const window2 = contextWindowTokensForModel(modelId);
30434
+ function formatContextUsageCompact(u2, modelId, vendor) {
30435
+ const caps = vendor ? getCapabilities(vendor) : capabilitiesForModelId(modelId);
30436
+ const id = modelId ?? caps.defaultModelId();
30437
+ const window2 = caps.contextWindowFor(id);
28823
30438
  const total = totalContextTokens(u2);
28824
30439
  if (total <= 0 || window2 <= 0)
28825
30440
  return null;
@@ -28827,10 +30442,8 @@ function formatContextUsageCompact(u2, modelId) {
28827
30442
  const speed = formatTotalSpeed(u2.total_speed_tokens_per_second);
28828
30443
  return [`${pct2}% \xB7 ${formatTokShort(total)}/${formatTokShort(window2)}`, speed].filter(Boolean).join(" \xB7 ");
28829
30444
  }
28830
- var STD_CTX = 200000;
28831
30445
  var init_context_meter = __esm(() => {
28832
- init_claude_settings2();
28833
- init_models();
30446
+ init_registry2();
28834
30447
  });
28835
30448
 
28836
30449
  // src/tui/panes/chat/store.ts
@@ -28877,7 +30490,7 @@ function setMessagesFromHistory(state, past, usageMetrics) {
28877
30490
  for (const m2 of past) {
28878
30491
  appendRowsFromMessage(rows, toolIndexById, m2);
28879
30492
  }
28880
- const latestUsage = usageMetrics ?? deriveSessionUsageMetrics(past);
30493
+ const latestUsage = usageMetrics;
28881
30494
  return {
28882
30495
  ...state,
28883
30496
  messages: capMessages(rows, new Date().toISOString()),
@@ -28955,12 +30568,13 @@ function applyEvent(state, ev, nowIso = new Date().toISOString()) {
28955
30568
  case "usage":
28956
30569
  return {
28957
30570
  ...state,
28958
- lastUsage: withTotalSpeedForTurn({
30571
+ lastUsage: {
28959
30572
  input_tokens: ev.input_tokens,
28960
30573
  output_tokens: ev.output_tokens,
28961
30574
  cache_read_input_tokens: ev.cache_read_input_tokens,
28962
- cache_creation_input_tokens: ev.cache_creation_input_tokens
28963
- }, state.activeTurnStartedAt, nowIso)
30575
+ cache_creation_input_tokens: ev.cache_creation_input_tokens,
30576
+ total_speed_tokens_per_second: ev.total_speed_tokens_per_second
30577
+ }
28964
30578
  };
28965
30579
  case "done":
28966
30580
  return { ...state, isStreaming: false, activeTurnStartedAt: undefined };
@@ -29062,16 +30676,6 @@ function pushSystemError(state, message, nowIso = new Date().toISOString()) {
29062
30676
  }
29063
30677
  function appendRowsFromMessage(rows, toolIndexById, m2) {
29064
30678
  const ts = m2.timestamp;
29065
- if (typeof m2.content === "string") {
29066
- if (m2.content.length === 0)
29067
- return;
29068
- const row = textRow(m2.role, m2.content, ts);
29069
- if (row)
29070
- rows.push(row);
29071
- return;
29072
- }
29073
- if (!Array.isArray(m2.content))
29074
- return;
29075
30679
  let textBuf = "";
29076
30680
  const flushText = () => {
29077
30681
  if (textBuf.length === 0)
@@ -29081,25 +30685,18 @@ function appendRowsFromMessage(rows, toolIndexById, m2) {
29081
30685
  rows.push(row);
29082
30686
  textBuf = "";
29083
30687
  };
29084
- for (const block of m2.content) {
29085
- if (typeof block === "string") {
29086
- textBuf += block;
29087
- continue;
29088
- }
29089
- if (!block || typeof block !== "object")
29090
- continue;
29091
- const b2 = block;
29092
- if (b2.type === "text" && typeof b2.text === "string") {
29093
- textBuf += b2.text;
30688
+ for (const block of m2.blocks) {
30689
+ if (block.type === "text") {
30690
+ textBuf += block.text;
29094
30691
  continue;
29095
30692
  }
29096
- if (b2.type === "tool_use") {
30693
+ if (block.type === "tool_call") {
29097
30694
  flushText();
29098
- const id = typeof b2.id === "string" ? b2.id : undefined;
30695
+ const id = block.callId.length > 0 ? block.callId : undefined;
29099
30696
  const row = {
29100
30697
  kind: "tool",
29101
- name: typeof b2.name === "string" ? b2.name : "",
29102
- input: b2.input,
30698
+ name: block.name,
30699
+ input: block.input,
29103
30700
  done: false,
29104
30701
  ts,
29105
30702
  toolUseId: id
@@ -29110,11 +30707,11 @@ function appendRowsFromMessage(rows, toolIndexById, m2) {
29110
30707
  toolIndexById.set(id, idx);
29111
30708
  continue;
29112
30709
  }
29113
- if (b2.type === "tool_result") {
30710
+ if (block.type === "tool_result") {
29114
30711
  flushText();
29115
- const id = typeof b2.tool_use_id === "string" ? b2.tool_use_id : undefined;
30712
+ const id = block.callId.length > 0 ? block.callId : undefined;
29116
30713
  const idx = id !== undefined ? toolIndexById.get(id) : undefined;
29117
- const output = b2.content;
30714
+ const output = block.output;
29118
30715
  if (idx !== undefined) {
29119
30716
  const target = rows[idx];
29120
30717
  if (target && target.kind === "tool") {
@@ -29347,6 +30944,7 @@ var init_use_chat_session = __esm(() => {
29347
30944
  });
29348
30945
 
29349
30946
  // src/tui/panes/chat/Chat.tsx
30947
+ import { TextAttributes as TextAttributes25 } from "@opentui/core";
29350
30948
  function Chat(props) {
29351
30949
  const {
29352
30950
  theme
@@ -29422,8 +31020,10 @@ function Chat(props) {
29422
31020
  if (!u2)
29423
31021
  return null;
29424
31022
  const task = props.orchestrator.getTask(tid);
29425
- const modelId2 = task?.model ?? resolveDefaultModelId();
29426
- return formatContextUsageCompact(u2, modelId2);
31023
+ const tab = task?.tabs.find((t2) => t2.id === tabId);
31024
+ const vendor = tab?.vendor ?? task?.vendor ?? "claude";
31025
+ const modelId2 = tab?.model ?? task?.model ?? getCapabilities(vendor).defaultModelId();
31026
+ return formatContextUsageCompact(u2, modelId2, vendor);
29427
31027
  });
29428
31028
  createEffect(on(contextMeterLabel, (label) => {
29429
31029
  props.onContextMeter?.(label ?? null);
@@ -29482,7 +31082,9 @@ function Chat(props) {
29482
31082
  const id = props.taskId();
29483
31083
  if (!id)
29484
31084
  return;
29485
- return tasksAcc().find((t2) => t2.id === id)?.model;
31085
+ const task = tasksAcc().find((t2) => t2.id === id);
31086
+ const tab = task?.tabs.find((t2) => t2.id === activeTabId());
31087
+ return tab?.model ?? task?.model;
29486
31088
  });
29487
31089
  const worktreePath = createMemo(() => {
29488
31090
  const id = props.taskId();
@@ -29491,14 +31093,22 @@ function Chat(props) {
29491
31093
  return tasksAcc().find((t2) => t2.id === id)?.worktreePath ?? undefined;
29492
31094
  });
29493
31095
  const modelLabel = createMemo(() => modelLabelFor(modelId()));
31096
+ const inputPlaceholder = createMemo(() => {
31097
+ const id = props.taskId();
31098
+ const task = id ? tasksAcc().find((t2) => t2.id === id) : undefined;
31099
+ const tab = task?.tabs.find((t2) => t2.id === activeTabId());
31100
+ const vendor = tab?.vendor ?? task?.vendor ?? "claude";
31101
+ return getIdentity(vendor).inputPlaceholder;
31102
+ });
29494
31103
  async function chooseModel() {
29495
31104
  const id = props.taskId();
29496
31105
  if (!id)
29497
31106
  return;
31107
+ const tabId = activeTabId() ?? undefined;
29498
31108
  const result = await ModelPicker.show(dialog, modelId());
29499
31109
  if (result === undefined)
29500
31110
  return;
29501
- await props.orchestrator.setModel(id, result).catch((err) => {
31111
+ await props.orchestrator.setModel(id, result, tabId).catch((err) => {
29502
31112
  console.error("[kobe] setModel failed:", err);
29503
31113
  });
29504
31114
  }
@@ -29586,7 +31196,7 @@ function Chat(props) {
29586
31196
  try {
29587
31197
  await props.orchestrator.runTask(taskId, dispatched.text, tabId);
29588
31198
  } catch (err) {
29589
- patchActiveState((s2) => pushSystemError(s2, `queued runTask failed: ${stringifyErr2(err)}`));
31199
+ patchActiveState((s2) => pushSystemError(s2, `queued runTask failed: ${stringifyErr3(err)}`));
29590
31200
  }
29591
31201
  } finally {
29592
31202
  dispatching = false;
@@ -29608,7 +31218,7 @@ function Chat(props) {
29608
31218
  try {
29609
31219
  await props.orchestrator.clearTab(taskId, tabId);
29610
31220
  } catch (err) {
29611
- patchActiveState((s2) => pushSystemError(s2, `/clear failed: ${stringifyErr2(err)}`));
31221
+ patchActiveState((s2) => pushSystemError(s2, `/clear failed: ${stringifyErr3(err)}`));
29612
31222
  }
29613
31223
  return;
29614
31224
  }
@@ -29622,7 +31232,7 @@ function Chat(props) {
29622
31232
  try {
29623
31233
  await props.orchestrator.steerTask(taskId, text, tabId);
29624
31234
  } catch (err) {
29625
- patchActiveState((s2) => pushSystemError(s2, `steer failed: ${stringifyErr2(err)}`));
31235
+ patchActiveState((s2) => pushSystemError(s2, `steer failed: ${stringifyErr3(err)}`));
29626
31236
  }
29627
31237
  } finally {
29628
31238
  dispatching = false;
@@ -29642,7 +31252,7 @@ function Chat(props) {
29642
31252
  try {
29643
31253
  await props.orchestrator.runTask(taskId, text, tabId);
29644
31254
  } catch (err) {
29645
- patchActiveState((s2) => pushSystemError(s2, `runTask failed: ${stringifyErr2(err)}`));
31255
+ patchActiveState((s2) => pushSystemError(s2, `runTask failed: ${stringifyErr3(err)}`));
29646
31256
  }
29647
31257
  }
29648
31258
  function cancelQueued(id) {
@@ -29666,7 +31276,7 @@ function Chat(props) {
29666
31276
  setExpandedFoldStartIndex(null);
29667
31277
  props.orchestrator.setActiveTab(taskId, tab.id);
29668
31278
  } catch (err) {
29669
- patchActiveState((s2) => pushSystemError(s2, `createTab failed: ${stringifyErr2(err)}`));
31279
+ patchActiveState((s2) => pushSystemError(s2, `createTab failed: ${stringifyErr3(err)}`));
29670
31280
  }
29671
31281
  }
29672
31282
  async function closeActiveTab() {
@@ -29684,7 +31294,7 @@ function Chat(props) {
29684
31294
  setExpandedFoldStartIndex(null);
29685
31295
  }
29686
31296
  } catch (err) {
29687
- patchActiveState((s2) => pushSystemError(s2, `closeTab failed: ${stringifyErr2(err)}`));
31297
+ patchActiveState((s2) => pushSystemError(s2, `closeTab failed: ${stringifyErr3(err)}`));
29688
31298
  }
29689
31299
  }
29690
31300
  function selectTabByIndex(idx) {
@@ -29737,7 +31347,7 @@ function Chat(props) {
29737
31347
  try {
29738
31348
  await props.orchestrator.interruptTask(taskId, tabId);
29739
31349
  } catch (err) {
29740
- patchActiveState((s2) => pushSystemError(s2, `interrupt failed: ${stringifyErr2(err)}`));
31350
+ patchActiveState((s2) => pushSystemError(s2, `interrupt failed: ${stringifyErr3(err)}`));
29741
31351
  }
29742
31352
  }
29743
31353
  useBindings(() => ({
@@ -29813,7 +31423,7 @@ function Chat(props) {
29813
31423
  kind: "ask_question",
29814
31424
  answers
29815
31425
  }).catch((err) => {
29816
- patchActiveState((s2) => pushSystemError(s2, `respondToInput failed: ${stringifyErr2(err)}`));
31426
+ patchActiveState((s2) => pushSystemError(s2, `respondToInput failed: ${stringifyErr3(err)}`));
29817
31427
  });
29818
31428
  setDraft("");
29819
31429
  return;
@@ -29871,9 +31481,6 @@ function Chat(props) {
29871
31481
  get showEmptyPlaceholder() {
29872
31482
  return !showThinking();
29873
31483
  },
29874
- get error() {
29875
- return activeState().error;
29876
- },
29877
31484
  onApprove: (requestId, approve) => {
29878
31485
  const taskId = props.taskId();
29879
31486
  if (!taskId)
@@ -29882,7 +31489,7 @@ function Chat(props) {
29882
31489
  kind: "approve_plan",
29883
31490
  approve
29884
31491
  }).catch((err) => {
29885
- patchActiveState((s2) => pushSystemError(s2, `respondToInput failed: ${stringifyErr2(err)}`));
31492
+ patchActiveState((s2) => pushSystemError(s2, `respondToInput failed: ${stringifyErr3(err)}`));
29886
31493
  });
29887
31494
  },
29888
31495
  onAnswer: (requestId, answers) => {
@@ -29893,7 +31500,7 @@ function Chat(props) {
29893
31500
  kind: "ask_question",
29894
31501
  answers
29895
31502
  }).catch((err) => {
29896
- patchActiveState((s2) => pushSystemError(s2, `respondToInput failed: ${stringifyErr2(err)}`));
31503
+ patchActiveState((s2) => pushSystemError(s2, `respondToInput failed: ${stringifyErr3(err)}`));
29897
31504
  });
29898
31505
  },
29899
31506
  onClaimComposerFocus: setQuestionInlineFocus,
@@ -29923,6 +31530,44 @@ function Chat(props) {
29923
31530
  });
29924
31531
  }
29925
31532
  }), null);
31533
+ insert(_el$, createComponent2(Show, {
31534
+ get when() {
31535
+ return activeState().error;
31536
+ },
31537
+ children: (err) => (() => {
31538
+ var _el$7 = createElement("box"), _el$8 = createElement("text"), _el$0 = createElement("text"), _el$10 = createElement("text");
31539
+ insertNode(_el$7, _el$8);
31540
+ insertNode(_el$7, _el$0);
31541
+ insertNode(_el$7, _el$10);
31542
+ setProp(_el$7, "flexDirection", "row");
31543
+ setProp(_el$7, "gap", 1);
31544
+ setProp(_el$7, "paddingLeft", 1);
31545
+ setProp(_el$7, "paddingRight", 1);
31546
+ setProp(_el$7, "flexShrink", 0);
31547
+ insertNode(_el$8, createTextNode(`!`));
31548
+ setProp(_el$8, "wrapMode", "none");
31549
+ insertNode(_el$0, createTextNode(`error`));
31550
+ setProp(_el$0, "wrapMode", "none");
31551
+ setProp(_el$10, "wrapMode", "none");
31552
+ insert(_el$10, err);
31553
+ effect((_p$) => {
31554
+ var { backgroundElement: _v$, warning: _v$2 } = theme, _v$3 = TextAttributes25.BOLD, _v$4 = theme.warning, _v$5 = theme.textMuted;
31555
+ _v$ !== _p$.e && (_p$.e = setProp(_el$7, "backgroundColor", _v$, _p$.e));
31556
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$8, "fg", _v$2, _p$.t));
31557
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$8, "attributes", _v$3, _p$.a));
31558
+ _v$4 !== _p$.o && (_p$.o = setProp(_el$0, "fg", _v$4, _p$.o));
31559
+ _v$5 !== _p$.i && (_p$.i = setProp(_el$10, "fg", _v$5, _p$.i));
31560
+ return _p$;
31561
+ }, {
31562
+ e: undefined,
31563
+ t: undefined,
31564
+ a: undefined,
31565
+ o: undefined,
31566
+ i: undefined
31567
+ });
31568
+ return _el$7;
31569
+ })()
31570
+ }), null);
29926
31571
  insert(_el$, createComponent2(Show, {
29927
31572
  get when() {
29928
31573
  return !pendingQuestion();
@@ -29951,6 +31596,7 @@ function Chat(props) {
29951
31596
  permissionMode,
29952
31597
  onCyclePermissionMode: cyclePermissionMode,
29953
31598
  modelLabel,
31599
+ inputPlaceholder,
29954
31600
  onChooseModel: () => void chooseModel(),
29955
31601
  worktreePath,
29956
31602
  queue: () => activeState().queue,
@@ -29962,7 +31608,7 @@ function Chat(props) {
29962
31608
  return _el$;
29963
31609
  })();
29964
31610
  }
29965
- function stringifyErr2(err) {
31611
+ function stringifyErr3(err) {
29966
31612
  if (err instanceof Error)
29967
31613
  return err.message;
29968
31614
  try {
@@ -29981,6 +31627,7 @@ var init_Chat = __esm(() => {
29981
31627
  init_solid();
29982
31628
  init_solid();
29983
31629
  init_solid();
31630
+ init_registry2();
29984
31631
  init_dev();
29985
31632
  init_resume_dialog();
29986
31633
  init_keybindings();
@@ -29992,7 +31639,6 @@ var init_Chat = __esm(() => {
29992
31639
  init_MessageList();
29993
31640
  init_ModelPicker();
29994
31641
  init_builtin_slashes();
29995
- init_models();
29996
31642
  init_user_slashes();
29997
31643
  init_context_meter();
29998
31644
  init_store2();
@@ -30000,7 +31646,7 @@ var init_Chat = __esm(() => {
30000
31646
  });
30001
31647
 
30002
31648
  // src/tui/component/sidebar.tsx
30003
- import { TextAttributes as TextAttributes25 } from "@opentui/core";
31649
+ import { TextAttributes as TextAttributes26 } from "@opentui/core";
30004
31650
  function Sidebar(props) {
30005
31651
  const {
30006
31652
  theme
@@ -30026,7 +31672,7 @@ function Sidebar(props) {
30026
31672
  setProp(_el$2, "paddingBottom", 1);
30027
31673
  insert(_el$3, () => props.title);
30028
31674
  effect((_p$) => {
30029
- var _v$ = theme.text, _v$2 = TextAttributes25.BOLD;
31675
+ var _v$ = theme.text, _v$2 = TextAttributes26.BOLD;
30030
31676
  _v$ !== _p$.e && (_p$.e = setProp(_el$3, "fg", _v$, _p$.e));
30031
31677
  _v$2 !== _p$.t && (_p$.t = setProp(_el$3, "attributes", _v$2, _p$.t));
30032
31678
  return _p$;
@@ -30145,12 +31791,12 @@ var init_sidebar = __esm(() => {
30145
31791
  });
30146
31792
 
30147
31793
  // src/tui/panes/sidebar/git-head.ts
30148
- import { spawnSync as spawnSync9 } from "child_process";
31794
+ import { spawnSync as spawnSync10 } from "child_process";
30149
31795
  function readCurrentBranch(repo) {
30150
31796
  if (!repo)
30151
31797
  return "";
30152
31798
  try {
30153
- const out = spawnSync9("git", ["symbolic-ref", "--short", "HEAD"], {
31799
+ const out = spawnSync10("git", ["symbolic-ref", "--short", "HEAD"], {
30154
31800
  cwd: repo,
30155
31801
  encoding: "utf8",
30156
31802
  stdio: ["ignore", "pipe", "pipe"]
@@ -30160,7 +31806,7 @@ function readCurrentBranch(repo) {
30160
31806
  if (name && name !== "HEAD")
30161
31807
  return name;
30162
31808
  }
30163
- const head = spawnSync9("git", ["rev-parse", "--verify", "HEAD"], {
31809
+ const head = spawnSync10("git", ["rev-parse", "--verify", "HEAD"], {
30164
31810
  cwd: repo,
30165
31811
  encoding: "utf8",
30166
31812
  stdio: ["ignore", "pipe", "pipe"]
@@ -30285,7 +31931,7 @@ var init_keys4 = __esm(() => {
30285
31931
  });
30286
31932
 
30287
31933
  // src/tui/panes/sidebar/Sidebar.tsx
30288
- import { TextAttributes as TextAttributes26 } from "@opentui/core";
31934
+ import { TextAttributes as TextAttributes27 } from "@opentui/core";
30289
31935
  function Sidebar2(props) {
30290
31936
  const {
30291
31937
  theme
@@ -30377,7 +32023,7 @@ function Sidebar2(props) {
30377
32023
  return () => _c$() ? `[ ${tab.label} ]` : tab.label;
30378
32024
  })());
30379
32025
  effect((_p$) => {
30380
- var _v$7 = active() ? theme.primary : theme.textMuted, _v$8 = active() ? TextAttributes26.BOLD : undefined;
32026
+ var _v$7 = active() ? theme.primary : theme.textMuted, _v$8 = active() ? TextAttributes27.BOLD : undefined;
30381
32027
  _v$7 !== _p$.e && (_p$.e = setProp(_el$13, "fg", _v$7, _p$.e));
30382
32028
  _v$8 !== _p$.t && (_p$.t = setProp(_el$13, "attributes", _v$8, _p$.t));
30383
32029
  return _p$;
@@ -30472,7 +32118,7 @@ function Sidebar2(props) {
30472
32118
  }
30473
32119
  }), null);
30474
32120
  effect((_p$) => {
30475
- var _v$9 = isCursor() ? theme.primary : isSelected() ? theme.backgroundElement : undefined, _v$0 = isCursor() ? theme.selectedListItemText : badgeColor(), _v$1 = isCursor() ? theme.selectedListItemText : theme.text, _v$10 = (isMain || isSelected() && !isCursor()) && !isCursor() ? TextAttributes26.BOLD : undefined;
32121
+ var _v$9 = isCursor() ? theme.primary : isSelected() ? theme.backgroundElement : undefined, _v$0 = isCursor() ? theme.selectedListItemText : badgeColor(), _v$1 = isCursor() ? theme.selectedListItemText : theme.text, _v$10 = (isMain || isSelected() && !isCursor()) && !isCursor() ? TextAttributes27.BOLD : undefined;
30476
32122
  _v$9 !== _p$.e && (_p$.e = setProp(_el$14, "backgroundColor", _v$9, _p$.e));
30477
32123
  _v$0 !== _p$.t && (_p$.t = setProp(_el$15, "fg", _v$0, _p$.t));
30478
32124
  _v$1 !== _p$.a && (_p$.a = setProp(_el$16, "fg", _v$1, _p$.a));
@@ -30510,7 +32156,7 @@ function Sidebar2(props) {
30510
32156
  setProp(_el$11, "wrapMode", "none");
30511
32157
  setProp(_el$11, "onMouseUp", () => props.onAddTask?.());
30512
32158
  effect((_p$) => {
30513
- var _v$ = props.width ? props.width() : SIDEBAR_WIDTH, _v$2 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$3 = TextAttributes26.BOLD, _v$4 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$5 = TextAttributes26.BOLD, _v$6 = theme.textMuted;
32159
+ var _v$ = props.width ? props.width() : SIDEBAR_WIDTH, _v$2 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$3 = TextAttributes27.BOLD, _v$4 = focusedAccessor() ? theme.focusAccent : theme.textMuted, _v$5 = TextAttributes27.BOLD, _v$6 = theme.textMuted;
30514
32160
  _v$ !== _p$.e && (_p$.e = setProp(_el$, "width", _v$, _p$.e));
30515
32161
  _v$2 !== _p$.t && (_p$.t = setProp(_el$3, "fg", _v$2, _p$.t));
30516
32162
  _v$3 !== _p$.a && (_p$.a = setProp(_el$3, "attributes", _v$3, _p$.a));
@@ -30580,12 +32226,12 @@ var init_Sidebar = __esm(() => {
30580
32226
  });
30581
32227
 
30582
32228
  // src/orchestrator/bridge/server.ts
30583
- import { mkdir as mkdir3, unlink as unlink3 } from "fs/promises";
32229
+ import { mkdir as mkdir3, unlink as unlink4 } from "fs/promises";
30584
32230
  import { createServer } from "net";
30585
32231
  import { dirname as dirname5 } from "path";
30586
32232
  async function startBridgeServer(orch, socketPath) {
30587
32233
  await mkdir3(dirname5(socketPath), { recursive: true });
30588
- await unlink3(socketPath).catch(() => {});
32234
+ await unlink4(socketPath).catch(() => {});
30589
32235
  const conns = new Set;
30590
32236
  const server = createServer((conn) => {
30591
32237
  conns.add(conn);
@@ -30626,7 +32272,7 @@ async function startBridgeServer(orch, socketPath) {
30626
32272
  conn.destroy();
30627
32273
  conns.clear();
30628
32274
  await new Promise((resolve4) => server.close(() => resolve4()));
30629
- await unlink3(socketPath).catch(() => {});
32275
+ await unlink4(socketPath).catch(() => {});
30630
32276
  }
30631
32277
  };
30632
32278
  }
@@ -30719,23 +32365,19 @@ __export(exports_bridge, {
30719
32365
  bridgeSocketPathForHome: () => bridgeSocketPathForHome
30720
32366
  });
30721
32367
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
30722
- import { homedir as homedir12, tmpdir as tmpdir3 } from "os";
30723
- import { join as join15 } from "path";
32368
+ import { homedir as homedir16 } from "os";
32369
+ import { join as join16 } from "path";
30724
32370
  import { fileURLToPath as fileURLToPath2 } from "url";
30725
32371
  function bridgeSocketPathForHome(home, pid = process.pid) {
30726
- const runDir = join15(home, ".kobe", "run");
30727
- const preferred = join15(runDir, `bridge-${pid}.sock`);
30728
- const macTempSocket = process.platform === "darwin" && preferred.startsWith(tmpdir3());
30729
- if (preferred.length <= UNIX_SOCKET_PATH_LIMIT && !macTempSocket)
30730
- return preferred;
30731
- const shortTmp = process.platform === "darwin" ? "/tmp" : tmpdir3();
30732
- return join15(shortTmp, `kobe-bridge-${pid}.sock`);
32372
+ const runDir = join16(home, ".kobe", "run");
32373
+ return fitSocketPath(join16(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
30733
32374
  }
30734
32375
  async function startBridge(orch, opts = {}) {
30735
- const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir12();
30736
- const runDir = join15(home, ".kobe", "run");
32376
+ const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir16();
32377
+ const runDir = join16(home, ".kobe", "run");
30737
32378
  const socketPath = bridgeSocketPathForHome(home);
30738
- const mcpConfigPath = join15(runDir, `mcp-${process.pid}.json`);
32379
+ const mcpConfigPath = join16(runDir, `mcp-${process.pid}.json`);
32380
+ await mkdir4(runDir, { recursive: true });
30739
32381
  const server = await startBridgeServer(orch, socketPath);
30740
32382
  await mkdir4(runDir, { recursive: true });
30741
32383
  const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
@@ -30758,14 +32400,14 @@ async function startBridge(orch, opts = {}) {
30758
32400
  }
30759
32401
  };
30760
32402
  }
30761
- var UNIX_SOCKET_PATH_LIMIT = 103;
30762
32403
  var init_bridge = __esm(() => {
32404
+ init_paths2();
30763
32405
  init_server();
30764
32406
  });
30765
32407
 
30766
32408
  // src/tui/app.tsx
30767
- import { homedir as homedir13 } from "os";
30768
- import { join as join16 } from "path";
32409
+ import { homedir as homedir17 } from "os";
32410
+ import { join as join17 } from "path";
30769
32411
  function Shell(props) {
30770
32412
  const themeCtx = useTheme();
30771
32413
  const {
@@ -30821,8 +32463,8 @@ function Shell(props) {
30821
32463
  return pp.prompt;
30822
32464
  });
30823
32465
  const worktreePathAcc = createMemo(() => {
30824
- const path9 = activeTask()?.worktreePath;
30825
- return path9 ? path9 : null;
32466
+ const path12 = activeTask()?.worktreePath;
32467
+ return path12 ? path12 : null;
30826
32468
  });
30827
32469
  const taskIdNullAcc = createMemo(() => selectedId());
30828
32470
  const diffBaseAcc = createMemo(() => worktreePathAcc() ? "HEAD" : null);
@@ -31292,17 +32934,17 @@ async function startApp() {
31292
32934
  } of loadUserThemes()) {
31293
32935
  addTheme(name, theme);
31294
32936
  }
31295
- const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir13();
32937
+ const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir17();
31296
32938
  let orchestrator;
31297
32939
  if (process.env.KOBE_TEST_ENGINE || process.env.KOBE_NO_DAEMON === "1") {
31298
- const engine3 = await buildEngine();
32940
+ const engines = await buildEngines();
31299
32941
  const store2 = new TaskIndexStore({
31300
32942
  homeDir: homeDir2
31301
32943
  });
31302
32944
  await store2.load();
31303
32945
  const worktrees = new GitWorktreeManager;
31304
32946
  orchestrator = new Orchestrator({
31305
- engine: engine3,
32947
+ engines,
31306
32948
  store: store2,
31307
32949
  worktrees
31308
32950
  });
@@ -31393,7 +33035,7 @@ var exports_tui = {};
31393
33035
  __export(exports_tui, {
31394
33036
  startTui: () => startTui
31395
33037
  });
31396
- import { TextAttributes as TextAttributes27 } from "@opentui/core";
33038
+ import { TextAttributes as TextAttributes28 } from "@opentui/core";
31397
33039
  function HelpHint() {
31398
33040
  const {
31399
33041
  theme
@@ -31467,7 +33109,7 @@ function Banner() {
31467
33109
  insert(_el$20, selected);
31468
33110
  insert(_el$12, createComponent2(HelpHint, {}), null);
31469
33111
  effect((_p$) => {
31470
- var _v$7 = theme.primary, _v$8 = TextAttributes27.BOLD, _v$9 = theme.borderActive, _v$0 = theme.text, _v$1 = theme.textMuted, _v$10 = {
33112
+ var _v$7 = theme.primary, _v$8 = TextAttributes28.BOLD, _v$9 = theme.borderActive, _v$0 = theme.text, _v$1 = theme.textMuted, _v$10 = {
31471
33113
  fg: theme.accent
31472
33114
  };
31473
33115
  _v$7 !== _p$.e && (_p$.e = setProp(_el$13, "fg", _v$7, _p$.e));