@sma1lboy/kobe 0.7.29 → 0.7.31
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 +437 -167
- package/dist/web-ui/assets/AppShell-79UyF3Dn.js +8 -0
- package/dist/web-ui/assets/{ChatTerminal-Dzrtw3pW.js → ChatTerminal-DyPmK4ll.js} +2 -2
- package/dist/web-ui/assets/{IssuePeek-C209KHal.js → IssuePeek-z77QfvyZ.js} +61 -60
- package/dist/web-ui/assets/ViewToggle-DAvvHlkd.js +1 -0
- package/dist/web-ui/assets/board-DEmq9YrO.js +1 -0
- package/dist/web-ui/assets/index-B9eBRSoV.css +2 -0
- package/dist/web-ui/assets/{index-DfvJr9CT.js → index-Bp9dfUdH.js} +4 -4
- package/dist/web-ui/assets/issues-Fo-1cGh2.js +1 -0
- package/dist/web-ui/assets/routes-K4B3f4it.js +1 -0
- package/dist/web-ui/assets/{tabs-may5sub7.js → tabs-CmaDBSUQ.js} +1 -1
- package/dist/web-ui/assets/task._taskId-xBuyIrYG.js +1 -0
- package/dist/web-ui/assets/{useNavigate-1MDuT9d4.js → useNavigate-CXvSccc8.js} +1 -1
- package/dist/web-ui/assets/vendor-BOFoJ49C.js +1 -0
- package/dist/web-ui/index.html +4 -4
- package/dist/web-ui/pty-server.mjs +74 -18
- package/package.json +1 -1
- package/dist/web-ui/assets/AppShell-D7vN32jP.js +0 -8
- package/dist/web-ui/assets/ViewToggle-eyNy-OIE.js +0 -1
- package/dist/web-ui/assets/board-Bf9WC1dC.js +0 -1
- package/dist/web-ui/assets/engines-Dg-YdbV1.js +0 -1
- package/dist/web-ui/assets/index-gdVt3uX3.css +0 -2
- package/dist/web-ui/assets/issues-CsoGRcf8.js +0 -1
- package/dist/web-ui/assets/routes-BdRb63td.js +0 -1
- package/dist/web-ui/assets/task._taskId-9HaGMHxf.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -90,7 +90,7 @@ var init_package = __esm(() => {
|
|
|
90
90
|
package_default = {
|
|
91
91
|
$schema: "https://json.schemastore.org/package.json",
|
|
92
92
|
name: "@sma1lboy/kobe",
|
|
93
|
-
version: "0.7.
|
|
93
|
+
version: "0.7.31",
|
|
94
94
|
description: "TUI orchestrator for Claude Code (codename)",
|
|
95
95
|
type: "module",
|
|
96
96
|
packageManager: "bun@1.3.13",
|
|
@@ -395,6 +395,9 @@ import { mkdir, readFile as readFileAsync, readdir as readdirAsync } from "fs/pr
|
|
|
395
395
|
function shQuote(s) {
|
|
396
396
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
397
397
|
}
|
|
398
|
+
function shToken(s) {
|
|
399
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(s) ? s : shQuote(s);
|
|
400
|
+
}
|
|
398
401
|
function shJoin(argv) {
|
|
399
402
|
return argv.map(shQuote).join(" ");
|
|
400
403
|
}
|
|
@@ -545,7 +548,8 @@ class RemoteExecHost {
|
|
|
545
548
|
}
|
|
546
549
|
wrapCommand(command, opts = {}) {
|
|
547
550
|
const remote = opts.cwd ? `cd ${shQuote(opts.cwd)} && ${command}` : command;
|
|
548
|
-
|
|
551
|
+
const connect = sshConnectArgs(this.spec, { tty: opts.tty }).map(shToken).join(" ");
|
|
552
|
+
return `${connect} ${shQuote(remote)}`;
|
|
549
553
|
}
|
|
550
554
|
}
|
|
551
555
|
var defaultSpawner = (argv, env) => {
|
|
@@ -1025,7 +1029,20 @@ var init_add_remote = __esm(() => {
|
|
|
1025
1029
|
});
|
|
1026
1030
|
|
|
1027
1031
|
// src/types/task.ts
|
|
1028
|
-
|
|
1032
|
+
function isTaskStatus(value) {
|
|
1033
|
+
return typeof value === "string" && TASK_STATUSES.includes(value);
|
|
1034
|
+
}
|
|
1035
|
+
var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude", TASK_STATUSES;
|
|
1036
|
+
var init_task = __esm(() => {
|
|
1037
|
+
TASK_STATUSES = [
|
|
1038
|
+
"backlog",
|
|
1039
|
+
"in_progress",
|
|
1040
|
+
"in_review",
|
|
1041
|
+
"done",
|
|
1042
|
+
"canceled",
|
|
1043
|
+
"error"
|
|
1044
|
+
];
|
|
1045
|
+
});
|
|
1029
1046
|
|
|
1030
1047
|
// src/orchestrator/index/ulid.ts
|
|
1031
1048
|
function encodeTime(now, len) {
|
|
@@ -1363,7 +1380,7 @@ function coerceTask(value) {
|
|
|
1363
1380
|
if (typeof v.id !== "string" || typeof v.title !== "string" || typeof v.repo !== "string" || typeof v.branch !== "string" || typeof v.worktreePath !== "string" || typeof v.status !== "string" || typeof v.createdAt !== "string" || typeof v.updatedAt !== "string") {
|
|
1364
1381
|
return null;
|
|
1365
1382
|
}
|
|
1366
|
-
if (!
|
|
1383
|
+
if (!isTaskStatus2(v.status))
|
|
1367
1384
|
return null;
|
|
1368
1385
|
const archived = typeof v.archived === "boolean" ? v.archived : false;
|
|
1369
1386
|
const kind = v.kind === "main" ? "main" : "task";
|
|
@@ -1420,11 +1437,12 @@ function isPRCheckState(v) {
|
|
|
1420
1437
|
function isVendorId(v) {
|
|
1421
1438
|
return v === "claude" || v === "codex";
|
|
1422
1439
|
}
|
|
1423
|
-
function
|
|
1440
|
+
function isTaskStatus2(s) {
|
|
1424
1441
|
return s === "backlog" || s === "in_progress" || s === "in_review" || s === "done" || s === "canceled" || s === "error";
|
|
1425
1442
|
}
|
|
1426
1443
|
var CURRENT_VERSION2 = 3;
|
|
1427
1444
|
var init_store2 = __esm(() => {
|
|
1445
|
+
init_task();
|
|
1428
1446
|
init_ulid();
|
|
1429
1447
|
});
|
|
1430
1448
|
|
|
@@ -3121,9 +3139,10 @@ function deriveTitleFromPrompt(prompt) {
|
|
|
3121
3139
|
const collapsed = prompt.replace(/\s+/g, " ").trim();
|
|
3122
3140
|
if (collapsed.length === 0)
|
|
3123
3141
|
return "";
|
|
3124
|
-
|
|
3142
|
+
const points = [...collapsed];
|
|
3143
|
+
if (points.length <= TITLE_CHAR_CAP)
|
|
3125
3144
|
return collapsed;
|
|
3126
|
-
return `${
|
|
3145
|
+
return `${points.slice(0, TITLE_CHAR_CAP).join("")}\u2026`;
|
|
3127
3146
|
}
|
|
3128
3147
|
function autoBranch(title, taskId) {
|
|
3129
3148
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
@@ -3689,11 +3708,6 @@ class Orchestrator {
|
|
|
3689
3708
|
return this.tasksAcc;
|
|
3690
3709
|
}
|
|
3691
3710
|
subscribeTasks(listener) {
|
|
3692
|
-
try {
|
|
3693
|
-
listener(this.store.list());
|
|
3694
|
-
} catch (err) {
|
|
3695
|
-
console.error("[kobe Orchestrator] task listener threw on subscribe:", err);
|
|
3696
|
-
}
|
|
3697
3711
|
return this.store.subscribe(listener);
|
|
3698
3712
|
}
|
|
3699
3713
|
dispose() {
|
|
@@ -3982,6 +3996,7 @@ var PLACEHOLDER_TASK_TITLE = "(new task)";
|
|
|
3982
3996
|
var init_core = __esm(() => {
|
|
3983
3997
|
init_dev();
|
|
3984
3998
|
init_repos();
|
|
3999
|
+
init_task();
|
|
3985
4000
|
init_errors();
|
|
3986
4001
|
init_slug_allocator();
|
|
3987
4002
|
});
|
|
@@ -4154,6 +4169,7 @@ var init_client_log = __esm(() => {
|
|
|
4154
4169
|
|
|
4155
4170
|
// ../kobe-daemon/src/client/index.ts
|
|
4156
4171
|
import { connect } from "net";
|
|
4172
|
+
import { StringDecoder } from "string_decoder";
|
|
4157
4173
|
|
|
4158
4174
|
class KobeDaemonClient {
|
|
4159
4175
|
socketPath;
|
|
@@ -4274,7 +4290,9 @@ class KobeDaemonClient {
|
|
|
4274
4290
|
};
|
|
4275
4291
|
socket.once("connect", onConnect);
|
|
4276
4292
|
socket.once("error", onError);
|
|
4277
|
-
|
|
4293
|
+
const decoder = new StringDecoder("utf8");
|
|
4294
|
+
this.buffer = "";
|
|
4295
|
+
socket.on("data", (chunk) => this.onData(decoder.write(chunk)));
|
|
4278
4296
|
socket.on("close", () => this.onSocketClose(socket));
|
|
4279
4297
|
});
|
|
4280
4298
|
}
|
|
@@ -5067,6 +5085,24 @@ function normalizeClaudeContent(content) {
|
|
|
5067
5085
|
return out;
|
|
5068
5086
|
}
|
|
5069
5087
|
|
|
5088
|
+
// src/engine/claude-code-local/synthetic.ts
|
|
5089
|
+
function isSyntheticClaudeRecord(record) {
|
|
5090
|
+
return record.isMeta === true || record.isCompactSummary === true;
|
|
5091
|
+
}
|
|
5092
|
+
function isClaudeCommandBreadcrumb(blocks) {
|
|
5093
|
+
if (blocks.length === 0)
|
|
5094
|
+
return false;
|
|
5095
|
+
for (const b of blocks) {
|
|
5096
|
+
if (b.type !== "text")
|
|
5097
|
+
return false;
|
|
5098
|
+
const t = b.text.trim();
|
|
5099
|
+
if (!t.startsWith("<command-name>") && !t.startsWith("<command-message>") && !t.startsWith("<local-command")) {
|
|
5100
|
+
return false;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
return true;
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5070
5106
|
// src/engine/claude-code-local/history.ts
|
|
5071
5107
|
import { appendFile as appendFile2, mkdir as mkdir4, readFile as readFile2, readdir, stat, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
5072
5108
|
import { homedir as homedir8 } from "os";
|
|
@@ -5148,6 +5184,8 @@ function parseJsonl(raw, sessionId) {
|
|
|
5148
5184
|
return out;
|
|
5149
5185
|
}
|
|
5150
5186
|
function extractMessage(record, fallbackSessionId) {
|
|
5187
|
+
if (isSyntheticClaudeRecord(record))
|
|
5188
|
+
return null;
|
|
5151
5189
|
const inner = isObject(record.message) ? record.message : record;
|
|
5152
5190
|
const role = inner.role;
|
|
5153
5191
|
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
@@ -5155,6 +5193,8 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
5155
5193
|
if (!("content" in inner))
|
|
5156
5194
|
return null;
|
|
5157
5195
|
const blocks = normalizeClaudeContent(inner.content);
|
|
5196
|
+
if (role === "user" && isClaudeCommandBreadcrumb(blocks))
|
|
5197
|
+
return null;
|
|
5158
5198
|
const ts = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
5159
5199
|
const sid = typeof record.sessionId === "string" ? record.sessionId : fallbackSessionId;
|
|
5160
5200
|
const usage = extractUsage(inner.usage);
|
|
@@ -5557,9 +5597,12 @@ async function listRolloutFiles(deps = defaultDeps7) {
|
|
|
5557
5597
|
return out;
|
|
5558
5598
|
}
|
|
5559
5599
|
async function findRolloutFile(sessionId, deps = defaultDeps7) {
|
|
5600
|
+
if (!sessionId)
|
|
5601
|
+
return;
|
|
5602
|
+
const want = sessionId.toLowerCase();
|
|
5560
5603
|
const all = await listRolloutFiles(deps);
|
|
5561
5604
|
for (const p of all) {
|
|
5562
|
-
if (path8.basename(p).
|
|
5605
|
+
if (path8.basename(p).match(UUID_AT_END)?.[1]?.toLowerCase() === want)
|
|
5563
5606
|
return p;
|
|
5564
5607
|
}
|
|
5565
5608
|
return;
|
|
@@ -5855,7 +5898,7 @@ function deriveCodexUsageMetrics(raw) {
|
|
|
5855
5898
|
if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
|
|
5856
5899
|
latestUsageTimestampMs = timestampMs;
|
|
5857
5900
|
latestUsage = snapshot;
|
|
5858
|
-
} else if (
|
|
5901
|
+
} else if (latestUsageTimestampMs === null) {
|
|
5859
5902
|
latestUsage = snapshot;
|
|
5860
5903
|
}
|
|
5861
5904
|
}
|
|
@@ -6426,6 +6469,7 @@ async function deriveTitleFromSessionId(vendor, sessionId) {
|
|
|
6426
6469
|
var MAX_SESSIONS_SCANNED = 8;
|
|
6427
6470
|
var init_auto_title = __esm(() => {
|
|
6428
6471
|
init_registry();
|
|
6472
|
+
init_task();
|
|
6429
6473
|
});
|
|
6430
6474
|
|
|
6431
6475
|
// src/tui/panes/terminal/launch.ts
|
|
@@ -6859,6 +6903,7 @@ var realRunner, realDeps;
|
|
|
6859
6903
|
var init_chat_tab_naming = __esm(() => {
|
|
6860
6904
|
init_auto_title();
|
|
6861
6905
|
init_client2();
|
|
6906
|
+
init_task();
|
|
6862
6907
|
realRunner = { capture: runTmuxCapturing, run: runTmux };
|
|
6863
6908
|
realDeps = {
|
|
6864
6909
|
runner: realRunner,
|
|
@@ -6953,6 +6998,7 @@ var init_auto_title_poller = __esm(() => {
|
|
|
6953
6998
|
init_auto_title();
|
|
6954
6999
|
init_core();
|
|
6955
7000
|
init_chat_tab_naming();
|
|
7001
|
+
init_task();
|
|
6956
7002
|
});
|
|
6957
7003
|
|
|
6958
7004
|
// ../kobe-daemon/src/daemon/event-bus.ts
|
|
@@ -7261,24 +7307,16 @@ function createDaemonHandlerRegistry() {
|
|
|
7261
7307
|
async handle(payload, ctx) {
|
|
7262
7308
|
const taskId = requireString(payload, "taskId");
|
|
7263
7309
|
const status = requireString(payload, "status");
|
|
7264
|
-
if (status
|
|
7310
|
+
if (!isTaskStatus(status))
|
|
7265
7311
|
throw new Error("status must be a TaskStatus");
|
|
7266
|
-
}
|
|
7267
7312
|
const linked = status === "done" ? ctx.orch.getTask(taskId) : undefined;
|
|
7268
7313
|
const prevStatus = linked?.status;
|
|
7269
7314
|
await ctx.orch.setStatus(taskId, status);
|
|
7270
7315
|
if (status === "done" && prevStatus !== "done" && linked) {
|
|
7271
7316
|
try {
|
|
7272
|
-
const
|
|
7273
|
-
|
|
7274
|
-
if (found && found.status !== "done") {
|
|
7275
|
-
const next = await ctx.issues.mutate(linked.repo, {
|
|
7276
|
-
type: "setStatus",
|
|
7277
|
-
id: found.id,
|
|
7278
|
-
status: "done"
|
|
7279
|
-
});
|
|
7317
|
+
const next = await ctx.issues.mirrorTaskDone(linked.repo, taskId);
|
|
7318
|
+
if (next)
|
|
7280
7319
|
ctx.bus.publish("issue.snapshot", next);
|
|
7281
|
-
}
|
|
7282
7320
|
} catch (err) {
|
|
7283
7321
|
logDaemonError("issue-done-mirror", err);
|
|
7284
7322
|
}
|
|
@@ -7524,6 +7562,7 @@ function optionalActivityDetail(payload) {
|
|
|
7524
7562
|
var init_handlers = __esm(() => {
|
|
7525
7563
|
init_hook_events();
|
|
7526
7564
|
init_status_rules();
|
|
7565
|
+
init_task();
|
|
7527
7566
|
init_version();
|
|
7528
7567
|
init_cwd_task();
|
|
7529
7568
|
init_protocol();
|
|
@@ -7660,7 +7699,7 @@ class IssuesStore {
|
|
|
7660
7699
|
}
|
|
7661
7700
|
async list(repo) {
|
|
7662
7701
|
const { repoRoot, repoKey } = await resolveRepo(repo);
|
|
7663
|
-
return withLock(
|
|
7702
|
+
return withLock(this.path, async () => {
|
|
7664
7703
|
const store = await readStore(this.path);
|
|
7665
7704
|
const record = store.repos[repoKey] ?? null;
|
|
7666
7705
|
if (record && record.repoRoot !== repoRoot) {
|
|
@@ -7670,12 +7709,30 @@ class IssuesStore {
|
|
|
7670
7709
|
return response(repoRoot, record);
|
|
7671
7710
|
});
|
|
7672
7711
|
}
|
|
7712
|
+
async mirrorTaskDone(repo, taskId) {
|
|
7713
|
+
const { repoRoot, repoKey } = await resolveRepo(repo);
|
|
7714
|
+
if (!taskId)
|
|
7715
|
+
return null;
|
|
7716
|
+
return withLock(this.path, async () => {
|
|
7717
|
+
const store = await readStore(this.path);
|
|
7718
|
+
const record = store.repos[repoKey];
|
|
7719
|
+
if (!record)
|
|
7720
|
+
return null;
|
|
7721
|
+
const issue = record.issues.find((i) => i.taskId === taskId);
|
|
7722
|
+
if (!issue || issue.status === "done")
|
|
7723
|
+
return null;
|
|
7724
|
+
issue.status = "done";
|
|
7725
|
+
record.repoRoot = repoRoot;
|
|
7726
|
+
await writeStore(this.path, store);
|
|
7727
|
+
return response(repoRoot, record);
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7673
7730
|
async mutate(repo, op) {
|
|
7674
7731
|
const { repoRoot, repoKey } = await resolveRepo(repo);
|
|
7675
7732
|
if (!op || typeof op !== "object" || Array.isArray(op) || typeof op.type !== "string") {
|
|
7676
7733
|
throw new Error("missing op");
|
|
7677
7734
|
}
|
|
7678
|
-
return withLock(
|
|
7735
|
+
return withLock(this.path, async () => {
|
|
7679
7736
|
const store = await readStore(this.path);
|
|
7680
7737
|
let record = store.repos[repoKey];
|
|
7681
7738
|
if (!record) {
|
|
@@ -7818,6 +7875,76 @@ function startKeybindingsWatcher(bus, options = {}) {
|
|
|
7818
7875
|
var DEFAULT_KEYBINDINGS_DEBOUNCE_MS = 200;
|
|
7819
7876
|
var init_keybindings_watcher = () => {};
|
|
7820
7877
|
|
|
7878
|
+
// ../kobe-daemon/src/daemon/lifetime.ts
|
|
7879
|
+
class DaemonLifetime {
|
|
7880
|
+
clients;
|
|
7881
|
+
idleGraceMs;
|
|
7882
|
+
onIdleStop;
|
|
7883
|
+
schedule;
|
|
7884
|
+
log;
|
|
7885
|
+
cancelIdle = null;
|
|
7886
|
+
stopping = false;
|
|
7887
|
+
constructor(options) {
|
|
7888
|
+
this.clients = options.clients;
|
|
7889
|
+
this.idleGraceMs = options.idleGraceMs;
|
|
7890
|
+
this.onIdleStop = options.onIdleStop;
|
|
7891
|
+
this.schedule = options.schedule ?? defaultSchedule;
|
|
7892
|
+
this.log = options.log ?? logDaemonInfo;
|
|
7893
|
+
}
|
|
7894
|
+
guiCount() {
|
|
7895
|
+
let n = 0;
|
|
7896
|
+
for (const c of this.clients())
|
|
7897
|
+
if (c.holdsLifetime)
|
|
7898
|
+
n++;
|
|
7899
|
+
return n;
|
|
7900
|
+
}
|
|
7901
|
+
hasSubscribers() {
|
|
7902
|
+
for (const c of this.clients())
|
|
7903
|
+
if (c.subscribed)
|
|
7904
|
+
return true;
|
|
7905
|
+
return false;
|
|
7906
|
+
}
|
|
7907
|
+
isStopping() {
|
|
7908
|
+
return this.stopping;
|
|
7909
|
+
}
|
|
7910
|
+
markStopping() {
|
|
7911
|
+
this.stopping = true;
|
|
7912
|
+
this.clearIdle();
|
|
7913
|
+
}
|
|
7914
|
+
guiAttached() {
|
|
7915
|
+
this.clearIdle();
|
|
7916
|
+
}
|
|
7917
|
+
clientDisconnected(wasGui) {
|
|
7918
|
+
if (wasGui)
|
|
7919
|
+
this.maybeArm();
|
|
7920
|
+
}
|
|
7921
|
+
clearIdle() {
|
|
7922
|
+
if (this.cancelIdle) {
|
|
7923
|
+
this.cancelIdle();
|
|
7924
|
+
this.cancelIdle = null;
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
maybeArm() {
|
|
7928
|
+
if (this.stopping || this.guiCount() > 0)
|
|
7929
|
+
return;
|
|
7930
|
+
this.clearIdle();
|
|
7931
|
+
this.log("idle", `last gui gone \u2014 arming ${this.idleGraceMs}ms idle-stop grace`);
|
|
7932
|
+
this.cancelIdle = this.schedule(() => {
|
|
7933
|
+
this.cancelIdle = null;
|
|
7934
|
+
if (this.stopping || this.guiCount() > 0)
|
|
7935
|
+
return;
|
|
7936
|
+
this.log("idle", "grace elapsed with no gui \u2014 self-stopping");
|
|
7937
|
+
this.onIdleStop();
|
|
7938
|
+
}, this.idleGraceMs);
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
var defaultSchedule = (fn, ms) => {
|
|
7942
|
+
const t = setTimeout(fn, ms);
|
|
7943
|
+
t.unref?.();
|
|
7944
|
+
return () => clearTimeout(t);
|
|
7945
|
+
};
|
|
7946
|
+
var init_lifetime = () => {};
|
|
7947
|
+
|
|
7821
7948
|
// ../kobe-daemon/src/daemon/ui-prefs-watcher.ts
|
|
7822
7949
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, watch as watch2 } from "fs";
|
|
7823
7950
|
import { homedir as homedir15 } from "os";
|
|
@@ -8125,6 +8252,7 @@ var init_worktree_changes_collector = __esm(() => {
|
|
|
8125
8252
|
import { mkdir as mkdir7, readFile as readFile8, unlink as unlink4, writeFile as writeFile5 } from "fs/promises";
|
|
8126
8253
|
import { createServer } from "net";
|
|
8127
8254
|
import { dirname as dirname8 } from "path";
|
|
8255
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
8128
8256
|
function resolveIdleGraceMs() {
|
|
8129
8257
|
const raw = process.env.KOBE_DAEMON_IDLE_GRACE_MS;
|
|
8130
8258
|
if (raw === undefined)
|
|
@@ -8138,42 +8266,11 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8138
8266
|
const startedAt = options.startedAt ?? new Date;
|
|
8139
8267
|
const clients = new Set;
|
|
8140
8268
|
let nextClientId = 1;
|
|
8141
|
-
const
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
for (const c of clients)
|
|
8147
|
-
if (c.holdsLifetime)
|
|
8148
|
-
n++;
|
|
8149
|
-
return n;
|
|
8150
|
-
}
|
|
8151
|
-
function hasSubscribers() {
|
|
8152
|
-
for (const c of clients)
|
|
8153
|
-
if (c.subscribed)
|
|
8154
|
-
return true;
|
|
8155
|
-
return false;
|
|
8156
|
-
}
|
|
8157
|
-
function cancelIdleTimer() {
|
|
8158
|
-
if (idleTimer) {
|
|
8159
|
-
clearTimeout(idleTimer);
|
|
8160
|
-
idleTimer = null;
|
|
8161
|
-
}
|
|
8162
|
-
}
|
|
8163
|
-
function maybeArmIdleShutdown() {
|
|
8164
|
-
if (stopping || guiCount() > 0)
|
|
8165
|
-
return;
|
|
8166
|
-
cancelIdleTimer();
|
|
8167
|
-
logDaemonInfo("idle", `last gui gone \u2014 arming ${idleGraceMs}ms idle-stop grace`);
|
|
8168
|
-
idleTimer = setTimeout(() => {
|
|
8169
|
-
idleTimer = null;
|
|
8170
|
-
if (stopping || guiCount() > 0)
|
|
8171
|
-
return;
|
|
8172
|
-
logDaemonInfo("idle", "grace elapsed with no gui \u2014 self-stopping");
|
|
8173
|
-
stopSoon().catch((err) => logDaemonError("daemon-idle-shutdown", err));
|
|
8174
|
-
}, idleGraceMs);
|
|
8175
|
-
idleTimer.unref?.();
|
|
8176
|
-
}
|
|
8269
|
+
const lifetime = new DaemonLifetime({
|
|
8270
|
+
clients: () => clients,
|
|
8271
|
+
idleGraceMs: resolveIdleGraceMs(),
|
|
8272
|
+
onIdleStop: () => void stopSoon().catch((err) => logDaemonError("daemon-idle-shutdown", err))
|
|
8273
|
+
});
|
|
8177
8274
|
const bus = new DaemonEventBus;
|
|
8178
8275
|
bus.onPublish((event) => {
|
|
8179
8276
|
broadcast(clients, { type: "event", name: event.channel, payload: event.payload });
|
|
@@ -8194,18 +8291,18 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8194
8291
|
channels: null
|
|
8195
8292
|
};
|
|
8196
8293
|
clients.add(client);
|
|
8294
|
+
const decoder = new StringDecoder2("utf8");
|
|
8197
8295
|
socket.on("data", (chunk) => {
|
|
8198
|
-
client.buffer +=
|
|
8296
|
+
client.buffer += decoder.write(chunk);
|
|
8199
8297
|
drainClientBuffer(client);
|
|
8200
8298
|
});
|
|
8201
8299
|
socket.on("error", () => {});
|
|
8202
8300
|
socket.on("close", () => {
|
|
8203
8301
|
clients.delete(client);
|
|
8204
8302
|
if (client.subscribed) {
|
|
8205
|
-
logDaemonInfo("conn", `client #${client.id} (${client.holdsLifetime ? "gui" : "pane"}) disconnected \u2014 ${clients.size} client(s), ${guiCount()} gui left`);
|
|
8303
|
+
logDaemonInfo("conn", `client #${client.id} (${client.holdsLifetime ? "gui" : "pane"}) disconnected \u2014 ${clients.size} client(s), ${lifetime.guiCount()} gui left`);
|
|
8206
8304
|
}
|
|
8207
|
-
|
|
8208
|
-
maybeArmIdleShutdown();
|
|
8305
|
+
lifetime.clientDisconnected(client.holdsLifetime);
|
|
8209
8306
|
});
|
|
8210
8307
|
});
|
|
8211
8308
|
const unsubscribeStore = orch.subscribeTasks((snapshot) => {
|
|
@@ -8223,7 +8320,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8223
8320
|
updateTimer.unref?.();
|
|
8224
8321
|
}
|
|
8225
8322
|
const autoTitlePollMs = options.autoTitlePollMs ?? DEFAULT_AUTO_TITLE_POLL_MS;
|
|
8226
|
-
const stopAutoTitlePoller = startAutoTitlePoller(orch, autoTitlePollMs, hasSubscribers);
|
|
8323
|
+
const stopAutoTitlePoller = startAutoTitlePoller(orch, autoTitlePollMs, () => lifetime.hasSubscribers());
|
|
8227
8324
|
const stopUiPrefsWatcher = startUiPrefsWatcher(bus, {
|
|
8228
8325
|
statePath: defaultUiPrefsStatePath(options.homeDir),
|
|
8229
8326
|
debounceMs: options.uiPrefsDebounceMs ?? DEFAULT_UI_PREFS_DEBOUNCE_MS
|
|
@@ -8232,15 +8329,14 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8232
8329
|
path: defaultKeybindingsPath(options.homeDir),
|
|
8233
8330
|
debounceMs: options.keybindingsDebounceMs ?? DEFAULT_KEYBINDINGS_DEBOUNCE_MS
|
|
8234
8331
|
});
|
|
8235
|
-
const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS, hasSubscribers);
|
|
8332
|
+
const stopWorktreeChangesCollector = startWorktreeChangesCollector(orch, bus, options.worktreeChangesTickMs ?? DEFAULT_WORKTREE_CHANGES_TICK_MS, () => lifetime.hasSubscribers());
|
|
8236
8333
|
const serverApi = {
|
|
8237
8334
|
socketPath,
|
|
8238
8335
|
pidPath,
|
|
8239
8336
|
startedAt,
|
|
8240
8337
|
clients,
|
|
8241
8338
|
async close() {
|
|
8242
|
-
|
|
8243
|
-
cancelIdleTimer();
|
|
8339
|
+
lifetime.markStopping();
|
|
8244
8340
|
unsubscribeStore();
|
|
8245
8341
|
if (updateTimer)
|
|
8246
8342
|
clearInterval(updateTimer);
|
|
@@ -8269,10 +8365,9 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8269
8365
|
await writeFile5(pidPath, `${process.pid}
|
|
8270
8366
|
`, "utf8");
|
|
8271
8367
|
async function stopSoon() {
|
|
8272
|
-
if (
|
|
8368
|
+
if (lifetime.isStopping())
|
|
8273
8369
|
return;
|
|
8274
|
-
|
|
8275
|
-
cancelIdleTimer();
|
|
8370
|
+
lifetime.markStopping();
|
|
8276
8371
|
await options.onStop?.();
|
|
8277
8372
|
setTimeout(() => {
|
|
8278
8373
|
serverApi.close().catch((err) => logDaemonError("daemon-shutdown", err));
|
|
@@ -8289,8 +8384,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8289
8384
|
client.channels = normalizeChannelFilter(payload.channels);
|
|
8290
8385
|
const firstSubscriber = !wasSubscribed;
|
|
8291
8386
|
if (client.holdsLifetime)
|
|
8292
|
-
|
|
8293
|
-
logDaemonInfo("conn", `client #${client.id} subscribed as ${role}${client.channels ? ` [${[...client.channels].join(",")}]` : ""} \u2014 ${clients.size} client(s), ${guiCount()} gui${firstSubscriber ? " (collectors resume)" : ""}`);
|
|
8387
|
+
lifetime.guiAttached();
|
|
8388
|
+
logDaemonInfo("conn", `client #${client.id} subscribed as ${role}${client.channels ? ` [${[...client.channels].join(",")}]` : ""} \u2014 ${clients.size} client(s), ${lifetime.guiCount()} gui${firstSubscriber ? " (collectors resume)" : ""}`);
|
|
8294
8389
|
for (const event of bus.snapshot()) {
|
|
8295
8390
|
if (client.channels && !client.channels.has(event.channel))
|
|
8296
8391
|
continue;
|
|
@@ -8312,7 +8407,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
8312
8407
|
bus,
|
|
8313
8408
|
activity,
|
|
8314
8409
|
issues,
|
|
8315
|
-
daemon: { startedAt, socketPath, pid: process.pid, guiCount, stopSoon },
|
|
8410
|
+
daemon: { startedAt, socketPath, pid: process.pid, guiCount: () => lifetime.guiCount(), stopSoon },
|
|
8316
8411
|
clientId: client.id
|
|
8317
8412
|
});
|
|
8318
8413
|
}
|
|
@@ -8382,6 +8477,7 @@ var init_server = __esm(() => {
|
|
|
8382
8477
|
init_handlers();
|
|
8383
8478
|
init_issues_store();
|
|
8384
8479
|
init_keybindings_watcher();
|
|
8480
|
+
init_lifetime();
|
|
8385
8481
|
init_paths2();
|
|
8386
8482
|
init_protocol();
|
|
8387
8483
|
init_ui_prefs_watcher();
|
|
@@ -11091,6 +11187,68 @@ var init_tmux_border_theme = __esm(() => {
|
|
|
11091
11187
|
FOCUS_ACCENT_SLOT_NAMES2 = ["primary", "success", "info"];
|
|
11092
11188
|
});
|
|
11093
11189
|
|
|
11190
|
+
// src/tui/panes/terminal/layout-coord.ts
|
|
11191
|
+
var exports_layout_coord = {};
|
|
11192
|
+
__export(exports_layout_coord, {
|
|
11193
|
+
recordGen: () => recordGen,
|
|
11194
|
+
isLatestGen: () => isLatestGen,
|
|
11195
|
+
genAgeMs: () => genAgeMs,
|
|
11196
|
+
coalesceLayoutWork: () => coalesceLayoutWork,
|
|
11197
|
+
RESIZE_GUARD_MS: () => RESIZE_GUARD_MS,
|
|
11198
|
+
LAYOUT_COALESCE_MS: () => LAYOUT_COALESCE_MS
|
|
11199
|
+
});
|
|
11200
|
+
import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
|
|
11201
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
11202
|
+
import { join as join8 } from "path";
|
|
11203
|
+
function coordDir() {
|
|
11204
|
+
return join8(kobeStateDir(), "layout-coord");
|
|
11205
|
+
}
|
|
11206
|
+
function genPath(session, kind) {
|
|
11207
|
+
const hash = createHash5("sha1").update(session).digest("hex").slice(0, 16);
|
|
11208
|
+
return join8(coordDir(), `${hash}.${kind}`);
|
|
11209
|
+
}
|
|
11210
|
+
function recordGen(session, kind) {
|
|
11211
|
+
const nonce = randomUUID2();
|
|
11212
|
+
try {
|
|
11213
|
+
mkdirSync5(coordDir(), { recursive: true });
|
|
11214
|
+
const path11 = genPath(session, kind);
|
|
11215
|
+
const tmp = `${path11}.${nonce}.tmp`;
|
|
11216
|
+
writeFileSync2(tmp, `${Date.now()}
|
|
11217
|
+
${nonce}`);
|
|
11218
|
+
renameSync2(tmp, path11);
|
|
11219
|
+
} catch {}
|
|
11220
|
+
return nonce;
|
|
11221
|
+
}
|
|
11222
|
+
function isLatestGen(session, kind, nonce) {
|
|
11223
|
+
try {
|
|
11224
|
+
return readFileSync8(genPath(session, kind), "utf8").split(`
|
|
11225
|
+
`)[1]?.trim() === nonce;
|
|
11226
|
+
} catch {
|
|
11227
|
+
return true;
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
function genAgeMs(session, kind, now = Date.now()) {
|
|
11231
|
+
try {
|
|
11232
|
+
const ts = Number.parseInt(readFileSync8(genPath(session, kind), "utf8").split(`
|
|
11233
|
+
`)[0] ?? "", 10);
|
|
11234
|
+
return Number.isFinite(ts) ? now - ts : Number.POSITIVE_INFINITY;
|
|
11235
|
+
} catch {
|
|
11236
|
+
return Number.POSITIVE_INFINITY;
|
|
11237
|
+
}
|
|
11238
|
+
}
|
|
11239
|
+
async function coalesceLayoutWork(session, kind, work, debounceMs = LAYOUT_COALESCE_MS) {
|
|
11240
|
+
const nonce = recordGen(session, kind);
|
|
11241
|
+
if (debounceMs > 0)
|
|
11242
|
+
await new Promise((resolve5) => setTimeout(resolve5, debounceMs));
|
|
11243
|
+
if (!isLatestGen(session, kind, nonce))
|
|
11244
|
+
return;
|
|
11245
|
+
await work();
|
|
11246
|
+
}
|
|
11247
|
+
var LAYOUT_COALESCE_MS = 120, RESIZE_GUARD_MS = 400;
|
|
11248
|
+
var init_layout_coord = __esm(() => {
|
|
11249
|
+
init_env();
|
|
11250
|
+
});
|
|
11251
|
+
|
|
11094
11252
|
// src/tui/panes/terminal/pane-heal.ts
|
|
11095
11253
|
function parseKobePaneRows(stdout) {
|
|
11096
11254
|
const rows = [];
|
|
@@ -11204,6 +11362,7 @@ async function globalRightColumnResizeArgs() {
|
|
|
11204
11362
|
return (await globalLayoutPrefs()).rcArgs;
|
|
11205
11363
|
}
|
|
11206
11364
|
async function healWorkspaceLayout(session, versions) {
|
|
11365
|
+
recordGen(session, "resize");
|
|
11207
11366
|
const { tasksWidth, rcArgs } = await globalLayoutPrefs();
|
|
11208
11367
|
const rows = await listKobePanes(session);
|
|
11209
11368
|
if (!rows)
|
|
@@ -11241,7 +11400,7 @@ async function captureGlobalLayout(session) {
|
|
|
11241
11400
|
"-t",
|
|
11242
11401
|
`=${session}`,
|
|
11243
11402
|
"-F",
|
|
11244
|
-
"#{@kobe_role}\t#{pane_width}\t#{pane_height}\t#{window_width}\t#{window_height}"
|
|
11403
|
+
"#{@kobe_role}\t#{pane_width}\t#{pane_height}\t#{window_width}\t#{window_height}\t#{window_zoomed_flag}"
|
|
11245
11404
|
]);
|
|
11246
11405
|
if (code !== 0)
|
|
11247
11406
|
return;
|
|
@@ -11249,6 +11408,8 @@ async function captureGlobalLayout(session) {
|
|
|
11249
11408
|
`).map((line) => line.split("\t")).filter((cols) => (cols[0]?.trim() ?? "") !== "");
|
|
11250
11409
|
if (rows.length === 0)
|
|
11251
11410
|
return;
|
|
11411
|
+
if (rows.some((cols) => cols[5]?.trim() === "1"))
|
|
11412
|
+
return;
|
|
11252
11413
|
const winW = Number.parseInt(rows[0][3]?.trim() ?? "", 10);
|
|
11253
11414
|
const winH = Number.parseInt(rows[0][4]?.trim() ?? "", 10);
|
|
11254
11415
|
const sets = [];
|
|
@@ -11272,6 +11433,28 @@ async function captureGlobalLayout(session) {
|
|
|
11272
11433
|
if (sets.length > 0)
|
|
11273
11434
|
await runTmuxSequence(sets);
|
|
11274
11435
|
}
|
|
11436
|
+
function shouldCaptureDrag(stdout) {
|
|
11437
|
+
const rows = stdout.split(`
|
|
11438
|
+
`).map((line) => line.split("\t")).filter((cols) => (cols[0]?.trim() ?? "") !== "");
|
|
11439
|
+
if (rows.length === 0)
|
|
11440
|
+
return false;
|
|
11441
|
+
if (rows.some((cols) => cols[1]?.trim() === "1"))
|
|
11442
|
+
return false;
|
|
11443
|
+
const roles = new Set(rows.map((cols) => cols[0]?.trim()));
|
|
11444
|
+
return roles.has("tasks") && roles.has("ops");
|
|
11445
|
+
}
|
|
11446
|
+
async function captureGlobalLayoutOnDrag(session) {
|
|
11447
|
+
const { code, stdout } = await runTmuxCapturing([
|
|
11448
|
+
"list-panes",
|
|
11449
|
+
"-t",
|
|
11450
|
+
`=${session}`,
|
|
11451
|
+
"-F",
|
|
11452
|
+
"#{@kobe_role}\t#{window_zoomed_flag}"
|
|
11453
|
+
]);
|
|
11454
|
+
if (code !== 0 || !shouldCaptureDrag(stdout))
|
|
11455
|
+
return;
|
|
11456
|
+
await captureGlobalLayout(session);
|
|
11457
|
+
}
|
|
11275
11458
|
async function refreshKobeWorkspacePanes(session) {
|
|
11276
11459
|
const sessionOptions = await getSessionOptions(session, ["@kobe_worktree", "@kobe_task", "@kobe_vendor"]);
|
|
11277
11460
|
const cwd = sessionOptions["@kobe_worktree"] || process.cwd();
|
|
@@ -11298,6 +11481,7 @@ var init_pane_heal = __esm(() => {
|
|
|
11298
11481
|
init_tmux_border_theme();
|
|
11299
11482
|
init_version();
|
|
11300
11483
|
init_launch();
|
|
11484
|
+
init_layout_coord();
|
|
11301
11485
|
KOBE_PANE_LIST_FORMAT = `#{window_id} #{pane_id} #{@kobe_role} #{${PANE_VERSION_OPTION}} #{pane_width}`;
|
|
11302
11486
|
});
|
|
11303
11487
|
|
|
@@ -11527,6 +11711,7 @@ __export(exports_tmux, {
|
|
|
11527
11711
|
selectTasksPane: () => selectTasksPane,
|
|
11528
11712
|
refreshKobeWorkspacePanes: () => refreshKobeWorkspacePanes,
|
|
11529
11713
|
quickCreate: () => quickCreate,
|
|
11714
|
+
prepareWindowForSwitch: () => prepareWindowForSwitch,
|
|
11530
11715
|
prepareWindowForAttach: () => prepareWindowForAttach,
|
|
11531
11716
|
parseObservedSession: () => parseObservedSession,
|
|
11532
11717
|
openUpdateTab: () => openUpdateTab,
|
|
@@ -11544,6 +11729,7 @@ __export(exports_tmux, {
|
|
|
11544
11729
|
chatTabRenameBinding: () => chatTabRenameBinding,
|
|
11545
11730
|
chatTabCloseBinding: () => chatTabCloseBinding,
|
|
11546
11731
|
chatTabChooseEngineBindings: () => chatTabChooseEngineBindings,
|
|
11732
|
+
captureGlobalLayoutOnDrag: () => captureGlobalLayoutOnDrag,
|
|
11547
11733
|
captureGlobalLayout: () => captureGlobalLayout,
|
|
11548
11734
|
attachArgv: () => attachArgv,
|
|
11549
11735
|
PANE_VERSION_OPTION: () => PANE_VERSION_OPTION,
|
|
@@ -11562,11 +11748,26 @@ function positiveInt(value) {
|
|
|
11562
11748
|
return Number.isInteger(n) && n > 0 ? n : undefined;
|
|
11563
11749
|
}
|
|
11564
11750
|
async function prepareWindowForAttach(session) {
|
|
11751
|
+
recordGen(session, "resize");
|
|
11565
11752
|
const sizeArgs = tmuxInitialSizeArgs();
|
|
11566
11753
|
if (sizeArgs.length > 0)
|
|
11567
11754
|
await runTmux(["resize-window", "-t", `=${session}`, ...sizeArgs]);
|
|
11568
11755
|
await healWorkspaceLayout(session);
|
|
11569
11756
|
}
|
|
11757
|
+
async function attachedWindowSizeArgs() {
|
|
11758
|
+
const { code, stdout } = await runTmuxCapturing(["display-message", "-p", "#{window_width}\t#{window_height}"]);
|
|
11759
|
+
if (code !== 0)
|
|
11760
|
+
return [];
|
|
11761
|
+
const [w, h] = stdout.trim().split("\t").map((s) => Number.parseInt(s, 10));
|
|
11762
|
+
return Number.isInteger(w) && w > 0 && Number.isInteger(h) && h > 0 ? ["-x", `${w}`, "-y", `${h}`] : [];
|
|
11763
|
+
}
|
|
11764
|
+
async function prepareWindowForSwitch(session) {
|
|
11765
|
+
recordGen(session, "resize");
|
|
11766
|
+
const sizeArgs = await attachedWindowSizeArgs();
|
|
11767
|
+
if (sizeArgs.length > 0)
|
|
11768
|
+
await runTmux(["resize-window", "-t", `=${session}`, ...sizeArgs]);
|
|
11769
|
+
await healWorkspaceLayout(session);
|
|
11770
|
+
}
|
|
11570
11771
|
function focusBindCommand(key, dir) {
|
|
11571
11772
|
return [
|
|
11572
11773
|
"bind-key",
|
|
@@ -11698,6 +11899,8 @@ async function ensureSessionImpl(opts) {
|
|
|
11698
11899
|
const focusTasksTmuxCommand = `run-shell ${shellQuote(focusTasksCommand)}`;
|
|
11699
11900
|
const healLayoutCommand = `${envStr}${invStr} heal-layout --session '#{session_name}'`;
|
|
11700
11901
|
const healLayoutTmuxCommand = `run-shell -b ${shellQuote(healLayoutCommand)}`;
|
|
11902
|
+
const captureLayoutCommand = `${envStr}${invStr} capture-layout --session '#{session_name}'`;
|
|
11903
|
+
const captureLayoutTmuxCommand = `run-shell -b ${shellQuote(captureLayoutCommand)}`;
|
|
11701
11904
|
const userKeys = resolveUserTmuxKeys();
|
|
11702
11905
|
const unbinds = [];
|
|
11703
11906
|
if (userKeys.overridden.has(TMUX_FOCUS_ID)) {
|
|
@@ -11740,6 +11943,7 @@ async function ensureSessionImpl(opts) {
|
|
|
11740
11943
|
],
|
|
11741
11944
|
["set-option", "-g", "mouse", "on"],
|
|
11742
11945
|
["set-hook", "-g", "window-resized", healLayoutTmuxCommand],
|
|
11946
|
+
["set-hook", "-g", "window-layout-changed", captureLayoutTmuxCommand],
|
|
11743
11947
|
...unbinds,
|
|
11744
11948
|
...b["tmux.detach"] ? [
|
|
11745
11949
|
[
|
|
@@ -11790,6 +11994,7 @@ var init_tmux = __esm(() => {
|
|
|
11790
11994
|
init_tmux_border_theme();
|
|
11791
11995
|
init_chattab();
|
|
11792
11996
|
init_launch();
|
|
11997
|
+
init_layout_coord();
|
|
11793
11998
|
init_pane_heal();
|
|
11794
11999
|
init_client2();
|
|
11795
12000
|
init_chattab();
|
|
@@ -11808,17 +12013,17 @@ var exports_repo_init = {};
|
|
|
11808
12013
|
__export(exports_repo_init, {
|
|
11809
12014
|
resolveRepoInit: () => resolveRepoInit
|
|
11810
12015
|
});
|
|
11811
|
-
import { existsSync as existsSync7, readFileSync as
|
|
11812
|
-
import { join as
|
|
12016
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
|
|
12017
|
+
import { join as join9 } from "path";
|
|
11813
12018
|
function repoFileScript(worktreePath) {
|
|
11814
|
-
return existsSync7(
|
|
12019
|
+
return existsSync7(join9(worktreePath, INIT_SCRIPT_REL)) ? `sh ${INIT_SCRIPT_REL}` : undefined;
|
|
11815
12020
|
}
|
|
11816
12021
|
function repoFilePrompt(worktreePath) {
|
|
11817
|
-
const p =
|
|
12022
|
+
const p = join9(worktreePath, INIT_PROMPT_REL);
|
|
11818
12023
|
if (!existsSync7(p))
|
|
11819
12024
|
return;
|
|
11820
12025
|
try {
|
|
11821
|
-
const text =
|
|
12026
|
+
const text = readFileSync9(p, "utf8");
|
|
11822
12027
|
return text.trim().length > 0 ? text : undefined;
|
|
11823
12028
|
} catch {
|
|
11824
12029
|
return;
|
|
@@ -11836,8 +12041,8 @@ function resolveRepoInit(repoRoot, worktreePath) {
|
|
|
11836
12041
|
var INIT_SCRIPT_REL, INIT_PROMPT_REL;
|
|
11837
12042
|
var init_repo_init = __esm(() => {
|
|
11838
12043
|
init_repos();
|
|
11839
|
-
INIT_SCRIPT_REL =
|
|
11840
|
-
INIT_PROMPT_REL =
|
|
12044
|
+
INIT_SCRIPT_REL = join9(".kobe", "init.sh");
|
|
12045
|
+
INIT_PROMPT_REL = join9(".kobe", "init-prompt.md");
|
|
11841
12046
|
});
|
|
11842
12047
|
|
|
11843
12048
|
// src/cli/api-cmd.ts
|
|
@@ -12052,6 +12257,9 @@ function parseAgentsSpec(spec) {
|
|
|
12052
12257
|
if (!Number.isInteger(count) || count <= 0) {
|
|
12053
12258
|
throw new ApiError(`--agents count for "${vendor}" must be a positive integer`, "BAD_FLAG");
|
|
12054
12259
|
}
|
|
12260
|
+
if (out.length + count > FANOUT_CAP) {
|
|
12261
|
+
throw new ApiError(`--agents requests ${out.length + count} agents, exceeds the cap of ${FANOUT_CAP}`, "BAD_FLAG");
|
|
12262
|
+
}
|
|
12055
12263
|
for (let i = 0;i < count; i++)
|
|
12056
12264
|
out.push(vendor);
|
|
12057
12265
|
}
|
|
@@ -12501,7 +12709,7 @@ ${apiUsage()}`, "BAD_VERB", 2);
|
|
|
12501
12709
|
session?.close();
|
|
12502
12710
|
}
|
|
12503
12711
|
}
|
|
12504
|
-
var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10,
|
|
12712
|
+
var API_SCHEMA_VERSION = 2, FANOUT_CAP = 10, TASK_STATUSES2, ISSUE_STATUSES2, ApiError, F, VERB_ALIASES, VERB_GROUPS, VERBS, API_VERBS, GLOBAL_FLAGS, realPromptDeliveryOps, defaultApiRuntime;
|
|
12505
12713
|
var init_api_cmd = __esm(() => {
|
|
12506
12714
|
init_interactive_command();
|
|
12507
12715
|
init_feedback();
|
|
@@ -12510,7 +12718,7 @@ var init_api_cmd = __esm(() => {
|
|
|
12510
12718
|
init_vendor();
|
|
12511
12719
|
init_version();
|
|
12512
12720
|
init_daemon_session();
|
|
12513
|
-
|
|
12721
|
+
TASK_STATUSES2 = ["backlog", "in_progress", "in_review", "done", "canceled", "error"];
|
|
12514
12722
|
ISSUE_STATUSES2 = ["open", "doing", "hold", "done"];
|
|
12515
12723
|
ApiError = class ApiError extends Error {
|
|
12516
12724
|
code;
|
|
@@ -12602,7 +12810,7 @@ var init_api_cmd = __esm(() => {
|
|
|
12602
12810
|
{
|
|
12603
12811
|
name: "status",
|
|
12604
12812
|
type: "enum",
|
|
12605
|
-
values:
|
|
12813
|
+
values: TASK_STATUSES2,
|
|
12606
12814
|
default: "backlog",
|
|
12607
12815
|
description: "Initial lifecycle status."
|
|
12608
12816
|
},
|
|
@@ -12755,7 +12963,7 @@ var init_api_cmd = __esm(() => {
|
|
|
12755
12963
|
summary: "Set a task's lifecycle status.",
|
|
12756
12964
|
flags: [
|
|
12757
12965
|
F.taskId(),
|
|
12758
|
-
{ name: "status", type: "enum", required: true, values:
|
|
12966
|
+
{ name: "status", type: "enum", required: true, values: TASK_STATUSES2, description: "New status." }
|
|
12759
12967
|
],
|
|
12760
12968
|
handler: (ctx) => simpleRpc(ctx, "task.status", {
|
|
12761
12969
|
taskId: ctx.args.require("task-id"),
|
|
@@ -12948,8 +13156,8 @@ var exports_theme = {};
|
|
|
12948
13156
|
__export(exports_theme, {
|
|
12949
13157
|
runThemeSubcommand: () => runThemeSubcommand
|
|
12950
13158
|
});
|
|
12951
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
12952
|
-
import { basename as basename5, join as
|
|
13159
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
13160
|
+
import { basename as basename5, join as join10, resolve as resolve6 } from "path";
|
|
12953
13161
|
function fail3(message) {
|
|
12954
13162
|
process.stderr.write(`kobe theme: ${message}
|
|
12955
13163
|
`);
|
|
@@ -12980,7 +13188,7 @@ function listThemes() {
|
|
|
12980
13188
|
} else {
|
|
12981
13189
|
for (const f of userFiles) {
|
|
12982
13190
|
const name = f.slice(0, -".json".length);
|
|
12983
|
-
const path11 =
|
|
13191
|
+
const path11 = join10(dir, f);
|
|
12984
13192
|
const overridesBundled = BUNDLED_NAMES.includes(name) ? " (overrides built-in)" : "";
|
|
12985
13193
|
lines.push(` ${name}${overridesBundled} ${path11}`);
|
|
12986
13194
|
}
|
|
@@ -13009,7 +13217,7 @@ async function readSource(source) {
|
|
|
13009
13217
|
const abs = resolve6(process.cwd(), source);
|
|
13010
13218
|
let text;
|
|
13011
13219
|
try {
|
|
13012
|
-
text =
|
|
13220
|
+
text = readFileSync10(abs, "utf8");
|
|
13013
13221
|
} catch (err) {
|
|
13014
13222
|
fail3(`failed to read ${abs}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13015
13223
|
}
|
|
@@ -13069,12 +13277,12 @@ async function addTheme(args) {
|
|
|
13069
13277
|
fail3(`invalid theme name "${name}" (use letters, digits, '.', '_', '-')`);
|
|
13070
13278
|
}
|
|
13071
13279
|
const dir = userThemesDir();
|
|
13072
|
-
|
|
13073
|
-
const dest =
|
|
13280
|
+
mkdirSync6(dir, { recursive: true });
|
|
13281
|
+
const dest = join10(dir, `${name}.json`);
|
|
13074
13282
|
if (existsSync8(dest) && !opts.force) {
|
|
13075
13283
|
fail3(`${dest} already exists (pass --force to overwrite)`);
|
|
13076
13284
|
}
|
|
13077
|
-
|
|
13285
|
+
writeFileSync3(dest, `${JSON.stringify(result.theme, null, 2)}
|
|
13078
13286
|
`, "utf8");
|
|
13079
13287
|
process.stdout.write(`installed theme "${name}" -> ${dest}
|
|
13080
13288
|
`);
|
|
@@ -13088,7 +13296,7 @@ function removeTheme(args) {
|
|
|
13088
13296
|
if (BUNDLED_NAMES.includes(name)) {
|
|
13089
13297
|
fail3(`"${name}" is a built-in theme and cannot be removed`);
|
|
13090
13298
|
}
|
|
13091
|
-
const dest =
|
|
13299
|
+
const dest = join10(userThemesDir(), `${name}.json`);
|
|
13092
13300
|
if (!existsSync8(dest)) {
|
|
13093
13301
|
fail3(`no user theme named "${name}" (looked for ${dest})`);
|
|
13094
13302
|
}
|
|
@@ -13158,7 +13366,7 @@ __export(exports_feedback_cmd, {
|
|
|
13158
13366
|
runFeedbackSubcommand: () => runFeedbackSubcommand,
|
|
13159
13367
|
parseFeedbackArgs: () => parseFeedbackArgs
|
|
13160
13368
|
});
|
|
13161
|
-
import { readFileSync as
|
|
13369
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
13162
13370
|
function usageError2(message) {
|
|
13163
13371
|
process.stderr.write(`kobe feedback: ${message}
|
|
13164
13372
|
|
|
@@ -13168,8 +13376,8 @@ ${FEEDBACK_USAGE}
|
|
|
13168
13376
|
}
|
|
13169
13377
|
function readBodyFile(path11) {
|
|
13170
13378
|
if (path11 === "-")
|
|
13171
|
-
return
|
|
13172
|
-
return
|
|
13379
|
+
return readFileSync11(0, "utf8");
|
|
13380
|
+
return readFileSync11(path11, "utf8");
|
|
13173
13381
|
}
|
|
13174
13382
|
function parseFeedbackArgs(args) {
|
|
13175
13383
|
const parsed = { help: false };
|
|
@@ -13320,6 +13528,8 @@ async function runDaemonSubcommand(argv) {
|
|
|
13320
13528
|
try {
|
|
13321
13529
|
await client.request("daemon.stop");
|
|
13322
13530
|
console.log("kobe daemon: stop requested");
|
|
13531
|
+
} catch {
|
|
13532
|
+
console.log(`kobe daemon: no daemon running at ${socketPath}`);
|
|
13323
13533
|
} finally {
|
|
13324
13534
|
client.close();
|
|
13325
13535
|
}
|
|
@@ -13368,9 +13578,9 @@ var init_daemon_cmd = __esm(() => {
|
|
|
13368
13578
|
});
|
|
13369
13579
|
|
|
13370
13580
|
// src/lib/skill-install.ts
|
|
13371
|
-
import { existsSync as existsSync9, readFileSync as
|
|
13581
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
|
|
13372
13582
|
import { homedir as homedir17 } from "os";
|
|
13373
|
-
import { join as
|
|
13583
|
+
import { join as join11 } from "path";
|
|
13374
13584
|
function npxSkillsArgv(opts = {}) {
|
|
13375
13585
|
return ["skills", "add", SKILL_SOURCE_SLUG, "--skill", "kobe", "--agent", opts.agent ?? DEFAULT_SKILL_AGENT];
|
|
13376
13586
|
}
|
|
@@ -13380,7 +13590,7 @@ function npxSkillsCommand(opts = {}) {
|
|
|
13380
13590
|
function kobeSkillPaths(opts = {}) {
|
|
13381
13591
|
const home = opts.home ?? homedir17();
|
|
13382
13592
|
const cwd = opts.cwd ?? process.cwd();
|
|
13383
|
-
return [
|
|
13593
|
+
return [join11(home, SKILL_REL_PATH), join11(cwd, SKILL_REL_PATH)];
|
|
13384
13594
|
}
|
|
13385
13595
|
function parseSkillVersion(content) {
|
|
13386
13596
|
const m = content.match(/kobe-skill-version:\s*(\d+)/);
|
|
@@ -13393,7 +13603,7 @@ function kobeSkillState(opts) {
|
|
|
13393
13603
|
}
|
|
13394
13604
|
let installedVersion = null;
|
|
13395
13605
|
try {
|
|
13396
|
-
installedVersion = parseSkillVersion(
|
|
13606
|
+
installedVersion = parseSkillVersion(readFileSync12(path11, "utf8"));
|
|
13397
13607
|
} catch {
|
|
13398
13608
|
installedVersion = null;
|
|
13399
13609
|
}
|
|
@@ -13439,9 +13649,9 @@ __export(exports_maintenance, {
|
|
|
13439
13649
|
runReloadSubcommand: () => runReloadSubcommand,
|
|
13440
13650
|
runDoctorSubcommand: () => runDoctorSubcommand
|
|
13441
13651
|
});
|
|
13442
|
-
import { existsSync as existsSync10, readFileSync as
|
|
13652
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
|
|
13443
13653
|
import { unlink as unlink6 } from "fs/promises";
|
|
13444
|
-
import { join as
|
|
13654
|
+
import { join as join12 } from "path";
|
|
13445
13655
|
import { createInterface as createInterface2 } from "readline";
|
|
13446
13656
|
function isProcessAlive2(pid) {
|
|
13447
13657
|
try {
|
|
@@ -13488,7 +13698,7 @@ function describeFile(path11) {
|
|
|
13488
13698
|
}
|
|
13489
13699
|
function taskCount(tasksPath) {
|
|
13490
13700
|
try {
|
|
13491
|
-
const parsed = JSON.parse(
|
|
13701
|
+
const parsed = JSON.parse(readFileSync13(tasksPath, "utf8"));
|
|
13492
13702
|
return Array.isArray(parsed.tasks) ? parsed.tasks.length : null;
|
|
13493
13703
|
} catch {
|
|
13494
13704
|
return null;
|
|
@@ -13496,7 +13706,7 @@ function taskCount(tasksPath) {
|
|
|
13496
13706
|
}
|
|
13497
13707
|
function tailFile(path11, n) {
|
|
13498
13708
|
try {
|
|
13499
|
-
const lines =
|
|
13709
|
+
const lines = readFileSync13(path11, "utf8").split(`
|
|
13500
13710
|
`).filter((l) => l.trim().length > 0);
|
|
13501
13711
|
return lines.slice(-n).join(`
|
|
13502
13712
|
`);
|
|
@@ -13538,7 +13748,7 @@ async function runDoctorSubcommand(argv = []) {
|
|
|
13538
13748
|
const socketPath = defaultDaemonSocketPath();
|
|
13539
13749
|
const pidPath = defaultDaemonPidPath();
|
|
13540
13750
|
const logPath = defaultDaemonLogPath();
|
|
13541
|
-
const tasksPath =
|
|
13751
|
+
const tasksPath = join12(kobeStateDir(), "tasks.json");
|
|
13542
13752
|
const statePath2 = kvStatePath();
|
|
13543
13753
|
const out = ["kobe doctor", ` home: ${homeDir()}`, ` socket: ${socketPath}`, ""];
|
|
13544
13754
|
const status = await probeDaemonStatus(socketPath);
|
|
@@ -13657,7 +13867,7 @@ async function runResetSubcommand(argv) {
|
|
|
13657
13867
|
const yes = argv.includes("--yes") || argv.includes("-y");
|
|
13658
13868
|
const socketPath = defaultDaemonSocketPath();
|
|
13659
13869
|
const pidPath = defaultDaemonPidPath();
|
|
13660
|
-
const tasksPath =
|
|
13870
|
+
const tasksPath = join12(kobeStateDir(), "tasks.json");
|
|
13661
13871
|
const statePath2 = kvStatePath();
|
|
13662
13872
|
console.log("kobe reset will:");
|
|
13663
13873
|
console.log(" \u2022 stop the kobe daemon (graceful \u2192 SIGTERM \u2192 SIGKILL)");
|
|
@@ -14019,15 +14229,15 @@ var init_history4 = __esm(() => {
|
|
|
14019
14229
|
|
|
14020
14230
|
// src/web/notes.ts
|
|
14021
14231
|
import { mkdir as mkdir8, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
14022
|
-
import { join as
|
|
14232
|
+
import { join as join13 } from "path";
|
|
14023
14233
|
function notesDir() {
|
|
14024
|
-
return
|
|
14234
|
+
return join13(kobeStateDir(), "notes");
|
|
14025
14235
|
}
|
|
14026
14236
|
function isSafeTaskId(taskId) {
|
|
14027
14237
|
return typeof taskId === "string" && taskId.length > 0 && /^[A-Za-z0-9_-]+$/.test(taskId);
|
|
14028
14238
|
}
|
|
14029
14239
|
function noteFilePath(taskId) {
|
|
14030
|
-
return
|
|
14240
|
+
return join13(notesDir(), `${taskId}.md`);
|
|
14031
14241
|
}
|
|
14032
14242
|
async function handleGet(url) {
|
|
14033
14243
|
const taskId = url.searchParams.get("taskId");
|
|
@@ -14165,6 +14375,26 @@ var init_themes = __esm(() => {
|
|
|
14165
14375
|
WEB_THEMES = Object.fromEntries(Object.entries(THEME_JSONS).map(([name, json]) => [name, toWebPalette(json)]).filter((entry) => entry[1] !== null));
|
|
14166
14376
|
});
|
|
14167
14377
|
|
|
14378
|
+
// ../kobe-web/src/lib/repo-key.ts
|
|
14379
|
+
function normalizeRepoPath(path11) {
|
|
14380
|
+
return path11.length > 1 ? path11.replace(/\/+$/, "") : path11;
|
|
14381
|
+
}
|
|
14382
|
+
function repoSnapshotAliases(tasks, repoRoot) {
|
|
14383
|
+
const root = normalizeRepoPath(repoRoot);
|
|
14384
|
+
const aliases = new Set([repoRoot]);
|
|
14385
|
+
for (const task of tasks) {
|
|
14386
|
+
const taskRepo = normalizeRepoPath(task.repo);
|
|
14387
|
+
const taskWorktree = normalizeRepoPath(task.worktreePath);
|
|
14388
|
+
if (taskRepo === root || taskWorktree === root) {
|
|
14389
|
+
if (task.repo)
|
|
14390
|
+
aliases.add(task.repo);
|
|
14391
|
+
if (task.worktreePath)
|
|
14392
|
+
aliases.add(task.worktreePath);
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
return [...aliases];
|
|
14396
|
+
}
|
|
14397
|
+
|
|
14168
14398
|
// ../kobe-web/server/spa-channels.ts
|
|
14169
14399
|
var SPA_CHANNELS, SPA_CHANNEL_SET;
|
|
14170
14400
|
var init_spa_channels = __esm(() => {
|
|
@@ -14186,24 +14416,6 @@ var init_spa_channels = __esm(() => {
|
|
|
14186
14416
|
function sleep2(ms) {
|
|
14187
14417
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
14188
14418
|
}
|
|
14189
|
-
function normalizedPath(path11) {
|
|
14190
|
-
return path11.length > 1 ? path11.replace(/\/+$/, "") : path11;
|
|
14191
|
-
}
|
|
14192
|
-
function issueSnapshotAliases(tasks, repoRoot) {
|
|
14193
|
-
const root = normalizedPath(repoRoot);
|
|
14194
|
-
const aliases = new Set([repoRoot]);
|
|
14195
|
-
for (const task of tasks) {
|
|
14196
|
-
const taskRepo = normalizedPath(task.repo);
|
|
14197
|
-
const taskWorktree = normalizedPath(task.worktreePath);
|
|
14198
|
-
if (taskRepo === root || taskWorktree === root) {
|
|
14199
|
-
if (task.repo)
|
|
14200
|
-
aliases.add(task.repo);
|
|
14201
|
-
if (task.worktreePath)
|
|
14202
|
-
aliases.add(task.worktreePath);
|
|
14203
|
-
}
|
|
14204
|
-
}
|
|
14205
|
-
return [...aliases];
|
|
14206
|
-
}
|
|
14207
14419
|
|
|
14208
14420
|
class DaemonLink {
|
|
14209
14421
|
client = null;
|
|
@@ -14347,7 +14559,7 @@ class DaemonLink {
|
|
|
14347
14559
|
case "issue.snapshot": {
|
|
14348
14560
|
const state = payload;
|
|
14349
14561
|
const next = { ...this.issueSnapshots };
|
|
14350
|
-
for (const alias of
|
|
14562
|
+
for (const alias of repoSnapshotAliases(this.tasks, state.repoRoot)) {
|
|
14351
14563
|
next[alias] = { ...state, repoRoot: alias };
|
|
14352
14564
|
}
|
|
14353
14565
|
this.issueSnapshots = next;
|
|
@@ -14388,11 +14600,11 @@ var init_daemon_link = __esm(() => {
|
|
|
14388
14600
|
});
|
|
14389
14601
|
|
|
14390
14602
|
// ../kobe-web/server/issue-assets-route.ts
|
|
14391
|
-
import { createHash as
|
|
14603
|
+
import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
|
|
14392
14604
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
14393
|
-
import { join as
|
|
14605
|
+
import { join as join14, resolve as resolve7 } from "path";
|
|
14394
14606
|
function repoHashOf(repoRoot) {
|
|
14395
|
-
return
|
|
14607
|
+
return createHash6("sha1").update(repoRoot).digest("hex").slice(0, 16);
|
|
14396
14608
|
}
|
|
14397
14609
|
async function handlePost(req) {
|
|
14398
14610
|
const declared = Number.parseInt(req.headers.get("content-length") ?? "", 10);
|
|
@@ -14422,11 +14634,11 @@ async function handlePost(req) {
|
|
|
14422
14634
|
}
|
|
14423
14635
|
try {
|
|
14424
14636
|
const repoHash = repoHashOf(repoRoot);
|
|
14425
|
-
const dir =
|
|
14637
|
+
const dir = join14(issueAssetsDir(), repoHash);
|
|
14426
14638
|
await mkdir9(dir, { recursive: true });
|
|
14427
|
-
const assetId =
|
|
14639
|
+
const assetId = randomUUID3();
|
|
14428
14640
|
const name = `${assetId}.${ext}`;
|
|
14429
|
-
await Bun.write(
|
|
14641
|
+
await Bun.write(join14(dir, name), file);
|
|
14430
14642
|
return Response.json({ url: `${ASSETS_ROUTE}/${repoHash}/${name}` });
|
|
14431
14643
|
} catch (err) {
|
|
14432
14644
|
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 });
|
|
@@ -14444,7 +14656,7 @@ async function handleGet2(pathname) {
|
|
|
14444
14656
|
}
|
|
14445
14657
|
const root = issueAssetsDir();
|
|
14446
14658
|
const resolved = resolve7(root, repoHash, fileSeg);
|
|
14447
|
-
if (resolved !==
|
|
14659
|
+
if (resolved !== join14(root, repoHash, fileSeg) || !resolved.startsWith(`${root}/`)) {
|
|
14448
14660
|
return Response.json({ error: "invalid asset path" }, { status: 400 });
|
|
14449
14661
|
}
|
|
14450
14662
|
const ext = fileSeg.slice(fileSeg.lastIndexOf(".") + 1).toLowerCase();
|
|
@@ -14644,7 +14856,21 @@ var init_session = __esm(() => {
|
|
|
14644
14856
|
|
|
14645
14857
|
// ../kobe-web/server/bridge.ts
|
|
14646
14858
|
import { existsSync as existsSync11 } from "fs";
|
|
14647
|
-
import { join as
|
|
14859
|
+
import { join as join15, normalize as normalize2 } from "path";
|
|
14860
|
+
function originAllowed(req, allowedHost) {
|
|
14861
|
+
const origin = req.headers.get("origin");
|
|
14862
|
+
if (!origin)
|
|
14863
|
+
return true;
|
|
14864
|
+
if (LOCAL_ORIGIN.test(origin))
|
|
14865
|
+
return true;
|
|
14866
|
+
if (allowedHost) {
|
|
14867
|
+
try {
|
|
14868
|
+
if (new URL(origin).hostname === allowedHost)
|
|
14869
|
+
return true;
|
|
14870
|
+
} catch {}
|
|
14871
|
+
}
|
|
14872
|
+
return false;
|
|
14873
|
+
}
|
|
14648
14874
|
function sseResponse(register) {
|
|
14649
14875
|
let unregister = null;
|
|
14650
14876
|
let heartbeat = null;
|
|
@@ -14719,6 +14945,9 @@ async function enginesResponse() {
|
|
|
14719
14945
|
function cliInvocationResponse() {
|
|
14720
14946
|
return Response.json({ api: kobeApiInvocation() });
|
|
14721
14947
|
}
|
|
14948
|
+
function projectsResponse() {
|
|
14949
|
+
return Response.json({ projects: getSavedRepos() });
|
|
14950
|
+
}
|
|
14722
14951
|
function stringValue(value, fallback = "") {
|
|
14723
14952
|
return typeof value === "string" ? value : fallback;
|
|
14724
14953
|
}
|
|
@@ -14867,6 +15096,9 @@ function createRequestHandler(deps) {
|
|
|
14867
15096
|
const url = new URL(req.url);
|
|
14868
15097
|
if (url.pathname === WEB_HEALTH_PATH)
|
|
14869
15098
|
return new Response(WEB_HEALTH_MARKER);
|
|
15099
|
+
if (!originAllowed(req, deps.allowedHost)) {
|
|
15100
|
+
return new Response("forbidden: cross-origin request rejected", { status: 403 });
|
|
15101
|
+
}
|
|
14870
15102
|
if (url.pathname === "/events") {
|
|
14871
15103
|
return sseResponse((send2) => {
|
|
14872
15104
|
send2("snapshot", link.snapshot());
|
|
@@ -14888,6 +15120,8 @@ function createRequestHandler(deps) {
|
|
|
14888
15120
|
return enginesResponse();
|
|
14889
15121
|
if (url.pathname === "/api/cli-invocation" && req.method === "GET")
|
|
14890
15122
|
return cliInvocationResponse();
|
|
15123
|
+
if (url.pathname === "/api/projects" && req.method === "GET")
|
|
15124
|
+
return projectsResponse();
|
|
14891
15125
|
if (url.pathname === "/api/settings" && req.method === "GET")
|
|
14892
15126
|
return settingsSnapshot();
|
|
14893
15127
|
if (url.pathname === "/api/settings" && req.method === "PATCH")
|
|
@@ -14939,10 +15173,10 @@ async function quickPromptsPut(req) {
|
|
|
14939
15173
|
}
|
|
14940
15174
|
async function staticResponse(pathname, staticDir) {
|
|
14941
15175
|
const rel = pathname === "/" ? "/index.html" : pathname;
|
|
14942
|
-
const resolved = normalize2(
|
|
15176
|
+
const resolved = normalize2(join15(staticDir, rel));
|
|
14943
15177
|
if (!resolved.startsWith(staticDir))
|
|
14944
15178
|
return new Response("forbidden", { status: 403 });
|
|
14945
|
-
const file = Bun.file(existsSync11(resolved) ? resolved :
|
|
15179
|
+
const file = Bun.file(existsSync11(resolved) ? resolved : join15(staticDir, "index.html"));
|
|
14946
15180
|
if (!await file.exists()) {
|
|
14947
15181
|
return new Response("kobe web assets not built \u2014 run `bun --filter kobe-web build`", { status: 503 });
|
|
14948
15182
|
}
|
|
@@ -15003,8 +15237,9 @@ async function createBridgeServer(opts = {}) {
|
|
|
15003
15237
|
for (const send2 of sseSends)
|
|
15004
15238
|
send2("snapshot", link.snapshot());
|
|
15005
15239
|
});
|
|
15006
|
-
const handle = createRequestHandler({ link, sseSends, staticDir });
|
|
15007
15240
|
const hostname = process.env.KOBE_WEB_HOST?.trim() || "127.0.0.1";
|
|
15241
|
+
const allowedHost = LOCAL_ORIGIN.test(`http://${hostname}`) ? undefined : hostname;
|
|
15242
|
+
const handle = createRequestHandler({ link, sseSends, staticDir, allowedHost });
|
|
15008
15243
|
const server = Bun.serve({ port, hostname, idleTimeout: 0, fetch: handle });
|
|
15009
15244
|
return {
|
|
15010
15245
|
port: server.port ?? port,
|
|
@@ -15014,7 +15249,7 @@ async function createBridgeServer(opts = {}) {
|
|
|
15014
15249
|
}
|
|
15015
15250
|
};
|
|
15016
15251
|
}
|
|
15017
|
-
var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", FOCUS_ACCENTS, ENGINE_ID_RE, QUICK_PROMPT_KEYS;
|
|
15252
|
+
var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", LOCAL_ORIGIN, FOCUS_ACCENTS, ENGINE_ID_RE, QUICK_PROMPT_KEYS;
|
|
15018
15253
|
var init_bridge = __esm(() => {
|
|
15019
15254
|
init_account_detect();
|
|
15020
15255
|
init_interactive_command();
|
|
@@ -15032,6 +15267,7 @@ var init_bridge = __esm(() => {
|
|
|
15032
15267
|
init_issue_assets_route();
|
|
15033
15268
|
init_rpc_allowlist();
|
|
15034
15269
|
init_session();
|
|
15270
|
+
LOCAL_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
|
|
15035
15271
|
FOCUS_ACCENTS = ["primary", "success", "info"];
|
|
15036
15272
|
ENGINE_ID_RE = /^[a-z][a-z0-9_-]{0,47}$/;
|
|
15037
15273
|
QUICK_PROMPT_KEYS = {
|
|
@@ -15337,7 +15573,7 @@ __export(exports_hook_cmd, {
|
|
|
15337
15573
|
ensureGlobalKobeHooks: () => ensureGlobalKobeHooks
|
|
15338
15574
|
});
|
|
15339
15575
|
import { homedir as homedir19 } from "os";
|
|
15340
|
-
import { join as
|
|
15576
|
+
import { join as join16, resolve as resolve9 } from "path";
|
|
15341
15577
|
async function readTextWithTimeout(read, timeoutMs = STDIN_READ_TIMEOUT_MS) {
|
|
15342
15578
|
let raceTimer;
|
|
15343
15579
|
try {
|
|
@@ -15468,7 +15704,7 @@ function activityHookAdapters() {
|
|
|
15468
15704
|
return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsHooks());
|
|
15469
15705
|
}
|
|
15470
15706
|
function globalSettingsPath() {
|
|
15471
|
-
return
|
|
15707
|
+
return join16(homedir19(), ".claude", "settings.json");
|
|
15472
15708
|
}
|
|
15473
15709
|
function persistedSyncPath(stored) {
|
|
15474
15710
|
if (!stored || stored === "off")
|
|
@@ -15476,7 +15712,7 @@ function persistedSyncPath(stored) {
|
|
|
15476
15712
|
if (stored === "global")
|
|
15477
15713
|
return globalSettingsPath();
|
|
15478
15714
|
if (stored.startsWith("repo:"))
|
|
15479
|
-
return
|
|
15715
|
+
return join16(resolve9(stored.slice(5)), ".claude", "settings.json");
|
|
15480
15716
|
return stored;
|
|
15481
15717
|
}
|
|
15482
15718
|
async function ensureGlobalKobeHooks() {
|
|
@@ -17213,6 +17449,7 @@ var init_remote_orchestrator = __esm(() => {
|
|
|
17213
17449
|
init_protocol();
|
|
17214
17450
|
init_dev();
|
|
17215
17451
|
init_worktree_changes();
|
|
17452
|
+
init_task();
|
|
17216
17453
|
init_version();
|
|
17217
17454
|
});
|
|
17218
17455
|
|
|
@@ -20406,9 +20643,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
|
|
|
20406
20643
|
var init_pulse = () => {};
|
|
20407
20644
|
|
|
20408
20645
|
// src/tui/lib/sound.ts
|
|
20409
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
20646
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "fs";
|
|
20410
20647
|
import { tmpdir as tmpdir2 } from "os";
|
|
20411
|
-
import { basename as basename6, isAbsolute as isAbsolute3, join as
|
|
20648
|
+
import { basename as basename6, isAbsolute as isAbsolute3, join as join18, resolve as resolve10 } from "path";
|
|
20412
20649
|
function args(player, file, volume) {
|
|
20413
20650
|
if (player === "ffplay")
|
|
20414
20651
|
return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
|
|
@@ -20431,13 +20668,13 @@ function pickPlayer() {
|
|
|
20431
20668
|
return cachedPlayer;
|
|
20432
20669
|
const path12 = process.env.PATH ?? "";
|
|
20433
20670
|
const segments = path12.split(":").filter(Boolean);
|
|
20434
|
-
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(
|
|
20671
|
+
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join18(dir, p)))) ?? null;
|
|
20435
20672
|
return cachedPlayer;
|
|
20436
20673
|
}
|
|
20437
20674
|
async function ensureAsset() {
|
|
20438
20675
|
cachedPath ??= (async () => {
|
|
20439
|
-
|
|
20440
|
-
const dest =
|
|
20676
|
+
mkdirSync7(DIR, { recursive: true });
|
|
20677
|
+
const dest = join18(DIR, basename6(pulseAsset));
|
|
20441
20678
|
const out = Bun.file(dest);
|
|
20442
20679
|
if (await out.exists())
|
|
20443
20680
|
return dest;
|
|
@@ -20467,7 +20704,7 @@ var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
|
|
|
20467
20704
|
var init_sound = __esm(() => {
|
|
20468
20705
|
init_pulse();
|
|
20469
20706
|
pulseAsset = isAbsolute3(pulse_default) ? pulse_default : resolve10(import.meta.dir, pulse_default);
|
|
20470
|
-
DIR =
|
|
20707
|
+
DIR = join18(tmpdir2(), "kobe-sfx");
|
|
20471
20708
|
PLAYERS = [
|
|
20472
20709
|
"ffplay",
|
|
20473
20710
|
"mpv",
|
|
@@ -20592,10 +20829,10 @@ var init_apply_ui_prefs = __esm(() => {
|
|
|
20592
20829
|
});
|
|
20593
20830
|
|
|
20594
20831
|
// src/tui/lib/persisted-ui-prefs.ts
|
|
20595
|
-
import { readFileSync as
|
|
20832
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
20596
20833
|
function readPersistedUiPrefs(fallbackTheme) {
|
|
20597
20834
|
try {
|
|
20598
|
-
const parsed = JSON.parse(
|
|
20835
|
+
const parsed = JSON.parse(readFileSync14(kvStatePath(), "utf8"));
|
|
20599
20836
|
const theme = typeof parsed.activeTheme === "string" && hasTheme(parsed.activeTheme) ? parsed.activeTheme : fallbackTheme;
|
|
20600
20837
|
const transparent = parsed.transparentBackground === true;
|
|
20601
20838
|
const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOTS.includes(parsed.focusAccent) ? parsed.focusAccent : null;
|
|
@@ -20871,6 +21108,7 @@ var init_host = __esm(() => {
|
|
|
20871
21108
|
init_remote_orchestrator();
|
|
20872
21109
|
init_account_detect();
|
|
20873
21110
|
init_repos();
|
|
21111
|
+
init_task();
|
|
20874
21112
|
init_new_task_dialog();
|
|
20875
21113
|
init_theme2();
|
|
20876
21114
|
init_host_boot();
|
|
@@ -21052,6 +21290,10 @@ async function deliverFirstPromptToTask(orch, task, repo, vendor, prompt) {
|
|
|
21052
21290
|
async function jumpToTask(orch, task, repo, vendor) {
|
|
21053
21291
|
await ensureTaskSession2(orch, task, repo, vendor);
|
|
21054
21292
|
await orch.setActiveTask(task.id).catch(() => {});
|
|
21293
|
+
const {
|
|
21294
|
+
prepareWindowForSwitch: prepareWindowForSwitch2
|
|
21295
|
+
} = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
21296
|
+
await prepareWindowForSwitch2(tmuxSessionName(task.id));
|
|
21055
21297
|
await runTmux(["switch-client", "-t", `=${tmuxSessionName(task.id)}`]);
|
|
21056
21298
|
}
|
|
21057
21299
|
function QuickTaskPage(props) {
|
|
@@ -21146,6 +21388,7 @@ var init_host2 = __esm(() => {
|
|
|
21146
21388
|
init_repos();
|
|
21147
21389
|
init_client2();
|
|
21148
21390
|
init_prompt_delivery();
|
|
21391
|
+
init_task();
|
|
21149
21392
|
init_quick_task_composer();
|
|
21150
21393
|
init_theme2();
|
|
21151
21394
|
init_git_snapshot();
|
|
@@ -21646,7 +21889,7 @@ var init_dialog_confirm = __esm(() => {
|
|
|
21646
21889
|
|
|
21647
21890
|
// src/tui/component/settings-dialog/actions.ts
|
|
21648
21891
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
21649
|
-
import { join as
|
|
21892
|
+
import { join as join19 } from "path";
|
|
21650
21893
|
function hasRestartableDaemon(orchestrator) {
|
|
21651
21894
|
return orchestrator instanceof RemoteOrchestrator;
|
|
21652
21895
|
}
|
|
@@ -21663,7 +21906,7 @@ async function confirmResetState(dialog, kv, renderer) {
|
|
|
21663
21906
|
return;
|
|
21664
21907
|
kv.clear();
|
|
21665
21908
|
try {
|
|
21666
|
-
unlinkSync2(
|
|
21909
|
+
unlinkSync2(join19(homeDir(), ".kobe", "tasks.json"));
|
|
21667
21910
|
} catch (err) {
|
|
21668
21911
|
if (err.code !== "ENOENT") {
|
|
21669
21912
|
console.error("kobe: failed to delete tasks.json during reset:", err);
|
|
@@ -23549,6 +23792,7 @@ var init_settings_dialog = __esm(() => {
|
|
|
23549
23792
|
init_auto_status();
|
|
23550
23793
|
init_dispatcher();
|
|
23551
23794
|
init_repos();
|
|
23795
|
+
init_task();
|
|
23552
23796
|
init_vendor();
|
|
23553
23797
|
init_theme2();
|
|
23554
23798
|
init_editor_prefs();
|
|
@@ -23965,6 +24209,7 @@ var init_task_actions = __esm(() => {
|
|
|
23965
24209
|
init_interactive_command();
|
|
23966
24210
|
init_errors();
|
|
23967
24211
|
init_repos();
|
|
24212
|
+
init_task();
|
|
23968
24213
|
init_vendor();
|
|
23969
24214
|
init_tmux();
|
|
23970
24215
|
});
|
|
@@ -23972,7 +24217,7 @@ var init_task_actions = __esm(() => {
|
|
|
23972
24217
|
// src/tui/lib/worktree-opener.ts
|
|
23973
24218
|
import { spawn as spawn5 } from "child_process";
|
|
23974
24219
|
import { existsSync as existsSync15 } from "fs";
|
|
23975
|
-
import { basename as basename7, delimiter, isAbsolute as isAbsolute4, join as
|
|
24220
|
+
import { basename as basename7, delimiter, isAbsolute as isAbsolute4, join as join20 } from "path";
|
|
23976
24221
|
function executableOnPath(command, env, exists) {
|
|
23977
24222
|
if (isAbsolute4(command))
|
|
23978
24223
|
return exists(command);
|
|
@@ -23980,7 +24225,7 @@ function executableOnPath(command, env, exists) {
|
|
|
23980
24225
|
for (const dir of pathEnv.split(delimiter)) {
|
|
23981
24226
|
if (!dir)
|
|
23982
24227
|
continue;
|
|
23983
|
-
if (exists(
|
|
24228
|
+
if (exists(join20(dir, command)))
|
|
23984
24229
|
return true;
|
|
23985
24230
|
}
|
|
23986
24231
|
return false;
|
|
@@ -24114,10 +24359,10 @@ var init_background_poll = __esm(() => {
|
|
|
24114
24359
|
|
|
24115
24360
|
// src/tui/panes/sidebar/git-head.ts
|
|
24116
24361
|
import { stat as stat5 } from "fs/promises";
|
|
24117
|
-
import { join as
|
|
24362
|
+
import { join as join21 } from "path";
|
|
24118
24363
|
async function headFingerprint(repo) {
|
|
24119
24364
|
try {
|
|
24120
|
-
const st = await stat5(
|
|
24365
|
+
const st = await stat5(join21(repo, ".git", "HEAD"));
|
|
24121
24366
|
return `${st.mtimeMs}:${st.size}`;
|
|
24122
24367
|
} catch {
|
|
24123
24368
|
return null;
|
|
@@ -24634,8 +24879,14 @@ function Sidebar(props) {
|
|
|
24634
24879
|
return;
|
|
24635
24880
|
}
|
|
24636
24881
|
const idx = ids.indexOf(id);
|
|
24637
|
-
if (idx >= 0
|
|
24638
|
-
|
|
24882
|
+
if (idx >= 0) {
|
|
24883
|
+
if (idx !== cur)
|
|
24884
|
+
setCursorIndex(idx);
|
|
24885
|
+
} else if (ids.length === 0) {
|
|
24886
|
+
setCursorIndex(-1);
|
|
24887
|
+
} else if (cur < 0 || cur >= ids.length) {
|
|
24888
|
+
setCursorIndex(ids.length - 1);
|
|
24889
|
+
}
|
|
24639
24890
|
}));
|
|
24640
24891
|
createEffect(on(view, () => {
|
|
24641
24892
|
const ids = flatIds();
|
|
@@ -25707,6 +25958,7 @@ function TasksShell(props) {
|
|
|
25707
25958
|
initPrompt: init3.initPrompt
|
|
25708
25959
|
});
|
|
25709
25960
|
}
|
|
25961
|
+
await prepareWindowForSwitch(name);
|
|
25710
25962
|
await runTmux(["switch-client", "-t", `=${name}`]);
|
|
25711
25963
|
props.orch?.setActiveTask(id).catch(() => {});
|
|
25712
25964
|
return;
|
|
@@ -25745,6 +25997,7 @@ function TasksShell(props) {
|
|
|
25745
25997
|
notifyError("Couldn't start this task's session");
|
|
25746
25998
|
return;
|
|
25747
25999
|
}
|
|
26000
|
+
await prepareWindowForSwitch(name);
|
|
25748
26001
|
await runTmux(["switch-client", "-t", `=${name}`]);
|
|
25749
26002
|
props.orch?.setActiveTask(id).catch(() => {});
|
|
25750
26003
|
}
|
|
@@ -28061,7 +28314,7 @@ __export(exports_host7, {
|
|
|
28061
28314
|
startOpsHost: () => startOpsHost,
|
|
28062
28315
|
nextActivityPollDelay: () => nextActivityPollDelay
|
|
28063
28316
|
});
|
|
28064
|
-
import { createHash as
|
|
28317
|
+
import { createHash as createHash7 } from "crypto";
|
|
28065
28318
|
import { SyntaxStyle } from "@opentui/core";
|
|
28066
28319
|
function nextActivityPollDelay(currentMs, idleStreak) {
|
|
28067
28320
|
if (idleStreak < ACTIVITY_IDLE_RAMP_POLLS)
|
|
@@ -28234,7 +28487,7 @@ function basename8(p) {
|
|
|
28234
28487
|
return i >= 0 ? p.slice(i + 1) : p;
|
|
28235
28488
|
}
|
|
28236
28489
|
function fingerprint(text) {
|
|
28237
|
-
return
|
|
28490
|
+
return createHash7("sha1").update(text).digest("hex");
|
|
28238
28491
|
}
|
|
28239
28492
|
async function startOpsHost(args2) {
|
|
28240
28493
|
await bootPaneHost({
|
|
@@ -29035,7 +29288,24 @@ async function main() {
|
|
|
29035
29288
|
process.exit(2);
|
|
29036
29289
|
}
|
|
29037
29290
|
const { healSessionLayout: healSessionLayout2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
29038
|
-
await
|
|
29291
|
+
const { coalesceLayoutWork: coalesceLayoutWork2 } = await Promise.resolve().then(() => (init_layout_coord(), exports_layout_coord));
|
|
29292
|
+
await coalesceLayoutWork2(session, "heal", () => healSessionLayout2(session));
|
|
29293
|
+
return;
|
|
29294
|
+
}
|
|
29295
|
+
if (subcommand === "capture-layout") {
|
|
29296
|
+
const flags = parseOpsFlags(rest);
|
|
29297
|
+
const session = flags.session;
|
|
29298
|
+
if (!session) {
|
|
29299
|
+
console.error("kobe capture-layout: --session <name> is required");
|
|
29300
|
+
process.exit(2);
|
|
29301
|
+
}
|
|
29302
|
+
const { captureGlobalLayoutOnDrag: captureGlobalLayoutOnDrag2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
29303
|
+
const { coalesceLayoutWork: coalesceLayoutWork2, genAgeMs: genAgeMs2, RESIZE_GUARD_MS: RESIZE_GUARD_MS2 } = await Promise.resolve().then(() => (init_layout_coord(), exports_layout_coord));
|
|
29304
|
+
await coalesceLayoutWork2(session, "capture", async () => {
|
|
29305
|
+
if (genAgeMs2(session, "resize") < RESIZE_GUARD_MS2)
|
|
29306
|
+
return;
|
|
29307
|
+
await captureGlobalLayoutOnDrag2(session);
|
|
29308
|
+
});
|
|
29039
29309
|
return;
|
|
29040
29310
|
}
|
|
29041
29311
|
if (subcommand === "quick-task") {
|