reasonix 0.12.16 → 0.12.20
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/cli/index.js +606 -229
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +94 -1
- package/dist/index.js +390 -145
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -532,6 +532,7 @@ function matchesTool(hook, toolName) {
|
|
|
532
532
|
return false;
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
|
+
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
535
536
|
function defaultSpawner(input) {
|
|
536
537
|
return new Promise((resolve9) => {
|
|
537
538
|
const child = spawn(input.command, {
|
|
@@ -539,8 +540,11 @@ function defaultSpawner(input) {
|
|
|
539
540
|
shell: true,
|
|
540
541
|
stdio: ["pipe", "pipe", "pipe"]
|
|
541
542
|
});
|
|
542
|
-
|
|
543
|
-
|
|
543
|
+
const stdoutChunks = [];
|
|
544
|
+
const stderrChunks = [];
|
|
545
|
+
let stdoutBytes = 0;
|
|
546
|
+
let stderrBytes = 0;
|
|
547
|
+
let truncated = false;
|
|
544
548
|
let timedOut = false;
|
|
545
549
|
const timer = setTimeout(() => {
|
|
546
550
|
timedOut = true;
|
|
@@ -552,29 +556,46 @@ function defaultSpawner(input) {
|
|
|
552
556
|
}
|
|
553
557
|
}, 500);
|
|
554
558
|
}, input.timeoutMs);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
559
|
+
const onChunk = (kind, chunk) => {
|
|
560
|
+
const target = kind === "stdout" ? stdoutChunks : stderrChunks;
|
|
561
|
+
const seen = kind === "stdout" ? stdoutBytes : stderrBytes;
|
|
562
|
+
if (seen >= HOOK_OUTPUT_CAP_BYTES) {
|
|
563
|
+
truncated = true;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const remaining = HOOK_OUTPUT_CAP_BYTES - seen;
|
|
567
|
+
if (chunk.length > remaining) {
|
|
568
|
+
target.push(chunk.subarray(0, remaining));
|
|
569
|
+
if (kind === "stdout") stdoutBytes = HOOK_OUTPUT_CAP_BYTES;
|
|
570
|
+
else stderrBytes = HOOK_OUTPUT_CAP_BYTES;
|
|
571
|
+
truncated = true;
|
|
572
|
+
} else {
|
|
573
|
+
target.push(chunk);
|
|
574
|
+
if (kind === "stdout") stdoutBytes += chunk.length;
|
|
575
|
+
else stderrBytes += chunk.length;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
child.stdout.on("data", (chunk) => onChunk("stdout", chunk));
|
|
579
|
+
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
561
580
|
child.once("error", (err) => {
|
|
562
581
|
clearTimeout(timer);
|
|
563
582
|
resolve9({
|
|
564
583
|
exitCode: null,
|
|
565
|
-
stdout,
|
|
566
|
-
stderr,
|
|
584
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
585
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
567
586
|
timedOut: false,
|
|
568
|
-
spawnError: err
|
|
587
|
+
spawnError: err,
|
|
588
|
+
truncated: truncated || void 0
|
|
569
589
|
});
|
|
570
590
|
});
|
|
571
591
|
child.once("close", (code) => {
|
|
572
592
|
clearTimeout(timer);
|
|
573
593
|
resolve9({
|
|
574
594
|
exitCode: code,
|
|
575
|
-
stdout:
|
|
576
|
-
stderr:
|
|
577
|
-
timedOut
|
|
595
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
596
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
597
|
+
timedOut,
|
|
598
|
+
truncated: truncated || void 0
|
|
578
599
|
});
|
|
579
600
|
});
|
|
580
601
|
try {
|
|
@@ -589,7 +610,8 @@ function formatHookOutcomeMessage(outcome) {
|
|
|
589
610
|
const detail = (outcome.stderr || outcome.stdout || "").trim();
|
|
590
611
|
const tag = `${outcome.hook.scope}/${outcome.hook.event}`;
|
|
591
612
|
const cmd = outcome.hook.command.length > 60 ? `${outcome.hook.command.slice(0, 60)}\u2026` : outcome.hook.command;
|
|
592
|
-
const
|
|
613
|
+
const truncTag = outcome.truncated ? " (output truncated at 256KB)" : "";
|
|
614
|
+
const head = `hook ${tag} \`${cmd}\` ${outcome.decision}${truncTag}`;
|
|
593
615
|
return detail ? `${head}: ${detail}` : head;
|
|
594
616
|
}
|
|
595
617
|
function decideOutcome(event, raw) {
|
|
@@ -620,7 +642,8 @@ async function runHooks(opts) {
|
|
|
620
642
|
exitCode: raw.exitCode,
|
|
621
643
|
stdout: raw.stdout,
|
|
622
644
|
stderr: raw.stderr || (raw.spawnError ? raw.spawnError.message : "") || (raw.timedOut ? `hook timed out after ${timeoutMs}ms` : ""),
|
|
623
|
-
durationMs: Date.now() - start
|
|
645
|
+
durationMs: Date.now() - start,
|
|
646
|
+
truncated: raw.truncated
|
|
624
647
|
});
|
|
625
648
|
if (decision === "block") {
|
|
626
649
|
blocked = true;
|
|
@@ -1168,6 +1191,23 @@ var ImmutablePrefix = class {
|
|
|
1168
1191
|
*/
|
|
1169
1192
|
_toolSpecs;
|
|
1170
1193
|
fewShots;
|
|
1194
|
+
/**
|
|
1195
|
+
* Cached SHA-256 of the prefix payload. Computed lazily on first
|
|
1196
|
+
* `fingerprint` access, invalidated only by mutations that go
|
|
1197
|
+
* through `addTool` (the one legitimate post-construction mutation
|
|
1198
|
+
* path). The TUI reads `fingerprint` on every render — without the
|
|
1199
|
+
* cache, that means a fresh `JSON.stringify` + sha256 over the
|
|
1200
|
+
* full prefix (system prompt + tools list + few-shots, typically
|
|
1201
|
+
* 5-10KB) on every keystroke.
|
|
1202
|
+
*
|
|
1203
|
+
* The lazy-init also acts as a cheap drift guard: if some future
|
|
1204
|
+
* code path mutates `_toolSpecs` directly without going through
|
|
1205
|
+
* `addTool`, `fingerprint` will return the stale cached value
|
|
1206
|
+
* while the actual prefix sent to DeepSeek diverges — the cache
|
|
1207
|
+
* miss would be the first symptom. {@link verifyFingerprint}
|
|
1208
|
+
* lets dev / test code assert the cache matches reality.
|
|
1209
|
+
*/
|
|
1210
|
+
_fingerprintCache = null;
|
|
1171
1211
|
constructor(opts) {
|
|
1172
1212
|
this.system = opts.system;
|
|
1173
1213
|
this._toolSpecs = [...opts.toolSpecs ?? []];
|
|
@@ -1193,9 +1233,33 @@ var ImmutablePrefix = class {
|
|
|
1193
1233
|
if (!name) return false;
|
|
1194
1234
|
if (this._toolSpecs.some((t) => t.function?.name === name)) return false;
|
|
1195
1235
|
this._toolSpecs.push(spec);
|
|
1236
|
+
this._fingerprintCache = null;
|
|
1196
1237
|
return true;
|
|
1197
1238
|
}
|
|
1198
1239
|
get fingerprint() {
|
|
1240
|
+
if (this._fingerprintCache !== null) return this._fingerprintCache;
|
|
1241
|
+
this._fingerprintCache = this.computeFingerprint();
|
|
1242
|
+
return this._fingerprintCache;
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Recompute the fingerprint from scratch and assert it matches the
|
|
1246
|
+
* cached value. Returns the freshly-computed hash on success; throws
|
|
1247
|
+
* with a diff if the cache drifted, which always indicates a bug —
|
|
1248
|
+
* either a non-`addTool` mutation path was added, or `addTool`
|
|
1249
|
+
* forgot to invalidate the cache. Dev / test only; the live loop
|
|
1250
|
+
* doesn't call this on the hot path.
|
|
1251
|
+
*/
|
|
1252
|
+
verifyFingerprint() {
|
|
1253
|
+
const fresh = this.computeFingerprint();
|
|
1254
|
+
if (this._fingerprintCache !== null && this._fingerprintCache !== fresh) {
|
|
1255
|
+
throw new Error(
|
|
1256
|
+
`ImmutablePrefix fingerprint drift: cached=${this._fingerprintCache}, fresh=${fresh}. A mutation path bypassed addTool's cache invalidation \u2014 DeepSeek will see prefix churn that the TUI / transcript log don't know about.`
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
this._fingerprintCache = fresh;
|
|
1260
|
+
return fresh;
|
|
1261
|
+
}
|
|
1262
|
+
computeFingerprint() {
|
|
1199
1263
|
const blob = JSON.stringify({
|
|
1200
1264
|
system: this.system,
|
|
1201
1265
|
tools: this._toolSpecs,
|
|
@@ -1614,10 +1678,10 @@ function listSessions() {
|
|
|
1614
1678
|
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1615
1679
|
return files.map((file) => {
|
|
1616
1680
|
const path = join3(dir, file);
|
|
1617
|
-
const
|
|
1681
|
+
const stat2 = statSync(path);
|
|
1618
1682
|
const name = file.replace(/\.jsonl$/, "");
|
|
1619
1683
|
const messageCount = countLines(path);
|
|
1620
|
-
return { name, path, size:
|
|
1684
|
+
return { name, path, size: stat2.size, messageCount, mtime: stat2.mtime };
|
|
1621
1685
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
1622
1686
|
} catch {
|
|
1623
1687
|
return [];
|
|
@@ -1791,6 +1855,19 @@ var CacheFirstLoop = class {
|
|
|
1791
1855
|
* flip it live alongside `model`.
|
|
1792
1856
|
*/
|
|
1793
1857
|
autoEscalate = true;
|
|
1858
|
+
/**
|
|
1859
|
+
* Soft USD budget — see {@link CacheFirstLoopOptions.budgetUsd}.
|
|
1860
|
+
* Mutable so `/budget` slash can set / change / clear it mid-session.
|
|
1861
|
+
* `null` (the default) disables all budget checks.
|
|
1862
|
+
*/
|
|
1863
|
+
budgetUsd;
|
|
1864
|
+
/**
|
|
1865
|
+
* Set the first time a turn crosses 80% of the budget so the warning
|
|
1866
|
+
* doesn't repeat every turn afterwards. Cleared by `setBudget` (any
|
|
1867
|
+
* change re-arms the warning, including raising the cap above the
|
|
1868
|
+
* current spend).
|
|
1869
|
+
*/
|
|
1870
|
+
_budgetWarned = false;
|
|
1794
1871
|
sessionName;
|
|
1795
1872
|
/**
|
|
1796
1873
|
* Hook list, mutable so `/hooks reload` can swap it without
|
|
@@ -1856,6 +1933,7 @@ var CacheFirstLoop = class {
|
|
|
1856
1933
|
this.model = opts.model ?? "deepseek-v4-flash";
|
|
1857
1934
|
this.reasoningEffort = opts.reasoningEffort ?? "max";
|
|
1858
1935
|
if (opts.autoEscalate !== void 0) this.autoEscalate = opts.autoEscalate;
|
|
1936
|
+
this.budgetUsd = typeof opts.budgetUsd === "number" && opts.budgetUsd > 0 ? opts.budgetUsd : null;
|
|
1859
1937
|
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
1860
1938
|
this.hooks = opts.hooks ?? [];
|
|
1861
1939
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
@@ -2095,6 +2173,16 @@ var CacheFirstLoop = class {
|
|
|
2095
2173
|
}
|
|
2096
2174
|
this.stream = this.branchEnabled ? false : this._streamPreference;
|
|
2097
2175
|
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Set / change / clear the soft USD budget. `null` (or any non-
|
|
2178
|
+
* positive number) disables the cap entirely. Re-arms the 80%
|
|
2179
|
+
* warning so a user who bumps the cap mid-session sees a fresh
|
|
2180
|
+
* threshold message at the new boundary.
|
|
2181
|
+
*/
|
|
2182
|
+
setBudget(usd) {
|
|
2183
|
+
this.budgetUsd = typeof usd === "number" && usd > 0 ? usd : null;
|
|
2184
|
+
this._budgetWarned = false;
|
|
2185
|
+
}
|
|
2098
2186
|
/**
|
|
2099
2187
|
* Arm pro for the next turn (consumed at turn start). Called by
|
|
2100
2188
|
* `/pro`. Idempotent — repeated calls stay armed, `disarmPro()`
|
|
@@ -2256,6 +2344,26 @@ var CacheFirstLoop = class {
|
|
|
2256
2344
|
return userText;
|
|
2257
2345
|
}
|
|
2258
2346
|
async *step(userInput) {
|
|
2347
|
+
if (this.budgetUsd !== null) {
|
|
2348
|
+
const spent = this.stats.totalCost;
|
|
2349
|
+
if (spent >= this.budgetUsd) {
|
|
2350
|
+
yield {
|
|
2351
|
+
turn: this._turn,
|
|
2352
|
+
role: "error",
|
|
2353
|
+
content: "",
|
|
2354
|
+
error: `session budget exhausted \u2014 spent $${spent.toFixed(4)} \u2265 cap $${this.budgetUsd.toFixed(2)}. Bump the cap with /budget <usd>, clear it with /budget off, or end the session.`
|
|
2355
|
+
};
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
if (!this._budgetWarned && spent >= this.budgetUsd * 0.8) {
|
|
2359
|
+
this._budgetWarned = true;
|
|
2360
|
+
yield {
|
|
2361
|
+
turn: this._turn,
|
|
2362
|
+
role: "warning",
|
|
2363
|
+
content: `\u25B2 budget 80% used \u2014 $${spent.toFixed(4)} of $${this.budgetUsd.toFixed(2)}. Next turn or two likely trips the cap.`
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2259
2367
|
this._turn++;
|
|
2260
2368
|
this.scratch.reset();
|
|
2261
2369
|
this.repair.resetStorm();
|
|
@@ -3118,6 +3226,7 @@ function extractDeepSeekErrorMessage(body) {
|
|
|
3118
3226
|
|
|
3119
3227
|
// src/at-mentions.ts
|
|
3120
3228
|
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
3229
|
+
import { readdir, stat } from "fs/promises";
|
|
3121
3230
|
import { isAbsolute, join as join4, relative, resolve } from "path";
|
|
3122
3231
|
var DEFAULT_AT_MENTION_MAX_BYTES = 64 * 1024;
|
|
3123
3232
|
var DEFAULT_PICKER_IGNORE_DIRS = [
|
|
@@ -3172,6 +3281,58 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3172
3281
|
walk2(rootAbs, "");
|
|
3173
3282
|
return out;
|
|
3174
3283
|
}
|
|
3284
|
+
async function listFilesWithStatsAsync(root, opts = {}) {
|
|
3285
|
+
const maxResults = Math.max(1, opts.maxResults ?? 500);
|
|
3286
|
+
const ignore = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3287
|
+
const rootAbs = resolve(root);
|
|
3288
|
+
const out = [];
|
|
3289
|
+
const walk2 = async (dirAbs, dirRel) => {
|
|
3290
|
+
if (out.length >= maxResults) return;
|
|
3291
|
+
let entries;
|
|
3292
|
+
try {
|
|
3293
|
+
entries = await readdir(dirAbs, { withFileTypes: true });
|
|
3294
|
+
} catch {
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3298
|
+
const fileEnts = [];
|
|
3299
|
+
for (const ent of entries) {
|
|
3300
|
+
if (out.length >= maxResults) break;
|
|
3301
|
+
if (ent.isDirectory()) {
|
|
3302
|
+
if (ent.name.startsWith(".") || ignore.has(ent.name)) continue;
|
|
3303
|
+
if (fileEnts.length > 0) {
|
|
3304
|
+
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3305
|
+
fileEnts.length = 0;
|
|
3306
|
+
if (out.length >= maxResults) return;
|
|
3307
|
+
}
|
|
3308
|
+
await walk2(join4(dirAbs, ent.name), dirRel ? `${dirRel}/${ent.name}` : ent.name);
|
|
3309
|
+
} else if (ent.isFile()) {
|
|
3310
|
+
fileEnts.push(ent);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
if (fileEnts.length > 0 && out.length < maxResults) {
|
|
3314
|
+
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3315
|
+
}
|
|
3316
|
+
};
|
|
3317
|
+
await walk2(rootAbs, "");
|
|
3318
|
+
return out;
|
|
3319
|
+
}
|
|
3320
|
+
async function statBatch(ents, dirAbs, dirRel, out, maxResults) {
|
|
3321
|
+
const remaining = Math.max(0, maxResults - out.length);
|
|
3322
|
+
const batch = ents.slice(0, remaining);
|
|
3323
|
+
const stats = await Promise.all(
|
|
3324
|
+
batch.map(
|
|
3325
|
+
(e) => stat(join4(dirAbs, e.name)).then((s) => s.mtimeMs).catch(() => 0)
|
|
3326
|
+
)
|
|
3327
|
+
);
|
|
3328
|
+
for (let i = 0; i < batch.length; i++) {
|
|
3329
|
+
const ent = batch[i];
|
|
3330
|
+
out.push({
|
|
3331
|
+
path: dirRel ? `${dirRel}/${ent.name}` : ent.name,
|
|
3332
|
+
mtimeMs: stats[i] ?? 0
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3175
3336
|
var AT_PICKER_PREFIX = /(?:^|\s)@([a-zA-Z0-9_./\\-]*)$/;
|
|
3176
3337
|
function detectAtPicker(input) {
|
|
3177
3338
|
const m = AT_PICKER_PREFIX.exec(input);
|
|
@@ -4180,8 +4341,8 @@ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_L
|
|
|
4180
4341
|
},
|
|
4181
4342
|
fn: async (args) => {
|
|
4182
4343
|
const abs = safePath(args.path);
|
|
4183
|
-
const
|
|
4184
|
-
if (
|
|
4344
|
+
const stat2 = await fs.stat(abs);
|
|
4345
|
+
if (stat2.isDirectory()) {
|
|
4185
4346
|
throw new Error(`not a file: ${args.path} (it's a directory)`);
|
|
4186
4347
|
}
|
|
4187
4348
|
const raw = await fs.readFile(abs);
|
|
@@ -4450,13 +4611,13 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4450
4611
|
if (nameFilter && !e.name.toLowerCase().includes(nameFilter)) continue;
|
|
4451
4612
|
if (isLikelyBinaryByName(e.name)) continue;
|
|
4452
4613
|
const full = pathMod.join(dir, e.name);
|
|
4453
|
-
let
|
|
4614
|
+
let stat2;
|
|
4454
4615
|
try {
|
|
4455
|
-
|
|
4616
|
+
stat2 = await fs.stat(full);
|
|
4456
4617
|
} catch {
|
|
4457
4618
|
continue;
|
|
4458
4619
|
}
|
|
4459
|
-
if (
|
|
4620
|
+
if (stat2.size > 2 * 1024 * 1024) continue;
|
|
4460
4621
|
let raw;
|
|
4461
4622
|
try {
|
|
4462
4623
|
raw = await fs.readFile(full);
|
|
@@ -5491,6 +5652,8 @@ var JobRegistry = class {
|
|
|
5491
5652
|
};
|
|
5492
5653
|
this.jobs.set(id, job);
|
|
5493
5654
|
let readyMatched = false;
|
|
5655
|
+
let recentForReady = "";
|
|
5656
|
+
const READY_WINDOW = 1024;
|
|
5494
5657
|
const onData = (chunk) => {
|
|
5495
5658
|
const s = chunk.toString();
|
|
5496
5659
|
job.totalBytesWritten += s.length;
|
|
@@ -5503,8 +5666,9 @@ var JobRegistry = class {
|
|
|
5503
5666
|
${job.output.slice(start)}`;
|
|
5504
5667
|
}
|
|
5505
5668
|
if (!readyMatched) {
|
|
5669
|
+
recentForReady = (recentForReady + s).slice(-READY_WINDOW);
|
|
5506
5670
|
for (const re of READY_SIGNALS) {
|
|
5507
|
-
if (re.test(
|
|
5671
|
+
if (re.test(recentForReady)) {
|
|
5508
5672
|
readyMatched = true;
|
|
5509
5673
|
job.signalReady();
|
|
5510
5674
|
break;
|
|
@@ -6188,6 +6352,7 @@ ${r.output}` : header;
|
|
|
6188
6352
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
6189
6353
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
6190
6354
|
var DEFAULT_TOPK = 5;
|
|
6355
|
+
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
6191
6356
|
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";
|
|
6192
6357
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
6193
6358
|
async function webSearch(query, opts = {}) {
|
|
@@ -6267,7 +6432,13 @@ async function webFetch(url, opts = {}) {
|
|
|
6267
6432
|
}
|
|
6268
6433
|
if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
|
|
6269
6434
|
const contentType = resp.headers.get("content-type") ?? "";
|
|
6270
|
-
const
|
|
6435
|
+
const declaredLen = Number(resp.headers.get("content-length") ?? "");
|
|
6436
|
+
if (Number.isFinite(declaredLen) && declaredLen > FETCH_MAX_BYTES) {
|
|
6437
|
+
throw new Error(
|
|
6438
|
+
`web_fetch refused: content-length ${declaredLen} bytes exceeds ${FETCH_MAX_BYTES}-byte cap (${url})`
|
|
6439
|
+
);
|
|
6440
|
+
}
|
|
6441
|
+
const raw = await readBodyCapped(resp, FETCH_MAX_BYTES);
|
|
6271
6442
|
const title = extractTitle(raw);
|
|
6272
6443
|
const text = contentType.includes("text/html") ? htmlToText(raw) : raw;
|
|
6273
6444
|
const truncated = text.length > maxChars;
|
|
@@ -6276,6 +6447,37 @@ async function webFetch(url, opts = {}) {
|
|
|
6276
6447
|
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
6277
6448
|
return { url, title, text: finalText, truncated };
|
|
6278
6449
|
}
|
|
6450
|
+
async function readBodyCapped(resp, maxBytes) {
|
|
6451
|
+
if (!resp.body) return await resp.text();
|
|
6452
|
+
const reader = resp.body.getReader();
|
|
6453
|
+
const decoder = new TextDecoder("utf-8");
|
|
6454
|
+
let total = 0;
|
|
6455
|
+
let out = "";
|
|
6456
|
+
try {
|
|
6457
|
+
while (true) {
|
|
6458
|
+
const { value, done } = await reader.read();
|
|
6459
|
+
if (done) break;
|
|
6460
|
+
total += value.byteLength;
|
|
6461
|
+
if (total > maxBytes) {
|
|
6462
|
+
try {
|
|
6463
|
+
await reader.cancel();
|
|
6464
|
+
} catch {
|
|
6465
|
+
}
|
|
6466
|
+
throw new Error(
|
|
6467
|
+
`web_fetch refused: response body exceeded ${maxBytes}-byte cap (${total} bytes seen)`
|
|
6468
|
+
);
|
|
6469
|
+
}
|
|
6470
|
+
out += decoder.decode(value, { stream: true });
|
|
6471
|
+
}
|
|
6472
|
+
out += decoder.decode();
|
|
6473
|
+
} finally {
|
|
6474
|
+
try {
|
|
6475
|
+
reader.releaseLock();
|
|
6476
|
+
} catch {
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
return out;
|
|
6480
|
+
}
|
|
6279
6481
|
function htmlToText(html) {
|
|
6280
6482
|
let s = html;
|
|
6281
6483
|
s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
@@ -6889,6 +7091,107 @@ function truncate(s, n) {
|
|
|
6889
7091
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
6890
7092
|
}
|
|
6891
7093
|
|
|
7094
|
+
// src/version.ts
|
|
7095
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
|
|
7096
|
+
import { homedir as homedir5 } from "os";
|
|
7097
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
7098
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7099
|
+
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
7100
|
+
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7101
|
+
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
7102
|
+
function readPackageVersion() {
|
|
7103
|
+
try {
|
|
7104
|
+
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
7105
|
+
for (let i = 0; i < 6; i++) {
|
|
7106
|
+
const p = join9(dir, "package.json");
|
|
7107
|
+
if (existsSync9(p)) {
|
|
7108
|
+
const pkg = JSON.parse(readFileSync10(p, "utf8"));
|
|
7109
|
+
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
7110
|
+
return pkg.version;
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7113
|
+
const parent = dirname4(dir);
|
|
7114
|
+
if (parent === dir) break;
|
|
7115
|
+
dir = parent;
|
|
7116
|
+
}
|
|
7117
|
+
} catch {
|
|
7118
|
+
}
|
|
7119
|
+
return "0.0.0-dev";
|
|
7120
|
+
}
|
|
7121
|
+
var VERSION = readPackageVersion();
|
|
7122
|
+
function cachePath(homeDirOverride) {
|
|
7123
|
+
return join9(homeDirOverride ?? homedir5(), ".reasonix", "version-cache.json");
|
|
7124
|
+
}
|
|
7125
|
+
function readCache(homeDirOverride) {
|
|
7126
|
+
try {
|
|
7127
|
+
const raw = readFileSync10(cachePath(homeDirOverride), "utf8");
|
|
7128
|
+
const parsed = JSON.parse(raw);
|
|
7129
|
+
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
7130
|
+
return parsed;
|
|
7131
|
+
}
|
|
7132
|
+
} catch {
|
|
7133
|
+
}
|
|
7134
|
+
return null;
|
|
7135
|
+
}
|
|
7136
|
+
function writeCache(entry, homeDirOverride) {
|
|
7137
|
+
try {
|
|
7138
|
+
const p = cachePath(homeDirOverride);
|
|
7139
|
+
mkdirSync3(dirname4(p), { recursive: true });
|
|
7140
|
+
writeFileSync3(p, JSON.stringify(entry), "utf8");
|
|
7141
|
+
} catch {
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
async function getLatestVersion(opts = {}) {
|
|
7145
|
+
const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
|
|
7146
|
+
if (!opts.force) {
|
|
7147
|
+
const cached2 = readCache(opts.homeDir);
|
|
7148
|
+
if (cached2 && Date.now() - cached2.checkedAt < ttl) return cached2.version;
|
|
7149
|
+
}
|
|
7150
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
7151
|
+
if (!fetchImpl) return null;
|
|
7152
|
+
const url = opts.registryUrl ?? REGISTRY_URL;
|
|
7153
|
+
const timeout = opts.timeoutMs ?? LATEST_FETCH_TIMEOUT_MS;
|
|
7154
|
+
const controller = new AbortController();
|
|
7155
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
7156
|
+
try {
|
|
7157
|
+
const res = await fetchImpl(url, {
|
|
7158
|
+
signal: controller.signal,
|
|
7159
|
+
headers: { accept: "application/json" }
|
|
7160
|
+
});
|
|
7161
|
+
if (!res.ok) return null;
|
|
7162
|
+
const body = await res.json();
|
|
7163
|
+
if (typeof body.version !== "string") return null;
|
|
7164
|
+
writeCache({ version: body.version, checkedAt: Date.now() }, opts.homeDir);
|
|
7165
|
+
return body.version;
|
|
7166
|
+
} catch {
|
|
7167
|
+
return null;
|
|
7168
|
+
} finally {
|
|
7169
|
+
clearTimeout(timer);
|
|
7170
|
+
}
|
|
7171
|
+
}
|
|
7172
|
+
function compareVersions(a, b) {
|
|
7173
|
+
const [aCore = "0", aPre = ""] = a.split("-", 2);
|
|
7174
|
+
const [bCore = "0", bPre = ""] = b.split("-", 2);
|
|
7175
|
+
const aParts = aCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
7176
|
+
const bParts = bCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
7177
|
+
for (let i = 0; i < 3; i++) {
|
|
7178
|
+
const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
7179
|
+
if (diff !== 0) return diff;
|
|
7180
|
+
}
|
|
7181
|
+
if (!aPre && !bPre) return 0;
|
|
7182
|
+
if (!aPre) return 1;
|
|
7183
|
+
if (!bPre) return -1;
|
|
7184
|
+
return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
|
|
7185
|
+
}
|
|
7186
|
+
function isNpxInstall() {
|
|
7187
|
+
const bin = process.argv[1] ?? "";
|
|
7188
|
+
if (/[/\\]_npx[/\\]/.test(bin)) return true;
|
|
7189
|
+
if (/[/\\]\.pnpm[/\\]/.test(bin) && /dlx/i.test(bin)) return true;
|
|
7190
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
7191
|
+
if (ua.includes("npx/")) return true;
|
|
7192
|
+
return false;
|
|
7193
|
+
}
|
|
7194
|
+
|
|
6892
7195
|
// src/mcp/types.ts
|
|
6893
7196
|
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
6894
7197
|
function isJsonRpcError(msg) {
|
|
@@ -6917,7 +7220,7 @@ var McpClient = class {
|
|
|
6917
7220
|
nextProgressToken = 1;
|
|
6918
7221
|
constructor(opts) {
|
|
6919
7222
|
this.transport = opts.transport;
|
|
6920
|
-
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version:
|
|
7223
|
+
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: VERSION };
|
|
6921
7224
|
this.requestTimeoutMs = opts.requestTimeoutMs ?? 6e4;
|
|
6922
7225
|
}
|
|
6923
7226
|
/** Server's advertised capabilities, available after initialize(). */
|
|
@@ -7657,8 +7960,8 @@ async function trySection(load) {
|
|
|
7657
7960
|
}
|
|
7658
7961
|
|
|
7659
7962
|
// src/code/edit-blocks.ts
|
|
7660
|
-
import { existsSync as
|
|
7661
|
-
import { dirname as
|
|
7963
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
7964
|
+
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
7662
7965
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
7663
7966
|
function parseEditBlocks(text) {
|
|
7664
7967
|
const out = [];
|
|
@@ -7686,7 +7989,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
7686
7989
|
};
|
|
7687
7990
|
}
|
|
7688
7991
|
const searchEmpty = block.search.length === 0;
|
|
7689
|
-
const exists =
|
|
7992
|
+
const exists = existsSync10(absTarget);
|
|
7690
7993
|
try {
|
|
7691
7994
|
if (!exists) {
|
|
7692
7995
|
if (!searchEmpty) {
|
|
@@ -7696,11 +7999,11 @@ function applyEditBlock(block, rootDir) {
|
|
|
7696
7999
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
7697
8000
|
};
|
|
7698
8001
|
}
|
|
7699
|
-
|
|
7700
|
-
|
|
8002
|
+
mkdirSync4(dirname5(absTarget), { recursive: true });
|
|
8003
|
+
writeFileSync4(absTarget, block.replace, "utf8");
|
|
7701
8004
|
return { path: block.path, status: "created" };
|
|
7702
8005
|
}
|
|
7703
|
-
const content =
|
|
8006
|
+
const content = readFileSync11(absTarget, "utf8");
|
|
7704
8007
|
if (searchEmpty) {
|
|
7705
8008
|
return {
|
|
7706
8009
|
path: block.path,
|
|
@@ -7717,7 +8020,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
7717
8020
|
};
|
|
7718
8021
|
}
|
|
7719
8022
|
const replaced = `${content.slice(0, idx)}${block.replace}${content.slice(idx + block.search.length)}`;
|
|
7720
|
-
|
|
8023
|
+
writeFileSync4(absTarget, replaced, "utf8");
|
|
7721
8024
|
return { path: block.path, status: "applied" };
|
|
7722
8025
|
} catch (err) {
|
|
7723
8026
|
return { path: block.path, status: "error", message: err.message };
|
|
@@ -7734,12 +8037,12 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
7734
8037
|
if (seen.has(b.path)) continue;
|
|
7735
8038
|
seen.add(b.path);
|
|
7736
8039
|
const abs = resolve8(absRoot, b.path);
|
|
7737
|
-
if (!
|
|
8040
|
+
if (!existsSync10(abs)) {
|
|
7738
8041
|
snapshots.push({ path: b.path, prevContent: null });
|
|
7739
8042
|
continue;
|
|
7740
8043
|
}
|
|
7741
8044
|
try {
|
|
7742
|
-
snapshots.push({ path: b.path, prevContent:
|
|
8045
|
+
snapshots.push({ path: b.path, prevContent: readFileSync11(abs, "utf8") });
|
|
7743
8046
|
} catch {
|
|
7744
8047
|
snapshots.push({ path: b.path, prevContent: null });
|
|
7745
8048
|
}
|
|
@@ -7759,14 +8062,14 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
7759
8062
|
}
|
|
7760
8063
|
try {
|
|
7761
8064
|
if (snap.prevContent === null) {
|
|
7762
|
-
if (
|
|
8065
|
+
if (existsSync10(abs)) unlinkSync3(abs);
|
|
7763
8066
|
return {
|
|
7764
8067
|
path: snap.path,
|
|
7765
8068
|
status: "applied",
|
|
7766
8069
|
message: "removed (the edit had created it)"
|
|
7767
8070
|
};
|
|
7768
8071
|
}
|
|
7769
|
-
|
|
8072
|
+
writeFileSync4(abs, snap.prevContent, "utf8");
|
|
7770
8073
|
return {
|
|
7771
8074
|
path: snap.path,
|
|
7772
8075
|
status: "applied",
|
|
@@ -7782,8 +8085,8 @@ function sep() {
|
|
|
7782
8085
|
}
|
|
7783
8086
|
|
|
7784
8087
|
// src/code/prompt.ts
|
|
7785
|
-
import { existsSync as
|
|
7786
|
-
import { join as
|
|
8088
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
8089
|
+
import { join as join10 } from "path";
|
|
7787
8090
|
var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, edit_file, list_directory, directory_tree, search_files, search_content, get_file_info) rooted at the user's working directory, plus run_command / run_background for shell.
|
|
7788
8091
|
|
|
7789
8092
|
# Cite or shut up \u2014 non-negotiable
|
|
@@ -7989,11 +8292,11 @@ If \`semantic_search\` returns nothing useful (low scores, off-topic), THEN fall
|
|
|
7989
8292
|
function codeSystemPrompt(rootDir, opts = {}) {
|
|
7990
8293
|
const base = opts.hasSemanticSearch ? `${CODE_SYSTEM_PROMPT}${SEMANTIC_SEARCH_ROUTING}` : CODE_SYSTEM_PROMPT;
|
|
7991
8294
|
const withMemory = applyMemoryStack(base, rootDir);
|
|
7992
|
-
const gitignorePath =
|
|
7993
|
-
if (!
|
|
8295
|
+
const gitignorePath = join10(rootDir, ".gitignore");
|
|
8296
|
+
if (!existsSync11(gitignorePath)) return withMemory;
|
|
7994
8297
|
let content;
|
|
7995
8298
|
try {
|
|
7996
|
-
content =
|
|
8299
|
+
content = readFileSync12(gitignorePath, "utf8");
|
|
7997
8300
|
} catch {
|
|
7998
8301
|
return withMemory;
|
|
7999
8302
|
}
|
|
@@ -8013,15 +8316,15 @@ ${truncated}
|
|
|
8013
8316
|
}
|
|
8014
8317
|
|
|
8015
8318
|
// src/config.ts
|
|
8016
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
8017
|
-
import { homedir as
|
|
8018
|
-
import { dirname as
|
|
8319
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
8320
|
+
import { homedir as homedir6 } from "os";
|
|
8321
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
8019
8322
|
function defaultConfigPath() {
|
|
8020
|
-
return
|
|
8323
|
+
return join11(homedir6(), ".reasonix", "config.json");
|
|
8021
8324
|
}
|
|
8022
8325
|
function readConfig(path = defaultConfigPath()) {
|
|
8023
8326
|
try {
|
|
8024
|
-
const raw =
|
|
8327
|
+
const raw = readFileSync13(path, "utf8");
|
|
8025
8328
|
const parsed = JSON.parse(raw);
|
|
8026
8329
|
if (parsed && typeof parsed === "object") return parsed;
|
|
8027
8330
|
} catch {
|
|
@@ -8029,8 +8332,8 @@ function readConfig(path = defaultConfigPath()) {
|
|
|
8029
8332
|
return {};
|
|
8030
8333
|
}
|
|
8031
8334
|
function writeConfig(cfg, path = defaultConfigPath()) {
|
|
8032
|
-
|
|
8033
|
-
|
|
8335
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
8336
|
+
writeFileSync5(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
8034
8337
|
try {
|
|
8035
8338
|
chmodSync2(path, 384);
|
|
8036
8339
|
} catch {
|
|
@@ -8055,113 +8358,53 @@ function redactKey(key) {
|
|
|
8055
8358
|
return `${key.slice(0, 6)}\u2026${key.slice(-4)}`;
|
|
8056
8359
|
}
|
|
8057
8360
|
|
|
8058
|
-
// src/
|
|
8059
|
-
import {
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
if (existsSync11(p)) {
|
|
8072
|
-
const pkg = JSON.parse(readFileSync13(p, "utf8"));
|
|
8073
|
-
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
8074
|
-
return pkg.version;
|
|
8075
|
-
}
|
|
8076
|
-
}
|
|
8077
|
-
const parent = dirname6(dir);
|
|
8078
|
-
if (parent === dir) break;
|
|
8079
|
-
dir = parent;
|
|
8080
|
-
}
|
|
8081
|
-
} catch {
|
|
8082
|
-
}
|
|
8083
|
-
return "0.0.0-dev";
|
|
8084
|
-
}
|
|
8085
|
-
var VERSION = readPackageVersion();
|
|
8086
|
-
function cachePath(homeDirOverride) {
|
|
8087
|
-
return join11(homeDirOverride ?? homedir6(), ".reasonix", "version-cache.json");
|
|
8361
|
+
// src/usage.ts
|
|
8362
|
+
import {
|
|
8363
|
+
appendFileSync as appendFileSync2,
|
|
8364
|
+
existsSync as existsSync12,
|
|
8365
|
+
mkdirSync as mkdirSync6,
|
|
8366
|
+
readFileSync as readFileSync14,
|
|
8367
|
+
statSync as statSync5,
|
|
8368
|
+
writeFileSync as writeFileSync6
|
|
8369
|
+
} from "fs";
|
|
8370
|
+
import { homedir as homedir7 } from "os";
|
|
8371
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
8372
|
+
function defaultUsageLogPath(homeDirOverride) {
|
|
8373
|
+
return join12(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
|
|
8088
8374
|
}
|
|
8089
|
-
|
|
8375
|
+
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
8376
|
+
var USAGE_RETENTION_DAYS = 365;
|
|
8377
|
+
function compactUsageLogIfLarge(path, now) {
|
|
8378
|
+
let size;
|
|
8090
8379
|
try {
|
|
8091
|
-
|
|
8092
|
-
const parsed = JSON.parse(raw);
|
|
8093
|
-
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
8094
|
-
return parsed;
|
|
8095
|
-
}
|
|
8380
|
+
size = statSync5(path).size;
|
|
8096
8381
|
} catch {
|
|
8382
|
+
return;
|
|
8097
8383
|
}
|
|
8098
|
-
return
|
|
8099
|
-
|
|
8100
|
-
|
|
8384
|
+
if (size < USAGE_COMPACTION_THRESHOLD_BYTES) return;
|
|
8385
|
+
const cutoff = now - USAGE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
8386
|
+
let raw;
|
|
8101
8387
|
try {
|
|
8102
|
-
|
|
8103
|
-
mkdirSync5(dirname6(p), { recursive: true });
|
|
8104
|
-
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
8388
|
+
raw = readFileSync14(path, "utf8");
|
|
8105
8389
|
} catch {
|
|
8390
|
+
return;
|
|
8106
8391
|
}
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
const
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8392
|
+
const lines = raw.split(/\r?\n/);
|
|
8393
|
+
const kept = [];
|
|
8394
|
+
for (const line of lines) {
|
|
8395
|
+
if (!line.trim()) continue;
|
|
8396
|
+
try {
|
|
8397
|
+
const rec = JSON.parse(line);
|
|
8398
|
+
if (isValidRecord(rec) && rec.ts >= cutoff) kept.push(line);
|
|
8399
|
+
} catch {
|
|
8400
|
+
}
|
|
8113
8401
|
}
|
|
8114
|
-
|
|
8115
|
-
if (!fetchImpl) return null;
|
|
8116
|
-
const url = opts.registryUrl ?? REGISTRY_URL;
|
|
8117
|
-
const timeout = opts.timeoutMs ?? LATEST_FETCH_TIMEOUT_MS;
|
|
8118
|
-
const controller = new AbortController();
|
|
8119
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
8402
|
+
if (kept.length === lines.filter((l) => l.trim()).length) return;
|
|
8120
8403
|
try {
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
headers: { accept: "application/json" }
|
|
8124
|
-
});
|
|
8125
|
-
if (!res.ok) return null;
|
|
8126
|
-
const body = await res.json();
|
|
8127
|
-
if (typeof body.version !== "string") return null;
|
|
8128
|
-
writeCache({ version: body.version, checkedAt: Date.now() }, opts.homeDir);
|
|
8129
|
-
return body.version;
|
|
8404
|
+
writeFileSync6(path, kept.length > 0 ? `${kept.join("\n")}
|
|
8405
|
+
` : "", "utf8");
|
|
8130
8406
|
} catch {
|
|
8131
|
-
return null;
|
|
8132
|
-
} finally {
|
|
8133
|
-
clearTimeout(timer);
|
|
8134
|
-
}
|
|
8135
|
-
}
|
|
8136
|
-
function compareVersions(a, b) {
|
|
8137
|
-
const [aCore = "0", aPre = ""] = a.split("-", 2);
|
|
8138
|
-
const [bCore = "0", bPre = ""] = b.split("-", 2);
|
|
8139
|
-
const aParts = aCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
8140
|
-
const bParts = bCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
8141
|
-
for (let i = 0; i < 3; i++) {
|
|
8142
|
-
const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
8143
|
-
if (diff !== 0) return diff;
|
|
8144
8407
|
}
|
|
8145
|
-
if (!aPre && !bPre) return 0;
|
|
8146
|
-
if (!aPre) return 1;
|
|
8147
|
-
if (!bPre) return -1;
|
|
8148
|
-
return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
|
|
8149
|
-
}
|
|
8150
|
-
function isNpxInstall() {
|
|
8151
|
-
const bin = process.argv[1] ?? "";
|
|
8152
|
-
if (/[/\\]_npx[/\\]/.test(bin)) return true;
|
|
8153
|
-
if (/[/\\]\.pnpm[/\\]/.test(bin) && /dlx/i.test(bin)) return true;
|
|
8154
|
-
const ua = process.env.npm_config_user_agent ?? "";
|
|
8155
|
-
if (ua.includes("npx/")) return true;
|
|
8156
|
-
return false;
|
|
8157
|
-
}
|
|
8158
|
-
|
|
8159
|
-
// src/usage.ts
|
|
8160
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
|
|
8161
|
-
import { homedir as homedir7 } from "os";
|
|
8162
|
-
import { dirname as dirname7, join as join12 } from "path";
|
|
8163
|
-
function defaultUsageLogPath(homeDirOverride) {
|
|
8164
|
-
return join12(homeDirOverride ?? homedir7(), ".reasonix", "usage.jsonl");
|
|
8165
8408
|
}
|
|
8166
8409
|
function appendUsage(input) {
|
|
8167
8410
|
const record = {
|
|
@@ -8182,6 +8425,7 @@ function appendUsage(input) {
|
|
|
8182
8425
|
mkdirSync6(dirname7(path), { recursive: true });
|
|
8183
8426
|
appendFileSync2(path, `${JSON.stringify(record)}
|
|
8184
8427
|
`, "utf8");
|
|
8428
|
+
compactUsageLogIfLarge(path, record.ts);
|
|
8185
8429
|
} catch {
|
|
8186
8430
|
}
|
|
8187
8431
|
return record;
|
|
@@ -8401,6 +8645,7 @@ export {
|
|
|
8401
8645
|
isPlanStateEmpty,
|
|
8402
8646
|
isPlausibleKey,
|
|
8403
8647
|
listFilesSync,
|
|
8648
|
+
listFilesWithStatsAsync,
|
|
8404
8649
|
listFilesWithStatsSync,
|
|
8405
8650
|
listSessions,
|
|
8406
8651
|
loadApiKey,
|