@sma1lboy/kobe 0.7.18 → 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 +831 -406
- 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",
|
|
@@ -3919,7 +3920,9 @@ var init_protocol = __esm(() => {
|
|
|
3919
3920
|
"update",
|
|
3920
3921
|
"engine-state",
|
|
3921
3922
|
"ui-prefs",
|
|
3922
|
-
"keybindings"
|
|
3923
|
+
"keybindings",
|
|
3924
|
+
"task.jobs",
|
|
3925
|
+
"worktree.changes"
|
|
3923
3926
|
];
|
|
3924
3927
|
});
|
|
3925
3928
|
|
|
@@ -4067,6 +4070,7 @@ class KobeDaemonClient {
|
|
|
4067
4070
|
this.disposed = true;
|
|
4068
4071
|
this.socket?.end();
|
|
4069
4072
|
this.socket = null;
|
|
4073
|
+
this.failPending();
|
|
4070
4074
|
}
|
|
4071
4075
|
forceDisconnect() {
|
|
4072
4076
|
const socket = this.socket;
|
|
@@ -4074,6 +4078,15 @@ class KobeDaemonClient {
|
|
|
4074
4078
|
return;
|
|
4075
4079
|
this.socket = null;
|
|
4076
4080
|
socket.destroy();
|
|
4081
|
+
this.failPending();
|
|
4082
|
+
}
|
|
4083
|
+
failPending() {
|
|
4084
|
+
if (this.pending.size === 0)
|
|
4085
|
+
return;
|
|
4086
|
+
const err = new Error("daemon connection closed");
|
|
4087
|
+
for (const pending of this.pending.values())
|
|
4088
|
+
pending.reject(err);
|
|
4089
|
+
this.pending.clear();
|
|
4077
4090
|
}
|
|
4078
4091
|
on(name, handler) {
|
|
4079
4092
|
let set = this.handlers.get(name);
|
|
@@ -4148,9 +4161,7 @@ class KobeDaemonClient {
|
|
|
4148
4161
|
if (this.socket !== which)
|
|
4149
4162
|
return;
|
|
4150
4163
|
this.socket = null;
|
|
4151
|
-
|
|
4152
|
-
pending.reject(new Error("daemon connection closed"));
|
|
4153
|
-
this.pending.clear();
|
|
4164
|
+
this.failPending();
|
|
4154
4165
|
this.emitLifecycle("close");
|
|
4155
4166
|
}
|
|
4156
4167
|
emitLifecycle(name) {
|
|
@@ -5059,66 +5070,6 @@ var init_history = __esm(() => {
|
|
|
5059
5070
|
};
|
|
5060
5071
|
});
|
|
5061
5072
|
|
|
5062
|
-
// src/engine/claude-code-local/cost.ts
|
|
5063
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
5064
|
-
async function summarizeClaudeWorktreeCost(worktree) {
|
|
5065
|
-
const files = await listSessionFilesForWorktree(worktree);
|
|
5066
|
-
const base = {
|
|
5067
|
-
sessionCount: files.length,
|
|
5068
|
-
inputTokens: 0,
|
|
5069
|
-
outputTokens: 0,
|
|
5070
|
-
cacheReadTokens: 0,
|
|
5071
|
-
cacheCreateTokens: 0,
|
|
5072
|
-
lastActivityMs: files[0]?.mtimeMs ?? null
|
|
5073
|
-
};
|
|
5074
|
-
if (files.length === 0)
|
|
5075
|
-
return base;
|
|
5076
|
-
let input = 0;
|
|
5077
|
-
let output = 0;
|
|
5078
|
-
let cacheRead = 0;
|
|
5079
|
-
let cacheCreate = 0;
|
|
5080
|
-
for (const file of files) {
|
|
5081
|
-
let raw;
|
|
5082
|
-
try {
|
|
5083
|
-
raw = await readFile3(file.path, "utf8");
|
|
5084
|
-
} catch {
|
|
5085
|
-
continue;
|
|
5086
|
-
}
|
|
5087
|
-
for (const line of raw.split(`
|
|
5088
|
-
`)) {
|
|
5089
|
-
if (line.length === 0)
|
|
5090
|
-
continue;
|
|
5091
|
-
let parsed;
|
|
5092
|
-
try {
|
|
5093
|
-
parsed = JSON.parse(line);
|
|
5094
|
-
} catch {
|
|
5095
|
-
continue;
|
|
5096
|
-
}
|
|
5097
|
-
const usage = parsed.message?.usage;
|
|
5098
|
-
if (!usage)
|
|
5099
|
-
continue;
|
|
5100
|
-
if (typeof usage.input_tokens === "number")
|
|
5101
|
-
input += usage.input_tokens;
|
|
5102
|
-
if (typeof usage.output_tokens === "number")
|
|
5103
|
-
output += usage.output_tokens;
|
|
5104
|
-
if (typeof usage.cache_read_input_tokens === "number")
|
|
5105
|
-
cacheRead += usage.cache_read_input_tokens;
|
|
5106
|
-
if (typeof usage.cache_creation_input_tokens === "number")
|
|
5107
|
-
cacheCreate += usage.cache_creation_input_tokens;
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
return {
|
|
5111
|
-
...base,
|
|
5112
|
-
inputTokens: input,
|
|
5113
|
-
outputTokens: output,
|
|
5114
|
-
cacheReadTokens: cacheRead,
|
|
5115
|
-
cacheCreateTokens: cacheCreate
|
|
5116
|
-
};
|
|
5117
|
-
}
|
|
5118
|
-
var init_cost = __esm(() => {
|
|
5119
|
-
init_history();
|
|
5120
|
-
});
|
|
5121
|
-
|
|
5122
5073
|
// src/cli/invocation.ts
|
|
5123
5074
|
import { fileURLToPath } from "url";
|
|
5124
5075
|
function kobeCliInvocation() {
|
|
@@ -5215,7 +5166,7 @@ function opsPaneCommand(args) {
|
|
|
5215
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;
|
|
5216
5167
|
|
|
5217
5168
|
// src/engine/claude-code-local/hook-adapter.ts
|
|
5218
|
-
import { mkdir as mkdir5, readFile as
|
|
5169
|
+
import { mkdir as mkdir5, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
5219
5170
|
import { dirname as dirname4 } from "path";
|
|
5220
5171
|
function failureFromErrorType(errorType) {
|
|
5221
5172
|
if (typeof errorType !== "string")
|
|
@@ -5231,7 +5182,7 @@ function isObject2(v) {
|
|
|
5231
5182
|
}
|
|
5232
5183
|
async function readJsonObject(path8) {
|
|
5233
5184
|
try {
|
|
5234
|
-
const parsed = JSON.parse(await
|
|
5185
|
+
const parsed = JSON.parse(await readFile3(path8, "utf8"));
|
|
5235
5186
|
return isObject2(parsed) ? parsed : {};
|
|
5236
5187
|
} catch {
|
|
5237
5188
|
return {};
|
|
@@ -5445,7 +5396,7 @@ function validPositive(v) {
|
|
|
5445
5396
|
}
|
|
5446
5397
|
|
|
5447
5398
|
// src/engine/codex-local/history.ts
|
|
5448
|
-
import { readFile as
|
|
5399
|
+
import { readFile as readFile4, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
5449
5400
|
import { homedir as homedir9 } from "os";
|
|
5450
5401
|
import path8 from "path";
|
|
5451
5402
|
async function listRolloutFiles(deps = defaultDeps7) {
|
|
@@ -5492,6 +5443,26 @@ function rolloutCwd(raw) {
|
|
|
5492
5443
|
}
|
|
5493
5444
|
return "";
|
|
5494
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
|
+
}
|
|
5495
5466
|
async function listSessionIdsForWorktree(worktree, deps = defaultDeps7) {
|
|
5496
5467
|
if (!worktree)
|
|
5497
5468
|
return [];
|
|
@@ -5502,13 +5473,7 @@ async function listSessionIdsForWorktree(worktree, deps = defaultDeps7) {
|
|
|
5502
5473
|
if (scanned >= MAX_WORKTREE_SCAN)
|
|
5503
5474
|
break;
|
|
5504
5475
|
scanned++;
|
|
5505
|
-
|
|
5506
|
-
try {
|
|
5507
|
-
raw = await deps.readFile(file);
|
|
5508
|
-
} catch {
|
|
5509
|
-
continue;
|
|
5510
|
-
}
|
|
5511
|
-
if (rolloutCwd(raw) !== worktree)
|
|
5476
|
+
if (await rolloutCwdForFile(file, deps) !== worktree)
|
|
5512
5477
|
continue;
|
|
5513
5478
|
const id = path8.basename(file).match(UUID_AT_END)?.[1];
|
|
5514
5479
|
if (id)
|
|
@@ -5516,30 +5481,25 @@ async function listSessionIdsForWorktree(worktree, deps = defaultDeps7) {
|
|
|
5516
5481
|
}
|
|
5517
5482
|
return matches.reverse();
|
|
5518
5483
|
}
|
|
5519
|
-
async function
|
|
5484
|
+
async function findLatestRolloutForWorktree(worktree, deps = defaultDeps7) {
|
|
5520
5485
|
if (!worktree)
|
|
5521
|
-
return
|
|
5486
|
+
return null;
|
|
5522
5487
|
const files = await listRolloutFiles(deps);
|
|
5523
5488
|
let scanned = 0;
|
|
5524
5489
|
for (const file of files) {
|
|
5525
5490
|
if (scanned >= MAX_MTIME_SCAN)
|
|
5526
5491
|
break;
|
|
5527
5492
|
scanned++;
|
|
5528
|
-
|
|
5529
|
-
try {
|
|
5530
|
-
raw = await deps.readFile(file);
|
|
5531
|
-
} catch {
|
|
5532
|
-
continue;
|
|
5533
|
-
}
|
|
5534
|
-
if (rolloutCwd(raw) !== worktree)
|
|
5493
|
+
if (await rolloutCwdForFile(file, deps) !== worktree)
|
|
5535
5494
|
continue;
|
|
5536
5495
|
try {
|
|
5537
|
-
return (await deps.stat(file)).mtimeMs;
|
|
5538
|
-
} catch {
|
|
5539
|
-
return 0;
|
|
5540
|
-
}
|
|
5496
|
+
return { path: file, mtimeMs: (await deps.stat(file)).mtimeMs };
|
|
5497
|
+
} catch {}
|
|
5541
5498
|
}
|
|
5542
|
-
return
|
|
5499
|
+
return null;
|
|
5500
|
+
}
|
|
5501
|
+
async function latestTranscriptMtimeForWorktree2(worktree, deps = defaultDeps7) {
|
|
5502
|
+
return (await findLatestRolloutForWorktree(worktree, deps))?.mtimeMs ?? 0;
|
|
5543
5503
|
}
|
|
5544
5504
|
async function readHistory2(sessionId, deps = defaultDeps7) {
|
|
5545
5505
|
return (await readHistoryWithMetrics(sessionId, deps)).messages;
|
|
@@ -5772,7 +5732,7 @@ function parseTimestampMs(value) {
|
|
|
5772
5732
|
function isObject4(v) {
|
|
5773
5733
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5774
5734
|
}
|
|
5775
|
-
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;
|
|
5776
5736
|
var init_history2 = __esm(() => {
|
|
5777
5737
|
init_synthetic();
|
|
5778
5738
|
defaultDeps7 = {
|
|
@@ -5787,11 +5747,12 @@ var init_history2 = __esm(() => {
|
|
|
5787
5747
|
}
|
|
5788
5748
|
},
|
|
5789
5749
|
async readFile(p) {
|
|
5790
|
-
return await
|
|
5750
|
+
return await readFile4(p, "utf8");
|
|
5791
5751
|
},
|
|
5792
5752
|
stat: stat2
|
|
5793
5753
|
};
|
|
5794
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;
|
|
5795
5756
|
});
|
|
5796
5757
|
|
|
5797
5758
|
// src/engine/copilot-local/usage.ts
|
|
@@ -5829,7 +5790,7 @@ function numberOr2(value, fallback) {
|
|
|
5829
5790
|
}
|
|
5830
5791
|
|
|
5831
5792
|
// src/engine/copilot-local/history.ts
|
|
5832
|
-
import { readFile as
|
|
5793
|
+
import { readFile as readFile5, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
5833
5794
|
import { homedir as homedir10 } from "os";
|
|
5834
5795
|
import path9 from "path";
|
|
5835
5796
|
async function listSessionDirs(deps = defaultDeps8) {
|
|
@@ -5941,7 +5902,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
5941
5902
|
if (!text)
|
|
5942
5903
|
continue;
|
|
5943
5904
|
if (!firstUserMessage)
|
|
5944
|
-
firstUserMessage = text.slice(0, PREVIEW_CHAR_CAP);
|
|
5905
|
+
firstUserMessage = Buffer.from(text.slice(0, PREVIEW_CHAR_CAP), "utf8").toString("utf8");
|
|
5945
5906
|
messages.push({ role: "user", blocks: [{ type: "text", text }], timestamp, sessionId });
|
|
5946
5907
|
continue;
|
|
5947
5908
|
}
|
|
@@ -6009,7 +5970,7 @@ var init_history3 = __esm(() => {
|
|
|
6009
5970
|
}
|
|
6010
5971
|
},
|
|
6011
5972
|
async readFile(p) {
|
|
6012
|
-
return await
|
|
5973
|
+
return await readFile5(p, "utf8");
|
|
6013
5974
|
},
|
|
6014
5975
|
stat: stat3,
|
|
6015
5976
|
async rm(p) {
|
|
@@ -6048,7 +6009,7 @@ var init_hook_adapter2 = __esm(() => {
|
|
|
6048
6009
|
});
|
|
6049
6010
|
|
|
6050
6011
|
// src/engine/turn-detector.ts
|
|
6051
|
-
import { readFile as
|
|
6012
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
6052
6013
|
|
|
6053
6014
|
class EngineTurnDetector {
|
|
6054
6015
|
supportsCompletionMarkers() {
|
|
@@ -6121,42 +6082,71 @@ function timestampFromRecord(record, fallback) {
|
|
|
6121
6082
|
function isObject7(v) {
|
|
6122
6083
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6123
6084
|
}
|
|
6124
|
-
var ClaudeTurnDetector, CodexTurnDetector, UnknownTurnDetector;
|
|
6085
|
+
var defaultClaudeDeps, ClaudeTurnDetector, defaultCodexDeps, CodexTurnDetector, UnknownTurnDetector;
|
|
6125
6086
|
var init_turn_detector = __esm(() => {
|
|
6126
6087
|
init_history();
|
|
6127
6088
|
init_history2();
|
|
6128
6089
|
init_registry();
|
|
6090
|
+
defaultClaudeDeps = {
|
|
6091
|
+
listSessionFiles: (worktree) => listSessionFilesForWorktree(worktree),
|
|
6092
|
+
readFile: (path10) => readFile6(path10, "utf8")
|
|
6093
|
+
};
|
|
6129
6094
|
ClaudeTurnDetector = class ClaudeTurnDetector extends EngineTurnDetector {
|
|
6095
|
+
deps;
|
|
6130
6096
|
vendor = "claude";
|
|
6097
|
+
cache = new Map;
|
|
6098
|
+
constructor(deps = defaultClaudeDeps) {
|
|
6099
|
+
super();
|
|
6100
|
+
this.deps = deps;
|
|
6101
|
+
}
|
|
6131
6102
|
async latestCompletion(worktree) {
|
|
6132
|
-
const files = await
|
|
6103
|
+
const files = await this.deps.listSessionFiles(worktree);
|
|
6133
6104
|
let latest = null;
|
|
6105
|
+
const next = new Map;
|
|
6134
6106
|
for (const file of files.slice(0, 4)) {
|
|
6135
|
-
const
|
|
6136
|
-
|
|
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 });
|
|
6137
6116
|
if (marker && (!latest || marker.timestampMs > latest.timestampMs))
|
|
6138
6117
|
latest = marker;
|
|
6139
6118
|
}
|
|
6119
|
+
this.cache = next;
|
|
6140
6120
|
return latest;
|
|
6141
6121
|
}
|
|
6142
6122
|
};
|
|
6123
|
+
defaultCodexDeps = {
|
|
6124
|
+
findLatestRollout: (worktree) => findLatestRolloutForWorktree(worktree),
|
|
6125
|
+
readFile: (path10) => readFile6(path10, "utf8")
|
|
6126
|
+
};
|
|
6143
6127
|
CodexTurnDetector = class CodexTurnDetector extends EngineTurnDetector {
|
|
6128
|
+
deps;
|
|
6144
6129
|
vendor = "codex";
|
|
6130
|
+
cache = null;
|
|
6131
|
+
constructor(deps = defaultCodexDeps) {
|
|
6132
|
+
super();
|
|
6133
|
+
this.deps = deps;
|
|
6134
|
+
}
|
|
6145
6135
|
async latestCompletion(worktree) {
|
|
6146
6136
|
if (!worktree)
|
|
6147
6137
|
return null;
|
|
6148
|
-
const
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
scanned++;
|
|
6154
|
-
const raw = await readFile7(file, "utf8").catch(() => "");
|
|
6155
|
-
if (!raw || rolloutCwd(raw) !== worktree)
|
|
6156
|
-
continue;
|
|
6157
|
-
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;
|
|
6158
6143
|
}
|
|
6159
|
-
|
|
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;
|
|
6160
6150
|
}
|
|
6161
6151
|
};
|
|
6162
6152
|
UnknownTurnDetector = class UnknownTurnDetector extends EngineTurnDetector {
|
|
@@ -6182,7 +6172,6 @@ function customEngineEntry(vendor) {
|
|
|
6182
6172
|
displayName: vendor,
|
|
6183
6173
|
defaultCommand: [vendor],
|
|
6184
6174
|
history: EMPTY_HISTORY,
|
|
6185
|
-
summarizeCost: null,
|
|
6186
6175
|
detectAccount: async () => ({
|
|
6187
6176
|
binary: { found: false, error: "custom engine: kobe has no account detector for it" },
|
|
6188
6177
|
account: { kind: "none" }
|
|
@@ -6198,7 +6187,6 @@ var EMPTY_HISTORY, claudeHistoryReader, codexHistoryReader, copilotHistoryReader
|
|
|
6198
6187
|
var init_registry = __esm(() => {
|
|
6199
6188
|
init_vendor();
|
|
6200
6189
|
init_account_detect();
|
|
6201
|
-
init_cost();
|
|
6202
6190
|
init_history();
|
|
6203
6191
|
init_hook_adapter();
|
|
6204
6192
|
init_history2();
|
|
@@ -6241,7 +6229,6 @@ var init_registry = __esm(() => {
|
|
|
6241
6229
|
displayName: "Claude",
|
|
6242
6230
|
defaultCommand: ["claude"],
|
|
6243
6231
|
history: claudeHistoryReader,
|
|
6244
|
-
summarizeCost: (worktree) => summarizeClaudeWorktreeCost(worktree),
|
|
6245
6232
|
detectAccount: (deps) => detectClaudeAccount(deps),
|
|
6246
6233
|
createHookAdapter: () => new ClaudeHookAdapter,
|
|
6247
6234
|
createTurnDetector: () => new ClaudeTurnDetector
|
|
@@ -6252,7 +6239,6 @@ var init_registry = __esm(() => {
|
|
|
6252
6239
|
displayName: "Codex",
|
|
6253
6240
|
defaultCommand: ["codex"],
|
|
6254
6241
|
history: codexHistoryReader,
|
|
6255
|
-
summarizeCost: null,
|
|
6256
6242
|
detectAccount: (deps) => detectCodexAccount(deps),
|
|
6257
6243
|
createHookAdapter: () => new NoopHookAdapter("codex"),
|
|
6258
6244
|
createTurnDetector: () => new CodexTurnDetector
|
|
@@ -6263,7 +6249,6 @@ var init_registry = __esm(() => {
|
|
|
6263
6249
|
displayName: "Copilot",
|
|
6264
6250
|
defaultCommand: ["copilot"],
|
|
6265
6251
|
history: copilotHistoryReader,
|
|
6266
|
-
summarizeCost: null,
|
|
6267
6252
|
detectAccount: (deps) => detectCopilotAccount(deps),
|
|
6268
6253
|
createHookAdapter: () => new NoopHookAdapter("copilot"),
|
|
6269
6254
|
createTurnDetector: () => new UnknownTurnDetector("copilot")
|
|
@@ -6277,7 +6262,8 @@ function titleFromMessages(messages) {
|
|
|
6277
6262
|
if (!firstUser)
|
|
6278
6263
|
return "";
|
|
6279
6264
|
const text = firstUser.blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
6280
|
-
|
|
6265
|
+
const title = deriveTitleFromPrompt(text);
|
|
6266
|
+
return title.length > 0 ? Buffer.from(title, "utf8").toString("utf8") : title;
|
|
6281
6267
|
}
|
|
6282
6268
|
async function deriveTitleFromSession(worktree, vendor = DEFAULT_TASK_VENDOR) {
|
|
6283
6269
|
if (!worktree)
|
|
@@ -6331,6 +6317,7 @@ __export(exports_client, {
|
|
|
6331
6317
|
globalTasksPaneWidth: () => globalTasksPaneWidth,
|
|
6332
6318
|
getSessionOptions: () => getSessionOptions,
|
|
6333
6319
|
getSessionOption: () => getSessionOption,
|
|
6320
|
+
getServerOptions: () => getServerOptions,
|
|
6334
6321
|
getServerOption: () => getServerOption,
|
|
6335
6322
|
ensureFallbackSession: () => ensureFallbackSession,
|
|
6336
6323
|
currentSessionName: () => currentSessionName,
|
|
@@ -6469,6 +6456,22 @@ async function getServerOption(option) {
|
|
|
6469
6456
|
const { code, stdout } = await runTmuxCapturing(["show-options", "-sqv", option]);
|
|
6470
6457
|
return code === 0 ? stdout.trim() : "";
|
|
6471
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
|
+
}
|
|
6472
6475
|
async function globalTasksPaneWidth() {
|
|
6473
6476
|
const raw = await getServerOption(TASKS_WIDTH_OPTION);
|
|
6474
6477
|
const n = Number.parseInt(raw, 10);
|
|
@@ -6626,18 +6629,18 @@ async function listChatTabWindows(session, runner = realRunner) {
|
|
|
6626
6629
|
"-t",
|
|
6627
6630
|
`=${session}`,
|
|
6628
6631
|
"-F",
|
|
6629
|
-
`#{window_index} #{${CHAT_TAB_SESSION_ID_OPTION}}`
|
|
6632
|
+
`#{window_index} #{automatic-rename} #{${CHAT_TAB_SESSION_ID_OPTION}}`
|
|
6630
6633
|
]);
|
|
6631
6634
|
if (code !== 0)
|
|
6632
6635
|
return [];
|
|
6633
6636
|
const out = [];
|
|
6634
6637
|
for (const line of stdout.split(`
|
|
6635
6638
|
`)) {
|
|
6636
|
-
const
|
|
6637
|
-
const index = Number.parseInt((
|
|
6639
|
+
const [indexField, autoRename, sessionId] = line.split("\t");
|
|
6640
|
+
const index = Number.parseInt((indexField ?? "").trim(), 10);
|
|
6638
6641
|
if (!Number.isInteger(index))
|
|
6639
6642
|
continue;
|
|
6640
|
-
out.push({ index, sessionId:
|
|
6643
|
+
out.push({ index, sessionId: sessionId?.trim() ?? "", autoRename: autoRename?.trim() ?? "" });
|
|
6641
6644
|
}
|
|
6642
6645
|
return out;
|
|
6643
6646
|
}
|
|
@@ -6650,11 +6653,27 @@ async function windowNamedManually(session, index, runner) {
|
|
|
6650
6653
|
]);
|
|
6651
6654
|
return code === 0 && /\boff\b/.test(stdout);
|
|
6652
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
|
+
}
|
|
6653
6660
|
async function renameWindow(session, index, title, runner) {
|
|
6654
6661
|
return await runner.run(["rename-window", "-t", `=${session}:${index}`, "--", title]) === 0;
|
|
6655
6662
|
}
|
|
6656
6663
|
async function runChatTabNamingPass(orch, deps = realDeps) {
|
|
6657
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
|
+
};
|
|
6658
6677
|
for (const task of orch.listTasks()) {
|
|
6659
6678
|
if (task.archived || task.kind === "main" || !task.worktreePath)
|
|
6660
6679
|
continue;
|
|
@@ -6666,7 +6685,7 @@ async function runChatTabNamingPass(orch, deps = realDeps) {
|
|
|
6666
6685
|
const vendor = task.vendor ?? DEFAULT_TASK_VENDOR;
|
|
6667
6686
|
for (const w of windows) {
|
|
6668
6687
|
try {
|
|
6669
|
-
if (await
|
|
6688
|
+
if (await manuallyNamed(session, w))
|
|
6670
6689
|
continue;
|
|
6671
6690
|
const title = w.sessionId ? await deps.titleFromSessionId(vendor, w.sessionId) : w.index === originIndex ? await deps.titleFromWorktree(task.worktreePath, vendor) : "";
|
|
6672
6691
|
if (title && await renameWindow(session, w.index, title, deps.runner))
|
|
@@ -7071,8 +7090,20 @@ function createDaemonHandlerRegistry() {
|
|
|
7071
7090
|
name: "task.ensureWorktree",
|
|
7072
7091
|
async handle(payload, ctx) {
|
|
7073
7092
|
const taskId = requireString(payload, "taskId");
|
|
7074
|
-
|
|
7075
|
-
|
|
7093
|
+
ctx.bus.publish("task.jobs", { taskId, kind: "ensureWorktree", phase: "running" });
|
|
7094
|
+
try {
|
|
7095
|
+
const path11 = await ctx.orch.ensureWorktree(taskId);
|
|
7096
|
+
ctx.bus.publish("task.jobs", { taskId, kind: "ensureWorktree", phase: "done" });
|
|
7097
|
+
return { worktreePath: path11 };
|
|
7098
|
+
} catch (err) {
|
|
7099
|
+
ctx.bus.publish("task.jobs", {
|
|
7100
|
+
taskId,
|
|
7101
|
+
kind: "ensureWorktree",
|
|
7102
|
+
phase: "error",
|
|
7103
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7104
|
+
});
|
|
7105
|
+
throw err;
|
|
7106
|
+
}
|
|
7076
7107
|
}
|
|
7077
7108
|
},
|
|
7078
7109
|
{
|
|
@@ -7333,8 +7364,234 @@ var init_ui_prefs_watcher = __esm(() => {
|
|
|
7333
7364
|
FOCUS_ACCENT_SLOT_NAMES = ["primary", "success", "info"];
|
|
7334
7365
|
});
|
|
7335
7366
|
|
|
7367
|
+
// src/lib/poll-scheduling.ts
|
|
7368
|
+
import { spawn as spawn2 } from "child_process";
|
|
7369
|
+
function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
|
|
7370
|
+
if (timedOut)
|
|
7371
|
+
return startedAt + cfg.slowRetryMs;
|
|
7372
|
+
return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
|
|
7373
|
+
}
|
|
7374
|
+
function shouldPoll(state, now) {
|
|
7375
|
+
return !state.inFlight && now >= state.nextAllowedAt;
|
|
7376
|
+
}
|
|
7377
|
+
function maybeStartScheduledRun(state, cfg, run, onValue) {
|
|
7378
|
+
const startedAt = Date.now();
|
|
7379
|
+
if (!shouldPoll(state, startedAt))
|
|
7380
|
+
return false;
|
|
7381
|
+
state.inFlight = true;
|
|
7382
|
+
const controller = new AbortController;
|
|
7383
|
+
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
7384
|
+
(async () => {
|
|
7385
|
+
let value;
|
|
7386
|
+
let ok = false;
|
|
7387
|
+
try {
|
|
7388
|
+
value = await run(controller.signal);
|
|
7389
|
+
ok = true;
|
|
7390
|
+
} catch {}
|
|
7391
|
+
clearTimeout(timer);
|
|
7392
|
+
const timedOut = controller.signal.aborted;
|
|
7393
|
+
state.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
|
|
7394
|
+
state.inFlight = false;
|
|
7395
|
+
if (ok && !timedOut)
|
|
7396
|
+
onValue(value);
|
|
7397
|
+
})();
|
|
7398
|
+
return true;
|
|
7399
|
+
}
|
|
7400
|
+
function spawnCapture(cmd, args, opts) {
|
|
7401
|
+
return new Promise((resolve2) => {
|
|
7402
|
+
let out = "";
|
|
7403
|
+
let settled = false;
|
|
7404
|
+
const finish = (status) => {
|
|
7405
|
+
if (settled)
|
|
7406
|
+
return;
|
|
7407
|
+
settled = true;
|
|
7408
|
+
resolve2({ status, stdout: out });
|
|
7409
|
+
};
|
|
7410
|
+
const child = spawn2(cmd, args.slice(), {
|
|
7411
|
+
cwd: opts.cwd,
|
|
7412
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7413
|
+
env: opts.env,
|
|
7414
|
+
signal: opts.signal,
|
|
7415
|
+
killSignal: "SIGKILL"
|
|
7416
|
+
});
|
|
7417
|
+
child.stdout?.on("data", (chunk) => {
|
|
7418
|
+
out += String(chunk);
|
|
7419
|
+
});
|
|
7420
|
+
child.on("error", () => finish(null));
|
|
7421
|
+
child.on("close", (code) => finish(code));
|
|
7422
|
+
});
|
|
7423
|
+
}
|
|
7424
|
+
var init_poll_scheduling = () => {};
|
|
7425
|
+
|
|
7426
|
+
// src/tui/panes/sidebar/worktree-changes.ts
|
|
7427
|
+
var exports_worktree_changes = {};
|
|
7428
|
+
__export(exports_worktree_changes, {
|
|
7429
|
+
sameWorktreeChanges: () => sameWorktreeChanges,
|
|
7430
|
+
readWorktreeChanges: () => readWorktreeChanges,
|
|
7431
|
+
pickPushedChanges: () => pickPushedChanges,
|
|
7432
|
+
parsePorcelain: () => parsePorcelain2
|
|
7433
|
+
});
|
|
7434
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
7435
|
+
function sameWorktreeChanges(a, b) {
|
|
7436
|
+
return a.added === b.added && a.deleted === b.deleted;
|
|
7437
|
+
}
|
|
7438
|
+
function pickPushedChanges(pushed, worktreePath) {
|
|
7439
|
+
if (!pushed)
|
|
7440
|
+
return null;
|
|
7441
|
+
return pushed.get(worktreePath) ?? ZERO;
|
|
7442
|
+
}
|
|
7443
|
+
function readWorktreeChanges(worktreePath) {
|
|
7444
|
+
if (!worktreePath)
|
|
7445
|
+
return ZERO;
|
|
7446
|
+
try {
|
|
7447
|
+
const out = spawnSync7("git", ["status", "--porcelain=v1"], {
|
|
7448
|
+
cwd: worktreePath,
|
|
7449
|
+
encoding: "utf8",
|
|
7450
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7451
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }
|
|
7452
|
+
});
|
|
7453
|
+
if (out.status !== 0 || !out.stdout)
|
|
7454
|
+
return ZERO;
|
|
7455
|
+
return parsePorcelain2(out.stdout);
|
|
7456
|
+
} catch {
|
|
7457
|
+
return ZERO;
|
|
7458
|
+
}
|
|
7459
|
+
}
|
|
7460
|
+
function parsePorcelain2(text) {
|
|
7461
|
+
let added = 0;
|
|
7462
|
+
let deleted = 0;
|
|
7463
|
+
for (const line of text.split(`
|
|
7464
|
+
`)) {
|
|
7465
|
+
if (!line || line.startsWith("##"))
|
|
7466
|
+
continue;
|
|
7467
|
+
const x = line.charAt(0);
|
|
7468
|
+
const y = line.charAt(1);
|
|
7469
|
+
if (x === "D" || y === "D")
|
|
7470
|
+
deleted += 1;
|
|
7471
|
+
else
|
|
7472
|
+
added += 1;
|
|
7473
|
+
}
|
|
7474
|
+
return { added, deleted };
|
|
7475
|
+
}
|
|
7476
|
+
var ZERO;
|
|
7477
|
+
var init_worktree_changes = __esm(() => {
|
|
7478
|
+
ZERO = { added: 0, deleted: 0 };
|
|
7479
|
+
});
|
|
7480
|
+
|
|
7481
|
+
// ../kobe-daemon/src/daemon/worktree-changes-collector.ts
|
|
7482
|
+
async function runGitStatus(worktreePath, signal) {
|
|
7483
|
+
const res = await spawnCapture("git", ["status", "--porcelain=v1"], {
|
|
7484
|
+
cwd: worktreePath,
|
|
7485
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
7486
|
+
signal
|
|
7487
|
+
});
|
|
7488
|
+
if (res.status !== 0)
|
|
7489
|
+
throw new Error("git status failed");
|
|
7490
|
+
return parsePorcelain2(res.stdout);
|
|
7491
|
+
}
|
|
7492
|
+
function trackedWorktreePaths(tasks) {
|
|
7493
|
+
const paths = new Set;
|
|
7494
|
+
for (const task of tasks) {
|
|
7495
|
+
if (task.archived)
|
|
7496
|
+
continue;
|
|
7497
|
+
if (!task.worktreePath)
|
|
7498
|
+
continue;
|
|
7499
|
+
if (isRemoteRepoKey(task.repo) || isRemoteRepoKey(task.worktreePath))
|
|
7500
|
+
continue;
|
|
7501
|
+
paths.add(task.worktreePath);
|
|
7502
|
+
}
|
|
7503
|
+
return paths;
|
|
7504
|
+
}
|
|
7505
|
+
|
|
7506
|
+
class WorktreeChangesCollector {
|
|
7507
|
+
orch;
|
|
7508
|
+
bus;
|
|
7509
|
+
options;
|
|
7510
|
+
entries = new Map;
|
|
7511
|
+
stopped = false;
|
|
7512
|
+
constructor(orch, bus, options = {}) {
|
|
7513
|
+
this.orch = orch;
|
|
7514
|
+
this.bus = bus;
|
|
7515
|
+
this.options = options;
|
|
7516
|
+
}
|
|
7517
|
+
tick() {
|
|
7518
|
+
if (this.stopped)
|
|
7519
|
+
return;
|
|
7520
|
+
try {
|
|
7521
|
+
const tracked = trackedWorktreePaths(this.orch.listTasks());
|
|
7522
|
+
let pruned = false;
|
|
7523
|
+
for (const path11 of this.entries.keys()) {
|
|
7524
|
+
if (tracked.has(path11))
|
|
7525
|
+
continue;
|
|
7526
|
+
const entry = this.entries.get(path11);
|
|
7527
|
+
if (entry?.value)
|
|
7528
|
+
pruned = true;
|
|
7529
|
+
this.entries.delete(path11);
|
|
7530
|
+
}
|
|
7531
|
+
if (pruned)
|
|
7532
|
+
this.publish();
|
|
7533
|
+
for (const path11 of tracked)
|
|
7534
|
+
this.maybeCollect(path11);
|
|
7535
|
+
} catch (err) {
|
|
7536
|
+
logDaemonError("worktree-changes", err);
|
|
7537
|
+
}
|
|
7538
|
+
}
|
|
7539
|
+
stop() {
|
|
7540
|
+
this.stopped = true;
|
|
7541
|
+
}
|
|
7542
|
+
maybeCollect(worktreePath) {
|
|
7543
|
+
let entry = this.entries.get(worktreePath);
|
|
7544
|
+
if (!entry) {
|
|
7545
|
+
entry = { inFlight: false, nextAllowedAt: 0 };
|
|
7546
|
+
this.entries.set(worktreePath, entry);
|
|
7547
|
+
}
|
|
7548
|
+
const cadence = this.options.cadence ?? {
|
|
7549
|
+
timeoutMs: WORKTREE_CHANGES_TIMEOUT_MS,
|
|
7550
|
+
slowRetryMs: WORKTREE_CHANGES_SLOW_RETRY_MS,
|
|
7551
|
+
minIntervalMs: WORKTREE_CHANGES_MIN_INTERVAL_MS
|
|
7552
|
+
};
|
|
7553
|
+
const run = this.options.run ?? runGitStatus;
|
|
7554
|
+
maybeStartScheduledRun(entry, cadence, (signal) => run(worktreePath, signal), (value) => {
|
|
7555
|
+
if (this.stopped)
|
|
7556
|
+
return;
|
|
7557
|
+
if (this.entries.get(worktreePath) !== entry)
|
|
7558
|
+
return;
|
|
7559
|
+
if (entry.value && sameWorktreeChanges(entry.value, value))
|
|
7560
|
+
return;
|
|
7561
|
+
entry.value = value;
|
|
7562
|
+
this.publish();
|
|
7563
|
+
});
|
|
7564
|
+
}
|
|
7565
|
+
publish() {
|
|
7566
|
+
const changes = {};
|
|
7567
|
+
for (const [path11, entry] of this.entries) {
|
|
7568
|
+
if (entry.value)
|
|
7569
|
+
changes[path11] = entry.value;
|
|
7570
|
+
}
|
|
7571
|
+
this.bus.publish("worktree.changes", { changes });
|
|
7572
|
+
}
|
|
7573
|
+
}
|
|
7574
|
+
function startWorktreeChangesCollector(orch, bus, tickMs = DEFAULT_WORKTREE_CHANGES_TICK_MS) {
|
|
7575
|
+
if (tickMs <= 0)
|
|
7576
|
+
return () => {};
|
|
7577
|
+
const collector = new WorktreeChangesCollector(orch, bus);
|
|
7578
|
+
collector.tick();
|
|
7579
|
+
const timer = setInterval(() => collector.tick(), tickMs);
|
|
7580
|
+
timer.unref?.();
|
|
7581
|
+
return () => {
|
|
7582
|
+
clearInterval(timer);
|
|
7583
|
+
collector.stop();
|
|
7584
|
+
};
|
|
7585
|
+
}
|
|
7586
|
+
var DEFAULT_WORKTREE_CHANGES_TICK_MS = 2000, WORKTREE_CHANGES_TIMEOUT_MS = 4000, WORKTREE_CHANGES_SLOW_RETRY_MS = 60000, WORKTREE_CHANGES_MIN_INTERVAL_MS = 1500;
|
|
7587
|
+
var init_worktree_changes_collector = __esm(() => {
|
|
7588
|
+
init_poll_scheduling();
|
|
7589
|
+
init_repos();
|
|
7590
|
+
init_worktree_changes();
|
|
7591
|
+
});
|
|
7592
|
+
|
|
7336
7593
|
// ../kobe-daemon/src/daemon/server.ts
|
|
7337
|
-
import { mkdir as mkdir6, readFile as
|
|
7594
|
+
import { mkdir as mkdir6, readFile as readFile7, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
7338
7595
|
import { createServer } from "net";
|
|
7339
7596
|
import { dirname as dirname7 } from "path";
|
|
7340
7597
|
function resolveIdleGraceMs() {
|
|
@@ -7436,6 +7693,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7436
7693
|
path: defaultKeybindingsPath(options.homeDir),
|
|
7437
7694
|
debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
|
|
7438
7695
|
});
|
|
7696
|
+
const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS);
|
|
7439
7697
|
const serverApi = {
|
|
7440
7698
|
socketPath,
|
|
7441
7699
|
pidPath,
|
|
@@ -7450,6 +7708,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7450
7708
|
stopAutoTitlePoller();
|
|
7451
7709
|
stopUiPrefsWatcher();
|
|
7452
7710
|
stopKeybindingsWatcher();
|
|
7711
|
+
stopWorktreeChangesCollector();
|
|
7453
7712
|
activity.close();
|
|
7454
7713
|
broadcast(clients, { type: "event", name: "daemon.stopping", payload: {} });
|
|
7455
7714
|
for (const client of Array.from(clients)) {
|
|
@@ -7546,7 +7805,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7546
7805
|
}
|
|
7547
7806
|
async function readPidFile(pidPath) {
|
|
7548
7807
|
try {
|
|
7549
|
-
const raw = await
|
|
7808
|
+
const raw = await readFile7(pidPath, "utf8");
|
|
7550
7809
|
const pid = Number(raw.trim());
|
|
7551
7810
|
return Number.isFinite(pid) ? pid : null;
|
|
7552
7811
|
} catch {
|
|
@@ -7557,10 +7816,12 @@ function writeFrame(client, frame) {
|
|
|
7557
7816
|
client.socket.write(frameToLine(frame));
|
|
7558
7817
|
}
|
|
7559
7818
|
function broadcast(clients, frame) {
|
|
7819
|
+
let line = null;
|
|
7560
7820
|
for (const client of clients) {
|
|
7561
7821
|
if (!client.subscribed && frame.type === "event")
|
|
7562
7822
|
continue;
|
|
7563
|
-
|
|
7823
|
+
line ??= frameToLine(frame);
|
|
7824
|
+
client.socket.write(line);
|
|
7564
7825
|
}
|
|
7565
7826
|
}
|
|
7566
7827
|
var DEFAULT_UPDATE_POLL_MS, DEFAULT_IDLE_GRACE_MS = 3000;
|
|
@@ -7573,6 +7834,7 @@ var init_server = __esm(() => {
|
|
|
7573
7834
|
init_paths2();
|
|
7574
7835
|
init_protocol();
|
|
7575
7836
|
init_ui_prefs_watcher();
|
|
7837
|
+
init_worktree_changes_collector();
|
|
7576
7838
|
init_handlers();
|
|
7577
7839
|
DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
|
|
7578
7840
|
});
|
|
@@ -7641,7 +7903,7 @@ __export(exports_daemon_process, {
|
|
|
7641
7903
|
connectOrStartDaemon: () => connectOrStartDaemon,
|
|
7642
7904
|
connectIfRunning: () => connectIfRunning
|
|
7643
7905
|
});
|
|
7644
|
-
import { spawn as
|
|
7906
|
+
import { spawn as spawn3 } from "child_process";
|
|
7645
7907
|
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync } from "fs";
|
|
7646
7908
|
import { dirname as dirname8, resolve as resolve2 } from "path";
|
|
7647
7909
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -7655,7 +7917,7 @@ function spawnDetachedDaemon(command, args, env, logPath) {
|
|
|
7655
7917
|
} catch {
|
|
7656
7918
|
stdio = "ignore";
|
|
7657
7919
|
}
|
|
7658
|
-
const child =
|
|
7920
|
+
const child = spawn3(command, [...args], { detached: true, stdio, env });
|
|
7659
7921
|
child.unref();
|
|
7660
7922
|
if (logFd !== undefined) {
|
|
7661
7923
|
try {
|
|
@@ -7938,7 +8200,7 @@ var init_interactive_command = __esm(() => {
|
|
|
7938
8200
|
});
|
|
7939
8201
|
|
|
7940
8202
|
// src/lib/feedback.ts
|
|
7941
|
-
import { spawnSync as
|
|
8203
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
7942
8204
|
function parseRepoSlug(slug) {
|
|
7943
8205
|
const [owner, name] = slug.split("/");
|
|
7944
8206
|
if (!owner || !name)
|
|
@@ -7993,7 +8255,7 @@ function submitFeedback(input, deps = {}) {
|
|
|
7993
8255
|
throw new Error("package repository is not a GitHub repository");
|
|
7994
8256
|
const { owner, name } = parseRepoSlug(slug);
|
|
7995
8257
|
const categorySlug = input.categorySlug?.trim() || DEFAULT_FEEDBACK_CATEGORY_SLUG;
|
|
7996
|
-
const io = { spawn: deps.spawn ??
|
|
8258
|
+
const io = { spawn: deps.spawn ?? spawnSync8 };
|
|
7997
8259
|
const categoryData = runGhGraphql(DISCUSSION_CATEGORY_QUERY, { owner, name }, io);
|
|
7998
8260
|
const repository = categoryData.repository;
|
|
7999
8261
|
const repositoryId = repository?.id;
|
|
@@ -8132,6 +8394,13 @@ var init_keybindings_file = __esm(() => {
|
|
|
8132
8394
|
});
|
|
8133
8395
|
|
|
8134
8396
|
// src/tui/lib/keymap-overrides.ts
|
|
8397
|
+
function pairContract(first, second) {
|
|
8398
|
+
const layout = `alternating [${first}, ${second}] pairs`;
|
|
8399
|
+
return {
|
|
8400
|
+
layout,
|
|
8401
|
+
validateCount: (count) => count >= 2 && count % 2 === 0 ? null : `needs ${layout} (an even number of chords \u2014 got ${count})`
|
|
8402
|
+
};
|
|
8403
|
+
}
|
|
8135
8404
|
function normalizeChord(raw, opts) {
|
|
8136
8405
|
const trimmed = raw.trim().toLowerCase();
|
|
8137
8406
|
if (!trimmed)
|
|
@@ -8271,6 +8540,14 @@ function applyKeymapOverrides(keymap, entries) {
|
|
|
8271
8540
|
warnings.push(`${entry.id}: not customizable \u2014 the key is handled outside the keymap (doc-only row)`);
|
|
8272
8541
|
continue;
|
|
8273
8542
|
}
|
|
8543
|
+
const contract = SLOT_CONTRACTS[entry.id];
|
|
8544
|
+
if (contract && entry.keys.length > 0) {
|
|
8545
|
+
const problem = contract.validateCount(entry.keys.length);
|
|
8546
|
+
if (problem) {
|
|
8547
|
+
warnings.push(`${entry.id}: ${problem} \u2014 keeping the default`);
|
|
8548
|
+
continue;
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8274
8551
|
const keys = entry.keys.filter((chord) => {
|
|
8275
8552
|
if (chord.length === 1 && NO_BARE_LETTER_SCOPES.has(row.scope)) {
|
|
8276
8553
|
warnings.push(`${entry.id}: "${chord}" dropped \u2014 a bare character on a ${row.scope}-scope binding would steal typed input (add a modifier)`);
|
|
@@ -8282,6 +8559,10 @@ function applyKeymapOverrides(keymap, entries) {
|
|
|
8282
8559
|
warnings.push(`${entry.id}: no chords survived validation \u2014 keeping the default`);
|
|
8283
8560
|
continue;
|
|
8284
8561
|
}
|
|
8562
|
+
if (contract && keys.length !== entry.keys.length) {
|
|
8563
|
+
warnings.push(`${entry.id}: a dropped chord would shift the slot layout (${contract.layout}) \u2014 keeping the default`);
|
|
8564
|
+
continue;
|
|
8565
|
+
}
|
|
8285
8566
|
const defaultKeys = row.keys;
|
|
8286
8567
|
const mutable = row;
|
|
8287
8568
|
mutable.keys = keys;
|
|
@@ -8312,21 +8593,23 @@ function applyKeymapOverrides(keymap, entries) {
|
|
|
8312
8593
|
}
|
|
8313
8594
|
return { applied, warnings };
|
|
8314
8595
|
}
|
|
8315
|
-
var FIXED_BINDING_IDS, NO_BARE_LETTER_SCOPES, MOD_ALIASES, KEY_ALIASES, KNOWN_NAMED_KEYS, MOD_ORDER;
|
|
8596
|
+
var FIXED_BINDING_IDS, SLOT_CONTRACTS, NO_BARE_LETTER_SCOPES, MOD_ALIASES, KEY_ALIASES, KNOWN_NAMED_KEYS, MOD_ORDER;
|
|
8316
8597
|
var init_keymap_overrides = __esm(() => {
|
|
8317
8598
|
FIXED_BINDING_IDS = {
|
|
8318
|
-
"focus.numeric": "pane focus is positional (h/j/k/l \u2192 pane) and mirrors the tmux-layer ctrl+hjkl bindings",
|
|
8319
|
-
"sidebar.
|
|
8320
|
-
"sidebar.
|
|
8321
|
-
"sidebar.
|
|
8322
|
-
"
|
|
8323
|
-
"
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
"
|
|
8327
|
-
"files.
|
|
8328
|
-
"
|
|
8329
|
-
"
|
|
8599
|
+
"focus.numeric": "pane focus is positional (h/j/k/l \u2192 pane) and mirrors the tmux-layer ctrl+hjkl bindings \u2014 rebind tmux.focus instead",
|
|
8600
|
+
"sidebar.goto": "gg vs Shift+G is discriminated via evt.shift; shift+<letter> chords are inexpressible, so a rebind can't carry both halves",
|
|
8601
|
+
"sidebar.pin": "fires on Shift+P via evt.shift; shift+<letter> chords are inexpressible, so a rebind can't work",
|
|
8602
|
+
"sidebar.localMerge": "fires on Shift+M via evt.shift; shift+<letter> chords are inexpressible, so a rebind can't work",
|
|
8603
|
+
"chat.question.nav": "the question picker has no live registration site (display-only row) \u2014 rebinding would change Help without changing behavior",
|
|
8604
|
+
"chat.question.pick-number": "digits map to options positionally and the question picker has no live registration site (display-only row)"
|
|
8605
|
+
};
|
|
8606
|
+
SLOT_CONTRACTS = {
|
|
8607
|
+
"sidebar.nav": pairContract("down", "up"),
|
|
8608
|
+
"files.nav": pairContract("down", "up"),
|
|
8609
|
+
"sidebar.search.nav": pairContract("down", "up"),
|
|
8610
|
+
"files.hierarchy": pairContract("collapse", "expand"),
|
|
8611
|
+
"sidebar.view": pairContract("previous view", "next view"),
|
|
8612
|
+
"files.tab": pairContract("previous tab", "next tab")
|
|
8330
8613
|
};
|
|
8331
8614
|
NO_BARE_LETTER_SCOPES = new Set(["global", "workspace", "terminal"]);
|
|
8332
8615
|
MOD_ALIASES = {
|
|
@@ -10183,10 +10466,17 @@ function parseKobePaneRows(stdout) {
|
|
|
10183
10466
|
const line = raw.trim();
|
|
10184
10467
|
if (!line)
|
|
10185
10468
|
continue;
|
|
10186
|
-
const [windowId, paneId, role, version] = line.split("\t");
|
|
10469
|
+
const [windowId, paneId, role, version, paneWidth] = line.split("\t");
|
|
10187
10470
|
if (!windowId || !paneId || !role)
|
|
10188
10471
|
continue;
|
|
10189
|
-
|
|
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
|
+
});
|
|
10190
10480
|
}
|
|
10191
10481
|
return rows;
|
|
10192
10482
|
}
|
|
@@ -10251,38 +10541,18 @@ async function relaunchEngineInAllWindows(session, cwd, command, remoteKey) {
|
|
|
10251
10541
|
if (enginePanes.length === 0)
|
|
10252
10542
|
return false;
|
|
10253
10543
|
const cmd = keepAlive(wrapEngineLaunch(shellQuoteArgv(command), remoteKey, cwd));
|
|
10254
|
-
|
|
10255
|
-
await runTmux(["respawn-pane", "-k", "-c", localSpawnCwd(cwd), "-t", pane, cmd]);
|
|
10256
|
-
}
|
|
10544
|
+
await runTmuxSequence(enginePanes.map((pane) => ["respawn-pane", "-k", "-c", localSpawnCwd(cwd), "-t", pane, cmd]));
|
|
10257
10545
|
return true;
|
|
10258
10546
|
}
|
|
10259
|
-
async function
|
|
10260
|
-
const
|
|
10261
|
-
const
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
"
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
]);
|
|
10269
|
-
if (code !== 0)
|
|
10270
|
-
return;
|
|
10271
|
-
const mismatched = stdout.split(`
|
|
10272
|
-
`).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);
|
|
10273
|
-
if (mismatched.length === 0)
|
|
10274
|
-
return;
|
|
10275
|
-
await runTmuxSequence(mismatched.map((pane) => ["resize-pane", "-t", pane, "-x", `${target}`]));
|
|
10276
|
-
}
|
|
10277
|
-
async function rightColumnPercents() {
|
|
10278
|
-
const [width, height] = await Promise.all([
|
|
10279
|
-
getServerOption(RIGHT_COLUMN_WIDTH_OPTION),
|
|
10280
|
-
getServerOption(OPS_HEIGHT_OPTION)
|
|
10281
|
-
]);
|
|
10282
|
-
return {
|
|
10283
|
-
widthPct: clampPanePercent(Number.parseInt(width, 10)),
|
|
10284
|
-
heightPct: clampPanePercent(Number.parseInt(height, 10))
|
|
10285
|
-
};
|
|
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 };
|
|
10286
10556
|
}
|
|
10287
10557
|
function rightColumnResizeArgs(geom) {
|
|
10288
10558
|
const args = [];
|
|
@@ -10293,33 +10563,35 @@ function rightColumnResizeArgs(geom) {
|
|
|
10293
10563
|
return args;
|
|
10294
10564
|
}
|
|
10295
10565
|
async function globalRightColumnResizeArgs() {
|
|
10296
|
-
return
|
|
10566
|
+
return (await globalLayoutPrefs()).rcArgs;
|
|
10297
10567
|
}
|
|
10298
|
-
async function
|
|
10299
|
-
const
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
const { code, stdout } = await runTmuxCapturing([
|
|
10303
|
-
"list-panes",
|
|
10304
|
-
"-s",
|
|
10305
|
-
"-t",
|
|
10306
|
-
`=${session}`,
|
|
10307
|
-
"-F",
|
|
10308
|
-
"#{pane_id}\t#{@kobe_role}"
|
|
10309
|
-
]);
|
|
10310
|
-
if (code !== 0)
|
|
10311
|
-
return;
|
|
10312
|
-
const opsPanes = stdout.split(`
|
|
10313
|
-
`).map((line) => line.split("\t")).filter(([, role]) => role?.trim() === "ops").map(([id]) => id?.trim()).filter((id) => !!id);
|
|
10314
|
-
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)
|
|
10315
10572
|
return;
|
|
10316
|
-
|
|
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);
|
|
10317
10590
|
}
|
|
10318
10591
|
async function healSessionLayout(session) {
|
|
10319
10592
|
if (!await sessionExists(session))
|
|
10320
10593
|
return;
|
|
10321
|
-
await
|
|
10322
|
-
await healRightColumn(session);
|
|
10594
|
+
await healWorkspaceLayout(session);
|
|
10323
10595
|
}
|
|
10324
10596
|
async function captureGlobalLayout(session) {
|
|
10325
10597
|
const { code, stdout } = await runTmuxCapturing([
|
|
@@ -10358,18 +10630,6 @@ async function captureGlobalLayout(session) {
|
|
|
10358
10630
|
if (sets.length > 0)
|
|
10359
10631
|
await runTmuxSequence(sets);
|
|
10360
10632
|
}
|
|
10361
|
-
async function healKobePaneVersions(session, cwd, taskId, vendor) {
|
|
10362
|
-
const rows = await listKobePanes(session);
|
|
10363
|
-
if (!rows)
|
|
10364
|
-
return;
|
|
10365
|
-
const commands = respawnCommandsFor(planPaneHeals(rows, { currentVersion: CURRENT_VERSION, force: false }), {
|
|
10366
|
-
cwd,
|
|
10367
|
-
taskId,
|
|
10368
|
-
vendor
|
|
10369
|
-
});
|
|
10370
|
-
if (commands.length > 0)
|
|
10371
|
-
await runTmuxSequence(commands);
|
|
10372
|
-
}
|
|
10373
10633
|
async function refreshKobeWorkspacePanes(session) {
|
|
10374
10634
|
const sessionOptions = await getSessionOptions(session, ["@kobe_worktree", "@kobe_task", "@kobe_vendor"]);
|
|
10375
10635
|
const cwd = sessionOptions["@kobe_worktree"] || process.cwd();
|
|
@@ -10395,7 +10655,7 @@ var init_pane_heal = __esm(() => {
|
|
|
10395
10655
|
init_tmux_border_theme();
|
|
10396
10656
|
init_version();
|
|
10397
10657
|
init_launch();
|
|
10398
|
-
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}`;
|
|
10399
10659
|
});
|
|
10400
10660
|
|
|
10401
10661
|
// src/tui/panes/terminal/chattab.ts
|
|
@@ -10625,6 +10885,7 @@ __export(exports_tmux, {
|
|
|
10625
10885
|
refreshKobeWorkspacePanes: () => refreshKobeWorkspacePanes,
|
|
10626
10886
|
quickCreate: () => quickCreate,
|
|
10627
10887
|
prepareWindowForAttach: () => prepareWindowForAttach,
|
|
10888
|
+
parseObservedSession: () => parseObservedSession,
|
|
10628
10889
|
openUpdateTab: () => openUpdateTab,
|
|
10629
10890
|
openSettingsTab: () => openSettingsTab,
|
|
10630
10891
|
openNewTaskTab: () => openNewTaskTab,
|
|
@@ -10633,6 +10894,7 @@ __export(exports_tmux, {
|
|
|
10633
10894
|
kobeStatusRight: () => kobeStatusRight,
|
|
10634
10895
|
killSession: () => killSession,
|
|
10635
10896
|
healSessionLayout: () => healSessionLayout,
|
|
10897
|
+
focusBindCommand: () => focusBindCommand,
|
|
10636
10898
|
ensureSession: () => ensureSession,
|
|
10637
10899
|
currentSessionName: () => currentSessionName,
|
|
10638
10900
|
chatTabSwitchBindings: () => chatTabSwitchBindings,
|
|
@@ -10660,8 +10922,18 @@ async function prepareWindowForAttach(session) {
|
|
|
10660
10922
|
const sizeArgs = tmuxInitialSizeArgs();
|
|
10661
10923
|
if (sizeArgs.length > 0)
|
|
10662
10924
|
await runTmux(["resize-window", "-t", `=${session}`, ...sizeArgs]);
|
|
10663
|
-
await
|
|
10664
|
-
|
|
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
|
+
];
|
|
10665
10937
|
}
|
|
10666
10938
|
async function ensureSession(opts) {
|
|
10667
10939
|
const inflight = ensureSessionLocks.get(opts.name);
|
|
@@ -10675,16 +10947,33 @@ async function ensureSession(opts) {
|
|
|
10675
10947
|
ensureSessionLocks.delete(opts.name);
|
|
10676
10948
|
}
|
|
10677
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
|
+
}
|
|
10678
10970
|
async function observeSession(name) {
|
|
10679
10971
|
if (!await sessionExists(name))
|
|
10680
10972
|
return null;
|
|
10681
|
-
const
|
|
10682
|
-
|
|
10683
|
-
worktree:
|
|
10684
|
-
|
|
10685
|
-
claudePaneAlive: await claudePaneIdStrict(name) !== "",
|
|
10686
|
-
windowCount: await windowCount(name)
|
|
10687
|
-
};
|
|
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);
|
|
10688
10977
|
}
|
|
10689
10978
|
async function ensureSessionImpl(opts) {
|
|
10690
10979
|
const observed = await observeSession(opts.name);
|
|
@@ -10695,18 +10984,14 @@ async function ensureSessionImpl(opts) {
|
|
|
10695
10984
|
});
|
|
10696
10985
|
const remoteKey = remoteKeyForRepo(opts.repo);
|
|
10697
10986
|
if (action.kind === "reuse") {
|
|
10698
|
-
await
|
|
10699
|
-
await healRightColumn(opts.name);
|
|
10700
|
-
await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
|
|
10987
|
+
await healWorkspaceLayout(opts.name, { cwd: opts.cwd, taskId: opts.taskId, vendor: opts.vendor });
|
|
10701
10988
|
return true;
|
|
10702
10989
|
}
|
|
10703
10990
|
if (action.kind === "respawn-engine") {
|
|
10704
10991
|
if (await relaunchEngineInAllWindows(opts.name, opts.cwd, opts.command, remoteKey)) {
|
|
10705
10992
|
if (opts.vendor)
|
|
10706
10993
|
await setSessionOption(opts.name, "@kobe_vendor", opts.vendor);
|
|
10707
|
-
await
|
|
10708
|
-
await healRightColumn(opts.name);
|
|
10709
|
-
await healKobePaneVersions(opts.name, opts.cwd, opts.taskId, opts.vendor);
|
|
10994
|
+
await healWorkspaceLayout(opts.name, { cwd: opts.cwd, taskId: opts.taskId, vendor: opts.vendor });
|
|
10710
10995
|
return true;
|
|
10711
10996
|
}
|
|
10712
10997
|
}
|
|
@@ -10781,7 +11066,7 @@ async function ensureSessionImpl(opts) {
|
|
|
10781
11066
|
const focusDirections = ["-L", "-D", "-U", "-R"];
|
|
10782
11067
|
const focusBinds = userKeys.focus.flatMap((bind, i) => {
|
|
10783
11068
|
const dir = focusDirections[i];
|
|
10784
|
-
return bind && dir ? [
|
|
11069
|
+
return bind && dir ? [focusBindCommand(bind.key, dir)] : [];
|
|
10785
11070
|
});
|
|
10786
11071
|
const b = userKeys.binds;
|
|
10787
11072
|
await runTmuxSequence([
|
|
@@ -10841,7 +11126,7 @@ async function selectTasksPane(session) {
|
|
|
10841
11126
|
await runTmux(["select-pane", "-t", tasksPane]);
|
|
10842
11127
|
return tasksPane;
|
|
10843
11128
|
}
|
|
10844
|
-
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}";
|
|
10845
11130
|
var init_tmux = __esm(() => {
|
|
10846
11131
|
init_invocation();
|
|
10847
11132
|
init_interactive_command();
|
|
@@ -10857,6 +11142,12 @@ var init_tmux = __esm(() => {
|
|
|
10857
11142
|
init_client2();
|
|
10858
11143
|
init_chattab();
|
|
10859
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
|
+
};
|
|
10860
11151
|
ensureSessionLocks = new Map;
|
|
10861
11152
|
});
|
|
10862
11153
|
|
|
@@ -10897,51 +11188,6 @@ var init_repo_init = __esm(() => {
|
|
|
10897
11188
|
INIT_PROMPT_REL = join7(".kobe", "init-prompt.md");
|
|
10898
11189
|
});
|
|
10899
11190
|
|
|
10900
|
-
// src/tui/panes/sidebar/worktree-changes.ts
|
|
10901
|
-
var exports_worktree_changes = {};
|
|
10902
|
-
__export(exports_worktree_changes, {
|
|
10903
|
-
readWorktreeChanges: () => readWorktreeChanges,
|
|
10904
|
-
parsePorcelain: () => parsePorcelain2
|
|
10905
|
-
});
|
|
10906
|
-
import { spawnSync as spawnSync8 } from "child_process";
|
|
10907
|
-
function readWorktreeChanges(worktreePath) {
|
|
10908
|
-
if (!worktreePath)
|
|
10909
|
-
return ZERO;
|
|
10910
|
-
try {
|
|
10911
|
-
const out = spawnSync8("git", ["status", "--porcelain=v1"], {
|
|
10912
|
-
cwd: worktreePath,
|
|
10913
|
-
encoding: "utf8",
|
|
10914
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
10915
|
-
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }
|
|
10916
|
-
});
|
|
10917
|
-
if (out.status !== 0 || !out.stdout)
|
|
10918
|
-
return ZERO;
|
|
10919
|
-
return parsePorcelain2(out.stdout);
|
|
10920
|
-
} catch {
|
|
10921
|
-
return ZERO;
|
|
10922
|
-
}
|
|
10923
|
-
}
|
|
10924
|
-
function parsePorcelain2(text) {
|
|
10925
|
-
let added = 0;
|
|
10926
|
-
let deleted = 0;
|
|
10927
|
-
for (const line of text.split(`
|
|
10928
|
-
`)) {
|
|
10929
|
-
if (!line || line.startsWith("##"))
|
|
10930
|
-
continue;
|
|
10931
|
-
const x = line.charAt(0);
|
|
10932
|
-
const y = line.charAt(1);
|
|
10933
|
-
if (x === "D" || y === "D")
|
|
10934
|
-
deleted += 1;
|
|
10935
|
-
else
|
|
10936
|
-
added += 1;
|
|
10937
|
-
}
|
|
10938
|
-
return { added, deleted };
|
|
10939
|
-
}
|
|
10940
|
-
var ZERO;
|
|
10941
|
-
var init_worktree_changes = __esm(() => {
|
|
10942
|
-
ZERO = { added: 0, deleted: 0 };
|
|
10943
|
-
});
|
|
10944
|
-
|
|
10945
11191
|
// src/cli/api-cmd.ts
|
|
10946
11192
|
var exports_api_cmd = {};
|
|
10947
11193
|
__export(exports_api_cmd, {
|
|
@@ -12913,7 +13159,7 @@ async function handleDiffRequest(req, url) {
|
|
|
12913
13159
|
var GIT_TIMEOUT_MS = 15000;
|
|
12914
13160
|
|
|
12915
13161
|
// src/web/notes.ts
|
|
12916
|
-
import { mkdir as mkdir7, readFile as
|
|
13162
|
+
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
12917
13163
|
import { join as join11 } from "path";
|
|
12918
13164
|
function notesDir() {
|
|
12919
13165
|
return join11(kobeStateDir(), "notes");
|
|
@@ -12932,7 +13178,7 @@ async function handleGet(url) {
|
|
|
12932
13178
|
try {
|
|
12933
13179
|
let markdown = "";
|
|
12934
13180
|
try {
|
|
12935
|
-
markdown = await
|
|
13181
|
+
markdown = await readFile8(noteFilePath(taskId), "utf8");
|
|
12936
13182
|
} catch (err) {
|
|
12937
13183
|
if (err.code !== "ENOENT")
|
|
12938
13184
|
throw err;
|
|
@@ -13035,16 +13281,21 @@ class DaemonLink {
|
|
|
13035
13281
|
const socketPath = allowSpawn ? await ensureDaemonReachable() : defaultDaemonSocketPath();
|
|
13036
13282
|
const client = new KobeDaemonClient(socketPath);
|
|
13037
13283
|
await client.connect();
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13284
|
+
try {
|
|
13285
|
+
const hello = await client.request("hello", {
|
|
13286
|
+
protocolVersion: DAEMON_PROTOCOL_VERSION,
|
|
13287
|
+
minProtocolVersion: MIN_COMPATIBLE_PROTOCOL_VERSION
|
|
13288
|
+
});
|
|
13289
|
+
if (hello.tasks)
|
|
13290
|
+
this.tasks = hello.tasks;
|
|
13291
|
+
this.engineStates = {};
|
|
13292
|
+
client.on("*", (frame) => this.onFrame(frame.name, frame.payload));
|
|
13293
|
+
client.onLifecycle("close", () => this.onDrop(client));
|
|
13294
|
+
await client.subscribe({ role: "gui" });
|
|
13295
|
+
} catch (err) {
|
|
13296
|
+
client.close();
|
|
13297
|
+
throw err;
|
|
13298
|
+
}
|
|
13048
13299
|
this.client = client;
|
|
13049
13300
|
this.setConnected(true);
|
|
13050
13301
|
}
|
|
@@ -15101,6 +15352,30 @@ var init_solid = __esm(() => {
|
|
|
15101
15352
|
});
|
|
15102
15353
|
|
|
15103
15354
|
// src/client/remote-orchestrator.ts
|
|
15355
|
+
function parseWorktreeChangesPayload(payload) {
|
|
15356
|
+
const changes = payload?.changes;
|
|
15357
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes))
|
|
15358
|
+
return null;
|
|
15359
|
+
const map = new Map;
|
|
15360
|
+
for (const [path11, value] of Object.entries(changes)) {
|
|
15361
|
+
const counts = value;
|
|
15362
|
+
if (typeof counts?.added !== "number" || typeof counts.deleted !== "number")
|
|
15363
|
+
return null;
|
|
15364
|
+
map.set(path11, { added: counts.added, deleted: counts.deleted });
|
|
15365
|
+
}
|
|
15366
|
+
return map;
|
|
15367
|
+
}
|
|
15368
|
+
function sameWorktreeChangesMap(a, b) {
|
|
15369
|
+
if (a.size !== b.size)
|
|
15370
|
+
return false;
|
|
15371
|
+
for (const [path11, counts] of a) {
|
|
15372
|
+
const other = b.get(path11);
|
|
15373
|
+
if (!other || !sameWorktreeChanges(counts, other))
|
|
15374
|
+
return false;
|
|
15375
|
+
}
|
|
15376
|
+
return true;
|
|
15377
|
+
}
|
|
15378
|
+
|
|
15104
15379
|
class RemoteOrchestrator {
|
|
15105
15380
|
client;
|
|
15106
15381
|
tasksAcc;
|
|
@@ -15113,6 +15388,10 @@ class RemoteOrchestrator {
|
|
|
15113
15388
|
setDaemonVersionSig;
|
|
15114
15389
|
engineStateAcc;
|
|
15115
15390
|
setEngineStateSig;
|
|
15391
|
+
taskJobsAcc;
|
|
15392
|
+
setTaskJobsSig;
|
|
15393
|
+
worktreeChangesAcc;
|
|
15394
|
+
setWorktreeChangesSig;
|
|
15116
15395
|
uiPrefsAcc;
|
|
15117
15396
|
setUiPrefsSig;
|
|
15118
15397
|
keybindingsRevAcc;
|
|
@@ -15129,6 +15408,8 @@ class RemoteOrchestrator {
|
|
|
15129
15408
|
const [update, setUpdate] = createSignal(null);
|
|
15130
15409
|
const [daemonVersion, setDaemonVersion] = createSignal(null);
|
|
15131
15410
|
const [engineState, setEngineState] = createSignal(new Map);
|
|
15411
|
+
const [taskJobs, setTaskJobs] = createSignal(new Map);
|
|
15412
|
+
const [worktreeChanges, setWorktreeChanges] = createSignal(null);
|
|
15132
15413
|
const [uiPrefs, setUiPrefs] = createSignal(null);
|
|
15133
15414
|
const [keybindingsRev, setKeybindingsRev] = createSignal(null);
|
|
15134
15415
|
const [connectionState, setConnectionState] = createSignal("online");
|
|
@@ -15142,6 +15423,10 @@ class RemoteOrchestrator {
|
|
|
15142
15423
|
this.setDaemonVersionSig = (next) => setDaemonVersion(() => next);
|
|
15143
15424
|
this.engineStateAcc = engineState;
|
|
15144
15425
|
this.setEngineStateSig = (next) => setEngineState(() => next);
|
|
15426
|
+
this.taskJobsAcc = taskJobs;
|
|
15427
|
+
this.setTaskJobsSig = (next) => setTaskJobs(() => next);
|
|
15428
|
+
this.worktreeChangesAcc = worktreeChanges;
|
|
15429
|
+
this.setWorktreeChangesSig = (next) => setWorktreeChanges(() => next);
|
|
15145
15430
|
this.uiPrefsAcc = uiPrefs;
|
|
15146
15431
|
this.setUiPrefsSig = (next) => setUiPrefs(() => next);
|
|
15147
15432
|
this.keybindingsRevAcc = keybindingsRev;
|
|
@@ -15202,6 +15487,12 @@ class RemoteOrchestrator {
|
|
|
15202
15487
|
if (hello.tasks)
|
|
15203
15488
|
this.setTasks(hello.tasks.map(deserializeTask));
|
|
15204
15489
|
await this.client.subscribe({ role: this.role });
|
|
15490
|
+
if (hello.capabilities?.includes("worktree.changes")) {
|
|
15491
|
+
if (this.worktreeChangesAcc() === null)
|
|
15492
|
+
this.setWorktreeChangesSig(new Map);
|
|
15493
|
+
} else {
|
|
15494
|
+
this.setWorktreeChangesSig(null);
|
|
15495
|
+
}
|
|
15205
15496
|
this.setConnectionState("online");
|
|
15206
15497
|
logClient("orch", `subscribed as ${this.role} (${this.tasksAcc().length} tasks)`);
|
|
15207
15498
|
}
|
|
@@ -15234,6 +15525,12 @@ class RemoteOrchestrator {
|
|
|
15234
15525
|
engineStateSignal() {
|
|
15235
15526
|
return this.engineStateAcc;
|
|
15236
15527
|
}
|
|
15528
|
+
taskJobsSignal() {
|
|
15529
|
+
return this.taskJobsAcc;
|
|
15530
|
+
}
|
|
15531
|
+
worktreeChangesSignal() {
|
|
15532
|
+
return this.worktreeChangesAcc;
|
|
15533
|
+
}
|
|
15237
15534
|
uiPrefsSignal() {
|
|
15238
15535
|
return this.uiPrefsAcc;
|
|
15239
15536
|
}
|
|
@@ -15319,8 +15616,11 @@ class RemoteOrchestrator {
|
|
|
15319
15616
|
handleEvent(name, payload) {
|
|
15320
15617
|
if (name === "task.snapshot") {
|
|
15321
15618
|
const value = payload?.tasks;
|
|
15322
|
-
if (Array.isArray(value))
|
|
15619
|
+
if (Array.isArray(value)) {
|
|
15323
15620
|
this.setTasks(value.map(deserializeTask));
|
|
15621
|
+
this.pruneEngineState(value);
|
|
15622
|
+
this.pruneTaskJobs(value);
|
|
15623
|
+
}
|
|
15324
15624
|
return;
|
|
15325
15625
|
}
|
|
15326
15626
|
if (name === "active-task") {
|
|
@@ -15345,6 +15645,34 @@ class RemoteOrchestrator {
|
|
|
15345
15645
|
this.setEngineStateSig(next);
|
|
15346
15646
|
return;
|
|
15347
15647
|
}
|
|
15648
|
+
if (name === "task.jobs") {
|
|
15649
|
+
const p = payload;
|
|
15650
|
+
if (typeof p?.taskId !== "string" || p.kind !== "ensureWorktree")
|
|
15651
|
+
return;
|
|
15652
|
+
const current = this.taskJobsAcc();
|
|
15653
|
+
if (p.phase === "running") {
|
|
15654
|
+
const next = new Map(current);
|
|
15655
|
+
next.set(p.taskId, { kind: p.kind });
|
|
15656
|
+
this.setTaskJobsSig(next);
|
|
15657
|
+
return;
|
|
15658
|
+
}
|
|
15659
|
+
if ((p.phase === "done" || p.phase === "error") && current.has(p.taskId)) {
|
|
15660
|
+
const next = new Map(current);
|
|
15661
|
+
next.delete(p.taskId);
|
|
15662
|
+
this.setTaskJobsSig(next);
|
|
15663
|
+
}
|
|
15664
|
+
return;
|
|
15665
|
+
}
|
|
15666
|
+
if (name === "worktree.changes") {
|
|
15667
|
+
const next = parseWorktreeChangesPayload(payload);
|
|
15668
|
+
if (!next)
|
|
15669
|
+
return;
|
|
15670
|
+
const current = this.worktreeChangesAcc();
|
|
15671
|
+
if (current && sameWorktreeChangesMap(current, next))
|
|
15672
|
+
return;
|
|
15673
|
+
this.setWorktreeChangesSig(next);
|
|
15674
|
+
return;
|
|
15675
|
+
}
|
|
15348
15676
|
if (name === "ui-prefs") {
|
|
15349
15677
|
const p = payload;
|
|
15350
15678
|
if (typeof p?.theme !== "string")
|
|
@@ -15366,6 +15694,38 @@ class RemoteOrchestrator {
|
|
|
15366
15694
|
return;
|
|
15367
15695
|
}
|
|
15368
15696
|
}
|
|
15697
|
+
pruneEngineState(tasks) {
|
|
15698
|
+
const current = this.engineStateAcc();
|
|
15699
|
+
if (current.size === 0)
|
|
15700
|
+
return;
|
|
15701
|
+
const live = new Set(tasks.map((t) => t.id));
|
|
15702
|
+
let next = null;
|
|
15703
|
+
for (const key of current.keys()) {
|
|
15704
|
+
if (live.has(key))
|
|
15705
|
+
continue;
|
|
15706
|
+
if (!next)
|
|
15707
|
+
next = new Map(current);
|
|
15708
|
+
next.delete(key);
|
|
15709
|
+
}
|
|
15710
|
+
if (next)
|
|
15711
|
+
this.setEngineStateSig(next);
|
|
15712
|
+
}
|
|
15713
|
+
pruneTaskJobs(tasks) {
|
|
15714
|
+
const current = this.taskJobsAcc();
|
|
15715
|
+
if (current.size === 0)
|
|
15716
|
+
return;
|
|
15717
|
+
const live = new Set(tasks.map((t) => t.id));
|
|
15718
|
+
let next = null;
|
|
15719
|
+
for (const key of current.keys()) {
|
|
15720
|
+
if (live.has(key))
|
|
15721
|
+
continue;
|
|
15722
|
+
if (!next)
|
|
15723
|
+
next = new Map(current);
|
|
15724
|
+
next.delete(key);
|
|
15725
|
+
}
|
|
15726
|
+
if (next)
|
|
15727
|
+
this.setTaskJobsSig(next);
|
|
15728
|
+
}
|
|
15369
15729
|
}
|
|
15370
15730
|
function deserializeTask(s) {
|
|
15371
15731
|
return {
|
|
@@ -15389,6 +15749,7 @@ var init_remote_orchestrator = __esm(() => {
|
|
|
15389
15749
|
init_daemon_process();
|
|
15390
15750
|
init_protocol();
|
|
15391
15751
|
init_dev();
|
|
15752
|
+
init_worktree_changes();
|
|
15392
15753
|
init_version();
|
|
15393
15754
|
});
|
|
15394
15755
|
|
|
@@ -15917,7 +16278,7 @@ function dispatchKeyEvent(bindingStack, evt) {
|
|
|
15917
16278
|
continue;
|
|
15918
16279
|
const hit = cfg.bindings.find((b) => candidates.includes(b.key));
|
|
15919
16280
|
if (hit) {
|
|
15920
|
-
hit.cmd(evt);
|
|
16281
|
+
hit.cmd(evt, hit.slot);
|
|
15921
16282
|
evt.preventDefault();
|
|
15922
16283
|
return true;
|
|
15923
16284
|
}
|
|
@@ -16328,7 +16689,7 @@ function joinDrill(typedValue, baseExpanded, name) {
|
|
|
16328
16689
|
var init_path_helpers = () => {};
|
|
16329
16690
|
|
|
16330
16691
|
// src/tui/component/new-task-dialog/clone.ts
|
|
16331
|
-
import { spawn as
|
|
16692
|
+
import { spawn as spawn4 } from "child_process";
|
|
16332
16693
|
import * as fs5 from "fs";
|
|
16333
16694
|
import * as path11 from "path";
|
|
16334
16695
|
function deriveFolderName(url) {
|
|
@@ -16407,7 +16768,7 @@ function cloneRepo(url, target, onProgress) {
|
|
|
16407
16768
|
return new Promise((resolve8) => {
|
|
16408
16769
|
let stderrBuf = "";
|
|
16409
16770
|
try {
|
|
16410
|
-
const child =
|
|
16771
|
+
const child = spawn4("git", ["clone", "--progress", url, target], {
|
|
16411
16772
|
stdio: ["ignore", "ignore", "pipe"]
|
|
16412
16773
|
});
|
|
16413
16774
|
child.stderr?.setEncoding("utf-8");
|
|
@@ -17917,7 +18278,7 @@ function bumpKeymapVersion() {
|
|
|
17917
18278
|
setKeymapVersion((v) => v + 1);
|
|
17918
18279
|
}
|
|
17919
18280
|
function findBinding(id) {
|
|
17920
|
-
return
|
|
18281
|
+
return KEYMAP_BY_ID.get(id);
|
|
17921
18282
|
}
|
|
17922
18283
|
function chordsOf(id) {
|
|
17923
18284
|
return findBinding(id)?.keys ?? [];
|
|
@@ -17933,12 +18294,11 @@ function bindByIds(handlers) {
|
|
|
17933
18294
|
console.warn(`[kobe/keybindings] bindByIds: id="${id}" has no chords (or doesn't exist in KobeKeymap)`);
|
|
17934
18295
|
continue;
|
|
17935
18296
|
}
|
|
17936
|
-
|
|
17937
|
-
out.push({ key: c, cmd });
|
|
18297
|
+
chords.forEach((c, slot) => out.push({ key: c, cmd, slot }));
|
|
17938
18298
|
}
|
|
17939
18299
|
return out;
|
|
17940
18300
|
}
|
|
17941
|
-
var KobeKeymap, KEYMAP_DEFAULTS, keymapVersion, setKeymapVersion;
|
|
18301
|
+
var KobeKeymap, KEYMAP_DEFAULTS, keymapVersion, setKeymapVersion, KEYMAP_BY_ID;
|
|
17942
18302
|
var init_keybindings2 = __esm(() => {
|
|
17943
18303
|
init_dev();
|
|
17944
18304
|
KobeKeymap = [
|
|
@@ -18151,6 +18511,14 @@ var init_keybindings2 = __esm(() => {
|
|
|
18151
18511
|
description: "Open the update page (when a new version is available)",
|
|
18152
18512
|
hint: { keys: "u", label: "update", status: false }
|
|
18153
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
|
+
},
|
|
18154
18522
|
{
|
|
18155
18523
|
id: "tasks.toggleKeys",
|
|
18156
18524
|
scope: "sidebar",
|
|
@@ -18400,6 +18768,7 @@ var init_keybindings2 = __esm(() => {
|
|
|
18400
18768
|
];
|
|
18401
18769
|
KEYMAP_DEFAULTS = new Map(KobeKeymap.map((b) => [b.id, { keys: [...b.keys], hint: b.hint ? { ...b.hint } : undefined }]));
|
|
18402
18770
|
[keymapVersion, setKeymapVersion] = createSignal(0);
|
|
18771
|
+
KEYMAP_BY_ID = new Map(KobeKeymap.map((b) => [b.id, b]));
|
|
18403
18772
|
});
|
|
18404
18773
|
|
|
18405
18774
|
// src/tui/context/keybindings-user.ts
|
|
@@ -18839,20 +19208,28 @@ function UiPrefsSync(props) {
|
|
|
18839
19208
|
focusAccent: props.boot.focusAccent
|
|
18840
19209
|
});
|
|
18841
19210
|
const [prefsOrch, setPrefsOrch] = createSignal(null);
|
|
19211
|
+
let disposed = false;
|
|
18842
19212
|
onMount(() => {
|
|
18843
19213
|
(async () => {
|
|
19214
|
+
let remote = null;
|
|
18844
19215
|
try {
|
|
18845
19216
|
const client = await connectIfRunning();
|
|
18846
19217
|
if (!client) {
|
|
18847
19218
|
logClient("ui-prefs", "no daemon \u2014 keeping boot-time visual prefs");
|
|
18848
19219
|
return;
|
|
18849
19220
|
}
|
|
18850
|
-
|
|
19221
|
+
remote = new RemoteOrchestrator(client);
|
|
18851
19222
|
await remote.init();
|
|
18852
|
-
setPrefsOrch(remote);
|
|
18853
19223
|
} catch (err) {
|
|
18854
19224
|
logClientError("ui-prefs", err);
|
|
19225
|
+
remote?.dispose();
|
|
19226
|
+
return;
|
|
19227
|
+
}
|
|
19228
|
+
if (disposed) {
|
|
19229
|
+
remote.dispose();
|
|
19230
|
+
return;
|
|
18855
19231
|
}
|
|
19232
|
+
setPrefsOrch(remote);
|
|
18856
19233
|
})();
|
|
18857
19234
|
});
|
|
18858
19235
|
createEffect(() => {
|
|
@@ -18872,7 +19249,10 @@ function UiPrefsSync(props) {
|
|
|
18872
19249
|
lastKeybindingsRev = rev;
|
|
18873
19250
|
reloadUserKeybindings();
|
|
18874
19251
|
});
|
|
18875
|
-
onCleanup(() =>
|
|
19252
|
+
onCleanup(() => {
|
|
19253
|
+
disposed = true;
|
|
19254
|
+
prefsOrch()?.dispose();
|
|
19255
|
+
});
|
|
18876
19256
|
return null;
|
|
18877
19257
|
}
|
|
18878
19258
|
async function bootPaneHost(opts) {
|
|
@@ -19097,6 +19477,31 @@ function repoBasename(repo) {
|
|
|
19097
19477
|
function flattenIds(rows) {
|
|
19098
19478
|
return rows.map((r) => r.task.id);
|
|
19099
19479
|
}
|
|
19480
|
+
function sameSidebarRowTask(a, b) {
|
|
19481
|
+
return a === b || a.id === b.id && a.kind === b.kind && a.title === b.title && a.repo === b.repo && a.branch === b.branch && a.worktreePath === b.worktreePath && a.status === b.status && a.archived === b.archived && a.pinned === b.pinned && a.vendor === b.vendor;
|
|
19482
|
+
}
|
|
19483
|
+
function reconcileSidebarRows(prev, next) {
|
|
19484
|
+
if (prev.length === 0)
|
|
19485
|
+
return next;
|
|
19486
|
+
const prevById = new Map;
|
|
19487
|
+
for (const row of prev)
|
|
19488
|
+
prevById.set(row.task.id, row);
|
|
19489
|
+
let allReused = prev.length === next.length;
|
|
19490
|
+
const out = new Array(next.length);
|
|
19491
|
+
for (let i = 0;i < next.length; i++) {
|
|
19492
|
+
const fresh = next[i];
|
|
19493
|
+
const old = prevById.get(fresh.task.id);
|
|
19494
|
+
if (old && old.flatIndex === fresh.flatIndex && sameSidebarRowTask(old.task, fresh.task)) {
|
|
19495
|
+
out[i] = old;
|
|
19496
|
+
if (allReused && prev[i] !== old)
|
|
19497
|
+
allReused = false;
|
|
19498
|
+
} else {
|
|
19499
|
+
out[i] = fresh;
|
|
19500
|
+
allReused = false;
|
|
19501
|
+
}
|
|
19502
|
+
}
|
|
19503
|
+
return allReused ? prev : out;
|
|
19504
|
+
}
|
|
19100
19505
|
var init_groups = () => {};
|
|
19101
19506
|
|
|
19102
19507
|
// src/tui/quick-task/host.tsx
|
|
@@ -22039,7 +22444,7 @@ var init_task_actions = __esm(() => {
|
|
|
22039
22444
|
});
|
|
22040
22445
|
|
|
22041
22446
|
// src/tui/lib/worktree-opener.ts
|
|
22042
|
-
import { spawn as
|
|
22447
|
+
import { spawn as spawn5 } from "child_process";
|
|
22043
22448
|
import { existsSync as existsSync15 } from "fs";
|
|
22044
22449
|
import { basename as basename7, delimiter, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
22045
22450
|
function executableOnPath(command, env, exists) {
|
|
@@ -22098,7 +22503,7 @@ function buildOpenWorktreeCommand(worktreePath, opener) {
|
|
|
22098
22503
|
function openWorktree(worktreePath, opener, deps = {}) {
|
|
22099
22504
|
if (!worktreePath)
|
|
22100
22505
|
return false;
|
|
22101
|
-
const spawnFn = deps.spawn ??
|
|
22506
|
+
const spawnFn = deps.spawn ?? spawn5;
|
|
22102
22507
|
const [command, args2] = buildOpenWorktreeCommand(worktreePath, opener);
|
|
22103
22508
|
try {
|
|
22104
22509
|
const child = spawnFn(command, args2, { detached: true, stdio: "ignore" });
|
|
@@ -22147,15 +22552,6 @@ var init_worktree_opener = __esm(() => {
|
|
|
22147
22552
|
});
|
|
22148
22553
|
|
|
22149
22554
|
// src/tui/lib/background-poll.ts
|
|
22150
|
-
import { spawn as spawn5 } from "child_process";
|
|
22151
|
-
function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
|
|
22152
|
-
if (timedOut)
|
|
22153
|
-
return startedAt + cfg.slowRetryMs;
|
|
22154
|
-
return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
|
|
22155
|
-
}
|
|
22156
|
-
function shouldPoll(state, now) {
|
|
22157
|
-
return !state.inFlight && now >= state.nextAllowedAt;
|
|
22158
|
-
}
|
|
22159
22555
|
function createBackgroundPoller(cfg) {
|
|
22160
22556
|
const entries = new Map;
|
|
22161
22557
|
function entryFor(key) {
|
|
@@ -22177,95 +22573,75 @@ function createBackgroundPoller(cfg) {
|
|
|
22177
22573
|
if (!key)
|
|
22178
22574
|
return;
|
|
22179
22575
|
const entry = entryFor(key);
|
|
22180
|
-
|
|
22181
|
-
if (!shouldPoll(entry, startedAt))
|
|
22182
|
-
return;
|
|
22183
|
-
entry.inFlight = true;
|
|
22184
|
-
const controller = new AbortController;
|
|
22185
|
-
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
22186
|
-
(async () => {
|
|
22187
|
-
let value;
|
|
22188
|
-
let ok = false;
|
|
22189
|
-
try {
|
|
22190
|
-
value = await cfg.run(key, controller.signal);
|
|
22191
|
-
ok = true;
|
|
22192
|
-
} catch {}
|
|
22193
|
-
clearTimeout(timer);
|
|
22194
|
-
const timedOut = controller.signal.aborted;
|
|
22195
|
-
entry.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
|
|
22196
|
-
entry.inFlight = false;
|
|
22197
|
-
if (ok && !timedOut)
|
|
22198
|
-
entry.write(value);
|
|
22199
|
-
})();
|
|
22576
|
+
maybeStartScheduledRun(entry, cfg, (signal) => cfg.run(key, signal), (value) => entry.write(value));
|
|
22200
22577
|
},
|
|
22201
22578
|
reset() {
|
|
22202
22579
|
entries.clear();
|
|
22203
22580
|
}
|
|
22204
22581
|
};
|
|
22205
22582
|
}
|
|
22206
|
-
function spawnCapture(cmd, args2, opts) {
|
|
22207
|
-
return new Promise((resolve9) => {
|
|
22208
|
-
let out = "";
|
|
22209
|
-
let settled = false;
|
|
22210
|
-
const finish = (status) => {
|
|
22211
|
-
if (settled)
|
|
22212
|
-
return;
|
|
22213
|
-
settled = true;
|
|
22214
|
-
resolve9({ status, stdout: out });
|
|
22215
|
-
};
|
|
22216
|
-
const child = spawn5(cmd, args2.slice(), {
|
|
22217
|
-
cwd: opts.cwd,
|
|
22218
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
22219
|
-
env: opts.env,
|
|
22220
|
-
signal: opts.signal,
|
|
22221
|
-
killSignal: "SIGKILL"
|
|
22222
|
-
});
|
|
22223
|
-
child.stdout?.on("data", (chunk) => {
|
|
22224
|
-
out += String(chunk);
|
|
22225
|
-
});
|
|
22226
|
-
child.on("error", () => finish(null));
|
|
22227
|
-
child.on("close", (code) => finish(code));
|
|
22228
|
-
});
|
|
22229
|
-
}
|
|
22230
22583
|
var init_background_poll = __esm(() => {
|
|
22231
22584
|
init_dev();
|
|
22585
|
+
init_poll_scheduling();
|
|
22586
|
+
init_poll_scheduling();
|
|
22232
22587
|
});
|
|
22233
22588
|
|
|
22234
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
|
+
}
|
|
22235
22629
|
function currentBranch(repo) {
|
|
22236
22630
|
return poller.read(repo);
|
|
22237
22631
|
}
|
|
22238
22632
|
function pollCurrentBranch(repo) {
|
|
22239
22633
|
poller.poll(repo);
|
|
22240
22634
|
}
|
|
22241
|
-
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;
|
|
22242
22636
|
var init_git_head = __esm(() => {
|
|
22243
22637
|
init_background_poll();
|
|
22638
|
+
headCache = new Map;
|
|
22244
22639
|
poller = createBackgroundPoller({
|
|
22245
22640
|
initial: "",
|
|
22246
22641
|
timeoutMs: BRANCH_POLL_TIMEOUT_MS,
|
|
22247
22642
|
slowRetryMs: BRANCH_SLOW_RETRY_MS,
|
|
22248
22643
|
minIntervalMs: BRANCH_MIN_POLL_INTERVAL_MS,
|
|
22249
|
-
run:
|
|
22250
|
-
const ref = await spawnCapture("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
22251
|
-
cwd: repo,
|
|
22252
|
-
env: gitEnv(),
|
|
22253
|
-
signal
|
|
22254
|
-
});
|
|
22255
|
-
if (ref.status === 0) {
|
|
22256
|
-
const name = ref.stdout.trim();
|
|
22257
|
-
if (name && name !== "HEAD")
|
|
22258
|
-
return name;
|
|
22259
|
-
}
|
|
22260
|
-
const head = await spawnCapture("git", ["rev-parse", "--verify", "HEAD"], {
|
|
22261
|
-
cwd: repo,
|
|
22262
|
-
env: gitEnv(),
|
|
22263
|
-
signal
|
|
22264
|
-
});
|
|
22265
|
-
if (head.status === 0)
|
|
22266
|
-
return "(detached)";
|
|
22267
|
-
return "";
|
|
22268
|
-
}
|
|
22644
|
+
run: (repo, signal) => resolveBranchHead(repo, signal)
|
|
22269
22645
|
});
|
|
22270
22646
|
});
|
|
22271
22647
|
|
|
@@ -22366,20 +22742,18 @@ function useSidebarBindings(opts) {
|
|
|
22366
22742
|
useBindings(() => ({
|
|
22367
22743
|
enabled: opts.focused() && !searchModeAccessor(),
|
|
22368
22744
|
bindings: bindByIds({
|
|
22369
|
-
"sidebar.nav": (
|
|
22745
|
+
"sidebar.nav": (_evt, slot) => {
|
|
22746
|
+
const down = (slot ?? 0) % 2 === 0;
|
|
22370
22747
|
if (moveModeAccessor()) {
|
|
22371
22748
|
const id = cursorTaskId();
|
|
22372
22749
|
if (id === undefined)
|
|
22373
22750
|
return;
|
|
22374
|
-
|
|
22375
|
-
opts.onMoveRequest?.(id, 1);
|
|
22376
|
-
else if (evt.name === "k" || evt.name === "up")
|
|
22377
|
-
opts.onMoveRequest?.(id, -1);
|
|
22751
|
+
opts.onMoveRequest?.(id, down ? 1 : -1);
|
|
22378
22752
|
return;
|
|
22379
22753
|
}
|
|
22380
|
-
if (
|
|
22754
|
+
if (down)
|
|
22381
22755
|
ctrl.moveDown();
|
|
22382
|
-
else
|
|
22756
|
+
else
|
|
22383
22757
|
ctrl.moveUp();
|
|
22384
22758
|
},
|
|
22385
22759
|
"sidebar.select": () => {
|
|
@@ -22453,21 +22827,18 @@ function useSidebarBindings(opts) {
|
|
|
22453
22827
|
useBindings(() => ({
|
|
22454
22828
|
enabled: opts.focused(),
|
|
22455
22829
|
bindings: bindByIds({
|
|
22456
|
-
"sidebar.view": (
|
|
22457
|
-
|
|
22458
|
-
opts.onViewSwitch?.(1);
|
|
22459
|
-
else
|
|
22460
|
-
opts.onViewSwitch?.(-1);
|
|
22830
|
+
"sidebar.view": (_evt, slot) => {
|
|
22831
|
+
opts.onViewSwitch?.((slot ?? 0) % 2 === 0 ? -1 : 1);
|
|
22461
22832
|
}
|
|
22462
22833
|
})
|
|
22463
22834
|
}));
|
|
22464
22835
|
useBindings(() => ({
|
|
22465
22836
|
enabled: opts.focused() && searchModeAccessor(),
|
|
22466
22837
|
bindings: bindByIds({
|
|
22467
|
-
"sidebar.search.nav": (
|
|
22468
|
-
if (
|
|
22838
|
+
"sidebar.search.nav": (_evt, slot) => {
|
|
22839
|
+
if ((slot ?? 0) % 2 === 0)
|
|
22469
22840
|
ctrl.moveDown();
|
|
22470
|
-
else
|
|
22841
|
+
else
|
|
22471
22842
|
ctrl.moveUp();
|
|
22472
22843
|
},
|
|
22473
22844
|
"sidebar.search.submit": () => {
|
|
@@ -22523,11 +22894,12 @@ function buildSidebarRowView(opts) {
|
|
|
22523
22894
|
const activityBadge = activityBadgeFor(activityState);
|
|
22524
22895
|
const activityLabel = activityLabelFor(activityState);
|
|
22525
22896
|
const untrackedCustomEngine = isCustomEngineTask(task) && !hasActivity;
|
|
22526
|
-
const
|
|
22897
|
+
const materializing = opts.job !== undefined;
|
|
22898
|
+
const loading = materializing || !untrackedCustomEngine && (activityState === "running" || opts.live || !hasActivity && !isMain && task.status === "in_progress");
|
|
22527
22899
|
const spinner = IN_PROGRESS_SPINNER[opts.spinnerFrame] ?? IN_PROGRESS_SPINNER[0];
|
|
22528
|
-
const tone = untrackedCustomEngine ? "textMuted" : activityLabel?.tone ?? (loading ? "primary" : activityBadge?.tone ?? badge.tone);
|
|
22900
|
+
const tone = materializing ? "primary" : untrackedCustomEngine ? "textMuted" : activityLabel?.tone ?? (loading ? "primary" : activityBadge?.tone ?? badge.tone);
|
|
22529
22901
|
const fallbackSubtitle = untrackedCustomEngine ? NO_TRACKING_SUBTITLE : STATUS_LABEL[task.status];
|
|
22530
|
-
const subtitleText = activityLabel ? opts.truncateBranch(activityLabel.text, opts.subtitleBudget) : branch.length > 0 ? opts.truncateBranch(branch, opts.subtitleBudget) : opts.truncateBranch(fallbackSubtitle, opts.subtitleBudget);
|
|
22902
|
+
const subtitleText = materializing ? opts.truncateBranch(MATERIALIZING_SUBTITLE, opts.subtitleBudget) : activityLabel ? opts.truncateBranch(activityLabel.text, opts.subtitleBudget) : branch.length > 0 ? opts.truncateBranch(branch, opts.subtitleBudget) : opts.truncateBranch(fallbackSubtitle, opts.subtitleBudget);
|
|
22531
22903
|
const restGlyph = untrackedCustomEngine ? NO_TRACKING_GLYPH : activityBadge?.glyph ?? badge.glyph;
|
|
22532
22904
|
const restProjectGlyph = untrackedCustomEngine ? NO_TRACKING_GLYPH : activityBadge?.glyph ?? "\u2605";
|
|
22533
22905
|
return {
|
|
@@ -22540,6 +22912,14 @@ function buildSidebarRowView(opts) {
|
|
|
22540
22912
|
tone
|
|
22541
22913
|
};
|
|
22542
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
|
+
}
|
|
22543
22923
|
function activityBadgeFor(state) {
|
|
22544
22924
|
switch (state) {
|
|
22545
22925
|
case "rate_limited":
|
|
@@ -22554,7 +22934,7 @@ function activityBadgeFor(state) {
|
|
|
22554
22934
|
return null;
|
|
22555
22935
|
}
|
|
22556
22936
|
}
|
|
22557
|
-
var STATUS_BADGE, STATUS_LABEL, IN_PROGRESS_SPINNER, SPINNER_FRAME_MS = 100, NO_TRACKING_GLYPH = "\xB7", NO_TRACKING_SUBTITLE = "no activity tracking";
|
|
22937
|
+
var STATUS_BADGE, STATUS_LABEL, IN_PROGRESS_SPINNER, SPINNER_FRAME_MS = 100, NO_TRACKING_GLYPH = "\xB7", NO_TRACKING_SUBTITLE = "no activity tracking", MATERIALIZING_SUBTITLE = "materializing";
|
|
22558
22938
|
var init_row_view = __esm(() => {
|
|
22559
22939
|
init_vendor();
|
|
22560
22940
|
init_groups();
|
|
@@ -22592,7 +22972,7 @@ var init_worktree_changes_poller = __esm(() => {
|
|
|
22592
22972
|
ZERO2 = { added: 0, deleted: 0 };
|
|
22593
22973
|
poller2 = createBackgroundPoller({
|
|
22594
22974
|
initial: ZERO2,
|
|
22595
|
-
equals:
|
|
22975
|
+
equals: sameWorktreeChanges,
|
|
22596
22976
|
timeoutMs: POLL_TIMEOUT_MS,
|
|
22597
22977
|
slowRetryMs: SLOW_REPO_RETRY_MS,
|
|
22598
22978
|
minIntervalMs: MIN_POLL_INTERVAL_MS,
|
|
@@ -22680,7 +23060,7 @@ function Sidebar(props) {
|
|
|
22680
23060
|
const spinnerInterval = setInterval(() => setSpinnerFrame((n) => (n + 1) % IN_PROGRESS_SPINNER.length), SPINNER_FRAME_MS);
|
|
22681
23061
|
onCleanup(() => clearInterval(spinnerInterval));
|
|
22682
23062
|
const sortMode = () => props.sortMode?.() ?? "default";
|
|
22683
|
-
const rows = createMemo(() => buildRows(props.tasks(), view(), searchMode() ? searchQuery() : "", sortMode()));
|
|
23063
|
+
const rows = createMemo((prev) => reconcileSidebarRows(prev, buildRows(props.tasks(), view(), searchMode() ? searchQuery() : "", sortMode())), []);
|
|
22684
23064
|
const flatIds = createMemo(() => flattenIds(rows()));
|
|
22685
23065
|
const firstTaskFlatIndex = createMemo(() => {
|
|
22686
23066
|
const r = rows();
|
|
@@ -23029,10 +23409,15 @@ function Sidebar(props) {
|
|
|
23029
23409
|
}
|
|
23030
23410
|
};
|
|
23031
23411
|
const changes = createMemo(() => {
|
|
23412
|
+
const pushed = pickPushedChanges(props.worktreeChanges?.(), task.worktreePath);
|
|
23413
|
+
if (pushed)
|
|
23414
|
+
return pushed;
|
|
23032
23415
|
branchTick();
|
|
23033
23416
|
if (!task.archived)
|
|
23034
23417
|
pollWorktreeChanges(task.worktreePath);
|
|
23035
23418
|
return worktreeChanges(task.worktreePath);
|
|
23419
|
+
}, undefined, {
|
|
23420
|
+
equals: sameWorktreeChanges
|
|
23036
23421
|
});
|
|
23037
23422
|
const projectBranch = createMemo(() => {
|
|
23038
23423
|
branchTick();
|
|
@@ -23040,15 +23425,17 @@ function Sidebar(props) {
|
|
|
23040
23425
|
pollCurrentBranch(task.repo);
|
|
23041
23426
|
return isMain ? currentBranch(task.repo) : "";
|
|
23042
23427
|
});
|
|
23043
|
-
const
|
|
23428
|
+
const baseRowView = createMemo(() => buildSidebarRowView({
|
|
23044
23429
|
task,
|
|
23045
23430
|
activity: props.engineState?.().get(task.id),
|
|
23431
|
+
job: props.taskJobs?.().get(task.id),
|
|
23046
23432
|
live: isLive(),
|
|
23047
|
-
spinnerFrame:
|
|
23433
|
+
spinnerFrame: 0,
|
|
23048
23434
|
subtitleBudget: subtitleBudget(),
|
|
23049
23435
|
truncateBranch: truncateBranchLabel,
|
|
23050
23436
|
mainBranch: projectBranch()
|
|
23051
23437
|
}));
|
|
23438
|
+
const rowView = createMemo(() => withSpinnerFrame(baseRowView(), spinnerFrame));
|
|
23052
23439
|
const stateColor = () => isMain && !rowView().loading ? theme.primary : toneColor(rowView().tone);
|
|
23053
23440
|
const barColor = () => isCursor() ? theme.focusAccent : isSelected() ? theme.primary : undefined;
|
|
23054
23441
|
const barGlyph = () => isCursor() || isSelected() ? "\u258C" : " ";
|
|
@@ -23475,6 +23862,7 @@ var init_Sidebar = __esm(() => {
|
|
|
23475
23862
|
init_groups();
|
|
23476
23863
|
init_keys();
|
|
23477
23864
|
init_row_view();
|
|
23865
|
+
init_worktree_changes();
|
|
23478
23866
|
init_worktree_changes_poller();
|
|
23479
23867
|
VIEW_TABS = [{
|
|
23480
23868
|
view: "active",
|
|
@@ -23491,6 +23879,7 @@ __export(exports_host3, {
|
|
|
23491
23879
|
startTasksPane: () => startTasksPane
|
|
23492
23880
|
});
|
|
23493
23881
|
import { existsSync as existsSync16 } from "fs";
|
|
23882
|
+
import { stat as stat5 } from "fs/promises";
|
|
23494
23883
|
import { TextAttributes as TextAttributes12 } from "@opentui/core";
|
|
23495
23884
|
function worktreeCwdUsable(cwd) {
|
|
23496
23885
|
return !!cwd && worktreeUsable(cwd);
|
|
@@ -23672,6 +24061,17 @@ function TasksShell(props) {
|
|
|
23672
24061
|
notifyError(`Couldn't open worktree with ${opener.label}`);
|
|
23673
24062
|
}
|
|
23674
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
|
+
}
|
|
23675
24075
|
async function moveTask(id, delta) {
|
|
23676
24076
|
const task = props.tasks().find((t) => t.id === id);
|
|
23677
24077
|
if (!task || task.kind === "main" || !props.orch)
|
|
@@ -23719,7 +24119,8 @@ function TasksShell(props) {
|
|
|
23719
24119
|
if (id)
|
|
23720
24120
|
cycleVendor(id);
|
|
23721
24121
|
},
|
|
23722
|
-
"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))
|
|
23723
24124
|
})
|
|
23724
24125
|
}));
|
|
23725
24126
|
async function switchTo(id) {
|
|
@@ -23825,6 +24226,17 @@ function TasksShell(props) {
|
|
|
23825
24226
|
get engineState() {
|
|
23826
24227
|
return memo2(() => !!props.orch)() ? props.orch.engineStateSignal() : undefined;
|
|
23827
24228
|
},
|
|
24229
|
+
get taskJobs() {
|
|
24230
|
+
return memo2(() => !!props.orch)() ? props.orch.taskJobsSignal() : undefined;
|
|
24231
|
+
},
|
|
24232
|
+
get worktreeChanges() {
|
|
24233
|
+
return props.orch ? () => {
|
|
24234
|
+
const orch = props.orch;
|
|
24235
|
+
if (!orch || orch.connectionStateSignal()() !== "online")
|
|
24236
|
+
return null;
|
|
24237
|
+
return orch.worktreeChangesSignal()();
|
|
24238
|
+
} : undefined;
|
|
24239
|
+
},
|
|
23828
24240
|
onRenameRequest: (id) => void renameTask(id),
|
|
23829
24241
|
onDeleteRequest: (id) => void deleteTask(id),
|
|
23830
24242
|
onArchiveRequest: (id) => void archiveTask(id),
|
|
@@ -23949,6 +24361,10 @@ function ShortcutHints(props) {
|
|
|
23949
24361
|
k: "enter",
|
|
23950
24362
|
label: "open"
|
|
23951
24363
|
},
|
|
24364
|
+
{
|
|
24365
|
+
k: "right",
|
|
24366
|
+
label: "focus engine"
|
|
24367
|
+
},
|
|
23952
24368
|
{
|
|
23953
24369
|
k: "n",
|
|
23954
24370
|
label: "new task"
|
|
@@ -24109,10 +24525,21 @@ async function setupTasksPane(opts) {
|
|
|
24109
24525
|
await store2.load();
|
|
24110
24526
|
setFileTasks(store2.list());
|
|
24111
24527
|
};
|
|
24528
|
+
let lastTasksFileFingerprint = "";
|
|
24112
24529
|
const timer = setInterval(() => {
|
|
24113
24530
|
if (orch && orch.connectionStateSignal()() === "online")
|
|
24114
24531
|
return;
|
|
24115
|
-
|
|
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(() => {});
|
|
24116
24543
|
}, RELOAD_MS);
|
|
24117
24544
|
return {
|
|
24118
24545
|
root: () => [createComponent2(TasksShell, {
|
|
@@ -25028,26 +25455,24 @@ function useFileTreeBindings(opts) {
|
|
|
25028
25455
|
useBindings(() => ({
|
|
25029
25456
|
enabled: opts.focused(),
|
|
25030
25457
|
bindings: bindByIds({
|
|
25031
|
-
"files.nav": (
|
|
25032
|
-
if (
|
|
25458
|
+
"files.nav": (_evt, slot) => {
|
|
25459
|
+
if ((slot ?? 0) % 2 === 0)
|
|
25033
25460
|
opts.moveDown();
|
|
25034
|
-
else
|
|
25461
|
+
else
|
|
25035
25462
|
opts.moveUp();
|
|
25036
25463
|
},
|
|
25037
|
-
"files.hierarchy": (
|
|
25038
|
-
if (
|
|
25039
|
-
opts.expandOrDescend();
|
|
25040
|
-
else if (evt.name === "h" || evt.name === "left")
|
|
25464
|
+
"files.hierarchy": (_evt, slot) => {
|
|
25465
|
+
if ((slot ?? 0) % 2 === 0)
|
|
25041
25466
|
opts.collapseOrParent();
|
|
25467
|
+
else
|
|
25468
|
+
opts.expandOrDescend();
|
|
25042
25469
|
},
|
|
25043
|
-
"files.tab": (
|
|
25470
|
+
"files.tab": (_evt, slot) => {
|
|
25044
25471
|
const cur = opts.currentTab();
|
|
25045
25472
|
const idx = TAB_ORDER.indexOf(cur);
|
|
25046
25473
|
if (idx < 0)
|
|
25047
25474
|
return;
|
|
25048
|
-
const delta =
|
|
25049
|
-
if (delta === 0)
|
|
25050
|
-
return;
|
|
25475
|
+
const delta = (slot ?? 0) % 2 === 0 ? -1 : 1;
|
|
25051
25476
|
const next = TAB_ORDER[(idx + delta + TAB_ORDER.length) % TAB_ORDER.length];
|
|
25052
25477
|
if (next)
|
|
25053
25478
|
opts.setTab(next);
|