reasonix 0.2.2 → 0.3.0-alpha.2

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/index.js CHANGED
@@ -1355,6 +1355,14 @@ function recordFromLoopEvent(ev, extra) {
1355
1355
  if (ev.toolName !== void 0) rec.tool = ev.toolName;
1356
1356
  if (ev.toolArgs !== void 0) rec.args = ev.toolArgs;
1357
1357
  if (ev.error !== void 0) rec.error = ev.error;
1358
+ if (ev.planState && !isPlanStateEmptyShape(ev.planState)) {
1359
+ rec.planState = {
1360
+ subgoals: [...ev.planState.subgoals],
1361
+ hypotheses: [...ev.planState.hypotheses],
1362
+ uncertainties: [...ev.planState.uncertainties],
1363
+ rejectedPaths: [...ev.planState.rejectedPaths]
1364
+ };
1365
+ }
1358
1366
  if (ev.stats) {
1359
1367
  rec.usage = {
1360
1368
  prompt_tokens: ev.stats.usage.promptTokens,
@@ -1390,6 +1398,9 @@ function readTranscript(path) {
1390
1398
  const raw = readFileSync3(path, "utf8");
1391
1399
  return parseTranscript(raw);
1392
1400
  }
1401
+ function isPlanStateEmptyShape(s) {
1402
+ return s.subgoals.length === 0 && s.hypotheses.length === 0 && s.uncertainties.length === 0 && s.rejectedPaths.length === 0;
1403
+ }
1393
1404
  function parseTranscript(raw) {
1394
1405
  const out = { meta: null, records: [] };
1395
1406
  for (const line of raw.split(/\r?\n/)) {
@@ -1425,12 +1436,20 @@ function computeReplayStats(records) {
1425
1436
  const prefixHashes = /* @__PURE__ */ new Set();
1426
1437
  let userTurns = 0;
1427
1438
  let toolCalls = 0;
1439
+ let harvestedTurns = 0;
1440
+ let totalUncertainties = 0;
1441
+ let totalSubgoals = 0;
1428
1442
  for (const rec of records) {
1429
1443
  if (rec.role === "user") userTurns++;
1430
1444
  else if (rec.role === "tool") toolCalls++;
1431
1445
  else if (rec.role === "assistant_final") {
1432
1446
  if (rec.model) models.add(rec.model);
1433
1447
  if (rec.prefixHash) prefixHashes.add(rec.prefixHash);
1448
+ if (rec.planState) {
1449
+ harvestedTurns++;
1450
+ totalUncertainties += rec.planState.uncertainties.length;
1451
+ totalSubgoals += rec.planState.subgoals.length;
1452
+ }
1434
1453
  if (rec.usage && rec.model) {
1435
1454
  const u = new Usage(
1436
1455
  rec.usage.prompt_tokens ?? 0,
@@ -1458,6 +1477,9 @@ function computeReplayStats(records) {
1458
1477
  prefixHashes: [...prefixHashes],
1459
1478
  userTurns,
1460
1479
  toolCalls,
1480
+ harvestedTurns,
1481
+ totalUncertainties,
1482
+ totalSubgoals,
1461
1483
  ...summarizeTurns(turns)
1462
1484
  };
1463
1485
  }
@@ -1628,6 +1650,41 @@ function renderSummaryTable(report, _opts = {}) {
1628
1650
  )
1629
1651
  );
1630
1652
  lines.push(statRow("prefix hashes", a.stats.prefixHashes.length, b.stats.prefixHashes.length));
1653
+ if (a.stats.harvestedTurns > 0 || b.stats.harvestedTurns > 0) {
1654
+ lines.push(
1655
+ row(
1656
+ [
1657
+ "harvest turns",
1658
+ `${a.stats.harvestedTurns}`,
1659
+ `${b.stats.harvestedTurns}`,
1660
+ signed(b.stats.harvestedTurns - a.stats.harvestedTurns)
1661
+ ],
1662
+ [20, 14, 14, 14]
1663
+ )
1664
+ );
1665
+ lines.push(
1666
+ row(
1667
+ [
1668
+ " subgoals",
1669
+ `${a.stats.totalSubgoals}`,
1670
+ `${b.stats.totalSubgoals}`,
1671
+ signed(b.stats.totalSubgoals - a.stats.totalSubgoals)
1672
+ ],
1673
+ [20, 14, 14, 14]
1674
+ )
1675
+ );
1676
+ lines.push(
1677
+ row(
1678
+ [
1679
+ " uncertainties",
1680
+ `${a.stats.totalUncertainties}`,
1681
+ `${b.stats.totalUncertainties}`,
1682
+ signed(b.stats.totalUncertainties - a.stats.totalUncertainties)
1683
+ ],
1684
+ [20, 14, 14, 14]
1685
+ )
1686
+ );
1687
+ }
1631
1688
  lines.push("");
1632
1689
  const aPrefixStable = a.stats.prefixHashes.length <= 1;
1633
1690
  const bPrefixStable = b.stats.prefixHashes.length <= 1;
@@ -1699,6 +1756,17 @@ function renderMarkdown(report) {
1699
1756
  out.push(
1700
1757
  `| prefix hashes | ${a.stats.prefixHashes.length} | ${b.stats.prefixHashes.length} | \u2014 |`
1701
1758
  );
1759
+ if (a.stats.harvestedTurns > 0 || b.stats.harvestedTurns > 0) {
1760
+ out.push(
1761
+ `| harvest turns | ${a.stats.harvestedTurns} | ${b.stats.harvestedTurns} | ${signed(b.stats.harvestedTurns - a.stats.harvestedTurns)} |`
1762
+ );
1763
+ out.push(
1764
+ `| harvest subgoals | ${a.stats.totalSubgoals} | ${b.stats.totalSubgoals} | ${signed(b.stats.totalSubgoals - a.stats.totalSubgoals)} |`
1765
+ );
1766
+ out.push(
1767
+ `| harvest uncertainties | ${a.stats.totalUncertainties} | ${b.stats.totalUncertainties} | ${signed(b.stats.totalUncertainties - a.stats.totalUncertainties)} |`
1768
+ );
1769
+ }
1702
1770
  out.push("");
1703
1771
  out.push("## Turn-by-turn");
1704
1772
  out.push("");
@@ -1766,6 +1834,278 @@ function truncate(s, n) {
1766
1834
  return s.length > n ? `${s.slice(0, n)}\u2026` : s;
1767
1835
  }
1768
1836
 
1837
+ // src/mcp/types.ts
1838
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
1839
+ function isJsonRpcError(msg) {
1840
+ return "error" in msg;
1841
+ }
1842
+
1843
+ // src/mcp/client.ts
1844
+ var McpClient = class {
1845
+ transport;
1846
+ clientInfo;
1847
+ requestTimeoutMs;
1848
+ pending = /* @__PURE__ */ new Map();
1849
+ nextId = 1;
1850
+ readerStarted = false;
1851
+ initialized = false;
1852
+ _serverCapabilities = {};
1853
+ constructor(opts) {
1854
+ this.transport = opts.transport;
1855
+ this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: "0.3.0-dev" };
1856
+ this.requestTimeoutMs = opts.requestTimeoutMs ?? 6e4;
1857
+ }
1858
+ /** Server's advertised capabilities, available after initialize(). */
1859
+ get serverCapabilities() {
1860
+ return this._serverCapabilities;
1861
+ }
1862
+ /**
1863
+ * Complete the initialize → initialized handshake. Must be called
1864
+ * before any other method (otherwise compliant servers reject).
1865
+ */
1866
+ async initialize() {
1867
+ if (this.initialized) throw new Error("MCP client already initialized");
1868
+ this.startReaderIfNeeded();
1869
+ const result = await this.request("initialize", {
1870
+ protocolVersion: MCP_PROTOCOL_VERSION,
1871
+ capabilities: { tools: {} },
1872
+ clientInfo: this.clientInfo
1873
+ });
1874
+ this._serverCapabilities = result.capabilities ?? {};
1875
+ await this.transport.send({
1876
+ jsonrpc: "2.0",
1877
+ method: "notifications/initialized"
1878
+ });
1879
+ this.initialized = true;
1880
+ return result;
1881
+ }
1882
+ /** List tools the server exposes. */
1883
+ async listTools() {
1884
+ this.assertInitialized();
1885
+ return this.request("tools/list", {});
1886
+ }
1887
+ /** Invoke a tool by name. Returns the raw MCP result (caller unwraps content). */
1888
+ async callTool(name, args) {
1889
+ this.assertInitialized();
1890
+ return this.request("tools/call", {
1891
+ name,
1892
+ arguments: args ?? {}
1893
+ });
1894
+ }
1895
+ /** Close the transport and reject any outstanding requests. */
1896
+ async close() {
1897
+ for (const [, pending] of this.pending) {
1898
+ clearTimeout(pending.timeout);
1899
+ pending.reject(new Error("MCP client closed"));
1900
+ }
1901
+ this.pending.clear();
1902
+ await this.transport.close();
1903
+ }
1904
+ // ---------- internals ----------
1905
+ assertInitialized() {
1906
+ if (!this.initialized) throw new Error("MCP client not initialized \u2014 call initialize() first");
1907
+ }
1908
+ async request(method, params) {
1909
+ const id = this.nextId++;
1910
+ const frame = { jsonrpc: "2.0", id, method, params };
1911
+ const promise = new Promise((resolve2, reject) => {
1912
+ const timeout = setTimeout(() => {
1913
+ this.pending.delete(id);
1914
+ reject(
1915
+ new Error(`MCP request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`)
1916
+ );
1917
+ }, this.requestTimeoutMs);
1918
+ this.pending.set(id, {
1919
+ resolve: resolve2,
1920
+ reject,
1921
+ timeout
1922
+ });
1923
+ });
1924
+ await this.transport.send(frame);
1925
+ return promise;
1926
+ }
1927
+ startReaderIfNeeded() {
1928
+ if (this.readerStarted) return;
1929
+ this.readerStarted = true;
1930
+ void this.readLoop();
1931
+ }
1932
+ async readLoop() {
1933
+ try {
1934
+ for await (const msg of this.transport.messages()) {
1935
+ this.dispatch(msg);
1936
+ }
1937
+ } catch (err) {
1938
+ for (const [, pending] of this.pending) {
1939
+ clearTimeout(pending.timeout);
1940
+ pending.reject(err);
1941
+ }
1942
+ this.pending.clear();
1943
+ }
1944
+ }
1945
+ dispatch(msg) {
1946
+ if (!("id" in msg) || msg.id === null || msg.id === void 0) return;
1947
+ if (!("result" in msg) && !("error" in msg)) return;
1948
+ const pending = this.pending.get(msg.id);
1949
+ if (!pending) return;
1950
+ this.pending.delete(msg.id);
1951
+ clearTimeout(pending.timeout);
1952
+ const resp = msg;
1953
+ if (isJsonRpcError(resp)) {
1954
+ pending.reject(new Error(`MCP ${resp.error.code}: ${resp.error.message}`));
1955
+ } else {
1956
+ pending.resolve(resp.result);
1957
+ }
1958
+ }
1959
+ };
1960
+
1961
+ // src/mcp/stdio.ts
1962
+ import { spawn } from "child_process";
1963
+ var StdioTransport = class {
1964
+ child;
1965
+ queue = [];
1966
+ waiters = [];
1967
+ closed = false;
1968
+ stdoutBuffer = "";
1969
+ constructor(opts) {
1970
+ const env = opts.replaceEnv ? { ...opts.env ?? {} } : { ...process.env, ...opts.env ?? {} };
1971
+ const shell = opts.shell ?? process.platform === "win32";
1972
+ if (shell) {
1973
+ const line = [
1974
+ opts.command,
1975
+ ...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
1976
+ ].join(" ");
1977
+ this.child = spawn(line, [], {
1978
+ env,
1979
+ cwd: opts.cwd,
1980
+ stdio: ["pipe", "pipe", "inherit"],
1981
+ shell: true
1982
+ });
1983
+ } else {
1984
+ this.child = spawn(opts.command, opts.args ?? [], {
1985
+ env,
1986
+ cwd: opts.cwd,
1987
+ stdio: ["pipe", "pipe", "inherit"]
1988
+ });
1989
+ }
1990
+ this.child.stdout.setEncoding("utf8");
1991
+ this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
1992
+ this.child.on("close", () => this.onClose());
1993
+ this.child.on("error", (err) => {
1994
+ this.push({
1995
+ jsonrpc: "2.0",
1996
+ id: null,
1997
+ error: { code: -32e3, message: `transport error: ${err.message}` }
1998
+ });
1999
+ });
2000
+ }
2001
+ async send(message) {
2002
+ if (this.closed) throw new Error("MCP transport is closed");
2003
+ return new Promise((resolve2, reject) => {
2004
+ const line = `${JSON.stringify(message)}
2005
+ `;
2006
+ this.child.stdin.write(line, "utf8", (err) => {
2007
+ if (err) reject(err);
2008
+ else resolve2();
2009
+ });
2010
+ });
2011
+ }
2012
+ async *messages() {
2013
+ while (true) {
2014
+ if (this.queue.length > 0) {
2015
+ yield this.queue.shift();
2016
+ continue;
2017
+ }
2018
+ if (this.closed) return;
2019
+ const next = await new Promise((resolve2) => {
2020
+ this.waiters.push(resolve2);
2021
+ });
2022
+ if (next === null) return;
2023
+ yield next;
2024
+ }
2025
+ }
2026
+ async close() {
2027
+ if (this.closed) return;
2028
+ this.closed = true;
2029
+ while (this.waiters.length > 0) this.waiters.shift()(null);
2030
+ try {
2031
+ this.child.stdin.end();
2032
+ } catch {
2033
+ }
2034
+ if (this.child.exitCode === null && !this.child.killed) {
2035
+ this.child.kill("SIGTERM");
2036
+ }
2037
+ }
2038
+ /** Parse incoming stdout chunks into NDJSON messages. */
2039
+ onStdout(chunk) {
2040
+ this.stdoutBuffer += chunk;
2041
+ let newlineIdx;
2042
+ while ((newlineIdx = this.stdoutBuffer.indexOf("\n")) !== -1) {
2043
+ const line = this.stdoutBuffer.slice(0, newlineIdx).trim();
2044
+ this.stdoutBuffer = this.stdoutBuffer.slice(newlineIdx + 1);
2045
+ if (!line) continue;
2046
+ try {
2047
+ const msg = JSON.parse(line);
2048
+ this.push(msg);
2049
+ } catch {
2050
+ }
2051
+ }
2052
+ }
2053
+ onClose() {
2054
+ this.closed = true;
2055
+ while (this.waiters.length > 0) this.waiters.shift()(null);
2056
+ }
2057
+ push(msg) {
2058
+ const waiter = this.waiters.shift();
2059
+ if (waiter) waiter(msg);
2060
+ else this.queue.push(msg);
2061
+ }
2062
+ };
2063
+ function quoteArg(s, windows) {
2064
+ if (!windows) {
2065
+ return `'${s.replace(/'/g, "'\\''")}'`;
2066
+ }
2067
+ return `"${s.replace(/"/g, '""')}"`;
2068
+ }
2069
+
2070
+ // src/mcp/registry.ts
2071
+ async function bridgeMcpTools(client, opts = {}) {
2072
+ const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
2073
+ const prefix = opts.namePrefix ?? "";
2074
+ const result = { registry, registeredNames: [], skipped: [] };
2075
+ const listed = await client.listTools();
2076
+ for (const mcpTool of listed.tools) {
2077
+ if (!mcpTool.name) {
2078
+ result.skipped.push({ name: "?", reason: "empty tool name" });
2079
+ continue;
2080
+ }
2081
+ const registeredName = `${prefix}${mcpTool.name}`;
2082
+ registry.register({
2083
+ name: registeredName,
2084
+ description: mcpTool.description ?? "",
2085
+ parameters: mcpTool.inputSchema,
2086
+ fn: async (args) => {
2087
+ const toolResult = await client.callTool(mcpTool.name, args);
2088
+ return flattenMcpResult(toolResult);
2089
+ }
2090
+ });
2091
+ result.registeredNames.push(registeredName);
2092
+ }
2093
+ return result;
2094
+ }
2095
+ function flattenMcpResult(result) {
2096
+ const parts = result.content.map(blockToString);
2097
+ const joined = parts.join("\n").trim();
2098
+ if (result.isError) {
2099
+ return `ERROR: ${joined || "(no error message from server)"}`;
2100
+ }
2101
+ return joined;
2102
+ }
2103
+ function blockToString(block) {
2104
+ if (block.type === "text") return block.text;
2105
+ if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
2106
+ return `[unknown block: ${JSON.stringify(block)}]`;
2107
+ }
2108
+
1769
2109
  // src/config.ts
1770
2110
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "fs";
1771
2111
  import { homedir as homedir2 } from "os";
@@ -1810,13 +2150,16 @@ function redactKey(key) {
1810
2150
  }
1811
2151
 
1812
2152
  // src/index.ts
1813
- var VERSION = "0.2.2";
2153
+ var VERSION = "0.3.0-alpha.2";
1814
2154
  export {
1815
2155
  AppendOnlyLog,
1816
2156
  CacheFirstLoop,
1817
2157
  DeepSeekClient,
1818
2158
  ImmutablePrefix,
2159
+ MCP_PROTOCOL_VERSION,
2160
+ McpClient,
1819
2161
  SessionStats,
2162
+ StdioTransport,
1820
2163
  StormBreaker,
1821
2164
  ToolCallRepair,
1822
2165
  ToolRegistry,
@@ -1826,6 +2169,7 @@ export {
1826
2169
  aggregateBranchUsage,
1827
2170
  analyzeSchema,
1828
2171
  appendSessionMessage,
2172
+ bridgeMcpTools,
1829
2173
  claudeEquivalentCost,
1830
2174
  computeReplayStats,
1831
2175
  costUsd,
@@ -1835,8 +2179,10 @@ export {
1835
2179
  diffTranscripts,
1836
2180
  emptyPlanState,
1837
2181
  fetchWithRetry,
2182
+ flattenMcpResult,
1838
2183
  flattenSchema,
1839
2184
  harvest,
2185
+ isJsonRpcError,
1840
2186
  isPlanStateEmpty,
1841
2187
  isPlausibleKey,
1842
2188
  listSessions,