@sma1lboy/kobe 0.7.1 → 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 +270 -84
- 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 {
|
|
@@ -4409,7 +4525,7 @@ __export(exports_client, {
|
|
|
4409
4525
|
CLAUDE_ROLE_OPTION: () => CLAUDE_ROLE_OPTION,
|
|
4410
4526
|
CHAT_TAB_SESSION_ID_OPTION: () => CHAT_TAB_SESSION_ID_OPTION
|
|
4411
4527
|
});
|
|
4412
|
-
import { homedir as
|
|
4528
|
+
import { homedir as homedir7 } from "os";
|
|
4413
4529
|
function tmuxArgs(...args) {
|
|
4414
4530
|
return ["tmux", "-L", KOBE_TMUX_SOCKET, ...args];
|
|
4415
4531
|
}
|
|
@@ -4670,7 +4786,7 @@ var KOBE_TMUX_SOCKET, SAFE_SPAWN_CWD, PANE_ROLE_OPTION = "@kobe_role", CLAUDE_RO
|
|
|
4670
4786
|
var init_client2 = __esm(() => {
|
|
4671
4787
|
init_invocation();
|
|
4672
4788
|
KOBE_TMUX_SOCKET = process.env.KOBE_TMUX_SOCKET?.trim() || "kobe";
|
|
4673
|
-
SAFE_SPAWN_CWD =
|
|
4789
|
+
SAFE_SPAWN_CWD = homedir7() || "/";
|
|
4674
4790
|
CLAUDE_ROLE_OPTION = PANE_ROLE_OPTION;
|
|
4675
4791
|
});
|
|
4676
4792
|
|
|
@@ -4768,6 +4884,13 @@ function formatDaemonError(subsystem, err, now = new Date) {
|
|
|
4768
4884
|
function logDaemonError(subsystem, err) {
|
|
4769
4885
|
process.stderr.write(formatDaemonError(subsystem, err));
|
|
4770
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
|
+
}
|
|
4771
4894
|
function installDaemonCrashHandlers(log = (l) => process.stderr.write(l)) {
|
|
4772
4895
|
if (onRejection || onException)
|
|
4773
4896
|
return;
|
|
@@ -4843,54 +4966,10 @@ class DaemonEventBus {
|
|
|
4843
4966
|
}
|
|
4844
4967
|
}
|
|
4845
4968
|
|
|
4846
|
-
// src/daemon/paths.ts
|
|
4847
|
-
import { createHash as createHash2 } from "crypto";
|
|
4848
|
-
import { homedir as homedir7, tmpdir } from "os";
|
|
4849
|
-
import { join as join3 } from "path";
|
|
4850
|
-
function shortHomeTag(homeDir2) {
|
|
4851
|
-
return createHash2("sha1").update(homeDir2).digest("hex").slice(0, 8);
|
|
4852
|
-
}
|
|
4853
|
-
function fitSocketPath(naturalPath, homeDir2, role, pidTag) {
|
|
4854
|
-
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
4855
|
-
return naturalPath;
|
|
4856
|
-
const tag = shortHomeTag(homeDir2);
|
|
4857
|
-
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
4858
|
-
const fallback = join3(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
4859
|
-
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
4860
|
-
return fallback;
|
|
4861
|
-
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
4862
|
-
}
|
|
4863
|
-
function defaultDaemonSocketPath(homeDir2) {
|
|
4864
|
-
const override = process.env.KOBE_DAEMON_SOCKET_PATH;
|
|
4865
|
-
if (override && override.length > 0)
|
|
4866
|
-
return override;
|
|
4867
|
-
const explicit = homeDir2 ?? process.env.KOBE_HOME_DIR;
|
|
4868
|
-
if (explicit && explicit.length > 0) {
|
|
4869
|
-
return fitSocketPath(join3(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
4870
|
-
}
|
|
4871
|
-
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
4872
|
-
if (runtimeDir && runtimeDir.length > 0) {
|
|
4873
|
-
return fitSocketPath(join3(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
4874
|
-
}
|
|
4875
|
-
const home = homedir7();
|
|
4876
|
-
return fitSocketPath(join3(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
4877
|
-
}
|
|
4878
|
-
function defaultDaemonPidPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
|
|
4879
|
-
const override = process.env.KOBE_DAEMON_PID_PATH;
|
|
4880
|
-
if (override && override.length > 0)
|
|
4881
|
-
return override;
|
|
4882
|
-
return join3(homeDir2, ".kobe", "daemon.pid");
|
|
4883
|
-
}
|
|
4884
|
-
function defaultDaemonLogPath(homeDir2 = process.env.KOBE_HOME_DIR ?? homedir7()) {
|
|
4885
|
-
return join3(homeDir2, ".kobe", "daemon.log");
|
|
4886
|
-
}
|
|
4887
|
-
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
4888
|
-
var init_paths2 = () => {};
|
|
4889
|
-
|
|
4890
4969
|
// src/daemon/server.ts
|
|
4891
|
-
import { mkdir as
|
|
4970
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
|
|
4892
4971
|
import { createServer } from "net";
|
|
4893
|
-
import { dirname as
|
|
4972
|
+
import { dirname as dirname4 } from "path";
|
|
4894
4973
|
function resolveIdleGraceMs() {
|
|
4895
4974
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
4896
4975
|
if (raw === undefined)
|
|
@@ -4924,10 +5003,12 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4924
5003
|
if (stopping || guiCount() > 0)
|
|
4925
5004
|
return;
|
|
4926
5005
|
cancelIdleTimer();
|
|
5006
|
+
logDaemonInfo("idle", `last gui gone \u2014 arming ${idleGraceMs}ms idle-stop grace`);
|
|
4927
5007
|
idleTimer = setTimeout(() => {
|
|
4928
5008
|
idleTimer = null;
|
|
4929
5009
|
if (stopping || guiCount() > 0)
|
|
4930
5010
|
return;
|
|
5011
|
+
logDaemonInfo("idle", "grace elapsed with no gui \u2014 self-stopping");
|
|
4931
5012
|
stopSoon().catch((err) => logDaemonError("daemon-idle-shutdown", err));
|
|
4932
5013
|
}, idleGraceMs);
|
|
4933
5014
|
idleTimer.unref?.();
|
|
@@ -4936,8 +5017,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4936
5017
|
bus.onPublish((event) => {
|
|
4937
5018
|
broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
|
|
4938
5019
|
});
|
|
4939
|
-
await
|
|
4940
|
-
await
|
|
5020
|
+
await mkdir4(dirname4(socketPath), { recursive: true });
|
|
5021
|
+
await mkdir4(dirname4(pidPath), { recursive: true });
|
|
4941
5022
|
await unlink4(socketPath).catch(() => {});
|
|
4942
5023
|
const server = createServer((socket) => {
|
|
4943
5024
|
const client = {
|
|
@@ -4956,6 +5037,9 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4956
5037
|
socket.on("error", () => {});
|
|
4957
5038
|
socket.on("close", () => {
|
|
4958
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
|
+
}
|
|
4959
5043
|
if (client.holdsLifetime)
|
|
4960
5044
|
maybeArmIdleShutdown();
|
|
4961
5045
|
});
|
|
@@ -5148,6 +5232,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5148
5232
|
client.holdsLifetime = role === "gui";
|
|
5149
5233
|
if (client.holdsLifetime)
|
|
5150
5234
|
cancelIdleTimer();
|
|
5235
|
+
logDaemonInfo("conn", `client #${client.id} subscribed as ${role} \u2014 ${clients.size} client(s), ${guiCount()} gui`);
|
|
5151
5236
|
for (const event of bus.snapshot()) {
|
|
5152
5237
|
writeFrame(client, { type: "event", name: event.channel, payload: event.payload });
|
|
5153
5238
|
}
|
|
@@ -5327,13 +5412,13 @@ __export(exports_daemon_process, {
|
|
|
5327
5412
|
});
|
|
5328
5413
|
import { spawn } from "child_process";
|
|
5329
5414
|
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
5330
|
-
import { dirname as
|
|
5415
|
+
import { dirname as dirname5, resolve as resolve2 } from "path";
|
|
5331
5416
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5332
5417
|
function spawnDetachedDaemon(command, args, env, logPath) {
|
|
5333
5418
|
let stdio = "ignore";
|
|
5334
5419
|
let logFd;
|
|
5335
5420
|
try {
|
|
5336
|
-
mkdirSync2(
|
|
5421
|
+
mkdirSync2(dirname5(logPath), { recursive: true });
|
|
5337
5422
|
logFd = openSync(logPath, "a");
|
|
5338
5423
|
stdio = ["ignore", logFd, logFd];
|
|
5339
5424
|
} catch {
|
|
@@ -5400,7 +5485,7 @@ function resolveKobeSpawn(subcommand) {
|
|
|
5400
5485
|
if (here.startsWith("/$bunfs") || here.startsWith("B:\\~BUN")) {
|
|
5401
5486
|
return [process.execPath, ...subcommand];
|
|
5402
5487
|
}
|
|
5403
|
-
const dir =
|
|
5488
|
+
const dir = dirname5(here);
|
|
5404
5489
|
const sourceEntry = resolve2(dir, "../cli/index.ts");
|
|
5405
5490
|
if (existsSync(sourceEntry))
|
|
5406
5491
|
return [process.execPath, sourceEntry, ...subcommand];
|
|
@@ -5946,7 +6031,7 @@ async function refreshKobeWorkspacePanes(session) {
|
|
|
5946
6031
|
keepAlive(envPrefix + tasksPaneCommand(inv, { initialTaskId: taskId }))
|
|
5947
6032
|
], ["set-option", "-p", "-t", tasksPane.paneId, "@kobe_role", "tasks"], ["set-option", "-p", "-t", tasksPane.paneId, PANE_VERSION_OPTION, CURRENT_VERSION]);
|
|
5948
6033
|
}
|
|
5949
|
-
if (opsPane
|
|
6034
|
+
if (opsPane) {
|
|
5950
6035
|
commands.push([
|
|
5951
6036
|
"respawn-pane",
|
|
5952
6037
|
"-k",
|
|
@@ -5957,7 +6042,7 @@ async function refreshKobeWorkspacePanes(session) {
|
|
|
5957
6042
|
keepAlive(envPrefix + opsPaneCommand({
|
|
5958
6043
|
cwd,
|
|
5959
6044
|
taskId,
|
|
5960
|
-
claudePaneId: claudePane,
|
|
6045
|
+
claudePaneId: claudePane ?? null,
|
|
5961
6046
|
cliInvocation: inv,
|
|
5962
6047
|
vendor
|
|
5963
6048
|
}))
|
|
@@ -7154,6 +7239,7 @@ var init_skill_install = __esm(() => {
|
|
|
7154
7239
|
var exports_maintenance = {};
|
|
7155
7240
|
__export(exports_maintenance, {
|
|
7156
7241
|
runResetSubcommand: () => runResetSubcommand,
|
|
7242
|
+
runReloadSubcommand: () => runReloadSubcommand,
|
|
7157
7243
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
7158
7244
|
});
|
|
7159
7245
|
import { existsSync as existsSync5, readFileSync as readFileSync6, statSync } from "fs";
|
|
@@ -7407,6 +7493,54 @@ Stop daemon and kill kobe sessions? [y/N] `);
|
|
|
7407
7493
|
console.log(`
|
|
7408
7494
|
kobe: reset complete. Relaunch kobe to start fresh.`);
|
|
7409
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
|
+
}
|
|
7410
7544
|
var init_maintenance = __esm(() => {
|
|
7411
7545
|
init_client();
|
|
7412
7546
|
init_lifecycle();
|
|
@@ -8706,6 +8840,7 @@ class RemoteOrchestrator {
|
|
|
8706
8840
|
setConnectionState;
|
|
8707
8841
|
ensureReachable;
|
|
8708
8842
|
role;
|
|
8843
|
+
reconnecting = false;
|
|
8709
8844
|
constructor(client, options = {}) {
|
|
8710
8845
|
this.client = client;
|
|
8711
8846
|
const [tasks, setTasks] = createSignal([]);
|
|
@@ -8723,7 +8858,37 @@ class RemoteOrchestrator {
|
|
|
8723
8858
|
this.ensureReachable = options.ensureReachable ?? ensureDaemonReachable;
|
|
8724
8859
|
this.role = options.role ?? "pane";
|
|
8725
8860
|
this.client.on("*", (frame) => this.handleEvent(frame.name, frame.payload));
|
|
8726
|
-
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;
|
|
8727
8892
|
}
|
|
8728
8893
|
async init() {
|
|
8729
8894
|
const hello = await this.client.request("hello", {
|
|
@@ -8744,6 +8909,7 @@ class RemoteOrchestrator {
|
|
|
8744
8909
|
this.setTasks(hello.tasks.map(deserializeTask));
|
|
8745
8910
|
await this.client.subscribe({ role: this.role });
|
|
8746
8911
|
this.setConnectionState("online");
|
|
8912
|
+
logClient("orch", `subscribed as ${this.role} (${this.tasksAcc().length} tasks)`);
|
|
8747
8913
|
}
|
|
8748
8914
|
connectionStateSignal() {
|
|
8749
8915
|
return this.connectionStateAcc;
|
|
@@ -8877,6 +9043,7 @@ function deserializeTask(s) {
|
|
|
8877
9043
|
var init_remote_orchestrator = __esm(() => {
|
|
8878
9044
|
init_dev();
|
|
8879
9045
|
init_protocol();
|
|
9046
|
+
init_client_log();
|
|
8880
9047
|
init_daemon_process();
|
|
8881
9048
|
});
|
|
8882
9049
|
|
|
@@ -14619,7 +14786,7 @@ var init_focus = __esm(() => {
|
|
|
14619
14786
|
|
|
14620
14787
|
// src/tui/context/kv.tsx
|
|
14621
14788
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
14622
|
-
import { dirname as
|
|
14789
|
+
import { dirname as dirname6 } from "path";
|
|
14623
14790
|
function loadInitial() {
|
|
14624
14791
|
const statePath2 = kvStatePath();
|
|
14625
14792
|
try {
|
|
@@ -14647,7 +14814,7 @@ var init_kv = __esm(() => {
|
|
|
14647
14814
|
function writeNow(label) {
|
|
14648
14815
|
const statePath2 = kvStatePath();
|
|
14649
14816
|
try {
|
|
14650
|
-
mkdirSync4(
|
|
14817
|
+
mkdirSync4(dirname6(statePath2), {
|
|
14651
14818
|
recursive: true
|
|
14652
14819
|
});
|
|
14653
14820
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -14702,7 +14869,7 @@ var init_kv = __esm(() => {
|
|
|
14702
14869
|
}
|
|
14703
14870
|
const statePath2 = kvStatePath();
|
|
14704
14871
|
try {
|
|
14705
|
-
mkdirSync4(
|
|
14872
|
+
mkdirSync4(dirname6(statePath2), {
|
|
14706
14873
|
recursive: true
|
|
14707
14874
|
});
|
|
14708
14875
|
const tmp = `${statePath2}.tmp`;
|
|
@@ -16102,7 +16269,7 @@ function Sidebar(props) {
|
|
|
16102
16269
|
return readWorktreeChanges(task.worktreePath);
|
|
16103
16270
|
});
|
|
16104
16271
|
const titleText = isMain ? repoBasename(task.repo) : task.title;
|
|
16105
|
-
const loading = () => isLive() || task.status === "in_progress";
|
|
16272
|
+
const loading = () => isLive() || !isMain && task.status === "in_progress";
|
|
16106
16273
|
const subtitleText = createMemo(() => {
|
|
16107
16274
|
if (task.branch.length > 0)
|
|
16108
16275
|
return truncateBranchLabel(task.branch, subtitleBudget());
|
|
@@ -17042,6 +17209,7 @@ function ShortcutHints() {
|
|
|
17042
17209
|
})();
|
|
17043
17210
|
}
|
|
17044
17211
|
async function startTasksPane(opts = {}) {
|
|
17212
|
+
setClientLogContext("tasks");
|
|
17045
17213
|
for (const {
|
|
17046
17214
|
name,
|
|
17047
17215
|
theme
|
|
@@ -17056,21 +17224,28 @@ async function startTasksPane(opts = {}) {
|
|
|
17056
17224
|
const [fileTasks, setFileTasks] = createSignal(store2.list());
|
|
17057
17225
|
let orch = null;
|
|
17058
17226
|
try {
|
|
17059
|
-
const client = await
|
|
17060
|
-
|
|
17061
|
-
|
|
17062
|
-
|
|
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
|
+
}
|
|
17063
17235
|
} catch (err) {
|
|
17064
|
-
|
|
17236
|
+
logClientError("tasks-boot", err);
|
|
17237
|
+
logClient("tasks-boot", "daemon subscribe failed \u2014 polling tasks.json");
|
|
17065
17238
|
}
|
|
17066
|
-
const tasks = orch ? orch.tasksSignal() : fileTasks;
|
|
17239
|
+
const tasks = () => orch && orch.connectionStateSignal()() === "online" ? orch.tasksSignal()() : fileTasks();
|
|
17067
17240
|
const reload = async () => {
|
|
17068
|
-
if (orch)
|
|
17069
|
-
return;
|
|
17070
17241
|
await store2.load();
|
|
17071
17242
|
setFileTasks(store2.list());
|
|
17072
17243
|
};
|
|
17073
|
-
const timer =
|
|
17244
|
+
const timer = setInterval(() => {
|
|
17245
|
+
if (orch && orch.connectionStateSignal()() === "online")
|
|
17246
|
+
return;
|
|
17247
|
+
reload();
|
|
17248
|
+
}, RELOAD_MS);
|
|
17074
17249
|
await render(() => createComponent2(ThemeProvider, {
|
|
17075
17250
|
mode: "dark",
|
|
17076
17251
|
get theme() {
|
|
@@ -17130,6 +17305,7 @@ var init_host = __esm(() => {
|
|
|
17130
17305
|
init_client2();
|
|
17131
17306
|
init_solid();
|
|
17132
17307
|
init_dev();
|
|
17308
|
+
init_client_log();
|
|
17133
17309
|
init_daemon_process();
|
|
17134
17310
|
init_remote_orchestrator();
|
|
17135
17311
|
init_interactive_command();
|
|
@@ -19387,6 +19563,7 @@ function OpsApp(props) {
|
|
|
19387
19563
|
});
|
|
19388
19564
|
}
|
|
19389
19565
|
async function startOpsHost(args) {
|
|
19566
|
+
setClientLogContext("ops");
|
|
19390
19567
|
for (const {
|
|
19391
19568
|
name,
|
|
19392
19569
|
theme
|
|
@@ -19658,6 +19835,7 @@ var init_host5 = __esm(() => {
|
|
|
19658
19835
|
init_solid();
|
|
19659
19836
|
init_solid();
|
|
19660
19837
|
init_invocation();
|
|
19838
|
+
init_client_log();
|
|
19661
19839
|
init_turn_detector();
|
|
19662
19840
|
init_activity();
|
|
19663
19841
|
init_client2();
|
|
@@ -19728,6 +19906,7 @@ async function ensureRepos(orchestrator) {
|
|
|
19728
19906
|
return repos[0] ?? resolve6(process.cwd());
|
|
19729
19907
|
}
|
|
19730
19908
|
async function startDirectTmux() {
|
|
19909
|
+
setClientLogContext("gui");
|
|
19731
19910
|
if (!await tmuxAvailable()) {
|
|
19732
19911
|
console.error("kobe: tmux not found on PATH \u2014 install tmux to use kobe 0.6 direct mode");
|
|
19733
19912
|
process.exitCode = 1;
|
|
@@ -19797,6 +19976,7 @@ async function startDirectTmux() {
|
|
|
19797
19976
|
}
|
|
19798
19977
|
}
|
|
19799
19978
|
var init_direct = __esm(() => {
|
|
19979
|
+
init_client_log();
|
|
19800
19980
|
init_daemon_process();
|
|
19801
19981
|
init_remote_orchestrator();
|
|
19802
19982
|
init_interactive_command();
|
|
@@ -22083,6 +22263,7 @@ function topLevelUsage() {
|
|
|
22083
22263
|
" update [target] Self-update kobe",
|
|
22084
22264
|
" doctor Diagnose daemon / tmux / state (read-only)",
|
|
22085
22265
|
" reset [--hard] Recover a wedged install",
|
|
22266
|
+
" reload Restart Tasks/Ops panes in place (engine untouched)",
|
|
22086
22267
|
" kill-sessions Tear down kobe's tmux server (dev reset)",
|
|
22087
22268
|
"",
|
|
22088
22269
|
"Options:",
|
|
@@ -22335,6 +22516,11 @@ async function main() {
|
|
|
22335
22516
|
await runResetSubcommand2(rest);
|
|
22336
22517
|
return;
|
|
22337
22518
|
}
|
|
22519
|
+
if (subcommand === "reload") {
|
|
22520
|
+
const { runReloadSubcommand: runReloadSubcommand2 } = await Promise.resolve().then(() => (init_maintenance(), exports_maintenance));
|
|
22521
|
+
await runReloadSubcommand2(rest);
|
|
22522
|
+
return;
|
|
22523
|
+
}
|
|
22338
22524
|
if (subcommand === "new-chattab") {
|
|
22339
22525
|
const flags = parseOpsFlags(rest);
|
|
22340
22526
|
const session = flags.session;
|
package/package.json
CHANGED