reasonix 0.12.15 → 0.12.19
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 +496 -208
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +346 -145
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -666,6 +666,7 @@ function matchesTool(hook, toolName) {
|
|
|
666
666
|
return false;
|
|
667
667
|
}
|
|
668
668
|
}
|
|
669
|
+
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
669
670
|
function defaultSpawner(input) {
|
|
670
671
|
return new Promise((resolve13) => {
|
|
671
672
|
const child = spawn(input.command, {
|
|
@@ -673,8 +674,11 @@ function defaultSpawner(input) {
|
|
|
673
674
|
shell: true,
|
|
674
675
|
stdio: ["pipe", "pipe", "pipe"]
|
|
675
676
|
});
|
|
676
|
-
|
|
677
|
-
|
|
677
|
+
const stdoutChunks = [];
|
|
678
|
+
const stderrChunks = [];
|
|
679
|
+
let stdoutBytes = 0;
|
|
680
|
+
let stderrBytes = 0;
|
|
681
|
+
let truncated = false;
|
|
678
682
|
let timedOut = false;
|
|
679
683
|
const timer = setTimeout(() => {
|
|
680
684
|
timedOut = true;
|
|
@@ -686,29 +690,46 @@ function defaultSpawner(input) {
|
|
|
686
690
|
}
|
|
687
691
|
}, 500);
|
|
688
692
|
}, input.timeoutMs);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
693
|
+
const onChunk = (kind, chunk) => {
|
|
694
|
+
const target = kind === "stdout" ? stdoutChunks : stderrChunks;
|
|
695
|
+
const seen = kind === "stdout" ? stdoutBytes : stderrBytes;
|
|
696
|
+
if (seen >= HOOK_OUTPUT_CAP_BYTES) {
|
|
697
|
+
truncated = true;
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const remaining = HOOK_OUTPUT_CAP_BYTES - seen;
|
|
701
|
+
if (chunk.length > remaining) {
|
|
702
|
+
target.push(chunk.subarray(0, remaining));
|
|
703
|
+
if (kind === "stdout") stdoutBytes = HOOK_OUTPUT_CAP_BYTES;
|
|
704
|
+
else stderrBytes = HOOK_OUTPUT_CAP_BYTES;
|
|
705
|
+
truncated = true;
|
|
706
|
+
} else {
|
|
707
|
+
target.push(chunk);
|
|
708
|
+
if (kind === "stdout") stdoutBytes += chunk.length;
|
|
709
|
+
else stderrBytes += chunk.length;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
child.stdout.on("data", (chunk) => onChunk("stdout", chunk));
|
|
713
|
+
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
695
714
|
child.once("error", (err) => {
|
|
696
715
|
clearTimeout(timer);
|
|
697
716
|
resolve13({
|
|
698
717
|
exitCode: null,
|
|
699
|
-
stdout:
|
|
700
|
-
stderr,
|
|
718
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
719
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
701
720
|
timedOut: false,
|
|
702
|
-
spawnError: err
|
|
721
|
+
spawnError: err,
|
|
722
|
+
truncated: truncated || void 0
|
|
703
723
|
});
|
|
704
724
|
});
|
|
705
725
|
child.once("close", (code) => {
|
|
706
726
|
clearTimeout(timer);
|
|
707
727
|
resolve13({
|
|
708
728
|
exitCode: code,
|
|
709
|
-
stdout:
|
|
710
|
-
stderr:
|
|
711
|
-
timedOut
|
|
729
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
730
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
731
|
+
timedOut,
|
|
732
|
+
truncated: truncated || void 0
|
|
712
733
|
});
|
|
713
734
|
});
|
|
714
735
|
try {
|
|
@@ -723,7 +744,8 @@ function formatHookOutcomeMessage(outcome) {
|
|
|
723
744
|
const detail = (outcome.stderr || outcome.stdout || "").trim();
|
|
724
745
|
const tag = `${outcome.hook.scope}/${outcome.hook.event}`;
|
|
725
746
|
const cmd = outcome.hook.command.length > 60 ? `${outcome.hook.command.slice(0, 60)}\u2026` : outcome.hook.command;
|
|
726
|
-
const
|
|
747
|
+
const truncTag = outcome.truncated ? " (output truncated at 256KB)" : "";
|
|
748
|
+
const head = `hook ${tag} \`${cmd}\` ${outcome.decision}${truncTag}`;
|
|
727
749
|
return detail ? `${head}: ${detail}` : head;
|
|
728
750
|
}
|
|
729
751
|
function decideOutcome(event, raw) {
|
|
@@ -754,7 +776,8 @@ async function runHooks(opts) {
|
|
|
754
776
|
exitCode: raw.exitCode,
|
|
755
777
|
stdout: raw.stdout,
|
|
756
778
|
stderr: raw.stderr || (raw.spawnError ? raw.spawnError.message : "") || (raw.timedOut ? `hook timed out after ${timeoutMs}ms` : ""),
|
|
757
|
-
durationMs: Date.now() - start
|
|
779
|
+
durationMs: Date.now() - start,
|
|
780
|
+
truncated: raw.truncated
|
|
758
781
|
});
|
|
759
782
|
if (decision === "block") {
|
|
760
783
|
blocked = true;
|
|
@@ -1302,6 +1325,23 @@ var ImmutablePrefix = class {
|
|
|
1302
1325
|
*/
|
|
1303
1326
|
_toolSpecs;
|
|
1304
1327
|
fewShots;
|
|
1328
|
+
/**
|
|
1329
|
+
* Cached SHA-256 of the prefix payload. Computed lazily on first
|
|
1330
|
+
* `fingerprint` access, invalidated only by mutations that go
|
|
1331
|
+
* through `addTool` (the one legitimate post-construction mutation
|
|
1332
|
+
* path). The TUI reads `fingerprint` on every render — without the
|
|
1333
|
+
* cache, that means a fresh `JSON.stringify` + sha256 over the
|
|
1334
|
+
* full prefix (system prompt + tools list + few-shots, typically
|
|
1335
|
+
* 5-10KB) on every keystroke.
|
|
1336
|
+
*
|
|
1337
|
+
* The lazy-init also acts as a cheap drift guard: if some future
|
|
1338
|
+
* code path mutates `_toolSpecs` directly without going through
|
|
1339
|
+
* `addTool`, `fingerprint` will return the stale cached value
|
|
1340
|
+
* while the actual prefix sent to DeepSeek diverges — the cache
|
|
1341
|
+
* miss would be the first symptom. {@link verifyFingerprint}
|
|
1342
|
+
* lets dev / test code assert the cache matches reality.
|
|
1343
|
+
*/
|
|
1344
|
+
_fingerprintCache = null;
|
|
1305
1345
|
constructor(opts) {
|
|
1306
1346
|
this.system = opts.system;
|
|
1307
1347
|
this._toolSpecs = [...opts.toolSpecs ?? []];
|
|
@@ -1327,9 +1367,33 @@ var ImmutablePrefix = class {
|
|
|
1327
1367
|
if (!name) return false;
|
|
1328
1368
|
if (this._toolSpecs.some((t2) => t2.function?.name === name)) return false;
|
|
1329
1369
|
this._toolSpecs.push(spec);
|
|
1370
|
+
this._fingerprintCache = null;
|
|
1330
1371
|
return true;
|
|
1331
1372
|
}
|
|
1332
1373
|
get fingerprint() {
|
|
1374
|
+
if (this._fingerprintCache !== null) return this._fingerprintCache;
|
|
1375
|
+
this._fingerprintCache = this.computeFingerprint();
|
|
1376
|
+
return this._fingerprintCache;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Recompute the fingerprint from scratch and assert it matches the
|
|
1380
|
+
* cached value. Returns the freshly-computed hash on success; throws
|
|
1381
|
+
* with a diff if the cache drifted, which always indicates a bug —
|
|
1382
|
+
* either a non-`addTool` mutation path was added, or `addTool`
|
|
1383
|
+
* forgot to invalidate the cache. Dev / test only; the live loop
|
|
1384
|
+
* doesn't call this on the hot path.
|
|
1385
|
+
*/
|
|
1386
|
+
verifyFingerprint() {
|
|
1387
|
+
const fresh = this.computeFingerprint();
|
|
1388
|
+
if (this._fingerprintCache !== null && this._fingerprintCache !== fresh) {
|
|
1389
|
+
throw new Error(
|
|
1390
|
+
`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.`
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
this._fingerprintCache = fresh;
|
|
1394
|
+
return fresh;
|
|
1395
|
+
}
|
|
1396
|
+
computeFingerprint() {
|
|
1333
1397
|
const blob = JSON.stringify({
|
|
1334
1398
|
system: this.system,
|
|
1335
1399
|
tools: this._toolSpecs,
|
|
@@ -1748,15 +1812,25 @@ function listSessions() {
|
|
|
1748
1812
|
const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1749
1813
|
return files.map((file) => {
|
|
1750
1814
|
const path5 = join4(dir, file);
|
|
1751
|
-
const
|
|
1815
|
+
const stat2 = statSync(path5);
|
|
1752
1816
|
const name = file.replace(/\.jsonl$/, "");
|
|
1753
1817
|
const messageCount = countLines(path5);
|
|
1754
|
-
return { name, path: path5, size:
|
|
1818
|
+
return { name, path: path5, size: stat2.size, messageCount, mtime: stat2.mtime };
|
|
1755
1819
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
1756
1820
|
} catch {
|
|
1757
1821
|
return [];
|
|
1758
1822
|
}
|
|
1759
1823
|
}
|
|
1824
|
+
function pruneStaleSessions(daysOld = 90) {
|
|
1825
|
+
const cutoff = Date.now() - daysOld * 24 * 60 * 60 * 1e3;
|
|
1826
|
+
const deleted = [];
|
|
1827
|
+
for (const s of listSessions()) {
|
|
1828
|
+
if (s.mtime.getTime() < cutoff) {
|
|
1829
|
+
if (deleteSession(s.name)) deleted.push(s.name);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return deleted;
|
|
1833
|
+
}
|
|
1760
1834
|
function deleteSession(name) {
|
|
1761
1835
|
const path5 = sessionPath(name);
|
|
1762
1836
|
try {
|
|
@@ -3252,6 +3326,7 @@ function extractDeepSeekErrorMessage(body) {
|
|
|
3252
3326
|
|
|
3253
3327
|
// src/at-mentions.ts
|
|
3254
3328
|
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
3329
|
+
import { readdir, stat } from "fs/promises";
|
|
3255
3330
|
import { isAbsolute, join as join5, relative, resolve } from "path";
|
|
3256
3331
|
var DEFAULT_AT_MENTION_MAX_BYTES = 64 * 1024;
|
|
3257
3332
|
var DEFAULT_PICKER_IGNORE_DIRS = [
|
|
@@ -3306,6 +3381,58 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3306
3381
|
walk3(rootAbs, "");
|
|
3307
3382
|
return out;
|
|
3308
3383
|
}
|
|
3384
|
+
async function listFilesWithStatsAsync(root, opts = {}) {
|
|
3385
|
+
const maxResults = Math.max(1, opts.maxResults ?? 500);
|
|
3386
|
+
const ignore = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3387
|
+
const rootAbs = resolve(root);
|
|
3388
|
+
const out = [];
|
|
3389
|
+
const walk3 = async (dirAbs, dirRel) => {
|
|
3390
|
+
if (out.length >= maxResults) return;
|
|
3391
|
+
let entries;
|
|
3392
|
+
try {
|
|
3393
|
+
entries = await readdir(dirAbs, { withFileTypes: true });
|
|
3394
|
+
} catch {
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3398
|
+
const fileEnts = [];
|
|
3399
|
+
for (const ent of entries) {
|
|
3400
|
+
if (out.length >= maxResults) break;
|
|
3401
|
+
if (ent.isDirectory()) {
|
|
3402
|
+
if (ent.name.startsWith(".") || ignore.has(ent.name)) continue;
|
|
3403
|
+
if (fileEnts.length > 0) {
|
|
3404
|
+
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3405
|
+
fileEnts.length = 0;
|
|
3406
|
+
if (out.length >= maxResults) return;
|
|
3407
|
+
}
|
|
3408
|
+
await walk3(join5(dirAbs, ent.name), dirRel ? `${dirRel}/${ent.name}` : ent.name);
|
|
3409
|
+
} else if (ent.isFile()) {
|
|
3410
|
+
fileEnts.push(ent);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
if (fileEnts.length > 0 && out.length < maxResults) {
|
|
3414
|
+
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3415
|
+
}
|
|
3416
|
+
};
|
|
3417
|
+
await walk3(rootAbs, "");
|
|
3418
|
+
return out;
|
|
3419
|
+
}
|
|
3420
|
+
async function statBatch(ents, dirAbs, dirRel, out, maxResults) {
|
|
3421
|
+
const remaining = Math.max(0, maxResults - out.length);
|
|
3422
|
+
const batch = ents.slice(0, remaining);
|
|
3423
|
+
const stats2 = await Promise.all(
|
|
3424
|
+
batch.map(
|
|
3425
|
+
(e) => stat(join5(dirAbs, e.name)).then((s) => s.mtimeMs).catch(() => 0)
|
|
3426
|
+
)
|
|
3427
|
+
);
|
|
3428
|
+
for (let i = 0; i < batch.length; i++) {
|
|
3429
|
+
const ent = batch[i];
|
|
3430
|
+
out.push({
|
|
3431
|
+
path: dirRel ? `${dirRel}/${ent.name}` : ent.name,
|
|
3432
|
+
mtimeMs: stats2[i] ?? 0
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3309
3436
|
var AT_PICKER_PREFIX = /(?:^|\s)@([a-zA-Z0-9_./\\-]*)$/;
|
|
3310
3437
|
function detectAtPicker(input) {
|
|
3311
3438
|
const m = AT_PICKER_PREFIX.exec(input);
|
|
@@ -3666,8 +3793,8 @@ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_L
|
|
|
3666
3793
|
},
|
|
3667
3794
|
fn: async (args) => {
|
|
3668
3795
|
const abs = safePath(args.path);
|
|
3669
|
-
const
|
|
3670
|
-
if (
|
|
3796
|
+
const stat2 = await fs.stat(abs);
|
|
3797
|
+
if (stat2.isDirectory()) {
|
|
3671
3798
|
throw new Error(`not a file: ${args.path} (it's a directory)`);
|
|
3672
3799
|
}
|
|
3673
3800
|
const raw = await fs.readFile(abs);
|
|
@@ -3936,13 +4063,13 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
3936
4063
|
if (nameFilter && !e.name.toLowerCase().includes(nameFilter)) continue;
|
|
3937
4064
|
if (isLikelyBinaryByName(e.name)) continue;
|
|
3938
4065
|
const full = pathMod.join(dir, e.name);
|
|
3939
|
-
let
|
|
4066
|
+
let stat2;
|
|
3940
4067
|
try {
|
|
3941
|
-
|
|
4068
|
+
stat2 = await fs.stat(full);
|
|
3942
4069
|
} catch {
|
|
3943
4070
|
continue;
|
|
3944
4071
|
}
|
|
3945
|
-
if (
|
|
4072
|
+
if (stat2.size > 2 * 1024 * 1024) continue;
|
|
3946
4073
|
let raw;
|
|
3947
4074
|
try {
|
|
3948
4075
|
raw = await fs.readFile(full);
|
|
@@ -4923,6 +5050,8 @@ var JobRegistry = class {
|
|
|
4923
5050
|
};
|
|
4924
5051
|
this.jobs.set(id, job);
|
|
4925
5052
|
let readyMatched = false;
|
|
5053
|
+
let recentForReady = "";
|
|
5054
|
+
const READY_WINDOW = 1024;
|
|
4926
5055
|
const onData = (chunk) => {
|
|
4927
5056
|
const s = chunk.toString();
|
|
4928
5057
|
job.totalBytesWritten += s.length;
|
|
@@ -4935,8 +5064,9 @@ var JobRegistry = class {
|
|
|
4935
5064
|
${job.output.slice(start)}`;
|
|
4936
5065
|
}
|
|
4937
5066
|
if (!readyMatched) {
|
|
5067
|
+
recentForReady = (recentForReady + s).slice(-READY_WINDOW);
|
|
4938
5068
|
for (const re of READY_SIGNALS) {
|
|
4939
|
-
if (re.test(
|
|
5069
|
+
if (re.test(recentForReady)) {
|
|
4940
5070
|
readyMatched = true;
|
|
4941
5071
|
job.signalReady();
|
|
4942
5072
|
break;
|
|
@@ -5620,6 +5750,7 @@ ${r.output}` : header2;
|
|
|
5620
5750
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
5621
5751
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
5622
5752
|
var DEFAULT_TOPK = 5;
|
|
5753
|
+
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
5623
5754
|
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";
|
|
5624
5755
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
5625
5756
|
async function webSearch(query, opts = {}) {
|
|
@@ -5699,7 +5830,13 @@ async function webFetch(url, opts = {}) {
|
|
|
5699
5830
|
}
|
|
5700
5831
|
if (!resp.ok) throw new Error(`web_fetch ${resp.status} for ${url}`);
|
|
5701
5832
|
const contentType = resp.headers.get("content-type") ?? "";
|
|
5702
|
-
const
|
|
5833
|
+
const declaredLen = Number(resp.headers.get("content-length") ?? "");
|
|
5834
|
+
if (Number.isFinite(declaredLen) && declaredLen > FETCH_MAX_BYTES) {
|
|
5835
|
+
throw new Error(
|
|
5836
|
+
`web_fetch refused: content-length ${declaredLen} bytes exceeds ${FETCH_MAX_BYTES}-byte cap (${url})`
|
|
5837
|
+
);
|
|
5838
|
+
}
|
|
5839
|
+
const raw = await readBodyCapped(resp, FETCH_MAX_BYTES);
|
|
5703
5840
|
const title = extractTitle(raw);
|
|
5704
5841
|
const text = contentType.includes("text/html") ? htmlToText(raw) : raw;
|
|
5705
5842
|
const truncated = text.length > maxChars;
|
|
@@ -5708,6 +5845,37 @@ async function webFetch(url, opts = {}) {
|
|
|
5708
5845
|
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
5709
5846
|
return { url, title, text: finalText, truncated };
|
|
5710
5847
|
}
|
|
5848
|
+
async function readBodyCapped(resp, maxBytes) {
|
|
5849
|
+
if (!resp.body) return await resp.text();
|
|
5850
|
+
const reader = resp.body.getReader();
|
|
5851
|
+
const decoder = new TextDecoder("utf-8");
|
|
5852
|
+
let total = 0;
|
|
5853
|
+
let out = "";
|
|
5854
|
+
try {
|
|
5855
|
+
while (true) {
|
|
5856
|
+
const { value, done } = await reader.read();
|
|
5857
|
+
if (done) break;
|
|
5858
|
+
total += value.byteLength;
|
|
5859
|
+
if (total > maxBytes) {
|
|
5860
|
+
try {
|
|
5861
|
+
await reader.cancel();
|
|
5862
|
+
} catch {
|
|
5863
|
+
}
|
|
5864
|
+
throw new Error(
|
|
5865
|
+
`web_fetch refused: response body exceeded ${maxBytes}-byte cap (${total} bytes seen)`
|
|
5866
|
+
);
|
|
5867
|
+
}
|
|
5868
|
+
out += decoder.decode(value, { stream: true });
|
|
5869
|
+
}
|
|
5870
|
+
out += decoder.decode();
|
|
5871
|
+
} finally {
|
|
5872
|
+
try {
|
|
5873
|
+
reader.releaseLock();
|
|
5874
|
+
} catch {
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
return out;
|
|
5878
|
+
}
|
|
5711
5879
|
function htmlToText(html) {
|
|
5712
5880
|
let s = html;
|
|
5713
5881
|
s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
|
|
@@ -6352,6 +6520,107 @@ function truncate(s, n) {
|
|
|
6352
6520
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
6353
6521
|
}
|
|
6354
6522
|
|
|
6523
|
+
// src/version.ts
|
|
6524
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
6525
|
+
import { homedir as homedir4 } from "os";
|
|
6526
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
6527
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6528
|
+
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
6529
|
+
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6530
|
+
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
6531
|
+
function readPackageVersion() {
|
|
6532
|
+
try {
|
|
6533
|
+
let dir = dirname5(fileURLToPath2(import.meta.url));
|
|
6534
|
+
for (let i = 0; i < 6; i++) {
|
|
6535
|
+
const p = join7(dir, "package.json");
|
|
6536
|
+
if (existsSync6(p)) {
|
|
6537
|
+
const pkg = JSON.parse(readFileSync8(p, "utf8"));
|
|
6538
|
+
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
6539
|
+
return pkg.version;
|
|
6540
|
+
}
|
|
6541
|
+
}
|
|
6542
|
+
const parent = dirname5(dir);
|
|
6543
|
+
if (parent === dir) break;
|
|
6544
|
+
dir = parent;
|
|
6545
|
+
}
|
|
6546
|
+
} catch {
|
|
6547
|
+
}
|
|
6548
|
+
return "0.0.0-dev";
|
|
6549
|
+
}
|
|
6550
|
+
var VERSION = readPackageVersion();
|
|
6551
|
+
function cachePath(homeDirOverride) {
|
|
6552
|
+
return join7(homeDirOverride ?? homedir4(), ".reasonix", "version-cache.json");
|
|
6553
|
+
}
|
|
6554
|
+
function readCache(homeDirOverride) {
|
|
6555
|
+
try {
|
|
6556
|
+
const raw = readFileSync8(cachePath(homeDirOverride), "utf8");
|
|
6557
|
+
const parsed = JSON.parse(raw);
|
|
6558
|
+
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
6559
|
+
return parsed;
|
|
6560
|
+
}
|
|
6561
|
+
} catch {
|
|
6562
|
+
}
|
|
6563
|
+
return null;
|
|
6564
|
+
}
|
|
6565
|
+
function writeCache(entry, homeDirOverride) {
|
|
6566
|
+
try {
|
|
6567
|
+
const p = cachePath(homeDirOverride);
|
|
6568
|
+
mkdirSync3(dirname5(p), { recursive: true });
|
|
6569
|
+
writeFileSync3(p, JSON.stringify(entry), "utf8");
|
|
6570
|
+
} catch {
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
async function getLatestVersion(opts = {}) {
|
|
6574
|
+
const ttl = opts.ttlMs ?? LATEST_CACHE_TTL_MS;
|
|
6575
|
+
if (!opts.force) {
|
|
6576
|
+
const cached2 = readCache(opts.homeDir);
|
|
6577
|
+
if (cached2 && Date.now() - cached2.checkedAt < ttl) return cached2.version;
|
|
6578
|
+
}
|
|
6579
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
6580
|
+
if (!fetchImpl) return null;
|
|
6581
|
+
const url = opts.registryUrl ?? REGISTRY_URL;
|
|
6582
|
+
const timeout = opts.timeoutMs ?? LATEST_FETCH_TIMEOUT_MS;
|
|
6583
|
+
const controller = new AbortController();
|
|
6584
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
6585
|
+
try {
|
|
6586
|
+
const res = await fetchImpl(url, {
|
|
6587
|
+
signal: controller.signal,
|
|
6588
|
+
headers: { accept: "application/json" }
|
|
6589
|
+
});
|
|
6590
|
+
if (!res.ok) return null;
|
|
6591
|
+
const body = await res.json();
|
|
6592
|
+
if (typeof body.version !== "string") return null;
|
|
6593
|
+
writeCache({ version: body.version, checkedAt: Date.now() }, opts.homeDir);
|
|
6594
|
+
return body.version;
|
|
6595
|
+
} catch {
|
|
6596
|
+
return null;
|
|
6597
|
+
} finally {
|
|
6598
|
+
clearTimeout(timer);
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
function compareVersions(a, b) {
|
|
6602
|
+
const [aCore = "0", aPre = ""] = a.split("-", 2);
|
|
6603
|
+
const [bCore = "0", bPre = ""] = b.split("-", 2);
|
|
6604
|
+
const aParts = aCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
6605
|
+
const bParts = bCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
6606
|
+
for (let i = 0; i < 3; i++) {
|
|
6607
|
+
const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
6608
|
+
if (diff !== 0) return diff;
|
|
6609
|
+
}
|
|
6610
|
+
if (!aPre && !bPre) return 0;
|
|
6611
|
+
if (!aPre) return 1;
|
|
6612
|
+
if (!bPre) return -1;
|
|
6613
|
+
return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
|
|
6614
|
+
}
|
|
6615
|
+
function isNpxInstall() {
|
|
6616
|
+
const bin = process.argv[1] ?? "";
|
|
6617
|
+
if (/[/\\]_npx[/\\]/.test(bin)) return true;
|
|
6618
|
+
if (/[/\\]\.pnpm[/\\]/.test(bin) && /dlx/i.test(bin)) return true;
|
|
6619
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
6620
|
+
if (ua.includes("npx/")) return true;
|
|
6621
|
+
return false;
|
|
6622
|
+
}
|
|
6623
|
+
|
|
6355
6624
|
// src/mcp/types.ts
|
|
6356
6625
|
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
6357
6626
|
function isJsonRpcError(msg) {
|
|
@@ -6380,7 +6649,7 @@ var McpClient = class {
|
|
|
6380
6649
|
nextProgressToken = 1;
|
|
6381
6650
|
constructor(opts) {
|
|
6382
6651
|
this.transport = opts.transport;
|
|
6383
|
-
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version:
|
|
6652
|
+
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: VERSION };
|
|
6384
6653
|
this.requestTimeoutMs = opts.requestTimeoutMs ?? 6e4;
|
|
6385
6654
|
}
|
|
6386
6655
|
/** Server's advertised capabilities, available after initialize(). */
|
|
@@ -7120,8 +7389,8 @@ async function trySection(load) {
|
|
|
7120
7389
|
}
|
|
7121
7390
|
|
|
7122
7391
|
// src/code/edit-blocks.ts
|
|
7123
|
-
import { existsSync as
|
|
7124
|
-
import { dirname as
|
|
7392
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
7393
|
+
import { dirname as dirname6, resolve as resolve6 } from "path";
|
|
7125
7394
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
7126
7395
|
function parseEditBlocks(text) {
|
|
7127
7396
|
const out = [];
|
|
@@ -7149,7 +7418,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
7149
7418
|
};
|
|
7150
7419
|
}
|
|
7151
7420
|
const searchEmpty = block.search.length === 0;
|
|
7152
|
-
const exists =
|
|
7421
|
+
const exists = existsSync7(absTarget);
|
|
7153
7422
|
try {
|
|
7154
7423
|
if (!exists) {
|
|
7155
7424
|
if (!searchEmpty) {
|
|
@@ -7159,11 +7428,11 @@ function applyEditBlock(block, rootDir) {
|
|
|
7159
7428
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
7160
7429
|
};
|
|
7161
7430
|
}
|
|
7162
|
-
|
|
7163
|
-
|
|
7431
|
+
mkdirSync4(dirname6(absTarget), { recursive: true });
|
|
7432
|
+
writeFileSync4(absTarget, block.replace, "utf8");
|
|
7164
7433
|
return { path: block.path, status: "created" };
|
|
7165
7434
|
}
|
|
7166
|
-
const content =
|
|
7435
|
+
const content = readFileSync9(absTarget, "utf8");
|
|
7167
7436
|
if (searchEmpty) {
|
|
7168
7437
|
return {
|
|
7169
7438
|
path: block.path,
|
|
@@ -7180,7 +7449,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
7180
7449
|
};
|
|
7181
7450
|
}
|
|
7182
7451
|
const replaced = `${content.slice(0, idx)}${block.replace}${content.slice(idx + block.search.length)}`;
|
|
7183
|
-
|
|
7452
|
+
writeFileSync4(absTarget, replaced, "utf8");
|
|
7184
7453
|
return { path: block.path, status: "applied" };
|
|
7185
7454
|
} catch (err) {
|
|
7186
7455
|
return { path: block.path, status: "error", message: err.message };
|
|
@@ -7192,9 +7461,9 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
7192
7461
|
function toWholeFileEditBlock(path5, content, rootDir) {
|
|
7193
7462
|
const abs = resolve6(rootDir, path5);
|
|
7194
7463
|
let search = "";
|
|
7195
|
-
if (
|
|
7464
|
+
if (existsSync7(abs)) {
|
|
7196
7465
|
try {
|
|
7197
|
-
search =
|
|
7466
|
+
search = readFileSync9(abs, "utf8");
|
|
7198
7467
|
} catch {
|
|
7199
7468
|
search = "";
|
|
7200
7469
|
}
|
|
@@ -7209,12 +7478,12 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
7209
7478
|
if (seen.has(b.path)) continue;
|
|
7210
7479
|
seen.add(b.path);
|
|
7211
7480
|
const abs = resolve6(absRoot, b.path);
|
|
7212
|
-
if (!
|
|
7481
|
+
if (!existsSync7(abs)) {
|
|
7213
7482
|
snapshots.push({ path: b.path, prevContent: null });
|
|
7214
7483
|
continue;
|
|
7215
7484
|
}
|
|
7216
7485
|
try {
|
|
7217
|
-
snapshots.push({ path: b.path, prevContent:
|
|
7486
|
+
snapshots.push({ path: b.path, prevContent: readFileSync9(abs, "utf8") });
|
|
7218
7487
|
} catch {
|
|
7219
7488
|
snapshots.push({ path: b.path, prevContent: null });
|
|
7220
7489
|
}
|
|
@@ -7234,14 +7503,14 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
7234
7503
|
}
|
|
7235
7504
|
try {
|
|
7236
7505
|
if (snap.prevContent === null) {
|
|
7237
|
-
if (
|
|
7506
|
+
if (existsSync7(abs)) unlinkSync2(abs);
|
|
7238
7507
|
return {
|
|
7239
7508
|
path: snap.path,
|
|
7240
7509
|
status: "applied",
|
|
7241
7510
|
message: "removed (the edit had created it)"
|
|
7242
7511
|
};
|
|
7243
7512
|
}
|
|
7244
|
-
|
|
7513
|
+
writeFileSync4(abs, snap.prevContent, "utf8");
|
|
7245
7514
|
return {
|
|
7246
7515
|
path: snap.path,
|
|
7247
7516
|
status: "applied",
|
|
@@ -7256,114 +7525,54 @@ function sep() {
|
|
|
7256
7525
|
return process.platform === "win32" ? "\\" : "/";
|
|
7257
7526
|
}
|
|
7258
7527
|
|
|
7259
|
-
// src/
|
|
7260
|
-
import {
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
if (existsSync7(p)) {
|
|
7273
|
-
const pkg = JSON.parse(readFileSync9(p, "utf8"));
|
|
7274
|
-
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
7275
|
-
return pkg.version;
|
|
7276
|
-
}
|
|
7277
|
-
}
|
|
7278
|
-
const parent = dirname6(dir);
|
|
7279
|
-
if (parent === dir) break;
|
|
7280
|
-
dir = parent;
|
|
7281
|
-
}
|
|
7282
|
-
} catch {
|
|
7283
|
-
}
|
|
7284
|
-
return "0.0.0-dev";
|
|
7285
|
-
}
|
|
7286
|
-
var VERSION = readPackageVersion();
|
|
7287
|
-
function cachePath(homeDirOverride) {
|
|
7288
|
-
return join7(homeDirOverride ?? homedir4(), ".reasonix", "version-cache.json");
|
|
7528
|
+
// src/usage.ts
|
|
7529
|
+
import {
|
|
7530
|
+
appendFileSync as appendFileSync2,
|
|
7531
|
+
existsSync as existsSync8,
|
|
7532
|
+
mkdirSync as mkdirSync5,
|
|
7533
|
+
readFileSync as readFileSync10,
|
|
7534
|
+
statSync as statSync4,
|
|
7535
|
+
writeFileSync as writeFileSync5
|
|
7536
|
+
} from "fs";
|
|
7537
|
+
import { homedir as homedir5 } from "os";
|
|
7538
|
+
import { dirname as dirname7, join as join8 } from "path";
|
|
7539
|
+
function defaultUsageLogPath(homeDirOverride) {
|
|
7540
|
+
return join8(homeDirOverride ?? homedir5(), ".reasonix", "usage.jsonl");
|
|
7289
7541
|
}
|
|
7290
|
-
|
|
7542
|
+
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
7543
|
+
var USAGE_RETENTION_DAYS = 365;
|
|
7544
|
+
function compactUsageLogIfLarge(path5, now) {
|
|
7545
|
+
let size;
|
|
7291
7546
|
try {
|
|
7292
|
-
|
|
7293
|
-
const parsed = JSON.parse(raw);
|
|
7294
|
-
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
7295
|
-
return parsed;
|
|
7296
|
-
}
|
|
7547
|
+
size = statSync4(path5).size;
|
|
7297
7548
|
} catch {
|
|
7549
|
+
return;
|
|
7298
7550
|
}
|
|
7299
|
-
return
|
|
7300
|
-
|
|
7301
|
-
|
|
7551
|
+
if (size < USAGE_COMPACTION_THRESHOLD_BYTES) return;
|
|
7552
|
+
const cutoff = now - USAGE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
7553
|
+
let raw;
|
|
7302
7554
|
try {
|
|
7303
|
-
|
|
7304
|
-
mkdirSync4(dirname6(p), { recursive: true });
|
|
7305
|
-
writeFileSync4(p, JSON.stringify(entry), "utf8");
|
|
7555
|
+
raw = readFileSync10(path5, "utf8");
|
|
7306
7556
|
} catch {
|
|
7557
|
+
return;
|
|
7307
7558
|
}
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
const
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7559
|
+
const lines = raw.split(/\r?\n/);
|
|
7560
|
+
const kept = [];
|
|
7561
|
+
for (const line of lines) {
|
|
7562
|
+
if (!line.trim()) continue;
|
|
7563
|
+
try {
|
|
7564
|
+
const rec = JSON.parse(line);
|
|
7565
|
+
if (isValidRecord(rec) && rec.ts >= cutoff) kept.push(line);
|
|
7566
|
+
} catch {
|
|
7567
|
+
}
|
|
7314
7568
|
}
|
|
7315
|
-
|
|
7316
|
-
if (!fetchImpl) return null;
|
|
7317
|
-
const url = opts.registryUrl ?? REGISTRY_URL;
|
|
7318
|
-
const timeout = opts.timeoutMs ?? LATEST_FETCH_TIMEOUT_MS;
|
|
7319
|
-
const controller = new AbortController();
|
|
7320
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
7569
|
+
if (kept.length === lines.filter((l) => l.trim()).length) return;
|
|
7321
7570
|
try {
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
headers: { accept: "application/json" }
|
|
7325
|
-
});
|
|
7326
|
-
if (!res.ok) return null;
|
|
7327
|
-
const body = await res.json();
|
|
7328
|
-
if (typeof body.version !== "string") return null;
|
|
7329
|
-
writeCache({ version: body.version, checkedAt: Date.now() }, opts.homeDir);
|
|
7330
|
-
return body.version;
|
|
7571
|
+
writeFileSync5(path5, kept.length > 0 ? `${kept.join("\n")}
|
|
7572
|
+
` : "", "utf8");
|
|
7331
7573
|
} catch {
|
|
7332
|
-
return null;
|
|
7333
|
-
} finally {
|
|
7334
|
-
clearTimeout(timer);
|
|
7335
7574
|
}
|
|
7336
7575
|
}
|
|
7337
|
-
function compareVersions(a, b) {
|
|
7338
|
-
const [aCore = "0", aPre = ""] = a.split("-", 2);
|
|
7339
|
-
const [bCore = "0", bPre = ""] = b.split("-", 2);
|
|
7340
|
-
const aParts = aCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
7341
|
-
const bParts = bCore.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
7342
|
-
for (let i = 0; i < 3; i++) {
|
|
7343
|
-
const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
7344
|
-
if (diff !== 0) return diff;
|
|
7345
|
-
}
|
|
7346
|
-
if (!aPre && !bPre) return 0;
|
|
7347
|
-
if (!aPre) return 1;
|
|
7348
|
-
if (!bPre) return -1;
|
|
7349
|
-
return aPre < bPre ? -1 : aPre > bPre ? 1 : 0;
|
|
7350
|
-
}
|
|
7351
|
-
function isNpxInstall() {
|
|
7352
|
-
const bin = process.argv[1] ?? "";
|
|
7353
|
-
if (/[/\\]_npx[/\\]/.test(bin)) return true;
|
|
7354
|
-
if (/[/\\]\.pnpm[/\\]/.test(bin) && /dlx/i.test(bin)) return true;
|
|
7355
|
-
const ua = process.env.npm_config_user_agent ?? "";
|
|
7356
|
-
if (ua.includes("npx/")) return true;
|
|
7357
|
-
return false;
|
|
7358
|
-
}
|
|
7359
|
-
|
|
7360
|
-
// src/usage.ts
|
|
7361
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
|
|
7362
|
-
import { homedir as homedir5 } from "os";
|
|
7363
|
-
import { dirname as dirname7, join as join8 } from "path";
|
|
7364
|
-
function defaultUsageLogPath(homeDirOverride) {
|
|
7365
|
-
return join8(homeDirOverride ?? homedir5(), ".reasonix", "usage.jsonl");
|
|
7366
|
-
}
|
|
7367
7576
|
function appendUsage(input) {
|
|
7368
7577
|
const record = {
|
|
7369
7578
|
ts: input.now ?? Date.now(),
|
|
@@ -7383,6 +7592,7 @@ function appendUsage(input) {
|
|
|
7383
7592
|
mkdirSync5(dirname7(path5), { recursive: true });
|
|
7384
7593
|
appendFileSync2(path5, `${JSON.stringify(record)}
|
|
7385
7594
|
`, "utf8");
|
|
7595
|
+
compactUsageLogIfLarge(path5, record.ts);
|
|
7386
7596
|
} catch {
|
|
7387
7597
|
}
|
|
7388
7598
|
return record;
|
|
@@ -7521,7 +7731,7 @@ import { Box as Box22, Static, Text as Text20, useStdout as useStdout8 } from "i
|
|
|
7521
7731
|
import React24, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
|
|
7522
7732
|
|
|
7523
7733
|
// src/code/pending-edits.ts
|
|
7524
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as
|
|
7734
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
7525
7735
|
import { dirname as dirname8, join as join9 } from "path";
|
|
7526
7736
|
function pendingEditsPath(sessionName) {
|
|
7527
7737
|
return join9(sessionsDir(), `${sanitizeName(sessionName)}.pending.json`);
|
|
@@ -7535,7 +7745,7 @@ function savePendingEdits(sessionName, blocks) {
|
|
|
7535
7745
|
return;
|
|
7536
7746
|
}
|
|
7537
7747
|
mkdirSync6(dirname8(path5), { recursive: true });
|
|
7538
|
-
|
|
7748
|
+
writeFileSync6(path5, JSON.stringify(blocks, null, 2), "utf8");
|
|
7539
7749
|
} catch {
|
|
7540
7750
|
}
|
|
7541
7751
|
}
|
|
@@ -7581,7 +7791,7 @@ import {
|
|
|
7581
7791
|
renameSync,
|
|
7582
7792
|
statSync as statSync5,
|
|
7583
7793
|
unlinkSync as unlinkSync4,
|
|
7584
|
-
writeFileSync as
|
|
7794
|
+
writeFileSync as writeFileSync7
|
|
7585
7795
|
} from "fs";
|
|
7586
7796
|
import { dirname as dirname9, join as join10 } from "path";
|
|
7587
7797
|
function planStatePath(sessionName) {
|
|
@@ -7638,7 +7848,7 @@ function savePlanState(sessionName, steps, completedStepIds, extras) {
|
|
|
7638
7848
|
};
|
|
7639
7849
|
if (extras?.body) state.body = extras.body;
|
|
7640
7850
|
if (extras?.summary) state.summary = extras.summary;
|
|
7641
|
-
|
|
7851
|
+
writeFileSync7(path5, `${JSON.stringify(state, null, 2)}
|
|
7642
7852
|
`, "utf8");
|
|
7643
7853
|
} catch (err) {
|
|
7644
7854
|
process.stderr.write(
|
|
@@ -7906,7 +8116,7 @@ import {
|
|
|
7906
8116
|
readFileSync as readFileSync14,
|
|
7907
8117
|
readSync,
|
|
7908
8118
|
statSync as statSync6,
|
|
7909
|
-
writeFileSync as
|
|
8119
|
+
writeFileSync as writeFileSync8
|
|
7910
8120
|
} from "fs";
|
|
7911
8121
|
import { dirname as dirname11, isAbsolute as isAbsolute4, resolve as resolve7, sep as sep2 } from "path";
|
|
7912
8122
|
var MAX_BYTES = 4 * 1024 * 1024;
|
|
@@ -7986,14 +8196,14 @@ async function handleFile(method, rest, body, ctx) {
|
|
|
7986
8196
|
if (!existsSync11(target)) {
|
|
7987
8197
|
return { status: 404, body: { error: "file not found" } };
|
|
7988
8198
|
}
|
|
7989
|
-
const
|
|
7990
|
-
if (
|
|
8199
|
+
const stat2 = statSync6(target);
|
|
8200
|
+
if (stat2.isDirectory()) {
|
|
7991
8201
|
return { status: 400, body: { error: "path is a directory" } };
|
|
7992
8202
|
}
|
|
7993
|
-
if (
|
|
8203
|
+
if (stat2.size > MAX_BYTES) {
|
|
7994
8204
|
return {
|
|
7995
8205
|
status: 413,
|
|
7996
|
-
body: { error: `file too large (${
|
|
8206
|
+
body: { error: `file too large (${stat2.size} bytes; cap ${MAX_BYTES})` }
|
|
7997
8207
|
};
|
|
7998
8208
|
}
|
|
7999
8209
|
if (looksBinary(target)) {
|
|
@@ -8008,8 +8218,8 @@ async function handleFile(method, rest, body, ctx) {
|
|
|
8008
8218
|
body: {
|
|
8009
8219
|
path: requested,
|
|
8010
8220
|
absolute: target,
|
|
8011
|
-
size:
|
|
8012
|
-
mtime:
|
|
8221
|
+
size: stat2.size,
|
|
8222
|
+
mtime: stat2.mtime.getTime(),
|
|
8013
8223
|
content
|
|
8014
8224
|
}
|
|
8015
8225
|
};
|
|
@@ -8029,20 +8239,20 @@ async function handleFile(method, rest, body, ctx) {
|
|
|
8029
8239
|
if (!existsSync11(parent)) {
|
|
8030
8240
|
mkdirSync8(parent, { recursive: true });
|
|
8031
8241
|
}
|
|
8032
|
-
|
|
8242
|
+
writeFileSync8(target, content, "utf8");
|
|
8033
8243
|
ctx.audit?.({
|
|
8034
8244
|
ts: Date.now(),
|
|
8035
8245
|
action: "save-file",
|
|
8036
8246
|
payload: { path: requested, bytes: Buffer.byteLength(content, "utf8") }
|
|
8037
8247
|
});
|
|
8038
|
-
const
|
|
8248
|
+
const stat2 = statSync6(target);
|
|
8039
8249
|
return {
|
|
8040
8250
|
status: 200,
|
|
8041
8251
|
body: {
|
|
8042
8252
|
saved: true,
|
|
8043
8253
|
path: requested,
|
|
8044
|
-
size:
|
|
8045
|
-
mtime:
|
|
8254
|
+
size: stat2.size,
|
|
8255
|
+
mtime: stat2.mtime.getTime()
|
|
8046
8256
|
}
|
|
8047
8257
|
};
|
|
8048
8258
|
}
|
|
@@ -8139,7 +8349,7 @@ async function handleHealth(method, _rest, _body, ctx) {
|
|
|
8139
8349
|
}
|
|
8140
8350
|
|
|
8141
8351
|
// src/server/api/hooks.ts
|
|
8142
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as
|
|
8352
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
|
|
8143
8353
|
import { dirname as dirname12 } from "path";
|
|
8144
8354
|
function parseBody3(raw) {
|
|
8145
8355
|
if (!raw) return {};
|
|
@@ -8164,7 +8374,7 @@ function writeSettingsFile(path5, hooksBlock) {
|
|
|
8164
8374
|
const existing = readSettingsFile2(path5);
|
|
8165
8375
|
existing.hooks = hooksBlock;
|
|
8166
8376
|
mkdirSync9(dirname12(path5), { recursive: true });
|
|
8167
|
-
|
|
8377
|
+
writeFileSync9(path5, `${JSON.stringify(existing, null, 2)}
|
|
8168
8378
|
`, "utf8");
|
|
8169
8379
|
}
|
|
8170
8380
|
async function handleHooks(method, rest, body, ctx) {
|
|
@@ -8345,7 +8555,7 @@ import {
|
|
|
8345
8555
|
readdirSync as readdirSync5,
|
|
8346
8556
|
statSync as statSync8,
|
|
8347
8557
|
unlinkSync as unlinkSync5,
|
|
8348
|
-
writeFileSync as
|
|
8558
|
+
writeFileSync as writeFileSync10
|
|
8349
8559
|
} from "fs";
|
|
8350
8560
|
import { homedir as homedir7 } from "os";
|
|
8351
8561
|
import { dirname as dirname13, join as join13, resolve as resolvePath } from "path";
|
|
@@ -8372,11 +8582,11 @@ function listMemoryFiles(dir) {
|
|
|
8372
8582
|
if (!existsSync14(dir)) return [];
|
|
8373
8583
|
try {
|
|
8374
8584
|
return readdirSync5(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
8375
|
-
const
|
|
8585
|
+
const stat2 = statSync8(join13(dir, f));
|
|
8376
8586
|
return {
|
|
8377
8587
|
name: f.replace(/\.md$/, ""),
|
|
8378
|
-
size:
|
|
8379
|
-
mtime:
|
|
8588
|
+
size: stat2.size,
|
|
8589
|
+
mtime: stat2.mtime.getTime()
|
|
8380
8590
|
};
|
|
8381
8591
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
8382
8592
|
} catch {
|
|
@@ -8436,7 +8646,7 @@ async function handleMemory(method, rest, body, ctx) {
|
|
|
8436
8646
|
if (!cwd2) return { status: 503, body: { error: "no active project" } };
|
|
8437
8647
|
const path5 = join13(cwd2, PROJECT_MEMORY_FILE);
|
|
8438
8648
|
mkdirSync10(dirname13(path5), { recursive: true });
|
|
8439
|
-
|
|
8649
|
+
writeFileSync10(path5, contents, "utf8");
|
|
8440
8650
|
ctx.audit?.({ ts: Date.now(), action: "save-memory", payload: { scope, path: path5 } });
|
|
8441
8651
|
return { status: 200, body: { saved: true, path: path5 } };
|
|
8442
8652
|
}
|
|
@@ -8445,7 +8655,7 @@ async function handleMemory(method, rest, body, ctx) {
|
|
|
8445
8655
|
if (!dir) return { status: 503, body: { error: "no project root for project-mem" } };
|
|
8446
8656
|
mkdirSync10(dir, { recursive: true });
|
|
8447
8657
|
const path5 = join13(dir, `${name}.md`);
|
|
8448
|
-
|
|
8658
|
+
writeFileSync10(path5, contents, "utf8");
|
|
8449
8659
|
ctx.audit?.({ ts: Date.now(), action: "save-memory", payload: { scope, name, path: path5 } });
|
|
8450
8660
|
return { status: 200, body: { saved: true, path: path5 } };
|
|
8451
8661
|
}
|
|
@@ -8783,13 +8993,13 @@ async function* walkChunks(root, opts = {}) {
|
|
|
8783
8993
|
const ext = path.extname(name).toLowerCase();
|
|
8784
8994
|
if (BINARY_EXTS.has(ext)) continue;
|
|
8785
8995
|
const abs = path.join(dir, name);
|
|
8786
|
-
let
|
|
8996
|
+
let stat2;
|
|
8787
8997
|
try {
|
|
8788
|
-
|
|
8998
|
+
stat2 = await fs2.stat(abs);
|
|
8789
8999
|
} catch {
|
|
8790
9000
|
continue;
|
|
8791
9001
|
}
|
|
8792
|
-
if (
|
|
9002
|
+
if (stat2.size > maxFileBytes) continue;
|
|
8793
9003
|
let text;
|
|
8794
9004
|
try {
|
|
8795
9005
|
text = await fs2.readFile(abs, "utf8");
|
|
@@ -9165,8 +9375,8 @@ async function buildIndex(root, opts = {}) {
|
|
|
9165
9375
|
const abs = path3.join(root, chunk.path);
|
|
9166
9376
|
let mtimeMs = 0;
|
|
9167
9377
|
try {
|
|
9168
|
-
const
|
|
9169
|
-
mtimeMs =
|
|
9378
|
+
const stat2 = await fs4.stat(abs);
|
|
9379
|
+
mtimeMs = stat2.mtimeMs;
|
|
9170
9380
|
} catch {
|
|
9171
9381
|
continue;
|
|
9172
9382
|
}
|
|
@@ -9969,7 +10179,7 @@ import {
|
|
|
9969
10179
|
readdirSync as readdirSync6,
|
|
9970
10180
|
rmSync,
|
|
9971
10181
|
statSync as statSync9,
|
|
9972
|
-
writeFileSync as
|
|
10182
|
+
writeFileSync as writeFileSync11
|
|
9973
10183
|
} from "fs";
|
|
9974
10184
|
import { homedir as homedir8 } from "os";
|
|
9975
10185
|
import { dirname as dirname14, join as join14 } from "path";
|
|
@@ -10008,14 +10218,14 @@ function listSkills(dir, scope) {
|
|
|
10008
10218
|
const skillPath = join14(dir, entry, SKILL_FILE);
|
|
10009
10219
|
if (!existsSync16(skillPath)) continue;
|
|
10010
10220
|
try {
|
|
10011
|
-
const
|
|
10221
|
+
const stat2 = statSync9(skillPath);
|
|
10012
10222
|
const raw = readFileSync18(skillPath, "utf8");
|
|
10013
10223
|
const item = {
|
|
10014
10224
|
name: entry,
|
|
10015
10225
|
scope,
|
|
10016
10226
|
path: skillPath,
|
|
10017
|
-
size:
|
|
10018
|
-
mtime:
|
|
10227
|
+
size: stat2.size,
|
|
10228
|
+
mtime: stat2.mtime.getTime()
|
|
10019
10229
|
};
|
|
10020
10230
|
const desc = parseFrontmatterDescription(raw);
|
|
10021
10231
|
if (desc) item.description = desc;
|
|
@@ -10084,7 +10294,7 @@ async function handleSkills(method, rest, body, ctx) {
|
|
|
10084
10294
|
return { status: 400, body: { error: "body (string) required" } };
|
|
10085
10295
|
}
|
|
10086
10296
|
mkdirSync11(dirname14(skillPath), { recursive: true });
|
|
10087
|
-
|
|
10297
|
+
writeFileSync11(skillPath, contents, "utf8");
|
|
10088
10298
|
ctx.audit?.({
|
|
10089
10299
|
ts: Date.now(),
|
|
10090
10300
|
action: "save-skill",
|
|
@@ -11613,6 +11823,22 @@ function extOf(p) {
|
|
|
11613
11823
|
const m = /\.[^./\\]+$/.exec(p);
|
|
11614
11824
|
return m ? m[0] : "";
|
|
11615
11825
|
}
|
|
11826
|
+
var lineCountCache = /* @__PURE__ */ new Map();
|
|
11827
|
+
var LINE_COUNT_CACHE_LIMIT = 256;
|
|
11828
|
+
function getCachedLineCount(fullPath, mtimeMs) {
|
|
11829
|
+
const hit = lineCountCache.get(fullPath);
|
|
11830
|
+
if (!hit || hit.mtimeMs !== mtimeMs) return null;
|
|
11831
|
+
lineCountCache.delete(fullPath);
|
|
11832
|
+
lineCountCache.set(fullPath, hit);
|
|
11833
|
+
return hit.lineCount;
|
|
11834
|
+
}
|
|
11835
|
+
function setCachedLineCount(fullPath, mtimeMs, lineCount) {
|
|
11836
|
+
if (lineCountCache.size >= LINE_COUNT_CACHE_LIMIT) {
|
|
11837
|
+
const oldest = lineCountCache.keys().next().value;
|
|
11838
|
+
if (oldest !== void 0) lineCountCache.delete(oldest);
|
|
11839
|
+
}
|
|
11840
|
+
lineCountCache.set(fullPath, { mtimeMs, lineCount });
|
|
11841
|
+
}
|
|
11616
11842
|
function validateCitation(url, projectRoot) {
|
|
11617
11843
|
const parts = parseCitationUrl(url);
|
|
11618
11844
|
if (!parts || !parts.path) return { ok: false, reason: "empty path" };
|
|
@@ -11624,23 +11850,26 @@ function validateCitation(url, projectRoot) {
|
|
|
11624
11850
|
...siblings.map((ext) => baseFullPath.replace(/\.[^./\\]+$/, ext))
|
|
11625
11851
|
];
|
|
11626
11852
|
let fullPath = baseFullPath;
|
|
11627
|
-
let
|
|
11853
|
+
let stat2 = null;
|
|
11628
11854
|
for (const candidate of candidates) {
|
|
11629
11855
|
try {
|
|
11630
|
-
|
|
11856
|
+
stat2 = statSync11(candidate);
|
|
11631
11857
|
fullPath = candidate;
|
|
11632
11858
|
break;
|
|
11633
11859
|
} catch {
|
|
11634
11860
|
}
|
|
11635
11861
|
}
|
|
11636
|
-
if (!
|
|
11637
|
-
if (!
|
|
11862
|
+
if (!stat2) return { ok: false, reason: "file not found" };
|
|
11863
|
+
if (!stat2.isFile()) return { ok: false, reason: "not a file" };
|
|
11638
11864
|
if (parts.startLine === void 0) return { ok: true };
|
|
11639
|
-
let lineCount;
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11865
|
+
let lineCount = getCachedLineCount(fullPath, stat2.mtimeMs);
|
|
11866
|
+
if (lineCount === null) {
|
|
11867
|
+
try {
|
|
11868
|
+
lineCount = readFileSync19(fullPath, "utf8").split("\n").length;
|
|
11869
|
+
} catch {
|
|
11870
|
+
return { ok: false, reason: "unreadable" };
|
|
11871
|
+
}
|
|
11872
|
+
setCachedLineCount(fullPath, stat2.mtimeMs, lineCount);
|
|
11644
11873
|
}
|
|
11645
11874
|
if (parts.startLine < 1 || parts.startLine > lineCount) {
|
|
11646
11875
|
return { ok: false, reason: `line ${parts.startLine} > ${lineCount}` };
|
|
@@ -12216,23 +12445,33 @@ function gradientCells(width, glyph = GLYPH.block) {
|
|
|
12216
12445
|
|
|
12217
12446
|
// src/cli/ui/ticker.tsx
|
|
12218
12447
|
import React10, { createContext as createContext2, useContext as useContext2, useEffect as useEffect2, useState as useState3 } from "react";
|
|
12219
|
-
var
|
|
12220
|
-
var
|
|
12448
|
+
var FAST_TICK_MS = 120;
|
|
12449
|
+
var SLOW_TICK_MS = 1e3;
|
|
12450
|
+
var FastTickContext = createContext2(0);
|
|
12451
|
+
var SlowTickContext = createContext2(0);
|
|
12221
12452
|
function TickerProvider({ children, disabled }) {
|
|
12222
|
-
const [
|
|
12453
|
+
const [fast, setFast] = useState3(0);
|
|
12454
|
+
const [slow, setSlow] = useState3(0);
|
|
12223
12455
|
useEffect2(() => {
|
|
12224
12456
|
if (disabled) return;
|
|
12225
|
-
const
|
|
12226
|
-
|
|
12457
|
+
const fastId = setInterval(() => setFast((t2) => t2 + 1), FAST_TICK_MS);
|
|
12458
|
+
const slowId = setInterval(() => setSlow((t2) => t2 + 1), SLOW_TICK_MS);
|
|
12459
|
+
return () => {
|
|
12460
|
+
clearInterval(fastId);
|
|
12461
|
+
clearInterval(slowId);
|
|
12462
|
+
};
|
|
12227
12463
|
}, [disabled]);
|
|
12228
|
-
return /* @__PURE__ */ React10.createElement(
|
|
12464
|
+
return /* @__PURE__ */ React10.createElement(FastTickContext.Provider, { value: fast }, /* @__PURE__ */ React10.createElement(SlowTickContext.Provider, { value: slow }, children));
|
|
12229
12465
|
}
|
|
12230
12466
|
function useTick() {
|
|
12231
|
-
return useContext2(
|
|
12467
|
+
return useContext2(FastTickContext);
|
|
12468
|
+
}
|
|
12469
|
+
function useSlowTick() {
|
|
12470
|
+
return useContext2(SlowTickContext);
|
|
12232
12471
|
}
|
|
12233
12472
|
function useElapsedSeconds() {
|
|
12234
12473
|
const [start] = useState3(() => Date.now());
|
|
12235
|
-
|
|
12474
|
+
useSlowTick();
|
|
12236
12475
|
return Math.floor((Date.now() - start) / 1e3);
|
|
12237
12476
|
}
|
|
12238
12477
|
|
|
@@ -12425,7 +12664,7 @@ var EventRow = React11.memo(function EventRow2({
|
|
|
12425
12664
|
event.branch ? /* @__PURE__ */ React11.createElement(BranchBlock, { branch: event.branch }) : null,
|
|
12426
12665
|
event.reasoning ? /* @__PURE__ */ React11.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null,
|
|
12427
12666
|
!isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React11.createElement(PlanStateBlock, { planState: event.planState }) : null,
|
|
12428
|
-
event.text ? /* @__PURE__ */ React11.createElement(Markdown, { text: event.text, projectRoot }) : /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, "(
|
|
12667
|
+
event.text ? /* @__PURE__ */ React11.createElement(Markdown, { text: event.text, projectRoot }) : /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, "(empty body \u2014 likely tool-call only)"),
|
|
12429
12668
|
event.stats ? /* @__PURE__ */ React11.createElement(StatsLine, { stats: event.stats }) : null,
|
|
12430
12669
|
event.repair ? /* @__PURE__ */ React11.createElement(Text8, { color: COLOR.accent }, event.repair) : null
|
|
12431
12670
|
));
|
|
@@ -12563,9 +12802,7 @@ function Elapsed() {
|
|
|
12563
12802
|
return /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, `${mm}:${ss}`);
|
|
12564
12803
|
}
|
|
12565
12804
|
function PulsingAssistantGlyph() {
|
|
12566
|
-
|
|
12567
|
-
const on = Math.floor(tick / 4) % 2 === 0;
|
|
12568
|
-
return /* @__PURE__ */ React11.createElement(Text8, { color: "green", bold: true }, on ? ROLE_GLYPH.assistant : ROLE_GLYPH.assistantPulse);
|
|
12805
|
+
return /* @__PURE__ */ React11.createElement(Text8, { color: "green", bold: true }, ROLE_GLYPH.assistant);
|
|
12569
12806
|
}
|
|
12570
12807
|
function StreamingAssistant({ event }) {
|
|
12571
12808
|
if (event.branchProgress) {
|
|
@@ -12582,6 +12819,7 @@ function StreamingAssistant({ event }) {
|
|
|
12582
12819
|
const preFirstByte = !event.text && !event.reasoning && !toolCallBuild;
|
|
12583
12820
|
const reasoningOnly = !event.text && !!event.reasoning && !toolCallBuild;
|
|
12584
12821
|
const toolCallOnly = !event.text && !event.reasoning && !!toolCallBuild;
|
|
12822
|
+
const PILL_WIDTH = 8;
|
|
12585
12823
|
let pillBg;
|
|
12586
12824
|
let pillText;
|
|
12587
12825
|
let label;
|
|
@@ -12609,6 +12847,7 @@ function StreamingAssistant({ event }) {
|
|
|
12609
12847
|
}
|
|
12610
12848
|
label = parts.join(" \xB7 ");
|
|
12611
12849
|
}
|
|
12850
|
+
pillText = pillText.padEnd(PILL_WIDTH);
|
|
12612
12851
|
return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Box9, null, /* @__PURE__ */ React11.createElement(PulsingAssistantGlyph, null), /* @__PURE__ */ React11.createElement(Text8, null, " "), /* @__PURE__ */ React11.createElement(Pulse, null), /* @__PURE__ */ React11.createElement(Text8, null, " "), /* @__PURE__ */ React11.createElement(Text8, { backgroundColor: pillBg, color: "black", bold: true }, ` ${pillText} `), /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, ` ${label} `), /* @__PURE__ */ React11.createElement(Elapsed, null)), reasoningTail ? /* @__PURE__ */ React11.createElement(Box9, { paddingLeft: 3 }, /* @__PURE__ */ React11.createElement(Text8, { color: "#c4b5fd", italic: true, dimColor: true }, "\u21B3 ", reasoningTail)) : null, tail ? /* @__PURE__ */ React11.createElement(Box9, { paddingLeft: 3 }, /* @__PURE__ */ React11.createElement(Text8, { dimColor: true }, "\u25B8 ", tail)) : preFirstByte ? (
|
|
12613
12852
|
// Non-dim amber: first-time users misread the dim version as
|
|
12614
12853
|
// "app frozen". The reassurance has to be VISIBLE to do its job.
|
|
@@ -12631,7 +12870,8 @@ function formatReadyTail(tb) {
|
|
|
12631
12870
|
return ` \xB7 ${n} ready`;
|
|
12632
12871
|
}
|
|
12633
12872
|
function lastLine(s, maxChars) {
|
|
12634
|
-
const
|
|
12873
|
+
const tailSlice = s.length > maxChars * 4 ? s.slice(-maxChars * 4) : s;
|
|
12874
|
+
const flat = tailSlice.replace(/\s+/g, " ").trim();
|
|
12635
12875
|
if (!flat) return "";
|
|
12636
12876
|
return flat.length <= maxChars ? flat : `\u2026${flat.slice(-maxChars)}`;
|
|
12637
12877
|
}
|
|
@@ -12658,7 +12898,7 @@ function ModeStatusBar({
|
|
|
12658
12898
|
undoArmed,
|
|
12659
12899
|
jobs: jobs2
|
|
12660
12900
|
}) {
|
|
12661
|
-
|
|
12901
|
+
useSlowTick();
|
|
12662
12902
|
const running = jobs2?.runningCount() ?? 0;
|
|
12663
12903
|
const jobsTag = running > 0 ? /* @__PURE__ */ React12.createElement(Text9, { color: "yellow", bold: true }, ` \xB7 \u23F5 ${running} job${running === 1 ? "" : "s"}`) : null;
|
|
12664
12904
|
if (planMode) {
|
|
@@ -12685,7 +12925,7 @@ function ModePill({
|
|
|
12685
12925
|
function UndoBanner({
|
|
12686
12926
|
banner
|
|
12687
12927
|
}) {
|
|
12688
|
-
|
|
12928
|
+
useSlowTick();
|
|
12689
12929
|
const remainingMs = Math.max(0, banner.expiresAt - Date.now());
|
|
12690
12930
|
const remainingSec = Math.ceil(remainingMs / 1e3);
|
|
12691
12931
|
const ok = banner.results.filter((r) => r.status === "applied" || r.status === "created").length;
|
|
@@ -14165,7 +14405,7 @@ function describeRepair(repair) {
|
|
|
14165
14405
|
}
|
|
14166
14406
|
|
|
14167
14407
|
// src/cli/ui/hash-memory.ts
|
|
14168
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync20, writeFileSync as
|
|
14408
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
|
|
14169
14409
|
import { homedir as homedir9 } from "os";
|
|
14170
14410
|
import { dirname as dirname15, join as join17 } from "path";
|
|
14171
14411
|
var PROJECT_HEADER = `# Reasonix project memory
|
|
@@ -14216,7 +14456,7 @@ function appendBulletToFile(path5, note, newFileHeader) {
|
|
|
14216
14456
|
`;
|
|
14217
14457
|
if (!existsSync18(path5)) {
|
|
14218
14458
|
mkdirSync12(dirname15(path5), { recursive: true });
|
|
14219
|
-
|
|
14459
|
+
writeFileSync12(path5, `${newFileHeader}${bullet}`, "utf8");
|
|
14220
14460
|
return { path: path5, created: true };
|
|
14221
14461
|
}
|
|
14222
14462
|
let prefix = "";
|
|
@@ -14662,6 +14902,11 @@ var SLASH_COMMANDS = [
|
|
|
14662
14902
|
},
|
|
14663
14903
|
{ cmd: "sessions", summary: "list saved sessions (current marked with \u25B8)" },
|
|
14664
14904
|
{ cmd: "forget", summary: "delete the current session from disk" },
|
|
14905
|
+
{
|
|
14906
|
+
cmd: "prune-sessions",
|
|
14907
|
+
summary: "delete sessions idle \u2265N days (default 90) \u2014 frees disk on long-time installs",
|
|
14908
|
+
argsHint: "[days]"
|
|
14909
|
+
},
|
|
14665
14910
|
{ cmd: "setup", summary: "reminds you to exit and run `reasonix setup`" },
|
|
14666
14911
|
{
|
|
14667
14912
|
cmd: "semantic",
|
|
@@ -16631,9 +16876,9 @@ async function indexFileExists(rootDir) {
|
|
|
16631
16876
|
async function readIndexMeta(rootDir) {
|
|
16632
16877
|
const dataPath = path4.join(rootDir, ".reasonix", "semantic", "index.jsonl");
|
|
16633
16878
|
try {
|
|
16634
|
-
const
|
|
16635
|
-
if (
|
|
16636
|
-
return { chunks: Math.round(
|
|
16879
|
+
const stat2 = await fs5.stat(dataPath);
|
|
16880
|
+
if (stat2.size > 10 * 1024 * 1024) {
|
|
16881
|
+
return { chunks: Math.round(stat2.size / 500), files: 0 };
|
|
16637
16882
|
}
|
|
16638
16883
|
const raw = await fs5.readFile(dataPath, "utf8");
|
|
16639
16884
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
@@ -16657,6 +16902,7 @@ var handlers13 = {
|
|
|
16657
16902
|
};
|
|
16658
16903
|
|
|
16659
16904
|
// src/cli/ui/slash/handlers/sessions.ts
|
|
16905
|
+
var STALE_THRESHOLD_DAYS = 90;
|
|
16660
16906
|
var sessions = (_args, loop2) => {
|
|
16661
16907
|
const items = listSessions();
|
|
16662
16908
|
if (items.length === 0) {
|
|
@@ -16664,17 +16910,28 @@ var sessions = (_args, loop2) => {
|
|
|
16664
16910
|
info: "no saved sessions yet \u2014 chat normally and your messages will be saved automatically"
|
|
16665
16911
|
};
|
|
16666
16912
|
}
|
|
16913
|
+
const now = Date.now();
|
|
16667
16914
|
const lines = ["Saved sessions:"];
|
|
16915
|
+
let staleCount = 0;
|
|
16668
16916
|
for (const s of items) {
|
|
16669
16917
|
const sizeKb = (s.size / 1024).toFixed(1);
|
|
16670
16918
|
const when = s.mtime.toISOString().replace("T", " ").slice(0, 16);
|
|
16671
16919
|
const marker = s.name === loop2.sessionName ? "\u25B8" : " ";
|
|
16920
|
+
const ageDays = Math.floor((now - s.mtime.getTime()) / (24 * 60 * 60 * 1e3));
|
|
16921
|
+
const isStale = ageDays >= STALE_THRESHOLD_DAYS;
|
|
16922
|
+
const ageTag = isStale ? ` (${ageDays}d \u2014 stale)` : "";
|
|
16923
|
+
if (isStale) staleCount++;
|
|
16672
16924
|
lines.push(
|
|
16673
|
-
` ${marker} ${s.name.padEnd(22)} ${String(s.messageCount).padStart(5)} msgs ${sizeKb.padStart(7)} KB ${when}`
|
|
16925
|
+
` ${marker} ${s.name.padEnd(22)} ${String(s.messageCount).padStart(5)} msgs ${sizeKb.padStart(7)} KB ${when}${ageTag}`
|
|
16674
16926
|
);
|
|
16675
16927
|
}
|
|
16676
16928
|
lines.push("");
|
|
16677
16929
|
lines.push("Resume with: reasonix chat --session <name>");
|
|
16930
|
+
if (staleCount > 0) {
|
|
16931
|
+
lines.push(
|
|
16932
|
+
`${staleCount} session${staleCount === 1 ? "" : "s"} idle \u2265${STALE_THRESHOLD_DAYS} days \u2014 /prune-sessions to remove`
|
|
16933
|
+
);
|
|
16934
|
+
}
|
|
16678
16935
|
return { info: lines.join("\n") };
|
|
16679
16936
|
};
|
|
16680
16937
|
var forget = (_args, loop2) => {
|
|
@@ -16687,9 +16944,26 @@ var forget = (_args, loop2) => {
|
|
|
16687
16944
|
info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
|
|
16688
16945
|
};
|
|
16689
16946
|
};
|
|
16947
|
+
var pruneSessions = (args) => {
|
|
16948
|
+
const raw = args?.[0];
|
|
16949
|
+
const days = raw ? Number.parseInt(raw, 10) : STALE_THRESHOLD_DAYS;
|
|
16950
|
+
if (!Number.isFinite(days) || days < 1) {
|
|
16951
|
+
return {
|
|
16952
|
+
info: `\u25B8 usage: /prune-sessions [days] \u2014 defaults to ${STALE_THRESHOLD_DAYS}, must be \u22651`
|
|
16953
|
+
};
|
|
16954
|
+
}
|
|
16955
|
+
const removed = pruneStaleSessions(days);
|
|
16956
|
+
if (removed.length === 0) {
|
|
16957
|
+
return { info: `\u25B8 nothing to prune \u2014 no sessions idle \u2265${days} days` };
|
|
16958
|
+
}
|
|
16959
|
+
return {
|
|
16960
|
+
info: `\u25B8 pruned ${removed.length} session${removed.length === 1 ? "" : "s"} idle \u2265${days} days: ${removed.join(", ")}`
|
|
16961
|
+
};
|
|
16962
|
+
};
|
|
16690
16963
|
var handlers14 = {
|
|
16691
16964
|
sessions,
|
|
16692
|
-
forget
|
|
16965
|
+
forget,
|
|
16966
|
+
"prune-sessions": pruneSessions
|
|
16693
16967
|
};
|
|
16694
16968
|
|
|
16695
16969
|
// src/cli/ui/slash/handlers/skill.ts
|
|
@@ -16815,13 +17089,21 @@ function useCompletionPickers({
|
|
|
16815
17089
|
});
|
|
16816
17090
|
}, [slashMatches]);
|
|
16817
17091
|
const [atSelected, setAtSelected] = useState6(0);
|
|
16818
|
-
const atFiles =
|
|
16819
|
-
|
|
16820
|
-
|
|
16821
|
-
|
|
16822
|
-
|
|
16823
|
-
return [];
|
|
17092
|
+
const [atFiles, setAtFiles] = useState6([]);
|
|
17093
|
+
useEffect3(() => {
|
|
17094
|
+
if (!codeMode) {
|
|
17095
|
+
setAtFiles([]);
|
|
17096
|
+
return;
|
|
16824
17097
|
}
|
|
17098
|
+
let cancelled = false;
|
|
17099
|
+
listFilesWithStatsAsync(rootDir, { maxResults: 500 }).then((files) => {
|
|
17100
|
+
if (!cancelled) setAtFiles(files);
|
|
17101
|
+
}).catch(() => {
|
|
17102
|
+
if (!cancelled) setAtFiles([]);
|
|
17103
|
+
});
|
|
17104
|
+
return () => {
|
|
17105
|
+
cancelled = true;
|
|
17106
|
+
};
|
|
16825
17107
|
}, [codeMode, rootDir]);
|
|
16826
17108
|
const recentFilesRef = useRef3([]);
|
|
16827
17109
|
const recordRecentFile = useCallback((p) => {
|
|
@@ -17270,7 +17552,13 @@ function useSubagent({ session, setHistorical }) {
|
|
|
17270
17552
|
}
|
|
17271
17553
|
|
|
17272
17554
|
// src/cli/ui/App.tsx
|
|
17273
|
-
var FLUSH_INTERVAL_MS =
|
|
17555
|
+
var FLUSH_INTERVAL_MS = (() => {
|
|
17556
|
+
const raw = process.env.REASONIX_FLUSH_MS;
|
|
17557
|
+
if (!raw) return 33;
|
|
17558
|
+
const parsed = Number(raw);
|
|
17559
|
+
if (!Number.isFinite(parsed) || parsed < 16 || parsed > 1e3) return 33;
|
|
17560
|
+
return Math.round(parsed);
|
|
17561
|
+
})();
|
|
17274
17562
|
var PLAIN_UI = process.env.REASONIX_UI === "plain";
|
|
17275
17563
|
function LoopStatusRow({
|
|
17276
17564
|
loop: loop2
|
|
@@ -20201,7 +20489,7 @@ async function codeCommand(opts = {}) {
|
|
|
20201
20489
|
}
|
|
20202
20490
|
|
|
20203
20491
|
// src/cli/commands/diff.ts
|
|
20204
|
-
import { writeFileSync as
|
|
20492
|
+
import { writeFileSync as writeFileSync13 } from "fs";
|
|
20205
20493
|
import { basename as basename3 } from "path";
|
|
20206
20494
|
import { render as render2 } from "ink";
|
|
20207
20495
|
import React30 from "react";
|
|
@@ -20348,7 +20636,7 @@ async function diffCommand(opts) {
|
|
|
20348
20636
|
if (wantMarkdown) {
|
|
20349
20637
|
console.log(renderSummaryTable(report));
|
|
20350
20638
|
const md = renderMarkdown(report);
|
|
20351
|
-
|
|
20639
|
+
writeFileSync13(opts.mdPath, md, "utf8");
|
|
20352
20640
|
console.log(`
|
|
20353
20641
|
markdown report written to ${opts.mdPath}`);
|
|
20354
20642
|
return;
|