@sma1lboy/kobe 0.5.8 → 0.5.9
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/bin/kobed.js +140 -26
- package/dist/cli/index.js +567 -316
- package/package.json +1 -1
package/dist/bin/kobed.js
CHANGED
|
@@ -362,7 +362,8 @@ var init_binary = __esm(() => {
|
|
|
362
362
|
});
|
|
363
363
|
|
|
364
364
|
// src/engine/claude-code-local/history.ts
|
|
365
|
-
import {
|
|
365
|
+
import { randomUUID } from "crypto";
|
|
366
|
+
import { appendFile, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
|
|
366
367
|
import { homedir as homedir3 } from "os";
|
|
367
368
|
import path2 from "path";
|
|
368
369
|
function encodeCwd(cwd) {
|
|
@@ -459,6 +460,76 @@ function extractUsage(v) {
|
|
|
459
460
|
function isObject(v) {
|
|
460
461
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
461
462
|
}
|
|
463
|
+
async function appendInterruptedUserPrompt(sessionId, cwd, prompt, deps = defaultDeps2) {
|
|
464
|
+
if (!prompt || prompt.trim().length === 0)
|
|
465
|
+
return;
|
|
466
|
+
const projectDir = path2.join(deps.projectsDir(), encodeCwd(cwd));
|
|
467
|
+
const filePath = path2.join(projectDir, `${sessionId}.jsonl`);
|
|
468
|
+
let lines = [];
|
|
469
|
+
try {
|
|
470
|
+
const raw = await readFile(filePath, "utf8");
|
|
471
|
+
lines = raw.split(`
|
|
472
|
+
`).filter((l) => l.length > 0);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
if (err.code !== "ENOENT")
|
|
475
|
+
throw err;
|
|
476
|
+
await mkdir(projectDir, { recursive: true });
|
|
477
|
+
}
|
|
478
|
+
let lastConvIdx = -1;
|
|
479
|
+
let lastConvRecord = null;
|
|
480
|
+
let lastConvRole = null;
|
|
481
|
+
for (let i = lines.length - 1;i >= 0; i--) {
|
|
482
|
+
let parsed;
|
|
483
|
+
try {
|
|
484
|
+
parsed = JSON.parse(lines[i]);
|
|
485
|
+
} catch {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (!isObject(parsed))
|
|
489
|
+
continue;
|
|
490
|
+
const inner = isObject(parsed.message) ? parsed.message : parsed;
|
|
491
|
+
const role = inner.role;
|
|
492
|
+
if (role === "user" || role === "assistant") {
|
|
493
|
+
lastConvIdx = i;
|
|
494
|
+
lastConvRecord = parsed;
|
|
495
|
+
lastConvRole = role;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const now = new Date().toISOString();
|
|
500
|
+
if (lastConvRole === "user" && lastConvRecord && lastConvIdx >= 0) {
|
|
501
|
+
const inner = isObject(lastConvRecord.message) ? lastConvRecord.message : lastConvRecord;
|
|
502
|
+
const existing = typeof inner.content === "string" ? inner.content : "";
|
|
503
|
+
if (existing === prompt || existing.endsWith(`
|
|
504
|
+
|
|
505
|
+
${prompt}`))
|
|
506
|
+
return;
|
|
507
|
+
inner.content = existing.length > 0 ? `${existing}
|
|
508
|
+
|
|
509
|
+
${prompt}` : prompt;
|
|
510
|
+
lastConvRecord.timestamp = now;
|
|
511
|
+
lines[lastConvIdx] = JSON.stringify(lastConvRecord);
|
|
512
|
+
await writeFile(filePath, `${lines.join(`
|
|
513
|
+
`)}
|
|
514
|
+
`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const parentUuid = lastConvRecord && typeof lastConvRecord.uuid === "string" ? lastConvRecord.uuid : null;
|
|
518
|
+
const record = {
|
|
519
|
+
type: "user",
|
|
520
|
+
message: { role: "user", content: prompt },
|
|
521
|
+
uuid: randomUUID(),
|
|
522
|
+
parentUuid,
|
|
523
|
+
sessionId,
|
|
524
|
+
cwd,
|
|
525
|
+
timestamp: now,
|
|
526
|
+
isSidechain: false,
|
|
527
|
+
userType: "external",
|
|
528
|
+
version: "1.0.0"
|
|
529
|
+
};
|
|
530
|
+
await appendFile(filePath, `${JSON.stringify(record)}
|
|
531
|
+
`);
|
|
532
|
+
}
|
|
462
533
|
var defaultDeps2;
|
|
463
534
|
var init_history = __esm(() => {
|
|
464
535
|
defaultDeps2 = {
|
|
@@ -866,13 +937,21 @@ class ClaudeCodeLocal {
|
|
|
866
937
|
}
|
|
867
938
|
async stop(handle) {
|
|
868
939
|
const sid = handle.sessionId;
|
|
869
|
-
await this.registry.kill(sid, this.stopGraceMs);
|
|
870
940
|
const session = this.running.get(sid);
|
|
941
|
+
const shouldRescue = !!session && !session.completedNaturally && session.prompt.trim().length > 0;
|
|
942
|
+
const rescuePrompt = session?.prompt ?? "";
|
|
943
|
+
const rescueCwd = session?.cwd ?? handle.cwd;
|
|
944
|
+
await this.registry.kill(sid, this.stopGraceMs);
|
|
871
945
|
if (session) {
|
|
872
946
|
session.closed = true;
|
|
873
947
|
this.notify(session);
|
|
874
948
|
this.running.delete(sid);
|
|
875
949
|
}
|
|
950
|
+
if (shouldRescue) {
|
|
951
|
+
try {
|
|
952
|
+
await appendInterruptedUserPrompt(sid, rescueCwd, rescuePrompt);
|
|
953
|
+
} catch {}
|
|
954
|
+
}
|
|
876
955
|
}
|
|
877
956
|
async start(args) {
|
|
878
957
|
const binaryPath = await this.binaryPathResolver();
|
|
@@ -905,14 +984,17 @@ class ClaudeCodeLocal {
|
|
|
905
984
|
spawned,
|
|
906
985
|
queue,
|
|
907
986
|
waiters: [],
|
|
908
|
-
closed: false
|
|
987
|
+
closed: false,
|
|
988
|
+
completedNaturally: false,
|
|
989
|
+
prompt: args.prompt
|
|
909
990
|
};
|
|
910
991
|
this.running.set(sessionId, session);
|
|
911
992
|
this.registry.register({
|
|
912
993
|
sessionId,
|
|
913
994
|
cwd: args.cwd,
|
|
914
995
|
proc: spawned.proc,
|
|
915
|
-
startedAt: Date.now()
|
|
996
|
+
startedAt: Date.now(),
|
|
997
|
+
prompt: args.prompt
|
|
916
998
|
});
|
|
917
999
|
resolveHandle({ sessionId, cwd: args.cwd });
|
|
918
1000
|
};
|
|
@@ -934,6 +1016,9 @@ class ClaudeCodeLocal {
|
|
|
934
1016
|
try {
|
|
935
1017
|
for await (const ev of events) {
|
|
936
1018
|
queue.push(ev);
|
|
1019
|
+
if (ev.type === "done" && session) {
|
|
1020
|
+
session.completedNaturally = true;
|
|
1021
|
+
}
|
|
937
1022
|
if (session)
|
|
938
1023
|
this.notify(session);
|
|
939
1024
|
}
|
|
@@ -988,11 +1073,11 @@ var init_claude_code_local = __esm(() => {
|
|
|
988
1073
|
});
|
|
989
1074
|
|
|
990
1075
|
// src/orchestrator/bridge/server.ts
|
|
991
|
-
import { mkdir, unlink as unlink2 } from "fs/promises";
|
|
1076
|
+
import { mkdir as mkdir2, unlink as unlink2 } from "fs/promises";
|
|
992
1077
|
import { createServer } from "net";
|
|
993
1078
|
import { dirname as dirname2 } from "path";
|
|
994
1079
|
async function startBridgeServer(orch, socketPath) {
|
|
995
|
-
await
|
|
1080
|
+
await mkdir2(dirname2(socketPath), { recursive: true });
|
|
996
1081
|
await unlink2(socketPath).catch(() => {});
|
|
997
1082
|
const conns = new Set;
|
|
998
1083
|
const server = createServer((conn) => {
|
|
@@ -1125,7 +1210,7 @@ var exports_bridge = {};
|
|
|
1125
1210
|
__export(exports_bridge, {
|
|
1126
1211
|
startBridge: () => startBridge
|
|
1127
1212
|
});
|
|
1128
|
-
import { writeFile } from "fs/promises";
|
|
1213
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
1129
1214
|
import { homedir as homedir5 } from "os";
|
|
1130
1215
|
import { join as join3 } from "path";
|
|
1131
1216
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -1145,7 +1230,7 @@ async function startBridge(orch, opts = {}) {
|
|
|
1145
1230
|
}
|
|
1146
1231
|
}
|
|
1147
1232
|
};
|
|
1148
|
-
await
|
|
1233
|
+
await writeFile2(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf8");
|
|
1149
1234
|
process.env.KOBE_MCP_CONFIG = mcpConfigPath;
|
|
1150
1235
|
return {
|
|
1151
1236
|
socketPath,
|
|
@@ -3034,6 +3119,7 @@ class Orchestrator {
|
|
|
3034
3119
|
worktrees;
|
|
3035
3120
|
metadataSuggester;
|
|
3036
3121
|
handles = new Map;
|
|
3122
|
+
firstSpawnLatches = new Map;
|
|
3037
3123
|
subscribers = new Map;
|
|
3038
3124
|
pumps = new Map;
|
|
3039
3125
|
pendingInputBroker = new InMemoryPendingInputBroker;
|
|
@@ -3266,8 +3352,16 @@ class Orchestrator {
|
|
|
3266
3352
|
const renameTabId = this.resolveTab(task, tabId).id;
|
|
3267
3353
|
this.maybeRenameTempBranch(task.id, renameTabId, prompt);
|
|
3268
3354
|
}
|
|
3269
|
-
|
|
3355
|
+
let targetTab = this.resolveTab(task, tabId);
|
|
3270
3356
|
const key = tabKey(task.id, targetTab.id);
|
|
3357
|
+
if (!targetTab.sessionId) {
|
|
3358
|
+
const inflight = this.firstSpawnLatches.get(key);
|
|
3359
|
+
if (inflight) {
|
|
3360
|
+
await inflight.catch(() => {});
|
|
3361
|
+
task = this.requireTask(id);
|
|
3362
|
+
targetTab = this.resolveTab(task, tabId);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3271
3365
|
if (this.handles.has(key) === false) {
|
|
3272
3366
|
const running = this.countRunning();
|
|
3273
3367
|
if (running >= CONCURRENCY_CAP) {
|
|
@@ -3288,18 +3382,28 @@ class Orchestrator {
|
|
|
3288
3382
|
model: modelToUse
|
|
3289
3383
|
});
|
|
3290
3384
|
} else {
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3385
|
+
let releaseLatch = () => {};
|
|
3386
|
+
const latch = new Promise((resolve2) => {
|
|
3387
|
+
releaseLatch = resolve2;
|
|
3294
3388
|
});
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3389
|
+
this.firstSpawnLatches.set(key, latch);
|
|
3390
|
+
try {
|
|
3391
|
+
handle = await this.engine.spawn(task.worktreePath, promptToSend, {
|
|
3392
|
+
permissionMode: task.permissionMode,
|
|
3393
|
+
model: modelToUse
|
|
3394
|
+
});
|
|
3395
|
+
await this.updateTab(task.id, targetTab.id, { sessionId: handle.sessionId });
|
|
3396
|
+
if (task.title === PLACEHOLDER_TASK_TITLE && prompt && prompt.trim().length > 0) {
|
|
3397
|
+
const derived = deriveTitleFromPrompt(prompt);
|
|
3398
|
+
if (derived)
|
|
3399
|
+
await this.store.update(task.id, { title: derived });
|
|
3400
|
+
}
|
|
3401
|
+
if (prompt && prompt.trim().length > 0) {
|
|
3402
|
+
this.maybeUpgradeTitle(task.id, prompt);
|
|
3403
|
+
}
|
|
3404
|
+
} finally {
|
|
3405
|
+
releaseLatch();
|
|
3406
|
+
this.firstSpawnLatches.delete(key);
|
|
3303
3407
|
}
|
|
3304
3408
|
}
|
|
3305
3409
|
this.handles.set(key, handle);
|
|
@@ -3366,6 +3470,12 @@ class Orchestrator {
|
|
|
3366
3470
|
}
|
|
3367
3471
|
this.dispatchEvent(task.id, targetTab.id, { type: "done" });
|
|
3368
3472
|
}
|
|
3473
|
+
async steerTask(id, prompt, tabId) {
|
|
3474
|
+
const task = this.requireTask(id);
|
|
3475
|
+
const targetTab = this.resolveTab(task, tabId);
|
|
3476
|
+
await this.interruptTask(task.id, targetTab.id);
|
|
3477
|
+
await this.runTask(task.id, prompt, targetTab.id);
|
|
3478
|
+
}
|
|
3369
3479
|
async pauseTask(id) {
|
|
3370
3480
|
const task = this.requireTask(id);
|
|
3371
3481
|
if (task.status !== "in_progress") {
|
|
@@ -3796,7 +3906,7 @@ var init_core = __esm(() => {
|
|
|
3796
3906
|
});
|
|
3797
3907
|
|
|
3798
3908
|
// src/orchestrator/index/store.ts
|
|
3799
|
-
import { mkdir as
|
|
3909
|
+
import { mkdir as mkdir3, open, readFile as readFile3, rename, unlink as unlink3 } from "fs/promises";
|
|
3800
3910
|
import { homedir as homedir8 } from "os";
|
|
3801
3911
|
import { dirname as dirname4, join as join6 } from "path";
|
|
3802
3912
|
|
|
@@ -3870,7 +3980,7 @@ class TaskIndexStore {
|
|
|
3870
3980
|
return next;
|
|
3871
3981
|
}
|
|
3872
3982
|
async doSave() {
|
|
3873
|
-
await
|
|
3983
|
+
await mkdir3(dirname4(this.path), { recursive: true });
|
|
3874
3984
|
const payload = this.snapshot();
|
|
3875
3985
|
const json = `${JSON.stringify(payload, null, 2)}
|
|
3876
3986
|
`;
|
|
@@ -4421,7 +4531,7 @@ init_paths();
|
|
|
4421
4531
|
// src/daemon/server.ts
|
|
4422
4532
|
init_repos();
|
|
4423
4533
|
init_paths();
|
|
4424
|
-
import { mkdir as
|
|
4534
|
+
import { mkdir as mkdir4, readFile as readFile5, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
|
|
4425
4535
|
import { createServer as createServer2 } from "net";
|
|
4426
4536
|
import { dirname as dirname5 } from "path";
|
|
4427
4537
|
|
|
@@ -4761,8 +4871,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4761
4871
|
const startedAt = options.startedAt ?? new Date;
|
|
4762
4872
|
const clients = new Set;
|
|
4763
4873
|
let nextClientId = 1;
|
|
4764
|
-
await
|
|
4765
|
-
await
|
|
4874
|
+
await mkdir4(dirname5(socketPath), { recursive: true });
|
|
4875
|
+
await mkdir4(dirname5(pidPath), { recursive: true });
|
|
4766
4876
|
await unlink4(socketPath).catch(() => {});
|
|
4767
4877
|
const server = createServer2((socket) => {
|
|
4768
4878
|
const client = {
|
|
@@ -4820,7 +4930,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4820
4930
|
resolve2();
|
|
4821
4931
|
});
|
|
4822
4932
|
});
|
|
4823
|
-
await
|
|
4933
|
+
await writeFile4(pidPath, `${process.pid}
|
|
4824
4934
|
`, "utf8");
|
|
4825
4935
|
async function stopSoon() {
|
|
4826
4936
|
await options.onStop?.();
|
|
@@ -4990,6 +5100,10 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
4990
5100
|
await orch.interruptTask(requireString2(payload, "taskId"), optionalString2(payload, "tabId"));
|
|
4991
5101
|
return {};
|
|
4992
5102
|
}
|
|
5103
|
+
case "chat.steer": {
|
|
5104
|
+
await orch.steerTask(requireString2(payload, "taskId"), requireString2(payload, "text"), optionalString2(payload, "tabId"));
|
|
5105
|
+
return {};
|
|
5106
|
+
}
|
|
4993
5107
|
case "chat.input.pending": {
|
|
4994
5108
|
return { pending: orch.peekPendingInput(requireString2(payload, "taskId")) };
|
|
4995
5109
|
}
|