@sma1lboy/kobe 0.7.29 → 0.7.30
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 +432 -167
- package/dist/web-ui/assets/AppShell-DB2eNRP2.js +8 -0
- package/dist/web-ui/assets/{ChatTerminal-Dzrtw3pW.js → ChatTerminal-dv25rCIz.js} +2 -2
- package/dist/web-ui/assets/IssuePeek-Dh3qej2l.js +184 -0
- package/dist/web-ui/assets/ViewToggle-GsDQZY-j.js +1 -0
- package/dist/web-ui/assets/board-CbM3YF06.js +1 -0
- package/dist/web-ui/assets/index-LIEeaROP.css +2 -0
- package/dist/web-ui/assets/{index-DfvJr9CT.js → index-j-izWmuG.js} +4 -4
- package/dist/web-ui/assets/issues-BXc_TiwK.js +1 -0
- package/dist/web-ui/assets/routes-CS4Lj7ol.js +1 -0
- package/dist/web-ui/assets/{tabs-may5sub7.js → tabs-CmaDBSUQ.js} +1 -1
- package/dist/web-ui/assets/task._taskId-BQ5h5DZ2.js +1 -0
- package/dist/web-ui/assets/{useNavigate-1MDuT9d4.js → useNavigate-CXvSccc8.js} +1 -1
- package/dist/web-ui/assets/vendor-X3nYbtqQ.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/IssuePeek-C209KHal.js +0 -184
- 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.30",
|
|
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) {
|
|
@@ -7764,6 +7821,76 @@ var init_issues_store = __esm(() => {
|
|
|
7764
7821
|
locks = new Map;
|
|
7765
7822
|
});
|
|
7766
7823
|
|
|
7824
|
+
// ../kobe-daemon/src/daemon/lifetime.ts
|
|
7825
|
+
class DaemonLifetime {
|
|
7826
|
+
clients;
|
|
7827
|
+
idleGraceMs;
|
|
7828
|
+
onIdleStop;
|
|
7829
|
+
schedule;
|
|
7830
|
+
log;
|
|
7831
|
+
cancelIdle = null;
|
|
7832
|
+
stopping = false;
|
|
7833
|
+
constructor(options) {
|
|
7834
|
+
this.clients = options.clients;
|
|
7835
|
+
this.idleGraceMs = options.idleGraceMs;
|
|
7836
|
+
this.onIdleStop = options.onIdleStop;
|
|
7837
|
+
this.schedule = options.schedule ?? defaultSchedule;
|
|
7838
|
+
this.log = options.log ?? logDaemonInfo;
|
|
7839
|
+
}
|
|
7840
|
+
guiCount() {
|
|
7841
|
+
let n = 0;
|
|
7842
|
+
for (const c of this.clients())
|
|
7843
|
+
if (c.holdsLifetime)
|
|
7844
|
+
n++;
|
|
7845
|
+
return n;
|
|
7846
|
+
}
|
|
7847
|
+
hasSubscribers() {
|
|
7848
|
+
for (const c of this.clients())
|
|
7849
|
+
if (c.subscribed)
|
|
7850
|
+
return true;
|
|
7851
|
+
return false;
|
|
7852
|
+
}
|
|
7853
|
+
isStopping() {
|
|
7854
|
+
return this.stopping;
|
|
7855
|
+
}
|
|
7856
|
+
markStopping() {
|
|
7857
|
+
this.stopping = true;
|
|
7858
|
+
this.clearIdle();
|
|
7859
|
+
}
|
|
7860
|
+
guiAttached() {
|
|
7861
|
+
this.clearIdle();
|
|
7862
|
+
}
|
|
7863
|
+
clientDisconnected(wasGui) {
|
|
7864
|
+
if (wasGui)
|
|
7865
|
+
this.maybeArm();
|
|
7866
|
+
}
|
|
7867
|
+
clearIdle() {
|
|
7868
|
+
if (this.cancelIdle) {
|
|
7869
|
+
this.cancelIdle();
|
|
7870
|
+
this.cancelIdle = null;
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7873
|
+
maybeArm() {
|
|
7874
|
+
if (this.stopping || this.guiCount() > 0)
|
|
7875
|
+
return;
|
|
7876
|
+
this.clearIdle();
|
|
7877
|
+
this.log("idle", `last gui gone \u2014 arming ${this.idleGraceMs}ms idle-stop grace`);
|
|
7878
|
+
this.cancelIdle = this.schedule(() => {
|
|
7879
|
+
this.cancelIdle = null;
|
|
7880
|
+
if (this.stopping || this.guiCount() > 0)
|
|
7881
|
+
return;
|
|
7882
|
+
this.log("idle", "grace elapsed with no gui \u2014 self-stopping");
|
|
7883
|
+
this.onIdleStop();
|
|
7884
|
+
}, this.idleGraceMs);
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
var defaultSchedule = (fn, ms) => {
|
|
7888
|
+
const t = setTimeout(fn, ms);
|
|
7889
|
+
t.unref?.();
|
|
7890
|
+
return () => clearTimeout(t);
|
|
7891
|
+
};
|
|
7892
|
+
var init_lifetime = () => {};
|
|
7893
|
+
|
|
7767
7894
|
// ../kobe-daemon/src/daemon/keybindings-watcher.ts
|
|
7768
7895
|
import { mkdirSync as mkdirSync2, watch } from "fs";
|
|
7769
7896
|
import { homedir as homedir14 } 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
|
}
|
|
@@ -8381,6 +8476,7 @@ var init_server = __esm(() => {
|
|
|
8381
8476
|
init_auto_title_poller();
|
|
8382
8477
|
init_handlers();
|
|
8383
8478
|
init_issues_store();
|
|
8479
|
+
init_lifetime();
|
|
8384
8480
|
init_keybindings_watcher();
|
|
8385
8481
|
init_paths2();
|
|
8386
8482
|
init_protocol();
|
|
@@ -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;
|
|
@@ -14867,6 +15093,9 @@ function createRequestHandler(deps) {
|
|
|
14867
15093
|
const url = new URL(req.url);
|
|
14868
15094
|
if (url.pathname === WEB_HEALTH_PATH)
|
|
14869
15095
|
return new Response(WEB_HEALTH_MARKER);
|
|
15096
|
+
if (!originAllowed(req, deps.allowedHost)) {
|
|
15097
|
+
return new Response("forbidden: cross-origin request rejected", { status: 403 });
|
|
15098
|
+
}
|
|
14870
15099
|
if (url.pathname === "/events") {
|
|
14871
15100
|
return sseResponse((send2) => {
|
|
14872
15101
|
send2("snapshot", link.snapshot());
|
|
@@ -14939,10 +15168,10 @@ async function quickPromptsPut(req) {
|
|
|
14939
15168
|
}
|
|
14940
15169
|
async function staticResponse(pathname, staticDir) {
|
|
14941
15170
|
const rel = pathname === "/" ? "/index.html" : pathname;
|
|
14942
|
-
const resolved = normalize2(
|
|
15171
|
+
const resolved = normalize2(join15(staticDir, rel));
|
|
14943
15172
|
if (!resolved.startsWith(staticDir))
|
|
14944
15173
|
return new Response("forbidden", { status: 403 });
|
|
14945
|
-
const file = Bun.file(existsSync11(resolved) ? resolved :
|
|
15174
|
+
const file = Bun.file(existsSync11(resolved) ? resolved : join15(staticDir, "index.html"));
|
|
14946
15175
|
if (!await file.exists()) {
|
|
14947
15176
|
return new Response("kobe web assets not built \u2014 run `bun --filter kobe-web build`", { status: 503 });
|
|
14948
15177
|
}
|
|
@@ -15003,8 +15232,9 @@ async function createBridgeServer(opts = {}) {
|
|
|
15003
15232
|
for (const send2 of sseSends)
|
|
15004
15233
|
send2("snapshot", link.snapshot());
|
|
15005
15234
|
});
|
|
15006
|
-
const handle = createRequestHandler({ link, sseSends, staticDir });
|
|
15007
15235
|
const hostname = process.env.KOBE_WEB_HOST?.trim() || "127.0.0.1";
|
|
15236
|
+
const allowedHost = LOCAL_ORIGIN.test(`http://${hostname}`) ? undefined : hostname;
|
|
15237
|
+
const handle = createRequestHandler({ link, sseSends, staticDir, allowedHost });
|
|
15008
15238
|
const server = Bun.serve({ port, hostname, idleTimeout: 0, fetch: handle });
|
|
15009
15239
|
return {
|
|
15010
15240
|
port: server.port ?? port,
|
|
@@ -15014,7 +15244,7 @@ async function createBridgeServer(opts = {}) {
|
|
|
15014
15244
|
}
|
|
15015
15245
|
};
|
|
15016
15246
|
}
|
|
15017
|
-
var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", FOCUS_ACCENTS, ENGINE_ID_RE, QUICK_PROMPT_KEYS;
|
|
15247
|
+
var WEB_HEALTH_MARKER = "kobe-web", WEB_HEALTH_PATH = "/__kobe_web", LOCAL_ORIGIN, FOCUS_ACCENTS, ENGINE_ID_RE, QUICK_PROMPT_KEYS;
|
|
15018
15248
|
var init_bridge = __esm(() => {
|
|
15019
15249
|
init_account_detect();
|
|
15020
15250
|
init_interactive_command();
|
|
@@ -15032,6 +15262,7 @@ var init_bridge = __esm(() => {
|
|
|
15032
15262
|
init_issue_assets_route();
|
|
15033
15263
|
init_rpc_allowlist();
|
|
15034
15264
|
init_session();
|
|
15265
|
+
LOCAL_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
|
|
15035
15266
|
FOCUS_ACCENTS = ["primary", "success", "info"];
|
|
15036
15267
|
ENGINE_ID_RE = /^[a-z][a-z0-9_-]{0,47}$/;
|
|
15037
15268
|
QUICK_PROMPT_KEYS = {
|
|
@@ -15337,7 +15568,7 @@ __export(exports_hook_cmd, {
|
|
|
15337
15568
|
ensureGlobalKobeHooks: () => ensureGlobalKobeHooks
|
|
15338
15569
|
});
|
|
15339
15570
|
import { homedir as homedir19 } from "os";
|
|
15340
|
-
import { join as
|
|
15571
|
+
import { join as join16, resolve as resolve9 } from "path";
|
|
15341
15572
|
async function readTextWithTimeout(read, timeoutMs = STDIN_READ_TIMEOUT_MS) {
|
|
15342
15573
|
let raceTimer;
|
|
15343
15574
|
try {
|
|
@@ -15468,7 +15699,7 @@ function activityHookAdapters() {
|
|
|
15468
15699
|
return ALL_VENDORS.map((v) => createEngineHookAdapter(v)).filter((a) => a.supportsHooks());
|
|
15469
15700
|
}
|
|
15470
15701
|
function globalSettingsPath() {
|
|
15471
|
-
return
|
|
15702
|
+
return join16(homedir19(), ".claude", "settings.json");
|
|
15472
15703
|
}
|
|
15473
15704
|
function persistedSyncPath(stored) {
|
|
15474
15705
|
if (!stored || stored === "off")
|
|
@@ -15476,7 +15707,7 @@ function persistedSyncPath(stored) {
|
|
|
15476
15707
|
if (stored === "global")
|
|
15477
15708
|
return globalSettingsPath();
|
|
15478
15709
|
if (stored.startsWith("repo:"))
|
|
15479
|
-
return
|
|
15710
|
+
return join16(resolve9(stored.slice(5)), ".claude", "settings.json");
|
|
15480
15711
|
return stored;
|
|
15481
15712
|
}
|
|
15482
15713
|
async function ensureGlobalKobeHooks() {
|
|
@@ -17213,6 +17444,7 @@ var init_remote_orchestrator = __esm(() => {
|
|
|
17213
17444
|
init_protocol();
|
|
17214
17445
|
init_dev();
|
|
17215
17446
|
init_worktree_changes();
|
|
17447
|
+
init_task();
|
|
17216
17448
|
init_version();
|
|
17217
17449
|
});
|
|
17218
17450
|
|
|
@@ -20406,9 +20638,9 @@ var pulse_default = "../pulse-n3cq1btw.wav";
|
|
|
20406
20638
|
var init_pulse = () => {};
|
|
20407
20639
|
|
|
20408
20640
|
// src/tui/lib/sound.ts
|
|
20409
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
20641
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "fs";
|
|
20410
20642
|
import { tmpdir as tmpdir2 } from "os";
|
|
20411
|
-
import { basename as basename6, isAbsolute as isAbsolute3, join as
|
|
20643
|
+
import { basename as basename6, isAbsolute as isAbsolute3, join as join18, resolve as resolve10 } from "path";
|
|
20412
20644
|
function args(player, file, volume) {
|
|
20413
20645
|
if (player === "ffplay")
|
|
20414
20646
|
return [player, "-autoexit", "-nodisp", "-af", `volume=${volume}`, file];
|
|
@@ -20431,13 +20663,13 @@ function pickPlayer() {
|
|
|
20431
20663
|
return cachedPlayer;
|
|
20432
20664
|
const path12 = process.env.PATH ?? "";
|
|
20433
20665
|
const segments = path12.split(":").filter(Boolean);
|
|
20434
|
-
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(
|
|
20666
|
+
cachedPlayer = PLAYERS.find((p) => segments.some((dir) => existsSync14(join18(dir, p)))) ?? null;
|
|
20435
20667
|
return cachedPlayer;
|
|
20436
20668
|
}
|
|
20437
20669
|
async function ensureAsset() {
|
|
20438
20670
|
cachedPath ??= (async () => {
|
|
20439
|
-
|
|
20440
|
-
const dest =
|
|
20671
|
+
mkdirSync7(DIR, { recursive: true });
|
|
20672
|
+
const dest = join18(DIR, basename6(pulseAsset));
|
|
20441
20673
|
const out = Bun.file(dest);
|
|
20442
20674
|
if (await out.exists())
|
|
20443
20675
|
return dest;
|
|
@@ -20467,7 +20699,7 @@ var pulseAsset, DIR, PLAYERS, cachedPlayer, cachedPath;
|
|
|
20467
20699
|
var init_sound = __esm(() => {
|
|
20468
20700
|
init_pulse();
|
|
20469
20701
|
pulseAsset = isAbsolute3(pulse_default) ? pulse_default : resolve10(import.meta.dir, pulse_default);
|
|
20470
|
-
DIR =
|
|
20702
|
+
DIR = join18(tmpdir2(), "kobe-sfx");
|
|
20471
20703
|
PLAYERS = [
|
|
20472
20704
|
"ffplay",
|
|
20473
20705
|
"mpv",
|
|
@@ -20592,10 +20824,10 @@ var init_apply_ui_prefs = __esm(() => {
|
|
|
20592
20824
|
});
|
|
20593
20825
|
|
|
20594
20826
|
// src/tui/lib/persisted-ui-prefs.ts
|
|
20595
|
-
import { readFileSync as
|
|
20827
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
20596
20828
|
function readPersistedUiPrefs(fallbackTheme) {
|
|
20597
20829
|
try {
|
|
20598
|
-
const parsed = JSON.parse(
|
|
20830
|
+
const parsed = JSON.parse(readFileSync14(kvStatePath(), "utf8"));
|
|
20599
20831
|
const theme = typeof parsed.activeTheme === "string" && hasTheme(parsed.activeTheme) ? parsed.activeTheme : fallbackTheme;
|
|
20600
20832
|
const transparent = parsed.transparentBackground === true;
|
|
20601
20833
|
const focusAccent = typeof parsed.focusAccent === "string" && FOCUS_ACCENT_SLOTS.includes(parsed.focusAccent) ? parsed.focusAccent : null;
|
|
@@ -20871,6 +21103,7 @@ var init_host = __esm(() => {
|
|
|
20871
21103
|
init_remote_orchestrator();
|
|
20872
21104
|
init_account_detect();
|
|
20873
21105
|
init_repos();
|
|
21106
|
+
init_task();
|
|
20874
21107
|
init_new_task_dialog();
|
|
20875
21108
|
init_theme2();
|
|
20876
21109
|
init_host_boot();
|
|
@@ -21052,6 +21285,10 @@ async function deliverFirstPromptToTask(orch, task, repo, vendor, prompt) {
|
|
|
21052
21285
|
async function jumpToTask(orch, task, repo, vendor) {
|
|
21053
21286
|
await ensureTaskSession2(orch, task, repo, vendor);
|
|
21054
21287
|
await orch.setActiveTask(task.id).catch(() => {});
|
|
21288
|
+
const {
|
|
21289
|
+
prepareWindowForSwitch: prepareWindowForSwitch2
|
|
21290
|
+
} = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
21291
|
+
await prepareWindowForSwitch2(tmuxSessionName(task.id));
|
|
21055
21292
|
await runTmux(["switch-client", "-t", `=${tmuxSessionName(task.id)}`]);
|
|
21056
21293
|
}
|
|
21057
21294
|
function QuickTaskPage(props) {
|
|
@@ -21146,6 +21383,7 @@ var init_host2 = __esm(() => {
|
|
|
21146
21383
|
init_repos();
|
|
21147
21384
|
init_client2();
|
|
21148
21385
|
init_prompt_delivery();
|
|
21386
|
+
init_task();
|
|
21149
21387
|
init_quick_task_composer();
|
|
21150
21388
|
init_theme2();
|
|
21151
21389
|
init_git_snapshot();
|
|
@@ -21646,7 +21884,7 @@ var init_dialog_confirm = __esm(() => {
|
|
|
21646
21884
|
|
|
21647
21885
|
// src/tui/component/settings-dialog/actions.ts
|
|
21648
21886
|
import { unlinkSync as unlinkSync2 } from "fs";
|
|
21649
|
-
import { join as
|
|
21887
|
+
import { join as join19 } from "path";
|
|
21650
21888
|
function hasRestartableDaemon(orchestrator) {
|
|
21651
21889
|
return orchestrator instanceof RemoteOrchestrator;
|
|
21652
21890
|
}
|
|
@@ -21663,7 +21901,7 @@ async function confirmResetState(dialog, kv, renderer) {
|
|
|
21663
21901
|
return;
|
|
21664
21902
|
kv.clear();
|
|
21665
21903
|
try {
|
|
21666
|
-
unlinkSync2(
|
|
21904
|
+
unlinkSync2(join19(homeDir(), ".kobe", "tasks.json"));
|
|
21667
21905
|
} catch (err) {
|
|
21668
21906
|
if (err.code !== "ENOENT") {
|
|
21669
21907
|
console.error("kobe: failed to delete tasks.json during reset:", err);
|
|
@@ -23549,6 +23787,7 @@ var init_settings_dialog = __esm(() => {
|
|
|
23549
23787
|
init_auto_status();
|
|
23550
23788
|
init_dispatcher();
|
|
23551
23789
|
init_repos();
|
|
23790
|
+
init_task();
|
|
23552
23791
|
init_vendor();
|
|
23553
23792
|
init_theme2();
|
|
23554
23793
|
init_editor_prefs();
|
|
@@ -23965,6 +24204,7 @@ var init_task_actions = __esm(() => {
|
|
|
23965
24204
|
init_interactive_command();
|
|
23966
24205
|
init_errors();
|
|
23967
24206
|
init_repos();
|
|
24207
|
+
init_task();
|
|
23968
24208
|
init_vendor();
|
|
23969
24209
|
init_tmux();
|
|
23970
24210
|
});
|
|
@@ -23972,7 +24212,7 @@ var init_task_actions = __esm(() => {
|
|
|
23972
24212
|
// src/tui/lib/worktree-opener.ts
|
|
23973
24213
|
import { spawn as spawn5 } from "child_process";
|
|
23974
24214
|
import { existsSync as existsSync15 } from "fs";
|
|
23975
|
-
import { basename as basename7, delimiter, isAbsolute as isAbsolute4, join as
|
|
24215
|
+
import { basename as basename7, delimiter, isAbsolute as isAbsolute4, join as join20 } from "path";
|
|
23976
24216
|
function executableOnPath(command, env, exists) {
|
|
23977
24217
|
if (isAbsolute4(command))
|
|
23978
24218
|
return exists(command);
|
|
@@ -23980,7 +24220,7 @@ function executableOnPath(command, env, exists) {
|
|
|
23980
24220
|
for (const dir of pathEnv.split(delimiter)) {
|
|
23981
24221
|
if (!dir)
|
|
23982
24222
|
continue;
|
|
23983
|
-
if (exists(
|
|
24223
|
+
if (exists(join20(dir, command)))
|
|
23984
24224
|
return true;
|
|
23985
24225
|
}
|
|
23986
24226
|
return false;
|
|
@@ -24114,10 +24354,10 @@ var init_background_poll = __esm(() => {
|
|
|
24114
24354
|
|
|
24115
24355
|
// src/tui/panes/sidebar/git-head.ts
|
|
24116
24356
|
import { stat as stat5 } from "fs/promises";
|
|
24117
|
-
import { join as
|
|
24357
|
+
import { join as join21 } from "path";
|
|
24118
24358
|
async function headFingerprint(repo) {
|
|
24119
24359
|
try {
|
|
24120
|
-
const st = await stat5(
|
|
24360
|
+
const st = await stat5(join21(repo, ".git", "HEAD"));
|
|
24121
24361
|
return `${st.mtimeMs}:${st.size}`;
|
|
24122
24362
|
} catch {
|
|
24123
24363
|
return null;
|
|
@@ -24634,8 +24874,14 @@ function Sidebar(props) {
|
|
|
24634
24874
|
return;
|
|
24635
24875
|
}
|
|
24636
24876
|
const idx = ids.indexOf(id);
|
|
24637
|
-
if (idx >= 0
|
|
24638
|
-
|
|
24877
|
+
if (idx >= 0) {
|
|
24878
|
+
if (idx !== cur)
|
|
24879
|
+
setCursorIndex(idx);
|
|
24880
|
+
} else if (ids.length === 0) {
|
|
24881
|
+
setCursorIndex(-1);
|
|
24882
|
+
} else if (cur < 0 || cur >= ids.length) {
|
|
24883
|
+
setCursorIndex(ids.length - 1);
|
|
24884
|
+
}
|
|
24639
24885
|
}));
|
|
24640
24886
|
createEffect(on(view, () => {
|
|
24641
24887
|
const ids = flatIds();
|
|
@@ -25707,6 +25953,7 @@ function TasksShell(props) {
|
|
|
25707
25953
|
initPrompt: init3.initPrompt
|
|
25708
25954
|
});
|
|
25709
25955
|
}
|
|
25956
|
+
await prepareWindowForSwitch(name);
|
|
25710
25957
|
await runTmux(["switch-client", "-t", `=${name}`]);
|
|
25711
25958
|
props.orch?.setActiveTask(id).catch(() => {});
|
|
25712
25959
|
return;
|
|
@@ -25745,6 +25992,7 @@ function TasksShell(props) {
|
|
|
25745
25992
|
notifyError("Couldn't start this task's session");
|
|
25746
25993
|
return;
|
|
25747
25994
|
}
|
|
25995
|
+
await prepareWindowForSwitch(name);
|
|
25748
25996
|
await runTmux(["switch-client", "-t", `=${name}`]);
|
|
25749
25997
|
props.orch?.setActiveTask(id).catch(() => {});
|
|
25750
25998
|
}
|
|
@@ -28061,7 +28309,7 @@ __export(exports_host7, {
|
|
|
28061
28309
|
startOpsHost: () => startOpsHost,
|
|
28062
28310
|
nextActivityPollDelay: () => nextActivityPollDelay
|
|
28063
28311
|
});
|
|
28064
|
-
import { createHash as
|
|
28312
|
+
import { createHash as createHash7 } from "crypto";
|
|
28065
28313
|
import { SyntaxStyle } from "@opentui/core";
|
|
28066
28314
|
function nextActivityPollDelay(currentMs, idleStreak) {
|
|
28067
28315
|
if (idleStreak < ACTIVITY_IDLE_RAMP_POLLS)
|
|
@@ -28234,7 +28482,7 @@ function basename8(p) {
|
|
|
28234
28482
|
return i >= 0 ? p.slice(i + 1) : p;
|
|
28235
28483
|
}
|
|
28236
28484
|
function fingerprint(text) {
|
|
28237
|
-
return
|
|
28485
|
+
return createHash7("sha1").update(text).digest("hex");
|
|
28238
28486
|
}
|
|
28239
28487
|
async function startOpsHost(args2) {
|
|
28240
28488
|
await bootPaneHost({
|
|
@@ -29035,7 +29283,24 @@ async function main() {
|
|
|
29035
29283
|
process.exit(2);
|
|
29036
29284
|
}
|
|
29037
29285
|
const { healSessionLayout: healSessionLayout2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
29038
|
-
await
|
|
29286
|
+
const { coalesceLayoutWork: coalesceLayoutWork2 } = await Promise.resolve().then(() => (init_layout_coord(), exports_layout_coord));
|
|
29287
|
+
await coalesceLayoutWork2(session, "heal", () => healSessionLayout2(session));
|
|
29288
|
+
return;
|
|
29289
|
+
}
|
|
29290
|
+
if (subcommand === "capture-layout") {
|
|
29291
|
+
const flags = parseOpsFlags(rest);
|
|
29292
|
+
const session = flags.session;
|
|
29293
|
+
if (!session) {
|
|
29294
|
+
console.error("kobe capture-layout: --session <name> is required");
|
|
29295
|
+
process.exit(2);
|
|
29296
|
+
}
|
|
29297
|
+
const { captureGlobalLayoutOnDrag: captureGlobalLayoutOnDrag2 } = await Promise.resolve().then(() => (init_tmux(), exports_tmux));
|
|
29298
|
+
const { coalesceLayoutWork: coalesceLayoutWork2, genAgeMs: genAgeMs2, RESIZE_GUARD_MS: RESIZE_GUARD_MS2 } = await Promise.resolve().then(() => (init_layout_coord(), exports_layout_coord));
|
|
29299
|
+
await coalesceLayoutWork2(session, "capture", async () => {
|
|
29300
|
+
if (genAgeMs2(session, "resize") < RESIZE_GUARD_MS2)
|
|
29301
|
+
return;
|
|
29302
|
+
await captureGlobalLayoutOnDrag2(session);
|
|
29303
|
+
});
|
|
29039
29304
|
return;
|
|
29040
29305
|
}
|
|
29041
29306
|
if (subcommand === "quick-task") {
|