@sma1lboy/kobe 0.7.0 → 0.7.2
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 +537 -242
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -70,7 +70,7 @@ var init_package = __esm(() => {
|
|
|
70
70
|
package_default = {
|
|
71
71
|
$schema: "https://json.schemastore.org/package.json",
|
|
72
72
|
name: "@sma1lboy/kobe",
|
|
73
|
-
version: "0.7.
|
|
73
|
+
version: "0.7.2",
|
|
74
74
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
75
75
|
type: "module",
|
|
76
76
|
packageManager: "bun@1.3.13",
|
|
@@ -748,7 +748,8 @@ function coerceTask(value) {
|
|
|
748
748
|
if (!isTaskStatus(v.status))
|
|
749
749
|
return null;
|
|
750
750
|
const archived = typeof v.archived === "boolean" ? v.archived : false;
|
|
751
|
-
const
|
|
751
|
+
const kind = v.kind === "main" ? "main" : "task";
|
|
752
|
+
const healedStatus = kind === "main" ? v.status === "in_progress" || v.status === "done" ? "backlog" : v.status : v.status === "done" && !archived ? "in_progress" : v.status;
|
|
752
753
|
return {
|
|
753
754
|
id: toTaskId(v.id),
|
|
754
755
|
title: v.title,
|
|
@@ -758,7 +759,7 @@ function coerceTask(value) {
|
|
|
758
759
|
status: healedStatus,
|
|
759
760
|
archived,
|
|
760
761
|
pinned: typeof v.pinned === "boolean" ? v.pinned : false,
|
|
761
|
-
kind
|
|
762
|
+
kind,
|
|
762
763
|
vendor: isVendorId(v.vendor) ? v.vendor : DEFAULT_TASK_VENDOR,
|
|
763
764
|
prStatus: coercePRStatus(v.prStatus),
|
|
764
765
|
createdAt: v.createdAt,
|
|
@@ -3227,6 +3228,111 @@ var init_protocol = __esm(() => {
|
|
|
3227
3228
|
CHANNEL_NAMES = ["task.snapshot", "active-task", "update"];
|
|
3228
3229
|
});
|
|
3229
3230
|
|
|
3231
|
+
// src/daemon/paths.ts
|
|
3232
|
+
import { createHash as createHash2 } from "crypto";
|
|
3233
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
3234
|
+
import { join as join3 } from "path";
|
|
3235
|
+
function shortHomeTag(homeDir2) {
|
|
3236
|
+
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
3237
|
+
}
|
|
3238
|
+
function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
3239
|
+
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3240
|
+
return naturalPath;
|
|
3241
|
+
const tag = shortHomeTag(homeDir2);
|
|
3242
|
+
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
3243
|
+
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
3244
|
+
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
3245
|
+
return fallback;
|
|
3246
|
+
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
3247
|
+
}
|
|
3248
|
+
function defaultDaemonSocketPath(homeDir2) {
|
|
3249
|
+
const override = process.env.KOBE_DAEMON_SOCKET_PATH;
|
|
3250
|
+
if (override && override.length > 0)
|
|
3251
|
+
return override;
|
|
3252
|
+
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
3253
|
+
if (explicit && explicit.length > 0) {
|
|
3254
|
+
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
3255
|
+
}
|
|
3256
|
+
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
3257
|
+
if (runtimeDir && runtimeDir.length > 0) {
|
|
3258
|
+
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
3259
|
+
}
|
|
3260
|
+
const home = homedir3();
|
|
3261
|
+
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
3262
|
+
}
|
|
3263
|
+
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3264
|
+
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
3265
|
+
if (override && override.length > 0)
|
|
3266
|
+
return override;
|
|
3267
|
+
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
3268
|
+
}
|
|
3269
|
+
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3270
|
+
return join3(homeDir2, ".kobe", "daemon.log");
|
|
3271
|
+
}
|
|
3272
|
+
function defaultClientLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir3()) {
|
|
3273
|
+
return join3(homeDir2, ".kobe", "client.log");
|
|
3274
|
+
}
|
|
3275
|
+
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
3276
|
+
var init_paths2 = () => {};
|
|
3277
|
+
|
|
3278
|
+
// src/client/client-log.ts
|
|
3279
|
+
import { appendFile, mkdir as mkdir2 } from "fs/promises";
|
|
3280
|
+
import { dirname as dirname3 } from "path";
|
|
3281
|
+
function setClientLogContext(ctx) {
|
|
3282
|
+
context = ctx;
|
|
3283
|
+
}
|
|
3284
|
+
function coerceError(err) {
|
|
3285
|
+
if (err instanceof Error)
|
|
3286
|
+
return { message: err.message, stack: err.stack };
|
|
3287
|
+
let text;
|
|
3288
|
+
try {
|
|
3289
|
+
text = typeof err === "string" ? err : JSON.stringify(err);
|
|
3290
|
+
} catch {
|
|
3291
|
+
text = String(err);
|
|
3292
|
+
}
|
|
3293
|
+
return { message: text };
|
|
3294
|
+
}
|
|
3295
|
+
function formatClientEntry(subsystem, message, now = new Date) {
|
|
3296
|
+
return `[${now.toISOString()}] client ${context} [${subsystem}] pid=${process.pid}: ${message}
|
|
3297
|
+
`;
|
|
3298
|
+
}
|
|
3299
|
+
function warnOnce() {
|
|
3300
|
+
if (warnedOnce)
|
|
3301
|
+
return;
|
|
3302
|
+
warnedOnce = true;
|
|
3303
|
+
try {
|
|
3304
|
+
process.stderr.write(`[kobe] client log write failed; continuing without it
|
|
3305
|
+
`);
|
|
3306
|
+
} catch {}
|
|
3307
|
+
}
|
|
3308
|
+
function append(line) {
|
|
3309
|
+
const path3 = defaultClientLogPath();
|
|
3310
|
+
writeChain = writeChain.then(async () => {
|
|
3311
|
+
try {
|
|
3312
|
+
await appendFile(path3, line);
|
|
3313
|
+
} catch (err) {
|
|
3314
|
+
if (err?.code === "ENOENT") {
|
|
3315
|
+
await mkdir2(dirname3(path3), { recursive: true });
|
|
3316
|
+
await appendFile(path3, line);
|
|
3317
|
+
} else {
|
|
3318
|
+
throw err;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}).catch(() => warnOnce());
|
|
3322
|
+
}
|
|
3323
|
+
function logClient(subsystem, message) {
|
|
3324
|
+
append(formatClientEntry(subsystem, message));
|
|
3325
|
+
}
|
|
3326
|
+
function logClientError(subsystem, err) {
|
|
3327
|
+
const e = coerceError(err);
|
|
3328
|
+
append(formatClientEntry(subsystem, e.stack ?? e.message));
|
|
3329
|
+
}
|
|
3330
|
+
var context = "client", warnedOnce = false, writeChain;
|
|
3331
|
+
var init_client_log = __esm(() => {
|
|
3332
|
+
init_paths2();
|
|
3333
|
+
writeChain = Promise.resolve();
|
|
3334
|
+
});
|
|
3335
|
+
|
|
3230
3336
|
// src/client/index.ts
|
|
3231
3337
|
import { connect } from "net";
|
|
3232
3338
|
|
|
@@ -3259,6 +3365,9 @@ class KobeDaemonClient {
|
|
|
3259
3365
|
p.then(cleanup, cleanup);
|
|
3260
3366
|
return p;
|
|
3261
3367
|
}
|
|
3368
|
+
get isDisposed() {
|
|
3369
|
+
return this.disposed;
|
|
3370
|
+
}
|
|
3262
3371
|
close() {
|
|
3263
3372
|
this.disposed = true;
|
|
3264
3373
|
this.socket?.end();
|
|
@@ -3372,7 +3481,13 @@ class KobeDaemonClient {
|
|
|
3372
3481
|
}
|
|
3373
3482
|
}
|
|
3374
3483
|
onLine(line) {
|
|
3375
|
-
|
|
3484
|
+
let frame;
|
|
3485
|
+
try {
|
|
3486
|
+
frame = JSON.parse(line);
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
logClientError("client-frame", err);
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3376
3491
|
if (frame.type === "event") {
|
|
3377
3492
|
this.emit(frame);
|
|
3378
3493
|
return;
|
|
@@ -3397,6 +3512,7 @@ class KobeDaemonClient {
|
|
|
3397
3512
|
}
|
|
3398
3513
|
var init_client = __esm(() => {
|
|
3399
3514
|
init_protocol();
|
|
3515
|
+
init_client_log();
|
|
3400
3516
|
});
|
|
3401
3517
|
|
|
3402
3518
|
// src/engine/claude-code-local/normalize.ts
|
|
@@ -3446,8 +3562,8 @@ function normalizeClaudeContent(content) {
|
|
|
3446
3562
|
}
|
|
3447
3563
|
|
|
3448
3564
|
// src/engine/claude-code-local/history.ts
|
|
3449
|
-
import { appendFile, mkdir as
|
|
3450
|
-
import { homedir as
|
|
3565
|
+
import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
3566
|
+
import { homedir as homedir4 } from "os";
|
|
3451
3567
|
import path3 from "path";
|
|
3452
3568
|
function encodeCwd(cwd) {
|
|
3453
3569
|
return cwd.replace(/[/.]/g, "-");
|
|
@@ -3455,7 +3571,7 @@ function encodeCwd(cwd) {
|
|
|
3455
3571
|
async function listSessionFilesForWorktree(worktree) {
|
|
3456
3572
|
if (!worktree)
|
|
3457
3573
|
return [];
|
|
3458
|
-
const dir = path3.join(
|
|
3574
|
+
const dir = path3.join(homedir4(), ".claude", "projects", encodeCwd(worktree));
|
|
3459
3575
|
let entries;
|
|
3460
3576
|
try {
|
|
3461
3577
|
entries = await readdir(dir);
|
|
@@ -3561,7 +3677,7 @@ var defaultDeps;
|
|
|
3561
3677
|
var init_history = __esm(() => {
|
|
3562
3678
|
defaultDeps = {
|
|
3563
3679
|
projectsDir() {
|
|
3564
|
-
return path3.join(
|
|
3680
|
+
return path3.join(homedir4(), ".claude", "projects");
|
|
3565
3681
|
},
|
|
3566
3682
|
async readdir(p) {
|
|
3567
3683
|
try {
|
|
@@ -3663,7 +3779,7 @@ function validPositive(v) {
|
|
|
3663
3779
|
|
|
3664
3780
|
// src/engine/codex-local/history.ts
|
|
3665
3781
|
import { readFile as readFile3, readdir as readdir2, stat as stat2, unlink as unlink3 } from "fs/promises";
|
|
3666
|
-
import { homedir as
|
|
3782
|
+
import { homedir as homedir5 } from "os";
|
|
3667
3783
|
import path4 from "path";
|
|
3668
3784
|
async function listRolloutFiles(deps = defaultDeps2) {
|
|
3669
3785
|
const root = deps.sessionsDir();
|
|
@@ -3994,7 +4110,7 @@ var init_history2 = __esm(() => {
|
|
|
3994
4110
|
init_synthetic();
|
|
3995
4111
|
defaultDeps2 = {
|
|
3996
4112
|
sessionsDir() {
|
|
3997
|
-
return path4.join(
|
|
4113
|
+
return path4.join(homedir5(), ".codex", "sessions");
|
|
3998
4114
|
},
|
|
3999
4115
|
async readdir(p) {
|
|
4000
4116
|
try {
|
|
@@ -4028,14 +4144,14 @@ function copilotUsageToSnapshot(value) {
|
|
|
4028
4144
|
output += numberOr2(metrics.usage.outputTokens, 0);
|
|
4029
4145
|
cached += numberOr2(metrics.usage.cacheReadTokens, 0);
|
|
4030
4146
|
}
|
|
4031
|
-
const
|
|
4032
|
-
if (input === 0 && output === 0 && cached === 0 &&
|
|
4147
|
+
const context2 = numberOr2(value.currentTokens, 0);
|
|
4148
|
+
if (input === 0 && output === 0 && cached === 0 && context2 === 0)
|
|
4033
4149
|
return;
|
|
4034
4150
|
return {
|
|
4035
4151
|
input_tokens: input,
|
|
4036
4152
|
output_tokens: output,
|
|
4037
4153
|
...cached > 0 ? { cache_read_input_tokens: cached } : {},
|
|
4038
|
-
...
|
|
4154
|
+
...context2 > 0 ? { context_tokens: context2 } : {}
|
|
4039
4155
|
};
|
|
4040
4156
|
}
|
|
4041
4157
|
function isObject4(v) {
|
|
@@ -4047,7 +4163,7 @@ function numberOr2(value, fallback) {
|
|
|
4047
4163
|
|
|
4048
4164
|
// src/engine/copilot-local/history.ts
|
|
4049
4165
|
import { readFile as readFile4, readdir as readdir3, rm, stat as stat3 } from "fs/promises";
|
|
4050
|
-
import { homedir as
|
|
4166
|
+
import { homedir as homedir6 } from "os";
|
|
4051
4167
|
import path5 from "path";
|
|
4052
4168
|
async function listSessionDirs(deps = defaultDeps3) {
|
|
4053
4169
|
const root = path5.join(deps.copilotDir(), "session-state");
|
|
@@ -4216,7 +4332,7 @@ var init_history3 = __esm(() => {
|
|
|
4216
4332
|
const override = process.env.COPILOT_HOME?.trim();
|
|
4217
4333
|
if (override)
|
|
4218
4334
|
return override;
|
|
4219
|
-
return path5.join(
|
|
4335
|
+
return path5.join(homedir6(), ".copilot");
|
|
4220
4336
|
},
|
|
4221
4337
|
async readdir(p) {
|
|
4222
4338
|
try {
|
|
@@ -4288,6 +4404,90 @@ var init_auto_title = __esm(() => {
|
|
|
4288
4404
|
init_history3();
|
|
4289
4405
|
});
|
|
4290
4406
|
|
|
4407
|
+
// src/cli/invocation.ts
|
|
4408
|
+
import { fileURLToPath } from "url";
|
|
4409
|
+
function kobeCliInvocation() {
|
|
4410
|
+
const isBuilt = import.meta.url.endsWith(".js");
|
|
4411
|
+
if (isBuilt)
|
|
4412
|
+
return ["kobe"];
|
|
4413
|
+
const entry = fileURLToPath(new URL("./index.ts", import.meta.url));
|
|
4414
|
+
const preload = fileURLToPath(import.meta.resolve("@opentui/solid/preload"));
|
|
4415
|
+
return [process.execPath, "--preload", preload, "--conditions=browser", entry];
|
|
4416
|
+
}
|
|
4417
|
+
var init_invocation = () => {};
|
|
4418
|
+
|
|
4419
|
+
// src/tmux/session-layout.ts
|
|
4420
|
+
function shellQuote(s) {
|
|
4421
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4422
|
+
}
|
|
4423
|
+
function shellQuoteArgv(argv) {
|
|
4424
|
+
return argv.map(shellQuote).join(" ");
|
|
4425
|
+
}
|
|
4426
|
+
function keepAlive(cmd) {
|
|
4427
|
+
return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
|
|
4428
|
+
}
|
|
4429
|
+
function shQuote(s) {
|
|
4430
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4431
|
+
}
|
|
4432
|
+
function homeWelcomeCommand() {
|
|
4433
|
+
const msg = "\\n No task selected\\n\\n Press N to create a task, or pick one on the left.\\n\\n";
|
|
4434
|
+
return `clear; printf ${shQuote(msg)}; exec "\${SHELL:-/bin/sh}"`;
|
|
4435
|
+
}
|
|
4436
|
+
function engineLaunchLine(engineCmd, init) {
|
|
4437
|
+
const tail = keepAlive(engineCmd);
|
|
4438
|
+
const script = init?.initScript?.trim();
|
|
4439
|
+
if (!script)
|
|
4440
|
+
return tail;
|
|
4441
|
+
const group = ["{", script, "}"].join(`
|
|
4442
|
+
`);
|
|
4443
|
+
if (init?.markerPath) {
|
|
4444
|
+
const marker = shQuote(init.markerPath);
|
|
4445
|
+
const markerDir = shQuote(markerDirOf(init.markerPath));
|
|
4446
|
+
return [
|
|
4447
|
+
`if [ ! -f ${marker} ]; then`,
|
|
4448
|
+
group,
|
|
4449
|
+
`if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
|
|
4450
|
+
"fi",
|
|
4451
|
+
tail
|
|
4452
|
+
].join(`
|
|
4453
|
+
`);
|
|
4454
|
+
}
|
|
4455
|
+
return [group, tail].join(`
|
|
4456
|
+
`);
|
|
4457
|
+
}
|
|
4458
|
+
function markerDirOf(p) {
|
|
4459
|
+
const i = p.lastIndexOf("/");
|
|
4460
|
+
return i <= 0 ? "." : p.slice(0, i);
|
|
4461
|
+
}
|
|
4462
|
+
function fallbackOpsScript(cwd) {
|
|
4463
|
+
return `cd ${shellQuote(cwd)} && while :; do clear; printf "\\033[1m# %s\\033[0m\\n\\n" ${shellQuote(cwd)}; git status --short --branch 2>/dev/null | sed 's/^/ /' || true; printf "\\n"; if command -v lsd >/dev/null 2>&1; then lsd --tree --git -I node_modules -I .git --depth 2 .; elif command -v eza >/dev/null 2>&1; then eza --tree --git -L 2 -I 'node_modules|.git' .; elif command -v tree >/dev/null 2>&1; then tree -L 2 -I 'node_modules|.git'; else ls -la; fi; sleep 2; done`;
|
|
4464
|
+
}
|
|
4465
|
+
function previewWindowCommand(args) {
|
|
4466
|
+
const wt = shellQuote(args.worktree);
|
|
4467
|
+
const file = shellQuote(args.relPath);
|
|
4468
|
+
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
4469
|
+
const fallback = `cd ${wt} && if ! git diff --quiet HEAD -- ${file} 2>/dev/null; then ` + `git diff HEAD -- ${file} | { delta --paging=always 2>/dev/null || less -R; }; ` + `else bat --style=plain --paging=always ${file} 2>/dev/null || \${PAGER:-less} ${file} 2>/dev/null || cat ${file}; fi`;
|
|
4470
|
+
return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
|
|
4471
|
+
}
|
|
4472
|
+
function updatePageCommand(args) {
|
|
4473
|
+
return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
|
|
4474
|
+
}
|
|
4475
|
+
function tasksPaneCommand(cliInvocation, opts = {}) {
|
|
4476
|
+
const argv = [...cliInvocation, "tasks"];
|
|
4477
|
+
if (opts.initialTaskId)
|
|
4478
|
+
argv.push("--initial-task-id", opts.initialTaskId);
|
|
4479
|
+
return shellQuoteArgv(argv);
|
|
4480
|
+
}
|
|
4481
|
+
function opsPaneCommand(args) {
|
|
4482
|
+
if (args.taskId && args.claudePaneId) {
|
|
4483
|
+
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
4484
|
+
const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
|
|
4485
|
+
return `KOBE_FILETREE_WATCH=1 ${inv} ops --task-id ${shellQuote(args.taskId)} --worktree ${shellQuote(args.cwd)} ` + `--target-pane ${shellQuote(args.claudePaneId)}${vendorFlag} || { ${fallbackOpsScript(args.cwd)}; }`;
|
|
4486
|
+
}
|
|
4487
|
+
return fallbackOpsScript(args.cwd);
|
|
4488
|
+
}
|
|
4489
|
+
var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
|
|
4490
|
+
|
|
4291
4491
|
// src/tmux/client.ts
|
|
4292
4492
|
var exports_client = {};
|
|
4293
4493
|
__export(exports_client, {
|
|
@@ -4325,6 +4525,7 @@ __export(exports_client, {
|
|
|
4325
4525
|
CLAUDE_ROLE_OPTION: () => CLAUDE_ROLE_OPTION,
|
|
4326
4526
|
CHAT_TAB_SESSION_ID_OPTION: () => CHAT_TAB_SESSION_ID_OPTION
|
|
4327
4527
|
});
|
|
4528
|
+
import { homedir as homedir7 } from "os";
|
|
4328
4529
|
function tmuxArgs(...args) {
|
|
4329
4530
|
return ["tmux", "-L", KOBE_TMUX_SOCKET, ...args];
|
|
4330
4531
|
}
|
|
@@ -4344,16 +4545,34 @@ async function drainText(stream) {
|
|
|
4344
4545
|
}
|
|
4345
4546
|
}
|
|
4346
4547
|
async function runTmux(args) {
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4548
|
+
try {
|
|
4549
|
+
const proc = Bun.spawn(tmuxArgs(...args), {
|
|
4550
|
+
stdin: "ignore",
|
|
4551
|
+
cwd: SAFE_SPAWN_CWD,
|
|
4552
|
+
stdout: "ignore",
|
|
4553
|
+
stderr: "pipe"
|
|
4554
|
+
});
|
|
4555
|
+
const [errText, code] = await Promise.all([drainText(proc.stderr), proc.exited]);
|
|
4556
|
+
if (code !== 0 && errText.trim().length > 0) {
|
|
4557
|
+
console.error(`[kobe tmux] ${args.join(" ")} (${code}): ${errText.trim()}`);
|
|
4558
|
+
}
|
|
4559
|
+
return code;
|
|
4560
|
+
} catch {
|
|
4561
|
+
return 1;
|
|
4351
4562
|
}
|
|
4352
|
-
return code;
|
|
4353
4563
|
}
|
|
4354
4564
|
async function runTmuxQuiet(args) {
|
|
4355
|
-
|
|
4356
|
-
|
|
4565
|
+
try {
|
|
4566
|
+
const proc = Bun.spawn(tmuxArgs(...args), {
|
|
4567
|
+
stdin: "ignore",
|
|
4568
|
+
cwd: SAFE_SPAWN_CWD,
|
|
4569
|
+
stdout: "ignore",
|
|
4570
|
+
stderr: "ignore"
|
|
4571
|
+
});
|
|
4572
|
+
return await proc.exited;
|
|
4573
|
+
} catch {
|
|
4574
|
+
return 1;
|
|
4575
|
+
}
|
|
4357
4576
|
}
|
|
4358
4577
|
function tmuxCommandSequence(commands) {
|
|
4359
4578
|
const out = [];
|
|
@@ -4371,12 +4590,16 @@ async function runTmuxSequence(commands) {
|
|
|
4371
4590
|
return args.length === 0 ? 0 : runTmux(args);
|
|
4372
4591
|
}
|
|
4373
4592
|
async function runTmuxCapturing(args) {
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4593
|
+
try {
|
|
4594
|
+
const proc = Bun.spawn(tmuxArgs(...args), { stdin: "ignore", cwd: SAFE_SPAWN_CWD, stdout: "pipe", stderr: "pipe" });
|
|
4595
|
+
const [stdout, errText, code] = await Promise.all([drainText(proc.stdout), drainText(proc.stderr), proc.exited]);
|
|
4596
|
+
if (code !== 0 && errText.trim().length > 0) {
|
|
4597
|
+
console.error(`[kobe tmux] ${args.join(" ")} (${code}): ${errText.trim()}`);
|
|
4598
|
+
}
|
|
4599
|
+
return { code, stdout };
|
|
4600
|
+
} catch {
|
|
4601
|
+
return { code: 1, stdout: "" };
|
|
4378
4602
|
}
|
|
4379
|
-
return { code, stdout };
|
|
4380
4603
|
}
|
|
4381
4604
|
async function runTmuxSequenceCapturing(commands) {
|
|
4382
4605
|
const args = tmuxCommandSequence(commands);
|
|
@@ -4384,7 +4607,7 @@ async function runTmuxSequenceCapturing(commands) {
|
|
|
4384
4607
|
}
|
|
4385
4608
|
async function tmuxAvailable() {
|
|
4386
4609
|
try {
|
|
4387
|
-
const proc = Bun.spawn(["tmux", "-V"], { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
|
|
4610
|
+
const proc = Bun.spawn(["tmux", "-V"], { stdin: "ignore", cwd: SAFE_SPAWN_CWD, stdout: "ignore", stderr: "ignore" });
|
|
4388
4611
|
return await proc.exited === 0;
|
|
4389
4612
|
} catch {
|
|
4390
4613
|
return false;
|
|
@@ -4499,21 +4722,53 @@ async function killSession(name) {
|
|
|
4499
4722
|
}
|
|
4500
4723
|
async function ensureFallbackSession() {
|
|
4501
4724
|
const name = KOBE_HOME_SESSION;
|
|
4502
|
-
if (
|
|
4503
|
-
await
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4725
|
+
if (await sessionExists(name)) {
|
|
4726
|
+
if (await getSessionOption(name, HOME_KIND_OPTION) === "tasks")
|
|
4727
|
+
return name;
|
|
4728
|
+
await runTmux(["kill-session", "-t", `=${name}`]);
|
|
4729
|
+
}
|
|
4730
|
+
const r0 = await runTmuxCapturing([
|
|
4731
|
+
"new-session",
|
|
4732
|
+
"-d",
|
|
4733
|
+
"-s",
|
|
4734
|
+
name,
|
|
4735
|
+
"-c",
|
|
4736
|
+
SAFE_SPAWN_CWD,
|
|
4737
|
+
"-x",
|
|
4738
|
+
"220",
|
|
4739
|
+
"-y",
|
|
4740
|
+
"50",
|
|
4741
|
+
"-P",
|
|
4742
|
+
"-F",
|
|
4743
|
+
"#{pane_id}",
|
|
4744
|
+
homeWelcomeCommand()
|
|
4745
|
+
]);
|
|
4746
|
+
const mainPane = r0.stdout.trim();
|
|
4747
|
+
if (mainPane) {
|
|
4748
|
+
const r1 = await runTmuxCapturing([
|
|
4749
|
+
"split-window",
|
|
4750
|
+
"-h",
|
|
4751
|
+
"-b",
|
|
4752
|
+
"-t",
|
|
4753
|
+
mainPane,
|
|
4754
|
+
"-l",
|
|
4755
|
+
`${TASKS_PANE_WIDTH}`,
|
|
4513
4756
|
"-c",
|
|
4514
|
-
|
|
4757
|
+
SAFE_SPAWN_CWD,
|
|
4758
|
+
"-P",
|
|
4759
|
+
"-F",
|
|
4760
|
+
"#{pane_id}",
|
|
4761
|
+
keepAlive(tasksPaneCommand(kobeCliInvocation()))
|
|
4515
4762
|
]);
|
|
4763
|
+
const tasksPane = r1.stdout.trim();
|
|
4764
|
+
if (tasksPane) {
|
|
4765
|
+
await runTmuxSequence([
|
|
4766
|
+
["set-option", "-p", "-t", tasksPane, PANE_ROLE_OPTION, "tasks"],
|
|
4767
|
+
["select-pane", "-t", tasksPane]
|
|
4768
|
+
]);
|
|
4769
|
+
}
|
|
4516
4770
|
}
|
|
4771
|
+
await setSessionOption(name, HOME_KIND_OPTION, "tasks");
|
|
4517
4772
|
return name;
|
|
4518
4773
|
}
|
|
4519
4774
|
async function switchClientBeforeKill(killedName, nextSessionName) {
|
|
@@ -4527,9 +4782,11 @@ async function switchClientBeforeKill(killedName, nextSessionName) {
|
|
|
4527
4782
|
const fallback = await ensureFallbackSession();
|
|
4528
4783
|
await runTmux(["switch-client", "-t", `=${fallback}`]);
|
|
4529
4784
|
}
|
|
4530
|
-
var KOBE_TMUX_SOCKET, PANE_ROLE_OPTION = "@kobe_role", CLAUDE_ROLE_OPTION, CLAUDE_ROLE_VALUE = "claude", CHAT_TAB_SESSION_ID_OPTION = "@kobe_session_id", KOBE_HOME_SESSION = "kobe-home";
|
|
4785
|
+
var KOBE_TMUX_SOCKET, SAFE_SPAWN_CWD, PANE_ROLE_OPTION = "@kobe_role", CLAUDE_ROLE_OPTION, CLAUDE_ROLE_VALUE = "claude", CHAT_TAB_SESSION_ID_OPTION = "@kobe_session_id", KOBE_HOME_SESSION = "kobe-home", HOME_KIND_OPTION = "@kobe_home";
|
|
4531
4786
|
var init_client2 = __esm(() => {
|
|
4787
|
+
init_invocation();
|
|
4532
4788
|
KOBE_TMUX_SOCKET = process.env.KOBE_TMUX_SOCKET?.trim() || "kobe";
|
|
4789
|
+
SAFE_SPAWN_CWD = homedir7() || "/";
|
|
4533
4790
|
CLAUDE_ROLE_OPTION = PANE_ROLE_OPTION;
|
|
4534
4791
|
});
|
|
4535
4792
|
|
|
@@ -4627,6 +4884,13 @@ function formatDaemonError(subsystem, err, now = new Date) {
|
|
|
4627
4884
|
function logDaemonError(subsystem, err) {
|
|
4628
4885
|
process.stderr.write(formatDaemonError(subsystem, err));
|
|
4629
4886
|
}
|
|
4887
|
+
function formatDaemonInfo(subsystem, message, now = new Date) {
|
|
4888
|
+
return `[${now.toISOString()}] daemon [${subsystem}]: ${message}
|
|
4889
|
+
`;
|
|
4890
|
+
}
|
|
4891
|
+
function logDaemonInfo(subsystem, message) {
|
|
4892
|
+
process.stderr.write(formatDaemonInfo(subsystem, message));
|
|
4893
|
+
}
|
|
4630
4894
|
function installDaemonCrashHandlers(log = (l) => process.stderr.write(l)) {
|
|
4631
4895
|
if (onRejection || onException)
|
|
4632
4896
|
return;
|
|
@@ -4702,54 +4966,10 @@ class DaemonEventBus {
|
|
|
4702
4966
|
}
|
|
4703
4967
|
}
|
|
4704
4968
|
|
|
4705
|
-
// src/daemon/paths.ts
|
|
4706
|
-
import { createHash as createHash2 } from "crypto";
|
|
4707
|
-
import { homedir as homedir6, tmpdir } from "os";
|
|
4708
|
-
import { join as join3 } from "path";
|
|
4709
|
-
function shortHomeTag(homeDir2) {
|
|
4710
|
-
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
4711
|
-
}
|
|
4712
|
-
function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
4713
|
-
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
4714
|
-
return naturalPath;
|
|
4715
|
-
const tag = shortHomeTag(homeDir2);
|
|
4716
|
-
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
4717
|
-
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
4718
|
-
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
4719
|
-
return fallback;
|
|
4720
|
-
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
4721
|
-
}
|
|
4722
|
-
function defaultDaemonSocketPath(homeDir2) {
|
|
4723
|
-
const override = process.env.KOBE_DAEMON_SOCKET_PATH;
|
|
4724
|
-
if (override && override.length > 0)
|
|
4725
|
-
return override;
|
|
4726
|
-
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
4727
|
-
if (explicit && explicit.length > 0) {
|
|
4728
|
-
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
4729
|
-
}
|
|
4730
|
-
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
4731
|
-
if (runtimeDir && runtimeDir.length > 0) {
|
|
4732
|
-
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
4733
|
-
}
|
|
4734
|
-
const home = homedir6();
|
|
4735
|
-
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
4736
|
-
}
|
|
4737
|
-
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir6()) {
|
|
4738
|
-
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
4739
|
-
if (override && override.length > 0)
|
|
4740
|
-
return override;
|
|
4741
|
-
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
4742
|
-
}
|
|
4743
|
-
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir6()) {
|
|
4744
|
-
return join3(homeDir2, ".kobe", "daemon.log");
|
|
4745
|
-
}
|
|
4746
|
-
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
4747
|
-
var init_paths2 = () => {};
|
|
4748
|
-
|
|
4749
4969
|
// src/daemon/server.ts
|
|
4750
|
-
import { mkdir as
|
|
4970
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
4751
4971
|
import { createServer } from "net";
|
|
4752
|
-
import { dirname as
|
|
4972
|
+
import { dirname as dirname4 } from "path";
|
|
4753
4973
|
function resolveIdleGraceMs() {
|
|
4754
4974
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
4755
4975
|
if (raw === undefined)
|
|
@@ -4783,10 +5003,12 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4783
5003
|
if (stopping || guiCount() > 0)
|
|
4784
5004
|
return;
|
|
4785
5005
|
cancelIdleTimer();
|
|
5006
|
+
logDaemonInfo("idle", `last gui gone \u2014 arming ${idleGraceMs}ms idle-stop grace`);
|
|
4786
5007
|
idleTimer = setTimeout(() => {
|
|
4787
5008
|
idleTimer = null;
|
|
4788
5009
|
if (stopping || guiCount() > 0)
|
|
4789
5010
|
return;
|
|
5011
|
+
logDaemonInfo("idle", "grace elapsed with no gui \u2014 self-stopping");
|
|
4790
5012
|
stopSoon().catch((err) => logDaemonError("daemon-idle-shutdown", err));
|
|
4791
5013
|
}, idleGraceMs);
|
|
4792
5014
|
idleTimer.unref?.();
|
|
@@ -4795,8 +5017,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4795
5017
|
bus.onPublish((event) => {
|
|
4796
5018
|
broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
|
|
4797
5019
|
});
|
|
4798
|
-
await
|
|
4799
|
-
await
|
|
5020
|
+
await mkdir4(dirname4(socketPath), { recursive: true });
|
|
5021
|
+
await mkdir4(dirname4(pidPath), { recursive: true });
|
|
4800
5022
|
await unlink4(socketPath).catch(() => {});
|
|
4801
5023
|
const server = createServer((socket) => {
|
|
4802
5024
|
const client = {
|
|
@@ -4815,6 +5037,9 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4815
5037
|
socket.on("error", () => {});
|
|
4816
5038
|
socket.on("close", () => {
|
|
4817
5039
|
clients.delete(client);
|
|
5040
|
+
if (client.subscribed) {
|
|
5041
|
+
logDaemonInfo("conn", `client #${client.id} (${client.holdsLifetime ? "gui" : "pane"}) disconnected \u2014 ${clients.size} client(s), ${guiCount()} gui left`);
|
|
5042
|
+
}
|
|
4818
5043
|
if (client.holdsLifetime)
|
|
4819
5044
|
maybeArmIdleShutdown();
|
|
4820
5045
|
});
|
|
@@ -5007,6 +5232,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5007
5232
|
client.holdsLifetime = role === "gui";
|
|
5008
5233
|
if (client.holdsLifetime)
|
|
5009
5234
|
cancelIdleTimer();
|
|
5235
|
+
logDaemonInfo("conn", `client #${client.id} subscribed as ${role} \u2014 ${clients.size} client(s), ${guiCount()} gui`);
|
|
5010
5236
|
for (const event of bus.snapshot()) {
|
|
5011
5237
|
writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
|
|
5012
5238
|
}
|
|
@@ -5186,13 +5412,13 @@ __export(exports_daemon_process, {
|
|
|
5186
5412
|
});
|
|
5187
5413
|
import { spawn } from "child_process";
|
|
5188
5414
|
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5189
|
-
import { dirname as
|
|
5190
|
-
import { fileURLToPath } from "url";
|
|
5415
|
+
import { dirname as dirname5, resolve as resolve2 } from "path";
|
|
5416
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5191
5417
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
5192
5418
|
let stdio = "ignore";
|
|
5193
5419
|
let logFd;
|
|
5194
5420
|
try {
|
|
5195
|
-
mkdirSync2(
|
|
5421
|
+
mkdirSync2(dirname5(logPath), { recursive: true });
|
|
5196
5422
|
logFd = openSync(logPath, "a");
|
|
5197
5423
|
stdio = ["ignore", logFd, logFd];
|
|
5198
5424
|
} catch {
|
|
@@ -5255,11 +5481,11 @@ async function testDaemonResponds(socketPath, timeoutMs = DAEMON_HELLO_TIMEOUT_M
|
|
|
5255
5481
|
return alive;
|
|
5256
5482
|
}
|
|
5257
5483
|
function resolveKobeSpawn(subcommand) {
|
|
5258
|
-
const here =
|
|
5484
|
+
const here = fileURLToPath2(import.meta.url);
|
|
5259
5485
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
5260
5486
|
return [process.execPath, ...subcommand];
|
|
5261
5487
|
}
|
|
5262
|
-
const dir =
|
|
5488
|
+
const dir = dirname5(here);
|
|
5263
5489
|
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
5264
5490
|
if (existsSync(sourceEntry))
|
|
5265
5491
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
@@ -5513,83 +5739,6 @@ var init_prompt_delivery = __esm(() => {
|
|
|
5513
5739
|
init_client2();
|
|
5514
5740
|
});
|
|
5515
5741
|
|
|
5516
|
-
// src/cli/invocation.ts
|
|
5517
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5518
|
-
function kobeCliInvocation() {
|
|
5519
|
-
const isBuilt = import.meta.url.endsWith(".js");
|
|
5520
|
-
if (isBuilt)
|
|
5521
|
-
return ["kobe"];
|
|
5522
|
-
const entry = fileURLToPath2(new URL("./index.ts", import.meta.url));
|
|
5523
|
-
const preload = fileURLToPath2(import.meta.resolve("@opentui/solid/preload"));
|
|
5524
|
-
return [process.execPath, "--preload", preload, "--conditions=browser", entry];
|
|
5525
|
-
}
|
|
5526
|
-
var init_invocation = () => {};
|
|
5527
|
-
|
|
5528
|
-
// src/tmux/session-layout.ts
|
|
5529
|
-
function shellQuote(s) {
|
|
5530
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
5531
|
-
}
|
|
5532
|
-
function shellQuoteArgv(argv) {
|
|
5533
|
-
return argv.map(shellQuote).join(" ");
|
|
5534
|
-
}
|
|
5535
|
-
function keepAlive(cmd) {
|
|
5536
|
-
return `${cmd}; exec "\${SHELL:-/bin/sh}"`;
|
|
5537
|
-
}
|
|
5538
|
-
function shQuote(s) {
|
|
5539
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
5540
|
-
}
|
|
5541
|
-
function engineLaunchLine(engineCmd, init) {
|
|
5542
|
-
const tail = keepAlive(engineCmd);
|
|
5543
|
-
const script = init?.initScript?.trim();
|
|
5544
|
-
if (!script)
|
|
5545
|
-
return tail;
|
|
5546
|
-
const group = ["{", script, "}"].join(`
|
|
5547
|
-
`);
|
|
5548
|
-
if (init?.markerPath) {
|
|
5549
|
-
const marker = shQuote(init.markerPath);
|
|
5550
|
-
const markerDir = shQuote(markerDirOf(init.markerPath));
|
|
5551
|
-
return [
|
|
5552
|
-
`if [ ! -f ${marker} ]; then`,
|
|
5553
|
-
group,
|
|
5554
|
-
`if [ $? -eq 0 ]; then mkdir -p ${markerDir} && : > ${marker}; fi`,
|
|
5555
|
-
"fi",
|
|
5556
|
-
tail
|
|
5557
|
-
].join(`
|
|
5558
|
-
`);
|
|
5559
|
-
}
|
|
5560
|
-
return [group, tail].join(`
|
|
5561
|
-
`);
|
|
5562
|
-
}
|
|
5563
|
-
function markerDirOf(p) {
|
|
5564
|
-
const i = p.lastIndexOf("/");
|
|
5565
|
-
return i <= 0 ? "." : p.slice(0, i);
|
|
5566
|
-
}
|
|
5567
|
-
function fallbackOpsScript(cwd) {
|
|
5568
|
-
return `cd ${shellQuote(cwd)} && while :; do clear; printf "\\033[1m# %s\\033[0m\\n\\n" ${shellQuote(cwd)}; git status --short --branch 2>/dev/null | sed 's/^/ /' || true; printf "\\n"; if command -v lsd >/dev/null 2>&1; then lsd --tree --git -I node_modules -I .git --depth 2 .; elif command -v eza >/dev/null 2>&1; then eza --tree --git -L 2 -I 'node_modules|.git' .; elif command -v tree >/dev/null 2>&1; then tree -L 2 -I 'node_modules|.git'; else ls -la; fi; sleep 2; done`;
|
|
5569
|
-
}
|
|
5570
|
-
function previewWindowCommand(args) {
|
|
5571
|
-
const wt = shellQuote(args.worktree);
|
|
5572
|
-
const file = shellQuote(args.relPath);
|
|
5573
|
-
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
5574
|
-
const fallback = `cd ${wt} && if ! git diff --quiet HEAD -- ${file} 2>/dev/null; then ` + `git diff HEAD -- ${file} | { delta --paging=always 2>/dev/null || less -R; }; ` + `else bat --style=plain --paging=always ${file} 2>/dev/null || \${PAGER:-less} ${file} 2>/dev/null || cat ${file}; fi`;
|
|
5575
|
-
return `${inv} ops --worktree ${wt} --preview ${file} || { ${fallback}; }`;
|
|
5576
|
-
}
|
|
5577
|
-
function updatePageCommand(args) {
|
|
5578
|
-
return `${shellQuoteArgv([...args.cliInvocation, "update-page"])}`;
|
|
5579
|
-
}
|
|
5580
|
-
function tasksPaneCommand(cliInvocation) {
|
|
5581
|
-
return shellQuoteArgv([...cliInvocation, "tasks"]);
|
|
5582
|
-
}
|
|
5583
|
-
function opsPaneCommand(args) {
|
|
5584
|
-
if (args.taskId && args.claudePaneId) {
|
|
5585
|
-
const inv = args.cliInvocation.map(shellQuote).join(" ");
|
|
5586
|
-
const vendorFlag = args.vendor ? ` --vendor ${shellQuote(args.vendor)}` : "";
|
|
5587
|
-
return `KOBE_FILETREE_WATCH=1 ${inv} ops --task-id ${shellQuote(args.taskId)} --worktree ${shellQuote(args.cwd)} ` + `--target-pane ${shellQuote(args.claudePaneId)}${vendorFlag} || { ${fallbackOpsScript(args.cwd)}; }`;
|
|
5588
|
-
}
|
|
5589
|
-
return fallbackOpsScript(args.cwd);
|
|
5590
|
-
}
|
|
5591
|
-
var TASKS_PANE_WIDTH = 32, CLAUDE_PANE_PERCENT = 60, OPS_PANE_PERCENT = 50;
|
|
5592
|
-
|
|
5593
5742
|
// src/tui/panes/terminal/tmux.ts
|
|
5594
5743
|
var exports_tmux = {};
|
|
5595
5744
|
__export(exports_tmux, {
|
|
@@ -5712,6 +5861,7 @@ async function ensureSessionImpl(opts) {
|
|
|
5712
5861
|
const chooseEngineTmuxCommand = `run-shell ${shellQuote(chooseEngineCommand)}`;
|
|
5713
5862
|
await runTmuxSequence([
|
|
5714
5863
|
["set-option", "-g", "status", "on"],
|
|
5864
|
+
["set-window-option", "-g", "aggressive-resize", "on"],
|
|
5715
5865
|
["set-option", "-g", "monitor-activity", "on"],
|
|
5716
5866
|
["set-option", "-g", "visual-activity", "off"],
|
|
5717
5867
|
["set-option", "-g", "window-status-format", CHAT_TAB_STATUS_FORMAT],
|
|
@@ -5811,7 +5961,15 @@ async function healKobePaneVersions(session, cwd, taskId, vendor) {
|
|
|
5811
5961
|
const tasksPane = panes.find((pane) => pane.role === "tasks");
|
|
5812
5962
|
const opsPane = panes.find((pane) => pane.role === "ops");
|
|
5813
5963
|
if (tasksPane && tasksPane.version !== CURRENT_VERSION) {
|
|
5814
|
-
commands.push([
|
|
5964
|
+
commands.push([
|
|
5965
|
+
"respawn-pane",
|
|
5966
|
+
"-k",
|
|
5967
|
+
"-t",
|
|
5968
|
+
tasksPane.paneId,
|
|
5969
|
+
"-c",
|
|
5970
|
+
cwd,
|
|
5971
|
+
keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
|
|
5972
|
+
], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
|
|
5815
5973
|
}
|
|
5816
5974
|
if (opsPane && claudePane && opsPane.version !== CURRENT_VERSION) {
|
|
5817
5975
|
commands.push([
|
|
@@ -5863,9 +6021,17 @@ async function refreshKobeWorkspacePanes(session) {
|
|
|
5863
6021
|
const tasksPane = panes.find((pane) => pane.role === "tasks");
|
|
5864
6022
|
const opsPane = panes.find((pane) => pane.role === "ops");
|
|
5865
6023
|
if (tasksPane) {
|
|
5866
|
-
commands.push([
|
|
6024
|
+
commands.push([
|
|
6025
|
+
"respawn-pane",
|
|
6026
|
+
"-k",
|
|
6027
|
+
"-t",
|
|
6028
|
+
tasksPane.paneId,
|
|
6029
|
+
"-c",
|
|
6030
|
+
cwd,
|
|
6031
|
+
keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
|
|
6032
|
+
], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
|
|
5867
6033
|
}
|
|
5868
|
-
if (opsPane
|
|
6034
|
+
if (opsPane) {
|
|
5869
6035
|
commands.push([
|
|
5870
6036
|
"respawn-pane",
|
|
5871
6037
|
"-k",
|
|
@@ -5876,7 +6042,7 @@ async function refreshKobeWorkspacePanes(session) {
|
|
|
5876
6042
|
keepAlive(envPrefix + opsPaneCommand({
|
|
5877
6043
|
cwd,
|
|
5878
6044
|
taskId,
|
|
5879
|
-
claudePaneId: claudePane,
|
|
6045
|
+
claudePaneId: claudePane ?? null,
|
|
5880
6046
|
cliInvocation: inv,
|
|
5881
6047
|
vendor
|
|
5882
6048
|
}))
|
|
@@ -5920,7 +6086,7 @@ async function buildPanesAround(claudePane, args) {
|
|
|
5920
6086
|
"-P",
|
|
5921
6087
|
"-F",
|
|
5922
6088
|
"tasks=#{pane_id}",
|
|
5923
|
-
keepAlive(envPrefix + tasksPaneCommand(args.inv))
|
|
6089
|
+
keepAlive(envPrefix + tasksPaneCommand(args.inv, { initialTaskId: args.taskId }))
|
|
5924
6090
|
],
|
|
5925
6091
|
[
|
|
5926
6092
|
"split-window",
|
|
@@ -6921,9 +7087,9 @@ var init_theme = __esm(() => {
|
|
|
6921
7087
|
});
|
|
6922
7088
|
|
|
6923
7089
|
// src/core/index.ts
|
|
6924
|
-
import { homedir as
|
|
7090
|
+
import { homedir as homedir8 } from "os";
|
|
6925
7091
|
async function createKobeCore(options = {}) {
|
|
6926
|
-
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
7092
|
+
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir8();
|
|
6927
7093
|
const store = new TaskIndexStore({ homeDir: homeDir2 });
|
|
6928
7094
|
await store.load();
|
|
6929
7095
|
const worktrees = new GitWorktreeManager;
|
|
@@ -7041,10 +7207,10 @@ var init_daemon_cmd = __esm(() => {
|
|
|
7041
7207
|
|
|
7042
7208
|
// src/lib/skill-install.ts
|
|
7043
7209
|
import { existsSync as existsSync4 } from "fs";
|
|
7044
|
-
import { homedir as
|
|
7210
|
+
import { homedir as homedir9 } from "os";
|
|
7045
7211
|
import { join as join7 } from "path";
|
|
7046
7212
|
function kobeSkillPaths(opts = {}) {
|
|
7047
|
-
const home = opts.home ??
|
|
7213
|
+
const home = opts.home ?? homedir9();
|
|
7048
7214
|
const cwd = opts.cwd ?? process.cwd();
|
|
7049
7215
|
return [join7(home, SKILL_REL_PATH), join7(cwd, SKILL_REL_PATH)];
|
|
7050
7216
|
}
|
|
@@ -7073,6 +7239,7 @@ var init_skill_install = __esm(() => {
|
|
|
7073
7239
|
var exports_maintenance = {};
|
|
7074
7240
|
__export(exports_maintenance, {
|
|
7075
7241
|
runResetSubcommand: () => runResetSubcommand,
|
|
7242
|
+
runReloadSubcommand: () => runReloadSubcommand,
|
|
7076
7243
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
7077
7244
|
});
|
|
7078
7245
|
import { existsSync as existsSync5, readFileSync as readFileSync6, statSync } from "fs";
|
|
@@ -7326,6 +7493,54 @@ Stop daemon and kill kobe sessions? [y/N] `);
|
|
|
7326
7493
|
console.log(`
|
|
7327
7494
|
kobe: reset complete. Relaunch kobe to start fresh.`);
|
|
7328
7495
|
}
|
|
7496
|
+
async function runReloadSubcommand(argv = []) {
|
|
7497
|
+
if (argv.includes("--help") || argv.includes("-h") || argv.includes("help")) {
|
|
7498
|
+
process.stdout.write([
|
|
7499
|
+
"Usage: kobe reload",
|
|
7500
|
+
"",
|
|
7501
|
+
"Restart kobe's Tasks + Ops panes in every live session, in place.",
|
|
7502
|
+
"Picks up new kobe code without `kobe reset` \u2014 the engine (claude)",
|
|
7503
|
+
"panes and your running turns are never touched. Takes no options.",
|
|
7504
|
+
""
|
|
7505
|
+
].join(`
|
|
7506
|
+
`));
|
|
7507
|
+
return;
|
|
7508
|
+
}
|
|
7509
|
+
const unknown = argv.find((a) => a.length > 0);
|
|
7510
|
+
if (unknown !== undefined) {
|
|
7511
|
+
process.stderr.write(`kobe reload: unexpected argument "${unknown}"
|
|
7512
|
+
|
|
7513
|
+
Usage: kobe reload (takes no options)
|
|
7514
|
+
`);
|
|
7515
|
+
process.exit(2);
|
|
7516
|
+
}
|
|
7517
|
+
if (!await tmuxAvailable()) {
|
|
7518
|
+
console.log("kobe reload: tmux is not installed \u2014 no panes to reload");
|
|
7519
|
+
return;
|
|
7520
|
+
}
|
|
7521
|
+
const { code, stdout } = await tmuxQuiet(["list-sessions", "-F", "#{session_name}"]);
|
|
7522
|
+
if (code !== 0) {
|
|
7523
|
+
console.log(`kobe reload: no kobe tmux sessions on the \`${KOBE_TMUX_SOCKET}\` socket`);
|
|
7524
|
+
return;
|
|
7525
|
+
}
|
|
7526
|
+
const sessions = stdout.split(`
|
|
7527
|
+
`).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
7528
|
+
if (sessions.length === 0) {
|
|
7529
|
+
console.log(`kobe reload: no kobe tmux sessions on the \`${KOBE_TMUX_SOCKET}\` socket`);
|
|
7530
|
+
return;
|
|
7531
|
+
}
|
|
7532
|
+
const { refreshKobeWorkspacePanes: refreshKobeWorkspacePanes2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
7533
|
+
let reloaded = 0;
|
|
7534
|
+
for (const session of sessions) {
|
|
7535
|
+
try {
|
|
7536
|
+
await refreshKobeWorkspacePanes2(session);
|
|
7537
|
+
reloaded++;
|
|
7538
|
+
} catch (err) {
|
|
7539
|
+
console.error(` failed to reload session "${session}": ${err instanceof Error ? err.message : String(err)}`);
|
|
7540
|
+
}
|
|
7541
|
+
}
|
|
7542
|
+
console.log(`kobe: reloaded Tasks/Ops panes in ${reloaded}/${sessions.length} session(s) \u2014 engine panes untouched`);
|
|
7543
|
+
}
|
|
7329
7544
|
var init_maintenance = __esm(() => {
|
|
7330
7545
|
init_client();
|
|
7331
7546
|
init_lifecycle();
|
|
@@ -8625,6 +8840,7 @@ class RemoteOrchestrator {
|
|
|
8625
8840
|
setConnectionState;
|
|
8626
8841
|
ensureReachable;
|
|
8627
8842
|
role;
|
|
8843
|
+
reconnecting = false;
|
|
8628
8844
|
constructor(client, options = {}) {
|
|
8629
8845
|
this.client = client;
|
|
8630
8846
|
const [tasks, setTasks] = createSignal([]);
|
|
@@ -8642,7 +8858,37 @@ class RemoteOrchestrator {
|
|
|
8642
8858
|
this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
|
|
8643
8859
|
this.role = options.role ?? "pane";
|
|
8644
8860
|
this.client.on("*", (frame) => this.handleEvent(frame.name, frame.payload));
|
|
8645
|
-
this.client.onLifecycle("close", () =>
|
|
8861
|
+
this.client.onLifecycle("close", () => {
|
|
8862
|
+
this.setConnectionState("disconnected");
|
|
8863
|
+
if (this.role === "pane") {
|
|
8864
|
+
logClient("orch", "daemon socket closed \u2014 starting non-spawning reconnect loop");
|
|
8865
|
+
this.reconnectLoop();
|
|
8866
|
+
}
|
|
8867
|
+
});
|
|
8868
|
+
}
|
|
8869
|
+
async reconnectLoop() {
|
|
8870
|
+
if (this.reconnecting)
|
|
8871
|
+
return;
|
|
8872
|
+
this.reconnecting = true;
|
|
8873
|
+
let delayMs = 500;
|
|
8874
|
+
let attempt = 0;
|
|
8875
|
+
while (!this.client.isDisposed) {
|
|
8876
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
8877
|
+
if (this.client.isDisposed)
|
|
8878
|
+
break;
|
|
8879
|
+
attempt++;
|
|
8880
|
+
try {
|
|
8881
|
+
await this.init();
|
|
8882
|
+
logClient("orch", `reconnected and re-subscribed after ${attempt} attempt(s) \u2014 task list re-synced`);
|
|
8883
|
+
this.reconnecting = false;
|
|
8884
|
+
return;
|
|
8885
|
+
} catch (err) {
|
|
8886
|
+
if (attempt === 1 || attempt % 10 === 0)
|
|
8887
|
+
logClientError("orch-reconnect", err);
|
|
8888
|
+
delayMs = Math.min(delayMs * 2, 3000);
|
|
8889
|
+
}
|
|
8890
|
+
}
|
|
8891
|
+
this.reconnecting = false;
|
|
8646
8892
|
}
|
|
8647
8893
|
async init() {
|
|
8648
8894
|
const hello = await this.client.request("hello", {
|
|
@@ -8663,6 +8909,7 @@ class RemoteOrchestrator {
|
|
|
8663
8909
|
this.setTasks(hello.tasks.map(deserializeTask));
|
|
8664
8910
|
await this.client.subscribe({ role: this.role });
|
|
8665
8911
|
this.setConnectionState("online");
|
|
8912
|
+
logClient("orch", `subscribed as ${this.role} (${this.tasksAcc().length} tasks)`);
|
|
8666
8913
|
}
|
|
8667
8914
|
connectionStateSignal() {
|
|
8668
8915
|
return this.connectionStateAcc;
|
|
@@ -8796,6 +9043,7 @@ function deserializeTask(s) {
|
|
|
8796
9043
|
var init_remote_orchestrator = __esm(() => {
|
|
8797
9044
|
init_dev();
|
|
8798
9045
|
init_protocol();
|
|
9046
|
+
init_client_log();
|
|
8799
9047
|
init_daemon_process();
|
|
8800
9048
|
});
|
|
8801
9049
|
|
|
@@ -12476,7 +12724,7 @@ var init_rename_task_dialog = __esm(() => {
|
|
|
12476
12724
|
// src/engine/claude-code-local/binary.ts
|
|
12477
12725
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
12478
12726
|
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
12479
|
-
import { homedir as
|
|
12727
|
+
import { homedir as homedir11 } from "os";
|
|
12480
12728
|
import path7 from "path";
|
|
12481
12729
|
async function findClaudeBinary(deps = defaultDeps4) {
|
|
12482
12730
|
const checked = [];
|
|
@@ -12549,7 +12797,7 @@ var init_binary = __esm(() => {
|
|
|
12549
12797
|
return process.env[name];
|
|
12550
12798
|
},
|
|
12551
12799
|
home() {
|
|
12552
|
-
return
|
|
12800
|
+
return homedir11();
|
|
12553
12801
|
},
|
|
12554
12802
|
which(name) {
|
|
12555
12803
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -12580,7 +12828,7 @@ var init_binary = __esm(() => {
|
|
|
12580
12828
|
// src/engine/codex-local/binary.ts
|
|
12581
12829
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
12582
12830
|
import { existsSync as existsSync8, statSync as statSync4 } from "fs";
|
|
12583
|
-
import { homedir as
|
|
12831
|
+
import { homedir as homedir12 } from "os";
|
|
12584
12832
|
import path8 from "path";
|
|
12585
12833
|
async function findCodexBinary(deps = defaultDeps5) {
|
|
12586
12834
|
const checked = [];
|
|
@@ -12637,7 +12885,7 @@ var init_binary2 = __esm(() => {
|
|
|
12637
12885
|
return process.env[name];
|
|
12638
12886
|
},
|
|
12639
12887
|
home() {
|
|
12640
|
-
return
|
|
12888
|
+
return homedir12();
|
|
12641
12889
|
},
|
|
12642
12890
|
which(name) {
|
|
12643
12891
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -12668,7 +12916,7 @@ var init_binary2 = __esm(() => {
|
|
|
12668
12916
|
// src/engine/copilot-local/binary.ts
|
|
12669
12917
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
12670
12918
|
import { existsSync as existsSync9, statSync as statSync5 } from "fs";
|
|
12671
|
-
import { homedir as
|
|
12919
|
+
import { homedir as homedir13 } from "os";
|
|
12672
12920
|
import path9 from "path";
|
|
12673
12921
|
async function findCopilotBinary(deps = defaultDeps6) {
|
|
12674
12922
|
const checked = [];
|
|
@@ -12749,7 +12997,7 @@ var init_binary3 = __esm(() => {
|
|
|
12749
12997
|
return process.env[name];
|
|
12750
12998
|
},
|
|
12751
12999
|
home() {
|
|
12752
|
-
return
|
|
13000
|
+
return homedir13();
|
|
12753
13001
|
},
|
|
12754
13002
|
which(name) {
|
|
12755
13003
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -12774,7 +13022,7 @@ var init_binary3 = __esm(() => {
|
|
|
12774
13022
|
|
|
12775
13023
|
// src/engine/account-detect.ts
|
|
12776
13024
|
import { readFileSync as readFileSync7, statSync as statSync6 } from "fs";
|
|
12777
|
-
import { homedir as
|
|
13025
|
+
import { homedir as homedir14 } from "os";
|
|
12778
13026
|
import path10 from "path";
|
|
12779
13027
|
function claudeGlobalConfigPath(env, home) {
|
|
12780
13028
|
const override = env("CLAUDE_CONFIG_DIR")?.trim();
|
|
@@ -12992,7 +13240,7 @@ var init_account_detect = __esm(() => {
|
|
|
12992
13240
|
return process.env[name];
|
|
12993
13241
|
},
|
|
12994
13242
|
home() {
|
|
12995
|
-
return
|
|
13243
|
+
return homedir14();
|
|
12996
13244
|
},
|
|
12997
13245
|
findClaudeBinary() {
|
|
12998
13246
|
return findClaudeBinary();
|
|
@@ -14538,7 +14786,7 @@ var init_focus = __esm(() => {
|
|
|
14538
14786
|
|
|
14539
14787
|
// src/tui/context/kv.tsx
|
|
14540
14788
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
14541
|
-
import { dirname as
|
|
14789
|
+
import { dirname as dirname6 } from "path";
|
|
14542
14790
|
function loadInitial() {
|
|
14543
14791
|
const statePath2 = kvStatePath();
|
|
14544
14792
|
try {
|
|
@@ -14566,7 +14814,7 @@ var init_kv = __esm(() => {
|
|
|
14566
14814
|
function writeNow(label) {
|
|
14567
14815
|
const statePath2 = kvStatePath();
|
|
14568
14816
|
try {
|
|
14569
|
-
mkdirSync4(
|
|
14817
|
+
mkdirSync4(dirname6(statePath2), {
|
|
14570
14818
|
recursive: true
|
|
14571
14819
|
});
|
|
14572
14820
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -14621,7 +14869,7 @@ var init_kv = __esm(() => {
|
|
|
14621
14869
|
}
|
|
14622
14870
|
const statePath2 = kvStatePath();
|
|
14623
14871
|
try {
|
|
14624
|
-
mkdirSync4(
|
|
14872
|
+
mkdirSync4(dirname6(statePath2), {
|
|
14625
14873
|
recursive: true
|
|
14626
14874
|
});
|
|
14627
14875
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -15771,6 +16019,8 @@ function Sidebar(props) {
|
|
|
15771
16019
|
createEffect(on(view, () => {
|
|
15772
16020
|
const ids = flatIds();
|
|
15773
16021
|
setCursorIndex(ids.length > 0 ? 0 : -1);
|
|
16022
|
+
}, {
|
|
16023
|
+
defer: true
|
|
15774
16024
|
}));
|
|
15775
16025
|
createEffect(on([searchMode, searchQuery], () => {
|
|
15776
16026
|
if (!searchMode())
|
|
@@ -16019,7 +16269,7 @@ function Sidebar(props) {
|
|
|
16019
16269
|
return readWorktreeChanges(task.worktreePath);
|
|
16020
16270
|
});
|
|
16021
16271
|
const titleText = isMain ? repoBasename(task.repo) : task.title;
|
|
16022
|
-
const loading = () => isLive() || task.status === "in_progress";
|
|
16272
|
+
const loading = () => isLive() || !isMain && task.status === "in_progress";
|
|
16023
16273
|
const subtitleText = createMemo(() => {
|
|
16024
16274
|
if (task.branch.length > 0)
|
|
16025
16275
|
return truncateBranchLabel(task.branch, subtitleBudget());
|
|
@@ -16467,13 +16717,20 @@ function TasksShell(props) {
|
|
|
16467
16717
|
} = themeCtx;
|
|
16468
16718
|
const dialog = useDialog();
|
|
16469
16719
|
const kv = useKV();
|
|
16470
|
-
const [selectedId, setSelectedId] = createSignal(props.tasks()[0]?.id ?? null);
|
|
16720
|
+
const [selectedId, setSelectedId] = createSignal(props.tasks().some((t) => t.id === props.initialTaskId) ? props.initialTaskId : props.tasks()[0]?.id ?? null);
|
|
16471
16721
|
const [updateInfo, setUpdateInfo] = createSignal(null);
|
|
16472
16722
|
const dimensions = useTerminalDimensions();
|
|
16723
|
+
let activeConfirmed = !props.initialTaskId;
|
|
16473
16724
|
createEffect(() => {
|
|
16474
16725
|
const active = props.orch?.activeTaskSignal()();
|
|
16475
|
-
if (active)
|
|
16476
|
-
|
|
16726
|
+
if (!active)
|
|
16727
|
+
return;
|
|
16728
|
+
if (!activeConfirmed) {
|
|
16729
|
+
if (active !== props.initialTaskId)
|
|
16730
|
+
return;
|
|
16731
|
+
activeConfirmed = true;
|
|
16732
|
+
}
|
|
16733
|
+
setSelectedId(active);
|
|
16477
16734
|
});
|
|
16478
16735
|
onMount(() => {
|
|
16479
16736
|
themeCtx.setTransparentBackground(props.transparent);
|
|
@@ -16582,6 +16839,7 @@ function TasksShell(props) {
|
|
|
16582
16839
|
await switchClientBeforeKill(tmuxSessionName(id), nextTask ? tmuxSessionName(nextTask.id) : undefined).catch((err) => {
|
|
16583
16840
|
console.error("[kobe tasks] switch-client failed:", err);
|
|
16584
16841
|
});
|
|
16842
|
+
await props.orch.setActiveTask(nextTask?.id ?? null).catch(() => {});
|
|
16585
16843
|
await killSession(tmuxSessionName(id)).catch((err) => {
|
|
16586
16844
|
console.error("[kobe tasks] kill tmux session failed:", err);
|
|
16587
16845
|
});
|
|
@@ -16627,13 +16885,14 @@ function TasksShell(props) {
|
|
|
16627
16885
|
await switchClientBeforeKill(tmuxSessionName(id), nextTask ? tmuxSessionName(nextTask.id) : undefined).catch((err) => {
|
|
16628
16886
|
console.error("[kobe tasks] switch-client failed:", err);
|
|
16629
16887
|
});
|
|
16888
|
+
await props.orch.setActiveTask(nextTask?.id ?? null).catch(() => {});
|
|
16630
16889
|
await killSession(tmuxSessionName(id)).catch((err) => {
|
|
16631
16890
|
console.error("[kobe tasks] kill tmux session failed:", err);
|
|
16632
16891
|
});
|
|
16633
16892
|
await props.reload();
|
|
16634
16893
|
if (selectedId() === id) {
|
|
16635
16894
|
const remaining = props.tasks();
|
|
16636
|
-
setSelectedId((remaining.find((t) => !t.archived) ?? remaining[0])?.id ?? null);
|
|
16895
|
+
setSelectedId(nextTask?.id ?? (remaining.find((t) => !t.archived) ?? remaining[0])?.id ?? null);
|
|
16637
16896
|
}
|
|
16638
16897
|
}
|
|
16639
16898
|
async function renameTask(id) {
|
|
@@ -16949,7 +17208,8 @@ function ShortcutHints() {
|
|
|
16949
17208
|
return _el$3;
|
|
16950
17209
|
})();
|
|
16951
17210
|
}
|
|
16952
|
-
async function startTasksPane() {
|
|
17211
|
+
async function startTasksPane(opts = {}) {
|
|
17212
|
+
setClientLogContext("tasks");
|
|
16953
17213
|
for (const {
|
|
16954
17214
|
name,
|
|
16955
17215
|
theme
|
|
@@ -16964,21 +17224,28 @@ async function startTasksPane() {
|
|
|
16964
17224
|
const [fileTasks, setFileTasks] = createSignal(store2.list());
|
|
16965
17225
|
let orch = null;
|
|
16966
17226
|
try {
|
|
16967
|
-
const client = await
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
17227
|
+
const client = await connectIfRunning();
|
|
17228
|
+
if (client) {
|
|
17229
|
+
const remote = new RemoteOrchestrator(client);
|
|
17230
|
+
await remote.init();
|
|
17231
|
+
orch = remote;
|
|
17232
|
+
} else {
|
|
17233
|
+
logClient("tasks-boot", "no daemon running \u2014 polling tasks.json (a gui owns daemon lifecycle)");
|
|
17234
|
+
}
|
|
16971
17235
|
} catch (err) {
|
|
16972
|
-
|
|
17236
|
+
logClientError("tasks-boot", err);
|
|
17237
|
+
logClient("tasks-boot", "daemon subscribe failed \u2014 polling tasks.json");
|
|
16973
17238
|
}
|
|
16974
|
-
const tasks = orch ? orch.tasksSignal() : fileTasks;
|
|
17239
|
+
const tasks = () => orch && orch.connectionStateSignal()() === "online" ? orch.tasksSignal()() : fileTasks();
|
|
16975
17240
|
const reload = async () => {
|
|
16976
|
-
if (orch)
|
|
16977
|
-
return;
|
|
16978
17241
|
await store2.load();
|
|
16979
17242
|
setFileTasks(store2.list());
|
|
16980
17243
|
};
|
|
16981
|
-
const timer =
|
|
17244
|
+
const timer = setInterval(() => {
|
|
17245
|
+
if (orch && orch.connectionStateSignal()() === "online")
|
|
17246
|
+
return;
|
|
17247
|
+
reload();
|
|
17248
|
+
}, RELOAD_MS);
|
|
16982
17249
|
await render(() => createComponent2(ThemeProvider, {
|
|
16983
17250
|
mode: "dark",
|
|
16984
17251
|
get theme() {
|
|
@@ -16994,6 +17261,9 @@ async function startTasksPane() {
|
|
|
16994
17261
|
get children() {
|
|
16995
17262
|
return createComponent2(TasksShell, {
|
|
16996
17263
|
tasks,
|
|
17264
|
+
get initialTaskId() {
|
|
17265
|
+
return opts.initialTaskId;
|
|
17266
|
+
},
|
|
16997
17267
|
orch,
|
|
16998
17268
|
get transparent() {
|
|
16999
17269
|
return prefs.transparent;
|
|
@@ -17035,6 +17305,7 @@ var init_host = __esm(() => {
|
|
|
17035
17305
|
init_client2();
|
|
17036
17306
|
init_solid();
|
|
17037
17307
|
init_dev();
|
|
17308
|
+
init_client_log();
|
|
17038
17309
|
init_daemon_process();
|
|
17039
17310
|
init_remote_orchestrator();
|
|
17040
17311
|
init_interactive_command();
|
|
@@ -19135,14 +19406,16 @@ function OpsShell(props) {
|
|
|
19135
19406
|
onMount(() => {
|
|
19136
19407
|
let disposed = false;
|
|
19137
19408
|
async function poll() {
|
|
19138
|
-
|
|
19139
|
-
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
primed
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19409
|
+
try {
|
|
19410
|
+
const mtime = await latestTranscriptMtime(props.vendor, props.worktree);
|
|
19411
|
+
if (disposed)
|
|
19412
|
+
return;
|
|
19413
|
+
if (!primed) {
|
|
19414
|
+
primed = true;
|
|
19415
|
+
setBaseline(mtime);
|
|
19416
|
+
}
|
|
19417
|
+
setLatest(mtime);
|
|
19418
|
+
} catch {}
|
|
19146
19419
|
}
|
|
19147
19420
|
poll();
|
|
19148
19421
|
const timer = setInterval(() => void poll(), ACTIVITY_POLL_MS);
|
|
@@ -19176,31 +19449,35 @@ function OpsShell(props) {
|
|
|
19176
19449
|
await setWindowOption(props.targetPane, CHAT_TAB_STATE_OPTION2, state);
|
|
19177
19450
|
}
|
|
19178
19451
|
async function prime() {
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19452
|
+
try {
|
|
19453
|
+
paneHash = fingerprint(await capturePaneById(props.targetPane, 80));
|
|
19454
|
+
baselineCompletionId = (await detector.latestCompletion(props.worktree))?.id ?? null;
|
|
19455
|
+
await publish(detector.supportsCompletionMarkers() ? "idle" : "unknown");
|
|
19456
|
+
} catch {}
|
|
19182
19457
|
}
|
|
19183
19458
|
async function poll() {
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
paneHash
|
|
19189
|
-
|
|
19459
|
+
try {
|
|
19460
|
+
const nextPaneHash = fingerprint(await capturePaneById(props.targetPane, 80));
|
|
19461
|
+
if (disposed)
|
|
19462
|
+
return;
|
|
19463
|
+
if (nextPaneHash !== paneHash) {
|
|
19464
|
+
paneHash = nextPaneHash;
|
|
19465
|
+
observedPaneActivity = true;
|
|
19466
|
+
stablePolls = 0;
|
|
19467
|
+
await publish(detector.supportsCompletionMarkers() ? "running" : "unknown");
|
|
19468
|
+
} else if (observedPaneActivity) {
|
|
19469
|
+
stablePolls++;
|
|
19470
|
+
}
|
|
19471
|
+
if (!detector.supportsCompletionMarkers() || !observedPaneActivity || stablePolls < STABLE_POLLS_FOR_DONE)
|
|
19472
|
+
return;
|
|
19473
|
+
const marker = await detector.latestCompletion(props.worktree);
|
|
19474
|
+
if (disposed || !marker || marker.id === baselineCompletionId)
|
|
19475
|
+
return;
|
|
19476
|
+
baselineCompletionId = marker.id;
|
|
19477
|
+
observedPaneActivity = false;
|
|
19190
19478
|
stablePolls = 0;
|
|
19191
|
-
await publish(
|
|
19192
|
-
}
|
|
19193
|
-
stablePolls++;
|
|
19194
|
-
}
|
|
19195
|
-
if (!detector.supportsCompletionMarkers() || !observedPaneActivity || stablePolls < STABLE_POLLS_FOR_DONE)
|
|
19196
|
-
return;
|
|
19197
|
-
const marker = await detector.latestCompletion(props.worktree);
|
|
19198
|
-
if (disposed || !marker || marker.id === baselineCompletionId)
|
|
19199
|
-
return;
|
|
19200
|
-
baselineCompletionId = marker.id;
|
|
19201
|
-
observedPaneActivity = false;
|
|
19202
|
-
stablePolls = 0;
|
|
19203
|
-
await publish("done");
|
|
19479
|
+
await publish("done");
|
|
19480
|
+
} catch {}
|
|
19204
19481
|
}
|
|
19205
19482
|
prime();
|
|
19206
19483
|
const timer = setInterval(() => void poll(), TURN_STATUS_POLL_MS);
|
|
@@ -19286,6 +19563,7 @@ function OpsApp(props) {
|
|
|
19286
19563
|
});
|
|
19287
19564
|
}
|
|
19288
19565
|
async function startOpsHost(args) {
|
|
19566
|
+
setClientLogContext("ops");
|
|
19289
19567
|
for (const {
|
|
19290
19568
|
name,
|
|
19291
19569
|
theme
|
|
@@ -19557,6 +19835,7 @@ var init_host5 = __esm(() => {
|
|
|
19557
19835
|
init_solid();
|
|
19558
19836
|
init_solid();
|
|
19559
19837
|
init_invocation();
|
|
19838
|
+
init_client_log();
|
|
19560
19839
|
init_turn_detector();
|
|
19561
19840
|
init_activity();
|
|
19562
19841
|
init_client2();
|
|
@@ -19627,6 +19906,7 @@ async function ensureRepos(orchestrator) {
|
|
|
19627
19906
|
return repos[0] ?? resolve6(process.cwd());
|
|
19628
19907
|
}
|
|
19629
19908
|
async function startDirectTmux() {
|
|
19909
|
+
setClientLogContext("gui");
|
|
19630
19910
|
if (!await tmuxAvailable()) {
|
|
19631
19911
|
console.error("kobe: tmux not found on PATH \u2014 install tmux to use kobe 0.6 direct mode");
|
|
19632
19912
|
process.exitCode = 1;
|
|
@@ -19644,8 +19924,11 @@ async function startDirectTmux() {
|
|
|
19644
19924
|
cwdRepo
|
|
19645
19925
|
});
|
|
19646
19926
|
if (!task) {
|
|
19647
|
-
|
|
19648
|
-
|
|
19927
|
+
const home = await ensureFallbackSession();
|
|
19928
|
+
if (await attachTmux(attachArgv(home)) === null) {
|
|
19929
|
+
console.error("kobe: failed to attach to the kobe-home session");
|
|
19930
|
+
process.exitCode = 1;
|
|
19931
|
+
}
|
|
19649
19932
|
return;
|
|
19650
19933
|
}
|
|
19651
19934
|
const cwd = task.worktreePath || await orchestrator.ensureWorktree(task.id);
|
|
@@ -19693,6 +19976,7 @@ async function startDirectTmux() {
|
|
|
19693
19976
|
}
|
|
19694
19977
|
}
|
|
19695
19978
|
var init_direct = __esm(() => {
|
|
19979
|
+
init_client_log();
|
|
19696
19980
|
init_daemon_process();
|
|
19697
19981
|
init_remote_orchestrator();
|
|
19698
19982
|
init_interactive_command();
|
|
@@ -19700,6 +19984,7 @@ var init_direct = __esm(() => {
|
|
|
19700
19984
|
init_core();
|
|
19701
19985
|
init_repo_init();
|
|
19702
19986
|
init_repos();
|
|
19987
|
+
init_client2();
|
|
19703
19988
|
init_tmux();
|
|
19704
19989
|
});
|
|
19705
19990
|
|
|
@@ -21335,7 +21620,7 @@ var exports_app = {};
|
|
|
21335
21620
|
__export(exports_app, {
|
|
21336
21621
|
startApp: () => startApp
|
|
21337
21622
|
});
|
|
21338
|
-
import { homedir as
|
|
21623
|
+
import { homedir as homedir15 } from "os";
|
|
21339
21624
|
function Shell(props) {
|
|
21340
21625
|
const themeCtx = useTheme();
|
|
21341
21626
|
const {
|
|
@@ -21843,7 +22128,7 @@ async function startApp() {
|
|
|
21843
22128
|
} of loadUserThemes()) {
|
|
21844
22129
|
addTheme2(name, theme);
|
|
21845
22130
|
}
|
|
21846
|
-
const homeDir2 = process.env.KOBE_HOME_DIR ??
|
|
22131
|
+
const homeDir2 = process.env.KOBE_HOME_DIR ?? homedir15();
|
|
21847
22132
|
let orchestrator;
|
|
21848
22133
|
if (process.env.KOBE_NO_DAEMON === "1") {
|
|
21849
22134
|
const store2 = new TaskIndexStore({
|
|
@@ -21978,6 +22263,7 @@ function topLevelUsage() {
|
|
|
21978
22263
|
" update [target] Self-update kobe",
|
|
21979
22264
|
" doctor Diagnose daemon / tmux / state (read-only)",
|
|
21980
22265
|
" reset [--hard] Recover a wedged install",
|
|
22266
|
+
" reload Restart Tasks/Ops panes in place (engine untouched)",
|
|
21981
22267
|
" kill-sessions Tear down kobe's tmux server (dev reset)",
|
|
21982
22268
|
"",
|
|
21983
22269
|
"Options:",
|
|
@@ -22168,6 +22454,9 @@ function parseOpsFlags(argv) {
|
|
|
22168
22454
|
} else if (flag === "--repo") {
|
|
22169
22455
|
flags.repo = value;
|
|
22170
22456
|
i++;
|
|
22457
|
+
} else if (flag === "--initial-task-id") {
|
|
22458
|
+
flags.initialTaskId = value;
|
|
22459
|
+
i++;
|
|
22171
22460
|
}
|
|
22172
22461
|
}
|
|
22173
22462
|
return flags;
|
|
@@ -22227,6 +22516,11 @@ async function main() {
|
|
|
22227
22516
|
await runResetSubcommand2(rest);
|
|
22228
22517
|
return;
|
|
22229
22518
|
}
|
|
22519
|
+
if (subcommand === "reload") {
|
|
22520
|
+
const { runReloadSubcommand: runReloadSubcommand2 } = await Promise.resolve().then(() => (init_maintenance(), exports_maintenance));
|
|
22521
|
+
await runReloadSubcommand2(rest);
|
|
22522
|
+
return;
|
|
22523
|
+
}
|
|
22230
22524
|
if (subcommand === "new-chattab") {
|
|
22231
22525
|
const flags = parseOpsFlags(rest);
|
|
22232
22526
|
const session = flags.session;
|
|
@@ -22264,8 +22558,9 @@ async function main() {
|
|
|
22264
22558
|
return;
|
|
22265
22559
|
}
|
|
22266
22560
|
if (subcommand === "tasks") {
|
|
22561
|
+
const flags = parseOpsFlags(rest);
|
|
22267
22562
|
const { startTasksPane: startTasksPane2 } = await Promise.resolve().then(() => (init_host(), exports_host));
|
|
22268
|
-
await startTasksPane2();
|
|
22563
|
+
await startTasksPane2({ initialTaskId: flags.initialTaskId });
|
|
22269
22564
|
return;
|
|
22270
22565
|
}
|
|
22271
22566
|
if (subcommand === "settings") {
|
package/package.json
CHANGED