reasonix 0.4.6 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +622 -137
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +127 -14
- package/dist/index.js +437 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -96,8 +96,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
96
96
|
}
|
|
97
97
|
function sleep(ms, signal) {
|
|
98
98
|
if (ms <= 0) return Promise.resolve();
|
|
99
|
-
return new Promise((
|
|
100
|
-
const timer = setTimeout(
|
|
99
|
+
return new Promise((resolve5, reject) => {
|
|
100
|
+
const timer = setTimeout(resolve5, ms);
|
|
101
101
|
if (signal) {
|
|
102
102
|
const onAbort = () => {
|
|
103
103
|
clearTimeout(timer);
|
|
@@ -563,7 +563,7 @@ var ToolRegistry = class {
|
|
|
563
563
|
}
|
|
564
564
|
}));
|
|
565
565
|
}
|
|
566
|
-
async dispatch(name, argumentsRaw) {
|
|
566
|
+
async dispatch(name, argumentsRaw, opts = {}) {
|
|
567
567
|
const tool = this._tools.get(name);
|
|
568
568
|
if (!tool) {
|
|
569
569
|
return JSON.stringify({ error: `unknown tool: ${name}` });
|
|
@@ -580,7 +580,7 @@ var ToolRegistry = class {
|
|
|
580
580
|
args = nestArguments(args);
|
|
581
581
|
}
|
|
582
582
|
try {
|
|
583
|
-
const result = await tool.fn(args);
|
|
583
|
+
const result = await tool.fn(args, { signal: opts.signal });
|
|
584
584
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
585
585
|
} catch (err) {
|
|
586
586
|
return JSON.stringify({
|
|
@@ -614,8 +614,20 @@ async function bridgeMcpTools(client, opts = {}) {
|
|
|
614
614
|
name: registeredName,
|
|
615
615
|
description: mcpTool.description ?? "",
|
|
616
616
|
parameters: mcpTool.inputSchema,
|
|
617
|
-
fn: async (args) => {
|
|
618
|
-
const toolResult = await client.callTool(mcpTool.name, args
|
|
617
|
+
fn: async (args, ctx) => {
|
|
618
|
+
const toolResult = await client.callTool(mcpTool.name, args, {
|
|
619
|
+
// Forward server-side progress frames to the bridge caller,
|
|
620
|
+
// tagged with the registered name so multi-server UIs can
|
|
621
|
+
// disambiguate. No-op when `onProgress` isn't configured —
|
|
622
|
+
// the client then also omits the _meta.progressToken and
|
|
623
|
+
// the server won't emit progress.
|
|
624
|
+
onProgress: opts.onProgress ? (info) => opts.onProgress({ toolName: registeredName, ...info }) : void 0,
|
|
625
|
+
// Thread the tool-dispatch AbortSignal all the way down to
|
|
626
|
+
// the MCP request so Esc truly cancels in flight — the
|
|
627
|
+
// client will emit notifications/cancelled AND reject the
|
|
628
|
+
// pending promise immediately, no "wait for subprocess".
|
|
629
|
+
signal: ctx?.signal
|
|
630
|
+
});
|
|
619
631
|
return flattenMcpResult(toolResult, { maxChars: maxResultChars });
|
|
620
632
|
}
|
|
621
633
|
});
|
|
@@ -1204,11 +1216,13 @@ var CacheFirstLoop = class {
|
|
|
1204
1216
|
_turn = 0;
|
|
1205
1217
|
_streamPreference;
|
|
1206
1218
|
/**
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1209
|
-
*
|
|
1219
|
+
* AbortController per active turn. Threaded through the DeepSeek
|
|
1220
|
+
* HTTP calls AND every tool dispatch so Esc actually cancels the
|
|
1221
|
+
* in-flight network/subprocess work — not "we'll get to it after
|
|
1222
|
+
* the current call finishes." Re-created at the start of each
|
|
1223
|
+
* `step()` (the prior turn's signal has already fired).
|
|
1210
1224
|
*/
|
|
1211
|
-
|
|
1225
|
+
_turnAbort = new AbortController();
|
|
1212
1226
|
constructor(opts) {
|
|
1213
1227
|
this.client = opts.client;
|
|
1214
1228
|
this.prefix = opts.prefix;
|
|
@@ -1321,14 +1335,15 @@ var CacheFirstLoop = class {
|
|
|
1321
1335
|
return msgs;
|
|
1322
1336
|
}
|
|
1323
1337
|
/**
|
|
1324
|
-
* Signal the currently-running {@link step}
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1338
|
+
* Signal the currently-running {@link step} to stop **now**. Cancels
|
|
1339
|
+
* the in-flight network request (DeepSeek HTTP/SSE) AND any tool call
|
|
1340
|
+
* currently dispatching (MCP `notifications/cancelled` + promise
|
|
1341
|
+
* reject). The loop itself also sees `signal.aborted` at each
|
|
1342
|
+
* iteration boundary and exits quickly instead of looping again.
|
|
1343
|
+
* Called by the TUI on Esc.
|
|
1329
1344
|
*/
|
|
1330
1345
|
abort() {
|
|
1331
|
-
this.
|
|
1346
|
+
this._turnAbort.abort();
|
|
1332
1347
|
}
|
|
1333
1348
|
/**
|
|
1334
1349
|
* Drop everything in the log after (and including) the most recent
|
|
@@ -1366,13 +1381,14 @@ var CacheFirstLoop = class {
|
|
|
1366
1381
|
async *step(userInput) {
|
|
1367
1382
|
this._turn++;
|
|
1368
1383
|
this.scratch.reset();
|
|
1369
|
-
this.
|
|
1384
|
+
this._turnAbort = new AbortController();
|
|
1385
|
+
const signal = this._turnAbort.signal;
|
|
1370
1386
|
let pendingUser = userInput;
|
|
1371
1387
|
const toolSpecs = this.prefix.tools();
|
|
1372
1388
|
const warnAt = Math.max(1, Math.floor(this.maxToolIters * 0.7));
|
|
1373
1389
|
let warnedForIterBudget = false;
|
|
1374
1390
|
for (let iter = 0; iter < this.maxToolIters; iter++) {
|
|
1375
|
-
if (
|
|
1391
|
+
if (signal.aborted) {
|
|
1376
1392
|
yield {
|
|
1377
1393
|
turn: this._turn,
|
|
1378
1394
|
role: "warning",
|
|
@@ -1435,7 +1451,8 @@ var CacheFirstLoop = class {
|
|
|
1435
1451
|
{
|
|
1436
1452
|
model: this.model,
|
|
1437
1453
|
messages,
|
|
1438
|
-
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1454
|
+
tools: toolSpecs.length ? toolSpecs : void 0,
|
|
1455
|
+
signal
|
|
1439
1456
|
},
|
|
1440
1457
|
{
|
|
1441
1458
|
...this.branchOptions,
|
|
@@ -1444,8 +1461,8 @@ var CacheFirstLoop = class {
|
|
|
1444
1461
|
}
|
|
1445
1462
|
);
|
|
1446
1463
|
for (let k = 0; k < budget; k++) {
|
|
1447
|
-
const sample = queue.shift() ?? await new Promise((
|
|
1448
|
-
waiter =
|
|
1464
|
+
const sample = queue.shift() ?? await new Promise((resolve5) => {
|
|
1465
|
+
waiter = resolve5;
|
|
1449
1466
|
});
|
|
1450
1467
|
yield {
|
|
1451
1468
|
turn: this._turn,
|
|
@@ -1485,7 +1502,8 @@ var CacheFirstLoop = class {
|
|
|
1485
1502
|
for await (const chunk of this.client.stream({
|
|
1486
1503
|
model: this.model,
|
|
1487
1504
|
messages,
|
|
1488
|
-
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1505
|
+
tools: toolSpecs.length ? toolSpecs : void 0,
|
|
1506
|
+
signal
|
|
1489
1507
|
})) {
|
|
1490
1508
|
if (chunk.contentDelta) {
|
|
1491
1509
|
assistantContent += chunk.contentDelta;
|
|
@@ -1524,7 +1542,8 @@ var CacheFirstLoop = class {
|
|
|
1524
1542
|
const resp = await this.client.chat({
|
|
1525
1543
|
model: this.model,
|
|
1526
1544
|
messages,
|
|
1527
|
-
tools: toolSpecs.length ? toolSpecs : void 0
|
|
1545
|
+
tools: toolSpecs.length ? toolSpecs : void 0,
|
|
1546
|
+
signal
|
|
1528
1547
|
});
|
|
1529
1548
|
assistantContent = resp.content;
|
|
1530
1549
|
reasoningContent = resp.reasoningContent ?? "";
|
|
@@ -1588,7 +1607,7 @@ var CacheFirstLoop = class {
|
|
|
1588
1607
|
toolName: name,
|
|
1589
1608
|
toolArgs: args
|
|
1590
1609
|
};
|
|
1591
|
-
const result = await this.tools.dispatch(name, args);
|
|
1610
|
+
const result = await this.tools.dispatch(name, args, { signal });
|
|
1592
1611
|
this.appendAndPersist({
|
|
1593
1612
|
role: "tool",
|
|
1594
1613
|
tool_call_id: call.id ?? "",
|
|
@@ -1615,8 +1634,9 @@ var CacheFirstLoop = class {
|
|
|
1615
1634
|
});
|
|
1616
1635
|
const resp = await this.client.chat({
|
|
1617
1636
|
model: this.model,
|
|
1618
|
-
messages
|
|
1637
|
+
messages,
|
|
1619
1638
|
// no tools → model is forced to answer in text
|
|
1639
|
+
signal: this._turnAbort.signal
|
|
1620
1640
|
});
|
|
1621
1641
|
const rawContent = resp.content?.trim() ?? "";
|
|
1622
1642
|
const cleaned = stripHallucinatedToolMarkup(rawContent);
|
|
@@ -1712,13 +1732,298 @@ function formatLoopError(err) {
|
|
|
1712
1732
|
return msg;
|
|
1713
1733
|
}
|
|
1714
1734
|
|
|
1735
|
+
// src/tools/filesystem.ts
|
|
1736
|
+
import { promises as fs } from "fs";
|
|
1737
|
+
import * as pathMod from "path";
|
|
1738
|
+
var DEFAULT_MAX_READ_BYTES = 2 * 1024 * 1024;
|
|
1739
|
+
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
1740
|
+
function registerFilesystemTools(registry, opts) {
|
|
1741
|
+
const rootDir = pathMod.resolve(opts.rootDir);
|
|
1742
|
+
const allowWriting = opts.allowWriting !== false;
|
|
1743
|
+
const maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
1744
|
+
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
1745
|
+
const safePath = (raw) => {
|
|
1746
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
1747
|
+
throw new Error("path must be a non-empty string");
|
|
1748
|
+
}
|
|
1749
|
+
const resolved = pathMod.resolve(rootDir, raw);
|
|
1750
|
+
const normRoot = pathMod.resolve(rootDir);
|
|
1751
|
+
const rel = pathMod.relative(normRoot, resolved);
|
|
1752
|
+
if (rel.startsWith("..") || pathMod.isAbsolute(rel)) {
|
|
1753
|
+
throw new Error(`path escapes sandbox root (${normRoot}): ${raw}`);
|
|
1754
|
+
}
|
|
1755
|
+
return resolved;
|
|
1756
|
+
};
|
|
1757
|
+
registry.register({
|
|
1758
|
+
name: "read_file",
|
|
1759
|
+
description: "Read a file under the sandbox root. Returns the full contents (truncated with a notice if larger than the per-call cap). Paths may be relative to the root or absolute-under-root.",
|
|
1760
|
+
parameters: {
|
|
1761
|
+
type: "object",
|
|
1762
|
+
properties: {
|
|
1763
|
+
path: { type: "string", description: "Path to read (relative to rootDir or absolute)." },
|
|
1764
|
+
head: { type: "integer", description: "If set, return only the first N lines." },
|
|
1765
|
+
tail: { type: "integer", description: "If set, return only the last N lines." }
|
|
1766
|
+
},
|
|
1767
|
+
required: ["path"]
|
|
1768
|
+
},
|
|
1769
|
+
fn: async (args) => {
|
|
1770
|
+
const abs = safePath(args.path);
|
|
1771
|
+
const stat = await fs.stat(abs);
|
|
1772
|
+
if (stat.isDirectory()) {
|
|
1773
|
+
throw new Error(`not a file: ${args.path} (it's a directory)`);
|
|
1774
|
+
}
|
|
1775
|
+
const raw = await fs.readFile(abs);
|
|
1776
|
+
if (raw.length > maxReadBytes) {
|
|
1777
|
+
const head = raw.slice(0, maxReadBytes).toString("utf8");
|
|
1778
|
+
return `${head}
|
|
1779
|
+
|
|
1780
|
+
[\u2026truncated ${raw.length - maxReadBytes} bytes \u2014 file is ${raw.length} B, cap ${maxReadBytes} B. Retry with head/tail for targeted view.]`;
|
|
1781
|
+
}
|
|
1782
|
+
const text = raw.toString("utf8");
|
|
1783
|
+
if (typeof args.head === "number" && args.head > 0) {
|
|
1784
|
+
return text.split(/\r?\n/).slice(0, args.head).join("\n");
|
|
1785
|
+
}
|
|
1786
|
+
if (typeof args.tail === "number" && args.tail > 0) {
|
|
1787
|
+
let lines = text.split(/\r?\n/);
|
|
1788
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
1789
|
+
return lines.slice(Math.max(0, lines.length - args.tail)).join("\n");
|
|
1790
|
+
}
|
|
1791
|
+
return text;
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
registry.register({
|
|
1795
|
+
name: "list_directory",
|
|
1796
|
+
description: "List entries in a directory under the sandbox root. Returns one line per entry, marking directories with a trailing slash. Not recursive \u2014 use directory_tree for that.",
|
|
1797
|
+
parameters: {
|
|
1798
|
+
type: "object",
|
|
1799
|
+
properties: {
|
|
1800
|
+
path: { type: "string", description: "Directory to list (default: root)." }
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
fn: async (args) => {
|
|
1804
|
+
const abs = safePath(args.path ?? ".");
|
|
1805
|
+
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
1806
|
+
const lines = [];
|
|
1807
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1808
|
+
lines.push(e.isDirectory() ? `${e.name}/` : e.name);
|
|
1809
|
+
}
|
|
1810
|
+
return lines.join("\n") || "(empty directory)";
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
registry.register({
|
|
1814
|
+
name: "directory_tree",
|
|
1815
|
+
description: "Recursively list entries in a directory. Shows indented tree structure with directories marked '/'. Caps output so a huge tree doesn't drown the context.",
|
|
1816
|
+
parameters: {
|
|
1817
|
+
type: "object",
|
|
1818
|
+
properties: {
|
|
1819
|
+
path: { type: "string", description: "Root of the tree (default: sandbox root)." },
|
|
1820
|
+
maxDepth: { type: "integer", description: "Max recursion depth (default 4)." }
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1823
|
+
fn: async (args) => {
|
|
1824
|
+
const startAbs = safePath(args.path ?? ".");
|
|
1825
|
+
const maxDepth = typeof args.maxDepth === "number" ? args.maxDepth : 4;
|
|
1826
|
+
const lines = [];
|
|
1827
|
+
let totalBytes = 0;
|
|
1828
|
+
let truncated = false;
|
|
1829
|
+
const walk2 = async (dir, depth) => {
|
|
1830
|
+
if (truncated) return;
|
|
1831
|
+
if (depth > maxDepth) return;
|
|
1832
|
+
let entries;
|
|
1833
|
+
try {
|
|
1834
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1835
|
+
} catch {
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1839
|
+
for (const e of entries) {
|
|
1840
|
+
if (truncated) return;
|
|
1841
|
+
const indent = " ".repeat(depth);
|
|
1842
|
+
const line = e.isDirectory() ? `${indent}${e.name}/` : `${indent}${e.name}`;
|
|
1843
|
+
totalBytes += line.length + 1;
|
|
1844
|
+
if (totalBytes > maxListBytes) {
|
|
1845
|
+
lines.push(` [\u2026 tree truncated at ${maxListBytes} bytes \u2026]`);
|
|
1846
|
+
truncated = true;
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
lines.push(line);
|
|
1850
|
+
if (e.isDirectory()) {
|
|
1851
|
+
await walk2(pathMod.join(dir, e.name), depth + 1);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1855
|
+
await walk2(startAbs, 0);
|
|
1856
|
+
return lines.join("\n") || "(empty tree)";
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
registry.register({
|
|
1860
|
+
name: "search_files",
|
|
1861
|
+
description: "Find files whose NAME matches a substring or regex. Case-insensitive. Walks the directory recursively under the sandbox root. Returns one path per line.",
|
|
1862
|
+
parameters: {
|
|
1863
|
+
type: "object",
|
|
1864
|
+
properties: {
|
|
1865
|
+
path: { type: "string", description: "Directory to start the search at (default: root)." },
|
|
1866
|
+
pattern: {
|
|
1867
|
+
type: "string",
|
|
1868
|
+
description: "Substring (or regex) to match against filenames."
|
|
1869
|
+
}
|
|
1870
|
+
},
|
|
1871
|
+
required: ["pattern"]
|
|
1872
|
+
},
|
|
1873
|
+
fn: async (args) => {
|
|
1874
|
+
const startAbs = safePath(args.path ?? ".");
|
|
1875
|
+
const needle = args.pattern.toLowerCase();
|
|
1876
|
+
let re = null;
|
|
1877
|
+
try {
|
|
1878
|
+
re = new RegExp(args.pattern, "i");
|
|
1879
|
+
} catch {
|
|
1880
|
+
re = null;
|
|
1881
|
+
}
|
|
1882
|
+
const matches = [];
|
|
1883
|
+
let totalBytes = 0;
|
|
1884
|
+
const walk2 = async (dir) => {
|
|
1885
|
+
let entries;
|
|
1886
|
+
try {
|
|
1887
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1888
|
+
} catch {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
for (const e of entries) {
|
|
1892
|
+
const full = pathMod.join(dir, e.name);
|
|
1893
|
+
const lower = e.name.toLowerCase();
|
|
1894
|
+
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
1895
|
+
if (hit) {
|
|
1896
|
+
const rel = pathMod.relative(rootDir, full);
|
|
1897
|
+
if (totalBytes + rel.length + 1 > maxListBytes) {
|
|
1898
|
+
matches.push("[\u2026 search truncated \u2014 refine pattern \u2026]");
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
matches.push(rel);
|
|
1902
|
+
totalBytes += rel.length + 1;
|
|
1903
|
+
}
|
|
1904
|
+
if (e.isDirectory()) await walk2(full);
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
await walk2(startAbs);
|
|
1908
|
+
return matches.length === 0 ? "(no matches)" : matches.join("\n");
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
registry.register({
|
|
1912
|
+
name: "get_file_info",
|
|
1913
|
+
description: "Stat a path under the sandbox root. Returns type (file|directory|symlink), size in bytes, mtime in ISO-8601.",
|
|
1914
|
+
parameters: {
|
|
1915
|
+
type: "object",
|
|
1916
|
+
properties: {
|
|
1917
|
+
path: { type: "string" }
|
|
1918
|
+
},
|
|
1919
|
+
required: ["path"]
|
|
1920
|
+
},
|
|
1921
|
+
fn: async (args) => {
|
|
1922
|
+
const abs = safePath(args.path);
|
|
1923
|
+
const st = await fs.lstat(abs);
|
|
1924
|
+
const type = st.isDirectory() ? "directory" : st.isSymbolicLink() ? "symlink" : "file";
|
|
1925
|
+
return JSON.stringify({
|
|
1926
|
+
type,
|
|
1927
|
+
size: st.size,
|
|
1928
|
+
mtime: st.mtime.toISOString()
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
if (!allowWriting) return registry;
|
|
1933
|
+
registry.register({
|
|
1934
|
+
name: "write_file",
|
|
1935
|
+
description: "Create or overwrite a file under the sandbox root with the given content. Parent directories are created as needed.",
|
|
1936
|
+
parameters: {
|
|
1937
|
+
type: "object",
|
|
1938
|
+
properties: {
|
|
1939
|
+
path: { type: "string" },
|
|
1940
|
+
content: { type: "string" }
|
|
1941
|
+
},
|
|
1942
|
+
required: ["path", "content"]
|
|
1943
|
+
},
|
|
1944
|
+
fn: async (args) => {
|
|
1945
|
+
const abs = safePath(args.path);
|
|
1946
|
+
await fs.mkdir(pathMod.dirname(abs), { recursive: true });
|
|
1947
|
+
await fs.writeFile(abs, args.content, "utf8");
|
|
1948
|
+
return `wrote ${args.content.length} chars to ${pathMod.relative(rootDir, abs)}`;
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
registry.register({
|
|
1952
|
+
name: "edit_file",
|
|
1953
|
+
description: "Apply a SEARCH/REPLACE edit to an existing file. `search` must match exactly (whitespace sensitive) \u2014 no regex. The match must be unique in the file; otherwise the edit is refused to avoid surprise rewrites. This flat-string shape replaces the `{oldText, newText}[]` JSON array form that previously triggered R1 DSML hallucinations.",
|
|
1954
|
+
parameters: {
|
|
1955
|
+
type: "object",
|
|
1956
|
+
properties: {
|
|
1957
|
+
path: { type: "string" },
|
|
1958
|
+
search: { type: "string", description: "Exact text to find (must be unique)." },
|
|
1959
|
+
replace: { type: "string", description: "Text to substitute in place of `search`." }
|
|
1960
|
+
},
|
|
1961
|
+
required: ["path", "search", "replace"]
|
|
1962
|
+
},
|
|
1963
|
+
fn: async (args) => {
|
|
1964
|
+
const abs = safePath(args.path);
|
|
1965
|
+
const before = await fs.readFile(abs, "utf8");
|
|
1966
|
+
if (args.search.length === 0) {
|
|
1967
|
+
throw new Error("edit_file: search cannot be empty");
|
|
1968
|
+
}
|
|
1969
|
+
const firstIdx = before.indexOf(args.search);
|
|
1970
|
+
if (firstIdx < 0) {
|
|
1971
|
+
throw new Error(`edit_file: search text not found in ${pathMod.relative(rootDir, abs)}`);
|
|
1972
|
+
}
|
|
1973
|
+
const nextIdx = before.indexOf(args.search, firstIdx + 1);
|
|
1974
|
+
if (nextIdx >= 0) {
|
|
1975
|
+
throw new Error(
|
|
1976
|
+
`edit_file: search text appears multiple times in ${pathMod.relative(rootDir, abs)} \u2014 include more context to disambiguate`
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
const after = before.slice(0, firstIdx) + args.replace + before.slice(firstIdx + args.search.length);
|
|
1980
|
+
await fs.writeFile(abs, after, "utf8");
|
|
1981
|
+
return `edited ${pathMod.relative(rootDir, abs)} (${args.search.length}\u2192${args.replace.length} chars)`;
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
registry.register({
|
|
1985
|
+
name: "create_directory",
|
|
1986
|
+
description: "Create a directory (and any missing parents) under the sandbox root.",
|
|
1987
|
+
parameters: {
|
|
1988
|
+
type: "object",
|
|
1989
|
+
properties: { path: { type: "string" } },
|
|
1990
|
+
required: ["path"]
|
|
1991
|
+
},
|
|
1992
|
+
fn: async (args) => {
|
|
1993
|
+
const abs = safePath(args.path);
|
|
1994
|
+
await fs.mkdir(abs, { recursive: true });
|
|
1995
|
+
return `created ${pathMod.relative(rootDir, abs)}/`;
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
registry.register({
|
|
1999
|
+
name: "move_file",
|
|
2000
|
+
description: "Rename/move a file or directory under the sandbox root.",
|
|
2001
|
+
parameters: {
|
|
2002
|
+
type: "object",
|
|
2003
|
+
properties: {
|
|
2004
|
+
source: { type: "string" },
|
|
2005
|
+
destination: { type: "string" }
|
|
2006
|
+
},
|
|
2007
|
+
required: ["source", "destination"]
|
|
2008
|
+
},
|
|
2009
|
+
fn: async (args) => {
|
|
2010
|
+
const src = safePath(args.source);
|
|
2011
|
+
const dst = safePath(args.destination);
|
|
2012
|
+
await fs.mkdir(pathMod.dirname(dst), { recursive: true });
|
|
2013
|
+
await fs.rename(src, dst);
|
|
2014
|
+
return `moved ${pathMod.relative(rootDir, src)} \u2192 ${pathMod.relative(rootDir, dst)}`;
|
|
2015
|
+
}
|
|
2016
|
+
});
|
|
2017
|
+
return registry;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
1715
2020
|
// src/env.ts
|
|
1716
2021
|
import { readFileSync as readFileSync3 } from "fs";
|
|
1717
|
-
import { resolve } from "path";
|
|
2022
|
+
import { resolve as resolve2 } from "path";
|
|
1718
2023
|
function loadDotenv(path = ".env") {
|
|
1719
2024
|
let raw;
|
|
1720
2025
|
try {
|
|
1721
|
-
raw = readFileSync3(
|
|
2026
|
+
raw = readFileSync3(resolve2(process.cwd(), path), "utf8");
|
|
1722
2027
|
} catch {
|
|
1723
2028
|
return;
|
|
1724
2029
|
}
|
|
@@ -2279,6 +2584,13 @@ var McpClient = class {
|
|
|
2279
2584
|
_serverInfo = { name: "", version: "" };
|
|
2280
2585
|
_protocolVersion = "";
|
|
2281
2586
|
_instructions;
|
|
2587
|
+
// Progress-token → handler for notifications/progress routing. Tokens
|
|
2588
|
+
// are minted per call when the caller supplies an onProgress
|
|
2589
|
+
// callback; cleared when the final response lands (or the pending
|
|
2590
|
+
// request rejects). No leaks — the `try/finally` in callTool
|
|
2591
|
+
// guarantees cleanup even on timeout.
|
|
2592
|
+
progressHandlers = /* @__PURE__ */ new Map();
|
|
2593
|
+
nextProgressToken = 1;
|
|
2282
2594
|
constructor(opts) {
|
|
2283
2595
|
this.transport = opts.transport;
|
|
2284
2596
|
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: "0.3.0-dev" };
|
|
@@ -2333,13 +2645,36 @@ var McpClient = class {
|
|
|
2333
2645
|
this.assertInitialized();
|
|
2334
2646
|
return this.request("tools/list", {});
|
|
2335
2647
|
}
|
|
2336
|
-
/**
|
|
2337
|
-
|
|
2648
|
+
/**
|
|
2649
|
+
* Invoke a tool by name. When `onProgress` is supplied, attaches a
|
|
2650
|
+
* fresh progress token so the server can send incremental updates
|
|
2651
|
+
* via `notifications/progress`; they're routed to the callback until
|
|
2652
|
+
* the final response arrives (or the request times out, in which
|
|
2653
|
+
* case the handler is simply dropped — no extra notification).
|
|
2654
|
+
*
|
|
2655
|
+
* When `signal` is supplied, aborting it:
|
|
2656
|
+
* 1) fires `notifications/cancelled` to the server (MCP 2024-11-05
|
|
2657
|
+
* way of saying "forget this request, I no longer care"), and
|
|
2658
|
+
* 2) rejects the pending promise immediately with an AbortError,
|
|
2659
|
+
* so the caller doesn't have to wait for the subprocess to
|
|
2660
|
+
* finish its in-flight file write or network request.
|
|
2661
|
+
* The server MAY still emit a late response; we drop it in dispatch
|
|
2662
|
+
* since the request id is gone from `pending`.
|
|
2663
|
+
*/
|
|
2664
|
+
async callTool(name, args, opts = {}) {
|
|
2338
2665
|
this.assertInitialized();
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2666
|
+
const params = { name, arguments: args ?? {} };
|
|
2667
|
+
let token;
|
|
2668
|
+
if (opts.onProgress) {
|
|
2669
|
+
token = this.nextProgressToken++;
|
|
2670
|
+
this.progressHandlers.set(token, opts.onProgress);
|
|
2671
|
+
params._meta = { progressToken: token };
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
return await this.request("tools/call", params, opts.signal);
|
|
2675
|
+
} finally {
|
|
2676
|
+
if (token !== void 0) this.progressHandlers.delete(token);
|
|
2677
|
+
}
|
|
2343
2678
|
}
|
|
2344
2679
|
/**
|
|
2345
2680
|
* List resources the server exposes. Supports a pagination cursor;
|
|
@@ -2393,24 +2728,56 @@ var McpClient = class {
|
|
|
2393
2728
|
assertInitialized() {
|
|
2394
2729
|
if (!this.initialized) throw new Error("MCP client not initialized \u2014 call initialize() first");
|
|
2395
2730
|
}
|
|
2396
|
-
async request(method, params) {
|
|
2731
|
+
async request(method, params, signal) {
|
|
2397
2732
|
const id = this.nextId++;
|
|
2398
2733
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
2399
|
-
|
|
2734
|
+
let abortHandler = null;
|
|
2735
|
+
const promise = new Promise((resolve5, reject) => {
|
|
2400
2736
|
const timeout = setTimeout(() => {
|
|
2401
2737
|
this.pending.delete(id);
|
|
2738
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2402
2739
|
reject(
|
|
2403
2740
|
new Error(`MCP request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`)
|
|
2404
2741
|
);
|
|
2405
2742
|
}, this.requestTimeoutMs);
|
|
2406
2743
|
this.pending.set(id, {
|
|
2407
|
-
resolve:
|
|
2744
|
+
resolve: resolve5,
|
|
2408
2745
|
reject,
|
|
2409
2746
|
timeout
|
|
2410
2747
|
});
|
|
2748
|
+
if (signal) {
|
|
2749
|
+
if (signal.aborted) {
|
|
2750
|
+
this.pending.delete(id);
|
|
2751
|
+
clearTimeout(timeout);
|
|
2752
|
+
reject(new Error(`MCP request ${method} (id=${id}) aborted before send`));
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
abortHandler = () => {
|
|
2756
|
+
this.pending.delete(id);
|
|
2757
|
+
clearTimeout(timeout);
|
|
2758
|
+
void this.transport.send({
|
|
2759
|
+
jsonrpc: "2.0",
|
|
2760
|
+
method: "notifications/cancelled",
|
|
2761
|
+
params: { requestId: id, reason: "aborted by user" }
|
|
2762
|
+
}).catch(() => {
|
|
2763
|
+
});
|
|
2764
|
+
reject(new Error(`MCP request ${method} (id=${id}) aborted by user`));
|
|
2765
|
+
};
|
|
2766
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
2767
|
+
}
|
|
2411
2768
|
});
|
|
2412
|
-
|
|
2413
|
-
|
|
2769
|
+
try {
|
|
2770
|
+
await this.transport.send(frame);
|
|
2771
|
+
} catch (err) {
|
|
2772
|
+
this.pending.delete(id);
|
|
2773
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2774
|
+
throw err;
|
|
2775
|
+
}
|
|
2776
|
+
try {
|
|
2777
|
+
return await promise;
|
|
2778
|
+
} finally {
|
|
2779
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
2780
|
+
}
|
|
2414
2781
|
}
|
|
2415
2782
|
startReaderIfNeeded() {
|
|
2416
2783
|
if (this.readerStarted) return;
|
|
@@ -2431,7 +2798,16 @@ var McpClient = class {
|
|
|
2431
2798
|
}
|
|
2432
2799
|
}
|
|
2433
2800
|
dispatch(msg) {
|
|
2434
|
-
if (!("id" in msg) || msg.id === null || msg.id === void 0)
|
|
2801
|
+
if (!("id" in msg) || msg.id === null || msg.id === void 0) {
|
|
2802
|
+
if ("method" in msg && msg.method === "notifications/progress") {
|
|
2803
|
+
const p = msg.params;
|
|
2804
|
+
if (!p || p.progressToken === void 0) return;
|
|
2805
|
+
const handler = this.progressHandlers.get(p.progressToken);
|
|
2806
|
+
if (!handler) return;
|
|
2807
|
+
handler({ progress: p.progress, total: p.total, message: p.message });
|
|
2808
|
+
}
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2435
2811
|
if (!("result" in msg) && !("error" in msg)) return;
|
|
2436
2812
|
const pending = this.pending.get(msg.id);
|
|
2437
2813
|
if (!pending) return;
|
|
@@ -2488,12 +2864,12 @@ var StdioTransport = class {
|
|
|
2488
2864
|
}
|
|
2489
2865
|
async send(message) {
|
|
2490
2866
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
2491
|
-
return new Promise((
|
|
2867
|
+
return new Promise((resolve5, reject) => {
|
|
2492
2868
|
const line = `${JSON.stringify(message)}
|
|
2493
2869
|
`;
|
|
2494
2870
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
2495
2871
|
if (err) reject(err);
|
|
2496
|
-
else
|
|
2872
|
+
else resolve5();
|
|
2497
2873
|
});
|
|
2498
2874
|
});
|
|
2499
2875
|
}
|
|
@@ -2504,8 +2880,8 @@ var StdioTransport = class {
|
|
|
2504
2880
|
continue;
|
|
2505
2881
|
}
|
|
2506
2882
|
if (this.closed) return;
|
|
2507
|
-
const next = await new Promise((
|
|
2508
|
-
this.waiters.push(
|
|
2883
|
+
const next = await new Promise((resolve5) => {
|
|
2884
|
+
this.waiters.push(resolve5);
|
|
2509
2885
|
});
|
|
2510
2886
|
if (next === null) return;
|
|
2511
2887
|
yield next;
|
|
@@ -2571,8 +2947,8 @@ var SseTransport = class {
|
|
|
2571
2947
|
constructor(opts) {
|
|
2572
2948
|
this.url = opts.url;
|
|
2573
2949
|
this.headers = opts.headers ?? {};
|
|
2574
|
-
this.endpointReady = new Promise((
|
|
2575
|
-
this.resolveEndpoint =
|
|
2950
|
+
this.endpointReady = new Promise((resolve5, reject) => {
|
|
2951
|
+
this.resolveEndpoint = resolve5;
|
|
2576
2952
|
this.rejectEndpoint = reject;
|
|
2577
2953
|
});
|
|
2578
2954
|
this.endpointReady.catch(() => void 0);
|
|
@@ -2599,8 +2975,8 @@ var SseTransport = class {
|
|
|
2599
2975
|
continue;
|
|
2600
2976
|
}
|
|
2601
2977
|
if (this.closed) return;
|
|
2602
|
-
const next = await new Promise((
|
|
2603
|
-
this.waiters.push(
|
|
2978
|
+
const next = await new Promise((resolve5) => {
|
|
2979
|
+
this.waiters.push(resolve5);
|
|
2604
2980
|
});
|
|
2605
2981
|
if (next === null) return;
|
|
2606
2982
|
yield next;
|
|
@@ -2800,7 +3176,7 @@ async function trySection(load) {
|
|
|
2800
3176
|
|
|
2801
3177
|
// src/code/edit-blocks.ts
|
|
2802
3178
|
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2803
|
-
import { dirname as
|
|
3179
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
2804
3180
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
2805
3181
|
function parseEditBlocks(text) {
|
|
2806
3182
|
const out = [];
|
|
@@ -2818,8 +3194,8 @@ function parseEditBlocks(text) {
|
|
|
2818
3194
|
return out;
|
|
2819
3195
|
}
|
|
2820
3196
|
function applyEditBlock(block, rootDir) {
|
|
2821
|
-
const absRoot =
|
|
2822
|
-
const absTarget =
|
|
3197
|
+
const absRoot = resolve3(rootDir);
|
|
3198
|
+
const absTarget = resolve3(absRoot, block.path);
|
|
2823
3199
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
2824
3200
|
return {
|
|
2825
3201
|
path: block.path,
|
|
@@ -2838,7 +3214,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
2838
3214
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
2839
3215
|
};
|
|
2840
3216
|
}
|
|
2841
|
-
mkdirSync3(
|
|
3217
|
+
mkdirSync3(dirname4(absTarget), { recursive: true });
|
|
2842
3218
|
writeFileSync3(absTarget, block.replace, "utf8");
|
|
2843
3219
|
return { path: block.path, status: "created" };
|
|
2844
3220
|
}
|
|
@@ -2869,13 +3245,13 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
2869
3245
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
2870
3246
|
}
|
|
2871
3247
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
2872
|
-
const absRoot =
|
|
3248
|
+
const absRoot = resolve3(rootDir);
|
|
2873
3249
|
const seen = /* @__PURE__ */ new Set();
|
|
2874
3250
|
const snapshots = [];
|
|
2875
3251
|
for (const b of blocks) {
|
|
2876
3252
|
if (seen.has(b.path)) continue;
|
|
2877
3253
|
seen.add(b.path);
|
|
2878
|
-
const abs =
|
|
3254
|
+
const abs = resolve3(absRoot, b.path);
|
|
2879
3255
|
if (!existsSync2(abs)) {
|
|
2880
3256
|
snapshots.push({ path: b.path, prevContent: null });
|
|
2881
3257
|
continue;
|
|
@@ -2889,9 +3265,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
2889
3265
|
return snapshots;
|
|
2890
3266
|
}
|
|
2891
3267
|
function restoreSnapshots(snapshots, rootDir) {
|
|
2892
|
-
const absRoot =
|
|
3268
|
+
const absRoot = resolve3(rootDir);
|
|
2893
3269
|
return snapshots.map((snap) => {
|
|
2894
|
-
const abs =
|
|
3270
|
+
const abs = resolve3(absRoot, snap.path);
|
|
2895
3271
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
2896
3272
|
return {
|
|
2897
3273
|
path: snap.path,
|
|
@@ -2928,11 +3304,11 @@ var VERSION = "0.4.3";
|
|
|
2928
3304
|
|
|
2929
3305
|
// src/cli/commands/chat.tsx
|
|
2930
3306
|
import { render } from "ink";
|
|
2931
|
-
import React9, { useState as
|
|
3307
|
+
import React9, { useState as useState5 } from "react";
|
|
2932
3308
|
|
|
2933
3309
|
// src/cli/ui/App.tsx
|
|
2934
|
-
import { Box as Box7, Static, Text as Text7, useApp, useInput } from "ink";
|
|
2935
|
-
import React7, { useCallback, useEffect as
|
|
3310
|
+
import { Box as Box7, Static, Text as Text7, useApp, useInput as useInput2 } from "ink";
|
|
3311
|
+
import React7, { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
|
|
2936
3312
|
|
|
2937
3313
|
// src/cli/ui/EventLog.tsx
|
|
2938
3314
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
@@ -3271,9 +3647,44 @@ function truncate2(s, max) {
|
|
|
3271
3647
|
}
|
|
3272
3648
|
|
|
3273
3649
|
// src/cli/ui/PromptInput.tsx
|
|
3274
|
-
import { Box as Box4, Text as Text4 } from "ink";
|
|
3275
|
-
import
|
|
3276
|
-
|
|
3650
|
+
import { Box as Box4, Text as Text4, useInput } from "ink";
|
|
3651
|
+
import React4, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
3652
|
+
|
|
3653
|
+
// src/cli/ui/multiline-keys.ts
|
|
3654
|
+
var BACKSLASH_SUFFIX = /\\$/;
|
|
3655
|
+
function processMultilineKey(value, key) {
|
|
3656
|
+
if (key.tab || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.escape || key.pageUp || key.pageDown) {
|
|
3657
|
+
return { next: null, submit: false };
|
|
3658
|
+
}
|
|
3659
|
+
if (key.input === "\n" || key.ctrl && key.input === "j") {
|
|
3660
|
+
return { next: `${value}
|
|
3661
|
+
`, submit: false };
|
|
3662
|
+
}
|
|
3663
|
+
if (key.return) {
|
|
3664
|
+
if (key.shift) {
|
|
3665
|
+
return { next: `${value}
|
|
3666
|
+
`, submit: false };
|
|
3667
|
+
}
|
|
3668
|
+
if (BACKSLASH_SUFFIX.test(value)) {
|
|
3669
|
+
return { next: `${value.slice(0, -1)}
|
|
3670
|
+
`, submit: false };
|
|
3671
|
+
}
|
|
3672
|
+
return { next: null, submit: true, submitValue: value };
|
|
3673
|
+
}
|
|
3674
|
+
if (key.backspace || key.delete) {
|
|
3675
|
+
if (value.length === 0) return { next: null, submit: false };
|
|
3676
|
+
return { next: value.slice(0, -1), submit: false };
|
|
3677
|
+
}
|
|
3678
|
+
if ((key.ctrl || key.meta) && key.input.length === 0) {
|
|
3679
|
+
return { next: null, submit: false };
|
|
3680
|
+
}
|
|
3681
|
+
if (key.input.length > 0 && !key.ctrl && !key.meta) {
|
|
3682
|
+
return { next: value + key.input, submit: false };
|
|
3683
|
+
}
|
|
3684
|
+
return { next: null, submit: false };
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
// src/cli/ui/PromptInput.tsx
|
|
3277
3688
|
function PromptInput({
|
|
3278
3689
|
value,
|
|
3279
3690
|
onChange,
|
|
@@ -3281,17 +3692,53 @@ function PromptInput({
|
|
|
3281
3692
|
disabled,
|
|
3282
3693
|
placeholder
|
|
3283
3694
|
}) {
|
|
3284
|
-
const
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
onChange,
|
|
3290
|
-
onSubmit,
|
|
3291
|
-
focus: !disabled,
|
|
3292
|
-
placeholder: effectivePlaceholder
|
|
3695
|
+
const [showCursor, setShowCursor] = useState2(true);
|
|
3696
|
+
useEffect2(() => {
|
|
3697
|
+
if (disabled) {
|
|
3698
|
+
setShowCursor(false);
|
|
3699
|
+
return;
|
|
3293
3700
|
}
|
|
3294
|
-
|
|
3701
|
+
setShowCursor(true);
|
|
3702
|
+
const id = setInterval(() => setShowCursor((s) => !s), 500);
|
|
3703
|
+
return () => clearInterval(id);
|
|
3704
|
+
}, [disabled]);
|
|
3705
|
+
useInput(
|
|
3706
|
+
(input, key) => {
|
|
3707
|
+
const keyEvent = {
|
|
3708
|
+
input,
|
|
3709
|
+
return: key.return,
|
|
3710
|
+
shift: key.shift,
|
|
3711
|
+
ctrl: key.ctrl,
|
|
3712
|
+
meta: key.meta,
|
|
3713
|
+
backspace: key.backspace,
|
|
3714
|
+
delete: key.delete,
|
|
3715
|
+
tab: key.tab,
|
|
3716
|
+
upArrow: key.upArrow,
|
|
3717
|
+
downArrow: key.downArrow,
|
|
3718
|
+
leftArrow: key.leftArrow,
|
|
3719
|
+
rightArrow: key.rightArrow,
|
|
3720
|
+
escape: key.escape,
|
|
3721
|
+
pageUp: key.pageUp,
|
|
3722
|
+
pageDown: key.pageDown
|
|
3723
|
+
};
|
|
3724
|
+
const action = processMultilineKey(value, keyEvent);
|
|
3725
|
+
if (action.next !== null) onChange(action.next);
|
|
3726
|
+
if (action.submit) onSubmit(action.submitValue ?? value);
|
|
3727
|
+
},
|
|
3728
|
+
{ isActive: !disabled }
|
|
3729
|
+
);
|
|
3730
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command \xB7 Ctrl+J for newline";
|
|
3731
|
+
const lines = value.length > 0 ? value.split("\n") : [""];
|
|
3732
|
+
const borderColor = disabled ? "gray" : "cyan";
|
|
3733
|
+
return /* @__PURE__ */ React4.createElement(Box4, { borderStyle: "round", borderColor, paddingX: 1, flexDirection: "column" }, lines.map((line, i) => {
|
|
3734
|
+
const isLast = i === lines.length - 1;
|
|
3735
|
+
const isFirst = i === 0;
|
|
3736
|
+
const showPlaceholder = isFirst && value.length === 0;
|
|
3737
|
+
return (
|
|
3738
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: stable by construction — lines are derived from `value.split("\n")` and never reordered
|
|
3739
|
+
/* @__PURE__ */ React4.createElement(Box4, { key: i }, isFirst ? /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: borderColor }, "you \u203A", " ") : /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " "), showPlaceholder && isLast && !disabled ? /* @__PURE__ */ React4.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " ") : null, showPlaceholder ? /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, effectivePlaceholder) : /* @__PURE__ */ React4.createElement(Text4, null, line), !showPlaceholder && isLast && !disabled ? /* @__PURE__ */ React4.createElement(Text4, { color: borderColor }, showCursor ? "\u258C" : " ") : null)
|
|
3740
|
+
);
|
|
3741
|
+
}));
|
|
3295
3742
|
}
|
|
3296
3743
|
|
|
3297
3744
|
// src/cli/ui/SlashSuggestions.tsx
|
|
@@ -3785,22 +4232,24 @@ function App({
|
|
|
3785
4232
|
tools,
|
|
3786
4233
|
mcpSpecs,
|
|
3787
4234
|
mcpServers,
|
|
4235
|
+
progressSink,
|
|
3788
4236
|
codeMode
|
|
3789
4237
|
}) {
|
|
3790
4238
|
const { exit } = useApp();
|
|
3791
|
-
const [historical, setHistorical] =
|
|
3792
|
-
const [streaming, setStreaming] =
|
|
3793
|
-
const [input, setInput] =
|
|
3794
|
-
const [busy, setBusy] =
|
|
4239
|
+
const [historical, setHistorical] = useState3([]);
|
|
4240
|
+
const [streaming, setStreaming] = useState3(null);
|
|
4241
|
+
const [input, setInput] = useState3("");
|
|
4242
|
+
const [busy, setBusy] = useState3(false);
|
|
3795
4243
|
const abortedThisTurn = useRef(false);
|
|
3796
|
-
const [ongoingTool, setOngoingTool] =
|
|
4244
|
+
const [ongoingTool, setOngoingTool] = useState3(null);
|
|
4245
|
+
const [toolProgress, setToolProgress] = useState3(null);
|
|
3797
4246
|
const lastEditSnapshots = useRef(null);
|
|
3798
4247
|
const pendingEdits = useRef([]);
|
|
3799
4248
|
const promptHistory = useRef([]);
|
|
3800
4249
|
const historyCursor = useRef(-1);
|
|
3801
4250
|
const toolHistoryRef = useRef([]);
|
|
3802
|
-
const [slashSelected, setSlashSelected] =
|
|
3803
|
-
const [summary, setSummary] =
|
|
4251
|
+
const [slashSelected, setSlashSelected] = useState3(0);
|
|
4252
|
+
const [summary, setSummary] = useState3({
|
|
3804
4253
|
turns: 0,
|
|
3805
4254
|
totalCostUsd: 0,
|
|
3806
4255
|
claudeEquivalentUsd: 0,
|
|
@@ -3817,7 +4266,7 @@ function App({
|
|
|
3817
4266
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3818
4267
|
});
|
|
3819
4268
|
}
|
|
3820
|
-
|
|
4269
|
+
useEffect3(() => {
|
|
3821
4270
|
return () => {
|
|
3822
4271
|
transcriptRef.current?.end();
|
|
3823
4272
|
};
|
|
@@ -3826,7 +4275,7 @@ function App({
|
|
|
3826
4275
|
if (!input.startsWith("/") || input.includes(" ")) return null;
|
|
3827
4276
|
return suggestSlashCommands(input.slice(1), !!codeMode);
|
|
3828
4277
|
}, [input, codeMode]);
|
|
3829
|
-
|
|
4278
|
+
useEffect3(() => {
|
|
3830
4279
|
setSlashSelected((prev) => {
|
|
3831
4280
|
if (!slashMatches || slashMatches.length === 0) return 0;
|
|
3832
4281
|
if (prev >= slashMatches.length) return slashMatches.length - 1;
|
|
@@ -3845,8 +4294,21 @@ function App({
|
|
|
3845
4294
|
loopRef.current = l;
|
|
3846
4295
|
return l;
|
|
3847
4296
|
}, [model, system, harvest2, branch, session, tools]);
|
|
4297
|
+
useEffect3(() => {
|
|
4298
|
+
if (!progressSink) return;
|
|
4299
|
+
progressSink.current = (info) => {
|
|
4300
|
+
setToolProgress({
|
|
4301
|
+
progress: info.progress,
|
|
4302
|
+
total: info.total,
|
|
4303
|
+
message: info.message
|
|
4304
|
+
});
|
|
4305
|
+
};
|
|
4306
|
+
return () => {
|
|
4307
|
+
if (progressSink.current) progressSink.current = null;
|
|
4308
|
+
};
|
|
4309
|
+
}, [progressSink]);
|
|
3848
4310
|
const sessionBannerShown = useRef(false);
|
|
3849
|
-
|
|
4311
|
+
useEffect3(() => {
|
|
3850
4312
|
if (sessionBannerShown.current) return;
|
|
3851
4313
|
sessionBannerShown.current = true;
|
|
3852
4314
|
if (!session) {
|
|
@@ -3878,7 +4340,7 @@ function App({
|
|
|
3878
4340
|
]);
|
|
3879
4341
|
}
|
|
3880
4342
|
}, [session, loop]);
|
|
3881
|
-
|
|
4343
|
+
useInput2((_input, key) => {
|
|
3882
4344
|
if (key.escape && busy) {
|
|
3883
4345
|
if (abortedThisTurn.current) return;
|
|
3884
4346
|
abortedThisTurn.current = true;
|
|
@@ -4096,9 +4558,11 @@ function App({
|
|
|
4096
4558
|
}
|
|
4097
4559
|
} else if (ev.role === "tool_start") {
|
|
4098
4560
|
setOngoingTool({ name: ev.toolName ?? "?", args: ev.toolArgs });
|
|
4561
|
+
setToolProgress(null);
|
|
4099
4562
|
} else if (ev.role === "tool") {
|
|
4100
4563
|
flush();
|
|
4101
4564
|
setOngoingTool(null);
|
|
4565
|
+
setToolProgress(null);
|
|
4102
4566
|
toolHistoryRef.current.push({
|
|
4103
4567
|
toolName: ev.toolName ?? "?",
|
|
4104
4568
|
text: ev.content
|
|
@@ -4129,6 +4593,7 @@ function App({
|
|
|
4129
4593
|
clearInterval(timer);
|
|
4130
4594
|
setStreaming(null);
|
|
4131
4595
|
setOngoingTool(null);
|
|
4596
|
+
setToolProgress(null);
|
|
4132
4597
|
setSummary(loop.stats.summary());
|
|
4133
4598
|
setBusy(false);
|
|
4134
4599
|
}
|
|
@@ -4156,12 +4621,15 @@ function App({
|
|
|
4156
4621
|
harvestOn: loop.harvestEnabled,
|
|
4157
4622
|
branchBudget: loop.branchOptions.budget
|
|
4158
4623
|
}
|
|
4159
|
-
), /* @__PURE__ */ React7.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React7.createElement(EventRow, { key: item.id, event: item })), streaming ? /* @__PURE__ */ React7.createElement(Box7, { marginY: 1 }, /* @__PURE__ */ React7.createElement(EventRow, { event: streaming })) : null, ongoingTool ? /* @__PURE__ */ React7.createElement(OngoingToolRow, { tool: ongoingTool }) : null, /* @__PURE__ */ React7.createElement(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: busy }), /* @__PURE__ */ React7.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }));
|
|
4624
|
+
), /* @__PURE__ */ React7.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React7.createElement(EventRow, { key: item.id, event: item })), streaming ? /* @__PURE__ */ React7.createElement(Box7, { marginY: 1 }, /* @__PURE__ */ React7.createElement(EventRow, { event: streaming })) : null, ongoingTool ? /* @__PURE__ */ React7.createElement(OngoingToolRow, { tool: ongoingTool, progress: toolProgress }) : null, /* @__PURE__ */ React7.createElement(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: busy }), /* @__PURE__ */ React7.createElement(SlashSuggestions, { matches: slashMatches, selectedIndex: slashSelected }));
|
|
4160
4625
|
}
|
|
4161
|
-
function OngoingToolRow({
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4626
|
+
function OngoingToolRow({
|
|
4627
|
+
tool,
|
|
4628
|
+
progress
|
|
4629
|
+
}) {
|
|
4630
|
+
const [tick, setTick] = useState3(0);
|
|
4631
|
+
const [elapsed, setElapsed] = useState3(0);
|
|
4632
|
+
useEffect3(() => {
|
|
4165
4633
|
const start = Date.now();
|
|
4166
4634
|
const frameId = setInterval(() => setTick((t) => t + 1), 120);
|
|
4167
4635
|
const secId = setInterval(() => setElapsed(Math.floor((Date.now() - start) / 1e3)), 1e3);
|
|
@@ -4172,7 +4640,19 @@ function OngoingToolRow({ tool }) {
|
|
|
4172
4640
|
}, []);
|
|
4173
4641
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4174
4642
|
const summary = summarizeToolArgs(tool.name, tool.args);
|
|
4175
|
-
return /* @__PURE__ */ React7.createElement(Box7, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, frames[tick % frames.length]), /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, ` ${elapsed}s`)), summary ? /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, summary)) : null);
|
|
4643
|
+
return /* @__PURE__ */ React7.createElement(Box7, { marginY: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, frames[tick % frames.length]), /* @__PURE__ */ React7.createElement(Text7, { color: "yellow" }, ` tool<${tool.name}> running\u2026`), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, ` ${elapsed}s`)), progress ? /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, renderProgressLine(progress))) : null, summary ? /* @__PURE__ */ React7.createElement(Box7, { paddingLeft: 2 }, /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, summary)) : null);
|
|
4644
|
+
}
|
|
4645
|
+
function renderProgressLine(p) {
|
|
4646
|
+
const msg = p.message ? ` ${p.message}` : "";
|
|
4647
|
+
if (p.total && p.total > 0) {
|
|
4648
|
+
const ratio = Math.max(0, Math.min(1, p.progress / p.total));
|
|
4649
|
+
const width = 20;
|
|
4650
|
+
const filled = Math.round(ratio * width);
|
|
4651
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
|
|
4652
|
+
const pct2 = (ratio * 100).toFixed(0);
|
|
4653
|
+
return `[${bar}] ${p.progress}/${p.total} ${pct2}%${msg}`;
|
|
4654
|
+
}
|
|
4655
|
+
return `progress: ${p.progress}${msg}`;
|
|
4176
4656
|
}
|
|
4177
4657
|
function summarizeToolArgs(name, args) {
|
|
4178
4658
|
if (!args || args === "{}") return "";
|
|
@@ -4257,11 +4737,11 @@ function describeRepair(repair) {
|
|
|
4257
4737
|
|
|
4258
4738
|
// src/cli/ui/Setup.tsx
|
|
4259
4739
|
import { Box as Box8, Text as Text8, useApp as useApp2 } from "ink";
|
|
4260
|
-
import
|
|
4261
|
-
import React8, { useState as
|
|
4740
|
+
import TextInput from "ink-text-input";
|
|
4741
|
+
import React8, { useState as useState4 } from "react";
|
|
4262
4742
|
function Setup({ onReady }) {
|
|
4263
|
-
const [value, setValue] =
|
|
4264
|
-
const [error, setError] =
|
|
4743
|
+
const [value, setValue] = useState4("");
|
|
4744
|
+
const [error, setError] = useState4(null);
|
|
4265
4745
|
const { exit } = useApp2();
|
|
4266
4746
|
const handleSubmit = (raw) => {
|
|
4267
4747
|
const trimmed = raw.trim();
|
|
@@ -4283,7 +4763,7 @@ function Setup({ onReady }) {
|
|
|
4283
4763
|
onReady(trimmed);
|
|
4284
4764
|
};
|
|
4285
4765
|
return /* @__PURE__ */ React8.createElement(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React8.createElement(
|
|
4286
|
-
|
|
4766
|
+
TextInput,
|
|
4287
4767
|
{
|
|
4288
4768
|
value,
|
|
4289
4769
|
onChange: setValue,
|
|
@@ -4295,8 +4775,8 @@ function Setup({ onReady }) {
|
|
|
4295
4775
|
}
|
|
4296
4776
|
|
|
4297
4777
|
// src/cli/commands/chat.tsx
|
|
4298
|
-
function Root({ initialKey, tools, mcpSpecs, mcpServers, ...appProps }) {
|
|
4299
|
-
const [key, setKey] =
|
|
4778
|
+
function Root({ initialKey, tools, mcpSpecs, mcpServers, progressSink, ...appProps }) {
|
|
4779
|
+
const [key, setKey] = useState5(initialKey);
|
|
4300
4780
|
if (!key) {
|
|
4301
4781
|
return /* @__PURE__ */ React9.createElement(
|
|
4302
4782
|
Setup,
|
|
@@ -4321,6 +4801,7 @@ function Root({ initialKey, tools, mcpSpecs, mcpServers, ...appProps }) {
|
|
|
4321
4801
|
tools,
|
|
4322
4802
|
mcpSpecs,
|
|
4323
4803
|
mcpServers,
|
|
4804
|
+
progressSink,
|
|
4324
4805
|
codeMode: appProps.codeMode
|
|
4325
4806
|
}
|
|
4326
4807
|
);
|
|
@@ -4333,9 +4814,10 @@ async function chatCommand(opts) {
|
|
|
4333
4814
|
const successfulSpecs = [];
|
|
4334
4815
|
const failedSpecs = [];
|
|
4335
4816
|
const mcpServers = [];
|
|
4336
|
-
|
|
4817
|
+
const progressSink = { current: null };
|
|
4818
|
+
let tools = opts.seedTools;
|
|
4337
4819
|
if (requestedSpecs.length > 0) {
|
|
4338
|
-
tools = new ToolRegistry();
|
|
4820
|
+
if (!tools) tools = new ToolRegistry();
|
|
4339
4821
|
for (const raw of requestedSpecs) {
|
|
4340
4822
|
try {
|
|
4341
4823
|
const spec = parseMcpSpec(raw);
|
|
@@ -4343,7 +4825,11 @@ async function chatCommand(opts) {
|
|
|
4343
4825
|
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
4344
4826
|
const mcp2 = new McpClient({ transport });
|
|
4345
4827
|
await mcp2.initialize();
|
|
4346
|
-
const bridge = await bridgeMcpTools(mcp2, {
|
|
4828
|
+
const bridge = await bridgeMcpTools(mcp2, {
|
|
4829
|
+
registry: tools,
|
|
4830
|
+
namePrefix: prefix,
|
|
4831
|
+
onProgress: (info) => progressSink.current?.(info)
|
|
4832
|
+
});
|
|
4347
4833
|
let report;
|
|
4348
4834
|
try {
|
|
4349
4835
|
report = await inspectMcpServer(mcp2);
|
|
@@ -4381,7 +4867,7 @@ async function chatCommand(opts) {
|
|
|
4381
4867
|
);
|
|
4382
4868
|
}
|
|
4383
4869
|
}
|
|
4384
|
-
if (successfulSpecs.length === 0) {
|
|
4870
|
+
if (successfulSpecs.length === 0 && !opts.seedTools) {
|
|
4385
4871
|
tools = void 0;
|
|
4386
4872
|
}
|
|
4387
4873
|
}
|
|
@@ -4394,6 +4880,7 @@ async function chatCommand(opts) {
|
|
|
4394
4880
|
tools,
|
|
4395
4881
|
mcpSpecs,
|
|
4396
4882
|
mcpServers,
|
|
4883
|
+
progressSink,
|
|
4397
4884
|
...opts
|
|
4398
4885
|
}
|
|
4399
4886
|
),
|
|
@@ -4407,14 +4894,15 @@ async function chatCommand(opts) {
|
|
|
4407
4894
|
}
|
|
4408
4895
|
|
|
4409
4896
|
// src/cli/commands/code.tsx
|
|
4410
|
-
import { basename, resolve as
|
|
4897
|
+
import { basename, resolve as resolve4 } from "path";
|
|
4411
4898
|
async function codeCommand(opts = {}) {
|
|
4412
4899
|
const { codeSystemPrompt: codeSystemPrompt2 } = await import("./prompt-MMANQ36Z.js");
|
|
4413
|
-
const rootDir =
|
|
4900
|
+
const rootDir = resolve4(opts.dir ?? process.cwd());
|
|
4414
4901
|
const session = opts.noSession ? void 0 : `code-${sanitizeName(basename(rootDir))}`;
|
|
4415
|
-
const
|
|
4902
|
+
const tools = new ToolRegistry();
|
|
4903
|
+
registerFilesystemTools(tools, { rootDir });
|
|
4416
4904
|
process.stderr.write(
|
|
4417
|
-
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}"
|
|
4905
|
+
`\u25B8 reasonix code: rooted at ${rootDir}, session "${session ?? "(ephemeral)"}" \xB7 ${tools.size} native fs tool(s)
|
|
4418
4906
|
`
|
|
4419
4907
|
);
|
|
4420
4908
|
await chatCommand({
|
|
@@ -4424,13 +4912,10 @@ async function codeCommand(opts = {}) {
|
|
|
4424
4912
|
system: codeSystemPrompt2(rootDir),
|
|
4425
4913
|
transcript: opts.transcript,
|
|
4426
4914
|
session,
|
|
4427
|
-
|
|
4915
|
+
seedTools: tools,
|
|
4428
4916
|
codeMode: { rootDir }
|
|
4429
4917
|
});
|
|
4430
4918
|
}
|
|
4431
|
-
function quoteIfNeeded(s) {
|
|
4432
|
-
return /\s|"/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
|
|
4433
|
-
}
|
|
4434
4919
|
|
|
4435
4920
|
// src/cli/commands/diff.ts
|
|
4436
4921
|
import { writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -4439,8 +4924,8 @@ import { render as render2 } from "ink";
|
|
|
4439
4924
|
import React12 from "react";
|
|
4440
4925
|
|
|
4441
4926
|
// src/cli/ui/DiffApp.tsx
|
|
4442
|
-
import { Box as Box10, Static as Static2, Text as Text10, useApp as useApp3, useInput as
|
|
4443
|
-
import React11, { useState as
|
|
4927
|
+
import { Box as Box10, Static as Static2, Text as Text10, useApp as useApp3, useInput as useInput3 } from "ink";
|
|
4928
|
+
import React11, { useState as useState6 } from "react";
|
|
4444
4929
|
|
|
4445
4930
|
// src/cli/ui/RecordView.tsx
|
|
4446
4931
|
import { Box as Box9, Text as Text9 } from "ink";
|
|
@@ -4483,8 +4968,8 @@ function DiffApp({ report }) {
|
|
|
4483
4968
|
const { exit } = useApp3();
|
|
4484
4969
|
const maxIdx = Math.max(0, report.pairs.length - 1);
|
|
4485
4970
|
const initialIdx = report.firstDivergenceTurn ? report.pairs.findIndex((p) => p.turn === report.firstDivergenceTurn) : 0;
|
|
4486
|
-
const [idx, setIdx] =
|
|
4487
|
-
|
|
4971
|
+
const [idx, setIdx] = useState6(Math.max(0, initialIdx));
|
|
4972
|
+
useInput3((input, key) => {
|
|
4488
4973
|
if (input === "q" || key.ctrl && input === "c") {
|
|
4489
4974
|
exit();
|
|
4490
4975
|
return;
|
|
@@ -4728,13 +5213,13 @@ import { render as render3 } from "ink";
|
|
|
4728
5213
|
import React14 from "react";
|
|
4729
5214
|
|
|
4730
5215
|
// src/cli/ui/ReplayApp.tsx
|
|
4731
|
-
import { Box as Box11, Static as Static3, Text as Text11, useApp as useApp4, useInput as
|
|
4732
|
-
import React13, { useMemo as useMemo2, useState as
|
|
5216
|
+
import { Box as Box11, Static as Static3, Text as Text11, useApp as useApp4, useInput as useInput4 } from "ink";
|
|
5217
|
+
import React13, { useMemo as useMemo2, useState as useState7 } from "react";
|
|
4733
5218
|
function ReplayApp({ meta, pages }) {
|
|
4734
5219
|
const { exit } = useApp4();
|
|
4735
5220
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
4736
|
-
const [idx, setIdx] =
|
|
4737
|
-
|
|
5221
|
+
const [idx, setIdx] = useState7(maxIdx);
|
|
5222
|
+
useInput4((input, key) => {
|
|
4738
5223
|
if (input === "q" || key.ctrl && input === "c") {
|
|
4739
5224
|
exit();
|
|
4740
5225
|
return;
|
|
@@ -5089,13 +5574,13 @@ import { render as render4 } from "ink";
|
|
|
5089
5574
|
import React17 from "react";
|
|
5090
5575
|
|
|
5091
5576
|
// src/cli/ui/Wizard.tsx
|
|
5092
|
-
import { Box as Box13, Text as Text13, useApp as useApp5, useInput as
|
|
5093
|
-
import
|
|
5094
|
-
import React16, { useState as
|
|
5577
|
+
import { Box as Box13, Text as Text13, useApp as useApp5, useInput as useInput6 } from "ink";
|
|
5578
|
+
import TextInput2 from "ink-text-input";
|
|
5579
|
+
import React16, { useState as useState9 } from "react";
|
|
5095
5580
|
|
|
5096
5581
|
// src/cli/ui/Select.tsx
|
|
5097
|
-
import { Box as Box12, Text as Text12, useInput as
|
|
5098
|
-
import React15, { useState as
|
|
5582
|
+
import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
|
|
5583
|
+
import React15, { useState as useState8 } from "react";
|
|
5099
5584
|
function SingleSelect({
|
|
5100
5585
|
items,
|
|
5101
5586
|
initialValue,
|
|
@@ -5106,8 +5591,8 @@ function SingleSelect({
|
|
|
5106
5591
|
0,
|
|
5107
5592
|
items.findIndex((i) => i.value === initialValue && !i.disabled)
|
|
5108
5593
|
);
|
|
5109
|
-
const [index, setIndex] =
|
|
5110
|
-
|
|
5594
|
+
const [index, setIndex] = useState8(initialIndex === -1 ? 0 : initialIndex);
|
|
5595
|
+
useInput5((_input, key) => {
|
|
5111
5596
|
if (key.upArrow) {
|
|
5112
5597
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
5113
5598
|
} else if (key.downArrow) {
|
|
@@ -5136,12 +5621,12 @@ function MultiSelect({
|
|
|
5136
5621
|
onCancel,
|
|
5137
5622
|
footer
|
|
5138
5623
|
}) {
|
|
5139
|
-
const [index, setIndex] =
|
|
5624
|
+
const [index, setIndex] = useState8(() => {
|
|
5140
5625
|
const first = items.findIndex((i) => !i.disabled);
|
|
5141
5626
|
return first === -1 ? 0 : first;
|
|
5142
5627
|
});
|
|
5143
|
-
const [selected, setSelected] =
|
|
5144
|
-
|
|
5628
|
+
const [selected, setSelected] = useState8(new Set(initialSelected));
|
|
5629
|
+
useInput5((input, key) => {
|
|
5145
5630
|
if (key.upArrow) {
|
|
5146
5631
|
setIndex((i) => findNextEnabled(items, i, -1));
|
|
5147
5632
|
} else if (key.downArrow) {
|
|
@@ -5219,15 +5704,15 @@ var PRESET_DESCRIPTIONS = {
|
|
|
5219
5704
|
var CATALOG_BY_NAME = new Map(MCP_CATALOG.map((e) => [e.name, e]));
|
|
5220
5705
|
function Wizard({ onComplete, onCancel, existingApiKey, initial }) {
|
|
5221
5706
|
const { exit } = useApp5();
|
|
5222
|
-
const [step, setStep] =
|
|
5223
|
-
const [data, setData] =
|
|
5707
|
+
const [step, setStep] = useState9(existingApiKey ? "preset" : "apiKey");
|
|
5708
|
+
const [data, setData] = useState9({
|
|
5224
5709
|
apiKey: existingApiKey ?? "",
|
|
5225
5710
|
preset: initial?.preset ?? "fast",
|
|
5226
5711
|
selectedCatalog: deriveInitialCatalog(initial?.mcp ?? []),
|
|
5227
5712
|
catalogArgs: {}
|
|
5228
5713
|
});
|
|
5229
|
-
const [error, setError] =
|
|
5230
|
-
|
|
5714
|
+
const [error, setError] = useState9(null);
|
|
5715
|
+
useInput6((_input, key) => {
|
|
5231
5716
|
if (key.escape && step !== "saved" && onCancel) onCancel();
|
|
5232
5717
|
});
|
|
5233
5718
|
if (step === "apiKey") {
|
|
@@ -5343,9 +5828,9 @@ function ApiKeyStep({
|
|
|
5343
5828
|
error,
|
|
5344
5829
|
onError
|
|
5345
5830
|
}) {
|
|
5346
|
-
const [value, setValue] =
|
|
5831
|
+
const [value, setValue] = useState9("");
|
|
5347
5832
|
return /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React16.createElement(
|
|
5348
|
-
|
|
5833
|
+
TextInput2,
|
|
5349
5834
|
{
|
|
5350
5835
|
value,
|
|
5351
5836
|
onChange: setValue,
|
|
@@ -5369,9 +5854,9 @@ function McpArgsStep({
|
|
|
5369
5854
|
onSubmit,
|
|
5370
5855
|
onError
|
|
5371
5856
|
}) {
|
|
5372
|
-
const [value, setValue] =
|
|
5857
|
+
const [value, setValue] = useState9("");
|
|
5373
5858
|
return /* @__PURE__ */ React16.createElement(StepFrame, { title: `Configure ${entry.name}`, step: 2, total: 3 }, /* @__PURE__ */ React16.createElement(Box13, { flexDirection: "column" }, /* @__PURE__ */ React16.createElement(Text13, null, entry.summary), entry.note ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { dimColor: true }, entry.note)) : null, /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, null, "Required parameter: "), /* @__PURE__ */ React16.createElement(Text13, { bold: true }, entry.userArgs)), /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { bold: true, color: "cyan" }, entry.userArgs, " \u203A "), /* @__PURE__ */ React16.createElement(
|
|
5374
|
-
|
|
5859
|
+
TextInput2,
|
|
5375
5860
|
{
|
|
5376
5861
|
value,
|
|
5377
5862
|
onChange: setValue,
|
|
@@ -5389,13 +5874,13 @@ function McpArgsStep({
|
|
|
5389
5874
|
)), error ? /* @__PURE__ */ React16.createElement(Box13, { marginTop: 1 }, /* @__PURE__ */ React16.createElement(Text13, { color: "red" }, error)) : null));
|
|
5390
5875
|
}
|
|
5391
5876
|
function ReviewConfirm({ onConfirm }) {
|
|
5392
|
-
|
|
5877
|
+
useInput6((_i, key) => {
|
|
5393
5878
|
if (key.return) onConfirm();
|
|
5394
5879
|
});
|
|
5395
5880
|
return null;
|
|
5396
5881
|
}
|
|
5397
5882
|
function ExitOnEnter({ onExit }) {
|
|
5398
|
-
|
|
5883
|
+
useInput6((_i, key) => {
|
|
5399
5884
|
if (key.return) onExit();
|
|
5400
5885
|
});
|
|
5401
5886
|
return null;
|
|
@@ -5452,10 +5937,10 @@ function buildSpec(name, argsByName) {
|
|
|
5452
5937
|
const entry = CATALOG_BY_NAME.get(name);
|
|
5453
5938
|
if (!entry) return name;
|
|
5454
5939
|
const userArg = entry.userArgs ? argsByName[name] : void 0;
|
|
5455
|
-
const tail = userArg ? ` ${
|
|
5940
|
+
const tail = userArg ? ` ${quoteIfNeeded(userArg)}` : "";
|
|
5456
5941
|
return `${entry.name}=npx -y ${entry.package}${tail}`;
|
|
5457
5942
|
}
|
|
5458
|
-
function
|
|
5943
|
+
function quoteIfNeeded(s) {
|
|
5459
5944
|
return /\s|"/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
|
|
5460
5945
|
}
|
|
5461
5946
|
|