@sma1lboy/kobe 0.5.15 → 0.5.17
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 +23 -9
- package/dist/bin/kobed.js +2203 -279
- package/dist/cli/index.js +3334 -941
- package/package.json +3 -1
package/dist/bin/kobed.js
CHANGED
|
@@ -48,17 +48,38 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
50
|
// src/daemon/paths.ts
|
|
51
|
+
import { createHash } from "crypto";
|
|
51
52
|
import { homedir, tmpdir } from "os";
|
|
52
53
|
import { join } from "path";
|
|
53
|
-
function
|
|
54
|
+
function shortHomeTag(homeDir) {
|
|
55
|
+
return createHash("sha1").update(homeDir).digest("hex").slice(0, 8);
|
|
56
|
+
}
|
|
57
|
+
function fitSocketPath(naturalPath, homeDir, role, pidTag) {
|
|
58
|
+
if (Buffer.byteLength(naturalPath, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
59
|
+
return naturalPath;
|
|
60
|
+
const tag = shortHomeTag(homeDir);
|
|
61
|
+
const suffix = pidTag === undefined ? "" : `-${pidTag}`;
|
|
62
|
+
const fallback = join(tmpdir(), `kobe-${tag}-${role}${suffix}.sock`);
|
|
63
|
+
if (Buffer.byteLength(fallback, "utf8") <= SOCKET_PATH_SAFETY_LIMIT)
|
|
64
|
+
return fallback;
|
|
65
|
+
throw new Error(`kobe socket path exceeds ${SOCKET_PATH_SAFETY_LIMIT} bytes even after fallback: ${fallback}`);
|
|
66
|
+
}
|
|
67
|
+
function defaultDaemonSocketPath(homeDir) {
|
|
68
|
+
const explicit = homeDir ?? process.env.KOBE_HOME_DIR;
|
|
69
|
+
if (explicit && explicit.length > 0) {
|
|
70
|
+
return fitSocketPath(join(explicit, ".kobe", "daemon.sock"), explicit, "daemon");
|
|
71
|
+
}
|
|
54
72
|
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
55
|
-
if (runtimeDir && runtimeDir.length > 0)
|
|
56
|
-
return join(runtimeDir, "kobe.sock");
|
|
57
|
-
|
|
73
|
+
if (runtimeDir && runtimeDir.length > 0) {
|
|
74
|
+
return fitSocketPath(join(runtimeDir, "kobe.sock"), runtimeDir, "daemon");
|
|
75
|
+
}
|
|
76
|
+
const home = homedir();
|
|
77
|
+
return fitSocketPath(join(home, ".kobe", "daemon.sock"), home, "daemon");
|
|
58
78
|
}
|
|
59
79
|
function defaultDaemonPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
|
|
60
80
|
return join(homeDir, ".kobe", "daemon.pid");
|
|
61
81
|
}
|
|
82
|
+
var SOCKET_PATH_SAFETY_LIMIT = 100;
|
|
62
83
|
var init_paths = () => {};
|
|
63
84
|
|
|
64
85
|
// src/daemon/protocol.ts
|
|
@@ -78,6 +99,8 @@ function serializeTask(task) {
|
|
|
78
99
|
pinned: task.pinned ?? false,
|
|
79
100
|
permissionMode: task.permissionMode,
|
|
80
101
|
model: task.model,
|
|
102
|
+
modelEffort: task.modelEffort,
|
|
103
|
+
vendor: task.vendor,
|
|
81
104
|
createdAt: task.createdAt,
|
|
82
105
|
updatedAt: task.updatedAt
|
|
83
106
|
};
|
|
@@ -345,6 +368,86 @@ var init_daemon_process = __esm(() => {
|
|
|
345
368
|
init_client();
|
|
346
369
|
});
|
|
347
370
|
|
|
371
|
+
// src/session/usage-metrics.ts
|
|
372
|
+
function totalContextTokens(u) {
|
|
373
|
+
return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
374
|
+
}
|
|
375
|
+
function parseTimestampMs(value) {
|
|
376
|
+
const ms = new Date(value).getTime();
|
|
377
|
+
return Number.isFinite(ms) ? ms : null;
|
|
378
|
+
}
|
|
379
|
+
function mergeIntervals(intervals) {
|
|
380
|
+
if (intervals.length === 0)
|
|
381
|
+
return [];
|
|
382
|
+
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
383
|
+
const first = sorted[0];
|
|
384
|
+
if (!first)
|
|
385
|
+
return [];
|
|
386
|
+
const merged = [{ startMs: first.startMs, endMs: first.endMs }];
|
|
387
|
+
for (let i = 1;i < sorted.length; i++) {
|
|
388
|
+
const current = sorted[i];
|
|
389
|
+
const last = merged[merged.length - 1];
|
|
390
|
+
if (!current || !last)
|
|
391
|
+
continue;
|
|
392
|
+
if (current.startMs <= last.endMs) {
|
|
393
|
+
last.endMs = Math.max(last.endMs, current.endMs);
|
|
394
|
+
} else {
|
|
395
|
+
merged.push({ startMs: current.startMs, endMs: current.endMs });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return merged;
|
|
399
|
+
}
|
|
400
|
+
function durationMs(intervals) {
|
|
401
|
+
return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
|
|
402
|
+
}
|
|
403
|
+
function deriveSessionUsageMetrics(past) {
|
|
404
|
+
let latestUsage;
|
|
405
|
+
let latestUsageTimestampMs = null;
|
|
406
|
+
let lastUserTimestampMs = null;
|
|
407
|
+
let inputTokens = 0;
|
|
408
|
+
let outputTokens = 0;
|
|
409
|
+
const intervals = [];
|
|
410
|
+
for (const message of past) {
|
|
411
|
+
const timestampMs = parseTimestampMs(message.timestamp);
|
|
412
|
+
if (message.role === "user" && timestampMs !== null) {
|
|
413
|
+
lastUserTimestampMs = timestampMs;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (message.role !== "assistant" || !message.usage)
|
|
417
|
+
continue;
|
|
418
|
+
if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
|
|
419
|
+
latestUsageTimestampMs = timestampMs;
|
|
420
|
+
latestUsage = message.usage;
|
|
421
|
+
} else if (latestUsage === undefined) {
|
|
422
|
+
latestUsage = message.usage;
|
|
423
|
+
}
|
|
424
|
+
inputTokens += message.usage.input_tokens;
|
|
425
|
+
outputTokens += message.usage.output_tokens;
|
|
426
|
+
if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
|
|
427
|
+
intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!latestUsage)
|
|
431
|
+
return;
|
|
432
|
+
const totalDurationMs = durationMs(mergeIntervals(intervals));
|
|
433
|
+
if (totalDurationMs <= 0)
|
|
434
|
+
return latestUsage;
|
|
435
|
+
return {
|
|
436
|
+
...latestUsage,
|
|
437
|
+
total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function withTotalSpeedForTurn(usage, startedAtIso, endedAtIso) {
|
|
441
|
+
const startMs = startedAtIso ? parseTimestampMs(startedAtIso) : null;
|
|
442
|
+
const endMs = parseTimestampMs(endedAtIso);
|
|
443
|
+
if (startMs === null || endMs === null || endMs <= startMs)
|
|
444
|
+
return usage;
|
|
445
|
+
return {
|
|
446
|
+
...usage,
|
|
447
|
+
total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
348
451
|
// src/engine/claude-code-local/binary.ts
|
|
349
452
|
import { spawnSync } from "child_process";
|
|
350
453
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
@@ -449,10 +552,175 @@ var init_binary = __esm(() => {
|
|
|
449
552
|
};
|
|
450
553
|
});
|
|
451
554
|
|
|
555
|
+
// src/engine/claude-code-local/models.ts
|
|
556
|
+
function opus47EffortChoices(id, label) {
|
|
557
|
+
return CLAUDE_OPUS_EFFORT_LEVELS.map((effort) => ({
|
|
558
|
+
vendor: "claude",
|
|
559
|
+
id,
|
|
560
|
+
effort,
|
|
561
|
+
level: effort,
|
|
562
|
+
label: `${label} \xB7 ${effort}`,
|
|
563
|
+
hint: effort === "max" ? "deepest reasoning" : `${effort} effort`
|
|
564
|
+
}));
|
|
565
|
+
}
|
|
566
|
+
function parseContextWindowSize(modelIdentifier) {
|
|
567
|
+
const delimitedMatch = /(?:\(|\[)\s*(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])\s*(?:\)|\])/i.exec(modelIdentifier);
|
|
568
|
+
if (delimitedMatch?.[1] && delimitedMatch[2]) {
|
|
569
|
+
const parsed2 = Number.parseFloat(delimitedMatch[1].replace(/[,_]/g, ""));
|
|
570
|
+
if (Number.isFinite(parsed2) && parsed2 > 0) {
|
|
571
|
+
return Math.round(parsed2 * (delimitedMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const contextMatch = /\b(\d+(?:[,_]\d+)*(?:\.\d+)?)\s*([km])(?:\s*(?:token\s*)?context)?\b/i.exec(modelIdentifier);
|
|
575
|
+
if (!contextMatch?.[1] || !contextMatch[2])
|
|
576
|
+
return null;
|
|
577
|
+
const parsed = Number.parseFloat(contextMatch[1].replace(/[,_]/g, ""));
|
|
578
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
579
|
+
return null;
|
|
580
|
+
return Math.round(parsed * (contextMatch[2].toLowerCase() === "m" ? 1e6 : 1000));
|
|
581
|
+
}
|
|
582
|
+
function claudeContextWindowFor(modelId) {
|
|
583
|
+
const parsed = parseContextWindowSize(modelId);
|
|
584
|
+
if (parsed !== null)
|
|
585
|
+
return parsed;
|
|
586
|
+
if (modelId.includes("[1m]"))
|
|
587
|
+
return LONG_CTX;
|
|
588
|
+
const inCatalog = CLAUDE_MODELS.some((m) => m.id === modelId);
|
|
589
|
+
if (inCatalog)
|
|
590
|
+
return STD_CTX;
|
|
591
|
+
if (modelId.includes("1m") || modelId.includes("[1M]"))
|
|
592
|
+
return LONG_CTX;
|
|
593
|
+
return STD_CTX;
|
|
594
|
+
}
|
|
595
|
+
var CLAUDE_OPUS_EFFORT_LEVELS, CLAUDE_MODELS, LONG_CTX = 1e6, STD_CTX = 200000;
|
|
596
|
+
var init_models = __esm(() => {
|
|
597
|
+
CLAUDE_OPUS_EFFORT_LEVELS = [
|
|
598
|
+
"low",
|
|
599
|
+
"medium",
|
|
600
|
+
"high",
|
|
601
|
+
"xhigh",
|
|
602
|
+
"max"
|
|
603
|
+
];
|
|
604
|
+
CLAUDE_MODELS = [
|
|
605
|
+
{ vendor: "claude", id: "claude-opus-4-7[1m]", label: "Opus 4.7 1M", hint: "long context, default" },
|
|
606
|
+
...opus47EffortChoices("claude-opus-4-7[1m]", "Opus 4.7 1M"),
|
|
607
|
+
{ vendor: "claude", id: "claude-opus-4-7", label: "Opus 4.7", hint: "most capable, slowest" },
|
|
608
|
+
...opus47EffortChoices("claude-opus-4-7", "Opus 4.7"),
|
|
609
|
+
{ vendor: "claude", id: "claude-sonnet-4-6[1m]", label: "sonnet 4.6 (1M)", hint: "long context" },
|
|
610
|
+
{ vendor: "claude", id: "claude-sonnet-4-6", label: "sonnet 4.6" },
|
|
611
|
+
{ vendor: "claude", id: "claude-haiku-4-5-20251001", label: "haiku 4.5", hint: "fastest, cheapest" }
|
|
612
|
+
];
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// src/engine/claude-code-local/settings.ts
|
|
616
|
+
import { readFileSync } from "fs";
|
|
617
|
+
import { homedir as homedir3 } from "os";
|
|
618
|
+
import { join as join3 } from "path";
|
|
619
|
+
function readClaudeSettings() {
|
|
620
|
+
if (cached !== undefined)
|
|
621
|
+
return cached;
|
|
622
|
+
try {
|
|
623
|
+
const raw = readFileSync(SETTINGS_PATH, "utf8");
|
|
624
|
+
const parsed = JSON.parse(raw);
|
|
625
|
+
if (!parsed || typeof parsed !== "object") {
|
|
626
|
+
cached = null;
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
const obj = parsed;
|
|
630
|
+
const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
|
|
631
|
+
cached = { model };
|
|
632
|
+
return cached;
|
|
633
|
+
} catch {
|
|
634
|
+
cached = null;
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function resolveClaudeDefaultModelId() {
|
|
639
|
+
const settings = readClaudeSettings();
|
|
640
|
+
if (settings?.model && settings.model.length > 0)
|
|
641
|
+
return settings.model;
|
|
642
|
+
return CLAUDE_FALLBACK_DEFAULT_MODEL_ID;
|
|
643
|
+
}
|
|
644
|
+
var SETTINGS_PATH, cached, CLAUDE_FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
|
|
645
|
+
var init_settings = __esm(() => {
|
|
646
|
+
SETTINGS_PATH = join3(homedir3(), ".claude", "settings.json");
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// src/engine/claude-code-local/capabilities.ts
|
|
650
|
+
var claudeCapabilities, claudeIdentity;
|
|
651
|
+
var init_capabilities = __esm(() => {
|
|
652
|
+
init_models();
|
|
653
|
+
init_settings();
|
|
654
|
+
claudeCapabilities = {
|
|
655
|
+
vendorId: "claude",
|
|
656
|
+
label: "Claude Code",
|
|
657
|
+
models: CLAUDE_MODELS,
|
|
658
|
+
permissionModes: [
|
|
659
|
+
{ id: "default", label: "default" },
|
|
660
|
+
{ id: "plan", label: "plan mode" }
|
|
661
|
+
],
|
|
662
|
+
defaultModelId: resolveClaudeDefaultModelId,
|
|
663
|
+
contextWindowFor: claudeContextWindowFor
|
|
664
|
+
};
|
|
665
|
+
claudeIdentity = {
|
|
666
|
+
vendorId: "claude",
|
|
667
|
+
productName: "Claude Code",
|
|
668
|
+
shortName: "Claude",
|
|
669
|
+
assistantName: "Claude",
|
|
670
|
+
inputPlaceholder: "Ask Claude\u2026"
|
|
671
|
+
};
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// src/engine/claude-code-local/normalize.ts
|
|
675
|
+
function normalizeClaudeContent(content) {
|
|
676
|
+
if (typeof content === "string") {
|
|
677
|
+
return content.length > 0 ? [{ type: "text", text: content }] : [];
|
|
678
|
+
}
|
|
679
|
+
if (!Array.isArray(content))
|
|
680
|
+
return [];
|
|
681
|
+
const out = [];
|
|
682
|
+
for (const block of content) {
|
|
683
|
+
if (typeof block === "string") {
|
|
684
|
+
if (block.length > 0)
|
|
685
|
+
out.push({ type: "text", text: block });
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (!block || typeof block !== "object")
|
|
689
|
+
continue;
|
|
690
|
+
const b = block;
|
|
691
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
692
|
+
out.push({ type: "text", text: b.text });
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (b.type === "tool_use") {
|
|
696
|
+
out.push({
|
|
697
|
+
type: "tool_call",
|
|
698
|
+
callId: typeof b.id === "string" ? b.id : "",
|
|
699
|
+
name: typeof b.name === "string" ? b.name : "",
|
|
700
|
+
input: b.input
|
|
701
|
+
});
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (b.type === "tool_result") {
|
|
705
|
+
out.push({
|
|
706
|
+
type: "tool_result",
|
|
707
|
+
callId: typeof b.tool_use_id === "string" ? b.tool_use_id : "",
|
|
708
|
+
output: b.content,
|
|
709
|
+
isError: b.is_error === true
|
|
710
|
+
});
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (b.type === "thinking" && typeof b.thinking === "string") {
|
|
714
|
+
out.push({ type: "thinking", text: b.thinking });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return out;
|
|
718
|
+
}
|
|
719
|
+
|
|
452
720
|
// src/engine/claude-code-local/history.ts
|
|
453
721
|
import { randomUUID } from "crypto";
|
|
454
722
|
import { appendFile, mkdir, readFile, readdir, unlink, writeFile } from "fs/promises";
|
|
455
|
-
import { homedir as
|
|
723
|
+
import { homedir as homedir4 } from "os";
|
|
456
724
|
import path2 from "path";
|
|
457
725
|
function encodeCwd(cwd) {
|
|
458
726
|
return cwd.replace(/[/.]/g, "-");
|
|
@@ -523,11 +791,11 @@ function extractMessage(record, fallbackSessionId) {
|
|
|
523
791
|
return null;
|
|
524
792
|
if (!("content" in inner))
|
|
525
793
|
return null;
|
|
526
|
-
const
|
|
794
|
+
const blocks = normalizeClaudeContent(inner.content);
|
|
527
795
|
const ts = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
|
|
528
796
|
const sid = typeof record.sessionId === "string" ? record.sessionId : fallbackSessionId;
|
|
529
797
|
const usage = extractUsage(inner.usage);
|
|
530
|
-
return usage ? { role,
|
|
798
|
+
return usage ? { role, blocks, timestamp: ts, sessionId: sid, usage } : { role, blocks, timestamp: ts, sessionId: sid };
|
|
531
799
|
}
|
|
532
800
|
function extractUsage(v) {
|
|
533
801
|
if (!isObject(v))
|
|
@@ -622,7 +890,7 @@ var defaultDeps2;
|
|
|
622
890
|
var init_history = __esm(() => {
|
|
623
891
|
defaultDeps2 = {
|
|
624
892
|
projectsDir() {
|
|
625
|
-
return path2.join(
|
|
893
|
+
return path2.join(homedir4(), ".claude", "projects");
|
|
626
894
|
},
|
|
627
895
|
async readdir(p) {
|
|
628
896
|
try {
|
|
@@ -658,7 +926,12 @@ class SessionRegistry {
|
|
|
658
926
|
}
|
|
659
927
|
this.handles.set(handle.sessionId, handle);
|
|
660
928
|
}
|
|
661
|
-
unregister(sessionId) {
|
|
929
|
+
unregister(sessionId, proc) {
|
|
930
|
+
if (proc) {
|
|
931
|
+
const existing = this.handles.get(sessionId);
|
|
932
|
+
if (existing && existing.proc !== proc)
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
662
935
|
this.handles.delete(sessionId);
|
|
663
936
|
}
|
|
664
937
|
get(sessionId) {
|
|
@@ -706,7 +979,7 @@ function delay(ms) {
|
|
|
706
979
|
|
|
707
980
|
// src/engine/claude-code-local/sessions.ts
|
|
708
981
|
import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
|
|
709
|
-
import { homedir as
|
|
982
|
+
import { homedir as homedir5 } from "os";
|
|
710
983
|
import path3 from "path";
|
|
711
984
|
async function listSessionsForCwd(cwd, deps = defaultDeps3) {
|
|
712
985
|
const projectDir = path3.join(deps.projectsDir(), encodeCwd(cwd));
|
|
@@ -758,17 +1031,11 @@ function extractFirstUserMessage(lines) {
|
|
|
758
1031
|
return null;
|
|
759
1032
|
}
|
|
760
1033
|
function stringifyContent(content) {
|
|
761
|
-
|
|
762
|
-
return content.trim();
|
|
763
|
-
if (!Array.isArray(content))
|
|
764
|
-
return "";
|
|
1034
|
+
const blocks = normalizeClaudeContent(content);
|
|
765
1035
|
const parts = [];
|
|
766
|
-
for (const
|
|
767
|
-
if (
|
|
768
|
-
|
|
769
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
770
|
-
parts.push(block.text);
|
|
771
|
-
}
|
|
1036
|
+
for (const b of blocks) {
|
|
1037
|
+
if (b.type === "text")
|
|
1038
|
+
parts.push(b.text);
|
|
772
1039
|
}
|
|
773
1040
|
return parts.join(" ").trim();
|
|
774
1041
|
}
|
|
@@ -780,7 +1047,7 @@ var init_sessions = __esm(() => {
|
|
|
780
1047
|
init_history();
|
|
781
1048
|
defaultDeps3 = {
|
|
782
1049
|
projectsDir() {
|
|
783
|
-
return path3.join(
|
|
1050
|
+
return path3.join(homedir5(), ".claude", "projects");
|
|
784
1051
|
},
|
|
785
1052
|
async readdir(p) {
|
|
786
1053
|
try {
|
|
@@ -824,6 +1091,9 @@ function buildArgs(opts) {
|
|
|
824
1091
|
if (opts.model) {
|
|
825
1092
|
args.push("--model", opts.model);
|
|
826
1093
|
}
|
|
1094
|
+
if (opts.modelEffort) {
|
|
1095
|
+
args.push("--effort", opts.modelEffort);
|
|
1096
|
+
}
|
|
827
1097
|
if (opts.permissionMode) {
|
|
828
1098
|
args.push("--permission-mode", opts.permissionMode);
|
|
829
1099
|
}
|
|
@@ -924,6 +1194,13 @@ async function* parseStreamJson(lines, opts = {}) {
|
|
|
924
1194
|
};
|
|
925
1195
|
}
|
|
926
1196
|
const subtype = typeof msg.subtype === "string" ? msg.subtype : "success";
|
|
1197
|
+
const isApiError = msg.is_error === true || typeof msg.api_error_status === "number";
|
|
1198
|
+
if (isApiError) {
|
|
1199
|
+
const status = typeof msg.api_error_status === "number" ? ` ${msg.api_error_status}` : "";
|
|
1200
|
+
const result = typeof msg.result === "string" ? msg.result.trim() : "";
|
|
1201
|
+
yield { type: "error", message: result ? `claude API error${status}: ${result}` : `claude API error${status}` };
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
927
1204
|
if (subtype === "success") {
|
|
928
1205
|
yield { type: "done" };
|
|
929
1206
|
} else {
|
|
@@ -973,6 +1250,8 @@ function stringifyErr(err) {
|
|
|
973
1250
|
|
|
974
1251
|
// src/engine/claude-code-local/index.ts
|
|
975
1252
|
class ClaudeCodeLocal {
|
|
1253
|
+
identity = claudeIdentity;
|
|
1254
|
+
capabilities = claudeCapabilities;
|
|
976
1255
|
registry = new SessionRegistry;
|
|
977
1256
|
running = new Map;
|
|
978
1257
|
binaryPathResolver;
|
|
@@ -1015,7 +1294,9 @@ class ClaudeCodeLocal {
|
|
|
1015
1294
|
};
|
|
1016
1295
|
}
|
|
1017
1296
|
async readHistory(sessionId) {
|
|
1018
|
-
|
|
1297
|
+
const messages = await readHistory(sessionId);
|
|
1298
|
+
const usageMetrics = deriveSessionUsageMetrics(messages);
|
|
1299
|
+
return { messages, ...usageMetrics ? { usageMetrics } : {} };
|
|
1019
1300
|
}
|
|
1020
1301
|
async deleteHistory(sessionId) {
|
|
1021
1302
|
return deleteHistory(sessionId);
|
|
@@ -1049,6 +1330,7 @@ class ClaudeCodeLocal {
|
|
|
1049
1330
|
cwd: args.cwd,
|
|
1050
1331
|
prompt: args.prompt,
|
|
1051
1332
|
model: args.opts?.model,
|
|
1333
|
+
modelEffort: args.opts?.modelEffort,
|
|
1052
1334
|
permissionMode: cliPermissionMode,
|
|
1053
1335
|
env: args.opts?.env,
|
|
1054
1336
|
resumeSessionId: args.resumeSessionId
|
|
@@ -1074,7 +1356,8 @@ class ClaudeCodeLocal {
|
|
|
1074
1356
|
waiters: [],
|
|
1075
1357
|
closed: false,
|
|
1076
1358
|
completedNaturally: false,
|
|
1077
|
-
prompt: args.prompt
|
|
1359
|
+
prompt: args.prompt,
|
|
1360
|
+
spawnedAtIso: new Date().toISOString()
|
|
1078
1361
|
};
|
|
1079
1362
|
this.running.set(sessionId, session);
|
|
1080
1363
|
this.registry.register({
|
|
@@ -1103,7 +1386,8 @@ class ClaudeCodeLocal {
|
|
|
1103
1386
|
});
|
|
1104
1387
|
try {
|
|
1105
1388
|
for await (const ev of events) {
|
|
1106
|
-
|
|
1389
|
+
const enriched = enrichUsageEvent(ev, session?.spawnedAtIso);
|
|
1390
|
+
queue.push(enriched);
|
|
1107
1391
|
if (ev.type === "done" && session) {
|
|
1108
1392
|
session.completedNaturally = true;
|
|
1109
1393
|
}
|
|
@@ -1122,7 +1406,7 @@ class ClaudeCodeLocal {
|
|
|
1122
1406
|
if (session) {
|
|
1123
1407
|
session.closed = true;
|
|
1124
1408
|
this.notify(session);
|
|
1125
|
-
this.registry.unregister(session.sessionId);
|
|
1409
|
+
this.registry.unregister(session.sessionId, spawned.proc);
|
|
1126
1410
|
}
|
|
1127
1411
|
if (!bound) {
|
|
1128
1412
|
rejectHandle(new Error("claude exited without emitting a session id"));
|
|
@@ -1153,20 +1437,1022 @@ function drainStream(stream) {
|
|
|
1153
1437
|
s.on("data", () => {});
|
|
1154
1438
|
s.on("error", () => {});
|
|
1155
1439
|
}
|
|
1440
|
+
function enrichUsageEvent(ev, startedAtIso) {
|
|
1441
|
+
if (ev.type !== "usage")
|
|
1442
|
+
return ev;
|
|
1443
|
+
return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
|
|
1444
|
+
}
|
|
1156
1445
|
var init_claude_code_local = __esm(() => {
|
|
1157
1446
|
init_binary();
|
|
1447
|
+
init_capabilities();
|
|
1158
1448
|
init_history();
|
|
1159
1449
|
init_sessions();
|
|
1160
1450
|
init_spawn();
|
|
1161
1451
|
});
|
|
1162
1452
|
|
|
1453
|
+
// src/engine/codex-local/binary.ts
|
|
1454
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1455
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
1456
|
+
import { homedir as homedir6 } from "os";
|
|
1457
|
+
import path4 from "path";
|
|
1458
|
+
async function findCodexBinary(deps = defaultDeps4) {
|
|
1459
|
+
const checked = [];
|
|
1460
|
+
const tryPath = (p) => {
|
|
1461
|
+
if (!p)
|
|
1462
|
+
return;
|
|
1463
|
+
checked.push(p);
|
|
1464
|
+
return deps.fileExists(p) ? p : undefined;
|
|
1465
|
+
};
|
|
1466
|
+
const whichResult = deps.which("codex");
|
|
1467
|
+
if (whichResult) {
|
|
1468
|
+
checked.push(`which:${whichResult}`);
|
|
1469
|
+
if (deps.fileExists(whichResult))
|
|
1470
|
+
return whichResult;
|
|
1471
|
+
}
|
|
1472
|
+
const home = deps.home();
|
|
1473
|
+
for (const p of ["/opt/homebrew/bin/codex", "/usr/local/bin/codex", "/usr/bin/codex", "/bin/codex"]) {
|
|
1474
|
+
const candidate = tryPath(p);
|
|
1475
|
+
if (candidate)
|
|
1476
|
+
return candidate;
|
|
1477
|
+
}
|
|
1478
|
+
const nvmBin = deps.env("NVM_BIN");
|
|
1479
|
+
if (nvmBin) {
|
|
1480
|
+
const candidate = tryPath(path4.join(nvmBin, "codex"));
|
|
1481
|
+
if (candidate)
|
|
1482
|
+
return candidate;
|
|
1483
|
+
}
|
|
1484
|
+
for (const rel of [".local/bin/codex", ".bun/bin/codex", "bin/codex"]) {
|
|
1485
|
+
const candidate = tryPath(path4.join(home, rel));
|
|
1486
|
+
if (candidate)
|
|
1487
|
+
return candidate;
|
|
1488
|
+
}
|
|
1489
|
+
throw new CodexBinaryNotFoundError(checked);
|
|
1490
|
+
}
|
|
1491
|
+
var CodexBinaryNotFoundError, defaultDeps4;
|
|
1492
|
+
var init_binary2 = __esm(() => {
|
|
1493
|
+
CodexBinaryNotFoundError = class CodexBinaryNotFoundError extends Error {
|
|
1494
|
+
checkedPaths;
|
|
1495
|
+
constructor(checkedPaths) {
|
|
1496
|
+
super(`Codex CLI binary not found. Checked: ${checkedPaths.join(", ")}. Ensure 'codex' is on PATH (e.g. \`brew install codex\` or the official installer).`);
|
|
1497
|
+
this.name = "CodexBinaryNotFoundError";
|
|
1498
|
+
this.checkedPaths = checkedPaths;
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
defaultDeps4 = {
|
|
1502
|
+
fileExists(p) {
|
|
1503
|
+
try {
|
|
1504
|
+
return statSync2(p).isFile();
|
|
1505
|
+
} catch {
|
|
1506
|
+
return false;
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
env(name) {
|
|
1510
|
+
return process.env[name];
|
|
1511
|
+
},
|
|
1512
|
+
home() {
|
|
1513
|
+
return homedir6();
|
|
1514
|
+
},
|
|
1515
|
+
which(name) {
|
|
1516
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
1517
|
+
const out = spawnSync2(cmd, [name], { encoding: "utf8" });
|
|
1518
|
+
if (out.status !== 0)
|
|
1519
|
+
return;
|
|
1520
|
+
const first = out.stdout.split(`
|
|
1521
|
+
`).map((l) => l.trim()).filter(Boolean)[0];
|
|
1522
|
+
if (!first)
|
|
1523
|
+
return;
|
|
1524
|
+
if (first.startsWith("codex:") && first.includes("aliased to")) {
|
|
1525
|
+
const aliasTarget = first.split("aliased to")[1]?.trim();
|
|
1526
|
+
return aliasTarget && existsSync3(aliasTarget) ? aliasTarget : undefined;
|
|
1527
|
+
}
|
|
1528
|
+
return first;
|
|
1529
|
+
},
|
|
1530
|
+
readdir(p) {
|
|
1531
|
+
try {
|
|
1532
|
+
const fs = __require("fs");
|
|
1533
|
+
return fs.readdirSync(p);
|
|
1534
|
+
} catch {
|
|
1535
|
+
return [];
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
// src/engine/codex-local/models.ts
|
|
1542
|
+
function codexContextWindowFor(_modelId) {
|
|
1543
|
+
return DEFAULT_CTX;
|
|
1544
|
+
}
|
|
1545
|
+
var CODEX_GPT55_EFFORT_LEVELS, CODEX_MODELS, CODEX_FALLBACK_DEFAULT_MODEL_ID = "gpt-5.4-mini", DEFAULT_CTX = 400000;
|
|
1546
|
+
var init_models2 = __esm(() => {
|
|
1547
|
+
CODEX_GPT55_EFFORT_LEVELS = [
|
|
1548
|
+
"none",
|
|
1549
|
+
"low",
|
|
1550
|
+
"medium",
|
|
1551
|
+
"high",
|
|
1552
|
+
"xhigh"
|
|
1553
|
+
];
|
|
1554
|
+
CODEX_MODELS = [
|
|
1555
|
+
{ vendor: "codex", id: "gpt-5.5", label: "GPT-5.5", hint: "latest, ChatGPT-account compatible" },
|
|
1556
|
+
...CODEX_GPT55_EFFORT_LEVELS.map((effort) => ({
|
|
1557
|
+
vendor: "codex",
|
|
1558
|
+
id: "gpt-5.5",
|
|
1559
|
+
effort,
|
|
1560
|
+
level: effort,
|
|
1561
|
+
label: `GPT-5.5 \xB7 ${effort}`,
|
|
1562
|
+
hint: effort === "none" ? "no reasoning effort" : `${effort} reasoning`
|
|
1563
|
+
})),
|
|
1564
|
+
{ vendor: "codex", id: "gpt-5.4", label: "GPT-5.4", hint: "stable" },
|
|
1565
|
+
{ vendor: "codex", id: "gpt-5.4-mini", label: "GPT-5.4 mini", hint: "fastest, always supported" }
|
|
1566
|
+
];
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
// src/engine/codex-local/settings.ts
|
|
1570
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1571
|
+
import { homedir as homedir7 } from "os";
|
|
1572
|
+
import { join as join4 } from "path";
|
|
1573
|
+
function readModelFromConfig() {
|
|
1574
|
+
try {
|
|
1575
|
+
const raw = readFileSync2(CONFIG_PATH, "utf8");
|
|
1576
|
+
let inTable = false;
|
|
1577
|
+
for (const line of raw.split(`
|
|
1578
|
+
`)) {
|
|
1579
|
+
const t = line.trim();
|
|
1580
|
+
if (t.startsWith("[") && t.endsWith("]")) {
|
|
1581
|
+
inTable = true;
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
if (inTable)
|
|
1585
|
+
continue;
|
|
1586
|
+
const m = /^model\s*=\s*"([^"]+)"/.exec(t);
|
|
1587
|
+
if (m)
|
|
1588
|
+
return m[1] ?? null;
|
|
1589
|
+
}
|
|
1590
|
+
return null;
|
|
1591
|
+
} catch {
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function resolveCodexDefaultModelId() {
|
|
1596
|
+
if (cached2 === undefined)
|
|
1597
|
+
cached2 = readModelFromConfig();
|
|
1598
|
+
return cached2 ?? CODEX_FALLBACK_DEFAULT_MODEL_ID;
|
|
1599
|
+
}
|
|
1600
|
+
var CONFIG_PATH, cached2;
|
|
1601
|
+
var init_settings2 = __esm(() => {
|
|
1602
|
+
init_models2();
|
|
1603
|
+
CONFIG_PATH = join4(homedir7(), ".codex", "config.toml");
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
// src/engine/codex-local/capabilities.ts
|
|
1607
|
+
var codexCapabilities, codexIdentity;
|
|
1608
|
+
var init_capabilities2 = __esm(() => {
|
|
1609
|
+
init_models2();
|
|
1610
|
+
init_settings2();
|
|
1611
|
+
codexCapabilities = {
|
|
1612
|
+
vendorId: "codex",
|
|
1613
|
+
label: "Codex",
|
|
1614
|
+
models: CODEX_MODELS,
|
|
1615
|
+
permissionModes: [
|
|
1616
|
+
{ id: "default", label: "full access" },
|
|
1617
|
+
{ id: "plan", label: "plan mode" }
|
|
1618
|
+
],
|
|
1619
|
+
defaultModelId: resolveCodexDefaultModelId,
|
|
1620
|
+
contextWindowFor: codexContextWindowFor
|
|
1621
|
+
};
|
|
1622
|
+
codexIdentity = {
|
|
1623
|
+
vendorId: "codex",
|
|
1624
|
+
productName: "Codex",
|
|
1625
|
+
shortName: "Codex",
|
|
1626
|
+
assistantName: "Codex",
|
|
1627
|
+
inputPlaceholder: "Ask Codex\u2026"
|
|
1628
|
+
};
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
// src/engine/codex-local/normalize.ts
|
|
1632
|
+
function normalizeCodexContent(raw) {
|
|
1633
|
+
if (typeof raw === "string") {
|
|
1634
|
+
return raw.length > 0 ? [{ type: "text", text: raw }] : [];
|
|
1635
|
+
}
|
|
1636
|
+
if (!Array.isArray(raw))
|
|
1637
|
+
return [];
|
|
1638
|
+
const blocks = [];
|
|
1639
|
+
for (const item of raw) {
|
|
1640
|
+
if (typeof item === "string") {
|
|
1641
|
+
if (item.length > 0)
|
|
1642
|
+
blocks.push({ type: "text", text: item });
|
|
1643
|
+
continue;
|
|
1644
|
+
}
|
|
1645
|
+
if (!isObject4(item))
|
|
1646
|
+
continue;
|
|
1647
|
+
const t = typeof item.type === "string" ? item.type : undefined;
|
|
1648
|
+
if (t === "input_text" || t === "output_text") {
|
|
1649
|
+
const text = typeof item.text === "string" ? item.text : "";
|
|
1650
|
+
if (text.length > 0)
|
|
1651
|
+
blocks.push({ type: "text", text });
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
if (t)
|
|
1655
|
+
blocks.push({ type: "text", text: `[codex: ${t}]` });
|
|
1656
|
+
}
|
|
1657
|
+
return blocks;
|
|
1658
|
+
}
|
|
1659
|
+
function isObject4(v) {
|
|
1660
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// src/engine/codex-local/synthetic.ts
|
|
1664
|
+
function isSyntheticCodexUserRow(blocks) {
|
|
1665
|
+
if (blocks.length === 0)
|
|
1666
|
+
return false;
|
|
1667
|
+
for (const b of blocks) {
|
|
1668
|
+
if (b.type !== "text")
|
|
1669
|
+
return false;
|
|
1670
|
+
const t = (b.text ?? "").trim();
|
|
1671
|
+
if (!isEnvironmentContextEnvelope(t) && !isInstructionsEnvelope(t))
|
|
1672
|
+
return false;
|
|
1673
|
+
}
|
|
1674
|
+
return true;
|
|
1675
|
+
}
|
|
1676
|
+
function visibleCodexUserText(content) {
|
|
1677
|
+
const blocks = normalizeCodexContent(content);
|
|
1678
|
+
if (isSyntheticCodexUserRow(blocks))
|
|
1679
|
+
return null;
|
|
1680
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => b.text).join(" ").trim();
|
|
1681
|
+
return text.length > 0 ? text : null;
|
|
1682
|
+
}
|
|
1683
|
+
function isEnvironmentContextEnvelope(text) {
|
|
1684
|
+
return text.startsWith("<environment_context>") && text.endsWith("</environment_context>");
|
|
1685
|
+
}
|
|
1686
|
+
function isInstructionsEnvelope(text) {
|
|
1687
|
+
return text.startsWith("# AGENTS.md instructions for ") && text.includes(`
|
|
1688
|
+
<INSTRUCTIONS>
|
|
1689
|
+
`) && text.endsWith("</INSTRUCTIONS>");
|
|
1690
|
+
}
|
|
1691
|
+
var init_synthetic = () => {};
|
|
1692
|
+
|
|
1693
|
+
// src/engine/codex-local/history.ts
|
|
1694
|
+
import { readFile as readFile3, readdir as readdir3, unlink as unlink2 } from "fs/promises";
|
|
1695
|
+
import { homedir as homedir8 } from "os";
|
|
1696
|
+
import path5 from "path";
|
|
1697
|
+
async function listRolloutFiles(deps = defaultDeps5) {
|
|
1698
|
+
const root = deps.sessionsDir();
|
|
1699
|
+
const years = (await deps.readdir(root)).sort().reverse();
|
|
1700
|
+
const out = [];
|
|
1701
|
+
for (const y of years) {
|
|
1702
|
+
const yp = path5.join(root, y);
|
|
1703
|
+
const months = (await deps.readdir(yp)).sort().reverse();
|
|
1704
|
+
for (const m of months) {
|
|
1705
|
+
const mp = path5.join(yp, m);
|
|
1706
|
+
const days = (await deps.readdir(mp)).sort().reverse();
|
|
1707
|
+
for (const d of days) {
|
|
1708
|
+
const dp = path5.join(mp, d);
|
|
1709
|
+
const files = (await deps.readdir(dp)).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl"));
|
|
1710
|
+
files.sort().reverse();
|
|
1711
|
+
for (const f of files)
|
|
1712
|
+
out.push(path5.join(dp, f));
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
return out;
|
|
1717
|
+
}
|
|
1718
|
+
async function findRolloutFile(sessionId, deps = defaultDeps5) {
|
|
1719
|
+
const all = await listRolloutFiles(deps);
|
|
1720
|
+
for (const p of all) {
|
|
1721
|
+
if (path5.basename(p).endsWith(`-${sessionId}.jsonl`))
|
|
1722
|
+
return p;
|
|
1723
|
+
}
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
async function readHistoryWithMetrics(sessionId, deps = defaultDeps5) {
|
|
1727
|
+
const file = await findRolloutFile(sessionId, deps);
|
|
1728
|
+
if (!file)
|
|
1729
|
+
return { messages: [] };
|
|
1730
|
+
let raw;
|
|
1731
|
+
try {
|
|
1732
|
+
raw = await deps.readFile(file);
|
|
1733
|
+
} catch {
|
|
1734
|
+
return { messages: [] };
|
|
1735
|
+
}
|
|
1736
|
+
const messages = sortByTimestamp2(parseJsonl2(raw, sessionId));
|
|
1737
|
+
const usageMetrics = deriveCodexUsageMetrics(raw);
|
|
1738
|
+
return { messages, ...usageMetrics ? { usageMetrics } : {} };
|
|
1739
|
+
}
|
|
1740
|
+
async function deleteHistory2(sessionId, deps = defaultDeps5) {
|
|
1741
|
+
const file = await findRolloutFile(sessionId, deps);
|
|
1742
|
+
if (!file)
|
|
1743
|
+
return;
|
|
1744
|
+
try {
|
|
1745
|
+
await unlink2(file);
|
|
1746
|
+
} catch (err) {
|
|
1747
|
+
if (err.code === "ENOENT")
|
|
1748
|
+
return;
|
|
1749
|
+
throw err;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
function sortByTimestamp2(messages) {
|
|
1753
|
+
return messages.map((msg, idx) => ({ msg, idx })).sort((a, b) => {
|
|
1754
|
+
if (a.msg.timestamp < b.msg.timestamp)
|
|
1755
|
+
return -1;
|
|
1756
|
+
if (a.msg.timestamp > b.msg.timestamp)
|
|
1757
|
+
return 1;
|
|
1758
|
+
return a.idx - b.idx;
|
|
1759
|
+
}).map((entry) => entry.msg);
|
|
1760
|
+
}
|
|
1761
|
+
function parseJsonl2(raw, sessionId) {
|
|
1762
|
+
const out = [];
|
|
1763
|
+
for (const line of raw.split(`
|
|
1764
|
+
`)) {
|
|
1765
|
+
const trimmed = line.trim();
|
|
1766
|
+
if (!trimmed)
|
|
1767
|
+
continue;
|
|
1768
|
+
let parsed;
|
|
1769
|
+
try {
|
|
1770
|
+
parsed = JSON.parse(trimmed);
|
|
1771
|
+
} catch {
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (!isObject5(parsed))
|
|
1775
|
+
continue;
|
|
1776
|
+
if (parsed.type !== "response_item")
|
|
1777
|
+
continue;
|
|
1778
|
+
const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
|
|
1779
|
+
if (!payload)
|
|
1780
|
+
continue;
|
|
1781
|
+
if (payload.type !== "message")
|
|
1782
|
+
continue;
|
|
1783
|
+
const role = payload.role;
|
|
1784
|
+
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
1785
|
+
continue;
|
|
1786
|
+
const blocks = normalizeCodexContent(payload.content);
|
|
1787
|
+
if (role === "user" && isSyntheticCodexUserRow(blocks))
|
|
1788
|
+
continue;
|
|
1789
|
+
const ts = typeof parsed.timestamp === "string" ? parsed.timestamp : new Date().toISOString();
|
|
1790
|
+
out.push({ role, blocks, timestamp: ts, sessionId });
|
|
1791
|
+
}
|
|
1792
|
+
return out;
|
|
1793
|
+
}
|
|
1794
|
+
function deriveCodexUsageMetrics(raw) {
|
|
1795
|
+
let latestUsage;
|
|
1796
|
+
let latestUsageTimestampMs = null;
|
|
1797
|
+
let lastUserTimestampMs = null;
|
|
1798
|
+
let inputTokens = 0;
|
|
1799
|
+
let outputTokens = 0;
|
|
1800
|
+
const intervals = [];
|
|
1801
|
+
for (const line of raw.split(`
|
|
1802
|
+
`)) {
|
|
1803
|
+
const trimmed = line.trim();
|
|
1804
|
+
if (!trimmed)
|
|
1805
|
+
continue;
|
|
1806
|
+
let parsed;
|
|
1807
|
+
try {
|
|
1808
|
+
parsed = JSON.parse(trimmed);
|
|
1809
|
+
} catch {
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
if (!isObject5(parsed))
|
|
1813
|
+
continue;
|
|
1814
|
+
const timestampMs = typeof parsed.timestamp === "string" ? parseTimestampMs2(parsed.timestamp) : null;
|
|
1815
|
+
if (parsed.type === "response_item") {
|
|
1816
|
+
const payload = isObject5(parsed.payload) ? parsed.payload : undefined;
|
|
1817
|
+
if (payload?.type === "message" && payload.role === "user" && timestampMs !== null) {
|
|
1818
|
+
const blocks = normalizeCodexContent(payload.content);
|
|
1819
|
+
if (!isSyntheticCodexUserRow(blocks))
|
|
1820
|
+
lastUserTimestampMs = timestampMs;
|
|
1821
|
+
}
|
|
1822
|
+
continue;
|
|
1823
|
+
}
|
|
1824
|
+
if (parsed.type !== "turn.completed")
|
|
1825
|
+
continue;
|
|
1826
|
+
const usage = isObject5(parsed.usage) ? parsed.usage : undefined;
|
|
1827
|
+
if (!usage)
|
|
1828
|
+
continue;
|
|
1829
|
+
const snapshot = codexUsageToSnapshot(usage);
|
|
1830
|
+
if (!snapshot)
|
|
1831
|
+
continue;
|
|
1832
|
+
if (timestampMs !== null && (latestUsageTimestampMs === null || timestampMs > latestUsageTimestampMs)) {
|
|
1833
|
+
latestUsageTimestampMs = timestampMs;
|
|
1834
|
+
latestUsage = snapshot;
|
|
1835
|
+
} else if (latestUsage === undefined) {
|
|
1836
|
+
latestUsage = snapshot;
|
|
1837
|
+
}
|
|
1838
|
+
inputTokens += snapshot.input_tokens;
|
|
1839
|
+
outputTokens += snapshot.output_tokens;
|
|
1840
|
+
if (timestampMs !== null && lastUserTimestampMs !== null && timestampMs > lastUserTimestampMs) {
|
|
1841
|
+
intervals.push({ startMs: lastUserTimestampMs, endMs: timestampMs });
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (!latestUsage)
|
|
1845
|
+
return;
|
|
1846
|
+
const durationMs2 = mergedDurationMs(intervals);
|
|
1847
|
+
if (durationMs2 <= 0)
|
|
1848
|
+
return latestUsage;
|
|
1849
|
+
return {
|
|
1850
|
+
...latestUsage,
|
|
1851
|
+
total_speed_tokens_per_second: (inputTokens + outputTokens) / (durationMs2 / 1000)
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
function codexUsageToSnapshot(usage) {
|
|
1855
|
+
const input = numberOr(usage.input_tokens, 0);
|
|
1856
|
+
const output = numberOr(usage.output_tokens, 0) + numberOr(usage.reasoning_output_tokens, 0);
|
|
1857
|
+
const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
|
|
1858
|
+
if (input <= 0 && output <= 0 && cacheRead === undefined)
|
|
1859
|
+
return;
|
|
1860
|
+
return {
|
|
1861
|
+
input_tokens: input,
|
|
1862
|
+
output_tokens: output,
|
|
1863
|
+
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
function parseTimestampMs2(value) {
|
|
1867
|
+
const ms = new Date(value).getTime();
|
|
1868
|
+
return Number.isFinite(ms) ? ms : null;
|
|
1869
|
+
}
|
|
1870
|
+
function mergedDurationMs(intervals) {
|
|
1871
|
+
if (intervals.length === 0)
|
|
1872
|
+
return 0;
|
|
1873
|
+
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
1874
|
+
let total = 0;
|
|
1875
|
+
let current = sorted[0];
|
|
1876
|
+
if (!current)
|
|
1877
|
+
return 0;
|
|
1878
|
+
for (let i = 1;i < sorted.length; i++) {
|
|
1879
|
+
const next = sorted[i];
|
|
1880
|
+
if (!next)
|
|
1881
|
+
continue;
|
|
1882
|
+
if (next.startMs <= current.endMs) {
|
|
1883
|
+
current = { startMs: current.startMs, endMs: Math.max(current.endMs, next.endMs) };
|
|
1884
|
+
} else {
|
|
1885
|
+
total += current.endMs - current.startMs;
|
|
1886
|
+
current = next;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
total += current.endMs - current.startMs;
|
|
1890
|
+
return total;
|
|
1891
|
+
}
|
|
1892
|
+
function numberOr(v, fallback) {
|
|
1893
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
1894
|
+
}
|
|
1895
|
+
function isObject5(v) {
|
|
1896
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1897
|
+
}
|
|
1898
|
+
var defaultDeps5;
|
|
1899
|
+
var init_history2 = __esm(() => {
|
|
1900
|
+
init_synthetic();
|
|
1901
|
+
defaultDeps5 = {
|
|
1902
|
+
sessionsDir() {
|
|
1903
|
+
return path5.join(homedir8(), ".codex", "sessions");
|
|
1904
|
+
},
|
|
1905
|
+
async readdir(p) {
|
|
1906
|
+
try {
|
|
1907
|
+
return await readdir3(p);
|
|
1908
|
+
} catch {
|
|
1909
|
+
return [];
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
async readFile(p) {
|
|
1913
|
+
return await readFile3(p, "utf8");
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
1916
|
+
});
|
|
1917
|
+
|
|
1918
|
+
// src/engine/codex-local/sessions.ts
|
|
1919
|
+
import { open, stat as stat2 } from "fs/promises";
|
|
1920
|
+
async function listSessionsForCwd2(cwd, deps) {
|
|
1921
|
+
const files = await listRolloutFiles(deps);
|
|
1922
|
+
const out = [];
|
|
1923
|
+
for (const file of files) {
|
|
1924
|
+
const meta = await tryReadMeta(file);
|
|
1925
|
+
if (!meta)
|
|
1926
|
+
continue;
|
|
1927
|
+
if (meta.cwd !== cwd)
|
|
1928
|
+
continue;
|
|
1929
|
+
out.push({
|
|
1930
|
+
sessionId: meta.sessionId,
|
|
1931
|
+
mtimeMs: meta.mtimeMs,
|
|
1932
|
+
firstUserMessage: meta.firstUserMessage,
|
|
1933
|
+
messageCount: meta.messageCount
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1937
|
+
return out;
|
|
1938
|
+
}
|
|
1939
|
+
async function tryReadMeta(file) {
|
|
1940
|
+
let st;
|
|
1941
|
+
try {
|
|
1942
|
+
st = await stat2(file);
|
|
1943
|
+
} catch {
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
let sessionId;
|
|
1947
|
+
let cwd;
|
|
1948
|
+
let firstUser = null;
|
|
1949
|
+
let messageCount = 0;
|
|
1950
|
+
const handle = await open(file, "r").catch(() => null);
|
|
1951
|
+
if (!handle)
|
|
1952
|
+
return null;
|
|
1953
|
+
try {
|
|
1954
|
+
let buf = "";
|
|
1955
|
+
const processLine = (line) => {
|
|
1956
|
+
const parsed = safeParse(line);
|
|
1957
|
+
if (!parsed)
|
|
1958
|
+
return;
|
|
1959
|
+
if (parsed.type === "session_meta") {
|
|
1960
|
+
const payload = parsed.payload;
|
|
1961
|
+
if (isObject6(payload)) {
|
|
1962
|
+
if (typeof payload.id === "string")
|
|
1963
|
+
sessionId = payload.id;
|
|
1964
|
+
if (typeof payload.cwd === "string")
|
|
1965
|
+
cwd = payload.cwd;
|
|
1966
|
+
}
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
if (parsed.type === "response_item" && isObject6(parsed.payload)) {
|
|
1970
|
+
const p = parsed.payload;
|
|
1971
|
+
if (p.type === "message") {
|
|
1972
|
+
messageCount++;
|
|
1973
|
+
if (!firstUser && p.role === "user") {
|
|
1974
|
+
const text = visibleCodexUserText(p.content);
|
|
1975
|
+
if (text)
|
|
1976
|
+
firstUser = text.slice(0, PREVIEW_CHAR_CAP);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
const reader = handle.createReadStream({ encoding: "utf8" });
|
|
1982
|
+
for await (const chunk of reader) {
|
|
1983
|
+
buf += chunk;
|
|
1984
|
+
let nl = buf.indexOf(`
|
|
1985
|
+
`);
|
|
1986
|
+
while (nl !== -1) {
|
|
1987
|
+
const line = buf.slice(0, nl);
|
|
1988
|
+
buf = buf.slice(nl + 1);
|
|
1989
|
+
nl = buf.indexOf(`
|
|
1990
|
+
`);
|
|
1991
|
+
processLine(line);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
if (buf.trim())
|
|
1995
|
+
processLine(buf);
|
|
1996
|
+
} finally {
|
|
1997
|
+
await handle.close().catch(() => {});
|
|
1998
|
+
}
|
|
1999
|
+
if (!sessionId || !cwd)
|
|
2000
|
+
return null;
|
|
2001
|
+
return {
|
|
2002
|
+
sessionId,
|
|
2003
|
+
cwd,
|
|
2004
|
+
mtimeMs: st.mtimeMs,
|
|
2005
|
+
firstUserMessage: firstUser,
|
|
2006
|
+
messageCount
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
function safeParse(line) {
|
|
2010
|
+
const t = line.trim();
|
|
2011
|
+
if (!t)
|
|
2012
|
+
return null;
|
|
2013
|
+
try {
|
|
2014
|
+
const v = JSON.parse(t);
|
|
2015
|
+
return isObject6(v) ? v : null;
|
|
2016
|
+
} catch {
|
|
2017
|
+
return null;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
function isObject6(v) {
|
|
2021
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2022
|
+
}
|
|
2023
|
+
var PREVIEW_CHAR_CAP = 200;
|
|
2024
|
+
var init_sessions2 = __esm(() => {
|
|
2025
|
+
init_history2();
|
|
2026
|
+
init_synthetic();
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
// src/engine/codex-local/spawn.ts
|
|
2030
|
+
import { spawn as spawn3 } from "child_process";
|
|
2031
|
+
function spawnCodexProcess(opts) {
|
|
2032
|
+
const args = buildArgs2(opts);
|
|
2033
|
+
const proc = spawn3(opts.binaryPath, args, {
|
|
2034
|
+
cwd: opts.cwd,
|
|
2035
|
+
env: { ...process.env, ...opts.env ?? {} },
|
|
2036
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2037
|
+
});
|
|
2038
|
+
try {
|
|
2039
|
+
proc.stdin.end();
|
|
2040
|
+
} catch {}
|
|
2041
|
+
return {
|
|
2042
|
+
proc,
|
|
2043
|
+
stdout: proc.stdout,
|
|
2044
|
+
stderr: proc.stderr,
|
|
2045
|
+
args
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
function buildArgs2(opts) {
|
|
2049
|
+
const isResume = !!opts.resumeSessionId;
|
|
2050
|
+
const args = ["exec"];
|
|
2051
|
+
if (isResume) {
|
|
2052
|
+
args.push("resume", opts.resumeSessionId);
|
|
2053
|
+
}
|
|
2054
|
+
args.push("--json", "--skip-git-repo-check");
|
|
2055
|
+
if (opts.model) {
|
|
2056
|
+
args.push("-m", opts.model);
|
|
2057
|
+
}
|
|
2058
|
+
if (opts.modelEffort) {
|
|
2059
|
+
args.push("-c", `model_reasoning_effort="${opts.modelEffort}"`);
|
|
2060
|
+
}
|
|
2061
|
+
if (!isResume) {
|
|
2062
|
+
args.push("-C", opts.cwd);
|
|
2063
|
+
if (opts.permissionMode === "plan")
|
|
2064
|
+
args.push("-s", "read-only");
|
|
2065
|
+
}
|
|
2066
|
+
if (opts.permissionMode !== "plan") {
|
|
2067
|
+
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
2068
|
+
}
|
|
2069
|
+
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
2070
|
+
args.push(...opts.extraArgs);
|
|
2071
|
+
}
|
|
2072
|
+
args.push(opts.prompt);
|
|
2073
|
+
return args;
|
|
2074
|
+
}
|
|
2075
|
+
var init_spawn2 = () => {};
|
|
2076
|
+
|
|
2077
|
+
// src/engine/codex-local/stream.ts
|
|
2078
|
+
async function* parseStreamJson2(lines, opts = {}) {
|
|
2079
|
+
let sessionIdEmitted = false;
|
|
2080
|
+
const toolNameById = new Map;
|
|
2081
|
+
const startedByItemId = new Set;
|
|
2082
|
+
for await (const rawLine of lines) {
|
|
2083
|
+
const line = rawLine.trim();
|
|
2084
|
+
if (!line)
|
|
2085
|
+
continue;
|
|
2086
|
+
let msg;
|
|
2087
|
+
try {
|
|
2088
|
+
msg = JSON.parse(line);
|
|
2089
|
+
} catch (err) {
|
|
2090
|
+
yield { type: "error", message: `codex stream-json parse failed: ${stringifyErr2(err)}` };
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (!isObject7(msg))
|
|
2094
|
+
continue;
|
|
2095
|
+
const type = typeof msg.type === "string" ? msg.type : undefined;
|
|
2096
|
+
if (!type)
|
|
2097
|
+
continue;
|
|
2098
|
+
if (type === "session_meta" || type === "thread.started") {
|
|
2099
|
+
const sid = codexSessionId(msg);
|
|
2100
|
+
if (sid && !sessionIdEmitted) {
|
|
2101
|
+
sessionIdEmitted = true;
|
|
2102
|
+
opts.onSessionId?.(sid);
|
|
2103
|
+
}
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (type === "turn.started")
|
|
2107
|
+
continue;
|
|
2108
|
+
if (type === "item.started" || type === "item.completed") {
|
|
2109
|
+
const item = isObject7(msg.item) ? msg.item : undefined;
|
|
2110
|
+
if (!item)
|
|
2111
|
+
continue;
|
|
2112
|
+
const itemId = typeof item.id === "string" ? item.id : undefined;
|
|
2113
|
+
const itemType = typeof item.type === "string" ? item.type : "tool";
|
|
2114
|
+
if (itemType === "agent_message") {
|
|
2115
|
+
if (type !== "item.completed")
|
|
2116
|
+
continue;
|
|
2117
|
+
const text = typeof item.text === "string" ? item.text : "";
|
|
2118
|
+
if (text)
|
|
2119
|
+
yield { type: "assistant.delta", text };
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
if (itemId) {
|
|
2123
|
+
toolNameById.set(itemId, itemType);
|
|
2124
|
+
}
|
|
2125
|
+
if (type === "item.started") {
|
|
2126
|
+
if (itemId)
|
|
2127
|
+
startedByItemId.add(itemId);
|
|
2128
|
+
const input = stripIdAndType(item);
|
|
2129
|
+
yield { type: "tool.start", name: itemType, input };
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
if (itemId && !startedByItemId.has(itemId)) {
|
|
2133
|
+
const input = stripIdAndType(item);
|
|
2134
|
+
yield { type: "tool.start", name: itemType, input };
|
|
2135
|
+
}
|
|
2136
|
+
if (itemId)
|
|
2137
|
+
startedByItemId.delete(itemId);
|
|
2138
|
+
const output = stripIdAndType(item);
|
|
2139
|
+
yield { type: "tool.result", name: itemType, output };
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
if (type === "turn.completed") {
|
|
2143
|
+
const usage = isObject7(msg.usage) ? msg.usage : undefined;
|
|
2144
|
+
if (usage) {
|
|
2145
|
+
const inTok = numberOr2(usage.input_tokens, 0);
|
|
2146
|
+
const outTok = numberOr2(usage.output_tokens, 0) + numberOr2(usage.reasoning_output_tokens, 0);
|
|
2147
|
+
const cacheRead = typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined;
|
|
2148
|
+
yield {
|
|
2149
|
+
type: "usage",
|
|
2150
|
+
input_tokens: inTok,
|
|
2151
|
+
output_tokens: outTok,
|
|
2152
|
+
...cacheRead !== undefined ? { cache_read_input_tokens: cacheRead } : {}
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
yield { type: "done" };
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (type === "error") {
|
|
2159
|
+
const message = typeof msg.message === "string" ? msg.message : "codex emitted an error";
|
|
2160
|
+
yield { type: "error", message };
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
async function* readLines2(stream) {
|
|
2166
|
+
let buf = "";
|
|
2167
|
+
for await (const chunk of stream) {
|
|
2168
|
+
const text = typeof chunk === "string" ? chunk : Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
2169
|
+
buf += text;
|
|
2170
|
+
let nl = buf.indexOf(`
|
|
2171
|
+
`);
|
|
2172
|
+
while (nl !== -1) {
|
|
2173
|
+
yield buf.slice(0, nl);
|
|
2174
|
+
buf = buf.slice(nl + 1);
|
|
2175
|
+
nl = buf.indexOf(`
|
|
2176
|
+
`);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
if (buf.length > 0)
|
|
2180
|
+
yield buf;
|
|
2181
|
+
}
|
|
2182
|
+
function isObject7(v) {
|
|
2183
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2184
|
+
}
|
|
2185
|
+
function codexSessionId(msg) {
|
|
2186
|
+
if (msg.type === "session_meta") {
|
|
2187
|
+
const payload = isObject7(msg.payload) ? msg.payload : undefined;
|
|
2188
|
+
const id2 = payload?.id;
|
|
2189
|
+
return typeof id2 === "string" && id2.length > 0 ? id2 : undefined;
|
|
2190
|
+
}
|
|
2191
|
+
const id = msg.thread_id;
|
|
2192
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
2193
|
+
}
|
|
2194
|
+
function numberOr2(v, fallback) {
|
|
2195
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
2196
|
+
}
|
|
2197
|
+
function stripIdAndType(item) {
|
|
2198
|
+
const { id: _id, type: _type, ...rest } = item;
|
|
2199
|
+
return rest;
|
|
2200
|
+
}
|
|
2201
|
+
function stringifyErr2(err) {
|
|
2202
|
+
if (err instanceof Error)
|
|
2203
|
+
return err.message;
|
|
2204
|
+
try {
|
|
2205
|
+
return JSON.stringify(err);
|
|
2206
|
+
} catch {
|
|
2207
|
+
return String(err);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// src/engine/codex-local/index.ts
|
|
2212
|
+
class CodexLocal {
|
|
2213
|
+
identity = codexIdentity;
|
|
2214
|
+
capabilities = codexCapabilities;
|
|
2215
|
+
registry = new SessionRegistry;
|
|
2216
|
+
running = new Map;
|
|
2217
|
+
binaryPathResolver;
|
|
2218
|
+
stopGraceMs;
|
|
2219
|
+
constructor(opts = {}) {
|
|
2220
|
+
this.binaryPathResolver = opts.binaryPathResolver ?? findCodexBinary;
|
|
2221
|
+
this.stopGraceMs = opts.stopGraceMs ?? 5000;
|
|
2222
|
+
}
|
|
2223
|
+
async spawn(cwd, prompt, opts) {
|
|
2224
|
+
return this.start({ cwd, prompt, opts });
|
|
2225
|
+
}
|
|
2226
|
+
async resume(sessionId, prompt, opts) {
|
|
2227
|
+
const cwd = opts?.cwd ?? opts?.env?.KOBE_RESUME_CWD ?? process.cwd();
|
|
2228
|
+
return this.start({ cwd, prompt, opts, resumeSessionId: sessionId });
|
|
2229
|
+
}
|
|
2230
|
+
stream(handle) {
|
|
2231
|
+
const sid = handle.sessionId;
|
|
2232
|
+
const self = this;
|
|
2233
|
+
return {
|
|
2234
|
+
async* [Symbol.asyncIterator]() {
|
|
2235
|
+
const session = self.running.get(sid);
|
|
2236
|
+
if (!session)
|
|
2237
|
+
return;
|
|
2238
|
+
let idx = 0;
|
|
2239
|
+
while (true) {
|
|
2240
|
+
if (idx < session.queue.length) {
|
|
2241
|
+
const ev = session.queue[idx++];
|
|
2242
|
+
if (!ev)
|
|
2243
|
+
continue;
|
|
2244
|
+
yield ev;
|
|
2245
|
+
if (ev.type === "done" || ev.type === "error")
|
|
2246
|
+
return;
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
if (session.closed)
|
|
2250
|
+
return;
|
|
2251
|
+
await new Promise((resolve2) => session.waiters.push(resolve2));
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
async readHistory(sessionId) {
|
|
2257
|
+
return readHistoryWithMetrics(sessionId);
|
|
2258
|
+
}
|
|
2259
|
+
async deleteHistory(sessionId) {
|
|
2260
|
+
return deleteHistory2(sessionId);
|
|
2261
|
+
}
|
|
2262
|
+
async listSessions(cwd) {
|
|
2263
|
+
return listSessionsForCwd2(cwd);
|
|
2264
|
+
}
|
|
2265
|
+
async stop(handle) {
|
|
2266
|
+
const sid = handle.sessionId;
|
|
2267
|
+
await this.registry.kill(sid, this.stopGraceMs);
|
|
2268
|
+
const session = this.running.get(sid);
|
|
2269
|
+
if (session) {
|
|
2270
|
+
session.closed = true;
|
|
2271
|
+
this.notify(session);
|
|
2272
|
+
this.running.delete(sid);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
async start(args) {
|
|
2276
|
+
const binaryPath = await this.binaryPathResolver();
|
|
2277
|
+
const spawned = spawnCodexProcess({
|
|
2278
|
+
binaryPath,
|
|
2279
|
+
cwd: args.cwd,
|
|
2280
|
+
prompt: args.prompt,
|
|
2281
|
+
model: args.opts?.model,
|
|
2282
|
+
modelEffort: args.opts?.modelEffort,
|
|
2283
|
+
permissionMode: args.opts?.permissionMode,
|
|
2284
|
+
env: args.opts?.env,
|
|
2285
|
+
resumeSessionId: args.resumeSessionId
|
|
2286
|
+
});
|
|
2287
|
+
let resolveHandle = () => {};
|
|
2288
|
+
let rejectHandle = () => {};
|
|
2289
|
+
const handlePromise = new Promise((res, rej) => {
|
|
2290
|
+
resolveHandle = res;
|
|
2291
|
+
rejectHandle = rej;
|
|
2292
|
+
});
|
|
2293
|
+
const queue = [];
|
|
2294
|
+
let session;
|
|
2295
|
+
let bound = false;
|
|
2296
|
+
let stderrTail = "";
|
|
2297
|
+
const bind = (sessionId) => {
|
|
2298
|
+
if (bound) {
|
|
2299
|
+
if (session && session.sessionId !== sessionId) {
|
|
2300
|
+
queue.push({
|
|
2301
|
+
type: "error",
|
|
2302
|
+
message: `codex resumed to a different session id (got ${sessionId}, expected ${session.sessionId})`
|
|
2303
|
+
});
|
|
2304
|
+
this.notify(session);
|
|
2305
|
+
}
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
bound = true;
|
|
2309
|
+
session = {
|
|
2310
|
+
sessionId,
|
|
2311
|
+
cwd: args.cwd,
|
|
2312
|
+
spawned,
|
|
2313
|
+
queue,
|
|
2314
|
+
waiters: [],
|
|
2315
|
+
closed: false,
|
|
2316
|
+
spawnedAtIso: new Date().toISOString()
|
|
2317
|
+
};
|
|
2318
|
+
this.running.set(sessionId, session);
|
|
2319
|
+
this.registry.register({
|
|
2320
|
+
sessionId,
|
|
2321
|
+
cwd: args.cwd,
|
|
2322
|
+
proc: spawned.proc,
|
|
2323
|
+
startedAt: Date.now(),
|
|
2324
|
+
prompt: args.prompt
|
|
2325
|
+
});
|
|
2326
|
+
resolveHandle({ sessionId, cwd: args.cwd });
|
|
2327
|
+
};
|
|
2328
|
+
if (args.resumeSessionId) {
|
|
2329
|
+
try {
|
|
2330
|
+
bind(args.resumeSessionId);
|
|
2331
|
+
} catch (err) {
|
|
2332
|
+
try {
|
|
2333
|
+
spawned.proc.kill("SIGKILL");
|
|
2334
|
+
} catch {}
|
|
2335
|
+
rejectHandle(err);
|
|
2336
|
+
throw err;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
captureStderrTail(spawned.stderr, (chunk) => {
|
|
2340
|
+
stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_CAP);
|
|
2341
|
+
});
|
|
2342
|
+
const exitInfo = {
|
|
2343
|
+
code: null,
|
|
2344
|
+
signal: null,
|
|
2345
|
+
seen: false
|
|
2346
|
+
};
|
|
2347
|
+
const exitObserved = new Promise((resolve2) => {
|
|
2348
|
+
spawned.proc.once("exit", (code, signal) => {
|
|
2349
|
+
exitInfo.code = code;
|
|
2350
|
+
exitInfo.signal = signal;
|
|
2351
|
+
exitInfo.seen = true;
|
|
2352
|
+
if (!bound) {
|
|
2353
|
+
rejectHandle(new Error(formatExitMsg("codex exited before session id was captured", code, signal, stderrTail)));
|
|
2354
|
+
}
|
|
2355
|
+
resolve2();
|
|
2356
|
+
});
|
|
2357
|
+
});
|
|
2358
|
+
(async () => {
|
|
2359
|
+
const events = parseStreamJson2(readLines2(spawned.stdout), {
|
|
2360
|
+
onSessionId: (sid) => bind(sid)
|
|
2361
|
+
});
|
|
2362
|
+
try {
|
|
2363
|
+
for await (const ev of events) {
|
|
2364
|
+
const enriched = enrichUsageEvent2(ev, session?.spawnedAtIso);
|
|
2365
|
+
queue.push(enriched);
|
|
2366
|
+
if (session)
|
|
2367
|
+
this.notify(session);
|
|
2368
|
+
if ((enriched.type === "done" || enriched.type === "error") && session) {
|
|
2369
|
+
this.registry.unregister(session.sessionId, spawned.proc);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
const ev = {
|
|
2374
|
+
type: "error",
|
|
2375
|
+
message: `codex parser failure: ${err instanceof Error ? err.message : String(err)}`
|
|
2376
|
+
};
|
|
2377
|
+
queue.push(ev);
|
|
2378
|
+
if (session)
|
|
2379
|
+
this.notify(session);
|
|
2380
|
+
} finally {
|
|
2381
|
+
await Promise.race([exitObserved, new Promise((r) => setTimeout(r, 500))]);
|
|
2382
|
+
if (session) {
|
|
2383
|
+
const code = exitInfo.code;
|
|
2384
|
+
const lastEv = queue[queue.length - 1];
|
|
2385
|
+
if (exitInfo.seen && typeof code === "number" && code !== 0 && lastEv?.type !== "error") {
|
|
2386
|
+
queue.push({
|
|
2387
|
+
type: "error",
|
|
2388
|
+
message: formatExitMsg("codex exited", code, exitInfo.signal, stderrTail)
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
session.closed = true;
|
|
2392
|
+
this.notify(session);
|
|
2393
|
+
this.registry.unregister(session.sessionId, spawned.proc);
|
|
2394
|
+
if (this.running.get(session.sessionId) === session) {
|
|
2395
|
+
this.running.delete(session.sessionId);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
if (!bound) {
|
|
2399
|
+
rejectHandle(new Error("codex exited without emitting a session id"));
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
})();
|
|
2403
|
+
spawned.proc.once("error", (err) => {
|
|
2404
|
+
if (!bound)
|
|
2405
|
+
rejectHandle(err);
|
|
2406
|
+
});
|
|
2407
|
+
return handlePromise;
|
|
2408
|
+
}
|
|
2409
|
+
notify(session) {
|
|
2410
|
+
const waiters = session.waiters;
|
|
2411
|
+
session.waiters = [];
|
|
2412
|
+
for (const w of waiters)
|
|
2413
|
+
w();
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
function captureStderrTail(stream, onChunk) {
|
|
2417
|
+
const s = stream;
|
|
2418
|
+
s.on("data", (chunk) => {
|
|
2419
|
+
onChunk(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
2420
|
+
});
|
|
2421
|
+
s.on("error", () => {});
|
|
2422
|
+
}
|
|
2423
|
+
function formatExitMsg(prefix, code, signal, stderrTail) {
|
|
2424
|
+
const parts = [prefix];
|
|
2425
|
+
if (typeof code === "number")
|
|
2426
|
+
parts.push(`(code=${code}${signal ? `, signal=${signal}` : ""})`);
|
|
2427
|
+
else if (signal)
|
|
2428
|
+
parts.push(`(signal=${signal})`);
|
|
2429
|
+
const detail = stderrTail.trim().split(/\r?\n/).filter(Boolean).slice(-3).join(" | ");
|
|
2430
|
+
if (detail)
|
|
2431
|
+
parts.push(`: ${detail}`);
|
|
2432
|
+
return parts.join(" ").replace(/ : /, ": ");
|
|
2433
|
+
}
|
|
2434
|
+
function enrichUsageEvent2(ev, startedAtIso) {
|
|
2435
|
+
if (ev.type !== "usage")
|
|
2436
|
+
return ev;
|
|
2437
|
+
return { type: "usage", ...withTotalSpeedForTurn(ev, startedAtIso, new Date().toISOString()) };
|
|
2438
|
+
}
|
|
2439
|
+
var STDERR_TAIL_CAP;
|
|
2440
|
+
var init_codex_local = __esm(() => {
|
|
2441
|
+
init_binary2();
|
|
2442
|
+
init_capabilities2();
|
|
2443
|
+
init_history2();
|
|
2444
|
+
init_sessions2();
|
|
2445
|
+
init_spawn2();
|
|
2446
|
+
STDERR_TAIL_CAP = 4 * 1024;
|
|
2447
|
+
});
|
|
2448
|
+
|
|
1163
2449
|
// src/orchestrator/bridge/server.ts
|
|
1164
|
-
import { mkdir as mkdir2, unlink as
|
|
2450
|
+
import { mkdir as mkdir2, unlink as unlink3 } from "fs/promises";
|
|
1165
2451
|
import { createServer } from "net";
|
|
1166
2452
|
import { dirname as dirname2 } from "path";
|
|
1167
2453
|
async function startBridgeServer(orch, socketPath) {
|
|
1168
2454
|
await mkdir2(dirname2(socketPath), { recursive: true });
|
|
1169
|
-
await
|
|
2455
|
+
await unlink3(socketPath).catch(() => {});
|
|
1170
2456
|
const conns = new Set;
|
|
1171
2457
|
const server = createServer((conn) => {
|
|
1172
2458
|
conns.add(conn);
|
|
@@ -1207,7 +2493,7 @@ async function startBridgeServer(orch, socketPath) {
|
|
|
1207
2493
|
conn.destroy();
|
|
1208
2494
|
conns.clear();
|
|
1209
2495
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
1210
|
-
await
|
|
2496
|
+
await unlink3(socketPath).catch(() => {});
|
|
1211
2497
|
}
|
|
1212
2498
|
};
|
|
1213
2499
|
}
|
|
@@ -1300,23 +2586,19 @@ __export(exports_bridge, {
|
|
|
1300
2586
|
bridgeSocketPathForHome: () => bridgeSocketPathForHome
|
|
1301
2587
|
});
|
|
1302
2588
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
1303
|
-
import { homedir as
|
|
1304
|
-
import { join as
|
|
2589
|
+
import { homedir as homedir9 } from "os";
|
|
2590
|
+
import { join as join5 } from "path";
|
|
1305
2591
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1306
2592
|
function bridgeSocketPathForHome(home, pid = process.pid) {
|
|
1307
|
-
const runDir =
|
|
1308
|
-
|
|
1309
|
-
const macTempSocket = process.platform === "darwin" && preferred.startsWith(tmpdir2());
|
|
1310
|
-
if (preferred.length <= UNIX_SOCKET_PATH_LIMIT && !macTempSocket)
|
|
1311
|
-
return preferred;
|
|
1312
|
-
const shortTmp = process.platform === "darwin" ? "/tmp" : tmpdir2();
|
|
1313
|
-
return join3(shortTmp, `kobe-bridge-${pid}.sock`);
|
|
2593
|
+
const runDir = join5(home, ".kobe", "run");
|
|
2594
|
+
return fitSocketPath(join5(runDir, `bridge-${pid}.sock`), home, "bridge", pid);
|
|
1314
2595
|
}
|
|
1315
2596
|
async function startBridge(orch, opts = {}) {
|
|
1316
|
-
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
1317
|
-
const runDir =
|
|
2597
|
+
const home = opts.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir9();
|
|
2598
|
+
const runDir = join5(home, ".kobe", "run");
|
|
1318
2599
|
const socketPath = bridgeSocketPathForHome(home);
|
|
1319
|
-
const mcpConfigPath =
|
|
2600
|
+
const mcpConfigPath = join5(runDir, `mcp-${process.pid}.json`);
|
|
2601
|
+
await mkdir3(runDir, { recursive: true });
|
|
1320
2602
|
const server = await startBridgeServer(orch, socketPath);
|
|
1321
2603
|
await mkdir3(runDir, { recursive: true });
|
|
1322
2604
|
const moduleExt = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
@@ -1339,8 +2621,8 @@ async function startBridge(orch, opts = {}) {
|
|
|
1339
2621
|
}
|
|
1340
2622
|
};
|
|
1341
2623
|
}
|
|
1342
|
-
var UNIX_SOCKET_PATH_LIMIT = 103;
|
|
1343
2624
|
var init_bridge = __esm(() => {
|
|
2625
|
+
init_paths();
|
|
1344
2626
|
init_server();
|
|
1345
2627
|
});
|
|
1346
2628
|
|
|
@@ -2572,134 +3854,83 @@ var init_dev = __esm(() => {
|
|
|
2572
3854
|
}
|
|
2573
3855
|
});
|
|
2574
3856
|
|
|
2575
|
-
// src/engine/
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
function
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
const model = typeof obj.model === "string" && obj.model.length > 0 ? obj.model : undefined;
|
|
2591
|
-
cached = { model };
|
|
2592
|
-
return cached;
|
|
2593
|
-
} catch {
|
|
2594
|
-
cached = null;
|
|
2595
|
-
return null;
|
|
3857
|
+
// src/engine/registry.ts
|
|
3858
|
+
function getCapabilities(vendor) {
|
|
3859
|
+
return ENGINE_REGISTRY[vendor] ?? defaultCapabilities;
|
|
3860
|
+
}
|
|
3861
|
+
function getIdentity(vendor) {
|
|
3862
|
+
return ENGINE_IDENTITIES[vendor] ?? defaultIdentity;
|
|
3863
|
+
}
|
|
3864
|
+
function capabilitiesForModelId(modelId) {
|
|
3865
|
+
if (!modelId)
|
|
3866
|
+
return defaultCapabilities;
|
|
3867
|
+
for (const caps of Object.values(ENGINE_REGISTRY)) {
|
|
3868
|
+
if (!caps)
|
|
3869
|
+
continue;
|
|
3870
|
+
if (caps.models.some((m) => m.id === modelId))
|
|
3871
|
+
return caps;
|
|
2596
3872
|
}
|
|
3873
|
+
return defaultCapabilities;
|
|
2597
3874
|
}
|
|
2598
|
-
function
|
|
2599
|
-
const
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
}
|
|
2604
|
-
var SETTINGS_PATH, cached, FALLBACK_DEFAULT_MODEL_ID = "claude-opus-4-7[1m]";
|
|
2605
|
-
var init_claude_settings = __esm(() => {
|
|
2606
|
-
SETTINGS_PATH = join4(homedir6(), ".claude", "settings.json");
|
|
2607
|
-
});
|
|
2608
|
-
|
|
2609
|
-
// src/session/usage-metrics.ts
|
|
2610
|
-
function totalContextTokens(u) {
|
|
2611
|
-
return u.input_tokens + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
|
|
2612
|
-
}
|
|
2613
|
-
function parseTimestampMs(value) {
|
|
2614
|
-
const ms = new Date(value).getTime();
|
|
2615
|
-
return Number.isFinite(ms) ? ms : null;
|
|
2616
|
-
}
|
|
2617
|
-
function mergeIntervals(intervals) {
|
|
2618
|
-
if (intervals.length === 0)
|
|
2619
|
-
return [];
|
|
2620
|
-
const sorted = [...intervals].sort((a, b) => a.startMs - b.startMs);
|
|
2621
|
-
const first = sorted[0];
|
|
2622
|
-
if (!first)
|
|
2623
|
-
return [];
|
|
2624
|
-
const merged = [{ startMs: first.startMs, endMs: first.endMs }];
|
|
2625
|
-
for (let i = 1;i < sorted.length; i++) {
|
|
2626
|
-
const current = sorted[i];
|
|
2627
|
-
const last = merged[merged.length - 1];
|
|
2628
|
-
if (!current || !last)
|
|
3875
|
+
function allModels() {
|
|
3876
|
+
const seen = new Set;
|
|
3877
|
+
const out = [];
|
|
3878
|
+
for (const caps of Object.values(ENGINE_REGISTRY)) {
|
|
3879
|
+
if (!caps)
|
|
2629
3880
|
continue;
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
3881
|
+
for (const m of caps.models) {
|
|
3882
|
+
const key = `${m.vendor}:${m.id}:${m.effort ?? ""}`;
|
|
3883
|
+
if (seen.has(key))
|
|
3884
|
+
continue;
|
|
3885
|
+
seen.add(key);
|
|
3886
|
+
out.push(m);
|
|
2634
3887
|
}
|
|
2635
3888
|
}
|
|
2636
|
-
return
|
|
2637
|
-
}
|
|
2638
|
-
function durationMs(intervals) {
|
|
2639
|
-
return intervals.reduce((total, interval) => total + (interval.endMs - interval.startMs), 0);
|
|
3889
|
+
return out;
|
|
2640
3890
|
}
|
|
2641
|
-
function
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
let inputTokens = 0;
|
|
2646
|
-
let outputTokens = 0;
|
|
2647
|
-
const intervals = [];
|
|
2648
|
-
for (const message of past) {
|
|
2649
|
-
const timestampMs = parseTimestampMs(message.timestamp);
|
|
2650
|
-
if (message.role === "user" && timestampMs !== null) {
|
|
2651
|
-
lastUserTimestampMs = timestampMs;
|
|
2652
|
-
continue;
|
|
2653
|
-
}
|
|
2654
|
-
if (message.role !== "assistant" || !message.usage)
|
|
3891
|
+
function modelLabelFor(modelId, effort) {
|
|
3892
|
+
const resolved = modelId ?? defaultCapabilities.defaultModelId();
|
|
3893
|
+
for (const caps of Object.values(ENGINE_REGISTRY)) {
|
|
3894
|
+
if (!caps)
|
|
2655
3895
|
continue;
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
if (totalDurationMs <= 0)
|
|
2672
|
-
return latestUsage;
|
|
2673
|
-
return {
|
|
2674
|
-
...latestUsage,
|
|
2675
|
-
total_speed_tokens_per_second: (inputTokens + outputTokens) / (totalDurationMs / 1000)
|
|
3896
|
+
const match = caps.models.find((m) => m.id === resolved && (effort === undefined || m.effort === effort));
|
|
3897
|
+
if (match)
|
|
3898
|
+
return match.label;
|
|
3899
|
+
}
|
|
3900
|
+
if (effort)
|
|
3901
|
+
return `${resolved} \xB7 ${effort}`;
|
|
3902
|
+
return resolved;
|
|
3903
|
+
}
|
|
3904
|
+
var ENGINE_REGISTRY, ENGINE_IDENTITIES, defaultCapabilities, defaultIdentity;
|
|
3905
|
+
var init_registry = __esm(() => {
|
|
3906
|
+
init_capabilities();
|
|
3907
|
+
init_capabilities2();
|
|
3908
|
+
ENGINE_REGISTRY = {
|
|
3909
|
+
claude: claudeCapabilities,
|
|
3910
|
+
codex: codexCapabilities
|
|
2676
3911
|
};
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
const endMs = parseTimestampMs(endedAtIso);
|
|
2681
|
-
if (startMs === null || endMs === null || endMs <= startMs)
|
|
2682
|
-
return usage;
|
|
2683
|
-
return {
|
|
2684
|
-
...usage,
|
|
2685
|
-
total_speed_tokens_per_second: (usage.input_tokens + usage.output_tokens) / ((endMs - startMs) / 1000)
|
|
3912
|
+
ENGINE_IDENTITIES = {
|
|
3913
|
+
claude: claudeIdentity,
|
|
3914
|
+
codex: codexIdentity
|
|
2686
3915
|
};
|
|
2687
|
-
|
|
3916
|
+
defaultCapabilities = claudeCapabilities;
|
|
3917
|
+
defaultIdentity = claudeIdentity;
|
|
3918
|
+
});
|
|
2688
3919
|
|
|
2689
3920
|
// src/env.ts
|
|
2690
|
-
import { homedir as
|
|
2691
|
-
import { join as
|
|
3921
|
+
import { homedir as homedir10 } from "os";
|
|
3922
|
+
import { join as join6 } from "path";
|
|
2692
3923
|
function isDev() {
|
|
2693
3924
|
return process.env.KOBE_DEV === "1";
|
|
2694
3925
|
}
|
|
2695
3926
|
function homeDir() {
|
|
2696
|
-
return process.env.KOBE_HOME_DIR ??
|
|
3927
|
+
return process.env.KOBE_HOME_DIR ?? homedir10();
|
|
2697
3928
|
}
|
|
2698
3929
|
function kobeStateDir() {
|
|
2699
|
-
return
|
|
3930
|
+
return join6(homeDir(), ".kobe");
|
|
2700
3931
|
}
|
|
2701
3932
|
function kvStatePath() {
|
|
2702
|
-
return
|
|
3933
|
+
return join6(homeDir(), ".config", "kobe", "state.json");
|
|
2703
3934
|
}
|
|
2704
3935
|
var init_env = () => {};
|
|
2705
3936
|
|
|
@@ -2714,11 +3945,11 @@ __export(exports_repos, {
|
|
|
2714
3945
|
getSavedRepos: () => getSavedRepos,
|
|
2715
3946
|
addSavedRepo: () => addSavedRepo
|
|
2716
3947
|
});
|
|
2717
|
-
import { spawnSync as
|
|
2718
|
-
import { mkdirSync, readFileSync as
|
|
3948
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3949
|
+
import { mkdirSync, readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync } from "fs";
|
|
2719
3950
|
import { dirname as dirname3 } from "path";
|
|
2720
3951
|
function resolveRepoRoot(absPath) {
|
|
2721
|
-
const r =
|
|
3952
|
+
const r = spawnSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
2722
3953
|
cwd: absPath,
|
|
2723
3954
|
encoding: "utf8",
|
|
2724
3955
|
shell: false
|
|
@@ -2752,7 +3983,7 @@ function statePath() {
|
|
|
2752
3983
|
}
|
|
2753
3984
|
function load() {
|
|
2754
3985
|
try {
|
|
2755
|
-
const text =
|
|
3986
|
+
const text = readFileSync3(statePath(), "utf8");
|
|
2756
3987
|
const parsed = JSON.parse(text);
|
|
2757
3988
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2758
3989
|
return parsed;
|
|
@@ -2761,11 +3992,11 @@ function load() {
|
|
|
2761
3992
|
return {};
|
|
2762
3993
|
}
|
|
2763
3994
|
function save(state) {
|
|
2764
|
-
const
|
|
2765
|
-
mkdirSync(dirname3(
|
|
2766
|
-
const tmp = `${
|
|
3995
|
+
const path6 = statePath();
|
|
3996
|
+
mkdirSync(dirname3(path6), { recursive: true });
|
|
3997
|
+
const tmp = `${path6}.tmp`;
|
|
2767
3998
|
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2768
|
-
renameSync(tmp,
|
|
3999
|
+
renameSync(tmp, path6);
|
|
2769
4000
|
}
|
|
2770
4001
|
function getSavedRepos() {
|
|
2771
4002
|
const state = load();
|
|
@@ -2829,7 +4060,15 @@ function nextChatTabSeq(tabs) {
|
|
|
2829
4060
|
max = t.seq;
|
|
2830
4061
|
return max + 1;
|
|
2831
4062
|
}
|
|
2832
|
-
|
|
4063
|
+
function worktreeSlug(task) {
|
|
4064
|
+
if (task.kind === "main")
|
|
4065
|
+
return "";
|
|
4066
|
+
if (!task.worktreePath)
|
|
4067
|
+
return "";
|
|
4068
|
+
const match = task.worktreePath.match(/([^/\\]+)[/\\]*$/);
|
|
4069
|
+
return match ? match[1] ?? "" : "";
|
|
4070
|
+
}
|
|
4071
|
+
var toTaskId = (id) => id, DEFAULT_TASK_VENDOR = "claude";
|
|
2833
4072
|
|
|
2834
4073
|
// src/orchestrator/index/ulid.ts
|
|
2835
4074
|
function encodeTime(now, len) {
|
|
@@ -2891,7 +4130,7 @@ var init_ulid = __esm(() => {
|
|
|
2891
4130
|
});
|
|
2892
4131
|
|
|
2893
4132
|
// src/orchestrator/metadata-suggester.ts
|
|
2894
|
-
import { spawn as
|
|
4133
|
+
import { spawn as spawn4 } from "child_process";
|
|
2895
4134
|
|
|
2896
4135
|
class MetadataSuggester {
|
|
2897
4136
|
binaryPromise = null;
|
|
@@ -2920,7 +4159,7 @@ class MetadataSuggester {
|
|
|
2920
4159
|
return new Promise((resolve2) => {
|
|
2921
4160
|
let proc;
|
|
2922
4161
|
try {
|
|
2923
|
-
proc =
|
|
4162
|
+
proc = spawn4(binary, ["-p", builder(trimmed)], {
|
|
2924
4163
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2925
4164
|
env: process.env
|
|
2926
4165
|
});
|
|
@@ -3069,10 +4308,10 @@ class InMemoryPendingInputBroker {
|
|
|
3069
4308
|
}
|
|
3070
4309
|
|
|
3071
4310
|
// src/orchestrator/pr/build.ts
|
|
3072
|
-
import { spawnSync as
|
|
4311
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
3073
4312
|
function git(cwd, args) {
|
|
3074
4313
|
try {
|
|
3075
|
-
const out =
|
|
4314
|
+
const out = spawnSync4("git", args.slice(), {
|
|
3076
4315
|
cwd,
|
|
3077
4316
|
encoding: "utf8",
|
|
3078
4317
|
timeout: GIT_TIMEOUT_MS
|
|
@@ -3122,7 +4361,7 @@ var init_build = () => {};
|
|
|
3122
4361
|
|
|
3123
4362
|
// src/orchestrator/pr/instructions.ts
|
|
3124
4363
|
import { promises as fs } from "fs";
|
|
3125
|
-
import
|
|
4364
|
+
import path6 from "path";
|
|
3126
4365
|
function dirtyCountSentence(n) {
|
|
3127
4366
|
if (n <= 0)
|
|
3128
4367
|
return "There are no uncommitted changes.";
|
|
@@ -3150,7 +4389,7 @@ function renderPRInstructions(template, state) {
|
|
|
3150
4389
|
async function loadPRInstructionsTemplate(worktreePath) {
|
|
3151
4390
|
if (!worktreePath)
|
|
3152
4391
|
return DEFAULT_PR_INSTRUCTIONS_TEMPLATE;
|
|
3153
|
-
const file =
|
|
4392
|
+
const file = path6.join(worktreePath, ".kobe", "pr-instructions.md");
|
|
3154
4393
|
try {
|
|
3155
4394
|
const text = await fs.readFile(file, "utf8");
|
|
3156
4395
|
if (text.length === 0)
|
|
@@ -3201,10 +4440,11 @@ class SessionPump {
|
|
|
3201
4440
|
}
|
|
3202
4441
|
async run(taskId, tabId, handle) {
|
|
3203
4442
|
const tabKey = chatRunStateKey(taskId, tabId);
|
|
4443
|
+
const engine = this.env.engineFor(taskId, tabId);
|
|
3204
4444
|
let killedForInput = false;
|
|
3205
4445
|
let terminalEvent = null;
|
|
3206
4446
|
try {
|
|
3207
|
-
for await (const ev of
|
|
4447
|
+
for await (const ev of engine.stream(handle)) {
|
|
3208
4448
|
const inputReq = detectUserInputFromEngineEvent(ev);
|
|
3209
4449
|
if (inputReq) {
|
|
3210
4450
|
this.env.dispatch(taskId, tabId, ev);
|
|
@@ -3218,7 +4458,7 @@ class SessionPump {
|
|
|
3218
4458
|
});
|
|
3219
4459
|
killedForInput = true;
|
|
3220
4460
|
try {
|
|
3221
|
-
await
|
|
4461
|
+
await engine.stop(handle);
|
|
3222
4462
|
} catch {}
|
|
3223
4463
|
break;
|
|
3224
4464
|
}
|
|
@@ -3231,7 +4471,7 @@ class SessionPump {
|
|
|
3231
4471
|
} finally {
|
|
3232
4472
|
if (!killedForInput) {
|
|
3233
4473
|
try {
|
|
3234
|
-
await
|
|
4474
|
+
await engine.stop(handle);
|
|
3235
4475
|
} catch {}
|
|
3236
4476
|
}
|
|
3237
4477
|
}
|
|
@@ -3242,6 +4482,548 @@ var init_session_pump = __esm(() => {
|
|
|
3242
4482
|
init_core();
|
|
3243
4483
|
});
|
|
3244
4484
|
|
|
4485
|
+
// src/orchestrator/worktree/animal-names.ts
|
|
4486
|
+
var ANIMAL_NAMES;
|
|
4487
|
+
var init_animal_names = __esm(() => {
|
|
4488
|
+
ANIMAL_NAMES = [
|
|
4489
|
+
"aardvark",
|
|
4490
|
+
"addax",
|
|
4491
|
+
"adder",
|
|
4492
|
+
"agouti",
|
|
4493
|
+
"albatross",
|
|
4494
|
+
"alpaca",
|
|
4495
|
+
"anaconda",
|
|
4496
|
+
"anchovy",
|
|
4497
|
+
"anglerfish",
|
|
4498
|
+
"anole",
|
|
4499
|
+
"anteater",
|
|
4500
|
+
"antelope",
|
|
4501
|
+
"ape",
|
|
4502
|
+
"argali",
|
|
4503
|
+
"armadillo",
|
|
4504
|
+
"avocet",
|
|
4505
|
+
"axolotl",
|
|
4506
|
+
"baboon",
|
|
4507
|
+
"badger",
|
|
4508
|
+
"banteng",
|
|
4509
|
+
"barracuda",
|
|
4510
|
+
"basilisk",
|
|
4511
|
+
"bass",
|
|
4512
|
+
"bat",
|
|
4513
|
+
"beagle",
|
|
4514
|
+
"bear",
|
|
4515
|
+
"beaver",
|
|
4516
|
+
"bee",
|
|
4517
|
+
"beetle",
|
|
4518
|
+
"beluga",
|
|
4519
|
+
"betta",
|
|
4520
|
+
"bison",
|
|
4521
|
+
"blackbird",
|
|
4522
|
+
"blenny",
|
|
4523
|
+
"bluebird",
|
|
4524
|
+
"bluegill",
|
|
4525
|
+
"boar",
|
|
4526
|
+
"bobcat",
|
|
4527
|
+
"bonito",
|
|
4528
|
+
"bovid",
|
|
4529
|
+
"buffalo",
|
|
4530
|
+
"bullfrog",
|
|
4531
|
+
"bunting",
|
|
4532
|
+
"bushbuck",
|
|
4533
|
+
"butterfly",
|
|
4534
|
+
"buzzard",
|
|
4535
|
+
"caiman",
|
|
4536
|
+
"camel",
|
|
4537
|
+
"canary",
|
|
4538
|
+
"capybara",
|
|
4539
|
+
"caracal",
|
|
4540
|
+
"cardinal",
|
|
4541
|
+
"caribou",
|
|
4542
|
+
"carp",
|
|
4543
|
+
"cassowary",
|
|
4544
|
+
"caterpillar",
|
|
4545
|
+
"catfish",
|
|
4546
|
+
"chameleon",
|
|
4547
|
+
"chamois",
|
|
4548
|
+
"char",
|
|
4549
|
+
"cheetah",
|
|
4550
|
+
"chickadee",
|
|
4551
|
+
"chimp",
|
|
4552
|
+
"chinchilla",
|
|
4553
|
+
"chipmunk",
|
|
4554
|
+
"chough",
|
|
4555
|
+
"cicada",
|
|
4556
|
+
"civet",
|
|
4557
|
+
"clam",
|
|
4558
|
+
"clownfish",
|
|
4559
|
+
"cobra",
|
|
4560
|
+
"cockatoo",
|
|
4561
|
+
"condor",
|
|
4562
|
+
"coot",
|
|
4563
|
+
"copperhead",
|
|
4564
|
+
"coral",
|
|
4565
|
+
"cormorant",
|
|
4566
|
+
"cougar",
|
|
4567
|
+
"coyote",
|
|
4568
|
+
"crab",
|
|
4569
|
+
"crane",
|
|
4570
|
+
"crawfish",
|
|
4571
|
+
"crayfish",
|
|
4572
|
+
"cricket",
|
|
4573
|
+
"crocodile",
|
|
4574
|
+
"crow",
|
|
4575
|
+
"cuckoo",
|
|
4576
|
+
"curlew",
|
|
4577
|
+
"cuttlefish",
|
|
4578
|
+
"deer",
|
|
4579
|
+
"dikdik",
|
|
4580
|
+
"dingo",
|
|
4581
|
+
"dolphin",
|
|
4582
|
+
"donkey",
|
|
4583
|
+
"dormouse",
|
|
4584
|
+
"dotterel",
|
|
4585
|
+
"dove",
|
|
4586
|
+
"dragonfly",
|
|
4587
|
+
"dromedary",
|
|
4588
|
+
"duck",
|
|
4589
|
+
"dugong",
|
|
4590
|
+
"dunlin",
|
|
4591
|
+
"eagle",
|
|
4592
|
+
"echidna",
|
|
4593
|
+
"eel",
|
|
4594
|
+
"egret",
|
|
4595
|
+
"eider",
|
|
4596
|
+
"eland",
|
|
4597
|
+
"elephant",
|
|
4598
|
+
"elk",
|
|
4599
|
+
"emu",
|
|
4600
|
+
"ermine",
|
|
4601
|
+
"falcon",
|
|
4602
|
+
"fawn",
|
|
4603
|
+
"ferret",
|
|
4604
|
+
"finch",
|
|
4605
|
+
"firefly",
|
|
4606
|
+
"flamingo",
|
|
4607
|
+
"flounder",
|
|
4608
|
+
"fox",
|
|
4609
|
+
"frog",
|
|
4610
|
+
"gadwall",
|
|
4611
|
+
"gannet",
|
|
4612
|
+
"gaur",
|
|
4613
|
+
"gazelle",
|
|
4614
|
+
"gecko",
|
|
4615
|
+
"gemsbok",
|
|
4616
|
+
"gerbil",
|
|
4617
|
+
"gharial",
|
|
4618
|
+
"gibbon",
|
|
4619
|
+
"gnu",
|
|
4620
|
+
"goat",
|
|
4621
|
+
"goby",
|
|
4622
|
+
"godwit",
|
|
4623
|
+
"goldfish",
|
|
4624
|
+
"goose",
|
|
4625
|
+
"gopher",
|
|
4626
|
+
"goral",
|
|
4627
|
+
"gorilla",
|
|
4628
|
+
"goshawk",
|
|
4629
|
+
"grackle",
|
|
4630
|
+
"grasshopper",
|
|
4631
|
+
"grayling",
|
|
4632
|
+
"grebe",
|
|
4633
|
+
"grouse",
|
|
4634
|
+
"guanaco",
|
|
4635
|
+
"guillemot",
|
|
4636
|
+
"guineafowl",
|
|
4637
|
+
"guppy",
|
|
4638
|
+
"halibut",
|
|
4639
|
+
"hammerhead",
|
|
4640
|
+
"hamster",
|
|
4641
|
+
"hare",
|
|
4642
|
+
"harrier",
|
|
4643
|
+
"hartebeest",
|
|
4644
|
+
"hawk",
|
|
4645
|
+
"hedgehog",
|
|
4646
|
+
"heron",
|
|
4647
|
+
"herring",
|
|
4648
|
+
"hippo",
|
|
4649
|
+
"hornbill",
|
|
4650
|
+
"hornet",
|
|
4651
|
+
"horse",
|
|
4652
|
+
"hummingbird",
|
|
4653
|
+
"hyena",
|
|
4654
|
+
"ibex",
|
|
4655
|
+
"ibis",
|
|
4656
|
+
"iguana",
|
|
4657
|
+
"impala",
|
|
4658
|
+
"indri",
|
|
4659
|
+
"jacana",
|
|
4660
|
+
"jackal",
|
|
4661
|
+
"jaguar",
|
|
4662
|
+
"jay",
|
|
4663
|
+
"jellyfish",
|
|
4664
|
+
"jerboa",
|
|
4665
|
+
"junco",
|
|
4666
|
+
"kakapo",
|
|
4667
|
+
"kangaroo",
|
|
4668
|
+
"kestrel",
|
|
4669
|
+
"killdeer",
|
|
4670
|
+
"kingfisher",
|
|
4671
|
+
"kinglet",
|
|
4672
|
+
"kite",
|
|
4673
|
+
"kiwi",
|
|
4674
|
+
"koala",
|
|
4675
|
+
"kookaburra",
|
|
4676
|
+
"krait",
|
|
4677
|
+
"krill",
|
|
4678
|
+
"kudu",
|
|
4679
|
+
"ladybug",
|
|
4680
|
+
"lamprey",
|
|
4681
|
+
"langur",
|
|
4682
|
+
"lapwing",
|
|
4683
|
+
"lark",
|
|
4684
|
+
"lemming",
|
|
4685
|
+
"lemur",
|
|
4686
|
+
"leopard",
|
|
4687
|
+
"limpet",
|
|
4688
|
+
"lion",
|
|
4689
|
+
"lionfish",
|
|
4690
|
+
"lizard",
|
|
4691
|
+
"llama",
|
|
4692
|
+
"lobster",
|
|
4693
|
+
"locust",
|
|
4694
|
+
"loon",
|
|
4695
|
+
"lorikeet",
|
|
4696
|
+
"loris",
|
|
4697
|
+
"lynx",
|
|
4698
|
+
"macaque",
|
|
4699
|
+
"macaw",
|
|
4700
|
+
"mackerel",
|
|
4701
|
+
"magpie",
|
|
4702
|
+
"mallard",
|
|
4703
|
+
"mamba",
|
|
4704
|
+
"mammoth",
|
|
4705
|
+
"manatee",
|
|
4706
|
+
"mandrill",
|
|
4707
|
+
"manta",
|
|
4708
|
+
"mantis",
|
|
4709
|
+
"marlin",
|
|
4710
|
+
"marmoset",
|
|
4711
|
+
"marmot",
|
|
4712
|
+
"marten",
|
|
4713
|
+
"meerkat",
|
|
4714
|
+
"merganser",
|
|
4715
|
+
"mink",
|
|
4716
|
+
"minnow",
|
|
4717
|
+
"mole",
|
|
4718
|
+
"mongoose",
|
|
4719
|
+
"monitor",
|
|
4720
|
+
"monkey",
|
|
4721
|
+
"moose",
|
|
4722
|
+
"moth",
|
|
4723
|
+
"mouflon",
|
|
4724
|
+
"mouse",
|
|
4725
|
+
"mudpuppy",
|
|
4726
|
+
"mule",
|
|
4727
|
+
"muntjac",
|
|
4728
|
+
"musk",
|
|
4729
|
+
"muskox",
|
|
4730
|
+
"muskrat",
|
|
4731
|
+
"mussel",
|
|
4732
|
+
"mustang",
|
|
4733
|
+
"narwhal",
|
|
4734
|
+
"newt",
|
|
4735
|
+
"nightingale",
|
|
4736
|
+
"numbat",
|
|
4737
|
+
"nuthatch",
|
|
4738
|
+
"nyala",
|
|
4739
|
+
"ocelot",
|
|
4740
|
+
"octopus",
|
|
4741
|
+
"okapi",
|
|
4742
|
+
"opossum",
|
|
4743
|
+
"orangutan",
|
|
4744
|
+
"orca",
|
|
4745
|
+
"oriole",
|
|
4746
|
+
"oryx",
|
|
4747
|
+
"osprey",
|
|
4748
|
+
"ostrich",
|
|
4749
|
+
"otter",
|
|
4750
|
+
"owl",
|
|
4751
|
+
"oyster",
|
|
4752
|
+
"oystercatcher",
|
|
4753
|
+
"paca",
|
|
4754
|
+
"panda",
|
|
4755
|
+
"pangolin",
|
|
4756
|
+
"panther",
|
|
4757
|
+
"parakeet",
|
|
4758
|
+
"parrot",
|
|
4759
|
+
"parrotfish",
|
|
4760
|
+
"partridge",
|
|
4761
|
+
"peafowl",
|
|
4762
|
+
"pelican",
|
|
4763
|
+
"penguin",
|
|
4764
|
+
"perch",
|
|
4765
|
+
"petrel",
|
|
4766
|
+
"pheasant",
|
|
4767
|
+
"pigeon",
|
|
4768
|
+
"pika",
|
|
4769
|
+
"pike",
|
|
4770
|
+
"pintail",
|
|
4771
|
+
"pipefish",
|
|
4772
|
+
"platypus",
|
|
4773
|
+
"plover",
|
|
4774
|
+
"pollock",
|
|
4775
|
+
"pony",
|
|
4776
|
+
"porcupine",
|
|
4777
|
+
"porpoise",
|
|
4778
|
+
"possum",
|
|
4779
|
+
"ptarmigan",
|
|
4780
|
+
"puffin",
|
|
4781
|
+
"puma",
|
|
4782
|
+
"pupfish",
|
|
4783
|
+
"python",
|
|
4784
|
+
"quail",
|
|
4785
|
+
"quetzal",
|
|
4786
|
+
"quokka",
|
|
4787
|
+
"quoll",
|
|
4788
|
+
"rabbit",
|
|
4789
|
+
"raccoon",
|
|
4790
|
+
"rail",
|
|
4791
|
+
"rat",
|
|
4792
|
+
"rattler",
|
|
4793
|
+
"raven",
|
|
4794
|
+
"ray",
|
|
4795
|
+
"redpoll",
|
|
4796
|
+
"reindeer",
|
|
4797
|
+
"remora",
|
|
4798
|
+
"rhino",
|
|
4799
|
+
"roadrunner",
|
|
4800
|
+
"robin",
|
|
4801
|
+
"rook",
|
|
4802
|
+
"sable",
|
|
4803
|
+
"sailfish",
|
|
4804
|
+
"salamander",
|
|
4805
|
+
"salmon",
|
|
4806
|
+
"sandpiper",
|
|
4807
|
+
"sardine",
|
|
4808
|
+
"scallop",
|
|
4809
|
+
"scaup",
|
|
4810
|
+
"scorpion",
|
|
4811
|
+
"seahorse",
|
|
4812
|
+
"seal",
|
|
4813
|
+
"serval",
|
|
4814
|
+
"shark",
|
|
4815
|
+
"shearwater",
|
|
4816
|
+
"shrew",
|
|
4817
|
+
"shrike",
|
|
4818
|
+
"shrimp",
|
|
4819
|
+
"siskin",
|
|
4820
|
+
"sitatunga",
|
|
4821
|
+
"skate",
|
|
4822
|
+
"skink",
|
|
4823
|
+
"skua",
|
|
4824
|
+
"skunk",
|
|
4825
|
+
"sloth",
|
|
4826
|
+
"smelt",
|
|
4827
|
+
"snail",
|
|
4828
|
+
"snipe",
|
|
4829
|
+
"solenodon",
|
|
4830
|
+
"sparrow",
|
|
4831
|
+
"spider",
|
|
4832
|
+
"springbok",
|
|
4833
|
+
"squid",
|
|
4834
|
+
"squirrel",
|
|
4835
|
+
"starfish",
|
|
4836
|
+
"starling",
|
|
4837
|
+
"stilt",
|
|
4838
|
+
"stingray",
|
|
4839
|
+
"stoat",
|
|
4840
|
+
"stork",
|
|
4841
|
+
"sunbird",
|
|
4842
|
+
"sunfish",
|
|
4843
|
+
"swallow",
|
|
4844
|
+
"swan",
|
|
4845
|
+
"swift",
|
|
4846
|
+
"swordfish",
|
|
4847
|
+
"tahr",
|
|
4848
|
+
"takin",
|
|
4849
|
+
"tamarin",
|
|
4850
|
+
"tanager",
|
|
4851
|
+
"tapir",
|
|
4852
|
+
"tarantula",
|
|
4853
|
+
"tarpon",
|
|
4854
|
+
"tarsier",
|
|
4855
|
+
"teal",
|
|
4856
|
+
"tenrec",
|
|
4857
|
+
"terrapin",
|
|
4858
|
+
"tern",
|
|
4859
|
+
"tetra",
|
|
4860
|
+
"thrasher",
|
|
4861
|
+
"thrush",
|
|
4862
|
+
"tiger",
|
|
4863
|
+
"toad",
|
|
4864
|
+
"tortoise",
|
|
4865
|
+
"toucan",
|
|
4866
|
+
"treecreeper",
|
|
4867
|
+
"treefrog",
|
|
4868
|
+
"trout",
|
|
4869
|
+
"tuatara",
|
|
4870
|
+
"tuna",
|
|
4871
|
+
"turkey",
|
|
4872
|
+
"turtle",
|
|
4873
|
+
"urchin",
|
|
4874
|
+
"urial",
|
|
4875
|
+
"vicuna",
|
|
4876
|
+
"viper",
|
|
4877
|
+
"vole",
|
|
4878
|
+
"vulture",
|
|
4879
|
+
"wagtail",
|
|
4880
|
+
"wallaby",
|
|
4881
|
+
"walleye",
|
|
4882
|
+
"walrus",
|
|
4883
|
+
"warbler",
|
|
4884
|
+
"warthog",
|
|
4885
|
+
"waterbuck",
|
|
4886
|
+
"weasel",
|
|
4887
|
+
"wildebeest",
|
|
4888
|
+
"wolf",
|
|
4889
|
+
"wolverine",
|
|
4890
|
+
"wombat",
|
|
4891
|
+
"woodcock",
|
|
4892
|
+
"woodpecker",
|
|
4893
|
+
"wrasse",
|
|
4894
|
+
"wren",
|
|
4895
|
+
"yak",
|
|
4896
|
+
"zebra",
|
|
4897
|
+
"zebu",
|
|
4898
|
+
"zorilla"
|
|
4899
|
+
];
|
|
4900
|
+
});
|
|
4901
|
+
|
|
4902
|
+
// src/orchestrator/worktree/paths.ts
|
|
4903
|
+
import fs2 from "fs";
|
|
4904
|
+
import path7 from "path";
|
|
4905
|
+
function worktreeRootFor(repo) {
|
|
4906
|
+
if (!path7.isAbsolute(repo)) {
|
|
4907
|
+
throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
|
|
4908
|
+
}
|
|
4909
|
+
return path7.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
|
|
4910
|
+
}
|
|
4911
|
+
function worktreePathFor(repo, slug) {
|
|
4912
|
+
if (!slug || /[/\\\0]/.test(slug)) {
|
|
4913
|
+
throw new Error(`worktreePathFor: invalid slug: ${JSON.stringify(slug)}`);
|
|
4914
|
+
}
|
|
4915
|
+
return path7.join(worktreeRootFor(repo), slug);
|
|
4916
|
+
}
|
|
4917
|
+
function listWorktreeDirNames(repo) {
|
|
4918
|
+
const root = worktreeRootFor(repo);
|
|
4919
|
+
try {
|
|
4920
|
+
return fs2.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
4921
|
+
} catch {
|
|
4922
|
+
return [];
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
function isKobeManagedPath(repo, candidate) {
|
|
4926
|
+
if (!path7.isAbsolute(repo) || !path7.isAbsolute(candidate))
|
|
4927
|
+
return false;
|
|
4928
|
+
const root = canonicalize(worktreeRootFor(repo));
|
|
4929
|
+
const target = canonicalize(candidate);
|
|
4930
|
+
const rel = path7.relative(root, target);
|
|
4931
|
+
return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
|
|
4932
|
+
}
|
|
4933
|
+
function canonicalize(p) {
|
|
4934
|
+
try {
|
|
4935
|
+
return fs2.realpathSync(p);
|
|
4936
|
+
} catch {
|
|
4937
|
+
return path7.resolve(p);
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
|
|
4941
|
+
var init_paths2 = () => {};
|
|
4942
|
+
|
|
4943
|
+
// src/orchestrator/worktree/slug-allocator.ts
|
|
4944
|
+
class SlugAllocator {
|
|
4945
|
+
activeSlugs;
|
|
4946
|
+
random;
|
|
4947
|
+
pool;
|
|
4948
|
+
pendingByRepo = new Map;
|
|
4949
|
+
chain = Promise.resolve();
|
|
4950
|
+
constructor(activeSlugs, options = {}) {
|
|
4951
|
+
this.activeSlugs = activeSlugs;
|
|
4952
|
+
this.random = options.random ?? Math.random;
|
|
4953
|
+
this.pool = options.pool ?? ANIMAL_NAMES;
|
|
4954
|
+
if (this.pool.length === 0) {
|
|
4955
|
+
throw new Error("SlugAllocator: animal pool cannot be empty");
|
|
4956
|
+
}
|
|
4957
|
+
}
|
|
4958
|
+
async allocate(repo) {
|
|
4959
|
+
const previous = this.chain;
|
|
4960
|
+
let release;
|
|
4961
|
+
this.chain = new Promise((resolve2) => {
|
|
4962
|
+
release = resolve2;
|
|
4963
|
+
});
|
|
4964
|
+
await previous;
|
|
4965
|
+
try {
|
|
4966
|
+
return this.pickLocked(repo);
|
|
4967
|
+
} finally {
|
|
4968
|
+
release();
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
commit(repo, slug) {
|
|
4972
|
+
this.deletePending(repo, slug);
|
|
4973
|
+
}
|
|
4974
|
+
cancel(repo, slug) {
|
|
4975
|
+
this.deletePending(repo, slug);
|
|
4976
|
+
}
|
|
4977
|
+
pickLocked(repo) {
|
|
4978
|
+
const occupied = this.occupiedSlugs(repo);
|
|
4979
|
+
const candidates = this.pool.filter((n) => !occupied.has(n));
|
|
4980
|
+
if (candidates.length > 0) {
|
|
4981
|
+
const pick = candidates[Math.floor(this.random() * candidates.length)];
|
|
4982
|
+
this.addPending(repo, pick);
|
|
4983
|
+
return pick;
|
|
4984
|
+
}
|
|
4985
|
+
const base = this.pool[Math.floor(this.random() * this.pool.length)];
|
|
4986
|
+
for (let v = 2;; v++) {
|
|
4987
|
+
const candidate = `${base}-v${v}`;
|
|
4988
|
+
if (!occupied.has(candidate)) {
|
|
4989
|
+
this.addPending(repo, candidate);
|
|
4990
|
+
return candidate;
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
occupiedSlugs(repo) {
|
|
4995
|
+
const set = new Set(this.pendingByRepo.get(repo) ?? []);
|
|
4996
|
+
for (const slug of this.activeSlugs(repo)) {
|
|
4997
|
+
if (slug)
|
|
4998
|
+
set.add(slug);
|
|
4999
|
+
}
|
|
5000
|
+
for (const dir of listWorktreeDirNames(repo)) {
|
|
5001
|
+
set.add(dir);
|
|
5002
|
+
}
|
|
5003
|
+
return set;
|
|
5004
|
+
}
|
|
5005
|
+
addPending(repo, slug) {
|
|
5006
|
+
let pending = this.pendingByRepo.get(repo);
|
|
5007
|
+
if (!pending) {
|
|
5008
|
+
pending = new Set;
|
|
5009
|
+
this.pendingByRepo.set(repo, pending);
|
|
5010
|
+
}
|
|
5011
|
+
pending.add(slug);
|
|
5012
|
+
}
|
|
5013
|
+
deletePending(repo, slug) {
|
|
5014
|
+
const pending = this.pendingByRepo.get(repo);
|
|
5015
|
+
if (!pending)
|
|
5016
|
+
return;
|
|
5017
|
+
pending.delete(slug);
|
|
5018
|
+
if (pending.size === 0)
|
|
5019
|
+
this.pendingByRepo.delete(repo);
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
var init_slug_allocator = __esm(() => {
|
|
5023
|
+
init_animal_names();
|
|
5024
|
+
init_paths2();
|
|
5025
|
+
});
|
|
5026
|
+
|
|
3245
5027
|
// src/orchestrator/core.ts
|
|
3246
5028
|
var exports_core = {};
|
|
3247
5029
|
__export(exports_core, {
|
|
@@ -3291,7 +5073,8 @@ function summarizeWorktreeError(raw, repo, baseRef) {
|
|
|
3291
5073
|
}
|
|
3292
5074
|
|
|
3293
5075
|
class Orchestrator {
|
|
3294
|
-
|
|
5076
|
+
engines;
|
|
5077
|
+
fallbackEngine;
|
|
3295
5078
|
store;
|
|
3296
5079
|
worktrees;
|
|
3297
5080
|
metadataSuggester;
|
|
@@ -3303,16 +5086,38 @@ class Orchestrator {
|
|
|
3303
5086
|
requestIdCounter = 0;
|
|
3304
5087
|
sessionPump;
|
|
3305
5088
|
pendingWorktreeOpts = new Map;
|
|
5089
|
+
slugAllocator;
|
|
5090
|
+
ensureWorktreeLatches = new Map;
|
|
3306
5091
|
tasksAcc;
|
|
3307
5092
|
setTasks;
|
|
3308
5093
|
unsubscribeStore;
|
|
3309
5094
|
runStateAcc;
|
|
3310
5095
|
setRunState;
|
|
3311
5096
|
constructor(deps) {
|
|
3312
|
-
|
|
5097
|
+
const built = {};
|
|
5098
|
+
let fallback;
|
|
5099
|
+
if (deps.engines) {
|
|
5100
|
+
for (const [vendor, eng] of Object.entries(deps.engines)) {
|
|
5101
|
+
if (!eng)
|
|
5102
|
+
continue;
|
|
5103
|
+
built[vendor] = eng;
|
|
5104
|
+
fallback ??= eng;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
if (deps.engine) {
|
|
5108
|
+
const v = deps.engine.capabilities.vendorId;
|
|
5109
|
+
built[v] ??= deps.engine;
|
|
5110
|
+
fallback ??= deps.engine;
|
|
5111
|
+
}
|
|
5112
|
+
if (!fallback) {
|
|
5113
|
+
throw new Error("Orchestrator: no usable engine found; both deps.engine and deps.engines were examined but contained no valid engines.");
|
|
5114
|
+
}
|
|
5115
|
+
this.engines = built;
|
|
5116
|
+
this.fallbackEngine = fallback;
|
|
3313
5117
|
this.store = deps.store;
|
|
3314
5118
|
this.worktrees = deps.worktrees;
|
|
3315
5119
|
this.metadataSuggester = deps.metadataSuggester ?? new MetadataSuggester;
|
|
5120
|
+
this.slugAllocator = new SlugAllocator((repo) => this.store.list().filter((t) => t.repo === repo && !t.archived).map((t) => worktreeSlug(t)).filter((s) => s.length > 0));
|
|
3316
5121
|
const [tasks, setTasks] = createSignal(this.store.list());
|
|
3317
5122
|
this.tasksAcc = tasks;
|
|
3318
5123
|
this.setTasks = (next) => setTasks(() => next);
|
|
@@ -3323,13 +5128,89 @@ class Orchestrator {
|
|
|
3323
5128
|
this.runStateAcc = runState;
|
|
3324
5129
|
this.setRunState = (next) => setRunState(() => next);
|
|
3325
5130
|
this.sessionPump = new SessionPump({
|
|
3326
|
-
|
|
5131
|
+
engineFor: (taskId, tabId) => this.engineForTaskTabId(taskId, tabId),
|
|
3327
5132
|
broker: this.pendingInputBroker,
|
|
3328
5133
|
dispatch: (taskId, tabId, ev) => this.dispatchEvent(taskId, tabId, ev),
|
|
3329
5134
|
nextRequestId: () => `req-${++this.requestIdCounter}`,
|
|
3330
5135
|
onPendingInputChange: () => this.bumpRunState()
|
|
3331
5136
|
});
|
|
3332
5137
|
}
|
|
5138
|
+
engineForVendor(vendor) {
|
|
5139
|
+
const v = vendor ?? DEFAULT_TASK_VENDOR;
|
|
5140
|
+
return this.engines[v] ?? this.fallbackEngine;
|
|
5141
|
+
}
|
|
5142
|
+
engineForTask(task) {
|
|
5143
|
+
return this.engineForVendor(task.vendor);
|
|
5144
|
+
}
|
|
5145
|
+
vendorForTab(task, tab) {
|
|
5146
|
+
return tab.vendor ?? task.vendor ?? "claude";
|
|
5147
|
+
}
|
|
5148
|
+
modelForTab(task, tab, engine) {
|
|
5149
|
+
return tab.model ?? task.model ?? engine.capabilities.defaultModelId();
|
|
5150
|
+
}
|
|
5151
|
+
modelEffortForTab(task, tab) {
|
|
5152
|
+
return tab.modelEffort ?? task.modelEffort;
|
|
5153
|
+
}
|
|
5154
|
+
engineForTab(task, tab) {
|
|
5155
|
+
return this.engineForVendor(this.vendorForTab(task, tab));
|
|
5156
|
+
}
|
|
5157
|
+
async engineForTabRun(task, tab) {
|
|
5158
|
+
if (!tab.sessionId || tab.vendor)
|
|
5159
|
+
return this.engineForTab(task, tab);
|
|
5160
|
+
const resolved = await this.findEngineWithHistory(tab.sessionId, this.vendorForTab(task, tab));
|
|
5161
|
+
if (resolved.vendor && resolved.vendor !== tab.vendor) {
|
|
5162
|
+
await this.updateTab(task.id, tab.id, { vendor: resolved.vendor });
|
|
5163
|
+
}
|
|
5164
|
+
return resolved.engine;
|
|
5165
|
+
}
|
|
5166
|
+
async findEngineWithHistory(sessionId, preferredVendor) {
|
|
5167
|
+
const candidates = [];
|
|
5168
|
+
if (preferredVendor)
|
|
5169
|
+
candidates.push([preferredVendor, this.engineForVendor(preferredVendor)]);
|
|
5170
|
+
for (const [vendor, engine] of Object.entries(this.engines)) {
|
|
5171
|
+
if (!engine || vendor === preferredVendor)
|
|
5172
|
+
continue;
|
|
5173
|
+
candidates.push([vendor, engine]);
|
|
5174
|
+
}
|
|
5175
|
+
if (candidates.length === 0)
|
|
5176
|
+
candidates.push([undefined, this.fallbackEngine]);
|
|
5177
|
+
let fallback = candidates[0] ?? [undefined, this.fallbackEngine];
|
|
5178
|
+
let fallbackHistory;
|
|
5179
|
+
for (const [vendor, engine] of candidates) {
|
|
5180
|
+
try {
|
|
5181
|
+
const history = await engine.readHistory(sessionId);
|
|
5182
|
+
if (!fallbackHistory) {
|
|
5183
|
+
fallback = [vendor, engine];
|
|
5184
|
+
fallbackHistory = history;
|
|
5185
|
+
}
|
|
5186
|
+
if (history.messages.length > 0 || history.usageMetrics)
|
|
5187
|
+
return { engine, vendor, history };
|
|
5188
|
+
} catch {}
|
|
5189
|
+
}
|
|
5190
|
+
return { engine: fallback[1], vendor: fallback[0], history: fallbackHistory };
|
|
5191
|
+
}
|
|
5192
|
+
engineForTaskId(taskId) {
|
|
5193
|
+
const task = this.store.get(taskId);
|
|
5194
|
+
return task ? this.engineForTask(task) : this.fallbackEngine;
|
|
5195
|
+
}
|
|
5196
|
+
engineForTaskTabId(taskId, tabId) {
|
|
5197
|
+
const task = this.store.get(taskId);
|
|
5198
|
+
if (!task)
|
|
5199
|
+
return this.fallbackEngine;
|
|
5200
|
+
const tab = task.tabs.find((t) => t.id === tabId);
|
|
5201
|
+
return tab ? this.engineForTab(task, tab) : this.engineForTask(task);
|
|
5202
|
+
}
|
|
5203
|
+
engineForSessionId(sessionId) {
|
|
5204
|
+
for (const task of this.store.list()) {
|
|
5205
|
+
for (const tab of task.tabs) {
|
|
5206
|
+
if (tab.sessionId === sessionId)
|
|
5207
|
+
return this.engineForTab(task, tab);
|
|
5208
|
+
}
|
|
5209
|
+
if (task.sessionId === sessionId)
|
|
5210
|
+
return this.engineForTask(task);
|
|
5211
|
+
}
|
|
5212
|
+
return this.fallbackEngine;
|
|
5213
|
+
}
|
|
3333
5214
|
bumpRunState() {
|
|
3334
5215
|
const next = new Map;
|
|
3335
5216
|
for (const tabKey2 of this.pendingInputBroker.awaitingTabKeys()) {
|
|
@@ -3434,26 +5315,49 @@ class Orchestrator {
|
|
|
3434
5315
|
async ensureWorktree(task) {
|
|
3435
5316
|
if (task.worktreePath)
|
|
3436
5317
|
return task;
|
|
5318
|
+
const inflight = this.ensureWorktreeLatches.get(task.id);
|
|
5319
|
+
if (inflight) {
|
|
5320
|
+
await inflight;
|
|
5321
|
+
return this.requireTask(task.id);
|
|
5322
|
+
}
|
|
5323
|
+
const latch = this.doEnsureWorktree(task);
|
|
5324
|
+
this.ensureWorktreeLatches.set(task.id, latch);
|
|
5325
|
+
try {
|
|
5326
|
+
return await latch;
|
|
5327
|
+
} finally {
|
|
5328
|
+
this.ensureWorktreeLatches.delete(task.id);
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
async doEnsureWorktree(task) {
|
|
3437
5332
|
const opts = this.pendingWorktreeOpts.get(task.id);
|
|
3438
5333
|
const branch = opts?.branch ?? `kobe/tmp-${task.id.slice(-8).toLowerCase()}`;
|
|
3439
5334
|
const baseRef = opts?.baseRef;
|
|
5335
|
+
const slug = await this.slugAllocator.allocate(task.repo);
|
|
3440
5336
|
let info;
|
|
3441
5337
|
try {
|
|
3442
5338
|
info = await this.worktrees.createForTask({
|
|
3443
5339
|
repo: task.repo,
|
|
3444
|
-
|
|
5340
|
+
slug,
|
|
3445
5341
|
branch,
|
|
3446
5342
|
baseRef
|
|
3447
5343
|
});
|
|
3448
5344
|
} catch (err) {
|
|
5345
|
+
this.slugAllocator.cancel(task.repo, slug);
|
|
3449
5346
|
const message = err instanceof Error ? err.message : String(err);
|
|
3450
5347
|
throw new Error(summarizeWorktreeError(message, task.repo, baseRef ?? null), { cause: err });
|
|
3451
5348
|
}
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
5349
|
+
try {
|
|
5350
|
+
this.pendingWorktreeOpts.delete(task.id);
|
|
5351
|
+
const updated = await this.store.update(task.id, {
|
|
5352
|
+
branch: info.branch,
|
|
5353
|
+
worktreePath: info.path
|
|
5354
|
+
});
|
|
5355
|
+
this.slugAllocator.commit(task.repo, slug);
|
|
5356
|
+
return updated;
|
|
5357
|
+
} catch (err) {
|
|
5358
|
+
this.slugAllocator.cancel(task.repo, slug);
|
|
5359
|
+
throw err;
|
|
5360
|
+
}
|
|
3457
5361
|
}
|
|
3458
5362
|
async maybeRenameTempBranch(taskId, tabId, prompt) {
|
|
3459
5363
|
if (!prompt || prompt.trim().length === 0)
|
|
@@ -3549,14 +5453,17 @@ class Orchestrator {
|
|
|
3549
5453
|
if (prompt && prompt.trim().length > 0) {
|
|
3550
5454
|
this.dispatchEvent(task.id, targetTab.id, { type: "user.inject", text: prompt });
|
|
3551
5455
|
}
|
|
3552
|
-
const
|
|
5456
|
+
const engine = targetTab.sessionId ? await this.engineForTabRun(task, targetTab) : this.engineForTab(task, targetTab);
|
|
5457
|
+
const modelToUse = this.modelForTab(task, targetTab, engine);
|
|
5458
|
+
const modelEffortToUse = this.modelEffortForTab(task, targetTab);
|
|
3553
5459
|
let handle;
|
|
3554
5460
|
if (targetTab.sessionId) {
|
|
3555
|
-
handle = await
|
|
5461
|
+
handle = await engine.resume(targetTab.sessionId, promptToSend, {
|
|
3556
5462
|
cwd: task.worktreePath,
|
|
3557
5463
|
env: { KOBE_RESUME_CWD: task.worktreePath },
|
|
3558
5464
|
permissionMode: task.permissionMode,
|
|
3559
|
-
model: modelToUse
|
|
5465
|
+
model: modelToUse,
|
|
5466
|
+
modelEffort: modelEffortToUse
|
|
3560
5467
|
});
|
|
3561
5468
|
} else {
|
|
3562
5469
|
let releaseLatch = () => {};
|
|
@@ -3565,9 +5472,10 @@ class Orchestrator {
|
|
|
3565
5472
|
});
|
|
3566
5473
|
this.firstSpawnLatches.set(key, latch);
|
|
3567
5474
|
try {
|
|
3568
|
-
handle = await
|
|
5475
|
+
handle = await engine.spawn(task.worktreePath, promptToSend, {
|
|
3569
5476
|
permissionMode: task.permissionMode,
|
|
3570
|
-
model: modelToUse
|
|
5477
|
+
model: modelToUse,
|
|
5478
|
+
modelEffort: modelEffortToUse
|
|
3571
5479
|
});
|
|
3572
5480
|
await this.updateTab(task.id, targetTab.id, { sessionId: handle.sessionId });
|
|
3573
5481
|
if (task.title === PLACEHOLDER_TASK_TITLE && prompt && prompt.trim().length > 0) {
|
|
@@ -3640,7 +5548,7 @@ class Orchestrator {
|
|
|
3640
5548
|
text: "(turn interrupted \u2014 sending new prompt)"
|
|
3641
5549
|
});
|
|
3642
5550
|
try {
|
|
3643
|
-
await this.
|
|
5551
|
+
await this.engineForTask(task).stop(handle);
|
|
3644
5552
|
} finally {
|
|
3645
5553
|
this.handles.delete(key);
|
|
3646
5554
|
this.bumpRunState();
|
|
@@ -3682,11 +5590,13 @@ class Orchestrator {
|
|
|
3682
5590
|
return;
|
|
3683
5591
|
await this.store.update(task.id, { permissionMode: mode });
|
|
3684
5592
|
}
|
|
3685
|
-
async setModel(id, model) {
|
|
5593
|
+
async setModel(id, model, tabId, modelEffort) {
|
|
3686
5594
|
const task = this.requireTask(id);
|
|
3687
|
-
|
|
5595
|
+
const tab = this.resolveTab(task, tabId);
|
|
5596
|
+
const vendor = model ? capabilitiesForModelId(model).vendorId : this.vendorForTab(task, tab);
|
|
5597
|
+
if (tab.model === model && tab.modelEffort === modelEffort && this.vendorForTab(task, tab) === vendor)
|
|
3688
5598
|
return;
|
|
3689
|
-
await this.
|
|
5599
|
+
await this.updateTab(task.id, tab.id, { model, modelEffort, vendor });
|
|
3690
5600
|
}
|
|
3691
5601
|
async setTitle(id, title) {
|
|
3692
5602
|
const task = this.requireTask(id);
|
|
@@ -3750,7 +5660,7 @@ class Orchestrator {
|
|
|
3750
5660
|
}
|
|
3751
5661
|
if (task.sessionId) {
|
|
3752
5662
|
try {
|
|
3753
|
-
await this.
|
|
5663
|
+
await this.engineForTask(task).deleteHistory(task.sessionId);
|
|
3754
5664
|
} catch (err) {
|
|
3755
5665
|
console.error(`[kobe orchestrator] deleteTask: deleteHistory failed for ${task.id}:`, err);
|
|
3756
5666
|
}
|
|
@@ -3759,15 +5669,15 @@ class Orchestrator {
|
|
|
3759
5669
|
await this.store.remove(task.id);
|
|
3760
5670
|
}
|
|
3761
5671
|
async readHistory(sessionId) {
|
|
3762
|
-
|
|
3763
|
-
return await this.engine.readHistory(sessionId);
|
|
3764
|
-
} catch {
|
|
3765
|
-
return [];
|
|
3766
|
-
}
|
|
5672
|
+
return (await this.readHistoryWithMetrics(sessionId)).messages;
|
|
3767
5673
|
}
|
|
3768
5674
|
async readHistoryWithMetrics(sessionId) {
|
|
3769
|
-
const
|
|
3770
|
-
const
|
|
5675
|
+
const preferred = this.engineForSessionId(sessionId);
|
|
5676
|
+
const { history } = await this.findEngineWithHistory(sessionId, preferred.capabilities.vendorId);
|
|
5677
|
+
if (!history)
|
|
5678
|
+
return { messages: [] };
|
|
5679
|
+
const messages = [...history.messages];
|
|
5680
|
+
const usageMetrics = history.usageMetrics;
|
|
3771
5681
|
return {
|
|
3772
5682
|
messages,
|
|
3773
5683
|
...usageMetrics ? { usageMetrics } : {}
|
|
@@ -3777,14 +5687,16 @@ class Orchestrator {
|
|
|
3777
5687
|
const task = this.requireTask(id);
|
|
3778
5688
|
if (!task.worktreePath)
|
|
3779
5689
|
return [];
|
|
5690
|
+
const tab = this.resolveTab(task);
|
|
3780
5691
|
try {
|
|
3781
|
-
return await this.
|
|
5692
|
+
return await this.engineForTab(task, tab).listSessions(task.worktreePath);
|
|
3782
5693
|
} catch {
|
|
3783
5694
|
return [];
|
|
3784
5695
|
}
|
|
3785
5696
|
}
|
|
3786
5697
|
async openSessionInTab(id, sessionId, opts = {}) {
|
|
3787
5698
|
const task = this.requireTask(id);
|
|
5699
|
+
const active = this.resolveTab(task);
|
|
3788
5700
|
const existing = task.tabs.find((t) => t.sessionId === sessionId);
|
|
3789
5701
|
if (existing) {
|
|
3790
5702
|
await this.setActiveTab(task.id, existing.id);
|
|
@@ -3795,6 +5707,8 @@ class Orchestrator {
|
|
|
3795
5707
|
sessionId,
|
|
3796
5708
|
seq: nextChatTabSeq(task.tabs),
|
|
3797
5709
|
createdAt: new Date().toISOString(),
|
|
5710
|
+
model: active.model ?? task.model,
|
|
5711
|
+
vendor: this.vendorForTab(task, active),
|
|
3798
5712
|
...opts.title ? { title: opts.title } : {}
|
|
3799
5713
|
};
|
|
3800
5714
|
await this.store.update(task.id, { tabs: [...task.tabs, tab], activeTabId: tab.id });
|
|
@@ -3822,11 +5736,14 @@ class Orchestrator {
|
|
|
3822
5736
|
}
|
|
3823
5737
|
async createTab(id, opts = {}) {
|
|
3824
5738
|
const task = this.requireTask(id);
|
|
5739
|
+
const active = this.resolveTab(task);
|
|
3825
5740
|
const tab = {
|
|
3826
5741
|
id: ulid(),
|
|
3827
5742
|
sessionId: null,
|
|
3828
5743
|
seq: nextChatTabSeq(task.tabs),
|
|
3829
5744
|
createdAt: new Date().toISOString(),
|
|
5745
|
+
model: active.model ?? task.model,
|
|
5746
|
+
vendor: this.vendorForTab(task, active),
|
|
3830
5747
|
...opts.title ? { title: opts.title } : {}
|
|
3831
5748
|
};
|
|
3832
5749
|
const tabs = [...task.tabs, tab];
|
|
@@ -3906,7 +5823,7 @@ class Orchestrator {
|
|
|
3906
5823
|
if (!handle)
|
|
3907
5824
|
return;
|
|
3908
5825
|
try {
|
|
3909
|
-
await this.
|
|
5826
|
+
await this.engineForTaskId(taskId).stop(handle);
|
|
3910
5827
|
} finally {
|
|
3911
5828
|
this.handles.delete(key);
|
|
3912
5829
|
this.bumpRunState();
|
|
@@ -3915,12 +5832,13 @@ class Orchestrator {
|
|
|
3915
5832
|
async stopAllTabsForTask(taskId) {
|
|
3916
5833
|
const prefix = `${taskId}:`;
|
|
3917
5834
|
const keys = Array.from(this.handles.keys()).filter((k) => k.startsWith(prefix));
|
|
5835
|
+
const engine = this.engineForTaskId(taskId);
|
|
3918
5836
|
for (const key of keys) {
|
|
3919
5837
|
const handle = this.handles.get(key);
|
|
3920
5838
|
if (!handle)
|
|
3921
5839
|
continue;
|
|
3922
5840
|
try {
|
|
3923
|
-
await
|
|
5841
|
+
await engine.stop(handle);
|
|
3924
5842
|
} catch {}
|
|
3925
5843
|
this.handles.delete(key);
|
|
3926
5844
|
}
|
|
@@ -4046,12 +5964,13 @@ function deriveTitleFromPrompt(prompt) {
|
|
|
4046
5964
|
var CONCURRENCY_CAP = 20, PLACEHOLDER_TASK_TITLE = "(new task)", IllegalTransitionError, ConcurrencyCapError, PRPreconditionError, TaskNotFoundError, CannotDeleteMainTaskError, TITLE_CHAR_CAP = 40;
|
|
4047
5965
|
var init_core = __esm(() => {
|
|
4048
5966
|
init_dev();
|
|
4049
|
-
|
|
5967
|
+
init_registry();
|
|
4050
5968
|
init_repos();
|
|
4051
5969
|
init_ulid();
|
|
4052
5970
|
init_metadata_suggester();
|
|
4053
5971
|
init_pr();
|
|
4054
5972
|
init_session_pump();
|
|
5973
|
+
init_slug_allocator();
|
|
4055
5974
|
IllegalTransitionError = class IllegalTransitionError extends Error {
|
|
4056
5975
|
from;
|
|
4057
5976
|
to;
|
|
@@ -4091,9 +6010,9 @@ var init_core = __esm(() => {
|
|
|
4091
6010
|
});
|
|
4092
6011
|
|
|
4093
6012
|
// src/orchestrator/index/store.ts
|
|
4094
|
-
import { mkdir as mkdir4, open, readFile as
|
|
4095
|
-
import { homedir as
|
|
4096
|
-
import { dirname as dirname4, join as
|
|
6013
|
+
import { mkdir as mkdir4, open as open2, readFile as readFile4, rename, unlink as unlink4 } from "fs/promises";
|
|
6014
|
+
import { homedir as homedir11 } from "os";
|
|
6015
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
4097
6016
|
|
|
4098
6017
|
class TaskIndexStore {
|
|
4099
6018
|
homeDir;
|
|
@@ -4105,9 +6024,9 @@ class TaskIndexStore {
|
|
|
4105
6024
|
listeners = new Set;
|
|
4106
6025
|
saveChain = Promise.resolve();
|
|
4107
6026
|
constructor(options = {}) {
|
|
4108
|
-
this.homeDir = options.homeDir ??
|
|
4109
|
-
this.kobeDir =
|
|
4110
|
-
this.path =
|
|
6027
|
+
this.homeDir = options.homeDir ?? homedir11();
|
|
6028
|
+
this.kobeDir = join7(this.homeDir, ".kobe");
|
|
6029
|
+
this.path = join7(this.kobeDir, "tasks.json");
|
|
4111
6030
|
this.tmpPath = `${this.path}.tmp`;
|
|
4112
6031
|
}
|
|
4113
6032
|
subscribe(listener) {
|
|
@@ -4132,7 +6051,7 @@ class TaskIndexStore {
|
|
|
4132
6051
|
async load() {
|
|
4133
6052
|
let raw;
|
|
4134
6053
|
try {
|
|
4135
|
-
raw = await
|
|
6054
|
+
raw = await readFile4(this.path, "utf8");
|
|
4136
6055
|
} catch (err) {
|
|
4137
6056
|
const code = err.code;
|
|
4138
6057
|
if (code === "ENOENT") {
|
|
@@ -4169,7 +6088,7 @@ class TaskIndexStore {
|
|
|
4169
6088
|
const payload = this.snapshot();
|
|
4170
6089
|
const json = `${JSON.stringify(payload, null, 2)}
|
|
4171
6090
|
`;
|
|
4172
|
-
const handle = await
|
|
6091
|
+
const handle = await open2(this.tmpPath, "w", 420);
|
|
4173
6092
|
try {
|
|
4174
6093
|
await handle.writeFile(json, "utf8");
|
|
4175
6094
|
await handle.sync();
|
|
@@ -4197,7 +6116,17 @@ class TaskIndexStore {
|
|
|
4197
6116
|
activeTabId = activeIn && tabsIn.some((t) => t.id === activeIn) ? activeIn : tabsIn[0]?.id ?? "";
|
|
4198
6117
|
} else {
|
|
4199
6118
|
const tabId = ulid();
|
|
4200
|
-
tabs = [
|
|
6119
|
+
tabs = [
|
|
6120
|
+
{
|
|
6121
|
+
id: tabId,
|
|
6122
|
+
sessionId: sessionId ?? null,
|
|
6123
|
+
seq: 1,
|
|
6124
|
+
createdAt: now,
|
|
6125
|
+
...rest.model ? { model: rest.model } : {},
|
|
6126
|
+
...rest.modelEffort ? { modelEffort: rest.modelEffort } : {},
|
|
6127
|
+
vendor: rest.vendor ?? DEFAULT_TASK_VENDOR
|
|
6128
|
+
}
|
|
6129
|
+
];
|
|
4201
6130
|
activeTabId = tabId;
|
|
4202
6131
|
}
|
|
4203
6132
|
const firstSession = tabs[0]?.sessionId ?? null;
|
|
@@ -4264,13 +6193,13 @@ class TaskIndexStore {
|
|
|
4264
6193
|
}
|
|
4265
6194
|
async _unlinkForTests() {
|
|
4266
6195
|
try {
|
|
4267
|
-
await
|
|
6196
|
+
await unlink4(this.path);
|
|
4268
6197
|
} catch (err) {
|
|
4269
6198
|
if (err.code !== "ENOENT")
|
|
4270
6199
|
throw err;
|
|
4271
6200
|
}
|
|
4272
6201
|
try {
|
|
4273
|
-
await
|
|
6202
|
+
await unlink4(this.tmpPath);
|
|
4274
6203
|
} catch (err) {
|
|
4275
6204
|
if (err.code !== "ENOENT")
|
|
4276
6205
|
throw err;
|
|
@@ -4360,7 +6289,10 @@ function coerceTask(value) {
|
|
|
4360
6289
|
sessionId: tt.sessionId ?? null,
|
|
4361
6290
|
seq,
|
|
4362
6291
|
createdAt: tt.createdAt,
|
|
4363
|
-
...typeof tt.title === "string" ? { title: tt.title } : {}
|
|
6292
|
+
...typeof tt.title === "string" ? { title: tt.title } : {},
|
|
6293
|
+
...typeof tt.model === "string" ? { model: tt.model } : {},
|
|
6294
|
+
...isModelEffortLevel(tt.modelEffort) ? { modelEffort: tt.modelEffort } : {},
|
|
6295
|
+
...isVendorId(tt.vendor) ? { vendor: tt.vendor } : {}
|
|
4364
6296
|
};
|
|
4365
6297
|
tabs.push(tab);
|
|
4366
6298
|
}
|
|
@@ -4393,6 +6325,8 @@ function coerceTask(value) {
|
|
|
4393
6325
|
kind: v.kind === "main" ? "main" : "task",
|
|
4394
6326
|
permissionMode: isPermissionMode(v.permissionMode) ? v.permissionMode : undefined,
|
|
4395
6327
|
model: typeof v.model === "string" ? v.model : undefined,
|
|
6328
|
+
modelEffort: isModelEffortLevel(v.modelEffort) ? v.modelEffort : undefined,
|
|
6329
|
+
vendor: resolveTaskVendor(v.vendor, typeof v.model === "string" ? v.model : undefined),
|
|
4396
6330
|
createdAt: v.createdAt,
|
|
4397
6331
|
updatedAt: v.updatedAt
|
|
4398
6332
|
};
|
|
@@ -4400,20 +6334,36 @@ function coerceTask(value) {
|
|
|
4400
6334
|
function isPermissionMode(v) {
|
|
4401
6335
|
return v === "default" || v === "plan";
|
|
4402
6336
|
}
|
|
6337
|
+
function isModelEffortLevel(v) {
|
|
6338
|
+
return v === "none" || v === "minimal" || v === "low" || v === "medium" || v === "high" || v === "xhigh" || v === "max";
|
|
6339
|
+
}
|
|
6340
|
+
function isVendorId(v) {
|
|
6341
|
+
return typeof v === "string" && v in ENGINE_REGISTRY;
|
|
6342
|
+
}
|
|
6343
|
+
function resolveTaskVendor(rawVendor, modelId) {
|
|
6344
|
+
const stored = isVendorId(rawVendor) ? rawVendor : DEFAULT_TASK_VENDOR;
|
|
6345
|
+
if (!modelId)
|
|
6346
|
+
return stored;
|
|
6347
|
+
const matched = Object.values(ENGINE_REGISTRY).some((caps) => caps?.models.some((m) => m.id === modelId));
|
|
6348
|
+
if (!matched)
|
|
6349
|
+
return stored;
|
|
6350
|
+
return capabilitiesForModelId(modelId).vendorId;
|
|
6351
|
+
}
|
|
4403
6352
|
function isTaskStatus(s) {
|
|
4404
6353
|
return s === "backlog" || s === "in_progress" || s === "in_review" || s === "done" || s === "canceled" || s === "error";
|
|
4405
6354
|
}
|
|
4406
6355
|
var init_store = __esm(() => {
|
|
6356
|
+
init_registry();
|
|
4407
6357
|
init_ulid();
|
|
4408
6358
|
});
|
|
4409
6359
|
|
|
4410
6360
|
// src/orchestrator/worktree/git.ts
|
|
4411
|
-
import { spawnSync as
|
|
6361
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
4412
6362
|
function git2(args, opts) {
|
|
4413
6363
|
if (!opts.cwd) {
|
|
4414
6364
|
throw new Error("git(): cwd is required; refusing to inherit from process.cwd()");
|
|
4415
6365
|
}
|
|
4416
|
-
const proc =
|
|
6366
|
+
const proc = spawnSync5("git", [...args], {
|
|
4417
6367
|
cwd: opts.cwd,
|
|
4418
6368
|
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
4419
6369
|
encoding: "utf8",
|
|
@@ -4449,42 +6399,9 @@ var init_git = __esm(() => {
|
|
|
4449
6399
|
};
|
|
4450
6400
|
});
|
|
4451
6401
|
|
|
4452
|
-
// src/orchestrator/worktree/paths.ts
|
|
4453
|
-
import fs2 from "fs";
|
|
4454
|
-
import path5 from "path";
|
|
4455
|
-
function worktreeRootFor(repo) {
|
|
4456
|
-
if (!path5.isAbsolute(repo)) {
|
|
4457
|
-
throw new Error(`worktreeRootFor: repo must be an absolute path, got: ${repo}`);
|
|
4458
|
-
}
|
|
4459
|
-
return path5.join(repo, KOBE_WORKTREE_ROOT_SUBPATH);
|
|
4460
|
-
}
|
|
4461
|
-
function worktreePathFor(repo, taskId) {
|
|
4462
|
-
if (!taskId || /[/\\\0]/.test(taskId)) {
|
|
4463
|
-
throw new Error(`worktreePathFor: invalid taskId: ${JSON.stringify(taskId)}`);
|
|
4464
|
-
}
|
|
4465
|
-
return path5.join(worktreeRootFor(repo), taskId);
|
|
4466
|
-
}
|
|
4467
|
-
function isKobeManagedPath(repo, candidate) {
|
|
4468
|
-
if (!path5.isAbsolute(repo) || !path5.isAbsolute(candidate))
|
|
4469
|
-
return false;
|
|
4470
|
-
const root = canonicalize(worktreeRootFor(repo));
|
|
4471
|
-
const target = canonicalize(candidate);
|
|
4472
|
-
const rel = path5.relative(root, target);
|
|
4473
|
-
return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
4474
|
-
}
|
|
4475
|
-
function canonicalize(p) {
|
|
4476
|
-
try {
|
|
4477
|
-
return fs2.realpathSync(p);
|
|
4478
|
-
} catch {
|
|
4479
|
-
return path5.resolve(p);
|
|
4480
|
-
}
|
|
4481
|
-
}
|
|
4482
|
-
var KOBE_WORKTREE_ROOT_SUBPATH = ".claude/worktrees";
|
|
4483
|
-
var init_paths2 = () => {};
|
|
4484
|
-
|
|
4485
6402
|
// src/orchestrator/worktree/manager.ts
|
|
4486
6403
|
import fs3 from "fs";
|
|
4487
|
-
import
|
|
6404
|
+
import path8 from "path";
|
|
4488
6405
|
|
|
4489
6406
|
class GitWorktreeManager {
|
|
4490
6407
|
async create(repo, branch, worktreePath, baseRef) {
|
|
@@ -4502,7 +6419,7 @@ class GitWorktreeManager {
|
|
|
4502
6419
|
}
|
|
4503
6420
|
throw new Error(`create(): ${worktreePath} exists but is not a registered git worktree`);
|
|
4504
6421
|
}
|
|
4505
|
-
fs3.mkdirSync(
|
|
6422
|
+
fs3.mkdirSync(path8.dirname(worktreePath), { recursive: true });
|
|
4506
6423
|
const branchExists = this.branchExists(repo, branch);
|
|
4507
6424
|
const args = branchExists ? ["worktree", "add", worktreePath, branch] : baseRef ? ["worktree", "add", "-b", branch, worktreePath, baseRef] : ["worktree", "add", "-b", branch, worktreePath];
|
|
4508
6425
|
git2(args, { cwd: repo });
|
|
@@ -4516,7 +6433,7 @@ class GitWorktreeManager {
|
|
|
4516
6433
|
return info;
|
|
4517
6434
|
}
|
|
4518
6435
|
async createForTask(args) {
|
|
4519
|
-
const target = worktreePathFor(args.repo, args.
|
|
6436
|
+
const target = worktreePathFor(args.repo, args.slug);
|
|
4520
6437
|
return this.create(args.repo, args.branch, target, args.baseRef);
|
|
4521
6438
|
}
|
|
4522
6439
|
async remove(worktreePath, opts) {
|
|
@@ -4557,8 +6474,8 @@ class GitWorktreeManager {
|
|
|
4557
6474
|
if (!entry.branch || entry.detached)
|
|
4558
6475
|
continue;
|
|
4559
6476
|
const canonEntry = canonicalize2(entry.path);
|
|
4560
|
-
const rel =
|
|
4561
|
-
const callerPath =
|
|
6477
|
+
const rel = path8.relative(canonRoot, canonEntry);
|
|
6478
|
+
const callerPath = path8.join(callerRoot, rel);
|
|
4562
6479
|
const dirty = await this.isDirty(entry.path);
|
|
4563
6480
|
infos.push({
|
|
4564
6481
|
path: callerPath,
|
|
@@ -4619,9 +6536,9 @@ class GitWorktreeManager {
|
|
|
4619
6536
|
const gitDir = out.stdout.trim();
|
|
4620
6537
|
if (!gitDir)
|
|
4621
6538
|
return null;
|
|
4622
|
-
const absolute =
|
|
4623
|
-
const base =
|
|
4624
|
-
return base === ".git" ?
|
|
6539
|
+
const absolute = path8.isAbsolute(gitDir) ? gitDir : path8.resolve(worktreePath, gitDir);
|
|
6540
|
+
const base = path8.basename(absolute);
|
|
6541
|
+
return base === ".git" ? path8.dirname(absolute) : absolute;
|
|
4625
6542
|
} catch (err) {
|
|
4626
6543
|
if (err instanceof GitCommandError)
|
|
4627
6544
|
return null;
|
|
@@ -4661,7 +6578,7 @@ function parsePorcelain(out) {
|
|
|
4661
6578
|
return records;
|
|
4662
6579
|
}
|
|
4663
6580
|
function requireAbsolute(name, value) {
|
|
4664
|
-
if (!value || !
|
|
6581
|
+
if (!value || !path8.isAbsolute(value)) {
|
|
4665
6582
|
throw new Error(`${name} must be an absolute path, got: ${JSON.stringify(value)}`);
|
|
4666
6583
|
}
|
|
4667
6584
|
}
|
|
@@ -4669,7 +6586,7 @@ function canonicalize2(p) {
|
|
|
4669
6586
|
try {
|
|
4670
6587
|
return fs3.realpathSync(p);
|
|
4671
6588
|
} catch {
|
|
4672
|
-
return
|
|
6589
|
+
return path8.resolve(p);
|
|
4673
6590
|
}
|
|
4674
6591
|
}
|
|
4675
6592
|
var init_manager = __esm(() => {
|
|
@@ -4680,22 +6597,26 @@ var init_manager = __esm(() => {
|
|
|
4680
6597
|
// src/bin/kobed.ts
|
|
4681
6598
|
init_daemon_process();
|
|
4682
6599
|
init_client();
|
|
4683
|
-
import { unlink as
|
|
6600
|
+
import { unlink as unlink6 } from "fs/promises";
|
|
4684
6601
|
|
|
4685
6602
|
// src/core/index.ts
|
|
4686
6603
|
init_claude_code_local();
|
|
6604
|
+
init_codex_local();
|
|
4687
6605
|
init_bridge();
|
|
4688
6606
|
init_core();
|
|
4689
6607
|
init_store();
|
|
4690
6608
|
init_manager();
|
|
4691
|
-
import { homedir as
|
|
6609
|
+
import { homedir as homedir12 } from "os";
|
|
4692
6610
|
async function createKobeCore(options = {}) {
|
|
4693
|
-
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ??
|
|
6611
|
+
const homeDir2 = options.homeDir ?? process.env.KOBE_HOME_DIR ?? homedir12();
|
|
4694
6612
|
const store = new TaskIndexStore({ homeDir: homeDir2 });
|
|
4695
6613
|
await store.load();
|
|
4696
6614
|
const worktrees = new GitWorktreeManager;
|
|
4697
|
-
const
|
|
4698
|
-
|
|
6615
|
+
const engines = options.engines ?? (options.engine ? { [options.engine.capabilities.vendorId]: options.engine } : {
|
|
6616
|
+
claude: new ClaudeCodeLocal,
|
|
6617
|
+
codex: new CodexLocal
|
|
6618
|
+
});
|
|
6619
|
+
const orchestrator = new Orchestrator({ engines, store, worktrees });
|
|
4699
6620
|
const bridge = options.startMcpBridge === false ? null : await startBridge(orchestrator, { homeDir: homeDir2 });
|
|
4700
6621
|
return {
|
|
4701
6622
|
homeDir: homeDir2,
|
|
@@ -4716,16 +6637,16 @@ init_paths();
|
|
|
4716
6637
|
// src/daemon/server.ts
|
|
4717
6638
|
init_repos();
|
|
4718
6639
|
init_paths();
|
|
4719
|
-
import { mkdir as mkdir5, readFile as
|
|
6640
|
+
import { mkdir as mkdir5, readFile as readFile6, unlink as unlink5, writeFile as writeFile4 } from "fs/promises";
|
|
4720
6641
|
import { createServer as createServer2 } from "net";
|
|
4721
6642
|
import { dirname as dirname5 } from "path";
|
|
4722
6643
|
|
|
4723
6644
|
// src/engine/claude-code-local/plan-usage.ts
|
|
4724
6645
|
import { execFile } from "child_process";
|
|
4725
|
-
import { createHash } from "crypto";
|
|
4726
|
-
import { readFile as
|
|
4727
|
-
import { homedir as
|
|
4728
|
-
import { join as
|
|
6646
|
+
import { createHash as createHash2 } from "crypto";
|
|
6647
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
6648
|
+
import { homedir as homedir13, userInfo } from "os";
|
|
6649
|
+
import { join as join8 } from "path";
|
|
4729
6650
|
import { promisify } from "util";
|
|
4730
6651
|
var execFileAsync = promisify(execFile);
|
|
4731
6652
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
@@ -4736,7 +6657,7 @@ function keychainServiceName() {
|
|
|
4736
6657
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
4737
6658
|
if (!configDir)
|
|
4738
6659
|
return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}`;
|
|
4739
|
-
const hash =
|
|
6660
|
+
const hash = createHash2("sha256").update(configDir).digest("hex").slice(0, 8);
|
|
4740
6661
|
return `${KEYCHAIN_BASE}${KEYCHAIN_SUFFIX}-${hash}`;
|
|
4741
6662
|
}
|
|
4742
6663
|
function keychainAccount() {
|
|
@@ -4760,10 +6681,10 @@ async function readKeychainToken() {
|
|
|
4760
6681
|
}
|
|
4761
6682
|
}
|
|
4762
6683
|
async function readPlainTextToken() {
|
|
4763
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR ??
|
|
4764
|
-
const
|
|
6684
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR ?? join8(homedir13(), ".claude");
|
|
6685
|
+
const path9 = join8(configDir, ".credentials.json");
|
|
4765
6686
|
try {
|
|
4766
|
-
const raw = await
|
|
6687
|
+
const raw = await readFile5(path9, "utf8");
|
|
4767
6688
|
return parseStoredOAuth(raw);
|
|
4768
6689
|
} catch {
|
|
4769
6690
|
return null;
|
|
@@ -4876,7 +6797,7 @@ function createPlanUsagePoller(options) {
|
|
|
4876
6797
|
}
|
|
4877
6798
|
// src/daemon/rc-bridge.ts
|
|
4878
6799
|
init_binary();
|
|
4879
|
-
import { spawn as
|
|
6800
|
+
import { spawn as spawn5 } from "child_process";
|
|
4880
6801
|
var ENV_ID_RE = /Environment ID:\s*(env_[A-Za-z0-9]+)/;
|
|
4881
6802
|
var DEEPLINK_RE = /https:\/\/claude\.ai\/code\?environment=([A-Za-z0-9_]+)/;
|
|
4882
6803
|
var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
@@ -4884,7 +6805,7 @@ function createRcBridge(options = {}) {
|
|
|
4884
6805
|
const stopGraceMs = options.stopGraceMs ?? 5000;
|
|
4885
6806
|
const readyTimeoutMs = options.readyTimeoutMs ?? 30000;
|
|
4886
6807
|
const binaryPathResolver = options.binaryPathResolver ?? findClaudeBinary;
|
|
4887
|
-
const spawner = options.spawner ?? ((cmd, args, cwd) =>
|
|
6808
|
+
const spawner = options.spawner ?? ((cmd, args, cwd) => spawn5(cmd, [...args], {
|
|
4888
6809
|
cwd,
|
|
4889
6810
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4890
6811
|
env: { ...process.env }
|
|
@@ -5058,7 +6979,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5058
6979
|
let nextClientId = 1;
|
|
5059
6980
|
await mkdir5(dirname5(socketPath), { recursive: true });
|
|
5060
6981
|
await mkdir5(dirname5(pidPath), { recursive: true });
|
|
5061
|
-
await
|
|
6982
|
+
await unlink5(socketPath).catch(() => {});
|
|
5062
6983
|
const server = createServer2((socket) => {
|
|
5063
6984
|
const client = {
|
|
5064
6985
|
id: nextClientId++,
|
|
@@ -5103,8 +7024,8 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5103
7024
|
client.socket.destroy();
|
|
5104
7025
|
}
|
|
5105
7026
|
await new Promise((resolve2) => server.close(() => resolve2()));
|
|
5106
|
-
await
|
|
5107
|
-
await
|
|
7027
|
+
await unlink5(socketPath).catch(() => {});
|
|
7028
|
+
await unlink5(pidPath).catch(() => {});
|
|
5108
7029
|
}
|
|
5109
7030
|
};
|
|
5110
7031
|
planUsagePoller.start();
|
|
@@ -5218,7 +7139,11 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5218
7139
|
}
|
|
5219
7140
|
case "task.model": {
|
|
5220
7141
|
const taskId = requireString2(payload, "taskId");
|
|
5221
|
-
|
|
7142
|
+
const modelEffort = optionalString2(payload, "modelEffort");
|
|
7143
|
+
if (modelEffort !== undefined && modelEffort !== "none" && modelEffort !== "minimal" && modelEffort !== "low" && modelEffort !== "medium" && modelEffort !== "high" && modelEffort !== "xhigh" && modelEffort !== "max") {
|
|
7144
|
+
throw new Error("modelEffort must be a supported effort level");
|
|
7145
|
+
}
|
|
7146
|
+
await orch.setModel(taskId, optionalString2(payload, "model"), optionalString2(payload, "tabId"), modelEffort);
|
|
5222
7147
|
broadcastTaskUpdated(orch, clients, taskId);
|
|
5223
7148
|
return {};
|
|
5224
7149
|
}
|
|
@@ -5417,7 +7342,7 @@ async function startDaemonServer(orch, options = {}) {
|
|
|
5417
7342
|
}
|
|
5418
7343
|
async function readPidFile(pidPath) {
|
|
5419
7344
|
try {
|
|
5420
|
-
const raw = await
|
|
7345
|
+
const raw = await readFile6(pidPath, "utf8");
|
|
5421
7346
|
const pid = Number(raw.trim());
|
|
5422
7347
|
return Number.isFinite(pid) ? pid : null;
|
|
5423
7348
|
} catch {
|
|
@@ -5455,8 +7380,7 @@ async function readTaskHistory(orch, taskId, requestedSessionId, limit, before)
|
|
|
5455
7380
|
const sessionId = requestedSessionId ?? task?.tabs.find((t) => t.id === task.activeTabId)?.sessionId ?? task?.sessionId;
|
|
5456
7381
|
if (!sessionId)
|
|
5457
7382
|
return { messages: [], nextBefore: null, hasMore: false };
|
|
5458
|
-
const messages = await orch.
|
|
5459
|
-
const usageMetrics = deriveSessionUsageMetrics(messages);
|
|
7383
|
+
const { messages, usageMetrics } = await orch.readHistoryWithMetrics(sessionId);
|
|
5460
7384
|
const beforeIdx = before ? messages.findIndex((m) => `${m.timestamp}:${m.sessionId}` === before) : -1;
|
|
5461
7385
|
const end = beforeIdx >= 0 ? beforeIdx : messages.length;
|
|
5462
7386
|
const start = Math.max(0, end - limit);
|
|
@@ -5602,7 +7526,7 @@ async function main() {
|
|
|
5602
7526
|
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
5603
7527
|
} catch {}
|
|
5604
7528
|
}
|
|
5605
|
-
await
|
|
7529
|
+
await unlink6(socketPath).catch(() => {});
|
|
5606
7530
|
const next = await connectOrStartDaemon();
|
|
5607
7531
|
next.close();
|
|
5608
7532
|
console.log(`kobed: restarted, listening on ${socketPath}`);
|