reasonix 0.2.2 → 0.3.0-alpha.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 +23 -0
- package/dist/cli/index.js +714 -105
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +270 -2
- package/dist/index.js +327 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1360,6 +1360,14 @@ function recordFromLoopEvent(ev, extra) {
|
|
|
1360
1360
|
if (ev.toolName !== void 0) rec.tool = ev.toolName;
|
|
1361
1361
|
if (ev.toolArgs !== void 0) rec.args = ev.toolArgs;
|
|
1362
1362
|
if (ev.error !== void 0) rec.error = ev.error;
|
|
1363
|
+
if (ev.planState && !isPlanStateEmptyShape(ev.planState)) {
|
|
1364
|
+
rec.planState = {
|
|
1365
|
+
subgoals: [...ev.planState.subgoals],
|
|
1366
|
+
hypotheses: [...ev.planState.hypotheses],
|
|
1367
|
+
uncertainties: [...ev.planState.uncertainties],
|
|
1368
|
+
rejectedPaths: [...ev.planState.rejectedPaths]
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1363
1371
|
if (ev.stats) {
|
|
1364
1372
|
rec.usage = {
|
|
1365
1373
|
prompt_tokens: ev.stats.usage.promptTokens,
|
|
@@ -1395,6 +1403,9 @@ function readTranscript(path) {
|
|
|
1395
1403
|
const raw = readFileSync3(path, "utf8");
|
|
1396
1404
|
return parseTranscript(raw);
|
|
1397
1405
|
}
|
|
1406
|
+
function isPlanStateEmptyShape(s) {
|
|
1407
|
+
return s.subgoals.length === 0 && s.hypotheses.length === 0 && s.uncertainties.length === 0 && s.rejectedPaths.length === 0;
|
|
1408
|
+
}
|
|
1398
1409
|
function parseTranscript(raw) {
|
|
1399
1410
|
const out = { meta: null, records: [] };
|
|
1400
1411
|
for (const line of raw.split(/\r?\n/)) {
|
|
@@ -1448,12 +1459,20 @@ function computeReplayStats(records) {
|
|
|
1448
1459
|
const prefixHashes = /* @__PURE__ */ new Set();
|
|
1449
1460
|
let userTurns = 0;
|
|
1450
1461
|
let toolCalls = 0;
|
|
1462
|
+
let harvestedTurns = 0;
|
|
1463
|
+
let totalUncertainties = 0;
|
|
1464
|
+
let totalSubgoals = 0;
|
|
1451
1465
|
for (const rec of records) {
|
|
1452
1466
|
if (rec.role === "user") userTurns++;
|
|
1453
1467
|
else if (rec.role === "tool") toolCalls++;
|
|
1454
1468
|
else if (rec.role === "assistant_final") {
|
|
1455
1469
|
if (rec.model) models.add(rec.model);
|
|
1456
1470
|
if (rec.prefixHash) prefixHashes.add(rec.prefixHash);
|
|
1471
|
+
if (rec.planState) {
|
|
1472
|
+
harvestedTurns++;
|
|
1473
|
+
totalUncertainties += rec.planState.uncertainties.length;
|
|
1474
|
+
totalSubgoals += rec.planState.subgoals.length;
|
|
1475
|
+
}
|
|
1457
1476
|
if (rec.usage && rec.model) {
|
|
1458
1477
|
const u = new Usage(
|
|
1459
1478
|
rec.usage.prompt_tokens ?? 0,
|
|
@@ -1481,6 +1500,9 @@ function computeReplayStats(records) {
|
|
|
1481
1500
|
prefixHashes: [...prefixHashes],
|
|
1482
1501
|
userTurns,
|
|
1483
1502
|
toolCalls,
|
|
1503
|
+
harvestedTurns,
|
|
1504
|
+
totalUncertainties,
|
|
1505
|
+
totalSubgoals,
|
|
1484
1506
|
...summarizeTurns(turns)
|
|
1485
1507
|
};
|
|
1486
1508
|
}
|
|
@@ -1664,6 +1686,41 @@ function renderSummaryTable(report, _opts = {}) {
|
|
|
1664
1686
|
)
|
|
1665
1687
|
);
|
|
1666
1688
|
lines.push(statRow("prefix hashes", a.stats.prefixHashes.length, b.stats.prefixHashes.length));
|
|
1689
|
+
if (a.stats.harvestedTurns > 0 || b.stats.harvestedTurns > 0) {
|
|
1690
|
+
lines.push(
|
|
1691
|
+
row(
|
|
1692
|
+
[
|
|
1693
|
+
"harvest turns",
|
|
1694
|
+
`${a.stats.harvestedTurns}`,
|
|
1695
|
+
`${b.stats.harvestedTurns}`,
|
|
1696
|
+
signed(b.stats.harvestedTurns - a.stats.harvestedTurns)
|
|
1697
|
+
],
|
|
1698
|
+
[20, 14, 14, 14]
|
|
1699
|
+
)
|
|
1700
|
+
);
|
|
1701
|
+
lines.push(
|
|
1702
|
+
row(
|
|
1703
|
+
[
|
|
1704
|
+
" subgoals",
|
|
1705
|
+
`${a.stats.totalSubgoals}`,
|
|
1706
|
+
`${b.stats.totalSubgoals}`,
|
|
1707
|
+
signed(b.stats.totalSubgoals - a.stats.totalSubgoals)
|
|
1708
|
+
],
|
|
1709
|
+
[20, 14, 14, 14]
|
|
1710
|
+
)
|
|
1711
|
+
);
|
|
1712
|
+
lines.push(
|
|
1713
|
+
row(
|
|
1714
|
+
[
|
|
1715
|
+
" uncertainties",
|
|
1716
|
+
`${a.stats.totalUncertainties}`,
|
|
1717
|
+
`${b.stats.totalUncertainties}`,
|
|
1718
|
+
signed(b.stats.totalUncertainties - a.stats.totalUncertainties)
|
|
1719
|
+
],
|
|
1720
|
+
[20, 14, 14, 14]
|
|
1721
|
+
)
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1667
1724
|
lines.push("");
|
|
1668
1725
|
const aPrefixStable = a.stats.prefixHashes.length <= 1;
|
|
1669
1726
|
const bPrefixStable = b.stats.prefixHashes.length <= 1;
|
|
@@ -1735,6 +1792,17 @@ function renderMarkdown(report) {
|
|
|
1735
1792
|
out.push(
|
|
1736
1793
|
`| prefix hashes | ${a.stats.prefixHashes.length} | ${b.stats.prefixHashes.length} | \u2014 |`
|
|
1737
1794
|
);
|
|
1795
|
+
if (a.stats.harvestedTurns > 0 || b.stats.harvestedTurns > 0) {
|
|
1796
|
+
out.push(
|
|
1797
|
+
`| harvest turns | ${a.stats.harvestedTurns} | ${b.stats.harvestedTurns} | ${signed(b.stats.harvestedTurns - a.stats.harvestedTurns)} |`
|
|
1798
|
+
);
|
|
1799
|
+
out.push(
|
|
1800
|
+
`| harvest subgoals | ${a.stats.totalSubgoals} | ${b.stats.totalSubgoals} | ${signed(b.stats.totalSubgoals - a.stats.totalSubgoals)} |`
|
|
1801
|
+
);
|
|
1802
|
+
out.push(
|
|
1803
|
+
`| harvest uncertainties | ${a.stats.totalUncertainties} | ${b.stats.totalUncertainties} | ${signed(b.stats.totalUncertainties - a.stats.totalUncertainties)} |`
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1738
1806
|
out.push("");
|
|
1739
1807
|
out.push("## Turn-by-turn");
|
|
1740
1808
|
out.push("");
|
|
@@ -1802,6 +1870,258 @@ function truncate(s, n) {
|
|
|
1802
1870
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
1803
1871
|
}
|
|
1804
1872
|
|
|
1873
|
+
// src/mcp/types.ts
|
|
1874
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
1875
|
+
function isJsonRpcError(msg) {
|
|
1876
|
+
return "error" in msg;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/mcp/client.ts
|
|
1880
|
+
var McpClient = class {
|
|
1881
|
+
transport;
|
|
1882
|
+
clientInfo;
|
|
1883
|
+
requestTimeoutMs;
|
|
1884
|
+
pending = /* @__PURE__ */ new Map();
|
|
1885
|
+
nextId = 1;
|
|
1886
|
+
readerStarted = false;
|
|
1887
|
+
initialized = false;
|
|
1888
|
+
_serverCapabilities = {};
|
|
1889
|
+
constructor(opts) {
|
|
1890
|
+
this.transport = opts.transport;
|
|
1891
|
+
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: "0.3.0-dev" };
|
|
1892
|
+
this.requestTimeoutMs = opts.requestTimeoutMs ?? 6e4;
|
|
1893
|
+
}
|
|
1894
|
+
/** Server's advertised capabilities, available after initialize(). */
|
|
1895
|
+
get serverCapabilities() {
|
|
1896
|
+
return this._serverCapabilities;
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Complete the initialize → initialized handshake. Must be called
|
|
1900
|
+
* before any other method (otherwise compliant servers reject).
|
|
1901
|
+
*/
|
|
1902
|
+
async initialize() {
|
|
1903
|
+
if (this.initialized) throw new Error("MCP client already initialized");
|
|
1904
|
+
this.startReaderIfNeeded();
|
|
1905
|
+
const result = await this.request("initialize", {
|
|
1906
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
1907
|
+
capabilities: { tools: {} },
|
|
1908
|
+
clientInfo: this.clientInfo
|
|
1909
|
+
});
|
|
1910
|
+
this._serverCapabilities = result.capabilities ?? {};
|
|
1911
|
+
await this.transport.send({
|
|
1912
|
+
jsonrpc: "2.0",
|
|
1913
|
+
method: "notifications/initialized"
|
|
1914
|
+
});
|
|
1915
|
+
this.initialized = true;
|
|
1916
|
+
return result;
|
|
1917
|
+
}
|
|
1918
|
+
/** List tools the server exposes. */
|
|
1919
|
+
async listTools() {
|
|
1920
|
+
this.assertInitialized();
|
|
1921
|
+
return this.request("tools/list", {});
|
|
1922
|
+
}
|
|
1923
|
+
/** Invoke a tool by name. Returns the raw MCP result (caller unwraps content). */
|
|
1924
|
+
async callTool(name, args) {
|
|
1925
|
+
this.assertInitialized();
|
|
1926
|
+
return this.request("tools/call", {
|
|
1927
|
+
name,
|
|
1928
|
+
arguments: args ?? {}
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
/** Close the transport and reject any outstanding requests. */
|
|
1932
|
+
async close() {
|
|
1933
|
+
for (const [, pending] of this.pending) {
|
|
1934
|
+
clearTimeout(pending.timeout);
|
|
1935
|
+
pending.reject(new Error("MCP client closed"));
|
|
1936
|
+
}
|
|
1937
|
+
this.pending.clear();
|
|
1938
|
+
await this.transport.close();
|
|
1939
|
+
}
|
|
1940
|
+
// ---------- internals ----------
|
|
1941
|
+
assertInitialized() {
|
|
1942
|
+
if (!this.initialized) throw new Error("MCP client not initialized \u2014 call initialize() first");
|
|
1943
|
+
}
|
|
1944
|
+
async request(method, params) {
|
|
1945
|
+
const id = this.nextId++;
|
|
1946
|
+
const frame = { jsonrpc: "2.0", id, method, params };
|
|
1947
|
+
const promise = new Promise((resolve2, reject) => {
|
|
1948
|
+
const timeout = setTimeout(() => {
|
|
1949
|
+
this.pending.delete(id);
|
|
1950
|
+
reject(
|
|
1951
|
+
new Error(`MCP request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`)
|
|
1952
|
+
);
|
|
1953
|
+
}, this.requestTimeoutMs);
|
|
1954
|
+
this.pending.set(id, {
|
|
1955
|
+
resolve: resolve2,
|
|
1956
|
+
reject,
|
|
1957
|
+
timeout
|
|
1958
|
+
});
|
|
1959
|
+
});
|
|
1960
|
+
await this.transport.send(frame);
|
|
1961
|
+
return promise;
|
|
1962
|
+
}
|
|
1963
|
+
startReaderIfNeeded() {
|
|
1964
|
+
if (this.readerStarted) return;
|
|
1965
|
+
this.readerStarted = true;
|
|
1966
|
+
void this.readLoop();
|
|
1967
|
+
}
|
|
1968
|
+
async readLoop() {
|
|
1969
|
+
try {
|
|
1970
|
+
for await (const msg of this.transport.messages()) {
|
|
1971
|
+
this.dispatch(msg);
|
|
1972
|
+
}
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
for (const [, pending] of this.pending) {
|
|
1975
|
+
clearTimeout(pending.timeout);
|
|
1976
|
+
pending.reject(err);
|
|
1977
|
+
}
|
|
1978
|
+
this.pending.clear();
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
dispatch(msg) {
|
|
1982
|
+
if (!("id" in msg) || msg.id === null || msg.id === void 0) return;
|
|
1983
|
+
if (!("result" in msg) && !("error" in msg)) return;
|
|
1984
|
+
const pending = this.pending.get(msg.id);
|
|
1985
|
+
if (!pending) return;
|
|
1986
|
+
this.pending.delete(msg.id);
|
|
1987
|
+
clearTimeout(pending.timeout);
|
|
1988
|
+
const resp = msg;
|
|
1989
|
+
if (isJsonRpcError(resp)) {
|
|
1990
|
+
pending.reject(new Error(`MCP ${resp.error.code}: ${resp.error.message}`));
|
|
1991
|
+
} else {
|
|
1992
|
+
pending.resolve(resp.result);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
|
|
1997
|
+
// src/mcp/stdio.ts
|
|
1998
|
+
import { spawn } from "child_process";
|
|
1999
|
+
var StdioTransport = class {
|
|
2000
|
+
child;
|
|
2001
|
+
queue = [];
|
|
2002
|
+
waiters = [];
|
|
2003
|
+
closed = false;
|
|
2004
|
+
stdoutBuffer = "";
|
|
2005
|
+
constructor(opts) {
|
|
2006
|
+
const env = opts.replaceEnv ? { ...opts.env ?? {} } : { ...process.env, ...opts.env ?? {} };
|
|
2007
|
+
this.child = spawn(opts.command, opts.args ?? [], {
|
|
2008
|
+
env,
|
|
2009
|
+
cwd: opts.cwd,
|
|
2010
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
2011
|
+
});
|
|
2012
|
+
this.child.stdout.setEncoding("utf8");
|
|
2013
|
+
this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
2014
|
+
this.child.on("close", () => this.onClose());
|
|
2015
|
+
this.child.on("error", (err) => {
|
|
2016
|
+
this.push({
|
|
2017
|
+
jsonrpc: "2.0",
|
|
2018
|
+
id: null,
|
|
2019
|
+
error: { code: -32e3, message: `transport error: ${err.message}` }
|
|
2020
|
+
});
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
async send(message) {
|
|
2024
|
+
if (this.closed) throw new Error("MCP transport is closed");
|
|
2025
|
+
return new Promise((resolve2, reject) => {
|
|
2026
|
+
const line = `${JSON.stringify(message)}
|
|
2027
|
+
`;
|
|
2028
|
+
this.child.stdin.write(line, "utf8", (err) => {
|
|
2029
|
+
if (err) reject(err);
|
|
2030
|
+
else resolve2();
|
|
2031
|
+
});
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
async *messages() {
|
|
2035
|
+
while (true) {
|
|
2036
|
+
if (this.queue.length > 0) {
|
|
2037
|
+
yield this.queue.shift();
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
if (this.closed) return;
|
|
2041
|
+
const next = await new Promise((resolve2) => {
|
|
2042
|
+
this.waiters.push(resolve2);
|
|
2043
|
+
});
|
|
2044
|
+
if (next === null) return;
|
|
2045
|
+
yield next;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
async close() {
|
|
2049
|
+
if (this.closed) return;
|
|
2050
|
+
this.closed = true;
|
|
2051
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
2052
|
+
try {
|
|
2053
|
+
this.child.stdin.end();
|
|
2054
|
+
} catch {
|
|
2055
|
+
}
|
|
2056
|
+
if (this.child.exitCode === null && !this.child.killed) {
|
|
2057
|
+
this.child.kill("SIGTERM");
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
/** Parse incoming stdout chunks into NDJSON messages. */
|
|
2061
|
+
onStdout(chunk) {
|
|
2062
|
+
this.stdoutBuffer += chunk;
|
|
2063
|
+
let newlineIdx;
|
|
2064
|
+
while ((newlineIdx = this.stdoutBuffer.indexOf("\n")) !== -1) {
|
|
2065
|
+
const line = this.stdoutBuffer.slice(0, newlineIdx).trim();
|
|
2066
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newlineIdx + 1);
|
|
2067
|
+
if (!line) continue;
|
|
2068
|
+
try {
|
|
2069
|
+
const msg = JSON.parse(line);
|
|
2070
|
+
this.push(msg);
|
|
2071
|
+
} catch {
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
onClose() {
|
|
2076
|
+
this.closed = true;
|
|
2077
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
2078
|
+
}
|
|
2079
|
+
push(msg) {
|
|
2080
|
+
const waiter = this.waiters.shift();
|
|
2081
|
+
if (waiter) waiter(msg);
|
|
2082
|
+
else this.queue.push(msg);
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
|
|
2086
|
+
// src/mcp/registry.ts
|
|
2087
|
+
async function bridgeMcpTools(client, opts = {}) {
|
|
2088
|
+
const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
|
|
2089
|
+
const prefix = opts.namePrefix ?? "";
|
|
2090
|
+
const result = { registry, registeredNames: [], skipped: [] };
|
|
2091
|
+
const listed = await client.listTools();
|
|
2092
|
+
for (const mcpTool of listed.tools) {
|
|
2093
|
+
if (!mcpTool.name) {
|
|
2094
|
+
result.skipped.push({ name: "?", reason: "empty tool name" });
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
const registeredName = `${prefix}${mcpTool.name}`;
|
|
2098
|
+
registry.register({
|
|
2099
|
+
name: registeredName,
|
|
2100
|
+
description: mcpTool.description ?? "",
|
|
2101
|
+
parameters: mcpTool.inputSchema,
|
|
2102
|
+
fn: async (args) => {
|
|
2103
|
+
const toolResult = await client.callTool(mcpTool.name, args);
|
|
2104
|
+
return flattenMcpResult(toolResult);
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
result.registeredNames.push(registeredName);
|
|
2108
|
+
}
|
|
2109
|
+
return result;
|
|
2110
|
+
}
|
|
2111
|
+
function flattenMcpResult(result) {
|
|
2112
|
+
const parts = result.content.map(blockToString);
|
|
2113
|
+
const joined = parts.join("\n").trim();
|
|
2114
|
+
if (result.isError) {
|
|
2115
|
+
return `ERROR: ${joined || "(no error message from server)"}`;
|
|
2116
|
+
}
|
|
2117
|
+
return joined;
|
|
2118
|
+
}
|
|
2119
|
+
function blockToString(block) {
|
|
2120
|
+
if (block.type === "text") return block.text;
|
|
2121
|
+
if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
|
|
2122
|
+
return `[unknown block: ${JSON.stringify(block)}]`;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
1805
2125
|
// src/config.ts
|
|
1806
2126
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
1807
2127
|
import { homedir as homedir2 } from "os";
|
|
@@ -1846,23 +2166,93 @@ function redactKey(key) {
|
|
|
1846
2166
|
}
|
|
1847
2167
|
|
|
1848
2168
|
// src/index.ts
|
|
1849
|
-
var VERSION = "0.
|
|
2169
|
+
var VERSION = "0.3.0-alpha.1";
|
|
1850
2170
|
|
|
1851
2171
|
// src/cli/commands/chat.tsx
|
|
1852
2172
|
import { render } from "ink";
|
|
1853
|
-
import
|
|
2173
|
+
import React8, { useState as useState4 } from "react";
|
|
2174
|
+
|
|
2175
|
+
// src/mcp/shell-split.ts
|
|
2176
|
+
function shellSplit(input) {
|
|
2177
|
+
const tokens = [];
|
|
2178
|
+
let cur = "";
|
|
2179
|
+
let quote = null;
|
|
2180
|
+
let i = 0;
|
|
2181
|
+
const s = input;
|
|
2182
|
+
while (i < s.length) {
|
|
2183
|
+
const ch = s[i];
|
|
2184
|
+
if (quote) {
|
|
2185
|
+
if (ch === quote) {
|
|
2186
|
+
quote = null;
|
|
2187
|
+
i++;
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
if (ch === "\\" && quote === '"' && i + 1 < s.length) {
|
|
2191
|
+
cur += s[i + 1];
|
|
2192
|
+
i += 2;
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
cur += ch;
|
|
2196
|
+
i++;
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
if (ch === '"' || ch === "'") {
|
|
2200
|
+
quote = ch;
|
|
2201
|
+
i++;
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
2205
|
+
cur += s[i + 1];
|
|
2206
|
+
i += 2;
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2209
|
+
if (ch === " " || ch === " ") {
|
|
2210
|
+
if (cur.length > 0) {
|
|
2211
|
+
tokens.push(cur);
|
|
2212
|
+
cur = "";
|
|
2213
|
+
}
|
|
2214
|
+
i++;
|
|
2215
|
+
continue;
|
|
2216
|
+
}
|
|
2217
|
+
cur += ch;
|
|
2218
|
+
i++;
|
|
2219
|
+
}
|
|
2220
|
+
if (quote) {
|
|
2221
|
+
throw new Error(
|
|
2222
|
+
`shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
if (cur.length > 0) tokens.push(cur);
|
|
2226
|
+
return tokens;
|
|
2227
|
+
}
|
|
1854
2228
|
|
|
1855
2229
|
// src/cli/ui/App.tsx
|
|
1856
|
-
import { Box as
|
|
1857
|
-
import
|
|
2230
|
+
import { Box as Box6, Static, Text as Text6, useApp } from "ink";
|
|
2231
|
+
import React6, { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
1858
2232
|
|
|
1859
2233
|
// src/cli/ui/EventLog.tsx
|
|
1860
|
-
import { Box as
|
|
1861
|
-
import
|
|
2234
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
2235
|
+
import React3, { useEffect, useState } from "react";
|
|
1862
2236
|
|
|
1863
|
-
// src/cli/ui/
|
|
2237
|
+
// src/cli/ui/PlanStateBlock.tsx
|
|
1864
2238
|
import { Box, Text } from "ink";
|
|
1865
2239
|
import React from "react";
|
|
2240
|
+
function PlanStateBlock({ planState }) {
|
|
2241
|
+
const fields = [];
|
|
2242
|
+
if (planState.subgoals.length) fields.push(["subgoals", planState.subgoals, "cyan", false]);
|
|
2243
|
+
if (planState.hypotheses.length)
|
|
2244
|
+
fields.push(["hypotheses", planState.hypotheses, "green", false]);
|
|
2245
|
+
if (planState.uncertainties.length)
|
|
2246
|
+
fields.push(["uncertainties", planState.uncertainties, "yellow", false]);
|
|
2247
|
+
if (planState.rejectedPaths.length)
|
|
2248
|
+
fields.push(["rejected", planState.rejectedPaths, "red", true]);
|
|
2249
|
+
if (fields.length === 0) return null;
|
|
2250
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, fields.map(([label, items, color, dim]) => /* @__PURE__ */ React.createElement(Text, { key: label }, /* @__PURE__ */ React.createElement(Text, { color, bold: true, dimColor: dim }, "\u2039 ", label), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, ` (${items.length})`), /* @__PURE__ */ React.createElement(Text, null, `: ${items.join(" \xB7 ")}`))));
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/cli/ui/markdown.tsx
|
|
2254
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
2255
|
+
import React2 from "react";
|
|
1866
2256
|
var SUPERSCRIPT = {
|
|
1867
2257
|
"0": "\u2070",
|
|
1868
2258
|
"1": "\xB9",
|
|
@@ -1919,27 +2309,27 @@ function InlineMd({ text }) {
|
|
|
1919
2309
|
for (const m of text.matchAll(INLINE_RE)) {
|
|
1920
2310
|
const start = m.index ?? 0;
|
|
1921
2311
|
if (start > last) {
|
|
1922
|
-
parts.push(/* @__PURE__ */
|
|
2312
|
+
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `t${idx++}` }, text.slice(last, start)));
|
|
1923
2313
|
}
|
|
1924
2314
|
if (m[2] !== void 0) {
|
|
1925
2315
|
parts.push(
|
|
1926
|
-
/* @__PURE__ */
|
|
2316
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `b${idx++}`, bold: true }, m[2])
|
|
1927
2317
|
);
|
|
1928
2318
|
} else if (m[3] !== void 0) {
|
|
1929
2319
|
parts.push(
|
|
1930
|
-
/* @__PURE__ */
|
|
2320
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `c${idx++}`, color: "yellow" }, m[3])
|
|
1931
2321
|
);
|
|
1932
2322
|
} else if (m[4] !== void 0) {
|
|
1933
2323
|
parts.push(
|
|
1934
|
-
/* @__PURE__ */
|
|
2324
|
+
/* @__PURE__ */ React2.createElement(Text2, { key: `i${idx++}`, italic: true }, m[4])
|
|
1935
2325
|
);
|
|
1936
2326
|
}
|
|
1937
2327
|
last = start + m[0].length;
|
|
1938
2328
|
}
|
|
1939
2329
|
if (last < text.length) {
|
|
1940
|
-
parts.push(/* @__PURE__ */
|
|
2330
|
+
parts.push(/* @__PURE__ */ React2.createElement(Text2, { key: `t${idx++}` }, text.slice(last)));
|
|
1941
2331
|
}
|
|
1942
|
-
return /* @__PURE__ */
|
|
2332
|
+
return /* @__PURE__ */ React2.createElement(Text2, null, parts);
|
|
1943
2333
|
}
|
|
1944
2334
|
function parseBlocks(raw) {
|
|
1945
2335
|
const lines = raw.split(/\r?\n/);
|
|
@@ -2033,42 +2423,42 @@ function parseBlocks(raw) {
|
|
|
2033
2423
|
function BlockView({ block }) {
|
|
2034
2424
|
switch (block.kind) {
|
|
2035
2425
|
case "heading":
|
|
2036
|
-
return /* @__PURE__ */
|
|
2426
|
+
return /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "cyan" }, /* @__PURE__ */ React2.createElement(InlineMd, { text: block.text }));
|
|
2037
2427
|
case "paragraph":
|
|
2038
|
-
return /* @__PURE__ */
|
|
2428
|
+
return /* @__PURE__ */ React2.createElement(InlineMd, { text: block.text });
|
|
2039
2429
|
case "bullet":
|
|
2040
|
-
return /* @__PURE__ */
|
|
2430
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column" }, block.items.map((item, i) => /* @__PURE__ */ React2.createElement(Box2, { key: `${i}-${item.slice(0, 24)}` }, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, block.ordered ? ` ${block.start + i}. ` : " \u2022 "), /* @__PURE__ */ React2.createElement(InlineMd, { text: item }))));
|
|
2041
2431
|
case "code":
|
|
2042
|
-
return /* @__PURE__ */
|
|
2432
|
+
return /* @__PURE__ */ React2.createElement(Box2, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, block.text));
|
|
2043
2433
|
case "hr":
|
|
2044
|
-
return /* @__PURE__ */
|
|
2434
|
+
return /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2045
2435
|
}
|
|
2046
2436
|
}
|
|
2047
2437
|
function Markdown({ text }) {
|
|
2048
2438
|
const cleaned = stripMath(text);
|
|
2049
|
-
const blocks =
|
|
2050
|
-
return /* @__PURE__ */
|
|
2439
|
+
const blocks = React2.useMemo(() => parseBlocks(cleaned), [cleaned]);
|
|
2440
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", gap: 1 }, blocks.map((b, i) => /* @__PURE__ */ React2.createElement(BlockView, { key: `${i}-${b.kind}`, block: b })));
|
|
2051
2441
|
}
|
|
2052
2442
|
|
|
2053
2443
|
// src/cli/ui/EventLog.tsx
|
|
2054
|
-
var EventRow =
|
|
2444
|
+
var EventRow = React3.memo(function EventRow2({ event }) {
|
|
2055
2445
|
if (event.role === "user") {
|
|
2056
|
-
return /* @__PURE__ */
|
|
2446
|
+
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React3.createElement(Text3, null, event.text));
|
|
2057
2447
|
}
|
|
2058
2448
|
if (event.role === "assistant") {
|
|
2059
|
-
if (event.streaming) return /* @__PURE__ */
|
|
2060
|
-
return /* @__PURE__ */
|
|
2449
|
+
if (event.streaming) return /* @__PURE__ */ React3.createElement(StreamingAssistant, { event });
|
|
2450
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "assistant")), event.branch ? /* @__PURE__ */ React3.createElement(BranchBlock, { branch: event.branch }) : null, event.reasoning ? /* @__PURE__ */ React3.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React3.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React3.createElement(Markdown, { text: event.text }) : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React3.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React3.createElement(Text3, { color: "magenta" }, event.repair) : null);
|
|
2061
2451
|
}
|
|
2062
2452
|
if (event.role === "tool") {
|
|
2063
|
-
return /* @__PURE__ */
|
|
2453
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, `tool<${event.toolName ?? "?"}> \u2192`), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", truncate2(event.text, 400)));
|
|
2064
2454
|
}
|
|
2065
2455
|
if (event.role === "error") {
|
|
2066
|
-
return /* @__PURE__ */
|
|
2456
|
+
return /* @__PURE__ */ React3.createElement(Box3, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React3.createElement(Text3, { color: "red" }, event.text));
|
|
2067
2457
|
}
|
|
2068
2458
|
if (event.role === "info") {
|
|
2069
|
-
return /* @__PURE__ */
|
|
2459
|
+
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, event.text));
|
|
2070
2460
|
}
|
|
2071
|
-
return /* @__PURE__ */
|
|
2461
|
+
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, null, event.text));
|
|
2072
2462
|
});
|
|
2073
2463
|
function BranchBlock({ branch }) {
|
|
2074
2464
|
const per = branch.uncertainties.map((u, i) => {
|
|
@@ -2076,21 +2466,13 @@ function BranchBlock({ branch }) {
|
|
|
2076
2466
|
const t = (branch.temperatures[i] ?? 0).toFixed(1);
|
|
2077
2467
|
return `${marker} #${i} T=${t} u=${u}`;
|
|
2078
2468
|
}).join(" ");
|
|
2079
|
-
return /* @__PURE__ */
|
|
2080
|
-
}
|
|
2081
|
-
function PlanStateBlock({ planState }) {
|
|
2082
|
-
const lines = [];
|
|
2083
|
-
if (planState.subgoals.length) lines.push(["subgoals", planState.subgoals]);
|
|
2084
|
-
if (planState.hypotheses.length) lines.push(["hypotheses", planState.hypotheses]);
|
|
2085
|
-
if (planState.uncertainties.length) lines.push(["uncertainties", planState.uncertainties]);
|
|
2086
|
-
if (planState.rejectedPaths.length) lines.push(["rejected", planState.rejectedPaths]);
|
|
2087
|
-
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, lines.map(([label, items]) => /* @__PURE__ */ React2.createElement(Text2, { key: label, color: "magenta" }, "\u2039 ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, label), ` (${items.length}): ${items.join(" \xB7 ")}`)));
|
|
2469
|
+
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, "\u{1F500} branched ", /* @__PURE__ */ React3.createElement(Text3, { bold: true }, branch.budget), ` samples \u2192 picked #${branch.chosenIndex} `, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, per)));
|
|
2088
2470
|
}
|
|
2089
2471
|
function ReasoningBlock({ reasoning }) {
|
|
2090
2472
|
const max = 220;
|
|
2091
2473
|
const flat = reasoning.replace(/\s+/g, " ").trim();
|
|
2092
2474
|
const preview = flat.length <= max ? flat : `${flat.slice(0, max)}\u2026 (+${flat.length - max} chars)`;
|
|
2093
|
-
return /* @__PURE__ */
|
|
2475
|
+
return /* @__PURE__ */ React3.createElement(Box3, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", preview));
|
|
2094
2476
|
}
|
|
2095
2477
|
function Elapsed() {
|
|
2096
2478
|
const [s, setS] = useState(0);
|
|
@@ -2101,20 +2483,20 @@ function Elapsed() {
|
|
|
2101
2483
|
}, []);
|
|
2102
2484
|
const mm = String(Math.floor(s / 60)).padStart(2, "0");
|
|
2103
2485
|
const ss = String(s % 60).padStart(2, "0");
|
|
2104
|
-
return /* @__PURE__ */
|
|
2486
|
+
return /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, `${mm}:${ss}`);
|
|
2105
2487
|
}
|
|
2106
2488
|
function StreamingAssistant({ event }) {
|
|
2107
2489
|
if (event.branchProgress) {
|
|
2108
2490
|
const p = event.branchProgress;
|
|
2109
2491
|
if (p.completed === 0) {
|
|
2110
|
-
return /* @__PURE__ */
|
|
2492
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, "\u{1F500} launching ", p.total, " parallel samples (R1 thinking in parallel)\u2026", " "), /* @__PURE__ */ React3.createElement(Elapsed, null)), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", "spread across T=0.0/0.5/1.0 \xB7 typical wait 30-90s for reasoner"));
|
|
2111
2493
|
}
|
|
2112
2494
|
const pct2 = Math.round(p.completed / p.total * 100);
|
|
2113
|
-
return /* @__PURE__ */
|
|
2495
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React3.createElement(Text3, { color: "blue" }, "\u{1F500} branching ", p.completed, "/", p.total, " (", pct2, "%)", " "), /* @__PURE__ */ React3.createElement(Elapsed, null)), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " latest #", p.latestIndex, " T=", p.latestTemperature.toFixed(1), " u=", p.latestUncertainties, p.completed < p.total ? " \xB7 waiting for other samples\u2026" : " \xB7 selecting winner\u2026"));
|
|
2114
2496
|
}
|
|
2115
2497
|
const tail = lastLine(event.text, 140);
|
|
2116
2498
|
const reasoningTail = event.reasoning ? lastLine(event.reasoning, 120) : "";
|
|
2117
|
-
return /* @__PURE__ */
|
|
2499
|
+
return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "green" }, "assistant", " "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(streaming \xB7 ", event.text.length, event.reasoning ? ` + think ${event.reasoning.length}` : "", " chars)", " "), /* @__PURE__ */ React3.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React3.createElement(Text3, { dimColor: true, italic: true }, "\u21B3 thinking: ", reasoningTail) : null, tail ? /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "\u25B8 ", tail) : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true, italic: true }, " (waiting for first token\u2026)"));
|
|
2118
2500
|
}
|
|
2119
2501
|
function lastLine(s, maxChars) {
|
|
2120
2502
|
const flat = s.replace(/\s+/g, " ").trim();
|
|
@@ -2123,16 +2505,16 @@ function lastLine(s, maxChars) {
|
|
|
2123
2505
|
}
|
|
2124
2506
|
function StatsLine({ stats }) {
|
|
2125
2507
|
const hit = (stats.cacheHitRatio * 100).toFixed(1);
|
|
2126
|
-
return /* @__PURE__ */
|
|
2508
|
+
return /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " \u21B3 cache ", hit, "% \xB7 tokens ", stats.usage.promptTokens, "\u2192", stats.usage.completionTokens, " \xB7 $", stats.cost.toFixed(6));
|
|
2127
2509
|
}
|
|
2128
2510
|
function truncate2(s, max) {
|
|
2129
2511
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
2130
2512
|
}
|
|
2131
2513
|
|
|
2132
2514
|
// src/cli/ui/PromptInput.tsx
|
|
2133
|
-
import { Box as
|
|
2515
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
2134
2516
|
import TextInput from "ink-text-input";
|
|
2135
|
-
import
|
|
2517
|
+
import React4 from "react";
|
|
2136
2518
|
function PromptInput({
|
|
2137
2519
|
value,
|
|
2138
2520
|
onChange,
|
|
@@ -2141,7 +2523,7 @@ function PromptInput({
|
|
|
2141
2523
|
placeholder
|
|
2142
2524
|
}) {
|
|
2143
2525
|
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? "type a message, or /command";
|
|
2144
|
-
return /* @__PURE__ */
|
|
2526
|
+
return /* @__PURE__ */ React4.createElement(Box4, { borderStyle: "round", borderColor: disabled ? "gray" : "cyan", paddingX: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true, color: disabled ? "gray" : "cyan" }, "you \u203A", " "), /* @__PURE__ */ React4.createElement(
|
|
2145
2527
|
TextInput,
|
|
2146
2528
|
{
|
|
2147
2529
|
value,
|
|
@@ -2154,8 +2536,8 @@ function PromptInput({
|
|
|
2154
2536
|
}
|
|
2155
2537
|
|
|
2156
2538
|
// src/cli/ui/StatsPanel.tsx
|
|
2157
|
-
import { Box as
|
|
2158
|
-
import
|
|
2539
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
2540
|
+
import React5 from "react";
|
|
2159
2541
|
function StatsPanel({
|
|
2160
2542
|
summary,
|
|
2161
2543
|
model,
|
|
@@ -2166,7 +2548,7 @@ function StatsPanel({
|
|
|
2166
2548
|
const hitPct = (summary.cacheHitRatio * 100).toFixed(1);
|
|
2167
2549
|
const hitColor = summary.cacheHitRatio >= 0.7 ? "green" : summary.cacheHitRatio >= 0.4 ? "yellow" : "red";
|
|
2168
2550
|
const branchOn = (branchBudget ?? 1) > 1;
|
|
2169
|
-
return /* @__PURE__ */
|
|
2551
|
+
return /* @__PURE__ */ React5.createElement(Box5, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React5.createElement(Box5, { justifyContent: "space-between" }, /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "cyan", bold: true }, "Reasonix"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 model "), /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, model), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 prefix "), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, prefixHash), harvestOn ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React5.createElement(Text5, { color: "blue" }, " \xB7 branch", branchBudget) : null), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "turns ", summary.turns, " \xB7 type /help")), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "cache hit "), /* @__PURE__ */ React5.createElement(Text5, { color: hitColor, bold: true }, hitPct, "%")), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "cost "), /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, "$", summary.totalCostUsd.toFixed(6))), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "vs Claude "), /* @__PURE__ */ React5.createElement(Text5, null, "$", summary.claudeEquivalentUsd.toFixed(6))), /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "saving "), /* @__PURE__ */ React5.createElement(Text5, { color: "green", bold: true }, summary.savingsVsClaudePct.toFixed(1), "%"))));
|
|
2170
2552
|
}
|
|
2171
2553
|
|
|
2172
2554
|
// src/cli/ui/slash.ts
|
|
@@ -2299,7 +2681,7 @@ function handleSlash(cmd, args, loop) {
|
|
|
2299
2681
|
|
|
2300
2682
|
// src/cli/ui/App.tsx
|
|
2301
2683
|
var FLUSH_INTERVAL_MS = 60;
|
|
2302
|
-
function App({ model, system, transcript, harvest: harvest2, branch, session }) {
|
|
2684
|
+
function App({ model, system, transcript, harvest: harvest2, branch, session, tools }) {
|
|
2303
2685
|
const { exit } = useApp();
|
|
2304
2686
|
const [historical, setHistorical] = useState2([]);
|
|
2305
2687
|
const [streaming, setStreaming] = useState2(null);
|
|
@@ -2330,11 +2712,14 @@ function App({ model, system, transcript, harvest: harvest2, branch, session })
|
|
|
2330
2712
|
const loop = useMemo(() => {
|
|
2331
2713
|
if (loopRef.current) return loopRef.current;
|
|
2332
2714
|
const client = new DeepSeekClient();
|
|
2333
|
-
const prefix = new ImmutablePrefix({
|
|
2334
|
-
|
|
2715
|
+
const prefix = new ImmutablePrefix({
|
|
2716
|
+
system,
|
|
2717
|
+
toolSpecs: tools?.specs()
|
|
2718
|
+
});
|
|
2719
|
+
const l = new CacheFirstLoop({ client, prefix, tools, model, harvest: harvest2, branch, session });
|
|
2335
2720
|
loopRef.current = l;
|
|
2336
2721
|
return l;
|
|
2337
|
-
}, [model, system, harvest2, branch, session]);
|
|
2722
|
+
}, [model, system, harvest2, branch, session, tools]);
|
|
2338
2723
|
const sessionBannerShown = useRef(false);
|
|
2339
2724
|
useEffect2(() => {
|
|
2340
2725
|
if (sessionBannerShown.current) return;
|
|
@@ -2497,7 +2882,7 @@ function App({ model, system, transcript, harvest: harvest2, branch, session })
|
|
|
2497
2882
|
},
|
|
2498
2883
|
[busy, exit, loop, writeTranscript]
|
|
2499
2884
|
);
|
|
2500
|
-
return /* @__PURE__ */
|
|
2885
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(
|
|
2501
2886
|
StatsPanel,
|
|
2502
2887
|
{
|
|
2503
2888
|
summary,
|
|
@@ -2506,10 +2891,10 @@ function App({ model, system, transcript, harvest: harvest2, branch, session })
|
|
|
2506
2891
|
harvestOn: loop.harvestEnabled,
|
|
2507
2892
|
branchBudget: loop.branchOptions.budget
|
|
2508
2893
|
}
|
|
2509
|
-
), /* @__PURE__ */
|
|
2894
|
+
), /* @__PURE__ */ React6.createElement(Static, { items: historical }, (item) => /* @__PURE__ */ React6.createElement(EventRow, { key: item.id, event: item })), streaming ? /* @__PURE__ */ React6.createElement(Box6, { marginY: 1 }, /* @__PURE__ */ React6.createElement(EventRow, { event: streaming })) : null, /* @__PURE__ */ React6.createElement(PromptInput, { value: input, onChange: setInput, onSubmit: handleSubmit, disabled: busy }), /* @__PURE__ */ React6.createElement(CommandStrip, null));
|
|
2510
2895
|
}
|
|
2511
2896
|
function CommandStrip() {
|
|
2512
|
-
return /* @__PURE__ */
|
|
2897
|
+
return /* @__PURE__ */ React6.createElement(Box6, { paddingX: 2 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "/help \xB7 /preset ", "<fast|smart|max>", " \xB7 /sessions \xB7 /model \xB7 /harvest \xB7 /branch \xB7 /clear \xB7 /exit"));
|
|
2513
2898
|
}
|
|
2514
2899
|
function describeRepair(repair) {
|
|
2515
2900
|
const parts = [];
|
|
@@ -2520,9 +2905,9 @@ function describeRepair(repair) {
|
|
|
2520
2905
|
}
|
|
2521
2906
|
|
|
2522
2907
|
// src/cli/ui/Setup.tsx
|
|
2523
|
-
import { Box as
|
|
2908
|
+
import { Box as Box7, Text as Text7, useApp as useApp2 } from "ink";
|
|
2524
2909
|
import TextInput2 from "ink-text-input";
|
|
2525
|
-
import
|
|
2910
|
+
import React7, { useState as useState3 } from "react";
|
|
2526
2911
|
function Setup({ onReady }) {
|
|
2527
2912
|
const [value, setValue] = useState3("");
|
|
2528
2913
|
const [error, setError] = useState3(null);
|
|
@@ -2546,7 +2931,7 @@ function Setup({ onReady }) {
|
|
|
2546
2931
|
}
|
|
2547
2932
|
onReady(trimmed);
|
|
2548
2933
|
};
|
|
2549
|
-
return /* @__PURE__ */
|
|
2934
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React7.createElement(
|
|
2550
2935
|
TextInput2,
|
|
2551
2936
|
{
|
|
2552
2937
|
value,
|
|
@@ -2555,14 +2940,14 @@ function Setup({ onReady }) {
|
|
|
2555
2940
|
mask: "\u2022",
|
|
2556
2941
|
placeholder: "sk-..."
|
|
2557
2942
|
}
|
|
2558
|
-
)), error ? /* @__PURE__ */
|
|
2943
|
+
)), error ? /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { color: "red" }, error)) : value ? /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "(Type /exit to abort.)")));
|
|
2559
2944
|
}
|
|
2560
2945
|
|
|
2561
2946
|
// src/cli/commands/chat.tsx
|
|
2562
|
-
function Root({ initialKey, ...appProps }) {
|
|
2947
|
+
function Root({ initialKey, tools, ...appProps }) {
|
|
2563
2948
|
const [key, setKey] = useState4(initialKey);
|
|
2564
2949
|
if (!key) {
|
|
2565
|
-
return /* @__PURE__ */
|
|
2950
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2566
2951
|
Setup,
|
|
2567
2952
|
{
|
|
2568
2953
|
onReady: (k) => {
|
|
@@ -2573,7 +2958,7 @@ function Root({ initialKey, ...appProps }) {
|
|
|
2573
2958
|
);
|
|
2574
2959
|
}
|
|
2575
2960
|
process.env.DEEPSEEK_API_KEY = key;
|
|
2576
|
-
return /* @__PURE__ */
|
|
2961
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2577
2962
|
App,
|
|
2578
2963
|
{
|
|
2579
2964
|
model: appProps.model,
|
|
@@ -2581,51 +2966,86 @@ function Root({ initialKey, ...appProps }) {
|
|
|
2581
2966
|
transcript: appProps.transcript,
|
|
2582
2967
|
harvest: appProps.harvest,
|
|
2583
2968
|
branch: appProps.branch,
|
|
2584
|
-
session: appProps.session
|
|
2969
|
+
session: appProps.session,
|
|
2970
|
+
tools
|
|
2585
2971
|
}
|
|
2586
2972
|
);
|
|
2587
2973
|
}
|
|
2588
2974
|
async function chatCommand(opts) {
|
|
2589
2975
|
loadDotenv();
|
|
2590
2976
|
const initialKey = loadApiKey();
|
|
2591
|
-
|
|
2977
|
+
let mcp;
|
|
2978
|
+
let tools;
|
|
2979
|
+
if (opts.mcp) {
|
|
2980
|
+
const argv = shellSplit(opts.mcp);
|
|
2981
|
+
if (argv.length === 0) {
|
|
2982
|
+
process.stderr.write("error: --mcp requires a command\n");
|
|
2983
|
+
process.exit(2);
|
|
2984
|
+
}
|
|
2985
|
+
const [command, ...args] = argv;
|
|
2986
|
+
if (!command) {
|
|
2987
|
+
process.stderr.write("error: --mcp command is empty\n");
|
|
2988
|
+
process.exit(2);
|
|
2989
|
+
}
|
|
2990
|
+
const transport = new StdioTransport({ command, args });
|
|
2991
|
+
mcp = new McpClient({ transport });
|
|
2992
|
+
try {
|
|
2993
|
+
await mcp.initialize();
|
|
2994
|
+
const bridge = await bridgeMcpTools(mcp, { namePrefix: opts.mcpPrefix });
|
|
2995
|
+
tools = bridge.registry;
|
|
2996
|
+
process.stderr.write(
|
|
2997
|
+
`\u25B8 MCP: ${bridge.registeredNames.length} tool(s) from ${argv.join(" ")}
|
|
2998
|
+
`
|
|
2999
|
+
);
|
|
3000
|
+
} catch (err) {
|
|
3001
|
+
process.stderr.write(`MCP setup failed: ${err.message}
|
|
3002
|
+
`);
|
|
3003
|
+
await mcp.close();
|
|
3004
|
+
process.exit(1);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
const { waitUntilExit } = render(/* @__PURE__ */ React8.createElement(Root, { initialKey, tools, ...opts }), {
|
|
2592
3008
|
exitOnCtrlC: true
|
|
2593
3009
|
});
|
|
2594
|
-
|
|
3010
|
+
try {
|
|
3011
|
+
await waitUntilExit();
|
|
3012
|
+
} finally {
|
|
3013
|
+
await mcp?.close();
|
|
3014
|
+
}
|
|
2595
3015
|
}
|
|
2596
3016
|
|
|
2597
3017
|
// src/cli/commands/diff.ts
|
|
2598
3018
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2599
3019
|
import { basename } from "path";
|
|
2600
3020
|
import { render as render2 } from "ink";
|
|
2601
|
-
import
|
|
3021
|
+
import React11 from "react";
|
|
2602
3022
|
|
|
2603
3023
|
// src/cli/ui/DiffApp.tsx
|
|
2604
|
-
import { Box as
|
|
2605
|
-
import
|
|
3024
|
+
import { Box as Box9, Static as Static2, Text as Text9, useApp as useApp3, useInput } from "ink";
|
|
3025
|
+
import React10, { useState as useState5 } from "react";
|
|
2606
3026
|
|
|
2607
3027
|
// src/cli/ui/RecordView.tsx
|
|
2608
|
-
import { Box as
|
|
2609
|
-
import
|
|
3028
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
3029
|
+
import React9 from "react";
|
|
2610
3030
|
function RecordView({ rec, compact = false }) {
|
|
2611
3031
|
const toolArgsMax = compact ? 120 : 200;
|
|
2612
3032
|
const toolContentMax = compact ? 200 : 400;
|
|
2613
3033
|
if (rec.role === "user") {
|
|
2614
|
-
return /* @__PURE__ */
|
|
3034
|
+
return /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "cyan" }, "you \u203A", " "), /* @__PURE__ */ React9.createElement(Text8, null, rec.content));
|
|
2615
3035
|
}
|
|
2616
3036
|
if (rec.role === "assistant_final") {
|
|
2617
|
-
return /* @__PURE__ */
|
|
3037
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { bold: true, color: "green" }, "assistant"), rec.cost !== void 0 ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ React9.createElement(CacheBadge, { usage: rec.usage }) : null), rec.planState ? /* @__PURE__ */ React9.createElement(PlanStateBlock, { planState: rec.planState }) : null, rec.content ? /* @__PURE__ */ React9.createElement(Text8, null, rec.content) : /* @__PURE__ */ React9.createElement(Text8, { dimColor: true, italic: true }, "(tool-call response only)"));
|
|
2618
3038
|
}
|
|
2619
3039
|
if (rec.role === "tool") {
|
|
2620
|
-
return /* @__PURE__ */
|
|
3040
|
+
return /* @__PURE__ */ React9.createElement(Box8, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "yellow" }, "tool<", rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " args: ", truncate3(rec.args, toolArgsMax)) : null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \u2192 ", truncate3(rec.content, toolContentMax)));
|
|
2621
3041
|
}
|
|
2622
3042
|
if (rec.role === "error") {
|
|
2623
|
-
return /* @__PURE__ */
|
|
3043
|
+
return /* @__PURE__ */ React9.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text8, { color: "red", bold: true }, "error", " "), /* @__PURE__ */ React9.createElement(Text8, { color: "red" }, rec.error ?? rec.content));
|
|
2624
3044
|
}
|
|
2625
3045
|
if (rec.role === "done" || rec.role === "assistant_delta") {
|
|
2626
3046
|
return null;
|
|
2627
3047
|
}
|
|
2628
|
-
return /* @__PURE__ */
|
|
3048
|
+
return /* @__PURE__ */ React9.createElement(Box8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, "[", rec.role, "] ", rec.content));
|
|
2629
3049
|
}
|
|
2630
3050
|
function CacheBadge({ usage }) {
|
|
2631
3051
|
const hit = usage.prompt_cache_hit_tokens ?? 0;
|
|
@@ -2634,7 +3054,7 @@ function CacheBadge({ usage }) {
|
|
|
2634
3054
|
if (total === 0) return null;
|
|
2635
3055
|
const pct2 = hit / total * 100;
|
|
2636
3056
|
const color = pct2 >= 70 ? "green" : pct2 >= 40 ? "yellow" : "red";
|
|
2637
|
-
return /* @__PURE__ */
|
|
3057
|
+
return /* @__PURE__ */ React9.createElement(Text8, null, /* @__PURE__ */ React9.createElement(Text8, { dimColor: true }, " \xB7 cache "), /* @__PURE__ */ React9.createElement(Text8, { color }, pct2.toFixed(1), "%"));
|
|
2638
3058
|
}
|
|
2639
3059
|
function truncate3(s, max) {
|
|
2640
3060
|
return s.length <= max ? s : `${s.slice(0, max)}\u2026 (+${s.length - max} chars)`;
|
|
@@ -2668,7 +3088,7 @@ function DiffApp({ report }) {
|
|
|
2668
3088
|
}
|
|
2669
3089
|
});
|
|
2670
3090
|
const pair = report.pairs[idx];
|
|
2671
|
-
return /* @__PURE__ */
|
|
3091
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column" }, /* @__PURE__ */ React10.createElement(DiffHeader, { report }), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, paddingX: 1, justifyContent: "space-between" }, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan", bold: true }, "turn ", pair?.turn ?? "?", " (", idx + 1, " / ", report.pairs.length, ")"), /* @__PURE__ */ React10.createElement(Text9, null, pair ? /* @__PURE__ */ React10.createElement(KindBadge, { kind: pair.kind }) : null)), /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React10.createElement(Pane, { label: report.a.label, headerColor: "blue", records: paneRecords(pair, "a") }), /* @__PURE__ */ React10.createElement(Pane, { label: report.b.label, headerColor: "magenta", records: paneRecords(pair, "b") })), pair?.divergenceNote ? /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u2605 "), /* @__PURE__ */ React10.createElement(Text9, null, pair.divergenceNote)) : null, /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "j"), "/", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "\u2193"), " next \xB7 ", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "k"), "/", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "\u2191"), " ", "prev \xB7 ", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "n"), " next-diverge \xB7 ", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "N"), "/", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "p"), " ", "prev-diverge \xB7 ", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "g"), "/", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "G"), " first/last \xB7 ", /* @__PURE__ */ React10.createElement(Text9, { bold: true }, "q"), " ", "quit")));
|
|
2672
3092
|
}
|
|
2673
3093
|
function DiffHeader({ report }) {
|
|
2674
3094
|
const a = report.a;
|
|
@@ -2686,15 +3106,15 @@ function DiffHeader({ report }) {
|
|
|
2686
3106
|
} else if (a.stats.prefixHashes[0] && a.stats.prefixHashes[0] === b.stats.prefixHashes[0]) {
|
|
2687
3107
|
prefixLine = `shared prefix hash ${a.stats.prefixHashes[0].slice(0, 12)}\u2026 \u2014 cache delta attributable to log stability, not prompt change.`;
|
|
2688
3108
|
}
|
|
2689
|
-
return /* @__PURE__ */
|
|
3109
|
+
return /* @__PURE__ */ React10.createElement(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React10.createElement(Box9, { justifyContent: "space-between" }, /* @__PURE__ */ React10.createElement(Text9, null, /* @__PURE__ */ React10.createElement(Text9, { color: "cyan", bold: true }, "reasonix diff"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \xB7 A="), /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, a.label), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " vs B="), /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, b.label)), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, report.pairs.length, " turns aligned")), /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React10.createElement(Text9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "cache "), /* @__PURE__ */ React10.createElement(Text9, null, (a.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React10.createElement(Text9, null, (b.stats.cacheHitRatio * 100).toFixed(1), "%"), /* @__PURE__ */ React10.createElement(Text9, { color: cacheDelta >= 0 ? "green" : "red", bold: true }, " ", cacheDelta >= 0 ? "+" : "", (cacheDelta * 100).toFixed(1), "pp")), /* @__PURE__ */ React10.createElement(Text9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "cost "), /* @__PURE__ */ React10.createElement(Text9, null, "$", a.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, " \u2192 "), /* @__PURE__ */ React10.createElement(Text9, null, "$", b.stats.totalCostUsd.toFixed(6)), /* @__PURE__ */ React10.createElement(Text9, { color: costDelta2 <= 0 ? "green" : "red", bold: true }, " ", costDelta2 >= 0 ? "+" : "", costDelta2.toFixed(1), "%")), /* @__PURE__ */ React10.createElement(Text9, null, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true }, "model calls "), /* @__PURE__ */ React10.createElement(Text9, null, a.stats.turns, " \u2192 ", b.stats.turns))), prefixLine ? /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, italic: true }, prefixLine)) : null);
|
|
2690
3110
|
}
|
|
2691
3111
|
function Pane({
|
|
2692
3112
|
label,
|
|
2693
3113
|
headerColor,
|
|
2694
3114
|
records
|
|
2695
3115
|
}) {
|
|
2696
|
-
return /* @__PURE__ */
|
|
2697
|
-
|
|
3116
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3117
|
+
Box9,
|
|
2698
3118
|
{
|
|
2699
3119
|
flexDirection: "column",
|
|
2700
3120
|
flexGrow: 1,
|
|
@@ -2702,21 +3122,21 @@ function Pane({
|
|
|
2702
3122
|
borderStyle: "single",
|
|
2703
3123
|
borderColor: headerColor
|
|
2704
3124
|
},
|
|
2705
|
-
/* @__PURE__ */
|
|
2706
|
-
records.length === 0 ? /* @__PURE__ */
|
|
3125
|
+
/* @__PURE__ */ React10.createElement(Text9, { color: headerColor, bold: true }, label),
|
|
3126
|
+
records.length === 0 ? /* @__PURE__ */ React10.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React10.createElement(Text9, { dimColor: true, italic: true }, "(no records on this side for this turn)")) : /* @__PURE__ */ React10.createElement(Static2, { items: records.map((rec, i) => ({ key: `${label}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React10.createElement(RecordView, { key, rec, compact: true }))
|
|
2707
3127
|
);
|
|
2708
3128
|
}
|
|
2709
3129
|
function KindBadge({ kind }) {
|
|
2710
3130
|
if (kind === "match") {
|
|
2711
|
-
return /* @__PURE__ */
|
|
3131
|
+
return /* @__PURE__ */ React10.createElement(Text9, { color: "green" }, "\u2713 match");
|
|
2712
3132
|
}
|
|
2713
3133
|
if (kind === "diverge") {
|
|
2714
|
-
return /* @__PURE__ */
|
|
3134
|
+
return /* @__PURE__ */ React10.createElement(Text9, { color: "yellow" }, "\u2605 diverge");
|
|
2715
3135
|
}
|
|
2716
3136
|
if (kind === "only_in_a") {
|
|
2717
|
-
return /* @__PURE__ */
|
|
3137
|
+
return /* @__PURE__ */ React10.createElement(Text9, { color: "blue" }, "\u2190 only in A");
|
|
2718
3138
|
}
|
|
2719
|
-
return /* @__PURE__ */
|
|
3139
|
+
return /* @__PURE__ */ React10.createElement(Text9, { color: "magenta" }, "\u2192 only in B");
|
|
2720
3140
|
}
|
|
2721
3141
|
function paneRecords(pair, side) {
|
|
2722
3142
|
if (!pair) return [];
|
|
@@ -2747,7 +3167,7 @@ markdown report written to ${opts.mdPath}`);
|
|
|
2747
3167
|
return;
|
|
2748
3168
|
}
|
|
2749
3169
|
if (wantTui) {
|
|
2750
|
-
const { waitUntilExit } = render2(
|
|
3170
|
+
const { waitUntilExit } = render2(React11.createElement(DiffApp, { report }), {
|
|
2751
3171
|
exitOnCtrlC: true
|
|
2752
3172
|
});
|
|
2753
3173
|
await waitUntilExit();
|
|
@@ -2758,11 +3178,11 @@ markdown report written to ${opts.mdPath}`);
|
|
|
2758
3178
|
|
|
2759
3179
|
// src/cli/commands/replay.ts
|
|
2760
3180
|
import { render as render3 } from "ink";
|
|
2761
|
-
import
|
|
3181
|
+
import React13 from "react";
|
|
2762
3182
|
|
|
2763
3183
|
// src/cli/ui/ReplayApp.tsx
|
|
2764
|
-
import { Box as
|
|
2765
|
-
import
|
|
3184
|
+
import { Box as Box10, Static as Static3, Text as Text10, useApp as useApp4, useInput as useInput2 } from "ink";
|
|
3185
|
+
import React12, { useMemo as useMemo2, useState as useState6 } from "react";
|
|
2766
3186
|
function ReplayApp({ meta, pages }) {
|
|
2767
3187
|
const { exit } = useApp4();
|
|
2768
3188
|
const maxIdx = Math.max(0, pages.length - 1);
|
|
@@ -2797,14 +3217,14 @@ function ReplayApp({ meta, pages }) {
|
|
|
2797
3217
|
const prefixHash = cumStats.prefixHashes.length === 1 ? cumStats.prefixHashes[0].slice(0, 16) : cumStats.prefixHashes.length === 0 ? "(untracked)" : `(churned \xD7${cumStats.prefixHashes.length})`;
|
|
2798
3218
|
const currentPage = pages[idx];
|
|
2799
3219
|
const progressLabel = pages.length === 0 ? "empty transcript" : `turn ${idx + 1} / ${pages.length}`;
|
|
2800
|
-
return /* @__PURE__ */
|
|
3220
|
+
return /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(
|
|
2801
3221
|
StatsPanel,
|
|
2802
3222
|
{
|
|
2803
3223
|
summary,
|
|
2804
3224
|
model: cumStats.models[0] ?? meta?.model ?? "?",
|
|
2805
3225
|
prefixHash
|
|
2806
3226
|
}
|
|
2807
|
-
), /* @__PURE__ */
|
|
3227
|
+
), /* @__PURE__ */ React12.createElement(Box10, { flexDirection: "column", marginTop: 1, paddingX: 1 }, /* @__PURE__ */ React12.createElement(Box10, { justifyContent: "space-between" }, /* @__PURE__ */ React12.createElement(Text10, { color: "cyan", bold: true }, progressLabel), meta ? /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, meta.source, meta.task ? ` \xB7 ${meta.task}` : "", meta.mode ? ` \xB7 ${meta.mode}` : "") : null), currentPage ? /* @__PURE__ */ React12.createElement(Static3, { items: currentPage.records.map((rec, i) => ({ key: `${idx}-${i}`, rec })) }, ({ key, rec }) => /* @__PURE__ */ React12.createElement(RecordView, { key, rec })) : /* @__PURE__ */ React12.createElement(Text10, { dimColor: true, italic: true }, "no records")), /* @__PURE__ */ React12.createElement(Box10, { marginTop: 1, paddingX: 1, borderStyle: "single", borderColor: "gray" }, /* @__PURE__ */ React12.createElement(Text10, { dimColor: true }, /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "j"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "\u2193"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "space"), " next \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "k"), "/", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "\u2191"), " prev \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "g"), " first \xB7 ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "G"), " last \xB7", " ", /* @__PURE__ */ React12.createElement(Text10, { bold: true }, "q"), " quit")));
|
|
2808
3228
|
}
|
|
2809
3229
|
|
|
2810
3230
|
// src/cli/commands/replay.ts
|
|
@@ -2816,7 +3236,7 @@ async function replayCommand(opts) {
|
|
|
2816
3236
|
}
|
|
2817
3237
|
const { parsed } = replayFromFile(opts.path);
|
|
2818
3238
|
const pages = groupRecordsByTurn(parsed.records);
|
|
2819
|
-
const { waitUntilExit } = render3(
|
|
3239
|
+
const { waitUntilExit } = render3(React13.createElement(ReplayApp, { meta: parsed.meta, pages }), {
|
|
2820
3240
|
exitOnCtrlC: true
|
|
2821
3241
|
});
|
|
2822
3242
|
await waitUntilExit();
|
|
@@ -2854,6 +3274,11 @@ function printReplay(opts) {
|
|
|
2854
3274
|
} else if (stats.prefixHashes.length > 1) {
|
|
2855
3275
|
console.log(" (prefix churned \u2014 cache-hostile session)");
|
|
2856
3276
|
}
|
|
3277
|
+
if (stats.harvestedTurns > 0) {
|
|
3278
|
+
console.log(`harvest (Pillar 2): ${stats.harvestedTurns} turn(s) produced plan state`);
|
|
3279
|
+
console.log(` subgoals: ${stats.totalSubgoals}`);
|
|
3280
|
+
console.log(` uncertainties: ${stats.totalUncertainties}`);
|
|
3281
|
+
}
|
|
2857
3282
|
}
|
|
2858
3283
|
function sliceRecords(records, opts) {
|
|
2859
3284
|
if (opts.head !== void 0 && opts.head > 0) return records.slice(0, opts.head);
|
|
@@ -2873,6 +3298,21 @@ function renderRecord(rec) {
|
|
|
2873
3298
|
return total > 0 ? ` cache=${(hit / total * 100).toFixed(1)}%` : "";
|
|
2874
3299
|
})() : "";
|
|
2875
3300
|
console.log(`${turn} AGENT:${cost}${cache} ${oneLine(rec.content)}`);
|
|
3301
|
+
if (rec.planState) {
|
|
3302
|
+
const ps = rec.planState;
|
|
3303
|
+
if (ps.subgoals.length)
|
|
3304
|
+
console.log(` \u2039 subgoals (${ps.subgoals.length}): ${ps.subgoals.join(" \xB7 ")}`);
|
|
3305
|
+
if (ps.hypotheses.length)
|
|
3306
|
+
console.log(` \u2039 hypotheses (${ps.hypotheses.length}): ${ps.hypotheses.join(" \xB7 ")}`);
|
|
3307
|
+
if (ps.uncertainties.length)
|
|
3308
|
+
console.log(
|
|
3309
|
+
` \u2039 uncertainties(${ps.uncertainties.length}): ${ps.uncertainties.join(" \xB7 ")}`
|
|
3310
|
+
);
|
|
3311
|
+
if (ps.rejectedPaths.length)
|
|
3312
|
+
console.log(
|
|
3313
|
+
` \u2039 rejected (${ps.rejectedPaths.length}): ${ps.rejectedPaths.join(" \xB7 ")}`
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
2876
3316
|
} else if (rec.role === "tool") {
|
|
2877
3317
|
const args = rec.args ? ` args=${oneLine(rec.args, 80)}` : "";
|
|
2878
3318
|
console.log(`${turn} TOOL ${rec.tool ?? "?"}:${args} \u2192 ${oneLine(rec.content, 120)}`);
|
|
@@ -2926,18 +3366,76 @@ async function runCommand(opts) {
|
|
|
2926
3366
|
loadDotenv();
|
|
2927
3367
|
const apiKey = await ensureApiKey();
|
|
2928
3368
|
process.env.DEEPSEEK_API_KEY = apiKey;
|
|
3369
|
+
let mcp;
|
|
3370
|
+
let tools;
|
|
3371
|
+
if (opts.mcp) {
|
|
3372
|
+
const argv = shellSplit(opts.mcp);
|
|
3373
|
+
const [command, ...args] = argv;
|
|
3374
|
+
if (!command) {
|
|
3375
|
+
process.stderr.write("error: --mcp command is empty\n");
|
|
3376
|
+
process.exit(2);
|
|
3377
|
+
}
|
|
3378
|
+
mcp = new McpClient({ transport: new StdioTransport({ command, args }) });
|
|
3379
|
+
try {
|
|
3380
|
+
await mcp.initialize();
|
|
3381
|
+
const bridge = await bridgeMcpTools(mcp, { namePrefix: opts.mcpPrefix });
|
|
3382
|
+
tools = bridge.registry;
|
|
3383
|
+
process.stderr.write(
|
|
3384
|
+
`\u25B8 MCP: ${bridge.registeredNames.length} tool(s) from ${argv.join(" ")}
|
|
3385
|
+
`
|
|
3386
|
+
);
|
|
3387
|
+
} catch (err) {
|
|
3388
|
+
process.stderr.write(`MCP setup failed: ${err.message}
|
|
3389
|
+
`);
|
|
3390
|
+
await mcp.close();
|
|
3391
|
+
process.exit(1);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
2929
3394
|
const client = new DeepSeekClient();
|
|
2930
|
-
const prefix = new ImmutablePrefix({
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3395
|
+
const prefix = new ImmutablePrefix({
|
|
3396
|
+
system: opts.system,
|
|
3397
|
+
toolSpecs: tools?.specs()
|
|
3398
|
+
});
|
|
3399
|
+
const loop = new CacheFirstLoop({
|
|
3400
|
+
client,
|
|
3401
|
+
prefix,
|
|
3402
|
+
tools,
|
|
3403
|
+
model: opts.model,
|
|
3404
|
+
harvest: opts.harvest,
|
|
3405
|
+
branch: opts.branch
|
|
3406
|
+
});
|
|
3407
|
+
const prefixHash = prefix.fingerprint;
|
|
3408
|
+
let transcriptStream = null;
|
|
3409
|
+
if (opts.transcript) {
|
|
3410
|
+
transcriptStream = openTranscriptFile(opts.transcript, {
|
|
3411
|
+
version: 1,
|
|
3412
|
+
source: "reasonix run",
|
|
3413
|
+
model: opts.model,
|
|
3414
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3415
|
+
});
|
|
3416
|
+
writeRecord(transcriptStream, {
|
|
3417
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3418
|
+
turn: 1,
|
|
3419
|
+
role: "user",
|
|
3420
|
+
content: opts.task
|
|
3421
|
+
});
|
|
3422
|
+
}
|
|
3423
|
+
try {
|
|
3424
|
+
for await (const ev of loop.step(opts.task)) {
|
|
3425
|
+
if (ev.role === "assistant_delta" && ev.content) process.stdout.write(ev.content);
|
|
3426
|
+
if (ev.role === "tool") process.stdout.write(`
|
|
2935
3427
|
[tool ${ev.toolName}] ${ev.content}
|
|
2936
3428
|
`);
|
|
2937
|
-
|
|
3429
|
+
if (ev.role === "error") process.stderr.write(`
|
|
2938
3430
|
[error] ${ev.error}
|
|
2939
3431
|
`);
|
|
2940
|
-
|
|
3432
|
+
if (ev.role === "done") process.stdout.write("\n");
|
|
3433
|
+
if (transcriptStream && ev.role !== "assistant_delta") {
|
|
3434
|
+
writeRecord(transcriptStream, recordFromLoopEvent(ev, { model: opts.model, prefixHash }));
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
} finally {
|
|
3438
|
+
transcriptStream?.end();
|
|
2941
3439
|
}
|
|
2942
3440
|
const s = loop.stats.summary();
|
|
2943
3441
|
process.stdout.write(
|
|
@@ -2945,6 +3443,90 @@ async function runCommand(opts) {
|
|
|
2945
3443
|
\u2014 turns:${s.turns} cache:${(s.cacheHitRatio * 100).toFixed(1)}% cost:$${s.totalCostUsd.toFixed(6)} save-vs-claude:${s.savingsVsClaudePct.toFixed(1)}%
|
|
2946
3444
|
`
|
|
2947
3445
|
);
|
|
3446
|
+
if (opts.transcript) {
|
|
3447
|
+
process.stdout.write(`
|
|
3448
|
+
transcript: ${opts.transcript}
|
|
3449
|
+
`);
|
|
3450
|
+
process.stdout.write(` \u2192 npx reasonix replay ${opts.transcript}
|
|
3451
|
+
`);
|
|
3452
|
+
}
|
|
3453
|
+
await mcp?.close();
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// src/cli/commands/sessions.ts
|
|
3457
|
+
function sessionsCommand(opts) {
|
|
3458
|
+
if (opts.name) {
|
|
3459
|
+
inspectSession(opts.name, !!opts.verbose);
|
|
3460
|
+
} else {
|
|
3461
|
+
listAll();
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
function listAll() {
|
|
3465
|
+
const items = listSessions();
|
|
3466
|
+
if (items.length === 0) {
|
|
3467
|
+
console.log(
|
|
3468
|
+
"no saved sessions yet \u2014 run `reasonix chat` (sessions are auto-saved unless --no-session)."
|
|
3469
|
+
);
|
|
3470
|
+
return;
|
|
3471
|
+
}
|
|
3472
|
+
console.log("Saved sessions (~/.reasonix/sessions/):");
|
|
3473
|
+
console.log("");
|
|
3474
|
+
console.log(` ${"name".padEnd(22)} ${"msgs".padStart(6)} ${"size".padStart(8)} modified`);
|
|
3475
|
+
console.log(` ${"\u2500".repeat(60)}`);
|
|
3476
|
+
for (const s of items) {
|
|
3477
|
+
const sizeKb = `${(s.size / 1024).toFixed(1)} KB`;
|
|
3478
|
+
const when = s.mtime.toISOString().replace("T", " ").slice(0, 16);
|
|
3479
|
+
console.log(
|
|
3480
|
+
` ${s.name.padEnd(22)} ${String(s.messageCount).padStart(6)} ${sizeKb.padStart(8)} ${when}`
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
console.log("");
|
|
3484
|
+
console.log("Inspect: reasonix sessions <name>");
|
|
3485
|
+
console.log("Resume: reasonix chat --session <name>");
|
|
3486
|
+
}
|
|
3487
|
+
function inspectSession(name, verbose) {
|
|
3488
|
+
const path = sessionPath(name);
|
|
3489
|
+
const messages = loadSessionMessages(name);
|
|
3490
|
+
if (messages.length === 0) {
|
|
3491
|
+
console.error(`no session named "${name}" (or it's empty).`);
|
|
3492
|
+
console.error(`looked at: ${path}`);
|
|
3493
|
+
process.exit(1);
|
|
3494
|
+
}
|
|
3495
|
+
console.log(`[session] ${name} ${messages.length} messages ${path}`);
|
|
3496
|
+
console.log("");
|
|
3497
|
+
let turnIndex = 0;
|
|
3498
|
+
for (const msg of messages) {
|
|
3499
|
+
renderMessage(msg, turnIndex, verbose);
|
|
3500
|
+
if (msg.role === "user") turnIndex++;
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
function renderMessage(msg, turnIdx, verbose) {
|
|
3504
|
+
const turn = turnIdx > 0 ? `[t${turnIdx}]` : "[start]";
|
|
3505
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
3506
|
+
const flat = oneLine2(content);
|
|
3507
|
+
if (msg.role === "user") {
|
|
3508
|
+
console.log(`${turn} USER: ${flat}`);
|
|
3509
|
+
} else if (msg.role === "assistant") {
|
|
3510
|
+
console.log(`${turn} AGENT: ${flat || "(tool call only)"}`);
|
|
3511
|
+
if (verbose && msg.tool_calls?.length) {
|
|
3512
|
+
for (const tc of msg.tool_calls) {
|
|
3513
|
+
console.log(
|
|
3514
|
+
` \u2192 call ${tc.function?.name} ${truncate4(tc.function?.arguments ?? "", 80)}`
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
} else if (msg.role === "tool") {
|
|
3519
|
+
console.log(`${turn} TOOL ${msg.name ?? "?"}: ${truncate4(flat, 160)}`);
|
|
3520
|
+
} else if (msg.role === "system") {
|
|
3521
|
+
if (verbose) console.log(`${turn} SYSTEM: ${truncate4(flat, 160)}`);
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function oneLine2(s, max = 200) {
|
|
3525
|
+
const collapsed = s.replace(/\s+/g, " ").trim();
|
|
3526
|
+
return collapsed.length > max ? `${collapsed.slice(0, max)}\u2026` : collapsed;
|
|
3527
|
+
}
|
|
3528
|
+
function truncate4(s, max) {
|
|
3529
|
+
return s.length <= max ? s : `${s.slice(0, max)}\u2026`;
|
|
2948
3530
|
}
|
|
2949
3531
|
|
|
2950
3532
|
// src/cli/commands/stats.ts
|
|
@@ -2992,7 +3574,10 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
2992
3574
|
).option(
|
|
2993
3575
|
"--session <name>",
|
|
2994
3576
|
"Use a named session (default: 'default'). Resume the same session next time."
|
|
2995
|
-
).option("--no-session", "Disable session persistence for this run (ephemeral chat)").
|
|
3577
|
+
).option("--no-session", "Disable session persistence for this run (ephemeral chat)").option(
|
|
3578
|
+
"--mcp <command>",
|
|
3579
|
+
'Spawn an MCP server and bridge its tools. Shell-quoted: --mcp "npx -y @scope/server-x /path"'
|
|
3580
|
+
).option("--mcp-prefix <str>", "Prefix prepended to every MCP tool name (avoid collisions)").action(async (opts) => {
|
|
2996
3581
|
let session;
|
|
2997
3582
|
if (opts.session === false) {
|
|
2998
3583
|
session = void 0;
|
|
@@ -3007,15 +3592,39 @@ program.command("chat").description("Interactive Ink TUI with live cache/cost pa
|
|
|
3007
3592
|
transcript: opts.transcript,
|
|
3008
3593
|
harvest: !!opts.harvest,
|
|
3009
3594
|
branch: Number.isFinite(opts.branch) && opts.branch > 1 ? opts.branch : void 0,
|
|
3010
|
-
session
|
|
3595
|
+
session,
|
|
3596
|
+
mcp: opts.mcp,
|
|
3597
|
+
mcpPrefix: opts.mcpPrefix
|
|
3011
3598
|
});
|
|
3012
3599
|
});
|
|
3013
|
-
program.command("run <task>").description("Run a single task non-interactively, streaming output.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt", DEFAULT_SYSTEM).
|
|
3014
|
-
|
|
3600
|
+
program.command("run <task>").description("Run a single task non-interactively, streaming output.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt", DEFAULT_SYSTEM).option(
|
|
3601
|
+
"--harvest",
|
|
3602
|
+
"Extract typed plan state from R1 reasoning (Pillar 2, adds a cheap V3 call per turn)"
|
|
3603
|
+
).option(
|
|
3604
|
+
"--branch <n>",
|
|
3605
|
+
"Self-consistency: run N parallel samples per turn and pick the most confident",
|
|
3606
|
+
(v) => Number.parseInt(v, 10)
|
|
3607
|
+
).option("--transcript <path>", "Write a JSONL transcript to this path for replay/diff").option(
|
|
3608
|
+
"--mcp <command>",
|
|
3609
|
+
'Spawn an MCP server and bridge its tools. Shell-quoted: --mcp "npx -y @scope/server-x /path"'
|
|
3610
|
+
).option("--mcp-prefix <str>", "Prefix prepended to every MCP tool name (avoid collisions)").action(async (task, opts) => {
|
|
3611
|
+
await runCommand({
|
|
3612
|
+
task,
|
|
3613
|
+
model: opts.model,
|
|
3614
|
+
system: opts.system,
|
|
3615
|
+
harvest: !!opts.harvest,
|
|
3616
|
+
branch: Number.isFinite(opts.branch) && opts.branch > 1 ? opts.branch : void 0,
|
|
3617
|
+
transcript: opts.transcript,
|
|
3618
|
+
mcp: opts.mcp,
|
|
3619
|
+
mcpPrefix: opts.mcpPrefix
|
|
3620
|
+
});
|
|
3015
3621
|
});
|
|
3016
3622
|
program.command("stats <transcript>").description("Summarize a JSONL transcript produced by `reasonix chat --transcript`.").action((transcript) => {
|
|
3017
3623
|
statsCommand({ transcript });
|
|
3018
3624
|
});
|
|
3625
|
+
program.command("sessions [name]").description("List saved chat sessions, or inspect one by name.").option("-v, --verbose", "Include system prompts + tool-call metadata when inspecting").action((name, opts) => {
|
|
3626
|
+
sessionsCommand({ name, verbose: !!opts.verbose });
|
|
3627
|
+
});
|
|
3019
3628
|
program.command("replay <transcript>").description(
|
|
3020
3629
|
"Interactive Ink TUI to scrub through a transcript + rebuild its session summary (cost, cache, prefix stability). No API calls."
|
|
3021
3630
|
).option("--print", "Dump to stdout instead of mounting the TUI (auto when piped)").option("--head <n>", "stdout mode only \u2014 show first N records", (v) => Number.parseInt(v, 10)).option("--tail <n>", "stdout mode only \u2014 show last N records", (v) => Number.parseInt(v, 10)).action(async (transcript, opts) => {
|