reasonix 0.5.0 → 0.5.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
@@ -617,6 +617,170 @@ async function runHooks(opts) {
617
617
  return { event, outcomes, blocked };
618
618
  }
619
619
 
620
+ // src/tokenizer.ts
621
+ import { readFileSync as readFileSync2 } from "fs";
622
+ import { createRequire } from "module";
623
+ import { dirname, join as join2 } from "path";
624
+ import { fileURLToPath } from "url";
625
+ import { gunzipSync } from "zlib";
626
+ function buildByteToChar() {
627
+ const result = new Array(256);
628
+ const bs = [];
629
+ for (let b = 33; b <= 126; b++) bs.push(b);
630
+ for (let b = 161; b <= 172; b++) bs.push(b);
631
+ for (let b = 174; b <= 255; b++) bs.push(b);
632
+ const cs = bs.slice();
633
+ let n = 0;
634
+ for (let b = 0; b < 256; b++) {
635
+ if (!bs.includes(b)) {
636
+ bs.push(b);
637
+ cs.push(256 + n);
638
+ n++;
639
+ }
640
+ }
641
+ for (let i = 0; i < bs.length; i++) {
642
+ result[bs[i]] = String.fromCodePoint(cs[i]);
643
+ }
644
+ return result;
645
+ }
646
+ var cached = null;
647
+ function resolveDataPath() {
648
+ if (process.env.REASONIX_TOKENIZER_PATH) return process.env.REASONIX_TOKENIZER_PATH;
649
+ try {
650
+ const here = dirname(fileURLToPath(import.meta.url));
651
+ return join2(here, "..", "data", "deepseek-tokenizer.json.gz");
652
+ } catch {
653
+ const req = createRequire(import.meta.url);
654
+ return join2(
655
+ dirname(req.resolve("reasonix/package.json")),
656
+ "data",
657
+ "deepseek-tokenizer.json.gz"
658
+ );
659
+ }
660
+ }
661
+ function loadTokenizer() {
662
+ if (cached) return cached;
663
+ const buf = readFileSync2(resolveDataPath());
664
+ const json = gunzipSync(buf).toString("utf8");
665
+ const data = JSON.parse(json);
666
+ const mergeRank = /* @__PURE__ */ new Map();
667
+ for (let i = 0; i < data.model.merges.length; i++) {
668
+ mergeRank.set(data.model.merges[i], i);
669
+ }
670
+ const splitRegexes = [];
671
+ for (const p of data.pre_tokenizer.pretokenizers) {
672
+ if (p.type === "Split") {
673
+ splitRegexes.push(new RegExp(p.pattern.Regex, "gu"));
674
+ }
675
+ }
676
+ const addedMap = /* @__PURE__ */ new Map();
677
+ const addedContents = [];
678
+ for (const t of data.added_tokens) {
679
+ if (!t.special) {
680
+ addedMap.set(t.content, t.id);
681
+ addedContents.push(t.content);
682
+ }
683
+ }
684
+ addedContents.sort((a, b) => b.length - a.length);
685
+ const addedPattern = addedContents.length ? new RegExp(addedContents.map(escapeRegex).join("|"), "g") : null;
686
+ cached = {
687
+ vocab: data.model.vocab,
688
+ mergeRank,
689
+ splitRegexes,
690
+ byteToChar: buildByteToChar(),
691
+ addedPattern,
692
+ addedMap
693
+ };
694
+ return cached;
695
+ }
696
+ function escapeRegex(s) {
697
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
698
+ }
699
+ function applySplit(chunks, re) {
700
+ const out = [];
701
+ for (const chunk of chunks) {
702
+ if (!chunk) continue;
703
+ re.lastIndex = 0;
704
+ let last = 0;
705
+ for (const m of chunk.matchAll(re)) {
706
+ const idx = m.index ?? 0;
707
+ if (idx > last) out.push(chunk.slice(last, idx));
708
+ if (m[0].length > 0) out.push(m[0]);
709
+ last = idx + m[0].length;
710
+ }
711
+ if (last < chunk.length) out.push(chunk.slice(last));
712
+ }
713
+ return out;
714
+ }
715
+ function byteLevelEncode(s, byteToChar) {
716
+ const bytes = new TextEncoder().encode(s);
717
+ let out = "";
718
+ for (let i = 0; i < bytes.length; i++) out += byteToChar[bytes[i]];
719
+ return out;
720
+ }
721
+ function bpeEncode(piece, mergeRank) {
722
+ if (piece.length <= 1) return piece ? [piece] : [];
723
+ let word = Array.from(piece);
724
+ while (true) {
725
+ let bestIdx = -1;
726
+ let bestRank = Number.POSITIVE_INFINITY;
727
+ for (let i = 0; i < word.length - 1; i++) {
728
+ const pair = `${word[i]} ${word[i + 1]}`;
729
+ const rank = mergeRank.get(pair);
730
+ if (rank !== void 0 && rank < bestRank) {
731
+ bestRank = rank;
732
+ bestIdx = i;
733
+ if (rank === 0) break;
734
+ }
735
+ }
736
+ if (bestIdx < 0) break;
737
+ word = [
738
+ ...word.slice(0, bestIdx),
739
+ word[bestIdx] + word[bestIdx + 1],
740
+ ...word.slice(bestIdx + 2)
741
+ ];
742
+ if (word.length === 1) break;
743
+ }
744
+ return word;
745
+ }
746
+ function encode(text) {
747
+ if (!text) return [];
748
+ const t = loadTokenizer();
749
+ const ids = [];
750
+ const process2 = (segment) => {
751
+ if (!segment) return;
752
+ let chunks = [segment];
753
+ for (const re of t.splitRegexes) chunks = applySplit(chunks, re);
754
+ for (const chunk of chunks) {
755
+ if (!chunk) continue;
756
+ const byteLevel = byteLevelEncode(chunk, t.byteToChar);
757
+ const pieces = bpeEncode(byteLevel, t.mergeRank);
758
+ for (const p of pieces) {
759
+ const id = t.vocab[p];
760
+ if (id !== void 0) ids.push(id);
761
+ }
762
+ }
763
+ };
764
+ if (t.addedPattern) {
765
+ t.addedPattern.lastIndex = 0;
766
+ let last = 0;
767
+ for (const m of text.matchAll(t.addedPattern)) {
768
+ const idx = m.index ?? 0;
769
+ if (idx > last) process2(text.slice(last, idx));
770
+ const id = t.addedMap.get(m[0]);
771
+ if (id !== void 0) ids.push(id);
772
+ last = idx + m[0].length;
773
+ }
774
+ if (last < text.length) process2(text.slice(last));
775
+ } else {
776
+ process2(text);
777
+ }
778
+ return ids;
779
+ }
780
+ function countTokens(text) {
781
+ return encode(text).length;
782
+ }
783
+
620
784
  // src/repair/flatten.ts
621
785
  function analyzeSchema(schema) {
622
786
  if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
@@ -767,7 +931,14 @@ var ToolRegistry = class {
767
931
  try {
768
932
  const result = await tool.fn(args, { signal: opts.signal });
769
933
  const str = typeof result === "string" ? result : JSON.stringify(result);
770
- return opts.maxResultChars ? truncateForModel(str, opts.maxResultChars) : str;
934
+ let clipped = str;
935
+ if (opts.maxResultTokens !== void 0) {
936
+ clipped = truncateForModelByTokens(clipped, opts.maxResultTokens);
937
+ }
938
+ if (opts.maxResultChars !== void 0) {
939
+ clipped = truncateForModel(clipped, opts.maxResultChars);
940
+ }
941
+ return clipped;
771
942
  } catch (err) {
772
943
  const e = err;
773
944
  if (typeof e.toToolResult === "function") {
@@ -801,6 +972,7 @@ function hasDotKey(obj) {
801
972
 
802
973
  // src/mcp/registry.ts
803
974
  var DEFAULT_MAX_RESULT_CHARS = 32e3;
975
+ var DEFAULT_MAX_RESULT_TOKENS = 8e3;
804
976
  async function bridgeMcpTools(client, opts = {}) {
805
977
  const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
806
978
  const prefix = opts.namePrefix ?? "";
@@ -857,6 +1029,61 @@ function truncateForModel(s, maxChars) {
857
1029
 
858
1030
  ${tail}`;
859
1031
  }
1032
+ function truncateForModelByTokens(s, maxTokens) {
1033
+ if (maxTokens <= 0) return "";
1034
+ if (s.length <= maxTokens) return s;
1035
+ if (s.length <= maxTokens * 4) {
1036
+ const tokens = countTokens(s);
1037
+ if (tokens <= maxTokens) return s;
1038
+ }
1039
+ const markerOverhead = 48;
1040
+ const contentBudget = Math.max(0, maxTokens - markerOverhead);
1041
+ const tailBudget = Math.min(256, Math.floor(contentBudget * 0.1));
1042
+ const headBudget = Math.max(0, contentBudget - tailBudget);
1043
+ const head = sizePrefixToTokens(s, headBudget);
1044
+ const tail = sizeSuffixToTokens(s, tailBudget);
1045
+ const droppedChars = s.length - head.length - tail.length;
1046
+ const headTokens = head ? countTokens(head) : 0;
1047
+ const tailTokens = tail ? countTokens(tail) : 0;
1048
+ const sampleChars = head.length + tail.length;
1049
+ const sampleTokens = headTokens + tailTokens;
1050
+ const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
1051
+ const estTotalTokens = Math.ceil(s.length * ratio);
1052
+ const droppedTokens = Math.max(0, estTotalTokens - sampleTokens);
1053
+ return `${head}
1054
+
1055
+ [\u2026truncated ~${droppedTokens} tokens (${droppedChars} chars) \u2014 raise BridgeOptions.maxResultTokens, or call the tool with a narrower scope (filter, head, pagination)\u2026]
1056
+
1057
+ ${tail}`;
1058
+ }
1059
+ function sizePrefixToTokens(s, budget) {
1060
+ if (budget <= 0 || s.length === 0) return "";
1061
+ let size = Math.min(s.length, budget * 4);
1062
+ for (let iter = 0; iter < 6; iter++) {
1063
+ if (size <= 0) return "";
1064
+ const slice = s.slice(0, size);
1065
+ const count = countTokens(slice);
1066
+ if (count <= budget) return slice;
1067
+ const next = Math.floor(size * (budget / count) * 0.95);
1068
+ if (next >= size) return s.slice(0, Math.max(0, size - 1));
1069
+ size = next;
1070
+ }
1071
+ return s.slice(0, Math.max(0, size));
1072
+ }
1073
+ function sizeSuffixToTokens(s, budget) {
1074
+ if (budget <= 0 || s.length === 0) return "";
1075
+ let size = Math.min(s.length, budget * 4);
1076
+ for (let iter = 0; iter < 6; iter++) {
1077
+ if (size <= 0) return "";
1078
+ const slice = s.slice(-size);
1079
+ const count = countTokens(slice);
1080
+ if (count <= budget) return slice;
1081
+ const next = Math.floor(size * (budget / count) * 0.95);
1082
+ if (next >= size) return s.slice(-Math.max(0, size - 1));
1083
+ size = next;
1084
+ }
1085
+ return s.slice(-Math.max(0, size));
1086
+ }
860
1087
  function blockToString(block) {
861
1088
  if (block.type === "text") return block.text;
862
1089
  if (block.type === "image") return `[image ${block.mimeType}, ${block.data.length} chars base64]`;
@@ -1242,19 +1469,19 @@ import {
1242
1469
  chmodSync,
1243
1470
  existsSync as existsSync2,
1244
1471
  mkdirSync,
1245
- readFileSync as readFileSync2,
1472
+ readFileSync as readFileSync3,
1246
1473
  readdirSync,
1247
1474
  statSync,
1248
1475
  unlinkSync,
1249
1476
  writeFileSync
1250
1477
  } from "fs";
1251
1478
  import { homedir as homedir2 } from "os";
1252
- import { dirname, join as join2 } from "path";
1479
+ import { dirname as dirname2, join as join3 } from "path";
1253
1480
  function sessionsDir() {
1254
- return join2(homedir2(), ".reasonix", "sessions");
1481
+ return join3(homedir2(), ".reasonix", "sessions");
1255
1482
  }
1256
1483
  function sessionPath(name) {
1257
- return join2(sessionsDir(), `${sanitizeName(name)}.jsonl`);
1484
+ return join3(sessionsDir(), `${sanitizeName(name)}.jsonl`);
1258
1485
  }
1259
1486
  function sanitizeName(name) {
1260
1487
  const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
@@ -1264,7 +1491,7 @@ function loadSessionMessages(name) {
1264
1491
  const path = sessionPath(name);
1265
1492
  if (!existsSync2(path)) return [];
1266
1493
  try {
1267
- const raw = readFileSync2(path, "utf8");
1494
+ const raw = readFileSync3(path, "utf8");
1268
1495
  const out = [];
1269
1496
  for (const line of raw.split(/\r?\n/)) {
1270
1497
  const trimmed = line.trim();
@@ -1282,7 +1509,7 @@ function loadSessionMessages(name) {
1282
1509
  }
1283
1510
  function appendSessionMessage(name, message) {
1284
1511
  const path = sessionPath(name);
1285
- mkdirSync(dirname(path), { recursive: true });
1512
+ mkdirSync(dirname2(path), { recursive: true });
1286
1513
  appendFileSync(path, `${JSON.stringify(message)}
1287
1514
  `, "utf8");
1288
1515
  try {
@@ -1296,7 +1523,7 @@ function listSessions() {
1296
1523
  try {
1297
1524
  const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
1298
1525
  return files.map((file) => {
1299
- const path = join2(dir, file);
1526
+ const path = join3(dir, file);
1300
1527
  const stat = statSync(path);
1301
1528
  const name = file.replace(/\.jsonl$/, "");
1302
1529
  const messageCount = countLines(path);
@@ -1317,7 +1544,7 @@ function deleteSession(name) {
1317
1544
  }
1318
1545
  function rewriteSession(name, messages) {
1319
1546
  const path = sessionPath(name);
1320
- mkdirSync(dirname(path), { recursive: true });
1547
+ mkdirSync(dirname2(path), { recursive: true });
1321
1548
  const body = messages.map((m) => JSON.stringify(m)).join("\n");
1322
1549
  writeFileSync(path, body ? `${body}
1323
1550
  ` : "", "utf8");
@@ -1328,7 +1555,7 @@ function rewriteSession(name, messages) {
1328
1555
  }
1329
1556
  function countLines(path) {
1330
1557
  try {
1331
- const raw = readFileSync2(path, "utf8");
1558
+ const raw = readFileSync3(path, "utf8");
1332
1559
  return raw.split(/\r?\n/).filter((l) => l.trim()).length;
1333
1560
  } catch {
1334
1561
  return 0;
@@ -1975,7 +2202,7 @@ ${reason}`;
1975
2202
  } else {
1976
2203
  result = await this.tools.dispatch(name, args, {
1977
2204
  signal,
1978
- maxResultChars: DEFAULT_MAX_RESULT_CHARS
2205
+ maxResultTokens: DEFAULT_MAX_RESULT_TOKENS
1979
2206
  });
1980
2207
  const postReport = await runHooks({
1981
2208
  hooks: this.hooks,
@@ -2177,16 +2404,16 @@ function formatLoopError(err) {
2177
2404
  }
2178
2405
 
2179
2406
  // src/project-memory.ts
2180
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2181
- import { join as join3 } from "path";
2407
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
2408
+ import { join as join4 } from "path";
2182
2409
  var PROJECT_MEMORY_FILE = "REASONIX.md";
2183
2410
  var PROJECT_MEMORY_MAX_CHARS = 8e3;
2184
2411
  function readProjectMemory(rootDir) {
2185
- const path = join3(rootDir, PROJECT_MEMORY_FILE);
2412
+ const path = join4(rootDir, PROJECT_MEMORY_FILE);
2186
2413
  if (!existsSync3(path)) return null;
2187
2414
  let raw;
2188
2415
  try {
2189
- raw = readFileSync3(path, "utf8");
2416
+ raw = readFileSync4(path, "utf8");
2190
2417
  } catch {
2191
2418
  return null;
2192
2419
  }
@@ -2224,18 +2451,18 @@ import { createHash as createHash2 } from "crypto";
2224
2451
  import {
2225
2452
  existsSync as existsSync5,
2226
2453
  mkdirSync as mkdirSync2,
2227
- readFileSync as readFileSync5,
2454
+ readFileSync as readFileSync6,
2228
2455
  readdirSync as readdirSync3,
2229
2456
  unlinkSync as unlinkSync2,
2230
2457
  writeFileSync as writeFileSync2
2231
2458
  } from "fs";
2232
2459
  import { homedir as homedir4 } from "os";
2233
- import { join as join5, resolve as resolve2 } from "path";
2460
+ import { join as join6, resolve as resolve2 } from "path";
2234
2461
 
2235
2462
  // src/skills.ts
2236
- import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2463
+ import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2237
2464
  import { homedir as homedir3 } from "os";
2238
- import { join as join4, resolve } from "path";
2465
+ import { join as join5, resolve } from "path";
2239
2466
  var SKILLS_DIRNAME = "skills";
2240
2467
  var SKILL_FILE = "SKILL.md";
2241
2468
  var SKILLS_INDEX_MAX_CHARS = 4e3;
@@ -2282,11 +2509,11 @@ var SkillStore = class {
2282
2509
  const out = [];
2283
2510
  if (this.projectRoot) {
2284
2511
  out.push({
2285
- dir: join4(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
2512
+ dir: join5(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
2286
2513
  scope: "project"
2287
2514
  });
2288
2515
  }
2289
- out.push({ dir: join4(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
2516
+ out.push({ dir: join5(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
2290
2517
  return out;
2291
2518
  }
2292
2519
  /**
@@ -2322,11 +2549,11 @@ var SkillStore = class {
2322
2549
  if (!isValidSkillName(name)) return null;
2323
2550
  for (const { dir, scope } of this.roots()) {
2324
2551
  if (!existsSync4(dir)) continue;
2325
- const dirCandidate = join4(dir, name, SKILL_FILE);
2552
+ const dirCandidate = join5(dir, name, SKILL_FILE);
2326
2553
  if (existsSync4(dirCandidate) && statSync2(dirCandidate).isFile()) {
2327
2554
  return this.parse(dirCandidate, name, scope);
2328
2555
  }
2329
- const flatCandidate = join4(dir, `${name}.md`);
2556
+ const flatCandidate = join5(dir, `${name}.md`);
2330
2557
  if (existsSync4(flatCandidate) && statSync2(flatCandidate).isFile()) {
2331
2558
  return this.parse(flatCandidate, name, scope);
2332
2559
  }
@@ -2341,21 +2568,21 @@ var SkillStore = class {
2341
2568
  readEntry(dir, scope, entry) {
2342
2569
  if (entry.isDirectory()) {
2343
2570
  if (!isValidSkillName(entry.name)) return null;
2344
- const file = join4(dir, entry.name, SKILL_FILE);
2571
+ const file = join5(dir, entry.name, SKILL_FILE);
2345
2572
  if (!existsSync4(file)) return null;
2346
2573
  return this.parse(file, entry.name, scope);
2347
2574
  }
2348
2575
  if (entry.isFile() && entry.name.endsWith(".md")) {
2349
2576
  const stem = entry.name.slice(0, -3);
2350
2577
  if (!isValidSkillName(stem)) return null;
2351
- return this.parse(join4(dir, entry.name), stem, scope);
2578
+ return this.parse(join5(dir, entry.name), stem, scope);
2352
2579
  }
2353
2580
  return null;
2354
2581
  }
2355
2582
  parse(path, stem, scope) {
2356
2583
  let raw;
2357
2584
  try {
2358
- raw = readFileSync4(path, "utf8");
2585
+ raw = readFileSync5(path, "utf8");
2359
2586
  } catch {
2360
2587
  return null;
2361
2588
  }
@@ -2487,12 +2714,12 @@ function projectHash(rootDir) {
2487
2714
  }
2488
2715
  function scopeDir(opts) {
2489
2716
  if (opts.scope === "global") {
2490
- return join5(opts.homeDir, USER_MEMORY_DIR, "global");
2717
+ return join6(opts.homeDir, USER_MEMORY_DIR, "global");
2491
2718
  }
2492
2719
  if (!opts.projectRoot) {
2493
2720
  throw new Error("scope=project requires a projectRoot on MemoryStore");
2494
2721
  }
2495
- return join5(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
2722
+ return join6(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
2496
2723
  }
2497
2724
  function ensureDir(p) {
2498
2725
  if (!existsSync5(p)) mkdirSync2(p, { recursive: true });
@@ -2540,7 +2767,7 @@ var MemoryStore = class {
2540
2767
  homeDir;
2541
2768
  projectRoot;
2542
2769
  constructor(opts = {}) {
2543
- this.homeDir = opts.homeDir ?? join5(homedir4(), ".reasonix");
2770
+ this.homeDir = opts.homeDir ?? join6(homedir4(), ".reasonix");
2544
2771
  this.projectRoot = opts.projectRoot ? resolve2(opts.projectRoot) : void 0;
2545
2772
  }
2546
2773
  /** Directory this store writes `scope` files into, creating it if needed. */
@@ -2551,7 +2778,7 @@ var MemoryStore = class {
2551
2778
  }
2552
2779
  /** Absolute path to a memory file (no existence check). */
2553
2780
  pathFor(scope, name) {
2554
- return join5(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
2781
+ return join6(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
2555
2782
  }
2556
2783
  /** True iff this store is configured with a project scope available. */
2557
2784
  hasProjectScope() {
@@ -2563,14 +2790,14 @@ var MemoryStore = class {
2563
2790
  */
2564
2791
  loadIndex(scope) {
2565
2792
  if (scope === "project" && !this.projectRoot) return null;
2566
- const file = join5(
2793
+ const file = join6(
2567
2794
  scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
2568
2795
  MEMORY_INDEX_FILE
2569
2796
  );
2570
2797
  if (!existsSync5(file)) return null;
2571
2798
  let raw;
2572
2799
  try {
2573
- raw = readFileSync5(file, "utf8");
2800
+ raw = readFileSync6(file, "utf8");
2574
2801
  } catch {
2575
2802
  return null;
2576
2803
  }
@@ -2588,7 +2815,7 @@ var MemoryStore = class {
2588
2815
  if (!existsSync5(file)) {
2589
2816
  throw new Error(`memory not found: scope=${scope} name=${name}`);
2590
2817
  }
2591
- const raw = readFileSync5(file, "utf8");
2818
+ const raw = readFileSync6(file, "utf8");
2592
2819
  const { data, body } = parseFrontmatter2(raw);
2593
2820
  return {
2594
2821
  name: data.name ?? name,
@@ -2650,7 +2877,7 @@ var MemoryStore = class {
2650
2877
  createdAt: todayIso()
2651
2878
  };
2652
2879
  const dir = this.dir(input.scope);
2653
- const file = join5(dir, `${name}.md`);
2880
+ const file = join6(dir, `${name}.md`);
2654
2881
  const content = `${formatFrontmatter(entry)}${body}
2655
2882
  `;
2656
2883
  writeFileSync2(file, content, "utf8");
@@ -2684,7 +2911,7 @@ var MemoryStore = class {
2684
2911
  return;
2685
2912
  }
2686
2913
  const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
2687
- const indexPath = join5(dir, MEMORY_INDEX_FILE);
2914
+ const indexPath = join6(dir, MEMORY_INDEX_FILE);
2688
2915
  if (mdFiles.length === 0) {
2689
2916
  if (existsSync5(indexPath)) unlinkSync2(indexPath);
2690
2917
  return;
@@ -3717,6 +3944,50 @@ function tokenizeCommand(cmd) {
3717
3944
  if (cur.length > 0) out.push(cur);
3718
3945
  return out;
3719
3946
  }
3947
+ function detectShellOperator(cmd) {
3948
+ const opPrefix = /^(?:2>&1|&>|\|{1,2}|&{1,2}|2>{1,2}|>{1,2}|<{1,2})/;
3949
+ let cur = "";
3950
+ let curQuoted = false;
3951
+ let quote = null;
3952
+ const check = () => {
3953
+ if (cur.length === 0 && !curQuoted) return null;
3954
+ if (!curQuoted) {
3955
+ const m = opPrefix.exec(cur);
3956
+ if (m) return m[0] ?? null;
3957
+ }
3958
+ return null;
3959
+ };
3960
+ for (let i = 0; i < cmd.length; i++) {
3961
+ const ch = cmd[i];
3962
+ if (quote) {
3963
+ if (ch === quote) {
3964
+ quote = null;
3965
+ } else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
3966
+ cur += cmd[++i];
3967
+ curQuoted = true;
3968
+ } else {
3969
+ cur += ch;
3970
+ curQuoted = true;
3971
+ }
3972
+ continue;
3973
+ }
3974
+ if (ch === '"' || ch === "'") {
3975
+ quote = ch;
3976
+ curQuoted = true;
3977
+ continue;
3978
+ }
3979
+ if (ch === " " || ch === " ") {
3980
+ const op = check();
3981
+ if (op) return op;
3982
+ cur = "";
3983
+ curQuoted = false;
3984
+ continue;
3985
+ }
3986
+ cur += ch;
3987
+ }
3988
+ if (quote) return null;
3989
+ return check();
3990
+ }
3720
3991
  function isAllowed(cmd, extra = []) {
3721
3992
  const normalized = cmd.trim().replace(/\s+/g, " ");
3722
3993
  const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
@@ -3729,6 +4000,12 @@ function isAllowed(cmd, extra = []) {
3729
4000
  async function runCommand(cmd, opts) {
3730
4001
  const argv = tokenizeCommand(cmd);
3731
4002
  if (argv.length === 0) throw new Error("run_command: empty command");
4003
+ const operator = detectShellOperator(cmd);
4004
+ if (operator !== null) {
4005
+ throw new Error(
4006
+ `run_command: shell operator "${operator}" is not supported \u2014 this tool spawns one process, no shell expansion. Split into separate run_command calls and combine the output in your reasoning (e.g. instead of \`grep foo *.ts | wc -l\`, call \`grep -c foo *.ts\` or two separate commands). To pass "${operator}" as a literal argument, wrap it in quotes.`
4007
+ );
4008
+ }
3732
4009
  const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
3733
4010
  const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
3734
4011
  const spawnOpts = {
@@ -3906,7 +4183,7 @@ function registerShellTools(registry, opts) {
3906
4183
  properties: {
3907
4184
  command: {
3908
4185
  type: "string",
3909
- description: "Full command line. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
4186
+ description: 'Full command line. Tokenized with POSIX-ish quoting; no shell expansion. Pipes (`|`), redirects (`>`, `<`, `2>`), and `&&`/`||` chaining are rejected with an error \u2014 split into separate calls instead. To pass an operator character as a literal argument (e.g. a regex), wrap it in quotes: `grep "a|b" file.txt`.'
3910
4187
  },
3911
4188
  timeoutSec: {
3912
4189
  type: "integer",
@@ -4125,12 +4402,12 @@ ${i + 1}. ${r.title}`);
4125
4402
  }
4126
4403
 
4127
4404
  // src/env.ts
4128
- import { readFileSync as readFileSync6 } from "fs";
4405
+ import { readFileSync as readFileSync7 } from "fs";
4129
4406
  import { resolve as resolve5 } from "path";
4130
4407
  function loadDotenv(path = ".env") {
4131
4408
  let raw;
4132
4409
  try {
4133
- raw = readFileSync6(resolve5(process.cwd(), path), "utf8");
4410
+ raw = readFileSync7(resolve5(process.cwd(), path), "utf8");
4134
4411
  } catch {
4135
4412
  return;
4136
4413
  }
@@ -4149,7 +4426,7 @@ function loadDotenv(path = ".env") {
4149
4426
  }
4150
4427
 
4151
4428
  // src/transcript.ts
4152
- import { createWriteStream, readFileSync as readFileSync7 } from "fs";
4429
+ import { createWriteStream, readFileSync as readFileSync8 } from "fs";
4153
4430
  function recordFromLoopEvent(ev, extra) {
4154
4431
  const rec = {
4155
4432
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4200,7 +4477,7 @@ function openTranscriptFile(path, meta) {
4200
4477
  return stream;
4201
4478
  }
4202
4479
  function readTranscript(path) {
4203
- const raw = readFileSync7(path, "utf8");
4480
+ const raw = readFileSync8(path, "utf8");
4204
4481
  return parseTranscript(raw);
4205
4482
  }
4206
4483
  function isPlanStateEmptyShape(s) {
@@ -5255,8 +5532,8 @@ async function trySection(load) {
5255
5532
  }
5256
5533
 
5257
5534
  // src/code/edit-blocks.ts
5258
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
5259
- import { dirname as dirname3, resolve as resolve6 } from "path";
5535
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
5536
+ import { dirname as dirname4, resolve as resolve6 } from "path";
5260
5537
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
5261
5538
  function parseEditBlocks(text) {
5262
5539
  const out = [];
@@ -5294,11 +5571,11 @@ function applyEditBlock(block, rootDir) {
5294
5571
  message: "file does not exist; to create it, use an empty SEARCH block"
5295
5572
  };
5296
5573
  }
5297
- mkdirSync3(dirname3(absTarget), { recursive: true });
5574
+ mkdirSync3(dirname4(absTarget), { recursive: true });
5298
5575
  writeFileSync3(absTarget, block.replace, "utf8");
5299
5576
  return { path: block.path, status: "created" };
5300
5577
  }
5301
- const content = readFileSync8(absTarget, "utf8");
5578
+ const content = readFileSync9(absTarget, "utf8");
5302
5579
  if (searchEmpty) {
5303
5580
  return {
5304
5581
  path: block.path,
@@ -5337,7 +5614,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
5337
5614
  continue;
5338
5615
  }
5339
5616
  try {
5340
- snapshots.push({ path: b.path, prevContent: readFileSync8(abs, "utf8") });
5617
+ snapshots.push({ path: b.path, prevContent: readFileSync9(abs, "utf8") });
5341
5618
  } catch {
5342
5619
  snapshots.push({ path: b.path, prevContent: null });
5343
5620
  }
@@ -5380,8 +5657,8 @@ function sep() {
5380
5657
  }
5381
5658
 
5382
5659
  // src/code/prompt.ts
5383
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
5384
- import { join as join7 } from "path";
5660
+ import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
5661
+ import { join as join8 } from "path";
5385
5662
  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.
5386
5663
 
5387
5664
  # Cite or shut up \u2014 non-negotiable
@@ -5502,11 +5779,11 @@ Two different rules depending on which tool:
5502
5779
  `;
5503
5780
  function codeSystemPrompt(rootDir) {
5504
5781
  const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
5505
- const gitignorePath = join7(rootDir, ".gitignore");
5782
+ const gitignorePath = join8(rootDir, ".gitignore");
5506
5783
  if (!existsSync8(gitignorePath)) return withMemory;
5507
5784
  let content;
5508
5785
  try {
5509
- content = readFileSync9(gitignorePath, "utf8");
5786
+ content = readFileSync10(gitignorePath, "utf8");
5510
5787
  } catch {
5511
5788
  return withMemory;
5512
5789
  }
@@ -5526,15 +5803,15 @@ ${truncated}
5526
5803
  }
5527
5804
 
5528
5805
  // src/config.ts
5529
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
5806
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
5530
5807
  import { homedir as homedir5 } from "os";
5531
- import { dirname as dirname4, join as join8 } from "path";
5808
+ import { dirname as dirname5, join as join9 } from "path";
5532
5809
  function defaultConfigPath() {
5533
- return join8(homedir5(), ".reasonix", "config.json");
5810
+ return join9(homedir5(), ".reasonix", "config.json");
5534
5811
  }
5535
5812
  function readConfig(path = defaultConfigPath()) {
5536
5813
  try {
5537
- const raw = readFileSync10(path, "utf8");
5814
+ const raw = readFileSync11(path, "utf8");
5538
5815
  const parsed = JSON.parse(raw);
5539
5816
  if (parsed && typeof parsed === "object") return parsed;
5540
5817
  } catch {
@@ -5542,7 +5819,7 @@ function readConfig(path = defaultConfigPath()) {
5542
5819
  return {};
5543
5820
  }
5544
5821
  function writeConfig(cfg, path = defaultConfigPath()) {
5545
- mkdirSync4(dirname4(path), { recursive: true });
5822
+ mkdirSync4(dirname5(path), { recursive: true });
5546
5823
  writeFileSync4(path, JSON.stringify(cfg, null, 2), "utf8");
5547
5824
  try {
5548
5825
  chmodSync2(path, 384);
@@ -5569,25 +5846,25 @@ function redactKey(key) {
5569
5846
  }
5570
5847
 
5571
5848
  // src/version.ts
5572
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
5849
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
5573
5850
  import { homedir as homedir6 } from "os";
5574
- import { dirname as dirname5, join as join9 } from "path";
5575
- import { fileURLToPath } from "url";
5851
+ import { dirname as dirname6, join as join10 } from "path";
5852
+ import { fileURLToPath as fileURLToPath2 } from "url";
5576
5853
  var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
5577
5854
  var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
5578
5855
  var LATEST_FETCH_TIMEOUT_MS = 2e3;
5579
5856
  function readPackageVersion() {
5580
5857
  try {
5581
- let dir = dirname5(fileURLToPath(import.meta.url));
5858
+ let dir = dirname6(fileURLToPath2(import.meta.url));
5582
5859
  for (let i = 0; i < 6; i++) {
5583
- const p = join9(dir, "package.json");
5860
+ const p = join10(dir, "package.json");
5584
5861
  if (existsSync9(p)) {
5585
- const pkg = JSON.parse(readFileSync11(p, "utf8"));
5862
+ const pkg = JSON.parse(readFileSync12(p, "utf8"));
5586
5863
  if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
5587
5864
  return pkg.version;
5588
5865
  }
5589
5866
  }
5590
- const parent = dirname5(dir);
5867
+ const parent = dirname6(dir);
5591
5868
  if (parent === dir) break;
5592
5869
  dir = parent;
5593
5870
  }
@@ -5597,11 +5874,11 @@ function readPackageVersion() {
5597
5874
  }
5598
5875
  var VERSION = readPackageVersion();
5599
5876
  function cachePath(homeDirOverride) {
5600
- return join9(homeDirOverride ?? homedir6(), ".reasonix", "version-cache.json");
5877
+ return join10(homeDirOverride ?? homedir6(), ".reasonix", "version-cache.json");
5601
5878
  }
5602
5879
  function readCache(homeDirOverride) {
5603
5880
  try {
5604
- const raw = readFileSync11(cachePath(homeDirOverride), "utf8");
5881
+ const raw = readFileSync12(cachePath(homeDirOverride), "utf8");
5605
5882
  const parsed = JSON.parse(raw);
5606
5883
  if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
5607
5884
  return parsed;
@@ -5613,7 +5890,7 @@ function readCache(homeDirOverride) {
5613
5890
  function writeCache(entry, homeDirOverride) {
5614
5891
  try {
5615
5892
  const p = cachePath(homeDirOverride);
5616
- mkdirSync5(dirname5(p), { recursive: true });
5893
+ mkdirSync5(dirname6(p), { recursive: true });
5617
5894
  writeFileSync5(p, JSON.stringify(entry), "utf8");
5618
5895
  } catch {
5619
5896
  }
@@ -5621,8 +5898,8 @@ function writeCache(entry, homeDirOverride) {
5621
5898
  async function getLatestVersion(opts = {}) {
5622
5899
  const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
5623
5900
  if (!opts.force) {
5624
- const cached = readCache(opts.homeDir);
5625
- if (cached && Date.now() - cached.checkedAt < ttl) return cached.version;
5901
+ const cached2 = readCache(opts.homeDir);
5902
+ if (cached2 && Date.now() - cached2.checkedAt < ttl) return cached2.version;
5626
5903
  }
5627
5904
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
5628
5905
  if (!fetchImpl) return null;
@@ -5670,11 +5947,11 @@ function isNpxInstall() {
5670
5947
  }
5671
5948
 
5672
5949
  // src/usage.ts
5673
- import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
5950
+ import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
5674
5951
  import { homedir as homedir7 } from "os";
5675
- import { dirname as dirname6, join as join10 } from "path";
5952
+ import { dirname as dirname7, join as join11 } from "path";
5676
5953
  function defaultUsageLogPath(homeDirOverride) {
5677
- return join10(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
5954
+ return join11(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
5678
5955
  }
5679
5956
  function appendUsage(input) {
5680
5957
  const record = {
@@ -5690,7 +5967,7 @@ function appendUsage(input) {
5690
5967
  };
5691
5968
  const path = input.path ?? defaultUsageLogPath();
5692
5969
  try {
5693
- mkdirSync6(dirname6(path), { recursive: true });
5970
+ mkdirSync6(dirname7(path), { recursive: true });
5694
5971
  appendFileSync2(path, `${JSON.stringify(record)}
5695
5972
  `, "utf8");
5696
5973
  } catch {
@@ -5701,7 +5978,7 @@ function readUsageLog(path = defaultUsageLogPath()) {
5701
5978
  if (!existsSync10(path)) return [];
5702
5979
  let raw;
5703
5980
  try {
5704
- raw = readFileSync12(path, "utf8");
5981
+ raw = readFileSync13(path, "utf8");
5705
5982
  } catch {
5706
5983
  return [];
5707
5984
  }
@@ -5799,6 +6076,7 @@ export {
5799
6076
  CODE_SYSTEM_PROMPT,
5800
6077
  CacheFirstLoop,
5801
6078
  DEFAULT_MAX_RESULT_CHARS,
6079
+ DEFAULT_MAX_RESULT_TOKENS,
5802
6080
  DeepSeekClient,
5803
6081
  HOOK_EVENTS,
5804
6082
  HOOK_SETTINGS_DIRNAME,
@@ -5848,6 +6126,7 @@ export {
5848
6126
  defaultSelector,
5849
6127
  defaultUsageLogPath,
5850
6128
  deleteSession,
6129
+ detectShellOperator,
5851
6130
  diffTranscripts,
5852
6131
  emptyPlanState,
5853
6132
  fetchWithRetry,
@@ -5922,6 +6201,7 @@ export {
5922
6201
  stripHallucinatedToolMarkup,
5923
6202
  tokenizeCommand,
5924
6203
  truncateForModel,
6204
+ truncateForModelByTokens,
5925
6205
  webFetch,
5926
6206
  webSearch,
5927
6207
  withUtf8Codepage,