@sma1lboy/kobe 0.7.19 → 0.7.20
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.
- package/dist/cli/index.js +315 -230
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
|
|
|
90
90
|
package_default = {
|
|
91
91
|
$schema: "https://json.schemastore.org/package.json",
|
|
92
92
|
name: "@sma1lboy/kobe",
|
|
93
|
-
version: "0.7.
|
|
93
|
+
version: "0.7.20",
|
|
94
94
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
95
95
|
type: "module",
|
|
96
96
|
packageManager: "bun@1.3.13",
|
|
@@ -123,6 +123,7 @@ var init_package = __esm(() => {
|
|
|
123
123
|
"test:fast": "vitest run --passWithNoTests",
|
|
124
124
|
"test:socket": "KOBE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=1 --passWithNoTests",
|
|
125
125
|
"test:behavior": "KOBE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --passWithNoTests",
|
|
126
|
+
bench: "vitest bench --run",
|
|
126
127
|
lint: "biome check .",
|
|
127
128
|
knip: "knip-bun",
|
|
128
129
|
postinstall: "bun run scripts/check-preview-deps.ts || true",
|
|
@@ -5069,66 +5070,6 @@ var init_history = __esm(() => {
|
|
|
5069
5070
|
};
|
|
5070
5071
|
});
|
|
5071
5072
|
|
|
5072
|
-
// src/engine/claude-code-local/cost.ts
|
|
5073
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
5074
|
-
async function summarizeClaudeWorktreeCost(worktree) {
|
|
5075
|
-
const files = await listSessionFilesForWorktree(worktree);
|
|
5076
|
-
const base = {
|
|
5077
|
-
sessionCount: files.length,
|
|
5078
|
-
inputTokens: 0,
|
|
5079
|
-
outputTokens: 0,
|
|
5080
|
-
cacheReadTokens: 0,
|
|
5081
|
-
cacheCreateTokens: 0,
|
|
5082
|
-
lastActivityMs: files[0]?.mtimeMs ?? null
|
|
5083
|
-
};
|
|
5084
|
-
if (files.length === 0)
|
|
5085
|
-
return base;
|
|
5086
|
-
let input = 0;
|
|
5087
|
-
let output = 0;
|
|
5088
|
-
let cacheRead = 0;
|
|
5089
|
-
let cacheCreate = 0;
|
|
5090
|
-
for (const file of files) {
|
|
5091
|
-
let raw;
|
|
5092
|
-
try {
|
|
5093
|
-
raw = await readFile3(file.path, "utf8");
|
|
5094
|
-
} catch {
|
|
5095
|
-
continue;
|
|
5096
|
-
}
|
|
5097
|
-
for (const line of raw.split(`
|
|
5098
|
-
`)) {
|
|
5099
|
-
if (line.length === 0)
|
|
5100
|
-
continue;
|
|
5101
|
-
let parsed;
|
|
5102
|
-
try {
|
|
5103
|
-
parsed = JSON.parse(line);
|
|
5104
|
-
} catch {
|
|
5105
|
-
continue;
|
|
5106
|
-
}
|
|
5107
|
-
const usage = parsed.message?.usage;
|
|
5108
|
-
if (!usage)
|
|
5109
|
-
continue;
|
|
5110
|
-
if (typeof usage.input_tokens === "number")
|
|
5111
|
-
input += usage.input_tokens;
|
|
5112
|
-
if (typeof usage.output_tokens === "number")
|
|
5113
|
-
output += usage.output_tokens;
|
|
5114
|
-
if (typeof usage.cache_read_input_tokens === "number")
|
|
5115
|
-
cacheRead += usage.cache_read_input_tokens;
|
|
5116
|
-
if (typeof usage.cache_creation_input_tokens === "number")
|
|
5117
|
-
cacheCreate += usage.cache_creation_input_tokens;
|
|
5118
|
-
}
|
|
5119
|
-
}
|
|
5120
|
-
return {
|
|
5121
|
-
...base,
|
|
5122
|
-
inputTokens: input,
|
|
5123
|
-
outputTokens: output,
|
|
5124
|
-
cacheReadTokens: cacheRead,
|
|
5125
|
-
cacheCreateTokens: cacheCreate
|
|
5126
|
-
};
|
|
5127
|
-
}
|
|
5128
|
-
var init_cost = __esm(() => {
|
|
5129
|
-
init_history();
|
|
5130
|
-
});
|
|
5131
|
-
|
|
5132
5073
|
// src/cli/invocation.ts
|
|
5133
5074
|
import { fileURLToPath } from "url";
|
|
5134
5075
|
function kobeCliInvocation() {
|
|
@@ -5225,7 +5166,7 @@ function opsPaneCommand(args) {
|
|
|
5225
5166
|
var TASKS_PANE_WIDTH = 32, TASKS_WIDTH_OPTION = "@kobe_tasks_width", TASKS_PANE_WIDTH_MIN = 16, TASKS_PANE_WIDTH_MAX = 120, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50, RIGHT_COLUMN_WIDTH_OPTION = "@kobe_right_width_pct", OPS_HEIGHT_OPTION = "@kobe_ops_height_pct", PANE_PERCENT_MIN = 10, PANE_PERCENT_MAX = 90;
|
|
5226
5167
|
|
|
5227
5168
|
// src/engine/claude-code-local/hook-adapter.ts
|
|
5228
|
-
import { mkdir as mkdir5, readFile as
|
|
5169
|
+
import { mkdir as mkdir5, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
5229
5170
|
import { dirname as dirname4 } from "path";
|
|
5230
5171
|
function failureFromErrorType(errorType) {
|
|
5231
5172
|
if (typeof errorType !== "string")
|
|
@@ -5241,7 +5182,7 @@ function isObject2(v) {
|
|
|
5241
5182
|
}
|
|
5242
5183
|
async function readJsonObject(path8) {
|
|
5243
5184
|
try {
|
|
5244
|
-
const parsed = JSON.parse(await
|
|
5185
|
+
const parsed = JSON.parse(await readFile3(path8, "utf8"));
|
|
5245
5186
|
return isObject2(parsed) ? parsed : {};
|
|
5246
5187
|
} catch {
|
|
5247
5188
|
return {};
|
|
@@ -5455,7 +5396,7 @@ function validPositive(v) {
|
|
|
5455
5396
|
}
|
|
5456
5397
|
|
|
5457
5398
|
// src/engine/codex-local/history.ts
|
|
5458
|
-
import { readFile as
|
|
5399
|
+
import { readFile as readFile4, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
5459
5400
|
import { homedir as homedir9 } from "os";
|
|
5460
5401
|
import path8 from "path";
|
|
5461
5402
|
async function listRolloutFiles(deps = defaultDeps7) {
|
|
@@ -5502,6 +5443,26 @@ function rolloutCwd(raw) {
|
|
|
5502
5443
|
}
|
|
5503
5444
|
return "";
|
|
5504
5445
|
}
|
|
5446
|
+
async function rolloutCwdForFile(file, deps) {
|
|
5447
|
+
let cache = rolloutCwdCaches.get(deps);
|
|
5448
|
+
if (!cache) {
|
|
5449
|
+
cache = new Map;
|
|
5450
|
+
rolloutCwdCaches.set(deps, cache);
|
|
5451
|
+
}
|
|
5452
|
+
const hit = cache.get(file);
|
|
5453
|
+
if (hit !== undefined)
|
|
5454
|
+
return hit;
|
|
5455
|
+
let raw;
|
|
5456
|
+
try {
|
|
5457
|
+
raw = await deps.readFile(file);
|
|
5458
|
+
} catch {
|
|
5459
|
+
return null;
|
|
5460
|
+
}
|
|
5461
|
+
const cwd = rolloutCwd(raw);
|
|
5462
|
+
if (cwd)
|
|
5463
|
+
cache.set(file, cwd);
|
|
5464
|
+
return cwd;
|
|
5465
|
+
}
|
|
5505
5466
|
async function listSessionIdsForWorktree(worktree, deps = defaultDeps7) {
|
|
5506
5467
|
if (!worktree)
|
|
5507
5468
|
return [];
|
|
@@ -5512,13 +5473,7 @@ async function listSessionIdsForWorktree(worktree, deps = defaultDeps7) {
|
|
|
5512
5473
|
if (scanned >= MAX_WORKTREE_SCAN)
|
|
5513
5474
|
break;
|
|
5514
5475
|
scanned++;
|
|
5515
|
-
|
|
5516
|
-
try {
|
|
5517
|
-
raw = await deps.readFile(file);
|
|
5518
|
-
} catch {
|
|
5519
|
-
continue;
|
|
5520
|
-
}
|
|
5521
|
-
if (rolloutCwd(raw) !== worktree)
|
|
5476
|
+
if (await rolloutCwdForFile(file, deps) !== worktree)
|
|
5522
5477
|
continue;
|
|
5523
5478
|
const id = path8.basename(file).match(UUID_AT_END)?.[1];
|
|
5524
5479
|
if (id)
|
|
@@ -5526,30 +5481,25 @@ async function listSessionIdsForWorktree(worktree, deps = defaultDeps7) {
|
|
|
5526
5481
|
}
|
|
5527
5482
|
return matches.reverse();
|
|
5528
5483
|
}
|
|
5529
|
-
async function
|
|
5484
|
+
async function findLatestRolloutForWorktree(worktree, deps = defaultDeps7) {
|
|
5530
5485
|
if (!worktree)
|
|
5531
|
-
return
|
|
5486
|
+
return null;
|
|
5532
5487
|
const files = await listRolloutFiles(deps);
|
|
5533
5488
|
let scanned = 0;
|
|
5534
5489
|
for (const file of files) {
|
|
5535
5490
|
if (scanned >= MAX_MTIME_SCAN)
|
|
5536
5491
|
break;
|
|
5537
5492
|
scanned++;
|
|
5538
|
-
|
|
5539
|
-
try {
|
|
5540
|
-
raw = await deps.readFile(file);
|
|
5541
|
-
} catch {
|
|
5542
|
-
continue;
|
|
5543
|
-
}
|
|
5544
|
-
if (rolloutCwd(raw) !== worktree)
|
|
5493
|
+
if (await rolloutCwdForFile(file, deps) !== worktree)
|
|
5545
5494
|
continue;
|
|
5546
5495
|
try {
|
|
5547
|
-
return (await deps.stat(file)).mtimeMs;
|
|
5548
|
-
} catch {
|
|
5549
|
-
return 0;
|
|
5550
|
-
}
|
|
5496
|
+
return { path: file, mtimeMs: (await deps.stat(file)).mtimeMs };
|
|
5497
|
+
} catch {}
|
|
5551
5498
|
}
|
|
5552
|
-
return
|
|
5499
|
+
return null;
|
|
5500
|
+
}
|
|
5501
|
+
async function latestTranscriptMtimeForWorktree2(worktree, deps = defaultDeps7) {
|
|
5502
|
+
return (await findLatestRolloutForWorktree(worktree, deps))?.mtimeMs ?? 0;
|
|
5553
5503
|
}
|
|
5554
5504
|
async function readHistory2(sessionId, deps = defaultDeps7) {
|
|
5555
5505
|
return (await readHistoryWithMetrics(sessionId, deps)).messages;
|
|
@@ -5782,7 +5732,7 @@ function parseTimestampMs(value) {
|
|
|
5782
5732
|
function isObject4(v) {
|
|
5783
5733
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5784
5734
|
}
|
|
5785
|
-
var defaultDeps7, UUID_AT_END, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
|
|
5735
|
+
var defaultDeps7, UUID_AT_END, rolloutCwdCaches, MAX_WORKTREE_SCAN = 200, MAX_MTIME_SCAN = 12;
|
|
5786
5736
|
var init_history2 = __esm(() => {
|
|
5787
5737
|
init_synthetic();
|
|
5788
5738
|
defaultDeps7 = {
|
|
@@ -5797,11 +5747,12 @@ var init_history2 = __esm(() => {
|
|
|
5797
5747
|
}
|
|
5798
5748
|
},
|
|
5799
5749
|
async readFile(p) {
|
|
5800
|
-
return await
|
|
5750
|
+
return await readFile4(p, "utf8");
|
|
5801
5751
|
},
|
|
5802
5752
|
stat: stat2
|
|
5803
5753
|
};
|
|
5804
5754
|
UUID_AT_END = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
5755
|
+
rolloutCwdCaches = new WeakMap;
|
|
5805
5756
|
});
|
|
5806
5757
|
|
|
5807
5758
|
// src/engine/copilot-local/usage.ts
|
|
@@ -5839,7 +5790,7 @@ function numberOr2(value, fallback) {
|
|
|
5839
5790
|
}
|
|
5840
5791
|
|
|
5841
5792
|
// src/engine/copilot-local/history.ts
|
|
5842
|
-
import { readFile as
|
|
5793
|
+
import { readFile as readFile5, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
5843
5794
|
import { homedir as homedir10 } from "os";
|
|
5844
5795
|
import path9 from "path";
|
|
5845
5796
|
async function listSessionDirs(deps = defaultDeps8) {
|
|
@@ -6019,7 +5970,7 @@ var init_history3 = __esm(() => {
|
|
|
6019
5970
|
}
|
|
6020
5971
|
},
|
|
6021
5972
|
async readFile(p) {
|
|
6022
|
-
return await
|
|
5973
|
+
return await readFile5(p, "utf8");
|
|
6023
5974
|
},
|
|
6024
5975
|
stat: stat3,
|
|
6025
5976
|
async rm(p) {
|
|
@@ -6058,7 +6009,7 @@ var init_hook_adapter2 = __esm(() => {
|
|
|
6058
6009
|
});
|
|
6059
6010
|
|
|
6060
6011
|
// src/engine/turn-detector.ts
|
|
6061
|
-
import { readFile as
|
|
6012
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
6062
6013
|
|
|
6063
6014
|
class EngineTurnDetector {
|
|
6064
6015
|
supportsCompletionMarkers() {
|
|
@@ -6131,42 +6082,71 @@ function timestampFromRecord(record, fallback) {
|
|
|
6131
6082
|
function isObject7(v) {
|
|
6132
6083
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6133
6084
|
}
|
|
6134
|
-
var ClaudeTurnDetector, CodexTurnDetector, UnknownTurnDetector;
|
|
6085
|
+
var defaultClaudeDeps, ClaudeTurnDetector, defaultCodexDeps, CodexTurnDetector, UnknownTurnDetector;
|
|
6135
6086
|
var init_turn_detector = __esm(() => {
|
|
6136
6087
|
init_history();
|
|
6137
6088
|
init_history2();
|
|
6138
6089
|
init_registry();
|
|
6090
|
+
defaultClaudeDeps = {
|
|
6091
|
+
listSessionFiles: (worktree) => listSessionFilesForWorktree(worktree),
|
|
6092
|
+
readFile: (path10) => readFile6(path10, "utf8")
|
|
6093
|
+
};
|
|
6139
6094
|
ClaudeTurnDetector = class ClaudeTurnDetector extends EngineTurnDetector {
|
|
6095
|
+
deps;
|
|
6140
6096
|
vendor = "claude";
|
|
6097
|
+
cache = new Map;
|
|
6098
|
+
constructor(deps = defaultClaudeDeps) {
|
|
6099
|
+
super();
|
|
6100
|
+
this.deps = deps;
|
|
6101
|
+
}
|
|
6141
6102
|
async latestCompletion(worktree) {
|
|
6142
|
-
const files = await
|
|
6103
|
+
const files = await this.deps.listSessionFiles(worktree);
|
|
6143
6104
|
let latest = null;
|
|
6105
|
+
const next = new Map;
|
|
6144
6106
|
for (const file of files.slice(0, 4)) {
|
|
6145
|
-
const
|
|
6146
|
-
|
|
6107
|
+
const hit = this.cache.get(file.path);
|
|
6108
|
+
let marker;
|
|
6109
|
+
if (hit && file.mtimeMs > 0 && hit.mtimeMs === file.mtimeMs) {
|
|
6110
|
+
marker = hit.marker;
|
|
6111
|
+
} else {
|
|
6112
|
+
const raw = await this.deps.readFile(file.path).catch(() => "");
|
|
6113
|
+
marker = latestClaudeCompletionMarkerFromJsonl(raw, file.path, file.mtimeMs);
|
|
6114
|
+
}
|
|
6115
|
+
next.set(file.path, { mtimeMs: file.mtimeMs, marker });
|
|
6147
6116
|
if (marker && (!latest || marker.timestampMs > latest.timestampMs))
|
|
6148
6117
|
latest = marker;
|
|
6149
6118
|
}
|
|
6119
|
+
this.cache = next;
|
|
6150
6120
|
return latest;
|
|
6151
6121
|
}
|
|
6152
6122
|
};
|
|
6123
|
+
defaultCodexDeps = {
|
|
6124
|
+
findLatestRollout: (worktree) => findLatestRolloutForWorktree(worktree),
|
|
6125
|
+
readFile: (path10) => readFile6(path10, "utf8")
|
|
6126
|
+
};
|
|
6153
6127
|
CodexTurnDetector = class CodexTurnDetector extends EngineTurnDetector {
|
|
6128
|
+
deps;
|
|
6154
6129
|
vendor = "codex";
|
|
6130
|
+
cache = null;
|
|
6131
|
+
constructor(deps = defaultCodexDeps) {
|
|
6132
|
+
super();
|
|
6133
|
+
this.deps = deps;
|
|
6134
|
+
}
|
|
6155
6135
|
async latestCompletion(worktree) {
|
|
6156
6136
|
if (!worktree)
|
|
6157
6137
|
return null;
|
|
6158
|
-
const
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
scanned++;
|
|
6164
|
-
const raw = await readFile7(file, "utf8").catch(() => "");
|
|
6165
|
-
if (!raw || rolloutCwd(raw) !== worktree)
|
|
6166
|
-
continue;
|
|
6167
|
-
return latestCodexCompletionMarkerFromJsonl(raw, file);
|
|
6138
|
+
const found = await this.deps.findLatestRollout(worktree);
|
|
6139
|
+
if (!found)
|
|
6140
|
+
return null;
|
|
6141
|
+
if (this.cache && this.cache.path === found.path && found.mtimeMs > 0 && this.cache.mtimeMs === found.mtimeMs) {
|
|
6142
|
+
return this.cache.marker;
|
|
6168
6143
|
}
|
|
6169
|
-
|
|
6144
|
+
const raw = await this.deps.readFile(found.path).catch(() => "");
|
|
6145
|
+
if (!raw)
|
|
6146
|
+
return null;
|
|
6147
|
+
const marker = latestCodexCompletionMarkerFromJsonl(raw, found.path);
|
|
6148
|
+
this.cache = { path: found.path, mtimeMs: found.mtimeMs, marker };
|
|
6149
|
+
return marker;
|
|
6170
6150
|
}
|
|
6171
6151
|
};
|
|
6172
6152
|
UnknownTurnDetector = class UnknownTurnDetector extends EngineTurnDetector {
|
|
@@ -6192,7 +6172,6 @@ function customEngineEntry(vendor) {
|
|
|
6192
6172
|
displayName: vendor,
|
|
6193
6173
|
defaultCommand: [vendor],
|
|
6194
6174
|
history: EMPTY_HISTORY,
|
|
6195
|
-
summarizeCost: null,
|
|
6196
6175
|
detectAccount: async () => ({
|
|
6197
6176
|
binary: { found: false, error: "custom engine: kobe has no account detector for it" },
|
|
6198
6177
|
account: { kind: "none" }
|
|
@@ -6208,7 +6187,6 @@ var EMPTY_HISTORY, claudeHistoryReader, codexHistoryReader, copilotHistoryReader
|
|
|
6208
6187
|
var init_registry = __esm(() => {
|
|
6209
6188
|
init_vendor();
|
|
6210
6189
|
init_account_detect();
|
|
6211
|
-
init_cost();
|
|
6212
6190
|
init_history();
|
|
6213
6191
|
init_hook_adapter();
|
|
6214
6192
|
init_history2();
|
|
@@ -6251,7 +6229,6 @@ var init_registry = __esm(() => {
|
|
|
6251
6229
|
displayName: "Claude",
|
|
6252
6230
|
defaultCommand: ["claude"],
|
|
6253
6231
|
history: claudeHistoryReader,
|
|
6254
|
-
summarizeCost: (worktree) => summarizeClaudeWorktreeCost(worktree),
|
|
6255
6232
|
detectAccount: (deps) => detectClaudeAccount(deps),
|
|
6256
6233
|
createHookAdapter: () => new ClaudeHookAdapter,
|
|
6257
6234
|
createTurnDetector: () => new ClaudeTurnDetector
|
|
@@ -6262,7 +6239,6 @@ var init_registry = __esm(() => {
|
|
|
6262
6239
|
displayName: "Codex",
|
|
6263
6240
|
defaultCommand: ["codex"],
|
|
6264
6241
|
history: codexHistoryReader,
|
|
6265
|
-
summarizeCost: null,
|
|
6266
6242
|
detectAccount: (deps) => detectCodexAccount(deps),
|
|
6267
6243
|
createHookAdapter: () => new NoopHookAdapter("codex"),
|
|
6268
6244
|
createTurnDetector: () => new CodexTurnDetector
|
|
@@ -6273,7 +6249,6 @@ var init_registry = __esm(() => {
|
|
|
6273
6249
|
displayName: "Copilot",
|
|
6274
6250
|
defaultCommand: ["copilot"],
|
|
6275
6251
|
history: copilotHistoryReader,
|
|
6276
|
-
summarizeCost: null,
|
|
6277
6252
|
detectAccount: (deps) => detectCopilotAccount(deps),
|
|
6278
6253
|
createHookAdapter: () => new NoopHookAdapter("copilot"),
|
|
6279
6254
|
createTurnDetector: () => new UnknownTurnDetector("copilot")
|
|
@@ -6342,6 +6317,7 @@ __export(exports_client, {
|
|
|
6342
6317
|
globalTasksPaneWidth: () => globalTasksPaneWidth,
|
|
6343
6318
|
getSessionOptions: () => getSessionOptions,
|
|
6344
6319
|
getSessionOption: () => getSessionOption,
|
|
6320
|
+
getServerOptions: () => getServerOptions,
|
|
6345
6321
|
getServerOption: () => getServerOption,
|
|
6346
6322
|
ensureFallbackSession: () => ensureFallbackSession,
|
|
6347
6323
|
currentSessionName: () => currentSessionName,
|
|
@@ -6480,6 +6456,22 @@ async function getServerOption(option) {
|
|
|
6480
6456
|
const { code, stdout } = await runTmuxCapturing(["show-options", "-sqv", option]);
|
|
6481
6457
|
return code === 0 ? stdout.trim() : "";
|
|
6482
6458
|
}
|
|
6459
|
+
async function getServerOptions(options) {
|
|
6460
|
+
const values = Object.fromEntries(options.map((option) => [option, undefined]));
|
|
6461
|
+
const { code, stdout } = await runTmuxSequenceCapturing(options.map((option) => ["show-options", "-sq", option]));
|
|
6462
|
+
if (code !== 0)
|
|
6463
|
+
return values;
|
|
6464
|
+
for (const line of stdout.split(`
|
|
6465
|
+
`)) {
|
|
6466
|
+
const idx = line.indexOf(" ");
|
|
6467
|
+
if (idx <= 0)
|
|
6468
|
+
continue;
|
|
6469
|
+
const option = line.slice(0, idx);
|
|
6470
|
+
if (option in values)
|
|
6471
|
+
values[option] = line.slice(idx + 1).trim();
|
|
6472
|
+
}
|
|
6473
|
+
return values;
|
|
6474
|
+
}
|
|
6483
6475
|
async function globalTasksPaneWidth() {
|
|
6484
6476
|
const raw = await getServerOption(TASKS_WIDTH_OPTION);
|
|
6485
6477
|
const n = Number.parseInt(raw, 10);
|
|
@@ -6637,18 +6629,18 @@ async function listChatTabWindows(session, runner = realRunner) {
|
|
|
6637
6629
|
"-t",
|
|
6638
6630
|
`=${session}`,
|
|
6639
6631
|
"-F",
|
|
6640
|
-
`#{window_index} #{${CHAT_TAB_SESSION_ID_OPTION}}`
|
|
6632
|
+
`#{window_index} #{automatic-rename} #{${CHAT_TAB_SESSION_ID_OPTION}}`
|
|
6641
6633
|
]);
|
|
6642
6634
|
if (code !== 0)
|
|
6643
6635
|
return [];
|
|
6644
6636
|
const out = [];
|
|
6645
6637
|
for (const line of stdout.split(`
|
|
6646
6638
|
`)) {
|
|
6647
|
-
const
|
|
6648
|
-
const index = Number.parseInt((
|
|
6639
|
+
const [indexField, autoRename, sessionId] = line.split("\t");
|
|
6640
|
+
const index = Number.parseInt((indexField ?? "").trim(), 10);
|
|
6649
6641
|
if (!Number.isInteger(index))
|
|
6650
6642
|
continue;
|
|
6651
|
-
out.push({ index, sessionId:
|
|
6643
|
+
out.push({ index, sessionId: sessionId?.trim() ?? "", autoRename: autoRename?.trim() ?? "" });
|
|
6652
6644
|
}
|
|
6653
6645
|
return out;
|
|
6654
6646
|
}
|
|
@@ -6661,11 +6653,27 @@ async function windowNamedManually(session, index, runner) {
|
|
|
6661
6653
|
]);
|
|
6662
6654
|
return code === 0 && /\boff\b/.test(stdout);
|
|
6663
6655
|
}
|
|
6656
|
+
async function globalAutomaticRenameOff(runner) {
|
|
6657
|
+
const { code, stdout } = await runner.capture(["show-window-options", "-g", "automatic-rename"]);
|
|
6658
|
+
return code === 0 && /\boff\b/.test(stdout);
|
|
6659
|
+
}
|
|
6664
6660
|
async function renameWindow(session, index, title, runner) {
|
|
6665
6661
|
return await runner.run(["rename-window", "-t", `=${session}:${index}`, "--", title]) === 0;
|
|
6666
6662
|
}
|
|
6667
6663
|
async function runChatTabNamingPass(orch, deps = realDeps) {
|
|
6668
6664
|
let renamed = 0;
|
|
6665
|
+
let globalOff = null;
|
|
6666
|
+
const manuallyNamed = async (session, w) => {
|
|
6667
|
+
if (w.autoRename === "1")
|
|
6668
|
+
return false;
|
|
6669
|
+
if (w.autoRename === "0") {
|
|
6670
|
+
if (globalOff === null)
|
|
6671
|
+
globalOff = await globalAutomaticRenameOff(deps.runner);
|
|
6672
|
+
if (!globalOff)
|
|
6673
|
+
return true;
|
|
6674
|
+
}
|
|
6675
|
+
return windowNamedManually(session, w.index, deps.runner);
|
|
6676
|
+
};
|
|
6669
6677
|
for (const task of orch.listTasks()) {
|
|
6670
6678
|
if (task.archived || task.kind === "main" || !task.worktreePath)
|
|
6671
6679
|
continue;
|
|
@@ -6677,7 +6685,7 @@ async function runChatTabNamingPass(orch, deps = realDeps) {
|
|
|
6677
6685
|
const vendor = task.vendor ?? DEFAULT_TASK_VENDOR;
|
|
6678
6686
|
for (const w of windows) {
|
|
6679
6687
|
try {
|
|
6680
|
-
if (await
|
|
6688
|
+
if (await manuallyNamed(session, w))
|
|
6681
6689
|
continue;
|
|
6682
6690
|
const title = w.sessionId ? await deps.titleFromSessionId(vendor, w.sessionId) : w.index === originIndex ? await deps.titleFromWorktree(task.worktreePath, vendor) : "";
|
|
6683
6691
|
if (title && await renameWindow(session, w.index, title, deps.runner))
|
|
@@ -7583,7 +7591,7 @@ var init_worktree_changes_collector = __esm(() => {
|
|
|
7583
7591
|
});
|
|
7584
7592
|
|
|
7585
7593
|
// ../kobe-daemon/src/daemon/server.ts
|
|
7586
|
-
import { mkdir as mkdir6, readFile as
|
|
7594
|
+
import { mkdir as mkdir6, readFile as readFile7, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
7587
7595
|
import { createServer } from "net";
|
|
7588
7596
|
import { dirname as dirname7 } from "path";
|
|
7589
7597
|
function resolveIdleGraceMs() {
|
|
@@ -7797,7 +7805,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7797
7805
|
}
|
|
7798
7806
|
async function readPidFile(pidPath) {
|
|
7799
7807
|
try {
|
|
7800
|
-
const raw = await
|
|
7808
|
+
const raw = await readFile7(pidPath, "utf8");
|
|
7801
7809
|
const pid = Number(raw.trim());
|
|
7802
7810
|
return Number.isFinite(pid) ? pid : null;
|
|
7803
7811
|
} catch {
|
|
@@ -7808,10 +7816,12 @@ function writeFrame(client, frame) {
|
|
|
7808
7816
|
client.socket.write(frameToLine(frame));
|
|
7809
7817
|
}
|
|
7810
7818
|
function broadcast(clients, frame) {
|
|
7819
|
+
let line = null;
|
|
7811
7820
|
for (const client of clients) {
|
|
7812
7821
|
if (!client.subscribed && frame.type === "event")
|
|
7813
7822
|
continue;
|
|
7814
|
-
|
|
7823
|
+
line ??= frameToLine(frame);
|
|
7824
|
+
client.socket.write(line);
|
|
7815
7825
|
}
|
|
7816
7826
|
}
|
|
7817
7827
|
var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000;
|
|
@@ -10456,10 +10466,17 @@ function parseKobePaneRows(stdout) {
|
|
|
10456
10466
|
const line = raw.trim();
|
|
10457
10467
|
if (!line)
|
|
10458
10468
|
continue;
|
|
10459
|
-
const [windowId, paneId, role, version] = line.split("\t");
|
|
10469
|
+
const [windowId, paneId, role, version, paneWidth] = line.split("\t");
|
|
10460
10470
|
if (!windowId || !paneId || !role)
|
|
10461
10471
|
continue;
|
|
10462
|
-
|
|
10472
|
+
const width = Number.parseInt(paneWidth?.trim() ?? "", 10);
|
|
10473
|
+
rows.push({
|
|
10474
|
+
windowId: windowId.trim(),
|
|
10475
|
+
paneId: paneId.trim(),
|
|
10476
|
+
role: role.trim(),
|
|
10477
|
+
version: version?.trim() ?? "",
|
|
10478
|
+
...Number.isFinite(width) ? { paneWidth: width } : {}
|
|
10479
|
+
});
|
|
10463
10480
|
}
|
|
10464
10481
|
return rows;
|
|
10465
10482
|
}
|
|
@@ -10524,38 +10541,18 @@ async function relaunchEngineInAllWindows(session, cwd, command, remoteKey) {
|
|
|
10524
10541
|
if (enginePanes.length === 0)
|
|
10525
10542
|
return false;
|
|
10526
10543
|
const cmd = keepAlive(wrapEngineLaunch(shellQuoteArgv(command), remoteKey, cwd));
|
|
10527
|
-
|
|
10528
|
-
await runTmux(["respawn-pane", "-k", "-c", localSpawnCwd(cwd), "-t", pane, cmd]);
|
|
10529
|
-
}
|
|
10544
|
+
await runTmuxSequence(enginePanes.map((pane) => ["respawn-pane", "-k", "-c", localSpawnCwd(cwd), "-t", pane, cmd]));
|
|
10530
10545
|
return true;
|
|
10531
10546
|
}
|
|
10532
|
-
async function
|
|
10533
|
-
const
|
|
10534
|
-
const
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
"
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
]);
|
|
10542
|
-
if (code !== 0)
|
|
10543
|
-
return;
|
|
10544
|
-
const mismatched = stdout.split(`
|
|
10545
|
-
`).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "tasks").filter(([, , width]) => Number.parseInt(width?.trim() ?? "", 10) !== target).map(([id]) => id?.trim()).filter((id) => !!id);
|
|
10546
|
-
if (mismatched.length === 0)
|
|
10547
|
-
return;
|
|
10548
|
-
await runTmuxSequence(mismatched.map((pane) => ["resize-pane", "-t", pane, "-x", `${target}`]));
|
|
10549
|
-
}
|
|
10550
|
-
async function rightColumnPercents() {
|
|
10551
|
-
const [width, height] = await Promise.all([
|
|
10552
|
-
getServerOption(RIGHT_COLUMN_WIDTH_OPTION),
|
|
10553
|
-
getServerOption(OPS_HEIGHT_OPTION)
|
|
10554
|
-
]);
|
|
10555
|
-
return {
|
|
10556
|
-
widthPct: clampPanePercent(Number.parseInt(width, 10)),
|
|
10557
|
-
heightPct: clampPanePercent(Number.parseInt(height, 10))
|
|
10558
|
-
};
|
|
10547
|
+
async function globalLayoutPrefs() {
|
|
10548
|
+
const opts = await getServerOptions([TASKS_WIDTH_OPTION, RIGHT_COLUMN_WIDTH_OPTION, OPS_HEIGHT_OPTION]);
|
|
10549
|
+
const rawWidth = Number.parseInt(opts[TASKS_WIDTH_OPTION] ?? "", 10);
|
|
10550
|
+
const tasksWidth = Number.isFinite(rawWidth) && rawWidth > 0 ? clampTasksPaneWidth(rawWidth) : TASKS_PANE_WIDTH;
|
|
10551
|
+
const rcArgs = rightColumnResizeArgs({
|
|
10552
|
+
widthPct: clampPanePercent(Number.parseInt(opts[RIGHT_COLUMN_WIDTH_OPTION] ?? "", 10)),
|
|
10553
|
+
heightPct: clampPanePercent(Number.parseInt(opts[OPS_HEIGHT_OPTION] ?? "", 10))
|
|
10554
|
+
});
|
|
10555
|
+
return { tasksWidth, rcArgs };
|
|
10559
10556
|
}
|
|
10560
10557
|
function rightColumnResizeArgs(geom) {
|
|
10561
10558
|
const args = [];
|
|
@@ -10566,33 +10563,35 @@ function rightColumnResizeArgs(geom) {
|
|
|
10566
10563
|
return args;
|
|
10567
10564
|
}
|
|
10568
10565
|
async function globalRightColumnResizeArgs() {
|
|
10569
|
-
return
|
|
10566
|
+
return (await globalLayoutPrefs()).rcArgs;
|
|
10570
10567
|
}
|
|
10571
|
-
async function
|
|
10572
|
-
const
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
const { code, stdout } = await runTmuxCapturing([
|
|
10576
|
-
"list-panes",
|
|
10577
|
-
"-s",
|
|
10578
|
-
"-t",
|
|
10579
|
-
`=${session}`,
|
|
10580
|
-
"-F",
|
|
10581
|
-
"#{pane_id}\t#{@kobe_role}"
|
|
10582
|
-
]);
|
|
10583
|
-
if (code !== 0)
|
|
10584
|
-
return;
|
|
10585
|
-
const opsPanes = stdout.split(`
|
|
10586
|
-
`).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "ops").map(([id]) => id?.trim()).filter((id) => !!id);
|
|
10587
|
-
if (opsPanes.length === 0)
|
|
10568
|
+
async function healWorkspaceLayout(session, versions) {
|
|
10569
|
+
const { tasksWidth, rcArgs } = await globalLayoutPrefs();
|
|
10570
|
+
const rows = await listKobePanes(session);
|
|
10571
|
+
if (!rows)
|
|
10588
10572
|
return;
|
|
10589
|
-
|
|
10573
|
+
const commands = [];
|
|
10574
|
+
for (const row of rows) {
|
|
10575
|
+
if (row.role === "tasks" && row.paneWidth !== tasksWidth) {
|
|
10576
|
+
commands.push(["resize-pane", "-t", row.paneId, "-x", `${tasksWidth}`]);
|
|
10577
|
+
}
|
|
10578
|
+
}
|
|
10579
|
+
if (rcArgs.length > 0) {
|
|
10580
|
+
for (const row of rows) {
|
|
10581
|
+
if (row.role === "ops")
|
|
10582
|
+
commands.push(["resize-pane", "-t", row.paneId, ...rcArgs]);
|
|
10583
|
+
}
|
|
10584
|
+
}
|
|
10585
|
+
if (versions) {
|
|
10586
|
+
commands.push(...respawnCommandsFor(planPaneHeals(rows, { currentVersion: CURRENT_VERSION, force: false }), versions));
|
|
10587
|
+
}
|
|
10588
|
+
if (commands.length > 0)
|
|
10589
|
+
await runTmuxSequence(commands);
|
|
10590
10590
|
}
|
|
10591
10591
|
async function healSessionLayout(session) {
|
|
10592
10592
|
if (!await sessionExists(session))
|
|
10593
10593
|
return;
|
|
10594
|
-
await
|
|
10595
|
-
await healRightColumn(session);
|
|
10594
|
+
await healWorkspaceLayout(session);
|
|
10596
10595
|
}
|
|
10597
10596
|
async function captureGlobalLayout(session) {
|
|
10598
10597
|
const { code, stdout } = await runTmuxCapturing([
|
|
@@ -10631,18 +10630,6 @@ async function captureGlobalLayout(session) {
|
|
|
10631
10630
|
if (sets.length > 0)
|
|
10632
10631
|
await runTmuxSequence(sets);
|
|
10633
10632
|
}
|
|
10634
|
-
async function healKobePaneVersions(session, cwd, taskId, vendor) {
|
|
10635
|
-
const rows = await listKobePanes(session);
|
|
10636
|
-
if (!rows)
|
|
10637
|
-
return;
|
|
10638
|
-
const commands = respawnCommandsFor(planPaneHeals(rows, { currentVersion: CURRENT_VERSION, force: false }), {
|
|
10639
|
-
cwd,
|
|
10640
|
-
taskId,
|
|
10641
|
-
vendor
|
|
10642
|
-
});
|
|
10643
|
-
if (commands.length > 0)
|
|
10644
|
-
await runTmuxSequence(commands);
|
|
10645
|
-
}
|
|
10646
10633
|
async function refreshKobeWorkspacePanes(session) {
|
|
10647
10634
|
const sessionOptions = await getSessionOptions(session, ["@kobe_worktree", "@kobe_task", "@kobe_vendor"]);
|
|
10648
10635
|
const cwd = sessionOptions["@kobe_worktree"] || process.cwd();
|
|
@@ -10668,7 +10655,7 @@ var init_pane_heal = __esm(() => {
|
|
|
10668
10655
|
init_tmux_border_theme();
|
|
10669
10656
|
init_version();
|
|
10670
10657
|
init_launch();
|
|
10671
|
-
KOBE_PANE_LIST_FORMAT = `#{window_id} #{pane_id} #{@kobe_role} #{${PANE_VERSION_OPTION}}`;
|
|
10658
|
+
KOBE_PANE_LIST_FORMAT = `#{window_id} #{pane_id} #{@kobe_role} #{${PANE_VERSION_OPTION}} #{pane_width}`;
|
|
10672
10659
|
});
|
|
10673
10660
|
|
|
10674
10661
|
// src/tui/panes/terminal/chattab.ts
|
|
@@ -10898,6 +10885,7 @@ __export(exports_tmux, {
|
|
|
10898
10885
|
refreshKobeWorkspacePanes: () => refreshKobeWorkspacePanes,
|
|
10899
10886
|
quickCreate: () => quickCreate,
|
|
10900
10887
|
prepareWindowForAttach: () => prepareWindowForAttach,
|
|
10888
|
+
parseObservedSession: () => parseObservedSession,
|
|
10901
10889
|
openUpdateTab: () => openUpdateTab,
|
|
10902
10890
|
openSettingsTab: () => openSettingsTab,
|
|
10903
10891
|
openNewTaskTab: () => openNewTaskTab,
|
|
@@ -10906,6 +10894,7 @@ __export(exports_tmux, {
|
|
|
10906
10894
|
kobeStatusRight: () => kobeStatusRight,
|
|
10907
10895
|
killSession: () => killSession,
|
|
10908
10896
|
healSessionLayout: () => healSessionLayout,
|
|
10897
|
+
focusBindCommand: () => focusBindCommand,
|
|
10909
10898
|
ensureSession: () => ensureSession,
|
|
10910
10899
|
currentSessionName: () => currentSessionName,
|
|
10911
10900
|
chatTabSwitchBindings: () => chatTabSwitchBindings,
|
|
@@ -10933,8 +10922,18 @@ async function prepareWindowForAttach(session) {
|
|
|
10933
10922
|
const sizeArgs = tmuxInitialSizeArgs();
|
|
10934
10923
|
if (sizeArgs.length > 0)
|
|
10935
10924
|
await runTmux(["resize-window", "-t", `=${session}`, ...sizeArgs]);
|
|
10936
|
-
await
|
|
10937
|
-
|
|
10925
|
+
await healWorkspaceLayout(session);
|
|
10926
|
+
}
|
|
10927
|
+
function focusBindCommand(key, dir) {
|
|
10928
|
+
return [
|
|
10929
|
+
"bind-key",
|
|
10930
|
+
"-n",
|
|
10931
|
+
key,
|
|
10932
|
+
"if-shell",
|
|
10933
|
+
"-F",
|
|
10934
|
+
`#{?window_zoomed_flag,1,#{?${FOCUS_EDGE_VARS[dir]},,1}}`,
|
|
10935
|
+
`select-pane ${dir}`
|
|
10936
|
+
];
|
|
10938
10937
|
}
|
|
10939
10938
|
async function ensureSession(opts) {
|
|
10940
10939
|
const inflight = ensureSessionLocks.get(opts.name);
|
|
@@ -10948,16 +10947,33 @@ async function ensureSession(opts) {
|
|
|
10948
10947
|
ensureSessionLocks.delete(opts.name);
|
|
10949
10948
|
}
|
|
10950
10949
|
}
|
|
10950
|
+
function parseObservedSession(stdout) {
|
|
10951
|
+
let worktree = "";
|
|
10952
|
+
let vendor = "";
|
|
10953
|
+
let claudePaneAlive = false;
|
|
10954
|
+
const windows = new Set;
|
|
10955
|
+
for (const line of stdout.split(`
|
|
10956
|
+
`)) {
|
|
10957
|
+
const [windowId, active, role, wt, vd] = line.split("\t");
|
|
10958
|
+
if (!windowId?.trim())
|
|
10959
|
+
continue;
|
|
10960
|
+
windows.add(windowId.trim());
|
|
10961
|
+
if (!worktree && wt?.trim())
|
|
10962
|
+
worktree = wt.trim();
|
|
10963
|
+
if (!vendor && vd?.trim())
|
|
10964
|
+
vendor = vd.trim();
|
|
10965
|
+
if (active?.trim() === "1" && role?.trim() === "claude")
|
|
10966
|
+
claudePaneAlive = true;
|
|
10967
|
+
}
|
|
10968
|
+
return { worktree, vendor, claudePaneAlive, windowCount: windows.size };
|
|
10969
|
+
}
|
|
10951
10970
|
async function observeSession(name) {
|
|
10952
10971
|
if (!await sessionExists(name))
|
|
10953
10972
|
return null;
|
|
10954
|
-
const
|
|
10955
|
-
|
|
10956
|
-
worktree:
|
|
10957
|
-
|
|
10958
|
-
claudePaneAlive: await claudePaneIdStrict(name) !== "",
|
|
10959
|
-
windowCount: await windowCount(name)
|
|
10960
|
-
};
|
|
10973
|
+
const { code, stdout } = await runTmuxCapturing(["list-panes", "-s", "-t", `=${name}`, "-F", OBSERVE_SESSION_FORMAT]);
|
|
10974
|
+
if (code !== 0)
|
|
10975
|
+
return { worktree: "", vendor: "", claudePaneAlive: false, windowCount: 0 };
|
|
10976
|
+
return parseObservedSession(stdout);
|
|
10961
10977
|
}
|
|
10962
10978
|
async function ensureSessionImpl(opts) {
|
|
10963
10979
|
const observed = await observeSession(opts.name);
|
|
@@ -10968,18 +10984,14 @@ async function ensureSessionImpl(opts) {
|
|
|
10968
10984
|
});
|
|
10969
10985
|
const remoteKey = remoteKeyForRepo(opts.repo);
|
|
10970
10986
|
if (action.kind === "reuse") {
|
|
10971
|
-
await
|
|
10972
|
-
await healRightColumn(opts.name);
|
|
10973
|
-
await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
|
|
10987
|
+
await healWorkspaceLayout(opts.name, { cwd: opts.cwd, taskId: opts.taskId, vendor: opts.vendor });
|
|
10974
10988
|
return true;
|
|
10975
10989
|
}
|
|
10976
10990
|
if (action.kind === "respawn-engine") {
|
|
10977
10991
|
if (await relaunchEngineInAllWindows(opts.name, opts.cwd, opts.command, remoteKey)) {
|
|
10978
10992
|
if (opts.vendor)
|
|
10979
10993
|
await setSessionOption(opts.name, "@kobe_vendor", opts.vendor);
|
|
10980
|
-
await
|
|
10981
|
-
await healRightColumn(opts.name);
|
|
10982
|
-
await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
|
|
10994
|
+
await healWorkspaceLayout(opts.name, { cwd: opts.cwd, taskId: opts.taskId, vendor: opts.vendor });
|
|
10983
10995
|
return true;
|
|
10984
10996
|
}
|
|
10985
10997
|
}
|
|
@@ -11054,7 +11066,7 @@ async function ensureSessionImpl(opts) {
|
|
|
11054
11066
|
const focusDirections = ["-L", "-D", "-U", "-R"];
|
|
11055
11067
|
const focusBinds = userKeys.focus.flatMap((bind, i) => {
|
|
11056
11068
|
const dir = focusDirections[i];
|
|
11057
|
-
return bind && dir ? [
|
|
11069
|
+
return bind && dir ? [focusBindCommand(bind.key, dir)] : [];
|
|
11058
11070
|
});
|
|
11059
11071
|
const b = userKeys.binds;
|
|
11060
11072
|
await runTmuxSequence([
|
|
@@ -11114,7 +11126,7 @@ async function selectTasksPane(session) {
|
|
|
11114
11126
|
await runTmux(["select-pane", "-t", tasksPane]);
|
|
11115
11127
|
return tasksPane;
|
|
11116
11128
|
}
|
|
11117
|
-
var ensureSessionLocks;
|
|
11129
|
+
var FOCUS_EDGE_VARS, ensureSessionLocks, OBSERVE_SESSION_FORMAT = "#{window_id}\t#{window_active}\t#{@kobe_role}\t#{@kobe_worktree}\t#{@kobe_vendor}";
|
|
11118
11130
|
var init_tmux = __esm(() => {
|
|
11119
11131
|
init_invocation();
|
|
11120
11132
|
init_interactive_command();
|
|
@@ -11130,6 +11142,12 @@ var init_tmux = __esm(() => {
|
|
|
11130
11142
|
init_client2();
|
|
11131
11143
|
init_chattab();
|
|
11132
11144
|
init_pane_heal();
|
|
11145
|
+
FOCUS_EDGE_VARS = {
|
|
11146
|
+
"-L": "pane_at_left",
|
|
11147
|
+
"-D": "pane_at_bottom",
|
|
11148
|
+
"-U": "pane_at_top",
|
|
11149
|
+
"-R": "pane_at_right"
|
|
11150
|
+
};
|
|
11133
11151
|
ensureSessionLocks = new Map;
|
|
11134
11152
|
});
|
|
11135
11153
|
|
|
@@ -13141,7 +13159,7 @@ async function handleDiffRequest(req, url) {
|
|
|
13141
13159
|
var GIT_TIMEOUT_MS = 15000;
|
|
13142
13160
|
|
|
13143
13161
|
// src/web/notes.ts
|
|
13144
|
-
import { mkdir as mkdir7, readFile as
|
|
13162
|
+
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
13145
13163
|
import { join as join11 } from "path";
|
|
13146
13164
|
function notesDir() {
|
|
13147
13165
|
return join11(kobeStateDir(), "notes");
|
|
@@ -13160,7 +13178,7 @@ async function handleGet(url) {
|
|
|
13160
13178
|
try {
|
|
13161
13179
|
let markdown = "";
|
|
13162
13180
|
try {
|
|
13163
|
-
markdown = await
|
|
13181
|
+
markdown = await readFile8(noteFilePath(taskId), "utf8");
|
|
13164
13182
|
} catch (err) {
|
|
13165
13183
|
if (err.code !== "ENOENT")
|
|
13166
13184
|
throw err;
|
|
@@ -18260,7 +18278,7 @@ function bumpKeymapVersion() {
|
|
|
18260
18278
|
setKeymapVersion((v) => v + 1);
|
|
18261
18279
|
}
|
|
18262
18280
|
function findBinding(id) {
|
|
18263
|
-
return
|
|
18281
|
+
return KEYMAP_BY_ID.get(id);
|
|
18264
18282
|
}
|
|
18265
18283
|
function chordsOf(id) {
|
|
18266
18284
|
return findBinding(id)?.keys ?? [];
|
|
@@ -18280,7 +18298,7 @@ function bindByIds(handlers) {
|
|
|
18280
18298
|
}
|
|
18281
18299
|
return out;
|
|
18282
18300
|
}
|
|
18283
|
-
var KobeKeymap, KEYMAP_DEFAULTS, keymapVersion, setKeymapVersion;
|
|
18301
|
+
var KobeKeymap, KEYMAP_DEFAULTS, keymapVersion, setKeymapVersion, KEYMAP_BY_ID;
|
|
18284
18302
|
var init_keybindings2 = __esm(() => {
|
|
18285
18303
|
init_dev();
|
|
18286
18304
|
KobeKeymap = [
|
|
@@ -18493,6 +18511,14 @@ var init_keybindings2 = __esm(() => {
|
|
|
18493
18511
|
description: "Open the update page (when a new version is available)",
|
|
18494
18512
|
hint: { keys: "u", label: "update", status: false }
|
|
18495
18513
|
},
|
|
18514
|
+
{
|
|
18515
|
+
id: "tasks.focusEngine",
|
|
18516
|
+
scope: "sidebar",
|
|
18517
|
+
keys: ["right"],
|
|
18518
|
+
category: "Tasks pane",
|
|
18519
|
+
description: "Focus the engine pane of the current window",
|
|
18520
|
+
hint: { keys: "\u2192", label: "engine", status: false }
|
|
18521
|
+
},
|
|
18496
18522
|
{
|
|
18497
18523
|
id: "tasks.toggleKeys",
|
|
18498
18524
|
scope: "sidebar",
|
|
@@ -18742,6 +18768,7 @@ var init_keybindings2 = __esm(() => {
|
|
|
18742
18768
|
];
|
|
18743
18769
|
KEYMAP_DEFAULTS = new Map(KobeKeymap.map((b) => [b.id, { keys: [...b.keys], hint: b.hint ? { ...b.hint } : undefined }]));
|
|
18744
18770
|
[keymapVersion, setKeymapVersion] = createSignal(0);
|
|
18771
|
+
KEYMAP_BY_ID = new Map(KobeKeymap.map((b) => [b.id, b]));
|
|
18745
18772
|
});
|
|
18746
18773
|
|
|
18747
18774
|
// src/tui/context/keybindings-user.ts
|
|
@@ -22560,40 +22587,61 @@ var init_background_poll = __esm(() => {
|
|
|
22560
22587
|
});
|
|
22561
22588
|
|
|
22562
22589
|
// src/tui/panes/sidebar/git-head.ts
|
|
22590
|
+
import { stat as stat4 } from "fs/promises";
|
|
22591
|
+
import { join as join18 } from "path";
|
|
22592
|
+
async function headFingerprint(repo) {
|
|
22593
|
+
try {
|
|
22594
|
+
const st = await stat4(join18(repo, ".git", "HEAD"));
|
|
22595
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
22596
|
+
} catch {
|
|
22597
|
+
return null;
|
|
22598
|
+
}
|
|
22599
|
+
}
|
|
22600
|
+
async function resolveBranchHead(repo, signal, spawn6 = spawnCapture) {
|
|
22601
|
+
const fingerprint = await headFingerprint(repo);
|
|
22602
|
+
if (fingerprint !== null) {
|
|
22603
|
+
const cached4 = headCache.get(repo);
|
|
22604
|
+
if (cached4 && cached4.fingerprint === fingerprint)
|
|
22605
|
+
return cached4.value;
|
|
22606
|
+
}
|
|
22607
|
+
let value = "";
|
|
22608
|
+
const ref = await spawn6("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
22609
|
+
cwd: repo,
|
|
22610
|
+
env: gitEnv(),
|
|
22611
|
+
signal
|
|
22612
|
+
});
|
|
22613
|
+
const name = ref.status === 0 ? ref.stdout.trim() : "";
|
|
22614
|
+
if (name && name !== "HEAD") {
|
|
22615
|
+
value = name;
|
|
22616
|
+
} else {
|
|
22617
|
+
const head = await spawn6("git", ["rev-parse", "--verify", "HEAD"], {
|
|
22618
|
+
cwd: repo,
|
|
22619
|
+
env: gitEnv(),
|
|
22620
|
+
signal
|
|
22621
|
+
});
|
|
22622
|
+
if (head.status === 0)
|
|
22623
|
+
value = "(detached)";
|
|
22624
|
+
}
|
|
22625
|
+
if (fingerprint !== null)
|
|
22626
|
+
headCache.set(repo, { fingerprint, value });
|
|
22627
|
+
return value;
|
|
22628
|
+
}
|
|
22563
22629
|
function currentBranch(repo) {
|
|
22564
22630
|
return poller.read(repo);
|
|
22565
22631
|
}
|
|
22566
22632
|
function pollCurrentBranch(repo) {
|
|
22567
22633
|
poller.poll(repo);
|
|
22568
22634
|
}
|
|
22569
|
-
var BRANCH_POLL_TIMEOUT_MS = 2000, BRANCH_SLOW_RETRY_MS = 30000, BRANCH_MIN_POLL_INTERVAL_MS = 1500, gitEnv = () => ({ ...process.env, GIT_OPTIONAL_LOCKS: "0" }), poller;
|
|
22635
|
+
var BRANCH_POLL_TIMEOUT_MS = 2000, BRANCH_SLOW_RETRY_MS = 30000, BRANCH_MIN_POLL_INTERVAL_MS = 1500, gitEnv = () => ({ ...process.env, GIT_OPTIONAL_LOCKS: "0" }), headCache, poller;
|
|
22570
22636
|
var init_git_head = __esm(() => {
|
|
22571
22637
|
init_background_poll();
|
|
22638
|
+
headCache = new Map;
|
|
22572
22639
|
poller = createBackgroundPoller({
|
|
22573
22640
|
initial: "",
|
|
22574
22641
|
timeoutMs: BRANCH_POLL_TIMEOUT_MS,
|
|
22575
22642
|
slowRetryMs: BRANCH_SLOW_RETRY_MS,
|
|
22576
22643
|
minIntervalMs: BRANCH_MIN_POLL_INTERVAL_MS,
|
|
22577
|
-
run:
|
|
22578
|
-
const ref = await spawnCapture("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
22579
|
-
cwd: repo,
|
|
22580
|
-
env: gitEnv(),
|
|
22581
|
-
signal
|
|
22582
|
-
});
|
|
22583
|
-
if (ref.status === 0) {
|
|
22584
|
-
const name = ref.stdout.trim();
|
|
22585
|
-
if (name && name !== "HEAD")
|
|
22586
|
-
return name;
|
|
22587
|
-
}
|
|
22588
|
-
const head = await spawnCapture("git", ["rev-parse", "--verify", "HEAD"], {
|
|
22589
|
-
cwd: repo,
|
|
22590
|
-
env: gitEnv(),
|
|
22591
|
-
signal
|
|
22592
|
-
});
|
|
22593
|
-
if (head.status === 0)
|
|
22594
|
-
return "(detached)";
|
|
22595
|
-
return "";
|
|
22596
|
-
}
|
|
22644
|
+
run: (repo, signal) => resolveBranchHead(repo, signal)
|
|
22597
22645
|
});
|
|
22598
22646
|
});
|
|
22599
22647
|
|
|
@@ -22864,6 +22912,14 @@ function buildSidebarRowView(opts) {
|
|
|
22864
22912
|
tone
|
|
22865
22913
|
};
|
|
22866
22914
|
}
|
|
22915
|
+
function withSpinnerFrame(view, frame) {
|
|
22916
|
+
if (!view.loading)
|
|
22917
|
+
return view;
|
|
22918
|
+
const spinner = IN_PROGRESS_SPINNER[frame() % IN_PROGRESS_SPINNER.length] ?? "\u280B";
|
|
22919
|
+
if (spinner === view.stateGlyph && spinner === view.projectGlyph)
|
|
22920
|
+
return view;
|
|
22921
|
+
return { ...view, stateGlyph: spinner, projectGlyph: spinner };
|
|
22922
|
+
}
|
|
22867
22923
|
function activityBadgeFor(state) {
|
|
22868
22924
|
switch (state) {
|
|
22869
22925
|
case "rate_limited":
|
|
@@ -23369,16 +23425,17 @@ function Sidebar(props) {
|
|
|
23369
23425
|
pollCurrentBranch(task.repo);
|
|
23370
23426
|
return isMain ? currentBranch(task.repo) : "";
|
|
23371
23427
|
});
|
|
23372
|
-
const
|
|
23428
|
+
const baseRowView = createMemo(() => buildSidebarRowView({
|
|
23373
23429
|
task,
|
|
23374
23430
|
activity: props.engineState?.().get(task.id),
|
|
23375
23431
|
job: props.taskJobs?.().get(task.id),
|
|
23376
23432
|
live: isLive(),
|
|
23377
|
-
spinnerFrame:
|
|
23433
|
+
spinnerFrame: 0,
|
|
23378
23434
|
subtitleBudget: subtitleBudget(),
|
|
23379
23435
|
truncateBranch: truncateBranchLabel,
|
|
23380
23436
|
mainBranch: projectBranch()
|
|
23381
23437
|
}));
|
|
23438
|
+
const rowView = createMemo(() => withSpinnerFrame(baseRowView(), spinnerFrame));
|
|
23382
23439
|
const stateColor = () => isMain && !rowView().loading ? theme.primary : toneColor(rowView().tone);
|
|
23383
23440
|
const barColor = () => isCursor() ? theme.focusAccent : isSelected() ? theme.primary : undefined;
|
|
23384
23441
|
const barGlyph = () => isCursor() || isSelected() ? "\u258C" : " ";
|
|
@@ -23822,6 +23879,7 @@ __export(exports_host3, {
|
|
|
23822
23879
|
startTasksPane: () => startTasksPane
|
|
23823
23880
|
});
|
|
23824
23881
|
import { existsSync as existsSync16 } from "fs";
|
|
23882
|
+
import { stat as stat5 } from "fs/promises";
|
|
23825
23883
|
import { TextAttributes as TextAttributes12 } from "@opentui/core";
|
|
23826
23884
|
function worktreeCwdUsable(cwd) {
|
|
23827
23885
|
return !!cwd && worktreeUsable(cwd);
|
|
@@ -24003,6 +24061,17 @@ function TasksShell(props) {
|
|
|
24003
24061
|
notifyError(`Couldn't open worktree with ${opener.label}`);
|
|
24004
24062
|
}
|
|
24005
24063
|
}
|
|
24064
|
+
async function focusEnginePane() {
|
|
24065
|
+
if (!process.env.TMUX_PANE)
|
|
24066
|
+
return;
|
|
24067
|
+
const session = await currentSessionName();
|
|
24068
|
+
if (!session)
|
|
24069
|
+
return;
|
|
24070
|
+
const pane = await claudePaneIdStrict(session);
|
|
24071
|
+
if (!pane)
|
|
24072
|
+
return;
|
|
24073
|
+
await runTmux(["select-pane", "-t", pane]);
|
|
24074
|
+
}
|
|
24006
24075
|
async function moveTask(id, delta) {
|
|
24007
24076
|
const task = props.tasks().find((t) => t.id === id);
|
|
24008
24077
|
if (!task || task.kind === "main" || !props.orch)
|
|
@@ -24050,7 +24119,8 @@ function TasksShell(props) {
|
|
|
24050
24119
|
if (id)
|
|
24051
24120
|
cycleVendor(id);
|
|
24052
24121
|
},
|
|
24053
|
-
"tasks.toggleKeys": () => setKeysCollapsed(!keysCollapsed())
|
|
24122
|
+
"tasks.toggleKeys": () => setKeysCollapsed(!keysCollapsed()),
|
|
24123
|
+
"tasks.focusEngine": () => void focusEnginePane().catch((err) => console.error("[kobe tasks] focus engine pane failed:", err))
|
|
24054
24124
|
})
|
|
24055
24125
|
}));
|
|
24056
24126
|
async function switchTo(id) {
|
|
@@ -24291,6 +24361,10 @@ function ShortcutHints(props) {
|
|
|
24291
24361
|
k: "enter",
|
|
24292
24362
|
label: "open"
|
|
24293
24363
|
},
|
|
24364
|
+
{
|
|
24365
|
+
k: "right",
|
|
24366
|
+
label: "focus engine"
|
|
24367
|
+
},
|
|
24294
24368
|
{
|
|
24295
24369
|
k: "n",
|
|
24296
24370
|
label: "new task"
|
|
@@ -24451,10 +24525,21 @@ async function setupTasksPane(opts) {
|
|
|
24451
24525
|
await store2.load();
|
|
24452
24526
|
setFileTasks(store2.list());
|
|
24453
24527
|
};
|
|
24528
|
+
let lastTasksFileFingerprint = "";
|
|
24454
24529
|
const timer = setInterval(() => {
|
|
24455
24530
|
if (orch && orch.connectionStateSignal()() === "online")
|
|
24456
24531
|
return;
|
|
24457
|
-
|
|
24532
|
+
(async () => {
|
|
24533
|
+
let fingerprint = "missing";
|
|
24534
|
+
try {
|
|
24535
|
+
const st = await stat5(store2.filePath);
|
|
24536
|
+
fingerprint = `${st.mtimeMs}:${st.size}`;
|
|
24537
|
+
} catch {}
|
|
24538
|
+
if (fingerprint === lastTasksFileFingerprint)
|
|
24539
|
+
return;
|
|
24540
|
+
lastTasksFileFingerprint = fingerprint;
|
|
24541
|
+
await reload();
|
|
24542
|
+
})().catch(() => {});
|
|
24458
24543
|
}, RELOAD_MS);
|
|
24459
24544
|
return {
|
|
24460
24545
|
root: () => [createComponent2(TasksShell, {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@sma1lboy/kobe",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.20",
|
|
5
5
|
"description": "TUI orchestrator for Claude Code (codename)",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"packageManager": "bun@1.3.13",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"test:fast": "vitest run --passWithNoTests",
|
|
35
35
|
"test:socket": "KOBE_INCLUDE_SOCKET=1 vitest run test/daemon --pool forks --minWorkers=1 --maxWorkers=1 --passWithNoTests",
|
|
36
36
|
"test:behavior": "KOBE_INCLUDE_BEHAVIOR=1 vitest run test/behavior --passWithNoTests",
|
|
37
|
+
"bench": "vitest bench --run",
|
|
37
38
|
"lint": "biome check .",
|
|
38
39
|
"knip": "knip-bun",
|
|
39
40
|
"postinstall": "bun run scripts/check-preview-deps.ts || true",
|