reasonix 0.4.14 → 0.4.16

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((resolve4, reject) => {
51
- const timer = setTimeout(resolve4, ms);
50
+ return new Promise((resolve5, reject) => {
51
+ const timer = setTimeout(resolve5, ms);
52
52
  if (signal) {
53
53
  const onAbort = () => {
54
54
  clearTimeout(timer);
@@ -1488,8 +1488,8 @@ var CacheFirstLoop = class {
1488
1488
  }
1489
1489
  );
1490
1490
  for (let k = 0; k < budget; k++) {
1491
- const sample = queue.shift() ?? await new Promise((resolve4) => {
1492
- waiter = resolve4;
1491
+ const sample = queue.shift() ?? await new Promise((resolve5) => {
1492
+ waiter = resolve5;
1493
1493
  });
1494
1494
  yield {
1495
1495
  turn: this._turn,
@@ -2183,13 +2183,409 @@ function lineDiff(a, b) {
2183
2183
  return out;
2184
2184
  }
2185
2185
 
2186
+ // src/tools/shell.ts
2187
+ import { spawn } from "child_process";
2188
+ import * as pathMod2 from "path";
2189
+ var DEFAULT_TIMEOUT_SEC = 60;
2190
+ var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
2191
+ var BUILTIN_ALLOWLIST = [
2192
+ // Repo inspection
2193
+ "git status",
2194
+ "git diff",
2195
+ "git log",
2196
+ "git show",
2197
+ "git blame",
2198
+ "git branch",
2199
+ "git remote",
2200
+ "git rev-parse",
2201
+ "git config --get",
2202
+ // Filesystem inspection
2203
+ "ls",
2204
+ "pwd",
2205
+ "cat",
2206
+ "head",
2207
+ "tail",
2208
+ "wc",
2209
+ "file",
2210
+ "tree",
2211
+ "find",
2212
+ "grep",
2213
+ "rg",
2214
+ // Language version probes
2215
+ "node --version",
2216
+ "node -v",
2217
+ "npm --version",
2218
+ "npx --version",
2219
+ "python --version",
2220
+ "python3 --version",
2221
+ "cargo --version",
2222
+ "go version",
2223
+ "rustc --version",
2224
+ "deno --version",
2225
+ "bun --version",
2226
+ // Test runners (non-destructive by convention)
2227
+ "npm test",
2228
+ "npm run test",
2229
+ "npx vitest run",
2230
+ "npx vitest",
2231
+ "npx jest",
2232
+ "pytest",
2233
+ "python -m pytest",
2234
+ "cargo test",
2235
+ "cargo check",
2236
+ "cargo clippy",
2237
+ "go test",
2238
+ "go vet",
2239
+ "deno test",
2240
+ "bun test",
2241
+ // Linters / typecheckers (read-only by convention)
2242
+ "npm run lint",
2243
+ "npm run typecheck",
2244
+ "npx tsc --noEmit",
2245
+ "npx biome check",
2246
+ "npx eslint",
2247
+ "npx prettier --check",
2248
+ "ruff",
2249
+ "mypy"
2250
+ ];
2251
+ function tokenizeCommand(cmd) {
2252
+ const out = [];
2253
+ let cur = "";
2254
+ let quote = null;
2255
+ for (let i = 0; i < cmd.length; i++) {
2256
+ const ch = cmd[i];
2257
+ if (quote) {
2258
+ if (ch === quote) {
2259
+ quote = null;
2260
+ } else if (ch === "\\" && quote === '"' && i + 1 < cmd.length) {
2261
+ cur += cmd[++i];
2262
+ } else {
2263
+ cur += ch;
2264
+ }
2265
+ continue;
2266
+ }
2267
+ if (ch === '"' || ch === "'") {
2268
+ quote = ch;
2269
+ continue;
2270
+ }
2271
+ if (ch === " " || ch === " ") {
2272
+ if (cur.length > 0) {
2273
+ out.push(cur);
2274
+ cur = "";
2275
+ }
2276
+ continue;
2277
+ }
2278
+ cur += ch;
2279
+ }
2280
+ if (quote) throw new Error(`unclosed ${quote} in command`);
2281
+ if (cur.length > 0) out.push(cur);
2282
+ return out;
2283
+ }
2284
+ function isAllowed(cmd, extra = []) {
2285
+ const normalized = cmd.trim().replace(/\s+/g, " ");
2286
+ const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
2287
+ for (const prefix of allowlist) {
2288
+ if (normalized === prefix) return true;
2289
+ if (normalized.startsWith(`${prefix} `)) return true;
2290
+ }
2291
+ return false;
2292
+ }
2293
+ async function runCommand(cmd, opts) {
2294
+ const argv = tokenizeCommand(cmd);
2295
+ if (argv.length === 0) throw new Error("run_command: empty command");
2296
+ const timeoutMs = (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1e3;
2297
+ const maxChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2298
+ const spawnOpts = {
2299
+ cwd: opts.cwd,
2300
+ shell: false,
2301
+ // no shell-expansion — see header comment
2302
+ windowsHide: true,
2303
+ env: process.env
2304
+ };
2305
+ return await new Promise((resolve5, reject) => {
2306
+ let child;
2307
+ try {
2308
+ child = spawn(argv[0], argv.slice(1), spawnOpts);
2309
+ } catch (err) {
2310
+ reject(err);
2311
+ return;
2312
+ }
2313
+ let buf = "";
2314
+ let timedOut = false;
2315
+ const killTimer = setTimeout(() => {
2316
+ timedOut = true;
2317
+ child.kill("SIGKILL");
2318
+ }, timeoutMs);
2319
+ const onAbort = () => child.kill("SIGKILL");
2320
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
2321
+ const onData = (chunk) => {
2322
+ buf += chunk.toString();
2323
+ if (buf.length > maxChars * 2) buf = `${buf.slice(0, maxChars * 2)}`;
2324
+ };
2325
+ child.stdout?.on("data", onData);
2326
+ child.stderr?.on("data", onData);
2327
+ child.on("error", (err) => {
2328
+ clearTimeout(killTimer);
2329
+ opts.signal?.removeEventListener("abort", onAbort);
2330
+ reject(err);
2331
+ });
2332
+ child.on("close", (code) => {
2333
+ clearTimeout(killTimer);
2334
+ opts.signal?.removeEventListener("abort", onAbort);
2335
+ const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
2336
+
2337
+ [\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
2338
+ resolve5({ exitCode: code, output, timedOut });
2339
+ });
2340
+ });
2341
+ }
2342
+ var NeedsConfirmationError = class extends Error {
2343
+ command;
2344
+ constructor(command) {
2345
+ super(
2346
+ `run_command: "${command}" needs the user's approval before it runs. STOP calling tools now \u2014 the TUI has already prompted the user to press y (run) or n (deny). Wait for their next message; it will either be the command's output (if they approved) or an instruction to continue without it (if they denied). Don't retry the command or call other shell commands in the meantime.`
2347
+ );
2348
+ this.name = "NeedsConfirmationError";
2349
+ this.command = command;
2350
+ }
2351
+ };
2352
+ function registerShellTools(registry, opts) {
2353
+ const rootDir = pathMod2.resolve(opts.rootDir);
2354
+ const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
2355
+ const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
2356
+ const extraAllowed = opts.extraAllowed ?? [];
2357
+ const allowAll = opts.allowAll ?? false;
2358
+ registry.register({
2359
+ name: "run_command",
2360
+ description: "Run a shell command in the project root and return its combined stdout+stderr. Read-only and test commands (git status, ls, npm test, pytest, cargo test, grep, etc.) run immediately. Anything that could mutate state (npm install, git commit, rm, chmod) is refused and the user has to confirm in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
2361
+ parameters: {
2362
+ type: "object",
2363
+ properties: {
2364
+ command: {
2365
+ type: "string",
2366
+ description: "Full command line, e.g. 'npm test' or 'git diff src/foo.ts'. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
2367
+ },
2368
+ timeoutSec: {
2369
+ type: "integer",
2370
+ description: `Override the default ${timeoutSec}s timeout for a single command.`
2371
+ }
2372
+ },
2373
+ required: ["command"]
2374
+ },
2375
+ fn: async (args, ctx) => {
2376
+ const cmd = args.command.trim();
2377
+ if (!cmd) throw new Error("run_command: empty command");
2378
+ if (!allowAll && !isAllowed(cmd, extraAllowed)) {
2379
+ throw new NeedsConfirmationError(cmd);
2380
+ }
2381
+ const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
2382
+ const result = await runCommand(cmd, {
2383
+ cwd: rootDir,
2384
+ timeoutSec: effectiveTimeout,
2385
+ maxOutputChars,
2386
+ signal: ctx?.signal
2387
+ });
2388
+ return formatCommandResult(cmd, result);
2389
+ }
2390
+ });
2391
+ return registry;
2392
+ }
2393
+ function formatCommandResult(cmd, r) {
2394
+ const header = r.timedOut ? `$ ${cmd}
2395
+ [killed after timeout]` : `$ ${cmd}
2396
+ [exit ${r.exitCode ?? "?"}]`;
2397
+ return r.output ? `${header}
2398
+ ${r.output}` : header;
2399
+ }
2400
+
2401
+ // src/tools/web.ts
2402
+ var DEFAULT_FETCH_MAX_CHARS = 32e3;
2403
+ var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
2404
+ var DEFAULT_TOPK = 5;
2405
+ var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
2406
+ var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
2407
+ async function webSearch(query, opts = {}) {
2408
+ const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
2409
+ const resp = await fetch(`${MOJEEK_ENDPOINT}?q=${encodeURIComponent(query)}`, {
2410
+ headers: {
2411
+ "User-Agent": USER_AGENT,
2412
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9",
2413
+ "Accept-Language": "en-US,en;q=0.9"
2414
+ },
2415
+ signal: opts.signal,
2416
+ redirect: "follow"
2417
+ });
2418
+ if (!resp.ok) throw new Error(`web_search ${resp.status}`);
2419
+ const html = await resp.text();
2420
+ const results = parseMojeekResults(html).slice(0, topK);
2421
+ if (results.length === 0) {
2422
+ if (/no results found|did not match any documents/i.test(html)) return [];
2423
+ if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
2424
+ throw new Error("web_search: Mojeek anti-bot page \u2014 rate-limited or blocked");
2425
+ }
2426
+ throw new Error(
2427
+ `web_search: 0 results but response doesn't look like a real empty page (${html.length} chars, first 120: ${html.slice(0, 120).replace(/\s+/g, " ")})`
2428
+ );
2429
+ }
2430
+ return results;
2431
+ }
2432
+ function parseMojeekResults(html) {
2433
+ const titles = [];
2434
+ const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
2435
+ let m;
2436
+ while (true) {
2437
+ m = titleAnchorRe.exec(html);
2438
+ if (m === null) break;
2439
+ titles.push(m[0]);
2440
+ }
2441
+ const snippets = [];
2442
+ const snippetRe = /<p\b[^>]*\bclass="s"[^>]*>([\s\S]*?)<\/p>/g;
2443
+ while (true) {
2444
+ m = snippetRe.exec(html);
2445
+ if (m === null) break;
2446
+ snippets.push(m[1] ?? "");
2447
+ }
2448
+ const hrefRe = /href="([^"]+)"/;
2449
+ const innerRe = /<a\b[^>]*>([\s\S]*?)<\/a>/;
2450
+ const results = [];
2451
+ for (let i = 0; i < titles.length; i++) {
2452
+ const anchor = titles[i];
2453
+ const hrefMatch = anchor.match(hrefRe);
2454
+ const innerMatch = anchor.match(innerRe);
2455
+ if (!hrefMatch?.[1]) continue;
2456
+ results.push({
2457
+ title: decodeHtmlEntities(stripHtml(innerMatch?.[1] ?? "")).trim(),
2458
+ url: hrefMatch[1],
2459
+ snippet: decodeHtmlEntities(stripHtml(snippets[i] ?? "")).replace(/\s+/g, " ").trim()
2460
+ });
2461
+ }
2462
+ return results;
2463
+ }
2464
+ async function webFetch(url, opts = {}) {
2465
+ const maxChars = opts.maxChars ?? DEFAULT_FETCH_MAX_CHARS;
2466
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
2467
+ const ctl = new AbortController();
2468
+ const timer = setTimeout(() => ctl.abort(), timeoutMs);
2469
+ const cancel = () => ctl.abort();
2470
+ opts.signal?.addEventListener("abort", cancel, { once: true });
2471
+ let resp;
2472
+ try {
2473
+ resp = await fetch(url, {
2474
+ headers: { "User-Agent": USER_AGENT, Accept: "text/html,text/plain,*/*" },
2475
+ signal: ctl.signal,
2476
+ redirect: "follow"
2477
+ });
2478
+ } finally {
2479
+ clearTimeout(timer);
2480
+ opts.signal?.removeEventListener("abort", cancel);
2481
+ }
2482
+ if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
2483
+ const contentType = resp.headers.get("content-type") ?? "";
2484
+ const raw = await resp.text();
2485
+ const title = extractTitle(raw);
2486
+ const text = contentType.includes("text/html") ? htmlToText(raw) : raw;
2487
+ const truncated = text.length > maxChars;
2488
+ const finalText = truncated ? `${text.slice(0, maxChars)}
2489
+
2490
+ [\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
2491
+ return { url, title, text: finalText, truncated };
2492
+ }
2493
+ function htmlToText(html) {
2494
+ let s = html;
2495
+ s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
2496
+ s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
2497
+ s = s.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
2498
+ s = s.replace(/<nav[\s\S]*?<\/nav>/gi, "");
2499
+ s = s.replace(/<footer[\s\S]*?<\/footer>/gi, "");
2500
+ s = s.replace(/<aside[\s\S]*?<\/aside>/gi, "");
2501
+ s = s.replace(/<svg[\s\S]*?<\/svg>/gi, "");
2502
+ s = s.replace(/<\/?(p|div|br|h[1-6]|li|tr|section|article)\b[^>]*>/gi, "\n");
2503
+ s = s.replace(/<[^>]+>/g, "");
2504
+ s = decodeHtmlEntities(s);
2505
+ s = s.replace(/[ \t]+/g, " ");
2506
+ s = s.replace(/\n[ \t]+/g, "\n");
2507
+ s = s.replace(/\n{3,}/g, "\n\n");
2508
+ return s.trim();
2509
+ }
2510
+ function stripHtml(s) {
2511
+ return s.replace(/<[^>]+>/g, "");
2512
+ }
2513
+ function decodeHtmlEntities(s) {
2514
+ return s.replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
2515
+ }
2516
+ function extractTitle(html) {
2517
+ const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
2518
+ if (!m?.[1]) return void 0;
2519
+ return m[1].replace(/\s+/g, " ").trim() || void 0;
2520
+ }
2521
+ function registerWebTools(registry, opts = {}) {
2522
+ const defaultTopK = opts.defaultTopK ?? DEFAULT_TOPK;
2523
+ const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
2524
+ registry.register({
2525
+ name: "web_search",
2526
+ description: "Search the public web. Returns ranked results with title, url, and snippet. Use this when the question needs information more current than your training data, when you're unsure of a factual detail, or when the user asks about a specific webpage/library/release you haven't seen.",
2527
+ parameters: {
2528
+ type: "object",
2529
+ properties: {
2530
+ query: { type: "string", description: "Natural-language search query." },
2531
+ topK: {
2532
+ type: "integer",
2533
+ description: `Number of results to return (1..10). Default ${defaultTopK}.`
2534
+ }
2535
+ },
2536
+ required: ["query"]
2537
+ },
2538
+ fn: async (args, ctx) => {
2539
+ const results = await webSearch(args.query, {
2540
+ topK: args.topK ?? defaultTopK,
2541
+ signal: ctx?.signal
2542
+ });
2543
+ return formatSearchResults(args.query, results);
2544
+ }
2545
+ });
2546
+ registry.register({
2547
+ name: "web_fetch",
2548
+ description: "Download a URL and return its visible text content (HTML pages get scripts/styles/nav stripped). Truncated at the tool-result cap. Use after web_search when a snippet isn't enough.",
2549
+ parameters: {
2550
+ type: "object",
2551
+ properties: {
2552
+ url: { type: "string", description: "Absolute http:// or https:// URL." }
2553
+ },
2554
+ required: ["url"]
2555
+ },
2556
+ fn: async (args, ctx) => {
2557
+ if (!/^https?:\/\//i.test(args.url)) {
2558
+ throw new Error("web_fetch: url must start with http:// or https://");
2559
+ }
2560
+ const page = await webFetch(args.url, { maxChars: maxFetchChars, signal: ctx?.signal });
2561
+ const header = page.title ? `${page.title}
2562
+ ${page.url}` : page.url;
2563
+ return `${header}
2564
+
2565
+ ${page.text}`;
2566
+ }
2567
+ });
2568
+ return registry;
2569
+ }
2570
+ function formatSearchResults(query, results) {
2571
+ const lines = [`query: ${query}`, `
2572
+ results (${results.length}):`];
2573
+ results.forEach((r, i) => {
2574
+ lines.push(`
2575
+ ${i + 1}. ${r.title}`);
2576
+ lines.push(` ${r.url}`);
2577
+ if (r.snippet) lines.push(` ${r.snippet}`);
2578
+ });
2579
+ return lines.join("\n");
2580
+ }
2581
+
2186
2582
  // src/env.ts
2187
2583
  import { readFileSync as readFileSync2 } from "fs";
2188
- import { resolve as resolve2 } from "path";
2584
+ import { resolve as resolve3 } from "path";
2189
2585
  function loadDotenv(path = ".env") {
2190
2586
  let raw;
2191
2587
  try {
2192
- raw = readFileSync2(resolve2(process.cwd(), path), "utf8");
2588
+ raw = readFileSync2(resolve3(process.cwd(), path), "utf8");
2193
2589
  } catch {
2194
2590
  return;
2195
2591
  }
@@ -2871,7 +3267,7 @@ var McpClient = class {
2871
3267
  const id = this.nextId++;
2872
3268
  const frame = { jsonrpc: "2.0", id, method, params };
2873
3269
  let abortHandler = null;
2874
- const promise = new Promise((resolve4, reject) => {
3270
+ const promise = new Promise((resolve5, reject) => {
2875
3271
  const timeout = setTimeout(() => {
2876
3272
  this.pending.delete(id);
2877
3273
  if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
@@ -2880,7 +3276,7 @@ var McpClient = class {
2880
3276
  );
2881
3277
  }, this.requestTimeoutMs);
2882
3278
  this.pending.set(id, {
2883
- resolve: resolve4,
3279
+ resolve: resolve5,
2884
3280
  reject,
2885
3281
  timeout
2886
3282
  });
@@ -2962,7 +3358,7 @@ var McpClient = class {
2962
3358
  };
2963
3359
 
2964
3360
  // src/mcp/stdio.ts
2965
- import { spawn } from "child_process";
3361
+ import { spawn as spawn2 } from "child_process";
2966
3362
  var StdioTransport = class {
2967
3363
  child;
2968
3364
  queue = [];
@@ -2977,14 +3373,14 @@ var StdioTransport = class {
2977
3373
  opts.command,
2978
3374
  ...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
2979
3375
  ].join(" ");
2980
- this.child = spawn(line, [], {
3376
+ this.child = spawn2(line, [], {
2981
3377
  env,
2982
3378
  cwd: opts.cwd,
2983
3379
  stdio: ["pipe", "pipe", "inherit"],
2984
3380
  shell: true
2985
3381
  });
2986
3382
  } else {
2987
- this.child = spawn(opts.command, opts.args ?? [], {
3383
+ this.child = spawn2(opts.command, opts.args ?? [], {
2988
3384
  env,
2989
3385
  cwd: opts.cwd,
2990
3386
  stdio: ["pipe", "pipe", "inherit"]
@@ -3003,12 +3399,12 @@ var StdioTransport = class {
3003
3399
  }
3004
3400
  async send(message) {
3005
3401
  if (this.closed) throw new Error("MCP transport is closed");
3006
- return new Promise((resolve4, reject) => {
3402
+ return new Promise((resolve5, reject) => {
3007
3403
  const line = `${JSON.stringify(message)}
3008
3404
  `;
3009
3405
  this.child.stdin.write(line, "utf8", (err) => {
3010
3406
  if (err) reject(err);
3011
- else resolve4();
3407
+ else resolve5();
3012
3408
  });
3013
3409
  });
3014
3410
  }
@@ -3019,8 +3415,8 @@ var StdioTransport = class {
3019
3415
  continue;
3020
3416
  }
3021
3417
  if (this.closed) return;
3022
- const next = await new Promise((resolve4) => {
3023
- this.waiters.push(resolve4);
3418
+ const next = await new Promise((resolve5) => {
3419
+ this.waiters.push(resolve5);
3024
3420
  });
3025
3421
  if (next === null) return;
3026
3422
  yield next;
@@ -3086,8 +3482,8 @@ var SseTransport = class {
3086
3482
  constructor(opts) {
3087
3483
  this.url = opts.url;
3088
3484
  this.headers = opts.headers ?? {};
3089
- this.endpointReady = new Promise((resolve4, reject) => {
3090
- this.resolveEndpoint = resolve4;
3485
+ this.endpointReady = new Promise((resolve5, reject) => {
3486
+ this.resolveEndpoint = resolve5;
3091
3487
  this.rejectEndpoint = reject;
3092
3488
  });
3093
3489
  this.endpointReady.catch(() => void 0);
@@ -3114,8 +3510,8 @@ var SseTransport = class {
3114
3510
  continue;
3115
3511
  }
3116
3512
  if (this.closed) return;
3117
- const next = await new Promise((resolve4) => {
3118
- this.waiters.push(resolve4);
3513
+ const next = await new Promise((resolve5) => {
3514
+ this.waiters.push(resolve5);
3119
3515
  });
3120
3516
  if (next === null) return;
3121
3517
  yield next;
@@ -3315,7 +3711,7 @@ async function trySection(load) {
3315
3711
 
3316
3712
  // src/code/edit-blocks.ts
3317
3713
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
3318
- import { dirname as dirname3, resolve as resolve3 } from "path";
3714
+ import { dirname as dirname3, resolve as resolve4 } from "path";
3319
3715
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
3320
3716
  function parseEditBlocks(text) {
3321
3717
  const out = [];
@@ -3333,8 +3729,8 @@ function parseEditBlocks(text) {
3333
3729
  return out;
3334
3730
  }
3335
3731
  function applyEditBlock(block, rootDir) {
3336
- const absRoot = resolve3(rootDir);
3337
- const absTarget = resolve3(absRoot, block.path);
3732
+ const absRoot = resolve4(rootDir);
3733
+ const absTarget = resolve4(absRoot, block.path);
3338
3734
  if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
3339
3735
  return {
3340
3736
  path: block.path,
@@ -3384,13 +3780,13 @@ function applyEditBlocks(blocks, rootDir) {
3384
3780
  return blocks.map((b) => applyEditBlock(b, rootDir));
3385
3781
  }
3386
3782
  function snapshotBeforeEdits(blocks, rootDir) {
3387
- const absRoot = resolve3(rootDir);
3783
+ const absRoot = resolve4(rootDir);
3388
3784
  const seen = /* @__PURE__ */ new Set();
3389
3785
  const snapshots = [];
3390
3786
  for (const b of blocks) {
3391
3787
  if (seen.has(b.path)) continue;
3392
3788
  seen.add(b.path);
3393
- const abs = resolve3(absRoot, b.path);
3789
+ const abs = resolve4(absRoot, b.path);
3394
3790
  if (!existsSync2(abs)) {
3395
3791
  snapshots.push({ path: b.path, prevContent: null });
3396
3792
  continue;
@@ -3404,9 +3800,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
3404
3800
  return snapshots;
3405
3801
  }
3406
3802
  function restoreSnapshots(snapshots, rootDir) {
3407
- const absRoot = resolve3(rootDir);
3803
+ const absRoot = resolve4(rootDir);
3408
3804
  return snapshots.map((snap) => {
3409
- const abs = resolve3(absRoot, snap.path);
3805
+ const abs = resolve4(absRoot, snap.path);
3410
3806
  if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
3411
3807
  return {
3412
3808
  path: snap.path,
@@ -3556,7 +3952,7 @@ function redactKey(key) {
3556
3952
  }
3557
3953
 
3558
3954
  // src/index.ts
3559
- var VERSION = "0.4.14";
3955
+ var VERSION = "0.4.16";
3560
3956
  export {
3561
3957
  AppendOnlyLog,
3562
3958
  CODE_SYSTEM_PROMPT,
@@ -3566,6 +3962,7 @@ export {
3566
3962
  ImmutablePrefix,
3567
3963
  MCP_PROTOCOL_VERSION,
3568
3964
  McpClient,
3965
+ NeedsConfirmationError,
3569
3966
  SessionStats,
3570
3967
  SseTransport,
3571
3968
  StdioTransport,
@@ -3593,11 +3990,15 @@ export {
3593
3990
  fetchWithRetry,
3594
3991
  flattenMcpResult,
3595
3992
  flattenSchema,
3993
+ formatCommandResult,
3596
3994
  formatLoopError,
3995
+ formatSearchResults,
3597
3996
  harvest,
3598
3997
  healLoadedMessages,
3998
+ htmlToText,
3599
3999
  inputCostUsd,
3600
4000
  inspectMcpServer,
4001
+ isAllowed,
3601
4002
  isJsonRpcError,
3602
4003
  isPlanStateEmpty,
3603
4004
  isPlausibleKey,
@@ -3610,18 +4011,22 @@ export {
3610
4011
  outputCostUsd,
3611
4012
  parseEditBlocks,
3612
4013
  parseMcpSpec,
4014
+ parseMojeekResults,
3613
4015
  parseTranscript,
3614
4016
  readConfig,
3615
4017
  readTranscript,
3616
4018
  recordFromLoopEvent,
3617
4019
  redactKey,
3618
4020
  registerFilesystemTools,
4021
+ registerShellTools,
4022
+ registerWebTools,
3619
4023
  renderMarkdown as renderDiffMarkdown,
3620
4024
  renderSummaryTable as renderDiffSummary,
3621
4025
  repairTruncatedJson,
3622
4026
  replayFromFile,
3623
4027
  restoreSnapshots,
3624
4028
  runBranches,
4029
+ runCommand,
3625
4030
  sanitizeName as sanitizeSessionName,
3626
4031
  saveApiKey,
3627
4032
  scavengeToolCalls,
@@ -3630,7 +4035,10 @@ export {
3630
4035
  similarity,
3631
4036
  snapshotBeforeEdits,
3632
4037
  stripHallucinatedToolMarkup,
4038
+ tokenizeCommand,
3633
4039
  truncateForModel,
4040
+ webFetch,
4041
+ webSearch,
3634
4042
  writeConfig,
3635
4043
  writeMeta,
3636
4044
  writeRecord