reasonix 0.10.0 → 0.11.1
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-JDVY4JDU.js} +24 -3
- package/dist/cli/chunk-JDVY4JDU.js.map +1 -0
- package/dist/cli/index.js +2030 -482
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-LJ44NWSU.js → prompt-YRY4HPMZ.js} +2 -2
- package/dist/index.d.ts +14 -13
- package/dist/index.js +70 -8
- 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-YRY4HPMZ.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-JDVY4JDU.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((resolve12, reject) => {
|
|
160
|
+
const timer = setTimeout(resolve12, 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((resolve12) => {
|
|
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
|
+
resolve12({
|
|
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
|
+
resolve12({
|
|
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
|
-
const
|
|
724
|
-
const raw = await spawner({ command: hook.command, cwd, stdin:
|
|
723
|
+
const cwd2 = hook.cwd ?? opts.payload.cwd;
|
|
724
|
+
const raw = await spawner({ command: hook.command, cwd: cwd2, 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;
|
|
@@ -1867,7 +1867,11 @@ var CacheFirstLoop = class {
|
|
|
1867
1867
|
* tool call is one array length check.
|
|
1868
1868
|
*/
|
|
1869
1869
|
hooks;
|
|
1870
|
-
/**
|
|
1870
|
+
/**
|
|
1871
|
+
* `cwd` reported to hook stdin. Mutable so `/cwd` can switch the
|
|
1872
|
+
* working directory mid-session — the App keeps it in sync with
|
|
1873
|
+
* the same currentRootDir that drives tool re-registration.
|
|
1874
|
+
*/
|
|
1871
1875
|
hookCwd;
|
|
1872
1876
|
/** Number of messages that were pre-loaded from the session file. */
|
|
1873
1877
|
resumedMessageCount;
|
|
@@ -2198,13 +2202,13 @@ var CacheFirstLoop = class {
|
|
|
2198
2202
|
* with `<`.
|
|
2199
2203
|
*/
|
|
2200
2204
|
looksLikePartialEscalationMarker(buf) {
|
|
2201
|
-
const
|
|
2202
|
-
if (
|
|
2203
|
-
if (
|
|
2204
|
-
return NEEDS_PRO_MARKER_PREFIX.startsWith(
|
|
2205
|
+
const t2 = buf.trimStart();
|
|
2206
|
+
if (t2.length === 0) return true;
|
|
2207
|
+
if (t2.length <= NEEDS_PRO_MARKER_PREFIX.length) {
|
|
2208
|
+
return NEEDS_PRO_MARKER_PREFIX.startsWith(t2);
|
|
2205
2209
|
}
|
|
2206
|
-
if (!
|
|
2207
|
-
const rest =
|
|
2210
|
+
if (!t2.startsWith(NEEDS_PRO_MARKER_PREFIX)) return false;
|
|
2211
|
+
const rest = t2.slice(NEEDS_PRO_MARKER_PREFIX.length);
|
|
2208
2212
|
if (rest[0] !== ">" && rest[0] !== ":") return false;
|
|
2209
2213
|
return true;
|
|
2210
2214
|
}
|
|
@@ -2312,7 +2316,9 @@ var CacheFirstLoop = class {
|
|
|
2312
2316
|
this._proArmedForNextTurn = false;
|
|
2313
2317
|
armedConsumed = true;
|
|
2314
2318
|
}
|
|
2319
|
+
const carryAbort = this._turnAbort.signal.aborted;
|
|
2315
2320
|
this._turnAbort = new AbortController();
|
|
2321
|
+
if (carryAbort) this._turnAbort.abort();
|
|
2316
2322
|
const signal = this._turnAbort.signal;
|
|
2317
2323
|
if (armedConsumed) {
|
|
2318
2324
|
yield {
|
|
@@ -2342,6 +2348,7 @@ var CacheFirstLoop = class {
|
|
|
2342
2348
|
};
|
|
2343
2349
|
this.autoCompactToolResultsOnTurnEnd();
|
|
2344
2350
|
yield { turn: this._turn, role: "done", content: stoppedMsg };
|
|
2351
|
+
this._turnAbort = new AbortController();
|
|
2345
2352
|
return;
|
|
2346
2353
|
}
|
|
2347
2354
|
if (iter > 0) {
|
|
@@ -2435,8 +2442,8 @@ var CacheFirstLoop = class {
|
|
|
2435
2442
|
}
|
|
2436
2443
|
);
|
|
2437
2444
|
for (let k = 0; k < budget; k++) {
|
|
2438
|
-
const sample = queue.shift() ?? await new Promise((
|
|
2439
|
-
waiter =
|
|
2445
|
+
const sample = queue.shift() ?? await new Promise((resolve12) => {
|
|
2446
|
+
waiter = resolve12;
|
|
2440
2447
|
});
|
|
2441
2448
|
yield {
|
|
2442
2449
|
turn: this._turn,
|
|
@@ -3259,7 +3266,7 @@ function rankPickerCandidates(files, query, limitOrOpts) {
|
|
|
3259
3266
|
var AT_MENTION_PATTERN = /(?<=^|\s)@([a-zA-Z0-9_./\\-]+)/g;
|
|
3260
3267
|
function expandAtMentions(text, rootDir, opts = {}) {
|
|
3261
3268
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
3262
|
-
const
|
|
3269
|
+
const fs6 = opts.fs ?? defaultFs;
|
|
3263
3270
|
const root = resolve(rootDir);
|
|
3264
3271
|
const seen = /* @__PURE__ */ new Map();
|
|
3265
3272
|
const expansions = [];
|
|
@@ -3269,7 +3276,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
3269
3276
|
if (!cleaned) continue;
|
|
3270
3277
|
const token = `@${cleaned}`;
|
|
3271
3278
|
if (seen.has(token)) continue;
|
|
3272
|
-
const expansion = resolveMention(cleaned, root, maxBytes,
|
|
3279
|
+
const expansion = resolveMention(cleaned, root, maxBytes, fs6);
|
|
3273
3280
|
seen.set(token, expansion);
|
|
3274
3281
|
expansions.push(expansion);
|
|
3275
3282
|
}
|
|
@@ -3277,7 +3284,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
3277
3284
|
const blocks = [];
|
|
3278
3285
|
for (const ex of expansions) {
|
|
3279
3286
|
if (ex.ok) {
|
|
3280
|
-
const content = readSafe(root, ex.path,
|
|
3287
|
+
const content = readSafe(root, ex.path, fs6);
|
|
3281
3288
|
blocks.push(`<file path="${ex.path}">
|
|
3282
3289
|
${content}
|
|
3283
3290
|
</file>`);
|
|
@@ -3291,7 +3298,7 @@ ${content}
|
|
|
3291
3298
|
${blocks.join("\n\n")}`;
|
|
3292
3299
|
return { text: augmented, expansions };
|
|
3293
3300
|
}
|
|
3294
|
-
function resolveMention(rawPath, root, maxBytes,
|
|
3301
|
+
function resolveMention(rawPath, root, maxBytes, fs6) {
|
|
3295
3302
|
if (isAbsolute(rawPath)) {
|
|
3296
3303
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
3297
3304
|
}
|
|
@@ -3300,22 +3307,22 @@ function resolveMention(rawPath, root, maxBytes, fs2) {
|
|
|
3300
3307
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
3301
3308
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
3302
3309
|
}
|
|
3303
|
-
if (!
|
|
3310
|
+
if (!fs6.exists(resolved)) {
|
|
3304
3311
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "missing" };
|
|
3305
3312
|
}
|
|
3306
|
-
if (!
|
|
3313
|
+
if (!fs6.isFile(resolved)) {
|
|
3307
3314
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
3308
3315
|
}
|
|
3309
|
-
const size =
|
|
3316
|
+
const size = fs6.size(resolved);
|
|
3310
3317
|
if (size > maxBytes) {
|
|
3311
3318
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "too-large", bytes: size };
|
|
3312
3319
|
}
|
|
3313
3320
|
return { token: `@${rawPath}`, path: rawPath, ok: true, bytes: size };
|
|
3314
3321
|
}
|
|
3315
|
-
function readSafe(root, rawPath,
|
|
3322
|
+
function readSafe(root, rawPath, fs6) {
|
|
3316
3323
|
const resolved = resolve(root, rawPath);
|
|
3317
3324
|
try {
|
|
3318
|
-
return
|
|
3325
|
+
return fs6.read(resolved);
|
|
3319
3326
|
} catch {
|
|
3320
3327
|
return "(read failed)";
|
|
3321
3328
|
}
|
|
@@ -3513,9 +3520,9 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
3513
3520
|
".pyo"
|
|
3514
3521
|
]);
|
|
3515
3522
|
function isLikelyBinaryByName(name) {
|
|
3516
|
-
const
|
|
3517
|
-
if (
|
|
3518
|
-
return BINARY_EXTENSIONS.has(name.slice(
|
|
3523
|
+
const dot2 = name.lastIndexOf(".");
|
|
3524
|
+
if (dot2 < 0) return false;
|
|
3525
|
+
return BINARY_EXTENSIONS.has(name.slice(dot2).toLowerCase());
|
|
3519
3526
|
}
|
|
3520
3527
|
function registerFilesystemTools(registry, opts) {
|
|
3521
3528
|
const rootDir = pathMod.resolve(opts.rootDir);
|
|
@@ -4077,7 +4084,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
4077
4084
|
});
|
|
4078
4085
|
}
|
|
4079
4086
|
try {
|
|
4080
|
-
const
|
|
4087
|
+
const path5 = store.write({
|
|
4081
4088
|
name: args.name,
|
|
4082
4089
|
type: args.type,
|
|
4083
4090
|
scope: args.scope,
|
|
@@ -4090,7 +4097,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
4090
4097
|
"",
|
|
4091
4098
|
"TREAT THIS AS ESTABLISHED FACT for the rest of this session.",
|
|
4092
4099
|
"The user just told you \u2014 don't re-explore the filesystem to re-derive it.",
|
|
4093
|
-
`(Saved to ${
|
|
4100
|
+
`(Saved to ${path5}; pins into the system prompt on next /new or launch.)`
|
|
4094
4101
|
].join("\n");
|
|
4095
4102
|
} catch (err) {
|
|
4096
4103
|
return JSON.stringify({ error: `remember failed: ${err.message}` });
|
|
@@ -4568,7 +4575,11 @@ async function spawnSubagent(opts) {
|
|
|
4568
4575
|
stream: false
|
|
4569
4576
|
});
|
|
4570
4577
|
const onParentAbort = () => childLoop.abort();
|
|
4571
|
-
opts.parentSignal?.
|
|
4578
|
+
if (opts.parentSignal?.aborted) {
|
|
4579
|
+
childLoop.abort();
|
|
4580
|
+
} else {
|
|
4581
|
+
opts.parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
4582
|
+
}
|
|
4572
4583
|
let final = "";
|
|
4573
4584
|
let errorMessage;
|
|
4574
4585
|
let toolIter = 0;
|
|
@@ -4586,7 +4597,11 @@ async function spawnSubagent(opts) {
|
|
|
4586
4597
|
});
|
|
4587
4598
|
}
|
|
4588
4599
|
if (ev.role === "assistant_final") {
|
|
4589
|
-
|
|
4600
|
+
if (ev.forcedSummary) {
|
|
4601
|
+
errorMessage = ev.content?.trim() || "subagent ended without producing an answer";
|
|
4602
|
+
} else {
|
|
4603
|
+
final = ev.content ?? "";
|
|
4604
|
+
}
|
|
4590
4605
|
}
|
|
4591
4606
|
if (ev.role === "error") {
|
|
4592
4607
|
errorMessage = ev.error ?? "subagent error";
|
|
@@ -4635,12 +4650,12 @@ async function spawnSubagent(opts) {
|
|
|
4635
4650
|
}
|
|
4636
4651
|
function aggregateChildUsage(loop2) {
|
|
4637
4652
|
const agg = new Usage();
|
|
4638
|
-
for (const
|
|
4639
|
-
agg.promptTokens +=
|
|
4640
|
-
agg.completionTokens +=
|
|
4641
|
-
agg.totalTokens +=
|
|
4642
|
-
agg.promptCacheHitTokens +=
|
|
4643
|
-
agg.promptCacheMissTokens +=
|
|
4653
|
+
for (const t2 of loop2.stats.turns) {
|
|
4654
|
+
agg.promptTokens += t2.usage.promptTokens;
|
|
4655
|
+
agg.completionTokens += t2.usage.completionTokens;
|
|
4656
|
+
agg.totalTokens += t2.usage.totalTokens;
|
|
4657
|
+
agg.promptCacheHitTokens += t2.usage.promptCacheHitTokens;
|
|
4658
|
+
agg.promptCacheMissTokens += t2.usage.promptCacheMissTokens;
|
|
4644
4659
|
}
|
|
4645
4660
|
return agg;
|
|
4646
4661
|
}
|
|
@@ -5158,7 +5173,7 @@ async function runCommand(cmd, opts) {
|
|
|
5158
5173
|
};
|
|
5159
5174
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
5160
5175
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
5161
|
-
return await new Promise((
|
|
5176
|
+
return await new Promise((resolve12, reject) => {
|
|
5162
5177
|
let child;
|
|
5163
5178
|
try {
|
|
5164
5179
|
child = spawn3(bin, args, effectiveSpawnOpts);
|
|
@@ -5166,7 +5181,9 @@ async function runCommand(cmd, opts) {
|
|
|
5166
5181
|
reject(err);
|
|
5167
5182
|
return;
|
|
5168
5183
|
}
|
|
5169
|
-
|
|
5184
|
+
const chunks = [];
|
|
5185
|
+
let totalBytes = 0;
|
|
5186
|
+
const byteCap = maxChars * 2 * 4;
|
|
5170
5187
|
let timedOut = false;
|
|
5171
5188
|
const killTimer = setTimeout(() => {
|
|
5172
5189
|
timedOut = true;
|
|
@@ -5175,8 +5192,16 @@ async function runCommand(cmd, opts) {
|
|
|
5175
5192
|
const onAbort = () => child.kill("SIGKILL");
|
|
5176
5193
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5177
5194
|
const onData = (chunk) => {
|
|
5178
|
-
|
|
5179
|
-
if (
|
|
5195
|
+
const b = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
5196
|
+
if (totalBytes >= byteCap) return;
|
|
5197
|
+
const remaining = byteCap - totalBytes;
|
|
5198
|
+
if (b.length > remaining) {
|
|
5199
|
+
chunks.push(b.subarray(0, remaining));
|
|
5200
|
+
totalBytes = byteCap;
|
|
5201
|
+
} else {
|
|
5202
|
+
chunks.push(b);
|
|
5203
|
+
totalBytes += b.length;
|
|
5204
|
+
}
|
|
5180
5205
|
};
|
|
5181
5206
|
child.stdout?.on("data", onData);
|
|
5182
5207
|
child.stderr?.on("data", onData);
|
|
@@ -5188,13 +5213,29 @@ async function runCommand(cmd, opts) {
|
|
|
5188
5213
|
child.on("close", (code) => {
|
|
5189
5214
|
clearTimeout(killTimer);
|
|
5190
5215
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
5216
|
+
const merged = Buffer.concat(chunks);
|
|
5217
|
+
const buf = smartDecodeOutput(merged);
|
|
5191
5218
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
5192
5219
|
|
|
5193
5220
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
5194
|
-
|
|
5221
|
+
resolve12({ exitCode: code, output, timedOut });
|
|
5195
5222
|
});
|
|
5196
5223
|
});
|
|
5197
5224
|
}
|
|
5225
|
+
function smartDecodeOutput(buf) {
|
|
5226
|
+
if (buf.length === 0) return "";
|
|
5227
|
+
try {
|
|
5228
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buf);
|
|
5229
|
+
} catch {
|
|
5230
|
+
}
|
|
5231
|
+
if (process.platform === "win32") {
|
|
5232
|
+
try {
|
|
5233
|
+
return new TextDecoder("gb18030").decode(buf);
|
|
5234
|
+
} catch {
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
return buf.toString("utf8");
|
|
5238
|
+
}
|
|
5198
5239
|
function resolveExecutable(cmd, opts = {}) {
|
|
5199
5240
|
const platform = opts.platform ?? process.platform;
|
|
5200
5241
|
if (platform !== "win32") return cmd;
|
|
@@ -5664,10 +5705,10 @@ ${i + 1}. ${r.title}`);
|
|
|
5664
5705
|
// src/env.ts
|
|
5665
5706
|
import { readFileSync as readFileSync6 } from "fs";
|
|
5666
5707
|
import { resolve as resolve5 } from "path";
|
|
5667
|
-
function loadDotenv(
|
|
5708
|
+
function loadDotenv(path5 = ".env") {
|
|
5668
5709
|
let raw;
|
|
5669
5710
|
try {
|
|
5670
|
-
raw = readFileSync6(resolve5(process.cwd(),
|
|
5711
|
+
raw = readFileSync6(resolve5(process.cwd(), path5), "utf8");
|
|
5671
5712
|
} catch {
|
|
5672
5713
|
return;
|
|
5673
5714
|
}
|
|
@@ -5731,13 +5772,13 @@ function writeMeta(stream, meta) {
|
|
|
5731
5772
|
stream.write(`${JSON.stringify(line)}
|
|
5732
5773
|
`);
|
|
5733
5774
|
}
|
|
5734
|
-
function openTranscriptFile(
|
|
5735
|
-
const stream = createWriteStream(
|
|
5775
|
+
function openTranscriptFile(path5, meta) {
|
|
5776
|
+
const stream = createWriteStream(path5, { flags: "a" });
|
|
5736
5777
|
writeMeta(stream, meta);
|
|
5737
5778
|
return stream;
|
|
5738
5779
|
}
|
|
5739
|
-
function readTranscript(
|
|
5740
|
-
const raw = readFileSync7(
|
|
5780
|
+
function readTranscript(path5) {
|
|
5781
|
+
const raw = readFileSync7(path5, "utf8");
|
|
5741
5782
|
return parseTranscript(raw);
|
|
5742
5783
|
}
|
|
5743
5784
|
function isPlanStateEmptyShape(s) {
|
|
@@ -5786,8 +5827,8 @@ function computeCumulativeStats(pages, upToIdx) {
|
|
|
5786
5827
|
}
|
|
5787
5828
|
return computeReplayStats(flat);
|
|
5788
5829
|
}
|
|
5789
|
-
function replayFromFile(
|
|
5790
|
-
const parsed = readTranscript(
|
|
5830
|
+
function replayFromFile(path5) {
|
|
5831
|
+
const parsed = readTranscript(path5);
|
|
5791
5832
|
return { parsed, stats: computeReplayStats(parsed.records) };
|
|
5792
5833
|
}
|
|
5793
5834
|
function computeReplayStats(records) {
|
|
@@ -5844,15 +5885,15 @@ function computeReplayStats(records) {
|
|
|
5844
5885
|
};
|
|
5845
5886
|
}
|
|
5846
5887
|
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,
|
|
5888
|
+
const totalCost = turns.reduce((s, t2) => s + t2.cost, 0);
|
|
5889
|
+
const totalInput = turns.reduce((s, t2) => s + inputCostUsd(t2.model, t2.usage), 0);
|
|
5890
|
+
const totalOutput = turns.reduce((s, t2) => s + outputCostUsd(t2.model, t2.usage), 0);
|
|
5891
|
+
const totalClaude = turns.reduce((s, t2) => s + claudeEquivalentCost(t2.usage), 0);
|
|
5851
5892
|
let hit = 0;
|
|
5852
5893
|
let miss = 0;
|
|
5853
|
-
for (const
|
|
5854
|
-
hit +=
|
|
5855
|
-
miss +=
|
|
5894
|
+
for (const t2 of turns) {
|
|
5895
|
+
hit += t2.usage.promptCacheHitTokens;
|
|
5896
|
+
miss += t2.usage.promptCacheMissTokens;
|
|
5856
5897
|
}
|
|
5857
5898
|
const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;
|
|
5858
5899
|
const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;
|
|
@@ -5929,8 +5970,8 @@ function diffTranscripts(a, b) {
|
|
|
5929
5970
|
return { a: aSide, b: bSide, pairs, firstDivergenceTurn };
|
|
5930
5971
|
}
|
|
5931
5972
|
function classifyDivergence(a, b, aTools, bTools) {
|
|
5932
|
-
const aNames = aTools.map((
|
|
5933
|
-
const bNames = bTools.map((
|
|
5973
|
+
const aNames = aTools.map((t2) => t2.tool ?? "").sort();
|
|
5974
|
+
const bNames = bTools.map((t2) => t2.tool ?? "").sort();
|
|
5934
5975
|
if (aNames.join(",") !== bNames.join(",")) {
|
|
5935
5976
|
return `tool calls differ: A=[${aNames.join(",") || "\u2014"}] B=[${bNames.join(",") || "\u2014"}]`;
|
|
5936
5977
|
}
|
|
@@ -5960,7 +6001,7 @@ function tokenOverlap(a, b) {
|
|
|
5960
6001
|
const tb = new Set(b.toLowerCase().split(/\s+/).filter(Boolean));
|
|
5961
6002
|
if (ta.size === 0 && tb.size === 0) return 1;
|
|
5962
6003
|
let shared = 0;
|
|
5963
|
-
for (const
|
|
6004
|
+
for (const t2 of ta) if (tb.has(t2)) shared++;
|
|
5964
6005
|
return 2 * shared / (ta.size + tb.size);
|
|
5965
6006
|
}
|
|
5966
6007
|
function levenshtein(a, b) {
|
|
@@ -6153,8 +6194,8 @@ function renderMarkdown(report) {
|
|
|
6153
6194
|
out.push(`| turn | kind | ${a.label} tool calls | ${b.label} tool calls | note |`);
|
|
6154
6195
|
out.push("|---:|:---:|---|---|---|");
|
|
6155
6196
|
for (const p of report.pairs) {
|
|
6156
|
-
const aTools = p.aTools.map((
|
|
6157
|
-
const bTools = p.bTools.map((
|
|
6197
|
+
const aTools = p.aTools.map((t2) => t2.tool).filter(Boolean).join(", ") || "\u2014";
|
|
6198
|
+
const bTools = p.bTools.map((t2) => t2.tool).filter(Boolean).join(", ") || "\u2014";
|
|
6158
6199
|
out.push(`| ${p.turn} | ${p.kind} | ${aTools} | ${bTools} | ${p.divergenceNote ?? ""} |`);
|
|
6159
6200
|
}
|
|
6160
6201
|
out.push("");
|
|
@@ -6381,7 +6422,7 @@ var McpClient = class {
|
|
|
6381
6422
|
const id = this.nextId++;
|
|
6382
6423
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
6383
6424
|
let abortHandler = null;
|
|
6384
|
-
const promise = new Promise((
|
|
6425
|
+
const promise = new Promise((resolve12, reject) => {
|
|
6385
6426
|
const timeout = setTimeout(() => {
|
|
6386
6427
|
this.pending.delete(id);
|
|
6387
6428
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -6390,7 +6431,7 @@ var McpClient = class {
|
|
|
6390
6431
|
);
|
|
6391
6432
|
}, this.requestTimeoutMs);
|
|
6392
6433
|
this.pending.set(id, {
|
|
6393
|
-
resolve:
|
|
6434
|
+
resolve: resolve12,
|
|
6394
6435
|
reject,
|
|
6395
6436
|
timeout
|
|
6396
6437
|
});
|
|
@@ -6513,12 +6554,12 @@ var StdioTransport = class {
|
|
|
6513
6554
|
}
|
|
6514
6555
|
async send(message) {
|
|
6515
6556
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
6516
|
-
return new Promise((
|
|
6557
|
+
return new Promise((resolve12, reject) => {
|
|
6517
6558
|
const line = `${JSON.stringify(message)}
|
|
6518
6559
|
`;
|
|
6519
6560
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
6520
6561
|
if (err) reject(err);
|
|
6521
|
-
else
|
|
6562
|
+
else resolve12();
|
|
6522
6563
|
});
|
|
6523
6564
|
});
|
|
6524
6565
|
}
|
|
@@ -6529,8 +6570,8 @@ var StdioTransport = class {
|
|
|
6529
6570
|
continue;
|
|
6530
6571
|
}
|
|
6531
6572
|
if (this.closed) return;
|
|
6532
|
-
const next = await new Promise((
|
|
6533
|
-
this.waiters.push(
|
|
6573
|
+
const next = await new Promise((resolve12) => {
|
|
6574
|
+
this.waiters.push(resolve12);
|
|
6534
6575
|
});
|
|
6535
6576
|
if (next === null) return;
|
|
6536
6577
|
yield next;
|
|
@@ -6596,8 +6637,8 @@ var SseTransport = class {
|
|
|
6596
6637
|
constructor(opts) {
|
|
6597
6638
|
this.url = opts.url;
|
|
6598
6639
|
this.headers = opts.headers ?? {};
|
|
6599
|
-
this.endpointReady = new Promise((
|
|
6600
|
-
this.resolveEndpoint =
|
|
6640
|
+
this.endpointReady = new Promise((resolve12, reject) => {
|
|
6641
|
+
this.resolveEndpoint = resolve12;
|
|
6601
6642
|
this.rejectEndpoint = reject;
|
|
6602
6643
|
});
|
|
6603
6644
|
this.endpointReady.catch(() => void 0);
|
|
@@ -6624,8 +6665,8 @@ var SseTransport = class {
|
|
|
6624
6665
|
continue;
|
|
6625
6666
|
}
|
|
6626
6667
|
if (this.closed) return;
|
|
6627
|
-
const next = await new Promise((
|
|
6628
|
-
this.waiters.push(
|
|
6668
|
+
const next = await new Promise((resolve12) => {
|
|
6669
|
+
this.waiters.push(resolve12);
|
|
6629
6670
|
});
|
|
6630
6671
|
if (next === null) return;
|
|
6631
6672
|
yield next;
|
|
@@ -6893,8 +6934,8 @@ function applyEditBlock(block, rootDir) {
|
|
|
6893
6934
|
function applyEditBlocks(blocks, rootDir) {
|
|
6894
6935
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
6895
6936
|
}
|
|
6896
|
-
function toWholeFileEditBlock(
|
|
6897
|
-
const abs = resolve6(rootDir,
|
|
6937
|
+
function toWholeFileEditBlock(path5, content, rootDir) {
|
|
6938
|
+
const abs = resolve6(rootDir, path5);
|
|
6898
6939
|
let search = "";
|
|
6899
6940
|
if (existsSync6(abs)) {
|
|
6900
6941
|
try {
|
|
@@ -6903,7 +6944,7 @@ function toWholeFileEditBlock(path, content, rootDir) {
|
|
|
6903
6944
|
search = "";
|
|
6904
6945
|
}
|
|
6905
6946
|
}
|
|
6906
|
-
return { path, search, replace: content, offset: 0 };
|
|
6947
|
+
return { path: path5, search, replace: content, offset: 0 };
|
|
6907
6948
|
}
|
|
6908
6949
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
6909
6950
|
const absRoot = resolve6(rootDir);
|
|
@@ -7082,20 +7123,20 @@ function appendUsage(input) {
|
|
|
7082
7123
|
};
|
|
7083
7124
|
if (input.kind === "subagent") record.kind = "subagent";
|
|
7084
7125
|
if (input.subagent) record.subagent = input.subagent;
|
|
7085
|
-
const
|
|
7126
|
+
const path5 = input.path ?? defaultUsageLogPath();
|
|
7086
7127
|
try {
|
|
7087
|
-
mkdirSync5(dirname7(
|
|
7088
|
-
appendFileSync2(
|
|
7128
|
+
mkdirSync5(dirname7(path5), { recursive: true });
|
|
7129
|
+
appendFileSync2(path5, `${JSON.stringify(record)}
|
|
7089
7130
|
`, "utf8");
|
|
7090
7131
|
} catch {
|
|
7091
7132
|
}
|
|
7092
7133
|
return record;
|
|
7093
7134
|
}
|
|
7094
|
-
function readUsageLog(
|
|
7095
|
-
if (!existsSync8(
|
|
7135
|
+
function readUsageLog(path5 = defaultUsageLogPath()) {
|
|
7136
|
+
if (!existsSync8(path5)) return [];
|
|
7096
7137
|
let raw;
|
|
7097
7138
|
try {
|
|
7098
|
-
raw = readFileSync10(
|
|
7139
|
+
raw = readFileSync10(path5, "utf8");
|
|
7099
7140
|
} catch {
|
|
7100
7141
|
return [];
|
|
7101
7142
|
}
|
|
@@ -7199,10 +7240,10 @@ function aggregateUsage(records, opts = {}) {
|
|
|
7199
7240
|
subagents
|
|
7200
7241
|
};
|
|
7201
7242
|
}
|
|
7202
|
-
function formatLogSize(
|
|
7203
|
-
if (!existsSync8(
|
|
7243
|
+
function formatLogSize(path5 = defaultUsageLogPath()) {
|
|
7244
|
+
if (!existsSync8(path5)) return "";
|
|
7204
7245
|
try {
|
|
7205
|
-
const s = statSync4(
|
|
7246
|
+
const s = statSync4(path5);
|
|
7206
7247
|
const bytes = s.size;
|
|
7207
7248
|
if (bytes < 1024) return `${bytes} B`;
|
|
7208
7249
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -7213,13 +7254,14 @@ function formatLogSize(path = defaultUsageLogPath()) {
|
|
|
7213
7254
|
}
|
|
7214
7255
|
|
|
7215
7256
|
// src/cli/commands/chat.tsx
|
|
7216
|
-
import { existsSync as
|
|
7257
|
+
import { existsSync as existsSync15, statSync as statSync9 } from "fs";
|
|
7217
7258
|
import { render } from "ink";
|
|
7218
|
-
import
|
|
7259
|
+
import React27, { useState as useState12 } from "react";
|
|
7219
7260
|
|
|
7220
7261
|
// src/cli/ui/App.tsx
|
|
7221
|
-
import
|
|
7222
|
-
import
|
|
7262
|
+
import * as pathMod6 from "path";
|
|
7263
|
+
import { Box as Box22, Static, Text as Text20, useApp, useStdout as useStdout8 } from "ink";
|
|
7264
|
+
import React24, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
|
|
7223
7265
|
|
|
7224
7266
|
// src/code/pending-edits.ts
|
|
7225
7267
|
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
@@ -7229,24 +7271,24 @@ function pendingEditsPath(sessionName) {
|
|
|
7229
7271
|
}
|
|
7230
7272
|
function savePendingEdits(sessionName, blocks) {
|
|
7231
7273
|
if (!sessionName) return;
|
|
7232
|
-
const
|
|
7274
|
+
const path5 = pendingEditsPath(sessionName);
|
|
7233
7275
|
try {
|
|
7234
7276
|
if (blocks.length === 0) {
|
|
7235
|
-
if (existsSync9(
|
|
7277
|
+
if (existsSync9(path5)) unlinkSync3(path5);
|
|
7236
7278
|
return;
|
|
7237
7279
|
}
|
|
7238
|
-
mkdirSync6(dirname8(
|
|
7239
|
-
writeFileSync5(
|
|
7280
|
+
mkdirSync6(dirname8(path5), { recursive: true });
|
|
7281
|
+
writeFileSync5(path5, JSON.stringify(blocks, null, 2), "utf8");
|
|
7240
7282
|
} catch {
|
|
7241
7283
|
}
|
|
7242
7284
|
}
|
|
7243
7285
|
function loadPendingEdits(sessionName) {
|
|
7244
7286
|
if (!sessionName) return null;
|
|
7245
|
-
const
|
|
7246
|
-
if (!existsSync9(
|
|
7287
|
+
const path5 = pendingEditsPath(sessionName);
|
|
7288
|
+
if (!existsSync9(path5)) return null;
|
|
7247
7289
|
let raw;
|
|
7248
7290
|
try {
|
|
7249
|
-
raw = readFileSync11(
|
|
7291
|
+
raw = readFileSync11(path5, "utf8");
|
|
7250
7292
|
} catch {
|
|
7251
7293
|
return null;
|
|
7252
7294
|
}
|
|
@@ -7266,9 +7308,9 @@ function loadPendingEdits(sessionName) {
|
|
|
7266
7308
|
}
|
|
7267
7309
|
function clearPendingEdits(sessionName) {
|
|
7268
7310
|
if (!sessionName) return;
|
|
7269
|
-
const
|
|
7311
|
+
const path5 = pendingEditsPath(sessionName);
|
|
7270
7312
|
try {
|
|
7271
|
-
if (existsSync9(
|
|
7313
|
+
if (existsSync9(path5)) unlinkSync3(path5);
|
|
7272
7314
|
} catch {
|
|
7273
7315
|
}
|
|
7274
7316
|
}
|
|
@@ -7289,10 +7331,10 @@ function planStatePath(sessionName) {
|
|
|
7289
7331
|
return join10(sessionsDir(), `${sanitizeName(sessionName)}.plan.json`);
|
|
7290
7332
|
}
|
|
7291
7333
|
function loadPlanState(sessionName) {
|
|
7292
|
-
const
|
|
7293
|
-
if (!existsSync10(
|
|
7334
|
+
const path5 = planStatePath(sessionName);
|
|
7335
|
+
if (!existsSync10(path5)) return null;
|
|
7294
7336
|
try {
|
|
7295
|
-
const raw = readFileSync12(
|
|
7337
|
+
const raw = readFileSync12(path5, "utf8");
|
|
7296
7338
|
const parsed = JSON.parse(raw);
|
|
7297
7339
|
if (!parsed || typeof parsed !== "object") return null;
|
|
7298
7340
|
if (parsed.version !== 1) return null;
|
|
@@ -7328,9 +7370,9 @@ function loadPlanState(sessionName) {
|
|
|
7328
7370
|
}
|
|
7329
7371
|
}
|
|
7330
7372
|
function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
7331
|
-
const
|
|
7373
|
+
const path5 = planStatePath(sessionName);
|
|
7332
7374
|
try {
|
|
7333
|
-
mkdirSync7(dirname9(
|
|
7375
|
+
mkdirSync7(dirname9(path5), { recursive: true });
|
|
7334
7376
|
const state = {
|
|
7335
7377
|
version: 1,
|
|
7336
7378
|
steps,
|
|
@@ -7339,7 +7381,7 @@ function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
|
7339
7381
|
};
|
|
7340
7382
|
if (extras?.body) state.body = extras.body;
|
|
7341
7383
|
if (extras?.summary) state.summary = extras.summary;
|
|
7342
|
-
writeFileSync6(
|
|
7384
|
+
writeFileSync6(path5, `${JSON.stringify(state, null, 2)}
|
|
7343
7385
|
`, "utf8");
|
|
7344
7386
|
} catch (err) {
|
|
7345
7387
|
process.stderr.write(
|
|
@@ -7349,9 +7391,9 @@ function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
|
7349
7391
|
}
|
|
7350
7392
|
}
|
|
7351
7393
|
function clearPlanState(sessionName) {
|
|
7352
|
-
const
|
|
7394
|
+
const path5 = planStatePath(sessionName);
|
|
7353
7395
|
try {
|
|
7354
|
-
if (existsSync10(
|
|
7396
|
+
if (existsSync10(path5)) unlinkSync4(path5);
|
|
7355
7397
|
} catch {
|
|
7356
7398
|
}
|
|
7357
7399
|
}
|
|
@@ -7419,9 +7461,9 @@ function listPlanArchives(sessionName) {
|
|
|
7419
7461
|
return summaries;
|
|
7420
7462
|
}
|
|
7421
7463
|
function relativeTime(updatedAt, now = Date.now()) {
|
|
7422
|
-
const
|
|
7423
|
-
if (Number.isNaN(
|
|
7424
|
-
const diffMs = Math.max(0, now -
|
|
7464
|
+
const t2 = Date.parse(updatedAt);
|
|
7465
|
+
if (Number.isNaN(t2)) return updatedAt;
|
|
7466
|
+
const diffMs = Math.max(0, now - t2);
|
|
7425
7467
|
const sec = Math.floor(diffMs / 1e3);
|
|
7426
7468
|
if (sec < 60) return `${sec}s ago`;
|
|
7427
7469
|
const min = Math.floor(sec / 60);
|
|
@@ -7466,7 +7508,7 @@ function registerSkillTools(registry, opts = {}) {
|
|
|
7466
7508
|
}
|
|
7467
7509
|
const stripped = raw.replace(/\[[^\]]*\]/g, " ").trim();
|
|
7468
7510
|
const tokens = stripped.split(/\s+/).filter(Boolean);
|
|
7469
|
-
const name = tokens.find((
|
|
7511
|
+
const name = tokens.find((t2) => /^[a-zA-Z0-9]/.test(t2)) ?? "";
|
|
7470
7512
|
if (!name) {
|
|
7471
7513
|
return JSON.stringify({
|
|
7472
7514
|
error: "run_skill requires a 'name' argument",
|
|
@@ -7511,6 +7553,60 @@ ${skill2.body}${argsBlock}`;
|
|
|
7511
7553
|
return registry;
|
|
7512
7554
|
}
|
|
7513
7555
|
|
|
7556
|
+
// src/tools/workspace.ts
|
|
7557
|
+
import { existsSync as existsSync11, statSync as statSync6 } from "fs";
|
|
7558
|
+
import * as pathMod4 from "path";
|
|
7559
|
+
var WorkspaceConfirmationError = class extends Error {
|
|
7560
|
+
path;
|
|
7561
|
+
constructor(path5) {
|
|
7562
|
+
super(
|
|
7563
|
+
`change_workspace: switching to "${path5}" needs the user's approval before it takes effect. STOP calling tools now \u2014 the TUI has already prompted the user to press Enter (switch) or Esc (deny). Wait for their next message; it will either confirm the switch (and your subsequent file/shell tools will resolve against the new root) or tell you to continue without changing directories.`
|
|
7564
|
+
);
|
|
7565
|
+
this.name = "WorkspaceConfirmationError";
|
|
7566
|
+
this.path = path5;
|
|
7567
|
+
}
|
|
7568
|
+
};
|
|
7569
|
+
function registerWorkspaceTool(registry) {
|
|
7570
|
+
registry.register({
|
|
7571
|
+
name: "change_workspace",
|
|
7572
|
+
description: "Switch the session's working directory to a different project root. Re-registers filesystem / shell / memory tools against the new path so subsequent file reads, edits, and run_command calls all land there. EVERY switch requires explicit user approval via a modal \u2014 do NOT batch switches or chain a switch with subsequent tool calls before the user has confirmed. Use ONLY when the user explicitly asked to change directory or open a different project; never use to 'preview' a sibling repo. MCP servers stay anchored to the original launch root (their child processes can't be reconnected mid-session); the modal warns the user about this.",
|
|
7573
|
+
parameters: {
|
|
7574
|
+
type: "object",
|
|
7575
|
+
required: ["path"],
|
|
7576
|
+
properties: {
|
|
7577
|
+
path: {
|
|
7578
|
+
type: "string",
|
|
7579
|
+
description: "Target directory. Absolute paths land verbatim. Leading `~` expands to the user's home. Relative paths resolve against the user's launch cwd (not the current session root, so paths the user typed in chat resolve where they expect)."
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
},
|
|
7583
|
+
fn: (rawArgs) => {
|
|
7584
|
+
const args = rawArgs ?? {};
|
|
7585
|
+
if (typeof args.path !== "string" || args.path.trim() === "") {
|
|
7586
|
+
throw new Error("change_workspace: `path` must be a non-empty string");
|
|
7587
|
+
}
|
|
7588
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7589
|
+
const expanded = args.path.startsWith("~") && home ? pathMod4.join(home, args.path.slice(1)) : args.path;
|
|
7590
|
+
const abs = pathMod4.resolve(expanded);
|
|
7591
|
+
if (!existsSync11(abs)) {
|
|
7592
|
+
throw new Error(`change_workspace: path does not exist \u2014 ${abs}`);
|
|
7593
|
+
}
|
|
7594
|
+
try {
|
|
7595
|
+
if (!statSync6(abs).isDirectory()) {
|
|
7596
|
+
throw new Error(`change_workspace: not a directory \u2014 ${abs}`);
|
|
7597
|
+
}
|
|
7598
|
+
} catch (err) {
|
|
7599
|
+
if (err.code === "ENOENT") {
|
|
7600
|
+
throw new Error(`change_workspace: path does not exist \u2014 ${abs}`);
|
|
7601
|
+
}
|
|
7602
|
+
throw err;
|
|
7603
|
+
}
|
|
7604
|
+
throw new WorkspaceConfirmationError(abs);
|
|
7605
|
+
}
|
|
7606
|
+
});
|
|
7607
|
+
return registry;
|
|
7608
|
+
}
|
|
7609
|
+
|
|
7514
7610
|
// src/cli/ui/AtMentionSuggestions.tsx
|
|
7515
7611
|
import { Box, Text } from "ink";
|
|
7516
7612
|
import React from "react";
|
|
@@ -7529,12 +7625,12 @@ function AtMentionSuggestions({
|
|
|
7529
7625
|
const shown = matches.slice(windowStart, windowStart + MAX);
|
|
7530
7626
|
const hiddenAbove = windowStart;
|
|
7531
7627
|
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((
|
|
7628
|
+
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
7629
|
}
|
|
7534
|
-
function FileRow({ path, isSelected }) {
|
|
7535
|
-
const slash =
|
|
7536
|
-
const dir = slash >= 0 ? `${
|
|
7537
|
-
const base = slash >= 0 ?
|
|
7630
|
+
function FileRow({ path: path5, isSelected }) {
|
|
7631
|
+
const slash = path5.lastIndexOf("/");
|
|
7632
|
+
const dir = slash >= 0 ? `${path5.slice(0, slash)}/` : "";
|
|
7633
|
+
const base = slash >= 0 ? path5.slice(slash + 1) : path5;
|
|
7538
7634
|
if (isSelected) {
|
|
7539
7635
|
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { backgroundColor: "#67e8f9", color: "black", bold: true }, ` \u25B8 ${base}${dir ? ` ${dir}` : ""} `));
|
|
7540
7636
|
}
|
|
@@ -7555,8 +7651,8 @@ function ModalCard({
|
|
|
7555
7651
|
icon,
|
|
7556
7652
|
children
|
|
7557
7653
|
}) {
|
|
7558
|
-
const { stdout:
|
|
7559
|
-
const cols =
|
|
7654
|
+
const { stdout: stdout3 } = useStdout();
|
|
7655
|
+
const cols = stdout3?.columns ?? 80;
|
|
7560
7656
|
const ruleWidth = Math.min(80, Math.max(28, cols - 4));
|
|
7561
7657
|
const titleText = icon ? ` ${icon} ${title} ` : ` ${title} `;
|
|
7562
7658
|
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 +8215,8 @@ function capLines(lines, maxLines, indent) {
|
|
|
8119
8215
|
var MODAL_OVERHEAD_ROWS = 18;
|
|
8120
8216
|
var MIN_DIFF_ROWS = 8;
|
|
8121
8217
|
function EditConfirm({ block, onChoose }) {
|
|
8122
|
-
const { stdout:
|
|
8123
|
-
const rows =
|
|
8218
|
+
const { stdout: stdout3 } = useStdout2();
|
|
8219
|
+
const rows = stdout3?.rows ?? 40;
|
|
8124
8220
|
const budget = Math.max(MIN_DIFF_ROWS, rows - MODAL_OVERHEAD_ROWS);
|
|
8125
8221
|
const allLines = useMemo(
|
|
8126
8222
|
() => formatEditBlockDiff(block, { contextLines: 2, maxLines: 1e5, indent: " " }),
|
|
@@ -8305,8 +8401,8 @@ function RiskLegend() {
|
|
|
8305
8401
|
var PlanStepList = React8.memo(PlanStepListInner);
|
|
8306
8402
|
|
|
8307
8403
|
// src/cli/ui/markdown.tsx
|
|
8308
|
-
import { readFileSync as readFileSync13, statSync as
|
|
8309
|
-
import { isAbsolute as isAbsolute4, join as
|
|
8404
|
+
import { readFileSync as readFileSync13, statSync as statSync7 } from "fs";
|
|
8405
|
+
import { isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
8310
8406
|
import { Box as Box8, Text as Text7 } from "ink";
|
|
8311
8407
|
import React9 from "react";
|
|
8312
8408
|
var SUPERSCRIPT = {
|
|
@@ -8348,7 +8444,32 @@ function toSubscript(s) {
|
|
|
8348
8444
|
for (const c of s) out += SUBSCRIPT[c] ?? c;
|
|
8349
8445
|
return out;
|
|
8350
8446
|
}
|
|
8447
|
+
var HAS_MATH_RE = new RegExp(
|
|
8448
|
+
[
|
|
8449
|
+
"\\$",
|
|
8450
|
+
// dollar-delimited (block or inline)
|
|
8451
|
+
"\\\\[([]",
|
|
8452
|
+
// \( or \[
|
|
8453
|
+
"\\\\[a-zA-Z]+\\s*\\{",
|
|
8454
|
+
// \anyCommand{...} — covers catch-all braced transforms
|
|
8455
|
+
// Bare (no-brace) LaTeX commands the pipeline knows how to handle.
|
|
8456
|
+
// Listed explicitly because a generic `\\[a-zA-Z]+` would also match
|
|
8457
|
+
// Windows paths (`F:\TEST1`) and re-introduce the bug we're fixing.
|
|
8458
|
+
"\\\\(?:cdot|times|div|pm|mp|leq|geq|neq|approx|in|notin|infty|sum|prod|int|alpha|beta|gamma|delta|theta|lambda|mu|pi|sigma|phi|omega|implies|iff|to|rightarrow|leftarrow|Rightarrow|Leftarrow|ldots|cdots|quad|qquad)(?![a-zA-Z])",
|
|
8459
|
+
"[\\^_]\\{",
|
|
8460
|
+
// LaTeX braced super/subscript: ^{2}, _{ij}
|
|
8461
|
+
"\\^[0-9+\\-n](?![A-Za-z])",
|
|
8462
|
+
// LaTeX single-char super: ^2, ^-, ^n
|
|
8463
|
+
"_[0-9+\\-](?![A-Za-z])",
|
|
8464
|
+
// LaTeX single-char sub: _1, _+, _-
|
|
8465
|
+
"\\^[A-Za-z0-9+\\-]+\\^",
|
|
8466
|
+
// Pandoc super: ^2^, ^abc^
|
|
8467
|
+
"(?<!~)~[A-Za-z0-9+\\-]+~(?!~)"
|
|
8468
|
+
// Pandoc sub: ~2~ (lookarounds avoid ~~strike~~)
|
|
8469
|
+
].join("|")
|
|
8470
|
+
);
|
|
8351
8471
|
function stripMath(s) {
|
|
8472
|
+
if (!HAS_MATH_RE.test(s)) return s;
|
|
8352
8473
|
return s.replace(/\$\$([\s\S]+?)\$\$/g, (_m, c) => `
|
|
8353
8474
|
|
|
8354
8475
|
${c.trim()}
|
|
@@ -8522,7 +8643,7 @@ function validateCitation(url, projectRoot) {
|
|
|
8522
8643
|
const parts = parseCitationUrl(url);
|
|
8523
8644
|
if (!parts || !parts.path) return { ok: false, reason: "empty path" };
|
|
8524
8645
|
const normalized = parts.path.replace(/^[/\\]+/, "");
|
|
8525
|
-
const baseFullPath = isAbsolute4(normalized) ? normalized :
|
|
8646
|
+
const baseFullPath = isAbsolute4(normalized) ? normalized : join12(projectRoot, normalized);
|
|
8526
8647
|
const siblings = SIBLING_EXTENSIONS.get(extOf(baseFullPath)) ?? [];
|
|
8527
8648
|
const candidates = [
|
|
8528
8649
|
baseFullPath,
|
|
@@ -8532,7 +8653,7 @@ function validateCitation(url, projectRoot) {
|
|
|
8532
8653
|
let stat = null;
|
|
8533
8654
|
for (const candidate of candidates) {
|
|
8534
8655
|
try {
|
|
8535
|
-
stat =
|
|
8656
|
+
stat = statSync7(candidate);
|
|
8536
8657
|
fullPath = candidate;
|
|
8537
8658
|
break;
|
|
8538
8659
|
} catch {
|
|
@@ -9110,10 +9231,10 @@ function gradientCells(width, glyph = GLYPH.block) {
|
|
|
9110
9231
|
if (width <= 0) return cells;
|
|
9111
9232
|
const last = GRADIENT.length - 1;
|
|
9112
9233
|
for (let i = 0; i < width; i++) {
|
|
9113
|
-
const
|
|
9114
|
-
const lo = Math.floor(
|
|
9234
|
+
const t2 = width === 1 ? 0 : i * last / (width - 1);
|
|
9235
|
+
const lo = Math.floor(t2);
|
|
9115
9236
|
const hi = Math.min(last, lo + 1);
|
|
9116
|
-
const color =
|
|
9237
|
+
const color = t2 - lo < 0.5 ? GRADIENT[lo] : GRADIENT[hi];
|
|
9117
9238
|
cells.push({ ch: glyph, color });
|
|
9118
9239
|
}
|
|
9119
9240
|
return cells;
|
|
@@ -9127,7 +9248,7 @@ function TickerProvider({ children, disabled }) {
|
|
|
9127
9248
|
const [tick, setTick] = useState3(0);
|
|
9128
9249
|
useEffect2(() => {
|
|
9129
9250
|
if (disabled) return;
|
|
9130
|
-
const id = setInterval(() => setTick((
|
|
9251
|
+
const id = setInterval(() => setTick((t2) => t2 + 1), TICK_MS);
|
|
9131
9252
|
return () => clearInterval(id);
|
|
9132
9253
|
}, [disabled]);
|
|
9133
9254
|
return /* @__PURE__ */ React10.createElement(TickContext.Provider, { value: tick }, children);
|
|
@@ -9416,8 +9537,8 @@ var EventRow = React11.memo(function EventRow2({
|
|
|
9416
9537
|
return /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(Text8, null, event.text));
|
|
9417
9538
|
});
|
|
9418
9539
|
function TurnSeparator() {
|
|
9419
|
-
const { stdout:
|
|
9420
|
-
const cols =
|
|
9540
|
+
const { stdout: stdout3 } = useStdout3();
|
|
9541
|
+
const cols = stdout3?.columns ?? 80;
|
|
9421
9542
|
const width = Math.max(16, cols - 2);
|
|
9422
9543
|
const sideWidth = Math.max(2, Math.floor((width - 5) / 2));
|
|
9423
9544
|
const leftCells = gradientCells(sideWidth, "\u2500");
|
|
@@ -9448,10 +9569,10 @@ function EditFileDiff({ text }) {
|
|
|
9448
9569
|
function BranchBlock({ branch: branch2 }) {
|
|
9449
9570
|
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
9571
|
const chosen = i === branch2.chosenIndex;
|
|
9451
|
-
const
|
|
9572
|
+
const t2 = (branch2.temperatures[i] ?? 0).toFixed(1);
|
|
9452
9573
|
return (
|
|
9453
9574
|
// 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=${
|
|
9575
|
+
/* @__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
9576
|
);
|
|
9456
9577
|
})));
|
|
9457
9578
|
}
|
|
@@ -9575,8 +9696,8 @@ function ModeStatusBar({
|
|
|
9575
9696
|
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
9697
|
}
|
|
9577
9698
|
function ModeBarFrame({ children }) {
|
|
9578
|
-
const { stdout:
|
|
9579
|
-
const cols =
|
|
9699
|
+
const { stdout: stdout3 } = useStdout4();
|
|
9700
|
+
const cols = stdout3?.columns ?? 80;
|
|
9580
9701
|
const ruleWidth = Math.max(20, cols - 2);
|
|
9581
9702
|
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
9703
|
}
|
|
@@ -9635,26 +9756,26 @@ function summarizeToolArgs(name, args) {
|
|
|
9635
9756
|
return args.length > 80 ? `${args.slice(0, 80)}\u2026` : args;
|
|
9636
9757
|
}
|
|
9637
9758
|
const hasSuffix = (s) => name === s || name.endsWith(`_${s}`);
|
|
9638
|
-
const
|
|
9759
|
+
const path5 = typeof parsed.path === "string" ? parsed.path : void 0;
|
|
9639
9760
|
if (hasSuffix("read_file")) {
|
|
9640
9761
|
const head = typeof parsed.head === "number" ? `, head=${parsed.head}` : "";
|
|
9641
9762
|
const tail = typeof parsed.tail === "number" ? `, tail=${parsed.tail}` : "";
|
|
9642
|
-
return `path: ${
|
|
9763
|
+
return `path: ${path5 ?? "?"}${head}${tail}`;
|
|
9643
9764
|
}
|
|
9644
9765
|
if (hasSuffix("write_file")) {
|
|
9645
9766
|
const content = typeof parsed.content === "string" ? parsed.content : "";
|
|
9646
|
-
return `path: ${
|
|
9767
|
+
return `path: ${path5 ?? "?"} (${content.length} chars)`;
|
|
9647
9768
|
}
|
|
9648
9769
|
if (hasSuffix("edit_file")) {
|
|
9649
9770
|
const edits = Array.isArray(parsed.edits) ? parsed.edits.length : 0;
|
|
9650
|
-
return `path: ${
|
|
9771
|
+
return `path: ${path5 ?? "?"} (${edits} edit${edits === 1 ? "" : "s"})`;
|
|
9651
9772
|
}
|
|
9652
9773
|
if (hasSuffix("list_directory") || hasSuffix("directory_tree")) {
|
|
9653
|
-
return `path: ${
|
|
9774
|
+
return `path: ${path5 ?? "?"}`;
|
|
9654
9775
|
}
|
|
9655
9776
|
if (hasSuffix("search_files")) {
|
|
9656
9777
|
const pattern = typeof parsed.pattern === "string" ? parsed.pattern : "?";
|
|
9657
|
-
return `path: ${
|
|
9778
|
+
return `path: ${path5 ?? "?"} \xB7 pattern: ${pattern}`;
|
|
9658
9779
|
}
|
|
9659
9780
|
if (hasSuffix("move_file")) {
|
|
9660
9781
|
const src = typeof parsed.source === "string" ? parsed.source : "?";
|
|
@@ -9662,7 +9783,7 @@ function summarizeToolArgs(name, args) {
|
|
|
9662
9783
|
return `${src} \u2192 ${dst}`;
|
|
9663
9784
|
}
|
|
9664
9785
|
if (hasSuffix("get_file_info")) {
|
|
9665
|
-
return `path: ${
|
|
9786
|
+
return `path: ${path5 ?? "?"}`;
|
|
9666
9787
|
}
|
|
9667
9788
|
return args.length > 80 ? `${args.slice(0, 80)}\u2026` : args;
|
|
9668
9789
|
}
|
|
@@ -10377,8 +10498,8 @@ function PromptInput({
|
|
|
10377
10498
|
if (action.historyHandoff === "prev") onHistoryPrev?.();
|
|
10378
10499
|
if (action.historyHandoff === "next") onHistoryNext?.();
|
|
10379
10500
|
}, !disabled);
|
|
10380
|
-
const { stdout:
|
|
10381
|
-
const cols =
|
|
10501
|
+
const { stdout: stdout3 } = useStdout5();
|
|
10502
|
+
const cols = stdout3?.columns ?? 80;
|
|
10382
10503
|
const narrow = cols <= 90;
|
|
10383
10504
|
const promptBody = narrow ? "\u203A " : "you \u203A ";
|
|
10384
10505
|
const promptPrefix = BAR + promptBody;
|
|
@@ -10749,8 +10870,8 @@ function StatsPanel({
|
|
|
10749
10870
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
10750
10871
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model2] ?? DEFAULT_CONTEXT_TOKENS;
|
|
10751
10872
|
const ctxRatio = summary.lastPromptTokens / ctxMax;
|
|
10752
|
-
const { stdout:
|
|
10753
|
-
const columns =
|
|
10873
|
+
const { stdout: stdout3 } = useStdout6();
|
|
10874
|
+
const columns = stdout3?.columns ?? 80;
|
|
10754
10875
|
const narrow = columns < NARROW_BREAKPOINT;
|
|
10755
10876
|
const coldStart = summary.turns <= COLD_START_TURNS;
|
|
10756
10877
|
return /* @__PURE__ */ React21.createElement(Box19, { flexDirection: "column", paddingX: 1, marginBottom: 1 }, /* @__PURE__ */ React21.createElement(
|
|
@@ -10916,8 +11037,8 @@ function formatTokens(n) {
|
|
|
10916
11037
|
import { Box as Box20, Text as Text18, useStdout as useStdout7 } from "ink";
|
|
10917
11038
|
import React22 from "react";
|
|
10918
11039
|
function WelcomeBanner({ inCodeMode }) {
|
|
10919
|
-
const { stdout:
|
|
10920
|
-
const cols =
|
|
11040
|
+
const { stdout: stdout3 } = useStdout7();
|
|
11041
|
+
const cols = stdout3?.columns ?? 80;
|
|
10921
11042
|
const ruleWidth = Math.min(60, Math.max(28, cols - 4));
|
|
10922
11043
|
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
11044
|
}
|
|
@@ -10935,6 +11056,39 @@ function Hint({ cmd, desc }) {
|
|
|
10935
11056
|
return /* @__PURE__ */ React22.createElement(BarRow, null, /* @__PURE__ */ React22.createElement(Text18, { bold: true, color: COLOR.accent }, cmd.padEnd(8)), /* @__PURE__ */ React22.createElement(Text18, { dimColor: true }, ` ${desc}`));
|
|
10936
11057
|
}
|
|
10937
11058
|
|
|
11059
|
+
// src/cli/ui/WorkspaceConfirm.tsx
|
|
11060
|
+
import { Box as Box21, Text as Text19 } from "ink";
|
|
11061
|
+
import React23 from "react";
|
|
11062
|
+
function WorkspaceConfirm({
|
|
11063
|
+
path: path5,
|
|
11064
|
+
currentRoot,
|
|
11065
|
+
mcpServerCount,
|
|
11066
|
+
onChoose
|
|
11067
|
+
}) {
|
|
11068
|
+
const subtitle = mcpServerCount > 0 ? `MCP servers (${mcpServerCount}) stay anchored to the original launch root.` : "Re-registers filesystem / shell / memory tools at the new path.";
|
|
11069
|
+
return /* @__PURE__ */ React23.createElement(ModalCard, { accent: "#f59e0b", icon: "\u21C4", title: "switch workspace", subtitle }, /* @__PURE__ */ React23.createElement(Box21, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React23.createElement(Box21, null, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "from "), /* @__PURE__ */ React23.createElement(Text19, { color: "#a3a3a3" }, currentRoot)), /* @__PURE__ */ React23.createElement(Box21, null, /* @__PURE__ */ React23.createElement(Text19, { dimColor: true }, "to "), /* @__PURE__ */ React23.createElement(Text19, { color: "#67e8f9", bold: true }, path5))), /* @__PURE__ */ React23.createElement(
|
|
11070
|
+
SingleSelect,
|
|
11071
|
+
{
|
|
11072
|
+
initialValue: "switch",
|
|
11073
|
+
items: [
|
|
11074
|
+
{
|
|
11075
|
+
value: "switch",
|
|
11076
|
+
label: "Switch",
|
|
11077
|
+
hint: "Re-register filesystem / shell / memory tools against the new root."
|
|
11078
|
+
},
|
|
11079
|
+
{
|
|
11080
|
+
value: "deny",
|
|
11081
|
+
label: "Deny",
|
|
11082
|
+
hint: "Tell the model the user refused; it will continue without changing directories."
|
|
11083
|
+
}
|
|
11084
|
+
],
|
|
11085
|
+
onSubmit: (v) => onChoose(v),
|
|
11086
|
+
onCancel: () => onChoose("deny"),
|
|
11087
|
+
footer: "[\u2191\u2193] navigate \xB7 [Enter] select \xB7 [Esc] deny"
|
|
11088
|
+
}
|
|
11089
|
+
));
|
|
11090
|
+
}
|
|
11091
|
+
|
|
10938
11092
|
// src/cli/ui/bang.ts
|
|
10939
11093
|
function detectBangCommand(text) {
|
|
10940
11094
|
if (!text.startsWith("!")) return null;
|
|
@@ -10978,7 +11132,7 @@ function parseEditIndices(raw, max) {
|
|
|
10978
11132
|
if (!trimmed) return { ok: [] };
|
|
10979
11133
|
if (max <= 0) return { error: "no pending edits to address" };
|
|
10980
11134
|
const seen = /* @__PURE__ */ new Set();
|
|
10981
|
-
const tokens = trimmed.split(",").map((
|
|
11135
|
+
const tokens = trimmed.split(",").map((t2) => t2.trim()).filter((t2) => t2.length > 0);
|
|
10982
11136
|
if (tokens.length === 0) return { ok: [] };
|
|
10983
11137
|
for (const tok of tokens) {
|
|
10984
11138
|
const range = tok.match(/^(\d+)-(\d+)$/);
|
|
@@ -11028,9 +11182,9 @@ function describeRepair(repair) {
|
|
|
11028
11182
|
}
|
|
11029
11183
|
|
|
11030
11184
|
// src/cli/ui/hash-memory.ts
|
|
11031
|
-
import { appendFileSync as appendFileSync3, existsSync as
|
|
11185
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
11032
11186
|
import { homedir as homedir6 } from "os";
|
|
11033
|
-
import { dirname as dirname10, join as
|
|
11187
|
+
import { dirname as dirname10, join as join13 } from "path";
|
|
11034
11188
|
var PROJECT_HEADER = `# Reasonix project memory
|
|
11035
11189
|
|
|
11036
11190
|
Notes the user pinned via the \`#\` prompt prefix. The whole file is
|
|
@@ -11062,34 +11216,34 @@ function detectHashMemory(text) {
|
|
|
11062
11216
|
return { kind: "memory", note: body };
|
|
11063
11217
|
}
|
|
11064
11218
|
function appendProjectMemory(rootDir, note) {
|
|
11065
|
-
return appendBulletToFile(
|
|
11219
|
+
return appendBulletToFile(join13(rootDir, PROJECT_MEMORY_FILE), note, PROJECT_HEADER);
|
|
11066
11220
|
}
|
|
11067
11221
|
var GLOBAL_MEMORY_DIR = ".reasonix";
|
|
11068
11222
|
var GLOBAL_MEMORY_FILE = "REASONIX.md";
|
|
11069
11223
|
function globalMemoryPath(homeDir = homedir6()) {
|
|
11070
|
-
return
|
|
11224
|
+
return join13(homeDir, GLOBAL_MEMORY_DIR, GLOBAL_MEMORY_FILE);
|
|
11071
11225
|
}
|
|
11072
11226
|
function appendGlobalMemory(note, homeDir) {
|
|
11073
11227
|
return appendBulletToFile(globalMemoryPath(homeDir), note, GLOBAL_HEADER);
|
|
11074
11228
|
}
|
|
11075
|
-
function appendBulletToFile(
|
|
11229
|
+
function appendBulletToFile(path5, note, newFileHeader) {
|
|
11076
11230
|
const trimmed = note.trim();
|
|
11077
11231
|
if (!trimmed) throw new Error("note body cannot be empty");
|
|
11078
11232
|
const bullet = `- ${trimmed}
|
|
11079
11233
|
`;
|
|
11080
|
-
if (!
|
|
11081
|
-
mkdirSync8(dirname10(
|
|
11082
|
-
writeFileSync7(
|
|
11083
|
-
return { path, created: true };
|
|
11234
|
+
if (!existsSync12(path5)) {
|
|
11235
|
+
mkdirSync8(dirname10(path5), { recursive: true });
|
|
11236
|
+
writeFileSync7(path5, `${newFileHeader}${bullet}`, "utf8");
|
|
11237
|
+
return { path: path5, created: true };
|
|
11084
11238
|
}
|
|
11085
11239
|
let prefix = "";
|
|
11086
11240
|
try {
|
|
11087
|
-
const existing = readFileSync14(
|
|
11241
|
+
const existing = readFileSync14(path5, "utf8");
|
|
11088
11242
|
if (existing.length > 0 && !existing.endsWith("\n")) prefix = "\n";
|
|
11089
11243
|
} catch {
|
|
11090
11244
|
}
|
|
11091
|
-
appendFileSync3(
|
|
11092
|
-
return { path, created: false };
|
|
11245
|
+
appendFileSync3(path5, `${prefix}${bullet}`, "utf8");
|
|
11246
|
+
return { path: path5, created: false };
|
|
11093
11247
|
}
|
|
11094
11248
|
|
|
11095
11249
|
// src/cli/ui/loop.ts
|
|
@@ -11425,6 +11579,11 @@ var SLASH_COMMANDS = [
|
|
|
11425
11579
|
argsHint: "[reload]",
|
|
11426
11580
|
summary: "list active hooks (settings.json under .reasonix/) \xB7 reload re-reads from disk"
|
|
11427
11581
|
},
|
|
11582
|
+
{
|
|
11583
|
+
cmd: "cwd",
|
|
11584
|
+
argsHint: "<path>",
|
|
11585
|
+
summary: "switch session working directory (re-registers code tools, reloads hooks; MCP servers stay)"
|
|
11586
|
+
},
|
|
11428
11587
|
{
|
|
11429
11588
|
cmd: "update",
|
|
11430
11589
|
summary: "show current vs latest version + the shell command to upgrade"
|
|
@@ -11454,6 +11613,10 @@ var SLASH_COMMANDS = [
|
|
|
11454
11613
|
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
11455
11614
|
{ cmd: "forget", summary: "delete the current session from disk" },
|
|
11456
11615
|
{ cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
|
|
11616
|
+
{
|
|
11617
|
+
cmd: "semantic",
|
|
11618
|
+
summary: "show semantic_search status \u2014 built? Ollama installed? how to enable"
|
|
11619
|
+
},
|
|
11457
11620
|
{ cmd: "clear", summary: "clear visible scrollback only (log/context kept)" },
|
|
11458
11621
|
{ cmd: "new", summary: "start a fresh conversation (clear context + scrollback)" },
|
|
11459
11622
|
{
|
|
@@ -11567,8 +11730,12 @@ function parseSlash(text) {
|
|
|
11567
11730
|
return { cmd, args: parts.slice(1) };
|
|
11568
11731
|
}
|
|
11569
11732
|
|
|
11733
|
+
// src/cli/ui/slash/handlers/admin.ts
|
|
11734
|
+
import { existsSync as existsSync14, statSync as statSync8 } from "fs";
|
|
11735
|
+
import * as pathMod5 from "path";
|
|
11736
|
+
|
|
11570
11737
|
// src/cli/commands/stats.ts
|
|
11571
|
-
import { existsSync as
|
|
11738
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "fs";
|
|
11572
11739
|
function statsCommand(opts) {
|
|
11573
11740
|
if (opts.transcript) {
|
|
11574
11741
|
transcriptSummary(opts.transcript);
|
|
@@ -11576,12 +11743,12 @@ function statsCommand(opts) {
|
|
|
11576
11743
|
}
|
|
11577
11744
|
dashboard(opts);
|
|
11578
11745
|
}
|
|
11579
|
-
function transcriptSummary(
|
|
11580
|
-
if (!
|
|
11581
|
-
console.error(`no such transcript: ${
|
|
11746
|
+
function transcriptSummary(path5) {
|
|
11747
|
+
if (!existsSync13(path5)) {
|
|
11748
|
+
console.error(`no such transcript: ${path5}`);
|
|
11582
11749
|
process.exit(1);
|
|
11583
11750
|
}
|
|
11584
|
-
const lines = readFileSync15(
|
|
11751
|
+
const lines = readFileSync15(path5, "utf8").split(/\r?\n/).filter(Boolean);
|
|
11585
11752
|
let assistantTurns = 0;
|
|
11586
11753
|
let toolCalls = 0;
|
|
11587
11754
|
let lastTurn = 0;
|
|
@@ -11594,25 +11761,25 @@ function transcriptSummary(path) {
|
|
|
11594
11761
|
} catch {
|
|
11595
11762
|
}
|
|
11596
11763
|
}
|
|
11597
|
-
console.log(`transcript: ${
|
|
11764
|
+
console.log(`transcript: ${path5}`);
|
|
11598
11765
|
console.log(`assistant turns: ${assistantTurns}`);
|
|
11599
11766
|
console.log(`tool invocations: ${toolCalls}`);
|
|
11600
11767
|
console.log(`last turn index: ${lastTurn}`);
|
|
11601
11768
|
}
|
|
11602
11769
|
function dashboard(opts) {
|
|
11603
|
-
const
|
|
11604
|
-
const records = readUsageLog(
|
|
11770
|
+
const path5 = opts.logPath ?? defaultUsageLogPath();
|
|
11771
|
+
const records = readUsageLog(path5);
|
|
11605
11772
|
if (records.length === 0) {
|
|
11606
11773
|
console.log("no usage data yet.");
|
|
11607
11774
|
console.log("");
|
|
11608
|
-
console.log(` ${
|
|
11775
|
+
console.log(` ${path5}`);
|
|
11609
11776
|
console.log("");
|
|
11610
11777
|
console.log("run `reasonix chat`, `reasonix code`, or `reasonix run <task>` \u2014 every turn");
|
|
11611
11778
|
console.log("appends one line to the log and `reasonix stats` will roll it up.");
|
|
11612
11779
|
return;
|
|
11613
11780
|
}
|
|
11614
11781
|
const agg = aggregateUsage(records, { now: opts.now });
|
|
11615
|
-
console.log(renderDashboard(agg,
|
|
11782
|
+
console.log(renderDashboard(agg, path5));
|
|
11616
11783
|
}
|
|
11617
11784
|
function renderDashboard(agg, logPath) {
|
|
11618
11785
|
const lines = [];
|
|
@@ -11784,14 +11951,14 @@ var update = (_args, _loop, ctx) => {
|
|
|
11784
11951
|
return { info: lines.join("\n") };
|
|
11785
11952
|
};
|
|
11786
11953
|
var stats = () => {
|
|
11787
|
-
const
|
|
11788
|
-
const records = readUsageLog(
|
|
11954
|
+
const path5 = defaultUsageLogPath();
|
|
11955
|
+
const records = readUsageLog(path5);
|
|
11789
11956
|
if (records.length === 0) {
|
|
11790
11957
|
return {
|
|
11791
11958
|
info: [
|
|
11792
11959
|
"no usage data yet.",
|
|
11793
11960
|
"",
|
|
11794
|
-
` ${
|
|
11961
|
+
` ${path5}`,
|
|
11795
11962
|
"",
|
|
11796
11963
|
"every turn you run here appends one record \u2014 this session's turns",
|
|
11797
11964
|
"will show up in the dashboard once you send a message."
|
|
@@ -11799,11 +11966,53 @@ var stats = () => {
|
|
|
11799
11966
|
};
|
|
11800
11967
|
}
|
|
11801
11968
|
const agg = aggregateUsage(records);
|
|
11802
|
-
return { info: renderDashboard(agg,
|
|
11969
|
+
return { info: renderDashboard(agg, path5) };
|
|
11970
|
+
};
|
|
11971
|
+
var cwd = (args, _loop, ctx) => {
|
|
11972
|
+
if (!ctx.setCwd) {
|
|
11973
|
+
return {
|
|
11974
|
+
info: "/cwd is not available in this context (no setCwd callback wired)."
|
|
11975
|
+
};
|
|
11976
|
+
}
|
|
11977
|
+
const raw = (args[0] ?? "").trim();
|
|
11978
|
+
if (!raw) {
|
|
11979
|
+
return {
|
|
11980
|
+
info: "usage: /cwd <path> (absolute or relative, ~ expands to home)"
|
|
11981
|
+
};
|
|
11982
|
+
}
|
|
11983
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
11984
|
+
const expanded = raw.startsWith("~") && home ? pathMod5.join(home, raw.slice(1)) : raw;
|
|
11985
|
+
const abs = pathMod5.resolve(expanded);
|
|
11986
|
+
if (!existsSync14(abs)) {
|
|
11987
|
+
return { info: `\u25B8 /cwd: path does not exist \u2014 ${abs}` };
|
|
11988
|
+
}
|
|
11989
|
+
let isDir = false;
|
|
11990
|
+
try {
|
|
11991
|
+
isDir = statSync8(abs).isDirectory();
|
|
11992
|
+
} catch {
|
|
11993
|
+
}
|
|
11994
|
+
if (!isDir) {
|
|
11995
|
+
return { info: `\u25B8 /cwd: not a directory \u2014 ${abs}` };
|
|
11996
|
+
}
|
|
11997
|
+
let info;
|
|
11998
|
+
try {
|
|
11999
|
+
info = ctx.setCwd(abs);
|
|
12000
|
+
} catch (err) {
|
|
12001
|
+
return { info: `\u25B8 /cwd failed: ${err.message}` };
|
|
12002
|
+
}
|
|
12003
|
+
const lines = [info];
|
|
12004
|
+
if (ctx.mcpServers && ctx.mcpServers.length > 0) {
|
|
12005
|
+
lines.push(
|
|
12006
|
+
` note: ${ctx.mcpServers.length} MCP server(s) still anchored to the original cwd \u2014`,
|
|
12007
|
+
" their tools won't follow this switch. Restart the session for full reset."
|
|
12008
|
+
);
|
|
12009
|
+
}
|
|
12010
|
+
return { info: lines.join("\n") };
|
|
11803
12011
|
};
|
|
11804
12012
|
var handlers = {
|
|
11805
12013
|
hook: hooks,
|
|
11806
12014
|
hooks,
|
|
12015
|
+
cwd,
|
|
11807
12016
|
update,
|
|
11808
12017
|
stats
|
|
11809
12018
|
};
|
|
@@ -12101,8 +12310,8 @@ ${gitTail(commit2)}` };
|
|
|
12101
12310
|
}
|
|
12102
12311
|
function gitTail(res) {
|
|
12103
12312
|
const stderr = res.stderr ?? "";
|
|
12104
|
-
const
|
|
12105
|
-
const body = stderr.trim() ||
|
|
12313
|
+
const stdout3 = res.stdout ?? "";
|
|
12314
|
+
const body = stderr.trim() || stdout3.trim();
|
|
12106
12315
|
if (body) return body;
|
|
12107
12316
|
if (res.error) return res.error.message;
|
|
12108
12317
|
return "(no output from git)";
|
|
@@ -12361,7 +12570,7 @@ var mcp = (_args, loop2, ctx) => {
|
|
|
12361
12570
|
}
|
|
12362
12571
|
if (toolSpecs.length > 0) {
|
|
12363
12572
|
lines.push(`Tools in registry (${toolSpecs.length}):`);
|
|
12364
|
-
for (const
|
|
12573
|
+
for (const t2 of toolSpecs) lines.push(` \xB7 ${t2.function.name}`);
|
|
12365
12574
|
}
|
|
12366
12575
|
lines.push("");
|
|
12367
12576
|
lines.push("To change this set, exit and run `reasonix setup`.");
|
|
@@ -12757,9 +12966,9 @@ var context = (_args, loop2) => {
|
|
|
12757
12966
|
const top = [...toolBreakdown].sort((a, b) => b.tokens - a.tokens).slice(0, 5);
|
|
12758
12967
|
lines.push("");
|
|
12759
12968
|
lines.push(`Top tool results by cost (of ${toolBreakdown.length} total):`);
|
|
12760
|
-
for (const
|
|
12969
|
+
for (const t2 of top) {
|
|
12761
12970
|
lines.push(
|
|
12762
|
-
` turn ${String(
|
|
12971
|
+
` turn ${String(t2.turn).padStart(3)} ${t2.name.padEnd(22)} ${compactNum(t2.tokens).padStart(8)} tokens`
|
|
12763
12972
|
);
|
|
12764
12973
|
}
|
|
12765
12974
|
}
|
|
@@ -12901,6 +13110,378 @@ var handlers9 = {
|
|
|
12901
13110
|
replay
|
|
12902
13111
|
};
|
|
12903
13112
|
|
|
13113
|
+
// src/cli/ui/slash/handlers/semantic.ts
|
|
13114
|
+
import { promises as fs2 } from "fs";
|
|
13115
|
+
import path from "path";
|
|
13116
|
+
|
|
13117
|
+
// src/index/semantic/embedding.ts
|
|
13118
|
+
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
13119
|
+
var DEFAULT_EMBED_MODEL = "nomic-embed-text";
|
|
13120
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
13121
|
+
var EmbeddingError = class extends Error {
|
|
13122
|
+
constructor(message, cause) {
|
|
13123
|
+
super(message);
|
|
13124
|
+
this.cause = cause;
|
|
13125
|
+
this.name = "EmbeddingError";
|
|
13126
|
+
}
|
|
13127
|
+
cause;
|
|
13128
|
+
};
|
|
13129
|
+
async function embed(text, opts = {}) {
|
|
13130
|
+
const baseUrl = opts.baseUrl ?? process.env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL;
|
|
13131
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? DEFAULT_EMBED_MODEL;
|
|
13132
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
13133
|
+
const controller = new AbortController();
|
|
13134
|
+
const onCallerAbort = () => controller.abort(opts.signal?.reason);
|
|
13135
|
+
if (opts.signal) {
|
|
13136
|
+
if (opts.signal.aborted) controller.abort(opts.signal.reason);
|
|
13137
|
+
else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
13138
|
+
}
|
|
13139
|
+
const timer = setTimeout(() => controller.abort(new Error("embedding timeout")), timeoutMs);
|
|
13140
|
+
let res;
|
|
13141
|
+
try {
|
|
13142
|
+
res = await fetch(`${baseUrl}/api/embeddings`, {
|
|
13143
|
+
method: "POST",
|
|
13144
|
+
headers: { "content-type": "application/json" },
|
|
13145
|
+
body: JSON.stringify({ model: model2, prompt: text }),
|
|
13146
|
+
signal: controller.signal
|
|
13147
|
+
});
|
|
13148
|
+
} catch (err) {
|
|
13149
|
+
clearTimeout(timer);
|
|
13150
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
13151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13152
|
+
if (/ECONNREFUSED|connect ECONNREFUSED|fetch failed/i.test(msg)) {
|
|
13153
|
+
throw new EmbeddingError(
|
|
13154
|
+
`Cannot reach Ollama at ${baseUrl}. Install from https://ollama.com, then run \`ollama pull ${model2}\` and \`ollama serve\`. Override the URL via OLLAMA_URL.`,
|
|
13155
|
+
err
|
|
13156
|
+
);
|
|
13157
|
+
}
|
|
13158
|
+
throw new EmbeddingError(`embedding request failed: ${msg}`, err);
|
|
13159
|
+
} finally {
|
|
13160
|
+
clearTimeout(timer);
|
|
13161
|
+
if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
|
|
13162
|
+
}
|
|
13163
|
+
if (!res.ok) {
|
|
13164
|
+
const body = await res.text().catch(() => "");
|
|
13165
|
+
if (res.status === 404 && /model.*not found/i.test(body)) {
|
|
13166
|
+
throw new EmbeddingError(
|
|
13167
|
+
`Embedding model "${model2}" not pulled. Run \`ollama pull ${model2}\` once, then retry.`
|
|
13168
|
+
);
|
|
13169
|
+
}
|
|
13170
|
+
throw new EmbeddingError(`Ollama returned ${res.status}: ${body.slice(0, 200)}`);
|
|
13171
|
+
}
|
|
13172
|
+
const json = await res.json();
|
|
13173
|
+
if (!json.embedding || !Array.isArray(json.embedding)) {
|
|
13174
|
+
throw new EmbeddingError(`Ollama response missing 'embedding' array`);
|
|
13175
|
+
}
|
|
13176
|
+
const out = new Float32Array(json.embedding.length);
|
|
13177
|
+
for (let i = 0; i < json.embedding.length; i++) {
|
|
13178
|
+
const v = json.embedding[i];
|
|
13179
|
+
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
13180
|
+
throw new EmbeddingError(`embedding[${i}] is not a finite number`);
|
|
13181
|
+
}
|
|
13182
|
+
out[i] = v;
|
|
13183
|
+
}
|
|
13184
|
+
return out;
|
|
13185
|
+
}
|
|
13186
|
+
async function embedAll(texts, opts = {}) {
|
|
13187
|
+
const out = new Array(texts.length).fill(null);
|
|
13188
|
+
for (let i = 0; i < texts.length; i++) {
|
|
13189
|
+
if (opts.signal?.aborted) {
|
|
13190
|
+
throw new EmbeddingError("embedding aborted");
|
|
13191
|
+
}
|
|
13192
|
+
const text = texts[i];
|
|
13193
|
+
if (text === void 0) continue;
|
|
13194
|
+
try {
|
|
13195
|
+
out[i] = await embed(text, opts);
|
|
13196
|
+
} catch (err) {
|
|
13197
|
+
opts.onError?.(i, err);
|
|
13198
|
+
}
|
|
13199
|
+
opts.onProgress?.(i + 1, texts.length);
|
|
13200
|
+
}
|
|
13201
|
+
return out;
|
|
13202
|
+
}
|
|
13203
|
+
async function probeOllama(opts = {}) {
|
|
13204
|
+
const baseUrl = opts.baseUrl ?? process.env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL;
|
|
13205
|
+
try {
|
|
13206
|
+
const res = await fetch(`${baseUrl}/api/tags`, { signal: opts.signal });
|
|
13207
|
+
if (!res.ok) return { ok: false, error: `Ollama returned ${res.status}` };
|
|
13208
|
+
const json = await res.json();
|
|
13209
|
+
const models2 = (json.models ?? []).map((m) => m.name).filter((n) => typeof n === "string");
|
|
13210
|
+
return { ok: true, models: models2 };
|
|
13211
|
+
} catch (err) {
|
|
13212
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13213
|
+
return { ok: false, error: msg };
|
|
13214
|
+
}
|
|
13215
|
+
}
|
|
13216
|
+
|
|
13217
|
+
// src/index/semantic/i18n.ts
|
|
13218
|
+
var cachedLocale = null;
|
|
13219
|
+
function detectLocale() {
|
|
13220
|
+
if (cachedLocale) return cachedLocale;
|
|
13221
|
+
const override = (process.env.REASONIX_LANG ?? "").toLowerCase();
|
|
13222
|
+
if (override === "zh" || override === "en") {
|
|
13223
|
+
cachedLocale = override;
|
|
13224
|
+
return cachedLocale;
|
|
13225
|
+
}
|
|
13226
|
+
const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LC_MESSAGES ?? "";
|
|
13227
|
+
if (/^zh[-_]/i.test(env)) {
|
|
13228
|
+
cachedLocale = "zh";
|
|
13229
|
+
return "zh";
|
|
13230
|
+
}
|
|
13231
|
+
try {
|
|
13232
|
+
const sys = new Intl.DateTimeFormat().resolvedOptions().locale ?? "";
|
|
13233
|
+
if (/^zh[-_]/i.test(sys)) {
|
|
13234
|
+
cachedLocale = "zh";
|
|
13235
|
+
return "zh";
|
|
13236
|
+
}
|
|
13237
|
+
} catch {
|
|
13238
|
+
}
|
|
13239
|
+
cachedLocale = "en";
|
|
13240
|
+
return "en";
|
|
13241
|
+
}
|
|
13242
|
+
function t(key, vars = {}) {
|
|
13243
|
+
const loc = detectLocale();
|
|
13244
|
+
const dict = loc === "zh" ? ZH : EN;
|
|
13245
|
+
const tpl = dict[key] ?? EN[key];
|
|
13246
|
+
return tpl.replace(/\{(\w+)\}/g, (_m, name) => {
|
|
13247
|
+
const v = vars[name];
|
|
13248
|
+
return v === void 0 ? `{${name}}` : String(v);
|
|
13249
|
+
});
|
|
13250
|
+
}
|
|
13251
|
+
var EN = {
|
|
13252
|
+
// ── preflight ─────────────────────────────────────────────────────
|
|
13253
|
+
ollamaNotFound: "\u2717 `ollama` not found on PATH.\n Install from https://ollama.com (one-time, ~150 MB), then retry.\n",
|
|
13254
|
+
daemonNotReachableHint: "\u2717 Ollama daemon not reachable. Run `ollama serve` and retry, or pass --yes to start it automatically.\n",
|
|
13255
|
+
daemonStartConfirm: "Ollama daemon isn't running. Start `ollama serve` now?",
|
|
13256
|
+
daemonAbortStart: "\u2717 aborted \u2014 start `ollama serve` yourself and retry.\n",
|
|
13257
|
+
daemonStarting: "\u25B8 starting `ollama serve`\u2026\n",
|
|
13258
|
+
daemonStartTimeout: "\u2717 daemon didn't come up within 15s. Try `ollama serve` in a separate terminal and retry.\n",
|
|
13259
|
+
daemonReady: "\u2713 daemon up{pid}\n",
|
|
13260
|
+
modelNotPulledHint: '\u2717 embedding model "{model}" not pulled. Run `ollama pull {model}` and retry, or pass --yes to pull it automatically.\n',
|
|
13261
|
+
modelPullConfirm: `Embedding model "{model}" isn't pulled yet. Pull it now? (~274 MB for nomic-embed-text)`,
|
|
13262
|
+
modelAbortPull: "\u2717 aborted \u2014 pull the model yourself and retry.\n",
|
|
13263
|
+
modelPulling: "\u25B8 pulling {model}\u2026\n",
|
|
13264
|
+
modelPullFailed: "\u2717 `ollama pull {model}` failed (exit {code}).\n",
|
|
13265
|
+
modelPulled: "\u2713 {model} pulled\n",
|
|
13266
|
+
// ── progress ─────────────────────────────────────────────────────
|
|
13267
|
+
// The TTY-mode progress writer paints `<spinner> <status> <elapsed>s`
|
|
13268
|
+
// every 120ms. The status itself comes from one of these keys based
|
|
13269
|
+
// on the current phase. {files}, {done}, {total}, {pct} are
|
|
13270
|
+
// substituted by the writer.
|
|
13271
|
+
progressStarting: "starting\u2026",
|
|
13272
|
+
progressScan: "scanning project \xB7 {files} files",
|
|
13273
|
+
progressEmbed: "embedding {done}/{total} chunks \xB7 {pct}%",
|
|
13274
|
+
progressEmbedHeartbeat: " {done}/{total}\n",
|
|
13275
|
+
progressScanLine: "scanning files\u2026\n",
|
|
13276
|
+
progressEmbedLine: "embedding {total} chunks across {files} files\u2026\n",
|
|
13277
|
+
// Final result line after a successful build.
|
|
13278
|
+
indexSuccess: "\u2713 indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed) in {seconds}s\n",
|
|
13279
|
+
indexSuccessWithSkips: "\u2713 indexed {scanned} files ({changed} changed, {added} new chunks, {removed} stale removed, {skipped} skipped due to embed errors) in {seconds}s\n",
|
|
13280
|
+
indexNothingToDo: " (nothing to do \u2014 re-run with --rebuild to force a full rebuild)\n",
|
|
13281
|
+
indexFailed: "\u2717 index failed: {msg}\n",
|
|
13282
|
+
// ── /semantic slash ──────────────────────────────────────────────
|
|
13283
|
+
slashHeader: "semantic_search status",
|
|
13284
|
+
slashEnabled: "\u2713 enabled \u2014 index built, tool registered.",
|
|
13285
|
+
slashEnabledDetail: " index size: {chunks} chunks across {files} files",
|
|
13286
|
+
slashEnabledHowto: " the model will call semantic_search automatically when it fits.",
|
|
13287
|
+
slashIndexMissing: "\u2717 no index built yet for this project.",
|
|
13288
|
+
slashHowToBuild: " to enable, exit Reasonix and run in your shell:\n reasonix index",
|
|
13289
|
+
slashOllamaMissing: " prerequisite: install Ollama from https://ollama.com",
|
|
13290
|
+
slashDaemonDown: " Ollama is installed but the daemon isn't running. start it with: ollama serve",
|
|
13291
|
+
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."
|
|
13292
|
+
};
|
|
13293
|
+
var ZH = {
|
|
13294
|
+
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",
|
|
13295
|
+
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",
|
|
13296
|
+
daemonStartConfirm: "Ollama \u5B88\u62A4\u8FDB\u7A0B\u672A\u8FD0\u884C\u3002\u73B0\u5728\u542F\u52A8 `ollama serve` \u5417\uFF1F",
|
|
13297
|
+
daemonAbortStart: "\u2717 \u5DF2\u53D6\u6D88\u2014\u2014\u8BF7\u81EA\u884C\u8FD0\u884C `ollama serve` \u540E\u91CD\u8BD5\u3002\n",
|
|
13298
|
+
daemonStarting: "\u25B8 \u6B63\u5728\u542F\u52A8 `ollama serve`\u2026\n",
|
|
13299
|
+
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",
|
|
13300
|
+
daemonReady: "\u2713 \u5B88\u62A4\u8FDB\u7A0B\u5DF2\u542F\u52A8{pid}\n",
|
|
13301
|
+
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',
|
|
13302
|
+
modelPullConfirm: '\u5D4C\u5165\u6A21\u578B "{model}" \u8FD8\u672A\u4E0B\u8F7D\u3002\u73B0\u5728\u4E0B\u8F7D\u5417\uFF1F\uFF08nomic-embed-text \u7EA6 274 MB\uFF09',
|
|
13303
|
+
modelAbortPull: "\u2717 \u5DF2\u53D6\u6D88\u2014\u2014\u8BF7\u81EA\u884C\u4E0B\u8F7D\u6A21\u578B\u540E\u91CD\u8BD5\u3002\n",
|
|
13304
|
+
modelPulling: "\u25B8 \u6B63\u5728\u4E0B\u8F7D {model}\u2026\n",
|
|
13305
|
+
modelPullFailed: "\u2717 `ollama pull {model}` \u5931\u8D25\uFF08\u9000\u51FA\u7801 {code}\uFF09\u3002\n",
|
|
13306
|
+
modelPulled: "\u2713 {model} \u4E0B\u8F7D\u5B8C\u6210\n",
|
|
13307
|
+
progressStarting: "\u6B63\u5728\u542F\u52A8\u2026",
|
|
13308
|
+
progressScan: "\u626B\u63CF\u9879\u76EE \xB7 \u5DF2\u626B\u63CF {files} \u4E2A\u6587\u4EF6",
|
|
13309
|
+
progressEmbed: "\u6B63\u5728\u5411\u91CF\u5316 {done}/{total} \u4E2A\u7247\u6BB5 \xB7 {pct}%",
|
|
13310
|
+
progressEmbedHeartbeat: " {done}/{total}\n",
|
|
13311
|
+
progressScanLine: "\u6B63\u5728\u626B\u63CF\u6587\u4EF6\u2026\n",
|
|
13312
|
+
progressEmbedLine: "\u6B63\u5728\u5411\u91CF\u5316 {total} \u4E2A\u7247\u6BB5\uFF08\u6D89\u53CA {files} \u4E2A\u6587\u4EF6\uFF09\u2026\n",
|
|
13313
|
+
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",
|
|
13314
|
+
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",
|
|
13315
|
+
indexNothingToDo: " \uFF08\u6CA1\u6709\u53D8\u5316\u2014\u2014\u52A0 --rebuild \u5F3A\u5236\u91CD\u5EFA\uFF09\n",
|
|
13316
|
+
indexFailed: "\u2717 \u5EFA\u7ACB\u7D22\u5F15\u5931\u8D25\uFF1A{msg}\n",
|
|
13317
|
+
slashHeader: "semantic_search \u72B6\u6001",
|
|
13318
|
+
slashEnabled: "\u2713 \u5DF2\u542F\u7528\u2014\u2014\u7D22\u5F15\u5DF2\u5EFA\u597D\uFF0C\u5DE5\u5177\u5DF2\u6CE8\u518C\u3002",
|
|
13319
|
+
slashEnabledDetail: " \u7D22\u5F15\u89C4\u6A21\uFF1A{chunks} \u4E2A\u7247\u6BB5\uFF0C{files} \u4E2A\u6587\u4EF6",
|
|
13320
|
+
slashEnabledHowto: " \u6A21\u578B\u5728\u5408\u9002\u7684\u65F6\u5019\u4F1A\u81EA\u52A8\u8C03\u7528 semantic_search\u3002",
|
|
13321
|
+
slashIndexMissing: "\u2717 \u5F53\u524D\u9879\u76EE\u8FD8\u6CA1\u6709\u7D22\u5F15\u3002",
|
|
13322
|
+
slashHowToBuild: " \u542F\u7528\u65B9\u5F0F\uFF1A\u9000\u51FA Reasonix\uFF0C\u5728\u7EC8\u7AEF\u8FD0\u884C\uFF1A\n reasonix index",
|
|
13323
|
+
slashOllamaMissing: " \u524D\u7F6E\u4F9D\u8D56\uFF1A\u4ECE https://ollama.com \u5B89\u88C5 Ollama",
|
|
13324
|
+
slashDaemonDown: " \u5DF2\u88C5 Ollama \u4F46\u5B88\u62A4\u8FDB\u7A0B\u672A\u542F\u52A8\uFF0C\u8BF7\u8FD0\u884C\uFF1Aollama serve",
|
|
13325
|
+
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'
|
|
13326
|
+
};
|
|
13327
|
+
|
|
13328
|
+
// src/index/semantic/ollama-launcher.ts
|
|
13329
|
+
import { spawn as spawn5, spawnSync as spawnSync2 } from "child_process";
|
|
13330
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
13331
|
+
function findOllamaBinary() {
|
|
13332
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
13333
|
+
const out = spawnSync2(cmd, ["ollama"], { encoding: "utf8" });
|
|
13334
|
+
if (out.status !== 0) return null;
|
|
13335
|
+
const first = out.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
13336
|
+
return first ? first.trim() : null;
|
|
13337
|
+
}
|
|
13338
|
+
async function checkOllamaStatus(modelName, baseUrl) {
|
|
13339
|
+
const binary = findOllamaBinary();
|
|
13340
|
+
const probe = await probeOllama({ baseUrl });
|
|
13341
|
+
const installedModels = probe.ok ? probe.models : [];
|
|
13342
|
+
const wanted = modelName.includes(":") ? modelName : `${modelName}:latest`;
|
|
13343
|
+
const modelPulled = installedModels.some((m) => m === modelName || m === wanted);
|
|
13344
|
+
return {
|
|
13345
|
+
binaryFound: binary !== null,
|
|
13346
|
+
daemonRunning: probe.ok,
|
|
13347
|
+
modelPulled,
|
|
13348
|
+
modelName,
|
|
13349
|
+
installedModels
|
|
13350
|
+
};
|
|
13351
|
+
}
|
|
13352
|
+
async function startOllamaDaemon(opts = {}) {
|
|
13353
|
+
const timeoutMs = opts.timeoutMs ?? 15e3;
|
|
13354
|
+
const child = spawn5("ollama", ["serve"], {
|
|
13355
|
+
detached: true,
|
|
13356
|
+
stdio: "ignore",
|
|
13357
|
+
windowsHide: true
|
|
13358
|
+
});
|
|
13359
|
+
child.unref();
|
|
13360
|
+
const pid = child.pid ?? null;
|
|
13361
|
+
const start = Date.now();
|
|
13362
|
+
while (Date.now() - start < timeoutMs) {
|
|
13363
|
+
if (opts.signal?.aborted) return { ready: false, pid };
|
|
13364
|
+
const probe = await probeOllama({ baseUrl: opts.baseUrl, signal: opts.signal });
|
|
13365
|
+
if (probe.ok) return { ready: true, pid };
|
|
13366
|
+
await sleep2(500);
|
|
13367
|
+
}
|
|
13368
|
+
return { ready: false, pid };
|
|
13369
|
+
}
|
|
13370
|
+
async function pullOllamaModel(modelName, opts = {}) {
|
|
13371
|
+
return new Promise((resolve12) => {
|
|
13372
|
+
const child = spawn5("ollama", ["pull", modelName], {
|
|
13373
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13374
|
+
windowsHide: true
|
|
13375
|
+
});
|
|
13376
|
+
if (opts.signal) {
|
|
13377
|
+
const onAbort = () => child.kill();
|
|
13378
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
13379
|
+
child.once("exit", () => opts.signal?.removeEventListener("abort", onAbort));
|
|
13380
|
+
}
|
|
13381
|
+
streamLines(child.stdout, (l) => opts.onLine?.(l, "stdout"));
|
|
13382
|
+
streamLines(child.stderr, (l) => opts.onLine?.(l, "stderr"));
|
|
13383
|
+
child.once("exit", (code) => resolve12(code ?? -1));
|
|
13384
|
+
child.once("error", () => resolve12(-1));
|
|
13385
|
+
});
|
|
13386
|
+
}
|
|
13387
|
+
function streamLines(stream, cb) {
|
|
13388
|
+
if (!stream) return;
|
|
13389
|
+
let buf = "";
|
|
13390
|
+
stream.setEncoding("utf8");
|
|
13391
|
+
stream.on("data", (chunk) => {
|
|
13392
|
+
buf += chunk;
|
|
13393
|
+
let nl = buf.indexOf("\n");
|
|
13394
|
+
while (nl !== -1) {
|
|
13395
|
+
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
13396
|
+
buf = buf.slice(nl + 1);
|
|
13397
|
+
if (line.length > 0) cb(line);
|
|
13398
|
+
nl = buf.indexOf("\n");
|
|
13399
|
+
}
|
|
13400
|
+
});
|
|
13401
|
+
stream.on("end", () => {
|
|
13402
|
+
if (buf.length > 0) cb(buf.replace(/\r$/, ""));
|
|
13403
|
+
});
|
|
13404
|
+
}
|
|
13405
|
+
|
|
13406
|
+
// src/cli/ui/slash/handlers/semantic.ts
|
|
13407
|
+
var semantic = (_args, _loop, ctx) => {
|
|
13408
|
+
const root = ctx.codeRoot;
|
|
13409
|
+
if (!root) {
|
|
13410
|
+
return {
|
|
13411
|
+
info: "/semantic is only available inside `reasonix code` (needs a project root)."
|
|
13412
|
+
};
|
|
13413
|
+
}
|
|
13414
|
+
void (async () => {
|
|
13415
|
+
const status2 = await renderSemanticStatus(root);
|
|
13416
|
+
ctx.postInfo?.(status2);
|
|
13417
|
+
})();
|
|
13418
|
+
return { info: "\u25B8 checking semantic_search status\u2026" };
|
|
13419
|
+
};
|
|
13420
|
+
async function renderSemanticStatus(rootDir) {
|
|
13421
|
+
const lines = [t("slashHeader"), ""];
|
|
13422
|
+
const indexExists2 = await indexFileExists(rootDir);
|
|
13423
|
+
if (indexExists2) {
|
|
13424
|
+
const meta = await readIndexMeta(rootDir);
|
|
13425
|
+
lines.push(t("slashEnabled"));
|
|
13426
|
+
if (meta) {
|
|
13427
|
+
lines.push(
|
|
13428
|
+
t("slashEnabledDetail", {
|
|
13429
|
+
chunks: meta.chunks,
|
|
13430
|
+
files: meta.files
|
|
13431
|
+
})
|
|
13432
|
+
);
|
|
13433
|
+
}
|
|
13434
|
+
lines.push(t("slashEnabledHowto"));
|
|
13435
|
+
return lines.join("\n");
|
|
13436
|
+
}
|
|
13437
|
+
lines.push(t("slashIndexMissing"));
|
|
13438
|
+
lines.push(t("slashIndexInfo"));
|
|
13439
|
+
lines.push("");
|
|
13440
|
+
if (findOllamaBinary() === null) {
|
|
13441
|
+
lines.push(t("slashOllamaMissing"));
|
|
13442
|
+
} else {
|
|
13443
|
+
const probe = await probeOllama();
|
|
13444
|
+
if (!probe.ok) lines.push(t("slashDaemonDown"));
|
|
13445
|
+
}
|
|
13446
|
+
lines.push(t("slashHowToBuild"));
|
|
13447
|
+
return lines.join("\n");
|
|
13448
|
+
}
|
|
13449
|
+
async function indexFileExists(rootDir) {
|
|
13450
|
+
try {
|
|
13451
|
+
await fs2.access(path.join(rootDir, ".reasonix", "semantic", "index.meta.json"));
|
|
13452
|
+
return true;
|
|
13453
|
+
} catch {
|
|
13454
|
+
return false;
|
|
13455
|
+
}
|
|
13456
|
+
}
|
|
13457
|
+
async function readIndexMeta(rootDir) {
|
|
13458
|
+
const dataPath = path.join(rootDir, ".reasonix", "semantic", "index.jsonl");
|
|
13459
|
+
try {
|
|
13460
|
+
const stat = await fs2.stat(dataPath);
|
|
13461
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
13462
|
+
return { chunks: Math.round(stat.size / 500), files: 0 };
|
|
13463
|
+
}
|
|
13464
|
+
const raw = await fs2.readFile(dataPath, "utf8");
|
|
13465
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
13466
|
+
let chunks = 0;
|
|
13467
|
+
for (const line of raw.split("\n")) {
|
|
13468
|
+
if (line.length === 0) continue;
|
|
13469
|
+
chunks++;
|
|
13470
|
+
try {
|
|
13471
|
+
const parsed = JSON.parse(line);
|
|
13472
|
+
if (parsed.p) seenPaths.add(parsed.p);
|
|
13473
|
+
} catch {
|
|
13474
|
+
}
|
|
13475
|
+
}
|
|
13476
|
+
return { chunks, files: seenPaths.size };
|
|
13477
|
+
} catch {
|
|
13478
|
+
return null;
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13481
|
+
var handlers10 = {
|
|
13482
|
+
semantic
|
|
13483
|
+
};
|
|
13484
|
+
|
|
12904
13485
|
// src/cli/ui/slash/handlers/sessions.ts
|
|
12905
13486
|
var sessions = (_args, loop2) => {
|
|
12906
13487
|
const items = listSessions();
|
|
@@ -12932,7 +13513,7 @@ var forget = (_args, loop2) => {
|
|
|
12932
13513
|
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
13514
|
};
|
|
12934
13515
|
};
|
|
12935
|
-
var
|
|
13516
|
+
var handlers11 = {
|
|
12936
13517
|
sessions,
|
|
12937
13518
|
forget
|
|
12938
13519
|
};
|
|
@@ -13008,7 +13589,7 @@ ${found.body}${argsLine}`;
|
|
|
13008
13589
|
resubmit: payload
|
|
13009
13590
|
};
|
|
13010
13591
|
};
|
|
13011
|
-
var
|
|
13592
|
+
var handlers12 = {
|
|
13012
13593
|
skill,
|
|
13013
13594
|
skills: skill
|
|
13014
13595
|
};
|
|
@@ -13025,7 +13606,8 @@ var HANDLERS = {
|
|
|
13025
13606
|
...handlers8,
|
|
13026
13607
|
...handlers9,
|
|
13027
13608
|
...handlers10,
|
|
13028
|
-
...handlers11
|
|
13609
|
+
...handlers11,
|
|
13610
|
+
...handlers12
|
|
13029
13611
|
};
|
|
13030
13612
|
function handleSlash(cmd, args, loop2, ctx = {}) {
|
|
13031
13613
|
const h = HANDLERS[cmd];
|
|
@@ -13039,6 +13621,7 @@ function useCompletionPickers({
|
|
|
13039
13621
|
input,
|
|
13040
13622
|
setInput,
|
|
13041
13623
|
codeMode,
|
|
13624
|
+
rootDir,
|
|
13042
13625
|
models: models2,
|
|
13043
13626
|
mcpServers
|
|
13044
13627
|
}) {
|
|
@@ -13056,13 +13639,13 @@ function useCompletionPickers({
|
|
|
13056
13639
|
}, [slashMatches]);
|
|
13057
13640
|
const [atSelected, setAtSelected] = useState6(0);
|
|
13058
13641
|
const atFiles = useMemo2(() => {
|
|
13059
|
-
if (!codeMode
|
|
13642
|
+
if (!codeMode) return [];
|
|
13060
13643
|
try {
|
|
13061
|
-
return listFilesWithStatsSync(
|
|
13644
|
+
return listFilesWithStatsSync(rootDir, { maxResults: 500 });
|
|
13062
13645
|
} catch {
|
|
13063
13646
|
return [];
|
|
13064
13647
|
}
|
|
13065
|
-
}, [codeMode
|
|
13648
|
+
}, [codeMode, rootDir]);
|
|
13066
13649
|
const recentFilesRef = useRef3([]);
|
|
13067
13650
|
const recordRecentFile = useCallback((p) => {
|
|
13068
13651
|
const list = recentFilesRef.current;
|
|
@@ -13072,10 +13655,10 @@ function useCompletionPickers({
|
|
|
13072
13655
|
if (list.length > 20) list.length = 20;
|
|
13073
13656
|
}, []);
|
|
13074
13657
|
const atPicker = useMemo2(() => {
|
|
13075
|
-
if (!codeMode
|
|
13658
|
+
if (!codeMode) return null;
|
|
13076
13659
|
if (slashMatches !== null) return null;
|
|
13077
13660
|
return detectAtPicker(input);
|
|
13078
|
-
}, [codeMode
|
|
13661
|
+
}, [codeMode, input, slashMatches]);
|
|
13079
13662
|
const atMatches = useMemo2(() => {
|
|
13080
13663
|
if (!atPicker) return null;
|
|
13081
13664
|
return rankPickerCandidates(atFiles, atPicker.query, {
|
|
@@ -13339,16 +13922,16 @@ function useEditHistory(codeMode) {
|
|
|
13339
13922
|
const status2 = entryStatus(entry);
|
|
13340
13923
|
const header2 = `\u25B8 edit #${entry.id} \xB7 ${when} \xB7 ${entry.source} \xB7 ${status2} \xB7 ${files.length} file(s)`;
|
|
13341
13924
|
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 ===
|
|
13925
|
+
const fileLines = files.map((path5) => {
|
|
13926
|
+
const fileBlocks = entry.blocks.filter((b) => b.path === path5);
|
|
13344
13927
|
let removed = 0;
|
|
13345
13928
|
let added = 0;
|
|
13346
13929
|
for (const b of fileBlocks) {
|
|
13347
13930
|
removed += countLines3(b.search);
|
|
13348
13931
|
added += countLines3(b.replace);
|
|
13349
13932
|
}
|
|
13350
|
-
const state = entry.undoneFiles.has(
|
|
13351
|
-
return ` ${state.padEnd(7)} -${String(removed).padStart(3)}/+${String(added).padStart(3)} ${
|
|
13933
|
+
const state = entry.undoneFiles.has(path5) ? "UNDONE" : "applied";
|
|
13934
|
+
return ` ${state.padEnd(7)} -${String(removed).padStart(3)}/+${String(added).padStart(3)} ${path5} (${fileBlocks.length} block${fileBlocks.length === 1 ? "" : "s"})`;
|
|
13352
13935
|
});
|
|
13353
13936
|
return [
|
|
13354
13937
|
header2,
|
|
@@ -13515,13 +14098,13 @@ var PLAIN_UI = process.env.REASONIX_UI === "plain";
|
|
|
13515
14098
|
function LoopStatusRow({
|
|
13516
14099
|
loop: loop2
|
|
13517
14100
|
}) {
|
|
13518
|
-
const [, setTick] =
|
|
13519
|
-
|
|
13520
|
-
const id = setInterval(() => setTick((
|
|
14101
|
+
const [, setTick] = React24.useState(0);
|
|
14102
|
+
React24.useEffect(() => {
|
|
14103
|
+
const id = setInterval(() => setTick((t2) => t2 + 1), 1e3);
|
|
13521
14104
|
return () => clearInterval(id);
|
|
13522
14105
|
}, []);
|
|
13523
14106
|
const nextFireMs = Math.max(0, loop2.nextFireAt - Date.now());
|
|
13524
|
-
return /* @__PURE__ */
|
|
14107
|
+
return /* @__PURE__ */ React24.createElement(Box22, null, /* @__PURE__ */ React24.createElement(Text20, { color: "cyan" }, `\u25B8 ${formatLoopStatus(loop2.prompt, nextFireMs, loop2.iter)} \xB7 /loop stop or type to cancel`));
|
|
13525
14108
|
}
|
|
13526
14109
|
function App({
|
|
13527
14110
|
model: model2,
|
|
@@ -13547,19 +14130,19 @@ function App({
|
|
|
13547
14130
|
}, [busy]);
|
|
13548
14131
|
const [ongoingTool, setOngoingTool] = useState10(null);
|
|
13549
14132
|
const [toolProgress, setToolProgress] = useState10(null);
|
|
13550
|
-
const { stdout:
|
|
14133
|
+
const { stdout: stdout3 } = useStdout8();
|
|
13551
14134
|
useEffect6(() => {
|
|
13552
|
-
if (!
|
|
13553
|
-
|
|
13554
|
-
|
|
14135
|
+
if (!stdout3 || !stdout3.isTTY) return;
|
|
14136
|
+
stdout3.write("\x1B[?2004h");
|
|
14137
|
+
stdout3.write("\x1B[>4;2m");
|
|
13555
14138
|
return () => {
|
|
13556
|
-
|
|
13557
|
-
|
|
14139
|
+
stdout3.write("\x1B[?2004l");
|
|
14140
|
+
stdout3.write("\x1B[>4m");
|
|
13558
14141
|
};
|
|
13559
|
-
}, [
|
|
14142
|
+
}, [stdout3]);
|
|
13560
14143
|
const [isResizing, setIsResizing] = useState10(false);
|
|
13561
14144
|
useEffect6(() => {
|
|
13562
|
-
if (!
|
|
14145
|
+
if (!stdout3 || !stdout3.isTTY) return;
|
|
13563
14146
|
let timer = null;
|
|
13564
14147
|
const onResize = () => {
|
|
13565
14148
|
setIsResizing(true);
|
|
@@ -13569,21 +14152,23 @@ function App({
|
|
|
13569
14152
|
timer = null;
|
|
13570
14153
|
}, 400);
|
|
13571
14154
|
};
|
|
13572
|
-
|
|
14155
|
+
stdout3.on("resize", onResize);
|
|
13573
14156
|
return () => {
|
|
13574
|
-
|
|
14157
|
+
stdout3.off("resize", onResize);
|
|
13575
14158
|
if (timer) clearTimeout(timer);
|
|
13576
14159
|
};
|
|
13577
|
-
}, [
|
|
14160
|
+
}, [stdout3]);
|
|
13578
14161
|
const { activity: subagentActivity, sinkRef: subagentSinkRef } = useSubagent({
|
|
13579
14162
|
session,
|
|
13580
14163
|
setHistorical
|
|
13581
14164
|
});
|
|
13582
14165
|
const [statusLine, setStatusLine] = useState10(null);
|
|
14166
|
+
const [currentRootDir, setCurrentRootDir] = useState10(
|
|
14167
|
+
() => codeMode?.rootDir ?? process.cwd()
|
|
14168
|
+
);
|
|
13583
14169
|
const [hookList, setHookList] = useState10(
|
|
13584
14170
|
() => loadHooks({ projectRoot: codeMode?.rootDir })
|
|
13585
14171
|
);
|
|
13586
|
-
const hookCwd = codeMode?.rootDir ?? process.cwd();
|
|
13587
14172
|
const {
|
|
13588
14173
|
undoBanner,
|
|
13589
14174
|
recordEdit,
|
|
@@ -13598,7 +14183,7 @@ function App({
|
|
|
13598
14183
|
const [pendingCount, setPendingCount] = useState10(0);
|
|
13599
14184
|
const syncPendingCount = useCallback4(() => {
|
|
13600
14185
|
setPendingCount(pendingEdits.current.length);
|
|
13601
|
-
setPendingTick((
|
|
14186
|
+
setPendingTick((t2) => t2 + 1);
|
|
13602
14187
|
}, []);
|
|
13603
14188
|
const [editMode, setEditMode] = useState10(() => codeMode ? loadEditMode() : "review");
|
|
13604
14189
|
const editModeRef = useRef6(editMode);
|
|
@@ -13625,6 +14210,7 @@ function App({
|
|
|
13625
14210
|
}, 1200);
|
|
13626
14211
|
}, [editMode]);
|
|
13627
14212
|
const [pendingShell, setPendingShell] = useState10(null);
|
|
14213
|
+
const [pendingWorkspace, setPendingWorkspace] = useState10(null);
|
|
13628
14214
|
const [pendingPlan, setPendingPlan] = useState10(null);
|
|
13629
14215
|
const [stagedInput, setStagedInput] = useState10(null);
|
|
13630
14216
|
const [pendingCheckpoint, setPendingCheckpoint] = useState10(null);
|
|
@@ -13719,6 +14305,9 @@ function App({
|
|
|
13719
14305
|
}
|
|
13720
14306
|
});
|
|
13721
14307
|
}
|
|
14308
|
+
if (tools && !tools.has("change_workspace")) {
|
|
14309
|
+
registerWorkspaceTool(tools);
|
|
14310
|
+
}
|
|
13722
14311
|
const prefix = new ImmutablePrefix({
|
|
13723
14312
|
system,
|
|
13724
14313
|
toolSpecs: tools?.specs()
|
|
@@ -13732,7 +14321,7 @@ function App({
|
|
|
13732
14321
|
branch: branch2,
|
|
13733
14322
|
session,
|
|
13734
14323
|
hooks: hookList,
|
|
13735
|
-
hookCwd,
|
|
14324
|
+
hookCwd: currentRootDir,
|
|
13736
14325
|
// Restore the user's last-chosen effort cap. Without this a
|
|
13737
14326
|
// `/effort high` silently reverted to `max` on relaunch — the
|
|
13738
14327
|
// loop's constructor default wins over persisted state.
|
|
@@ -13744,6 +14333,49 @@ function App({
|
|
|
13744
14333
|
useEffect6(() => {
|
|
13745
14334
|
loop2.hooks = hookList;
|
|
13746
14335
|
}, [loop2, hookList]);
|
|
14336
|
+
const applyCwdChange = useCallback4(
|
|
14337
|
+
(newRoot) => {
|
|
14338
|
+
setCurrentRootDir(newRoot);
|
|
14339
|
+
const fresh = loadHooks({ projectRoot: codeMode ? newRoot : void 0 });
|
|
14340
|
+
setHookList(fresh);
|
|
14341
|
+
const codeRebound = codeMode?.reregisterTools !== void 0;
|
|
14342
|
+
if (codeMode?.reregisterTools) {
|
|
14343
|
+
codeMode.reregisterTools(newRoot);
|
|
14344
|
+
}
|
|
14345
|
+
if (tools) {
|
|
14346
|
+
registerSkillTools(tools, {
|
|
14347
|
+
projectRoot: codeMode ? newRoot : void 0,
|
|
14348
|
+
subagentRunner: async (skill2, task) => {
|
|
14349
|
+
const result = await spawnSubagent({
|
|
14350
|
+
client: loop2.client,
|
|
14351
|
+
parentRegistry: tools,
|
|
14352
|
+
system: skill2.body,
|
|
14353
|
+
task,
|
|
14354
|
+
model: skill2.model,
|
|
14355
|
+
sink: subagentSinkRef.current,
|
|
14356
|
+
skillName: skill2.name
|
|
14357
|
+
});
|
|
14358
|
+
return formatSubagentResult(result);
|
|
14359
|
+
}
|
|
14360
|
+
});
|
|
14361
|
+
}
|
|
14362
|
+
const lines = [`\u25B8 cwd \u2192 ${newRoot}`, ` hooks reloaded (${fresh.length} active)`];
|
|
14363
|
+
if (codeMode) {
|
|
14364
|
+
lines.push(
|
|
14365
|
+
codeRebound ? " filesystem / shell / memory tools rebound to new root" : " warning: reregisterTools callback missing \u2014 tool sandbox unchanged"
|
|
14366
|
+
);
|
|
14367
|
+
lines.push(
|
|
14368
|
+
" note: system prompt context (gitignore, REASONIX.md stack) was",
|
|
14369
|
+
" baked at session start and still references the original root."
|
|
14370
|
+
);
|
|
14371
|
+
}
|
|
14372
|
+
return lines.join("\n");
|
|
14373
|
+
},
|
|
14374
|
+
[codeMode, loop2, tools, subagentSinkRef]
|
|
14375
|
+
);
|
|
14376
|
+
useEffect6(() => {
|
|
14377
|
+
loop2.hookCwd = currentRootDir;
|
|
14378
|
+
}, [loop2, currentRootDir]);
|
|
13747
14379
|
const {
|
|
13748
14380
|
balance,
|
|
13749
14381
|
models: models2,
|
|
@@ -13768,7 +14400,14 @@ function App({
|
|
|
13768
14400
|
slashArgSelected,
|
|
13769
14401
|
setSlashArgSelected,
|
|
13770
14402
|
pickSlashArg
|
|
13771
|
-
} = useCompletionPickers({
|
|
14403
|
+
} = useCompletionPickers({
|
|
14404
|
+
input,
|
|
14405
|
+
setInput,
|
|
14406
|
+
codeMode,
|
|
14407
|
+
rootDir: currentRootDir,
|
|
14408
|
+
models: models2,
|
|
14409
|
+
mcpServers
|
|
14410
|
+
});
|
|
13772
14411
|
useEffect6(() => {
|
|
13773
14412
|
if (!progressSink) return;
|
|
13774
14413
|
progressSink.current = (info) => {
|
|
@@ -13888,11 +14527,11 @@ function App({
|
|
|
13888
14527
|
if (key.escape && busy) {
|
|
13889
14528
|
if (abortedThisTurn.current) return;
|
|
13890
14529
|
abortedThisTurn.current = true;
|
|
13891
|
-
const
|
|
13892
|
-
if (
|
|
14530
|
+
const resolve12 = editReviewResolveRef.current;
|
|
14531
|
+
if (resolve12) {
|
|
13893
14532
|
editReviewResolveRef.current = null;
|
|
13894
14533
|
setPendingEditReview(null);
|
|
13895
|
-
|
|
14534
|
+
resolve12("reject");
|
|
13896
14535
|
}
|
|
13897
14536
|
if (activeLoopRef.current) stopLoop();
|
|
13898
14537
|
loop2.abort();
|
|
@@ -13915,7 +14554,7 @@ function App({
|
|
|
13915
14554
|
]);
|
|
13916
14555
|
return;
|
|
13917
14556
|
}
|
|
13918
|
-
if (codeMode && key.shift && key.tab && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
|
|
14557
|
+
if (codeMode && key.shift && key.tab && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision) {
|
|
13919
14558
|
setEditMode((m) => {
|
|
13920
14559
|
const next = m === "review" ? "auto" : m === "auto" ? "yolo" : "review";
|
|
13921
14560
|
const message = next === "yolo" ? "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run. /undo still rolls back edits. Use carefully." : next === "auto" ? "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo. Shell commands still ask." : "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)";
|
|
@@ -13927,7 +14566,7 @@ function App({
|
|
|
13927
14566
|
});
|
|
13928
14567
|
return;
|
|
13929
14568
|
}
|
|
13930
|
-
if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision && // Fire when EITHER the banner is up OR there's any non-undone
|
|
14569
|
+
if (codeMode && input.length === 0 && (chKey === "u" || chKey === "U") && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !walkthroughActive && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision && // Fire when EITHER the banner is up OR there's any non-undone
|
|
13931
14570
|
// history entry — the keybind is useful long after the 5-second
|
|
13932
14571
|
// banner expires, which users rightly want.
|
|
13933
14572
|
(undoBanner || hasUndoable())) {
|
|
@@ -14016,11 +14655,11 @@ function App({
|
|
|
14016
14655
|
block = { path: relPath, search, replace, offset: 0 };
|
|
14017
14656
|
} else {
|
|
14018
14657
|
const content = typeof args.content === "string" ? args.content : "";
|
|
14019
|
-
block = toWholeFileEditBlock(relPath, content,
|
|
14658
|
+
block = toWholeFileEditBlock(relPath, content, currentRootDir);
|
|
14020
14659
|
}
|
|
14021
14660
|
const applyNow = () => {
|
|
14022
|
-
const snaps = snapshotBeforeEdits([block],
|
|
14023
|
-
const results = applyEditBlocks([block],
|
|
14661
|
+
const snaps = snapshotBeforeEdits([block], currentRootDir);
|
|
14662
|
+
const results = applyEditBlocks([block], currentRootDir);
|
|
14024
14663
|
const good = results.some((r) => r.status === "applied" || r.status === "created");
|
|
14025
14664
|
if (good) {
|
|
14026
14665
|
recordEdit("auto", [block], results, snaps);
|
|
@@ -14089,8 +14728,8 @@ function App({
|
|
|
14089
14728
|
if (selected.length === 0) {
|
|
14090
14729
|
return "\u25B8 no edits matched those indices \u2014 nothing applied. Use /apply with no args to commit them all.";
|
|
14091
14730
|
}
|
|
14092
|
-
const snaps = snapshotBeforeEdits(selected,
|
|
14093
|
-
const results = applyEditBlocks(selected,
|
|
14731
|
+
const snaps = snapshotBeforeEdits(selected, currentRootDir);
|
|
14732
|
+
const results = applyEditBlocks(selected, currentRootDir);
|
|
14094
14733
|
const anyApplied = results.some((r) => r.status === "applied" || r.status === "created");
|
|
14095
14734
|
if (anyApplied) recordEdit("review-apply", selected, results, snaps);
|
|
14096
14735
|
pendingEdits.current = remaining;
|
|
@@ -14101,7 +14740,7 @@ function App({
|
|
|
14101
14740
|
\u25B8 ${remaining.length} edit block(s) still pending \u2014 /apply or /discard to clear them.` : "";
|
|
14102
14741
|
return formatEditResults(results) + tail;
|
|
14103
14742
|
},
|
|
14104
|
-
[codeMode, session, syncPendingCount, recordEdit]
|
|
14743
|
+
[codeMode, currentRootDir, session, syncPendingCount, recordEdit]
|
|
14105
14744
|
);
|
|
14106
14745
|
const codeDiscard = useCallback4(
|
|
14107
14746
|
(indices) => {
|
|
@@ -14266,7 +14905,7 @@ function App({
|
|
|
14266
14905
|
const hashParse = detectHashMemory(text);
|
|
14267
14906
|
if (hashParse?.kind === "memory" || hashParse?.kind === "memory-global") {
|
|
14268
14907
|
const isGlobal = hashParse.kind === "memory-global";
|
|
14269
|
-
const memRoot =
|
|
14908
|
+
const memRoot = currentRootDir;
|
|
14270
14909
|
promptHistory.current.push(text);
|
|
14271
14910
|
try {
|
|
14272
14911
|
const result = isGlobal ? appendGlobalMemory(hashParse.note) : appendProjectMemory(memRoot, hashParse.note);
|
|
@@ -14297,7 +14936,7 @@ function App({
|
|
|
14297
14936
|
}
|
|
14298
14937
|
const bangCmd = detectBangCommand(text);
|
|
14299
14938
|
if (bangCmd !== null) {
|
|
14300
|
-
const bangRoot =
|
|
14939
|
+
const bangRoot = currentRootDir;
|
|
14301
14940
|
promptHistory.current.push(text);
|
|
14302
14941
|
setHistorical((prev) => [
|
|
14303
14942
|
...prev,
|
|
@@ -14360,10 +14999,10 @@ function App({
|
|
|
14360
14999
|
codeDiscard: codeMode ? codeDiscard : void 0,
|
|
14361
15000
|
codeHistory: codeMode ? codeHistory : void 0,
|
|
14362
15001
|
codeShowEdit: codeMode ? codeShowEdit : void 0,
|
|
14363
|
-
codeRoot: codeMode
|
|
15002
|
+
codeRoot: codeMode ? currentRootDir : void 0,
|
|
14364
15003
|
pendingEditCount: codeMode ? pendingEdits.current.length : void 0,
|
|
14365
15004
|
toolHistory: () => toolHistoryRef.current,
|
|
14366
|
-
memoryRoot:
|
|
15005
|
+
memoryRoot: currentRootDir,
|
|
14367
15006
|
planMode,
|
|
14368
15007
|
setPlanMode: codeMode ? togglePlanMode : void 0,
|
|
14369
15008
|
clearPendingPlan: codeMode ? clearPendingPlan : void 0,
|
|
@@ -14387,10 +15026,11 @@ function App({
|
|
|
14387
15026
|
{ id: `sys-late-${Date.now()}-${Math.random()}`, role: "info", text: text2 }
|
|
14388
15027
|
]),
|
|
14389
15028
|
reloadHooks: () => {
|
|
14390
|
-
const fresh = loadHooks({ projectRoot: codeMode
|
|
15029
|
+
const fresh = loadHooks({ projectRoot: codeMode ? currentRootDir : void 0 });
|
|
14391
15030
|
setHookList(fresh);
|
|
14392
15031
|
return fresh.length;
|
|
14393
15032
|
},
|
|
15033
|
+
setCwd: (newRoot) => applyCwdChange(newRoot),
|
|
14394
15034
|
latestVersion,
|
|
14395
15035
|
refreshLatestVersion,
|
|
14396
15036
|
models: models2,
|
|
@@ -14403,7 +15043,7 @@ function App({
|
|
|
14403
15043
|
return;
|
|
14404
15044
|
}
|
|
14405
15045
|
if (result.clear && result.info) {
|
|
14406
|
-
|
|
15046
|
+
stdout3?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
14407
15047
|
setHistorical([
|
|
14408
15048
|
{
|
|
14409
15049
|
id: `sys-${Date.now()}`,
|
|
@@ -14420,7 +15060,7 @@ function App({
|
|
|
14420
15060
|
return;
|
|
14421
15061
|
}
|
|
14422
15062
|
if (result.clear) {
|
|
14423
|
-
|
|
15063
|
+
stdout3?.write("\x1B[2J\x1B[3J\x1B[H");
|
|
14424
15064
|
setHistorical([]);
|
|
14425
15065
|
if (codeMode) {
|
|
14426
15066
|
pendingEdits.current = [];
|
|
@@ -14471,7 +15111,7 @@ function App({
|
|
|
14471
15111
|
if (hookList.some((h) => h.event === "UserPromptSubmit")) {
|
|
14472
15112
|
const promptReport = await runHooks({
|
|
14473
15113
|
hooks: hookList,
|
|
14474
|
-
payload: { event: "UserPromptSubmit", cwd:
|
|
15114
|
+
payload: { event: "UserPromptSubmit", cwd: currentRootDir, prompt: text }
|
|
14475
15115
|
});
|
|
14476
15116
|
if (promptReport.outcomes.length > 0) {
|
|
14477
15117
|
setHistorical((prev) => [
|
|
@@ -14539,8 +15179,8 @@ function App({
|
|
|
14539
15179
|
};
|
|
14540
15180
|
const timer = PLAIN_UI ? null : setInterval(flush, FLUSH_INTERVAL_MS);
|
|
14541
15181
|
let modelInput = text;
|
|
14542
|
-
if (codeMode
|
|
14543
|
-
const expanded = expandAtMentions(text,
|
|
15182
|
+
if (codeMode) {
|
|
15183
|
+
const expanded = expandAtMentions(text, currentRootDir);
|
|
14544
15184
|
if (expanded.expansions.length > 0) {
|
|
14545
15185
|
modelInput = expanded.text;
|
|
14546
15186
|
const inlined = expanded.expansions.filter((ex) => ex.ok).map((ex) => `${ex.path} (${(ex.bytes ?? 0).toLocaleString()} bytes)`);
|
|
@@ -14671,8 +15311,8 @@ function App({
|
|
|
14671
15311
|
const blocks = parseEditBlocks(finalText);
|
|
14672
15312
|
if (blocks.length > 0) {
|
|
14673
15313
|
if (editModeRef.current === "auto" || editModeRef.current === "yolo") {
|
|
14674
|
-
const snaps = snapshotBeforeEdits(blocks,
|
|
14675
|
-
const results = applyEditBlocks(blocks,
|
|
15314
|
+
const snaps = snapshotBeforeEdits(blocks, currentRootDir);
|
|
15315
|
+
const results = applyEditBlocks(blocks, currentRootDir);
|
|
14676
15316
|
const good = results.some(
|
|
14677
15317
|
(r) => r.status === "applied" || r.status === "created"
|
|
14678
15318
|
);
|
|
@@ -14758,6 +15398,18 @@ function App({
|
|
|
14758
15398
|
} catch {
|
|
14759
15399
|
}
|
|
14760
15400
|
}
|
|
15401
|
+
if (ev.toolName === "change_workspace" && ev.content.includes('"WorkspaceConfirmationError:') && ev.toolArgs) {
|
|
15402
|
+
try {
|
|
15403
|
+
const parsed = JSON.parse(ev.toolArgs);
|
|
15404
|
+
if (typeof parsed.path === "string" && parsed.path.trim()) {
|
|
15405
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
15406
|
+
const expanded = parsed.path.startsWith("~") && home ? pathMod6.join(home, parsed.path.slice(1)) : parsed.path;
|
|
15407
|
+
const abs = pathMod6.resolve(expanded);
|
|
15408
|
+
setPendingWorkspace({ path: abs });
|
|
15409
|
+
}
|
|
15410
|
+
} catch {
|
|
15411
|
+
}
|
|
15412
|
+
}
|
|
14761
15413
|
if (codeMode && ev.toolName === "submit_plan" && ev.content.includes('"PlanProposedError:')) {
|
|
14762
15414
|
try {
|
|
14763
15415
|
const parsed = JSON.parse(ev.content);
|
|
@@ -14880,7 +15532,7 @@ function App({
|
|
|
14880
15532
|
hooks: hookList,
|
|
14881
15533
|
payload: {
|
|
14882
15534
|
event: "Stop",
|
|
14883
|
-
cwd:
|
|
15535
|
+
cwd: currentRootDir,
|
|
14884
15536
|
lastAssistantText: streamRef.text,
|
|
14885
15537
|
turn: loop2.stats.summary().turns
|
|
14886
15538
|
}
|
|
@@ -14918,8 +15570,8 @@ function App({
|
|
|
14918
15570
|
codeMode,
|
|
14919
15571
|
codeShowEdit,
|
|
14920
15572
|
codeUndo,
|
|
15573
|
+
currentRootDir,
|
|
14921
15574
|
exit2,
|
|
14922
|
-
hookCwd,
|
|
14923
15575
|
hookList,
|
|
14924
15576
|
loop2,
|
|
14925
15577
|
latestVersion,
|
|
@@ -14950,11 +15602,12 @@ function App({
|
|
|
14950
15602
|
refreshModels,
|
|
14951
15603
|
proArmed,
|
|
14952
15604
|
persistPlanState,
|
|
14953
|
-
|
|
15605
|
+
stdout3,
|
|
14954
15606
|
stopLoop,
|
|
14955
15607
|
startLoop,
|
|
14956
15608
|
getLoopStatus,
|
|
14957
|
-
startWalkthrough
|
|
15609
|
+
startWalkthrough,
|
|
15610
|
+
applyCwdChange
|
|
14958
15611
|
]
|
|
14959
15612
|
);
|
|
14960
15613
|
useEffect6(() => {
|
|
@@ -15011,13 +15664,13 @@ function App({
|
|
|
15011
15664
|
} else {
|
|
15012
15665
|
if (choice === "always_allow") {
|
|
15013
15666
|
const prefix = derivePrefix(cmd);
|
|
15014
|
-
addProjectShellAllowed(
|
|
15667
|
+
addProjectShellAllowed(currentRootDir, prefix);
|
|
15015
15668
|
setHistorical((prev) => [
|
|
15016
15669
|
...prev,
|
|
15017
15670
|
{
|
|
15018
15671
|
id: `sh-allow-${Date.now()}`,
|
|
15019
15672
|
role: "info",
|
|
15020
|
-
text: `\u25B8 always allowed "${prefix}" for ${
|
|
15673
|
+
text: `\u25B8 always allowed "${prefix}" for ${currentRootDir}`
|
|
15021
15674
|
}
|
|
15022
15675
|
]);
|
|
15023
15676
|
}
|
|
@@ -15034,7 +15687,7 @@ function App({
|
|
|
15034
15687
|
let jobId = null;
|
|
15035
15688
|
let preview = "";
|
|
15036
15689
|
try {
|
|
15037
|
-
const res = await codeMode.jobs.start(cmd, { cwd:
|
|
15690
|
+
const res = await codeMode.jobs.start(cmd, { cwd: currentRootDir });
|
|
15038
15691
|
startedOk = true;
|
|
15039
15692
|
jobId = res.jobId;
|
|
15040
15693
|
preview = res.preview;
|
|
@@ -15068,7 +15721,7 @@ ${msg}`;
|
|
|
15068
15721
|
} else {
|
|
15069
15722
|
let body;
|
|
15070
15723
|
try {
|
|
15071
|
-
const res = await runCommand(cmd, { cwd:
|
|
15724
|
+
const res = await runCommand(cmd, { cwd: currentRootDir });
|
|
15072
15725
|
body = formatCommandResult(cmd, res);
|
|
15073
15726
|
} catch (err) {
|
|
15074
15727
|
body = `$ ${cmd}
|
|
@@ -15090,7 +15743,7 @@ ${body}`;
|
|
|
15090
15743
|
await handleSubmit(synthetic);
|
|
15091
15744
|
}
|
|
15092
15745
|
},
|
|
15093
|
-
[pendingShell, codeMode, handleSubmit, busy, loop2]
|
|
15746
|
+
[pendingShell, codeMode, currentRootDir, handleSubmit, busy, loop2]
|
|
15094
15747
|
);
|
|
15095
15748
|
useEffect6(() => {
|
|
15096
15749
|
if (!busy && queuedSubmit !== null) {
|
|
@@ -15099,6 +15752,40 @@ ${body}`;
|
|
|
15099
15752
|
void handleSubmit(text);
|
|
15100
15753
|
}
|
|
15101
15754
|
}, [busy, queuedSubmit, handleSubmit]);
|
|
15755
|
+
const handleWorkspaceConfirm = useCallback4(
|
|
15756
|
+
async (choice) => {
|
|
15757
|
+
const pending = pendingWorkspace;
|
|
15758
|
+
if (!pending) return;
|
|
15759
|
+
const target = pending.path;
|
|
15760
|
+
setPendingWorkspace(null);
|
|
15761
|
+
let synthetic;
|
|
15762
|
+
if (choice === "deny") {
|
|
15763
|
+
setHistorical((prev) => [
|
|
15764
|
+
...prev,
|
|
15765
|
+
{
|
|
15766
|
+
id: `ws-deny-${Date.now()}`,
|
|
15767
|
+
role: "info",
|
|
15768
|
+
text: `\u25B8 denied workspace switch: ${target}`
|
|
15769
|
+
}
|
|
15770
|
+
]);
|
|
15771
|
+
synthetic = `I denied switching the workspace to \`${target}\`. Please continue without changing directories.`;
|
|
15772
|
+
} else {
|
|
15773
|
+
const info = applyCwdChange(target);
|
|
15774
|
+
setHistorical((prev) => [
|
|
15775
|
+
...prev,
|
|
15776
|
+
{ id: `ws-switch-${Date.now()}`, role: "info", text: info }
|
|
15777
|
+
]);
|
|
15778
|
+
synthetic = `I approved the workspace switch. The session is now rooted at \`${target}\` \u2014 your filesystem / shell / memory tools resolve against that path on every subsequent call. Continue with my original request from this new root.`;
|
|
15779
|
+
}
|
|
15780
|
+
if (busy) {
|
|
15781
|
+
loop2.abort();
|
|
15782
|
+
setQueuedSubmit(synthetic);
|
|
15783
|
+
} else {
|
|
15784
|
+
await handleSubmit(synthetic);
|
|
15785
|
+
}
|
|
15786
|
+
},
|
|
15787
|
+
[pendingWorkspace, applyCwdChange, busy, loop2, handleSubmit]
|
|
15788
|
+
);
|
|
15102
15789
|
const handlePlanConfirm = useCallback4(
|
|
15103
15790
|
async (choice) => {
|
|
15104
15791
|
const hadPendingPlan = pendingPlan !== null;
|
|
@@ -15405,12 +16092,12 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15405
16092
|
async (choice) => handleReviseConfirmRef.current(choice),
|
|
15406
16093
|
[]
|
|
15407
16094
|
);
|
|
15408
|
-
return /* @__PURE__ */
|
|
16095
|
+
return /* @__PURE__ */ React24.createElement(React24.Fragment, null, /* @__PURE__ */ React24.createElement(
|
|
15409
16096
|
TickerProvider,
|
|
15410
16097
|
{
|
|
15411
|
-
disabled: PLAIN_UI || isResizing || !!pendingPlan || !!pendingShell || !!pendingEditReview || walkthroughActive || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
|
|
16098
|
+
disabled: PLAIN_UI || isResizing || !!pendingPlan || !!pendingShell || !!pendingWorkspace || !!pendingEditReview || walkthroughActive || !!pendingCheckpoint || !!stagedCheckpointRevise || !!pendingChoice || !!stagedChoiceCustom || !!pendingRevision
|
|
15412
16099
|
},
|
|
15413
|
-
/* @__PURE__ */
|
|
16100
|
+
/* @__PURE__ */ React24.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React24.createElement(
|
|
15414
16101
|
StatsPanel,
|
|
15415
16102
|
{
|
|
15416
16103
|
summary,
|
|
@@ -15427,28 +16114,28 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15427
16114
|
proArmed,
|
|
15428
16115
|
escalated: turnOnPro
|
|
15429
16116
|
}
|
|
15430
|
-
), /* @__PURE__ */
|
|
16117
|
+
), /* @__PURE__ */ React24.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React24.createElement(EventRow, { key: item.id, event: item, projectRoot: currentRootDir })), !historical.some((e) => e.role === "user" || e.role === "assistant") && !busy && !streaming ? /* @__PURE__ */ React24.createElement(WelcomeBanner, { inCodeMode: !!codeMode }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && streaming ? /* @__PURE__ */ React24.createElement(Box22, { marginY: 1 }, /* @__PURE__ */ React24.createElement(EventRow, { event: streaming, projectRoot: currentRootDir })) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && ongoingTool ? /* @__PURE__ */ React24.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && subagentActivity ? /* @__PURE__ */ React24.createElement(SubagentRow, { activity: subagentActivity }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !ongoingTool && statusLine ? /* @__PURE__ */ React24.createElement(StatusRow, { text: statusLine }) : null, !PLAIN_UI && undoBanner && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && !pendingChoice && !stagedChoiceCustom && !pendingRevision ? /* @__PURE__ */ React24.createElement(UndoBanner, { banner: undoBanner }) : null, !PLAIN_UI && !pendingShell && !pendingWorkspace && !pendingPlan && !stagedInput && !pendingEditReview && !pendingCheckpoint && !stagedCheckpointRevise && busy && !streaming && !ongoingTool && !statusLine ? /* @__PURE__ */ React24.createElement(StatusRow, { text: "processing\u2026" }) : null, stagedInput ? /* @__PURE__ */ React24.createElement(
|
|
15431
16118
|
PlanRefineInput,
|
|
15432
16119
|
{
|
|
15433
16120
|
mode: stagedInput.mode,
|
|
15434
16121
|
onSubmit: handleStagedInputSubmit,
|
|
15435
16122
|
onCancel: handleStagedInputCancel
|
|
15436
16123
|
}
|
|
15437
|
-
) : stagedCheckpointRevise ? /* @__PURE__ */
|
|
16124
|
+
) : stagedCheckpointRevise ? /* @__PURE__ */ React24.createElement(
|
|
15438
16125
|
PlanRefineInput,
|
|
15439
16126
|
{
|
|
15440
16127
|
mode: "checkpoint-revise",
|
|
15441
16128
|
onSubmit: handleCheckpointReviseSubmit,
|
|
15442
16129
|
onCancel: handleCheckpointReviseCancel
|
|
15443
16130
|
}
|
|
15444
|
-
) : stagedChoiceCustom ? /* @__PURE__ */
|
|
16131
|
+
) : stagedChoiceCustom ? /* @__PURE__ */ React24.createElement(
|
|
15445
16132
|
PlanRefineInput,
|
|
15446
16133
|
{
|
|
15447
16134
|
mode: "choice-custom",
|
|
15448
16135
|
onSubmit: handleChoiceCustomSubmit,
|
|
15449
16136
|
onCancel: handleChoiceCustomCancel
|
|
15450
16137
|
}
|
|
15451
|
-
) : pendingChoice ? /* @__PURE__ */
|
|
16138
|
+
) : pendingChoice ? /* @__PURE__ */ React24.createElement(
|
|
15452
16139
|
ChoiceConfirm,
|
|
15453
16140
|
{
|
|
15454
16141
|
question: pendingChoice.question,
|
|
@@ -15456,7 +16143,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15456
16143
|
allowCustom: pendingChoice.allowCustom,
|
|
15457
16144
|
onChoose: stableHandleChoiceConfirm
|
|
15458
16145
|
}
|
|
15459
|
-
) : pendingRevision ? /* @__PURE__ */
|
|
16146
|
+
) : pendingRevision ? /* @__PURE__ */ React24.createElement(
|
|
15460
16147
|
PlanReviseConfirm,
|
|
15461
16148
|
{
|
|
15462
16149
|
reason: pendingRevision.reason,
|
|
@@ -15467,7 +16154,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15467
16154
|
summary: pendingRevision.summary,
|
|
15468
16155
|
onChoose: stableHandleReviseConfirm
|
|
15469
16156
|
}
|
|
15470
|
-
) : pendingCheckpoint ? /* @__PURE__ */
|
|
16157
|
+
) : pendingCheckpoint ? /* @__PURE__ */ React24.createElement(
|
|
15471
16158
|
PlanCheckpointConfirm,
|
|
15472
16159
|
{
|
|
15473
16160
|
stepId: pendingCheckpoint.stepId,
|
|
@@ -15478,16 +16165,16 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15478
16165
|
completedStepIds: completedStepIdsRef.current,
|
|
15479
16166
|
onChoose: stableHandleCheckpointConfirm
|
|
15480
16167
|
}
|
|
15481
|
-
) : pendingPlan ? /* @__PURE__ */
|
|
16168
|
+
) : pendingPlan ? /* @__PURE__ */ React24.createElement(
|
|
15482
16169
|
PlanConfirm,
|
|
15483
16170
|
{
|
|
15484
16171
|
plan: pendingPlan,
|
|
15485
16172
|
steps: planStepsRef.current ?? void 0,
|
|
15486
16173
|
summary: planSummaryRef.current ?? void 0,
|
|
15487
16174
|
onChoose: stableHandlePlanConfirm,
|
|
15488
|
-
projectRoot:
|
|
16175
|
+
projectRoot: currentRootDir
|
|
15489
16176
|
}
|
|
15490
|
-
) : pendingShell ? /* @__PURE__ */
|
|
16177
|
+
) : pendingShell ? /* @__PURE__ */ React24.createElement(
|
|
15491
16178
|
ShellConfirm,
|
|
15492
16179
|
{
|
|
15493
16180
|
command: pendingShell.command,
|
|
@@ -15495,26 +16182,34 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15495
16182
|
kind: pendingShell.kind,
|
|
15496
16183
|
onChoose: handleShellConfirm
|
|
15497
16184
|
}
|
|
15498
|
-
) :
|
|
16185
|
+
) : pendingWorkspace ? /* @__PURE__ */ React24.createElement(
|
|
16186
|
+
WorkspaceConfirm,
|
|
16187
|
+
{
|
|
16188
|
+
path: pendingWorkspace.path,
|
|
16189
|
+
currentRoot: currentRootDir,
|
|
16190
|
+
mcpServerCount: mcpServers?.length ?? 0,
|
|
16191
|
+
onChoose: handleWorkspaceConfirm
|
|
16192
|
+
}
|
|
16193
|
+
) : pendingEditReview ? /* @__PURE__ */ React24.createElement(
|
|
15499
16194
|
EditConfirm,
|
|
15500
16195
|
{
|
|
15501
16196
|
block: pendingEditReview,
|
|
15502
16197
|
onChoose: (choice) => {
|
|
15503
|
-
const
|
|
15504
|
-
if (
|
|
16198
|
+
const resolve12 = editReviewResolveRef.current;
|
|
16199
|
+
if (resolve12) {
|
|
15505
16200
|
editReviewResolveRef.current = null;
|
|
15506
|
-
|
|
16201
|
+
resolve12(choice);
|
|
15507
16202
|
}
|
|
15508
16203
|
}
|
|
15509
16204
|
}
|
|
15510
|
-
) : walkthroughActive && pendingEdits.current.length > 0 ? /* @__PURE__ */
|
|
16205
|
+
) : walkthroughActive && pendingEdits.current.length > 0 ? /* @__PURE__ */ React24.createElement(
|
|
15511
16206
|
EditConfirm,
|
|
15512
16207
|
{
|
|
15513
16208
|
key: `walk-${pendingTick}`,
|
|
15514
16209
|
block: pendingEdits.current[0],
|
|
15515
16210
|
onChoose: handleWalkChoice
|
|
15516
16211
|
}
|
|
15517
|
-
) : /* @__PURE__ */
|
|
16212
|
+
) : /* @__PURE__ */ React24.createElement(React24.Fragment, null, codeMode ? /* @__PURE__ */ React24.createElement(
|
|
15518
16213
|
ModeStatusBar,
|
|
15519
16214
|
{
|
|
15520
16215
|
editMode,
|
|
@@ -15524,7 +16219,7 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15524
16219
|
undoArmed: !!undoBanner || hasUndoable(),
|
|
15525
16220
|
jobs: codeMode.jobs
|
|
15526
16221
|
}
|
|
15527
|
-
) : null, activeLoop ? /* @__PURE__ */
|
|
16222
|
+
) : null, activeLoop ? /* @__PURE__ */ React24.createElement(LoopStatusRow, { loop: activeLoop }) : null, /* @__PURE__ */ React24.createElement(
|
|
15528
16223
|
PromptInput,
|
|
15529
16224
|
{
|
|
15530
16225
|
value: input,
|
|
@@ -15534,14 +16229,14 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15534
16229
|
onHistoryPrev: recallPrev,
|
|
15535
16230
|
onHistoryNext: recallNext
|
|
15536
16231
|
}
|
|
15537
|
-
), /* @__PURE__ */
|
|
16232
|
+
), /* @__PURE__ */ React24.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }), /* @__PURE__ */ React24.createElement(
|
|
15538
16233
|
AtMentionSuggestions,
|
|
15539
16234
|
{
|
|
15540
16235
|
matches: atMatches,
|
|
15541
16236
|
selectedIndex: atSelected,
|
|
15542
16237
|
query: atPicker?.query ?? ""
|
|
15543
16238
|
}
|
|
15544
|
-
), slashArgContext ? /* @__PURE__ */
|
|
16239
|
+
), slashArgContext ? /* @__PURE__ */ React24.createElement(
|
|
15545
16240
|
SlashArgPicker,
|
|
15546
16241
|
{
|
|
15547
16242
|
matches: slashArgMatches,
|
|
@@ -15555,15 +16250,15 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
15555
16250
|
}
|
|
15556
16251
|
|
|
15557
16252
|
// src/cli/ui/SessionPicker.tsx
|
|
15558
|
-
import { Box as
|
|
15559
|
-
import
|
|
16253
|
+
import { Box as Box23, Text as Text21 } from "ink";
|
|
16254
|
+
import React25 from "react";
|
|
15560
16255
|
function SessionPicker({
|
|
15561
16256
|
sessionName,
|
|
15562
16257
|
messageCount,
|
|
15563
16258
|
lastActive,
|
|
15564
16259
|
onChoose
|
|
15565
16260
|
}) {
|
|
15566
|
-
return /* @__PURE__ */
|
|
16261
|
+
return /* @__PURE__ */ React25.createElement(Box23, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React25.createElement(Box23, { marginBottom: 1 }, /* @__PURE__ */ React25.createElement(Text21, { bold: true, color: "cyan" }, `Session "${sessionName}" has ${messageCount} prior message${messageCount === 1 ? "" : "s"}`), /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, ` \xB7 last active ${relativeTime2(lastActive)}`)), /* @__PURE__ */ React25.createElement(
|
|
15567
16262
|
SingleSelect,
|
|
15568
16263
|
{
|
|
15569
16264
|
initialValue: "new",
|
|
@@ -15586,7 +16281,7 @@ function SessionPicker({
|
|
|
15586
16281
|
],
|
|
15587
16282
|
onSubmit: (v) => onChoose(v)
|
|
15588
16283
|
}
|
|
15589
|
-
), /* @__PURE__ */
|
|
16284
|
+
), /* @__PURE__ */ React25.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text21, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] select")));
|
|
15590
16285
|
}
|
|
15591
16286
|
function relativeTime2(date) {
|
|
15592
16287
|
const ms = Date.now() - date.getTime();
|
|
@@ -15602,9 +16297,9 @@ function relativeTime2(date) {
|
|
|
15602
16297
|
}
|
|
15603
16298
|
|
|
15604
16299
|
// src/cli/ui/Setup.tsx
|
|
15605
|
-
import { Box as
|
|
16300
|
+
import { Box as Box24, Text as Text22, useApp as useApp2 } from "ink";
|
|
15606
16301
|
import TextInput from "ink-text-input";
|
|
15607
|
-
import
|
|
16302
|
+
import React26, { useState as useState11 } from "react";
|
|
15608
16303
|
function Setup({ onReady }) {
|
|
15609
16304
|
const [value, setValue] = useState11("");
|
|
15610
16305
|
const [error, setError] = useState11(null);
|
|
@@ -15628,7 +16323,7 @@ function Setup({ onReady }) {
|
|
|
15628
16323
|
}
|
|
15629
16324
|
onReady(trimmed);
|
|
15630
16325
|
};
|
|
15631
|
-
return /* @__PURE__ */
|
|
16326
|
+
return /* @__PURE__ */ React26.createElement(Box24, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React26.createElement(Text22, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React26.createElement(
|
|
15632
16327
|
TextInput,
|
|
15633
16328
|
{
|
|
15634
16329
|
value,
|
|
@@ -15637,7 +16332,7 @@ function Setup({ onReady }) {
|
|
|
15637
16332
|
mask: "\u2022",
|
|
15638
16333
|
placeholder: "sk-..."
|
|
15639
16334
|
}
|
|
15640
|
-
)), error ? /* @__PURE__ */
|
|
16335
|
+
)), error ? /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { color: "red" }, error)) : value ? /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React26.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text22, { dimColor: true }, "(Type /exit to abort.)")));
|
|
15641
16336
|
}
|
|
15642
16337
|
|
|
15643
16338
|
// src/cli/commands/chat.tsx
|
|
@@ -15653,7 +16348,7 @@ function Root({
|
|
|
15653
16348
|
const [key, setKey] = useState12(initialKey);
|
|
15654
16349
|
const [pending, setPending] = useState12(sessionPreview);
|
|
15655
16350
|
if (!key) {
|
|
15656
|
-
return /* @__PURE__ */
|
|
16351
|
+
return /* @__PURE__ */ React27.createElement(
|
|
15657
16352
|
Setup,
|
|
15658
16353
|
{
|
|
15659
16354
|
onReady: (k) => {
|
|
@@ -15665,7 +16360,7 @@ function Root({
|
|
|
15665
16360
|
}
|
|
15666
16361
|
process.env.DEEPSEEK_API_KEY = key;
|
|
15667
16362
|
if (pending && appProps.session) {
|
|
15668
|
-
return /* @__PURE__ */
|
|
16363
|
+
return /* @__PURE__ */ React27.createElement(KeystrokeProvider, null, /* @__PURE__ */ React27.createElement(
|
|
15669
16364
|
SessionPicker,
|
|
15670
16365
|
{
|
|
15671
16366
|
sessionName: appProps.session,
|
|
@@ -15680,7 +16375,7 @@ function Root({
|
|
|
15680
16375
|
}
|
|
15681
16376
|
));
|
|
15682
16377
|
}
|
|
15683
|
-
return /* @__PURE__ */
|
|
16378
|
+
return /* @__PURE__ */ React27.createElement(KeystrokeProvider, null, /* @__PURE__ */ React27.createElement(
|
|
15684
16379
|
App,
|
|
15685
16380
|
{
|
|
15686
16381
|
model: appProps.model,
|
|
@@ -15778,14 +16473,14 @@ async function chatCommand(opts) {
|
|
|
15778
16473
|
const prior = loadSessionMessages(opts.session);
|
|
15779
16474
|
if (prior.length > 0) {
|
|
15780
16475
|
const p = sessionPath(opts.session);
|
|
15781
|
-
const mtime =
|
|
16476
|
+
const mtime = existsSync15(p) ? statSync9(p).mtime : /* @__PURE__ */ new Date();
|
|
15782
16477
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
15783
16478
|
}
|
|
15784
16479
|
} else if (opts.session && opts.forceNew) {
|
|
15785
16480
|
rewriteSession(opts.session, []);
|
|
15786
16481
|
}
|
|
15787
16482
|
const { waitUntilExit } = render(
|
|
15788
|
-
/* @__PURE__ */
|
|
16483
|
+
/* @__PURE__ */ React27.createElement(
|
|
15789
16484
|
Root,
|
|
15790
16485
|
{
|
|
15791
16486
|
initialKey,
|
|
@@ -15809,84 +16504,733 @@ async function chatCommand(opts) {
|
|
|
15809
16504
|
}
|
|
15810
16505
|
|
|
15811
16506
|
// src/cli/commands/code.tsx
|
|
15812
|
-
import { basename as basename2, resolve as
|
|
15813
|
-
async function codeCommand(opts = {}) {
|
|
15814
|
-
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-LJ44NWSU.js");
|
|
15815
|
-
const rootDir = resolve7(opts.dir ?? process.cwd());
|
|
15816
|
-
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
15817
|
-
const tools = new ToolRegistry();
|
|
15818
|
-
registerFilesystemTools(tools, { rootDir });
|
|
15819
|
-
const jobs2 = new JobRegistry();
|
|
15820
|
-
registerShellTools(tools, {
|
|
15821
|
-
rootDir,
|
|
15822
|
-
// Per-project "always allow" list persisted from prior ShellConfirm
|
|
15823
|
-
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
15824
|
-
// GETTER form — re-read every dispatch so a prefix the user adds
|
|
15825
|
-
// via ShellConfirm mid-session takes effect on the next shell call
|
|
15826
|
-
// instead of waiting for `/new` or a relaunch.
|
|
15827
|
-
extraAllowed: () => loadProjectShellAllowed(rootDir),
|
|
15828
|
-
// `yolo` edit-mode disables shell confirmations entirely. Re-read
|
|
15829
|
-
// from config on each dispatch so /mode yolo (or Shift+Tab cycling
|
|
15830
|
-
// through to it) flips the gate live without forcing a relaunch.
|
|
15831
|
-
allowAll: () => loadEditMode() === "yolo",
|
|
15832
|
-
jobs: jobs2
|
|
15833
|
-
});
|
|
15834
|
-
registerPlanTool(tools);
|
|
15835
|
-
registerChoiceTool(tools);
|
|
15836
|
-
registerMemoryTools(tools, { projectRoot: rootDir });
|
|
15837
|
-
process.stderr.write(
|
|
15838
|
-
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)
|
|
15839
|
-
`
|
|
15840
|
-
);
|
|
15841
|
-
process.once("exit", () => {
|
|
15842
|
-
void jobs2.shutdown();
|
|
15843
|
-
});
|
|
15844
|
-
await chatCommand({
|
|
15845
|
-
model: opts.model ?? "deepseek-v4-flash",
|
|
15846
|
-
harvest: opts.harvest ?? false,
|
|
15847
|
-
system: codeSystemPrompt2(rootDir),
|
|
15848
|
-
transcript: opts.transcript,
|
|
15849
|
-
session,
|
|
15850
|
-
seedTools: tools,
|
|
15851
|
-
codeMode: { rootDir, jobs: jobs2 },
|
|
15852
|
-
forceResume: opts.forceResume,
|
|
15853
|
-
forceNew: opts.forceNew
|
|
15854
|
-
});
|
|
15855
|
-
}
|
|
15856
|
-
|
|
15857
|
-
// src/cli/commands/diff.ts
|
|
15858
|
-
import { writeFileSync as writeFileSync8 } from "fs";
|
|
15859
|
-
import { basename as basename3 } from "path";
|
|
15860
|
-
import { render as render2 } from "ink";
|
|
15861
|
-
import React29 from "react";
|
|
16507
|
+
import { basename as basename2, resolve as resolve10 } from "path";
|
|
15862
16508
|
|
|
15863
|
-
// src/
|
|
15864
|
-
import {
|
|
15865
|
-
import
|
|
16509
|
+
// src/index/semantic/builder.ts
|
|
16510
|
+
import { promises as fs5 } from "fs";
|
|
16511
|
+
import path4 from "path";
|
|
15866
16512
|
|
|
15867
|
-
// src/
|
|
15868
|
-
import {
|
|
15869
|
-
import
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
16513
|
+
// src/index/semantic/chunker.ts
|
|
16514
|
+
import { promises as fs3 } from "fs";
|
|
16515
|
+
import path2 from "path";
|
|
16516
|
+
var DEFAULT_MAX_CHUNK_CHARS = 4e3;
|
|
16517
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
16518
|
+
"node_modules",
|
|
16519
|
+
".git",
|
|
16520
|
+
"dist",
|
|
16521
|
+
"build",
|
|
16522
|
+
"out",
|
|
16523
|
+
".next",
|
|
16524
|
+
".nuxt",
|
|
16525
|
+
"target",
|
|
16526
|
+
".venv",
|
|
16527
|
+
"venv",
|
|
16528
|
+
"__pycache__",
|
|
16529
|
+
".pytest_cache",
|
|
16530
|
+
".mypy_cache",
|
|
16531
|
+
".cache",
|
|
16532
|
+
"coverage",
|
|
16533
|
+
".turbo",
|
|
16534
|
+
".vercel",
|
|
16535
|
+
".reasonix"
|
|
16536
|
+
]);
|
|
16537
|
+
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
16538
|
+
"package-lock.json",
|
|
16539
|
+
"yarn.lock",
|
|
16540
|
+
"pnpm-lock.yaml",
|
|
16541
|
+
"Cargo.lock",
|
|
16542
|
+
"poetry.lock",
|
|
16543
|
+
"Pipfile.lock",
|
|
16544
|
+
"go.sum",
|
|
16545
|
+
".DS_Store"
|
|
16546
|
+
]);
|
|
16547
|
+
var BINARY_EXTS = /* @__PURE__ */ new Set([
|
|
16548
|
+
// Images
|
|
16549
|
+
".png",
|
|
16550
|
+
".jpg",
|
|
16551
|
+
".jpeg",
|
|
16552
|
+
".gif",
|
|
16553
|
+
".webp",
|
|
16554
|
+
".bmp",
|
|
16555
|
+
".ico",
|
|
16556
|
+
".tiff",
|
|
16557
|
+
// Fonts
|
|
16558
|
+
".woff",
|
|
16559
|
+
".woff2",
|
|
16560
|
+
".ttf",
|
|
16561
|
+
".otf",
|
|
16562
|
+
".eot",
|
|
16563
|
+
// Archives / binaries
|
|
16564
|
+
".zip",
|
|
16565
|
+
".tar",
|
|
16566
|
+
".gz",
|
|
16567
|
+
".rar",
|
|
16568
|
+
".7z",
|
|
16569
|
+
".exe",
|
|
16570
|
+
".dll",
|
|
16571
|
+
".so",
|
|
16572
|
+
".dylib",
|
|
16573
|
+
".class",
|
|
16574
|
+
".jar",
|
|
16575
|
+
".wasm",
|
|
16576
|
+
".o",
|
|
16577
|
+
".a",
|
|
16578
|
+
// Media
|
|
16579
|
+
".mp3",
|
|
16580
|
+
".mp4",
|
|
16581
|
+
".wav",
|
|
16582
|
+
".ogg",
|
|
16583
|
+
".webm",
|
|
16584
|
+
".mov",
|
|
16585
|
+
// Other
|
|
16586
|
+
".pdf",
|
|
16587
|
+
".sqlite",
|
|
16588
|
+
".db"
|
|
16589
|
+
]);
|
|
16590
|
+
function chunkText(text, filePath, windowLines, overlap, maxChunkChars = DEFAULT_MAX_CHUNK_CHARS) {
|
|
16591
|
+
const lines = text.split(/\r?\n/);
|
|
16592
|
+
if (lines.length === 0 || lines.length === 1 && lines[0] === "") return [];
|
|
16593
|
+
const stride = Math.max(1, windowLines - overlap);
|
|
16594
|
+
const chunks = [];
|
|
16595
|
+
for (let start = 0; start < lines.length; start += stride) {
|
|
16596
|
+
const end = Math.min(lines.length, start + windowLines);
|
|
16597
|
+
const slice = lines.slice(start, end).join("\n").trim();
|
|
16598
|
+
if (slice.length === 0) {
|
|
16599
|
+
if (end >= lines.length) break;
|
|
16600
|
+
continue;
|
|
16601
|
+
}
|
|
16602
|
+
const window = {
|
|
16603
|
+
path: filePath,
|
|
16604
|
+
startLine: start + 1,
|
|
16605
|
+
endLine: end,
|
|
16606
|
+
text: slice
|
|
16607
|
+
};
|
|
16608
|
+
for (const sub of safeSplit(window, maxChunkChars)) chunks.push(sub);
|
|
16609
|
+
if (end >= lines.length) break;
|
|
16610
|
+
}
|
|
16611
|
+
return chunks;
|
|
16612
|
+
}
|
|
16613
|
+
function safeSplit(chunk, maxChars) {
|
|
16614
|
+
if (chunk.text.length <= maxChars) return [chunk];
|
|
16615
|
+
const lines = chunk.text.split("\n");
|
|
16616
|
+
const out = [];
|
|
16617
|
+
let bufLines = [];
|
|
16618
|
+
let bufStart = chunk.startLine;
|
|
16619
|
+
let bufLen = 0;
|
|
16620
|
+
const flush = (untilLineNo) => {
|
|
16621
|
+
if (bufLines.length === 0) return;
|
|
16622
|
+
out.push({
|
|
16623
|
+
path: chunk.path,
|
|
16624
|
+
startLine: bufStart,
|
|
16625
|
+
endLine: untilLineNo,
|
|
16626
|
+
text: bufLines.join("\n")
|
|
16627
|
+
});
|
|
16628
|
+
bufLines = [];
|
|
16629
|
+
bufLen = 0;
|
|
16630
|
+
};
|
|
16631
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16632
|
+
const line = lines[i] ?? "";
|
|
16633
|
+
const lineLen = line.length + 1;
|
|
16634
|
+
if (lineLen > maxChars) {
|
|
16635
|
+
flush(chunk.startLine + i - 1);
|
|
16636
|
+
out.push({
|
|
16637
|
+
path: chunk.path,
|
|
16638
|
+
startLine: chunk.startLine + i,
|
|
16639
|
+
endLine: chunk.startLine + i,
|
|
16640
|
+
text: line.slice(0, maxChars)
|
|
16641
|
+
});
|
|
16642
|
+
bufStart = chunk.startLine + i + 1;
|
|
16643
|
+
continue;
|
|
16644
|
+
}
|
|
16645
|
+
if (bufLen + lineLen > maxChars && bufLines.length > 0) {
|
|
16646
|
+
flush(chunk.startLine + i - 1);
|
|
16647
|
+
bufStart = chunk.startLine + i;
|
|
16648
|
+
}
|
|
16649
|
+
bufLines.push(line);
|
|
16650
|
+
bufLen += lineLen;
|
|
16651
|
+
}
|
|
16652
|
+
flush(chunk.endLine);
|
|
16653
|
+
return out;
|
|
16654
|
+
}
|
|
16655
|
+
async function* walkChunks(root, opts = {}) {
|
|
16656
|
+
const windowLines = opts.windowLines ?? 60;
|
|
16657
|
+
const overlap = Math.min(opts.overlap ?? 12, Math.max(0, windowLines - 1));
|
|
16658
|
+
const maxFileBytes = opts.maxFileBytes ?? 256 * 1024;
|
|
16659
|
+
const maxChunkChars = opts.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
|
|
16660
|
+
const stack = [root];
|
|
16661
|
+
while (stack.length > 0) {
|
|
16662
|
+
const dir = stack.pop();
|
|
16663
|
+
if (!dir) break;
|
|
16664
|
+
let entries;
|
|
16665
|
+
try {
|
|
16666
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
16667
|
+
} catch {
|
|
16668
|
+
continue;
|
|
16669
|
+
}
|
|
16670
|
+
for (const entry of entries) {
|
|
16671
|
+
const name = entry.name;
|
|
16672
|
+
if (entry.isDirectory()) {
|
|
16673
|
+
if (SKIP_DIRS.has(name) || name.startsWith(".")) {
|
|
16674
|
+
if (SKIP_DIRS.has(name) || name === ".git") continue;
|
|
16675
|
+
}
|
|
16676
|
+
stack.push(path2.join(dir, name));
|
|
16677
|
+
continue;
|
|
16678
|
+
}
|
|
16679
|
+
if (!entry.isFile()) continue;
|
|
16680
|
+
if (SKIP_FILES.has(name)) continue;
|
|
16681
|
+
const ext = path2.extname(name).toLowerCase();
|
|
16682
|
+
if (BINARY_EXTS.has(ext)) continue;
|
|
16683
|
+
const abs = path2.join(dir, name);
|
|
16684
|
+
let stat;
|
|
16685
|
+
try {
|
|
16686
|
+
stat = await fs3.stat(abs);
|
|
16687
|
+
} catch {
|
|
16688
|
+
continue;
|
|
16689
|
+
}
|
|
16690
|
+
if (stat.size > maxFileBytes) continue;
|
|
16691
|
+
let text;
|
|
16692
|
+
try {
|
|
16693
|
+
text = await fs3.readFile(abs, "utf8");
|
|
16694
|
+
} catch {
|
|
16695
|
+
continue;
|
|
16696
|
+
}
|
|
16697
|
+
if (text.indexOf("\0") !== -1) continue;
|
|
16698
|
+
const rel = path2.relative(root, abs).split(path2.sep).join("/");
|
|
16699
|
+
for (const chunk of chunkText(text, rel, windowLines, overlap, maxChunkChars)) {
|
|
16700
|
+
yield chunk;
|
|
16701
|
+
}
|
|
16702
|
+
}
|
|
16703
|
+
}
|
|
16704
|
+
}
|
|
16705
|
+
|
|
16706
|
+
// src/index/semantic/store.ts
|
|
16707
|
+
import { promises as fs4 } from "fs";
|
|
16708
|
+
import path3 from "path";
|
|
16709
|
+
var STORE_VERSION = 1;
|
|
16710
|
+
var META_FILE = "index.meta.json";
|
|
16711
|
+
var DATA_FILE = "index.jsonl";
|
|
16712
|
+
var SemanticStore = class {
|
|
16713
|
+
constructor(indexDir, model2) {
|
|
16714
|
+
this.indexDir = indexDir;
|
|
16715
|
+
this.model = model2;
|
|
16716
|
+
}
|
|
16717
|
+
indexDir;
|
|
16718
|
+
model;
|
|
16719
|
+
entries = [];
|
|
16720
|
+
byPath = /* @__PURE__ */ new Map();
|
|
16721
|
+
dim = 0;
|
|
16722
|
+
/** True when no entries are loaded — the index doesn't exist or is empty. */
|
|
16723
|
+
get empty() {
|
|
16724
|
+
return this.entries.length === 0;
|
|
16725
|
+
}
|
|
16726
|
+
/** Total number of indexed chunks. */
|
|
16727
|
+
get size() {
|
|
16728
|
+
return this.entries.length;
|
|
16729
|
+
}
|
|
16730
|
+
/** Read-only view, mostly for tests. */
|
|
16731
|
+
get all() {
|
|
16732
|
+
return this.entries;
|
|
16733
|
+
}
|
|
16734
|
+
/** Last-known mtime per indexed file (ms epoch) for incremental rebuilds. */
|
|
16735
|
+
fileMtimes() {
|
|
16736
|
+
const out = /* @__PURE__ */ new Map();
|
|
16737
|
+
for (const [p, group] of this.byPath) {
|
|
16738
|
+
const first = group[0];
|
|
16739
|
+
if (first) out.set(p, first.mtimeMs);
|
|
16740
|
+
}
|
|
16741
|
+
return out;
|
|
16742
|
+
}
|
|
16743
|
+
/** Append entries to in-memory state and to disk. Re-indexes the
|
|
16744
|
+
* `byPath` map. Caller is responsible for L2-normalizing each
|
|
16745
|
+
* embedding before calling — the search hot path assumes unit vectors. */
|
|
16746
|
+
async add(entries) {
|
|
16747
|
+
if (entries.length === 0) return;
|
|
16748
|
+
if (this.dim === 0) this.dim = entries[0].embedding.length;
|
|
16749
|
+
const lines = [];
|
|
16750
|
+
for (const e of entries) {
|
|
16751
|
+
if (e.embedding.length !== this.dim) {
|
|
16752
|
+
throw new Error(
|
|
16753
|
+
`embedding dim mismatch: expected ${this.dim}, got ${e.embedding.length} for ${e.path}:${e.startLine}`
|
|
16754
|
+
);
|
|
16755
|
+
}
|
|
16756
|
+
this.entries.push(e);
|
|
16757
|
+
const list = this.byPath.get(e.path);
|
|
16758
|
+
if (list) list.push(e);
|
|
16759
|
+
else this.byPath.set(e.path, [e]);
|
|
16760
|
+
lines.push(serializeEntry(e));
|
|
16761
|
+
}
|
|
16762
|
+
await fs4.mkdir(this.indexDir, { recursive: true });
|
|
16763
|
+
await fs4.appendFile(path3.join(this.indexDir, DATA_FILE), `${lines.join("\n")}
|
|
16764
|
+
`, "utf8");
|
|
16765
|
+
await this.writeMeta();
|
|
16766
|
+
}
|
|
16767
|
+
/**
|
|
16768
|
+
* Drop every entry whose `path` is in `paths`. Used by incremental
|
|
16769
|
+
* rebuild: when a file's mtime changes, the existing entries for
|
|
16770
|
+
* it are evicted before re-chunking + re-embedding. Implementation
|
|
16771
|
+
* rewrites the JSONL — append-only is fine for adds, but deletes
|
|
16772
|
+
* need a compaction pass.
|
|
16773
|
+
*/
|
|
16774
|
+
async remove(paths) {
|
|
16775
|
+
if (paths.length === 0) return 0;
|
|
16776
|
+
const drop = new Set(paths);
|
|
16777
|
+
const before = this.entries.length;
|
|
16778
|
+
this.entries = this.entries.filter((e) => !drop.has(e.path));
|
|
16779
|
+
for (const p of paths) this.byPath.delete(p);
|
|
16780
|
+
const removed = before - this.entries.length;
|
|
16781
|
+
if (removed > 0) await this.flush();
|
|
16782
|
+
return removed;
|
|
16783
|
+
}
|
|
16784
|
+
/**
|
|
16785
|
+
* Top-K cosine search. `query` MUST already be L2-normalized — the
|
|
16786
|
+
* caller embeds + normalizes once per query. Filtering hits by
|
|
16787
|
+
* minimum score (`minScore`) is optional; tune via UI to suppress
|
|
16788
|
+
* weakly relevant snippets that distract the model.
|
|
16789
|
+
*/
|
|
16790
|
+
search(query, topK = 8, minScore = 0) {
|
|
16791
|
+
if (this.entries.length === 0) return [];
|
|
16792
|
+
if (query.length !== this.dim && this.dim !== 0) {
|
|
16793
|
+
throw new Error(`query dim ${query.length} \u2260 index dim ${this.dim}`);
|
|
16794
|
+
}
|
|
16795
|
+
const heap = [];
|
|
16796
|
+
for (const entry of this.entries) {
|
|
16797
|
+
const score = dot(query, entry.embedding);
|
|
16798
|
+
if (score < minScore) continue;
|
|
16799
|
+
if (heap.length < topK) {
|
|
16800
|
+
heap.push({ entry, score });
|
|
16801
|
+
if (heap.length === topK) heap.sort((a, b) => a.score - b.score);
|
|
16802
|
+
} else if (score > heap[0].score) {
|
|
16803
|
+
heap[0] = { entry, score };
|
|
16804
|
+
for (let i = 0; i < heap.length - 1; i++) {
|
|
16805
|
+
if (heap[i].score > heap[i + 1].score) {
|
|
16806
|
+
const tmp = heap[i];
|
|
16807
|
+
heap[i] = heap[i + 1];
|
|
16808
|
+
heap[i + 1] = tmp;
|
|
16809
|
+
}
|
|
16810
|
+
}
|
|
16811
|
+
}
|
|
16812
|
+
}
|
|
16813
|
+
return heap.sort((a, b) => b.score - a.score);
|
|
16814
|
+
}
|
|
16815
|
+
/**
|
|
16816
|
+
* Rewrite the JSONL on disk with the current in-memory state. Used
|
|
16817
|
+
* after `remove` and from `flush`. We write to a temp file and
|
|
16818
|
+
* rename so a Ctrl+C mid-write never leaves the index half-empty.
|
|
16819
|
+
*/
|
|
16820
|
+
async flush() {
|
|
16821
|
+
await fs4.mkdir(this.indexDir, { recursive: true });
|
|
16822
|
+
const tmp = path3.join(this.indexDir, `${DATA_FILE}.tmp`);
|
|
16823
|
+
const final = path3.join(this.indexDir, DATA_FILE);
|
|
16824
|
+
const lines = this.entries.map(serializeEntry).join("\n");
|
|
16825
|
+
await fs4.writeFile(tmp, lines.length > 0 ? `${lines}
|
|
16826
|
+
` : "", "utf8");
|
|
16827
|
+
await fs4.rename(tmp, final);
|
|
16828
|
+
await this.writeMeta();
|
|
16829
|
+
}
|
|
16830
|
+
async writeMeta() {
|
|
16831
|
+
const meta = {
|
|
16832
|
+
version: STORE_VERSION,
|
|
16833
|
+
model: this.model,
|
|
16834
|
+
dim: this.dim,
|
|
16835
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16836
|
+
};
|
|
16837
|
+
await fs4.writeFile(
|
|
16838
|
+
path3.join(this.indexDir, META_FILE),
|
|
16839
|
+
`${JSON.stringify(meta, null, 2)}
|
|
16840
|
+
`,
|
|
16841
|
+
"utf8"
|
|
16842
|
+
);
|
|
16843
|
+
}
|
|
16844
|
+
/** Drop everything from disk + memory. Used by `--rebuild`. */
|
|
16845
|
+
async wipe() {
|
|
16846
|
+
this.entries = [];
|
|
16847
|
+
this.byPath.clear();
|
|
16848
|
+
this.dim = 0;
|
|
16849
|
+
await fs4.rm(path3.join(this.indexDir, DATA_FILE), { force: true });
|
|
16850
|
+
await fs4.rm(path3.join(this.indexDir, META_FILE), { force: true });
|
|
16851
|
+
}
|
|
16852
|
+
};
|
|
16853
|
+
async function openStore(indexDir, model2) {
|
|
16854
|
+
const store = new SemanticStore(indexDir, model2);
|
|
16855
|
+
const dataPath = path3.join(indexDir, DATA_FILE);
|
|
16856
|
+
const metaPath = path3.join(indexDir, META_FILE);
|
|
16857
|
+
let meta = null;
|
|
16858
|
+
try {
|
|
16859
|
+
const raw2 = await fs4.readFile(metaPath, "utf8");
|
|
16860
|
+
meta = JSON.parse(raw2);
|
|
16861
|
+
} catch {
|
|
16862
|
+
}
|
|
16863
|
+
if (meta) {
|
|
16864
|
+
if (meta.version !== STORE_VERSION) {
|
|
16865
|
+
throw new Error(
|
|
16866
|
+
`Index format version ${meta.version} does not match current ${STORE_VERSION}. Run \`reasonix index --rebuild\`.`
|
|
16867
|
+
);
|
|
16868
|
+
}
|
|
16869
|
+
if (meta.model !== model2) {
|
|
16870
|
+
throw new Error(
|
|
16871
|
+
`Index was built with model "${meta.model}" but current is "${model2}". Run \`reasonix index --rebuild\`.`
|
|
16872
|
+
);
|
|
16873
|
+
}
|
|
16874
|
+
}
|
|
16875
|
+
let raw;
|
|
16876
|
+
try {
|
|
16877
|
+
raw = await fs4.readFile(dataPath, "utf8");
|
|
16878
|
+
} catch {
|
|
16879
|
+
return store;
|
|
16880
|
+
}
|
|
16881
|
+
for (const line of raw.split("\n")) {
|
|
16882
|
+
if (line.length === 0) continue;
|
|
16883
|
+
try {
|
|
16884
|
+
const entry = deserializeEntry(line);
|
|
16885
|
+
store.dim = entry.embedding.length;
|
|
16886
|
+
store.entries.push(entry);
|
|
16887
|
+
const map = store.byPath;
|
|
16888
|
+
const list = map.get(entry.path);
|
|
16889
|
+
if (list) list.push(entry);
|
|
16890
|
+
else map.set(entry.path, [entry]);
|
|
16891
|
+
} catch {
|
|
16892
|
+
}
|
|
16893
|
+
}
|
|
16894
|
+
return store;
|
|
16895
|
+
}
|
|
16896
|
+
function normalize(v) {
|
|
16897
|
+
let sum = 0;
|
|
16898
|
+
for (let i = 0; i < v.length; i++) sum += v[i] * v[i];
|
|
16899
|
+
const inv = sum > 0 ? 1 / Math.sqrt(sum) : 0;
|
|
16900
|
+
for (let i = 0; i < v.length; i++) v[i] = v[i] * inv;
|
|
16901
|
+
return v;
|
|
16902
|
+
}
|
|
16903
|
+
function dot(a, b) {
|
|
16904
|
+
let s = 0;
|
|
16905
|
+
for (let i = 0; i < a.length; i++) s += a[i] * b[i];
|
|
16906
|
+
return s;
|
|
16907
|
+
}
|
|
16908
|
+
function serializeEntry(e) {
|
|
16909
|
+
const buf = Buffer.from(e.embedding.buffer, e.embedding.byteOffset, e.embedding.byteLength);
|
|
16910
|
+
return JSON.stringify({
|
|
16911
|
+
p: e.path,
|
|
16912
|
+
s: e.startLine,
|
|
16913
|
+
e: e.endLine,
|
|
16914
|
+
m: e.mtimeMs,
|
|
16915
|
+
t: e.text,
|
|
16916
|
+
v: buf.toString("base64")
|
|
16917
|
+
});
|
|
16918
|
+
}
|
|
16919
|
+
function deserializeEntry(line) {
|
|
16920
|
+
const parsed = JSON.parse(line);
|
|
16921
|
+
const bytes = Buffer.from(parsed.v, "base64");
|
|
16922
|
+
const f32 = new Float32Array(
|
|
16923
|
+
bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
|
|
16924
|
+
);
|
|
16925
|
+
return {
|
|
16926
|
+
path: parsed.p,
|
|
16927
|
+
startLine: parsed.s,
|
|
16928
|
+
endLine: parsed.e,
|
|
16929
|
+
mtimeMs: parsed.m,
|
|
16930
|
+
text: parsed.t,
|
|
16931
|
+
embedding: f32
|
|
16932
|
+
};
|
|
16933
|
+
}
|
|
16934
|
+
|
|
16935
|
+
// src/index/semantic/builder.ts
|
|
16936
|
+
var INDEX_DIR_NAME = path4.join(".reasonix", "semantic");
|
|
16937
|
+
async function buildIndex(root, opts = {}) {
|
|
16938
|
+
const t0 = Date.now();
|
|
16939
|
+
const indexDir = path4.join(root, INDEX_DIR_NAME);
|
|
16940
|
+
const probe = await probeOllama({ baseUrl: opts.baseUrl, signal: opts.signal });
|
|
16941
|
+
if (!probe.ok) {
|
|
16942
|
+
throw new Error(
|
|
16943
|
+
`Ollama is not reachable: ${probe.error}. Install from https://ollama.com, then \`ollama serve\` and \`ollama pull ${opts.model ?? "nomic-embed-text"}\`.`
|
|
16944
|
+
);
|
|
16945
|
+
}
|
|
16946
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
16947
|
+
const store = await openStore(indexDir, model2);
|
|
16948
|
+
if (opts.rebuild) await store.wipe();
|
|
16949
|
+
const lastMtimes = store.fileMtimes();
|
|
16950
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
16951
|
+
const fileChunks = /* @__PURE__ */ new Map();
|
|
16952
|
+
let filesScanned = 0;
|
|
16953
|
+
let filesSkipped = 0;
|
|
16954
|
+
for await (const chunk of walkChunks(root, {
|
|
16955
|
+
windowLines: opts.windowLines,
|
|
16956
|
+
overlap: opts.overlap,
|
|
16957
|
+
maxFileBytes: opts.maxFileBytes
|
|
16958
|
+
})) {
|
|
16959
|
+
seenPaths.add(chunk.path);
|
|
16960
|
+
let bucket = fileChunks.get(chunk.path);
|
|
16961
|
+
if (!bucket) {
|
|
16962
|
+
filesScanned++;
|
|
16963
|
+
const abs = path4.join(root, chunk.path);
|
|
16964
|
+
let mtimeMs = 0;
|
|
16965
|
+
try {
|
|
16966
|
+
const stat = await fs5.stat(abs);
|
|
16967
|
+
mtimeMs = stat.mtimeMs;
|
|
16968
|
+
} catch {
|
|
16969
|
+
continue;
|
|
16970
|
+
}
|
|
16971
|
+
const last = lastMtimes.get(chunk.path);
|
|
16972
|
+
if (last !== void 0 && last === mtimeMs && !opts.rebuild) {
|
|
16973
|
+
filesSkipped++;
|
|
16974
|
+
continue;
|
|
16975
|
+
}
|
|
16976
|
+
bucket = { chunks: [], mtimeMs };
|
|
16977
|
+
fileChunks.set(chunk.path, bucket);
|
|
16978
|
+
}
|
|
16979
|
+
bucket.chunks.push(chunk);
|
|
16980
|
+
opts.onProgress?.({ phase: "scan", filesScanned });
|
|
16981
|
+
}
|
|
16982
|
+
const deletedPaths = [];
|
|
16983
|
+
for (const oldPath of lastMtimes.keys()) {
|
|
16984
|
+
if (!seenPaths.has(oldPath)) deletedPaths.push(oldPath);
|
|
16985
|
+
}
|
|
16986
|
+
const replacePaths = [...fileChunks.keys()].filter((p) => lastMtimes.has(p));
|
|
16987
|
+
const removed = await store.remove([...deletedPaths, ...replacePaths]);
|
|
16988
|
+
let chunksAdded = 0;
|
|
16989
|
+
let chunksSkipped = 0;
|
|
16990
|
+
const filesChanged = fileChunks.size;
|
|
16991
|
+
let chunksTotal = 0;
|
|
16992
|
+
for (const { chunks } of fileChunks.values()) chunksTotal += chunks.length;
|
|
16993
|
+
let chunksDone = 0;
|
|
16994
|
+
for (const [, bucket] of fileChunks) {
|
|
16995
|
+
if (bucket.chunks.length === 0) continue;
|
|
16996
|
+
const texts = bucket.chunks.map((c) => c.text);
|
|
16997
|
+
const vectors = await embedAll(texts, {
|
|
16998
|
+
...opts,
|
|
16999
|
+
onProgress: (done, total) => {
|
|
17000
|
+
opts.onProgress?.({
|
|
17001
|
+
phase: "embed",
|
|
17002
|
+
filesScanned,
|
|
17003
|
+
filesChanged,
|
|
17004
|
+
chunksTotal,
|
|
17005
|
+
chunksDone: chunksDone + done
|
|
17006
|
+
});
|
|
17007
|
+
if (done === total) chunksDone += total;
|
|
17008
|
+
},
|
|
17009
|
+
onError: (idx, err) => {
|
|
17010
|
+
chunksSkipped++;
|
|
17011
|
+
const c = bucket.chunks[idx];
|
|
17012
|
+
const where = c ? `${c.path}:${c.startLine}-${c.endLine}` : `chunk #${idx}`;
|
|
17013
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17014
|
+
process.stderr.write(`
|
|
17015
|
+
! skipped ${where}: ${msg}
|
|
17016
|
+
`);
|
|
17017
|
+
}
|
|
17018
|
+
});
|
|
17019
|
+
const entries = [];
|
|
17020
|
+
for (let i = 0; i < bucket.chunks.length; i++) {
|
|
17021
|
+
const vec = vectors[i];
|
|
17022
|
+
if (!vec) continue;
|
|
17023
|
+
const c = bucket.chunks[i];
|
|
17024
|
+
if (!c) continue;
|
|
17025
|
+
normalize(vec);
|
|
17026
|
+
entries.push({
|
|
17027
|
+
path: c.path,
|
|
17028
|
+
startLine: c.startLine,
|
|
17029
|
+
endLine: c.endLine,
|
|
17030
|
+
text: c.text,
|
|
17031
|
+
embedding: vec,
|
|
17032
|
+
mtimeMs: bucket.mtimeMs
|
|
17033
|
+
});
|
|
17034
|
+
}
|
|
17035
|
+
if (entries.length > 0) await store.add(entries);
|
|
17036
|
+
chunksAdded += entries.length;
|
|
17037
|
+
}
|
|
17038
|
+
opts.onProgress?.({
|
|
17039
|
+
phase: "done",
|
|
17040
|
+
filesScanned,
|
|
17041
|
+
filesSkipped,
|
|
17042
|
+
filesChanged,
|
|
17043
|
+
chunksTotal,
|
|
17044
|
+
chunksDone
|
|
17045
|
+
});
|
|
17046
|
+
return {
|
|
17047
|
+
filesScanned,
|
|
17048
|
+
filesChanged,
|
|
17049
|
+
chunksAdded,
|
|
17050
|
+
chunksRemoved: removed,
|
|
17051
|
+
chunksSkipped,
|
|
17052
|
+
durationMs: Date.now() - t0
|
|
17053
|
+
};
|
|
17054
|
+
}
|
|
17055
|
+
async function querySemantic(root, query, opts = {}) {
|
|
17056
|
+
const indexDir = path4.join(root, INDEX_DIR_NAME);
|
|
17057
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
17058
|
+
const store = await openStore(indexDir, model2);
|
|
17059
|
+
if (store.empty) return null;
|
|
17060
|
+
const qvec = await embed(query, opts);
|
|
17061
|
+
normalize(qvec);
|
|
17062
|
+
return store.search(qvec, opts.topK ?? 8, opts.minScore ?? 0.3);
|
|
17063
|
+
}
|
|
17064
|
+
async function indexExists(root) {
|
|
17065
|
+
const meta = path4.join(root, INDEX_DIR_NAME, "index.meta.json");
|
|
17066
|
+
try {
|
|
17067
|
+
await fs5.access(meta);
|
|
17068
|
+
return true;
|
|
17069
|
+
} catch {
|
|
17070
|
+
return false;
|
|
17071
|
+
}
|
|
17072
|
+
}
|
|
17073
|
+
|
|
17074
|
+
// src/index/semantic/tool.ts
|
|
17075
|
+
async function registerSemanticSearchTool(registry, opts) {
|
|
17076
|
+
if (!await indexExists(opts.root)) return false;
|
|
17077
|
+
const defaultTopK = opts.defaultTopK ?? 8;
|
|
17078
|
+
const defaultMinScore = opts.defaultMinScore ?? 0.3;
|
|
17079
|
+
registry.register({
|
|
17080
|
+
name: "semantic_search",
|
|
17081
|
+
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.",
|
|
17082
|
+
readOnly: true,
|
|
17083
|
+
parameters: {
|
|
17084
|
+
type: "object",
|
|
17085
|
+
properties: {
|
|
17086
|
+
query: {
|
|
17087
|
+
type: "string",
|
|
17088
|
+
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."
|
|
17089
|
+
},
|
|
17090
|
+
topK: {
|
|
17091
|
+
type: "integer",
|
|
17092
|
+
description: `Number of snippets to return (1..16). Default ${defaultTopK}.`
|
|
17093
|
+
},
|
|
17094
|
+
minScore: {
|
|
17095
|
+
type: "number",
|
|
17096
|
+
description: `Drop snippets with cosine score below this (0..1). Default ${defaultMinScore}. Raise for stricter matches; lower if the index is small.`
|
|
17097
|
+
}
|
|
17098
|
+
},
|
|
17099
|
+
required: ["query"]
|
|
17100
|
+
},
|
|
17101
|
+
fn: async (args, ctx) => {
|
|
17102
|
+
const hits = await querySemantic(opts.root, args.query, {
|
|
17103
|
+
topK: args.topK ?? defaultTopK,
|
|
17104
|
+
minScore: args.minScore ?? defaultMinScore,
|
|
17105
|
+
baseUrl: opts.baseUrl,
|
|
17106
|
+
model: opts.model,
|
|
17107
|
+
signal: ctx?.signal
|
|
17108
|
+
});
|
|
17109
|
+
if (hits === null) {
|
|
17110
|
+
return "No semantic index found for this project. Run `reasonix index` to build one.";
|
|
17111
|
+
}
|
|
17112
|
+
if (hits.length === 0) {
|
|
17113
|
+
return `query: ${args.query}
|
|
17114
|
+
|
|
17115
|
+
no matches above the score threshold (${args.minScore ?? defaultMinScore}).`;
|
|
17116
|
+
}
|
|
17117
|
+
return formatHits(args.query, hits);
|
|
17118
|
+
}
|
|
17119
|
+
});
|
|
17120
|
+
return true;
|
|
17121
|
+
}
|
|
17122
|
+
function formatHits(query, hits) {
|
|
17123
|
+
const lines = [`query: ${query}`, `
|
|
17124
|
+
results (${hits.length}):`];
|
|
17125
|
+
hits.forEach((h, i) => {
|
|
17126
|
+
const { entry, score } = h;
|
|
17127
|
+
lines.push(
|
|
17128
|
+
`
|
|
17129
|
+
${i + 1}. ${entry.path}:${entry.startLine}-${entry.endLine} (score ${score.toFixed(3)})`
|
|
17130
|
+
);
|
|
17131
|
+
const preview = entry.text.split("\n").slice(0, 8).join("\n");
|
|
17132
|
+
lines.push(indentBlock(preview, " "));
|
|
17133
|
+
if (entry.text.split("\n").length > 8) {
|
|
17134
|
+
lines.push(
|
|
17135
|
+
` \u2026(${entry.text.split("\n").length - 8} more lines \u2014 read_file ${entry.path}:${entry.startLine} for the full chunk)`
|
|
17136
|
+
);
|
|
17137
|
+
}
|
|
17138
|
+
});
|
|
17139
|
+
return lines.join("\n");
|
|
17140
|
+
}
|
|
17141
|
+
function indentBlock(text, prefix) {
|
|
17142
|
+
return text.split("\n").map((l) => prefix + l).join("\n");
|
|
17143
|
+
}
|
|
17144
|
+
async function bootstrapSemanticSearchInCodeMode(registry, rootDir, opts = {}) {
|
|
17145
|
+
if (await indexExists(rootDir)) {
|
|
17146
|
+
await registerSemanticSearchTool(registry, { ...opts, root: rootDir });
|
|
17147
|
+
return { enabled: true };
|
|
17148
|
+
}
|
|
17149
|
+
return { enabled: false };
|
|
17150
|
+
}
|
|
17151
|
+
|
|
17152
|
+
// src/cli/commands/code.tsx
|
|
17153
|
+
async function codeCommand(opts = {}) {
|
|
17154
|
+
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-YRY4HPMZ.js");
|
|
17155
|
+
const rootDir = resolve10(opts.dir ?? process.cwd());
|
|
17156
|
+
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename2(rootDir))}`;
|
|
17157
|
+
const tools = new ToolRegistry();
|
|
17158
|
+
const jobs2 = new JobRegistry();
|
|
17159
|
+
const registerRootedTools = (root) => {
|
|
17160
|
+
registerFilesystemTools(tools, { rootDir: root });
|
|
17161
|
+
registerShellTools(tools, {
|
|
17162
|
+
rootDir: root,
|
|
17163
|
+
// Per-project "always allow" list persisted from prior ShellConfirm
|
|
17164
|
+
// choices; merged on top of the built-in allowlist in shell.ts.
|
|
17165
|
+
// GETTER form — re-read every dispatch so a prefix the user adds
|
|
17166
|
+
// via ShellConfirm mid-session takes effect on the next shell call
|
|
17167
|
+
// instead of waiting for `/new` or a relaunch.
|
|
17168
|
+
extraAllowed: () => loadProjectShellAllowed(root),
|
|
17169
|
+
// `yolo` edit-mode disables shell confirmations entirely. Re-read
|
|
17170
|
+
// from config on each dispatch so /mode yolo (or Shift+Tab cycling
|
|
17171
|
+
// through to it) flips the gate live without forcing a relaunch.
|
|
17172
|
+
allowAll: () => loadEditMode() === "yolo",
|
|
17173
|
+
jobs: jobs2
|
|
17174
|
+
});
|
|
17175
|
+
registerMemoryTools(tools, { projectRoot: root });
|
|
17176
|
+
};
|
|
17177
|
+
registerRootedTools(rootDir);
|
|
17178
|
+
registerPlanTool(tools);
|
|
17179
|
+
registerChoiceTool(tools);
|
|
17180
|
+
const semantic2 = await bootstrapSemanticSearchInCodeMode(tools, rootDir);
|
|
17181
|
+
process.stderr.write(
|
|
17182
|
+
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native tool(s)${semantic2.enabled ? " \xB7 semantic_search on" : ""}
|
|
17183
|
+
`
|
|
17184
|
+
);
|
|
17185
|
+
process.once("exit", () => {
|
|
17186
|
+
void jobs2.shutdown();
|
|
17187
|
+
});
|
|
17188
|
+
await chatCommand({
|
|
17189
|
+
model: opts.model ?? "deepseek-v4-flash",
|
|
17190
|
+
harvest: opts.harvest ?? false,
|
|
17191
|
+
system: codeSystemPrompt2(rootDir, { hasSemanticSearch: semantic2.enabled }),
|
|
17192
|
+
transcript: opts.transcript,
|
|
17193
|
+
session,
|
|
17194
|
+
seedTools: tools,
|
|
17195
|
+
codeMode: { rootDir, jobs: jobs2, reregisterTools: registerRootedTools },
|
|
17196
|
+
forceResume: opts.forceResume,
|
|
17197
|
+
forceNew: opts.forceNew
|
|
17198
|
+
});
|
|
17199
|
+
}
|
|
17200
|
+
|
|
17201
|
+
// src/cli/commands/diff.ts
|
|
17202
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
17203
|
+
import { basename as basename3 } from "path";
|
|
17204
|
+
import { render as render2 } from "ink";
|
|
17205
|
+
import React30 from "react";
|
|
17206
|
+
|
|
17207
|
+
// src/cli/ui/DiffApp.tsx
|
|
17208
|
+
import { Box as Box26, Static as Static2, Text as Text24, useApp as useApp3, useInput } from "ink";
|
|
17209
|
+
import React29, { useState as useState13 } from "react";
|
|
17210
|
+
|
|
17211
|
+
// src/cli/ui/RecordView.tsx
|
|
17212
|
+
import { Box as Box25, Text as Text23 } from "ink";
|
|
17213
|
+
import React28 from "react";
|
|
17214
|
+
function RecordView({ rec, compact: compact2 = false }) {
|
|
17215
|
+
const toolArgsMax = compact2 ? 120 : 200;
|
|
17216
|
+
const toolContentMax = compact2 ? 200 : 400;
|
|
17217
|
+
if (rec.role === "user") {
|
|
15874
17218
|
const content = rec.content.includes("\n") ? rec.content.split("\n").join("\n ") : rec.content;
|
|
15875
|
-
return /* @__PURE__ */
|
|
17219
|
+
return /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React28.createElement(Text23, null, content));
|
|
15876
17220
|
}
|
|
15877
17221
|
if (rec.role === "assistant_final") {
|
|
15878
|
-
return /* @__PURE__ */
|
|
17222
|
+
return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React28.createElement(Box25, null, /* @__PURE__ */ React28.createElement(Text23, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React28.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React28.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React28.createElement(Text23, null, rec.content) : /* @__PURE__ */ React28.createElement(Text23, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
15879
17223
|
}
|
|
15880
17224
|
if (rec.role === "tool") {
|
|
15881
|
-
return /* @__PURE__ */
|
|
17225
|
+
return /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " args: ", truncate2(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \u2192 ", truncate2(rec.content, toolContentMax)));
|
|
15882
17226
|
}
|
|
15883
17227
|
if (rec.role === "error") {
|
|
15884
|
-
return /* @__PURE__ */
|
|
17228
|
+
return /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React28.createElement(Text23, { color: "red" }, rec.error ?? rec.content));
|
|
15885
17229
|
}
|
|
15886
17230
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
15887
17231
|
return null;
|
|
15888
17232
|
}
|
|
15889
|
-
return /* @__PURE__ */
|
|
17233
|
+
return /* @__PURE__ */ React28.createElement(Box25, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
15890
17234
|
}
|
|
15891
17235
|
function CacheBadge({ usage }) {
|
|
15892
17236
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -15895,7 +17239,7 @@ function CacheBadge({ usage }) {
|
|
|
15895
17239
|
if (total === 0) return null;
|
|
15896
17240
|
const pct2 = hit / total * 100;
|
|
15897
17241
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
15898
|
-
return /* @__PURE__ */
|
|
17242
|
+
return /* @__PURE__ */ React28.createElement(Text23, null, /* @__PURE__ */ React28.createElement(Text23, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React28.createElement(Text23, { color }, pct2.toFixed(1), "%"));
|
|
15899
17243
|
}
|
|
15900
17244
|
function truncate2(s, max) {
|
|
15901
17245
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -15929,7 +17273,7 @@ function DiffApp({ report }) {
|
|
|
15929
17273
|
}
|
|
15930
17274
|
});
|
|
15931
17275
|
const pair = report.pairs[idx];
|
|
15932
|
-
return /* @__PURE__ */
|
|
17276
|
+
return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column" }, /* @__PURE__ */ React29.createElement(DiffHeader, { report }), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React29.createElement(Text24, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React29.createElement(Text24, null, pair ? /* @__PURE__ */ React29.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React29.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React29.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React29.createElement(Text24, null, pair.divergenceNote)) : null, /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "j"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "k"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "N"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "g"), "/", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "q"), " ", "quit")));
|
|
15933
17277
|
}
|
|
15934
17278
|
function DiffHeader({ report }) {
|
|
15935
17279
|
const a = report.a;
|
|
@@ -15947,15 +17291,15 @@ function DiffHeader({ report }) {
|
|
|
15947
17291
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
15948
17292
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
15949
17293
|
}
|
|
15950
|
-
return /* @__PURE__ */
|
|
17294
|
+
return /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React29.createElement(Box26, { justifyContent: "space-between" }, /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React29.createElement(Text24, { color: "blue" }, a.label), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " vs B="), /* @__PURE__ */ React29.createElement(Text24, { color: "magenta" }, b.label)), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "cache "), /* @__PURE__ */ React29.createElement(Text24, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React29.createElement(Text24, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React29.createElement(Text24, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "cost "), /* @__PURE__ */ React29.createElement(Text24, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React29.createElement(Text24, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React29.createElement(Text24, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React29.createElement(Text24, null, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true }, "model calls "), /* @__PURE__ */ React29.createElement(Text24, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
15951
17295
|
}
|
|
15952
17296
|
function Pane({
|
|
15953
17297
|
label,
|
|
15954
17298
|
headerColor,
|
|
15955
17299
|
records
|
|
15956
17300
|
}) {
|
|
15957
|
-
return /* @__PURE__ */
|
|
15958
|
-
|
|
17301
|
+
return /* @__PURE__ */ React29.createElement(
|
|
17302
|
+
Box26,
|
|
15959
17303
|
{
|
|
15960
17304
|
flexDirection: "column",
|
|
15961
17305
|
flexGrow: 1,
|
|
@@ -15963,21 +17307,21 @@ function Pane({
|
|
|
15963
17307
|
borderStyle: "single",
|
|
15964
17308
|
borderColor: headerColor
|
|
15965
17309
|
},
|
|
15966
|
-
/* @__PURE__ */
|
|
15967
|
-
records.length === 0 ? /* @__PURE__ */
|
|
17310
|
+
/* @__PURE__ */ React29.createElement(Text24, { color: headerColor, bold: true }, label),
|
|
17311
|
+
records.length === 0 ? /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React29.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React29.createElement(RecordView, { key, rec, compact: true }))
|
|
15968
17312
|
);
|
|
15969
17313
|
}
|
|
15970
17314
|
function KindBadge({ kind }) {
|
|
15971
17315
|
if (kind === "match") {
|
|
15972
|
-
return /* @__PURE__ */
|
|
17316
|
+
return /* @__PURE__ */ React29.createElement(Text24, { color: "green" }, "\u2713 match");
|
|
15973
17317
|
}
|
|
15974
17318
|
if (kind === "diverge") {
|
|
15975
|
-
return /* @__PURE__ */
|
|
17319
|
+
return /* @__PURE__ */ React29.createElement(Text24, { color: "yellow" }, "\u2605 diverge");
|
|
15976
17320
|
}
|
|
15977
17321
|
if (kind === "only_in_a") {
|
|
15978
|
-
return /* @__PURE__ */
|
|
17322
|
+
return /* @__PURE__ */ React29.createElement(Text24, { color: "blue" }, "\u2190 only in A");
|
|
15979
17323
|
}
|
|
15980
|
-
return /* @__PURE__ */
|
|
17324
|
+
return /* @__PURE__ */ React29.createElement(Text24, { color: "magenta" }, "\u2192 only in B");
|
|
15981
17325
|
}
|
|
15982
17326
|
function paneRecords(pair, side) {
|
|
15983
17327
|
if (!pair) return [];
|
|
@@ -16008,7 +17352,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
16008
17352
|
return;
|
|
16009
17353
|
}
|
|
16010
17354
|
if (wantTui) {
|
|
16011
|
-
const { waitUntilExit } = render2(
|
|
17355
|
+
const { waitUntilExit } = render2(React30.createElement(DiffApp, { report }), {
|
|
16012
17356
|
exitOnCtrlC: true,
|
|
16013
17357
|
patchConsole: false
|
|
16014
17358
|
});
|
|
@@ -16018,6 +17362,200 @@ markdown report written to ${opts.mdPath}`);
|
|
|
16018
17362
|
console.log(renderSummaryTable(report));
|
|
16019
17363
|
}
|
|
16020
17364
|
|
|
17365
|
+
// src/cli/commands/index.ts
|
|
17366
|
+
import { resolve as resolve11 } from "path";
|
|
17367
|
+
|
|
17368
|
+
// src/index/semantic/preflight.ts
|
|
17369
|
+
import { stdin as stdin2, stdout } from "process";
|
|
17370
|
+
import { createInterface } from "readline/promises";
|
|
17371
|
+
async function ollamaPreflight(opts) {
|
|
17372
|
+
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
17373
|
+
const status2 = await checkOllamaStatus(opts.model, opts.baseUrl);
|
|
17374
|
+
if (!status2.binaryFound) {
|
|
17375
|
+
log(t("ollamaNotFound"));
|
|
17376
|
+
return false;
|
|
17377
|
+
}
|
|
17378
|
+
if (!status2.daemonRunning) {
|
|
17379
|
+
if (!opts.interactive && !opts.yesToAll) {
|
|
17380
|
+
log(t("daemonNotReachableHint"));
|
|
17381
|
+
return false;
|
|
17382
|
+
}
|
|
17383
|
+
const ok = opts.yesToAll || await confirm(t("daemonStartConfirm"), true);
|
|
17384
|
+
if (!ok) {
|
|
17385
|
+
log(t("daemonAbortStart"));
|
|
17386
|
+
return false;
|
|
17387
|
+
}
|
|
17388
|
+
log(t("daemonStarting"));
|
|
17389
|
+
const started = await startOllamaDaemon({ baseUrl: opts.baseUrl, timeoutMs: 15e3 });
|
|
17390
|
+
if (!started.ready) {
|
|
17391
|
+
log(t("daemonStartTimeout"));
|
|
17392
|
+
return false;
|
|
17393
|
+
}
|
|
17394
|
+
log(t("daemonReady", { pid: started.pid ? ` (pid ${started.pid})` : "" }));
|
|
17395
|
+
}
|
|
17396
|
+
const after = status2.daemonRunning ? status2 : await checkOllamaStatus(opts.model, opts.baseUrl);
|
|
17397
|
+
if (!after.modelPulled) {
|
|
17398
|
+
if (!opts.interactive && !opts.yesToAll) {
|
|
17399
|
+
log(t("modelNotPulledHint", { model: opts.model }));
|
|
17400
|
+
return false;
|
|
17401
|
+
}
|
|
17402
|
+
const ok = opts.yesToAll || await confirm(t("modelPullConfirm", { model: opts.model }), true);
|
|
17403
|
+
if (!ok) {
|
|
17404
|
+
log(t("modelAbortPull"));
|
|
17405
|
+
return false;
|
|
17406
|
+
}
|
|
17407
|
+
log(t("modelPulling", { model: opts.model }));
|
|
17408
|
+
const ESC = String.fromCharCode(27);
|
|
17409
|
+
const ANSI_CSI = new RegExp(`${ESC}\\[[0-9;]*[A-Za-z]`, "g");
|
|
17410
|
+
const code = await pullOllamaModel(opts.model, {
|
|
17411
|
+
onLine: (line) => {
|
|
17412
|
+
const cleaned = line.replace(ANSI_CSI, "").trim();
|
|
17413
|
+
if (cleaned.length === 0) return;
|
|
17414
|
+
log(` ${cleaned}
|
|
17415
|
+
`);
|
|
17416
|
+
}
|
|
17417
|
+
});
|
|
17418
|
+
if (code !== 0) {
|
|
17419
|
+
log(t("modelPullFailed", { model: opts.model, code }));
|
|
17420
|
+
return false;
|
|
17421
|
+
}
|
|
17422
|
+
log(t("modelPulled", { model: opts.model }));
|
|
17423
|
+
}
|
|
17424
|
+
return true;
|
|
17425
|
+
}
|
|
17426
|
+
async function confirm(question, defaultYes) {
|
|
17427
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
17428
|
+
const rl = createInterface({ input: stdin2, output: stdout });
|
|
17429
|
+
try {
|
|
17430
|
+
const raw = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
17431
|
+
if (raw === "") return defaultYes;
|
|
17432
|
+
return raw === "y" || raw === "yes";
|
|
17433
|
+
} finally {
|
|
17434
|
+
rl.close();
|
|
17435
|
+
}
|
|
17436
|
+
}
|
|
17437
|
+
|
|
17438
|
+
// src/cli/commands/index.ts
|
|
17439
|
+
async function indexCommand(opts = {}) {
|
|
17440
|
+
const root = resolve11(opts.dir ?? process.cwd());
|
|
17441
|
+
const tty = process.stderr.isTTY === true && process.stdin.isTTY === true;
|
|
17442
|
+
const model2 = opts.model ?? process.env.REASONIX_EMBED_MODEL ?? "nomic-embed-text";
|
|
17443
|
+
const preflightOk = await ollamaPreflight({
|
|
17444
|
+
model: model2,
|
|
17445
|
+
baseUrl: opts.ollamaUrl,
|
|
17446
|
+
interactive: tty && !opts.yes,
|
|
17447
|
+
yesToAll: opts.yes ?? false
|
|
17448
|
+
});
|
|
17449
|
+
if (!preflightOk) process.exit(1);
|
|
17450
|
+
const writer = makeProgressWriter(tty);
|
|
17451
|
+
const t0 = Date.now();
|
|
17452
|
+
let result;
|
|
17453
|
+
try {
|
|
17454
|
+
result = await buildIndex(root, {
|
|
17455
|
+
rebuild: opts.rebuild,
|
|
17456
|
+
model: model2,
|
|
17457
|
+
baseUrl: opts.ollamaUrl,
|
|
17458
|
+
onProgress: (p) => writer.update(p)
|
|
17459
|
+
});
|
|
17460
|
+
} catch (err) {
|
|
17461
|
+
writer.clear();
|
|
17462
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17463
|
+
process.stderr.write(t("indexFailed", { msg }));
|
|
17464
|
+
process.exit(1);
|
|
17465
|
+
}
|
|
17466
|
+
writer.clear();
|
|
17467
|
+
const seconds = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
17468
|
+
const successKey = result.chunksSkipped > 0 ? "indexSuccessWithSkips" : "indexSuccess";
|
|
17469
|
+
process.stderr.write(
|
|
17470
|
+
t(successKey, {
|
|
17471
|
+
scanned: result.filesScanned,
|
|
17472
|
+
changed: result.filesChanged,
|
|
17473
|
+
added: result.chunksAdded,
|
|
17474
|
+
removed: result.chunksRemoved,
|
|
17475
|
+
skipped: result.chunksSkipped,
|
|
17476
|
+
seconds
|
|
17477
|
+
})
|
|
17478
|
+
);
|
|
17479
|
+
if (result.filesChanged === 0 && !opts.rebuild) {
|
|
17480
|
+
process.stderr.write(t("indexNothingToDo"));
|
|
17481
|
+
}
|
|
17482
|
+
}
|
|
17483
|
+
var SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17484
|
+
var SPINNER_INTERVAL_MS = 120;
|
|
17485
|
+
function makeProgressWriter(tty) {
|
|
17486
|
+
if (!tty) return makeNonTtyWriter();
|
|
17487
|
+
return makeTtyWriter();
|
|
17488
|
+
}
|
|
17489
|
+
function makeNonTtyWriter() {
|
|
17490
|
+
let lastPhase = null;
|
|
17491
|
+
let lastChunks = 0;
|
|
17492
|
+
return {
|
|
17493
|
+
update(p) {
|
|
17494
|
+
if (p.phase !== lastPhase) {
|
|
17495
|
+
lastPhase = p.phase;
|
|
17496
|
+
if (p.phase === "scan") {
|
|
17497
|
+
process.stderr.write(t("progressScanLine"));
|
|
17498
|
+
} else if (p.phase === "embed") {
|
|
17499
|
+
process.stderr.write(
|
|
17500
|
+
t("progressEmbedLine", {
|
|
17501
|
+
total: p.chunksTotal ?? 0,
|
|
17502
|
+
files: p.filesChanged ?? 0
|
|
17503
|
+
})
|
|
17504
|
+
);
|
|
17505
|
+
}
|
|
17506
|
+
}
|
|
17507
|
+
if (p.phase === "embed" && p.chunksDone !== void 0 && p.chunksDone - lastChunks >= 50) {
|
|
17508
|
+
lastChunks = p.chunksDone;
|
|
17509
|
+
process.stderr.write(
|
|
17510
|
+
t("progressEmbedHeartbeat", {
|
|
17511
|
+
done: p.chunksDone,
|
|
17512
|
+
total: p.chunksTotal ?? "?"
|
|
17513
|
+
})
|
|
17514
|
+
);
|
|
17515
|
+
}
|
|
17516
|
+
},
|
|
17517
|
+
clear() {
|
|
17518
|
+
}
|
|
17519
|
+
};
|
|
17520
|
+
}
|
|
17521
|
+
function makeTtyWriter() {
|
|
17522
|
+
let status2 = t("progressStarting");
|
|
17523
|
+
let lastLineLen = 0;
|
|
17524
|
+
let frameIdx = 0;
|
|
17525
|
+
const startTs = Date.now();
|
|
17526
|
+
const repaint = () => {
|
|
17527
|
+
const frame = SPINNER_FRAMES2[frameIdx % SPINNER_FRAMES2.length];
|
|
17528
|
+
frameIdx++;
|
|
17529
|
+
const elapsed = ((Date.now() - startTs) / 1e3).toFixed(1);
|
|
17530
|
+
const line = `${frame} ${status2} ${elapsed}s`;
|
|
17531
|
+
const padded = line + " ".repeat(Math.max(0, lastLineLen - line.length));
|
|
17532
|
+
process.stderr.write(`\r${padded}`);
|
|
17533
|
+
lastLineLen = line.length;
|
|
17534
|
+
};
|
|
17535
|
+
repaint();
|
|
17536
|
+
const interval = setInterval(repaint, SPINNER_INTERVAL_MS);
|
|
17537
|
+
return {
|
|
17538
|
+
update(p) {
|
|
17539
|
+
if (p.phase === "scan") {
|
|
17540
|
+
status2 = t("progressScan", { files: p.filesScanned ?? 0 });
|
|
17541
|
+
} else if (p.phase === "embed") {
|
|
17542
|
+
const done = p.chunksDone ?? 0;
|
|
17543
|
+
const total = p.chunksTotal ?? 0;
|
|
17544
|
+
const pct2 = total > 0 ? (done / total * 100).toFixed(0) : "0";
|
|
17545
|
+
status2 = t("progressEmbed", { done, total, pct: pct2 });
|
|
17546
|
+
}
|
|
17547
|
+
repaint();
|
|
17548
|
+
},
|
|
17549
|
+
clear() {
|
|
17550
|
+
clearInterval(interval);
|
|
17551
|
+
if (lastLineLen > 0) {
|
|
17552
|
+
process.stderr.write(`\r${" ".repeat(lastLineLen)}\r`);
|
|
17553
|
+
lastLineLen = 0;
|
|
17554
|
+
}
|
|
17555
|
+
}
|
|
17556
|
+
};
|
|
17557
|
+
}
|
|
17558
|
+
|
|
16021
17559
|
// src/cli/commands/mcp-inspect.ts
|
|
16022
17560
|
async function mcpInspectCommand(opts) {
|
|
16023
17561
|
const spec = parseMcpSpec(opts.spec);
|
|
@@ -16064,9 +17602,9 @@ function formatSection(title, section, render5) {
|
|
|
16064
17602
|
for (const item of section.items) lines.push(` ${render5(item)}`);
|
|
16065
17603
|
return lines.join("\n");
|
|
16066
17604
|
}
|
|
16067
|
-
function toolLine(
|
|
16068
|
-
const desc =
|
|
16069
|
-
return `\xB7 ${
|
|
17605
|
+
function toolLine(t2) {
|
|
17606
|
+
const desc = t2.description ? ` \u2014 ${oneLine(t2.description, 80)}` : "";
|
|
17607
|
+
return `\xB7 ${t2.name}${desc}`;
|
|
16070
17608
|
}
|
|
16071
17609
|
function resourceLine(r) {
|
|
16072
17610
|
const mime = r.mimeType ? ` [${r.mimeType}]` : "";
|
|
@@ -16149,11 +17687,11 @@ function pad2(s, width) {
|
|
|
16149
17687
|
|
|
16150
17688
|
// src/cli/commands/replay.ts
|
|
16151
17689
|
import { render as render3 } from "ink";
|
|
16152
|
-
import
|
|
17690
|
+
import React32 from "react";
|
|
16153
17691
|
|
|
16154
17692
|
// src/cli/ui/ReplayApp.tsx
|
|
16155
|
-
import { Box as
|
|
16156
|
-
import
|
|
17693
|
+
import { Box as Box27, Static as Static3, Text as Text25, useApp as useApp4, useInput as useInput2 } from "ink";
|
|
17694
|
+
import React31, { useMemo as useMemo4, useState as useState14 } from "react";
|
|
16157
17695
|
function ReplayApp({ meta, pages }) {
|
|
16158
17696
|
const { exit: exit2 } = useApp4();
|
|
16159
17697
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
@@ -16193,14 +17731,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
16193
17731
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
16194
17732
|
const currentPage = pages[idx];
|
|
16195
17733
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
16196
|
-
return /* @__PURE__ */
|
|
17734
|
+
return /* @__PURE__ */ React31.createElement(Box27, { flexDirection: "column" }, /* @__PURE__ */ React31.createElement(
|
|
16197
17735
|
StatsPanel,
|
|
16198
17736
|
{
|
|
16199
17737
|
summary,
|
|
16200
17738
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
16201
17739
|
prefixHash
|
|
16202
17740
|
}
|
|
16203
|
-
), /* @__PURE__ */
|
|
17741
|
+
), /* @__PURE__ */ React31.createElement(Box27, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React31.createElement(Box27, { justifyContent: "space-between" }, /* @__PURE__ */ React31.createElement(Text25, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React31.createElement(Text25, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React31.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React31.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React31.createElement(Text25, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React31.createElement(Box27, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React31.createElement(Text25, { dimColor: true }, /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "j"), "/", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "k"), "/", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React31.createElement(Text25, { bold: true }, "q"), " quit")));
|
|
16204
17742
|
}
|
|
16205
17743
|
|
|
16206
17744
|
// src/cli/commands/replay.ts
|
|
@@ -16212,7 +17750,7 @@ async function replayCommand(opts) {
|
|
|
16212
17750
|
}
|
|
16213
17751
|
const { parsed } = replayFromFile(opts.path);
|
|
16214
17752
|
const pages = groupRecordsByTurn(parsed.records);
|
|
16215
|
-
const { waitUntilExit } = render3(
|
|
17753
|
+
const { waitUntilExit } = render3(React32.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
16216
17754
|
exitOnCtrlC: true,
|
|
16217
17755
|
patchConsole: false
|
|
16218
17756
|
});
|
|
@@ -16306,12 +17844,12 @@ function oneLine2(s, max = 200) {
|
|
|
16306
17844
|
}
|
|
16307
17845
|
|
|
16308
17846
|
// src/cli/commands/run.ts
|
|
16309
|
-
import { stdin as
|
|
16310
|
-
import { createInterface } from "readline/promises";
|
|
17847
|
+
import { stdin as stdin3, stdout as stdout2 } from "process";
|
|
17848
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
16311
17849
|
async function ensureApiKey() {
|
|
16312
17850
|
const existing = loadApiKey();
|
|
16313
17851
|
if (existing) return existing;
|
|
16314
|
-
if (!
|
|
17852
|
+
if (!stdin3.isTTY) {
|
|
16315
17853
|
process.stderr.write(
|
|
16316
17854
|
"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
17855
|
);
|
|
@@ -16320,7 +17858,7 @@ async function ensureApiKey() {
|
|
|
16320
17858
|
process.stdout.write(
|
|
16321
17859
|
"DeepSeek API key not configured.\nGet one at https://platform.deepseek.com/api_keys\n"
|
|
16322
17860
|
);
|
|
16323
|
-
const rl =
|
|
17861
|
+
const rl = createInterface2({ input: stdin3, output: stdout2 });
|
|
16324
17862
|
try {
|
|
16325
17863
|
while (true) {
|
|
16326
17864
|
const answer = (await rl.question("API key \u203A ")).trim();
|
|
@@ -16471,14 +18009,14 @@ function listAll() {
|
|
|
16471
18009
|
console.log("Resume: reasonix chat --session <name>");
|
|
16472
18010
|
}
|
|
16473
18011
|
function inspectSession(name, verbose) {
|
|
16474
|
-
const
|
|
18012
|
+
const path5 = sessionPath(name);
|
|
16475
18013
|
const messages = loadSessionMessages(name);
|
|
16476
18014
|
if (messages.length === 0) {
|
|
16477
18015
|
console.error(`no session named "${name}" (or it's empty).`);
|
|
16478
|
-
console.error(`looked at: ${
|
|
18016
|
+
console.error(`looked at: ${path5}`);
|
|
16479
18017
|
process.exit(1);
|
|
16480
18018
|
}
|
|
16481
|
-
console.log(`[session] ${name} ${messages.length} messages ${
|
|
18019
|
+
console.log(`[session] ${name} ${messages.length} messages ${path5}`);
|
|
16482
18020
|
console.log("");
|
|
16483
18021
|
let turnIndex = 0;
|
|
16484
18022
|
for (const msg of messages) {
|
|
@@ -16517,12 +18055,12 @@ function truncate3(s, max) {
|
|
|
16517
18055
|
|
|
16518
18056
|
// src/cli/commands/setup.tsx
|
|
16519
18057
|
import { render as render4 } from "ink";
|
|
16520
|
-
import
|
|
18058
|
+
import React34 from "react";
|
|
16521
18059
|
|
|
16522
18060
|
// src/cli/ui/Wizard.tsx
|
|
16523
|
-
import { Box as
|
|
18061
|
+
import { Box as Box28, Text as Text26, useApp as useApp5, useInput as useInput3 } from "ink";
|
|
16524
18062
|
import TextInput2 from "ink-text-input";
|
|
16525
|
-
import
|
|
18063
|
+
import React33, { useState as useState15 } from "react";
|
|
16526
18064
|
|
|
16527
18065
|
// src/cli/ui/presets.ts
|
|
16528
18066
|
var PRESETS = {
|
|
@@ -16570,7 +18108,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16570
18108
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
16571
18109
|
});
|
|
16572
18110
|
if (step === "apiKey") {
|
|
16573
|
-
return /* @__PURE__ */
|
|
18111
|
+
return /* @__PURE__ */ React33.createElement(
|
|
16574
18112
|
ApiKeyStep,
|
|
16575
18113
|
{
|
|
16576
18114
|
onSubmit: (key) => {
|
|
@@ -16584,7 +18122,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16584
18122
|
);
|
|
16585
18123
|
}
|
|
16586
18124
|
if (step === "preset") {
|
|
16587
|
-
return /* @__PURE__ */
|
|
18125
|
+
return /* @__PURE__ */ React33.createElement(StepFrame, { title: "Pick a preset", step: 1, total: 3 }, /* @__PURE__ */ React33.createElement(
|
|
16588
18126
|
SingleSelect,
|
|
16589
18127
|
{
|
|
16590
18128
|
items: presetItems(),
|
|
@@ -16594,10 +18132,10 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16594
18132
|
setStep("mcp");
|
|
16595
18133
|
}
|
|
16596
18134
|
}
|
|
16597
|
-
), /* @__PURE__ */
|
|
18135
|
+
), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "[\u2191\u2193] navigate \xB7 [Enter] confirm \xB7 [Esc] cancel")));
|
|
16598
18136
|
}
|
|
16599
18137
|
if (step === "mcp") {
|
|
16600
|
-
return /* @__PURE__ */
|
|
18138
|
+
return /* @__PURE__ */ React33.createElement(StepFrame, { title: "Which MCP servers should Reasonix wire up for you?", step: 2, total: 3 }, /* @__PURE__ */ React33.createElement(
|
|
16601
18139
|
MultiSelect,
|
|
16602
18140
|
{
|
|
16603
18141
|
items: mcpItems(),
|
|
@@ -16622,7 +18160,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16622
18160
|
}
|
|
16623
18161
|
const currentName = pending[0];
|
|
16624
18162
|
const entry = CATALOG_BY_NAME.get(currentName);
|
|
16625
|
-
return /* @__PURE__ */
|
|
18163
|
+
return /* @__PURE__ */ React33.createElement(
|
|
16626
18164
|
McpArgsStep,
|
|
16627
18165
|
{
|
|
16628
18166
|
entry,
|
|
@@ -16640,7 +18178,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16640
18178
|
}
|
|
16641
18179
|
if (step === "review") {
|
|
16642
18180
|
const specs = data.selectedCatalog.map((name) => buildSpec(name, data.catalogArgs));
|
|
16643
|
-
return /* @__PURE__ */
|
|
18181
|
+
return /* @__PURE__ */ React33.createElement(StepFrame, { title: "Ready to save", step: 3, total: 3 }, /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column" }, /* @__PURE__ */ React33.createElement(SummaryLine, { label: "API key", value: redactKey(data.apiKey) }), /* @__PURE__ */ React33.createElement(SummaryLine, { label: "Preset", value: data.preset }), /* @__PURE__ */ React33.createElement(
|
|
16644
18182
|
SummaryLine,
|
|
16645
18183
|
{
|
|
16646
18184
|
label: "MCP",
|
|
@@ -16648,8 +18186,8 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16648
18186
|
}
|
|
16649
18187
|
), specs.map((spec, i) => (
|
|
16650
18188
|
// biome-ignore lint/suspicious/noArrayIndexKey: review-only render, order fixed
|
|
16651
|
-
/* @__PURE__ */
|
|
16652
|
-
)), /* @__PURE__ */
|
|
18189
|
+
/* @__PURE__ */ React33.createElement(Box28, { key: i, paddingLeft: 14 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "\xB7 ", spec))
|
|
18190
|
+
)), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Saves to ", defaultConfigPath())), error ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { color: "red" }, error)) : null, /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "[Enter] save \xB7 [Esc] cancel"))), /* @__PURE__ */ React33.createElement(
|
|
16653
18191
|
ReviewConfirm,
|
|
16654
18192
|
{
|
|
16655
18193
|
onConfirm: () => {
|
|
@@ -16675,7 +18213,7 @@ function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
|
16675
18213
|
}
|
|
16676
18214
|
));
|
|
16677
18215
|
}
|
|
16678
|
-
return /* @__PURE__ */
|
|
18216
|
+
return /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "green" }, "\u25B8 Saved."), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Run `reasonix` any time to start chatting \u2014 your settings are remembered.")), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "[Enter] to exit")), /* @__PURE__ */ React33.createElement(ExitOnEnter, { onExit: exit2 }));
|
|
16679
18217
|
}
|
|
16680
18218
|
function ApiKeyStep({
|
|
16681
18219
|
onSubmit,
|
|
@@ -16683,7 +18221,7 @@ function ApiKeyStep({
|
|
|
16683
18221
|
onError
|
|
16684
18222
|
}) {
|
|
16685
18223
|
const [value, setValue] = useState15("");
|
|
16686
|
-
return /* @__PURE__ */
|
|
18224
|
+
return /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React33.createElement(
|
|
16687
18225
|
TextInput2,
|
|
16688
18226
|
{
|
|
16689
18227
|
value,
|
|
@@ -16700,7 +18238,7 @@ function ApiKeyStep({
|
|
|
16700
18238
|
mask: "\u2022",
|
|
16701
18239
|
placeholder: "sk-..."
|
|
16702
18240
|
}
|
|
16703
|
-
)), error ? /* @__PURE__ */
|
|
18241
|
+
)), error ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { color: "red" }, error)) : value ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "preview: ", redactKey(value))) : null);
|
|
16704
18242
|
}
|
|
16705
18243
|
function McpArgsStep({
|
|
16706
18244
|
entry,
|
|
@@ -16709,7 +18247,7 @@ function McpArgsStep({
|
|
|
16709
18247
|
onError
|
|
16710
18248
|
}) {
|
|
16711
18249
|
const [value, setValue] = useState15("");
|
|
16712
|
-
return /* @__PURE__ */
|
|
18250
|
+
return /* @__PURE__ */ React33.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column" }, /* @__PURE__ */ React33.createElement(Text26, null, entry.summary), entry.note ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, null, "Required parameter: "), /* @__PURE__ */ React33.createElement(Text26, { bold: true }, entry.userArgs)), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React33.createElement(
|
|
16713
18251
|
TextInput2,
|
|
16714
18252
|
{
|
|
16715
18253
|
value,
|
|
@@ -16725,7 +18263,7 @@ function McpArgsStep({
|
|
|
16725
18263
|
},
|
|
16726
18264
|
placeholder: placeholderFor(entry)
|
|
16727
18265
|
}
|
|
16728
|
-
)), error ? /* @__PURE__ */
|
|
18266
|
+
)), error ? /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1 }, /* @__PURE__ */ React33.createElement(Text26, { color: "red" }, error)) : null));
|
|
16729
18267
|
}
|
|
16730
18268
|
function ReviewConfirm({ onConfirm }) {
|
|
16731
18269
|
useInput3((_i, key) => {
|
|
@@ -16745,10 +18283,10 @@ function StepFrame({
|
|
|
16745
18283
|
total,
|
|
16746
18284
|
children
|
|
16747
18285
|
}) {
|
|
16748
|
-
return /* @__PURE__ */
|
|
18286
|
+
return /* @__PURE__ */ React33.createElement(Box28, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React33.createElement(Box28, null, /* @__PURE__ */ React33.createElement(Text26, { dimColor: true }, "Step ", step, "/", total, " \xB7", " "), /* @__PURE__ */ React33.createElement(Text26, { bold: true, color: "cyan" }, title)), /* @__PURE__ */ React33.createElement(Box28, { marginTop: 1, flexDirection: "column" }, children));
|
|
16749
18287
|
}
|
|
16750
18288
|
function SummaryLine({ label, value }) {
|
|
16751
|
-
return /* @__PURE__ */
|
|
18289
|
+
return /* @__PURE__ */ React33.createElement(Box28, null, /* @__PURE__ */ React33.createElement(Text26, null, label.padEnd(12)), /* @__PURE__ */ React33.createElement(Text26, { bold: true }, value));
|
|
16752
18290
|
}
|
|
16753
18291
|
function presetItems() {
|
|
16754
18292
|
return ["fast", "smart", "max"].map((name) => ({
|
|
@@ -16804,7 +18342,7 @@ async function setupCommand(_opts = {}) {
|
|
|
16804
18342
|
const existingKey = loadApiKey();
|
|
16805
18343
|
const existing = readConfig();
|
|
16806
18344
|
const { waitUntilExit, unmount } = render4(
|
|
16807
|
-
/* @__PURE__ */
|
|
18345
|
+
/* @__PURE__ */ React34.createElement(
|
|
16808
18346
|
Wizard,
|
|
16809
18347
|
{
|
|
16810
18348
|
existingApiKey: existingKey,
|
|
@@ -16822,7 +18360,7 @@ async function setupCommand(_opts = {}) {
|
|
|
16822
18360
|
}
|
|
16823
18361
|
|
|
16824
18362
|
// src/cli/commands/update.ts
|
|
16825
|
-
import { spawn as
|
|
18363
|
+
import { spawn as spawn6 } from "child_process";
|
|
16826
18364
|
function planUpdate(input) {
|
|
16827
18365
|
const diff = compareVersions(input.current, input.latest);
|
|
16828
18366
|
if (diff > 0) {
|
|
@@ -16852,13 +18390,13 @@ function planUpdate(input) {
|
|
|
16852
18390
|
};
|
|
16853
18391
|
}
|
|
16854
18392
|
function defaultSpawn(argv) {
|
|
16855
|
-
return new Promise((
|
|
16856
|
-
const child =
|
|
18393
|
+
return new Promise((resolve12, reject) => {
|
|
18394
|
+
const child = spawn6(argv[0], argv.slice(1), {
|
|
16857
18395
|
stdio: "inherit",
|
|
16858
18396
|
shell: process.platform === "win32"
|
|
16859
18397
|
});
|
|
16860
18398
|
child.once("error", reject);
|
|
16861
|
-
child.once("exit", (code) =>
|
|
18399
|
+
child.once("exit", (code) => resolve12(code ?? 1));
|
|
16862
18400
|
});
|
|
16863
18401
|
}
|
|
16864
18402
|
async function updateCommand(opts = {}) {
|
|
@@ -17154,6 +18692,16 @@ program.command("update").description(
|
|
|
17154
18692
|
).option("--dry-run", "Print the plan without executing the install").action(async (opts) => {
|
|
17155
18693
|
await updateCommand({ dryRun: !!opts.dryRun });
|
|
17156
18694
|
});
|
|
18695
|
+
program.command("index").description(
|
|
18696
|
+
"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."
|
|
18697
|
+
).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(
|
|
18698
|
+
"-y, --yes",
|
|
18699
|
+
"Skip preflight prompts \u2014 auto-start the daemon and pull the model if missing (use in scripts)"
|
|
18700
|
+
).action(
|
|
18701
|
+
async (opts) => {
|
|
18702
|
+
await indexCommand(opts);
|
|
18703
|
+
}
|
|
18704
|
+
);
|
|
17157
18705
|
program.parseAsync(process.argv).catch((err) => {
|
|
17158
18706
|
console.error(err);
|
|
17159
18707
|
process.exit(1);
|