@sma1lboy/kobe 0.7.18 → 0.7.19
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 +517 -177
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
|
|
|
90
90
|
package_default = {
|
|
91
91
|
$schema: "https://json.schemastore.org/package.json",
|
|
92
92
|
name: "@sma1lboy/kobe",
|
|
93
|
-
version: "0.7.
|
|
93
|
+
version: "0.7.19",
|
|
94
94
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
95
95
|
type: "module",
|
|
96
96
|
packageManager: "bun@1.3.13",
|
|
@@ -3919,7 +3919,9 @@ var init_protocol = __esm(() => {
|
|
|
3919
3919
|
"update",
|
|
3920
3920
|
"engine-state",
|
|
3921
3921
|
"ui-prefs",
|
|
3922
|
-
"keybindings"
|
|
3922
|
+
"keybindings",
|
|
3923
|
+
"task.jobs",
|
|
3924
|
+
"worktree.changes"
|
|
3923
3925
|
];
|
|
3924
3926
|
});
|
|
3925
3927
|
|
|
@@ -4067,6 +4069,7 @@ class KobeDaemonClient {
|
|
|
4067
4069
|
this.disposed = true;
|
|
4068
4070
|
this.socket?.end();
|
|
4069
4071
|
this.socket = null;
|
|
4072
|
+
this.failPending();
|
|
4070
4073
|
}
|
|
4071
4074
|
forceDisconnect() {
|
|
4072
4075
|
const socket = this.socket;
|
|
@@ -4074,6 +4077,15 @@ class KobeDaemonClient {
|
|
|
4074
4077
|
return;
|
|
4075
4078
|
this.socket = null;
|
|
4076
4079
|
socket.destroy();
|
|
4080
|
+
this.failPending();
|
|
4081
|
+
}
|
|
4082
|
+
failPending() {
|
|
4083
|
+
if (this.pending.size === 0)
|
|
4084
|
+
return;
|
|
4085
|
+
const err = new Error("daemon connection closed");
|
|
4086
|
+
for (const pending of this.pending.values())
|
|
4087
|
+
pending.reject(err);
|
|
4088
|
+
this.pending.clear();
|
|
4077
4089
|
}
|
|
4078
4090
|
on(name, handler) {
|
|
4079
4091
|
let set = this.handlers.get(name);
|
|
@@ -4148,9 +4160,7 @@ class KobeDaemonClient {
|
|
|
4148
4160
|
if (this.socket !== which)
|
|
4149
4161
|
return;
|
|
4150
4162
|
this.socket = null;
|
|
4151
|
-
|
|
4152
|
-
pending.reject(new Error("daemon connection closed"));
|
|
4153
|
-
this.pending.clear();
|
|
4163
|
+
this.failPending();
|
|
4154
4164
|
this.emitLifecycle("close");
|
|
4155
4165
|
}
|
|
4156
4166
|
emitLifecycle(name) {
|
|
@@ -5941,7 +5951,7 @@ function parseEvents(raw, fallbackSessionId) {
|
|
|
5941
5951
|
if (!text)
|
|
5942
5952
|
continue;
|
|
5943
5953
|
if (!firstUserMessage)
|
|
5944
|
-
firstUserMessage = text.slice(0, PREVIEW_CHAR_CAP);
|
|
5954
|
+
firstUserMessage = Buffer.from(text.slice(0, PREVIEW_CHAR_CAP), "utf8").toString("utf8");
|
|
5945
5955
|
messages.push({ role: "user", blocks: [{ type: "text", text }], timestamp, sessionId });
|
|
5946
5956
|
continue;
|
|
5947
5957
|
}
|
|
@@ -6277,7 +6287,8 @@ function titleFromMessages(messages) {
|
|
|
6277
6287
|
if (!firstUser)
|
|
6278
6288
|
return "";
|
|
6279
6289
|
const text = firstUser.blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
6280
|
-
|
|
6290
|
+
const title = deriveTitleFromPrompt(text);
|
|
6291
|
+
return title.length > 0 ? Buffer.from(title, "utf8").toString("utf8") : title;
|
|
6281
6292
|
}
|
|
6282
6293
|
async function deriveTitleFromSession(worktree, vendor = DEFAULT_TASK_VENDOR) {
|
|
6283
6294
|
if (!worktree)
|
|
@@ -7071,8 +7082,20 @@ function createDaemonHandlerRegistry() {
|
|
|
7071
7082
|
name: "task.ensureWorktree",
|
|
7072
7083
|
async handle(payload, ctx) {
|
|
7073
7084
|
const taskId = requireString(payload, "taskId");
|
|
7074
|
-
|
|
7075
|
-
|
|
7085
|
+
ctx.bus.publish("task.jobs", { taskId, kind: "ensureWorktree", phase: "running" });
|
|
7086
|
+
try {
|
|
7087
|
+
const path11 = await ctx.orch.ensureWorktree(taskId);
|
|
7088
|
+
ctx.bus.publish("task.jobs", { taskId, kind: "ensureWorktree", phase: "done" });
|
|
7089
|
+
return { worktreePath: path11 };
|
|
7090
|
+
} catch (err) {
|
|
7091
|
+
ctx.bus.publish("task.jobs", {
|
|
7092
|
+
taskId,
|
|
7093
|
+
kind: "ensureWorktree",
|
|
7094
|
+
phase: "error",
|
|
7095
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7096
|
+
});
|
|
7097
|
+
throw err;
|
|
7098
|
+
}
|
|
7076
7099
|
}
|
|
7077
7100
|
},
|
|
7078
7101
|
{
|
|
@@ -7333,6 +7356,232 @@ var init_ui_prefs_watcher = __esm(() => {
|
|
|
7333
7356
|
FOCUS_ACCENT_SLOT_NAMES = ["primary", "success", "info"];
|
|
7334
7357
|
});
|
|
7335
7358
|
|
|
7359
|
+
// src/lib/poll-scheduling.ts
|
|
7360
|
+
import { spawn as spawn2 } from "child_process";
|
|
7361
|
+
function computeNextAllowedAt(startedAt, finishedAt, timedOut, cfg) {
|
|
7362
|
+
if (timedOut)
|
|
7363
|
+
return startedAt + cfg.slowRetryMs;
|
|
7364
|
+
return finishedAt + Math.max(cfg.minIntervalMs, (finishedAt - startedAt) * 5);
|
|
7365
|
+
}
|
|
7366
|
+
function shouldPoll(state, now) {
|
|
7367
|
+
return !state.inFlight && now >= state.nextAllowedAt;
|
|
7368
|
+
}
|
|
7369
|
+
function maybeStartScheduledRun(state, cfg, run, onValue) {
|
|
7370
|
+
const startedAt = Date.now();
|
|
7371
|
+
if (!shouldPoll(state, startedAt))
|
|
7372
|
+
return false;
|
|
7373
|
+
state.inFlight = true;
|
|
7374
|
+
const controller = new AbortController;
|
|
7375
|
+
const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);
|
|
7376
|
+
(async () => {
|
|
7377
|
+
let value;
|
|
7378
|
+
let ok = false;
|
|
7379
|
+
try {
|
|
7380
|
+
value = await run(controller.signal);
|
|
7381
|
+
ok = true;
|
|
7382
|
+
} catch {}
|
|
7383
|
+
clearTimeout(timer);
|
|
7384
|
+
const timedOut = controller.signal.aborted;
|
|
7385
|
+
state.nextAllowedAt = computeNextAllowedAt(startedAt, Date.now(), timedOut, cfg);
|
|
7386
|
+
state.inFlight = false;
|
|
7387
|
+
if (ok && !timedOut)
|
|
7388
|
+
onValue(value);
|
|
7389
|
+
})();
|
|
7390
|
+
return true;
|
|
7391
|
+
}
|
|
7392
|
+
function spawnCapture(cmd, args, opts) {
|
|
7393
|
+
return new Promise((resolve2) => {
|
|
7394
|
+
let out = "";
|
|
7395
|
+
let settled = false;
|
|
7396
|
+
const finish = (status) => {
|
|
7397
|
+
if (settled)
|
|
7398
|
+
return;
|
|
7399
|
+
settled = true;
|
|
7400
|
+
resolve2({ status, stdout: out });
|
|
7401
|
+
};
|
|
7402
|
+
const child = spawn2(cmd, args.slice(), {
|
|
7403
|
+
cwd: opts.cwd,
|
|
7404
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7405
|
+
env: opts.env,
|
|
7406
|
+
signal: opts.signal,
|
|
7407
|
+
killSignal: "SIGKILL"
|
|
7408
|
+
});
|
|
7409
|
+
child.stdout?.on("data", (chunk) => {
|
|
7410
|
+
out += String(chunk);
|
|
7411
|
+
});
|
|
7412
|
+
child.on("error", () => finish(null));
|
|
7413
|
+
child.on("close", (code) => finish(code));
|
|
7414
|
+
});
|
|
7415
|
+
}
|
|
7416
|
+
var init_poll_scheduling = () => {};
|
|
7417
|
+
|
|
7418
|
+
// src/tui/panes/sidebar/worktree-changes.ts
|
|
7419
|
+
var exports_worktree_changes = {};
|
|
7420
|
+
__export(exports_worktree_changes, {
|
|
7421
|
+
sameWorktreeChanges: () => sameWorktreeChanges,
|
|
7422
|
+
readWorktreeChanges: () => readWorktreeChanges,
|
|
7423
|
+
pickPushedChanges: () => pickPushedChanges,
|
|
7424
|
+
parsePorcelain: () => parsePorcelain2
|
|
7425
|
+
});
|
|
7426
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
7427
|
+
function sameWorktreeChanges(a, b) {
|
|
7428
|
+
return a.added === b.added && a.deleted === b.deleted;
|
|
7429
|
+
}
|
|
7430
|
+
function pickPushedChanges(pushed, worktreePath) {
|
|
7431
|
+
if (!pushed)
|
|
7432
|
+
return null;
|
|
7433
|
+
return pushed.get(worktreePath) ?? ZERO;
|
|
7434
|
+
}
|
|
7435
|
+
function readWorktreeChanges(worktreePath) {
|
|
7436
|
+
if (!worktreePath)
|
|
7437
|
+
return ZERO;
|
|
7438
|
+
try {
|
|
7439
|
+
const out = spawnSync7("git", ["status", "--porcelain=v1"], {
|
|
7440
|
+
cwd: worktreePath,
|
|
7441
|
+
encoding: "utf8",
|
|
7442
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
7443
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }
|
|
7444
|
+
});
|
|
7445
|
+
if (out.status !== 0 || !out.stdout)
|
|
7446
|
+
return ZERO;
|
|
7447
|
+
return parsePorcelain2(out.stdout);
|
|
7448
|
+
} catch {
|
|
7449
|
+
return ZERO;
|
|
7450
|
+
}
|
|
7451
|
+
}
|
|
7452
|
+
function parsePorcelain2(text) {
|
|
7453
|
+
let added = 0;
|
|
7454
|
+
let deleted = 0;
|
|
7455
|
+
for (const line of text.split(`
|
|
7456
|
+
`)) {
|
|
7457
|
+
if (!line || line.startsWith("##"))
|
|
7458
|
+
continue;
|
|
7459
|
+
const x = line.charAt(0);
|
|
7460
|
+
const y = line.charAt(1);
|
|
7461
|
+
if (x === "D" || y === "D")
|
|
7462
|
+
deleted += 1;
|
|
7463
|
+
else
|
|
7464
|
+
added += 1;
|
|
7465
|
+
}
|
|
7466
|
+
return { added, deleted };
|
|
7467
|
+
}
|
|
7468
|
+
var ZERO;
|
|
7469
|
+
var init_worktree_changes = __esm(() => {
|
|
7470
|
+
ZERO = { added: 0, deleted: 0 };
|
|
7471
|
+
});
|
|
7472
|
+
|
|
7473
|
+
// ../kobe-daemon/src/daemon/worktree-changes-collector.ts
|
|
7474
|
+
async function runGitStatus(worktreePath, signal) {
|
|
7475
|
+
const res = await spawnCapture("git", ["status", "--porcelain=v1"], {
|
|
7476
|
+
cwd: worktreePath,
|
|
7477
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
7478
|
+
signal
|
|
7479
|
+
});
|
|
7480
|
+
if (res.status !== 0)
|
|
7481
|
+
throw new Error("git status failed");
|
|
7482
|
+
return parsePorcelain2(res.stdout);
|
|
7483
|
+
}
|
|
7484
|
+
function trackedWorktreePaths(tasks) {
|
|
7485
|
+
const paths = new Set;
|
|
7486
|
+
for (const task of tasks) {
|
|
7487
|
+
if (task.archived)
|
|
7488
|
+
continue;
|
|
7489
|
+
if (!task.worktreePath)
|
|
7490
|
+
continue;
|
|
7491
|
+
if (isRemoteRepoKey(task.repo) || isRemoteRepoKey(task.worktreePath))
|
|
7492
|
+
continue;
|
|
7493
|
+
paths.add(task.worktreePath);
|
|
7494
|
+
}
|
|
7495
|
+
return paths;
|
|
7496
|
+
}
|
|
7497
|
+
|
|
7498
|
+
class WorktreeChangesCollector {
|
|
7499
|
+
orch;
|
|
7500
|
+
bus;
|
|
7501
|
+
options;
|
|
7502
|
+
entries = new Map;
|
|
7503
|
+
stopped = false;
|
|
7504
|
+
constructor(orch, bus, options = {}) {
|
|
7505
|
+
this.orch = orch;
|
|
7506
|
+
this.bus = bus;
|
|
7507
|
+
this.options = options;
|
|
7508
|
+
}
|
|
7509
|
+
tick() {
|
|
7510
|
+
if (this.stopped)
|
|
7511
|
+
return;
|
|
7512
|
+
try {
|
|
7513
|
+
const tracked = trackedWorktreePaths(this.orch.listTasks());
|
|
7514
|
+
let pruned = false;
|
|
7515
|
+
for (const path11 of this.entries.keys()) {
|
|
7516
|
+
if (tracked.has(path11))
|
|
7517
|
+
continue;
|
|
7518
|
+
const entry = this.entries.get(path11);
|
|
7519
|
+
if (entry?.value)
|
|
7520
|
+
pruned = true;
|
|
7521
|
+
this.entries.delete(path11);
|
|
7522
|
+
}
|
|
7523
|
+
if (pruned)
|
|
7524
|
+
this.publish();
|
|
7525
|
+
for (const path11 of tracked)
|
|
7526
|
+
this.maybeCollect(path11);
|
|
7527
|
+
} catch (err) {
|
|
7528
|
+
logDaemonError("worktree-changes", err);
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
stop() {
|
|
7532
|
+
this.stopped = true;
|
|
7533
|
+
}
|
|
7534
|
+
maybeCollect(worktreePath) {
|
|
7535
|
+
let entry = this.entries.get(worktreePath);
|
|
7536
|
+
if (!entry) {
|
|
7537
|
+
entry = { inFlight: false, nextAllowedAt: 0 };
|
|
7538
|
+
this.entries.set(worktreePath, entry);
|
|
7539
|
+
}
|
|
7540
|
+
const cadence = this.options.cadence ?? {
|
|
7541
|
+
timeoutMs: WORKTREE_CHANGES_TIMEOUT_MS,
|
|
7542
|
+
slowRetryMs: WORKTREE_CHANGES_SLOW_RETRY_MS,
|
|
7543
|
+
minIntervalMs: WORKTREE_CHANGES_MIN_INTERVAL_MS
|
|
7544
|
+
};
|
|
7545
|
+
const run = this.options.run ?? runGitStatus;
|
|
7546
|
+
maybeStartScheduledRun(entry, cadence, (signal) => run(worktreePath, signal), (value) => {
|
|
7547
|
+
if (this.stopped)
|
|
7548
|
+
return;
|
|
7549
|
+
if (this.entries.get(worktreePath) !== entry)
|
|
7550
|
+
return;
|
|
7551
|
+
if (entry.value && sameWorktreeChanges(entry.value, value))
|
|
7552
|
+
return;
|
|
7553
|
+
entry.value = value;
|
|
7554
|
+
this.publish();
|
|
7555
|
+
});
|
|
7556
|
+
}
|
|
7557
|
+
publish() {
|
|
7558
|
+
const changes = {};
|
|
7559
|
+
for (const [path11, entry] of this.entries) {
|
|
7560
|
+
if (entry.value)
|
|
7561
|
+
changes[path11] = entry.value;
|
|
7562
|
+
}
|
|
7563
|
+
this.bus.publish("worktree.changes", { changes });
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
function startWorktreeChangesCollector(orch, bus, tickMs = DEFAULT_WORKTREE_CHANGES_TICK_MS) {
|
|
7567
|
+
if (tickMs <= 0)
|
|
7568
|
+
return () => {};
|
|
7569
|
+
const collector = new WorktreeChangesCollector(orch, bus);
|
|
7570
|
+
collector.tick();
|
|
7571
|
+
const timer = setInterval(() => collector.tick(), tickMs);
|
|
7572
|
+
timer.unref?.();
|
|
7573
|
+
return () => {
|
|
7574
|
+
clearInterval(timer);
|
|
7575
|
+
collector.stop();
|
|
7576
|
+
};
|
|
7577
|
+
}
|
|
7578
|
+
var DEFAULT_WORKTREE_CHANGES_TICK_MS = 2000, WORKTREE_CHANGES_TIMEOUT_MS = 4000, WORKTREE_CHANGES_SLOW_RETRY_MS = 60000, WORKTREE_CHANGES_MIN_INTERVAL_MS = 1500;
|
|
7579
|
+
var init_worktree_changes_collector = __esm(() => {
|
|
7580
|
+
init_poll_scheduling();
|
|
7581
|
+
init_repos();
|
|
7582
|
+
init_worktree_changes();
|
|
7583
|
+
});
|
|
7584
|
+
|
|
7336
7585
|
// ../kobe-daemon/src/daemon/server.ts
|
|
7337
7586
|
import { mkdir as mkdir6, readFile as readFile8, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
7338
7587
|
import { createServer } from "net";
|
|
@@ -7436,6 +7685,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7436
7685
|
path: defaultKeybindingsPath(options.homeDir),
|
|
7437
7686
|
debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
|
|
7438
7687
|
});
|
|
7688
|
+
const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS);
|
|
7439
7689
|
const serverApi = {
|
|
7440
7690
|
socketPath,
|
|
7441
7691
|
pidPath,
|
|
@@ -7450,6 +7700,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
7450
7700
|
stopAutoTitlePoller();
|
|
7451
7701
|
stopUiPrefsWatcher();
|
|
7452
7702
|
stopKeybindingsWatcher();
|
|
7703
|
+
stopWorktreeChangesCollector();
|
|
7453
7704
|
activity.close();
|
|
7454
7705
|
broadcast(clients, { type: "event", name: "daemon.stopping", payload: {} });
|
|
7455
7706
|
for (const client of Array.from(clients)) {
|
|
@@ -7573,6 +7824,7 @@ var init_server = __esm(() => {
|
|
|
7573
7824
|
init_paths2();
|
|
7574
7825
|
init_protocol();
|
|
7575
7826
|
init_ui_prefs_watcher();
|
|
7827
|
+
init_worktree_changes_collector();
|
|
7576
7828
|
init_handlers();
|
|
7577
7829
|
DEFAULT_UPDATE_POLL_MS = 6 * 60 * 60 * 1000;
|
|
7578
7830
|
});
|
|
@@ -7641,7 +7893,7 @@ __export(exports_daemon_process, {
|
|
|
7641
7893
|
connectOrStartDaemon: () => connectOrStartDaemon,
|
|
7642
7894
|
connectIfRunning: () => connectIfRunning
|
|
7643
7895
|
});
|
|
7644
|
-
import { spawn as
|
|
7896
|
+
import { spawn as spawn3 } from "child_process";
|
|
7645
7897
|
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync } from "fs";
|
|
7646
7898
|
import { dirname as dirname8, resolve as resolve2 } from "path";
|
|
7647
7899
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -7655,7 +7907,7 @@ function spawnDetachedDaemon(command, args, env, logPath) {
|
|
|
7655
7907
|
} catch {
|
|
7656
7908
|
stdio = "ignore";
|
|
7657
7909
|
}
|
|
7658
|
-
const child =
|
|
7910
|
+
const child = spawn3(command, [...args], { detached: true, stdio, env });
|
|
7659
7911
|
child.unref();
|
|
7660
7912
|
if (logFd !== undefined) {
|
|
7661
7913
|
try {
|
|
@@ -7938,7 +8190,7 @@ var init_interactive_command = __esm(() => {
|
|
|
7938
8190
|
});
|
|
7939
8191
|
|
|
7940
8192
|
// src/lib/feedback.ts
|
|
7941
|
-
import { spawnSync as
|
|
8193
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
7942
8194
|
function parseRepoSlug(slug) {
|
|
7943
8195
|
const [owner, name] = slug.split("/");
|
|
7944
8196
|
if (!owner || !name)
|
|
@@ -7993,7 +8245,7 @@ function submitFeedback(input, deps = {}) {
|
|
|
7993
8245
|
throw new Error("package repository is not a GitHub repository");
|
|
7994
8246
|
const { owner, name } = parseRepoSlug(slug);
|
|
7995
8247
|
const categorySlug = input.categorySlug?.trim() || DEFAULT_FEEDBACK_CATEGORY_SLUG;
|
|
7996
|
-
const io = { spawn: deps.spawn ??
|
|
8248
|
+
const io = { spawn: deps.spawn ?? spawnSync8 };
|
|
7997
8249
|
const categoryData = runGhGraphql(DISCUSSION_CATEGORY_QUERY, { owner, name }, io);
|
|
7998
8250
|
const repository = categoryData.repository;
|
|
7999
8251
|
const repositoryId = repository?.id;
|
|
@@ -8132,6 +8384,13 @@ var init_keybindings_file = __esm(() => {
|
|
|
8132
8384
|
});
|
|
8133
8385
|
|
|
8134
8386
|
// src/tui/lib/keymap-overrides.ts
|
|
8387
|
+
function pairContract(first, second) {
|
|
8388
|
+
const layout = `alternating [${first}, ${second}] pairs`;
|
|
8389
|
+
return {
|
|
8390
|
+
layout,
|
|
8391
|
+
validateCount: (count) => count >= 2 && count % 2 === 0 ? null : `needs ${layout} (an even number of chords \u2014 got ${count})`
|
|
8392
|
+
};
|
|
8393
|
+
}
|
|
8135
8394
|
function normalizeChord(raw, opts) {
|
|
8136
8395
|
const trimmed = raw.trim().toLowerCase();
|
|
8137
8396
|
if (!trimmed)
|
|
@@ -8271,6 +8530,14 @@ function applyKeymapOverrides(keymap, entries) {
|
|
|
8271
8530
|
warnings.push(`${entry.id}: not customizable \u2014 the key is handled outside the keymap (doc-only row)`);
|
|
8272
8531
|
continue;
|
|
8273
8532
|
}
|
|
8533
|
+
const contract = SLOT_CONTRACTS[entry.id];
|
|
8534
|
+
if (contract && entry.keys.length > 0) {
|
|
8535
|
+
const problem = contract.validateCount(entry.keys.length);
|
|
8536
|
+
if (problem) {
|
|
8537
|
+
warnings.push(`${entry.id}: ${problem} \u2014 keeping the default`);
|
|
8538
|
+
continue;
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8274
8541
|
const keys = entry.keys.filter((chord) => {
|
|
8275
8542
|
if (chord.length === 1 && NO_BARE_LETTER_SCOPES.has(row.scope)) {
|
|
8276
8543
|
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 +8549,10 @@ function applyKeymapOverrides(keymap, entries) {
|
|
|
8282
8549
|
warnings.push(`${entry.id}: no chords survived validation \u2014 keeping the default`);
|
|
8283
8550
|
continue;
|
|
8284
8551
|
}
|
|
8552
|
+
if (contract && keys.length !== entry.keys.length) {
|
|
8553
|
+
warnings.push(`${entry.id}: a dropped chord would shift the slot layout (${contract.layout}) \u2014 keeping the default`);
|
|
8554
|
+
continue;
|
|
8555
|
+
}
|
|
8285
8556
|
const defaultKeys = row.keys;
|
|
8286
8557
|
const mutable = row;
|
|
8287
8558
|
mutable.keys = keys;
|
|
@@ -8312,21 +8583,23 @@ function applyKeymapOverrides(keymap, entries) {
|
|
|
8312
8583
|
}
|
|
8313
8584
|
return { applied, warnings };
|
|
8314
8585
|
}
|
|
8315
|
-
var FIXED_BINDING_IDS, NO_BARE_LETTER_SCOPES, MOD_ALIASES, KEY_ALIASES, KNOWN_NAMED_KEYS, MOD_ORDER;
|
|
8586
|
+
var FIXED_BINDING_IDS, SLOT_CONTRACTS, NO_BARE_LETTER_SCOPES, MOD_ALIASES, KEY_ALIASES, KNOWN_NAMED_KEYS, MOD_ORDER;
|
|
8316
8587
|
var init_keymap_overrides = __esm(() => {
|
|
8317
8588
|
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
|
-
"
|
|
8589
|
+
"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",
|
|
8590
|
+
"sidebar.goto": "gg vs Shift+G is discriminated via evt.shift; shift+<letter> chords are inexpressible, so a rebind can't carry both halves",
|
|
8591
|
+
"sidebar.pin": "fires on Shift+P via evt.shift; shift+<letter> chords are inexpressible, so a rebind can't work",
|
|
8592
|
+
"sidebar.localMerge": "fires on Shift+M via evt.shift; shift+<letter> chords are inexpressible, so a rebind can't work",
|
|
8593
|
+
"chat.question.nav": "the question picker has no live registration site (display-only row) \u2014 rebinding would change Help without changing behavior",
|
|
8594
|
+
"chat.question.pick-number": "digits map to options positionally and the question picker has no live registration site (display-only row)"
|
|
8595
|
+
};
|
|
8596
|
+
SLOT_CONTRACTS = {
|
|
8597
|
+
"sidebar.nav": pairContract("down", "up"),
|
|
8598
|
+
"files.nav": pairContract("down", "up"),
|
|
8599
|
+
"sidebar.search.nav": pairContract("down", "up"),
|
|
8600
|
+
"files.hierarchy": pairContract("collapse", "expand"),
|
|
8601
|
+
"sidebar.view": pairContract("previous view", "next view"),
|
|
8602
|
+
"files.tab": pairContract("previous tab", "next tab")
|
|
8330
8603
|
};
|
|
8331
8604
|
NO_BARE_LETTER_SCOPES = new Set(["global", "workspace", "terminal"]);
|
|
8332
8605
|
MOD_ALIASES = {
|
|
@@ -10897,51 +11170,6 @@ var init_repo_init = __esm(() => {
|
|
|
10897
11170
|
INIT_PROMPT_REL = join7(".kobe", "init-prompt.md");
|
|
10898
11171
|
});
|
|
10899
11172
|
|
|
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
11173
|
// src/cli/api-cmd.ts
|
|
10946
11174
|
var exports_api_cmd = {};
|
|
10947
11175
|
__export(exports_api_cmd, {
|
|
@@ -13035,16 +13263,21 @@ class DaemonLink {
|
|
|
13035
13263
|
const socketPath = allowSpawn ? await ensureDaemonReachable() : defaultDaemonSocketPath();
|
|
13036
13264
|
const client = new KobeDaemonClient(socketPath);
|
|
13037
13265
|
await client.connect();
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13266
|
+
try {
|
|
13267
|
+
const hello = await client.request("hello", {
|
|
13268
|
+
protocolVersion: DAEMON_PROTOCOL_VERSION,
|
|
13269
|
+
minProtocolVersion: MIN_COMPATIBLE_PROTOCOL_VERSION
|
|
13270
|
+
});
|
|
13271
|
+
if (hello.tasks)
|
|
13272
|
+
this.tasks = hello.tasks;
|
|
13273
|
+
this.engineStates = {};
|
|
13274
|
+
client.on("*", (frame) => this.onFrame(frame.name, frame.payload));
|
|
13275
|
+
client.onLifecycle("close", () => this.onDrop(client));
|
|
13276
|
+
await client.subscribe({ role: "gui" });
|
|
13277
|
+
} catch (err) {
|
|
13278
|
+
client.close();
|
|
13279
|
+
throw err;
|
|
13280
|
+
}
|
|
13048
13281
|
this.client = client;
|
|
13049
13282
|
this.setConnected(true);
|
|
13050
13283
|
}
|
|
@@ -15101,6 +15334,30 @@ var init_solid = __esm(() => {
|
|
|
15101
15334
|
});
|
|
15102
15335
|
|
|
15103
15336
|
// src/client/remote-orchestrator.ts
|
|
15337
|
+
function parseWorktreeChangesPayload(payload) {
|
|
15338
|
+
const changes = payload?.changes;
|
|
15339
|
+
if (!changes || typeof changes !== "object" || Array.isArray(changes))
|
|
15340
|
+
return null;
|
|
15341
|
+
const map = new Map;
|
|
15342
|
+
for (const [path11, value] of Object.entries(changes)) {
|
|
15343
|
+
const counts = value;
|
|
15344
|
+
if (typeof counts?.added !== "number" || typeof counts.deleted !== "number")
|
|
15345
|
+
return null;
|
|
15346
|
+
map.set(path11, { added: counts.added, deleted: counts.deleted });
|
|
15347
|
+
}
|
|
15348
|
+
return map;
|
|
15349
|
+
}
|
|
15350
|
+
function sameWorktreeChangesMap(a, b) {
|
|
15351
|
+
if (a.size !== b.size)
|
|
15352
|
+
return false;
|
|
15353
|
+
for (const [path11, counts] of a) {
|
|
15354
|
+
const other = b.get(path11);
|
|
15355
|
+
if (!other || !sameWorktreeChanges(counts, other))
|
|
15356
|
+
return false;
|
|
15357
|
+
}
|
|
15358
|
+
return true;
|
|
15359
|
+
}
|
|
15360
|
+
|
|
15104
15361
|
class RemoteOrchestrator {
|
|
15105
15362
|
client;
|
|
15106
15363
|
tasksAcc;
|
|
@@ -15113,6 +15370,10 @@ class RemoteOrchestrator {
|
|
|
15113
15370
|
setDaemonVersionSig;
|
|
15114
15371
|
engineStateAcc;
|
|
15115
15372
|
setEngineStateSig;
|
|
15373
|
+
taskJobsAcc;
|
|
15374
|
+
setTaskJobsSig;
|
|
15375
|
+
worktreeChangesAcc;
|
|
15376
|
+
setWorktreeChangesSig;
|
|
15116
15377
|
uiPrefsAcc;
|
|
15117
15378
|
setUiPrefsSig;
|
|
15118
15379
|
keybindingsRevAcc;
|
|
@@ -15129,6 +15390,8 @@ class RemoteOrchestrator {
|
|
|
15129
15390
|
const [update, setUpdate] = createSignal(null);
|
|
15130
15391
|
const [daemonVersion, setDaemonVersion] = createSignal(null);
|
|
15131
15392
|
const [engineState, setEngineState] = createSignal(new Map);
|
|
15393
|
+
const [taskJobs, setTaskJobs] = createSignal(new Map);
|
|
15394
|
+
const [worktreeChanges, setWorktreeChanges] = createSignal(null);
|
|
15132
15395
|
const [uiPrefs, setUiPrefs] = createSignal(null);
|
|
15133
15396
|
const [keybindingsRev, setKeybindingsRev] = createSignal(null);
|
|
15134
15397
|
const [connectionState, setConnectionState] = createSignal("online");
|
|
@@ -15142,6 +15405,10 @@ class RemoteOrchestrator {
|
|
|
15142
15405
|
this.setDaemonVersionSig = (next) => setDaemonVersion(() => next);
|
|
15143
15406
|
this.engineStateAcc = engineState;
|
|
15144
15407
|
this.setEngineStateSig = (next) => setEngineState(() => next);
|
|
15408
|
+
this.taskJobsAcc = taskJobs;
|
|
15409
|
+
this.setTaskJobsSig = (next) => setTaskJobs(() => next);
|
|
15410
|
+
this.worktreeChangesAcc = worktreeChanges;
|
|
15411
|
+
this.setWorktreeChangesSig = (next) => setWorktreeChanges(() => next);
|
|
15145
15412
|
this.uiPrefsAcc = uiPrefs;
|
|
15146
15413
|
this.setUiPrefsSig = (next) => setUiPrefs(() => next);
|
|
15147
15414
|
this.keybindingsRevAcc = keybindingsRev;
|
|
@@ -15202,6 +15469,12 @@ class RemoteOrchestrator {
|
|
|
15202
15469
|
if (hello.tasks)
|
|
15203
15470
|
this.setTasks(hello.tasks.map(deserializeTask));
|
|
15204
15471
|
await this.client.subscribe({ role: this.role });
|
|
15472
|
+
if (hello.capabilities?.includes("worktree.changes")) {
|
|
15473
|
+
if (this.worktreeChangesAcc() === null)
|
|
15474
|
+
this.setWorktreeChangesSig(new Map);
|
|
15475
|
+
} else {
|
|
15476
|
+
this.setWorktreeChangesSig(null);
|
|
15477
|
+
}
|
|
15205
15478
|
this.setConnectionState("online");
|
|
15206
15479
|
logClient("orch", `subscribed as ${this.role} (${this.tasksAcc().length} tasks)`);
|
|
15207
15480
|
}
|
|
@@ -15234,6 +15507,12 @@ class RemoteOrchestrator {
|
|
|
15234
15507
|
engineStateSignal() {
|
|
15235
15508
|
return this.engineStateAcc;
|
|
15236
15509
|
}
|
|
15510
|
+
taskJobsSignal() {
|
|
15511
|
+
return this.taskJobsAcc;
|
|
15512
|
+
}
|
|
15513
|
+
worktreeChangesSignal() {
|
|
15514
|
+
return this.worktreeChangesAcc;
|
|
15515
|
+
}
|
|
15237
15516
|
uiPrefsSignal() {
|
|
15238
15517
|
return this.uiPrefsAcc;
|
|
15239
15518
|
}
|
|
@@ -15319,8 +15598,11 @@ class RemoteOrchestrator {
|
|
|
15319
15598
|
handleEvent(name, payload) {
|
|
15320
15599
|
if (name === "task.snapshot") {
|
|
15321
15600
|
const value = payload?.tasks;
|
|
15322
|
-
if (Array.isArray(value))
|
|
15601
|
+
if (Array.isArray(value)) {
|
|
15323
15602
|
this.setTasks(value.map(deserializeTask));
|
|
15603
|
+
this.pruneEngineState(value);
|
|
15604
|
+
this.pruneTaskJobs(value);
|
|
15605
|
+
}
|
|
15324
15606
|
return;
|
|
15325
15607
|
}
|
|
15326
15608
|
if (name === "active-task") {
|
|
@@ -15345,6 +15627,34 @@ class RemoteOrchestrator {
|
|
|
15345
15627
|
this.setEngineStateSig(next);
|
|
15346
15628
|
return;
|
|
15347
15629
|
}
|
|
15630
|
+
if (name === "task.jobs") {
|
|
15631
|
+
const p = payload;
|
|
15632
|
+
if (typeof p?.taskId !== "string" || p.kind !== "ensureWorktree")
|
|
15633
|
+
return;
|
|
15634
|
+
const current = this.taskJobsAcc();
|
|
15635
|
+
if (p.phase === "running") {
|
|
15636
|
+
const next = new Map(current);
|
|
15637
|
+
next.set(p.taskId, { kind: p.kind });
|
|
15638
|
+
this.setTaskJobsSig(next);
|
|
15639
|
+
return;
|
|
15640
|
+
}
|
|
15641
|
+
if ((p.phase === "done" || p.phase === "error") && current.has(p.taskId)) {
|
|
15642
|
+
const next = new Map(current);
|
|
15643
|
+
next.delete(p.taskId);
|
|
15644
|
+
this.setTaskJobsSig(next);
|
|
15645
|
+
}
|
|
15646
|
+
return;
|
|
15647
|
+
}
|
|
15648
|
+
if (name === "worktree.changes") {
|
|
15649
|
+
const next = parseWorktreeChangesPayload(payload);
|
|
15650
|
+
if (!next)
|
|
15651
|
+
return;
|
|
15652
|
+
const current = this.worktreeChangesAcc();
|
|
15653
|
+
if (current && sameWorktreeChangesMap(current, next))
|
|
15654
|
+
return;
|
|
15655
|
+
this.setWorktreeChangesSig(next);
|
|
15656
|
+
return;
|
|
15657
|
+
}
|
|
15348
15658
|
if (name === "ui-prefs") {
|
|
15349
15659
|
const p = payload;
|
|
15350
15660
|
if (typeof p?.theme !== "string")
|
|
@@ -15366,6 +15676,38 @@ class RemoteOrchestrator {
|
|
|
15366
15676
|
return;
|
|
15367
15677
|
}
|
|
15368
15678
|
}
|
|
15679
|
+
pruneEngineState(tasks) {
|
|
15680
|
+
const current = this.engineStateAcc();
|
|
15681
|
+
if (current.size === 0)
|
|
15682
|
+
return;
|
|
15683
|
+
const live = new Set(tasks.map((t) => t.id));
|
|
15684
|
+
let next = null;
|
|
15685
|
+
for (const key of current.keys()) {
|
|
15686
|
+
if (live.has(key))
|
|
15687
|
+
continue;
|
|
15688
|
+
if (!next)
|
|
15689
|
+
next = new Map(current);
|
|
15690
|
+
next.delete(key);
|
|
15691
|
+
}
|
|
15692
|
+
if (next)
|
|
15693
|
+
this.setEngineStateSig(next);
|
|
15694
|
+
}
|
|
15695
|
+
pruneTaskJobs(tasks) {
|
|
15696
|
+
const current = this.taskJobsAcc();
|
|
15697
|
+
if (current.size === 0)
|
|
15698
|
+
return;
|
|
15699
|
+
const live = new Set(tasks.map((t) => t.id));
|
|
15700
|
+
let next = null;
|
|
15701
|
+
for (const key of current.keys()) {
|
|
15702
|
+
if (live.has(key))
|
|
15703
|
+
continue;
|
|
15704
|
+
if (!next)
|
|
15705
|
+
next = new Map(current);
|
|
15706
|
+
next.delete(key);
|
|
15707
|
+
}
|
|
15708
|
+
if (next)
|
|
15709
|
+
this.setTaskJobsSig(next);
|
|
15710
|
+
}
|
|
15369
15711
|
}
|
|
15370
15712
|
function deserializeTask(s) {
|
|
15371
15713
|
return {
|
|
@@ -15389,6 +15731,7 @@ var init_remote_orchestrator = __esm(() => {
|
|
|
15389
15731
|
init_daemon_process();
|
|
15390
15732
|
init_protocol();
|
|
15391
15733
|
init_dev();
|
|
15734
|
+
init_worktree_changes();
|
|
15392
15735
|
init_version();
|
|
15393
15736
|
});
|
|
15394
15737
|
|
|
@@ -15917,7 +16260,7 @@ function dispatchKeyEvent(bindingStack, evt) {
|
|
|
15917
16260
|
continue;
|
|
15918
16261
|
const hit = cfg.bindings.find((b) => candidates.includes(b.key));
|
|
15919
16262
|
if (hit) {
|
|
15920
|
-
hit.cmd(evt);
|
|
16263
|
+
hit.cmd(evt, hit.slot);
|
|
15921
16264
|
evt.preventDefault();
|
|
15922
16265
|
return true;
|
|
15923
16266
|
}
|
|
@@ -16328,7 +16671,7 @@ function joinDrill(typedValue, baseExpanded, name) {
|
|
|
16328
16671
|
var init_path_helpers = () => {};
|
|
16329
16672
|
|
|
16330
16673
|
// src/tui/component/new-task-dialog/clone.ts
|
|
16331
|
-
import { spawn as
|
|
16674
|
+
import { spawn as spawn4 } from "child_process";
|
|
16332
16675
|
import * as fs5 from "fs";
|
|
16333
16676
|
import * as path11 from "path";
|
|
16334
16677
|
function deriveFolderName(url) {
|
|
@@ -16407,7 +16750,7 @@ function cloneRepo(url, target, onProgress) {
|
|
|
16407
16750
|
return new Promise((resolve8) => {
|
|
16408
16751
|
let stderrBuf = "";
|
|
16409
16752
|
try {
|
|
16410
|
-
const child =
|
|
16753
|
+
const child = spawn4("git", ["clone", "--progress", url, target], {
|
|
16411
16754
|
stdio: ["ignore", "ignore", "pipe"]
|
|
16412
16755
|
});
|
|
16413
16756
|
child.stderr?.setEncoding("utf-8");
|
|
@@ -17933,8 +18276,7 @@ function bindByIds(handlers) {
|
|
|
17933
18276
|
console.warn(`[kobe/keybindings] bindByIds: id="${id}" has no chords (or doesn't exist in KobeKeymap)`);
|
|
17934
18277
|
continue;
|
|
17935
18278
|
}
|
|
17936
|
-
|
|
17937
|
-
out.push({ key: c, cmd });
|
|
18279
|
+
chords.forEach((c, slot) => out.push({ key: c, cmd, slot }));
|
|
17938
18280
|
}
|
|
17939
18281
|
return out;
|
|
17940
18282
|
}
|
|
@@ -18839,20 +19181,28 @@ function UiPrefsSync(props) {
|
|
|
18839
19181
|
focusAccent: props.boot.focusAccent
|
|
18840
19182
|
});
|
|
18841
19183
|
const [prefsOrch, setPrefsOrch] = createSignal(null);
|
|
19184
|
+
let disposed = false;
|
|
18842
19185
|
onMount(() => {
|
|
18843
19186
|
(async () => {
|
|
19187
|
+
let remote = null;
|
|
18844
19188
|
try {
|
|
18845
19189
|
const client = await connectIfRunning();
|
|
18846
19190
|
if (!client) {
|
|
18847
19191
|
logClient("ui-prefs", "no daemon \u2014 keeping boot-time visual prefs");
|
|
18848
19192
|
return;
|
|
18849
19193
|
}
|
|
18850
|
-
|
|
19194
|
+
remote = new RemoteOrchestrator(client);
|
|
18851
19195
|
await remote.init();
|
|
18852
|
-
setPrefsOrch(remote);
|
|
18853
19196
|
} catch (err) {
|
|
18854
19197
|
logClientError("ui-prefs", err);
|
|
19198
|
+
remote?.dispose();
|
|
19199
|
+
return;
|
|
19200
|
+
}
|
|
19201
|
+
if (disposed) {
|
|
19202
|
+
remote.dispose();
|
|
19203
|
+
return;
|
|
18855
19204
|
}
|
|
19205
|
+
setPrefsOrch(remote);
|
|
18856
19206
|
})();
|
|
18857
19207
|
});
|
|
18858
19208
|
createEffect(() => {
|
|
@@ -18872,7 +19222,10 @@ function UiPrefsSync(props) {
|
|
|
18872
19222
|
lastKeybindingsRev = rev;
|
|
18873
19223
|
reloadUserKeybindings();
|
|
18874
19224
|
});
|
|
18875
|
-
onCleanup(() =>
|
|
19225
|
+
onCleanup(() => {
|
|
19226
|
+
disposed = true;
|
|
19227
|
+
prefsOrch()?.dispose();
|
|
19228
|
+
});
|
|
18876
19229
|
return null;
|
|
18877
19230
|
}
|
|
18878
19231
|
async function bootPaneHost(opts) {
|
|
@@ -19097,6 +19450,31 @@ function repoBasename(repo) {
|
|
|
19097
19450
|
function flattenIds(rows) {
|
|
19098
19451
|
return rows.map((r) => r.task.id);
|
|
19099
19452
|
}
|
|
19453
|
+
function sameSidebarRowTask(a, b) {
|
|
19454
|
+
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;
|
|
19455
|
+
}
|
|
19456
|
+
function reconcileSidebarRows(prev, next) {
|
|
19457
|
+
if (prev.length === 0)
|
|
19458
|
+
return next;
|
|
19459
|
+
const prevById = new Map;
|
|
19460
|
+
for (const row of prev)
|
|
19461
|
+
prevById.set(row.task.id, row);
|
|
19462
|
+
let allReused = prev.length === next.length;
|
|
19463
|
+
const out = new Array(next.length);
|
|
19464
|
+
for (let i = 0;i < next.length; i++) {
|
|
19465
|
+
const fresh = next[i];
|
|
19466
|
+
const old = prevById.get(fresh.task.id);
|
|
19467
|
+
if (old && old.flatIndex === fresh.flatIndex && sameSidebarRowTask(old.task, fresh.task)) {
|
|
19468
|
+
out[i] = old;
|
|
19469
|
+
if (allReused && prev[i] !== old)
|
|
19470
|
+
allReused = false;
|
|
19471
|
+
} else {
|
|
19472
|
+
out[i] = fresh;
|
|
19473
|
+
allReused = false;
|
|
19474
|
+
}
|
|
19475
|
+
}
|
|
19476
|
+
return allReused ? prev : out;
|
|
19477
|
+
}
|
|
19100
19478
|
var init_groups = () => {};
|
|
19101
19479
|
|
|
19102
19480
|
// src/tui/quick-task/host.tsx
|
|
@@ -22039,7 +22417,7 @@ var init_task_actions = __esm(() => {
|
|
|
22039
22417
|
});
|
|
22040
22418
|
|
|
22041
22419
|
// src/tui/lib/worktree-opener.ts
|
|
22042
|
-
import { spawn as
|
|
22420
|
+
import { spawn as spawn5 } from "child_process";
|
|
22043
22421
|
import { existsSync as existsSync15 } from "fs";
|
|
22044
22422
|
import { basename as basename7, delimiter, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
22045
22423
|
function executableOnPath(command, env, exists) {
|
|
@@ -22098,7 +22476,7 @@ function buildOpenWorktreeCommand(worktreePath, opener) {
|
|
|
22098
22476
|
function openWorktree(worktreePath, opener, deps = {}) {
|
|
22099
22477
|
if (!worktreePath)
|
|
22100
22478
|
return false;
|
|
22101
|
-
const spawnFn = deps.spawn ??
|
|
22479
|
+
const spawnFn = deps.spawn ?? spawn5;
|
|
22102
22480
|
const [command, args2] = buildOpenWorktreeCommand(worktreePath, opener);
|
|
22103
22481
|
try {
|
|
22104
22482
|
const child = spawnFn(command, args2, { detached: true, stdio: "ignore" });
|
|
@@ -22147,15 +22525,6 @@ var init_worktree_opener = __esm(() => {
|
|
|
22147
22525
|
});
|
|
22148
22526
|
|
|
22149
22527
|
// 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
22528
|
function createBackgroundPoller(cfg) {
|
|
22160
22529
|
const entries = new Map;
|
|
22161
22530
|
function entryFor(key) {
|
|
@@ -22177,58 +22546,17 @@ function createBackgroundPoller(cfg) {
|
|
|
22177
22546
|
if (!key)
|
|
22178
22547
|
return;
|
|
22179
22548
|
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
|
-
})();
|
|
22549
|
+
maybeStartScheduledRun(entry, cfg, (signal) => cfg.run(key, signal), (value) => entry.write(value));
|
|
22200
22550
|
},
|
|
22201
22551
|
reset() {
|
|
22202
22552
|
entries.clear();
|
|
22203
22553
|
}
|
|
22204
22554
|
};
|
|
22205
22555
|
}
|
|
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
22556
|
var init_background_poll = __esm(() => {
|
|
22231
22557
|
init_dev();
|
|
22558
|
+
init_poll_scheduling();
|
|
22559
|
+
init_poll_scheduling();
|
|
22232
22560
|
});
|
|
22233
22561
|
|
|
22234
22562
|
// src/tui/panes/sidebar/git-head.ts
|
|
@@ -22366,20 +22694,18 @@ function useSidebarBindings(opts) {
|
|
|
22366
22694
|
useBindings(() => ({
|
|
22367
22695
|
enabled: opts.focused() && !searchModeAccessor(),
|
|
22368
22696
|
bindings: bindByIds({
|
|
22369
|
-
"sidebar.nav": (
|
|
22697
|
+
"sidebar.nav": (_evt, slot) => {
|
|
22698
|
+
const down = (slot ?? 0) % 2 === 0;
|
|
22370
22699
|
if (moveModeAccessor()) {
|
|
22371
22700
|
const id = cursorTaskId();
|
|
22372
22701
|
if (id === undefined)
|
|
22373
22702
|
return;
|
|
22374
|
-
|
|
22375
|
-
opts.onMoveRequest?.(id, 1);
|
|
22376
|
-
else if (evt.name === "k" || evt.name === "up")
|
|
22377
|
-
opts.onMoveRequest?.(id, -1);
|
|
22703
|
+
opts.onMoveRequest?.(id, down ? 1 : -1);
|
|
22378
22704
|
return;
|
|
22379
22705
|
}
|
|
22380
|
-
if (
|
|
22706
|
+
if (down)
|
|
22381
22707
|
ctrl.moveDown();
|
|
22382
|
-
else
|
|
22708
|
+
else
|
|
22383
22709
|
ctrl.moveUp();
|
|
22384
22710
|
},
|
|
22385
22711
|
"sidebar.select": () => {
|
|
@@ -22453,21 +22779,18 @@ function useSidebarBindings(opts) {
|
|
|
22453
22779
|
useBindings(() => ({
|
|
22454
22780
|
enabled: opts.focused(),
|
|
22455
22781
|
bindings: bindByIds({
|
|
22456
|
-
"sidebar.view": (
|
|
22457
|
-
|
|
22458
|
-
opts.onViewSwitch?.(1);
|
|
22459
|
-
else
|
|
22460
|
-
opts.onViewSwitch?.(-1);
|
|
22782
|
+
"sidebar.view": (_evt, slot) => {
|
|
22783
|
+
opts.onViewSwitch?.((slot ?? 0) % 2 === 0 ? -1 : 1);
|
|
22461
22784
|
}
|
|
22462
22785
|
})
|
|
22463
22786
|
}));
|
|
22464
22787
|
useBindings(() => ({
|
|
22465
22788
|
enabled: opts.focused() && searchModeAccessor(),
|
|
22466
22789
|
bindings: bindByIds({
|
|
22467
|
-
"sidebar.search.nav": (
|
|
22468
|
-
if (
|
|
22790
|
+
"sidebar.search.nav": (_evt, slot) => {
|
|
22791
|
+
if ((slot ?? 0) % 2 === 0)
|
|
22469
22792
|
ctrl.moveDown();
|
|
22470
|
-
else
|
|
22793
|
+
else
|
|
22471
22794
|
ctrl.moveUp();
|
|
22472
22795
|
},
|
|
22473
22796
|
"sidebar.search.submit": () => {
|
|
@@ -22523,11 +22846,12 @@ function buildSidebarRowView(opts) {
|
|
|
22523
22846
|
const activityBadge = activityBadgeFor(activityState);
|
|
22524
22847
|
const activityLabel = activityLabelFor(activityState);
|
|
22525
22848
|
const untrackedCustomEngine = isCustomEngineTask(task) && !hasActivity;
|
|
22526
|
-
const
|
|
22849
|
+
const materializing = opts.job !== undefined;
|
|
22850
|
+
const loading = materializing || !untrackedCustomEngine && (activityState === "running" || opts.live || !hasActivity && !isMain && task.status === "in_progress");
|
|
22527
22851
|
const spinner = IN_PROGRESS_SPINNER[opts.spinnerFrame] ?? IN_PROGRESS_SPINNER[0];
|
|
22528
|
-
const tone = untrackedCustomEngine ? "textMuted" : activityLabel?.tone ?? (loading ? "primary" : activityBadge?.tone ?? badge.tone);
|
|
22852
|
+
const tone = materializing ? "primary" : untrackedCustomEngine ? "textMuted" : activityLabel?.tone ?? (loading ? "primary" : activityBadge?.tone ?? badge.tone);
|
|
22529
22853
|
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);
|
|
22854
|
+
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
22855
|
const restGlyph = untrackedCustomEngine ? NO_TRACKING_GLYPH : activityBadge?.glyph ?? badge.glyph;
|
|
22532
22856
|
const restProjectGlyph = untrackedCustomEngine ? NO_TRACKING_GLYPH : activityBadge?.glyph ?? "\u2605";
|
|
22533
22857
|
return {
|
|
@@ -22554,7 +22878,7 @@ function activityBadgeFor(state) {
|
|
|
22554
22878
|
return null;
|
|
22555
22879
|
}
|
|
22556
22880
|
}
|
|
22557
|
-
var STATUS_BADGE, STATUS_LABEL, IN_PROGRESS_SPINNER, SPINNER_FRAME_MS = 100, NO_TRACKING_GLYPH = "\xB7", NO_TRACKING_SUBTITLE = "no activity tracking";
|
|
22881
|
+
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
22882
|
var init_row_view = __esm(() => {
|
|
22559
22883
|
init_vendor();
|
|
22560
22884
|
init_groups();
|
|
@@ -22592,7 +22916,7 @@ var init_worktree_changes_poller = __esm(() => {
|
|
|
22592
22916
|
ZERO2 = { added: 0, deleted: 0 };
|
|
22593
22917
|
poller2 = createBackgroundPoller({
|
|
22594
22918
|
initial: ZERO2,
|
|
22595
|
-
equals:
|
|
22919
|
+
equals: sameWorktreeChanges,
|
|
22596
22920
|
timeoutMs: POLL_TIMEOUT_MS,
|
|
22597
22921
|
slowRetryMs: SLOW_REPO_RETRY_MS,
|
|
22598
22922
|
minIntervalMs: MIN_POLL_INTERVAL_MS,
|
|
@@ -22680,7 +23004,7 @@ function Sidebar(props) {
|
|
|
22680
23004
|
const spinnerInterval = setInterval(() => setSpinnerFrame((n) => (n + 1) % IN_PROGRESS_SPINNER.length), SPINNER_FRAME_MS);
|
|
22681
23005
|
onCleanup(() => clearInterval(spinnerInterval));
|
|
22682
23006
|
const sortMode = () => props.sortMode?.() ?? "default";
|
|
22683
|
-
const rows = createMemo(() => buildRows(props.tasks(), view(), searchMode() ? searchQuery() : "", sortMode()));
|
|
23007
|
+
const rows = createMemo((prev) => reconcileSidebarRows(prev, buildRows(props.tasks(), view(), searchMode() ? searchQuery() : "", sortMode())), []);
|
|
22684
23008
|
const flatIds = createMemo(() => flattenIds(rows()));
|
|
22685
23009
|
const firstTaskFlatIndex = createMemo(() => {
|
|
22686
23010
|
const r = rows();
|
|
@@ -23029,10 +23353,15 @@ function Sidebar(props) {
|
|
|
23029
23353
|
}
|
|
23030
23354
|
};
|
|
23031
23355
|
const changes = createMemo(() => {
|
|
23356
|
+
const pushed = pickPushedChanges(props.worktreeChanges?.(), task.worktreePath);
|
|
23357
|
+
if (pushed)
|
|
23358
|
+
return pushed;
|
|
23032
23359
|
branchTick();
|
|
23033
23360
|
if (!task.archived)
|
|
23034
23361
|
pollWorktreeChanges(task.worktreePath);
|
|
23035
23362
|
return worktreeChanges(task.worktreePath);
|
|
23363
|
+
}, undefined, {
|
|
23364
|
+
equals: sameWorktreeChanges
|
|
23036
23365
|
});
|
|
23037
23366
|
const projectBranch = createMemo(() => {
|
|
23038
23367
|
branchTick();
|
|
@@ -23043,6 +23372,7 @@ function Sidebar(props) {
|
|
|
23043
23372
|
const rowView = createMemo(() => buildSidebarRowView({
|
|
23044
23373
|
task,
|
|
23045
23374
|
activity: props.engineState?.().get(task.id),
|
|
23375
|
+
job: props.taskJobs?.().get(task.id),
|
|
23046
23376
|
live: isLive(),
|
|
23047
23377
|
spinnerFrame: spinnerFrame(),
|
|
23048
23378
|
subtitleBudget: subtitleBudget(),
|
|
@@ -23475,6 +23805,7 @@ var init_Sidebar = __esm(() => {
|
|
|
23475
23805
|
init_groups();
|
|
23476
23806
|
init_keys();
|
|
23477
23807
|
init_row_view();
|
|
23808
|
+
init_worktree_changes();
|
|
23478
23809
|
init_worktree_changes_poller();
|
|
23479
23810
|
VIEW_TABS = [{
|
|
23480
23811
|
view: "active",
|
|
@@ -23825,6 +24156,17 @@ function TasksShell(props) {
|
|
|
23825
24156
|
get engineState() {
|
|
23826
24157
|
return memo2(() => !!props.orch)() ? props.orch.engineStateSignal() : undefined;
|
|
23827
24158
|
},
|
|
24159
|
+
get taskJobs() {
|
|
24160
|
+
return memo2(() => !!props.orch)() ? props.orch.taskJobsSignal() : undefined;
|
|
24161
|
+
},
|
|
24162
|
+
get worktreeChanges() {
|
|
24163
|
+
return props.orch ? () => {
|
|
24164
|
+
const orch = props.orch;
|
|
24165
|
+
if (!orch || orch.connectionStateSignal()() !== "online")
|
|
24166
|
+
return null;
|
|
24167
|
+
return orch.worktreeChangesSignal()();
|
|
24168
|
+
} : undefined;
|
|
24169
|
+
},
|
|
23828
24170
|
onRenameRequest: (id) => void renameTask(id),
|
|
23829
24171
|
onDeleteRequest: (id) => void deleteTask(id),
|
|
23830
24172
|
onArchiveRequest: (id) => void archiveTask(id),
|
|
@@ -25028,26 +25370,24 @@ function useFileTreeBindings(opts) {
|
|
|
25028
25370
|
useBindings(() => ({
|
|
25029
25371
|
enabled: opts.focused(),
|
|
25030
25372
|
bindings: bindByIds({
|
|
25031
|
-
"files.nav": (
|
|
25032
|
-
if (
|
|
25373
|
+
"files.nav": (_evt, slot) => {
|
|
25374
|
+
if ((slot ?? 0) % 2 === 0)
|
|
25033
25375
|
opts.moveDown();
|
|
25034
|
-
else
|
|
25376
|
+
else
|
|
25035
25377
|
opts.moveUp();
|
|
25036
25378
|
},
|
|
25037
|
-
"files.hierarchy": (
|
|
25038
|
-
if (
|
|
25039
|
-
opts.expandOrDescend();
|
|
25040
|
-
else if (evt.name === "h" || evt.name === "left")
|
|
25379
|
+
"files.hierarchy": (_evt, slot) => {
|
|
25380
|
+
if ((slot ?? 0) % 2 === 0)
|
|
25041
25381
|
opts.collapseOrParent();
|
|
25382
|
+
else
|
|
25383
|
+
opts.expandOrDescend();
|
|
25042
25384
|
},
|
|
25043
|
-
"files.tab": (
|
|
25385
|
+
"files.tab": (_evt, slot) => {
|
|
25044
25386
|
const cur = opts.currentTab();
|
|
25045
25387
|
const idx = TAB_ORDER.indexOf(cur);
|
|
25046
25388
|
if (idx < 0)
|
|
25047
25389
|
return;
|
|
25048
|
-
const delta =
|
|
25049
|
-
if (delta === 0)
|
|
25050
|
-
return;
|
|
25390
|
+
const delta = (slot ?? 0) % 2 === 0 ? -1 : 1;
|
|
25051
25391
|
const next = TAB_ORDER[(idx + delta + TAB_ORDER.length) % TAB_ORDER.length];
|
|
25052
25392
|
if (next)
|
|
25053
25393
|
opts.setTab(next);
|
package/package.json
CHANGED