@sma1lboy/kobe 0.5.13 → 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/README.md +2 -5
- package/dist/bin/kobed.js +1584 -313
- package/dist/cli/index.js +10057 -2701
- package/package.json +5 -2
package/dist/bin/kobed.js
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
33
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4
34
|
var __returnValue = (v) => v;
|
|
5
35
|
function __exportSetter(name, newValue) {
|
|
6
36
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -18,17 +48,38 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
18
48
|
var __require = import.meta.require;
|
|
19
49
|
|
|
20
50
|
// src/daemon/paths.ts
|
|
51
|
+
import { createHash } from "crypto";
|
|
21
52
|
import { homedir, tmpdir } from "os";
|
|
22
53
|
import { join } from "path";
|
|
23
|
-
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
|
+
}
|
|
24
72
|
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
25
|
-
if (runtimeDir && runtimeDir.length > 0)
|
|
26
|
-
return join(runtimeDir, "kobe.sock");
|
|
27
|
-
|
|
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");
|
|
28
78
|
}
|
|
29
79
|
function defaultDaemonPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
|
|
30
80
|
return join(homeDir, ".kobe", "daemon.pid");
|
|
31
81
|
}
|
|
82
|
+
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
32
83
|
var init_paths = () => {};
|
|
33
84
|
|
|
34
85
|
// src/daemon/protocol.ts
|
|
@@ -48,6 +99,7 @@ function serializeTask(task) {
|
|
|
48
99
|
pinned: task.pinned ?? false,
|
|
49
100
|
permissionMode: task.permissionMode,
|
|
50
101
|
model: task.model,
|
|
102
|
+
vendor: task.vendor,
|
|
51
103
|
createdAt: task.createdAt,
|
|
52
104
|
updatedAt: task.updatedAt
|
|
53
105
|
};
|
|
@@ -315,6 +367,86 @@ var init_daemon_process = __esm(() => {
|
|
|
315
367
|
init_client();
|
|
316
368
|
});
|
|
317
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
|
+
|
|
318
450
|
// src/engine/claude-code-local/binary.ts
|
|
319
451
|
import { spawnSync } from "child_process";
|
|
320
452
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
@@ -419,10 +551,152 @@ var init_binary = __esm(() => {
|
|
|
419
551
|
};
|
|
420
552
|
});
|
|
421
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
|
+
|
|
422
696
|
// src/engine/claude-code-local/history.ts
|
|
423
697
|
import { randomUUID } from "crypto";
|
|
424
698
|
import { appendFile, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
|
|
425
|
-
import { homedir as
|
|
699
|
+
import { homedir as homedir4 } from "os";
|
|
426
700
|
import path2 from "path";
|
|
427
701
|
function encodeCwd(cwd) {
|
|
428
702
|
return cwd.replace(/[/.]/g, "-");
|
|
@@ -493,11 +767,11 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
493
767
|
return null;
|
|
494
768
|
if (!("content" in inner))
|
|
495
769
|
return null;
|
|
496
|
-
const
|
|
770
|
+
const blocks = normalizeClaudeContent(inner.content);
|
|
497
771
|
const ts = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
498
772
|
const sid = typeof record.sessionId === "string" ? record.sessionId : fallbackSessionId;
|
|
499
773
|
const usage = extractUsage(inner.usage);
|
|
500
|
-
return usage ? { role,
|
|
774
|
+
return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
|
|
501
775
|
}
|
|
502
776
|
function extractUsage(v) {
|
|
503
777
|
if (!isObject(v))
|
|
@@ -592,7 +866,7 @@ var defaultDeps2;
|
|
|
592
866
|
var init_history = __esm(() => {
|
|
593
867
|
defaultDeps2 = {
|
|
594
868
|
projectsDir() {
|
|
595
|
-
return path2.join(
|
|
869
|
+
return path2.join(homedir4(), ".claude", "projects");
|
|
596
870
|
},
|
|
597
871
|
async readdir(p) {
|
|
598
872
|
try {
|
|
@@ -676,7 +950,7 @@ function delay(ms) {
|
|
|
676
950
|
|
|
677
951
|
// src/engine/claude-code-local/sessions.ts
|
|
678
952
|
import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
|
|
679
|
-
import { homedir as
|
|
953
|
+
import { homedir as homedir5 } from "os";
|
|
680
954
|
import path3 from "path";
|
|
681
955
|
async function listSessionsForCwd(cwd, deps = defaultDeps3) {
|
|
682
956
|
const projectDir = path3.join(deps.projectsDir(), encodeCwd(cwd));
|
|
@@ -728,17 +1002,11 @@ function extractFirstUserMessage(lines) {
|
|
|
728
1002
|
return null;
|
|
729
1003
|
}
|
|
730
1004
|
function stringifyContent(content) {
|
|
731
|
-
|
|
732
|
-
return content.trim();
|
|
733
|
-
if (!Array.isArray(content))
|
|
734
|
-
return "";
|
|
1005
|
+
const blocks = normalizeClaudeContent(content);
|
|
735
1006
|
const parts = [];
|
|
736
|
-
for (const
|
|
737
|
-
if (
|
|
738
|
-
|
|
739
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
740
|
-
parts.push(block.text);
|
|
741
|
-
}
|
|
1007
|
+
for (const b of blocks) {
|
|
1008
|
+
if (b.type === "text")
|
|
1009
|
+
parts.push(b.text);
|
|
742
1010
|
}
|
|
743
1011
|
return parts.join(" ").trim();
|
|
744
1012
|
}
|
|
@@ -750,7 +1018,7 @@ var init_sessions = __esm(() => {
|
|
|
750
1018
|
init_history();
|
|
751
1019
|
defaultDeps3 = {
|
|
752
1020
|
projectsDir() {
|
|
753
|
-
return path3.join(
|
|
1021
|
+
return path3.join(homedir5(), ".claude", "projects");
|
|
754
1022
|
},
|
|
755
1023
|
async readdir(p) {
|
|
756
1024
|
try {
|
|
@@ -863,47 +1131,981 @@ async function* parseStreamJson(lines, opts = {}) {
|
|
|
863
1131
|
}
|
|
864
1132
|
continue;
|
|
865
1133
|
}
|
|
866
|
-
if (type === "user") {
|
|
867
|
-
const content = extractContentBlocks(msg);
|
|
868
|
-
for (const block of content) {
|
|
869
|
-
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")
|
|
870
2059
|
continue;
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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;
|
|
878
2074
|
}
|
|
2075
|
+
if (itemId && !startedByItemId.has(itemId)) {
|
|
2076
|
+
const input = stripIdAndType(item);
|
|
2077
|
+
yield { type: "tool.start", name: itemType, input };
|
|
2078
|
+
}
|
|
2079
|
+
if (itemId)
|
|
2080
|
+
startedByItemId.delete(itemId);
|
|
2081
|
+
const output = stripIdAndType(item);
|
|
2082
|
+
yield { type: "tool.result", name: itemType, output };
|
|
879
2083
|
continue;
|
|
880
2084
|
}
|
|
881
|
-
if (type === "
|
|
882
|
-
const usage =
|
|
2085
|
+
if (type === "turn.completed") {
|
|
2086
|
+
const usage = isObject7(msg.usage) ? msg.usage : undefined;
|
|
883
2087
|
if (usage) {
|
|
884
|
-
const inTok =
|
|
885
|
-
const outTok =
|
|
886
|
-
const cacheRead = typeof usage.
|
|
887
|
-
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;
|
|
888
2091
|
yield {
|
|
889
2092
|
type: "usage",
|
|
890
2093
|
input_tokens: inTok,
|
|
891
2094
|
output_tokens: outTok,
|
|
892
|
-
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
893
|
-
...cacheCreate !== undefined ? { cache_creation_input_tokens: cacheCreate } : {}
|
|
2095
|
+
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
894
2096
|
};
|
|
895
2097
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
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 };
|
|
902
2104
|
return;
|
|
903
2105
|
}
|
|
904
2106
|
}
|
|
905
2107
|
}
|
|
906
|
-
async function*
|
|
2108
|
+
async function* readLines2(stream) {
|
|
907
2109
|
let buf = "";
|
|
908
2110
|
for await (const chunk of stream) {
|
|
909
2111
|
const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
@@ -920,18 +2122,17 @@ async function* readLines(stream) {
|
|
|
920
2122
|
if (buf.length > 0)
|
|
921
2123
|
yield buf;
|
|
922
2124
|
}
|
|
923
|
-
function
|
|
2125
|
+
function isObject7(v) {
|
|
924
2126
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
925
2127
|
}
|
|
926
|
-
function
|
|
927
|
-
|
|
928
|
-
return msg.content;
|
|
929
|
-
const inner = msg.message;
|
|
930
|
-
if (isObject3(inner) && Array.isArray(inner.content))
|
|
931
|
-
return inner.content;
|
|
932
|
-
return [];
|
|
2128
|
+
function numberOr2(v, fallback) {
|
|
2129
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
933
2130
|
}
|
|
934
|
-
function
|
|
2131
|
+
function stripIdAndType(item) {
|
|
2132
|
+
const { id: _id, type: _type, ...rest } = item;
|
|
2133
|
+
return rest;
|
|
2134
|
+
}
|
|
2135
|
+
function stringifyErr2(err) {
|
|
935
2136
|
if (err instanceof Error)
|
|
936
2137
|
return err.message;
|
|
937
2138
|
try {
|
|
@@ -941,14 +2142,16 @@ function stringifyErr(err) {
|
|
|
941
2142
|
}
|
|
942
2143
|
}
|
|
943
2144
|
|
|
944
|
-
// src/engine/
|
|
945
|
-
class
|
|
2145
|
+
// src/engine/codex-local/index.ts
|
|
2146
|
+
class CodexLocal {
|
|
2147
|
+
identity = codexIdentity;
|
|
2148
|
+
capabilities = codexCapabilities;
|
|
946
2149
|
registry = new SessionRegistry;
|
|
947
2150
|
running = new Map;
|
|
948
2151
|
binaryPathResolver;
|
|
949
2152
|
stopGraceMs;
|
|
950
2153
|
constructor(opts = {}) {
|
|
951
|
-
this.binaryPathResolver = opts.binaryPathResolver ??
|
|
2154
|
+
this.binaryPathResolver = opts.binaryPathResolver ?? findCodexBinary;
|
|
952
2155
|
this.stopGraceMs = opts.stopGraceMs ?? 5000;
|
|
953
2156
|
}
|
|
954
2157
|
async spawn(cwd, prompt, opts) {
|
|
@@ -985,41 +2188,32 @@ class ClaudeCodeLocal {
|
|
|
985
2188
|
};
|
|
986
2189
|
}
|
|
987
2190
|
async readHistory(sessionId) {
|
|
988
|
-
return
|
|
2191
|
+
return readHistoryWithMetrics(sessionId);
|
|
989
2192
|
}
|
|
990
2193
|
async deleteHistory(sessionId) {
|
|
991
|
-
return
|
|
2194
|
+
return deleteHistory2(sessionId);
|
|
992
2195
|
}
|
|
993
2196
|
async listSessions(cwd) {
|
|
994
|
-
return
|
|
2197
|
+
return listSessionsForCwd2(cwd);
|
|
995
2198
|
}
|
|
996
2199
|
async stop(handle) {
|
|
997
2200
|
const sid = handle.sessionId;
|
|
998
|
-
const session = this.running.get(sid);
|
|
999
|
-
const shouldRescue = !!session && !session.completedNaturally && session.prompt.trim().length > 0;
|
|
1000
|
-
const rescuePrompt = session?.prompt ?? "";
|
|
1001
|
-
const rescueCwd = session?.cwd ?? handle.cwd;
|
|
1002
2201
|
await this.registry.kill(sid, this.stopGraceMs);
|
|
2202
|
+
const session = this.running.get(sid);
|
|
1003
2203
|
if (session) {
|
|
1004
2204
|
session.closed = true;
|
|
1005
2205
|
this.notify(session);
|
|
1006
2206
|
this.running.delete(sid);
|
|
1007
2207
|
}
|
|
1008
|
-
if (shouldRescue) {
|
|
1009
|
-
try {
|
|
1010
|
-
await appendInterruptedUserPrompt(sid, rescueCwd, rescuePrompt);
|
|
1011
|
-
} catch {}
|
|
1012
|
-
}
|
|
1013
2208
|
}
|
|
1014
2209
|
async start(args) {
|
|
1015
2210
|
const binaryPath = await this.binaryPathResolver();
|
|
1016
|
-
const
|
|
1017
|
-
const spawned = spawnClaudeProcess({
|
|
2211
|
+
const spawned = spawnCodexProcess({
|
|
1018
2212
|
binaryPath,
|
|
1019
2213
|
cwd: args.cwd,
|
|
1020
2214
|
prompt: args.prompt,
|
|
1021
2215
|
model: args.opts?.model,
|
|
1022
|
-
permissionMode:
|
|
2216
|
+
permissionMode: args.opts?.permissionMode,
|
|
1023
2217
|
env: args.opts?.env,
|
|
1024
2218
|
resumeSessionId: args.resumeSessionId
|
|
1025
2219
|
});
|
|
@@ -1043,8 +2237,7 @@ class ClaudeCodeLocal {
|
|
|
1043
2237
|
queue,
|
|
1044
2238
|
waiters: [],
|
|
1045
2239
|
closed: false,
|
|
1046
|
-
|
|
1047
|
-
prompt: args.prompt
|
|
2240
|
+
spawnedAtIso: new Date().toISOString()
|
|
1048
2241
|
};
|
|
1049
2242
|
this.running.set(sessionId, session);
|
|
1050
2243
|
this.registry.register({
|
|
@@ -1068,22 +2261,20 @@ class ClaudeCodeLocal {
|
|
|
1068
2261
|
}
|
|
1069
2262
|
}
|
|
1070
2263
|
(async () => {
|
|
1071
|
-
const events =
|
|
2264
|
+
const events = parseStreamJson2(readLines2(spawned.stdout), {
|
|
1072
2265
|
onSessionId: (sid) => bind(sid)
|
|
1073
2266
|
});
|
|
1074
2267
|
try {
|
|
1075
2268
|
for await (const ev of events) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
session.completedNaturally = true;
|
|
1079
|
-
}
|
|
2269
|
+
const enriched = enrichUsageEvent2(ev, session?.spawnedAtIso);
|
|
2270
|
+
queue.push(enriched);
|
|
1080
2271
|
if (session)
|
|
1081
2272
|
this.notify(session);
|
|
1082
2273
|
}
|
|
1083
2274
|
} catch (err) {
|
|
1084
2275
|
const ev = {
|
|
1085
2276
|
type: "error",
|
|
1086
|
-
message: `parser failure: ${err instanceof Error ? err.message : String(err)}`
|
|
2277
|
+
message: `codex parser failure: ${err instanceof Error ? err.message : String(err)}`
|
|
1087
2278
|
};
|
|
1088
2279
|
queue.push(ev);
|
|
1089
2280
|
if (session)
|
|
@@ -1093,20 +2284,21 @@ class ClaudeCodeLocal {
|
|
|
1093
2284
|
session.closed = true;
|
|
1094
2285
|
this.notify(session);
|
|
1095
2286
|
this.registry.unregister(session.sessionId);
|
|
2287
|
+
this.running.delete(session.sessionId);
|
|
1096
2288
|
}
|
|
1097
2289
|
if (!bound) {
|
|
1098
|
-
rejectHandle(new Error("
|
|
2290
|
+
rejectHandle(new Error("codex exited without emitting a session id"));
|
|
1099
2291
|
}
|
|
1100
2292
|
}
|
|
1101
2293
|
})();
|
|
1102
|
-
|
|
2294
|
+
drainStream2(spawned.stderr);
|
|
1103
2295
|
spawned.proc.once("error", (err) => {
|
|
1104
2296
|
if (!bound)
|
|
1105
2297
|
rejectHandle(err);
|
|
1106
2298
|
});
|
|
1107
2299
|
spawned.proc.once("exit", () => {
|
|
1108
2300
|
if (!bound) {
|
|
1109
|
-
rejectHandle(new Error("
|
|
2301
|
+
rejectHandle(new Error("codex exited before session id was captured"));
|
|
1110
2302
|
}
|
|
1111
2303
|
});
|
|
1112
2304
|
return handlePromise;
|
|
@@ -1118,25 +2310,31 @@ class ClaudeCodeLocal {
|
|
|
1118
2310
|
w();
|
|
1119
2311
|
}
|
|
1120
2312
|
}
|
|
1121
|
-
function
|
|
2313
|
+
function drainStream2(stream) {
|
|
1122
2314
|
const s = stream;
|
|
1123
2315
|
s.on("data", () => {});
|
|
1124
2316
|
s.on("error", () => {});
|
|
1125
2317
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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();
|
|
1131
2329
|
});
|
|
1132
2330
|
|
|
1133
2331
|
// src/orchestrator/bridge/server.ts
|
|
1134
|
-
import { mkdir as mkdir2, unlink as
|
|
2332
|
+
import { mkdir as mkdir2, unlink as unlink3 } from "fs/promises";
|
|
1135
2333
|
import { createServer } from "net";
|
|
1136
2334
|
import { dirname as dirname2 } from "path";
|
|
1137
2335
|
async function startBridgeServer(orch, socketPath) {
|
|
1138
2336
|
await mkdir2(dirname2(socketPath), { recursive: true });
|
|
1139
|
-
await
|
|
2337
|
+
await unlink3(socketPath).catch(() => {});
|
|
1140
2338
|
const conns = new Set;
|
|
1141
2339
|
const server = createServer((conn) => {
|
|
1142
2340
|
conns.add(conn);
|
|
@@ -1177,7 +2375,7 @@ async function startBridgeServer(orch, socketPath) {
|
|
|
1177
2375
|
conn.destroy();
|
|
1178
2376
|
conns.clear();
|
|
1179
2377
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
1180
|
-
await
|
|
2378
|
+
await unlink3(socketPath).catch(() => {});
|
|
1181
2379
|
}
|
|
1182
2380
|
};
|
|
1183
2381
|
}
|
|
@@ -1270,23 +2468,19 @@ __export(exports_bridge, {
|
|
|
1270
2468
|
bridgeSocketPathForHome: () => bridgeSocketPathForHome
|
|
1271
2469
|
});
|
|
1272
2470
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
1273
|
-
import { homedir as
|
|
1274
|
-
import { join as
|
|
2471
|
+
import { homedir as homedir9 } from "os";
|
|
2472
|
+
import { join as join5 } from "path";
|
|
1275
2473
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1276
2474
|
function bridgeSocketPathForHome(home, pid = process.pid) {
|
|
1277
|
-
const runDir =
|
|
1278
|
-
|
|
1279
|
-
const macTempSocket = process.platform === "darwin" && preferred.startsWith(tmpdir2());
|
|
1280
|
-
if (preferred.length <= UNIX_SOCKET_PATH_LIMIT && !macTempSocket)
|
|
1281
|
-
return preferred;
|
|
1282
|
-
const shortTmp = process.platform === "darwin" ? "/tmp" : tmpdir2();
|
|
1283
|
-
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);
|
|
1284
2477
|
}
|
|
1285
2478
|
async function startBridge(orch, opts = {}) {
|
|
1286
|
-
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
1287
|
-
const runDir =
|
|
2479
|
+
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
|
|
2480
|
+
const runDir = join5(home, ".kobe", "run");
|
|
1288
2481
|
const socketPath = bridgeSocketPathForHome(home);
|
|
1289
|
-
const mcpConfigPath =
|
|
2482
|
+
const mcpConfigPath = join5(runDir, `mcp-${process.pid}.json`);
|
|
2483
|
+
await mkdir3(runDir, { recursive: true });
|
|
1290
2484
|
const server = await startBridgeServer(orch, socketPath);
|
|
1291
2485
|
await mkdir3(runDir, { recursive: true });
|
|
1292
2486
|
const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
@@ -1309,8 +2503,8 @@ async function startBridge(orch, opts = {}) {
|
|
|
1309
2503
|
}
|
|
1310
2504
|
};
|
|
1311
2505
|
}
|
|
1312
|
-
var UNIX_SOCKET_PATH_LIMIT = 103;
|
|
1313
2506
|
var init_bridge = __esm(() => {
|
|
2507
|
+
init_paths();
|
|
1314
2508
|
init_server();
|
|
1315
2509
|
});
|
|
1316
2510
|
|
|
@@ -2542,137 +3736,81 @@ var init_dev = __esm(() => {
|
|
|
2542
3736
|
}
|
|
2543
3737
|
});
|
|
2544
3738
|
|
|
2545
|
-
// src/engine/
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
function
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
|
|
2561
|
-
cached = { model };
|
|
2562
|
-
return cached;
|
|
2563
|
-
} catch {
|
|
2564
|
-
cached = null;
|
|
2565
|
-
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;
|
|
2566
3754
|
}
|
|
3755
|
+
return defaultCapabilities;
|
|
2567
3756
|
}
|
|
2568
|
-
function
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
}
|
|
2574
|
-
var SETTINGS_PATH, cached, FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
|
|
2575
|
-
var init_claude_settings = __esm(() => {
|
|
2576
|
-
SETTINGS_PATH = join4(homedir6(), ".claude", "settings.json");
|
|
2577
|
-
});
|
|
2578
|
-
|
|
2579
|
-
// src/session/usage-metrics.ts
|
|
2580
|
-
function totalContextTokens(u) {
|
|
2581
|
-
return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
2582
|
-
}
|
|
2583
|
-
function parseTimestampMs(value) {
|
|
2584
|
-
const ms = new Date(value).getTime();
|
|
2585
|
-
return Number.isFinite(ms) ? ms : null;
|
|
2586
|
-
}
|
|
2587
|
-
function mergeIntervals(intervals) {
|
|
2588
|
-
if (intervals.length === 0)
|
|
2589
|
-
return [];
|
|
2590
|
-
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
2591
|
-
const first = sorted[0];
|
|
2592
|
-
if (!first)
|
|
2593
|
-
return [];
|
|
2594
|
-
const merged = [{ startMs: first.startMs, endMs: first.endMs }];
|
|
2595
|
-
for (let i = 1;i < sorted.length; i++) {
|
|
2596
|
-
const current = sorted[i];
|
|
2597
|
-
const last = merged[merged.length - 1];
|
|
2598
|
-
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)
|
|
2599
3762
|
continue;
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
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);
|
|
2604
3769
|
}
|
|
2605
3770
|
}
|
|
2606
|
-
return
|
|
2607
|
-
}
|
|
2608
|
-
function durationMs(intervals) {
|
|
2609
|
-
return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
|
|
3771
|
+
return out;
|
|
2610
3772
|
}
|
|
2611
|
-
function
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
let inputTokens = 0;
|
|
2616
|
-
let outputTokens = 0;
|
|
2617
|
-
const intervals = [];
|
|
2618
|
-
for (const message of past) {
|
|
2619
|
-
const timestampMs = parseTimestampMs(message.timestamp);
|
|
2620
|
-
if (message.role === "user" && timestampMs !== null) {
|
|
2621
|
-
lastUserTimestampMs = timestampMs;
|
|
2622
|
-
continue;
|
|
2623
|
-
}
|
|
2624
|
-
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)
|
|
2625
3777
|
continue;
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
return;
|
|
2640
|
-
const totalDurationMs = durationMs(mergeIntervals(intervals));
|
|
2641
|
-
if (totalDurationMs <= 0)
|
|
2642
|
-
return latestUsage;
|
|
2643
|
-
return {
|
|
2644
|
-
...latestUsage,
|
|
2645
|
-
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
|
|
2646
3791
|
};
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
const endMs = parseTimestampMs(endedAtIso);
|
|
2651
|
-
if (startMs === null || endMs === null || endMs <= startMs)
|
|
2652
|
-
return usage;
|
|
2653
|
-
return {
|
|
2654
|
-
...usage,
|
|
2655
|
-
total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
|
|
3792
|
+
ENGINE_IDENTITIES = {
|
|
3793
|
+
claude: claudeIdentity,
|
|
3794
|
+
codex: codexIdentity
|
|
2656
3795
|
};
|
|
2657
|
-
|
|
3796
|
+
defaultCapabilities = claudeCapabilities;
|
|
3797
|
+
defaultIdentity = claudeIdentity;
|
|
3798
|
+
});
|
|
2658
3799
|
|
|
2659
3800
|
// src/env.ts
|
|
2660
|
-
import { homedir as
|
|
2661
|
-
import { join as
|
|
3801
|
+
import { homedir as homedir10 } from "os";
|
|
3802
|
+
import { join as join6 } from "path";
|
|
2662
3803
|
function isDev() {
|
|
2663
3804
|
return process.env.KOBE_DEV === "1";
|
|
2664
3805
|
}
|
|
2665
3806
|
function homeDir() {
|
|
2666
|
-
return process.env.KOBE_HOME_DIR ??
|
|
3807
|
+
return process.env.KOBE_HOME_DIR ?? homedir10();
|
|
2667
3808
|
}
|
|
2668
3809
|
function kobeStateDir() {
|
|
2669
|
-
return
|
|
3810
|
+
return join6(homeDir(), ".kobe");
|
|
2670
3811
|
}
|
|
2671
3812
|
function kvStatePath() {
|
|
2672
|
-
return
|
|
2673
|
-
}
|
|
2674
|
-
function tmuxBin() {
|
|
2675
|
-
return process.env.KOBE_TMUX_BIN ?? "tmux";
|
|
3813
|
+
return join6(homeDir(), ".config", "kobe", "state.json");
|
|
2676
3814
|
}
|
|
2677
3815
|
var init_env = () => {};
|
|
2678
3816
|
|
|
@@ -2687,11 +3825,11 @@ __export(exports_repos, {
|
|
|
2687
3825
|
getSavedRepos: () => getSavedRepos,
|
|
2688
3826
|
addSavedRepo: () => addSavedRepo
|
|
2689
3827
|
});
|
|
2690
|
-
import { spawnSync as
|
|
2691
|
-
import { mkdirSync, readFileSync as
|
|
3828
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3829
|
+
import { mkdirSync, readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync } from "fs";
|
|
2692
3830
|
import { dirname as dirname3 } from "path";
|
|
2693
3831
|
function resolveRepoRoot(absPath) {
|
|
2694
|
-
const r =
|
|
3832
|
+
const r = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
2695
3833
|
cwd: absPath,
|
|
2696
3834
|
encoding: "utf8",
|
|
2697
3835
|
shell: false
|
|
@@ -2725,7 +3863,7 @@ function statePath() {
|
|
|
2725
3863
|
}
|
|
2726
3864
|
function load() {
|
|
2727
3865
|
try {
|
|
2728
|
-
const text =
|
|
3866
|
+
const text = readFileSync3(statePath(), "utf8");
|
|
2729
3867
|
const parsed = JSON.parse(text);
|
|
2730
3868
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2731
3869
|
return parsed;
|
|
@@ -2734,11 +3872,11 @@ function load() {
|
|
|
2734
3872
|
return {};
|
|
2735
3873
|
}
|
|
2736
3874
|
function save(state) {
|
|
2737
|
-
const
|
|
2738
|
-
mkdirSync(dirname3(
|
|
2739
|
-
const tmp = `${
|
|
3875
|
+
const path6 = statePath();
|
|
3876
|
+
mkdirSync(dirname3(path6), { recursive: true });
|
|
3877
|
+
const tmp = `${path6}.tmp`;
|
|
2740
3878
|
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2741
|
-
renameSync(tmp,
|
|
3879
|
+
renameSync(tmp, path6);
|
|
2742
3880
|
}
|
|
2743
3881
|
function getSavedRepos() {
|
|
2744
3882
|
const state = load();
|
|
@@ -2802,7 +3940,7 @@ function nextChatTabSeq(tabs) {
|
|
|
2802
3940
|
max = t.seq;
|
|
2803
3941
|
return max + 1;
|
|
2804
3942
|
}
|
|
2805
|
-
var toTaskId = (id) => id;
|
|
3943
|
+
var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude";
|
|
2806
3944
|
|
|
2807
3945
|
// src/orchestrator/index/ulid.ts
|
|
2808
3946
|
function encodeTime(now, len) {
|
|
@@ -2864,7 +4002,7 @@ var init_ulid = __esm(() => {
|
|
|
2864
4002
|
});
|
|
2865
4003
|
|
|
2866
4004
|
// src/orchestrator/metadata-suggester.ts
|
|
2867
|
-
import { spawn as
|
|
4005
|
+
import { spawn as spawn4 } from "child_process";
|
|
2868
4006
|
|
|
2869
4007
|
class MetadataSuggester {
|
|
2870
4008
|
binaryPromise = null;
|
|
@@ -2893,7 +4031,7 @@ class MetadataSuggester {
|
|
|
2893
4031
|
return new Promise((resolve2) => {
|
|
2894
4032
|
let proc;
|
|
2895
4033
|
try {
|
|
2896
|
-
proc =
|
|
4034
|
+
proc = spawn4(binary, ["-p", builder(trimmed)], {
|
|
2897
4035
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2898
4036
|
env: process.env
|
|
2899
4037
|
});
|
|
@@ -3042,10 +4180,10 @@ class InMemoryPendingInputBroker {
|
|
|
3042
4180
|
}
|
|
3043
4181
|
|
|
3044
4182
|
// src/orchestrator/pr/build.ts
|
|
3045
|
-
import { spawnSync as
|
|
4183
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
3046
4184
|
function git(cwd, args) {
|
|
3047
4185
|
try {
|
|
3048
|
-
const out =
|
|
4186
|
+
const out = spawnSync4("git", args.slice(), {
|
|
3049
4187
|
cwd,
|
|
3050
4188
|
encoding: "utf8",
|
|
3051
4189
|
timeout: GIT_TIMEOUT_MS
|
|
@@ -3095,7 +4233,7 @@ var init_build = () => {};
|
|
|
3095
4233
|
|
|
3096
4234
|
// src/orchestrator/pr/instructions.ts
|
|
3097
4235
|
import { promises as fs } from "fs";
|
|
3098
|
-
import
|
|
4236
|
+
import path6 from "path";
|
|
3099
4237
|
function dirtyCountSentence(n) {
|
|
3100
4238
|
if (n <= 0)
|
|
3101
4239
|
return "There are no uncommitted changes.";
|
|
@@ -3123,7 +4261,7 @@ function renderPRInstructions(template, state) {
|
|
|
3123
4261
|
async function loadPRInstructionsTemplate(worktreePath) {
|
|
3124
4262
|
if (!worktreePath)
|
|
3125
4263
|
return DEFAULT_PR_INSTRUCTIONS_TEMPLATE;
|
|
3126
|
-
const file =
|
|
4264
|
+
const file = path6.join(worktreePath, ".kobe", "pr-instructions.md");
|
|
3127
4265
|
try {
|
|
3128
4266
|
const text = await fs.readFile(file, "utf8");
|
|
3129
4267
|
if (text.length === 0)
|
|
@@ -3174,10 +4312,11 @@ class SessionPump {
|
|
|
3174
4312
|
}
|
|
3175
4313
|
async run(taskId, tabId, handle) {
|
|
3176
4314
|
const tabKey = chatRunStateKey(taskId, tabId);
|
|
4315
|
+
const engine = this.env.engineFor(taskId, tabId);
|
|
3177
4316
|
let killedForInput = false;
|
|
3178
4317
|
let terminalEvent = null;
|
|
3179
4318
|
try {
|
|
3180
|
-
for await (const ev of
|
|
4319
|
+
for await (const ev of engine.stream(handle)) {
|
|
3181
4320
|
const inputReq = detectUserInputFromEngineEvent(ev);
|
|
3182
4321
|
if (inputReq) {
|
|
3183
4322
|
this.env.dispatch(taskId, tabId, ev);
|
|
@@ -3191,7 +4330,7 @@ class SessionPump {
|
|
|
3191
4330
|
});
|
|
3192
4331
|
killedForInput = true;
|
|
3193
4332
|
try {
|
|
3194
|
-
await
|
|
4333
|
+
await engine.stop(handle);
|
|
3195
4334
|
} catch {}
|
|
3196
4335
|
break;
|
|
3197
4336
|
}
|
|
@@ -3204,7 +4343,7 @@ class SessionPump {
|
|
|
3204
4343
|
} finally {
|
|
3205
4344
|
if (!killedForInput) {
|
|
3206
4345
|
try {
|
|
3207
|
-
await
|
|
4346
|
+
await engine.stop(handle);
|
|
3208
4347
|
} catch {}
|
|
3209
4348
|
}
|
|
3210
4349
|
}
|
|
@@ -3264,7 +4403,8 @@ function summarizeWorktreeError(raw, repo, baseRef) {
|
|
|
3264
4403
|
}
|
|
3265
4404
|
|
|
3266
4405
|
class Orchestrator {
|
|
3267
|
-
|
|
4406
|
+
engines;
|
|
4407
|
+
fallbackEngine;
|
|
3268
4408
|
store;
|
|
3269
4409
|
worktrees;
|
|
3270
4410
|
metadataSuggester;
|
|
@@ -3282,7 +4422,26 @@ class Orchestrator {
|
|
|
3282
4422
|
runStateAcc;
|
|
3283
4423
|
setRunState;
|
|
3284
4424
|
constructor(deps) {
|
|
3285
|
-
|
|
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;
|
|
3286
4445
|
this.store = deps.store;
|
|
3287
4446
|
this.worktrees = deps.worktrees;
|
|
3288
4447
|
this.metadataSuggester = deps.metadataSuggester ?? new MetadataSuggester;
|
|
@@ -3296,13 +4455,86 @@ class Orchestrator {
|
|
|
3296
4455
|
this.runStateAcc = runState;
|
|
3297
4456
|
this.setRunState = (next) => setRunState(() => next);
|
|
3298
4457
|
this.sessionPump = new SessionPump({
|
|
3299
|
-
|
|
4458
|
+
engineFor: (taskId, tabId) => this.engineForTaskTabId(taskId, tabId),
|
|
3300
4459
|
broker: this.pendingInputBroker,
|
|
3301
4460
|
dispatch: (taskId, tabId, ev) => this.dispatchEvent(taskId, tabId, ev),
|
|
3302
4461
|
nextRequestId: () => `req-${++this.requestIdCounter}`,
|
|
3303
4462
|
onPendingInputChange: () => this.bumpRunState()
|
|
3304
4463
|
});
|
|
3305
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
|
+
}
|
|
3306
4538
|
bumpRunState() {
|
|
3307
4539
|
const next = new Map;
|
|
3308
4540
|
for (const tabKey2 of this.pendingInputBroker.awaitingTabKeys()) {
|
|
@@ -3522,10 +4754,11 @@ class Orchestrator {
|
|
|
3522
4754
|
if (prompt && prompt.trim().length > 0) {
|
|
3523
4755
|
this.dispatchEvent(task.id, targetTab.id, { type: "user.inject", text: prompt });
|
|
3524
4756
|
}
|
|
3525
|
-
const
|
|
4757
|
+
const engine = targetTab.sessionId ? await this.engineForTabRun(task, targetTab) : this.engineForTab(task, targetTab);
|
|
4758
|
+
const modelToUse = this.modelForTab(task, targetTab, engine);
|
|
3526
4759
|
let handle;
|
|
3527
4760
|
if (targetTab.sessionId) {
|
|
3528
|
-
handle = await
|
|
4761
|
+
handle = await engine.resume(targetTab.sessionId, promptToSend, {
|
|
3529
4762
|
cwd: task.worktreePath,
|
|
3530
4763
|
env: { KOBE_RESUME_CWD: task.worktreePath },
|
|
3531
4764
|
permissionMode: task.permissionMode,
|
|
@@ -3538,7 +4771,7 @@ class Orchestrator {
|
|
|
3538
4771
|
});
|
|
3539
4772
|
this.firstSpawnLatches.set(key, latch);
|
|
3540
4773
|
try {
|
|
3541
|
-
handle = await
|
|
4774
|
+
handle = await engine.spawn(task.worktreePath, promptToSend, {
|
|
3542
4775
|
permissionMode: task.permissionMode,
|
|
3543
4776
|
model: modelToUse
|
|
3544
4777
|
});
|
|
@@ -3613,7 +4846,7 @@ class Orchestrator {
|
|
|
3613
4846
|
text: "(turn interrupted \u2014 sending new prompt)"
|
|
3614
4847
|
});
|
|
3615
4848
|
try {
|
|
3616
|
-
await this.
|
|
4849
|
+
await this.engineForTask(task).stop(handle);
|
|
3617
4850
|
} finally {
|
|
3618
4851
|
this.handles.delete(key);
|
|
3619
4852
|
this.bumpRunState();
|
|
@@ -3655,11 +4888,13 @@ class Orchestrator {
|
|
|
3655
4888
|
return;
|
|
3656
4889
|
await this.store.update(task.id, { permissionMode: mode });
|
|
3657
4890
|
}
|
|
3658
|
-
async setModel(id, model) {
|
|
4891
|
+
async setModel(id, model, tabId) {
|
|
3659
4892
|
const task = this.requireTask(id);
|
|
3660
|
-
|
|
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)
|
|
3661
4896
|
return;
|
|
3662
|
-
await this.
|
|
4897
|
+
await this.updateTab(task.id, tab.id, { model, vendor });
|
|
3663
4898
|
}
|
|
3664
4899
|
async setTitle(id, title) {
|
|
3665
4900
|
const task = this.requireTask(id);
|
|
@@ -3723,7 +4958,7 @@ class Orchestrator {
|
|
|
3723
4958
|
}
|
|
3724
4959
|
if (task.sessionId) {
|
|
3725
4960
|
try {
|
|
3726
|
-
await this.
|
|
4961
|
+
await this.engineForTask(task).deleteHistory(task.sessionId);
|
|
3727
4962
|
} catch (err) {
|
|
3728
4963
|
console.error(`[kobe orchestrator] deleteTask: deleteHistory failed for ${task.id}:`, err);
|
|
3729
4964
|
}
|
|
@@ -3732,15 +4967,15 @@ class Orchestrator {
|
|
|
3732
4967
|
await this.store.remove(task.id);
|
|
3733
4968
|
}
|
|
3734
4969
|
async readHistory(sessionId) {
|
|
3735
|
-
|
|
3736
|
-
return await this.engine.readHistory(sessionId);
|
|
3737
|
-
} catch {
|
|
3738
|
-
return [];
|
|
3739
|
-
}
|
|
4970
|
+
return (await this.readHistoryWithMetrics(sessionId)).messages;
|
|
3740
4971
|
}
|
|
3741
4972
|
async readHistoryWithMetrics(sessionId) {
|
|
3742
|
-
const
|
|
3743
|
-
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;
|
|
3744
4979
|
return {
|
|
3745
4980
|
messages,
|
|
3746
4981
|
...usageMetrics ? { usageMetrics } : {}
|
|
@@ -3750,14 +4985,16 @@ class Orchestrator {
|
|
|
3750
4985
|
const task = this.requireTask(id);
|
|
3751
4986
|
if (!task.worktreePath)
|
|
3752
4987
|
return [];
|
|
4988
|
+
const tab = this.resolveTab(task);
|
|
3753
4989
|
try {
|
|
3754
|
-
return await this.
|
|
4990
|
+
return await this.engineForTab(task, tab).listSessions(task.worktreePath);
|
|
3755
4991
|
} catch {
|
|
3756
4992
|
return [];
|
|
3757
4993
|
}
|
|
3758
4994
|
}
|
|
3759
4995
|
async openSessionInTab(id, sessionId, opts = {}) {
|
|
3760
4996
|
const task = this.requireTask(id);
|
|
4997
|
+
const active = this.resolveTab(task);
|
|
3761
4998
|
const existing = task.tabs.find((t) => t.sessionId === sessionId);
|
|
3762
4999
|
if (existing) {
|
|
3763
5000
|
await this.setActiveTab(task.id, existing.id);
|
|
@@ -3768,6 +5005,8 @@ class Orchestrator {
|
|
|
3768
5005
|
sessionId,
|
|
3769
5006
|
seq: nextChatTabSeq(task.tabs),
|
|
3770
5007
|
createdAt: new Date().toISOString(),
|
|
5008
|
+
model: active.model ?? task.model,
|
|
5009
|
+
vendor: this.vendorForTab(task, active),
|
|
3771
5010
|
...opts.title ? { title: opts.title } : {}
|
|
3772
5011
|
};
|
|
3773
5012
|
await this.store.update(task.id, { tabs: [...task.tabs, tab], activeTabId: tab.id });
|
|
@@ -3795,11 +5034,14 @@ class Orchestrator {
|
|
|
3795
5034
|
}
|
|
3796
5035
|
async createTab(id, opts = {}) {
|
|
3797
5036
|
const task = this.requireTask(id);
|
|
5037
|
+
const active = this.resolveTab(task);
|
|
3798
5038
|
const tab = {
|
|
3799
5039
|
id: ulid(),
|
|
3800
5040
|
sessionId: null,
|
|
3801
5041
|
seq: nextChatTabSeq(task.tabs),
|
|
3802
5042
|
createdAt: new Date().toISOString(),
|
|
5043
|
+
model: active.model ?? task.model,
|
|
5044
|
+
vendor: this.vendorForTab(task, active),
|
|
3803
5045
|
...opts.title ? { title: opts.title } : {}
|
|
3804
5046
|
};
|
|
3805
5047
|
const tabs = [...task.tabs, tab];
|
|
@@ -3879,7 +5121,7 @@ class Orchestrator {
|
|
|
3879
5121
|
if (!handle)
|
|
3880
5122
|
return;
|
|
3881
5123
|
try {
|
|
3882
|
-
await this.
|
|
5124
|
+
await this.engineForTaskId(taskId).stop(handle);
|
|
3883
5125
|
} finally {
|
|
3884
5126
|
this.handles.delete(key);
|
|
3885
5127
|
this.bumpRunState();
|
|
@@ -3888,12 +5130,13 @@ class Orchestrator {
|
|
|
3888
5130
|
async stopAllTabsForTask(taskId) {
|
|
3889
5131
|
const prefix = `${taskId}:`;
|
|
3890
5132
|
const keys = Array.from(this.handles.keys()).filter((k) => k.startsWith(prefix));
|
|
5133
|
+
const engine = this.engineForTaskId(taskId);
|
|
3891
5134
|
for (const key of keys) {
|
|
3892
5135
|
const handle = this.handles.get(key);
|
|
3893
5136
|
if (!handle)
|
|
3894
5137
|
continue;
|
|
3895
5138
|
try {
|
|
3896
|
-
await
|
|
5139
|
+
await engine.stop(handle);
|
|
3897
5140
|
} catch {}
|
|
3898
5141
|
this.handles.delete(key);
|
|
3899
5142
|
}
|
|
@@ -4019,7 +5262,7 @@ function deriveTitleFromPrompt(prompt) {
|
|
|
4019
5262
|
var CONCURRENCY_CAP = 20, PLACEHOLDER_TASK_TITLE = "(new task)", IllegalTransitionError, ConcurrencyCapError, PRPreconditionError, TaskNotFoundError, CannotDeleteMainTaskError, TITLE_CHAR_CAP = 40;
|
|
4020
5263
|
var init_core = __esm(() => {
|
|
4021
5264
|
init_dev();
|
|
4022
|
-
|
|
5265
|
+
init_registry();
|
|
4023
5266
|
init_repos();
|
|
4024
5267
|
init_ulid();
|
|
4025
5268
|
init_metadata_suggester();
|
|
@@ -4064,9 +5307,9 @@ var init_core = __esm(() => {
|
|
|
4064
5307
|
});
|
|
4065
5308
|
|
|
4066
5309
|
// src/orchestrator/index/store.ts
|
|
4067
|
-
import { mkdir as mkdir4, open, readFile as
|
|
4068
|
-
import { homedir as
|
|
4069
|
-
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";
|
|
4070
5313
|
|
|
4071
5314
|
class TaskIndexStore {
|
|
4072
5315
|
homeDir;
|
|
@@ -4078,9 +5321,9 @@ class TaskIndexStore {
|
|
|
4078
5321
|
listeners = new Set;
|
|
4079
5322
|
saveChain = Promise.resolve();
|
|
4080
5323
|
constructor(options = {}) {
|
|
4081
|
-
this.homeDir = options.homeDir ??
|
|
4082
|
-
this.kobeDir =
|
|
4083
|
-
this.path =
|
|
5324
|
+
this.homeDir = options.homeDir ?? homedir11();
|
|
5325
|
+
this.kobeDir = join7(this.homeDir, ".kobe");
|
|
5326
|
+
this.path = join7(this.kobeDir, "tasks.json");
|
|
4084
5327
|
this.tmpPath = `${this.path}.tmp`;
|
|
4085
5328
|
}
|
|
4086
5329
|
subscribe(listener) {
|
|
@@ -4105,7 +5348,7 @@ class TaskIndexStore {
|
|
|
4105
5348
|
async load() {
|
|
4106
5349
|
let raw;
|
|
4107
5350
|
try {
|
|
4108
|
-
raw = await
|
|
5351
|
+
raw = await readFile4(this.path, "utf8");
|
|
4109
5352
|
} catch (err) {
|
|
4110
5353
|
const code = err.code;
|
|
4111
5354
|
if (code === "ENOENT") {
|
|
@@ -4142,7 +5385,7 @@ class TaskIndexStore {
|
|
|
4142
5385
|
const payload = this.snapshot();
|
|
4143
5386
|
const json = `${JSON.stringify(payload, null, 2)}
|
|
4144
5387
|
`;
|
|
4145
|
-
const handle = await
|
|
5388
|
+
const handle = await open2(this.tmpPath, "w", 420);
|
|
4146
5389
|
try {
|
|
4147
5390
|
await handle.writeFile(json, "utf8");
|
|
4148
5391
|
await handle.sync();
|
|
@@ -4170,7 +5413,16 @@ class TaskIndexStore {
|
|
|
4170
5413
|
activeTabId = activeIn && tabsIn.some((t) => t.id === activeIn) ? activeIn : tabsIn[0]?.id ?? "";
|
|
4171
5414
|
} else {
|
|
4172
5415
|
const tabId = ulid();
|
|
4173
|
-
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
|
+
];
|
|
4174
5426
|
activeTabId = tabId;
|
|
4175
5427
|
}
|
|
4176
5428
|
const firstSession = tabs[0]?.sessionId ?? null;
|
|
@@ -4237,13 +5489,13 @@ class TaskIndexStore {
|
|
|
4237
5489
|
}
|
|
4238
5490
|
async _unlinkForTests() {
|
|
4239
5491
|
try {
|
|
4240
|
-
await
|
|
5492
|
+
await unlink4(this.path);
|
|
4241
5493
|
} catch (err) {
|
|
4242
5494
|
if (err.code !== "ENOENT")
|
|
4243
5495
|
throw err;
|
|
4244
5496
|
}
|
|
4245
5497
|
try {
|
|
4246
|
-
await
|
|
5498
|
+
await unlink4(this.tmpPath);
|
|
4247
5499
|
} catch (err) {
|
|
4248
5500
|
if (err.code !== "ENOENT")
|
|
4249
5501
|
throw err;
|
|
@@ -4333,7 +5585,9 @@ function coerceTask(value) {
|
|
|
4333
5585
|
sessionId: tt.sessionId ?? null,
|
|
4334
5586
|
seq,
|
|
4335
5587
|
createdAt: tt.createdAt,
|
|
4336
|
-
...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 } : {}
|
|
4337
5591
|
};
|
|
4338
5592
|
tabs.push(tab);
|
|
4339
5593
|
}
|
|
@@ -4366,6 +5620,7 @@ function coerceTask(value) {
|
|
|
4366
5620
|
kind: v.kind === "main" ? "main" : "task",
|
|
4367
5621
|
permissionMode: isPermissionMode(v.permissionMode) ? v.permissionMode : undefined,
|
|
4368
5622
|
model: typeof v.model === "string" ? v.model : undefined,
|
|
5623
|
+
vendor: resolveTaskVendor(v.vendor, typeof v.model === "string" ? v.model : undefined),
|
|
4369
5624
|
createdAt: v.createdAt,
|
|
4370
5625
|
updatedAt: v.updatedAt
|
|
4371
5626
|
};
|
|
@@ -4373,20 +5628,33 @@ function coerceTask(value) {
|
|
|
4373
5628
|
function isPermissionMode(v) {
|
|
4374
5629
|
return v === "default" || v === "plan";
|
|
4375
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
|
+
}
|
|
4376
5643
|
function isTaskStatus(s) {
|
|
4377
5644
|
return s === "backlog" || s === "in_progress" || s === "in_review" || s === "done" || s === "canceled" || s === "error";
|
|
4378
5645
|
}
|
|
4379
5646
|
var init_store = __esm(() => {
|
|
5647
|
+
init_registry();
|
|
4380
5648
|
init_ulid();
|
|
4381
5649
|
});
|
|
4382
5650
|
|
|
4383
5651
|
// src/orchestrator/worktree/git.ts
|
|
4384
|
-
import { spawnSync as
|
|
5652
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
4385
5653
|
function git2(args, opts) {
|
|
4386
5654
|
if (!opts.cwd) {
|
|
4387
5655
|
throw new Error("git(): cwd is required; refusing to inherit from process.cwd()");
|
|
4388
5656
|
}
|
|
4389
|
-
const proc =
|
|
5657
|
+
const proc = spawnSync5("git", [...args], {
|
|
4390
5658
|
cwd: opts.cwd,
|
|
4391
5659
|
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
4392
5660
|
encoding: "utf8",
|
|
@@ -4424,32 +5692,32 @@ var init_git = __esm(() => {
|
|
|
4424
5692
|
|
|
4425
5693
|
// src/orchestrator/worktree/paths.ts
|
|
4426
5694
|
import fs2 from "fs";
|
|
4427
|
-
import
|
|
5695
|
+
import path7 from "path";
|
|
4428
5696
|
function worktreeRootFor(repo) {
|
|
4429
|
-
if (!
|
|
5697
|
+
if (!path7.isAbsolute(repo)) {
|
|
4430
5698
|
throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
|
|
4431
5699
|
}
|
|
4432
|
-
return
|
|
5700
|
+
return path7.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
|
|
4433
5701
|
}
|
|
4434
5702
|
function worktreePathFor(repo, taskId) {
|
|
4435
5703
|
if (!taskId || /[/\\\0]/.test(taskId)) {
|
|
4436
5704
|
throw new Error(`worktreePathFor: invalid taskId: ${JSON.stringify(taskId)}`);
|
|
4437
5705
|
}
|
|
4438
|
-
return
|
|
5706
|
+
return path7.join(worktreeRootFor(repo), taskId);
|
|
4439
5707
|
}
|
|
4440
5708
|
function isKobeManagedPath(repo, candidate) {
|
|
4441
|
-
if (!
|
|
5709
|
+
if (!path7.isAbsolute(repo) || !path7.isAbsolute(candidate))
|
|
4442
5710
|
return false;
|
|
4443
5711
|
const root = canonicalize(worktreeRootFor(repo));
|
|
4444
5712
|
const target = canonicalize(candidate);
|
|
4445
|
-
const rel =
|
|
4446
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
5713
|
+
const rel = path7.relative(root, target);
|
|
5714
|
+
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
4447
5715
|
}
|
|
4448
5716
|
function canonicalize(p) {
|
|
4449
5717
|
try {
|
|
4450
5718
|
return fs2.realpathSync(p);
|
|
4451
5719
|
} catch {
|
|
4452
|
-
return
|
|
5720
|
+
return path7.resolve(p);
|
|
4453
5721
|
}
|
|
4454
5722
|
}
|
|
4455
5723
|
var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
|
|
@@ -4457,7 +5725,7 @@ var init_paths2 = () => {};
|
|
|
4457
5725
|
|
|
4458
5726
|
// src/orchestrator/worktree/manager.ts
|
|
4459
5727
|
import fs3 from "fs";
|
|
4460
|
-
import
|
|
5728
|
+
import path8 from "path";
|
|
4461
5729
|
|
|
4462
5730
|
class GitWorktreeManager {
|
|
4463
5731
|
async create(repo, branch, worktreePath, baseRef) {
|
|
@@ -4475,7 +5743,7 @@ class GitWorktreeManager {
|
|
|
4475
5743
|
}
|
|
4476
5744
|
throw new Error(`create(): ${worktreePath} exists but is not a registered git worktree`);
|
|
4477
5745
|
}
|
|
4478
|
-
fs3.mkdirSync(
|
|
5746
|
+
fs3.mkdirSync(path8.dirname(worktreePath), { recursive: true });
|
|
4479
5747
|
const branchExists = this.branchExists(repo, branch);
|
|
4480
5748
|
const args = branchExists ? ["worktree", "add", worktreePath, branch] : baseRef ? ["worktree", "add", "-b", branch, worktreePath, baseRef] : ["worktree", "add", "-b", branch, worktreePath];
|
|
4481
5749
|
git2(args, { cwd: repo });
|
|
@@ -4530,8 +5798,8 @@ class GitWorktreeManager {
|
|
|
4530
5798
|
if (!entry.branch || entry.detached)
|
|
4531
5799
|
continue;
|
|
4532
5800
|
const canonEntry = canonicalize2(entry.path);
|
|
4533
|
-
const rel =
|
|
4534
|
-
const callerPath =
|
|
5801
|
+
const rel = path8.relative(canonRoot, canonEntry);
|
|
5802
|
+
const callerPath = path8.join(callerRoot, rel);
|
|
4535
5803
|
const dirty = await this.isDirty(entry.path);
|
|
4536
5804
|
infos.push({
|
|
4537
5805
|
path: callerPath,
|
|
@@ -4592,9 +5860,9 @@ class GitWorktreeManager {
|
|
|
4592
5860
|
const gitDir = out.stdout.trim();
|
|
4593
5861
|
if (!gitDir)
|
|
4594
5862
|
return null;
|
|
4595
|
-
const absolute =
|
|
4596
|
-
const base =
|
|
4597
|
-
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;
|
|
4598
5866
|
} catch (err) {
|
|
4599
5867
|
if (err instanceof GitCommandError)
|
|
4600
5868
|
return null;
|
|
@@ -4634,7 +5902,7 @@ function parsePorcelain(out) {
|
|
|
4634
5902
|
return records;
|
|
4635
5903
|
}
|
|
4636
5904
|
function requireAbsolute(name, value) {
|
|
4637
|
-
if (!value || !
|
|
5905
|
+
if (!value || !path8.isAbsolute(value)) {
|
|
4638
5906
|
throw new Error(`${name} must be an absolute path, got: ${JSON.stringify(value)}`);
|
|
4639
5907
|
}
|
|
4640
5908
|
}
|
|
@@ -4642,7 +5910,7 @@ function canonicalize2(p) {
|
|
|
4642
5910
|
try {
|
|
4643
5911
|
return fs3.realpathSync(p);
|
|
4644
5912
|
} catch {
|
|
4645
|
-
return
|
|
5913
|
+
return path8.resolve(p);
|
|
4646
5914
|
}
|
|
4647
5915
|
}
|
|
4648
5916
|
var init_manager = __esm(() => {
|
|
@@ -4653,22 +5921,26 @@ var init_manager = __esm(() => {
|
|
|
4653
5921
|
// src/bin/kobed.ts
|
|
4654
5922
|
init_daemon_process();
|
|
4655
5923
|
init_client();
|
|
4656
|
-
import { unlink as
|
|
5924
|
+
import { unlink as unlink6 } from "fs/promises";
|
|
4657
5925
|
|
|
4658
5926
|
// src/core/index.ts
|
|
4659
5927
|
init_claude_code_local();
|
|
5928
|
+
init_codex_local();
|
|
4660
5929
|
init_bridge();
|
|
4661
5930
|
init_core();
|
|
4662
5931
|
init_store();
|
|
4663
5932
|
init_manager();
|
|
4664
|
-
import { homedir as
|
|
5933
|
+
import { homedir as homedir12 } from "os";
|
|
4665
5934
|
async function createKobeCore(options = {}) {
|
|
4666
|
-
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
5935
|
+
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir12();
|
|
4667
5936
|
const store = new TaskIndexStore({ homeDir: homeDir2 });
|
|
4668
5937
|
await store.load();
|
|
4669
5938
|
const worktrees = new GitWorktreeManager;
|
|
4670
|
-
const
|
|
4671
|
-
|
|
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 });
|
|
4672
5944
|
const bridge = options.startMcpBridge === false ? null : await startBridge(orchestrator, { homeDir: homeDir2 });
|
|
4673
5945
|
return {
|
|
4674
5946
|
homeDir: homeDir2,
|
|
@@ -4689,16 +5961,16 @@ init_paths();
|
|
|
4689
5961
|
// src/daemon/server.ts
|
|
4690
5962
|
init_repos();
|
|
4691
5963
|
init_paths();
|
|
4692
|
-
import { mkdir as mkdir5, readFile as
|
|
5964
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink5, writeFile as writeFile4 } from "fs/promises";
|
|
4693
5965
|
import { createServer as createServer2 } from "net";
|
|
4694
5966
|
import { dirname as dirname5 } from "path";
|
|
4695
5967
|
|
|
4696
5968
|
// src/engine/claude-code-local/plan-usage.ts
|
|
4697
5969
|
import { execFile } from "child_process";
|
|
4698
|
-
import { createHash } from "crypto";
|
|
4699
|
-
import { readFile as
|
|
4700
|
-
import { homedir as
|
|
4701
|
-
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";
|
|
4702
5974
|
import { promisify } from "util";
|
|
4703
5975
|
var execFileAsync = promisify(execFile);
|
|
4704
5976
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -4709,7 +5981,7 @@ function keychainServiceName() {
|
|
|
4709
5981
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4710
5982
|
if (!configDir)
|
|
4711
5983
|
return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}`;
|
|
4712
|
-
const hash =
|
|
5984
|
+
const hash = createHash2("sha256").update(configDir).digest("hex").slice(0, 8);
|
|
4713
5985
|
return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}-${hash}`;
|
|
4714
5986
|
}
|
|
4715
5987
|
function keychainAccount() {
|
|
@@ -4733,10 +6005,10 @@ async function readKeychainToken() {
|
|
|
4733
6005
|
}
|
|
4734
6006
|
}
|
|
4735
6007
|
async function readPlainTextToken() {
|
|
4736
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ??
|
|
4737
|
-
const
|
|
6008
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? join8(homedir13(), ".claude");
|
|
6009
|
+
const path9 = join8(configDir, ".credentials.json");
|
|
4738
6010
|
try {
|
|
4739
|
-
const raw = await
|
|
6011
|
+
const raw = await readFile5(path9, "utf8");
|
|
4740
6012
|
return parseStoredOAuth(raw);
|
|
4741
6013
|
} catch {
|
|
4742
6014
|
return null;
|
|
@@ -4849,7 +6121,7 @@ function createPlanUsagePoller(options) {
|
|
|
4849
6121
|
}
|
|
4850
6122
|
// src/daemon/rc-bridge.ts
|
|
4851
6123
|
init_binary();
|
|
4852
|
-
import { spawn as
|
|
6124
|
+
import { spawn as spawn5 } from "child_process";
|
|
4853
6125
|
var ENV_ID_RE = /Environment ID:\s*(env_[A-Za-z0-9]+)/;
|
|
4854
6126
|
var DEEPLINK_RE = /https:\/\/claude\.ai\/code\?environment=([A-Za-z0-9_]+)/;
|
|
4855
6127
|
var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
@@ -4857,7 +6129,7 @@ function createRcBridge(options = {}) {
|
|
|
4857
6129
|
const stopGraceMs = options.stopGraceMs ?? 5000;
|
|
4858
6130
|
const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
|
|
4859
6131
|
const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
|
|
4860
|
-
const spawner = options.spawner ?? ((cmd, args, cwd) =>
|
|
6132
|
+
const spawner = options.spawner ?? ((cmd, args, cwd) => spawn5(cmd, [...args], {
|
|
4861
6133
|
cwd,
|
|
4862
6134
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4863
6135
|
env: { ...process.env }
|
|
@@ -5031,7 +6303,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5031
6303
|
let nextClientId = 1;
|
|
5032
6304
|
await mkdir5(dirname5(socketPath), { recursive: true });
|
|
5033
6305
|
await mkdir5(dirname5(pidPath), { recursive: true });
|
|
5034
|
-
await
|
|
6306
|
+
await unlink5(socketPath).catch(() => {});
|
|
5035
6307
|
const server = createServer2((socket) => {
|
|
5036
6308
|
const client = {
|
|
5037
6309
|
id: nextClientId++,
|
|
@@ -5076,8 +6348,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5076
6348
|
client.socket.destroy();
|
|
5077
6349
|
}
|
|
5078
6350
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
5079
|
-
await
|
|
5080
|
-
await
|
|
6351
|
+
await unlink5(socketPath).catch(() => {});
|
|
6352
|
+
await unlink5(pidPath).catch(() => {});
|
|
5081
6353
|
}
|
|
5082
6354
|
};
|
|
5083
6355
|
planUsagePoller.start();
|
|
@@ -5191,7 +6463,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5191
6463
|
}
|
|
5192
6464
|
case "task.model": {
|
|
5193
6465
|
const taskId = requireString2(payload, "taskId");
|
|
5194
|
-
await orch.setModel(taskId, optionalString2(payload, "model"));
|
|
6466
|
+
await orch.setModel(taskId, optionalString2(payload, "model"), optionalString2(payload, "tabId"));
|
|
5195
6467
|
broadcastTaskUpdated(orch, clients, taskId);
|
|
5196
6468
|
return {};
|
|
5197
6469
|
}
|
|
@@ -5390,7 +6662,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5390
6662
|
}
|
|
5391
6663
|
async function readPidFile(pidPath) {
|
|
5392
6664
|
try {
|
|
5393
|
-
const raw = await
|
|
6665
|
+
const raw = await readFile6(pidPath, "utf8");
|
|
5394
6666
|
const pid = Number(raw.trim());
|
|
5395
6667
|
return Number.isFinite(pid) ? pid : null;
|
|
5396
6668
|
} catch {
|
|
@@ -5428,8 +6700,7 @@ async function readTaskHistory(orch, taskId, requestedSessionId, limit, before)
|
|
|
5428
6700
|
const sessionId = requestedSessionId ?? task?.tabs.find((t) => t.id === task.activeTabId)?.sessionId ?? task?.sessionId;
|
|
5429
6701
|
if (!sessionId)
|
|
5430
6702
|
return { messages: [], nextBefore: null, hasMore: false };
|
|
5431
|
-
const messages = await orch.
|
|
5432
|
-
const usageMetrics = deriveSessionUsageMetrics(messages);
|
|
6703
|
+
const { messages, usageMetrics } = await orch.readHistoryWithMetrics(sessionId);
|
|
5433
6704
|
const beforeIdx = before ? messages.findIndex((m) => `${m.timestamp}:${m.sessionId}` === before) : -1;
|
|
5434
6705
|
const end = beforeIdx >= 0 ? beforeIdx : messages.length;
|
|
5435
6706
|
const start = Math.max(0, end - limit);
|
|
@@ -5575,7 +6846,7 @@ async function main() {
|
|
|
5575
6846
|
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
5576
6847
|
} catch {}
|
|
5577
6848
|
}
|
|
5578
|
-
await
|
|
6849
|
+
await unlink6(socketPath).catch(() => {});
|
|
5579
6850
|
const next = await connectOrStartDaemon();
|
|
5580
6851
|
next.close();
|
|
5581
6852
|
console.log(`kobed: restarted, listening on ${socketPath}`);
|