reasonix 0.22.0 → 0.23.1
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 +1377 -886
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +222 -119
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -477,10 +477,10 @@ function globalSettingsPath(homeDirOverride) {
|
|
|
477
477
|
function projectSettingsPath(projectRoot) {
|
|
478
478
|
return join(projectRoot, HOOK_SETTINGS_DIRNAME, HOOK_SETTINGS_FILENAME);
|
|
479
479
|
}
|
|
480
|
-
function readSettingsFile(
|
|
481
|
-
if (!existsSync(
|
|
480
|
+
function readSettingsFile(path2) {
|
|
481
|
+
if (!existsSync(path2)) return null;
|
|
482
482
|
try {
|
|
483
|
-
const raw = readFileSync(
|
|
483
|
+
const raw = readFileSync(path2, "utf8");
|
|
484
484
|
const parsed = JSON.parse(raw);
|
|
485
485
|
if (parsed && typeof parsed === "object") return parsed;
|
|
486
486
|
} catch {
|
|
@@ -890,14 +890,14 @@ function collect(prefix, schema, out, required, isRootRequired) {
|
|
|
890
890
|
out[prefix] = schema;
|
|
891
891
|
if (isRootRequired) required.push(prefix);
|
|
892
892
|
}
|
|
893
|
-
function setByPath(target,
|
|
893
|
+
function setByPath(target, path2, value) {
|
|
894
894
|
let cur = target;
|
|
895
|
-
for (let i = 0; i <
|
|
896
|
-
const key =
|
|
895
|
+
for (let i = 0; i < path2.length - 1; i++) {
|
|
896
|
+
const key = path2[i];
|
|
897
897
|
if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
898
898
|
cur = cur[key];
|
|
899
899
|
}
|
|
900
|
-
cur[
|
|
900
|
+
cur[path2[path2.length - 1]] = value;
|
|
901
901
|
}
|
|
902
902
|
|
|
903
903
|
// src/tools.ts
|
|
@@ -1311,10 +1311,10 @@ function sanitizeName(name) {
|
|
|
1311
1311
|
return cleaned || "default";
|
|
1312
1312
|
}
|
|
1313
1313
|
function loadSessionMessages(name) {
|
|
1314
|
-
const
|
|
1315
|
-
if (!existsSync3(
|
|
1314
|
+
const path2 = sessionPath(name);
|
|
1315
|
+
if (!existsSync3(path2)) return [];
|
|
1316
1316
|
try {
|
|
1317
|
-
const raw = readFileSync3(
|
|
1317
|
+
const raw = readFileSync3(path2, "utf8");
|
|
1318
1318
|
const out = [];
|
|
1319
1319
|
for (const line of raw.split(/\r?\n/)) {
|
|
1320
1320
|
const trimmed = line.trim();
|
|
@@ -1331,12 +1331,12 @@ function loadSessionMessages(name) {
|
|
|
1331
1331
|
}
|
|
1332
1332
|
}
|
|
1333
1333
|
function appendSessionMessage(name, message) {
|
|
1334
|
-
const
|
|
1335
|
-
mkdirSync(dirname2(
|
|
1336
|
-
appendFileSync(
|
|
1334
|
+
const path2 = sessionPath(name);
|
|
1335
|
+
mkdirSync(dirname2(path2), { recursive: true });
|
|
1336
|
+
appendFileSync(path2, `${JSON.stringify(message)}
|
|
1337
1337
|
`, "utf8");
|
|
1338
1338
|
try {
|
|
1339
|
-
chmodSync(
|
|
1339
|
+
chmodSync(path2, 384);
|
|
1340
1340
|
} catch {
|
|
1341
1341
|
}
|
|
1342
1342
|
}
|
|
@@ -1348,13 +1348,13 @@ function listSessions() {
|
|
|
1348
1348
|
(f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl")
|
|
1349
1349
|
);
|
|
1350
1350
|
return files.map((file) => {
|
|
1351
|
-
const
|
|
1352
|
-
const stat2 = statSync(
|
|
1351
|
+
const path2 = join3(dir, file);
|
|
1352
|
+
const stat2 = statSync(path2);
|
|
1353
1353
|
const name = file.replace(/\.jsonl$/, "");
|
|
1354
|
-
const messageCount = countLines(
|
|
1354
|
+
const messageCount = countLines(path2);
|
|
1355
1355
|
return {
|
|
1356
1356
|
name,
|
|
1357
|
-
path,
|
|
1357
|
+
path: path2,
|
|
1358
1358
|
size: stat2.size,
|
|
1359
1359
|
messageCount,
|
|
1360
1360
|
mtime: stat2.mtime,
|
|
@@ -1379,11 +1379,11 @@ function loadSessionMeta(name) {
|
|
|
1379
1379
|
}
|
|
1380
1380
|
}
|
|
1381
1381
|
function deleteSession(name) {
|
|
1382
|
-
const
|
|
1382
|
+
const path2 = sessionPath(name);
|
|
1383
1383
|
try {
|
|
1384
|
-
unlinkSync(
|
|
1384
|
+
unlinkSync(path2);
|
|
1385
1385
|
for (const ext of [".events.jsonl", ".pending.json", ".meta.json", ".plan.json"]) {
|
|
1386
|
-
const sidecar =
|
|
1386
|
+
const sidecar = path2.replace(/\.jsonl$/, ext);
|
|
1387
1387
|
try {
|
|
1388
1388
|
unlinkSync(sidecar);
|
|
1389
1389
|
} catch {
|
|
@@ -1395,19 +1395,19 @@ function deleteSession(name) {
|
|
|
1395
1395
|
}
|
|
1396
1396
|
}
|
|
1397
1397
|
function rewriteSession(name, messages) {
|
|
1398
|
-
const
|
|
1399
|
-
mkdirSync(dirname2(
|
|
1398
|
+
const path2 = sessionPath(name);
|
|
1399
|
+
mkdirSync(dirname2(path2), { recursive: true });
|
|
1400
1400
|
const body = messages.map((m) => JSON.stringify(m)).join("\n");
|
|
1401
|
-
writeFileSync(
|
|
1401
|
+
writeFileSync(path2, body ? `${body}
|
|
1402
1402
|
` : "", "utf8");
|
|
1403
1403
|
try {
|
|
1404
|
-
chmodSync(
|
|
1404
|
+
chmodSync(path2, 384);
|
|
1405
1405
|
} catch {
|
|
1406
1406
|
}
|
|
1407
1407
|
}
|
|
1408
|
-
function countLines(
|
|
1408
|
+
function countLines(path2) {
|
|
1409
1409
|
try {
|
|
1410
|
-
const raw = readFileSync3(
|
|
1410
|
+
const raw = readFileSync3(path2, "utf8");
|
|
1411
1411
|
return raw.split(/\r?\n/).filter((l) => l.trim()).length;
|
|
1412
1412
|
} catch {
|
|
1413
1413
|
return 0;
|
|
@@ -1573,7 +1573,7 @@ var StormBreaker = class {
|
|
|
1573
1573
|
if (count >= this.threshold - 1) {
|
|
1574
1574
|
return {
|
|
1575
1575
|
suppress: true,
|
|
1576
|
-
reason:
|
|
1576
|
+
reason: `${name} called with identical args ${count + 1} times \u2014 repeat-loop guard tripped`
|
|
1577
1577
|
};
|
|
1578
1578
|
}
|
|
1579
1579
|
this.recent.push({ name, args, readOnly });
|
|
@@ -1858,6 +1858,7 @@ var CacheFirstLoop = class {
|
|
|
1858
1858
|
_escalateThisTurn = false;
|
|
1859
1859
|
_turnFailureCount = 0;
|
|
1860
1860
|
_turnFailureTypes = {};
|
|
1861
|
+
_turnSelfCorrected = false;
|
|
1861
1862
|
constructor(opts) {
|
|
1862
1863
|
this.client = opts.client;
|
|
1863
1864
|
this.prefix = opts.prefix;
|
|
@@ -1902,7 +1903,12 @@ var CacheFirstLoop = class {
|
|
|
1902
1903
|
}
|
|
1903
1904
|
return def.readOnly !== true;
|
|
1904
1905
|
};
|
|
1905
|
-
this.repair = new ToolCallRepair({
|
|
1906
|
+
this.repair = new ToolCallRepair({
|
|
1907
|
+
allowedToolNames: allowedNames,
|
|
1908
|
+
isMutating,
|
|
1909
|
+
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
1910
|
+
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
1911
|
+
});
|
|
1906
1912
|
this.sessionName = opts.session ?? null;
|
|
1907
1913
|
if (this.sessionName) {
|
|
1908
1914
|
const prior = loadSessionMessages(this.sessionName);
|
|
@@ -1984,6 +1990,21 @@ var CacheFirstLoop = class {
|
|
|
1984
1990
|
}
|
|
1985
1991
|
}
|
|
1986
1992
|
}
|
|
1993
|
+
/** Swap the just-appended assistant entry — used by self-correction to restore the original tool_calls without dropping reasoning_content. */
|
|
1994
|
+
replaceTailAssistantMessage(message) {
|
|
1995
|
+
const entries = this.log.entries;
|
|
1996
|
+
const tail = entries[entries.length - 1];
|
|
1997
|
+
if (!tail || tail.role !== "assistant") return;
|
|
1998
|
+
const kept = entries.slice(0, -1);
|
|
1999
|
+
kept.push(message);
|
|
2000
|
+
this.log.compactInPlace(kept);
|
|
2001
|
+
if (this.sessionName) {
|
|
2002
|
+
try {
|
|
2003
|
+
rewriteSession(this.sessionName, kept);
|
|
2004
|
+
} catch {
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
1987
2008
|
/** "New chat" — drops messages but keeps session + immutable prefix (cache-first invariant). */
|
|
1988
2009
|
clearLog() {
|
|
1989
2010
|
const dropped = this.log.length;
|
|
@@ -2084,7 +2105,7 @@ var CacheFirstLoop = class {
|
|
|
2084
2105
|
if (repair) {
|
|
2085
2106
|
if (repair.scavenged > 0) bump("scavenged", repair.scavenged);
|
|
2086
2107
|
if (repair.truncationsFixed > 0) bump("truncated", repair.truncationsFixed);
|
|
2087
|
-
if (repair.stormsBroken > 0) bump("
|
|
2108
|
+
if (repair.stormsBroken > 0) bump("repeat-loop", repair.stormsBroken);
|
|
2088
2109
|
}
|
|
2089
2110
|
if (bumped && !this._escalateThisTurn && this.autoEscalate && this._turnFailureCount >= FAILURE_ESCALATION_THRESHOLD) {
|
|
2090
2111
|
this._escalateThisTurn = true;
|
|
@@ -2154,6 +2175,7 @@ var CacheFirstLoop = class {
|
|
|
2154
2175
|
this.repair.resetStorm();
|
|
2155
2176
|
this._turnFailureCount = 0;
|
|
2156
2177
|
this._turnFailureTypes = {};
|
|
2178
|
+
this._turnSelfCorrected = false;
|
|
2157
2179
|
this._escalateThisTurn = false;
|
|
2158
2180
|
let armedConsumed = false;
|
|
2159
2181
|
if (this._proArmedForNextTurn) {
|
|
@@ -2505,10 +2527,35 @@ var CacheFirstLoop = class {
|
|
|
2505
2527
|
content: `\u21E7 auto-escalating to ${ESCALATION_MODEL} for the rest of this turn \u2014 flash hit ${this.formatFailureBreakdown()}. Next turn falls back to ${this.model} unless /pro is armed.`
|
|
2506
2528
|
};
|
|
2507
2529
|
}
|
|
2530
|
+
const allSuppressed = report.stormsBroken > 0 && repairedCalls.length === 0 && toolCalls.length > 0;
|
|
2531
|
+
if (allSuppressed && !this._turnSelfCorrected) {
|
|
2532
|
+
this._turnSelfCorrected = true;
|
|
2533
|
+
this.replaceTailAssistantMessage(
|
|
2534
|
+
this.assistantMessage(
|
|
2535
|
+
assistantContent,
|
|
2536
|
+
toolCalls,
|
|
2537
|
+
this.modelForCurrentCall(),
|
|
2538
|
+
reasoningContent
|
|
2539
|
+
)
|
|
2540
|
+
);
|
|
2541
|
+
for (const call of toolCalls) {
|
|
2542
|
+
this.appendAndPersist({
|
|
2543
|
+
role: "tool",
|
|
2544
|
+
tool_call_id: call.id ?? "",
|
|
2545
|
+
name: call.function?.name ?? "",
|
|
2546
|
+
content: "[repeat-loop guard] this call was suppressed because it was identical to a previous call in this turn. Earlier results for it are above \u2014 try a meaningfully different approach, or stop and answer if you have enough."
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
yield {
|
|
2550
|
+
turn: this._turn,
|
|
2551
|
+
role: "warning",
|
|
2552
|
+
content: "Caught a repeated tool call \u2014 let the model see the issue and retry with a different approach."
|
|
2553
|
+
};
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2508
2556
|
if (report.stormsBroken > 0) {
|
|
2509
2557
|
const noteTail = report.notes.length ? ` \u2014 ${report.notes[report.notes.length - 1]}` : "";
|
|
2510
|
-
const
|
|
2511
|
-
const phrase = allSuppressed ? `stopped the model from calling the same tool with identical args repeatedly (all ${toolCalls.length} call(s) this turn were already in the recent-repeat window). Likely a stuck retry \u2014 reword your instruction, rule out the underlying blocker, or try /retry after fixing it` : `suppressed ${report.stormsBroken} repeat tool call(s) that had fired 3+ times with identical args in a sliding window`;
|
|
2558
|
+
const phrase = allSuppressed ? "Stopped a stuck retry loop \u2014 the model kept calling the same tool with identical args after a self-correction nudge. Try /retry, rephrase, or rule out the underlying blocker." : `Suppressed ${report.stormsBroken} repeated tool call(s) \u2014 same name + args fired 3+ times.`;
|
|
2512
2559
|
yield {
|
|
2513
2560
|
turn: this._turn,
|
|
2514
2561
|
role: "warning",
|
|
@@ -2516,7 +2563,6 @@ var CacheFirstLoop = class {
|
|
|
2516
2563
|
};
|
|
2517
2564
|
}
|
|
2518
2565
|
if (repairedCalls.length === 0) {
|
|
2519
|
-
const allSuppressed = report.stormsBroken > 0 && toolCalls.length > 0;
|
|
2520
2566
|
if (allSuppressed) {
|
|
2521
2567
|
yield* this.forceSummaryAfterIterLimit({ reason: "stuck" });
|
|
2522
2568
|
return;
|
|
@@ -2741,6 +2787,11 @@ function stripHallucinatedToolMarkup(s) {
|
|
|
2741
2787
|
out = out.replace(/<|DSML|[\s\S]*$/g, "");
|
|
2742
2788
|
return out.trim();
|
|
2743
2789
|
}
|
|
2790
|
+
function parsePositiveIntEnv(raw) {
|
|
2791
|
+
if (!raw) return void 0;
|
|
2792
|
+
const n = Number.parseInt(raw, 10);
|
|
2793
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
2794
|
+
}
|
|
2744
2795
|
function safeParseToolArgs(raw) {
|
|
2745
2796
|
try {
|
|
2746
2797
|
return JSON.parse(raw);
|
|
@@ -2982,9 +3033,39 @@ function extractDeepSeekErrorMessage(body) {
|
|
|
2982
3033
|
}
|
|
2983
3034
|
|
|
2984
3035
|
// src/at-mentions.ts
|
|
2985
|
-
import { existsSync as existsSync4, readFileSync as
|
|
3036
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2986
3037
|
import { readdir, stat } from "fs/promises";
|
|
2987
3038
|
import { isAbsolute, join as join4, relative, resolve } from "path";
|
|
3039
|
+
|
|
3040
|
+
// src/gitignore.ts
|
|
3041
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3042
|
+
import { readFile } from "fs/promises";
|
|
3043
|
+
import path from "path";
|
|
3044
|
+
import ignore from "ignore";
|
|
3045
|
+
async function loadGitignoreAt(dirAbs) {
|
|
3046
|
+
try {
|
|
3047
|
+
return ignore().add(await readFile(path.join(dirAbs, ".gitignore"), "utf8"));
|
|
3048
|
+
} catch {
|
|
3049
|
+
return null;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
function loadGitignoreAtSync(dirAbs) {
|
|
3053
|
+
try {
|
|
3054
|
+
return ignore().add(readFileSync4(path.join(dirAbs, ".gitignore"), "utf8"));
|
|
3055
|
+
} catch {
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
function ignoredByLayers(layers, abs, isDir) {
|
|
3060
|
+
for (const layer of layers) {
|
|
3061
|
+
const rel = path.relative(layer.dirAbs, abs).split(path.sep).join("/");
|
|
3062
|
+
if (!rel || rel.startsWith("..")) continue;
|
|
3063
|
+
if (layer.ig.ignores(isDir ? `${rel}/` : rel)) return true;
|
|
3064
|
+
}
|
|
3065
|
+
return false;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
// src/at-mentions.ts
|
|
2988
3069
|
var DEFAULT_AT_MENTION_MAX_BYTES = 64 * 1024;
|
|
2989
3070
|
var DEFAULT_PICKER_IGNORE_DIRS = [
|
|
2990
3071
|
"node_modules",
|
|
@@ -3006,12 +3087,18 @@ function listFilesSync(root, opts = {}) {
|
|
|
3006
3087
|
return listFilesWithStatsSync(root, opts).map((e) => e.path);
|
|
3007
3088
|
}
|
|
3008
3089
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
3009
|
-
const maxResults = Math.max(1, opts.maxResults ??
|
|
3010
|
-
const
|
|
3090
|
+
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
3091
|
+
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3011
3092
|
const rootAbs = resolve(root);
|
|
3093
|
+
const respectGi = opts.respectGitignore !== false;
|
|
3012
3094
|
const out = [];
|
|
3013
|
-
const walk2 = (dirAbs, dirRel) => {
|
|
3095
|
+
const walk2 = (dirAbs, dirRel, layers) => {
|
|
3014
3096
|
if (out.length >= maxResults) return;
|
|
3097
|
+
let effectiveLayers = layers;
|
|
3098
|
+
if (respectGi) {
|
|
3099
|
+
const ig = loadGitignoreAtSync(dirAbs);
|
|
3100
|
+
if (ig) effectiveLayers = [...layers, { dirAbs, ig }];
|
|
3101
|
+
}
|
|
3015
3102
|
let entries;
|
|
3016
3103
|
try {
|
|
3017
3104
|
entries = readdirSync2(dirAbs, { withFileTypes: true });
|
|
@@ -3022,29 +3109,38 @@ function listFilesWithStatsSync(root, opts = {}) {
|
|
|
3022
3109
|
for (const ent of entries) {
|
|
3023
3110
|
if (out.length >= maxResults) return;
|
|
3024
3111
|
const relPath = dirRel ? `${dirRel}/${ent.name}` : ent.name;
|
|
3112
|
+
const absPath = join4(dirAbs, ent.name);
|
|
3025
3113
|
if (ent.isDirectory()) {
|
|
3026
|
-
if (ent.name.startsWith(".") ||
|
|
3027
|
-
|
|
3114
|
+
if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
|
|
3115
|
+
if (ignoredByLayers(effectiveLayers, absPath, true)) continue;
|
|
3116
|
+
walk2(absPath, relPath, effectiveLayers);
|
|
3028
3117
|
} else if (ent.isFile()) {
|
|
3118
|
+
if (ignoredByLayers(effectiveLayers, absPath, false)) continue;
|
|
3029
3119
|
let mtimeMs = 0;
|
|
3030
3120
|
try {
|
|
3031
|
-
mtimeMs = statSync2(
|
|
3121
|
+
mtimeMs = statSync2(absPath).mtimeMs;
|
|
3032
3122
|
} catch {
|
|
3033
3123
|
}
|
|
3034
3124
|
out.push({ path: relPath, mtimeMs });
|
|
3035
3125
|
}
|
|
3036
3126
|
}
|
|
3037
3127
|
};
|
|
3038
|
-
walk2(rootAbs, "");
|
|
3128
|
+
walk2(rootAbs, "", []);
|
|
3039
3129
|
return out;
|
|
3040
3130
|
}
|
|
3041
3131
|
async function listFilesWithStatsAsync(root, opts = {}) {
|
|
3042
|
-
const maxResults = Math.max(1, opts.maxResults ??
|
|
3043
|
-
const
|
|
3132
|
+
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
3133
|
+
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
3044
3134
|
const rootAbs = resolve(root);
|
|
3135
|
+
const respectGi = opts.respectGitignore !== false;
|
|
3045
3136
|
const out = [];
|
|
3046
|
-
const walk2 = async (dirAbs, dirRel) => {
|
|
3137
|
+
const walk2 = async (dirAbs, dirRel, layers) => {
|
|
3047
3138
|
if (out.length >= maxResults) return;
|
|
3139
|
+
let effectiveLayers = layers;
|
|
3140
|
+
if (respectGi) {
|
|
3141
|
+
const ig = await loadGitignoreAt(dirAbs);
|
|
3142
|
+
if (ig) effectiveLayers = [...layers, { dirAbs, ig }];
|
|
3143
|
+
}
|
|
3048
3144
|
let entries;
|
|
3049
3145
|
try {
|
|
3050
3146
|
entries = await readdir(dirAbs, { withFileTypes: true });
|
|
@@ -3055,35 +3151,42 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
3055
3151
|
const fileEnts = [];
|
|
3056
3152
|
for (const ent of entries) {
|
|
3057
3153
|
if (out.length >= maxResults) break;
|
|
3154
|
+
const relPath = dirRel ? `${dirRel}/${ent.name}` : ent.name;
|
|
3155
|
+
const absPath = join4(dirAbs, ent.name);
|
|
3058
3156
|
if (ent.isDirectory()) {
|
|
3059
|
-
if (ent.name.startsWith(".") ||
|
|
3157
|
+
if (ent.name.startsWith(".") || ignoreDirs.has(ent.name)) continue;
|
|
3158
|
+
if (ignoredByLayers(effectiveLayers, absPath, true)) continue;
|
|
3060
3159
|
if (fileEnts.length > 0) {
|
|
3061
|
-
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3160
|
+
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults, effectiveLayers);
|
|
3062
3161
|
fileEnts.length = 0;
|
|
3063
3162
|
if (out.length >= maxResults) return;
|
|
3064
3163
|
}
|
|
3065
|
-
await walk2(
|
|
3164
|
+
await walk2(absPath, relPath, effectiveLayers);
|
|
3066
3165
|
} else if (ent.isFile()) {
|
|
3067
3166
|
fileEnts.push(ent);
|
|
3068
3167
|
}
|
|
3069
3168
|
}
|
|
3070
3169
|
if (fileEnts.length > 0 && out.length < maxResults) {
|
|
3071
|
-
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults);
|
|
3170
|
+
await statBatch(fileEnts, dirAbs, dirRel, out, maxResults, effectiveLayers);
|
|
3072
3171
|
}
|
|
3073
3172
|
};
|
|
3074
|
-
await walk2(rootAbs, "");
|
|
3173
|
+
await walk2(rootAbs, "", []);
|
|
3075
3174
|
return out;
|
|
3076
3175
|
}
|
|
3077
|
-
async function statBatch(ents, dirAbs, dirRel, out, maxResults) {
|
|
3078
|
-
const
|
|
3079
|
-
const
|
|
3176
|
+
async function statBatch(ents, dirAbs, dirRel, out, maxResults, layers) {
|
|
3177
|
+
const accepted = [];
|
|
3178
|
+
for (const e of ents) {
|
|
3179
|
+
if (out.length + accepted.length >= maxResults) break;
|
|
3180
|
+
if (ignoredByLayers(layers, join4(dirAbs, e.name), false)) continue;
|
|
3181
|
+
accepted.push(e);
|
|
3182
|
+
}
|
|
3080
3183
|
const stats = await Promise.all(
|
|
3081
|
-
|
|
3184
|
+
accepted.map(
|
|
3082
3185
|
(e) => stat(join4(dirAbs, e.name)).then((s) => s.mtimeMs).catch(() => 0)
|
|
3083
3186
|
)
|
|
3084
3187
|
);
|
|
3085
|
-
for (let i = 0; i <
|
|
3086
|
-
const ent =
|
|
3188
|
+
for (let i = 0; i < accepted.length; i++) {
|
|
3189
|
+
const ent = accepted[i];
|
|
3087
3190
|
out.push({
|
|
3088
3191
|
path: dirRel ? `${dirRel}/${ent.name}` : ent.name,
|
|
3089
3192
|
mtimeMs: stats[i] ?? 0
|
|
@@ -3224,20 +3327,20 @@ var defaultFs = {
|
|
|
3224
3327
|
return 0;
|
|
3225
3328
|
}
|
|
3226
3329
|
},
|
|
3227
|
-
read: (p) =>
|
|
3330
|
+
read: (p) => readFileSync5(p, "utf8")
|
|
3228
3331
|
};
|
|
3229
3332
|
|
|
3230
3333
|
// src/memory/project.ts
|
|
3231
|
-
import { existsSync as existsSync5, readFileSync as
|
|
3334
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
3232
3335
|
import { join as join5 } from "path";
|
|
3233
3336
|
var PROJECT_MEMORY_FILE = "REASONIX.md";
|
|
3234
3337
|
var PROJECT_MEMORY_MAX_CHARS = 8e3;
|
|
3235
3338
|
function readProjectMemory(rootDir) {
|
|
3236
|
-
const
|
|
3237
|
-
if (!existsSync5(
|
|
3339
|
+
const path2 = join5(rootDir, PROJECT_MEMORY_FILE);
|
|
3340
|
+
if (!existsSync5(path2)) return null;
|
|
3238
3341
|
let raw;
|
|
3239
3342
|
try {
|
|
3240
|
-
raw =
|
|
3343
|
+
raw = readFileSync6(path2, "utf8");
|
|
3241
3344
|
} catch {
|
|
3242
3345
|
return null;
|
|
3243
3346
|
}
|
|
@@ -3247,7 +3350,7 @@ function readProjectMemory(rootDir) {
|
|
|
3247
3350
|
const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;
|
|
3248
3351
|
const content = truncated ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
|
|
3249
3352
|
\u2026 (truncated ${originalChars - PROJECT_MEMORY_MAX_CHARS} chars)` : trimmed;
|
|
3250
|
-
return { path, content, originalChars, truncated };
|
|
3353
|
+
return { path: path2, content, originalChars, truncated };
|
|
3251
3354
|
}
|
|
3252
3355
|
function memoryEnabled() {
|
|
3253
3356
|
const env = process.env.REASONIX_MEMORY;
|
|
@@ -3275,7 +3378,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
3275
3378
|
import {
|
|
3276
3379
|
existsSync as existsSync7,
|
|
3277
3380
|
mkdirSync as mkdirSync2,
|
|
3278
|
-
readFileSync as
|
|
3381
|
+
readFileSync as readFileSync8,
|
|
3279
3382
|
readdirSync as readdirSync4,
|
|
3280
3383
|
unlinkSync as unlinkSync2,
|
|
3281
3384
|
writeFileSync as writeFileSync2
|
|
@@ -3284,7 +3387,7 @@ import { homedir as homedir4 } from "os";
|
|
|
3284
3387
|
import { join as join7, resolve as resolve3 } from "path";
|
|
3285
3388
|
|
|
3286
3389
|
// src/skills.ts
|
|
3287
|
-
import { existsSync as existsSync6, readFileSync as
|
|
3390
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
3288
3391
|
import { homedir as homedir3 } from "os";
|
|
3289
3392
|
import { join as join6, resolve as resolve2 } from "path";
|
|
3290
3393
|
|
|
@@ -3421,10 +3524,10 @@ var SkillStore = class {
|
|
|
3421
3524
|
}
|
|
3422
3525
|
return null;
|
|
3423
3526
|
}
|
|
3424
|
-
parse(
|
|
3527
|
+
parse(path2, stem, scope) {
|
|
3425
3528
|
let raw;
|
|
3426
3529
|
try {
|
|
3427
|
-
raw =
|
|
3530
|
+
raw = readFileSync7(path2, "utf8");
|
|
3428
3531
|
} catch {
|
|
3429
3532
|
return null;
|
|
3430
3533
|
}
|
|
@@ -3435,7 +3538,7 @@ var SkillStore = class {
|
|
|
3435
3538
|
description: (data.description ?? "").trim(),
|
|
3436
3539
|
body: body.trim(),
|
|
3437
3540
|
scope,
|
|
3438
|
-
path,
|
|
3541
|
+
path: path2,
|
|
3439
3542
|
allowedTools: data["allowed-tools"],
|
|
3440
3543
|
runAs: parseRunAs(data.runAs),
|
|
3441
3544
|
model: data.model?.startsWith("deepseek-") ? data.model : void 0
|
|
@@ -3758,7 +3861,7 @@ var MemoryStore = class {
|
|
|
3758
3861
|
if (!existsSync7(file)) return null;
|
|
3759
3862
|
let raw;
|
|
3760
3863
|
try {
|
|
3761
|
-
raw =
|
|
3864
|
+
raw = readFileSync8(file, "utf8");
|
|
3762
3865
|
} catch {
|
|
3763
3866
|
return null;
|
|
3764
3867
|
}
|
|
@@ -3776,7 +3879,7 @@ var MemoryStore = class {
|
|
|
3776
3879
|
if (!existsSync7(file)) {
|
|
3777
3880
|
throw new Error(`memory not found: scope=${scope} name=${name}`);
|
|
3778
3881
|
}
|
|
3779
|
-
const raw =
|
|
3882
|
+
const raw = readFileSync8(file, "utf8");
|
|
3780
3883
|
const { data, body } = parseFrontmatter2(raw);
|
|
3781
3884
|
return {
|
|
3782
3885
|
name: data.name ?? name,
|
|
@@ -3878,11 +3981,11 @@ var MemoryStore = class {
|
|
|
3878
3981
|
}
|
|
3879
3982
|
};
|
|
3880
3983
|
function readGlobalReasonixMemory(homeDir = join7(homedir4(), ".reasonix")) {
|
|
3881
|
-
const
|
|
3882
|
-
if (!existsSync7(
|
|
3984
|
+
const path2 = join7(homeDir, "REASONIX.md");
|
|
3985
|
+
if (!existsSync7(path2)) return null;
|
|
3883
3986
|
let raw;
|
|
3884
3987
|
try {
|
|
3885
|
-
raw =
|
|
3988
|
+
raw = readFileSync8(path2, "utf8");
|
|
3886
3989
|
} catch {
|
|
3887
3990
|
return null;
|
|
3888
3991
|
}
|
|
@@ -3892,7 +3995,7 @@ function readGlobalReasonixMemory(homeDir = join7(homedir4(), ".reasonix")) {
|
|
|
3892
3995
|
const truncated = originalChars > 8e3;
|
|
3893
3996
|
const content = truncated ? `${trimmed.slice(0, 8e3)}
|
|
3894
3997
|
\u2026 (truncated ${originalChars - 8e3} chars)` : trimmed;
|
|
3895
|
-
return { path, content, originalChars, truncated };
|
|
3998
|
+
return { path: path2, content, originalChars, truncated };
|
|
3896
3999
|
}
|
|
3897
4000
|
function applyGlobalReasonixMemory(basePrompt, homeDir) {
|
|
3898
4001
|
if (!memoryEnabled()) return basePrompt;
|
|
@@ -4613,7 +4716,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
4613
4716
|
});
|
|
4614
4717
|
}
|
|
4615
4718
|
try {
|
|
4616
|
-
const
|
|
4719
|
+
const path2 = store.write({
|
|
4617
4720
|
name: args.name,
|
|
4618
4721
|
type: args.type,
|
|
4619
4722
|
scope: args.scope,
|
|
@@ -4626,7 +4729,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
4626
4729
|
"",
|
|
4627
4730
|
"TREAT THIS AS ESTABLISHED FACT for the rest of this session.",
|
|
4628
4731
|
"The user just told you \u2014 don't re-explore the filesystem to re-derive it.",
|
|
4629
|
-
`(Saved to ${
|
|
4732
|
+
`(Saved to ${path2}; pins into the system prompt on next /new or launch.)`
|
|
4630
4733
|
].join("\n");
|
|
4631
4734
|
} catch (err) {
|
|
4632
4735
|
return JSON.stringify({ error: `remember failed: ${err.message}` });
|
|
@@ -6355,12 +6458,12 @@ ${i + 1}. ${r.title}`);
|
|
|
6355
6458
|
}
|
|
6356
6459
|
|
|
6357
6460
|
// src/env.ts
|
|
6358
|
-
import { readFileSync as
|
|
6461
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
6359
6462
|
import { resolve as resolve7 } from "path";
|
|
6360
|
-
function loadDotenv(
|
|
6463
|
+
function loadDotenv(path2 = ".env") {
|
|
6361
6464
|
let raw;
|
|
6362
6465
|
try {
|
|
6363
|
-
raw =
|
|
6466
|
+
raw = readFileSync9(resolve7(process.cwd(), path2), "utf8");
|
|
6364
6467
|
} catch {
|
|
6365
6468
|
return;
|
|
6366
6469
|
}
|
|
@@ -6379,7 +6482,7 @@ function loadDotenv(path = ".env") {
|
|
|
6379
6482
|
}
|
|
6380
6483
|
|
|
6381
6484
|
// src/transcript/log.ts
|
|
6382
|
-
import { createWriteStream, readFileSync as
|
|
6485
|
+
import { createWriteStream, readFileSync as readFileSync10 } from "fs";
|
|
6383
6486
|
function recordFromLoopEvent(ev, extra) {
|
|
6384
6487
|
const rec = {
|
|
6385
6488
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6424,13 +6527,13 @@ function writeMeta(stream, meta) {
|
|
|
6424
6527
|
stream.write(`${JSON.stringify(line)}
|
|
6425
6528
|
`);
|
|
6426
6529
|
}
|
|
6427
|
-
function openTranscriptFile(
|
|
6428
|
-
const stream = createWriteStream(
|
|
6530
|
+
function openTranscriptFile(path2, meta) {
|
|
6531
|
+
const stream = createWriteStream(path2, { flags: "a" });
|
|
6429
6532
|
writeMeta(stream, meta);
|
|
6430
6533
|
return stream;
|
|
6431
6534
|
}
|
|
6432
|
-
function readTranscript(
|
|
6433
|
-
const raw =
|
|
6535
|
+
function readTranscript(path2) {
|
|
6536
|
+
const raw = readFileSync10(path2, "utf8");
|
|
6434
6537
|
return parseTranscript(raw);
|
|
6435
6538
|
}
|
|
6436
6539
|
function isPlanStateEmptyShape(s) {
|
|
@@ -6461,8 +6564,8 @@ function parseTranscript(raw) {
|
|
|
6461
6564
|
}
|
|
6462
6565
|
|
|
6463
6566
|
// src/transcript/replay.ts
|
|
6464
|
-
function replayFromFile(
|
|
6465
|
-
const parsed = readTranscript(
|
|
6567
|
+
function replayFromFile(path2) {
|
|
6568
|
+
const parsed = readTranscript(path2);
|
|
6466
6569
|
return { parsed, stats: computeReplayStats(parsed.records) };
|
|
6467
6570
|
}
|
|
6468
6571
|
function computeReplayStats(records) {
|
|
@@ -6877,7 +6980,7 @@ function truncate(s, n) {
|
|
|
6877
6980
|
}
|
|
6878
6981
|
|
|
6879
6982
|
// src/version.ts
|
|
6880
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as
|
|
6983
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
|
|
6881
6984
|
import { homedir as homedir5 } from "os";
|
|
6882
6985
|
import { dirname as dirname4, join as join9 } from "path";
|
|
6883
6986
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -6890,7 +6993,7 @@ function readPackageVersion() {
|
|
|
6890
6993
|
for (let i = 0; i < 6; i++) {
|
|
6891
6994
|
const p = join9(dir, "package.json");
|
|
6892
6995
|
if (existsSync9(p)) {
|
|
6893
|
-
const pkg = JSON.parse(
|
|
6996
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
6894
6997
|
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
6895
6998
|
return pkg.version;
|
|
6896
6999
|
}
|
|
@@ -6909,7 +7012,7 @@ function cachePath(homeDirOverride) {
|
|
|
6909
7012
|
}
|
|
6910
7013
|
function readCache(homeDirOverride) {
|
|
6911
7014
|
try {
|
|
6912
|
-
const raw =
|
|
7015
|
+
const raw = readFileSync11(cachePath(homeDirOverride), "utf8");
|
|
6913
7016
|
const parsed = JSON.parse(raw);
|
|
6914
7017
|
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
6915
7018
|
return parsed;
|
|
@@ -7715,7 +7818,7 @@ async function trySection(load) {
|
|
|
7715
7818
|
}
|
|
7716
7819
|
|
|
7717
7820
|
// src/code/edit-blocks.ts
|
|
7718
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as
|
|
7821
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
7719
7822
|
import { dirname as dirname5, resolve as resolve8 } from "path";
|
|
7720
7823
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
7721
7824
|
function parseEditBlocks(text) {
|
|
@@ -7758,7 +7861,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
7758
7861
|
writeFileSync4(absTarget, block.replace, "utf8");
|
|
7759
7862
|
return { path: block.path, status: "created" };
|
|
7760
7863
|
}
|
|
7761
|
-
const content =
|
|
7864
|
+
const content = readFileSync12(absTarget, "utf8");
|
|
7762
7865
|
if (searchEmpty) {
|
|
7763
7866
|
return {
|
|
7764
7867
|
path: block.path,
|
|
@@ -7797,7 +7900,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
7797
7900
|
continue;
|
|
7798
7901
|
}
|
|
7799
7902
|
try {
|
|
7800
|
-
snapshots.push({ path: b.path, prevContent:
|
|
7903
|
+
snapshots.push({ path: b.path, prevContent: readFileSync12(abs, "utf8") });
|
|
7801
7904
|
} catch {
|
|
7802
7905
|
snapshots.push({ path: b.path, prevContent: null });
|
|
7803
7906
|
}
|
|
@@ -7840,7 +7943,7 @@ function sep() {
|
|
|
7840
7943
|
}
|
|
7841
7944
|
|
|
7842
7945
|
// src/code/prompt.ts
|
|
7843
|
-
import { existsSync as existsSync11, readFileSync as
|
|
7946
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
7844
7947
|
import { join as join10 } from "path";
|
|
7845
7948
|
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.
|
|
7846
7949
|
|
|
@@ -8048,7 +8151,7 @@ function codeSystemPrompt(rootDir, opts = {}) {
|
|
|
8048
8151
|
if (existsSync11(gitignorePath)) {
|
|
8049
8152
|
let content;
|
|
8050
8153
|
try {
|
|
8051
|
-
content =
|
|
8154
|
+
content = readFileSync13(gitignorePath, "utf8");
|
|
8052
8155
|
} catch {
|
|
8053
8156
|
}
|
|
8054
8157
|
if (content !== void 0) {
|
|
@@ -8079,37 +8182,37 @@ ${appendParts.join("\n\n")}`;
|
|
|
8079
8182
|
}
|
|
8080
8183
|
|
|
8081
8184
|
// src/config.ts
|
|
8082
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as
|
|
8185
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
|
|
8083
8186
|
import { homedir as homedir6 } from "os";
|
|
8084
8187
|
import { dirname as dirname6, join as join11 } from "path";
|
|
8085
8188
|
function defaultConfigPath() {
|
|
8086
8189
|
return join11(homedir6(), ".reasonix", "config.json");
|
|
8087
8190
|
}
|
|
8088
|
-
function readConfig(
|
|
8191
|
+
function readConfig(path2 = defaultConfigPath()) {
|
|
8089
8192
|
try {
|
|
8090
|
-
const raw =
|
|
8193
|
+
const raw = readFileSync14(path2, "utf8");
|
|
8091
8194
|
const parsed = JSON.parse(raw);
|
|
8092
8195
|
if (parsed && typeof parsed === "object") return parsed;
|
|
8093
8196
|
} catch {
|
|
8094
8197
|
}
|
|
8095
8198
|
return {};
|
|
8096
8199
|
}
|
|
8097
|
-
function writeConfig(cfg,
|
|
8098
|
-
mkdirSync5(dirname6(
|
|
8099
|
-
writeFileSync5(
|
|
8200
|
+
function writeConfig(cfg, path2 = defaultConfigPath()) {
|
|
8201
|
+
mkdirSync5(dirname6(path2), { recursive: true });
|
|
8202
|
+
writeFileSync5(path2, JSON.stringify(cfg, null, 2), "utf8");
|
|
8100
8203
|
try {
|
|
8101
|
-
chmodSync2(
|
|
8204
|
+
chmodSync2(path2, 384);
|
|
8102
8205
|
} catch {
|
|
8103
8206
|
}
|
|
8104
8207
|
}
|
|
8105
|
-
function loadApiKey(
|
|
8208
|
+
function loadApiKey(path2 = defaultConfigPath()) {
|
|
8106
8209
|
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
|
8107
|
-
return readConfig(
|
|
8210
|
+
return readConfig(path2).apiKey;
|
|
8108
8211
|
}
|
|
8109
|
-
function saveApiKey(key,
|
|
8110
|
-
const cfg = readConfig(
|
|
8212
|
+
function saveApiKey(key, path2 = defaultConfigPath()) {
|
|
8213
|
+
const cfg = readConfig(path2);
|
|
8111
8214
|
cfg.apiKey = key.trim();
|
|
8112
|
-
writeConfig(cfg,
|
|
8215
|
+
writeConfig(cfg, path2);
|
|
8113
8216
|
}
|
|
8114
8217
|
function isPlausibleKey(key) {
|
|
8115
8218
|
const trimmed = key.trim();
|
|
@@ -8126,7 +8229,7 @@ import {
|
|
|
8126
8229
|
appendFileSync as appendFileSync2,
|
|
8127
8230
|
existsSync as existsSync12,
|
|
8128
8231
|
mkdirSync as mkdirSync6,
|
|
8129
|
-
readFileSync as
|
|
8232
|
+
readFileSync as readFileSync15,
|
|
8130
8233
|
statSync as statSync5,
|
|
8131
8234
|
writeFileSync as writeFileSync6
|
|
8132
8235
|
} from "fs";
|
|
@@ -8137,10 +8240,10 @@ function defaultUsageLogPath(homeDirOverride) {
|
|
|
8137
8240
|
}
|
|
8138
8241
|
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
8139
8242
|
var USAGE_RETENTION_DAYS = 365;
|
|
8140
|
-
function compactUsageLogIfLarge(
|
|
8243
|
+
function compactUsageLogIfLarge(path2, now) {
|
|
8141
8244
|
let size;
|
|
8142
8245
|
try {
|
|
8143
|
-
size = statSync5(
|
|
8246
|
+
size = statSync5(path2).size;
|
|
8144
8247
|
} catch {
|
|
8145
8248
|
return;
|
|
8146
8249
|
}
|
|
@@ -8148,7 +8251,7 @@ function compactUsageLogIfLarge(path, now) {
|
|
|
8148
8251
|
const cutoff = now - USAGE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
8149
8252
|
let raw;
|
|
8150
8253
|
try {
|
|
8151
|
-
raw =
|
|
8254
|
+
raw = readFileSync15(path2, "utf8");
|
|
8152
8255
|
} catch {
|
|
8153
8256
|
return;
|
|
8154
8257
|
}
|
|
@@ -8164,7 +8267,7 @@ function compactUsageLogIfLarge(path, now) {
|
|
|
8164
8267
|
}
|
|
8165
8268
|
if (kept.length === lines.filter((l) => l.trim()).length) return;
|
|
8166
8269
|
try {
|
|
8167
|
-
writeFileSync6(
|
|
8270
|
+
writeFileSync6(path2, kept.length > 0 ? `${kept.join("\n")}
|
|
8168
8271
|
` : "", "utf8");
|
|
8169
8272
|
} catch {
|
|
8170
8273
|
}
|
|
@@ -8183,21 +8286,21 @@ function appendUsage(input) {
|
|
|
8183
8286
|
};
|
|
8184
8287
|
if (input.kind === "subagent") record.kind = "subagent";
|
|
8185
8288
|
if (input.subagent) record.subagent = input.subagent;
|
|
8186
|
-
const
|
|
8289
|
+
const path2 = input.path ?? defaultUsageLogPath();
|
|
8187
8290
|
try {
|
|
8188
|
-
mkdirSync6(dirname7(
|
|
8189
|
-
appendFileSync2(
|
|
8291
|
+
mkdirSync6(dirname7(path2), { recursive: true });
|
|
8292
|
+
appendFileSync2(path2, `${JSON.stringify(record)}
|
|
8190
8293
|
`, "utf8");
|
|
8191
|
-
compactUsageLogIfLarge(
|
|
8294
|
+
compactUsageLogIfLarge(path2, record.ts);
|
|
8192
8295
|
} catch {
|
|
8193
8296
|
}
|
|
8194
8297
|
return record;
|
|
8195
8298
|
}
|
|
8196
|
-
function readUsageLog(
|
|
8197
|
-
if (!existsSync12(
|
|
8299
|
+
function readUsageLog(path2 = defaultUsageLogPath()) {
|
|
8300
|
+
if (!existsSync12(path2)) return [];
|
|
8198
8301
|
let raw;
|
|
8199
8302
|
try {
|
|
8200
|
-
raw =
|
|
8303
|
+
raw = readFileSync15(path2, "utf8");
|
|
8201
8304
|
} catch {
|
|
8202
8305
|
return [];
|
|
8203
8306
|
}
|
|
@@ -8303,10 +8406,10 @@ function aggregateUsage(records, opts = {}) {
|
|
|
8303
8406
|
subagents
|
|
8304
8407
|
};
|
|
8305
8408
|
}
|
|
8306
|
-
function formatLogSize(
|
|
8307
|
-
if (!existsSync12(
|
|
8409
|
+
function formatLogSize(path2 = defaultUsageLogPath()) {
|
|
8410
|
+
if (!existsSync12(path2)) return "";
|
|
8308
8411
|
try {
|
|
8309
|
-
const s = statSync5(
|
|
8412
|
+
const s = statSync5(path2);
|
|
8310
8413
|
const bytes = s.size;
|
|
8311
8414
|
if (bytes < 1024) return `${bytes} B`;
|
|
8312
8415
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|