reasonix 0.3.2 → 0.4.3

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
@@ -47,8 +47,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
47
47
  }
48
48
  function sleep(ms, signal) {
49
49
  if (ms <= 0) return Promise.resolve();
50
- return new Promise((resolve2, reject) => {
51
- const timer = setTimeout(resolve2, ms);
50
+ return new Promise((resolve3, reject) => {
51
+ const timer = setTimeout(resolve3, ms);
52
52
  if (signal) {
53
53
  const onAbort = () => {
54
54
  clearTimeout(timer);
@@ -1020,6 +1020,11 @@ var DEEPSEEK_PRICING = {
1020
1020
  "deepseek-reasoner": { inputCacheHit: 0.14, inputCacheMiss: 0.55, output: 2.19 }
1021
1021
  };
1022
1022
  var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
1023
+ var DEEPSEEK_CONTEXT_TOKENS = {
1024
+ "deepseek-chat": 131072,
1025
+ "deepseek-reasoner": 131072
1026
+ };
1027
+ var DEFAULT_CONTEXT_TOKENS = 131072;
1023
1028
  function costUsd(model, usage) {
1024
1029
  const p = DEEPSEEK_PRICING[model];
1025
1030
  if (!p) return 0;
@@ -1113,7 +1118,7 @@ var CacheFirstLoop = class {
1113
1118
  this.prefix = opts.prefix;
1114
1119
  this.tools = opts.tools ?? new ToolRegistry();
1115
1120
  this.model = opts.model ?? "deepseek-chat";
1116
- this.maxToolIters = opts.maxToolIters ?? 24;
1121
+ this.maxToolIters = opts.maxToolIters ?? 64;
1117
1122
  if (typeof opts.branch === "number") {
1118
1123
  this.branchOptions = { budget: opts.branch };
1119
1124
  } else if (opts.branch && typeof opts.branch === "object") {
@@ -1229,6 +1234,39 @@ var CacheFirstLoop = class {
1229
1234
  abort() {
1230
1235
  this._aborted = true;
1231
1236
  }
1237
+ /**
1238
+ * Drop everything in the log after (and including) the most recent
1239
+ * user message. Used by `/retry` so the caller can re-send that
1240
+ * message with a fresh turn instead of layering another response on
1241
+ * top of the prior exchange. Returns the content of the dropped user
1242
+ * message, or `null` if there isn't one yet.
1243
+ *
1244
+ * Persists by rewriting the session file — otherwise the next
1245
+ * launch would rehydrate the old exchange and `/retry` would seem
1246
+ * to have done nothing.
1247
+ */
1248
+ retryLastUser() {
1249
+ const entries = this.log.entries;
1250
+ let lastUserIdx = -1;
1251
+ for (let i = entries.length - 1; i >= 0; i--) {
1252
+ if (entries[i].role === "user") {
1253
+ lastUserIdx = i;
1254
+ break;
1255
+ }
1256
+ }
1257
+ if (lastUserIdx < 0) return null;
1258
+ const raw = entries[lastUserIdx].content;
1259
+ const userText = typeof raw === "string" ? raw : "";
1260
+ const preserved = entries.slice(0, lastUserIdx).map((m) => ({ ...m }));
1261
+ this.log.compactInPlace(preserved);
1262
+ if (this.sessionName) {
1263
+ try {
1264
+ rewriteSession(this.sessionName, preserved);
1265
+ } catch {
1266
+ }
1267
+ }
1268
+ return userText;
1269
+ }
1232
1270
  async *step(userInput) {
1233
1271
  this._turn++;
1234
1272
  this.scratch.reset();
@@ -1242,9 +1280,17 @@ var CacheFirstLoop = class {
1242
1280
  yield {
1243
1281
  turn: this._turn,
1244
1282
  role: "warning",
1245
- content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 forcing summary from what was gathered`
1283
+ content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)`
1246
1284
  };
1247
- yield* this.forceSummaryAfterIterLimit({ reason: "aborted" });
1285
+ const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
1286
+ this.appendAndPersist({ role: "assistant", content: stoppedMsg });
1287
+ yield {
1288
+ turn: this._turn,
1289
+ role: "assistant_final",
1290
+ content: stoppedMsg,
1291
+ forcedSummary: true
1292
+ };
1293
+ yield { turn: this._turn, role: "done", content: stoppedMsg };
1248
1294
  return;
1249
1295
  }
1250
1296
  if (!warnedForIterBudget && iter >= warnAt) {
@@ -1302,8 +1348,8 @@ var CacheFirstLoop = class {
1302
1348
  }
1303
1349
  );
1304
1350
  for (let k = 0; k < budget; k++) {
1305
- const sample = queue.shift() ?? await new Promise((resolve2) => {
1306
- waiter = resolve2;
1351
+ const sample = queue.shift() ?? await new Promise((resolve3) => {
1352
+ waiter = resolve3;
1307
1353
  });
1308
1354
  yield {
1309
1355
  turn: this._turn,
@@ -1423,9 +1469,28 @@ var CacheFirstLoop = class {
1423
1469
  yield { turn: this._turn, role: "done", content: assistantContent };
1424
1470
  return;
1425
1471
  }
1472
+ const ctxMax = DEEPSEEK_CONTEXT_TOKENS[this.model] ?? DEFAULT_CONTEXT_TOKENS;
1473
+ if (usage && usage.promptTokens / ctxMax > 0.8) {
1474
+ yield {
1475
+ turn: this._turn,
1476
+ role: "warning",
1477
+ content: `context ${usage.promptTokens}/${ctxMax} (${Math.round(
1478
+ usage.promptTokens / ctxMax * 100
1479
+ )}%) \u2014 more tools would overflow. Forcing summary from what was gathered.`
1480
+ };
1481
+ yield* this.forceSummaryAfterIterLimit({ reason: "context-guard" });
1482
+ return;
1483
+ }
1426
1484
  for (const call of repairedCalls) {
1427
1485
  const name = call.function?.name ?? "";
1428
1486
  const args = call.function?.arguments ?? "{}";
1487
+ yield {
1488
+ turn: this._turn,
1489
+ role: "tool_start",
1490
+ content: "",
1491
+ toolName: name,
1492
+ toolArgs: args
1493
+ };
1429
1494
  const result = await this.tools.dispatch(name, args);
1430
1495
  this.appendAndPersist({
1431
1496
  role: "tool",
@@ -1447,13 +1512,19 @@ var CacheFirstLoop = class {
1447
1512
  async *forceSummaryAfterIterLimit(opts = { reason: "budget" }) {
1448
1513
  try {
1449
1514
  const messages = this.buildMessages(null);
1515
+ messages.push({
1516
+ role: "user",
1517
+ content: "I'm out of tool-call budget for this turn. Summarize in plain prose what you learned from the tool results above. Do NOT emit any tool calls, function-call markup, DSML invocations, or SEARCH/REPLACE edit blocks \u2014 they will be silently discarded. Just plain text."
1518
+ });
1450
1519
  const resp = await this.client.chat({
1451
1520
  model: this.model,
1452
1521
  messages
1453
1522
  // no tools → model is forced to answer in text
1454
1523
  });
1455
- const summary = resp.content?.trim() || "(model returned no text; try a narrower question or raise --max-tool-iters)";
1456
- const reasonPrefix = opts.reason === "aborted" ? "[aborted by user (Esc) \u2014 summarizing what I found so far]" : `[tool-call budget (${this.maxToolIters}) reached \u2014 forcing summary from what I found]`;
1524
+ const rawContent = resp.content?.trim() ?? "";
1525
+ const cleaned = stripHallucinatedToolMarkup(rawContent);
1526
+ const summary = cleaned || "(model emitted fake tool-call markup instead of a prose summary \u2014 try /retry with a narrower question, or /think to inspect R1's reasoning)";
1527
+ const reasonPrefix = reasonPrefixFor(opts.reason, this.maxToolIters);
1457
1528
  const annotated = `${reasonPrefix}
1458
1529
 
1459
1530
  ${summary}`;
@@ -1463,11 +1534,12 @@ ${summary}`;
1463
1534
  turn: this._turn,
1464
1535
  role: "assistant_final",
1465
1536
  content: annotated,
1466
- stats: summaryStats
1537
+ stats: summaryStats,
1538
+ forcedSummary: true
1467
1539
  };
1468
1540
  yield { turn: this._turn, role: "done", content: summary };
1469
1541
  } catch (err) {
1470
- const label = opts.reason === "aborted" ? "aborted by user" : `tool-call budget (${this.maxToolIters}) reached`;
1542
+ const label = errorLabelFor(opts.reason, this.maxToolIters);
1471
1543
  yield {
1472
1544
  turn: this._turn,
1473
1545
  role: "error",
@@ -1492,6 +1564,26 @@ ${summary}`;
1492
1564
  return msg;
1493
1565
  }
1494
1566
  };
1567
+ function stripHallucinatedToolMarkup(s) {
1568
+ let out = s;
1569
+ out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
1570
+ out = out.replace(/<\|DSML\|function_calls>[\s\S]*?<\/?\|DSML\|function_calls>/g, "");
1571
+ out = out.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "");
1572
+ out = out.replace(/<|DSML|[\s\S]*$/g, "");
1573
+ return out.trim();
1574
+ }
1575
+ function reasonPrefixFor(reason, iterCap) {
1576
+ if (reason === "aborted") return "[aborted by user (Esc) \u2014 summarizing what I found so far]";
1577
+ if (reason === "context-guard") {
1578
+ return "[context budget running low \u2014 summarizing before the next call would overflow]";
1579
+ }
1580
+ return `[tool-call budget (${iterCap}) reached \u2014 forcing summary from what I found]`;
1581
+ }
1582
+ function errorLabelFor(reason, iterCap) {
1583
+ if (reason === "aborted") return "aborted by user";
1584
+ if (reason === "context-guard") return "context-guard triggered (prompt > 80% of window)";
1585
+ return `tool-call budget (${iterCap}) reached`;
1586
+ }
1495
1587
  function summarizeBranch(chosen, samples) {
1496
1588
  return {
1497
1589
  budget: samples.length,
@@ -2114,7 +2206,7 @@ var McpClient = class {
2114
2206
  async request(method, params) {
2115
2207
  const id = this.nextId++;
2116
2208
  const frame = { jsonrpc: "2.0", id, method, params };
2117
- const promise = new Promise((resolve2, reject) => {
2209
+ const promise = new Promise((resolve3, reject) => {
2118
2210
  const timeout = setTimeout(() => {
2119
2211
  this.pending.delete(id);
2120
2212
  reject(
@@ -2122,7 +2214,7 @@ var McpClient = class {
2122
2214
  );
2123
2215
  }, this.requestTimeoutMs);
2124
2216
  this.pending.set(id, {
2125
- resolve: resolve2,
2217
+ resolve: resolve3,
2126
2218
  reject,
2127
2219
  timeout
2128
2220
  });
@@ -2206,12 +2298,12 @@ var StdioTransport = class {
2206
2298
  }
2207
2299
  async send(message) {
2208
2300
  if (this.closed) throw new Error("MCP transport is closed");
2209
- return new Promise((resolve2, reject) => {
2301
+ return new Promise((resolve3, reject) => {
2210
2302
  const line = `${JSON.stringify(message)}
2211
2303
  `;
2212
2304
  this.child.stdin.write(line, "utf8", (err) => {
2213
2305
  if (err) reject(err);
2214
- else resolve2();
2306
+ else resolve3();
2215
2307
  });
2216
2308
  });
2217
2309
  }
@@ -2222,8 +2314,8 @@ var StdioTransport = class {
2222
2314
  continue;
2223
2315
  }
2224
2316
  if (this.closed) return;
2225
- const next = await new Promise((resolve2) => {
2226
- this.waiters.push(resolve2);
2317
+ const next = await new Promise((resolve3) => {
2318
+ this.waiters.push(resolve3);
2227
2319
  });
2228
2320
  if (next === null) return;
2229
2321
  yield next;
@@ -2289,8 +2381,8 @@ var SseTransport = class {
2289
2381
  constructor(opts) {
2290
2382
  this.url = opts.url;
2291
2383
  this.headers = opts.headers ?? {};
2292
- this.endpointReady = new Promise((resolve2, reject) => {
2293
- this.resolveEndpoint = resolve2;
2384
+ this.endpointReady = new Promise((resolve3, reject) => {
2385
+ this.resolveEndpoint = resolve3;
2294
2386
  this.rejectEndpoint = reject;
2295
2387
  });
2296
2388
  this.endpointReady.catch(() => void 0);
@@ -2317,8 +2409,8 @@ var SseTransport = class {
2317
2409
  continue;
2318
2410
  }
2319
2411
  if (this.closed) return;
2320
- const next = await new Promise((resolve2) => {
2321
- this.waiters.push(resolve2);
2412
+ const next = await new Promise((resolve3) => {
2413
+ this.waiters.push(resolve3);
2322
2414
  });
2323
2415
  if (next === null) return;
2324
2416
  yield next;
@@ -2486,16 +2578,215 @@ function parseMcpSpec(input) {
2486
2578
  return { transport: "stdio", name, command, args };
2487
2579
  }
2488
2580
 
2581
+ // src/code/edit-blocks.ts
2582
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
2583
+ import { dirname as dirname2, resolve as resolve2 } from "path";
2584
+ var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
2585
+ function parseEditBlocks(text) {
2586
+ const out = [];
2587
+ BLOCK_RE.lastIndex = 0;
2588
+ let m = BLOCK_RE.exec(text);
2589
+ while (m !== null) {
2590
+ out.push({
2591
+ path: m[1].trim(),
2592
+ search: m[2],
2593
+ replace: m[3],
2594
+ offset: m.index
2595
+ });
2596
+ m = BLOCK_RE.exec(text);
2597
+ }
2598
+ return out;
2599
+ }
2600
+ function applyEditBlock(block, rootDir) {
2601
+ const absRoot = resolve2(rootDir);
2602
+ const absTarget = resolve2(absRoot, block.path);
2603
+ if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
2604
+ return {
2605
+ path: block.path,
2606
+ status: "path-escape",
2607
+ message: `resolved path ${absTarget} is outside rootDir ${absRoot}`
2608
+ };
2609
+ }
2610
+ const searchEmpty = block.search.length === 0;
2611
+ const exists = existsSync2(absTarget);
2612
+ try {
2613
+ if (!exists) {
2614
+ if (!searchEmpty) {
2615
+ return {
2616
+ path: block.path,
2617
+ status: "file-missing",
2618
+ message: "file does not exist; to create it, use an empty SEARCH block"
2619
+ };
2620
+ }
2621
+ mkdirSync2(dirname2(absTarget), { recursive: true });
2622
+ writeFileSync2(absTarget, block.replace, "utf8");
2623
+ return { path: block.path, status: "created" };
2624
+ }
2625
+ const content = readFileSync4(absTarget, "utf8");
2626
+ if (searchEmpty) {
2627
+ return {
2628
+ path: block.path,
2629
+ status: "not-found",
2630
+ message: "empty SEARCH only creates new files \u2014 this file already exists"
2631
+ };
2632
+ }
2633
+ const idx = content.indexOf(block.search);
2634
+ if (idx === -1) {
2635
+ return {
2636
+ path: block.path,
2637
+ status: "not-found",
2638
+ message: "SEARCH text does not match the current file content exactly"
2639
+ };
2640
+ }
2641
+ const replaced = `${content.slice(0, idx)}${block.replace}${content.slice(idx + block.search.length)}`;
2642
+ writeFileSync2(absTarget, replaced, "utf8");
2643
+ return { path: block.path, status: "applied" };
2644
+ } catch (err) {
2645
+ return { path: block.path, status: "error", message: err.message };
2646
+ }
2647
+ }
2648
+ function applyEditBlocks(blocks, rootDir) {
2649
+ return blocks.map((b) => applyEditBlock(b, rootDir));
2650
+ }
2651
+ function snapshotBeforeEdits(blocks, rootDir) {
2652
+ const absRoot = resolve2(rootDir);
2653
+ const seen = /* @__PURE__ */ new Set();
2654
+ const snapshots = [];
2655
+ for (const b of blocks) {
2656
+ if (seen.has(b.path)) continue;
2657
+ seen.add(b.path);
2658
+ const abs = resolve2(absRoot, b.path);
2659
+ if (!existsSync2(abs)) {
2660
+ snapshots.push({ path: b.path, prevContent: null });
2661
+ continue;
2662
+ }
2663
+ try {
2664
+ snapshots.push({ path: b.path, prevContent: readFileSync4(abs, "utf8") });
2665
+ } catch {
2666
+ snapshots.push({ path: b.path, prevContent: null });
2667
+ }
2668
+ }
2669
+ return snapshots;
2670
+ }
2671
+ function restoreSnapshots(snapshots, rootDir) {
2672
+ const absRoot = resolve2(rootDir);
2673
+ return snapshots.map((snap) => {
2674
+ const abs = resolve2(absRoot, snap.path);
2675
+ if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
2676
+ return {
2677
+ path: snap.path,
2678
+ status: "path-escape",
2679
+ message: "snapshot path escapes rootDir \u2014 refusing to restore"
2680
+ };
2681
+ }
2682
+ try {
2683
+ if (snap.prevContent === null) {
2684
+ if (existsSync2(abs)) unlinkSync2(abs);
2685
+ return {
2686
+ path: snap.path,
2687
+ status: "applied",
2688
+ message: "removed (the edit had created it)"
2689
+ };
2690
+ }
2691
+ writeFileSync2(abs, snap.prevContent, "utf8");
2692
+ return {
2693
+ path: snap.path,
2694
+ status: "applied",
2695
+ message: "restored to pre-edit content"
2696
+ };
2697
+ } catch (err) {
2698
+ return { path: snap.path, status: "error", message: err.message };
2699
+ }
2700
+ });
2701
+ }
2702
+ function sep() {
2703
+ return process.platform === "win32" ? "\\" : "/";
2704
+ }
2705
+
2706
+ // src/code/prompt.ts
2707
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
2708
+ import { join as join2 } from "path";
2709
+ var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.
2710
+
2711
+ # When to edit vs. when to explore
2712
+
2713
+ Only propose edits when the user explicitly asks you to change, fix, add, remove, refactor, or write something. Do NOT propose edits when the user asks you to:
2714
+ - analyze, read, explore, describe, or summarize a project
2715
+ - explain how something works
2716
+ - answer a question about the code
2717
+
2718
+ In those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.
2719
+
2720
+ When you do propose edits, the user will review them and decide whether to \`/apply\` or \`/discard\`. Don't assume they'll accept \u2014 write as if each edit will be audited, because it will.
2721
+
2722
+ # Editing files
2723
+
2724
+ When you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:
2725
+
2726
+ path/to/file.ext
2727
+ <<<<<<< SEARCH
2728
+ exact existing lines from the file, including whitespace
2729
+ =======
2730
+ the new lines
2731
+ >>>>>>> REPLACE
2732
+
2733
+ Rules:
2734
+ - Always read_file first so your SEARCH matches byte-for-byte. If it doesn't match, the edit is rejected and you'll have to retry with the exact current content.
2735
+ - One edit per block. Multiple blocks in one response are fine.
2736
+ - To create a new file, leave SEARCH empty:
2737
+ path/to/new.ts
2738
+ <<<<<<< SEARCH
2739
+ =======
2740
+ (whole file content here)
2741
+ >>>>>>> REPLACE
2742
+ - Do NOT use write_file to change existing files \u2014 the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).
2743
+ - Paths are relative to the working directory. Don't use absolute paths.
2744
+
2745
+ # Exploration
2746
+
2747
+ - Avoid listing or reading inside these common dependency / build directories unless the user explicitly asks about them: node_modules, dist, build, out, .next, .nuxt, .svelte-kit, .git, .venv, venv, __pycache__, target, coverage, .turbo, .cache. They're expensive and usually irrelevant.
2748
+ - Prefer search_files / grep over list_directory when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees.
2749
+
2750
+ # Style
2751
+
2752
+ - Show edits; don't narrate them in prose. "Here's the fix:" is enough.
2753
+ - One short paragraph explaining *why*, then the blocks.
2754
+ - If you need to explore first (list / grep / read), do it with tool calls before writing any prose \u2014 silence while exploring is fine.
2755
+ `;
2756
+ function codeSystemPrompt(rootDir) {
2757
+ const gitignorePath = join2(rootDir, ".gitignore");
2758
+ if (!existsSync3(gitignorePath)) return CODE_SYSTEM_PROMPT;
2759
+ let content;
2760
+ try {
2761
+ content = readFileSync5(gitignorePath, "utf8");
2762
+ } catch {
2763
+ return CODE_SYSTEM_PROMPT;
2764
+ }
2765
+ const MAX = 2e3;
2766
+ const truncated = content.length > MAX ? `${content.slice(0, MAX)}
2767
+ \u2026 (truncated ${content.length - MAX} chars)` : content;
2768
+ return `${CODE_SYSTEM_PROMPT}
2769
+
2770
+ # Project .gitignore
2771
+
2772
+ The user's repo ships this .gitignore \u2014 treat every pattern as "don't traverse or edit inside these paths unless explicitly asked":
2773
+
2774
+ \`\`\`
2775
+ ${truncated}
2776
+ \`\`\`
2777
+ `;
2778
+ }
2779
+
2489
2780
  // src/config.ts
2490
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2781
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
2491
2782
  import { homedir as homedir2 } from "os";
2492
- import { dirname as dirname2, join as join2 } from "path";
2783
+ import { dirname as dirname3, join as join3 } from "path";
2493
2784
  function defaultConfigPath() {
2494
- return join2(homedir2(), ".reasonix", "config.json");
2785
+ return join3(homedir2(), ".reasonix", "config.json");
2495
2786
  }
2496
2787
  function readConfig(path = defaultConfigPath()) {
2497
2788
  try {
2498
- const raw = readFileSync4(path, "utf8");
2789
+ const raw = readFileSync6(path, "utf8");
2499
2790
  const parsed = JSON.parse(raw);
2500
2791
  if (parsed && typeof parsed === "object") return parsed;
2501
2792
  } catch {
@@ -2503,8 +2794,8 @@ function readConfig(path = defaultConfigPath()) {
2503
2794
  return {};
2504
2795
  }
2505
2796
  function writeConfig(cfg, path = defaultConfigPath()) {
2506
- mkdirSync2(dirname2(path), { recursive: true });
2507
- writeFileSync2(path, JSON.stringify(cfg, null, 2), "utf8");
2797
+ mkdirSync3(dirname3(path), { recursive: true });
2798
+ writeFileSync3(path, JSON.stringify(cfg, null, 2), "utf8");
2508
2799
  try {
2509
2800
  chmodSync2(path, 384);
2510
2801
  } catch {
@@ -2530,9 +2821,10 @@ function redactKey(key) {
2530
2821
  }
2531
2822
 
2532
2823
  // src/index.ts
2533
- var VERSION = "0.3.2";
2824
+ var VERSION = "0.4.3";
2534
2825
  export {
2535
2826
  AppendOnlyLog,
2827
+ CODE_SYSTEM_PROMPT,
2536
2828
  CacheFirstLoop,
2537
2829
  DEFAULT_MAX_RESULT_CHARS,
2538
2830
  DeepSeekClient,
@@ -2551,8 +2843,11 @@ export {
2551
2843
  aggregateBranchUsage,
2552
2844
  analyzeSchema,
2553
2845
  appendSessionMessage,
2846
+ applyEditBlock,
2847
+ applyEditBlocks,
2554
2848
  bridgeMcpTools,
2555
2849
  claudeEquivalentCost,
2850
+ codeSystemPrompt,
2556
2851
  computeReplayStats,
2557
2852
  costUsd,
2558
2853
  defaultConfigPath,
@@ -2575,6 +2870,7 @@ export {
2575
2870
  loadSessionMessages,
2576
2871
  nestArguments,
2577
2872
  openTranscriptFile,
2873
+ parseEditBlocks,
2578
2874
  parseMcpSpec,
2579
2875
  parseTranscript,
2580
2876
  readConfig,
@@ -2585,6 +2881,7 @@ export {
2585
2881
  renderSummaryTable as renderDiffSummary,
2586
2882
  repairTruncatedJson,
2587
2883
  replayFromFile,
2884
+ restoreSnapshots,
2588
2885
  runBranches,
2589
2886
  sanitizeName as sanitizeSessionName,
2590
2887
  saveApiKey,
@@ -2592,6 +2889,8 @@ export {
2592
2889
  sessionPath,
2593
2890
  sessionsDir,
2594
2891
  similarity,
2892
+ snapshotBeforeEdits,
2893
+ stripHallucinatedToolMarkup,
2595
2894
  truncateForModel,
2596
2895
  writeConfig,
2597
2896
  writeMeta,