reasonix 0.10.0 → 0.11.0
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 +4 -0
- package/README.zh-CN.md +721 -0
- package/dist/cli/{chunk-WRG56OKI.js → chunk-GXABXQMU.js} +14 -3
- package/dist/cli/chunk-GXABXQMU.js.map +1 -0
- package/dist/cli/index.js +1534 -297
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-LJ44NWSU.js → prompt-FMYQ7IDW.js} +2 -2
- package/dist/index.d.ts +7 -10
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-WRG56OKI.js.map +0 -1
- /package/dist/cli/{prompt-LJ44NWSU.js.map → prompt-FMYQ7IDW.js.map} +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
memoryEnabled,
|
|
11
11
|
readProjectMemory,
|
|
12
12
|
sanitizeMemoryName
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GXABXQMU.js";
|
|
14
14
|
|
|
15
15
|
// src/cli/index.ts
|
|
16
16
|
import { Command } from "commander";
|
|
@@ -22,80 +22,80 @@ import { dirname, join } from "path";
|
|
|
22
22
|
function defaultConfigPath() {
|
|
23
23
|
return join(homedir(), ".reasonix", "config.json");
|
|
24
24
|
}
|
|
25
|
-
function readConfig(
|
|
25
|
+
function readConfig(path5 = defaultConfigPath()) {
|
|
26
26
|
try {
|
|
27
|
-
const raw = readFileSync(
|
|
27
|
+
const raw = readFileSync(path5, "utf8");
|
|
28
28
|
const parsed = JSON.parse(raw);
|
|
29
29
|
if (parsed && typeof parsed === "object") return parsed;
|
|
30
30
|
} catch {
|
|
31
31
|
}
|
|
32
32
|
return {};
|
|
33
33
|
}
|
|
34
|
-
function writeConfig(cfg,
|
|
35
|
-
mkdirSync(dirname(
|
|
36
|
-
writeFileSync(
|
|
34
|
+
function writeConfig(cfg, path5 = defaultConfigPath()) {
|
|
35
|
+
mkdirSync(dirname(path5), { recursive: true });
|
|
36
|
+
writeFileSync(path5, JSON.stringify(cfg, null, 2), "utf8");
|
|
37
37
|
try {
|
|
38
|
-
chmodSync(
|
|
38
|
+
chmodSync(path5, 384);
|
|
39
39
|
} catch {
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
function loadApiKey(
|
|
42
|
+
function loadApiKey(path5 = defaultConfigPath()) {
|
|
43
43
|
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
|
44
|
-
return readConfig(
|
|
44
|
+
return readConfig(path5).apiKey;
|
|
45
45
|
}
|
|
46
|
-
function searchEnabled(
|
|
46
|
+
function searchEnabled(path5 = defaultConfigPath()) {
|
|
47
47
|
const env = process.env.REASONIX_SEARCH;
|
|
48
48
|
if (env === "off" || env === "false" || env === "0") return false;
|
|
49
|
-
const cfg = readConfig(
|
|
49
|
+
const cfg = readConfig(path5).search;
|
|
50
50
|
if (cfg === false) return false;
|
|
51
51
|
return true;
|
|
52
52
|
}
|
|
53
|
-
function saveApiKey(key,
|
|
54
|
-
const cfg = readConfig(
|
|
53
|
+
function saveApiKey(key, path5 = defaultConfigPath()) {
|
|
54
|
+
const cfg = readConfig(path5);
|
|
55
55
|
cfg.apiKey = key.trim();
|
|
56
|
-
writeConfig(cfg,
|
|
56
|
+
writeConfig(cfg, path5);
|
|
57
57
|
}
|
|
58
|
-
function loadProjectShellAllowed(rootDir,
|
|
59
|
-
const cfg = readConfig(
|
|
58
|
+
function loadProjectShellAllowed(rootDir, path5 = defaultConfigPath()) {
|
|
59
|
+
const cfg = readConfig(path5);
|
|
60
60
|
return cfg.projects?.[rootDir]?.shellAllowed ?? [];
|
|
61
61
|
}
|
|
62
|
-
function addProjectShellAllowed(rootDir, prefix,
|
|
62
|
+
function addProjectShellAllowed(rootDir, prefix, path5 = defaultConfigPath()) {
|
|
63
63
|
const trimmed = prefix.trim();
|
|
64
64
|
if (!trimmed) return;
|
|
65
|
-
const cfg = readConfig(
|
|
65
|
+
const cfg = readConfig(path5);
|
|
66
66
|
if (!cfg.projects) cfg.projects = {};
|
|
67
67
|
if (!cfg.projects[rootDir]) cfg.projects[rootDir] = {};
|
|
68
68
|
const existing = cfg.projects[rootDir].shellAllowed ?? [];
|
|
69
69
|
if (existing.includes(trimmed)) return;
|
|
70
70
|
cfg.projects[rootDir].shellAllowed = [...existing, trimmed];
|
|
71
|
-
writeConfig(cfg,
|
|
71
|
+
writeConfig(cfg, path5);
|
|
72
72
|
}
|
|
73
|
-
function loadEditMode(
|
|
74
|
-
const v = readConfig(
|
|
73
|
+
function loadEditMode(path5 = defaultConfigPath()) {
|
|
74
|
+
const v = readConfig(path5).editMode;
|
|
75
75
|
return v === "auto" ? "auto" : "review";
|
|
76
76
|
}
|
|
77
|
-
function saveEditMode(mode2,
|
|
78
|
-
const cfg = readConfig(
|
|
77
|
+
function saveEditMode(mode2, path5 = defaultConfigPath()) {
|
|
78
|
+
const cfg = readConfig(path5);
|
|
79
79
|
cfg.editMode = mode2;
|
|
80
|
-
writeConfig(cfg,
|
|
80
|
+
writeConfig(cfg, path5);
|
|
81
81
|
}
|
|
82
|
-
function editModeHintShown(
|
|
83
|
-
return readConfig(
|
|
82
|
+
function editModeHintShown(path5 = defaultConfigPath()) {
|
|
83
|
+
return readConfig(path5).editModeHintShown === true;
|
|
84
84
|
}
|
|
85
|
-
function loadReasoningEffort(
|
|
86
|
-
const v = readConfig(
|
|
85
|
+
function loadReasoningEffort(path5 = defaultConfigPath()) {
|
|
86
|
+
const v = readConfig(path5).reasoningEffort;
|
|
87
87
|
return v === "high" ? "high" : "max";
|
|
88
88
|
}
|
|
89
|
-
function saveReasoningEffort(effort2,
|
|
90
|
-
const cfg = readConfig(
|
|
89
|
+
function saveReasoningEffort(effort2, path5 = defaultConfigPath()) {
|
|
90
|
+
const cfg = readConfig(path5);
|
|
91
91
|
cfg.reasoningEffort = effort2;
|
|
92
|
-
writeConfig(cfg,
|
|
92
|
+
writeConfig(cfg, path5);
|
|
93
93
|
}
|
|
94
|
-
function markEditModeHintShown(
|
|
95
|
-
const cfg = readConfig(
|
|
94
|
+
function markEditModeHintShown(path5 = defaultConfigPath()) {
|
|
95
|
+
const cfg = readConfig(path5);
|
|
96
96
|
if (cfg.editModeHintShown === true) return;
|
|
97
97
|
cfg.editModeHintShown = true;
|
|
98
|
-
writeConfig(cfg,
|
|
98
|
+
writeConfig(cfg, path5);
|
|
99
99
|
}
|
|
100
100
|
function isPlausibleKey(key) {
|
|
101
101
|
const trimmed = key.trim();
|
|
@@ -156,8 +156,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
156
156
|
}
|
|
157
157
|
function sleep(ms, signal) {
|
|
158
158
|
if (ms <= 0) return Promise.resolve();
|
|
159
|
-
return new Promise((
|
|
160
|
-
const timer = setTimeout(
|
|
159
|
+
return new Promise((resolve9, reject) => {
|
|
160
|
+
const timer = setTimeout(resolve9, ms);
|
|
161
161
|
if (signal) {
|
|
162
162
|
const onAbort = () => {
|
|
163
163
|
clearTimeout(timer);
|
|
@@ -597,10 +597,10 @@ function globalSettingsPath(homeDirOverride) {
|
|
|
597
597
|
function projectSettingsPath(projectRoot) {
|
|
598
598
|
return join2(projectRoot, HOOK_SETTINGS_DIRNAME, HOOK_SETTINGS_FILENAME);
|
|
599
599
|
}
|
|
600
|
-
function readSettingsFile(
|
|
601
|
-
if (!existsSync(
|
|
600
|
+
function readSettingsFile(path5) {
|
|
601
|
+
if (!existsSync(path5)) return null;
|
|
602
602
|
try {
|
|
603
|
-
const raw = readFileSync2(
|
|
603
|
+
const raw = readFileSync2(path5, "utf8");
|
|
604
604
|
const parsed = JSON.parse(raw);
|
|
605
605
|
if (parsed && typeof parsed === "object") return parsed;
|
|
606
606
|
} catch {
|
|
@@ -642,13 +642,13 @@ function matchesTool(hook, toolName) {
|
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
644
|
function defaultSpawner(input) {
|
|
645
|
-
return new Promise((
|
|
645
|
+
return new Promise((resolve9) => {
|
|
646
646
|
const child = spawn(input.command, {
|
|
647
647
|
cwd: input.cwd,
|
|
648
648
|
shell: true,
|
|
649
649
|
stdio: ["pipe", "pipe", "pipe"]
|
|
650
650
|
});
|
|
651
|
-
let
|
|
651
|
+
let stdout3 = "";
|
|
652
652
|
let stderr = "";
|
|
653
653
|
let timedOut = false;
|
|
654
654
|
const timer = setTimeout(() => {
|
|
@@ -662,16 +662,16 @@ function defaultSpawner(input) {
|
|
|
662
662
|
}, 500);
|
|
663
663
|
}, input.timeoutMs);
|
|
664
664
|
child.stdout.on("data", (chunk) => {
|
|
665
|
-
|
|
665
|
+
stdout3 += chunk.toString("utf8");
|
|
666
666
|
});
|
|
667
667
|
child.stderr.on("data", (chunk) => {
|
|
668
668
|
stderr += chunk.toString("utf8");
|
|
669
669
|
});
|
|
670
670
|
child.once("error", (err) => {
|
|
671
671
|
clearTimeout(timer);
|
|
672
|
-
|
|
672
|
+
resolve9({
|
|
673
673
|
exitCode: null,
|
|
674
|
-
stdout:
|
|
674
|
+
stdout: stdout3,
|
|
675
675
|
stderr,
|
|
676
676
|
timedOut: false,
|
|
677
677
|
spawnError: err
|
|
@@ -679,9 +679,9 @@ function defaultSpawner(input) {
|
|
|
679
679
|
});
|
|
680
680
|
child.once("close", (code) => {
|
|
681
681
|
clearTimeout(timer);
|
|
682
|
-
|
|
682
|
+
resolve9({
|
|
683
683
|
exitCode: code,
|
|
684
|
-
stdout:
|
|
684
|
+
stdout: stdout3.trim(),
|
|
685
685
|
stderr: stderr.trim(),
|
|
686
686
|
timedOut
|
|
687
687
|
});
|
|
@@ -715,13 +715,13 @@ async function runHooks(opts) {
|
|
|
715
715
|
const matching = opts.hooks.filter((h) => h.event === event && matchesTool(h, toolName));
|
|
716
716
|
const outcomes = [];
|
|
717
717
|
let blocked = false;
|
|
718
|
-
const
|
|
718
|
+
const stdin4 = `${JSON.stringify(opts.payload)}
|
|
719
719
|
`;
|
|
720
720
|
for (const hook of matching) {
|
|
721
721
|
const start = Date.now();
|
|
722
722
|
const timeoutMs = hook.timeout ?? DEFAULT_TIMEOUTS_MS[event];
|
|
723
723
|
const cwd = hook.cwd ?? opts.payload.cwd;
|
|
724
|
-
const raw = await spawner({ command: hook.command, cwd, stdin:
|
|
724
|
+
const raw = await spawner({ command: hook.command, cwd, stdin: stdin4, timeoutMs });
|
|
725
725
|
const decision = decideOutcome(event, raw);
|
|
726
726
|
outcomes.push({
|
|
727
727
|
hook,
|
|
@@ -804,10 +804,10 @@ function loadTokenizer() {
|
|
|
804
804
|
}
|
|
805
805
|
const addedMap = /* @__PURE__ */ new Map();
|
|
806
806
|
const addedContents = [];
|
|
807
|
-
for (const
|
|
808
|
-
if (!
|
|
809
|
-
addedMap.set(
|
|
810
|
-
addedContents.push(
|
|
807
|
+
for (const t2 of data.added_tokens) {
|
|
808
|
+
if (!t2.special) {
|
|
809
|
+
addedMap.set(t2.content, t2.id);
|
|
810
|
+
addedContents.push(t2.content);
|
|
811
811
|
}
|
|
812
812
|
}
|
|
813
813
|
addedContents.sort((a, b) => b.length - a.length);
|
|
@@ -874,29 +874,29 @@ function bpeEncode(piece, mergeRank) {
|
|
|
874
874
|
}
|
|
875
875
|
function encode(text) {
|
|
876
876
|
if (!text) return [];
|
|
877
|
-
const
|
|
877
|
+
const t2 = loadTokenizer();
|
|
878
878
|
const ids = [];
|
|
879
879
|
const process2 = (segment) => {
|
|
880
880
|
if (!segment) return;
|
|
881
881
|
let chunks = [segment];
|
|
882
|
-
for (const re of
|
|
882
|
+
for (const re of t2.splitRegexes) chunks = applySplit(chunks, re);
|
|
883
883
|
for (const chunk of chunks) {
|
|
884
884
|
if (!chunk) continue;
|
|
885
|
-
const byteLevel = byteLevelEncode(chunk,
|
|
886
|
-
const pieces = bpeEncode(byteLevel,
|
|
885
|
+
const byteLevel = byteLevelEncode(chunk, t2.byteToChar);
|
|
886
|
+
const pieces = bpeEncode(byteLevel, t2.mergeRank);
|
|
887
887
|
for (const p of pieces) {
|
|
888
|
-
const id =
|
|
888
|
+
const id = t2.vocab[p];
|
|
889
889
|
if (id !== void 0) ids.push(id);
|
|
890
890
|
}
|
|
891
891
|
}
|
|
892
892
|
};
|
|
893
|
-
if (
|
|
894
|
-
|
|
893
|
+
if (t2.addedPattern) {
|
|
894
|
+
t2.addedPattern.lastIndex = 0;
|
|
895
895
|
let last = 0;
|
|
896
|
-
for (const m of text.matchAll(
|
|
896
|
+
for (const m of text.matchAll(t2.addedPattern)) {
|
|
897
897
|
const idx = m.index ?? 0;
|
|
898
898
|
if (idx > last) process2(text.slice(last, idx));
|
|
899
|
-
const id =
|
|
899
|
+
const id = t2.addedMap.get(m[0]);
|
|
900
900
|
if (id !== void 0) ids.push(id);
|
|
901
901
|
last = idx + m[0].length;
|
|
902
902
|
}
|
|
@@ -987,14 +987,14 @@ function collect(prefix, schema, out, required, isRootRequired) {
|
|
|
987
987
|
out[prefix] = schema;
|
|
988
988
|
if (isRootRequired) required.push(prefix);
|
|
989
989
|
}
|
|
990
|
-
function setByPath(target,
|
|
990
|
+
function setByPath(target, path5, value) {
|
|
991
991
|
let cur = target;
|
|
992
|
-
for (let i = 0; i <
|
|
993
|
-
const key =
|
|
992
|
+
for (let i = 0; i < path5.length - 1; i++) {
|
|
993
|
+
const key = path5[i];
|
|
994
994
|
if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
995
995
|
cur = cur[key];
|
|
996
996
|
}
|
|
997
|
-
cur[
|
|
997
|
+
cur[path5[path5.length - 1]] = value;
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
1000
|
// src/tools.ts
|
|
@@ -1060,12 +1060,12 @@ var ToolRegistry = class {
|
|
|
1060
1060
|
return Boolean(this._tools.get(name)?.flatSchema);
|
|
1061
1061
|
}
|
|
1062
1062
|
specs() {
|
|
1063
|
-
return [...this._tools.values()].map((
|
|
1063
|
+
return [...this._tools.values()].map((t2) => ({
|
|
1064
1064
|
type: "function",
|
|
1065
1065
|
function: {
|
|
1066
|
-
name:
|
|
1067
|
-
description:
|
|
1068
|
-
parameters:
|
|
1066
|
+
name: t2.name,
|
|
1067
|
+
description: t2.description ?? "",
|
|
1068
|
+
parameters: t2.flatSchema ?? t2.parameters ?? { type: "object", properties: {} }
|
|
1069
1069
|
}
|
|
1070
1070
|
}));
|
|
1071
1071
|
}
|
|
@@ -1277,7 +1277,7 @@ var ImmutablePrefix = class {
|
|
|
1277
1277
|
return [{ role: "system", content: this.system }, ...this.fewShots.map((m) => ({ ...m }))];
|
|
1278
1278
|
}
|
|
1279
1279
|
tools() {
|
|
1280
|
-
return this.toolSpecs.map((
|
|
1280
|
+
return this.toolSpecs.map((t2) => structuredClone(t2));
|
|
1281
1281
|
}
|
|
1282
1282
|
get fingerprint() {
|
|
1283
1283
|
const blob = JSON.stringify({
|
|
@@ -1660,10 +1660,10 @@ function sanitizeName(name) {
|
|
|
1660
1660
|
return cleaned || "default";
|
|
1661
1661
|
}
|
|
1662
1662
|
function loadSessionMessages(name) {
|
|
1663
|
-
const
|
|
1664
|
-
if (!existsSync3(
|
|
1663
|
+
const path5 = sessionPath(name);
|
|
1664
|
+
if (!existsSync3(path5)) return [];
|
|
1665
1665
|
try {
|
|
1666
|
-
const raw = readFileSync4(
|
|
1666
|
+
const raw = readFileSync4(path5, "utf8");
|
|
1667
1667
|
const out = [];
|
|
1668
1668
|
for (const line of raw.split(/\r?\n/)) {
|
|
1669
1669
|
const trimmed = line.trim();
|
|
@@ -1680,12 +1680,12 @@ function loadSessionMessages(name) {
|
|
|
1680
1680
|
}
|
|
1681
1681
|
}
|
|
1682
1682
|
function appendSessionMessage(name, message) {
|
|
1683
|
-
const
|
|
1684
|
-
mkdirSync2(dirname3(
|
|
1685
|
-
appendFileSync(
|
|
1683
|
+
const path5 = sessionPath(name);
|
|
1684
|
+
mkdirSync2(dirname3(path5), { recursive: true });
|
|
1685
|
+
appendFileSync(path5, `${JSON.stringify(message)}
|
|
1686
1686
|
`, "utf8");
|
|
1687
1687
|
try {
|
|
1688
|
-
chmodSync2(
|
|
1688
|
+
chmodSync2(path5, 384);
|
|
1689
1689
|
} catch {
|
|
1690
1690
|
}
|
|
1691
1691
|
}
|
|
@@ -1695,21 +1695,21 @@ function listSessions() {
|
|
|
1695
1695
|
try {
|
|
1696
1696
|
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1697
1697
|
return files.map((file) => {
|
|
1698
|
-
const
|
|
1699
|
-
const stat = statSync(
|
|
1698
|
+
const path5 = join4(dir, file);
|
|
1699
|
+
const stat = statSync(path5);
|
|
1700
1700
|
const name = file.replace(/\.jsonl$/, "");
|
|
1701
|
-
const messageCount = countLines(
|
|
1702
|
-
return { name, path, size: stat.size, messageCount, mtime: stat.mtime };
|
|
1701
|
+
const messageCount = countLines(path5);
|
|
1702
|
+
return { name, path: path5, size: stat.size, messageCount, mtime: stat.mtime };
|
|
1703
1703
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
1704
1704
|
} catch {
|
|
1705
1705
|
return [];
|
|
1706
1706
|
}
|
|
1707
1707
|
}
|
|
1708
1708
|
function deleteSession(name) {
|
|
1709
|
-
const
|
|
1709
|
+
const path5 = sessionPath(name);
|
|
1710
1710
|
try {
|
|
1711
|
-
unlinkSync(
|
|
1712
|
-
const sidecar =
|
|
1711
|
+
unlinkSync(path5);
|
|
1712
|
+
const sidecar = path5.replace(/\.jsonl$/, ".pending.json");
|
|
1713
1713
|
try {
|
|
1714
1714
|
unlinkSync(sidecar);
|
|
1715
1715
|
} catch {
|
|
@@ -1720,19 +1720,19 @@ function deleteSession(name) {
|
|
|
1720
1720
|
}
|
|
1721
1721
|
}
|
|
1722
1722
|
function rewriteSession(name, messages) {
|
|
1723
|
-
const
|
|
1724
|
-
mkdirSync2(dirname3(
|
|
1723
|
+
const path5 = sessionPath(name);
|
|
1724
|
+
mkdirSync2(dirname3(path5), { recursive: true });
|
|
1725
1725
|
const body = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
1726
|
-
writeFileSync2(
|
|
1726
|
+
writeFileSync2(path5, body ? `${body}
|
|
1727
1727
|
` : "", "utf8");
|
|
1728
1728
|
try {
|
|
1729
|
-
chmodSync2(
|
|
1729
|
+
chmodSync2(path5, 384);
|
|
1730
1730
|
} catch {
|
|
1731
1731
|
}
|
|
1732
1732
|
}
|
|
1733
|
-
function countLines(
|
|
1733
|
+
function countLines(path5) {
|
|
1734
1734
|
try {
|
|
1735
|
-
const raw = readFileSync4(
|
|
1735
|
+
const raw = readFileSync4(path5, "utf8");
|
|
1736
1736
|
return raw.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
1737
1737
|
} catch {
|
|
1738
1738
|
return 0;
|
|
@@ -1788,27 +1788,27 @@ var SessionStats = class {
|
|
|
1788
1788
|
return stats2;
|
|
1789
1789
|
}
|
|
1790
1790
|
get totalCost() {
|
|
1791
|
-
return this.turns.reduce((sum,
|
|
1791
|
+
return this.turns.reduce((sum, t2) => sum + t2.cost, 0);
|
|
1792
1792
|
}
|
|
1793
1793
|
get totalClaudeEquivalent() {
|
|
1794
|
-
return this.turns.reduce((sum,
|
|
1794
|
+
return this.turns.reduce((sum, t2) => sum + claudeEquivalentCost(t2.usage), 0);
|
|
1795
1795
|
}
|
|
1796
1796
|
get savingsVsClaude() {
|
|
1797
1797
|
const c = this.totalClaudeEquivalent;
|
|
1798
1798
|
return c > 0 ? 1 - this.totalCost / c : 0;
|
|
1799
1799
|
}
|
|
1800
1800
|
get totalInputCost() {
|
|
1801
|
-
return this.turns.reduce((sum,
|
|
1801
|
+
return this.turns.reduce((sum, t2) => sum + inputCostUsd(t2.model, t2.usage), 0);
|
|
1802
1802
|
}
|
|
1803
1803
|
get totalOutputCost() {
|
|
1804
|
-
return this.turns.reduce((sum,
|
|
1804
|
+
return this.turns.reduce((sum, t2) => sum + outputCostUsd(t2.model, t2.usage), 0);
|
|
1805
1805
|
}
|
|
1806
1806
|
get aggregateCacheHitRatio() {
|
|
1807
1807
|
let hit = 0;
|
|
1808
1808
|
let miss = 0;
|
|
1809
|
-
for (const
|
|
1810
|
-
hit +=
|
|
1811
|
-
miss +=
|
|
1809
|
+
for (const t2 of this.turns) {
|
|
1810
|
+
hit += t2.usage.promptCacheHitTokens;
|
|
1811
|
+
miss += t2.usage.promptCacheMissTokens;
|
|
1812
1812
|
}
|
|
1813
1813
|
const denom = hit + miss;
|
|
1814
1814
|
return denom > 0 ? hit / denom : 0;
|
|
@@ -2198,13 +2198,13 @@ var CacheFirstLoop = class {
|
|
|
2198
2198
|
* with `<`.
|
|
2199
2199
|
*/
|
|
2200
2200
|
looksLikePartialEscalationMarker(buf) {
|
|
2201
|
-
const
|
|
2202
|
-
if (
|
|
2203
|
-
if (
|
|
2204
|
-
return NEEDS_PRO_MARKER_PREFIX.startsWith(
|
|
2201
|
+
const t2 = buf.trimStart();
|
|
2202
|
+
if (t2.length === 0) return true;
|
|
2203
|
+
if (t2.length <= NEEDS_PRO_MARKER_PREFIX.length) {
|
|
2204
|
+
return NEEDS_PRO_MARKER_PREFIX.startsWith(t2);
|
|
2205
2205
|
}
|
|
2206
|
-
if (!
|
|
2207
|
-
const rest =
|
|
2206
|
+
if (!t2.startsWith(NEEDS_PRO_MARKER_PREFIX)) return false;
|
|
2207
|
+
const rest = t2.slice(NEEDS_PRO_MARKER_PREFIX.length);
|
|
2208
2208
|
if (rest[0] !== ">" && rest[0] !== ":") return false;
|
|
2209
2209
|
return true;
|
|
2210
2210
|
}
|
|
@@ -2312,7 +2312,9 @@ var CacheFirstLoop = class {
|
|
|
2312
2312
|
this._proArmedForNextTurn = false;
|
|
2313
2313
|
armedConsumed = true;
|
|
2314
2314
|
}
|
|
2315
|
+
const carryAbort = this._turnAbort.signal.aborted;
|
|
2315
2316
|
this._turnAbort = new AbortController();
|
|
2317
|
+
if (carryAbort) this._turnAbort.abort();
|
|
2316
2318
|
const signal = this._turnAbort.signal;
|
|
2317
2319
|
if (armedConsumed) {
|
|
2318
2320
|
yield {
|
|
@@ -2435,8 +2437,8 @@ var CacheFirstLoop = class {
|
|
|
2435
2437
|
}
|
|
2436
2438
|
);
|
|
2437
2439
|
for (let k = 0; k < budget; k++) {
|
|
2438
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2439
|
-
waiter =
|
|
2440
|
+
const sample = queue.shift() ?? await new Promise((resolve9) => {
|
|
2441
|
+
waiter = resolve9;
|
|
2440
2442
|
});
|
|
2441
2443
|
yield {
|
|
2442
2444
|
turn: this._turn,
|
|
@@ -3259,7 +3261,7 @@ function rankPickerCandidates(files, query, limitOrOpts) {
|
|
|
3259
3261
|
var AT_MENTION_PATTERN = /(?<=^|\s)@([a-zA-Z0-9_./\\-]+)/g;
|
|
3260
3262
|
function expandAtMentions(text, rootDir, opts = {}) {
|
|
3261
3263
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
3262
|
-
const
|
|
3264
|
+
const fs6 = opts.fs ?? defaultFs;
|
|
3263
3265
|
const root = resolve(rootDir);
|
|
3264
3266
|
const seen = /* @__PURE__ */ new Map();
|
|
3265
3267
|
const expansions = [];
|
|
@@ -3269,7 +3271,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
3269
3271
|
if (!cleaned) continue;
|
|
3270
3272
|
const token = `@${cleaned}`;
|
|
3271
3273
|
if (seen.has(token)) continue;
|
|
3272
|
-
const expansion = resolveMention(cleaned, root, maxBytes,
|
|
3274
|
+
const expansion = resolveMention(cleaned, root, maxBytes, fs6);
|
|
3273
3275
|
seen.set(token, expansion);
|
|
3274
3276
|
expansions.push(expansion);
|
|
3275
3277
|
}
|
|
@@ -3277,7 +3279,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
3277
3279
|
const blocks = [];
|
|
3278
3280
|
for (const ex of expansions) {
|
|
3279
3281
|
if (ex.ok) {
|
|
3280
|
-
const content = readSafe(root, ex.path,
|
|
3282
|
+
const content = readSafe(root, ex.path, fs6);
|
|
3281
3283
|
blocks.push(`<file path="${ex.path}">
|
|
3282
3284
|
${content}
|
|
3283
3285
|
</file>`);
|
|
@@ -3291,7 +3293,7 @@ ${content}
|
|
|
3291
3293
|
${blocks.join("\n\n")}`;
|
|
3292
3294
|
return { text: augmented, expansions };
|
|
3293
3295
|
}
|
|
3294
|
-
function resolveMention(rawPath, root, maxBytes,
|
|
3296
|
+
function resolveMention(rawPath, root, maxBytes, fs6) {
|
|
3295
3297
|
if (isAbsolute(rawPath)) {
|
|
3296
3298
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
3297
3299
|
}
|
|
@@ -3300,22 +3302,22 @@ function resolveMention(rawPath, root, maxBytes, fs2) {
|
|
|
3300
3302
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
3301
3303
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
3302
3304
|
}
|
|
3303
|
-
if (!
|
|
3305
|
+
if (!fs6.exists(resolved)) {
|
|
3304
3306
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "missing" };
|
|
3305
3307
|
}
|
|
3306
|
-
if (!
|
|
3308
|
+
if (!fs6.isFile(resolved)) {
|
|
3307
3309
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
3308
3310
|
}
|
|
3309
|
-
const size =
|
|
3311
|
+
const size = fs6.size(resolved);
|
|
3310
3312
|
if (size > maxBytes) {
|
|
3311
3313
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "too-large", bytes: size };
|
|
3312
3314
|
}
|
|
3313
3315
|
return { token: `@${rawPath}`, path: rawPath, ok: true, bytes: size };
|
|
3314
3316
|
}
|
|
3315
|
-
function readSafe(root, rawPath,
|
|
3317
|
+
function readSafe(root, rawPath, fs6) {
|
|
3316
3318
|
const resolved = resolve(root, rawPath);
|
|
3317
3319
|
try {
|
|
3318
|
-
return
|
|
3320
|
+
return fs6.read(resolved);
|
|
3319
3321
|
} catch {
|
|
3320
3322
|
return "(read failed)";
|
|
3321
3323
|
}
|
|
@@ -3513,9 +3515,9 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3513
3515
|
".pyo"
|
|
3514
3516
|
]);
|
|
3515
3517
|
function isLikelyBinaryByName(name) {
|
|
3516
|
-
const
|
|
3517
|
-
if (
|
|
3518
|
-
return BINARY_EXTENSIONS.has(name.slice(
|
|
3518
|
+
const dot2 = name.lastIndexOf(".");
|
|
3519
|
+
if (dot2 < 0) return false;
|
|
3520
|
+
return BINARY_EXTENSIONS.has(name.slice(dot2).toLowerCase());
|
|
3519
3521
|
}
|
|
3520
3522
|
function registerFilesystemTools(registry, opts) {
|
|
3521
3523
|
const rootDir = pathMod.resolve(opts.rootDir);
|
|
@@ -4077,7 +4079,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
4077
4079
|
});
|
|
4078
4080
|
}
|
|
4079
4081
|
try {
|
|
4080
|
-
const
|
|
4082
|
+
const path5 = store.write({
|
|
4081
4083
|
name: args.name,
|
|
4082
4084
|
type: args.type,
|
|
4083
4085
|
scope: args.scope,
|
|
@@ -4090,7 +4092,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
4090
4092
|
"",
|
|
4091
4093
|
"TREAT THIS AS ESTABLISHED FACT for the rest of this session.",
|
|
4092
4094
|
"The user just told you \u2014 don't re-explore the filesystem to re-derive it.",
|
|
4093
|
-
`(Saved to ${
|
|
4095
|
+
`(Saved to ${path5}; pins into the system prompt on next /new or launch.)`
|
|
4094
4096
|
].join("\n");
|
|
4095
4097
|
} catch (err) {
|
|
4096
4098
|
return JSON.stringify({ error: `remember failed: ${err.message}` });
|
|
@@ -4568,7 +4570,11 @@ async function spawnSubagent(opts) {
|
|
|
4568
4570
|
stream: false
|
|
4569
4571
|
});
|
|
4570
4572
|
const onParentAbort = () => childLoop.abort();
|
|
4571
|
-
opts.parentSignal?.
|
|
4573
|
+
if (opts.parentSignal?.aborted) {
|
|
4574
|
+
childLoop.abort();
|
|
4575
|
+
} else {
|
|
4576
|
+
opts.parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
4577
|
+
}
|
|
4572
4578
|
let final = "";
|
|
4573
4579
|
let errorMessage;
|
|
4574
4580
|
let toolIter = 0;
|
|
@@ -4586,7 +4592,11 @@ async function spawnSubagent(opts) {
|
|
|
4586
4592
|
});
|
|
4587
4593
|
}
|
|
4588
4594
|
if (ev.role === "assistant_final") {
|
|
4589
|
-
|
|
4595
|
+
if (ev.forcedSummary) {
|
|
4596
|
+
errorMessage = ev.content?.trim() || "subagent ended without producing an answer";
|
|
4597
|
+
} else {
|
|
4598
|
+
final = ev.content ?? "";
|
|
4599
|
+
}
|
|
4590
4600
|
}
|
|
4591
4601
|
if (ev.role === "error") {
|
|
4592
4602
|
errorMessage = ev.error ?? "subagent error";
|
|
@@ -4635,12 +4645,12 @@ async function spawnSubagent(opts) {
|
|
|
4635
4645
|
}
|
|
4636
4646
|
function aggregateChildUsage(loop2) {
|
|
4637
4647
|
const agg = new Usage();
|
|
4638
|
-
for (const
|
|
4639
|
-
agg.promptTokens +=
|
|
4640
|
-
agg.completionTokens +=
|
|
4641
|
-
agg.totalTokens +=
|
|
4642
|
-
agg.promptCacheHitTokens +=
|
|
4643
|
-
agg.promptCacheMissTokens +=
|
|
4648
|
+
for (const t2 of loop2.stats.turns) {
|
|
4649
|
+
agg.promptTokens += t2.usage.promptTokens;
|
|
4650
|
+
agg.completionTokens += t2.usage.completionTokens;
|
|
4651
|
+
agg.totalTokens += t2.usage.totalTokens;
|
|
4652
|
+
agg.promptCacheHitTokens += t2.usage.promptCacheHitTokens;
|
|
4653
|
+
agg.promptCacheMissTokens += t2.usage.promptCacheMissTokens;
|
|
4644
4654
|
}
|
|
4645
4655
|
return agg;
|
|
4646
4656
|
}
|
|
@@ -5158,7 +5168,7 @@ async function runCommand(cmd, opts) {
|
|
|
5158
5168
|
};
|
|
5159
5169
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
5160
5170
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
5161
|
-
return await new Promise((
|
|
5171
|
+
return await new Promise((resolve9, reject) => {
|
|
5162
5172
|
let child;
|
|
5163
5173
|
try {
|
|
5164
5174
|
child = spawn3(bin, args, effectiveSpawnOpts);
|
|
@@ -5191,7 +5201,7 @@ async function runCommand(cmd, opts) {
|
|
|
5191
5201
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
5192
5202
|
|
|
5193
5203
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
5194
|
-
|
|
5204
|
+
resolve9({ exitCode: code, output, timedOut });
|
|
5195
5205
|
});
|
|
5196
5206
|
});
|
|
5197
5207
|
}
|
|
@@ -5664,10 +5674,10 @@ ${i + 1}. ${r.title}`);
|
|
|
5664
5674
|
// src/env.ts
|
|
5665
5675
|
import { readFileSync as readFileSync6 } from "fs";
|
|
5666
5676
|
import { resolve as resolve5 } from "path";
|
|
5667
|
-
function loadDotenv(
|
|
5677
|
+
function loadDotenv(path5 = ".env") {
|
|
5668
5678
|
let raw;
|
|
5669
5679
|
try {
|
|
5670
|
-
raw = readFileSync6(resolve5(process.cwd(),
|
|
5680
|
+
raw = readFileSync6(resolve5(process.cwd(), path5), "utf8");
|
|
5671
5681
|
} catch {
|
|
5672
5682
|
return;
|
|
5673
5683
|
}
|
|
@@ -5731,13 +5741,13 @@ function writeMeta(stream, meta) {
|
|
|
5731
5741
|
stream.write(`${JSON.stringify(line)}
|
|
5732
5742
|
`);
|
|
5733
5743
|
}
|
|
5734
|
-
function openTranscriptFile(
|
|
5735
|
-
const stream = createWriteStream(
|
|
5744
|
+
function openTranscriptFile(path5, meta) {
|
|
5745
|
+
const stream = createWriteStream(path5, { flags: "a" });
|
|
5736
5746
|
writeMeta(stream, meta);
|
|
5737
5747
|
return stream;
|
|
5738
5748
|
}
|
|
5739
|
-
function readTranscript(
|
|
5740
|
-
const raw = readFileSync7(
|
|
5749
|
+
function readTranscript(path5) {
|
|
5750
|
+
const raw = readFileSync7(path5, "utf8");
|
|
5741
5751
|
return parseTranscript(raw);
|
|
5742
5752
|
}
|
|
5743
5753
|
function isPlanStateEmptyShape(s) {
|
|
@@ -5786,8 +5796,8 @@ function computeCumulativeStats(pages, upToIdx) {
|
|
|
5786
5796
|
}
|
|
5787
5797
|
return computeReplayStats(flat);
|
|
5788
5798
|
}
|
|
5789
|
-
function replayFromFile(
|
|
5790
|
-
const parsed = readTranscript(
|
|
5799
|
+
function replayFromFile(path5) {
|
|
5800
|
+
const parsed = readTranscript(path5);
|
|
5791
5801
|
return { parsed, stats: computeReplayStats(parsed.records) };
|
|
5792
5802
|
}
|
|
5793
5803
|
function computeReplayStats(records) {
|
|
@@ -5844,15 +5854,15 @@ function computeReplayStats(records) {
|
|
|
5844
5854
|
};
|
|
5845
5855
|
}
|
|
5846
5856
|
function summarizeTurns(turns) {
|
|
5847
|
-
const totalCost = turns.reduce((s,
|
|
5848
|
-
const totalInput = turns.reduce((s,
|
|
5849
|
-
const totalOutput = turns.reduce((s,
|
|
5850
|
-
const totalClaude = turns.reduce((s,
|
|
5857
|
+
const totalCost = turns.reduce((s, t2) => s + t2.cost, 0);
|
|
5858
|
+
const totalInput = turns.reduce((s, t2) => s + inputCostUsd(t2.model, t2.usage), 0);
|
|
5859
|
+
const totalOutput = turns.reduce((s, t2) => s + outputCostUsd(t2.model, t2.usage), 0);
|
|
5860
|
+
const totalClaude = turns.reduce((s, t2) => s + claudeEquivalentCost(t2.usage), 0);
|
|
5851
5861
|
let hit = 0;
|
|
5852
5862
|
let miss = 0;
|
|
5853
|
-
for (const
|
|
5854
|
-
hit +=
|
|
5855
|
-
miss +=
|
|
5863
|
+
for (const t2 of turns) {
|
|
5864
|
+
hit += t2.usage.promptCacheHitTokens;
|
|
5865
|
+
miss += t2.usage.promptCacheMissTokens;
|
|
5856
5866
|
}
|
|
5857
5867
|
const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;
|
|
5858
5868
|
const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;
|
|
@@ -5929,8 +5939,8 @@ function diffTranscripts(a, b) {
|
|
|
5929
5939
|
return { a: aSide, b: bSide, pairs, firstDivergenceTurn };
|
|
5930
5940
|
}
|
|
5931
5941
|
function classifyDivergence(a, b, aTools, bTools) {
|
|
5932
|
-
const aNames = aTools.map((
|
|
5933
|
-
const bNames = bTools.map((
|
|
5942
|
+
const aNames = aTools.map((t2) => t2.tool ?? "").sort();
|
|
5943
|
+
const bNames = bTools.map((t2) => t2.tool ?? "").sort();
|
|
5934
5944
|
if (aNames.join(",") !== bNames.join(",")) {
|
|
5935
5945
|
return `tool calls differ: A=[${aNames.join(",") || "\u2014"}] B=[${bNames.join(",") || "\u2014"}]`;
|
|
5936
5946
|
}
|
|
@@ -5960,7 +5970,7 @@ function tokenOverlap(a, b) {
|
|
|
5960
5970
|
const tb = new Set(b.toLowerCase().split(/\s+/).filter(Boolean));
|
|
5961
5971
|
if (ta.size === 0 && tb.size === 0) return 1;
|
|
5962
5972
|
let shared = 0;
|
|
5963
|
-
for (const
|
|
5973
|
+
for (const t2 of ta) if (tb.has(t2)) shared++;
|
|
5964
5974
|
return 2 * shared / (ta.size + tb.size);
|
|
5965
5975
|
}
|
|
5966
5976
|
function levenshtein(a, b) {
|
|
@@ -6153,8 +6163,8 @@ function renderMarkdown(report) {
|
|
|
6153
6163
|
out.push(`| turn | kind | ${a.label} tool calls | ${b.label} tool calls | note |`);
|
|
6154
6164
|
out.push("|---:|:---:|---|---|---|");
|
|
6155
6165
|
for (const p of report.pairs) {
|
|
6156
|
-
const aTools = p.aTools.map((
|
|
6157
|
-
const bTools = p.bTools.map((
|
|
6166
|
+
const aTools = p.aTools.map((t2) => t2.tool).filter(Boolean).join(", ") || "\u2014";
|
|
6167
|
+
const bTools = p.bTools.map((t2) => t2.tool).filter(Boolean).join(", ") || "\u2014";
|
|
6158
6168
|
out.push(`| ${p.turn} | ${p.kind} | ${aTools} | ${bTools} | ${p.divergenceNote ?? ""} |`);
|
|
6159
6169
|
}
|
|
6160
6170
|
out.push("");
|
|
@@ -6381,7 +6391,7 @@ var McpClient = class {
|
|
|
6381
6391
|
const id = this.nextId++;
|
|
6382
6392
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
6383
6393
|
let abortHandler = null;
|
|
6384
|
-
const promise = new Promise((
|
|
6394
|
+
const promise = new Promise((resolve9, reject) => {
|
|
6385
6395
|
const timeout = setTimeout(() => {
|
|
6386
6396
|
this.pending.delete(id);
|
|
6387
6397
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -6390,7 +6400,7 @@ var McpClient = class {
|
|
|
6390
6400
|
);
|
|
6391
6401
|
}, this.requestTimeoutMs);
|
|
6392
6402
|
this.pending.set(id, {
|
|
6393
|
-
resolve:
|
|
6403
|
+
resolve: resolve9,
|
|
6394
6404
|
reject,
|
|
6395
6405
|
timeout
|
|
6396
6406
|
});
|
|
@@ -6513,12 +6523,12 @@ var StdioTransport = class {
|
|
|
6513
6523
|
}
|
|
6514
6524
|
async send(message) {
|
|
6515
6525
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
6516
|
-
return new Promise((
|
|
6526
|
+
return new Promise((resolve9, reject) => {
|
|
6517
6527
|
const line = `${JSON.stringify(message)}
|
|
6518
6528
|
`;
|
|
6519
6529
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
6520
6530
|
if (err) reject(err);
|
|
6521
|
-
else
|
|
6531
|
+
else resolve9();
|
|
6522
6532
|
});
|
|
6523
6533
|
});
|
|
6524
6534
|
}
|
|
@@ -6529,8 +6539,8 @@ var StdioTransport = class {
|
|
|
6529
6539
|
continue;
|
|
6530
6540
|
}
|
|
6531
6541
|
if (this.closed) return;
|
|
6532
|
-
const next = await new Promise((
|
|
6533
|
-
this.waiters.push(
|
|
6542
|
+
const next = await new Promise((resolve9) => {
|
|
6543
|
+
this.waiters.push(resolve9);
|
|
6534
6544
|
});
|
|
6535
6545
|
if (next === null) return;
|
|
6536
6546
|
yield next;
|
|
@@ -6596,8 +6606,8 @@ var SseTransport = class {
|
|
|
6596
6606
|
constructor(opts) {
|
|
6597
6607
|
this.url = opts.url;
|
|
6598
6608
|
this.headers = opts.headers ?? {};
|
|
6599
|
-
this.endpointReady = new Promise((
|
|
6600
|
-
this.resolveEndpoint =
|
|
6609
|
+
this.endpointReady = new Promise((resolve9, reject) => {
|
|
6610
|
+
this.resolveEndpoint = resolve9;
|
|
6601
6611
|
this.rejectEndpoint = reject;
|
|
6602
6612
|
});
|
|
6603
6613
|
this.endpointReady.catch(() => void 0);
|
|
@@ -6624,8 +6634,8 @@ var SseTransport = class {
|
|
|
6624
6634
|
continue;
|
|
6625
6635
|
}
|
|
6626
6636
|
if (this.closed) return;
|
|
6627
|
-
const next = await new Promise((
|
|
6628
|
-
this.waiters.push(
|
|
6637
|
+
const next = await new Promise((resolve9) => {
|
|
6638
|
+
this.waiters.push(resolve9);
|
|
6629
6639
|
});
|
|
6630
6640
|
if (next === null) return;
|
|
6631
6641
|
yield next;
|
|
@@ -6893,8 +6903,8 @@ function applyEditBlock(block, rootDir) {
|
|
|
6893
6903
|
function applyEditBlocks(blocks, rootDir) {
|
|
6894
6904
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
6895
6905
|
}
|
|
6896
|
-
function toWholeFileEditBlock(
|
|
6897
|
-
const abs = resolve6(rootDir,
|
|
6906
|
+
function toWholeFileEditBlock(path5, content, rootDir) {
|
|
6907
|
+
const abs = resolve6(rootDir, path5);
|
|
6898
6908
|
let search = "";
|
|
6899
6909
|
if (existsSync6(abs)) {
|
|
6900
6910
|
try {
|
|
@@ -6903,7 +6913,7 @@ function toWholeFileEditBlock(path, content, rootDir) {
|
|
|
6903
6913
|
search = "";
|
|
6904
6914
|
}
|
|
6905
6915
|
}
|
|
6906
|
-
return { path, search, replace: content, offset: 0 };
|
|
6916
|
+
return { path: path5, search, replace: content, offset: 0 };
|
|
6907
6917
|
}
|
|
6908
6918
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
6909
6919
|
const absRoot = resolve6(rootDir);
|
|
@@ -7082,20 +7092,20 @@ function appendUsage(input) {
|
|
|
7082
7092
|
};
|
|
7083
7093
|
if (input.kind === "subagent") record.kind = "subagent";
|
|
7084
7094
|
if (input.subagent) record.subagent = input.subagent;
|
|
7085
|
-
const
|
|
7095
|
+
const path5 = input.path ?? defaultUsageLogPath();
|
|
7086
7096
|
try {
|
|
7087
|
-
mkdirSync5(dirname7(
|
|
7088
|
-
appendFileSync2(
|
|
7097
|
+
mkdirSync5(dirname7(path5), { recursive: true });
|
|
7098
|
+
appendFileSync2(path5, `${JSON.stringify(record)}
|
|
7089
7099
|
`, "utf8");
|
|
7090
7100
|
} catch {
|
|
7091
7101
|
}
|
|
7092
7102
|
return record;
|
|
7093
7103
|
}
|
|
7094
|
-
function readUsageLog(
|
|
7095
|
-
if (!existsSync8(
|
|
7104
|
+
function readUsageLog(path5 = defaultUsageLogPath()) {
|
|
7105
|
+
if (!existsSync8(path5)) return [];
|
|
7096
7106
|
let raw;
|
|
7097
7107
|
try {
|
|
7098
|
-
raw = readFileSync10(
|
|
7108
|
+
raw = readFileSync10(path5, "utf8");
|
|
7099
7109
|
} catch {
|
|
7100
7110
|
return [];
|
|
7101
7111
|
}
|
|
@@ -7199,10 +7209,10 @@ function aggregateUsage(records, opts = {}) {
|
|
|
7199
7209
|
subagents
|
|
7200
7210
|
};
|
|
7201
7211
|
}
|
|
7202
|
-
function formatLogSize(
|
|
7203
|
-
if (!existsSync8(
|
|
7212
|
+
function formatLogSize(path5 = defaultUsageLogPath()) {
|
|
7213
|
+
if (!existsSync8(path5)) return "";
|
|
7204
7214
|
try {
|
|
7205
|
-
const s = statSync4(
|
|
7215
|
+
const s = statSync4(path5);
|
|
7206
7216
|
const bytes = s.size;
|
|
7207
7217
|
if (bytes < 1024) return `${bytes} B`;
|
|
7208
7218
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -7229,24 +7239,24 @@ function pendingEditsPath(sessionName) {
|
|
|
7229
7239
|
}
|
|
7230
7240
|
function savePendingEdits(sessionName, blocks) {
|
|
7231
7241
|
if (!sessionName) return;
|
|
7232
|
-
const
|
|
7242
|
+
const path5 = pendingEditsPath(sessionName);
|
|
7233
7243
|
try {
|
|
7234
7244
|
if (blocks.length === 0) {
|
|
7235
|
-
if (existsSync9(
|
|
7245
|
+
if (existsSync9(path5)) unlinkSync3(path5);
|
|
7236
7246
|
return;
|
|
7237
7247
|
}
|
|
7238
|
-
mkdirSync6(dirname8(
|
|
7239
|
-
writeFileSync5(
|
|
7248
|
+
mkdirSync6(dirname8(path5), { recursive: true });
|
|
7249
|
+
writeFileSync5(path5, JSON.stringify(blocks, null, 2), "utf8");
|
|
7240
7250
|
} catch {
|
|
7241
7251
|
}
|
|
7242
7252
|
}
|
|
7243
7253
|
function loadPendingEdits(sessionName) {
|
|
7244
7254
|
if (!sessionName) return null;
|
|
7245
|
-
const
|
|
7246
|
-
if (!existsSync9(
|
|
7255
|
+
const path5 = pendingEditsPath(sessionName);
|
|
7256
|
+
if (!existsSync9(path5)) return null;
|
|
7247
7257
|
let raw;
|
|
7248
7258
|
try {
|
|
7249
|
-
raw = readFileSync11(
|
|
7259
|
+
raw = readFileSync11(path5, "utf8");
|
|
7250
7260
|
} catch {
|
|
7251
7261
|
return null;
|
|
7252
7262
|
}
|
|
@@ -7266,9 +7276,9 @@ function loadPendingEdits(sessionName) {
|
|
|
7266
7276
|
}
|
|
7267
7277
|
function clearPendingEdits(sessionName) {
|
|
7268
7278
|
if (!sessionName) return;
|
|
7269
|
-
const
|
|
7279
|
+
const path5 = pendingEditsPath(sessionName);
|
|
7270
7280
|
try {
|
|
7271
|
-
if (existsSync9(
|
|
7281
|
+
if (existsSync9(path5)) unlinkSync3(path5);
|
|
7272
7282
|
} catch {
|
|
7273
7283
|
}
|
|
7274
7284
|
}
|
|
@@ -7289,10 +7299,10 @@ function planStatePath(sessionName) {
|
|
|
7289
7299
|
return join10(sessionsDir(), `${sanitizeName(sessionName)}.plan.json`);
|
|
7290
7300
|
}
|
|
7291
7301
|
function loadPlanState(sessionName) {
|
|
7292
|
-
const
|
|
7293
|
-
if (!existsSync10(
|
|
7302
|
+
const path5 = planStatePath(sessionName);
|
|
7303
|
+
if (!existsSync10(path5)) return null;
|
|
7294
7304
|
try {
|
|
7295
|
-
const raw = readFileSync12(
|
|
7305
|
+
const raw = readFileSync12(path5, "utf8");
|
|
7296
7306
|
const parsed = JSON.parse(raw);
|
|
7297
7307
|
if (!parsed || typeof parsed !== "object") return null;
|
|
7298
7308
|
if (parsed.version !== 1) return null;
|
|
@@ -7328,9 +7338,9 @@ function loadPlanState(sessionName) {
|
|
|
7328
7338
|
}
|
|
7329
7339
|
}
|
|
7330
7340
|
function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
7331
|
-
const
|
|
7341
|
+
const path5 = planStatePath(sessionName);
|
|
7332
7342
|
try {
|
|
7333
|
-
mkdirSync7(dirname9(
|
|
7343
|
+
mkdirSync7(dirname9(path5), { recursive: true });
|
|
7334
7344
|
const state = {
|
|
7335
7345
|
version: 1,
|
|
7336
7346
|
steps,
|
|
@@ -7339,7 +7349,7 @@ function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
|
7339
7349
|
};
|
|
7340
7350
|
if (extras?.body) state.body = extras.body;
|
|
7341
7351
|
if (extras?.summary) state.summary = extras.summary;
|
|
7342
|
-
writeFileSync6(
|
|
7352
|
+
writeFileSync6(path5, `${JSON.stringify(state, null, 2)}
|
|
7343
7353
|
`, "utf8");
|
|
7344
7354
|
} catch (err) {
|
|
7345
7355
|
process.stderr.write(
|
|
@@ -7349,9 +7359,9 @@ function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
|
7349
7359
|
}
|
|
7350
7360
|
}
|
|
7351
7361
|
function clearPlanState(sessionName) {
|
|
7352
|
-
const
|
|
7362
|
+
const path5 = planStatePath(sessionName);
|
|
7353
7363
|
try {
|
|
7354
|
-
if (existsSync10(
|
|
7364
|
+
if (existsSync10(path5)) unlinkSync4(path5);
|
|
7355
7365
|
} catch {
|
|
7356
7366
|
}
|
|
7357
7367
|
}
|
|
@@ -7419,9 +7429,9 @@ function listPlanArchives(sessionName) {
|
|
|
7419
7429
|
return summaries;
|
|
7420
7430
|
}
|
|
7421
7431
|
function relativeTime(updatedAt, now = Date.now()) {
|
|
7422
|
-
const
|
|
7423
|
-
if (Number.isNaN(
|
|
7424
|
-
const diffMs = Math.max(0, now -
|
|
7432
|
+
const t2 = Date.parse(updatedAt);
|
|
7433
|
+
if (Number.isNaN(t2)) return updatedAt;
|
|
7434
|
+
const diffMs = Math.max(0, now - t2);
|
|
7425
7435
|
const sec = Math.floor(diffMs / 1e3);
|
|
7426
7436
|
if (sec < 60) return `${sec}s ago`;
|
|
7427
7437
|
const min = Math.floor(sec / 60);
|
|
@@ -7466,7 +7476,7 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
7466
7476
|
}
|
|
7467
7477
|
const stripped = raw.replace(/\[[^\]]*\]/g, " ").trim();
|
|
7468
7478
|
const tokens = stripped.split(/\s+/).filter(Boolean);
|
|
7469
|
-
const name = tokens.find((
|
|
7479
|
+
const name = tokens.find((t2) => /^[a-zA-Z0-9]/.test(t2)) ?? "";
|
|
7470
7480
|
if (!name) {
|
|
7471
7481
|
return JSON.stringify({
|
|
7472
7482
|
error: "run_skill requires a 'name' argument",
|
|
@@ -7529,12 +7539,12 @@ function AtMentionSuggestions({
|
|
|
7529
7539
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
7530
7540
|
const hiddenAbove = windowStart;
|
|
7531
7541
|
const hiddenBelow = total - windowStart - shown.length;
|
|
7532
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingX: 1, marginTop: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((
|
|
7542
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingX: 1, marginTop: 1 }, hiddenAbove > 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " \u2191 ", hiddenAbove, " more above") : null, shown.map((path5, i) => /* @__PURE__ */ React.createElement(FileRow, { key: path5, path: path5, isSelected: windowStart + i === selectedIndex })), hiddenBelow > 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " \u2193 ", hiddenBelow, " more below") : null, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " [\u2191\u2193] navigate \xB7 [Tab]/[Enter] pick \xB7 file content inlined on send"));
|
|
7533
7543
|
}
|
|
7534
|
-
function FileRow({ path, isSelected }) {
|
|
7535
|
-
const slash =
|
|
7536
|
-
const dir = slash >= 0 ? `${
|
|
7537
|
-
const base = slash >= 0 ?
|
|
7544
|
+
function FileRow({ path: path5, isSelected }) {
|
|
7545
|
+
const slash = path5.lastIndexOf("/");
|
|
7546
|
+
const dir = slash >= 0 ? `${path5.slice(0, slash)}/` : "";
|
|
7547
|
+
const base = slash >= 0 ? path5.slice(slash + 1) : path5;
|
|
7538
7548
|
if (isSelected) {
|
|
7539
7549
|
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { backgroundColor: "#67e8f9", color: "black", bold: true }, ` \u25B8 ${base}${dir ? ` ${dir}` : ""} `));
|
|
7540
7550
|
}
|
|
@@ -7555,8 +7565,8 @@ function ModalCard({
|
|
|
7555
7565
|
icon,
|
|
7556
7566
|
children
|
|
7557
7567
|
}) {
|
|
7558
|
-
const { stdout:
|
|
7559
|
-
const cols =
|
|
7568
|
+
const { stdout: stdout3 } = useStdout();
|
|
7569
|
+
const cols = stdout3?.columns ?? 80;
|
|
7560
7570
|
const ruleWidth = Math.min(80, Math.max(28, cols - 4));
|
|
7561
7571
|
const titleText = icon ? ` ${icon} ${title} ` : ` ${title} `;
|
|
7562
7572
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color: accent }, "\u2594".repeat(ruleWidth))), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { backgroundColor: accent, color: "black", bold: true }, titleText), subtitle ? /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, ` ${subtitle}`) : null), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1, flexDirection: "column" }, children), /* @__PURE__ */ React2.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: accent, dimColor: true }, "\u2581".repeat(ruleWidth))));
|
|
@@ -8119,8 +8129,8 @@ function capLines(lines, maxLines, indent) {
|
|
|
8119
8129
|
var MODAL_OVERHEAD_ROWS = 18;
|
|
8120
8130
|
var MIN_DIFF_ROWS = 8;
|
|
8121
8131
|
function EditConfirm({ block, onChoose }) {
|
|
8122
|
-
const { stdout:
|
|
8123
|
-
const rows =
|
|
8132
|
+
const { stdout: stdout3 } = useStdout2();
|
|
8133
|
+
const rows = stdout3?.rows ?? 40;
|
|
8124
8134
|
const budget = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
|
|
8125
8135
|
const allLines = useMemo(
|
|
8126
8136
|
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
@@ -9110,10 +9120,10 @@ function gradientCells(width, glyph = GLYPH.block) {
|
|
|
9110
9120
|
if (width <= 0) return cells;
|
|
9111
9121
|
const last = GRADIENT.length - 1;
|
|
9112
9122
|
for (let i = 0; i < width; i++) {
|
|
9113
|
-
const
|
|
9114
|
-
const lo = Math.floor(
|
|
9123
|
+
const t2 = width === 1 ? 0 : i * last / (width - 1);
|
|
9124
|
+
const lo = Math.floor(t2);
|
|
9115
9125
|
const hi = Math.min(last, lo + 1);
|
|
9116
|
-
const color =
|
|
9126
|
+
const color = t2 - lo < 0.5 ? GRADIENT[lo] : GRADIENT[hi];
|
|
9117
9127
|
cells.push({ ch: glyph, color });
|
|
9118
9128
|
}
|
|
9119
9129
|
return cells;
|
|
@@ -9127,7 +9137,7 @@ function TickerProvider({ children, disabled }) {
|
|
|
9127
9137
|
const [tick, setTick] = useState3(0);
|
|
9128
9138
|
useEffect2(() => {
|
|
9129
9139
|
if (disabled) return;
|
|
9130
|
-
const id = setInterval(() => setTick((
|
|
9140
|
+
const id = setInterval(() => setTick((t2) => t2 + 1), TICK_MS);
|
|
9131
9141
|
return () => clearInterval(id);
|
|
9132
9142
|
}, [disabled]);
|
|
9133
9143
|
return /* @__PURE__ */ React10.createElement(TickContext.Provider, { value: tick }, children);
|
|
@@ -9416,8 +9426,8 @@ var EventRow = React11.memo(function EventRow2({
|
|
|
9416
9426
|
return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text8, null, event.text));
|
|
9417
9427
|
});
|
|
9418
9428
|
function TurnSeparator() {
|
|
9419
|
-
const { stdout:
|
|
9420
|
-
const cols =
|
|
9429
|
+
const { stdout: stdout3 } = useStdout3();
|
|
9430
|
+
const cols = stdout3?.columns ?? 80;
|
|
9421
9431
|
const width = Math.max(16, cols - 2);
|
|
9422
9432
|
const sideWidth = Math.max(2, Math.floor((width - 5) / 2));
|
|
9423
9433
|
const leftCells = gradientCells(sideWidth, "\u2500");
|
|
@@ -9448,10 +9458,10 @@ function EditFileDiff({ text }) {
|
|
|
9448
9458
|
function BranchBlock({ branch: branch2 }) {
|
|
9449
9459
|
return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text8, { backgroundColor: "#93c5fd", color: "black", bold: true }, ` \u2387 BRANCH \xD7${branch2.budget} `), /* @__PURE__ */ React11.createElement(Text8, null, " "), /* @__PURE__ */ React11.createElement(Text8, { color: "#93c5fd" }, "picked "), /* @__PURE__ */ React11.createElement(Text8, { color: "#93c5fd", bold: true }, "#", branch2.chosenIndex)), /* @__PURE__ */ React11.createElement(Box9, { paddingLeft: 2, marginTop: 1 }, branch2.uncertainties.map((u, i) => {
|
|
9450
9460
|
const chosen = i === branch2.chosenIndex;
|
|
9451
|
-
const
|
|
9461
|
+
const t2 = (branch2.temperatures[i] ?? 0).toFixed(1);
|
|
9452
9462
|
return (
|
|
9453
9463
|
// biome-ignore lint/suspicious/noArrayIndexKey: branch index is positional and stable
|
|
9454
|
-
/* @__PURE__ */ React11.createElement(Text8, { key: `b-${i}` }, /* @__PURE__ */ React11.createElement(Text8, { color: chosen ? "#93c5fd" : "#475569", bold: chosen }, chosen ? "\u25B8 " : " "), /* @__PURE__ */ React11.createElement(Text8, { color: chosen ? "#93c5fd" : "#94a3b8", bold: chosen }, `#${i}`), /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, ` T=${
|
|
9464
|
+
/* @__PURE__ */ React11.createElement(Text8, { key: `b-${i}` }, /* @__PURE__ */ React11.createElement(Text8, { color: chosen ? "#93c5fd" : "#475569", bold: chosen }, chosen ? "\u25B8 " : " "), /* @__PURE__ */ React11.createElement(Text8, { color: chosen ? "#93c5fd" : "#94a3b8", bold: chosen }, `#${i}`), /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, ` T=${t2} u=${u} `))
|
|
9455
9465
|
);
|
|
9456
9466
|
})));
|
|
9457
9467
|
}
|
|
@@ -9575,8 +9585,8 @@ function ModeStatusBar({
|
|
|
9575
9585
|
return /* @__PURE__ */ React12.createElement(ModeBarFrame, null, /* @__PURE__ */ React12.createElement(ModePill, { label, bg, flash }), /* @__PURE__ */ React12.createElement(Text9, { dimColor: true }, ` ${mid} \xB7 Shift+Tab to flip`), jobsTag);
|
|
9576
9586
|
}
|
|
9577
9587
|
function ModeBarFrame({ children }) {
|
|
9578
|
-
const { stdout:
|
|
9579
|
-
const cols =
|
|
9588
|
+
const { stdout: stdout3 } = useStdout4();
|
|
9589
|
+
const cols = stdout3?.columns ?? 80;
|
|
9580
9590
|
const ruleWidth = Math.max(20, cols - 2);
|
|
9581
9591
|
return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, /* @__PURE__ */ React12.createElement(Text9, { color: "#475569", dimColor: true }, "\u254C".repeat(ruleWidth))), /* @__PURE__ */ React12.createElement(Box10, { paddingX: 1 }, children));
|
|
9582
9592
|
}
|
|
@@ -9635,26 +9645,26 @@ function summarizeToolArgs(name, args) {
|
|
|
9635
9645
|
return args.length > 80 ? `${args.slice(0, 80)}\u2026` : args;
|
|
9636
9646
|
}
|
|
9637
9647
|
const hasSuffix = (s) => name === s || name.endsWith(`_${s}`);
|
|
9638
|
-
const
|
|
9648
|
+
const path5 = typeof parsed.path === "string" ? parsed.path : void 0;
|
|
9639
9649
|
if (hasSuffix("read_file")) {
|
|
9640
9650
|
const head = typeof parsed.head === "number" ? `, head=${parsed.head}` : "";
|
|
9641
9651
|
const tail = typeof parsed.tail === "number" ? `, tail=${parsed.tail}` : "";
|
|
9642
|
-
return `path: ${
|
|
9652
|
+
return `path: ${path5 ?? "?"}${head}${tail}`;
|
|
9643
9653
|
}
|
|
9644
9654
|
if (hasSuffix("write_file")) {
|
|
9645
9655
|
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
9646
|
-
return `path: ${
|
|
9656
|
+
return `path: ${path5 ?? "?"} (${content.length} chars)`;
|
|
9647
9657
|
}
|
|
9648
9658
|
if (hasSuffix("edit_file")) {
|
|
9649
9659
|
const edits = Array.isArray(parsed.edits) ? parsed.edits.length : 0;
|
|
9650
|
-
return `path: ${
|
|
9660
|
+
return `path: ${path5 ?? "?"} (${edits} edit${edits === 1 ? "" : "s"})`;
|
|
9651
9661
|
}
|
|
9652
9662
|
if (hasSuffix("list_directory") || hasSuffix("directory_tree")) {
|
|
9653
|
-
return `path: ${
|
|
9663
|
+
return `path: ${path5 ?? "?"}`;
|
|
9654
9664
|
}
|
|
9655
9665
|
if (hasSuffix("search_files")) {
|
|
9656
9666
|
const pattern = typeof parsed.pattern === "string" ? parsed.pattern : "?";
|
|
9657
|
-
return `path: ${
|
|
9667
|
+
return `path: ${path5 ?? "?"} \xB7 pattern: ${pattern}`;
|
|
9658
9668
|
}
|
|
9659
9669
|
if (hasSuffix("move_file")) {
|
|
9660
9670
|
const src = typeof parsed.source === "string" ? parsed.source : "?";
|
|
@@ -9662,7 +9672,7 @@ function summarizeToolArgs(name, args) {
|
|
|
9662
9672
|
return `${src} \u2192 ${dst}`;
|
|
9663
9673
|
}
|
|
9664
9674
|
if (hasSuffix("get_file_info")) {
|
|
9665
|
-
return `path: ${
|
|
9675
|
+
return `path: ${path5 ?? "?"}`;
|
|
9666
9676
|
}
|
|
9667
9677
|
return args.length > 80 ? `${args.slice(0, 80)}\u2026` : args;
|
|
9668
9678
|
}
|
|
@@ -10377,8 +10387,8 @@ function PromptInput({
|
|
|
10377
10387
|
if (action.historyHandoff === "prev") onHistoryPrev?.();
|
|
10378
10388
|
if (action.historyHandoff === "next") onHistoryNext?.();
|
|
10379
10389
|
}, !disabled);
|
|
10380
|
-
const { stdout:
|
|
10381
|
-
const cols =
|
|
10390
|
+
const { stdout: stdout3 } = useStdout5();
|
|
10391
|
+
const cols = stdout3?.columns ?? 80;
|
|
10382
10392
|
const narrow = cols <= 90;
|
|
10383
10393
|
const promptBody = narrow ? "\u203A " : "you \u203A ";
|
|
10384
10394
|
const promptPrefix = BAR + promptBody;
|
|
@@ -10749,8 +10759,8 @@ function StatsPanel({
|
|
|
10749
10759
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
10750
10760
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model2] ?? DEFAULT_CONTEXT_TOKENS;
|
|
10751
10761
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
10752
|
-
const { stdout:
|
|
10753
|
-
const columns =
|
|
10762
|
+
const { stdout: stdout3 } = useStdout6();
|
|
10763
|
+
const columns = stdout3?.columns ?? 80;
|
|
10754
10764
|
const narrow = columns < NARROW_BREAKPOINT;
|
|
10755
10765
|
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
10756
10766
|
return /* @__PURE__ */ React21.createElement(Box19, { flexDirection: "column", paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
|
|
@@ -10916,8 +10926,8 @@ function formatTokens(n) {
|
|
|
10916
10926
|
import { Box as Box20, Text as Text18, useStdout as useStdout7 } from "ink";
|
|
10917
10927
|
import React22 from "react";
|
|
10918
10928
|
function WelcomeBanner({ inCodeMode }) {
|
|
10919
|
-
const { stdout:
|
|
10920
|
-
const cols =
|
|
10929
|
+
const { stdout: stdout3 } = useStdout7();
|
|
10930
|
+
const cols = stdout3?.columns ?? 80;
|
|
10921
10931
|
const ruleWidth = Math.min(60, Math.max(28, cols - 4));
|
|
10922
10932
|
return /* @__PURE__ */ React22.createElement(Box20, { flexDirection: "column", paddingX: 1, marginY: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth }), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.brand }, "\u25C8 welcome"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " \xB7 type a message to start")), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.primary }, "quick start")), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/help", desc: "every command + keyboard shortcut" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "/skill", desc: "invoke a stored playbook" }), inCodeMode ? /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "@path", desc: "inline a file in your message" }), /* @__PURE__ */ React22.createElement(Hint, { cmd: "!cmd", desc: "run a shell command, output goes to context" })) : null, /* @__PURE__ */ React22.createElement(Hint, { cmd: "/exit", desc: "quit (Ctrl+C also works)" }), /* @__PURE__ */ React22.createElement(BarRow, null), /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { dimColor: true, italic: true }, "tip:"), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, " Ctrl+J inserts a newline \xB7 trailing \\ also continues")), /* @__PURE__ */ React22.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React22.createElement(GradientRule, { width: ruleWidth, thin: true })));
|
|
10923
10933
|
}
|
|
@@ -10978,7 +10988,7 @@ function parseEditIndices(raw, max) {
|
|
|
10978
10988
|
if (!trimmed) return { ok: [] };
|
|
10979
10989
|
if (max <= 0) return { error: "no pending edits to address" };
|
|
10980
10990
|
const seen = /* @__PURE__ */ new Set();
|
|
10981
|
-
const tokens = trimmed.split(",").map((
|
|
10991
|
+
const tokens = trimmed.split(",").map((t2) => t2.trim()).filter((t2) => t2.length > 0);
|
|
10982
10992
|
if (tokens.length === 0) return { ok: [] };
|
|
10983
10993
|
for (const tok of tokens) {
|
|
10984
10994
|
const range = tok.match(/^(\d+)-(\d+)$/);
|
|
@@ -11072,24 +11082,24 @@ function globalMemoryPath(homeDir = homedir6()) {
|
|
|
11072
11082
|
function appendGlobalMemory(note, homeDir) {
|
|
11073
11083
|
return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
|
|
11074
11084
|
}
|
|
11075
|
-
function appendBulletToFile(
|
|
11085
|
+
function appendBulletToFile(path5, note, newFileHeader) {
|
|
11076
11086
|
const trimmed = note.trim();
|
|
11077
11087
|
if (!trimmed) throw new Error("note body cannot be empty");
|
|
11078
11088
|
const bullet = `- ${trimmed}
|
|
11079
11089
|
`;
|
|
11080
|
-
if (!existsSync11(
|
|
11081
|
-
mkdirSync8(dirname10(
|
|
11082
|
-
writeFileSync7(
|
|
11083
|
-
return { path, created: true };
|
|
11090
|
+
if (!existsSync11(path5)) {
|
|
11091
|
+
mkdirSync8(dirname10(path5), { recursive: true });
|
|
11092
|
+
writeFileSync7(path5, `${newFileHeader}${bullet}`, "utf8");
|
|
11093
|
+
return { path: path5, created: true };
|
|
11084
11094
|
}
|
|
11085
11095
|
let prefix = "";
|
|
11086
11096
|
try {
|
|
11087
|
-
const existing = readFileSync14(
|
|
11097
|
+
const existing = readFileSync14(path5, "utf8");
|
|
11088
11098
|
if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
|
|
11089
11099
|
} catch {
|
|
11090
11100
|
}
|
|
11091
|
-
appendFileSync3(
|
|
11092
|
-
return { path, created: false };
|
|
11101
|
+
appendFileSync3(path5, `${prefix}${bullet}`, "utf8");
|
|
11102
|
+
return { path: path5, created: false };
|
|
11093
11103
|
}
|
|
11094
11104
|
|
|
11095
11105
|
// src/cli/ui/loop.ts
|
|
@@ -11454,6 +11464,10 @@ var SLASH_COMMANDS = [
|
|
|
11454
11464
|
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
11455
11465
|
{ cmd: "forget", summary: "delete the current session from disk" },
|
|
11456
11466
|
{ cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
|
|
11467
|
+
{
|
|
11468
|
+
cmd: "semantic",
|
|
11469
|
+
summary: "show semantic_search status \u2014 built? Ollama installed? how to enable"
|
|
11470
|
+
},
|
|
11457
11471
|
{ cmd: "clear", summary: "clear visible scrollback only (log/context kept)" },
|
|
11458
11472
|
{ cmd: "new", summary: "start a fresh conversation (clear context + scrollback)" },
|
|
11459
11473
|
{
|
|
@@ -11576,12 +11590,12 @@ function statsCommand(opts) {
|
|
|
11576
11590
|
}
|
|
11577
11591
|
dashboard(opts);
|
|
11578
11592
|
}
|
|
11579
|
-
function transcriptSummary(
|
|
11580
|
-
if (!existsSync12(
|
|
11581
|
-
console.error(`no such transcript: ${
|
|
11593
|
+
function transcriptSummary(path5) {
|
|
11594
|
+
if (!existsSync12(path5)) {
|
|
11595
|
+
console.error(`no such transcript: ${path5}`);
|
|
11582
11596
|
process.exit(1);
|
|
11583
11597
|
}
|
|
11584
|
-
const lines = readFileSync15(
|
|
11598
|
+
const lines = readFileSync15(path5, "utf8").split(/\r?\n/).filter(Boolean);
|
|
11585
11599
|
let assistantTurns = 0;
|
|
11586
11600
|
let toolCalls = 0;
|
|
11587
11601
|
let lastTurn = 0;
|
|
@@ -11594,25 +11608,25 @@ function transcriptSummary(path) {
|
|
|
11594
11608
|
} catch {
|
|
11595
11609
|
}
|
|
11596
11610
|
}
|
|
11597
|
-
console.log(`transcript: ${
|
|
11611
|
+
console.log(`transcript: ${path5}`);
|
|
11598
11612
|
console.log(`assistant turns: ${assistantTurns}`);
|
|
11599
11613
|
console.log(`tool invocations: ${toolCalls}`);
|
|
11600
11614
|
console.log(`last turn index: ${lastTurn}`);
|
|
11601
11615
|
}
|
|
11602
11616
|
function dashboard(opts) {
|
|
11603
|
-
const
|
|
11604
|
-
const records = readUsageLog(
|
|
11617
|
+
const path5 = opts.logPath ?? defaultUsageLogPath();
|
|
11618
|
+
const records = readUsageLog(path5);
|
|
11605
11619
|
if (records.length === 0) {
|
|
11606
11620
|
console.log("no usage data yet.");
|
|
11607
11621
|
console.log("");
|
|
11608
|
-
console.log(` ${
|
|
11622
|
+
console.log(` ${path5}`);
|
|
11609
11623
|
console.log("");
|
|
11610
11624
|
console.log("run `reasonix chat`, `reasonix code`, or `reasonix run <task>` \u2014 every turn");
|
|
11611
11625
|
console.log("appends one line to the log and `reasonix stats` will roll it up.");
|
|
11612
11626
|
return;
|
|
11613
11627
|
}
|
|
11614
11628
|
const agg = aggregateUsage(records, { now: opts.now });
|
|
11615
|
-
console.log(renderDashboard(agg,
|
|
11629
|
+
console.log(renderDashboard(agg, path5));
|
|
11616
11630
|
}
|
|
11617
11631
|
function renderDashboard(agg, logPath) {
|
|
11618
11632
|
const lines = [];
|
|
@@ -11784,14 +11798,14 @@ var update = (_args, _loop, ctx) => {
|
|
|
11784
11798
|
return { info: lines.join("\n") };
|
|
11785
11799
|
};
|
|
11786
11800
|
var stats = () => {
|
|
11787
|
-
const
|
|
11788
|
-
const records = readUsageLog(
|
|
11801
|
+
const path5 = defaultUsageLogPath();
|
|
11802
|
+
const records = readUsageLog(path5);
|
|
11789
11803
|
if (records.length === 0) {
|
|
11790
11804
|
return {
|
|
11791
11805
|
info: [
|
|
11792
11806
|
"no usage data yet.",
|
|
11793
11807
|
"",
|
|
11794
|
-
` ${
|
|
11808
|
+
` ${path5}`,
|
|
11795
11809
|
"",
|
|
11796
11810
|
"every turn you run here appends one record \u2014 this session's turns",
|
|
11797
11811
|
"will show up in the dashboard once you send a message."
|
|
@@ -11799,7 +11813,7 @@ var stats = () => {
|
|
|
11799
11813
|
};
|
|
11800
11814
|
}
|
|
11801
11815
|
const agg = aggregateUsage(records);
|
|
11802
|
-
return { info: renderDashboard(agg,
|
|
11816
|
+
return { info: renderDashboard(agg, path5) };
|
|
11803
11817
|
};
|
|
11804
11818
|
var handlers = {
|
|
11805
11819
|
hook: hooks,
|
|
@@ -12101,8 +12115,8 @@ ${gitTail(commit2)}` };
|
|
|
12101
12115
|
}
|
|
12102
12116
|
function gitTail(res) {
|
|
12103
12117
|
const stderr = res.stderr ?? "";
|
|
12104
|
-
const
|
|
12105
|
-
const body = stderr.trim() ||
|
|
12118
|
+
const stdout3 = res.stdout ?? "";
|
|
12119
|
+
const body = stderr.trim() || stdout3.trim();
|
|
12106
12120
|
if (body) return body;
|
|
12107
12121
|
if (res.error) return res.error.message;
|
|
12108
12122
|
return "(no output from git)";
|
|
@@ -12361,7 +12375,7 @@ var mcp = (_args, loop2, ctx) => {
|
|
|
12361
12375
|
}
|
|
12362
12376
|
if (toolSpecs.length > 0) {
|
|
12363
12377
|
lines.push(`Tools in registry (${toolSpecs.length}):`);
|
|
12364
|
-
for (const
|
|
12378
|
+
for (const t2 of toolSpecs) lines.push(` \xB7 ${t2.function.name}`);
|
|
12365
12379
|
}
|
|
12366
12380
|
lines.push("");
|
|
12367
12381
|
lines.push("To change this set, exit and run `reasonix setup`.");
|
|
@@ -12757,9 +12771,9 @@ var context = (_args, loop2) => {
|
|
|
12757
12771
|
const top = [...toolBreakdown].sort((a, b) => b.tokens - a.tokens).slice(0, 5);
|
|
12758
12772
|
lines.push("");
|
|
12759
12773
|
lines.push(`Top tool results by cost (of ${toolBreakdown.length} total):`);
|
|
12760
|
-
for (const
|
|
12774
|
+
for (const t2 of top) {
|
|
12761
12775
|
lines.push(
|
|
12762
|
-
` turn ${String(
|
|
12776
|
+
` turn ${String(t2.turn).padStart(3)} ${t2.name.padEnd(22)} ${compactNum(t2.tokens).padStart(8)} tokens`
|
|
12763
12777
|
);
|
|
12764
12778
|
}
|
|
12765
12779
|
}
|
|
@@ -12901,6 +12915,378 @@ var handlers9 = {
|
|
|
12901
12915
|
replay
|
|
12902
12916
|
};
|
|
12903
12917
|
|
|
12918
|
+
// src/cli/ui/slash/handlers/semantic.ts
|
|
12919
|
+
import { promises as fs2 } from "fs";
|
|
12920
|
+
import path from "path";
|
|
12921
|
+
|
|
12922
|
+
// src/index/semantic/embedding.ts
|
|
12923
|
+
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
12924
|
+
var DEFAULT_EMBED_MODEL = "nomic-embed-text";
|
|
12925
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
12926
|
+
var EmbeddingError = class extends Error {
|
|
12927
|
+
constructor(message, cause) {
|
|
12928
|
+
super(message);
|
|
12929
|
+
this.cause = cause;
|
|
12930
|
+
this.name = "EmbeddingError";
|
|
12931
|
+
}
|
|
12932
|
+
cause;
|
|
12933
|
+
};
|
|
12934
|
+
async function embed(text, opts = {}) {
|
|
12935
|
+
const baseUrl = opts.baseUrl ?? process.env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL;
|
|
12936
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? DEFAULT_EMBED_MODEL;
|
|
12937
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
12938
|
+
const controller = new AbortController();
|
|
12939
|
+
const onCallerAbort = () => controller.abort(opts.signal?.reason);
|
|
12940
|
+
if (opts.signal) {
|
|
12941
|
+
if (opts.signal.aborted) controller.abort(opts.signal.reason);
|
|
12942
|
+
else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
12943
|
+
}
|
|
12944
|
+
const timer = setTimeout(() => controller.abort(new Error("embedding timeout")), timeoutMs);
|
|
12945
|
+
let res;
|
|
12946
|
+
try {
|
|
12947
|
+
res = await fetch(`${baseUrl}/api/embeddings`, {
|
|
12948
|
+
method: "POST",
|
|
12949
|
+
headers: { "content-type": "application/json" },
|
|
12950
|
+
body: JSON.stringify({ model: model2, prompt: text }),
|
|
12951
|
+
signal: controller.signal
|
|
12952
|
+
});
|
|
12953
|
+
} catch (err) {
|
|
12954
|
+
clearTimeout(timer);
|
|
12955
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
12956
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12957
|
+
if (/ECONNREFUSED|connect ECONNREFUSED|fetch failed/i.test(msg)) {
|
|
12958
|
+
throw new EmbeddingError(
|
|
12959
|
+
`Cannot reach Ollama at ${baseUrl}. Install from https://ollama.com, then run \`ollama pull ${model2}\` and \`ollama serve\`. Override the URL via OLLAMA_URL.`,
|
|
12960
|
+
err
|
|
12961
|
+
);
|
|
12962
|
+
}
|
|
12963
|
+
throw new EmbeddingError(`embedding request failed: ${msg}`, err);
|
|
12964
|
+
} finally {
|
|
12965
|
+
clearTimeout(timer);
|
|
12966
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
12967
|
+
}
|
|
12968
|
+
if (!res.ok) {
|
|
12969
|
+
const body = await res.text().catch(() => "");
|
|
12970
|
+
if (res.status === 404 && /model.*not found/i.test(body)) {
|
|
12971
|
+
throw new EmbeddingError(
|
|
12972
|
+
`Embedding model "${model2}" not pulled. Run \`ollama pull ${model2}\` once, then retry.`
|
|
12973
|
+
);
|
|
12974
|
+
}
|
|
12975
|
+
throw new EmbeddingError(`Ollama returned ${res.status}: ${body.slice(0, 200)}`);
|
|
12976
|
+
}
|
|
12977
|
+
const json = await res.json();
|
|
12978
|
+
if (!json.embedding || !Array.isArray(json.embedding)) {
|
|
12979
|
+
throw new EmbeddingError(`Ollama response missing 'embedding' array`);
|
|
12980
|
+
}
|
|
12981
|
+
const out = new Float32Array(json.embedding.length);
|
|
12982
|
+
for (let i = 0; i < json.embedding.length; i++) {
|
|
12983
|
+
const v = json.embedding[i];
|
|
12984
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
12985
|
+
throw new EmbeddingError(`embedding[${i}] is not a finite number`);
|
|
12986
|
+
}
|
|
12987
|
+
out[i] = v;
|
|
12988
|
+
}
|
|
12989
|
+
return out;
|
|
12990
|
+
}
|
|
12991
|
+
async function embedAll(texts, opts = {}) {
|
|
12992
|
+
const out = new Array(texts.length).fill(null);
|
|
12993
|
+
for (let i = 0; i < texts.length; i++) {
|
|
12994
|
+
if (opts.signal?.aborted) {
|
|
12995
|
+
throw new EmbeddingError("embedding aborted");
|
|
12996
|
+
}
|
|
12997
|
+
const text = texts[i];
|
|
12998
|
+
if (text === void 0) continue;
|
|
12999
|
+
try {
|
|
13000
|
+
out[i] = await embed(text, opts);
|
|
13001
|
+
} catch (err) {
|
|
13002
|
+
opts.onError?.(i, err);
|
|
13003
|
+
}
|
|
13004
|
+
opts.onProgress?.(i + 1, texts.length);
|
|
13005
|
+
}
|
|
13006
|
+
return out;
|
|
13007
|
+
}
|
|
13008
|
+
async function probeOllama(opts = {}) {
|
|
13009
|
+
const baseUrl = opts.baseUrl ?? process.env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL;
|
|
13010
|
+
try {
|
|
13011
|
+
const res = await fetch(`${baseUrl}/api/tags`, { signal: opts.signal });
|
|
13012
|
+
if (!res.ok) return { ok: false, error: `Ollama returned ${res.status}` };
|
|
13013
|
+
const json = await res.json();
|
|
13014
|
+
const models2 = (json.models ?? []).map((m) => m.name).filter((n) => typeof n === "string");
|
|
13015
|
+
return { ok: true, models: models2 };
|
|
13016
|
+
} catch (err) {
|
|
13017
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13018
|
+
return { ok: false, error: msg };
|
|
13019
|
+
}
|
|
13020
|
+
}
|
|
13021
|
+
|
|
13022
|
+
// src/index/semantic/i18n.ts
|
|
13023
|
+
var cachedLocale = null;
|
|
13024
|
+
function detectLocale() {
|
|
13025
|
+
if (cachedLocale) return cachedLocale;
|
|
13026
|
+
const override = (process.env.REASONIX_LANG ?? "").toLowerCase();
|
|
13027
|
+
if (override === "zh" || override === "en") {
|
|
13028
|
+
cachedLocale = override;
|
|
13029
|
+
return cachedLocale;
|
|
13030
|
+
}
|
|
13031
|
+
const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_MESSAGES ?? "";
|
|
13032
|
+
if (/^zh[-_]/i.test(env)) {
|
|
13033
|
+
cachedLocale = "zh";
|
|
13034
|
+
return "zh";
|
|
13035
|
+
}
|
|
13036
|
+
try {
|
|
13037
|
+
const sys = new Intl.DateTimeFormat().resolvedOptions().locale ?? "";
|
|
13038
|
+
if (/^zh[-_]/i.test(sys)) {
|
|
13039
|
+
cachedLocale = "zh";
|
|
13040
|
+
return "zh";
|
|
13041
|
+
}
|
|
13042
|
+
} catch {
|
|
13043
|
+
}
|
|
13044
|
+
cachedLocale = "en";
|
|
13045
|
+
return "en";
|
|
13046
|
+
}
|
|
13047
|
+
function t(key, vars = {}) {
|
|
13048
|
+
const loc = detectLocale();
|
|
13049
|
+
const dict = loc === "zh" ? ZH : EN;
|
|
13050
|
+
const tpl = dict[key] ?? EN[key];
|
|
13051
|
+
return tpl.replace(/\{(\w+)\}/g, (_m, name) => {
|
|
13052
|
+
const v = vars[name];
|
|
13053
|
+
return v === void 0 ? `{${name}}` : String(v);
|
|
13054
|
+
});
|
|
13055
|
+
}
|
|
13056
|
+
var EN = {
|
|
13057
|
+
// ── preflight ─────────────────────────────────────────────────────
|
|
13058
|
+
ollamaNotFound: "\u2717 `ollama` not found on PATH.\n Install from https://ollama.com (one-time, ~150 MB), then retry.\n",
|
|
13059
|
+
daemonNotReachableHint: "\u2717 Ollama daemon not reachable. Run `ollama serve` and retry, or pass --yes to start it automatically.\n",
|
|
13060
|
+
daemonStartConfirm: "Ollama daemon isn't running. Start `ollama serve` now?",
|
|
13061
|
+
daemonAbortStart: "\u2717 aborted \u2014 start `ollama serve` yourself and retry.\n",
|
|
13062
|
+
daemonStarting: "\u25B8 starting `ollama serve`\u2026\n",
|
|
13063
|
+
daemonStartTimeout: "\u2717 daemon didn't come up within 15s. Try `ollama serve` in a separate terminal and retry.\n",
|
|
13064
|
+
daemonReady: "\u2713 daemon up{pid}\n",
|
|
13065
|
+
modelNotPulledHint: '\u2717 embedding model "{model}" not pulled. Run `ollama pull {model}` and retry, or pass --yes to pull it automatically.\n',
|
|
13066
|
+
modelPullConfirm: `Embedding model "{model}" isn't pulled yet. Pull it now? (~274 MB for nomic-embed-text)`,
|
|
13067
|
+
modelAbortPull: "\u2717 aborted \u2014 pull the model yourself and retry.\n",
|
|
13068
|
+
modelPulling: "\u25B8 pulling {model}\u2026\n",
|
|
13069
|
+
modelPullFailed: "\u2717 `ollama pull {model}` failed (exit {code}).\n",
|
|
13070
|
+
modelPulled: "\u2713 {model} pulled\n",
|
|
13071
|
+
// ── progress ─────────────────────────────────────────────────────
|
|
13072
|
+
// The TTY-mode progress writer paints `<spinner> <status> <elapsed>s`
|
|
13073
|
+
// every 120ms. The status itself comes from one of these keys based
|
|
13074
|
+
// on the current phase. {files}, {done}, {total}, {pct} are
|
|
13075
|
+
// substituted by the writer.
|
|
13076
|
+
progressStarting: "starting\u2026",
|
|
13077
|
+
progressScan: "scanning project \xB7 {files} files",
|
|
13078
|
+
progressEmbed: "embedding {done}/{total} chunks \xB7 {pct}%",
|
|
13079
|
+
progressEmbedHeartbeat: " {done}/{total}\n",
|
|
13080
|
+
progressScanLine: "scanning files\u2026\n",
|
|
13081
|
+
progressEmbedLine: "embedding {total} chunks across {files} files\u2026\n",
|
|
13082
|
+
// Final result line after a successful build.
|
|
13083
|
+
indexSuccess: "\u2713 indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed) in {seconds}s\n",
|
|
13084
|
+
indexSuccessWithSkips: "\u2713 indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed, {skipped} skipped due to embed errors) in {seconds}s\n",
|
|
13085
|
+
indexNothingToDo: " (nothing to do \u2014 re-run with --rebuild to force a full rebuild)\n",
|
|
13086
|
+
indexFailed: "\u2717 index failed: {msg}\n",
|
|
13087
|
+
// ── /semantic slash ──────────────────────────────────────────────
|
|
13088
|
+
slashHeader: "semantic_search status",
|
|
13089
|
+
slashEnabled: "\u2713 enabled \u2014 index built, tool registered.",
|
|
13090
|
+
slashEnabledDetail: " index size: {chunks} chunks across {files} files",
|
|
13091
|
+
slashEnabledHowto: " the model will call semantic_search automatically when it fits.",
|
|
13092
|
+
slashIndexMissing: "\u2717 no index built yet for this project.",
|
|
13093
|
+
slashHowToBuild: " to enable, exit Reasonix and run in your shell:\n reasonix index",
|
|
13094
|
+
slashOllamaMissing: " prerequisite: install Ollama from https://ollama.com",
|
|
13095
|
+
slashDaemonDown: " Ollama is installed but the daemon isn't running. start it with: ollama serve",
|
|
13096
|
+
slashIndexInfo: " what semantic_search does: cross-language code understanding via local embeddings.\n better than grep when you describe WHAT something does, not WHICH token to find."
|
|
13097
|
+
};
|
|
13098
|
+
var ZH = {
|
|
13099
|
+
ollamaNotFound: "\u2717 \u672A\u627E\u5230 `ollama`\u3002\n \u8BF7\u8BBF\u95EE https://ollama.com \u5B89\u88C5\uFF08\u4E00\u6B21\u6027\uFF0C\u7EA6 150 MB\uFF09\uFF0C\u7136\u540E\u91CD\u8BD5\u3002\n",
|
|
13100
|
+
daemonNotReachableHint: "\u2717 Ollama \u5B88\u62A4\u8FDB\u7A0B\u672A\u542F\u52A8\u3002\u8BF7\u8FD0\u884C `ollama serve` \u540E\u91CD\u8BD5\uFF0C\u6216\u52A0 --yes \u8BA9\u6211\u81EA\u52A8\u542F\u52A8\u3002\n",
|
|
13101
|
+
daemonStartConfirm: "Ollama \u5B88\u62A4\u8FDB\u7A0B\u672A\u8FD0\u884C\u3002\u73B0\u5728\u542F\u52A8 `ollama serve` \u5417\uFF1F",
|
|
13102
|
+
daemonAbortStart: "\u2717 \u5DF2\u53D6\u6D88\u2014\u2014\u8BF7\u81EA\u884C\u8FD0\u884C `ollama serve` \u540E\u91CD\u8BD5\u3002\n",
|
|
13103
|
+
daemonStarting: "\u25B8 \u6B63\u5728\u542F\u52A8 `ollama serve`\u2026\n",
|
|
13104
|
+
daemonStartTimeout: "\u2717 15 \u79D2\u5185\u5B88\u62A4\u8FDB\u7A0B\u672A\u5C31\u7EEA\u3002\u8BF7\u5728\u53E6\u4E00\u4E2A\u7EC8\u7AEF\u8FD0\u884C `ollama serve` \u540E\u91CD\u8BD5\u3002\n",
|
|
13105
|
+
daemonReady: "\u2713 \u5B88\u62A4\u8FDB\u7A0B\u5DF2\u542F\u52A8{pid}\n",
|
|
13106
|
+
modelNotPulledHint: '\u2717 \u5D4C\u5165\u6A21\u578B "{model}" \u672A\u4E0B\u8F7D\u3002\u8BF7\u8FD0\u884C `ollama pull {model}` \u540E\u91CD\u8BD5\uFF0C\u6216\u52A0 --yes \u8BA9\u6211\u81EA\u52A8\u4E0B\u8F7D\u3002\n',
|
|
13107
|
+
modelPullConfirm: '\u5D4C\u5165\u6A21\u578B "{model}" \u8FD8\u672A\u4E0B\u8F7D\u3002\u73B0\u5728\u4E0B\u8F7D\u5417\uFF1F\uFF08nomic-embed-text \u7EA6 274 MB\uFF09',
|
|
13108
|
+
modelAbortPull: "\u2717 \u5DF2\u53D6\u6D88\u2014\u2014\u8BF7\u81EA\u884C\u4E0B\u8F7D\u6A21\u578B\u540E\u91CD\u8BD5\u3002\n",
|
|
13109
|
+
modelPulling: "\u25B8 \u6B63\u5728\u4E0B\u8F7D {model}\u2026\n",
|
|
13110
|
+
modelPullFailed: "\u2717 `ollama pull {model}` \u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\n",
|
|
13111
|
+
modelPulled: "\u2713 {model} \u4E0B\u8F7D\u5B8C\u6210\n",
|
|
13112
|
+
progressStarting: "\u6B63\u5728\u542F\u52A8\u2026",
|
|
13113
|
+
progressScan: "\u626B\u63CF\u9879\u76EE \xB7 \u5DF2\u626B\u63CF {files} \u4E2A\u6587\u4EF6",
|
|
13114
|
+
progressEmbed: "\u6B63\u5728\u5411\u91CF\u5316 {done}/{total} \u4E2A\u7247\u6BB5 \xB7 {pct}%",
|
|
13115
|
+
progressEmbedHeartbeat: " {done}/{total}\n",
|
|
13116
|
+
progressScanLine: "\u6B63\u5728\u626B\u63CF\u6587\u4EF6\u2026\n",
|
|
13117
|
+
progressEmbedLine: "\u6B63\u5728\u5411\u91CF\u5316 {total} \u4E2A\u7247\u6BB5\uFF08\u6D89\u53CA {files} \u4E2A\u6587\u4EF6\uFF09\u2026\n",
|
|
13118
|
+
indexSuccess: "\u2713 \u5DF2\u5EFA\u7ACB\u7D22\u5F15\uFF1A\u626B\u63CF {scanned} \u4E2A\u6587\u4EF6\uFF08{changed} \u4E2A\u6709\u53D8\u5316\uFF0C\u65B0\u589E {added} \u4E2A\u7247\u6BB5\uFF0C\u79FB\u9664 {removed} \u4E2A\u8FC7\u671F\uFF09\uFF1B\u8017\u65F6 {seconds}s\n",
|
|
13119
|
+
indexSuccessWithSkips: "\u2713 \u5DF2\u5EFA\u7ACB\u7D22\u5F15\uFF1A\u626B\u63CF {scanned} \u4E2A\u6587\u4EF6\uFF08{changed} \u4E2A\u6709\u53D8\u5316\uFF0C\u65B0\u589E {added} \u4E2A\u7247\u6BB5\uFF0C\u79FB\u9664 {removed} \u4E2A\u8FC7\u671F\uFF0C\u8DF3\u8FC7 {skipped} \u4E2A\u5D4C\u5165\u5931\u8D25\u7684\u7247\u6BB5\uFF09\uFF1B\u8017\u65F6 {seconds}s\n",
|
|
13120
|
+
indexNothingToDo: " \uFF08\u6CA1\u6709\u53D8\u5316\u2014\u2014\u52A0 --rebuild \u5F3A\u5236\u91CD\u5EFA\uFF09\n",
|
|
13121
|
+
indexFailed: "\u2717 \u5EFA\u7ACB\u7D22\u5F15\u5931\u8D25\uFF1A{msg}\n",
|
|
13122
|
+
slashHeader: "semantic_search \u72B6\u6001",
|
|
13123
|
+
slashEnabled: "\u2713 \u5DF2\u542F\u7528\u2014\u2014\u7D22\u5F15\u5DF2\u5EFA\u597D\uFF0C\u5DE5\u5177\u5DF2\u6CE8\u518C\u3002",
|
|
13124
|
+
slashEnabledDetail: " \u7D22\u5F15\u89C4\u6A21\uFF1A{chunks} \u4E2A\u7247\u6BB5\uFF0C{files} \u4E2A\u6587\u4EF6",
|
|
13125
|
+
slashEnabledHowto: " \u6A21\u578B\u5728\u5408\u9002\u7684\u65F6\u5019\u4F1A\u81EA\u52A8\u8C03\u7528 semantic_search\u3002",
|
|
13126
|
+
slashIndexMissing: "\u2717 \u5F53\u524D\u9879\u76EE\u8FD8\u6CA1\u6709\u7D22\u5F15\u3002",
|
|
13127
|
+
slashHowToBuild: " \u542F\u7528\u65B9\u5F0F\uFF1A\u9000\u51FA Reasonix\uFF0C\u5728\u7EC8\u7AEF\u8FD0\u884C\uFF1A\n reasonix index",
|
|
13128
|
+
slashOllamaMissing: " \u524D\u7F6E\u4F9D\u8D56\uFF1A\u4ECE https://ollama.com \u5B89\u88C5 Ollama",
|
|
13129
|
+
slashDaemonDown: " \u5DF2\u88C5 Ollama \u4F46\u5B88\u62A4\u8FDB\u7A0B\u672A\u542F\u52A8\uFF0C\u8BF7\u8FD0\u884C\uFF1Aollama serve",
|
|
13130
|
+
slashIndexInfo: ' semantic_search \u7528\u672C\u5730 embedding \u505A\u8DE8\u8BED\u8A00\u4EE3\u7801\u7406\u89E3\u3002\n \u5F53\u4F60\u63CF\u8FF0"\u505A\u4EC0\u4E48"\u800C\u4E0D\u662F\u5177\u4F53 token \u65F6\uFF0C\u6BD4 grep \u66F4\u597D\u3002'
|
|
13131
|
+
};
|
|
13132
|
+
|
|
13133
|
+
// src/index/semantic/ollama-launcher.ts
|
|
13134
|
+
import { spawn as spawn5, spawnSync as spawnSync2 } from "child_process";
|
|
13135
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
13136
|
+
function findOllamaBinary() {
|
|
13137
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
13138
|
+
const out = spawnSync2(cmd, ["ollama"], { encoding: "utf8" });
|
|
13139
|
+
if (out.status !== 0) return null;
|
|
13140
|
+
const first = out.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
13141
|
+
return first ? first.trim() : null;
|
|
13142
|
+
}
|
|
13143
|
+
async function checkOllamaStatus(modelName, baseUrl) {
|
|
13144
|
+
const binary = findOllamaBinary();
|
|
13145
|
+
const probe = await probeOllama({ baseUrl });
|
|
13146
|
+
const installedModels = probe.ok ? probe.models : [];
|
|
13147
|
+
const wanted = modelName.includes(":") ? modelName : `${modelName}:latest`;
|
|
13148
|
+
const modelPulled = installedModels.some((m) => m === modelName || m === wanted);
|
|
13149
|
+
return {
|
|
13150
|
+
binaryFound: binary !== null,
|
|
13151
|
+
daemonRunning: probe.ok,
|
|
13152
|
+
modelPulled,
|
|
13153
|
+
modelName,
|
|
13154
|
+
installedModels
|
|
13155
|
+
};
|
|
13156
|
+
}
|
|
13157
|
+
async function startOllamaDaemon(opts = {}) {
|
|
13158
|
+
const timeoutMs = opts.timeoutMs ?? 15e3;
|
|
13159
|
+
const child = spawn5("ollama", ["serve"], {
|
|
13160
|
+
detached: true,
|
|
13161
|
+
stdio: "ignore",
|
|
13162
|
+
windowsHide: true
|
|
13163
|
+
});
|
|
13164
|
+
child.unref();
|
|
13165
|
+
const pid = child.pid ?? null;
|
|
13166
|
+
const start = Date.now();
|
|
13167
|
+
while (Date.now() - start < timeoutMs) {
|
|
13168
|
+
if (opts.signal?.aborted) return { ready: false, pid };
|
|
13169
|
+
const probe = await probeOllama({ baseUrl: opts.baseUrl, signal: opts.signal });
|
|
13170
|
+
if (probe.ok) return { ready: true, pid };
|
|
13171
|
+
await sleep2(500);
|
|
13172
|
+
}
|
|
13173
|
+
return { ready: false, pid };
|
|
13174
|
+
}
|
|
13175
|
+
async function pullOllamaModel(modelName, opts = {}) {
|
|
13176
|
+
return new Promise((resolve9) => {
|
|
13177
|
+
const child = spawn5("ollama", ["pull", modelName], {
|
|
13178
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13179
|
+
windowsHide: true
|
|
13180
|
+
});
|
|
13181
|
+
if (opts.signal) {
|
|
13182
|
+
const onAbort = () => child.kill();
|
|
13183
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
13184
|
+
child.once("exit", () => opts.signal?.removeEventListener("abort", onAbort));
|
|
13185
|
+
}
|
|
13186
|
+
streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
|
|
13187
|
+
streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
|
|
13188
|
+
child.once("exit", (code) => resolve9(code ?? -1));
|
|
13189
|
+
child.once("error", () => resolve9(-1));
|
|
13190
|
+
});
|
|
13191
|
+
}
|
|
13192
|
+
function streamLines(stream, cb) {
|
|
13193
|
+
if (!stream) return;
|
|
13194
|
+
let buf = "";
|
|
13195
|
+
stream.setEncoding("utf8");
|
|
13196
|
+
stream.on("data", (chunk) => {
|
|
13197
|
+
buf += chunk;
|
|
13198
|
+
let nl = buf.indexOf("\n");
|
|
13199
|
+
while (nl !== -1) {
|
|
13200
|
+
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
13201
|
+
buf = buf.slice(nl + 1);
|
|
13202
|
+
if (line.length > 0) cb(line);
|
|
13203
|
+
nl = buf.indexOf("\n");
|
|
13204
|
+
}
|
|
13205
|
+
});
|
|
13206
|
+
stream.on("end", () => {
|
|
13207
|
+
if (buf.length > 0) cb(buf.replace(/\r$/, ""));
|
|
13208
|
+
});
|
|
13209
|
+
}
|
|
13210
|
+
|
|
13211
|
+
// src/cli/ui/slash/handlers/semantic.ts
|
|
13212
|
+
var semantic = (_args, _loop, ctx) => {
|
|
13213
|
+
const root = ctx.codeRoot;
|
|
13214
|
+
if (!root) {
|
|
13215
|
+
return {
|
|
13216
|
+
info: "/semantic is only available inside `reasonix code` (needs a project root)."
|
|
13217
|
+
};
|
|
13218
|
+
}
|
|
13219
|
+
void (async () => {
|
|
13220
|
+
const status2 = await renderSemanticStatus(root);
|
|
13221
|
+
ctx.postInfo?.(status2);
|
|
13222
|
+
})();
|
|
13223
|
+
return { info: "\u25B8 checking semantic_search status\u2026" };
|
|
13224
|
+
};
|
|
13225
|
+
async function renderSemanticStatus(rootDir) {
|
|
13226
|
+
const lines = [t("slashHeader"), ""];
|
|
13227
|
+
const indexExists2 = await indexFileExists(rootDir);
|
|
13228
|
+
if (indexExists2) {
|
|
13229
|
+
const meta = await readIndexMeta(rootDir);
|
|
13230
|
+
lines.push(t("slashEnabled"));
|
|
13231
|
+
if (meta) {
|
|
13232
|
+
lines.push(
|
|
13233
|
+
t("slashEnabledDetail", {
|
|
13234
|
+
chunks: meta.chunks,
|
|
13235
|
+
files: meta.files
|
|
13236
|
+
})
|
|
13237
|
+
);
|
|
13238
|
+
}
|
|
13239
|
+
lines.push(t("slashEnabledHowto"));
|
|
13240
|
+
return lines.join("\n");
|
|
13241
|
+
}
|
|
13242
|
+
lines.push(t("slashIndexMissing"));
|
|
13243
|
+
lines.push(t("slashIndexInfo"));
|
|
13244
|
+
lines.push("");
|
|
13245
|
+
if (findOllamaBinary() === null) {
|
|
13246
|
+
lines.push(t("slashOllamaMissing"));
|
|
13247
|
+
} else {
|
|
13248
|
+
const probe = await probeOllama();
|
|
13249
|
+
if (!probe.ok) lines.push(t("slashDaemonDown"));
|
|
13250
|
+
}
|
|
13251
|
+
lines.push(t("slashHowToBuild"));
|
|
13252
|
+
return lines.join("\n");
|
|
13253
|
+
}
|
|
13254
|
+
async function indexFileExists(rootDir) {
|
|
13255
|
+
try {
|
|
13256
|
+
await fs2.access(path.join(rootDir, ".reasonix", "semantic", "index.meta.json"));
|
|
13257
|
+
return true;
|
|
13258
|
+
} catch {
|
|
13259
|
+
return false;
|
|
13260
|
+
}
|
|
13261
|
+
}
|
|
13262
|
+
async function readIndexMeta(rootDir) {
|
|
13263
|
+
const dataPath = path.join(rootDir, ".reasonix", "semantic", "index.jsonl");
|
|
13264
|
+
try {
|
|
13265
|
+
const stat = await fs2.stat(dataPath);
|
|
13266
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
13267
|
+
return { chunks: Math.round(stat.size / 500), files: 0 };
|
|
13268
|
+
}
|
|
13269
|
+
const raw = await fs2.readFile(dataPath, "utf8");
|
|
13270
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
13271
|
+
let chunks = 0;
|
|
13272
|
+
for (const line of raw.split("\n")) {
|
|
13273
|
+
if (line.length === 0) continue;
|
|
13274
|
+
chunks++;
|
|
13275
|
+
try {
|
|
13276
|
+
const parsed = JSON.parse(line);
|
|
13277
|
+
if (parsed.p) seenPaths.add(parsed.p);
|
|
13278
|
+
} catch {
|
|
13279
|
+
}
|
|
13280
|
+
}
|
|
13281
|
+
return { chunks, files: seenPaths.size };
|
|
13282
|
+
} catch {
|
|
13283
|
+
return null;
|
|
13284
|
+
}
|
|
13285
|
+
}
|
|
13286
|
+
var handlers10 = {
|
|
13287
|
+
semantic
|
|
13288
|
+
};
|
|
13289
|
+
|
|
12904
13290
|
// src/cli/ui/slash/handlers/sessions.ts
|
|
12905
13291
|
var sessions = (_args, loop2) => {
|
|
12906
13292
|
const items = listSessions();
|
|
@@ -12932,7 +13318,7 @@ var forget = (_args, loop2) => {
|
|
|
12932
13318
|
info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
|
|
12933
13319
|
};
|
|
12934
13320
|
};
|
|
12935
|
-
var
|
|
13321
|
+
var handlers11 = {
|
|
12936
13322
|
sessions,
|
|
12937
13323
|
forget
|
|
12938
13324
|
};
|
|
@@ -13008,7 +13394,7 @@ ${found.body}${argsLine}`;
|
|
|
13008
13394
|
resubmit: payload
|
|
13009
13395
|
};
|
|
13010
13396
|
};
|
|
13011
|
-
var
|
|
13397
|
+
var handlers12 = {
|
|
13012
13398
|
skill,
|
|
13013
13399
|
skills: skill
|
|
13014
13400
|
};
|
|
@@ -13025,7 +13411,8 @@ var HANDLERS = {
|
|
|
13025
13411
|
...handlers8,
|
|
13026
13412
|
...handlers9,
|
|
13027
13413
|
...handlers10,
|
|
13028
|
-
...handlers11
|
|
13414
|
+
...handlers11,
|
|
13415
|
+
...handlers12
|
|
13029
13416
|
};
|
|
13030
13417
|
function handleSlash(cmd, args, loop2, ctx = {}) {
|
|
13031
13418
|
const h = HANDLERS[cmd];
|
|
@@ -13339,16 +13726,16 @@ function useEditHistory(codeMode) {
|
|
|
13339
13726
|
const status2 = entryStatus(entry);
|
|
13340
13727
|
const header2 = `\u25B8 edit #${entry.id} \xB7 ${when} \xB7 ${entry.source} \xB7 ${status2} \xB7 ${files.length} file(s)`;
|
|
13341
13728
|
const countLines3 = (s) => s.length === 0 ? 0 : (s.match(/\n/g)?.length ?? 0) + 1;
|
|
13342
|
-
const fileLines = files.map((
|
|
13343
|
-
const fileBlocks = entry.blocks.filter((b) => b.path ===
|
|
13729
|
+
const fileLines = files.map((path5) => {
|
|
13730
|
+
const fileBlocks = entry.blocks.filter((b) => b.path === path5);
|
|
13344
13731
|
let removed = 0;
|
|
13345
13732
|
let added = 0;
|
|
13346
13733
|
for (const b of fileBlocks) {
|
|
13347
13734
|
removed += countLines3(b.search);
|
|
13348
13735
|
added += countLines3(b.replace);
|
|
13349
13736
|
}
|
|
13350
|
-
const state = entry.undoneFiles.has(
|
|
13351
|
-
return ` ${state.padEnd(7)} -${String(removed).padStart(3)}/+${String(added).padStart(3)} ${
|
|
13737
|
+
const state = entry.undoneFiles.has(path5) ? "UNDONE" : "applied";
|
|
13738
|
+
return ` ${state.padEnd(7)} -${String(removed).padStart(3)}/+${String(added).padStart(3)} ${path5} (${fileBlocks.length} block${fileBlocks.length === 1 ? "" : "s"})`;
|
|
13352
13739
|
});
|
|
13353
13740
|
return [
|
|
13354
13741
|
header2,
|
|
@@ -13517,7 +13904,7 @@ function LoopStatusRow({
|
|
|
13517
13904
|
}) {
|
|
13518
13905
|
const [, setTick] = React23.useState(0);
|
|
13519
13906
|
React23.useEffect(() => {
|
|
13520
|
-
const id = setInterval(() => setTick((
|
|
13907
|
+
const id = setInterval(() => setTick((t2) => t2 + 1), 1e3);
|
|
13521
13908
|
return () => clearInterval(id);
|
|
13522
13909
|
}, []);
|
|
13523
13910
|
const nextFireMs = Math.max(0, loop2.nextFireAt - Date.now());
|
|
@@ -13547,19 +13934,19 @@ function App({
|
|
|
13547
13934
|
}, [busy]);
|
|
13548
13935
|
const [ongoingTool, setOngoingTool] = useState10(null);
|
|
13549
13936
|
const [toolProgress, setToolProgress] = useState10(null);
|
|
13550
|
-
const { stdout:
|
|
13937
|
+
const { stdout: stdout3 } = useStdout8();
|
|
13551
13938
|
useEffect6(() => {
|
|
13552
|
-
if (!
|
|
13553
|
-
|
|
13554
|
-
|
|
13939
|
+
if (!stdout3 || !stdout3.isTTY) return;
|
|
13940
|
+
stdout3.write("\x1B[?2004h");
|
|
13941
|
+
stdout3.write("\x1B[>4;2m");
|
|
13555
13942
|
return () => {
|
|
13556
|
-
|
|
13557
|
-
|
|
13943
|
+
stdout3.write("\x1B[?2004l");
|
|
13944
|
+
stdout3.write("\x1B[>4m");
|
|
13558
13945
|
};
|
|
13559
|
-
}, [
|
|
13946
|
+
}, [stdout3]);
|
|
13560
13947
|
const [isResizing, setIsResizing] = useState10(false);
|
|
13561
13948
|
useEffect6(() => {
|
|
13562
|
-
if (!
|
|
13949
|
+
if (!stdout3 || !stdout3.isTTY) return;
|
|
13563
13950
|
let timer = null;
|
|
13564
13951
|
const onResize = () => {
|
|
13565
13952
|
setIsResizing(true);
|
|
@@ -13569,12 +13956,12 @@ function App({
|
|
|
13569
13956
|
timer = null;
|
|
13570
13957
|
}, 400);
|
|
13571
13958
|
};
|
|
13572
|
-
|
|
13959
|
+
stdout3.on("resize", onResize);
|
|
13573
13960
|
return () => {
|
|
13574
|
-
|
|
13961
|
+
stdout3.off("resize", onResize);
|
|
13575
13962
|
if (timer) clearTimeout(timer);
|
|
13576
13963
|
};
|
|
13577
|
-
}, [
|
|
13964
|
+
}, [stdout3]);
|
|
13578
13965
|
const { activity: subagentActivity, sinkRef: subagentSinkRef } = useSubagent({
|
|
13579
13966
|
session,
|
|
13580
13967
|
setHistorical
|
|
@@ -13598,7 +13985,7 @@ function App({
|
|
|
13598
13985
|
const [pendingCount, setPendingCount] = useState10(0);
|
|
13599
13986
|
const syncPendingCount = useCallback4(() => {
|
|
13600
13987
|
setPendingCount(pendingEdits.current.length);
|
|
13601
|
-
setPendingTick((
|
|
13988
|
+
setPendingTick((t2) => t2 + 1);
|
|
13602
13989
|
}, []);
|
|
13603
13990
|
const [editMode, setEditMode] = useState10(() => codeMode ? loadEditMode() : "review");
|
|
13604
13991
|
const editModeRef = useRef6(editMode);
|
|
@@ -13888,11 +14275,11 @@ function App({
|
|
|
13888
14275
|
if (key.escape && busy) {
|
|
13889
14276
|
if (abortedThisTurn.current) return;
|
|
13890
14277
|
abortedThisTurn.current = true;
|
|
13891
|
-
const
|
|
13892
|
-
if (
|
|
14278
|
+
const resolve9 = editReviewResolveRef.current;
|
|
14279
|
+
if (resolve9) {
|
|
13893
14280
|
editReviewResolveRef.current = null;
|
|
13894
14281
|
setPendingEditReview(null);
|
|
13895
|
-
|
|
14282
|
+
resolve9("reject");
|
|
13896
14283
|
}
|
|
13897
14284
|
if (activeLoopRef.current) stopLoop();
|
|
13898
14285
|
loop2.abort();
|
|
@@ -14403,7 +14790,7 @@ function App({
|
|
|
14403
14790
|
return;
|
|
14404
14791
|
}
|
|
14405
14792
|
if (result.clear && result.info) {
|
|
14406
|
-
|
|
14793
|
+
stdout3?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
14407
14794
|
setHistorical([
|
|
14408
14795
|
{
|
|
14409
14796
|
id: `sys-${Date.now()}`,
|
|
@@ -14420,7 +14807,7 @@ function App({
|
|
|
14420
14807
|
return;
|
|
14421
14808
|
}
|
|
14422
14809
|
if (result.clear) {
|
|
14423
|
-
|
|
14810
|
+
stdout3?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
14424
14811
|
setHistorical([]);
|
|
14425
14812
|
if (codeMode) {
|
|
14426
14813
|
pendingEdits.current = [];
|
|
@@ -14950,7 +15337,7 @@ function App({
|
|
|
14950
15337
|
refreshModels,
|
|
14951
15338
|
proArmed,
|
|
14952
15339
|
persistPlanState,
|
|
14953
|
-
|
|
15340
|
+
stdout3,
|
|
14954
15341
|
stopLoop,
|
|
14955
15342
|
startLoop,
|
|
14956
15343
|
getLoopStatus,
|
|
@@ -15500,10 +15887,10 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15500
15887
|
{
|
|
15501
15888
|
block: pendingEditReview,
|
|
15502
15889
|
onChoose: (choice) => {
|
|
15503
|
-
const
|
|
15504
|
-
if (
|
|
15890
|
+
const resolve9 = editReviewResolveRef.current;
|
|
15891
|
+
if (resolve9) {
|
|
15505
15892
|
editReviewResolveRef.current = null;
|
|
15506
|
-
|
|
15893
|
+
resolve9(choice);
|
|
15507
15894
|
}
|
|
15508
15895
|
}
|
|
15509
15896
|
}
|
|
@@ -15810,8 +16197,653 @@ async function chatCommand(opts) {
|
|
|
15810
16197
|
|
|
15811
16198
|
// src/cli/commands/code.tsx
|
|
15812
16199
|
import { basename as basename2, resolve as resolve7 } from "path";
|
|
16200
|
+
|
|
16201
|
+
// src/index/semantic/builder.ts
|
|
16202
|
+
import { promises as fs5 } from "fs";
|
|
16203
|
+
import path4 from "path";
|
|
16204
|
+
|
|
16205
|
+
// src/index/semantic/chunker.ts
|
|
16206
|
+
import { promises as fs3 } from "fs";
|
|
16207
|
+
import path2 from "path";
|
|
16208
|
+
var DEFAULT_MAX_CHUNK_CHARS = 4e3;
|
|
16209
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
16210
|
+
"node_modules",
|
|
16211
|
+
".git",
|
|
16212
|
+
"dist",
|
|
16213
|
+
"build",
|
|
16214
|
+
"out",
|
|
16215
|
+
".next",
|
|
16216
|
+
".nuxt",
|
|
16217
|
+
"target",
|
|
16218
|
+
".venv",
|
|
16219
|
+
"venv",
|
|
16220
|
+
"__pycache__",
|
|
16221
|
+
".pytest_cache",
|
|
16222
|
+
".mypy_cache",
|
|
16223
|
+
".cache",
|
|
16224
|
+
"coverage",
|
|
16225
|
+
".turbo",
|
|
16226
|
+
".vercel",
|
|
16227
|
+
".reasonix"
|
|
16228
|
+
]);
|
|
16229
|
+
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
16230
|
+
"package-lock.json",
|
|
16231
|
+
"yarn.lock",
|
|
16232
|
+
"pnpm-lock.yaml",
|
|
16233
|
+
"Cargo.lock",
|
|
16234
|
+
"poetry.lock",
|
|
16235
|
+
"Pipfile.lock",
|
|
16236
|
+
"go.sum",
|
|
16237
|
+
".DS_Store"
|
|
16238
|
+
]);
|
|
16239
|
+
var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
16240
|
+
// Images
|
|
16241
|
+
".png",
|
|
16242
|
+
".jpg",
|
|
16243
|
+
".jpeg",
|
|
16244
|
+
".gif",
|
|
16245
|
+
".webp",
|
|
16246
|
+
".bmp",
|
|
16247
|
+
".ico",
|
|
16248
|
+
".tiff",
|
|
16249
|
+
// Fonts
|
|
16250
|
+
".woff",
|
|
16251
|
+
".woff2",
|
|
16252
|
+
".ttf",
|
|
16253
|
+
".otf",
|
|
16254
|
+
".eot",
|
|
16255
|
+
// Archives / binaries
|
|
16256
|
+
".zip",
|
|
16257
|
+
".tar",
|
|
16258
|
+
".gz",
|
|
16259
|
+
".rar",
|
|
16260
|
+
".7z",
|
|
16261
|
+
".exe",
|
|
16262
|
+
".dll",
|
|
16263
|
+
".so",
|
|
16264
|
+
".dylib",
|
|
16265
|
+
".class",
|
|
16266
|
+
".jar",
|
|
16267
|
+
".wasm",
|
|
16268
|
+
".o",
|
|
16269
|
+
".a",
|
|
16270
|
+
// Media
|
|
16271
|
+
".mp3",
|
|
16272
|
+
".mp4",
|
|
16273
|
+
".wav",
|
|
16274
|
+
".ogg",
|
|
16275
|
+
".webm",
|
|
16276
|
+
".mov",
|
|
16277
|
+
// Other
|
|
16278
|
+
".pdf",
|
|
16279
|
+
".sqlite",
|
|
16280
|
+
".db"
|
|
16281
|
+
]);
|
|
16282
|
+
function chunkText(text, filePath, windowLines, overlap, maxChunkChars = DEFAULT_MAX_CHUNK_CHARS) {
|
|
16283
|
+
const lines = text.split(/\r?\n/);
|
|
16284
|
+
if (lines.length === 0 || lines.length === 1 && lines[0] === "") return [];
|
|
16285
|
+
const stride = Math.max(1, windowLines - overlap);
|
|
16286
|
+
const chunks = [];
|
|
16287
|
+
for (let start = 0; start < lines.length; start += stride) {
|
|
16288
|
+
const end = Math.min(lines.length, start + windowLines);
|
|
16289
|
+
const slice = lines.slice(start, end).join("\n").trim();
|
|
16290
|
+
if (slice.length === 0) {
|
|
16291
|
+
if (end >= lines.length) break;
|
|
16292
|
+
continue;
|
|
16293
|
+
}
|
|
16294
|
+
const window = {
|
|
16295
|
+
path: filePath,
|
|
16296
|
+
startLine: start + 1,
|
|
16297
|
+
endLine: end,
|
|
16298
|
+
text: slice
|
|
16299
|
+
};
|
|
16300
|
+
for (const sub of safeSplit(window, maxChunkChars)) chunks.push(sub);
|
|
16301
|
+
if (end >= lines.length) break;
|
|
16302
|
+
}
|
|
16303
|
+
return chunks;
|
|
16304
|
+
}
|
|
16305
|
+
function safeSplit(chunk, maxChars) {
|
|
16306
|
+
if (chunk.text.length <= maxChars) return [chunk];
|
|
16307
|
+
const lines = chunk.text.split("\n");
|
|
16308
|
+
const out = [];
|
|
16309
|
+
let bufLines = [];
|
|
16310
|
+
let bufStart = chunk.startLine;
|
|
16311
|
+
let bufLen = 0;
|
|
16312
|
+
const flush = (untilLineNo) => {
|
|
16313
|
+
if (bufLines.length === 0) return;
|
|
16314
|
+
out.push({
|
|
16315
|
+
path: chunk.path,
|
|
16316
|
+
startLine: bufStart,
|
|
16317
|
+
endLine: untilLineNo,
|
|
16318
|
+
text: bufLines.join("\n")
|
|
16319
|
+
});
|
|
16320
|
+
bufLines = [];
|
|
16321
|
+
bufLen = 0;
|
|
16322
|
+
};
|
|
16323
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16324
|
+
const line = lines[i] ?? "";
|
|
16325
|
+
const lineLen = line.length + 1;
|
|
16326
|
+
if (lineLen > maxChars) {
|
|
16327
|
+
flush(chunk.startLine + i - 1);
|
|
16328
|
+
out.push({
|
|
16329
|
+
path: chunk.path,
|
|
16330
|
+
startLine: chunk.startLine + i,
|
|
16331
|
+
endLine: chunk.startLine + i,
|
|
16332
|
+
text: line.slice(0, maxChars)
|
|
16333
|
+
});
|
|
16334
|
+
bufStart = chunk.startLine + i + 1;
|
|
16335
|
+
continue;
|
|
16336
|
+
}
|
|
16337
|
+
if (bufLen + lineLen > maxChars && bufLines.length > 0) {
|
|
16338
|
+
flush(chunk.startLine + i - 1);
|
|
16339
|
+
bufStart = chunk.startLine + i;
|
|
16340
|
+
}
|
|
16341
|
+
bufLines.push(line);
|
|
16342
|
+
bufLen += lineLen;
|
|
16343
|
+
}
|
|
16344
|
+
flush(chunk.endLine);
|
|
16345
|
+
return out;
|
|
16346
|
+
}
|
|
16347
|
+
async function* walkChunks(root, opts = {}) {
|
|
16348
|
+
const windowLines = opts.windowLines ?? 60;
|
|
16349
|
+
const overlap = Math.min(opts.overlap ?? 12, Math.max(0, windowLines - 1));
|
|
16350
|
+
const maxFileBytes = opts.maxFileBytes ?? 256 * 1024;
|
|
16351
|
+
const maxChunkChars = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
|
|
16352
|
+
const stack = [root];
|
|
16353
|
+
while (stack.length > 0) {
|
|
16354
|
+
const dir = stack.pop();
|
|
16355
|
+
if (!dir) break;
|
|
16356
|
+
let entries;
|
|
16357
|
+
try {
|
|
16358
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
16359
|
+
} catch {
|
|
16360
|
+
continue;
|
|
16361
|
+
}
|
|
16362
|
+
for (const entry of entries) {
|
|
16363
|
+
const name = entry.name;
|
|
16364
|
+
if (entry.isDirectory()) {
|
|
16365
|
+
if (SKIP_DIRS.has(name) || name.startsWith(".")) {
|
|
16366
|
+
if (SKIP_DIRS.has(name) || name === ".git") continue;
|
|
16367
|
+
}
|
|
16368
|
+
stack.push(path2.join(dir, name));
|
|
16369
|
+
continue;
|
|
16370
|
+
}
|
|
16371
|
+
if (!entry.isFile()) continue;
|
|
16372
|
+
if (SKIP_FILES.has(name)) continue;
|
|
16373
|
+
const ext = path2.extname(name).toLowerCase();
|
|
16374
|
+
if (BINARY_EXTS.has(ext)) continue;
|
|
16375
|
+
const abs = path2.join(dir, name);
|
|
16376
|
+
let stat;
|
|
16377
|
+
try {
|
|
16378
|
+
stat = await fs3.stat(abs);
|
|
16379
|
+
} catch {
|
|
16380
|
+
continue;
|
|
16381
|
+
}
|
|
16382
|
+
if (stat.size > maxFileBytes) continue;
|
|
16383
|
+
let text;
|
|
16384
|
+
try {
|
|
16385
|
+
text = await fs3.readFile(abs, "utf8");
|
|
16386
|
+
} catch {
|
|
16387
|
+
continue;
|
|
16388
|
+
}
|
|
16389
|
+
if (text.indexOf("\0") !== -1) continue;
|
|
16390
|
+
const rel = path2.relative(root, abs).split(path2.sep).join("/");
|
|
16391
|
+
for (const chunk of chunkText(text, rel, windowLines, overlap, maxChunkChars)) {
|
|
16392
|
+
yield chunk;
|
|
16393
|
+
}
|
|
16394
|
+
}
|
|
16395
|
+
}
|
|
16396
|
+
}
|
|
16397
|
+
|
|
16398
|
+
// src/index/semantic/store.ts
|
|
16399
|
+
import { promises as fs4 } from "fs";
|
|
16400
|
+
import path3 from "path";
|
|
16401
|
+
var STORE_VERSION = 1;
|
|
16402
|
+
var META_FILE = "index.meta.json";
|
|
16403
|
+
var DATA_FILE = "index.jsonl";
|
|
16404
|
+
var SemanticStore = class {
|
|
16405
|
+
constructor(indexDir, model2) {
|
|
16406
|
+
this.indexDir = indexDir;
|
|
16407
|
+
this.model = model2;
|
|
16408
|
+
}
|
|
16409
|
+
indexDir;
|
|
16410
|
+
model;
|
|
16411
|
+
entries = [];
|
|
16412
|
+
byPath = /* @__PURE__ */ new Map();
|
|
16413
|
+
dim = 0;
|
|
16414
|
+
/** True when no entries are loaded — the index doesn't exist or is empty. */
|
|
16415
|
+
get empty() {
|
|
16416
|
+
return this.entries.length === 0;
|
|
16417
|
+
}
|
|
16418
|
+
/** Total number of indexed chunks. */
|
|
16419
|
+
get size() {
|
|
16420
|
+
return this.entries.length;
|
|
16421
|
+
}
|
|
16422
|
+
/** Read-only view, mostly for tests. */
|
|
16423
|
+
get all() {
|
|
16424
|
+
return this.entries;
|
|
16425
|
+
}
|
|
16426
|
+
/** Last-known mtime per indexed file (ms epoch) for incremental rebuilds. */
|
|
16427
|
+
fileMtimes() {
|
|
16428
|
+
const out = /* @__PURE__ */ new Map();
|
|
16429
|
+
for (const [p, group] of this.byPath) {
|
|
16430
|
+
const first = group[0];
|
|
16431
|
+
if (first) out.set(p, first.mtimeMs);
|
|
16432
|
+
}
|
|
16433
|
+
return out;
|
|
16434
|
+
}
|
|
16435
|
+
/** Append entries to in-memory state and to disk. Re-indexes the
|
|
16436
|
+
* `byPath` map. Caller is responsible for L2-normalizing each
|
|
16437
|
+
* embedding before calling — the search hot path assumes unit vectors. */
|
|
16438
|
+
async add(entries) {
|
|
16439
|
+
if (entries.length === 0) return;
|
|
16440
|
+
if (this.dim === 0) this.dim = entries[0].embedding.length;
|
|
16441
|
+
const lines = [];
|
|
16442
|
+
for (const e of entries) {
|
|
16443
|
+
if (e.embedding.length !== this.dim) {
|
|
16444
|
+
throw new Error(
|
|
16445
|
+
`embedding dim mismatch: expected ${this.dim}, got ${e.embedding.length} for ${e.path}:${e.startLine}`
|
|
16446
|
+
);
|
|
16447
|
+
}
|
|
16448
|
+
this.entries.push(e);
|
|
16449
|
+
const list = this.byPath.get(e.path);
|
|
16450
|
+
if (list) list.push(e);
|
|
16451
|
+
else this.byPath.set(e.path, [e]);
|
|
16452
|
+
lines.push(serializeEntry(e));
|
|
16453
|
+
}
|
|
16454
|
+
await fs4.mkdir(this.indexDir, { recursive: true });
|
|
16455
|
+
await fs4.appendFile(path3.join(this.indexDir, DATA_FILE), `${lines.join("\n")}
|
|
16456
|
+
`, "utf8");
|
|
16457
|
+
await this.writeMeta();
|
|
16458
|
+
}
|
|
16459
|
+
/**
|
|
16460
|
+
* Drop every entry whose `path` is in `paths`. Used by incremental
|
|
16461
|
+
* rebuild: when a file's mtime changes, the existing entries for
|
|
16462
|
+
* it are evicted before re-chunking + re-embedding. Implementation
|
|
16463
|
+
* rewrites the JSONL — append-only is fine for adds, but deletes
|
|
16464
|
+
* need a compaction pass.
|
|
16465
|
+
*/
|
|
16466
|
+
async remove(paths) {
|
|
16467
|
+
if (paths.length === 0) return 0;
|
|
16468
|
+
const drop = new Set(paths);
|
|
16469
|
+
const before = this.entries.length;
|
|
16470
|
+
this.entries = this.entries.filter((e) => !drop.has(e.path));
|
|
16471
|
+
for (const p of paths) this.byPath.delete(p);
|
|
16472
|
+
const removed = before - this.entries.length;
|
|
16473
|
+
if (removed > 0) await this.flush();
|
|
16474
|
+
return removed;
|
|
16475
|
+
}
|
|
16476
|
+
/**
|
|
16477
|
+
* Top-K cosine search. `query` MUST already be L2-normalized — the
|
|
16478
|
+
* caller embeds + normalizes once per query. Filtering hits by
|
|
16479
|
+
* minimum score (`minScore`) is optional; tune via UI to suppress
|
|
16480
|
+
* weakly relevant snippets that distract the model.
|
|
16481
|
+
*/
|
|
16482
|
+
search(query, topK = 8, minScore = 0) {
|
|
16483
|
+
if (this.entries.length === 0) return [];
|
|
16484
|
+
if (query.length !== this.dim && this.dim !== 0) {
|
|
16485
|
+
throw new Error(`query dim ${query.length} \u2260 index dim ${this.dim}`);
|
|
16486
|
+
}
|
|
16487
|
+
const heap = [];
|
|
16488
|
+
for (const entry of this.entries) {
|
|
16489
|
+
const score = dot(query, entry.embedding);
|
|
16490
|
+
if (score < minScore) continue;
|
|
16491
|
+
if (heap.length < topK) {
|
|
16492
|
+
heap.push({ entry, score });
|
|
16493
|
+
if (heap.length === topK) heap.sort((a, b) => a.score - b.score);
|
|
16494
|
+
} else if (score > heap[0].score) {
|
|
16495
|
+
heap[0] = { entry, score };
|
|
16496
|
+
for (let i = 0; i < heap.length - 1; i++) {
|
|
16497
|
+
if (heap[i].score > heap[i + 1].score) {
|
|
16498
|
+
const tmp = heap[i];
|
|
16499
|
+
heap[i] = heap[i + 1];
|
|
16500
|
+
heap[i + 1] = tmp;
|
|
16501
|
+
}
|
|
16502
|
+
}
|
|
16503
|
+
}
|
|
16504
|
+
}
|
|
16505
|
+
return heap.sort((a, b) => b.score - a.score);
|
|
16506
|
+
}
|
|
16507
|
+
/**
|
|
16508
|
+
* Rewrite the JSONL on disk with the current in-memory state. Used
|
|
16509
|
+
* after `remove` and from `flush`. We write to a temp file and
|
|
16510
|
+
* rename so a Ctrl+C mid-write never leaves the index half-empty.
|
|
16511
|
+
*/
|
|
16512
|
+
async flush() {
|
|
16513
|
+
await fs4.mkdir(this.indexDir, { recursive: true });
|
|
16514
|
+
const tmp = path3.join(this.indexDir, `${DATA_FILE}.tmp`);
|
|
16515
|
+
const final = path3.join(this.indexDir, DATA_FILE);
|
|
16516
|
+
const lines = this.entries.map(serializeEntry).join("\n");
|
|
16517
|
+
await fs4.writeFile(tmp, lines.length > 0 ? `${lines}
|
|
16518
|
+
` : "", "utf8");
|
|
16519
|
+
await fs4.rename(tmp, final);
|
|
16520
|
+
await this.writeMeta();
|
|
16521
|
+
}
|
|
16522
|
+
async writeMeta() {
|
|
16523
|
+
const meta = {
|
|
16524
|
+
version: STORE_VERSION,
|
|
16525
|
+
model: this.model,
|
|
16526
|
+
dim: this.dim,
|
|
16527
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16528
|
+
};
|
|
16529
|
+
await fs4.writeFile(
|
|
16530
|
+
path3.join(this.indexDir, META_FILE),
|
|
16531
|
+
`${JSON.stringify(meta, null, 2)}
|
|
16532
|
+
`,
|
|
16533
|
+
"utf8"
|
|
16534
|
+
);
|
|
16535
|
+
}
|
|
16536
|
+
/** Drop everything from disk + memory. Used by `--rebuild`. */
|
|
16537
|
+
async wipe() {
|
|
16538
|
+
this.entries = [];
|
|
16539
|
+
this.byPath.clear();
|
|
16540
|
+
this.dim = 0;
|
|
16541
|
+
await fs4.rm(path3.join(this.indexDir, DATA_FILE), { force: true });
|
|
16542
|
+
await fs4.rm(path3.join(this.indexDir, META_FILE), { force: true });
|
|
16543
|
+
}
|
|
16544
|
+
};
|
|
16545
|
+
async function openStore(indexDir, model2) {
|
|
16546
|
+
const store = new SemanticStore(indexDir, model2);
|
|
16547
|
+
const dataPath = path3.join(indexDir, DATA_FILE);
|
|
16548
|
+
const metaPath = path3.join(indexDir, META_FILE);
|
|
16549
|
+
let meta = null;
|
|
16550
|
+
try {
|
|
16551
|
+
const raw2 = await fs4.readFile(metaPath, "utf8");
|
|
16552
|
+
meta = JSON.parse(raw2);
|
|
16553
|
+
} catch {
|
|
16554
|
+
}
|
|
16555
|
+
if (meta) {
|
|
16556
|
+
if (meta.version !== STORE_VERSION) {
|
|
16557
|
+
throw new Error(
|
|
16558
|
+
`Index format version ${meta.version} does not match current ${STORE_VERSION}. Run \`reasonix index --rebuild\`.`
|
|
16559
|
+
);
|
|
16560
|
+
}
|
|
16561
|
+
if (meta.model !== model2) {
|
|
16562
|
+
throw new Error(
|
|
16563
|
+
`Index was built with model "${meta.model}" but current is "${model2}". Run \`reasonix index --rebuild\`.`
|
|
16564
|
+
);
|
|
16565
|
+
}
|
|
16566
|
+
}
|
|
16567
|
+
let raw;
|
|
16568
|
+
try {
|
|
16569
|
+
raw = await fs4.readFile(dataPath, "utf8");
|
|
16570
|
+
} catch {
|
|
16571
|
+
return store;
|
|
16572
|
+
}
|
|
16573
|
+
for (const line of raw.split("\n")) {
|
|
16574
|
+
if (line.length === 0) continue;
|
|
16575
|
+
try {
|
|
16576
|
+
const entry = deserializeEntry(line);
|
|
16577
|
+
store.dim = entry.embedding.length;
|
|
16578
|
+
store.entries.push(entry);
|
|
16579
|
+
const map = store.byPath;
|
|
16580
|
+
const list = map.get(entry.path);
|
|
16581
|
+
if (list) list.push(entry);
|
|
16582
|
+
else map.set(entry.path, [entry]);
|
|
16583
|
+
} catch {
|
|
16584
|
+
}
|
|
16585
|
+
}
|
|
16586
|
+
return store;
|
|
16587
|
+
}
|
|
16588
|
+
function normalize(v) {
|
|
16589
|
+
let sum = 0;
|
|
16590
|
+
for (let i = 0; i < v.length; i++) sum += v[i] * v[i];
|
|
16591
|
+
const inv = sum > 0 ? 1 / Math.sqrt(sum) : 0;
|
|
16592
|
+
for (let i = 0; i < v.length; i++) v[i] = v[i] * inv;
|
|
16593
|
+
return v;
|
|
16594
|
+
}
|
|
16595
|
+
function dot(a, b) {
|
|
16596
|
+
let s = 0;
|
|
16597
|
+
for (let i = 0; i < a.length; i++) s += a[i] * b[i];
|
|
16598
|
+
return s;
|
|
16599
|
+
}
|
|
16600
|
+
function serializeEntry(e) {
|
|
16601
|
+
const buf = Buffer.from(e.embedding.buffer, e.embedding.byteOffset, e.embedding.byteLength);
|
|
16602
|
+
return JSON.stringify({
|
|
16603
|
+
p: e.path,
|
|
16604
|
+
s: e.startLine,
|
|
16605
|
+
e: e.endLine,
|
|
16606
|
+
m: e.mtimeMs,
|
|
16607
|
+
t: e.text,
|
|
16608
|
+
v: buf.toString("base64")
|
|
16609
|
+
});
|
|
16610
|
+
}
|
|
16611
|
+
function deserializeEntry(line) {
|
|
16612
|
+
const parsed = JSON.parse(line);
|
|
16613
|
+
const bytes = Buffer.from(parsed.v, "base64");
|
|
16614
|
+
const f32 = new Float32Array(
|
|
16615
|
+
bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
|
|
16616
|
+
);
|
|
16617
|
+
return {
|
|
16618
|
+
path: parsed.p,
|
|
16619
|
+
startLine: parsed.s,
|
|
16620
|
+
endLine: parsed.e,
|
|
16621
|
+
mtimeMs: parsed.m,
|
|
16622
|
+
text: parsed.t,
|
|
16623
|
+
embedding: f32
|
|
16624
|
+
};
|
|
16625
|
+
}
|
|
16626
|
+
|
|
16627
|
+
// src/index/semantic/builder.ts
|
|
16628
|
+
var INDEX_DIR_NAME = path4.join(".reasonix", "semantic");
|
|
16629
|
+
async function buildIndex(root, opts = {}) {
|
|
16630
|
+
const t0 = Date.now();
|
|
16631
|
+
const indexDir = path4.join(root, INDEX_DIR_NAME);
|
|
16632
|
+
const probe = await probeOllama({ baseUrl: opts.baseUrl, signal: opts.signal });
|
|
16633
|
+
if (!probe.ok) {
|
|
16634
|
+
throw new Error(
|
|
16635
|
+
`Ollama is not reachable: ${probe.error}. Install from https://ollama.com, then \`ollama serve\` and \`ollama pull ${opts.model ?? "nomic-embed-text"}\`.`
|
|
16636
|
+
);
|
|
16637
|
+
}
|
|
16638
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
16639
|
+
const store = await openStore(indexDir, model2);
|
|
16640
|
+
if (opts.rebuild) await store.wipe();
|
|
16641
|
+
const lastMtimes = store.fileMtimes();
|
|
16642
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
16643
|
+
const fileChunks = /* @__PURE__ */ new Map();
|
|
16644
|
+
let filesScanned = 0;
|
|
16645
|
+
let filesSkipped = 0;
|
|
16646
|
+
for await (const chunk of walkChunks(root, {
|
|
16647
|
+
windowLines: opts.windowLines,
|
|
16648
|
+
overlap: opts.overlap,
|
|
16649
|
+
maxFileBytes: opts.maxFileBytes
|
|
16650
|
+
})) {
|
|
16651
|
+
seenPaths.add(chunk.path);
|
|
16652
|
+
let bucket = fileChunks.get(chunk.path);
|
|
16653
|
+
if (!bucket) {
|
|
16654
|
+
filesScanned++;
|
|
16655
|
+
const abs = path4.join(root, chunk.path);
|
|
16656
|
+
let mtimeMs = 0;
|
|
16657
|
+
try {
|
|
16658
|
+
const stat = await fs5.stat(abs);
|
|
16659
|
+
mtimeMs = stat.mtimeMs;
|
|
16660
|
+
} catch {
|
|
16661
|
+
continue;
|
|
16662
|
+
}
|
|
16663
|
+
const last = lastMtimes.get(chunk.path);
|
|
16664
|
+
if (last !== void 0 && last === mtimeMs && !opts.rebuild) {
|
|
16665
|
+
filesSkipped++;
|
|
16666
|
+
continue;
|
|
16667
|
+
}
|
|
16668
|
+
bucket = { chunks: [], mtimeMs };
|
|
16669
|
+
fileChunks.set(chunk.path, bucket);
|
|
16670
|
+
}
|
|
16671
|
+
bucket.chunks.push(chunk);
|
|
16672
|
+
opts.onProgress?.({ phase: "scan", filesScanned });
|
|
16673
|
+
}
|
|
16674
|
+
const deletedPaths = [];
|
|
16675
|
+
for (const oldPath of lastMtimes.keys()) {
|
|
16676
|
+
if (!seenPaths.has(oldPath)) deletedPaths.push(oldPath);
|
|
16677
|
+
}
|
|
16678
|
+
const replacePaths = [...fileChunks.keys()].filter((p) => lastMtimes.has(p));
|
|
16679
|
+
const removed = await store.remove([...deletedPaths, ...replacePaths]);
|
|
16680
|
+
let chunksAdded = 0;
|
|
16681
|
+
let chunksSkipped = 0;
|
|
16682
|
+
const filesChanged = fileChunks.size;
|
|
16683
|
+
let chunksTotal = 0;
|
|
16684
|
+
for (const { chunks } of fileChunks.values()) chunksTotal += chunks.length;
|
|
16685
|
+
let chunksDone = 0;
|
|
16686
|
+
for (const [, bucket] of fileChunks) {
|
|
16687
|
+
if (bucket.chunks.length === 0) continue;
|
|
16688
|
+
const texts = bucket.chunks.map((c) => c.text);
|
|
16689
|
+
const vectors = await embedAll(texts, {
|
|
16690
|
+
...opts,
|
|
16691
|
+
onProgress: (done, total) => {
|
|
16692
|
+
opts.onProgress?.({
|
|
16693
|
+
phase: "embed",
|
|
16694
|
+
filesScanned,
|
|
16695
|
+
filesChanged,
|
|
16696
|
+
chunksTotal,
|
|
16697
|
+
chunksDone: chunksDone + done
|
|
16698
|
+
});
|
|
16699
|
+
if (done === total) chunksDone += total;
|
|
16700
|
+
},
|
|
16701
|
+
onError: (idx, err) => {
|
|
16702
|
+
chunksSkipped++;
|
|
16703
|
+
const c = bucket.chunks[idx];
|
|
16704
|
+
const where = c ? `${c.path}:${c.startLine}-${c.endLine}` : `chunk #${idx}`;
|
|
16705
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16706
|
+
process.stderr.write(`
|
|
16707
|
+
! skipped ${where}: ${msg}
|
|
16708
|
+
`);
|
|
16709
|
+
}
|
|
16710
|
+
});
|
|
16711
|
+
const entries = [];
|
|
16712
|
+
for (let i = 0; i < bucket.chunks.length; i++) {
|
|
16713
|
+
const vec = vectors[i];
|
|
16714
|
+
if (!vec) continue;
|
|
16715
|
+
const c = bucket.chunks[i];
|
|
16716
|
+
if (!c) continue;
|
|
16717
|
+
normalize(vec);
|
|
16718
|
+
entries.push({
|
|
16719
|
+
path: c.path,
|
|
16720
|
+
startLine: c.startLine,
|
|
16721
|
+
endLine: c.endLine,
|
|
16722
|
+
text: c.text,
|
|
16723
|
+
embedding: vec,
|
|
16724
|
+
mtimeMs: bucket.mtimeMs
|
|
16725
|
+
});
|
|
16726
|
+
}
|
|
16727
|
+
if (entries.length > 0) await store.add(entries);
|
|
16728
|
+
chunksAdded += entries.length;
|
|
16729
|
+
}
|
|
16730
|
+
opts.onProgress?.({
|
|
16731
|
+
phase: "done",
|
|
16732
|
+
filesScanned,
|
|
16733
|
+
filesSkipped,
|
|
16734
|
+
filesChanged,
|
|
16735
|
+
chunksTotal,
|
|
16736
|
+
chunksDone
|
|
16737
|
+
});
|
|
16738
|
+
return {
|
|
16739
|
+
filesScanned,
|
|
16740
|
+
filesChanged,
|
|
16741
|
+
chunksAdded,
|
|
16742
|
+
chunksRemoved: removed,
|
|
16743
|
+
chunksSkipped,
|
|
16744
|
+
durationMs: Date.now() - t0
|
|
16745
|
+
};
|
|
16746
|
+
}
|
|
16747
|
+
async function querySemantic(root, query, opts = {}) {
|
|
16748
|
+
const indexDir = path4.join(root, INDEX_DIR_NAME);
|
|
16749
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
16750
|
+
const store = await openStore(indexDir, model2);
|
|
16751
|
+
if (store.empty) return null;
|
|
16752
|
+
const qvec = await embed(query, opts);
|
|
16753
|
+
normalize(qvec);
|
|
16754
|
+
return store.search(qvec, opts.topK ?? 8, opts.minScore ?? 0.3);
|
|
16755
|
+
}
|
|
16756
|
+
async function indexExists(root) {
|
|
16757
|
+
const meta = path4.join(root, INDEX_DIR_NAME, "index.meta.json");
|
|
16758
|
+
try {
|
|
16759
|
+
await fs5.access(meta);
|
|
16760
|
+
return true;
|
|
16761
|
+
} catch {
|
|
16762
|
+
return false;
|
|
16763
|
+
}
|
|
16764
|
+
}
|
|
16765
|
+
|
|
16766
|
+
// src/index/semantic/tool.ts
|
|
16767
|
+
async function registerSemanticSearchTool(registry, opts) {
|
|
16768
|
+
if (!await indexExists(opts.root)) return false;
|
|
16769
|
+
const defaultTopK = opts.defaultTopK ?? 8;
|
|
16770
|
+
const defaultMinScore = opts.defaultMinScore ?? 0.3;
|
|
16771
|
+
registry.register({
|
|
16772
|
+
name: "semantic_search",
|
|
16773
|
+
description: "FIRST CHOICE for descriptive queries. Use this BEFORE search_content (grep) when the user describes WHAT code does ('where do we handle X', 'which file owns Y', 'how does Z work', 'find the logic that \u2026'). Returns ranked snippets ordered by semantic relevance \u2014 finds the right file even when your description shares no words with the code. Falls back to search_content / search_files only for: exact identifiers, regex patterns, or counting occurrences of a known token. If your first instinct is grep on a paraphrased question, you are wrong \u2014 try semantic_search first.",
|
|
16774
|
+
readOnly: true,
|
|
16775
|
+
parameters: {
|
|
16776
|
+
type: "object",
|
|
16777
|
+
properties: {
|
|
16778
|
+
query: {
|
|
16779
|
+
type: "string",
|
|
16780
|
+
description: "Natural-language description, phrased as a question or noun phrase: 'where do we validate the session cookie?' / 'retry backoff logic' / 'code that prevents user changes from immediately landing on disk'. Do NOT pass exact identifiers \u2014 those are search_content's job."
|
|
16781
|
+
},
|
|
16782
|
+
topK: {
|
|
16783
|
+
type: "integer",
|
|
16784
|
+
description: `Number of snippets to return (1..16). Default ${defaultTopK}.`
|
|
16785
|
+
},
|
|
16786
|
+
minScore: {
|
|
16787
|
+
type: "number",
|
|
16788
|
+
description: `Drop snippets with cosine score below this (0..1). Default ${defaultMinScore}. Raise for stricter matches; lower if the index is small.`
|
|
16789
|
+
}
|
|
16790
|
+
},
|
|
16791
|
+
required: ["query"]
|
|
16792
|
+
},
|
|
16793
|
+
fn: async (args, ctx) => {
|
|
16794
|
+
const hits = await querySemantic(opts.root, args.query, {
|
|
16795
|
+
topK: args.topK ?? defaultTopK,
|
|
16796
|
+
minScore: args.minScore ?? defaultMinScore,
|
|
16797
|
+
baseUrl: opts.baseUrl,
|
|
16798
|
+
model: opts.model,
|
|
16799
|
+
signal: ctx?.signal
|
|
16800
|
+
});
|
|
16801
|
+
if (hits === null) {
|
|
16802
|
+
return "No semantic index found for this project. Run `reasonix index` to build one.";
|
|
16803
|
+
}
|
|
16804
|
+
if (hits.length === 0) {
|
|
16805
|
+
return `query: ${args.query}
|
|
16806
|
+
|
|
16807
|
+
no matches above the score threshold (${args.minScore ?? defaultMinScore}).`;
|
|
16808
|
+
}
|
|
16809
|
+
return formatHits(args.query, hits);
|
|
16810
|
+
}
|
|
16811
|
+
});
|
|
16812
|
+
return true;
|
|
16813
|
+
}
|
|
16814
|
+
function formatHits(query, hits) {
|
|
16815
|
+
const lines = [`query: ${query}`, `
|
|
16816
|
+
results (${hits.length}):`];
|
|
16817
|
+
hits.forEach((h, i) => {
|
|
16818
|
+
const { entry, score } = h;
|
|
16819
|
+
lines.push(
|
|
16820
|
+
`
|
|
16821
|
+
${i + 1}. ${entry.path}:${entry.startLine}-${entry.endLine} (score ${score.toFixed(3)})`
|
|
16822
|
+
);
|
|
16823
|
+
const preview = entry.text.split("\n").slice(0, 8).join("\n");
|
|
16824
|
+
lines.push(indentBlock(preview, " "));
|
|
16825
|
+
if (entry.text.split("\n").length > 8) {
|
|
16826
|
+
lines.push(
|
|
16827
|
+
` \u2026(${entry.text.split("\n").length - 8} more lines \u2014 read_file ${entry.path}:${entry.startLine} for the full chunk)`
|
|
16828
|
+
);
|
|
16829
|
+
}
|
|
16830
|
+
});
|
|
16831
|
+
return lines.join("\n");
|
|
16832
|
+
}
|
|
16833
|
+
function indentBlock(text, prefix) {
|
|
16834
|
+
return text.split("\n").map((l) => prefix + l).join("\n");
|
|
16835
|
+
}
|
|
16836
|
+
async function bootstrapSemanticSearchInCodeMode(registry, rootDir, opts = {}) {
|
|
16837
|
+
if (await indexExists(rootDir)) {
|
|
16838
|
+
await registerSemanticSearchTool(registry, { ...opts, root: rootDir });
|
|
16839
|
+
return { enabled: true };
|
|
16840
|
+
}
|
|
16841
|
+
return { enabled: false };
|
|
16842
|
+
}
|
|
16843
|
+
|
|
16844
|
+
// src/cli/commands/code.tsx
|
|
15813
16845
|
async function codeCommand(opts = {}) {
|
|
15814
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-
|
|
16846
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-FMYQ7IDW.js");
|
|
15815
16847
|
const rootDir = resolve7(opts.dir ?? process.cwd());
|
|
15816
16848
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
15817
16849
|
const tools = new ToolRegistry();
|
|
@@ -15834,8 +16866,9 @@ async function codeCommand(opts = {}) {
|
|
|
15834
16866
|
registerPlanTool(tools);
|
|
15835
16867
|
registerChoiceTool(tools);
|
|
15836
16868
|
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
16869
|
+
const semantic2 = await bootstrapSemanticSearchInCodeMode(tools, rootDir);
|
|
15837
16870
|
process.stderr.write(
|
|
15838
|
-
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
16871
|
+
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)${semantic2.enabled ? " \xB7 semantic_search on" : ""}
|
|
15839
16872
|
`
|
|
15840
16873
|
);
|
|
15841
16874
|
process.once("exit", () => {
|
|
@@ -15844,7 +16877,7 @@ async function codeCommand(opts = {}) {
|
|
|
15844
16877
|
await chatCommand({
|
|
15845
16878
|
model: opts.model ?? "deepseek-v4-flash",
|
|
15846
16879
|
harvest: opts.harvest ?? false,
|
|
15847
|
-
system: codeSystemPrompt2(rootDir),
|
|
16880
|
+
system: codeSystemPrompt2(rootDir, { hasSemanticSearch: semantic2.enabled }),
|
|
15848
16881
|
transcript: opts.transcript,
|
|
15849
16882
|
session,
|
|
15850
16883
|
seedTools: tools,
|
|
@@ -16018,6 +17051,200 @@ markdown report written to ${opts.mdPath}`);
|
|
|
16018
17051
|
console.log(renderSummaryTable(report));
|
|
16019
17052
|
}
|
|
16020
17053
|
|
|
17054
|
+
// src/cli/commands/index.ts
|
|
17055
|
+
import { resolve as resolve8 } from "path";
|
|
17056
|
+
|
|
17057
|
+
// src/index/semantic/preflight.ts
|
|
17058
|
+
import { stdin as stdin2, stdout } from "process";
|
|
17059
|
+
import { createInterface } from "readline/promises";
|
|
17060
|
+
async function ollamaPreflight(opts) {
|
|
17061
|
+
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
17062
|
+
const status2 = await checkOllamaStatus(opts.model, opts.baseUrl);
|
|
17063
|
+
if (!status2.binaryFound) {
|
|
17064
|
+
log(t("ollamaNotFound"));
|
|
17065
|
+
return false;
|
|
17066
|
+
}
|
|
17067
|
+
if (!status2.daemonRunning) {
|
|
17068
|
+
if (!opts.interactive && !opts.yesToAll) {
|
|
17069
|
+
log(t("daemonNotReachableHint"));
|
|
17070
|
+
return false;
|
|
17071
|
+
}
|
|
17072
|
+
const ok = opts.yesToAll || await confirm(t("daemonStartConfirm"), true);
|
|
17073
|
+
if (!ok) {
|
|
17074
|
+
log(t("daemonAbortStart"));
|
|
17075
|
+
return false;
|
|
17076
|
+
}
|
|
17077
|
+
log(t("daemonStarting"));
|
|
17078
|
+
const started = await startOllamaDaemon({ baseUrl: opts.baseUrl, timeoutMs: 15e3 });
|
|
17079
|
+
if (!started.ready) {
|
|
17080
|
+
log(t("daemonStartTimeout"));
|
|
17081
|
+
return false;
|
|
17082
|
+
}
|
|
17083
|
+
log(t("daemonReady", { pid: started.pid ? ` (pid ${started.pid})` : "" }));
|
|
17084
|
+
}
|
|
17085
|
+
const after = status2.daemonRunning ? status2 : await checkOllamaStatus(opts.model, opts.baseUrl);
|
|
17086
|
+
if (!after.modelPulled) {
|
|
17087
|
+
if (!opts.interactive && !opts.yesToAll) {
|
|
17088
|
+
log(t("modelNotPulledHint", { model: opts.model }));
|
|
17089
|
+
return false;
|
|
17090
|
+
}
|
|
17091
|
+
const ok = opts.yesToAll || await confirm(t("modelPullConfirm", { model: opts.model }), true);
|
|
17092
|
+
if (!ok) {
|
|
17093
|
+
log(t("modelAbortPull"));
|
|
17094
|
+
return false;
|
|
17095
|
+
}
|
|
17096
|
+
log(t("modelPulling", { model: opts.model }));
|
|
17097
|
+
const ESC = String.fromCharCode(27);
|
|
17098
|
+
const ANSI_CSI = new RegExp(`${ESC}\\[[0-9;]*[A-Za-z]`, "g");
|
|
17099
|
+
const code = await pullOllamaModel(opts.model, {
|
|
17100
|
+
onLine: (line) => {
|
|
17101
|
+
const cleaned = line.replace(ANSI_CSI, "").trim();
|
|
17102
|
+
if (cleaned.length === 0) return;
|
|
17103
|
+
log(` ${cleaned}
|
|
17104
|
+
`);
|
|
17105
|
+
}
|
|
17106
|
+
});
|
|
17107
|
+
if (code !== 0) {
|
|
17108
|
+
log(t("modelPullFailed", { model: opts.model, code }));
|
|
17109
|
+
return false;
|
|
17110
|
+
}
|
|
17111
|
+
log(t("modelPulled", { model: opts.model }));
|
|
17112
|
+
}
|
|
17113
|
+
return true;
|
|
17114
|
+
}
|
|
17115
|
+
async function confirm(question, defaultYes) {
|
|
17116
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
17117
|
+
const rl = createInterface({ input: stdin2, output: stdout });
|
|
17118
|
+
try {
|
|
17119
|
+
const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
17120
|
+
if (raw === "") return defaultYes;
|
|
17121
|
+
return raw === "y" || raw === "yes";
|
|
17122
|
+
} finally {
|
|
17123
|
+
rl.close();
|
|
17124
|
+
}
|
|
17125
|
+
}
|
|
17126
|
+
|
|
17127
|
+
// src/cli/commands/index.ts
|
|
17128
|
+
async function indexCommand(opts = {}) {
|
|
17129
|
+
const root = resolve8(opts.dir ?? process.cwd());
|
|
17130
|
+
const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
|
|
17131
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
17132
|
+
const preflightOk = await ollamaPreflight({
|
|
17133
|
+
model: model2,
|
|
17134
|
+
baseUrl: opts.ollamaUrl,
|
|
17135
|
+
interactive: tty && !opts.yes,
|
|
17136
|
+
yesToAll: opts.yes ?? false
|
|
17137
|
+
});
|
|
17138
|
+
if (!preflightOk) process.exit(1);
|
|
17139
|
+
const writer = makeProgressWriter(tty);
|
|
17140
|
+
const t0 = Date.now();
|
|
17141
|
+
let result;
|
|
17142
|
+
try {
|
|
17143
|
+
result = await buildIndex(root, {
|
|
17144
|
+
rebuild: opts.rebuild,
|
|
17145
|
+
model: model2,
|
|
17146
|
+
baseUrl: opts.ollamaUrl,
|
|
17147
|
+
onProgress: (p) => writer.update(p)
|
|
17148
|
+
});
|
|
17149
|
+
} catch (err) {
|
|
17150
|
+
writer.clear();
|
|
17151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17152
|
+
process.stderr.write(t("indexFailed", { msg }));
|
|
17153
|
+
process.exit(1);
|
|
17154
|
+
}
|
|
17155
|
+
writer.clear();
|
|
17156
|
+
const seconds = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
17157
|
+
const successKey = result.chunksSkipped > 0 ? "indexSuccessWithSkips" : "indexSuccess";
|
|
17158
|
+
process.stderr.write(
|
|
17159
|
+
t(successKey, {
|
|
17160
|
+
scanned: result.filesScanned,
|
|
17161
|
+
changed: result.filesChanged,
|
|
17162
|
+
added: result.chunksAdded,
|
|
17163
|
+
removed: result.chunksRemoved,
|
|
17164
|
+
skipped: result.chunksSkipped,
|
|
17165
|
+
seconds
|
|
17166
|
+
})
|
|
17167
|
+
);
|
|
17168
|
+
if (result.filesChanged === 0 && !opts.rebuild) {
|
|
17169
|
+
process.stderr.write(t("indexNothingToDo"));
|
|
17170
|
+
}
|
|
17171
|
+
}
|
|
17172
|
+
var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17173
|
+
var SPINNER_INTERVAL_MS = 120;
|
|
17174
|
+
function makeProgressWriter(tty) {
|
|
17175
|
+
if (!tty) return makeNonTtyWriter();
|
|
17176
|
+
return makeTtyWriter();
|
|
17177
|
+
}
|
|
17178
|
+
function makeNonTtyWriter() {
|
|
17179
|
+
let lastPhase = null;
|
|
17180
|
+
let lastChunks = 0;
|
|
17181
|
+
return {
|
|
17182
|
+
update(p) {
|
|
17183
|
+
if (p.phase !== lastPhase) {
|
|
17184
|
+
lastPhase = p.phase;
|
|
17185
|
+
if (p.phase === "scan") {
|
|
17186
|
+
process.stderr.write(t("progressScanLine"));
|
|
17187
|
+
} else if (p.phase === "embed") {
|
|
17188
|
+
process.stderr.write(
|
|
17189
|
+
t("progressEmbedLine", {
|
|
17190
|
+
total: p.chunksTotal ?? 0,
|
|
17191
|
+
files: p.filesChanged ?? 0
|
|
17192
|
+
})
|
|
17193
|
+
);
|
|
17194
|
+
}
|
|
17195
|
+
}
|
|
17196
|
+
if (p.phase === "embed" && p.chunksDone !== void 0 && p.chunksDone - lastChunks >= 50) {
|
|
17197
|
+
lastChunks = p.chunksDone;
|
|
17198
|
+
process.stderr.write(
|
|
17199
|
+
t("progressEmbedHeartbeat", {
|
|
17200
|
+
done: p.chunksDone,
|
|
17201
|
+
total: p.chunksTotal ?? "?"
|
|
17202
|
+
})
|
|
17203
|
+
);
|
|
17204
|
+
}
|
|
17205
|
+
},
|
|
17206
|
+
clear() {
|
|
17207
|
+
}
|
|
17208
|
+
};
|
|
17209
|
+
}
|
|
17210
|
+
function makeTtyWriter() {
|
|
17211
|
+
let status2 = t("progressStarting");
|
|
17212
|
+
let lastLineLen = 0;
|
|
17213
|
+
let frameIdx = 0;
|
|
17214
|
+
const startTs = Date.now();
|
|
17215
|
+
const repaint = () => {
|
|
17216
|
+
const frame = SPINNER_FRAMES2[frameIdx % SPINNER_FRAMES2.length];
|
|
17217
|
+
frameIdx++;
|
|
17218
|
+
const elapsed = ((Date.now() - startTs) / 1e3).toFixed(1);
|
|
17219
|
+
const line = `${frame} ${status2} ${elapsed}s`;
|
|
17220
|
+
const padded = line + " ".repeat(Math.max(0, lastLineLen - line.length));
|
|
17221
|
+
process.stderr.write(`\r${padded}`);
|
|
17222
|
+
lastLineLen = line.length;
|
|
17223
|
+
};
|
|
17224
|
+
repaint();
|
|
17225
|
+
const interval = setInterval(repaint, SPINNER_INTERVAL_MS);
|
|
17226
|
+
return {
|
|
17227
|
+
update(p) {
|
|
17228
|
+
if (p.phase === "scan") {
|
|
17229
|
+
status2 = t("progressScan", { files: p.filesScanned ?? 0 });
|
|
17230
|
+
} else if (p.phase === "embed") {
|
|
17231
|
+
const done = p.chunksDone ?? 0;
|
|
17232
|
+
const total = p.chunksTotal ?? 0;
|
|
17233
|
+
const pct2 = total > 0 ? (done / total * 100).toFixed(0) : "0";
|
|
17234
|
+
status2 = t("progressEmbed", { done, total, pct: pct2 });
|
|
17235
|
+
}
|
|
17236
|
+
repaint();
|
|
17237
|
+
},
|
|
17238
|
+
clear() {
|
|
17239
|
+
clearInterval(interval);
|
|
17240
|
+
if (lastLineLen > 0) {
|
|
17241
|
+
process.stderr.write(`\r${" ".repeat(lastLineLen)}\r`);
|
|
17242
|
+
lastLineLen = 0;
|
|
17243
|
+
}
|
|
17244
|
+
}
|
|
17245
|
+
};
|
|
17246
|
+
}
|
|
17247
|
+
|
|
16021
17248
|
// src/cli/commands/mcp-inspect.ts
|
|
16022
17249
|
async function mcpInspectCommand(opts) {
|
|
16023
17250
|
const spec = parseMcpSpec(opts.spec);
|
|
@@ -16064,9 +17291,9 @@ function formatSection(title, section, render5) {
|
|
|
16064
17291
|
for (const item of section.items) lines.push(` ${render5(item)}`);
|
|
16065
17292
|
return lines.join("\n");
|
|
16066
17293
|
}
|
|
16067
|
-
function toolLine(
|
|
16068
|
-
const desc =
|
|
16069
|
-
return `\xB7 ${
|
|
17294
|
+
function toolLine(t2) {
|
|
17295
|
+
const desc = t2.description ? ` \u2014 ${oneLine(t2.description, 80)}` : "";
|
|
17296
|
+
return `\xB7 ${t2.name}${desc}`;
|
|
16070
17297
|
}
|
|
16071
17298
|
function resourceLine(r) {
|
|
16072
17299
|
const mime = r.mimeType ? ` [${r.mimeType}]` : "";
|
|
@@ -16306,12 +17533,12 @@ function oneLine2(s, max = 200) {
|
|
|
16306
17533
|
}
|
|
16307
17534
|
|
|
16308
17535
|
// src/cli/commands/run.ts
|
|
16309
|
-
import { stdin as
|
|
16310
|
-
import { createInterface } from "readline/promises";
|
|
17536
|
+
import { stdin as stdin3, stdout as stdout2 } from "process";
|
|
17537
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
16311
17538
|
async function ensureApiKey() {
|
|
16312
17539
|
const existing = loadApiKey();
|
|
16313
17540
|
if (existing) return existing;
|
|
16314
|
-
if (!
|
|
17541
|
+
if (!stdin3.isTTY) {
|
|
16315
17542
|
process.stderr.write(
|
|
16316
17543
|
"DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
|
|
16317
17544
|
);
|
|
@@ -16320,7 +17547,7 @@ async function ensureApiKey() {
|
|
|
16320
17547
|
process.stdout.write(
|
|
16321
17548
|
"DeepSeek API key not configured.\nGet one at https://platform.deepseek.com/api_keys\n"
|
|
16322
17549
|
);
|
|
16323
|
-
const rl =
|
|
17550
|
+
const rl = createInterface2({ input: stdin3, output: stdout2 });
|
|
16324
17551
|
try {
|
|
16325
17552
|
while (true) {
|
|
16326
17553
|
const answer = (await rl.question("API key \u203A ")).trim();
|
|
@@ -16471,14 +17698,14 @@ function listAll() {
|
|
|
16471
17698
|
console.log("Resume: reasonix chat --session <name>");
|
|
16472
17699
|
}
|
|
16473
17700
|
function inspectSession(name, verbose) {
|
|
16474
|
-
const
|
|
17701
|
+
const path5 = sessionPath(name);
|
|
16475
17702
|
const messages = loadSessionMessages(name);
|
|
16476
17703
|
if (messages.length === 0) {
|
|
16477
17704
|
console.error(`no session named "${name}" (or it's empty).`);
|
|
16478
|
-
console.error(`looked at: ${
|
|
17705
|
+
console.error(`looked at: ${path5}`);
|
|
16479
17706
|
process.exit(1);
|
|
16480
17707
|
}
|
|
16481
|
-
console.log(`[session] ${name} ${messages.length} messages ${
|
|
17708
|
+
console.log(`[session] ${name} ${messages.length} messages ${path5}`);
|
|
16482
17709
|
console.log("");
|
|
16483
17710
|
let turnIndex = 0;
|
|
16484
17711
|
for (const msg of messages) {
|
|
@@ -16822,7 +18049,7 @@ async function setupCommand(_opts = {}) {
|
|
|
16822
18049
|
}
|
|
16823
18050
|
|
|
16824
18051
|
// src/cli/commands/update.ts
|
|
16825
|
-
import { spawn as
|
|
18052
|
+
import { spawn as spawn6 } from "child_process";
|
|
16826
18053
|
function planUpdate(input) {
|
|
16827
18054
|
const diff = compareVersions(input.current, input.latest);
|
|
16828
18055
|
if (diff > 0) {
|
|
@@ -16852,13 +18079,13 @@ function planUpdate(input) {
|
|
|
16852
18079
|
};
|
|
16853
18080
|
}
|
|
16854
18081
|
function defaultSpawn(argv) {
|
|
16855
|
-
return new Promise((
|
|
16856
|
-
const child =
|
|
18082
|
+
return new Promise((resolve9, reject) => {
|
|
18083
|
+
const child = spawn6(argv[0], argv.slice(1), {
|
|
16857
18084
|
stdio: "inherit",
|
|
16858
18085
|
shell: process.platform === "win32"
|
|
16859
18086
|
});
|
|
16860
18087
|
child.once("error", reject);
|
|
16861
|
-
child.once("exit", (code) =>
|
|
18088
|
+
child.once("exit", (code) => resolve9(code ?? 1));
|
|
16862
18089
|
});
|
|
16863
18090
|
}
|
|
16864
18091
|
async function updateCommand(opts = {}) {
|
|
@@ -17154,6 +18381,16 @@ program.command("update").description(
|
|
|
17154
18381
|
).option("--dry-run", "Print the plan without executing the install").action(async (opts) => {
|
|
17155
18382
|
await updateCommand({ dryRun: !!opts.dryRun });
|
|
17156
18383
|
});
|
|
18384
|
+
program.command("index").description(
|
|
18385
|
+
"Build (or incrementally refresh) a local semantic search index for the project so `reasonix code` can answer 'where do we\u2026' questions by meaning, not just by token. Uses Ollama as the embedding backend; missing daemon / model is offered to the user with a confirm prompt."
|
|
18386
|
+
).option("--rebuild", "Wipe and rebuild from scratch").option("--model <name>", "Embedding model (default: nomic-embed-text)").option("--dir <path>", "Project root to index (default: cwd)").option("--ollama-url <url>", "Override Ollama base URL (default: http://localhost:11434)").option(
|
|
18387
|
+
"-y, --yes",
|
|
18388
|
+
"Skip preflight prompts \u2014 auto-start the daemon and pull the model if missing (use in scripts)"
|
|
18389
|
+
).action(
|
|
18390
|
+
async (opts) => {
|
|
18391
|
+
await indexCommand(opts);
|
|
18392
|
+
}
|
|
18393
|
+
);
|
|
17157
18394
|
program.parseAsync(process.argv).catch((err) => {
|
|
17158
18395
|
console.error(err);
|
|
17159
18396
|
process.exit(1);
|