reasonix 0.5.3 → 0.5.6

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((resolve7, reject) => {
51
- const timer = setTimeout(resolve7, ms);
50
+ return new Promise((resolve8, reject) => {
51
+ const timer = setTimeout(resolve8, ms);
52
52
  if (signal) {
53
53
  const onAbort = () => {
54
54
  clearTimeout(timer);
@@ -520,7 +520,7 @@ function matchesTool(hook, toolName) {
520
520
  }
521
521
  }
522
522
  function defaultSpawner(input) {
523
- return new Promise((resolve7) => {
523
+ return new Promise((resolve8) => {
524
524
  const child = spawn(input.command, {
525
525
  cwd: input.cwd,
526
526
  shell: true,
@@ -547,7 +547,7 @@ function defaultSpawner(input) {
547
547
  });
548
548
  child.once("error", (err) => {
549
549
  clearTimeout(timer);
550
- resolve7({
550
+ resolve8({
551
551
  exitCode: null,
552
552
  stdout,
553
553
  stderr,
@@ -557,7 +557,7 @@ function defaultSpawner(input) {
557
557
  });
558
558
  child.once("close", (code) => {
559
559
  clearTimeout(timer);
560
- resolve7({
560
+ resolve8({
561
561
  exitCode: code,
562
562
  stdout: stdout.trim(),
563
563
  stderr: stderr.trim(),
@@ -618,7 +618,7 @@ async function runHooks(opts) {
618
618
  }
619
619
 
620
620
  // src/tokenizer.ts
621
- import { readFileSync as readFileSync2 } from "fs";
621
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
622
622
  import { createRequire } from "module";
623
623
  import { dirname, join as join2 } from "path";
624
624
  import { fileURLToPath } from "url";
@@ -646,17 +646,24 @@ function buildByteToChar() {
646
646
  var cached = null;
647
647
  function resolveDataPath() {
648
648
  if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
649
+ const candidates = [];
649
650
  try {
650
651
  const here = dirname(fileURLToPath(import.meta.url));
651
- return join2(here, "..", "data", "deepseek-tokenizer.json.gz");
652
+ candidates.push(join2(here, "..", "data", "deepseek-tokenizer.json.gz"));
653
+ candidates.push(join2(here, "..", "..", "data", "deepseek-tokenizer.json.gz"));
652
654
  } catch {
655
+ }
656
+ try {
653
657
  const req = createRequire(import.meta.url);
654
- return join2(
655
- dirname(req.resolve("reasonix/package.json")),
656
- "data",
657
- "deepseek-tokenizer.json.gz"
658
+ candidates.push(
659
+ join2(dirname(req.resolve("reasonix/package.json")), "data", "deepseek-tokenizer.json.gz")
658
660
  );
661
+ } catch {
662
+ }
663
+ for (const p of candidates) {
664
+ if (existsSync2(p)) return p;
659
665
  }
666
+ return candidates[0] ?? join2(process.cwd(), "data", "deepseek-tokenizer.json.gz");
660
667
  }
661
668
  function loadTokenizer() {
662
669
  if (cached) return cached;
@@ -780,6 +787,25 @@ function encode(text) {
780
787
  function countTokens(text) {
781
788
  return encode(text).length;
782
789
  }
790
+ function estimateConversationTokens(messages) {
791
+ let total = 0;
792
+ for (const m of messages) {
793
+ if (typeof m.content === "string" && m.content) {
794
+ total += countTokens(m.content);
795
+ }
796
+ if (m.tool_calls && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
797
+ total += countTokens(JSON.stringify(m.tool_calls));
798
+ }
799
+ }
800
+ return total;
801
+ }
802
+ function estimateRequestTokens(messages, toolSpecs) {
803
+ let total = estimateConversationTokens(messages);
804
+ if (toolSpecs && toolSpecs.length > 0) {
805
+ total += countTokens(JSON.stringify(toolSpecs));
806
+ }
807
+ return total;
808
+ }
783
809
 
784
810
  // src/repair/flatten.ts
785
811
  function analyzeSchema(schema) {
@@ -1467,7 +1493,7 @@ function signature2(call) {
1467
1493
  import {
1468
1494
  appendFileSync,
1469
1495
  chmodSync,
1470
- existsSync as existsSync2,
1496
+ existsSync as existsSync3,
1471
1497
  mkdirSync,
1472
1498
  readFileSync as readFileSync3,
1473
1499
  readdirSync,
@@ -1489,7 +1515,7 @@ function sanitizeName(name) {
1489
1515
  }
1490
1516
  function loadSessionMessages(name) {
1491
1517
  const path = sessionPath(name);
1492
- if (!existsSync2(path)) return [];
1518
+ if (!existsSync3(path)) return [];
1493
1519
  try {
1494
1520
  const raw = readFileSync3(path, "utf8");
1495
1521
  const out = [];
@@ -1519,7 +1545,7 @@ function appendSessionMessage(name, message) {
1519
1545
  }
1520
1546
  function listSessions() {
1521
1547
  const dir = sessionsDir();
1522
- if (!existsSync2(dir)) return [];
1548
+ if (!existsSync3(dir)) return [];
1523
1549
  try {
1524
1550
  const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
1525
1551
  return files.map((file) => {
@@ -1715,9 +1741,9 @@ var CacheFirstLoop = class {
1715
1741
  this.sessionName = opts.session ?? null;
1716
1742
  if (this.sessionName) {
1717
1743
  const prior = loadSessionMessages(this.sessionName);
1718
- const { messages, healedCount, healedFrom } = healLoadedMessages(
1744
+ const { messages, healedCount, tokensSaved } = healLoadedMessagesByTokens(
1719
1745
  prior,
1720
- DEFAULT_MAX_RESULT_CHARS
1746
+ DEFAULT_MAX_RESULT_TOKENS
1721
1747
  );
1722
1748
  for (const msg of messages) this.log.append(msg);
1723
1749
  this.resumedMessageCount = messages.length;
@@ -1727,7 +1753,7 @@ var CacheFirstLoop = class {
1727
1753
  } catch {
1728
1754
  }
1729
1755
  process.stderr.write(
1730
- `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${healedFrom > 0 ? ` (was ${healedFrom.toLocaleString()} chars oversized)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
1756
+ `\u25B8 session "${this.sessionName}": healed ${healedCount} entr${healedCount === 1 ? "y" : "ies"}${tokensSaved > 0 ? ` (shrunk ${tokensSaved.toLocaleString()} tokens of oversized tool output)` : " (dropped dangling tool_calls tail)"}. Rewrote session file.
1731
1757
  `
1732
1758
  );
1733
1759
  }
@@ -1923,7 +1949,32 @@ var CacheFirstLoop = class {
1923
1949
  content: `${iter}/${this.maxToolIters} tool calls used \u2014 approaching budget. Press Esc to force a summary now.`
1924
1950
  };
1925
1951
  }
1926
- const messages = this.buildMessages(pendingUser);
1952
+ let messages = this.buildMessages(pendingUser);
1953
+ {
1954
+ const ctxMax2 = DEEPSEEK_CONTEXT_TOKENS[this.model] ?? DEFAULT_CONTEXT_TOKENS;
1955
+ const estimate = estimateRequestTokens(messages, this.prefix.toolSpecs);
1956
+ if (estimate / ctxMax2 > 0.95) {
1957
+ const result = this.compact(1e3);
1958
+ if (result.healedCount > 0) {
1959
+ yield {
1960
+ turn: this._turn,
1961
+ role: "warning",
1962
+ content: `preflight: request ~${estimate.toLocaleString()}/${ctxMax2.toLocaleString()} tokens (${Math.round(
1963
+ estimate / ctxMax2 * 100
1964
+ )}%) \u2014 pre-compacted ${result.healedCount} tool result(s), saved ${result.tokensSaved.toLocaleString()} tokens. Sending.`
1965
+ };
1966
+ messages = this.buildMessages(pendingUser);
1967
+ } else {
1968
+ yield {
1969
+ turn: this._turn,
1970
+ role: "warning",
1971
+ content: `preflight: request ~${estimate.toLocaleString()}/${ctxMax2.toLocaleString()} tokens (${Math.round(
1972
+ estimate / ctxMax2 * 100
1973
+ )}%) and nothing to auto-compact \u2014 DeepSeek will likely 400. Run /forget or /clear to start fresh.`
1974
+ };
1975
+ }
1976
+ }
1977
+ }
1927
1978
  let assistantContent = "";
1928
1979
  let reasoningContent = "";
1929
1980
  let toolCalls = [];
@@ -1971,8 +2022,8 @@ var CacheFirstLoop = class {
1971
2022
  }
1972
2023
  );
1973
2024
  for (let k = 0; k < budget; k++) {
1974
- const sample = queue.shift() ?? await new Promise((resolve7) => {
1975
- waiter = resolve7;
2025
+ const sample = queue.shift() ?? await new Promise((resolve8) => {
2026
+ waiter = resolve8;
1976
2027
  });
1977
2028
  yield {
1978
2029
  turn: this._turn,
@@ -2371,15 +2422,12 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
2371
2422
  });
2372
2423
  return { messages: out, healedCount, tokensSaved, charsSaved };
2373
2424
  }
2374
- function healLoadedMessages(messages, maxChars) {
2375
- const shrunk = shrinkOversizedToolResults(messages, maxChars);
2376
- let healedCount = shrunk.healedCount;
2425
+ function fixToolCallPairing(messages) {
2377
2426
  const out = [];
2378
- const openCallIds = /* @__PURE__ */ new Set();
2379
2427
  let droppedAssistantCalls = 0;
2380
2428
  let droppedStrayTools = 0;
2381
- for (let i = 0; i < shrunk.messages.length; i++) {
2382
- const msg = shrunk.messages[i];
2429
+ for (let i = 0; i < messages.length; i++) {
2430
+ const msg = messages[i];
2383
2431
  if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
2384
2432
  const needed = /* @__PURE__ */ new Set();
2385
2433
  for (const call of msg.tool_calls) {
@@ -2387,8 +2435,8 @@ function healLoadedMessages(messages, maxChars) {
2387
2435
  }
2388
2436
  const candidates = [];
2389
2437
  let j = i + 1;
2390
- while (j < shrunk.messages.length && needed.size > 0) {
2391
- const nxt = shrunk.messages[j];
2438
+ while (j < messages.length && needed.size > 0) {
2439
+ const nxt = messages[j];
2392
2440
  if (nxt.role !== "tool") break;
2393
2441
  const id = nxt.tool_call_id ?? "";
2394
2442
  if (!needed.has(id)) break;
@@ -2413,8 +2461,24 @@ function healLoadedMessages(messages, maxChars) {
2413
2461
  }
2414
2462
  out.push(msg);
2415
2463
  }
2416
- healedCount += droppedAssistantCalls + droppedStrayTools;
2417
- return { messages: out, healedCount, healedFrom: shrunk.healedFrom };
2464
+ return { messages: out, droppedAssistantCalls, droppedStrayTools };
2465
+ }
2466
+ function healLoadedMessages(messages, maxChars) {
2467
+ const shrunk = shrinkOversizedToolResults(messages, maxChars);
2468
+ const paired = fixToolCallPairing(shrunk.messages);
2469
+ const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
2470
+ return { messages: paired.messages, healedCount, healedFrom: shrunk.healedFrom };
2471
+ }
2472
+ function healLoadedMessagesByTokens(messages, maxTokens) {
2473
+ const shrunk = shrinkOversizedToolResultsByTokens(messages, maxTokens);
2474
+ const paired = fixToolCallPairing(shrunk.messages);
2475
+ const healedCount = shrunk.healedCount + paired.droppedAssistantCalls + paired.droppedStrayTools;
2476
+ return {
2477
+ messages: paired.messages,
2478
+ healedCount,
2479
+ tokensSaved: shrunk.tokensSaved,
2480
+ charsSaved: shrunk.charsSaved
2481
+ };
2418
2482
  }
2419
2483
  function formatLoopError(err) {
2420
2484
  const msg = err.message ?? "";
@@ -2426,17 +2490,104 @@ function formatLoopError(err) {
2426
2490
  return msg;
2427
2491
  }
2428
2492
 
2493
+ // src/at-mentions.ts
2494
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
2495
+ import { isAbsolute, relative, resolve } from "path";
2496
+ var DEFAULT_AT_MENTION_MAX_BYTES = 64 * 1024;
2497
+ var AT_MENTION_PATTERN = /(?<=^|\s)@([a-zA-Z0-9_./\\-]+)/g;
2498
+ function expandAtMentions(text, rootDir, opts = {}) {
2499
+ const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
2500
+ const fs2 = opts.fs ?? defaultFs;
2501
+ const root = resolve(rootDir);
2502
+ const seen = /* @__PURE__ */ new Map();
2503
+ const expansions = [];
2504
+ for (const match of text.matchAll(AT_MENTION_PATTERN)) {
2505
+ const rawPath = match[1] ?? "";
2506
+ const cleaned = rawPath.replace(/\.+$/, "");
2507
+ if (!cleaned) continue;
2508
+ const token = `@${cleaned}`;
2509
+ if (seen.has(token)) continue;
2510
+ const expansion = resolveMention(cleaned, root, maxBytes, fs2);
2511
+ seen.set(token, expansion);
2512
+ expansions.push(expansion);
2513
+ }
2514
+ if (expansions.length === 0) return { text, expansions };
2515
+ const blocks = [];
2516
+ for (const ex of expansions) {
2517
+ if (ex.ok) {
2518
+ const content = readSafe(root, ex.path, fs2);
2519
+ blocks.push(`<file path="${ex.path}">
2520
+ ${content}
2521
+ </file>`);
2522
+ } else {
2523
+ blocks.push(`<file path="${ex.path}" skipped="${ex.skip}" />`);
2524
+ }
2525
+ }
2526
+ const augmented = `${text}
2527
+
2528
+ [Referenced files]
2529
+ ${blocks.join("\n\n")}`;
2530
+ return { text: augmented, expansions };
2531
+ }
2532
+ function resolveMention(rawPath, root, maxBytes, fs2) {
2533
+ if (isAbsolute(rawPath)) {
2534
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
2535
+ }
2536
+ const resolved = resolve(root, rawPath);
2537
+ const rel = relative(root, resolved);
2538
+ if (rel.startsWith("..") || isAbsolute(rel)) {
2539
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
2540
+ }
2541
+ if (!fs2.exists(resolved)) {
2542
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "missing" };
2543
+ }
2544
+ if (!fs2.isFile(resolved)) {
2545
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
2546
+ }
2547
+ const size = fs2.size(resolved);
2548
+ if (size > maxBytes) {
2549
+ return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "too-large", bytes: size };
2550
+ }
2551
+ return { token: `@${rawPath}`, path: rawPath, ok: true, bytes: size };
2552
+ }
2553
+ function readSafe(root, rawPath, fs2) {
2554
+ const resolved = resolve(root, rawPath);
2555
+ try {
2556
+ return fs2.read(resolved);
2557
+ } catch {
2558
+ return "(read failed)";
2559
+ }
2560
+ }
2561
+ var defaultFs = {
2562
+ exists: (p) => existsSync4(p),
2563
+ isFile: (p) => {
2564
+ try {
2565
+ return statSync2(p).isFile();
2566
+ } catch {
2567
+ return false;
2568
+ }
2569
+ },
2570
+ size: (p) => {
2571
+ try {
2572
+ return statSync2(p).size;
2573
+ } catch {
2574
+ return 0;
2575
+ }
2576
+ },
2577
+ read: (p) => readFileSync4(p, "utf8")
2578
+ };
2579
+
2429
2580
  // src/project-memory.ts
2430
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
2581
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2431
2582
  import { join as join4 } from "path";
2432
2583
  var PROJECT_MEMORY_FILE = "REASONIX.md";
2433
2584
  var PROJECT_MEMORY_MAX_CHARS = 8e3;
2434
2585
  function readProjectMemory(rootDir) {
2435
2586
  const path = join4(rootDir, PROJECT_MEMORY_FILE);
2436
- if (!existsSync3(path)) return null;
2587
+ if (!existsSync5(path)) return null;
2437
2588
  let raw;
2438
2589
  try {
2439
- raw = readFileSync4(path, "utf8");
2590
+ raw = readFileSync5(path, "utf8");
2440
2591
  } catch {
2441
2592
  return null;
2442
2593
  }
@@ -2472,20 +2623,20 @@ ${mem.content}
2472
2623
  // src/user-memory.ts
2473
2624
  import { createHash as createHash2 } from "crypto";
2474
2625
  import {
2475
- existsSync as existsSync5,
2626
+ existsSync as existsSync7,
2476
2627
  mkdirSync as mkdirSync2,
2477
- readFileSync as readFileSync6,
2628
+ readFileSync as readFileSync7,
2478
2629
  readdirSync as readdirSync3,
2479
2630
  unlinkSync as unlinkSync2,
2480
2631
  writeFileSync as writeFileSync2
2481
2632
  } from "fs";
2482
2633
  import { homedir as homedir4 } from "os";
2483
- import { join as join6, resolve as resolve2 } from "path";
2634
+ import { join as join6, resolve as resolve3 } from "path";
2484
2635
 
2485
2636
  // src/skills.ts
2486
- import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2637
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
2487
2638
  import { homedir as homedir3 } from "os";
2488
- import { join as join5, resolve } from "path";
2639
+ import { join as join5, resolve as resolve2 } from "path";
2489
2640
  var SKILLS_DIRNAME = "skills";
2490
2641
  var SKILL_FILE = "SKILL.md";
2491
2642
  var SKILLS_INDEX_MAX_CHARS = 4e3;
@@ -2516,7 +2667,7 @@ var SkillStore = class {
2516
2667
  disableBuiltins;
2517
2668
  constructor(opts = {}) {
2518
2669
  this.homeDir = opts.homeDir ?? homedir3();
2519
- this.projectRoot = opts.projectRoot ? resolve(opts.projectRoot) : void 0;
2670
+ this.projectRoot = opts.projectRoot ? resolve2(opts.projectRoot) : void 0;
2520
2671
  this.disableBuiltins = opts.disableBuiltins === true;
2521
2672
  }
2522
2673
  /** True iff this store was configured with a project root. */
@@ -2547,7 +2698,7 @@ var SkillStore = class {
2547
2698
  list() {
2548
2699
  const byName = /* @__PURE__ */ new Map();
2549
2700
  for (const { dir, scope } of this.roots()) {
2550
- if (!existsSync4(dir)) continue;
2701
+ if (!existsSync6(dir)) continue;
2551
2702
  let entries;
2552
2703
  try {
2553
2704
  entries = readdirSync2(dir, { withFileTypes: true });
@@ -2571,13 +2722,13 @@ var SkillStore = class {
2571
2722
  read(name) {
2572
2723
  if (!isValidSkillName(name)) return null;
2573
2724
  for (const { dir, scope } of this.roots()) {
2574
- if (!existsSync4(dir)) continue;
2725
+ if (!existsSync6(dir)) continue;
2575
2726
  const dirCandidate = join5(dir, name, SKILL_FILE);
2576
- if (existsSync4(dirCandidate) && statSync2(dirCandidate).isFile()) {
2727
+ if (existsSync6(dirCandidate) && statSync3(dirCandidate).isFile()) {
2577
2728
  return this.parse(dirCandidate, name, scope);
2578
2729
  }
2579
2730
  const flatCandidate = join5(dir, `${name}.md`);
2580
- if (existsSync4(flatCandidate) && statSync2(flatCandidate).isFile()) {
2731
+ if (existsSync6(flatCandidate) && statSync3(flatCandidate).isFile()) {
2581
2732
  return this.parse(flatCandidate, name, scope);
2582
2733
  }
2583
2734
  }
@@ -2592,7 +2743,7 @@ var SkillStore = class {
2592
2743
  if (entry.isDirectory()) {
2593
2744
  if (!isValidSkillName(entry.name)) return null;
2594
2745
  const file = join5(dir, entry.name, SKILL_FILE);
2595
- if (!existsSync4(file)) return null;
2746
+ if (!existsSync6(file)) return null;
2596
2747
  return this.parse(file, entry.name, scope);
2597
2748
  }
2598
2749
  if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -2605,7 +2756,7 @@ var SkillStore = class {
2605
2756
  parse(path, stem, scope) {
2606
2757
  let raw;
2607
2758
  try {
2608
- raw = readFileSync5(path, "utf8");
2759
+ raw = readFileSync6(path, "utf8");
2609
2760
  } catch {
2610
2761
  return null;
2611
2762
  }
@@ -2732,7 +2883,7 @@ function sanitizeMemoryName(raw) {
2732
2883
  return trimmed;
2733
2884
  }
2734
2885
  function projectHash(rootDir) {
2735
- const abs = resolve2(rootDir);
2886
+ const abs = resolve3(rootDir);
2736
2887
  return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
2737
2888
  }
2738
2889
  function scopeDir(opts) {
@@ -2745,7 +2896,7 @@ function scopeDir(opts) {
2745
2896
  return join6(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
2746
2897
  }
2747
2898
  function ensureDir(p) {
2748
- if (!existsSync5(p)) mkdirSync2(p, { recursive: true });
2899
+ if (!existsSync7(p)) mkdirSync2(p, { recursive: true });
2749
2900
  }
2750
2901
  function parseFrontmatter2(raw) {
2751
2902
  const lines = raw.split(/\r?\n/);
@@ -2791,7 +2942,7 @@ var MemoryStore = class {
2791
2942
  projectRoot;
2792
2943
  constructor(opts = {}) {
2793
2944
  this.homeDir = opts.homeDir ?? join6(homedir4(), ".reasonix");
2794
- this.projectRoot = opts.projectRoot ? resolve2(opts.projectRoot) : void 0;
2945
+ this.projectRoot = opts.projectRoot ? resolve3(opts.projectRoot) : void 0;
2795
2946
  }
2796
2947
  /** Directory this store writes `scope` files into, creating it if needed. */
2797
2948
  dir(scope) {
@@ -2817,10 +2968,10 @@ var MemoryStore = class {
2817
2968
  scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
2818
2969
  MEMORY_INDEX_FILE
2819
2970
  );
2820
- if (!existsSync5(file)) return null;
2971
+ if (!existsSync7(file)) return null;
2821
2972
  let raw;
2822
2973
  try {
2823
- raw = readFileSync6(file, "utf8");
2974
+ raw = readFileSync7(file, "utf8");
2824
2975
  } catch {
2825
2976
  return null;
2826
2977
  }
@@ -2835,10 +2986,10 @@ var MemoryStore = class {
2835
2986
  /** Read one memory file's body (frontmatter stripped). Throws if missing. */
2836
2987
  read(scope, name) {
2837
2988
  const file = this.pathFor(scope, name);
2838
- if (!existsSync5(file)) {
2989
+ if (!existsSync7(file)) {
2839
2990
  throw new Error(`memory not found: scope=${scope} name=${name}`);
2840
2991
  }
2841
- const raw = readFileSync6(file, "utf8");
2992
+ const raw = readFileSync7(file, "utf8");
2842
2993
  const { data, body } = parseFrontmatter2(raw);
2843
2994
  return {
2844
2995
  name: data.name ?? name,
@@ -2859,7 +3010,7 @@ var MemoryStore = class {
2859
3010
  const scopes = this.projectRoot ? ["global", "project"] : ["global"];
2860
3011
  for (const scope of scopes) {
2861
3012
  const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
2862
- if (!existsSync5(dir)) continue;
3013
+ if (!existsSync7(dir)) continue;
2863
3014
  let entries;
2864
3015
  try {
2865
3016
  entries = readdirSync3(dir);
@@ -2913,7 +3064,7 @@ var MemoryStore = class {
2913
3064
  throw new Error("cannot delete project-scoped memory: no projectRoot configured");
2914
3065
  }
2915
3066
  const file = this.pathFor(scope, rawName);
2916
- if (!existsSync5(file)) return false;
3067
+ if (!existsSync7(file)) return false;
2917
3068
  unlinkSync2(file);
2918
3069
  this.regenerateIndex(scope);
2919
3070
  return true;
@@ -2926,7 +3077,7 @@ var MemoryStore = class {
2926
3077
  */
2927
3078
  regenerateIndex(scope) {
2928
3079
  const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
2929
- if (!existsSync5(dir)) return;
3080
+ if (!existsSync7(dir)) return;
2930
3081
  let files;
2931
3082
  try {
2932
3083
  files = readdirSync3(dir);
@@ -2936,7 +3087,7 @@ var MemoryStore = class {
2936
3087
  const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
2937
3088
  const indexPath = join6(dir, MEMORY_INDEX_FILE);
2938
3089
  if (mdFiles.length === 0) {
2939
- if (existsSync5(indexPath)) unlinkSync2(indexPath);
3090
+ if (existsSync7(indexPath)) unlinkSync2(indexPath);
2940
3091
  return;
2941
3092
  }
2942
3093
  const lines = [];
@@ -3870,7 +4021,7 @@ function forkRegistryExcluding(parent, exclude) {
3870
4021
 
3871
4022
  // src/tools/shell.ts
3872
4023
  import { spawn as spawn2 } from "child_process";
3873
- import { existsSync as existsSync6, statSync as statSync3 } from "fs";
4024
+ import { existsSync as existsSync8, statSync as statSync4 } from "fs";
3874
4025
  import * as pathMod2 from "path";
3875
4026
  var DEFAULT_TIMEOUT_SEC = 60;
3876
4027
  var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
@@ -4040,7 +4191,7 @@ async function runCommand(cmd, opts) {
4040
4191
  };
4041
4192
  const { bin, args, spawnOverrides } = prepareSpawn(argv);
4042
4193
  const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
4043
- return await new Promise((resolve7, reject) => {
4194
+ return await new Promise((resolve8, reject) => {
4044
4195
  let child;
4045
4196
  try {
4046
4197
  child = spawn2(bin, args, effectiveSpawnOpts);
@@ -4073,7 +4224,7 @@ async function runCommand(cmd, opts) {
4073
4224
  const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
4074
4225
 
4075
4226
  [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
4076
- resolve7({ exitCode: code, output, timedOut });
4227
+ resolve8({ exitCode: code, output, timedOut });
4077
4228
  });
4078
4229
  });
4079
4230
  }
@@ -4098,7 +4249,7 @@ function resolveExecutable(cmd, opts = {}) {
4098
4249
  }
4099
4250
  function defaultIsFile(full) {
4100
4251
  try {
4101
- return existsSync6(full) && statSync3(full).isFile();
4252
+ return existsSync8(full) && statSync4(full).isFile();
4102
4253
  } catch {
4103
4254
  return false;
4104
4255
  }
@@ -4425,12 +4576,12 @@ ${i + 1}. ${r.title}`);
4425
4576
  }
4426
4577
 
4427
4578
  // src/env.ts
4428
- import { readFileSync as readFileSync7 } from "fs";
4429
- import { resolve as resolve5 } from "path";
4579
+ import { readFileSync as readFileSync8 } from "fs";
4580
+ import { resolve as resolve6 } from "path";
4430
4581
  function loadDotenv(path = ".env") {
4431
4582
  let raw;
4432
4583
  try {
4433
- raw = readFileSync7(resolve5(process.cwd(), path), "utf8");
4584
+ raw = readFileSync8(resolve6(process.cwd(), path), "utf8");
4434
4585
  } catch {
4435
4586
  return;
4436
4587
  }
@@ -4449,7 +4600,7 @@ function loadDotenv(path = ".env") {
4449
4600
  }
4450
4601
 
4451
4602
  // src/transcript.ts
4452
- import { createWriteStream, readFileSync as readFileSync8 } from "fs";
4603
+ import { createWriteStream, readFileSync as readFileSync9 } from "fs";
4453
4604
  function recordFromLoopEvent(ev, extra) {
4454
4605
  const rec = {
4455
4606
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4500,7 +4651,7 @@ function openTranscriptFile(path, meta) {
4500
4651
  return stream;
4501
4652
  }
4502
4653
  function readTranscript(path) {
4503
- const raw = readFileSync8(path, "utf8");
4654
+ const raw = readFileSync9(path, "utf8");
4504
4655
  return parseTranscript(raw);
4505
4656
  }
4506
4657
  function isPlanStateEmptyShape(s) {
@@ -5112,7 +5263,7 @@ var McpClient = class {
5112
5263
  const id = this.nextId++;
5113
5264
  const frame = { jsonrpc: "2.0", id, method, params };
5114
5265
  let abortHandler = null;
5115
- const promise = new Promise((resolve7, reject) => {
5266
+ const promise = new Promise((resolve8, reject) => {
5116
5267
  const timeout = setTimeout(() => {
5117
5268
  this.pending.delete(id);
5118
5269
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -5121,7 +5272,7 @@ var McpClient = class {
5121
5272
  );
5122
5273
  }, this.requestTimeoutMs);
5123
5274
  this.pending.set(id, {
5124
- resolve: resolve7,
5275
+ resolve: resolve8,
5125
5276
  reject,
5126
5277
  timeout
5127
5278
  });
@@ -5244,12 +5395,12 @@ var StdioTransport = class {
5244
5395
  }
5245
5396
  async send(message) {
5246
5397
  if (this.closed) throw new Error("MCP transport is closed");
5247
- return new Promise((resolve7, reject) => {
5398
+ return new Promise((resolve8, reject) => {
5248
5399
  const line = `${JSON.stringify(message)}
5249
5400
  `;
5250
5401
  this.child.stdin.write(line, "utf8", (err) => {
5251
5402
  if (err) reject(err);
5252
- else resolve7();
5403
+ else resolve8();
5253
5404
  });
5254
5405
  });
5255
5406
  }
@@ -5260,8 +5411,8 @@ var StdioTransport = class {
5260
5411
  continue;
5261
5412
  }
5262
5413
  if (this.closed) return;
5263
- const next = await new Promise((resolve7) => {
5264
- this.waiters.push(resolve7);
5414
+ const next = await new Promise((resolve8) => {
5415
+ this.waiters.push(resolve8);
5265
5416
  });
5266
5417
  if (next === null) return;
5267
5418
  yield next;
@@ -5327,8 +5478,8 @@ var SseTransport = class {
5327
5478
  constructor(opts) {
5328
5479
  this.url = opts.url;
5329
5480
  this.headers = opts.headers ?? {};
5330
- this.endpointReady = new Promise((resolve7, reject) => {
5331
- this.resolveEndpoint = resolve7;
5481
+ this.endpointReady = new Promise((resolve8, reject) => {
5482
+ this.resolveEndpoint = resolve8;
5332
5483
  this.rejectEndpoint = reject;
5333
5484
  });
5334
5485
  this.endpointReady.catch(() => void 0);
@@ -5355,8 +5506,8 @@ var SseTransport = class {
5355
5506
  continue;
5356
5507
  }
5357
5508
  if (this.closed) return;
5358
- const next = await new Promise((resolve7) => {
5359
- this.waiters.push(resolve7);
5509
+ const next = await new Promise((resolve8) => {
5510
+ this.waiters.push(resolve8);
5360
5511
  });
5361
5512
  if (next === null) return;
5362
5513
  yield next;
@@ -5555,8 +5706,8 @@ async function trySection(load) {
5555
5706
  }
5556
5707
 
5557
5708
  // src/code/edit-blocks.ts
5558
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
5559
- import { dirname as dirname4, resolve as resolve6 } from "path";
5709
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
5710
+ import { dirname as dirname4, resolve as resolve7 } from "path";
5560
5711
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
5561
5712
  function parseEditBlocks(text) {
5562
5713
  const out = [];
@@ -5574,8 +5725,8 @@ function parseEditBlocks(text) {
5574
5725
  return out;
5575
5726
  }
5576
5727
  function applyEditBlock(block, rootDir) {
5577
- const absRoot = resolve6(rootDir);
5578
- const absTarget = resolve6(absRoot, block.path);
5728
+ const absRoot = resolve7(rootDir);
5729
+ const absTarget = resolve7(absRoot, block.path);
5579
5730
  if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
5580
5731
  return {
5581
5732
  path: block.path,
@@ -5584,7 +5735,7 @@ function applyEditBlock(block, rootDir) {
5584
5735
  };
5585
5736
  }
5586
5737
  const searchEmpty = block.search.length === 0;
5587
- const exists = existsSync7(absTarget);
5738
+ const exists = existsSync9(absTarget);
5588
5739
  try {
5589
5740
  if (!exists) {
5590
5741
  if (!searchEmpty) {
@@ -5598,7 +5749,7 @@ function applyEditBlock(block, rootDir) {
5598
5749
  writeFileSync3(absTarget, block.replace, "utf8");
5599
5750
  return { path: block.path, status: "created" };
5600
5751
  }
5601
- const content = readFileSync9(absTarget, "utf8");
5752
+ const content = readFileSync10(absTarget, "utf8");
5602
5753
  if (searchEmpty) {
5603
5754
  return {
5604
5755
  path: block.path,
@@ -5625,19 +5776,19 @@ function applyEditBlocks(blocks, rootDir) {
5625
5776
  return blocks.map((b) => applyEditBlock(b, rootDir));
5626
5777
  }
5627
5778
  function snapshotBeforeEdits(blocks, rootDir) {
5628
- const absRoot = resolve6(rootDir);
5779
+ const absRoot = resolve7(rootDir);
5629
5780
  const seen = /* @__PURE__ */ new Set();
5630
5781
  const snapshots = [];
5631
5782
  for (const b of blocks) {
5632
5783
  if (seen.has(b.path)) continue;
5633
5784
  seen.add(b.path);
5634
- const abs = resolve6(absRoot, b.path);
5635
- if (!existsSync7(abs)) {
5785
+ const abs = resolve7(absRoot, b.path);
5786
+ if (!existsSync9(abs)) {
5636
5787
  snapshots.push({ path: b.path, prevContent: null });
5637
5788
  continue;
5638
5789
  }
5639
5790
  try {
5640
- snapshots.push({ path: b.path, prevContent: readFileSync9(abs, "utf8") });
5791
+ snapshots.push({ path: b.path, prevContent: readFileSync10(abs, "utf8") });
5641
5792
  } catch {
5642
5793
  snapshots.push({ path: b.path, prevContent: null });
5643
5794
  }
@@ -5645,9 +5796,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
5645
5796
  return snapshots;
5646
5797
  }
5647
5798
  function restoreSnapshots(snapshots, rootDir) {
5648
- const absRoot = resolve6(rootDir);
5799
+ const absRoot = resolve7(rootDir);
5649
5800
  return snapshots.map((snap) => {
5650
- const abs = resolve6(absRoot, snap.path);
5801
+ const abs = resolve7(absRoot, snap.path);
5651
5802
  if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
5652
5803
  return {
5653
5804
  path: snap.path,
@@ -5657,7 +5808,7 @@ function restoreSnapshots(snapshots, rootDir) {
5657
5808
  }
5658
5809
  try {
5659
5810
  if (snap.prevContent === null) {
5660
- if (existsSync7(abs)) unlinkSync3(abs);
5811
+ if (existsSync9(abs)) unlinkSync3(abs);
5661
5812
  return {
5662
5813
  path: snap.path,
5663
5814
  status: "applied",
@@ -5680,7 +5831,7 @@ function sep() {
5680
5831
  }
5681
5832
 
5682
5833
  // src/code/prompt.ts
5683
- import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
5834
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
5684
5835
  import { join as join8 } from "path";
5685
5836
  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.
5686
5837
 
@@ -5803,10 +5954,10 @@ Two different rules depending on which tool:
5803
5954
  function codeSystemPrompt(rootDir) {
5804
5955
  const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
5805
5956
  const gitignorePath = join8(rootDir, ".gitignore");
5806
- if (!existsSync8(gitignorePath)) return withMemory;
5957
+ if (!existsSync10(gitignorePath)) return withMemory;
5807
5958
  let content;
5808
5959
  try {
5809
- content = readFileSync10(gitignorePath, "utf8");
5960
+ content = readFileSync11(gitignorePath, "utf8");
5810
5961
  } catch {
5811
5962
  return withMemory;
5812
5963
  }
@@ -5826,7 +5977,7 @@ ${truncated}
5826
5977
  }
5827
5978
 
5828
5979
  // src/config.ts
5829
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
5980
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "fs";
5830
5981
  import { homedir as homedir5 } from "os";
5831
5982
  import { dirname as dirname5, join as join9 } from "path";
5832
5983
  function defaultConfigPath() {
@@ -5834,7 +5985,7 @@ function defaultConfigPath() {
5834
5985
  }
5835
5986
  function readConfig(path = defaultConfigPath()) {
5836
5987
  try {
5837
- const raw = readFileSync11(path, "utf8");
5988
+ const raw = readFileSync12(path, "utf8");
5838
5989
  const parsed = JSON.parse(raw);
5839
5990
  if (parsed && typeof parsed === "object") return parsed;
5840
5991
  } catch {
@@ -5869,7 +6020,7 @@ function redactKey(key) {
5869
6020
  }
5870
6021
 
5871
6022
  // src/version.ts
5872
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
6023
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
5873
6024
  import { homedir as homedir6 } from "os";
5874
6025
  import { dirname as dirname6, join as join10 } from "path";
5875
6026
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -5881,8 +6032,8 @@ function readPackageVersion() {
5881
6032
  let dir = dirname6(fileURLToPath2(import.meta.url));
5882
6033
  for (let i = 0; i < 6; i++) {
5883
6034
  const p = join10(dir, "package.json");
5884
- if (existsSync9(p)) {
5885
- const pkg = JSON.parse(readFileSync12(p, "utf8"));
6035
+ if (existsSync11(p)) {
6036
+ const pkg = JSON.parse(readFileSync13(p, "utf8"));
5886
6037
  if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
5887
6038
  return pkg.version;
5888
6039
  }
@@ -5901,7 +6052,7 @@ function cachePath(homeDirOverride) {
5901
6052
  }
5902
6053
  function readCache(homeDirOverride) {
5903
6054
  try {
5904
- const raw = readFileSync12(cachePath(homeDirOverride), "utf8");
6055
+ const raw = readFileSync13(cachePath(homeDirOverride), "utf8");
5905
6056
  const parsed = JSON.parse(raw);
5906
6057
  if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
5907
6058
  return parsed;
@@ -5970,7 +6121,7 @@ function isNpxInstall() {
5970
6121
  }
5971
6122
 
5972
6123
  // src/usage.ts
5973
- import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
6124
+ import { appendFileSync as appendFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
5974
6125
  import { homedir as homedir7 } from "os";
5975
6126
  import { dirname as dirname7, join as join11 } from "path";
5976
6127
  function defaultUsageLogPath(homeDirOverride) {
@@ -5998,10 +6149,10 @@ function appendUsage(input) {
5998
6149
  return record;
5999
6150
  }
6000
6151
  function readUsageLog(path = defaultUsageLogPath()) {
6001
- if (!existsSync10(path)) return [];
6152
+ if (!existsSync12(path)) return [];
6002
6153
  let raw;
6003
6154
  try {
6004
- raw = readFileSync13(path, "utf8");
6155
+ raw = readFileSync14(path, "utf8");
6005
6156
  } catch {
6006
6157
  return [];
6007
6158
  }
@@ -6083,9 +6234,9 @@ function aggregateUsage(records, opts = {}) {
6083
6234
  };
6084
6235
  }
6085
6236
  function formatLogSize(path = defaultUsageLogPath()) {
6086
- if (!existsSync10(path)) return "";
6237
+ if (!existsSync12(path)) return "";
6087
6238
  try {
6088
- const s = statSync4(path);
6239
+ const s = statSync5(path);
6089
6240
  const bytes = s.size;
6090
6241
  if (bytes < 1024) return `${bytes} B`;
6091
6242
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -6095,9 +6246,11 @@ function formatLogSize(path = defaultUsageLogPath()) {
6095
6246
  }
6096
6247
  }
6097
6248
  export {
6249
+ AT_MENTION_PATTERN,
6098
6250
  AppendOnlyLog,
6099
6251
  CODE_SYSTEM_PROMPT,
6100
6252
  CacheFirstLoop,
6253
+ DEFAULT_AT_MENTION_MAX_BYTES,
6101
6254
  DEFAULT_MAX_RESULT_CHARS,
6102
6255
  DEFAULT_MAX_RESULT_TOKENS,
6103
6256
  DeepSeekClient,
@@ -6152,7 +6305,9 @@ export {
6152
6305
  detectShellOperator,
6153
6306
  diffTranscripts,
6154
6307
  emptyPlanState,
6308
+ expandAtMentions,
6155
6309
  fetchWithRetry,
6310
+ fixToolCallPairing,
6156
6311
  flattenMcpResult,
6157
6312
  flattenSchema,
6158
6313
  forkRegistryExcluding,
@@ -6165,6 +6320,7 @@ export {
6165
6320
  globalSettingsPath,
6166
6321
  harvest,
6167
6322
  healLoadedMessages,
6323
+ healLoadedMessagesByTokens,
6168
6324
  htmlToText,
6169
6325
  injectPowerShellUtf8,
6170
6326
  inputCostUsd,