@sma1lboy/kobe 0.5.15 → 0.5.16
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 +1554 -310
- package/dist/cli/index.js +2404 -762
- package/package.json +3 -1
package/dist/bin/kobed.js
CHANGED
|
@@ -48,17 +48,38 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
50
|
// src/daemon/paths.ts
|
|
51
|
+
import { createHash } from "crypto";
|
|
51
52
|
import { homedir, tmpdir } from "os";
|
|
52
53
|
import { join } from "path";
|
|
53
|
-
function
|
|
54
|
+
function shortHomeTag(homeDir) {
|
|
55
|
+
return createHash("sha1").update(homeDir).digest("hex").slice(0, 8);
|
|
56
|
+
}
|
|
57
|
+
function fitSocketPath(naturalPath, homeDir, role, pidTag) {
|
|
58
|
+
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
59
|
+
return naturalPath;
|
|
60
|
+
const tag = shortHomeTag(homeDir);
|
|
61
|
+
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
62
|
+
const fallback = join(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
63
|
+
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
64
|
+
return fallback;
|
|
65
|
+
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
66
|
+
}
|
|
67
|
+
function defaultDaemonSocketPath(homeDir) {
|
|
68
|
+
const explicit = homeDir ?? process.env.KOBE_HOME_DIR;
|
|
69
|
+
if (explicit && explicit.length > 0) {
|
|
70
|
+
return fitSocketPath(join(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
71
|
+
}
|
|
54
72
|
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
55
|
-
if (runtimeDir && runtimeDir.length > 0)
|
|
56
|
-
return join(runtimeDir, "kobe.sock");
|
|
57
|
-
|
|
73
|
+
if (runtimeDir && runtimeDir.length > 0) {
|
|
74
|
+
return fitSocketPath(join(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
75
|
+
}
|
|
76
|
+
const home = homedir();
|
|
77
|
+
return fitSocketPath(join(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
58
78
|
}
|
|
59
79
|
function defaultDaemonPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
|
|
60
80
|
return join(homeDir, ".kobe", "daemon.pid");
|
|
61
81
|
}
|
|
82
|
+
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
62
83
|
var init_paths = () => {};
|
|
63
84
|
|
|
64
85
|
// src/daemon/protocol.ts
|
|
@@ -78,6 +99,7 @@ function serializeTask(task) {
|
|
|
78
99
|
pinned: task.pinned ?? false,
|
|
79
100
|
permissionMode: task.permissionMode,
|
|
80
101
|
model: task.model,
|
|
102
|
+
vendor: task.vendor,
|
|
81
103
|
createdAt: task.createdAt,
|
|
82
104
|
updatedAt: task.updatedAt
|
|
83
105
|
};
|
|
@@ -345,6 +367,86 @@ var init_daemon_process = __esm(() => {
|
|
|
345
367
|
init_client();
|
|
346
368
|
});
|
|
347
369
|
|
|
370
|
+
// src/session/usage-metrics.ts
|
|
371
|
+
function totalContextTokens(u) {
|
|
372
|
+
return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
373
|
+
}
|
|
374
|
+
function parseTimestampMs(value) {
|
|
375
|
+
const ms = new Date(value).getTime();
|
|
376
|
+
return Number.isFinite(ms) ? ms : null;
|
|
377
|
+
}
|
|
378
|
+
function mergeIntervals(intervals) {
|
|
379
|
+
if (intervals.length === 0)
|
|
380
|
+
return [];
|
|
381
|
+
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
382
|
+
const first = sorted[0];
|
|
383
|
+
if (!first)
|
|
384
|
+
return [];
|
|
385
|
+
const merged = [{ startMs: first.startMs, endMs: first.endMs }];
|
|
386
|
+
for (let i = 1;i < sorted.length; i++) {
|
|
387
|
+
const current = sorted[i];
|
|
388
|
+
const last = merged[merged.length - 1];
|
|
389
|
+
if (!current || !last)
|
|
390
|
+
continue;
|
|
391
|
+
if (current.startMs <= last.endMs) {
|
|
392
|
+
last.endMs = Math.max(last.endMs, current.endMs);
|
|
393
|
+
} else {
|
|
394
|
+
merged.push({ startMs: current.startMs, endMs: current.endMs });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return merged;
|
|
398
|
+
}
|
|
399
|
+
function durationMs(intervals) {
|
|
400
|
+
return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
|
|
401
|
+
}
|
|
402
|
+
function deriveSessionUsageMetrics(past) {
|
|
403
|
+
let latestUsage;
|
|
404
|
+
let latestUsageTimestampMs = null;
|
|
405
|
+
let lastUserTimestampMs = null;
|
|
406
|
+
let inputTokens = 0;
|
|
407
|
+
let outputTokens = 0;
|
|
408
|
+
const intervals = [];
|
|
409
|
+
for (const message of past) {
|
|
410
|
+
const timestampMs = parseTimestampMs(message.timestamp);
|
|
411
|
+
if (message.role === "user" && timestampMs !== null) {
|
|
412
|
+
lastUserTimestampMs = timestampMs;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (message.role !== "assistant" || !message.usage)
|
|
416
|
+
continue;
|
|
417
|
+
if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
|
|
418
|
+
latestUsageTimestampMs = timestampMs;
|
|
419
|
+
latestUsage = message.usage;
|
|
420
|
+
} else if (latestUsage === undefined) {
|
|
421
|
+
latestUsage = message.usage;
|
|
422
|
+
}
|
|
423
|
+
inputTokens += message.usage.input_tokens;
|
|
424
|
+
outputTokens += message.usage.output_tokens;
|
|
425
|
+
if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
|
|
426
|
+
intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (!latestUsage)
|
|
430
|
+
return;
|
|
431
|
+
const totalDurationMs = durationMs(mergeIntervals(intervals));
|
|
432
|
+
if (totalDurationMs <= 0)
|
|
433
|
+
return latestUsage;
|
|
434
|
+
return {
|
|
435
|
+
...latestUsage,
|
|
436
|
+
total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
|
|
440
|
+
const startMs = startedAtIso ? parseTimestampMs(startedAtIso) : null;
|
|
441
|
+
const endMs = parseTimestampMs(endedAtIso);
|
|
442
|
+
if (startMs === null || endMs === null || endMs <= startMs)
|
|
443
|
+
return usage;
|
|
444
|
+
return {
|
|
445
|
+
...usage,
|
|
446
|
+
total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
348
450
|
// src/engine/claude-code-local/binary.ts
|
|
349
451
|
import { spawnSync } from "child_process";
|
|
350
452
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
@@ -449,10 +551,152 @@ var init_binary = __esm(() => {
|
|
|
449
551
|
};
|
|
450
552
|
});
|
|
451
553
|
|
|
554
|
+
// src/engine/claude-code-local/models.ts
|
|
555
|
+
function parseContextWindowSize(modelIdentifier) {
|
|
556
|
+
const delimitedMatch = /(?:\(|\[)\s*(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])\s*(?:\)|\])/i.exec(modelIdentifier);
|
|
557
|
+
if (delimitedMatch?.[1] && delimitedMatch[2]) {
|
|
558
|
+
const parsed2 = Number.parseFloat(delimitedMatch[1].replace(/[,_]/g, ""));
|
|
559
|
+
if (Number.isFinite(parsed2) && parsed2 > 0) {
|
|
560
|
+
return Math.round(parsed2 * (delimitedMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const contextMatch = /\b(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])(?:\s*(?:token\s*)?context)?\b/i.exec(modelIdentifier);
|
|
564
|
+
if (!contextMatch?.[1] || !contextMatch[2])
|
|
565
|
+
return null;
|
|
566
|
+
const parsed = Number.parseFloat(contextMatch[1].replace(/[,_]/g, ""));
|
|
567
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
568
|
+
return null;
|
|
569
|
+
return Math.round(parsed * (contextMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
|
|
570
|
+
}
|
|
571
|
+
function claudeContextWindowFor(modelId) {
|
|
572
|
+
const parsed = parseContextWindowSize(modelId);
|
|
573
|
+
if (parsed !== null)
|
|
574
|
+
return parsed;
|
|
575
|
+
if (modelId.includes("[1m]"))
|
|
576
|
+
return LONG_CTX;
|
|
577
|
+
const inCatalog = CLAUDE_MODELS.some((m) => m.id === modelId);
|
|
578
|
+
if (inCatalog)
|
|
579
|
+
return STD_CTX;
|
|
580
|
+
if (modelId.includes("1m") || modelId.includes("[1M]"))
|
|
581
|
+
return LONG_CTX;
|
|
582
|
+
return STD_CTX;
|
|
583
|
+
}
|
|
584
|
+
var CLAUDE_MODELS, LONG_CTX = 1e6, STD_CTX = 200000;
|
|
585
|
+
var init_models = __esm(() => {
|
|
586
|
+
CLAUDE_MODELS = [
|
|
587
|
+
{ vendor: "claude", id: "claude-opus-4-7[1m]", label: "Opus 4.7 1M", hint: "long context, default" },
|
|
588
|
+
{ vendor: "claude", id: "claude-opus-4-7", label: "Opus 4.7", hint: "most capable, slowest" },
|
|
589
|
+
{ vendor: "claude", id: "claude-sonnet-4-6[1m]", label: "sonnet 4.6 (1M)", hint: "long context" },
|
|
590
|
+
{ vendor: "claude", id: "claude-sonnet-4-6", label: "sonnet 4.6" },
|
|
591
|
+
{ vendor: "claude", id: "claude-haiku-4-5-20251001", label: "haiku 4.5", hint: "fastest, cheapest" }
|
|
592
|
+
];
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// src/engine/claude-code-local/settings.ts
|
|
596
|
+
import { readFileSync } from "fs";
|
|
597
|
+
import { homedir as homedir3 } from "os";
|
|
598
|
+
import { join as join3 } from "path";
|
|
599
|
+
function readClaudeSettings() {
|
|
600
|
+
if (cached !== undefined)
|
|
601
|
+
return cached;
|
|
602
|
+
try {
|
|
603
|
+
const raw = readFileSync(SETTINGS_PATH, "utf8");
|
|
604
|
+
const parsed = JSON.parse(raw);
|
|
605
|
+
if (!parsed || typeof parsed !== "object") {
|
|
606
|
+
cached = null;
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const obj = parsed;
|
|
610
|
+
const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
|
|
611
|
+
cached = { model };
|
|
612
|
+
return cached;
|
|
613
|
+
} catch {
|
|
614
|
+
cached = null;
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function resolveClaudeDefaultModelId() {
|
|
619
|
+
const settings = readClaudeSettings();
|
|
620
|
+
if (settings?.model && settings.model.length > 0)
|
|
621
|
+
return settings.model;
|
|
622
|
+
return CLAUDE_FALLBACK_DEFAULT_MODEL_ID;
|
|
623
|
+
}
|
|
624
|
+
var SETTINGS_PATH, cached, CLAUDE_FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
|
|
625
|
+
var init_settings = __esm(() => {
|
|
626
|
+
SETTINGS_PATH = join3(homedir3(), ".claude", "settings.json");
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// src/engine/claude-code-local/capabilities.ts
|
|
630
|
+
var claudeCapabilities, claudeIdentity;
|
|
631
|
+
var init_capabilities = __esm(() => {
|
|
632
|
+
init_models();
|
|
633
|
+
init_settings();
|
|
634
|
+
claudeCapabilities = {
|
|
635
|
+
vendorId: "claude",
|
|
636
|
+
label: "Claude Code",
|
|
637
|
+
models: CLAUDE_MODELS,
|
|
638
|
+
defaultModelId: resolveClaudeDefaultModelId,
|
|
639
|
+
contextWindowFor: claudeContextWindowFor
|
|
640
|
+
};
|
|
641
|
+
claudeIdentity = {
|
|
642
|
+
vendorId: "claude",
|
|
643
|
+
productName: "Claude Code",
|
|
644
|
+
shortName: "Claude",
|
|
645
|
+
assistantName: "Claude",
|
|
646
|
+
inputPlaceholder: "Ask Claude\u2026"
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// src/engine/claude-code-local/normalize.ts
|
|
651
|
+
function normalizeClaudeContent(content) {
|
|
652
|
+
if (typeof content === "string") {
|
|
653
|
+
return content.length > 0 ? [{ type: "text", text: content }] : [];
|
|
654
|
+
}
|
|
655
|
+
if (!Array.isArray(content))
|
|
656
|
+
return [];
|
|
657
|
+
const out = [];
|
|
658
|
+
for (const block of content) {
|
|
659
|
+
if (typeof block === "string") {
|
|
660
|
+
if (block.length > 0)
|
|
661
|
+
out.push({ type: "text", text: block });
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (!block || typeof block !== "object")
|
|
665
|
+
continue;
|
|
666
|
+
const b = block;
|
|
667
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
668
|
+
out.push({ type: "text", text: b.text });
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (b.type === "tool_use") {
|
|
672
|
+
out.push({
|
|
673
|
+
type: "tool_call",
|
|
674
|
+
callId: typeof b.id === "string" ? b.id : "",
|
|
675
|
+
name: typeof b.name === "string" ? b.name : "",
|
|
676
|
+
input: b.input
|
|
677
|
+
});
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (b.type === "tool_result") {
|
|
681
|
+
out.push({
|
|
682
|
+
type: "tool_result",
|
|
683
|
+
callId: typeof b.tool_use_id === "string" ? b.tool_use_id : "",
|
|
684
|
+
output: b.content,
|
|
685
|
+
isError: b.is_error === true
|
|
686
|
+
});
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (b.type === "thinking" && typeof b.thinking === "string") {
|
|
690
|
+
out.push({ type: "thinking", text: b.thinking });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
|
|
452
696
|
// src/engine/claude-code-local/history.ts
|
|
453
697
|
import { randomUUID } from "crypto";
|
|
454
698
|
import { appendFile, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
|
|
455
|
-
import { homedir as
|
|
699
|
+
import { homedir as homedir4 } from "os";
|
|
456
700
|
import path2 from "path";
|
|
457
701
|
function encodeCwd(cwd) {
|
|
458
702
|
return cwd.replace(/[/.]/g, "-");
|
|
@@ -523,11 +767,11 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
523
767
|
return null;
|
|
524
768
|
if (!("content" in inner))
|
|
525
769
|
return null;
|
|
526
|
-
const
|
|
770
|
+
const blocks = normalizeClaudeContent(inner.content);
|
|
527
771
|
const ts = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
528
772
|
const sid = typeof record.sessionId === "string" ? record.sessionId : fallbackSessionId;
|
|
529
773
|
const usage = extractUsage(inner.usage);
|
|
530
|
-
return usage ? { role,
|
|
774
|
+
return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
|
|
531
775
|
}
|
|
532
776
|
function extractUsage(v) {
|
|
533
777
|
if (!isObject(v))
|
|
@@ -622,7 +866,7 @@ var defaultDeps2;
|
|
|
622
866
|
var init_history = __esm(() => {
|
|
623
867
|
defaultDeps2 = {
|
|
624
868
|
projectsDir() {
|
|
625
|
-
return path2.join(
|
|
869
|
+
return path2.join(homedir4(), ".claude", "projects");
|
|
626
870
|
},
|
|
627
871
|
async readdir(p) {
|
|
628
872
|
try {
|
|
@@ -706,7 +950,7 @@ function delay(ms) {
|
|
|
706
950
|
|
|
707
951
|
// src/engine/claude-code-local/sessions.ts
|
|
708
952
|
import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
|
|
709
|
-
import { homedir as
|
|
953
|
+
import { homedir as homedir5 } from "os";
|
|
710
954
|
import path3 from "path";
|
|
711
955
|
async function listSessionsForCwd(cwd, deps = defaultDeps3) {
|
|
712
956
|
const projectDir = path3.join(deps.projectsDir(), encodeCwd(cwd));
|
|
@@ -758,17 +1002,11 @@ function extractFirstUserMessage(lines) {
|
|
|
758
1002
|
return null;
|
|
759
1003
|
}
|
|
760
1004
|
function stringifyContent(content) {
|
|
761
|
-
|
|
762
|
-
return content.trim();
|
|
763
|
-
if (!Array.isArray(content))
|
|
764
|
-
return "";
|
|
1005
|
+
const blocks = normalizeClaudeContent(content);
|
|
765
1006
|
const parts = [];
|
|
766
|
-
for (const
|
|
767
|
-
if (
|
|
768
|
-
|
|
769
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
770
|
-
parts.push(block.text);
|
|
771
|
-
}
|
|
1007
|
+
for (const b of blocks) {
|
|
1008
|
+
if (b.type === "text")
|
|
1009
|
+
parts.push(b.text);
|
|
772
1010
|
}
|
|
773
1011
|
return parts.join(" ").trim();
|
|
774
1012
|
}
|
|
@@ -780,7 +1018,7 @@ var init_sessions = __esm(() => {
|
|
|
780
1018
|
init_history();
|
|
781
1019
|
defaultDeps3 = {
|
|
782
1020
|
projectsDir() {
|
|
783
|
-
return path3.join(
|
|
1021
|
+
return path3.join(homedir5(), ".claude", "projects");
|
|
784
1022
|
},
|
|
785
1023
|
async readdir(p) {
|
|
786
1024
|
try {
|
|
@@ -893,47 +1131,981 @@ async function* parseStreamJson(lines, opts = {}) {
|
|
|
893
1131
|
}
|
|
894
1132
|
continue;
|
|
895
1133
|
}
|
|
896
|
-
if (type === "user") {
|
|
897
|
-
const content = extractContentBlocks(msg);
|
|
898
|
-
for (const block of content) {
|
|
899
|
-
if (!isObject3(block))
|
|
1134
|
+
if (type === "user") {
|
|
1135
|
+
const content = extractContentBlocks(msg);
|
|
1136
|
+
for (const block of content) {
|
|
1137
|
+
if (!isObject3(block))
|
|
1138
|
+
continue;
|
|
1139
|
+
const blockType = typeof block.type === "string" ? block.type : undefined;
|
|
1140
|
+
if (blockType === "tool_result") {
|
|
1141
|
+
const id = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
|
|
1142
|
+
const name = id && toolNameById.get(id) || "tool";
|
|
1143
|
+
const output = "content" in block ? block.content : undefined;
|
|
1144
|
+
yield { type: "tool.result", name, output };
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
if (type === "result") {
|
|
1150
|
+
const usage = isObject3(msg.usage) ? msg.usage : undefined;
|
|
1151
|
+
if (usage) {
|
|
1152
|
+
const inTok = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
|
|
1153
|
+
const outTok = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
|
|
1154
|
+
const cacheRead = typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : undefined;
|
|
1155
|
+
const cacheCreate = typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : undefined;
|
|
1156
|
+
yield {
|
|
1157
|
+
type: "usage",
|
|
1158
|
+
input_tokens: inTok,
|
|
1159
|
+
output_tokens: outTok,
|
|
1160
|
+
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {},
|
|
1161
|
+
...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
const subtype = typeof msg.subtype === "string" ? msg.subtype : "success";
|
|
1165
|
+
const isApiError = msg.is_error === true || typeof msg.api_error_status === "number";
|
|
1166
|
+
if (isApiError) {
|
|
1167
|
+
const status = typeof msg.api_error_status === "number" ? ` ${msg.api_error_status}` : "";
|
|
1168
|
+
const result = typeof msg.result === "string" ? msg.result.trim() : "";
|
|
1169
|
+
yield { type: "error", message: result ? `claude API error${status}: ${result}` : `claude API error${status}` };
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (subtype === "success") {
|
|
1173
|
+
yield { type: "done" };
|
|
1174
|
+
} else {
|
|
1175
|
+
yield { type: "error", message: `claude session ended: ${subtype}` };
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
async function* readLines(stream) {
|
|
1182
|
+
let buf = "";
|
|
1183
|
+
for await (const chunk of stream) {
|
|
1184
|
+
const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
1185
|
+
buf += text;
|
|
1186
|
+
let nl = buf.indexOf(`
|
|
1187
|
+
`);
|
|
1188
|
+
while (nl !== -1) {
|
|
1189
|
+
yield buf.slice(0, nl);
|
|
1190
|
+
buf = buf.slice(nl + 1);
|
|
1191
|
+
nl = buf.indexOf(`
|
|
1192
|
+
`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (buf.length > 0)
|
|
1196
|
+
yield buf;
|
|
1197
|
+
}
|
|
1198
|
+
function isObject3(v) {
|
|
1199
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1200
|
+
}
|
|
1201
|
+
function extractContentBlocks(msg) {
|
|
1202
|
+
if (Array.isArray(msg.content))
|
|
1203
|
+
return msg.content;
|
|
1204
|
+
const inner = msg.message;
|
|
1205
|
+
if (isObject3(inner) && Array.isArray(inner.content))
|
|
1206
|
+
return inner.content;
|
|
1207
|
+
return [];
|
|
1208
|
+
}
|
|
1209
|
+
function stringifyErr(err) {
|
|
1210
|
+
if (err instanceof Error)
|
|
1211
|
+
return err.message;
|
|
1212
|
+
try {
|
|
1213
|
+
return JSON.stringify(err);
|
|
1214
|
+
} catch {
|
|
1215
|
+
return String(err);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/engine/claude-code-local/index.ts
|
|
1220
|
+
class ClaudeCodeLocal {
|
|
1221
|
+
identity = claudeIdentity;
|
|
1222
|
+
capabilities = claudeCapabilities;
|
|
1223
|
+
registry = new SessionRegistry;
|
|
1224
|
+
running = new Map;
|
|
1225
|
+
binaryPathResolver;
|
|
1226
|
+
stopGraceMs;
|
|
1227
|
+
constructor(opts = {}) {
|
|
1228
|
+
this.binaryPathResolver = opts.binaryPathResolver ?? findClaudeBinary;
|
|
1229
|
+
this.stopGraceMs = opts.stopGraceMs ?? 5000;
|
|
1230
|
+
}
|
|
1231
|
+
async spawn(cwd, prompt, opts) {
|
|
1232
|
+
return this.start({ cwd, prompt, opts });
|
|
1233
|
+
}
|
|
1234
|
+
async resume(sessionId, prompt, opts) {
|
|
1235
|
+
const cwd = opts?.cwd ?? opts?.env?.KOBE_RESUME_CWD ?? process.cwd();
|
|
1236
|
+
return this.start({ cwd, prompt, opts, resumeSessionId: sessionId });
|
|
1237
|
+
}
|
|
1238
|
+
stream(handle) {
|
|
1239
|
+
const sid = handle.sessionId;
|
|
1240
|
+
const self = this;
|
|
1241
|
+
return {
|
|
1242
|
+
async* [Symbol.asyncIterator]() {
|
|
1243
|
+
const session = self.running.get(sid);
|
|
1244
|
+
if (!session)
|
|
1245
|
+
return;
|
|
1246
|
+
let idx = 0;
|
|
1247
|
+
while (true) {
|
|
1248
|
+
if (idx < session.queue.length) {
|
|
1249
|
+
const ev = session.queue[idx++];
|
|
1250
|
+
if (!ev)
|
|
1251
|
+
continue;
|
|
1252
|
+
yield ev;
|
|
1253
|
+
if (ev.type === "done" || ev.type === "error")
|
|
1254
|
+
return;
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (session.closed)
|
|
1258
|
+
return;
|
|
1259
|
+
await new Promise((resolve2) => session.waiters.push(resolve2));
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
async readHistory(sessionId) {
|
|
1265
|
+
const messages = await readHistory(sessionId);
|
|
1266
|
+
const usageMetrics = deriveSessionUsageMetrics(messages);
|
|
1267
|
+
return { messages, ...usageMetrics ? { usageMetrics } : {} };
|
|
1268
|
+
}
|
|
1269
|
+
async deleteHistory(sessionId) {
|
|
1270
|
+
return deleteHistory(sessionId);
|
|
1271
|
+
}
|
|
1272
|
+
async listSessions(cwd) {
|
|
1273
|
+
return listSessionsForCwd(cwd);
|
|
1274
|
+
}
|
|
1275
|
+
async stop(handle) {
|
|
1276
|
+
const sid = handle.sessionId;
|
|
1277
|
+
const session = this.running.get(sid);
|
|
1278
|
+
const shouldRescue = !!session && !session.completedNaturally && session.prompt.trim().length > 0;
|
|
1279
|
+
const rescuePrompt = session?.prompt ?? "";
|
|
1280
|
+
const rescueCwd = session?.cwd ?? handle.cwd;
|
|
1281
|
+
await this.registry.kill(sid, this.stopGraceMs);
|
|
1282
|
+
if (session) {
|
|
1283
|
+
session.closed = true;
|
|
1284
|
+
this.notify(session);
|
|
1285
|
+
this.running.delete(sid);
|
|
1286
|
+
}
|
|
1287
|
+
if (shouldRescue) {
|
|
1288
|
+
try {
|
|
1289
|
+
await appendInterruptedUserPrompt(sid, rescueCwd, rescuePrompt);
|
|
1290
|
+
} catch {}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
async start(args) {
|
|
1294
|
+
const binaryPath = await this.binaryPathResolver();
|
|
1295
|
+
const cliPermissionMode = args.opts?.permissionMode === "plan" ? "plan" : "bypassPermissions";
|
|
1296
|
+
const spawned = spawnClaudeProcess({
|
|
1297
|
+
binaryPath,
|
|
1298
|
+
cwd: args.cwd,
|
|
1299
|
+
prompt: args.prompt,
|
|
1300
|
+
model: args.opts?.model,
|
|
1301
|
+
permissionMode: cliPermissionMode,
|
|
1302
|
+
env: args.opts?.env,
|
|
1303
|
+
resumeSessionId: args.resumeSessionId
|
|
1304
|
+
});
|
|
1305
|
+
let resolveHandle = () => {};
|
|
1306
|
+
let rejectHandle = () => {};
|
|
1307
|
+
const handlePromise = new Promise((res, rej) => {
|
|
1308
|
+
resolveHandle = res;
|
|
1309
|
+
rejectHandle = rej;
|
|
1310
|
+
});
|
|
1311
|
+
const queue = [];
|
|
1312
|
+
let session;
|
|
1313
|
+
let bound = false;
|
|
1314
|
+
const bind = (sessionId) => {
|
|
1315
|
+
if (bound)
|
|
1316
|
+
return;
|
|
1317
|
+
bound = true;
|
|
1318
|
+
session = {
|
|
1319
|
+
sessionId,
|
|
1320
|
+
cwd: args.cwd,
|
|
1321
|
+
spawned,
|
|
1322
|
+
queue,
|
|
1323
|
+
waiters: [],
|
|
1324
|
+
closed: false,
|
|
1325
|
+
completedNaturally: false,
|
|
1326
|
+
prompt: args.prompt,
|
|
1327
|
+
spawnedAtIso: new Date().toISOString()
|
|
1328
|
+
};
|
|
1329
|
+
this.running.set(sessionId, session);
|
|
1330
|
+
this.registry.register({
|
|
1331
|
+
sessionId,
|
|
1332
|
+
cwd: args.cwd,
|
|
1333
|
+
proc: spawned.proc,
|
|
1334
|
+
startedAt: Date.now(),
|
|
1335
|
+
prompt: args.prompt
|
|
1336
|
+
});
|
|
1337
|
+
resolveHandle({ sessionId, cwd: args.cwd });
|
|
1338
|
+
};
|
|
1339
|
+
if (args.resumeSessionId) {
|
|
1340
|
+
try {
|
|
1341
|
+
bind(args.resumeSessionId);
|
|
1342
|
+
} catch (err) {
|
|
1343
|
+
try {
|
|
1344
|
+
spawned.proc.kill("SIGKILL");
|
|
1345
|
+
} catch {}
|
|
1346
|
+
rejectHandle(err);
|
|
1347
|
+
throw err;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
(async () => {
|
|
1351
|
+
const events = parseStreamJson(readLines(spawned.stdout), {
|
|
1352
|
+
onSessionId: (sid) => bind(sid)
|
|
1353
|
+
});
|
|
1354
|
+
try {
|
|
1355
|
+
for await (const ev of events) {
|
|
1356
|
+
const enriched = enrichUsageEvent(ev, session?.spawnedAtIso);
|
|
1357
|
+
queue.push(enriched);
|
|
1358
|
+
if (ev.type === "done" && session) {
|
|
1359
|
+
session.completedNaturally = true;
|
|
1360
|
+
}
|
|
1361
|
+
if (session)
|
|
1362
|
+
this.notify(session);
|
|
1363
|
+
}
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
const ev = {
|
|
1366
|
+
type: "error",
|
|
1367
|
+
message: `parser failure: ${err instanceof Error ? err.message : String(err)}`
|
|
1368
|
+
};
|
|
1369
|
+
queue.push(ev);
|
|
1370
|
+
if (session)
|
|
1371
|
+
this.notify(session);
|
|
1372
|
+
} finally {
|
|
1373
|
+
if (session) {
|
|
1374
|
+
session.closed = true;
|
|
1375
|
+
this.notify(session);
|
|
1376
|
+
this.registry.unregister(session.sessionId);
|
|
1377
|
+
}
|
|
1378
|
+
if (!bound) {
|
|
1379
|
+
rejectHandle(new Error("claude exited without emitting a session id"));
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
})();
|
|
1383
|
+
drainStream(spawned.stderr);
|
|
1384
|
+
spawned.proc.once("error", (err) => {
|
|
1385
|
+
if (!bound)
|
|
1386
|
+
rejectHandle(err);
|
|
1387
|
+
});
|
|
1388
|
+
spawned.proc.once("exit", () => {
|
|
1389
|
+
if (!bound) {
|
|
1390
|
+
rejectHandle(new Error("claude exited before session id was captured"));
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
return handlePromise;
|
|
1394
|
+
}
|
|
1395
|
+
notify(session) {
|
|
1396
|
+
const waiters = session.waiters;
|
|
1397
|
+
session.waiters = [];
|
|
1398
|
+
for (const w of waiters)
|
|
1399
|
+
w();
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
function drainStream(stream) {
|
|
1403
|
+
const s = stream;
|
|
1404
|
+
s.on("data", () => {});
|
|
1405
|
+
s.on("error", () => {});
|
|
1406
|
+
}
|
|
1407
|
+
function enrichUsageEvent(ev, startedAtIso) {
|
|
1408
|
+
if (ev.type !== "usage")
|
|
1409
|
+
return ev;
|
|
1410
|
+
return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
|
|
1411
|
+
}
|
|
1412
|
+
var init_claude_code_local = __esm(() => {
|
|
1413
|
+
init_binary();
|
|
1414
|
+
init_capabilities();
|
|
1415
|
+
init_history();
|
|
1416
|
+
init_sessions();
|
|
1417
|
+
init_spawn();
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
// src/engine/codex-local/binary.ts
|
|
1421
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1422
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
1423
|
+
import { homedir as homedir6 } from "os";
|
|
1424
|
+
import path4 from "path";
|
|
1425
|
+
async function findCodexBinary(deps = defaultDeps4) {
|
|
1426
|
+
const checked = [];
|
|
1427
|
+
const tryPath = (p) => {
|
|
1428
|
+
if (!p)
|
|
1429
|
+
return;
|
|
1430
|
+
checked.push(p);
|
|
1431
|
+
return deps.fileExists(p) ? p : undefined;
|
|
1432
|
+
};
|
|
1433
|
+
const whichResult = deps.which("codex");
|
|
1434
|
+
if (whichResult) {
|
|
1435
|
+
checked.push(`which:${whichResult}`);
|
|
1436
|
+
if (deps.fileExists(whichResult))
|
|
1437
|
+
return whichResult;
|
|
1438
|
+
}
|
|
1439
|
+
const home = deps.home();
|
|
1440
|
+
for (const p of ["/opt/homebrew/bin/codex", "/usr/local/bin/codex", "/usr/bin/codex", "/bin/codex"]) {
|
|
1441
|
+
const candidate = tryPath(p);
|
|
1442
|
+
if (candidate)
|
|
1443
|
+
return candidate;
|
|
1444
|
+
}
|
|
1445
|
+
const nvmBin = deps.env("NVM_BIN");
|
|
1446
|
+
if (nvmBin) {
|
|
1447
|
+
const candidate = tryPath(path4.join(nvmBin, "codex"));
|
|
1448
|
+
if (candidate)
|
|
1449
|
+
return candidate;
|
|
1450
|
+
}
|
|
1451
|
+
for (const rel of [".local/bin/codex", ".bun/bin/codex", "bin/codex"]) {
|
|
1452
|
+
const candidate = tryPath(path4.join(home, rel));
|
|
1453
|
+
if (candidate)
|
|
1454
|
+
return candidate;
|
|
1455
|
+
}
|
|
1456
|
+
throw new CodexBinaryNotFoundError(checked);
|
|
1457
|
+
}
|
|
1458
|
+
var CodexBinaryNotFoundError, defaultDeps4;
|
|
1459
|
+
var init_binary2 = __esm(() => {
|
|
1460
|
+
CodexBinaryNotFoundError = class CodexBinaryNotFoundError extends Error {
|
|
1461
|
+
checkedPaths;
|
|
1462
|
+
constructor(checkedPaths) {
|
|
1463
|
+
super(`Codex CLI binary not found. Checked: ${checkedPaths.join(", ")}. Ensure 'codex' is on PATH (e.g. \`brew install codex\` or the official installer).`);
|
|
1464
|
+
this.name = "CodexBinaryNotFoundError";
|
|
1465
|
+
this.checkedPaths = checkedPaths;
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
defaultDeps4 = {
|
|
1469
|
+
fileExists(p) {
|
|
1470
|
+
try {
|
|
1471
|
+
return statSync2(p).isFile();
|
|
1472
|
+
} catch {
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
},
|
|
1476
|
+
env(name) {
|
|
1477
|
+
return process.env[name];
|
|
1478
|
+
},
|
|
1479
|
+
home() {
|
|
1480
|
+
return homedir6();
|
|
1481
|
+
},
|
|
1482
|
+
which(name) {
|
|
1483
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
1484
|
+
const out = spawnSync2(cmd, [name], { encoding: "utf8" });
|
|
1485
|
+
if (out.status !== 0)
|
|
1486
|
+
return;
|
|
1487
|
+
const first = out.stdout.split(`
|
|
1488
|
+
`).map((l) => l.trim()).filter(Boolean)[0];
|
|
1489
|
+
if (!first)
|
|
1490
|
+
return;
|
|
1491
|
+
if (first.startsWith("codex:") && first.includes("aliased to")) {
|
|
1492
|
+
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
1493
|
+
return aliasTarget && existsSync3(aliasTarget) ? aliasTarget : undefined;
|
|
1494
|
+
}
|
|
1495
|
+
return first;
|
|
1496
|
+
},
|
|
1497
|
+
readdir(p) {
|
|
1498
|
+
try {
|
|
1499
|
+
const fs = __require("fs");
|
|
1500
|
+
return fs.readdirSync(p);
|
|
1501
|
+
} catch {
|
|
1502
|
+
return [];
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
// src/engine/codex-local/models.ts
|
|
1509
|
+
function codexContextWindowFor(_modelId) {
|
|
1510
|
+
return DEFAULT_CTX;
|
|
1511
|
+
}
|
|
1512
|
+
var CODEX_MODELS, CODEX_FALLBACK_DEFAULT_MODEL_ID = "gpt-5.4-mini", DEFAULT_CTX = 400000;
|
|
1513
|
+
var init_models2 = __esm(() => {
|
|
1514
|
+
CODEX_MODELS = [
|
|
1515
|
+
{ vendor: "codex", id: "gpt-5.5", label: "GPT-5.5", hint: "latest, ChatGPT-account compatible" },
|
|
1516
|
+
{ vendor: "codex", id: "gpt-5.4", label: "GPT-5.4", hint: "stable" },
|
|
1517
|
+
{ vendor: "codex", id: "gpt-5.4-mini", label: "GPT-5.4 mini", hint: "fastest, always supported" }
|
|
1518
|
+
];
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
// src/engine/codex-local/settings.ts
|
|
1522
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1523
|
+
import { homedir as homedir7 } from "os";
|
|
1524
|
+
import { join as join4 } from "path";
|
|
1525
|
+
function readModelFromConfig() {
|
|
1526
|
+
try {
|
|
1527
|
+
const raw = readFileSync2(CONFIG_PATH, "utf8");
|
|
1528
|
+
let inTable = false;
|
|
1529
|
+
for (const line of raw.split(`
|
|
1530
|
+
`)) {
|
|
1531
|
+
const t = line.trim();
|
|
1532
|
+
if (t.startsWith("[") && t.endsWith("]")) {
|
|
1533
|
+
inTable = true;
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
if (inTable)
|
|
1537
|
+
continue;
|
|
1538
|
+
const m = /^model\s*=\s*"([^"]+)"/.exec(t);
|
|
1539
|
+
if (m)
|
|
1540
|
+
return m[1] ?? null;
|
|
1541
|
+
}
|
|
1542
|
+
return null;
|
|
1543
|
+
} catch {
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
function resolveCodexDefaultModelId() {
|
|
1548
|
+
if (cached2 === undefined)
|
|
1549
|
+
cached2 = readModelFromConfig();
|
|
1550
|
+
return cached2 ?? CODEX_FALLBACK_DEFAULT_MODEL_ID;
|
|
1551
|
+
}
|
|
1552
|
+
var CONFIG_PATH, cached2;
|
|
1553
|
+
var init_settings2 = __esm(() => {
|
|
1554
|
+
init_models2();
|
|
1555
|
+
CONFIG_PATH = join4(homedir7(), ".codex", "config.toml");
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
// src/engine/codex-local/capabilities.ts
|
|
1559
|
+
var codexCapabilities, codexIdentity;
|
|
1560
|
+
var init_capabilities2 = __esm(() => {
|
|
1561
|
+
init_models2();
|
|
1562
|
+
init_settings2();
|
|
1563
|
+
codexCapabilities = {
|
|
1564
|
+
vendorId: "codex",
|
|
1565
|
+
label: "Codex",
|
|
1566
|
+
models: CODEX_MODELS,
|
|
1567
|
+
defaultModelId: resolveCodexDefaultModelId,
|
|
1568
|
+
contextWindowFor: codexContextWindowFor
|
|
1569
|
+
};
|
|
1570
|
+
codexIdentity = {
|
|
1571
|
+
vendorId: "codex",
|
|
1572
|
+
productName: "Codex",
|
|
1573
|
+
shortName: "Codex",
|
|
1574
|
+
assistantName: "Codex",
|
|
1575
|
+
inputPlaceholder: "Ask Codex\u2026"
|
|
1576
|
+
};
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
// src/engine/codex-local/normalize.ts
|
|
1580
|
+
function normalizeCodexContent(raw) {
|
|
1581
|
+
if (typeof raw === "string") {
|
|
1582
|
+
return raw.length > 0 ? [{ type: "text", text: raw }] : [];
|
|
1583
|
+
}
|
|
1584
|
+
if (!Array.isArray(raw))
|
|
1585
|
+
return [];
|
|
1586
|
+
const blocks = [];
|
|
1587
|
+
for (const item of raw) {
|
|
1588
|
+
if (typeof item === "string") {
|
|
1589
|
+
if (item.length > 0)
|
|
1590
|
+
blocks.push({ type: "text", text: item });
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
if (!isObject4(item))
|
|
1594
|
+
continue;
|
|
1595
|
+
const t = typeof item.type === "string" ? item.type : undefined;
|
|
1596
|
+
if (t === "input_text" || t === "output_text") {
|
|
1597
|
+
const text = typeof item.text === "string" ? item.text : "";
|
|
1598
|
+
if (text.length > 0)
|
|
1599
|
+
blocks.push({ type: "text", text });
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
if (t)
|
|
1603
|
+
blocks.push({ type: "text", text: `[codex: ${t}]` });
|
|
1604
|
+
}
|
|
1605
|
+
return blocks;
|
|
1606
|
+
}
|
|
1607
|
+
function isObject4(v) {
|
|
1608
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/engine/codex-local/history.ts
|
|
1612
|
+
import { readFile as readFile3, readdir as readdir3, unlink as unlink2 } from "fs/promises";
|
|
1613
|
+
import { homedir as homedir8 } from "os";
|
|
1614
|
+
import path5 from "path";
|
|
1615
|
+
async function listRolloutFiles(deps = defaultDeps5) {
|
|
1616
|
+
const root = deps.sessionsDir();
|
|
1617
|
+
const years = (await deps.readdir(root)).sort().reverse();
|
|
1618
|
+
const out = [];
|
|
1619
|
+
for (const y of years) {
|
|
1620
|
+
const yp = path5.join(root, y);
|
|
1621
|
+
const months = (await deps.readdir(yp)).sort().reverse();
|
|
1622
|
+
for (const m of months) {
|
|
1623
|
+
const mp = path5.join(yp, m);
|
|
1624
|
+
const days = (await deps.readdir(mp)).sort().reverse();
|
|
1625
|
+
for (const d of days) {
|
|
1626
|
+
const dp = path5.join(mp, d);
|
|
1627
|
+
const files = (await deps.readdir(dp)).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl"));
|
|
1628
|
+
files.sort().reverse();
|
|
1629
|
+
for (const f of files)
|
|
1630
|
+
out.push(path5.join(dp, f));
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return out;
|
|
1635
|
+
}
|
|
1636
|
+
async function findRolloutFile(sessionId, deps = defaultDeps5) {
|
|
1637
|
+
const all = await listRolloutFiles(deps);
|
|
1638
|
+
for (const p of all) {
|
|
1639
|
+
if (path5.basename(p).endsWith(`-${sessionId}.jsonl`))
|
|
1640
|
+
return p;
|
|
1641
|
+
}
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
async function readHistoryWithMetrics(sessionId, deps = defaultDeps5) {
|
|
1645
|
+
const file = await findRolloutFile(sessionId, deps);
|
|
1646
|
+
if (!file)
|
|
1647
|
+
return { messages: [] };
|
|
1648
|
+
let raw;
|
|
1649
|
+
try {
|
|
1650
|
+
raw = await deps.readFile(file);
|
|
1651
|
+
} catch {
|
|
1652
|
+
return { messages: [] };
|
|
1653
|
+
}
|
|
1654
|
+
const messages = sortByTimestamp2(parseJsonl2(raw, sessionId));
|
|
1655
|
+
const usageMetrics = deriveCodexUsageMetrics(raw);
|
|
1656
|
+
return { messages, ...usageMetrics ? { usageMetrics } : {} };
|
|
1657
|
+
}
|
|
1658
|
+
async function deleteHistory2(sessionId, deps = defaultDeps5) {
|
|
1659
|
+
const file = await findRolloutFile(sessionId, deps);
|
|
1660
|
+
if (!file)
|
|
1661
|
+
return;
|
|
1662
|
+
try {
|
|
1663
|
+
await unlink2(file);
|
|
1664
|
+
} catch (err) {
|
|
1665
|
+
if (err.code === "ENOENT")
|
|
1666
|
+
return;
|
|
1667
|
+
throw err;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function sortByTimestamp2(messages) {
|
|
1671
|
+
return messages.map((msg, idx) => ({ msg, idx })).sort((a, b) => {
|
|
1672
|
+
if (a.msg.timestamp < b.msg.timestamp)
|
|
1673
|
+
return -1;
|
|
1674
|
+
if (a.msg.timestamp > b.msg.timestamp)
|
|
1675
|
+
return 1;
|
|
1676
|
+
return a.idx - b.idx;
|
|
1677
|
+
}).map((entry) => entry.msg);
|
|
1678
|
+
}
|
|
1679
|
+
function parseJsonl2(raw, sessionId) {
|
|
1680
|
+
const out = [];
|
|
1681
|
+
for (const line of raw.split(`
|
|
1682
|
+
`)) {
|
|
1683
|
+
const trimmed = line.trim();
|
|
1684
|
+
if (!trimmed)
|
|
1685
|
+
continue;
|
|
1686
|
+
let parsed;
|
|
1687
|
+
try {
|
|
1688
|
+
parsed = JSON.parse(trimmed);
|
|
1689
|
+
} catch {
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
if (!isObject5(parsed))
|
|
1693
|
+
continue;
|
|
1694
|
+
if (parsed.type !== "response_item")
|
|
1695
|
+
continue;
|
|
1696
|
+
const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
|
|
1697
|
+
if (!payload)
|
|
1698
|
+
continue;
|
|
1699
|
+
if (payload.type !== "message")
|
|
1700
|
+
continue;
|
|
1701
|
+
const role = payload.role;
|
|
1702
|
+
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
1703
|
+
continue;
|
|
1704
|
+
const blocks = normalizeCodexContent(payload.content);
|
|
1705
|
+
if (role === "user" && isEnvironmentContextEnvelope(blocks))
|
|
1706
|
+
continue;
|
|
1707
|
+
const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
|
|
1708
|
+
out.push({ role, blocks, timestamp: ts, sessionId });
|
|
1709
|
+
}
|
|
1710
|
+
return out;
|
|
1711
|
+
}
|
|
1712
|
+
function deriveCodexUsageMetrics(raw) {
|
|
1713
|
+
let latestUsage;
|
|
1714
|
+
let latestUsageTimestampMs = null;
|
|
1715
|
+
let lastUserTimestampMs = null;
|
|
1716
|
+
let inputTokens = 0;
|
|
1717
|
+
let outputTokens = 0;
|
|
1718
|
+
const intervals = [];
|
|
1719
|
+
for (const line of raw.split(`
|
|
1720
|
+
`)) {
|
|
1721
|
+
const trimmed = line.trim();
|
|
1722
|
+
if (!trimmed)
|
|
1723
|
+
continue;
|
|
1724
|
+
let parsed;
|
|
1725
|
+
try {
|
|
1726
|
+
parsed = JSON.parse(trimmed);
|
|
1727
|
+
} catch {
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
if (!isObject5(parsed))
|
|
1731
|
+
continue;
|
|
1732
|
+
const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs2(parsed.timestamp) : null;
|
|
1733
|
+
if (parsed.type === "response_item") {
|
|
1734
|
+
const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
|
|
1735
|
+
if (payload?.type === "message" && payload.role === "user" && timestampMs !== null) {
|
|
1736
|
+
const blocks = normalizeCodexContent(payload.content);
|
|
1737
|
+
if (!isEnvironmentContextEnvelope(blocks))
|
|
1738
|
+
lastUserTimestampMs = timestampMs;
|
|
1739
|
+
}
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
if (parsed.type !== "turn.completed")
|
|
1743
|
+
continue;
|
|
1744
|
+
const usage = isObject5(parsed.usage) ? parsed.usage : undefined;
|
|
1745
|
+
if (!usage)
|
|
1746
|
+
continue;
|
|
1747
|
+
const snapshot = codexUsageToSnapshot(usage);
|
|
1748
|
+
if (!snapshot)
|
|
1749
|
+
continue;
|
|
1750
|
+
if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
|
|
1751
|
+
latestUsageTimestampMs = timestampMs;
|
|
1752
|
+
latestUsage = snapshot;
|
|
1753
|
+
} else if (latestUsage === undefined) {
|
|
1754
|
+
latestUsage = snapshot;
|
|
1755
|
+
}
|
|
1756
|
+
inputTokens += snapshot.input_tokens;
|
|
1757
|
+
outputTokens += snapshot.output_tokens;
|
|
1758
|
+
if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
|
|
1759
|
+
intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (!latestUsage)
|
|
1763
|
+
return;
|
|
1764
|
+
const durationMs2 = mergedDurationMs(intervals);
|
|
1765
|
+
if (durationMs2 <= 0)
|
|
1766
|
+
return latestUsage;
|
|
1767
|
+
return {
|
|
1768
|
+
...latestUsage,
|
|
1769
|
+
total_speed_tokens_per_second: (inputTokens + outputTokens) / (durationMs2 / 1000)
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
function codexUsageToSnapshot(usage) {
|
|
1773
|
+
const input = numberOr(usage.input_tokens, 0);
|
|
1774
|
+
const output = numberOr(usage.output_tokens, 0) + numberOr(usage.reasoning_output_tokens, 0);
|
|
1775
|
+
const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
|
|
1776
|
+
if (input <= 0 && output <= 0 && cacheRead === undefined)
|
|
1777
|
+
return;
|
|
1778
|
+
return {
|
|
1779
|
+
input_tokens: input,
|
|
1780
|
+
output_tokens: output,
|
|
1781
|
+
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function parseTimestampMs2(value) {
|
|
1785
|
+
const ms = new Date(value).getTime();
|
|
1786
|
+
return Number.isFinite(ms) ? ms : null;
|
|
1787
|
+
}
|
|
1788
|
+
function mergedDurationMs(intervals) {
|
|
1789
|
+
if (intervals.length === 0)
|
|
1790
|
+
return 0;
|
|
1791
|
+
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
1792
|
+
let total = 0;
|
|
1793
|
+
let current = sorted[0];
|
|
1794
|
+
if (!current)
|
|
1795
|
+
return 0;
|
|
1796
|
+
for (let i = 1;i < sorted.length; i++) {
|
|
1797
|
+
const next = sorted[i];
|
|
1798
|
+
if (!next)
|
|
1799
|
+
continue;
|
|
1800
|
+
if (next.startMs <= current.endMs) {
|
|
1801
|
+
current = { startMs: current.startMs, endMs: Math.max(current.endMs, next.endMs) };
|
|
1802
|
+
} else {
|
|
1803
|
+
total += current.endMs - current.startMs;
|
|
1804
|
+
current = next;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
total += current.endMs - current.startMs;
|
|
1808
|
+
return total;
|
|
1809
|
+
}
|
|
1810
|
+
function numberOr(v, fallback) {
|
|
1811
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
1812
|
+
}
|
|
1813
|
+
function isEnvironmentContextEnvelope(blocks) {
|
|
1814
|
+
if (blocks.length === 0)
|
|
1815
|
+
return false;
|
|
1816
|
+
for (const b of blocks) {
|
|
1817
|
+
if (b.type !== "text")
|
|
1818
|
+
return false;
|
|
1819
|
+
const t = (b.text ?? "").trim();
|
|
1820
|
+
if (!t.startsWith("<environment_context>") || !t.endsWith("</environment_context>"))
|
|
1821
|
+
return false;
|
|
1822
|
+
}
|
|
1823
|
+
return true;
|
|
1824
|
+
}
|
|
1825
|
+
function isObject5(v) {
|
|
1826
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1827
|
+
}
|
|
1828
|
+
var defaultDeps5;
|
|
1829
|
+
var init_history2 = __esm(() => {
|
|
1830
|
+
defaultDeps5 = {
|
|
1831
|
+
sessionsDir() {
|
|
1832
|
+
return path5.join(homedir8(), ".codex", "sessions");
|
|
1833
|
+
},
|
|
1834
|
+
async readdir(p) {
|
|
1835
|
+
try {
|
|
1836
|
+
return await readdir3(p);
|
|
1837
|
+
} catch {
|
|
1838
|
+
return [];
|
|
1839
|
+
}
|
|
1840
|
+
},
|
|
1841
|
+
async readFile(p) {
|
|
1842
|
+
return await readFile3(p, "utf8");
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
// src/engine/codex-local/sessions.ts
|
|
1848
|
+
import { open, stat as stat2 } from "fs/promises";
|
|
1849
|
+
async function listSessionsForCwd2(cwd, deps) {
|
|
1850
|
+
const files = await listRolloutFiles(deps);
|
|
1851
|
+
const out = [];
|
|
1852
|
+
for (const file of files) {
|
|
1853
|
+
const meta = await tryReadMeta(file);
|
|
1854
|
+
if (!meta)
|
|
1855
|
+
continue;
|
|
1856
|
+
if (meta.cwd !== cwd)
|
|
1857
|
+
continue;
|
|
1858
|
+
out.push({
|
|
1859
|
+
sessionId: meta.sessionId,
|
|
1860
|
+
mtimeMs: meta.mtimeMs,
|
|
1861
|
+
firstUserMessage: meta.firstUserMessage,
|
|
1862
|
+
messageCount: meta.messageCount
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1866
|
+
return out;
|
|
1867
|
+
}
|
|
1868
|
+
async function tryReadMeta(file) {
|
|
1869
|
+
let st;
|
|
1870
|
+
try {
|
|
1871
|
+
st = await stat2(file);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
let sessionId;
|
|
1876
|
+
let cwd;
|
|
1877
|
+
let firstUser = null;
|
|
1878
|
+
let messageCount = 0;
|
|
1879
|
+
const handle = await open(file, "r").catch(() => null);
|
|
1880
|
+
if (!handle)
|
|
1881
|
+
return null;
|
|
1882
|
+
try {
|
|
1883
|
+
let buf = "";
|
|
1884
|
+
let lineCount = 0;
|
|
1885
|
+
const processLine = (line) => {
|
|
1886
|
+
lineCount++;
|
|
1887
|
+
const parsed = safeParse(line);
|
|
1888
|
+
if (parsed) {
|
|
1889
|
+
if (parsed.type === "session_meta") {
|
|
1890
|
+
const payload = parsed.payload;
|
|
1891
|
+
if (isObject6(payload)) {
|
|
1892
|
+
if (typeof payload.id === "string")
|
|
1893
|
+
sessionId = payload.id;
|
|
1894
|
+
if (typeof payload.cwd === "string")
|
|
1895
|
+
cwd = payload.cwd;
|
|
1896
|
+
}
|
|
1897
|
+
} else if (parsed.type === "response_item" && isObject6(parsed.payload)) {
|
|
1898
|
+
const p = parsed.payload;
|
|
1899
|
+
if (p.type === "message") {
|
|
1900
|
+
messageCount++;
|
|
1901
|
+
if (!firstUser && p.role === "user") {
|
|
1902
|
+
firstUser = extractText(p.content)?.slice(0, PREVIEW_CHAR_CAP) ?? null;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
return lineCount >= PREVIEW_HEAD_LINES;
|
|
1908
|
+
};
|
|
1909
|
+
const reader = handle.createReadStream({ encoding: "utf8" });
|
|
1910
|
+
outer:
|
|
1911
|
+
for await (const chunk of reader) {
|
|
1912
|
+
buf += chunk;
|
|
1913
|
+
let nl = buf.indexOf(`
|
|
1914
|
+
`);
|
|
1915
|
+
while (nl !== -1) {
|
|
1916
|
+
const line = buf.slice(0, nl);
|
|
1917
|
+
buf = buf.slice(nl + 1);
|
|
1918
|
+
nl = buf.indexOf(`
|
|
1919
|
+
`);
|
|
1920
|
+
if (processLine(line))
|
|
1921
|
+
break outer;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
if (lineCount < PREVIEW_HEAD_LINES && buf.trim())
|
|
1925
|
+
processLine(buf);
|
|
1926
|
+
} finally {
|
|
1927
|
+
await handle.close().catch(() => {});
|
|
1928
|
+
}
|
|
1929
|
+
if (!sessionId || !cwd)
|
|
1930
|
+
return null;
|
|
1931
|
+
return {
|
|
1932
|
+
sessionId,
|
|
1933
|
+
cwd,
|
|
1934
|
+
mtimeMs: st.mtimeMs,
|
|
1935
|
+
firstUserMessage: firstUser,
|
|
1936
|
+
messageCount
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function safeParse(line) {
|
|
1940
|
+
const t = line.trim();
|
|
1941
|
+
if (!t)
|
|
1942
|
+
return null;
|
|
1943
|
+
try {
|
|
1944
|
+
const v = JSON.parse(t);
|
|
1945
|
+
return isObject6(v) ? v : null;
|
|
1946
|
+
} catch {
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
function extractText(content) {
|
|
1951
|
+
if (typeof content === "string")
|
|
1952
|
+
return content;
|
|
1953
|
+
if (!Array.isArray(content))
|
|
1954
|
+
return null;
|
|
1955
|
+
for (const item of content) {
|
|
1956
|
+
if (typeof item === "string" && item.length > 0)
|
|
1957
|
+
return item;
|
|
1958
|
+
if (isObject6(item)) {
|
|
1959
|
+
const t = item.type;
|
|
1960
|
+
if ((t === "input_text" || t === "output_text" || t === "text") && typeof item.text === "string") {
|
|
1961
|
+
return item.text;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
function isObject6(v) {
|
|
1968
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1969
|
+
}
|
|
1970
|
+
var PREVIEW_HEAD_LINES = 40, PREVIEW_CHAR_CAP = 200;
|
|
1971
|
+
var init_sessions2 = __esm(() => {
|
|
1972
|
+
init_history2();
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
// src/engine/codex-local/spawn.ts
|
|
1976
|
+
import { spawn as spawn3 } from "child_process";
|
|
1977
|
+
function spawnCodexProcess(opts) {
|
|
1978
|
+
const args = buildArgs2(opts);
|
|
1979
|
+
const proc = spawn3(opts.binaryPath, args, {
|
|
1980
|
+
cwd: opts.cwd,
|
|
1981
|
+
env: { ...process.env, ...opts.env ?? {} },
|
|
1982
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1983
|
+
});
|
|
1984
|
+
try {
|
|
1985
|
+
proc.stdin.end();
|
|
1986
|
+
} catch {}
|
|
1987
|
+
return {
|
|
1988
|
+
proc,
|
|
1989
|
+
stdout: proc.stdout,
|
|
1990
|
+
stderr: proc.stderr,
|
|
1991
|
+
args
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
function buildArgs2(opts) {
|
|
1995
|
+
const isResume = !!opts.resumeSessionId;
|
|
1996
|
+
const args = ["exec"];
|
|
1997
|
+
if (isResume) {
|
|
1998
|
+
args.push("resume", opts.resumeSessionId);
|
|
1999
|
+
}
|
|
2000
|
+
args.push("--json", "--skip-git-repo-check");
|
|
2001
|
+
if (opts.model) {
|
|
2002
|
+
args.push("-m", opts.model);
|
|
2003
|
+
}
|
|
2004
|
+
if (!isResume) {
|
|
2005
|
+
args.push("-C", opts.cwd);
|
|
2006
|
+
if (opts.permissionMode === "plan")
|
|
2007
|
+
args.push("-s", "read-only");
|
|
2008
|
+
}
|
|
2009
|
+
if (opts.permissionMode !== "plan") {
|
|
2010
|
+
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
2011
|
+
}
|
|
2012
|
+
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
2013
|
+
args.push(...opts.extraArgs);
|
|
2014
|
+
}
|
|
2015
|
+
args.push(opts.prompt);
|
|
2016
|
+
return args;
|
|
2017
|
+
}
|
|
2018
|
+
var init_spawn2 = () => {};
|
|
2019
|
+
|
|
2020
|
+
// src/engine/codex-local/stream.ts
|
|
2021
|
+
async function* parseStreamJson2(lines, opts = {}) {
|
|
2022
|
+
let sessionIdEmitted = false;
|
|
2023
|
+
const toolNameById = new Map;
|
|
2024
|
+
const startedByItemId = new Set;
|
|
2025
|
+
for await (const rawLine of lines) {
|
|
2026
|
+
const line = rawLine.trim();
|
|
2027
|
+
if (!line)
|
|
2028
|
+
continue;
|
|
2029
|
+
let msg;
|
|
2030
|
+
try {
|
|
2031
|
+
msg = JSON.parse(line);
|
|
2032
|
+
} catch (err) {
|
|
2033
|
+
yield { type: "error", message: `codex stream-json parse failed: ${stringifyErr2(err)}` };
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
if (!isObject7(msg))
|
|
2037
|
+
continue;
|
|
2038
|
+
const type = typeof msg.type === "string" ? msg.type : undefined;
|
|
2039
|
+
if (!type)
|
|
2040
|
+
continue;
|
|
2041
|
+
if (type === "thread.started") {
|
|
2042
|
+
const sid = typeof msg.thread_id === "string" ? msg.thread_id : undefined;
|
|
2043
|
+
if (sid && !sessionIdEmitted) {
|
|
2044
|
+
sessionIdEmitted = true;
|
|
2045
|
+
opts.onSessionId?.(sid);
|
|
2046
|
+
}
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
if (type === "turn.started")
|
|
2050
|
+
continue;
|
|
2051
|
+
if (type === "item.started" || type === "item.completed") {
|
|
2052
|
+
const item = isObject7(msg.item) ? msg.item : undefined;
|
|
2053
|
+
if (!item)
|
|
2054
|
+
continue;
|
|
2055
|
+
const itemId = typeof item.id === "string" ? item.id : undefined;
|
|
2056
|
+
const itemType = typeof item.type === "string" ? item.type : "tool";
|
|
2057
|
+
if (itemType === "agent_message") {
|
|
2058
|
+
if (type !== "item.completed")
|
|
900
2059
|
continue;
|
|
901
|
-
const
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
2060
|
+
const text = typeof item.text === "string" ? item.text : "";
|
|
2061
|
+
if (text)
|
|
2062
|
+
yield { type: "assistant.delta", text };
|
|
2063
|
+
continue;
|
|
2064
|
+
}
|
|
2065
|
+
if (itemId) {
|
|
2066
|
+
toolNameById.set(itemId, itemType);
|
|
2067
|
+
}
|
|
2068
|
+
if (type === "item.started") {
|
|
2069
|
+
if (itemId)
|
|
2070
|
+
startedByItemId.add(itemId);
|
|
2071
|
+
const input = stripIdAndType(item);
|
|
2072
|
+
yield { type: "tool.start", name: itemType, input };
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
if (itemId && !startedByItemId.has(itemId)) {
|
|
2076
|
+
const input = stripIdAndType(item);
|
|
2077
|
+
yield { type: "tool.start", name: itemType, input };
|
|
908
2078
|
}
|
|
2079
|
+
if (itemId)
|
|
2080
|
+
startedByItemId.delete(itemId);
|
|
2081
|
+
const output = stripIdAndType(item);
|
|
2082
|
+
yield { type: "tool.result", name: itemType, output };
|
|
909
2083
|
continue;
|
|
910
2084
|
}
|
|
911
|
-
if (type === "
|
|
912
|
-
const usage =
|
|
2085
|
+
if (type === "turn.completed") {
|
|
2086
|
+
const usage = isObject7(msg.usage) ? msg.usage : undefined;
|
|
913
2087
|
if (usage) {
|
|
914
|
-
const inTok =
|
|
915
|
-
const outTok =
|
|
916
|
-
const cacheRead = typeof usage.
|
|
917
|
-
const cacheCreate = typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : undefined;
|
|
2088
|
+
const inTok = numberOr2(usage.input_tokens, 0);
|
|
2089
|
+
const outTok = numberOr2(usage.output_tokens, 0) + numberOr2(usage.reasoning_output_tokens, 0);
|
|
2090
|
+
const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
|
|
918
2091
|
yield {
|
|
919
2092
|
type: "usage",
|
|
920
2093
|
input_tokens: inTok,
|
|
921
2094
|
output_tokens: outTok,
|
|
922
|
-
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
923
|
-
...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
|
|
2095
|
+
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
924
2096
|
};
|
|
925
2097
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
}
|
|
2098
|
+
yield { type: "done" };
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (type === "error") {
|
|
2102
|
+
const message = typeof msg.message === "string" ? msg.message : "codex emitted an error";
|
|
2103
|
+
yield { type: "error", message };
|
|
932
2104
|
return;
|
|
933
2105
|
}
|
|
934
2106
|
}
|
|
935
2107
|
}
|
|
936
|
-
async function*
|
|
2108
|
+
async function* readLines2(stream) {
|
|
937
2109
|
let buf = "";
|
|
938
2110
|
for await (const chunk of stream) {
|
|
939
2111
|
const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
@@ -950,18 +2122,17 @@ async function* readLines(stream) {
|
|
|
950
2122
|
if (buf.length > 0)
|
|
951
2123
|
yield buf;
|
|
952
2124
|
}
|
|
953
|
-
function
|
|
2125
|
+
function isObject7(v) {
|
|
954
2126
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
955
2127
|
}
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
return msg.content;
|
|
959
|
-
const inner = msg.message;
|
|
960
|
-
if (isObject3(inner) && Array.isArray(inner.content))
|
|
961
|
-
return inner.content;
|
|
962
|
-
return [];
|
|
2128
|
+
function numberOr2(v, fallback) {
|
|
2129
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
963
2130
|
}
|
|
964
|
-
function
|
|
2131
|
+
function stripIdAndType(item) {
|
|
2132
|
+
const { id: _id, type: _type, ...rest } = item;
|
|
2133
|
+
return rest;
|
|
2134
|
+
}
|
|
2135
|
+
function stringifyErr2(err) {
|
|
965
2136
|
if (err instanceof Error)
|
|
966
2137
|
return err.message;
|
|
967
2138
|
try {
|
|
@@ -971,14 +2142,16 @@ function stringifyErr(err) {
|
|
|
971
2142
|
}
|
|
972
2143
|
}
|
|
973
2144
|
|
|
974
|
-
// src/engine/
|
|
975
|
-
class
|
|
2145
|
+
// src/engine/codex-local/index.ts
|
|
2146
|
+
class CodexLocal {
|
|
2147
|
+
identity = codexIdentity;
|
|
2148
|
+
capabilities = codexCapabilities;
|
|
976
2149
|
registry = new SessionRegistry;
|
|
977
2150
|
running = new Map;
|
|
978
2151
|
binaryPathResolver;
|
|
979
2152
|
stopGraceMs;
|
|
980
2153
|
constructor(opts = {}) {
|
|
981
|
-
this.binaryPathResolver = opts.binaryPathResolver ??
|
|
2154
|
+
this.binaryPathResolver = opts.binaryPathResolver ?? findCodexBinary;
|
|
982
2155
|
this.stopGraceMs = opts.stopGraceMs ?? 5000;
|
|
983
2156
|
}
|
|
984
2157
|
async spawn(cwd, prompt, opts) {
|
|
@@ -1015,41 +2188,32 @@ class ClaudeCodeLocal {
|
|
|
1015
2188
|
};
|
|
1016
2189
|
}
|
|
1017
2190
|
async readHistory(sessionId) {
|
|
1018
|
-
return
|
|
2191
|
+
return readHistoryWithMetrics(sessionId);
|
|
1019
2192
|
}
|
|
1020
2193
|
async deleteHistory(sessionId) {
|
|
1021
|
-
return
|
|
2194
|
+
return deleteHistory2(sessionId);
|
|
1022
2195
|
}
|
|
1023
2196
|
async listSessions(cwd) {
|
|
1024
|
-
return
|
|
2197
|
+
return listSessionsForCwd2(cwd);
|
|
1025
2198
|
}
|
|
1026
2199
|
async stop(handle) {
|
|
1027
2200
|
const sid = handle.sessionId;
|
|
1028
|
-
const session = this.running.get(sid);
|
|
1029
|
-
const shouldRescue = !!session && !session.completedNaturally && session.prompt.trim().length > 0;
|
|
1030
|
-
const rescuePrompt = session?.prompt ?? "";
|
|
1031
|
-
const rescueCwd = session?.cwd ?? handle.cwd;
|
|
1032
2201
|
await this.registry.kill(sid, this.stopGraceMs);
|
|
2202
|
+
const session = this.running.get(sid);
|
|
1033
2203
|
if (session) {
|
|
1034
2204
|
session.closed = true;
|
|
1035
2205
|
this.notify(session);
|
|
1036
2206
|
this.running.delete(sid);
|
|
1037
2207
|
}
|
|
1038
|
-
if (shouldRescue) {
|
|
1039
|
-
try {
|
|
1040
|
-
await appendInterruptedUserPrompt(sid, rescueCwd, rescuePrompt);
|
|
1041
|
-
} catch {}
|
|
1042
|
-
}
|
|
1043
2208
|
}
|
|
1044
2209
|
async start(args) {
|
|
1045
2210
|
const binaryPath = await this.binaryPathResolver();
|
|
1046
|
-
const
|
|
1047
|
-
const spawned = spawnClaudeProcess({
|
|
2211
|
+
const spawned = spawnCodexProcess({
|
|
1048
2212
|
binaryPath,
|
|
1049
2213
|
cwd: args.cwd,
|
|
1050
2214
|
prompt: args.prompt,
|
|
1051
2215
|
model: args.opts?.model,
|
|
1052
|
-
permissionMode:
|
|
2216
|
+
permissionMode: args.opts?.permissionMode,
|
|
1053
2217
|
env: args.opts?.env,
|
|
1054
2218
|
resumeSessionId: args.resumeSessionId
|
|
1055
2219
|
});
|
|
@@ -1073,8 +2237,7 @@ class ClaudeCodeLocal {
|
|
|
1073
2237
|
queue,
|
|
1074
2238
|
waiters: [],
|
|
1075
2239
|
closed: false,
|
|
1076
|
-
|
|
1077
|
-
prompt: args.prompt
|
|
2240
|
+
spawnedAtIso: new Date().toISOString()
|
|
1078
2241
|
};
|
|
1079
2242
|
this.running.set(sessionId, session);
|
|
1080
2243
|
this.registry.register({
|
|
@@ -1098,22 +2261,20 @@ class ClaudeCodeLocal {
|
|
|
1098
2261
|
}
|
|
1099
2262
|
}
|
|
1100
2263
|
(async () => {
|
|
1101
|
-
const events =
|
|
2264
|
+
const events = parseStreamJson2(readLines2(spawned.stdout), {
|
|
1102
2265
|
onSessionId: (sid) => bind(sid)
|
|
1103
2266
|
});
|
|
1104
2267
|
try {
|
|
1105
2268
|
for await (const ev of events) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
session.completedNaturally = true;
|
|
1109
|
-
}
|
|
2269
|
+
const enriched = enrichUsageEvent2(ev, session?.spawnedAtIso);
|
|
2270
|
+
queue.push(enriched);
|
|
1110
2271
|
if (session)
|
|
1111
2272
|
this.notify(session);
|
|
1112
2273
|
}
|
|
1113
2274
|
} catch (err) {
|
|
1114
2275
|
const ev = {
|
|
1115
2276
|
type: "error",
|
|
1116
|
-
message: `parser failure: ${err instanceof Error ? err.message : String(err)}`
|
|
2277
|
+
message: `codex parser failure: ${err instanceof Error ? err.message : String(err)}`
|
|
1117
2278
|
};
|
|
1118
2279
|
queue.push(ev);
|
|
1119
2280
|
if (session)
|
|
@@ -1123,20 +2284,21 @@ class ClaudeCodeLocal {
|
|
|
1123
2284
|
session.closed = true;
|
|
1124
2285
|
this.notify(session);
|
|
1125
2286
|
this.registry.unregister(session.sessionId);
|
|
2287
|
+
this.running.delete(session.sessionId);
|
|
1126
2288
|
}
|
|
1127
2289
|
if (!bound) {
|
|
1128
|
-
rejectHandle(new Error("
|
|
2290
|
+
rejectHandle(new Error("codex exited without emitting a session id"));
|
|
1129
2291
|
}
|
|
1130
2292
|
}
|
|
1131
2293
|
})();
|
|
1132
|
-
|
|
2294
|
+
drainStream2(spawned.stderr);
|
|
1133
2295
|
spawned.proc.once("error", (err) => {
|
|
1134
2296
|
if (!bound)
|
|
1135
2297
|
rejectHandle(err);
|
|
1136
2298
|
});
|
|
1137
2299
|
spawned.proc.once("exit", () => {
|
|
1138
2300
|
if (!bound) {
|
|
1139
|
-
rejectHandle(new Error("
|
|
2301
|
+
rejectHandle(new Error("codex exited before session id was captured"));
|
|
1140
2302
|
}
|
|
1141
2303
|
});
|
|
1142
2304
|
return handlePromise;
|
|
@@ -1148,25 +2310,31 @@ class ClaudeCodeLocal {
|
|
|
1148
2310
|
w();
|
|
1149
2311
|
}
|
|
1150
2312
|
}
|
|
1151
|
-
function
|
|
2313
|
+
function drainStream2(stream) {
|
|
1152
2314
|
const s = stream;
|
|
1153
2315
|
s.on("data", () => {});
|
|
1154
2316
|
s.on("error", () => {});
|
|
1155
2317
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
2318
|
+
function enrichUsageEvent2(ev, startedAtIso) {
|
|
2319
|
+
if (ev.type !== "usage")
|
|
2320
|
+
return ev;
|
|
2321
|
+
return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
|
|
2322
|
+
}
|
|
2323
|
+
var init_codex_local = __esm(() => {
|
|
2324
|
+
init_binary2();
|
|
2325
|
+
init_capabilities2();
|
|
2326
|
+
init_history2();
|
|
2327
|
+
init_sessions2();
|
|
2328
|
+
init_spawn2();
|
|
1161
2329
|
});
|
|
1162
2330
|
|
|
1163
2331
|
// src/orchestrator/bridge/server.ts
|
|
1164
|
-
import { mkdir as mkdir2, unlink as
|
|
2332
|
+
import { mkdir as mkdir2, unlink as unlink3 } from "fs/promises";
|
|
1165
2333
|
import { createServer } from "net";
|
|
1166
2334
|
import { dirname as dirname2 } from "path";
|
|
1167
2335
|
async function startBridgeServer(orch, socketPath) {
|
|
1168
2336
|
await mkdir2(dirname2(socketPath), { recursive: true });
|
|
1169
|
-
await
|
|
2337
|
+
await unlink3(socketPath).catch(() => {});
|
|
1170
2338
|
const conns = new Set;
|
|
1171
2339
|
const server = createServer((conn) => {
|
|
1172
2340
|
conns.add(conn);
|
|
@@ -1207,7 +2375,7 @@ async function startBridgeServer(orch, socketPath) {
|
|
|
1207
2375
|
conn.destroy();
|
|
1208
2376
|
conns.clear();
|
|
1209
2377
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
1210
|
-
await
|
|
2378
|
+
await unlink3(socketPath).catch(() => {});
|
|
1211
2379
|
}
|
|
1212
2380
|
};
|
|
1213
2381
|
}
|
|
@@ -1300,23 +2468,19 @@ __export(exports_bridge, {
|
|
|
1300
2468
|
bridgeSocketPathForHome: () => bridgeSocketPathForHome
|
|
1301
2469
|
});
|
|
1302
2470
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
1303
|
-
import { homedir as
|
|
1304
|
-
import { join as
|
|
2471
|
+
import { homedir as homedir9 } from "os";
|
|
2472
|
+
import { join as join5 } from "path";
|
|
1305
2473
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1306
2474
|
function bridgeSocketPathForHome(home, pid = process.pid) {
|
|
1307
|
-
const runDir =
|
|
1308
|
-
|
|
1309
|
-
const macTempSocket = process.platform === "darwin" && preferred.startsWith(tmpdir2());
|
|
1310
|
-
if (preferred.length <= UNIX_SOCKET_PATH_LIMIT && !macTempSocket)
|
|
1311
|
-
return preferred;
|
|
1312
|
-
const shortTmp = process.platform === "darwin" ? "/tmp" : tmpdir2();
|
|
1313
|
-
return join3(shortTmp, `kobe-bridge-${pid}.sock`);
|
|
2475
|
+
const runDir = join5(home, ".kobe", "run");
|
|
2476
|
+
return fitSocketPath(join5(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
|
|
1314
2477
|
}
|
|
1315
2478
|
async function startBridge(orch, opts = {}) {
|
|
1316
|
-
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
1317
|
-
const runDir =
|
|
2479
|
+
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
|
|
2480
|
+
const runDir = join5(home, ".kobe", "run");
|
|
1318
2481
|
const socketPath = bridgeSocketPathForHome(home);
|
|
1319
|
-
const mcpConfigPath =
|
|
2482
|
+
const mcpConfigPath = join5(runDir, `mcp-${process.pid}.json`);
|
|
2483
|
+
await mkdir3(runDir, { recursive: true });
|
|
1320
2484
|
const server = await startBridgeServer(orch, socketPath);
|
|
1321
2485
|
await mkdir3(runDir, { recursive: true });
|
|
1322
2486
|
const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
@@ -1339,8 +2503,8 @@ async function startBridge(orch, opts = {}) {
|
|
|
1339
2503
|
}
|
|
1340
2504
|
};
|
|
1341
2505
|
}
|
|
1342
|
-
var UNIX_SOCKET_PATH_LIMIT = 103;
|
|
1343
2506
|
var init_bridge = __esm(() => {
|
|
2507
|
+
init_paths();
|
|
1344
2508
|
init_server();
|
|
1345
2509
|
});
|
|
1346
2510
|
|
|
@@ -2572,134 +3736,81 @@ var init_dev = __esm(() => {
|
|
|
2572
3736
|
}
|
|
2573
3737
|
});
|
|
2574
3738
|
|
|
2575
|
-
// src/engine/
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
function
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
|
|
2591
|
-
cached = { model };
|
|
2592
|
-
return cached;
|
|
2593
|
-
} catch {
|
|
2594
|
-
cached = null;
|
|
2595
|
-
return null;
|
|
3739
|
+
// src/engine/registry.ts
|
|
3740
|
+
function getCapabilities(vendor) {
|
|
3741
|
+
return ENGINE_REGISTRY[vendor] ?? defaultCapabilities;
|
|
3742
|
+
}
|
|
3743
|
+
function getIdentity(vendor) {
|
|
3744
|
+
return ENGINE_IDENTITIES[vendor] ?? defaultIdentity;
|
|
3745
|
+
}
|
|
3746
|
+
function capabilitiesForModelId(modelId) {
|
|
3747
|
+
if (!modelId)
|
|
3748
|
+
return defaultCapabilities;
|
|
3749
|
+
for (const caps of Object.values(ENGINE_REGISTRY)) {
|
|
3750
|
+
if (!caps)
|
|
3751
|
+
continue;
|
|
3752
|
+
if (caps.models.some((m) => m.id === modelId))
|
|
3753
|
+
return caps;
|
|
2596
3754
|
}
|
|
3755
|
+
return defaultCapabilities;
|
|
2597
3756
|
}
|
|
2598
|
-
function
|
|
2599
|
-
const
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
}
|
|
2604
|
-
var SETTINGS_PATH, cached, FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
|
|
2605
|
-
var init_claude_settings = __esm(() => {
|
|
2606
|
-
SETTINGS_PATH = join4(homedir6(), ".claude", "settings.json");
|
|
2607
|
-
});
|
|
2608
|
-
|
|
2609
|
-
// src/session/usage-metrics.ts
|
|
2610
|
-
function totalContextTokens(u) {
|
|
2611
|
-
return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
2612
|
-
}
|
|
2613
|
-
function parseTimestampMs(value) {
|
|
2614
|
-
const ms = new Date(value).getTime();
|
|
2615
|
-
return Number.isFinite(ms) ? ms : null;
|
|
2616
|
-
}
|
|
2617
|
-
function mergeIntervals(intervals) {
|
|
2618
|
-
if (intervals.length === 0)
|
|
2619
|
-
return [];
|
|
2620
|
-
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
2621
|
-
const first = sorted[0];
|
|
2622
|
-
if (!first)
|
|
2623
|
-
return [];
|
|
2624
|
-
const merged = [{ startMs: first.startMs, endMs: first.endMs }];
|
|
2625
|
-
for (let i = 1;i < sorted.length; i++) {
|
|
2626
|
-
const current = sorted[i];
|
|
2627
|
-
const last = merged[merged.length - 1];
|
|
2628
|
-
if (!current || !last)
|
|
3757
|
+
function allModels() {
|
|
3758
|
+
const seen = new Set;
|
|
3759
|
+
const out = [];
|
|
3760
|
+
for (const caps of Object.values(ENGINE_REGISTRY)) {
|
|
3761
|
+
if (!caps)
|
|
2629
3762
|
continue;
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
3763
|
+
for (const m of caps.models) {
|
|
3764
|
+
const key = `${m.vendor}:${m.id}`;
|
|
3765
|
+
if (seen.has(key))
|
|
3766
|
+
continue;
|
|
3767
|
+
seen.add(key);
|
|
3768
|
+
out.push(m);
|
|
2634
3769
|
}
|
|
2635
3770
|
}
|
|
2636
|
-
return
|
|
2637
|
-
}
|
|
2638
|
-
function durationMs(intervals) {
|
|
2639
|
-
return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
|
|
3771
|
+
return out;
|
|
2640
3772
|
}
|
|
2641
|
-
function
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
let inputTokens = 0;
|
|
2646
|
-
let outputTokens = 0;
|
|
2647
|
-
const intervals = [];
|
|
2648
|
-
for (const message of past) {
|
|
2649
|
-
const timestampMs = parseTimestampMs(message.timestamp);
|
|
2650
|
-
if (message.role === "user" && timestampMs !== null) {
|
|
2651
|
-
lastUserTimestampMs = timestampMs;
|
|
2652
|
-
continue;
|
|
2653
|
-
}
|
|
2654
|
-
if (message.role !== "assistant" || !message.usage)
|
|
3773
|
+
function modelLabelFor(modelId) {
|
|
3774
|
+
const resolved = modelId ?? defaultCapabilities.defaultModelId();
|
|
3775
|
+
for (const caps of Object.values(ENGINE_REGISTRY)) {
|
|
3776
|
+
if (!caps)
|
|
2655
3777
|
continue;
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
return;
|
|
2670
|
-
const totalDurationMs = durationMs(mergeIntervals(intervals));
|
|
2671
|
-
if (totalDurationMs <= 0)
|
|
2672
|
-
return latestUsage;
|
|
2673
|
-
return {
|
|
2674
|
-
...latestUsage,
|
|
2675
|
-
total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
|
|
3778
|
+
const match = caps.models.find((m) => m.id === resolved);
|
|
3779
|
+
if (match)
|
|
3780
|
+
return match.label;
|
|
3781
|
+
}
|
|
3782
|
+
return resolved;
|
|
3783
|
+
}
|
|
3784
|
+
var ENGINE_REGISTRY, ENGINE_IDENTITIES, defaultCapabilities, defaultIdentity;
|
|
3785
|
+
var init_registry = __esm(() => {
|
|
3786
|
+
init_capabilities();
|
|
3787
|
+
init_capabilities2();
|
|
3788
|
+
ENGINE_REGISTRY = {
|
|
3789
|
+
claude: claudeCapabilities,
|
|
3790
|
+
codex: codexCapabilities
|
|
2676
3791
|
};
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
const endMs = parseTimestampMs(endedAtIso);
|
|
2681
|
-
if (startMs === null || endMs === null || endMs <= startMs)
|
|
2682
|
-
return usage;
|
|
2683
|
-
return {
|
|
2684
|
-
...usage,
|
|
2685
|
-
total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
|
|
3792
|
+
ENGINE_IDENTITIES = {
|
|
3793
|
+
claude: claudeIdentity,
|
|
3794
|
+
codex: codexIdentity
|
|
2686
3795
|
};
|
|
2687
|
-
|
|
3796
|
+
defaultCapabilities = claudeCapabilities;
|
|
3797
|
+
defaultIdentity = claudeIdentity;
|
|
3798
|
+
});
|
|
2688
3799
|
|
|
2689
3800
|
// src/env.ts
|
|
2690
|
-
import { homedir as
|
|
2691
|
-
import { join as
|
|
3801
|
+
import { homedir as homedir10 } from "os";
|
|
3802
|
+
import { join as join6 } from "path";
|
|
2692
3803
|
function isDev() {
|
|
2693
3804
|
return process.env.KOBE_DEV === "1";
|
|
2694
3805
|
}
|
|
2695
3806
|
function homeDir() {
|
|
2696
|
-
return process.env.KOBE_HOME_DIR ??
|
|
3807
|
+
return process.env.KOBE_HOME_DIR ?? homedir10();
|
|
2697
3808
|
}
|
|
2698
3809
|
function kobeStateDir() {
|
|
2699
|
-
return
|
|
3810
|
+
return join6(homeDir(), ".kobe");
|
|
2700
3811
|
}
|
|
2701
3812
|
function kvStatePath() {
|
|
2702
|
-
return
|
|
3813
|
+
return join6(homeDir(), ".config", "kobe", "state.json");
|
|
2703
3814
|
}
|
|
2704
3815
|
var init_env = () => {};
|
|
2705
3816
|
|
|
@@ -2714,11 +3825,11 @@ __export(exports_repos, {
|
|
|
2714
3825
|
getSavedRepos: () => getSavedRepos,
|
|
2715
3826
|
addSavedRepo: () => addSavedRepo
|
|
2716
3827
|
});
|
|
2717
|
-
import { spawnSync as
|
|
2718
|
-
import { mkdirSync, readFileSync as
|
|
3828
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3829
|
+
import { mkdirSync, readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync } from "fs";
|
|
2719
3830
|
import { dirname as dirname3 } from "path";
|
|
2720
3831
|
function resolveRepoRoot(absPath) {
|
|
2721
|
-
const r =
|
|
3832
|
+
const r = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
2722
3833
|
cwd: absPath,
|
|
2723
3834
|
encoding: "utf8",
|
|
2724
3835
|
shell: false
|
|
@@ -2752,7 +3863,7 @@ function statePath() {
|
|
|
2752
3863
|
}
|
|
2753
3864
|
function load() {
|
|
2754
3865
|
try {
|
|
2755
|
-
const text =
|
|
3866
|
+
const text = readFileSync3(statePath(), "utf8");
|
|
2756
3867
|
const parsed = JSON.parse(text);
|
|
2757
3868
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2758
3869
|
return parsed;
|
|
@@ -2761,11 +3872,11 @@ function load() {
|
|
|
2761
3872
|
return {};
|
|
2762
3873
|
}
|
|
2763
3874
|
function save(state) {
|
|
2764
|
-
const
|
|
2765
|
-
mkdirSync(dirname3(
|
|
2766
|
-
const tmp = `${
|
|
3875
|
+
const path6 = statePath();
|
|
3876
|
+
mkdirSync(dirname3(path6), { recursive: true });
|
|
3877
|
+
const tmp = `${path6}.tmp`;
|
|
2767
3878
|
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2768
|
-
renameSync(tmp,
|
|
3879
|
+
renameSync(tmp, path6);
|
|
2769
3880
|
}
|
|
2770
3881
|
function getSavedRepos() {
|
|
2771
3882
|
const state = load();
|
|
@@ -2829,7 +3940,7 @@ function nextChatTabSeq(tabs) {
|
|
|
2829
3940
|
max = t.seq;
|
|
2830
3941
|
return max + 1;
|
|
2831
3942
|
}
|
|
2832
|
-
var toTaskId = (id) => id;
|
|
3943
|
+
var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude";
|
|
2833
3944
|
|
|
2834
3945
|
// src/orchestrator/index/ulid.ts
|
|
2835
3946
|
function encodeTime(now, len) {
|
|
@@ -2891,7 +4002,7 @@ var init_ulid = __esm(() => {
|
|
|
2891
4002
|
});
|
|
2892
4003
|
|
|
2893
4004
|
// src/orchestrator/metadata-suggester.ts
|
|
2894
|
-
import { spawn as
|
|
4005
|
+
import { spawn as spawn4 } from "child_process";
|
|
2895
4006
|
|
|
2896
4007
|
class MetadataSuggester {
|
|
2897
4008
|
binaryPromise = null;
|
|
@@ -2920,7 +4031,7 @@ class MetadataSuggester {
|
|
|
2920
4031
|
return new Promise((resolve2) => {
|
|
2921
4032
|
let proc;
|
|
2922
4033
|
try {
|
|
2923
|
-
proc =
|
|
4034
|
+
proc = spawn4(binary, ["-p", builder(trimmed)], {
|
|
2924
4035
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2925
4036
|
env: process.env
|
|
2926
4037
|
});
|
|
@@ -3069,10 +4180,10 @@ class InMemoryPendingInputBroker {
|
|
|
3069
4180
|
}
|
|
3070
4181
|
|
|
3071
4182
|
// src/orchestrator/pr/build.ts
|
|
3072
|
-
import { spawnSync as
|
|
4183
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
3073
4184
|
function git(cwd, args) {
|
|
3074
4185
|
try {
|
|
3075
|
-
const out =
|
|
4186
|
+
const out = spawnSync4("git", args.slice(), {
|
|
3076
4187
|
cwd,
|
|
3077
4188
|
encoding: "utf8",
|
|
3078
4189
|
timeout: GIT_TIMEOUT_MS
|
|
@@ -3122,7 +4233,7 @@ var init_build = () => {};
|
|
|
3122
4233
|
|
|
3123
4234
|
// src/orchestrator/pr/instructions.ts
|
|
3124
4235
|
import { promises as fs } from "fs";
|
|
3125
|
-
import
|
|
4236
|
+
import path6 from "path";
|
|
3126
4237
|
function dirtyCountSentence(n) {
|
|
3127
4238
|
if (n <= 0)
|
|
3128
4239
|
return "There are no uncommitted changes.";
|
|
@@ -3150,7 +4261,7 @@ function renderPRInstructions(template, state) {
|
|
|
3150
4261
|
async function loadPRInstructionsTemplate(worktreePath) {
|
|
3151
4262
|
if (!worktreePath)
|
|
3152
4263
|
return DEFAULT_PR_INSTRUCTIONS_TEMPLATE;
|
|
3153
|
-
const file =
|
|
4264
|
+
const file = path6.join(worktreePath, ".kobe", "pr-instructions.md");
|
|
3154
4265
|
try {
|
|
3155
4266
|
const text = await fs.readFile(file, "utf8");
|
|
3156
4267
|
if (text.length === 0)
|
|
@@ -3201,10 +4312,11 @@ class SessionPump {
|
|
|
3201
4312
|
}
|
|
3202
4313
|
async run(taskId, tabId, handle) {
|
|
3203
4314
|
const tabKey = chatRunStateKey(taskId, tabId);
|
|
4315
|
+
const engine = this.env.engineFor(taskId, tabId);
|
|
3204
4316
|
let killedForInput = false;
|
|
3205
4317
|
let terminalEvent = null;
|
|
3206
4318
|
try {
|
|
3207
|
-
for await (const ev of
|
|
4319
|
+
for await (const ev of engine.stream(handle)) {
|
|
3208
4320
|
const inputReq = detectUserInputFromEngineEvent(ev);
|
|
3209
4321
|
if (inputReq) {
|
|
3210
4322
|
this.env.dispatch(taskId, tabId, ev);
|
|
@@ -3218,7 +4330,7 @@ class SessionPump {
|
|
|
3218
4330
|
});
|
|
3219
4331
|
killedForInput = true;
|
|
3220
4332
|
try {
|
|
3221
|
-
await
|
|
4333
|
+
await engine.stop(handle);
|
|
3222
4334
|
} catch {}
|
|
3223
4335
|
break;
|
|
3224
4336
|
}
|
|
@@ -3231,7 +4343,7 @@ class SessionPump {
|
|
|
3231
4343
|
} finally {
|
|
3232
4344
|
if (!killedForInput) {
|
|
3233
4345
|
try {
|
|
3234
|
-
await
|
|
4346
|
+
await engine.stop(handle);
|
|
3235
4347
|
} catch {}
|
|
3236
4348
|
}
|
|
3237
4349
|
}
|
|
@@ -3291,7 +4403,8 @@ function summarizeWorktreeError(raw, repo, baseRef) {
|
|
|
3291
4403
|
}
|
|
3292
4404
|
|
|
3293
4405
|
class Orchestrator {
|
|
3294
|
-
|
|
4406
|
+
engines;
|
|
4407
|
+
fallbackEngine;
|
|
3295
4408
|
store;
|
|
3296
4409
|
worktrees;
|
|
3297
4410
|
metadataSuggester;
|
|
@@ -3309,7 +4422,26 @@ class Orchestrator {
|
|
|
3309
4422
|
runStateAcc;
|
|
3310
4423
|
setRunState;
|
|
3311
4424
|
constructor(deps) {
|
|
3312
|
-
|
|
4425
|
+
const built = {};
|
|
4426
|
+
let fallback;
|
|
4427
|
+
if (deps.engines) {
|
|
4428
|
+
for (const [vendor, eng] of Object.entries(deps.engines)) {
|
|
4429
|
+
if (!eng)
|
|
4430
|
+
continue;
|
|
4431
|
+
built[vendor] = eng;
|
|
4432
|
+
fallback ??= eng;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
if (deps.engine) {
|
|
4436
|
+
const v = deps.engine.capabilities.vendorId;
|
|
4437
|
+
built[v] ??= deps.engine;
|
|
4438
|
+
fallback ??= deps.engine;
|
|
4439
|
+
}
|
|
4440
|
+
if (!fallback) {
|
|
4441
|
+
throw new Error("Orchestrator: no usable engine found; both deps.engine and deps.engines were examined but contained no valid engines.");
|
|
4442
|
+
}
|
|
4443
|
+
this.engines = built;
|
|
4444
|
+
this.fallbackEngine = fallback;
|
|
3313
4445
|
this.store = deps.store;
|
|
3314
4446
|
this.worktrees = deps.worktrees;
|
|
3315
4447
|
this.metadataSuggester = deps.metadataSuggester ?? new MetadataSuggester;
|
|
@@ -3323,13 +4455,86 @@ class Orchestrator {
|
|
|
3323
4455
|
this.runStateAcc = runState;
|
|
3324
4456
|
this.setRunState = (next) => setRunState(() => next);
|
|
3325
4457
|
this.sessionPump = new SessionPump({
|
|
3326
|
-
|
|
4458
|
+
engineFor: (taskId, tabId) => this.engineForTaskTabId(taskId, tabId),
|
|
3327
4459
|
broker: this.pendingInputBroker,
|
|
3328
4460
|
dispatch: (taskId, tabId, ev) => this.dispatchEvent(taskId, tabId, ev),
|
|
3329
4461
|
nextRequestId: () => `req-${++this.requestIdCounter}`,
|
|
3330
4462
|
onPendingInputChange: () => this.bumpRunState()
|
|
3331
4463
|
});
|
|
3332
4464
|
}
|
|
4465
|
+
engineForVendor(vendor) {
|
|
4466
|
+
const v = vendor ?? DEFAULT_TASK_VENDOR;
|
|
4467
|
+
return this.engines[v] ?? this.fallbackEngine;
|
|
4468
|
+
}
|
|
4469
|
+
engineForTask(task) {
|
|
4470
|
+
return this.engineForVendor(task.vendor);
|
|
4471
|
+
}
|
|
4472
|
+
vendorForTab(task, tab) {
|
|
4473
|
+
return tab.vendor ?? task.vendor ?? "claude";
|
|
4474
|
+
}
|
|
4475
|
+
modelForTab(task, tab, engine) {
|
|
4476
|
+
return tab.model ?? task.model ?? engine.capabilities.defaultModelId();
|
|
4477
|
+
}
|
|
4478
|
+
engineForTab(task, tab) {
|
|
4479
|
+
return this.engineForVendor(this.vendorForTab(task, tab));
|
|
4480
|
+
}
|
|
4481
|
+
async engineForTabRun(task, tab) {
|
|
4482
|
+
if (!tab.sessionId || tab.vendor)
|
|
4483
|
+
return this.engineForTab(task, tab);
|
|
4484
|
+
const resolved = await this.findEngineWithHistory(tab.sessionId, this.vendorForTab(task, tab));
|
|
4485
|
+
if (resolved.vendor && resolved.vendor !== tab.vendor) {
|
|
4486
|
+
await this.updateTab(task.id, tab.id, { vendor: resolved.vendor });
|
|
4487
|
+
}
|
|
4488
|
+
return resolved.engine;
|
|
4489
|
+
}
|
|
4490
|
+
async findEngineWithHistory(sessionId, preferredVendor) {
|
|
4491
|
+
const candidates = [];
|
|
4492
|
+
if (preferredVendor)
|
|
4493
|
+
candidates.push([preferredVendor, this.engineForVendor(preferredVendor)]);
|
|
4494
|
+
for (const [vendor, engine] of Object.entries(this.engines)) {
|
|
4495
|
+
if (!engine || vendor === preferredVendor)
|
|
4496
|
+
continue;
|
|
4497
|
+
candidates.push([vendor, engine]);
|
|
4498
|
+
}
|
|
4499
|
+
if (candidates.length === 0)
|
|
4500
|
+
candidates.push([undefined, this.fallbackEngine]);
|
|
4501
|
+
let fallback = candidates[0] ?? [undefined, this.fallbackEngine];
|
|
4502
|
+
let fallbackHistory;
|
|
4503
|
+
for (const [vendor, engine] of candidates) {
|
|
4504
|
+
try {
|
|
4505
|
+
const history = await engine.readHistory(sessionId);
|
|
4506
|
+
if (!fallbackHistory) {
|
|
4507
|
+
fallback = [vendor, engine];
|
|
4508
|
+
fallbackHistory = history;
|
|
4509
|
+
}
|
|
4510
|
+
if (history.messages.length > 0 || history.usageMetrics)
|
|
4511
|
+
return { engine, vendor, history };
|
|
4512
|
+
} catch {}
|
|
4513
|
+
}
|
|
4514
|
+
return { engine: fallback[1], vendor: fallback[0], history: fallbackHistory };
|
|
4515
|
+
}
|
|
4516
|
+
engineForTaskId(taskId) {
|
|
4517
|
+
const task = this.store.get(taskId);
|
|
4518
|
+
return task ? this.engineForTask(task) : this.fallbackEngine;
|
|
4519
|
+
}
|
|
4520
|
+
engineForTaskTabId(taskId, tabId) {
|
|
4521
|
+
const task = this.store.get(taskId);
|
|
4522
|
+
if (!task)
|
|
4523
|
+
return this.fallbackEngine;
|
|
4524
|
+
const tab = task.tabs.find((t) => t.id === tabId);
|
|
4525
|
+
return tab ? this.engineForTab(task, tab) : this.engineForTask(task);
|
|
4526
|
+
}
|
|
4527
|
+
engineForSessionId(sessionId) {
|
|
4528
|
+
for (const task of this.store.list()) {
|
|
4529
|
+
for (const tab of task.tabs) {
|
|
4530
|
+
if (tab.sessionId === sessionId)
|
|
4531
|
+
return this.engineForTab(task, tab);
|
|
4532
|
+
}
|
|
4533
|
+
if (task.sessionId === sessionId)
|
|
4534
|
+
return this.engineForTask(task);
|
|
4535
|
+
}
|
|
4536
|
+
return this.fallbackEngine;
|
|
4537
|
+
}
|
|
3333
4538
|
bumpRunState() {
|
|
3334
4539
|
const next = new Map;
|
|
3335
4540
|
for (const tabKey2 of this.pendingInputBroker.awaitingTabKeys()) {
|
|
@@ -3549,10 +4754,11 @@ class Orchestrator {
|
|
|
3549
4754
|
if (prompt && prompt.trim().length > 0) {
|
|
3550
4755
|
this.dispatchEvent(task.id, targetTab.id, { type: "user.inject", text: prompt });
|
|
3551
4756
|
}
|
|
3552
|
-
const
|
|
4757
|
+
const engine = targetTab.sessionId ? await this.engineForTabRun(task, targetTab) : this.engineForTab(task, targetTab);
|
|
4758
|
+
const modelToUse = this.modelForTab(task, targetTab, engine);
|
|
3553
4759
|
let handle;
|
|
3554
4760
|
if (targetTab.sessionId) {
|
|
3555
|
-
handle = await
|
|
4761
|
+
handle = await engine.resume(targetTab.sessionId, promptToSend, {
|
|
3556
4762
|
cwd: task.worktreePath,
|
|
3557
4763
|
env: { KOBE_RESUME_CWD: task.worktreePath },
|
|
3558
4764
|
permissionMode: task.permissionMode,
|
|
@@ -3565,7 +4771,7 @@ class Orchestrator {
|
|
|
3565
4771
|
});
|
|
3566
4772
|
this.firstSpawnLatches.set(key, latch);
|
|
3567
4773
|
try {
|
|
3568
|
-
handle = await
|
|
4774
|
+
handle = await engine.spawn(task.worktreePath, promptToSend, {
|
|
3569
4775
|
permissionMode: task.permissionMode,
|
|
3570
4776
|
model: modelToUse
|
|
3571
4777
|
});
|
|
@@ -3640,7 +4846,7 @@ class Orchestrator {
|
|
|
3640
4846
|
text: "(turn interrupted \u2014 sending new prompt)"
|
|
3641
4847
|
});
|
|
3642
4848
|
try {
|
|
3643
|
-
await this.
|
|
4849
|
+
await this.engineForTask(task).stop(handle);
|
|
3644
4850
|
} finally {
|
|
3645
4851
|
this.handles.delete(key);
|
|
3646
4852
|
this.bumpRunState();
|
|
@@ -3682,11 +4888,13 @@ class Orchestrator {
|
|
|
3682
4888
|
return;
|
|
3683
4889
|
await this.store.update(task.id, { permissionMode: mode });
|
|
3684
4890
|
}
|
|
3685
|
-
async setModel(id, model) {
|
|
4891
|
+
async setModel(id, model, tabId) {
|
|
3686
4892
|
const task = this.requireTask(id);
|
|
3687
|
-
|
|
4893
|
+
const tab = this.resolveTab(task, tabId);
|
|
4894
|
+
const vendor = model ? capabilitiesForModelId(model).vendorId : this.vendorForTab(task, tab);
|
|
4895
|
+
if (tab.model === model && this.vendorForTab(task, tab) === vendor)
|
|
3688
4896
|
return;
|
|
3689
|
-
await this.
|
|
4897
|
+
await this.updateTab(task.id, tab.id, { model, vendor });
|
|
3690
4898
|
}
|
|
3691
4899
|
async setTitle(id, title) {
|
|
3692
4900
|
const task = this.requireTask(id);
|
|
@@ -3750,7 +4958,7 @@ class Orchestrator {
|
|
|
3750
4958
|
}
|
|
3751
4959
|
if (task.sessionId) {
|
|
3752
4960
|
try {
|
|
3753
|
-
await this.
|
|
4961
|
+
await this.engineForTask(task).deleteHistory(task.sessionId);
|
|
3754
4962
|
} catch (err) {
|
|
3755
4963
|
console.error(`[kobe orchestrator] deleteTask: deleteHistory failed for ${task.id}:`, err);
|
|
3756
4964
|
}
|
|
@@ -3759,15 +4967,15 @@ class Orchestrator {
|
|
|
3759
4967
|
await this.store.remove(task.id);
|
|
3760
4968
|
}
|
|
3761
4969
|
async readHistory(sessionId) {
|
|
3762
|
-
|
|
3763
|
-
return await this.engine.readHistory(sessionId);
|
|
3764
|
-
} catch {
|
|
3765
|
-
return [];
|
|
3766
|
-
}
|
|
4970
|
+
return (await this.readHistoryWithMetrics(sessionId)).messages;
|
|
3767
4971
|
}
|
|
3768
4972
|
async readHistoryWithMetrics(sessionId) {
|
|
3769
|
-
const
|
|
3770
|
-
const
|
|
4973
|
+
const preferred = this.engineForSessionId(sessionId);
|
|
4974
|
+
const { history } = await this.findEngineWithHistory(sessionId, preferred.capabilities.vendorId);
|
|
4975
|
+
if (!history)
|
|
4976
|
+
return { messages: [] };
|
|
4977
|
+
const messages = [...history.messages];
|
|
4978
|
+
const usageMetrics = history.usageMetrics;
|
|
3771
4979
|
return {
|
|
3772
4980
|
messages,
|
|
3773
4981
|
...usageMetrics ? { usageMetrics } : {}
|
|
@@ -3777,14 +4985,16 @@ class Orchestrator {
|
|
|
3777
4985
|
const task = this.requireTask(id);
|
|
3778
4986
|
if (!task.worktreePath)
|
|
3779
4987
|
return [];
|
|
4988
|
+
const tab = this.resolveTab(task);
|
|
3780
4989
|
try {
|
|
3781
|
-
return await this.
|
|
4990
|
+
return await this.engineForTab(task, tab).listSessions(task.worktreePath);
|
|
3782
4991
|
} catch {
|
|
3783
4992
|
return [];
|
|
3784
4993
|
}
|
|
3785
4994
|
}
|
|
3786
4995
|
async openSessionInTab(id, sessionId, opts = {}) {
|
|
3787
4996
|
const task = this.requireTask(id);
|
|
4997
|
+
const active = this.resolveTab(task);
|
|
3788
4998
|
const existing = task.tabs.find((t) => t.sessionId === sessionId);
|
|
3789
4999
|
if (existing) {
|
|
3790
5000
|
await this.setActiveTab(task.id, existing.id);
|
|
@@ -3795,6 +5005,8 @@ class Orchestrator {
|
|
|
3795
5005
|
sessionId,
|
|
3796
5006
|
seq: nextChatTabSeq(task.tabs),
|
|
3797
5007
|
createdAt: new Date().toISOString(),
|
|
5008
|
+
model: active.model ?? task.model,
|
|
5009
|
+
vendor: this.vendorForTab(task, active),
|
|
3798
5010
|
...opts.title ? { title: opts.title } : {}
|
|
3799
5011
|
};
|
|
3800
5012
|
await this.store.update(task.id, { tabs: [...task.tabs, tab], activeTabId: tab.id });
|
|
@@ -3822,11 +5034,14 @@ class Orchestrator {
|
|
|
3822
5034
|
}
|
|
3823
5035
|
async createTab(id, opts = {}) {
|
|
3824
5036
|
const task = this.requireTask(id);
|
|
5037
|
+
const active = this.resolveTab(task);
|
|
3825
5038
|
const tab = {
|
|
3826
5039
|
id: ulid(),
|
|
3827
5040
|
sessionId: null,
|
|
3828
5041
|
seq: nextChatTabSeq(task.tabs),
|
|
3829
5042
|
createdAt: new Date().toISOString(),
|
|
5043
|
+
model: active.model ?? task.model,
|
|
5044
|
+
vendor: this.vendorForTab(task, active),
|
|
3830
5045
|
...opts.title ? { title: opts.title } : {}
|
|
3831
5046
|
};
|
|
3832
5047
|
const tabs = [...task.tabs, tab];
|
|
@@ -3906,7 +5121,7 @@ class Orchestrator {
|
|
|
3906
5121
|
if (!handle)
|
|
3907
5122
|
return;
|
|
3908
5123
|
try {
|
|
3909
|
-
await this.
|
|
5124
|
+
await this.engineForTaskId(taskId).stop(handle);
|
|
3910
5125
|
} finally {
|
|
3911
5126
|
this.handles.delete(key);
|
|
3912
5127
|
this.bumpRunState();
|
|
@@ -3915,12 +5130,13 @@ class Orchestrator {
|
|
|
3915
5130
|
async stopAllTabsForTask(taskId) {
|
|
3916
5131
|
const prefix = `${taskId}:`;
|
|
3917
5132
|
const keys = Array.from(this.handles.keys()).filter((k) => k.startsWith(prefix));
|
|
5133
|
+
const engine = this.engineForTaskId(taskId);
|
|
3918
5134
|
for (const key of keys) {
|
|
3919
5135
|
const handle = this.handles.get(key);
|
|
3920
5136
|
if (!handle)
|
|
3921
5137
|
continue;
|
|
3922
5138
|
try {
|
|
3923
|
-
await
|
|
5139
|
+
await engine.stop(handle);
|
|
3924
5140
|
} catch {}
|
|
3925
5141
|
this.handles.delete(key);
|
|
3926
5142
|
}
|
|
@@ -4046,7 +5262,7 @@ function deriveTitleFromPrompt(prompt) {
|
|
|
4046
5262
|
var CONCURRENCY_CAP = 20, PLACEHOLDER_TASK_TITLE = "(new task)", IllegalTransitionError, ConcurrencyCapError, PRPreconditionError, TaskNotFoundError, CannotDeleteMainTaskError, TITLE_CHAR_CAP = 40;
|
|
4047
5263
|
var init_core = __esm(() => {
|
|
4048
5264
|
init_dev();
|
|
4049
|
-
|
|
5265
|
+
init_registry();
|
|
4050
5266
|
init_repos();
|
|
4051
5267
|
init_ulid();
|
|
4052
5268
|
init_metadata_suggester();
|
|
@@ -4091,9 +5307,9 @@ var init_core = __esm(() => {
|
|
|
4091
5307
|
});
|
|
4092
5308
|
|
|
4093
5309
|
// src/orchestrator/index/store.ts
|
|
4094
|
-
import { mkdir as mkdir4, open, readFile as
|
|
4095
|
-
import { homedir as
|
|
4096
|
-
import { dirname as dirname4, join as
|
|
5310
|
+
import { mkdir as mkdir4, open as open2, readFile as readFile4, rename, unlink as unlink4 } from "fs/promises";
|
|
5311
|
+
import { homedir as homedir11 } from "os";
|
|
5312
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
4097
5313
|
|
|
4098
5314
|
class TaskIndexStore {
|
|
4099
5315
|
homeDir;
|
|
@@ -4105,9 +5321,9 @@ class TaskIndexStore {
|
|
|
4105
5321
|
listeners = new Set;
|
|
4106
5322
|
saveChain = Promise.resolve();
|
|
4107
5323
|
constructor(options = {}) {
|
|
4108
|
-
this.homeDir = options.homeDir ??
|
|
4109
|
-
this.kobeDir =
|
|
4110
|
-
this.path =
|
|
5324
|
+
this.homeDir = options.homeDir ?? homedir11();
|
|
5325
|
+
this.kobeDir = join7(this.homeDir, ".kobe");
|
|
5326
|
+
this.path = join7(this.kobeDir, "tasks.json");
|
|
4111
5327
|
this.tmpPath = `${this.path}.tmp`;
|
|
4112
5328
|
}
|
|
4113
5329
|
subscribe(listener) {
|
|
@@ -4132,7 +5348,7 @@ class TaskIndexStore {
|
|
|
4132
5348
|
async load() {
|
|
4133
5349
|
let raw;
|
|
4134
5350
|
try {
|
|
4135
|
-
raw = await
|
|
5351
|
+
raw = await readFile4(this.path, "utf8");
|
|
4136
5352
|
} catch (err) {
|
|
4137
5353
|
const code = err.code;
|
|
4138
5354
|
if (code === "ENOENT") {
|
|
@@ -4169,7 +5385,7 @@ class TaskIndexStore {
|
|
|
4169
5385
|
const payload = this.snapshot();
|
|
4170
5386
|
const json = `${JSON.stringify(payload, null, 2)}
|
|
4171
5387
|
`;
|
|
4172
|
-
const handle = await
|
|
5388
|
+
const handle = await open2(this.tmpPath, "w", 420);
|
|
4173
5389
|
try {
|
|
4174
5390
|
await handle.writeFile(json, "utf8");
|
|
4175
5391
|
await handle.sync();
|
|
@@ -4197,7 +5413,16 @@ class TaskIndexStore {
|
|
|
4197
5413
|
activeTabId = activeIn && tabsIn.some((t) => t.id === activeIn) ? activeIn : tabsIn[0]?.id ?? "";
|
|
4198
5414
|
} else {
|
|
4199
5415
|
const tabId = ulid();
|
|
4200
|
-
tabs = [
|
|
5416
|
+
tabs = [
|
|
5417
|
+
{
|
|
5418
|
+
id: tabId,
|
|
5419
|
+
sessionId: sessionId ?? null,
|
|
5420
|
+
seq: 1,
|
|
5421
|
+
createdAt: now,
|
|
5422
|
+
...rest.model ? { model: rest.model } : {},
|
|
5423
|
+
vendor: rest.vendor ?? DEFAULT_TASK_VENDOR
|
|
5424
|
+
}
|
|
5425
|
+
];
|
|
4201
5426
|
activeTabId = tabId;
|
|
4202
5427
|
}
|
|
4203
5428
|
const firstSession = tabs[0]?.sessionId ?? null;
|
|
@@ -4264,13 +5489,13 @@ class TaskIndexStore {
|
|
|
4264
5489
|
}
|
|
4265
5490
|
async _unlinkForTests() {
|
|
4266
5491
|
try {
|
|
4267
|
-
await
|
|
5492
|
+
await unlink4(this.path);
|
|
4268
5493
|
} catch (err) {
|
|
4269
5494
|
if (err.code !== "ENOENT")
|
|
4270
5495
|
throw err;
|
|
4271
5496
|
}
|
|
4272
5497
|
try {
|
|
4273
|
-
await
|
|
5498
|
+
await unlink4(this.tmpPath);
|
|
4274
5499
|
} catch (err) {
|
|
4275
5500
|
if (err.code !== "ENOENT")
|
|
4276
5501
|
throw err;
|
|
@@ -4360,7 +5585,9 @@ function coerceTask(value) {
|
|
|
4360
5585
|
sessionId: tt.sessionId ?? null,
|
|
4361
5586
|
seq,
|
|
4362
5587
|
createdAt: tt.createdAt,
|
|
4363
|
-
...typeof tt.title === "string" ? { title: tt.title } : {}
|
|
5588
|
+
...typeof tt.title === "string" ? { title: tt.title } : {},
|
|
5589
|
+
...typeof tt.model === "string" ? { model: tt.model } : {},
|
|
5590
|
+
...isVendorId(tt.vendor) ? { vendor: tt.vendor } : {}
|
|
4364
5591
|
};
|
|
4365
5592
|
tabs.push(tab);
|
|
4366
5593
|
}
|
|
@@ -4393,6 +5620,7 @@ function coerceTask(value) {
|
|
|
4393
5620
|
kind: v.kind === "main" ? "main" : "task",
|
|
4394
5621
|
permissionMode: isPermissionMode(v.permissionMode) ? v.permissionMode : undefined,
|
|
4395
5622
|
model: typeof v.model === "string" ? v.model : undefined,
|
|
5623
|
+
vendor: resolveTaskVendor(v.vendor, typeof v.model === "string" ? v.model : undefined),
|
|
4396
5624
|
createdAt: v.createdAt,
|
|
4397
5625
|
updatedAt: v.updatedAt
|
|
4398
5626
|
};
|
|
@@ -4400,20 +5628,33 @@ function coerceTask(value) {
|
|
|
4400
5628
|
function isPermissionMode(v) {
|
|
4401
5629
|
return v === "default" || v === "plan";
|
|
4402
5630
|
}
|
|
5631
|
+
function isVendorId(v) {
|
|
5632
|
+
return typeof v === "string" && v in ENGINE_REGISTRY;
|
|
5633
|
+
}
|
|
5634
|
+
function resolveTaskVendor(rawVendor, modelId) {
|
|
5635
|
+
const stored = isVendorId(rawVendor) ? rawVendor : DEFAULT_TASK_VENDOR;
|
|
5636
|
+
if (!modelId)
|
|
5637
|
+
return stored;
|
|
5638
|
+
const matched = Object.values(ENGINE_REGISTRY).some((caps) => caps?.models.some((m) => m.id === modelId));
|
|
5639
|
+
if (!matched)
|
|
5640
|
+
return stored;
|
|
5641
|
+
return capabilitiesForModelId(modelId).vendorId;
|
|
5642
|
+
}
|
|
4403
5643
|
function isTaskStatus(s) {
|
|
4404
5644
|
return s === "backlog" || s === "in_progress" || s === "in_review" || s === "done" || s === "canceled" || s === "error";
|
|
4405
5645
|
}
|
|
4406
5646
|
var init_store = __esm(() => {
|
|
5647
|
+
init_registry();
|
|
4407
5648
|
init_ulid();
|
|
4408
5649
|
});
|
|
4409
5650
|
|
|
4410
5651
|
// src/orchestrator/worktree/git.ts
|
|
4411
|
-
import { spawnSync as
|
|
5652
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
4412
5653
|
function git2(args, opts) {
|
|
4413
5654
|
if (!opts.cwd) {
|
|
4414
5655
|
throw new Error("git(): cwd is required; refusing to inherit from process.cwd()");
|
|
4415
5656
|
}
|
|
4416
|
-
const proc =
|
|
5657
|
+
const proc = spawnSync5("git", [...args], {
|
|
4417
5658
|
cwd: opts.cwd,
|
|
4418
5659
|
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
4419
5660
|
encoding: "utf8",
|
|
@@ -4451,32 +5692,32 @@ var init_git = __esm(() => {
|
|
|
4451
5692
|
|
|
4452
5693
|
// src/orchestrator/worktree/paths.ts
|
|
4453
5694
|
import fs2 from "fs";
|
|
4454
|
-
import
|
|
5695
|
+
import path7 from "path";
|
|
4455
5696
|
function worktreeRootFor(repo) {
|
|
4456
|
-
if (!
|
|
5697
|
+
if (!path7.isAbsolute(repo)) {
|
|
4457
5698
|
throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
|
|
4458
5699
|
}
|
|
4459
|
-
return
|
|
5700
|
+
return path7.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
|
|
4460
5701
|
}
|
|
4461
5702
|
function worktreePathFor(repo, taskId) {
|
|
4462
5703
|
if (!taskId || /[/\\\0]/.test(taskId)) {
|
|
4463
5704
|
throw new Error(`worktreePathFor: invalid taskId: ${JSON.stringify(taskId)}`);
|
|
4464
5705
|
}
|
|
4465
|
-
return
|
|
5706
|
+
return path7.join(worktreeRootFor(repo), taskId);
|
|
4466
5707
|
}
|
|
4467
5708
|
function isKobeManagedPath(repo, candidate) {
|
|
4468
|
-
if (!
|
|
5709
|
+
if (!path7.isAbsolute(repo) || !path7.isAbsolute(candidate))
|
|
4469
5710
|
return false;
|
|
4470
5711
|
const root = canonicalize(worktreeRootFor(repo));
|
|
4471
5712
|
const target = canonicalize(candidate);
|
|
4472
|
-
const rel =
|
|
4473
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
5713
|
+
const rel = path7.relative(root, target);
|
|
5714
|
+
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
4474
5715
|
}
|
|
4475
5716
|
function canonicalize(p) {
|
|
4476
5717
|
try {
|
|
4477
5718
|
return fs2.realpathSync(p);
|
|
4478
5719
|
} catch {
|
|
4479
|
-
return
|
|
5720
|
+
return path7.resolve(p);
|
|
4480
5721
|
}
|
|
4481
5722
|
}
|
|
4482
5723
|
var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
|
|
@@ -4484,7 +5725,7 @@ var init_paths2 = () => {};
|
|
|
4484
5725
|
|
|
4485
5726
|
// src/orchestrator/worktree/manager.ts
|
|
4486
5727
|
import fs3 from "fs";
|
|
4487
|
-
import
|
|
5728
|
+
import path8 from "path";
|
|
4488
5729
|
|
|
4489
5730
|
class GitWorktreeManager {
|
|
4490
5731
|
async create(repo, branch, worktreePath, baseRef) {
|
|
@@ -4502,7 +5743,7 @@ class GitWorktreeManager {
|
|
|
4502
5743
|
}
|
|
4503
5744
|
throw new Error(`create(): ${worktreePath} exists but is not a registered git worktree`);
|
|
4504
5745
|
}
|
|
4505
|
-
fs3.mkdirSync(
|
|
5746
|
+
fs3.mkdirSync(path8.dirname(worktreePath), { recursive: true });
|
|
4506
5747
|
const branchExists = this.branchExists(repo, branch);
|
|
4507
5748
|
const args = branchExists ? ["worktree", "add", worktreePath, branch] : baseRef ? ["worktree", "add", "-b", branch, worktreePath, baseRef] : ["worktree", "add", "-b", branch, worktreePath];
|
|
4508
5749
|
git2(args, { cwd: repo });
|
|
@@ -4557,8 +5798,8 @@ class GitWorktreeManager {
|
|
|
4557
5798
|
if (!entry.branch || entry.detached)
|
|
4558
5799
|
continue;
|
|
4559
5800
|
const canonEntry = canonicalize2(entry.path);
|
|
4560
|
-
const rel =
|
|
4561
|
-
const callerPath =
|
|
5801
|
+
const rel = path8.relative(canonRoot, canonEntry);
|
|
5802
|
+
const callerPath = path8.join(callerRoot, rel);
|
|
4562
5803
|
const dirty = await this.isDirty(entry.path);
|
|
4563
5804
|
infos.push({
|
|
4564
5805
|
path: callerPath,
|
|
@@ -4619,9 +5860,9 @@ class GitWorktreeManager {
|
|
|
4619
5860
|
const gitDir = out.stdout.trim();
|
|
4620
5861
|
if (!gitDir)
|
|
4621
5862
|
return null;
|
|
4622
|
-
const absolute =
|
|
4623
|
-
const base =
|
|
4624
|
-
return base === ".git" ?
|
|
5863
|
+
const absolute = path8.isAbsolute(gitDir) ? gitDir : path8.resolve(worktreePath, gitDir);
|
|
5864
|
+
const base = path8.basename(absolute);
|
|
5865
|
+
return base === ".git" ? path8.dirname(absolute) : absolute;
|
|
4625
5866
|
} catch (err) {
|
|
4626
5867
|
if (err instanceof GitCommandError)
|
|
4627
5868
|
return null;
|
|
@@ -4661,7 +5902,7 @@ function parsePorcelain(out) {
|
|
|
4661
5902
|
return records;
|
|
4662
5903
|
}
|
|
4663
5904
|
function requireAbsolute(name, value) {
|
|
4664
|
-
if (!value || !
|
|
5905
|
+
if (!value || !path8.isAbsolute(value)) {
|
|
4665
5906
|
throw new Error(`${name} must be an absolute path, got: ${JSON.stringify(value)}`);
|
|
4666
5907
|
}
|
|
4667
5908
|
}
|
|
@@ -4669,7 +5910,7 @@ function canonicalize2(p) {
|
|
|
4669
5910
|
try {
|
|
4670
5911
|
return fs3.realpathSync(p);
|
|
4671
5912
|
} catch {
|
|
4672
|
-
return
|
|
5913
|
+
return path8.resolve(p);
|
|
4673
5914
|
}
|
|
4674
5915
|
}
|
|
4675
5916
|
var init_manager = __esm(() => {
|
|
@@ -4680,22 +5921,26 @@ var init_manager = __esm(() => {
|
|
|
4680
5921
|
// src/bin/kobed.ts
|
|
4681
5922
|
init_daemon_process();
|
|
4682
5923
|
init_client();
|
|
4683
|
-
import { unlink as
|
|
5924
|
+
import { unlink as unlink6 } from "fs/promises";
|
|
4684
5925
|
|
|
4685
5926
|
// src/core/index.ts
|
|
4686
5927
|
init_claude_code_local();
|
|
5928
|
+
init_codex_local();
|
|
4687
5929
|
init_bridge();
|
|
4688
5930
|
init_core();
|
|
4689
5931
|
init_store();
|
|
4690
5932
|
init_manager();
|
|
4691
|
-
import { homedir as
|
|
5933
|
+
import { homedir as homedir12 } from "os";
|
|
4692
5934
|
async function createKobeCore(options = {}) {
|
|
4693
|
-
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
5935
|
+
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir12();
|
|
4694
5936
|
const store = new TaskIndexStore({ homeDir: homeDir2 });
|
|
4695
5937
|
await store.load();
|
|
4696
5938
|
const worktrees = new GitWorktreeManager;
|
|
4697
|
-
const
|
|
4698
|
-
|
|
5939
|
+
const engines = options.engines ?? (options.engine ? { [options.engine.capabilities.vendorId]: options.engine } : {
|
|
5940
|
+
claude: new ClaudeCodeLocal,
|
|
5941
|
+
codex: new CodexLocal
|
|
5942
|
+
});
|
|
5943
|
+
const orchestrator = new Orchestrator({ engines, store, worktrees });
|
|
4699
5944
|
const bridge = options.startMcpBridge === false ? null : await startBridge(orchestrator, { homeDir: homeDir2 });
|
|
4700
5945
|
return {
|
|
4701
5946
|
homeDir: homeDir2,
|
|
@@ -4716,16 +5961,16 @@ init_paths();
|
|
|
4716
5961
|
// src/daemon/server.ts
|
|
4717
5962
|
init_repos();
|
|
4718
5963
|
init_paths();
|
|
4719
|
-
import { mkdir as mkdir5, readFile as
|
|
5964
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink5, writeFile as writeFile4 } from "fs/promises";
|
|
4720
5965
|
import { createServer as createServer2 } from "net";
|
|
4721
5966
|
import { dirname as dirname5 } from "path";
|
|
4722
5967
|
|
|
4723
5968
|
// src/engine/claude-code-local/plan-usage.ts
|
|
4724
5969
|
import { execFile } from "child_process";
|
|
4725
|
-
import { createHash } from "crypto";
|
|
4726
|
-
import { readFile as
|
|
4727
|
-
import { homedir as
|
|
4728
|
-
import { join as
|
|
5970
|
+
import { createHash as createHash2 } from "crypto";
|
|
5971
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
5972
|
+
import { homedir as homedir13, userInfo } from "os";
|
|
5973
|
+
import { join as join8 } from "path";
|
|
4729
5974
|
import { promisify } from "util";
|
|
4730
5975
|
var execFileAsync = promisify(execFile);
|
|
4731
5976
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -4736,7 +5981,7 @@ function keychainServiceName() {
|
|
|
4736
5981
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4737
5982
|
if (!configDir)
|
|
4738
5983
|
return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}`;
|
|
4739
|
-
const hash =
|
|
5984
|
+
const hash = createHash2("sha256").update(configDir).digest("hex").slice(0, 8);
|
|
4740
5985
|
return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}-${hash}`;
|
|
4741
5986
|
}
|
|
4742
5987
|
function keychainAccount() {
|
|
@@ -4760,10 +6005,10 @@ async function readKeychainToken() {
|
|
|
4760
6005
|
}
|
|
4761
6006
|
}
|
|
4762
6007
|
async function readPlainTextToken() {
|
|
4763
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ??
|
|
4764
|
-
const
|
|
6008
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? join8(homedir13(), ".claude");
|
|
6009
|
+
const path9 = join8(configDir, ".credentials.json");
|
|
4765
6010
|
try {
|
|
4766
|
-
const raw = await
|
|
6011
|
+
const raw = await readFile5(path9, "utf8");
|
|
4767
6012
|
return parseStoredOAuth(raw);
|
|
4768
6013
|
} catch {
|
|
4769
6014
|
return null;
|
|
@@ -4876,7 +6121,7 @@ function createPlanUsagePoller(options) {
|
|
|
4876
6121
|
}
|
|
4877
6122
|
// src/daemon/rc-bridge.ts
|
|
4878
6123
|
init_binary();
|
|
4879
|
-
import { spawn as
|
|
6124
|
+
import { spawn as spawn5 } from "child_process";
|
|
4880
6125
|
var ENV_ID_RE = /Environment ID:\s*(env_[A-Za-z0-9]+)/;
|
|
4881
6126
|
var DEEPLINK_RE = /https:\/\/claude\.ai\/code\?environment=([A-Za-z0-9_]+)/;
|
|
4882
6127
|
var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
@@ -4884,7 +6129,7 @@ function createRcBridge(options = {}) {
|
|
|
4884
6129
|
const stopGraceMs = options.stopGraceMs ?? 5000;
|
|
4885
6130
|
const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
|
|
4886
6131
|
const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
|
|
4887
|
-
const spawner = options.spawner ?? ((cmd, args, cwd) =>
|
|
6132
|
+
const spawner = options.spawner ?? ((cmd, args, cwd) => spawn5(cmd, [...args], {
|
|
4888
6133
|
cwd,
|
|
4889
6134
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4890
6135
|
env: { ...process.env }
|
|
@@ -5058,7 +6303,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5058
6303
|
let nextClientId = 1;
|
|
5059
6304
|
await mkdir5(dirname5(socketPath), { recursive: true });
|
|
5060
6305
|
await mkdir5(dirname5(pidPath), { recursive: true });
|
|
5061
|
-
await
|
|
6306
|
+
await unlink5(socketPath).catch(() => {});
|
|
5062
6307
|
const server = createServer2((socket) => {
|
|
5063
6308
|
const client = {
|
|
5064
6309
|
id: nextClientId++,
|
|
@@ -5103,8 +6348,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5103
6348
|
client.socket.destroy();
|
|
5104
6349
|
}
|
|
5105
6350
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
5106
|
-
await
|
|
5107
|
-
await
|
|
6351
|
+
await unlink5(socketPath).catch(() => {});
|
|
6352
|
+
await unlink5(pidPath).catch(() => {});
|
|
5108
6353
|
}
|
|
5109
6354
|
};
|
|
5110
6355
|
planUsagePoller.start();
|
|
@@ -5218,7 +6463,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5218
6463
|
}
|
|
5219
6464
|
case "task.model": {
|
|
5220
6465
|
const taskId = requireString2(payload, "taskId");
|
|
5221
|
-
await orch.setModel(taskId, optionalString2(payload, "model"));
|
|
6466
|
+
await orch.setModel(taskId, optionalString2(payload, "model"), optionalString2(payload, "tabId"));
|
|
5222
6467
|
broadcastTaskUpdated(orch, clients, taskId);
|
|
5223
6468
|
return {};
|
|
5224
6469
|
}
|
|
@@ -5417,7 +6662,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5417
6662
|
}
|
|
5418
6663
|
async function readPidFile(pidPath) {
|
|
5419
6664
|
try {
|
|
5420
|
-
const raw = await
|
|
6665
|
+
const raw = await readFile6(pidPath, "utf8");
|
|
5421
6666
|
const pid = Number(raw.trim());
|
|
5422
6667
|
return Number.isFinite(pid) ? pid : null;
|
|
5423
6668
|
} catch {
|
|
@@ -5455,8 +6700,7 @@ async function readTaskHistory(orch, taskId, requestedSessionId, limit, before)
|
|
|
5455
6700
|
const sessionId = requestedSessionId ?? task?.tabs.find((t) => t.id === task.activeTabId)?.sessionId ?? task?.sessionId;
|
|
5456
6701
|
if (!sessionId)
|
|
5457
6702
|
return { messages: [], nextBefore: null, hasMore: false };
|
|
5458
|
-
const messages = await orch.
|
|
5459
|
-
const usageMetrics = deriveSessionUsageMetrics(messages);
|
|
6703
|
+
const { messages, usageMetrics } = await orch.readHistoryWithMetrics(sessionId);
|
|
5460
6704
|
const beforeIdx = before ? messages.findIndex((m) => `${m.timestamp}:${m.sessionId}` === before) : -1;
|
|
5461
6705
|
const end = beforeIdx >= 0 ? beforeIdx : messages.length;
|
|
5462
6706
|
const start = Math.max(0, end - limit);
|
|
@@ -5602,7 +6846,7 @@ async function main() {
|
|
|
5602
6846
|
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
5603
6847
|
} catch {}
|
|
5604
6848
|
}
|
|
5605
|
-
await
|
|
6849
|
+
await unlink6(socketPath).catch(() => {});
|
|
5606
6850
|
const next = await connectOrStartDaemon();
|
|
5607
6851
|
next.close();
|
|
5608
6852
|
console.log(`kobed: restarted, listening on ${socketPath}`);
|