reasonix 0.3.0-alpha.4 → 0.3.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/dist/index.js CHANGED
@@ -404,6 +404,201 @@ function resolveTemperatures(budget, custom) {
404
404
  return out;
405
405
  }
406
406
 
407
+ // src/repair/flatten.ts
408
+ function analyzeSchema(schema) {
409
+ if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
410
+ let leafCount = 0;
411
+ let maxDepth = 0;
412
+ walk(schema, 0, (depth, isLeaf) => {
413
+ if (isLeaf) leafCount++;
414
+ if (depth > maxDepth) maxDepth = depth;
415
+ });
416
+ return {
417
+ shouldFlatten: leafCount > 10 || maxDepth > 2,
418
+ leafCount,
419
+ maxDepth
420
+ };
421
+ }
422
+ function flattenSchema(schema) {
423
+ const flatProps = {};
424
+ const required = [];
425
+ collect("", schema, flatProps, required, true);
426
+ return {
427
+ type: "object",
428
+ properties: flatProps,
429
+ required
430
+ };
431
+ }
432
+ function nestArguments(flatArgs) {
433
+ const out = {};
434
+ for (const [key, value] of Object.entries(flatArgs)) {
435
+ setByPath(out, key.split("."), value);
436
+ }
437
+ return out;
438
+ }
439
+ function walk(schema, depth, visit) {
440
+ if (schema.type === "object" && schema.properties) {
441
+ for (const child of Object.values(schema.properties)) {
442
+ walk(child, depth + 1, visit);
443
+ }
444
+ return;
445
+ }
446
+ if (schema.type === "array" && schema.items) {
447
+ walk(schema.items, depth + 1, visit);
448
+ return;
449
+ }
450
+ visit(depth, true);
451
+ }
452
+ function collect(prefix, schema, out, required, isRootRequired) {
453
+ if (schema.type === "object" && schema.properties) {
454
+ const requiredSet = new Set(schema.required ?? []);
455
+ for (const [key, child] of Object.entries(schema.properties)) {
456
+ const nextPrefix = prefix ? `${prefix}.${key}` : key;
457
+ const childRequired = isRootRequired && requiredSet.has(key);
458
+ collect(nextPrefix, child, out, required, childRequired);
459
+ }
460
+ return;
461
+ }
462
+ out[prefix] = schema;
463
+ if (isRootRequired) required.push(prefix);
464
+ }
465
+ function setByPath(target, path, value) {
466
+ let cur = target;
467
+ for (let i = 0; i < path.length - 1; i++) {
468
+ const key = path[i];
469
+ if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
470
+ cur = cur[key];
471
+ }
472
+ cur[path[path.length - 1]] = value;
473
+ }
474
+
475
+ // src/tools.ts
476
+ var ToolRegistry = class {
477
+ _tools = /* @__PURE__ */ new Map();
478
+ _autoFlatten;
479
+ constructor(opts = {}) {
480
+ this._autoFlatten = opts.autoFlatten !== false;
481
+ }
482
+ register(def) {
483
+ if (!def.name) throw new Error("tool requires a name");
484
+ const internal = { ...def };
485
+ if (this._autoFlatten && def.parameters) {
486
+ const decision = analyzeSchema(def.parameters);
487
+ if (decision.shouldFlatten) {
488
+ internal.flatSchema = flattenSchema(def.parameters);
489
+ }
490
+ }
491
+ this._tools.set(def.name, internal);
492
+ return this;
493
+ }
494
+ has(name) {
495
+ return this._tools.has(name);
496
+ }
497
+ get(name) {
498
+ return this._tools.get(name);
499
+ }
500
+ get size() {
501
+ return this._tools.size;
502
+ }
503
+ /** True if a registered tool's schema was flattened for the model. */
504
+ wasFlattened(name) {
505
+ return Boolean(this._tools.get(name)?.flatSchema);
506
+ }
507
+ specs() {
508
+ return [...this._tools.values()].map((t) => ({
509
+ type: "function",
510
+ function: {
511
+ name: t.name,
512
+ description: t.description ?? "",
513
+ parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
514
+ }
515
+ }));
516
+ }
517
+ async dispatch(name, argumentsRaw) {
518
+ const tool = this._tools.get(name);
519
+ if (!tool) {
520
+ return JSON.stringify({ error: `unknown tool: ${name}` });
521
+ }
522
+ let args;
523
+ try {
524
+ args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
525
+ } catch (err) {
526
+ return JSON.stringify({
527
+ error: `invalid tool arguments JSON: ${err.message}`
528
+ });
529
+ }
530
+ if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
531
+ args = nestArguments(args);
532
+ }
533
+ try {
534
+ const result = await tool.fn(args);
535
+ return typeof result === "string" ? result : JSON.stringify(result);
536
+ } catch (err) {
537
+ return JSON.stringify({
538
+ error: `${err.name}: ${err.message}`
539
+ });
540
+ }
541
+ }
542
+ };
543
+ function hasDotKey(obj) {
544
+ for (const k of Object.keys(obj)) {
545
+ if (k.includes(".")) return true;
546
+ }
547
+ return false;
548
+ }
549
+
550
+ // src/mcp/registry.ts
551
+ var DEFAULT_MAX_RESULT_CHARS = 32e3;
552
+ async function bridgeMcpTools(client, opts = {}) {
553
+ const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
554
+ const prefix = opts.namePrefix ?? "";
555
+ const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS;
556
+ const result = { registry, registeredNames: [], skipped: [] };
557
+ const listed = await client.listTools();
558
+ for (const mcpTool of listed.tools) {
559
+ if (!mcpTool.name) {
560
+ result.skipped.push({ name: "?", reason: "empty tool name" });
561
+ continue;
562
+ }
563
+ const registeredName = `${prefix}${mcpTool.name}`;
564
+ registry.register({
565
+ name: registeredName,
566
+ description: mcpTool.description ?? "",
567
+ parameters: mcpTool.inputSchema,
568
+ fn: async (args) => {
569
+ const toolResult = await client.callTool(mcpTool.name, args);
570
+ return flattenMcpResult(toolResult, { maxChars: maxResultChars });
571
+ }
572
+ });
573
+ result.registeredNames.push(registeredName);
574
+ }
575
+ return result;
576
+ }
577
+ function flattenMcpResult(result, opts = {}) {
578
+ const parts = result.content.map(blockToString);
579
+ const joined = parts.join("\n").trim();
580
+ const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
581
+ return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
582
+ }
583
+ function truncateForModel(s, maxChars) {
584
+ if (s.length <= maxChars) return s;
585
+ const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
586
+ const headBudget = Math.max(0, maxChars - tailBudget);
587
+ const head = s.slice(0, headBudget);
588
+ const tail = s.slice(-tailBudget);
589
+ const dropped = s.length - head.length - tail.length;
590
+ return `${head}
591
+
592
+ [\u2026truncated ${dropped} chars \u2014 raise BridgeOptions.maxResultChars, or call the tool with a narrower scope (filter, head, pagination)\u2026]
593
+
594
+ ${tail}`;
595
+ }
596
+ function blockToString(block) {
597
+ if (block.type === "text") return block.text;
598
+ if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
599
+ return `[unknown block: ${JSON.stringify(block)}]`;
600
+ }
601
+
407
602
  // src/memory.ts
408
603
  import { createHash } from "crypto";
409
604
  var ImmutablePrefix = class {
@@ -441,6 +636,16 @@ var AppendOnlyLog = class {
441
636
  extend(messages) {
442
637
  for (const m of messages) this.append(m);
443
638
  }
639
+ /**
640
+ * Bulk-replace entries. Intentionally named to be hard to reach for —
641
+ * this is the one mutation path that breaks the log's append-only
642
+ * spirit, reserved for compaction flows (`/compact`) and recovery
643
+ * where the caller has consciously decided to drop old history. Any
644
+ * other use is almost certainly wrong; append() is what you want.
645
+ */
646
+ compactInPlace(replacement) {
647
+ this._entries = [...replacement];
648
+ }
444
649
  get entries() {
445
650
  return this._entries;
446
651
  }
@@ -655,74 +860,6 @@ function repairTruncatedJson(input) {
655
860
  }
656
861
  }
657
862
 
658
- // src/repair/flatten.ts
659
- function analyzeSchema(schema) {
660
- if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
661
- let leafCount = 0;
662
- let maxDepth = 0;
663
- walk(schema, 0, (depth, isLeaf) => {
664
- if (isLeaf) leafCount++;
665
- if (depth > maxDepth) maxDepth = depth;
666
- });
667
- return {
668
- shouldFlatten: leafCount > 10 || maxDepth > 2,
669
- leafCount,
670
- maxDepth
671
- };
672
- }
673
- function flattenSchema(schema) {
674
- const flatProps = {};
675
- const required = [];
676
- collect("", schema, flatProps, required, true);
677
- return {
678
- type: "object",
679
- properties: flatProps,
680
- required
681
- };
682
- }
683
- function nestArguments(flatArgs) {
684
- const out = {};
685
- for (const [key, value] of Object.entries(flatArgs)) {
686
- setByPath(out, key.split("."), value);
687
- }
688
- return out;
689
- }
690
- function walk(schema, depth, visit) {
691
- if (schema.type === "object" && schema.properties) {
692
- for (const child of Object.values(schema.properties)) {
693
- walk(child, depth + 1, visit);
694
- }
695
- return;
696
- }
697
- if (schema.type === "array" && schema.items) {
698
- walk(schema.items, depth + 1, visit);
699
- return;
700
- }
701
- visit(depth, true);
702
- }
703
- function collect(prefix, schema, out, required, isRootRequired) {
704
- if (schema.type === "object" && schema.properties) {
705
- const requiredSet = new Set(schema.required ?? []);
706
- for (const [key, child] of Object.entries(schema.properties)) {
707
- const nextPrefix = prefix ? `${prefix}.${key}` : key;
708
- const childRequired = isRootRequired && requiredSet.has(key);
709
- collect(nextPrefix, child, out, required, childRequired);
710
- }
711
- return;
712
- }
713
- out[prefix] = schema;
714
- if (isRootRequired) required.push(prefix);
715
- }
716
- function setByPath(target, path, value) {
717
- let cur = target;
718
- for (let i = 0; i < path.length - 1; i++) {
719
- const key = path[i];
720
- if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
721
- cur = cur[key];
722
- }
723
- cur[path[path.length - 1]] = value;
724
- }
725
-
726
863
  // src/repair/index.ts
727
864
  var ToolCallRepair = class {
728
865
  storm;
@@ -787,7 +924,8 @@ import {
787
924
  readFileSync,
788
925
  readdirSync,
789
926
  statSync,
790
- unlinkSync
927
+ unlinkSync,
928
+ writeFileSync
791
929
  } from "fs";
792
930
  import { homedir } from "os";
793
931
  import { dirname, join } from "path";
@@ -856,6 +994,17 @@ function deleteSession(name) {
856
994
  return false;
857
995
  }
858
996
  }
997
+ function rewriteSession(name, messages) {
998
+ const path = sessionPath(name);
999
+ mkdirSync(dirname(path), { recursive: true });
1000
+ const body = messages.map((m) => JSON.stringify(m)).join("\n");
1001
+ writeFileSync(path, body ? `${body}
1002
+ ` : "", "utf8");
1003
+ try {
1004
+ chmodSync(path, 384);
1005
+ } catch {
1006
+ }
1007
+ }
859
1008
  function countLines(path) {
860
1009
  try {
861
1010
  const raw = readFileSync(path, "utf8");
@@ -914,12 +1063,14 @@ var SessionStats = class {
914
1063
  return denom > 0 ? hit / denom : 0;
915
1064
  }
916
1065
  summary() {
1066
+ const last = this.turns[this.turns.length - 1];
917
1067
  return {
918
1068
  turns: this.turns.length,
919
1069
  totalCostUsd: round(this.totalCost, 6),
920
1070
  claudeEquivalentUsd: round(this.totalClaudeEquivalent, 6),
921
1071
  savingsVsClaudePct: round(this.savingsVsClaude * 100, 2),
922
- cacheHitRatio: round(this.aggregateCacheHitRatio, 4)
1072
+ cacheHitRatio: round(this.aggregateCacheHitRatio, 4),
1073
+ lastPromptTokens: last?.usage.promptTokens ?? 0
923
1074
  };
924
1075
  }
925
1076
  };
@@ -928,81 +1079,6 @@ function round(n, digits) {
928
1079
  return Math.round(n * f) / f;
929
1080
  }
930
1081
 
931
- // src/tools.ts
932
- var ToolRegistry = class {
933
- _tools = /* @__PURE__ */ new Map();
934
- _autoFlatten;
935
- constructor(opts = {}) {
936
- this._autoFlatten = opts.autoFlatten !== false;
937
- }
938
- register(def) {
939
- if (!def.name) throw new Error("tool requires a name");
940
- const internal = { ...def };
941
- if (this._autoFlatten && def.parameters) {
942
- const decision = analyzeSchema(def.parameters);
943
- if (decision.shouldFlatten) {
944
- internal.flatSchema = flattenSchema(def.parameters);
945
- }
946
- }
947
- this._tools.set(def.name, internal);
948
- return this;
949
- }
950
- has(name) {
951
- return this._tools.has(name);
952
- }
953
- get(name) {
954
- return this._tools.get(name);
955
- }
956
- get size() {
957
- return this._tools.size;
958
- }
959
- /** True if a registered tool's schema was flattened for the model. */
960
- wasFlattened(name) {
961
- return Boolean(this._tools.get(name)?.flatSchema);
962
- }
963
- specs() {
964
- return [...this._tools.values()].map((t) => ({
965
- type: "function",
966
- function: {
967
- name: t.name,
968
- description: t.description ?? "",
969
- parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
970
- }
971
- }));
972
- }
973
- async dispatch(name, argumentsRaw) {
974
- const tool = this._tools.get(name);
975
- if (!tool) {
976
- return JSON.stringify({ error: `unknown tool: ${name}` });
977
- }
978
- let args;
979
- try {
980
- args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
981
- } catch (err) {
982
- return JSON.stringify({
983
- error: `invalid tool arguments JSON: ${err.message}`
984
- });
985
- }
986
- if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
987
- args = nestArguments(args);
988
- }
989
- try {
990
- const result = await tool.fn(args);
991
- return typeof result === "string" ? result : JSON.stringify(result);
992
- } catch (err) {
993
- return JSON.stringify({
994
- error: `${err.name}: ${err.message}`
995
- });
996
- }
997
- }
998
- };
999
- function hasDotKey(obj) {
1000
- for (const k of Object.keys(obj)) {
1001
- if (k.includes(".")) return true;
1002
- }
1003
- return false;
1004
- }
1005
-
1006
1082
  // src/loop.ts
1007
1083
  var CacheFirstLoop = class {
1008
1084
  client;
@@ -1031,7 +1107,7 @@ var CacheFirstLoop = class {
1031
1107
  this.prefix = opts.prefix;
1032
1108
  this.tools = opts.tools ?? new ToolRegistry();
1033
1109
  this.model = opts.model ?? "deepseek-chat";
1034
- this.maxToolIters = opts.maxToolIters ?? 8;
1110
+ this.maxToolIters = opts.maxToolIters ?? 24;
1035
1111
  if (typeof opts.branch === "number") {
1036
1112
  this.branchOptions = { budget: opts.branch };
1037
1113
  } else if (opts.branch && typeof opts.branch === "object") {
@@ -1050,12 +1126,49 @@ var CacheFirstLoop = class {
1050
1126
  this.sessionName = opts.session ?? null;
1051
1127
  if (this.sessionName) {
1052
1128
  const prior = loadSessionMessages(this.sessionName);
1053
- for (const msg of prior) this.log.append(msg);
1054
- this.resumedMessageCount = prior.length;
1129
+ const { messages, healedCount, healedFrom } = healLoadedMessages(
1130
+ prior,
1131
+ DEFAULT_MAX_RESULT_CHARS
1132
+ );
1133
+ for (const msg of messages) this.log.append(msg);
1134
+ this.resumedMessageCount = messages.length;
1135
+ if (healedCount > 0) {
1136
+ process.stderr.write(
1137
+ `\u25B8 session "${this.sessionName}": healed ${healedCount} oversized tool result(s) (was ${healedFrom.toLocaleString()} chars total). Old payloads were truncated to fit DeepSeek's context window; the conversation is preserved.
1138
+ `
1139
+ );
1140
+ }
1055
1141
  } else {
1056
1142
  this.resumedMessageCount = 0;
1057
1143
  }
1058
1144
  }
1145
+ /**
1146
+ * Shrink the log by re-truncating oversized tool results to a tighter
1147
+ * cap, and persist the result back to disk so the next launch doesn't
1148
+ * re-inherit a fat session file. Returns a summary the TUI can
1149
+ * display.
1150
+ *
1151
+ * Only tool-role messages are touched (same rationale as
1152
+ * {@link healLoadedMessages}). User and assistant messages carry
1153
+ * authored intent we can't mechanically shrink without losing
1154
+ * meaning.
1155
+ */
1156
+ compact(tightCapChars = 4e3) {
1157
+ const before = this.log.toMessages();
1158
+ const { messages, healedCount, healedFrom } = healLoadedMessages(before, tightCapChars);
1159
+ const afterBytes = messages.filter((m) => m.role === "tool").reduce((s, m) => s + (typeof m.content === "string" ? m.content.length : 0), 0);
1160
+ const charsSaved = healedFrom - afterBytes;
1161
+ if (healedCount > 0) {
1162
+ this.log.compactInPlace(messages);
1163
+ if (this.sessionName) {
1164
+ try {
1165
+ rewriteSession(this.sessionName, messages);
1166
+ } catch {
1167
+ }
1168
+ }
1169
+ }
1170
+ return { healedCount, charsSaved };
1171
+ }
1059
1172
  appendAndPersist(message) {
1060
1173
  this.log.append(message);
1061
1174
  if (this.sessionName) {
@@ -1245,7 +1358,7 @@ var CacheFirstLoop = class {
1245
1358
  turn: this._turn,
1246
1359
  role: "error",
1247
1360
  content: "",
1248
- error: err.message
1361
+ error: formatLoopError(err)
1249
1362
  };
1250
1363
  return;
1251
1364
  }
@@ -1293,7 +1406,38 @@ var CacheFirstLoop = class {
1293
1406
  };
1294
1407
  }
1295
1408
  }
1296
- yield { turn: this._turn, role: "done", content: "[max_tool_iters reached]" };
1409
+ yield* this.forceSummaryAfterIterLimit();
1410
+ }
1411
+ async *forceSummaryAfterIterLimit() {
1412
+ try {
1413
+ const messages = this.buildMessages(null);
1414
+ const resp = await this.client.chat({
1415
+ model: this.model,
1416
+ messages
1417
+ // no tools → model is forced to answer in text
1418
+ });
1419
+ const summary = resp.content?.trim() || "(model returned no text; try a narrower question or raise --max-tool-iters)";
1420
+ const annotated = `[tool-call budget (${this.maxToolIters}) reached \u2014 forcing summary from what I found]
1421
+
1422
+ ${summary}`;
1423
+ const summaryStats = this.stats.record(this._turn, this.model, resp.usage ?? new Usage());
1424
+ this.appendAndPersist({ role: "assistant", content: summary });
1425
+ yield {
1426
+ turn: this._turn,
1427
+ role: "assistant_final",
1428
+ content: annotated,
1429
+ stats: summaryStats
1430
+ };
1431
+ yield { turn: this._turn, role: "done", content: summary };
1432
+ } catch (err) {
1433
+ yield {
1434
+ turn: this._turn,
1435
+ role: "error",
1436
+ content: "",
1437
+ error: `tool-call budget (${this.maxToolIters}) reached and the fallback summary call failed: ${err.message}. Run /clear and retry with a narrower question, or pass --max-tool-iters higher.`
1438
+ };
1439
+ yield { turn: this._turn, role: "done", content: "" };
1440
+ }
1297
1441
  }
1298
1442
  async run(userInput, onEvent) {
1299
1443
  let final = "";
@@ -1318,6 +1462,28 @@ function summarizeBranch(chosen, samples) {
1318
1462
  temperatures: samples.map((s) => s.temperature)
1319
1463
  };
1320
1464
  }
1465
+ function healLoadedMessages(messages, maxChars) {
1466
+ let healedCount = 0;
1467
+ let healedFrom = 0;
1468
+ const out = messages.map((msg) => {
1469
+ if (msg.role !== "tool") return msg;
1470
+ const content = typeof msg.content === "string" ? msg.content : "";
1471
+ if (content.length <= maxChars) return msg;
1472
+ healedCount += 1;
1473
+ healedFrom += content.length;
1474
+ return { ...msg, content: truncateForModel(content, maxChars) };
1475
+ });
1476
+ return { messages: out, healedCount, healedFrom };
1477
+ }
1478
+ function formatLoopError(err) {
1479
+ const msg = err.message ?? "";
1480
+ if (msg.includes("maximum context length")) {
1481
+ const reqMatch = msg.match(/requested\s+(\d+)\s+tokens/);
1482
+ const requested = reqMatch ? `${Number(reqMatch[1]).toLocaleString()} tokens` : "too many tokens";
1483
+ return `Context overflow (DeepSeek 400): session history is ${requested}, past the 131,072-token limit. Usually this means a single tool call returned a huge payload. v0.3.0-alpha.6+ caps new tool results at 32k chars, AND auto-heals oversized history on session load \u2014 restart Reasonix and this session should come back trimmed. If it still overflows, run /forget (delete the session) or /clear (drop the displayed history) to start fresh.`;
1484
+ }
1485
+ return msg;
1486
+ }
1321
1487
 
1322
1488
  // src/env.ts
1323
1489
  import { readFileSync as readFileSync2 } from "fs";
@@ -1494,12 +1660,14 @@ function summarizeTurns(turns) {
1494
1660
  }
1495
1661
  const cacheHitRatio = hit + miss > 0 ? hit / (hit + miss) : 0;
1496
1662
  const savingsVsClaude = totalClaude > 0 ? 1 - totalCost / totalClaude : 0;
1663
+ const lastTurn = turns[turns.length - 1];
1497
1664
  return {
1498
1665
  turns: turns.length,
1499
1666
  totalCostUsd: round2(totalCost, 6),
1500
1667
  claudeEquivalentUsd: round2(totalClaude, 6),
1501
1668
  savingsVsClaudePct: round2(savingsVsClaude * 100, 2),
1502
- cacheHitRatio: round2(cacheHitRatio, 4)
1669
+ cacheHitRatio: round2(cacheHitRatio, 4),
1670
+ lastPromptTokens: lastTurn?.usage.promptTokens ?? 0
1503
1671
  };
1504
1672
  }
1505
1673
  function round2(n, digits) {
@@ -2206,45 +2374,6 @@ var SseTransport = class {
2206
2374
  }
2207
2375
  };
2208
2376
 
2209
- // src/mcp/registry.ts
2210
- async function bridgeMcpTools(client, opts = {}) {
2211
- const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
2212
- const prefix = opts.namePrefix ?? "";
2213
- const result = { registry, registeredNames: [], skipped: [] };
2214
- const listed = await client.listTools();
2215
- for (const mcpTool of listed.tools) {
2216
- if (!mcpTool.name) {
2217
- result.skipped.push({ name: "?", reason: "empty tool name" });
2218
- continue;
2219
- }
2220
- const registeredName = `${prefix}${mcpTool.name}`;
2221
- registry.register({
2222
- name: registeredName,
2223
- description: mcpTool.description ?? "",
2224
- parameters: mcpTool.inputSchema,
2225
- fn: async (args) => {
2226
- const toolResult = await client.callTool(mcpTool.name, args);
2227
- return flattenMcpResult(toolResult);
2228
- }
2229
- });
2230
- result.registeredNames.push(registeredName);
2231
- }
2232
- return result;
2233
- }
2234
- function flattenMcpResult(result) {
2235
- const parts = result.content.map(blockToString);
2236
- const joined = parts.join("\n").trim();
2237
- if (result.isError) {
2238
- return `ERROR: ${joined || "(no error message from server)"}`;
2239
- }
2240
- return joined;
2241
- }
2242
- function blockToString(block) {
2243
- if (block.type === "text") return block.text;
2244
- if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
2245
- return `[unknown block: ${JSON.stringify(block)}]`;
2246
- }
2247
-
2248
2377
  // src/mcp/shell-split.ts
2249
2378
  function shellSplit(input) {
2250
2379
  const tokens = [];
@@ -2320,7 +2449,7 @@ function parseMcpSpec(input) {
2320
2449
  }
2321
2450
 
2322
2451
  // src/config.ts
2323
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync } from "fs";
2452
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2324
2453
  import { homedir as homedir2 } from "os";
2325
2454
  import { dirname as dirname2, join as join2 } from "path";
2326
2455
  function defaultConfigPath() {
@@ -2337,7 +2466,7 @@ function readConfig(path = defaultConfigPath()) {
2337
2466
  }
2338
2467
  function writeConfig(cfg, path = defaultConfigPath()) {
2339
2468
  mkdirSync2(dirname2(path), { recursive: true });
2340
- writeFileSync(path, JSON.stringify(cfg, null, 2), "utf8");
2469
+ writeFileSync2(path, JSON.stringify(cfg, null, 2), "utf8");
2341
2470
  try {
2342
2471
  chmodSync2(path, 384);
2343
2472
  } catch {
@@ -2363,10 +2492,11 @@ function redactKey(key) {
2363
2492
  }
2364
2493
 
2365
2494
  // src/index.ts
2366
- var VERSION = "0.3.0-alpha.4";
2495
+ var VERSION = "0.3.1";
2367
2496
  export {
2368
2497
  AppendOnlyLog,
2369
2498
  CacheFirstLoop,
2499
+ DEFAULT_MAX_RESULT_CHARS,
2370
2500
  DeepSeekClient,
2371
2501
  ImmutablePrefix,
2372
2502
  MCP_PROTOCOL_VERSION,
@@ -2395,7 +2525,9 @@ export {
2395
2525
  fetchWithRetry,
2396
2526
  flattenMcpResult,
2397
2527
  flattenSchema,
2528
+ formatLoopError,
2398
2529
  harvest,
2530
+ healLoadedMessages,
2399
2531
  isJsonRpcError,
2400
2532
  isPlanStateEmpty,
2401
2533
  isPlausibleKey,
@@ -2422,6 +2554,7 @@ export {
2422
2554
  sessionPath,
2423
2555
  sessionsDir,
2424
2556
  similarity,
2557
+ truncateForModel,
2425
2558
  writeConfig,
2426
2559
  writeMeta,
2427
2560
  writeRecord