reasonix 0.40.0 → 0.41.0
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/README.md +21 -13
- package/README.zh-CN.md +19 -13
- package/dashboard/app.css +8 -4
- package/dashboard/dist/app.js +279 -224
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/acp-64VQZLDJ.js +708 -0
- package/dist/cli/acp-64VQZLDJ.js.map +1 -0
- package/dist/cli/chat-ZAGX52RV.js +46 -0
- package/dist/cli/{chunk-UCMTWZKU.js → chunk-2CXPDAWX.js} +2 -2
- package/dist/cli/{chunk-CLAN6PVH.js → chunk-4H3ZRJ2U.js} +19 -7
- package/dist/cli/chunk-4H3ZRJ2U.js.map +1 -0
- package/dist/cli/{chunk-A5LSGEEK.js → chunk-4W2CICFQ.js} +21 -10
- package/dist/cli/{chunk-A5LSGEEK.js.map → chunk-4W2CICFQ.js.map} +1 -1
- package/dist/cli/{chunk-CZSJILQP.js → chunk-65Q5HQ26.js} +39 -1
- package/dist/cli/chunk-65Q5HQ26.js.map +1 -0
- package/dist/cli/{chunk-XHQIK7B6.js → chunk-7SPOFTMT.js} +2 -2
- package/dist/cli/{chunk-5GKJLNP2.js → chunk-7VFNPMKG.js} +2 -2
- package/dist/cli/{chunk-UVRXTSK3.js → chunk-A3LL4XDV.js} +8 -2
- package/dist/cli/chunk-A3LL4XDV.js.map +1 -0
- package/dist/cli/{chunk-VLNRQMCI.js → chunk-A7VHMMDE.js} +2 -2
- package/dist/cli/{chunk-R4YTW7PR.js → chunk-ARF3N2SY.js} +56 -12
- package/dist/cli/chunk-ARF3N2SY.js.map +1 -0
- package/dist/cli/{chunk-AVB3WZWU.js → chunk-AT6GGIBV.js} +10 -10
- package/dist/cli/{chunk-RFX7TYVV.js → chunk-BOFL3T45.js} +14 -1
- package/dist/cli/chunk-BOFL3T45.js.map +1 -0
- package/dist/cli/{chunk-SZH34P45.js → chunk-BYZGO3BX.js} +43 -17
- package/dist/cli/chunk-BYZGO3BX.js.map +1 -0
- package/dist/cli/{chunk-7DLHHBGN.js → chunk-CD4SCQL4.js} +6 -4
- package/dist/cli/chunk-CD4SCQL4.js.map +1 -0
- package/dist/cli/{chunk-HCC42PEI.js → chunk-CFY2XLY6.js} +6 -2
- package/dist/cli/chunk-CFY2XLY6.js.map +1 -0
- package/dist/cli/{chunk-26UDIXLD.js → chunk-F2AV2QDK.js} +493 -460
- package/dist/cli/chunk-F2AV2QDK.js.map +1 -0
- package/dist/cli/{chunk-KMWKGPFZ.js → chunk-H4OLWRSX.js} +10 -1
- package/dist/cli/chunk-H4OLWRSX.js.map +1 -0
- package/dist/cli/{chunk-4YV2GBYG.js → chunk-IEA6JOIP.js} +291 -98
- package/dist/cli/chunk-IEA6JOIP.js.map +1 -0
- package/dist/cli/{chunk-WKOMCPXP.js → chunk-KZYLMMU5.js} +21 -13
- package/dist/cli/chunk-KZYLMMU5.js.map +1 -0
- package/dist/cli/{chunk-JWCTX5S4.js → chunk-L7W3HJZQ.js} +2 -2
- package/dist/cli/{chunk-MRLXEMZ7.js → chunk-LN27AKV3.js} +1 -1
- package/dist/cli/chunk-LN27AKV3.js.map +1 -0
- package/dist/cli/{chunk-IYF36OCJ.js → chunk-LTXADNCO.js} +2 -2
- package/dist/cli/{chunk-H7PHYVPM.js → chunk-MHGPBJ2T.js} +44 -8
- package/dist/cli/chunk-MHGPBJ2T.js.map +1 -0
- package/dist/cli/{chunk-ULBW7DYL.js → chunk-RAUPWSYA.js} +2 -2
- package/dist/cli/chunk-SXLJBFIV.js +245 -0
- package/dist/cli/chunk-SXLJBFIV.js.map +1 -0
- package/dist/cli/{chunk-4X3NY5ZM.js → chunk-UV7XJUJH.js} +2 -2
- package/dist/cli/{chunk-XJLZ4HKU.js → chunk-VFG4GIT3.js} +2 -2
- package/dist/cli/{chunk-FFNOMR32.js → chunk-WE3YZULK.js} +2 -2
- package/dist/cli/chunk-Y5XNV3NX.js +25 -0
- package/dist/cli/chunk-Y5XNV3NX.js.map +1 -0
- package/dist/cli/{chunk-XST7BSZJ.js → chunk-YJFKFTAL.js} +7 -1
- package/dist/cli/chunk-YJFKFTAL.js.map +1 -0
- package/dist/cli/{code-YQGVLIT2.js → code-X3M6ENTQ.js} +38 -35
- package/dist/cli/{code-YQGVLIT2.js.map → code-X3M6ENTQ.js.map} +1 -1
- package/dist/cli/{commands-FQZOBLLZ.js → commands-QY7MSQG7.js} +4 -4
- package/dist/cli/{commit-ZS24SHPG.js → commit-BRCQ3OQO.js} +3 -3
- package/dist/cli/{desktop-6OLENOOO.js → desktop-ZTMHQR2Y.js} +247 -28
- package/dist/cli/desktop-ZTMHQR2Y.js.map +1 -0
- package/dist/cli/{diff-2VUKNGEI.js → diff-YASCB7PU.js} +7 -7
- package/dist/cli/{doctor-JO2WNN6C.js → doctor-XCN5ETVP.js} +9 -9
- package/dist/cli/{events-APSVNROZ.js → events-2AJTXR7I.js} +3 -3
- package/dist/cli/index.js +69 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-DCKOE5RF.js → mcp-YMWBLRR7.js} +2 -2
- package/dist/cli/{mcp-browse-D6GBP5RQ.js → mcp-browse-XLDUE6SB.js} +7 -3
- package/dist/cli/mcp-browse-XLDUE6SB.js.map +1 -0
- package/dist/cli/{mcp-inspect-KFGFPJ3E.js → mcp-inspect-H4D2HSJP.js} +5 -7
- package/dist/cli/{mcp-inspect-KFGFPJ3E.js.map → mcp-inspect-H4D2HSJP.js.map} +1 -1
- package/dist/cli/{prompt-PKCCLLAD.js → prompt-RSIHN62V.js} +4 -3
- package/dist/cli/{prune-sessions-LV33R47N.js → prune-sessions-4N3BVST2.js} +2 -2
- package/dist/cli/{replay-WFCYX7XF.js → replay-3GTWM75X.js} +8 -8
- package/dist/cli/{run-IUJYEPMT.js → run-BLZPTRDX.js} +19 -21
- package/dist/cli/{run-IUJYEPMT.js.map → run-BLZPTRDX.js.map} +1 -1
- package/dist/cli/{server-CN4QPPVJ.js → server-DRFPXXSH.js} +16 -12
- package/dist/cli/{server-CN4QPPVJ.js.map → server-DRFPXXSH.js.map} +1 -1
- package/dist/cli/{sessions-F5GPGTJN.js → sessions-BOWFPTXT.js} +13 -13
- package/dist/cli/{setup-WWMDBPSB.js → setup-FQL2JJC2.js} +5 -5
- package/dist/cli/version-XQXYSJ5L.js +30 -0
- package/dist/index.d.ts +148 -103
- package/dist/index.js +468 -134
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/cli/chat-G7CUW4ZI.js +0 -45
- package/dist/cli/chunk-26UDIXLD.js.map +0 -1
- package/dist/cli/chunk-4YV2GBYG.js.map +0 -1
- package/dist/cli/chunk-7DLHHBGN.js.map +0 -1
- package/dist/cli/chunk-CLAN6PVH.js.map +0 -1
- package/dist/cli/chunk-CPTZ5OHX.js +0 -18
- package/dist/cli/chunk-CPTZ5OHX.js.map +0 -1
- package/dist/cli/chunk-CZSJILQP.js.map +0 -1
- package/dist/cli/chunk-H7PHYVPM.js.map +0 -1
- package/dist/cli/chunk-HCC42PEI.js.map +0 -1
- package/dist/cli/chunk-KMWKGPFZ.js.map +0 -1
- package/dist/cli/chunk-MRLXEMZ7.js.map +0 -1
- package/dist/cli/chunk-R4YTW7PR.js.map +0 -1
- package/dist/cli/chunk-RFX7TYVV.js.map +0 -1
- package/dist/cli/chunk-SZH34P45.js.map +0 -1
- package/dist/cli/chunk-UVRXTSK3.js.map +0 -1
- package/dist/cli/chunk-WKOMCPXP.js.map +0 -1
- package/dist/cli/chunk-XST7BSZJ.js.map +0 -1
- package/dist/cli/desktop-6OLENOOO.js.map +0 -1
- package/dist/cli/mcp-browse-D6GBP5RQ.js.map +0 -1
- package/dist/cli/version-KQUPV6T5.js +0 -30
- /package/dist/cli/{chat-G7CUW4ZI.js.map → chat-ZAGX52RV.js.map} +0 -0
- /package/dist/cli/{chunk-UCMTWZKU.js.map → chunk-2CXPDAWX.js.map} +0 -0
- /package/dist/cli/{chunk-XHQIK7B6.js.map → chunk-7SPOFTMT.js.map} +0 -0
- /package/dist/cli/{chunk-5GKJLNP2.js.map → chunk-7VFNPMKG.js.map} +0 -0
- /package/dist/cli/{chunk-VLNRQMCI.js.map → chunk-A7VHMMDE.js.map} +0 -0
- /package/dist/cli/{chunk-AVB3WZWU.js.map → chunk-AT6GGIBV.js.map} +0 -0
- /package/dist/cli/{chunk-JWCTX5S4.js.map → chunk-L7W3HJZQ.js.map} +0 -0
- /package/dist/cli/{chunk-IYF36OCJ.js.map → chunk-LTXADNCO.js.map} +0 -0
- /package/dist/cli/{chunk-ULBW7DYL.js.map → chunk-RAUPWSYA.js.map} +0 -0
- /package/dist/cli/{chunk-4X3NY5ZM.js.map → chunk-UV7XJUJH.js.map} +0 -0
- /package/dist/cli/{chunk-XJLZ4HKU.js.map → chunk-VFG4GIT3.js.map} +0 -0
- /package/dist/cli/{chunk-FFNOMR32.js.map → chunk-WE3YZULK.js.map} +0 -0
- /package/dist/cli/{commands-FQZOBLLZ.js.map → commands-QY7MSQG7.js.map} +0 -0
- /package/dist/cli/{commit-ZS24SHPG.js.map → commit-BRCQ3OQO.js.map} +0 -0
- /package/dist/cli/{diff-2VUKNGEI.js.map → diff-YASCB7PU.js.map} +0 -0
- /package/dist/cli/{doctor-JO2WNN6C.js.map → doctor-XCN5ETVP.js.map} +0 -0
- /package/dist/cli/{events-APSVNROZ.js.map → events-2AJTXR7I.js.map} +0 -0
- /package/dist/cli/{mcp-DCKOE5RF.js.map → mcp-YMWBLRR7.js.map} +0 -0
- /package/dist/cli/{prompt-PKCCLLAD.js.map → prompt-RSIHN62V.js.map} +0 -0
- /package/dist/cli/{prune-sessions-LV33R47N.js.map → prune-sessions-4N3BVST2.js.map} +0 -0
- /package/dist/cli/{replay-WFCYX7XF.js.map → replay-3GTWM75X.js.map} +0 -0
- /package/dist/cli/{sessions-F5GPGTJN.js.map → sessions-BOWFPTXT.js.map} +0 -0
- /package/dist/cli/{setup-WWMDBPSB.js.map → setup-FQL2JJC2.js.map} +0 -0
- /package/dist/cli/{version-KQUPV6T5.js.map → version-XQXYSJ5L.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
MemoryStore,
|
|
4
4
|
sanitizeMemoryName
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ARF3N2SY.js";
|
|
6
6
|
import {
|
|
7
7
|
countTokens,
|
|
8
8
|
estimateConversationTokens,
|
|
@@ -10,20 +10,20 @@ import {
|
|
|
10
10
|
} from "./chunk-DAEAAVDF.js";
|
|
11
11
|
import {
|
|
12
12
|
Usage
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-H4OLWRSX.js";
|
|
14
14
|
import {
|
|
15
15
|
applyEdit,
|
|
16
16
|
applyMultiEdit,
|
|
17
17
|
pauseGate
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-BYZGO3BX.js";
|
|
19
19
|
import {
|
|
20
20
|
NEGATIVE_CLAIM_RULE,
|
|
21
21
|
TUI_FORMATTING_RULES
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-CD4SCQL4.js";
|
|
23
23
|
import {
|
|
24
24
|
formatHookOutcomeMessage,
|
|
25
25
|
runHooks
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-WE3YZULK.js";
|
|
27
27
|
import {
|
|
28
28
|
ignoredByLayers,
|
|
29
29
|
loadGitignoreAt,
|
|
@@ -35,17 +35,18 @@ import {
|
|
|
35
35
|
loadSessionMessages,
|
|
36
36
|
loadSessionMeta,
|
|
37
37
|
rewriteSession
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-YJFKFTAL.js";
|
|
39
39
|
import {
|
|
40
40
|
t
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-MHGPBJ2T.js";
|
|
42
42
|
import {
|
|
43
43
|
DEFAULT_INDEX_EXCLUDES,
|
|
44
44
|
addProjectPathAllowed,
|
|
45
|
+
loadMemoryTypeRegistry,
|
|
45
46
|
loadProjectPathAllowed,
|
|
46
47
|
webSearchEndpoint,
|
|
47
48
|
webSearchEngine
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-65Q5HQ26.js";
|
|
49
50
|
import {
|
|
50
51
|
DEEPSEEK_CONTEXT_TOKENS,
|
|
51
52
|
DEFAULT_CONTEXT_TOKENS,
|
|
@@ -557,17 +558,25 @@ function missingRequiredParam(schema, args) {
|
|
|
557
558
|
// src/memory/runtime.ts
|
|
558
559
|
import { createHash } from "crypto";
|
|
559
560
|
var ImmutablePrefix = class {
|
|
561
|
+
/** Stable across turns; rebuilt only on /new when REASONIX.md changed on disk. */
|
|
560
562
|
system;
|
|
561
563
|
/** Each `addTool` costs one cache-miss turn — DeepSeek's prefix cache is keyed by full tool list. */
|
|
562
564
|
_toolSpecs;
|
|
563
565
|
fewShots;
|
|
564
|
-
/** Invalidated
|
|
566
|
+
/** Invalidated by addTool / removeTool / replaceSystem; bypassing any of those leaves cache stale → fingerprint diverges from sent prefix. */
|
|
565
567
|
_fingerprintCache = null;
|
|
566
568
|
constructor(opts) {
|
|
567
569
|
this.system = opts.system;
|
|
568
570
|
this._toolSpecs = [...opts.toolSpecs ?? []];
|
|
569
571
|
this.fewShots = Object.freeze([...opts.fewShots ?? []]);
|
|
570
572
|
}
|
|
573
|
+
/** Replaces the system prompt; returns true iff the string actually changed. Caller must accept a cache miss on the next turn. */
|
|
574
|
+
replaceSystem(s) {
|
|
575
|
+
if (this.system === s) return false;
|
|
576
|
+
this.system = s;
|
|
577
|
+
this._fingerprintCache = null;
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
571
580
|
get toolSpecs() {
|
|
572
581
|
return this._toolSpecs;
|
|
573
582
|
}
|
|
@@ -1051,6 +1060,10 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
|
1051
1060
|
}
|
|
1052
1061
|
|
|
1053
1062
|
// src/loop/healing.ts
|
|
1063
|
+
var _stampSeq = 0;
|
|
1064
|
+
function stampMissingIds(calls) {
|
|
1065
|
+
return calls.map((c) => c.id ? c : { ...c, id: `z-ext-${Date.now()}-${_stampSeq++}` });
|
|
1066
|
+
}
|
|
1054
1067
|
function fixToolCallPairing(messages) {
|
|
1055
1068
|
const out = [];
|
|
1056
1069
|
let droppedAssistantCalls = 0;
|
|
@@ -1058,9 +1071,10 @@ function fixToolCallPairing(messages) {
|
|
|
1058
1071
|
for (let i = 0; i < messages.length; i++) {
|
|
1059
1072
|
const msg = messages[i];
|
|
1060
1073
|
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
1074
|
+
const calls = stampMissingIds(msg.tool_calls);
|
|
1061
1075
|
const needed = /* @__PURE__ */ new Set();
|
|
1062
|
-
for (const call of
|
|
1063
|
-
if (call
|
|
1076
|
+
for (const call of calls) {
|
|
1077
|
+
if (call.id) needed.add(call.id);
|
|
1064
1078
|
}
|
|
1065
1079
|
const candidates = [];
|
|
1066
1080
|
let j = i + 1;
|
|
@@ -1074,7 +1088,7 @@ function fixToolCallPairing(messages) {
|
|
|
1074
1088
|
j++;
|
|
1075
1089
|
}
|
|
1076
1090
|
if (needed.size === 0) {
|
|
1077
|
-
out.push(msg);
|
|
1091
|
+
out.push({ ...msg, tool_calls: calls });
|
|
1078
1092
|
for (const r of candidates) out.push(r);
|
|
1079
1093
|
i = j - 1;
|
|
1080
1094
|
} else {
|
|
@@ -1138,6 +1152,32 @@ function* hookWarnings(outcomes, turn) {
|
|
|
1138
1152
|
}
|
|
1139
1153
|
}
|
|
1140
1154
|
|
|
1155
|
+
// src/loop/read-only-loop-tracker.ts
|
|
1156
|
+
var READONLY_LOOP_ESCALATION_THRESHOLD = 8;
|
|
1157
|
+
var ReadOnlyLoopTracker = class {
|
|
1158
|
+
streak = 0;
|
|
1159
|
+
threshold;
|
|
1160
|
+
constructor(threshold = READONLY_LOOP_ESCALATION_THRESHOLD) {
|
|
1161
|
+
this.threshold = Math.max(1, threshold);
|
|
1162
|
+
}
|
|
1163
|
+
reset() {
|
|
1164
|
+
this.streak = 0;
|
|
1165
|
+
}
|
|
1166
|
+
/** True ONLY on the call where the streak crosses the configured threshold. */
|
|
1167
|
+
noteAndCrossedThreshold(isReadOnly) {
|
|
1168
|
+
if (!isReadOnly) {
|
|
1169
|
+
this.streak = 0;
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
const before = this.streak;
|
|
1173
|
+
this.streak += 1;
|
|
1174
|
+
return before < this.threshold && this.streak >= this.threshold;
|
|
1175
|
+
}
|
|
1176
|
+
get currentStreak() {
|
|
1177
|
+
return this.streak;
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1141
1181
|
// src/loop/turn-failure-tracker.ts
|
|
1142
1182
|
var FAILURE_ESCALATION_THRESHOLD = 3;
|
|
1143
1183
|
var TurnFailureTracker = class {
|
|
@@ -1518,6 +1558,7 @@ var CacheFirstLoop = class {
|
|
|
1518
1558
|
confirmationGate;
|
|
1519
1559
|
/** Number of messages that were pre-loaded from the session file. */
|
|
1520
1560
|
resumedMessageCount;
|
|
1561
|
+
_rebuildSystem;
|
|
1521
1562
|
_turn = 0;
|
|
1522
1563
|
_streamPreference;
|
|
1523
1564
|
/** Threaded through HTTP + every tool dispatch so Esc cancels in-flight work, not after. */
|
|
@@ -1527,6 +1568,7 @@ var CacheFirstLoop = class {
|
|
|
1527
1568
|
_proArmedForNextTurn = false;
|
|
1528
1569
|
_escalateThisTurn = false;
|
|
1529
1570
|
_turnFailures;
|
|
1571
|
+
_readOnlyLoop;
|
|
1530
1572
|
_turnSelfCorrected = false;
|
|
1531
1573
|
_foldedThisTurn = false;
|
|
1532
1574
|
_toolDispatchesThisStep = 0;
|
|
@@ -1549,34 +1591,18 @@ var CacheFirstLoop = class {
|
|
|
1549
1591
|
this._turnFailures = new TurnFailureTracker(
|
|
1550
1592
|
resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
|
|
1551
1593
|
);
|
|
1594
|
+
this._readOnlyLoop = new ReadOnlyLoopTracker(
|
|
1595
|
+
parsePositiveIntEnv(process.env.REASONIX_READONLY_LOOP_THRESHOLD) ?? READONLY_LOOP_ESCALATION_THRESHOLD
|
|
1596
|
+
);
|
|
1552
1597
|
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
1553
1598
|
this.hooks = opts.hooks ?? [];
|
|
1554
1599
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
1555
1600
|
this.confirmationGate = opts.confirmationGate ?? pauseGate;
|
|
1601
|
+
this._rebuildSystem = opts.rebuildSystem ?? null;
|
|
1556
1602
|
this._streamPreference = opts.stream ?? true;
|
|
1557
1603
|
this.stream = this._streamPreference;
|
|
1558
1604
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
1559
1605
|
const registry = this.tools;
|
|
1560
|
-
const isMutating = (call) => {
|
|
1561
|
-
const name = call.function?.name;
|
|
1562
|
-
if (!name) return false;
|
|
1563
|
-
const def = registry.get(name);
|
|
1564
|
-
if (!def) return false;
|
|
1565
|
-
if (def.readOnlyCheck) {
|
|
1566
|
-
let args = {};
|
|
1567
|
-
try {
|
|
1568
|
-
args = JSON.parse(call.function?.arguments ?? "{}") ?? {};
|
|
1569
|
-
} catch {
|
|
1570
|
-
}
|
|
1571
|
-
try {
|
|
1572
|
-
if (def.readOnlyCheck(args)) return false;
|
|
1573
|
-
} catch (err) {
|
|
1574
|
-
process.stderr.write(`readOnlyCheck for ${name} threw: ${err.message}
|
|
1575
|
-
`);
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
return def.readOnly !== true;
|
|
1579
|
-
};
|
|
1580
1606
|
const isStormExempt = (call) => {
|
|
1581
1607
|
const name = call.function?.name;
|
|
1582
1608
|
if (!name) return false;
|
|
@@ -1584,7 +1610,7 @@ var CacheFirstLoop = class {
|
|
|
1584
1610
|
};
|
|
1585
1611
|
this.repair = new ToolCallRepair({
|
|
1586
1612
|
allowedToolNames: allowedNames,
|
|
1587
|
-
isMutating,
|
|
1613
|
+
isMutating: (call) => this.isMutating(call),
|
|
1588
1614
|
isStormExempt,
|
|
1589
1615
|
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
1590
1616
|
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
@@ -1677,7 +1703,7 @@ var CacheFirstLoop = class {
|
|
|
1677
1703
|
}
|
|
1678
1704
|
}
|
|
1679
1705
|
}
|
|
1680
|
-
/** "New chat" — drops in-memory messages, archives the on-disk transcript so it survives in Sessions, keeps sessionName so the prefix cache stays warm. */
|
|
1706
|
+
/** "New chat" — drops in-memory messages, archives the on-disk transcript so it survives in Sessions, keeps sessionName so the prefix cache stays warm. Re-runs the system-prompt builder if one was wired (issue #778: REASONIX.md edits otherwise need a restart). */
|
|
1681
1707
|
clearLog() {
|
|
1682
1708
|
const dropped = this.log.length;
|
|
1683
1709
|
this.log.compactInPlace([]);
|
|
@@ -1691,7 +1717,14 @@ var CacheFirstLoop = class {
|
|
|
1691
1717
|
}
|
|
1692
1718
|
this.scratch.reset();
|
|
1693
1719
|
this._inflight.clear();
|
|
1694
|
-
|
|
1720
|
+
let systemRebuilt = false;
|
|
1721
|
+
if (this._rebuildSystem) {
|
|
1722
|
+
try {
|
|
1723
|
+
systemRebuilt = this.prefix.replaceSystem(this._rebuildSystem());
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return { dropped, archived, systemRebuilt };
|
|
1695
1728
|
}
|
|
1696
1729
|
configure(opts) {
|
|
1697
1730
|
if (opts.model !== void 0) this.model = opts.model;
|
|
@@ -1737,6 +1770,35 @@ var CacheFirstLoop = class {
|
|
|
1737
1770
|
this._escalateThisTurn = true;
|
|
1738
1771
|
return true;
|
|
1739
1772
|
}
|
|
1773
|
+
/** Returns true ONLY on the call where the read-only streak crosses the threshold (#681). */
|
|
1774
|
+
noteReadOnlyToolCall(call) {
|
|
1775
|
+
const isReadOnly = !this.isMutating(call);
|
|
1776
|
+
if (!this._readOnlyLoop.noteAndCrossedThreshold(isReadOnly)) return false;
|
|
1777
|
+
if (this._escalateThisTurn || !this.autoEscalate) return false;
|
|
1778
|
+
this._escalateThisTurn = true;
|
|
1779
|
+
return true;
|
|
1780
|
+
}
|
|
1781
|
+
/** A call counts as mutating when its definition reports `readOnly !== true` and any dynamic `readOnlyCheck` doesn't override that for these args. */
|
|
1782
|
+
isMutating(call) {
|
|
1783
|
+
const name = call.function?.name;
|
|
1784
|
+
if (!name) return false;
|
|
1785
|
+
const def = this.tools.get(name);
|
|
1786
|
+
if (!def) return false;
|
|
1787
|
+
if (def.readOnlyCheck) {
|
|
1788
|
+
let args = {};
|
|
1789
|
+
try {
|
|
1790
|
+
args = JSON.parse(call.function?.arguments ?? "{}") ?? {};
|
|
1791
|
+
} catch {
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
if (def.readOnlyCheck(args)) return false;
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
process.stderr.write(`readOnlyCheck for ${name} threw: ${err.message}
|
|
1797
|
+
`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return def.readOnly !== true;
|
|
1801
|
+
}
|
|
1740
1802
|
async runOneToolCall(call, signal) {
|
|
1741
1803
|
const name = call.function?.name ?? "";
|
|
1742
1804
|
const args = call.function?.arguments ?? "{}";
|
|
@@ -1857,6 +1919,7 @@ ${reason}`
|
|
|
1857
1919
|
this.scratch.reset();
|
|
1858
1920
|
this.repair.resetStorm();
|
|
1859
1921
|
this._turnFailures.reset();
|
|
1922
|
+
this._readOnlyLoop.reset();
|
|
1860
1923
|
this._turnSelfCorrected = false;
|
|
1861
1924
|
this._escalateThisTurn = false;
|
|
1862
1925
|
this._foldedThisTurn = false;
|
|
@@ -2283,6 +2346,17 @@ ${reason}`
|
|
|
2283
2346
|
})
|
|
2284
2347
|
};
|
|
2285
2348
|
}
|
|
2349
|
+
if (this.noteReadOnlyToolCall(call)) {
|
|
2350
|
+
yield {
|
|
2351
|
+
turn: this._turn,
|
|
2352
|
+
role: "warning",
|
|
2353
|
+
content: t("loop.readOnlyLoopEscalation", {
|
|
2354
|
+
model: ESCALATION_MODEL,
|
|
2355
|
+
n: this._readOnlyLoop.currentStreak,
|
|
2356
|
+
fallback: this.model
|
|
2357
|
+
})
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2286
2360
|
yield {
|
|
2287
2361
|
turn: this._turn,
|
|
2288
2362
|
role: "tool",
|
|
@@ -2687,7 +2761,7 @@ function parseAtQuery(query) {
|
|
|
2687
2761
|
trailingSlash: false
|
|
2688
2762
|
};
|
|
2689
2763
|
}
|
|
2690
|
-
var AT_PICKER_PREFIX = /(?:^|\s)@([
|
|
2764
|
+
var AT_PICKER_PREFIX = /(?:^|\s)@([\p{L}\p{N}_./\\-]*)$/u;
|
|
2691
2765
|
function detectAtPicker(input) {
|
|
2692
2766
|
const m = AT_PICKER_PREFIX.exec(input);
|
|
2693
2767
|
if (!m) return null;
|
|
@@ -2773,7 +2847,7 @@ function fuzzySubseqScore(needle, target) {
|
|
|
2773
2847
|
const lengthPenalty = Math.floor(target.length / 4);
|
|
2774
2848
|
return quality + lengthPenalty;
|
|
2775
2849
|
}
|
|
2776
|
-
var AT_MENTION_PATTERN = /(?<=^|\s)@([
|
|
2850
|
+
var AT_MENTION_PATTERN = /(?<=^|\s)@([\p{L}\p{N}_./\\-]+)/gu;
|
|
2777
2851
|
function expandAtMentions(text, rootDir, opts = {}) {
|
|
2778
2852
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
2779
2853
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
@@ -2903,6 +2977,16 @@ var defaultFs = {
|
|
|
2903
2977
|
function registerMemoryTools(registry, opts = {}) {
|
|
2904
2978
|
const store = new MemoryStore({ homeDir: opts.homeDir, projectRoot: opts.projectRoot });
|
|
2905
2979
|
const hasProject = store.hasProjectScope();
|
|
2980
|
+
const registry_types = loadMemoryTypeRegistry();
|
|
2981
|
+
const customTypeNames = registry_types.filter((r) => !r.builtin).map((r) => r.name);
|
|
2982
|
+
const typeDescParts = [
|
|
2983
|
+
"'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
2984
|
+
];
|
|
2985
|
+
if (customTypeNames.length > 0) {
|
|
2986
|
+
typeDescParts.push(
|
|
2987
|
+
`Custom types declared in config: ${customTypeNames.join(", ")}. Any string is accepted; unknown types are stored verbatim and treated as 'reference' priority.`
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2906
2990
|
registry.register({
|
|
2907
2991
|
name: "remember",
|
|
2908
2992
|
description: "Save a memory for future sessions. Use when the user states a preference, corrects your approach, shares a non-obvious fact about this project, or explicitly asks you to remember something. Don't remember transient task state \u2014 only things worth recalling next session. The memory is written now but won't re-load into the system prompt until the next `/new` or launch.",
|
|
@@ -2911,8 +2995,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
2911
2995
|
properties: {
|
|
2912
2996
|
type: {
|
|
2913
2997
|
type: "string",
|
|
2914
|
-
|
|
2915
|
-
description: "'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
2998
|
+
description: typeDescParts.join(" ")
|
|
2916
2999
|
},
|
|
2917
3000
|
scope: {
|
|
2918
3001
|
type: "string",
|
|
@@ -2930,6 +3013,16 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
2930
3013
|
content: {
|
|
2931
3014
|
type: "string",
|
|
2932
3015
|
description: "Full memory body in markdown. For feedback/project types, structure as: rule/fact, then **Why:** line, then **How to apply:** line."
|
|
3016
|
+
},
|
|
3017
|
+
priority: {
|
|
3018
|
+
type: "string",
|
|
3019
|
+
enum: ["low", "medium", "high"],
|
|
3020
|
+
description: "Optional per-memory priority. `high` injects the entry into a `# HIGH PRIORITY constraints` block at the top of the system prompt \u2014 use sparingly, only for hard rules the model must never violate."
|
|
3021
|
+
},
|
|
3022
|
+
expires: {
|
|
3023
|
+
type: "string",
|
|
3024
|
+
enum: ["project_end"],
|
|
3025
|
+
description: "Optional lifecycle hint. `project_end` causes `/memory clear project` to also remove this entry even when it's stored at global scope."
|
|
2933
3026
|
}
|
|
2934
3027
|
},
|
|
2935
3028
|
required: ["type", "scope", "name", "description", "content"]
|
|
@@ -2946,7 +3039,9 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
2946
3039
|
type: args.type,
|
|
2947
3040
|
scope: args.scope,
|
|
2948
3041
|
description: args.description,
|
|
2949
|
-
body: args.content
|
|
3042
|
+
body: args.content,
|
|
3043
|
+
...args.priority ? { priority: args.priority } : {},
|
|
3044
|
+
...args.expires ? { expires: args.expires } : {}
|
|
2950
3045
|
});
|
|
2951
3046
|
const key = sanitizeMemoryName(args.name);
|
|
2952
3047
|
return [
|
|
@@ -3488,7 +3583,7 @@ ${i + 1}. ${r.title}`);
|
|
|
3488
3583
|
|
|
3489
3584
|
// src/tools/filesystem.ts
|
|
3490
3585
|
import { promises as fs3 } from "fs";
|
|
3491
|
-
import * as
|
|
3586
|
+
import * as pathMod4 from "path";
|
|
3492
3587
|
import picomatch2 from "picomatch";
|
|
3493
3588
|
|
|
3494
3589
|
// src/tools/fs/glob.ts
|
|
@@ -3554,15 +3649,149 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
3554
3649
|
return lines.join("\n");
|
|
3555
3650
|
}
|
|
3556
3651
|
|
|
3652
|
+
// src/tools/fs/outline.ts
|
|
3653
|
+
import * as pathMod2 from "path";
|
|
3654
|
+
var OUTLINE_MAX_ENTRIES = 30;
|
|
3655
|
+
var OUTLINE_TAIL_KEEP = 5;
|
|
3656
|
+
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
3657
|
+
var PY_DECL_RE = /^(?:async\s+)?(def|class)\s+(\w+)/;
|
|
3658
|
+
var GO_DECL_RE = /^(func|type|var|const)\s+(?:\([^)]+\)\s+)?(\w+)/;
|
|
3659
|
+
var RUST_DECL_RE = /^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|mod|type|const|static|union)\s+(\w+)/;
|
|
3660
|
+
var RUST_IMPL_RE = /^(?:unsafe\s+)?impl(?:\s*<[^>]+>)?\s+(?:[^{]+\s+for\s+)?(\w+)/;
|
|
3661
|
+
var MD_HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
3662
|
+
var MD_FENCE_RE = /^```/;
|
|
3663
|
+
var EXT_TO_LANG = {
|
|
3664
|
+
".ts": "ts",
|
|
3665
|
+
".tsx": "ts",
|
|
3666
|
+
".mts": "ts",
|
|
3667
|
+
".cts": "ts",
|
|
3668
|
+
".js": "ts",
|
|
3669
|
+
".jsx": "ts",
|
|
3670
|
+
".mjs": "ts",
|
|
3671
|
+
".cjs": "ts",
|
|
3672
|
+
".py": "py",
|
|
3673
|
+
".pyi": "py",
|
|
3674
|
+
".go": "go",
|
|
3675
|
+
".rs": "rust",
|
|
3676
|
+
".md": "md",
|
|
3677
|
+
".markdown": "md",
|
|
3678
|
+
".mdx": "md"
|
|
3679
|
+
};
|
|
3680
|
+
function extractOutline(filename, lines) {
|
|
3681
|
+
const ext = pathMod2.extname(filename).toLowerCase();
|
|
3682
|
+
const lang = EXT_TO_LANG[ext];
|
|
3683
|
+
if (!lang) return [];
|
|
3684
|
+
switch (lang) {
|
|
3685
|
+
case "ts":
|
|
3686
|
+
return extractTs(lines);
|
|
3687
|
+
case "py":
|
|
3688
|
+
return extractPython(lines);
|
|
3689
|
+
case "go":
|
|
3690
|
+
return extractGo(lines);
|
|
3691
|
+
case "rust":
|
|
3692
|
+
return extractRust(lines);
|
|
3693
|
+
case "md":
|
|
3694
|
+
return extractMarkdown(lines);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
function extractTs(lines) {
|
|
3698
|
+
const out = [];
|
|
3699
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3700
|
+
const line = lines[i];
|
|
3701
|
+
if (!line.startsWith("export ")) continue;
|
|
3702
|
+
const m = TS_EXPORT_RE.exec(line);
|
|
3703
|
+
if (!m) continue;
|
|
3704
|
+
out.push({ line: i + 1, text: `export ${m[1]} ${m[2]}` });
|
|
3705
|
+
}
|
|
3706
|
+
return out;
|
|
3707
|
+
}
|
|
3708
|
+
function extractPython(lines) {
|
|
3709
|
+
const out = [];
|
|
3710
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3711
|
+
const line = lines[i];
|
|
3712
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
3713
|
+
const m = PY_DECL_RE.exec(line);
|
|
3714
|
+
if (!m) continue;
|
|
3715
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
3716
|
+
}
|
|
3717
|
+
return out;
|
|
3718
|
+
}
|
|
3719
|
+
function extractGo(lines) {
|
|
3720
|
+
const out = [];
|
|
3721
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3722
|
+
const line = lines[i];
|
|
3723
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
3724
|
+
const m = GO_DECL_RE.exec(line);
|
|
3725
|
+
if (!m) continue;
|
|
3726
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
3727
|
+
}
|
|
3728
|
+
return out;
|
|
3729
|
+
}
|
|
3730
|
+
function extractRust(lines) {
|
|
3731
|
+
const out = [];
|
|
3732
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3733
|
+
const line = lines[i];
|
|
3734
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
3735
|
+
const implMatch = RUST_IMPL_RE.exec(line);
|
|
3736
|
+
if (implMatch) {
|
|
3737
|
+
out.push({ line: i + 1, text: `impl ${implMatch[1]}` });
|
|
3738
|
+
continue;
|
|
3739
|
+
}
|
|
3740
|
+
const m = RUST_DECL_RE.exec(line);
|
|
3741
|
+
if (!m) continue;
|
|
3742
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
3743
|
+
}
|
|
3744
|
+
return out;
|
|
3745
|
+
}
|
|
3746
|
+
function extractMarkdown(lines) {
|
|
3747
|
+
const out = [];
|
|
3748
|
+
let inFence = false;
|
|
3749
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3750
|
+
const line = lines[i];
|
|
3751
|
+
if (MD_FENCE_RE.test(line)) {
|
|
3752
|
+
inFence = !inFence;
|
|
3753
|
+
continue;
|
|
3754
|
+
}
|
|
3755
|
+
if (inFence) continue;
|
|
3756
|
+
const m = MD_HEADING_RE.exec(line);
|
|
3757
|
+
if (!m) continue;
|
|
3758
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
3759
|
+
}
|
|
3760
|
+
return out;
|
|
3761
|
+
}
|
|
3762
|
+
function formatOutline(entries) {
|
|
3763
|
+
const total = entries.length;
|
|
3764
|
+
if (total === 0) return "";
|
|
3765
|
+
const lastEntry = entries[total - 1];
|
|
3766
|
+
const width = String(lastEntry.line).length;
|
|
3767
|
+
const fmt = (e) => ` L${String(e.line).padStart(width, " ")} ${e.text}`;
|
|
3768
|
+
const header = `[outline: ${total} symbol${total === 1 ? "" : "s"}]`;
|
|
3769
|
+
if (total <= OUTLINE_MAX_ENTRIES) {
|
|
3770
|
+
return [header, ...entries.map(fmt)].join("\n");
|
|
3771
|
+
}
|
|
3772
|
+
const headCount = OUTLINE_MAX_ENTRIES - OUTLINE_TAIL_KEEP;
|
|
3773
|
+
const headEntries = entries.slice(0, headCount);
|
|
3774
|
+
const tailEntries = entries.slice(-OUTLINE_TAIL_KEEP);
|
|
3775
|
+
const omitted = total - OUTLINE_MAX_ENTRIES;
|
|
3776
|
+
const gapStart = headEntries[headEntries.length - 1].line;
|
|
3777
|
+
const gapEnd = tailEntries[0].line;
|
|
3778
|
+
return [
|
|
3779
|
+
header,
|
|
3780
|
+
...headEntries.map(fmt),
|
|
3781
|
+
` [\u2026 ${omitted} more symbol${omitted === 1 ? "" : "s"} between L${gapStart} and L${gapEnd} \u2026]`,
|
|
3782
|
+
...tailEntries.map(fmt)
|
|
3783
|
+
].join("\n");
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3557
3786
|
// src/tools/fs/search.ts
|
|
3558
3787
|
import { promises as fs2 } from "fs";
|
|
3559
|
-
import * as
|
|
3788
|
+
import * as pathMod3 from "path";
|
|
3560
3789
|
function throwIfAborted(signal) {
|
|
3561
3790
|
if (!signal?.aborted) return;
|
|
3562
3791
|
throw new DOMException("search aborted by user", "AbortError");
|
|
3563
3792
|
}
|
|
3564
3793
|
function displayRel2(rootDir, full) {
|
|
3565
|
-
return
|
|
3794
|
+
return pathMod3.relative(rootDir, full).replaceAll("\\", "/");
|
|
3566
3795
|
}
|
|
3567
3796
|
async function searchFiles(ctx, startAbs, args) {
|
|
3568
3797
|
throwIfAborted(args.signal);
|
|
@@ -3586,7 +3815,7 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
3586
3815
|
}
|
|
3587
3816
|
for (const e of entries) {
|
|
3588
3817
|
throwIfAborted(args.signal);
|
|
3589
|
-
const full =
|
|
3818
|
+
const full = pathMod3.join(dir, e.name);
|
|
3590
3819
|
const lower = e.name.toLowerCase();
|
|
3591
3820
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
3592
3821
|
if (hit) {
|
|
@@ -3665,11 +3894,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
3665
3894
|
throwIfAborted(args.signal);
|
|
3666
3895
|
if (e.isDirectory()) {
|
|
3667
3896
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
3668
|
-
await walk2(
|
|
3897
|
+
await walk2(pathMod3.join(dir, e.name));
|
|
3669
3898
|
continue;
|
|
3670
3899
|
}
|
|
3671
3900
|
if (!e.isFile()) continue;
|
|
3672
|
-
const full =
|
|
3901
|
+
const full = pathMod3.join(dir, e.name);
|
|
3673
3902
|
if (ctx.nameMatch && !ctx.nameMatch(e.name, displayRel2(ctx.rootDir, full))) continue;
|
|
3674
3903
|
if (ctx.isBinaryByName(e.name)) continue;
|
|
3675
3904
|
let fh;
|
|
@@ -3766,47 +3995,11 @@ var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
|
3766
3995
|
var DEFAULT_AUTO_PREVIEW_LINES = 200;
|
|
3767
3996
|
var AUTO_PREVIEW_HEAD_LINES = 80;
|
|
3768
3997
|
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
3769
|
-
var
|
|
3770
|
-
var OUTLINE_TAIL_KEEP = 5;
|
|
3771
|
-
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
3772
|
-
function extractTsExportOutline(lines) {
|
|
3773
|
-
const out = [];
|
|
3774
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3775
|
-
const line = lines[i];
|
|
3776
|
-
if (!line.startsWith("export ")) continue;
|
|
3777
|
-
const m = TS_EXPORT_RE.exec(line);
|
|
3778
|
-
if (!m) continue;
|
|
3779
|
-
out.push({ line: i + 1, kind: m[1], name: m[2] });
|
|
3780
|
-
}
|
|
3781
|
-
return out;
|
|
3782
|
-
}
|
|
3783
|
-
function formatOutline(entries) {
|
|
3784
|
-
const total = entries.length;
|
|
3785
|
-
if (total === 0) return "";
|
|
3786
|
-
const lastEntry = entries[total - 1];
|
|
3787
|
-
const width = String(lastEntry.line).length;
|
|
3788
|
-
const fmt = (e) => ` L${String(e.line).padStart(width, " ")} export ${e.kind} ${e.name}`;
|
|
3789
|
-
const header = `[outline: ${total} top-level export${total === 1 ? "" : "s"}]`;
|
|
3790
|
-
if (total <= OUTLINE_MAX_ENTRIES) {
|
|
3791
|
-
return [header, ...entries.map(fmt)].join("\n");
|
|
3792
|
-
}
|
|
3793
|
-
const headCount = OUTLINE_MAX_ENTRIES - OUTLINE_TAIL_KEEP;
|
|
3794
|
-
const headEntries = entries.slice(0, headCount);
|
|
3795
|
-
const tailEntries = entries.slice(-OUTLINE_TAIL_KEEP);
|
|
3796
|
-
const omitted = total - OUTLINE_MAX_ENTRIES;
|
|
3797
|
-
const gapStart = headEntries[headEntries.length - 1].line;
|
|
3798
|
-
const gapEnd = tailEntries[0].line;
|
|
3799
|
-
return [
|
|
3800
|
-
header,
|
|
3801
|
-
...headEntries.map(fmt),
|
|
3802
|
-
` [\u2026 ${omitted} more export${omitted === 1 ? "" : "s"} between L${gapStart} and L${gapEnd} \u2026]`,
|
|
3803
|
-
...tailEntries.map(fmt)
|
|
3804
|
-
].join("\n");
|
|
3805
|
-
}
|
|
3998
|
+
var OUTLINE_MAX_ENTRIES2 = 30;
|
|
3806
3999
|
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
3807
4000
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
3808
4001
|
function displayRel3(rootDir, full) {
|
|
3809
|
-
return
|
|
4002
|
+
return pathMod4.relative(rootDir, full).replaceAll("\\", "/");
|
|
3810
4003
|
}
|
|
3811
4004
|
var GLOB_METACHARS = /[*?{[]/;
|
|
3812
4005
|
function compileNameFilter(filter) {
|
|
@@ -3825,16 +4018,16 @@ function isLikelyBinaryByName(name) {
|
|
|
3825
4018
|
return BINARY_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
3826
4019
|
}
|
|
3827
4020
|
function registerFilesystemTools(registry, opts) {
|
|
3828
|
-
const rootDir =
|
|
4021
|
+
const rootDir = pathMod4.resolve(opts.rootDir);
|
|
3829
4022
|
const allowWriting = opts.allowWriting !== false;
|
|
3830
4023
|
const maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
3831
4024
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
3832
|
-
const normRoot =
|
|
4025
|
+
const normRoot = pathMod4.resolve(rootDir);
|
|
3833
4026
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
3834
4027
|
const inflightGate = /* @__PURE__ */ new Map();
|
|
3835
4028
|
function pathIsUnder(child, parent) {
|
|
3836
|
-
const rel =
|
|
3837
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
4029
|
+
const rel = pathMod4.relative(parent, child);
|
|
4030
|
+
return rel === "" || !rel.startsWith("..") && !pathMod4.isAbsolute(rel);
|
|
3838
4031
|
}
|
|
3839
4032
|
function looksLikeAbsoluteSystemPath(raw) {
|
|
3840
4033
|
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
@@ -3850,7 +4043,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3850
4043
|
if (pathIsUnder(abs, dir)) return;
|
|
3851
4044
|
}
|
|
3852
4045
|
const stat2 = await safeLstat(abs);
|
|
3853
|
-
const allowPrefix = stat2?.isDirectory() ? abs :
|
|
4046
|
+
const allowPrefix = stat2?.isDirectory() ? abs : pathMod4.dirname(abs);
|
|
3854
4047
|
let pending = inflightGate.get(allowPrefix);
|
|
3855
4048
|
if (!pending) {
|
|
3856
4049
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
@@ -3878,7 +4071,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3878
4071
|
throw new Error("path must be a non-empty string");
|
|
3879
4072
|
}
|
|
3880
4073
|
if (looksLikeAbsoluteSystemPath(raw)) {
|
|
3881
|
-
const abs =
|
|
4074
|
+
const abs = pathMod4.resolve(raw);
|
|
3882
4075
|
if (pathIsUnder(abs, normRoot)) return abs;
|
|
3883
4076
|
await ensureOutsideSandboxAllowed(abs, intent, toolName, ctx);
|
|
3884
4077
|
return abs;
|
|
@@ -3888,7 +4081,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3888
4081
|
normalized = normalized.slice(1);
|
|
3889
4082
|
}
|
|
3890
4083
|
if (normalized.length === 0) normalized = ".";
|
|
3891
|
-
const resolved =
|
|
4084
|
+
const resolved = pathMod4.resolve(rootDir, normalized);
|
|
3892
4085
|
if (!pathIsUnder(resolved, normRoot)) {
|
|
3893
4086
|
throw new Error(
|
|
3894
4087
|
`path escapes sandbox root (${normRoot}): ${raw} \u2014 use an absolute system path like /Users/foo or C:\\Users\\foo to request approved outside-sandbox access`
|
|
@@ -3910,7 +4103,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
3910
4103
|
- head: N \u2192 first N lines (imports, public API, small configs)
|
|
3911
4104
|
- tail: N \u2192 last N lines (recently-added code, log tails)
|
|
3912
4105
|
- range: "A-B" \u2192 inclusive line range A..B, 1-indexed (e.g. "120-180" around an edit site)
|
|
3913
|
-
When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker, plus a top-level
|
|
4106
|
+
When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker, plus a top-level symbol outline (TS/JS exports, Python def/class, Go func/type, Rust fn/struct/impl/trait, Markdown headings, with line numbers, capped at ${OUTLINE_MAX_ENTRIES2}) so you can pick a smart range without a follow-up grep. If you need the middle, re-call with a range. Prefer search_content to locate a symbol first only when the outline doesn't have what you want \u2014 one scoped read beats three full-file reads.`,
|
|
3914
4107
|
readOnly: true,
|
|
3915
4108
|
stormExempt: true,
|
|
3916
4109
|
parameters: {
|
|
@@ -3978,7 +4171,7 @@ ${slice.join("\n")}`;
|
|
|
3978
4171
|
const head = lines.slice(0, AUTO_PREVIEW_HEAD_LINES).join("\n");
|
|
3979
4172
|
const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
|
|
3980
4173
|
const omitted = totalLines - AUTO_PREVIEW_HEAD_LINES - AUTO_PREVIEW_TAIL_LINES;
|
|
3981
|
-
const outline = formatOutline(
|
|
4174
|
+
const outline = formatOutline(extractOutline(abs, lines));
|
|
3982
4175
|
const parts = [
|
|
3983
4176
|
`[auto-preview: head ${AUTO_PREVIEW_HEAD_LINES} + tail ${AUTO_PREVIEW_TAIL_LINES} of ${totalLines} lines]`,
|
|
3984
4177
|
head
|
|
@@ -4086,7 +4279,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4086
4279
|
lines.push(line);
|
|
4087
4280
|
emitted++;
|
|
4088
4281
|
if (e.isDirectory() && !skip) {
|
|
4089
|
-
await walk2(
|
|
4282
|
+
await walk2(pathMod4.join(dir, e.name), depth + 1);
|
|
4090
4283
|
}
|
|
4091
4284
|
}
|
|
4092
4285
|
};
|
|
@@ -4246,7 +4439,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4246
4439
|
},
|
|
4247
4440
|
fn: async (args, ctx) => {
|
|
4248
4441
|
const abs = await safePath(args.path, "write_file", ctx, "write");
|
|
4249
|
-
await fs3.mkdir(
|
|
4442
|
+
await fs3.mkdir(pathMod4.dirname(abs), { recursive: true });
|
|
4250
4443
|
await fs3.writeFile(abs, args.content, "utf8");
|
|
4251
4444
|
return `wrote ${args.content.length} chars to ${displayRel3(rootDir, abs)}`;
|
|
4252
4445
|
}
|
|
@@ -4332,7 +4525,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4332
4525
|
fn: async (args, ctx) => {
|
|
4333
4526
|
const src = await safePath(args.source, "move_file", ctx, "write");
|
|
4334
4527
|
const dst = await safePath(args.destination, "move_file", ctx, "write");
|
|
4335
|
-
await fs3.mkdir(
|
|
4528
|
+
await fs3.mkdir(pathMod4.dirname(dst), { recursive: true });
|
|
4336
4529
|
await fs3.rename(src, dst);
|
|
4337
4530
|
return `moved ${displayRel3(rootDir, src)} \u2192 ${displayRel3(rootDir, dst)}`;
|
|
4338
4531
|
}
|
|
@@ -4400,7 +4593,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
4400
4593
|
fn: async (args, ctx) => {
|
|
4401
4594
|
const src = await safePath(args.source, "copy_file", ctx);
|
|
4402
4595
|
const dst = await safePath(args.destination, "copy_file", ctx, "write");
|
|
4403
|
-
await fs3.mkdir(
|
|
4596
|
+
await fs3.mkdir(pathMod4.dirname(dst), { recursive: true });
|
|
4404
4597
|
await fs3.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
4405
4598
|
return `copied ${displayRel3(rootDir, src)} \u2192 ${displayRel3(rootDir, dst)}`;
|
|
4406
4599
|
}
|
|
@@ -5234,4 +5427,4 @@ export {
|
|
|
5234
5427
|
snapshotBeforeEdits,
|
|
5235
5428
|
restoreSnapshots
|
|
5236
5429
|
};
|
|
5237
|
-
//# sourceMappingURL=chunk-
|
|
5430
|
+
//# sourceMappingURL=chunk-IEA6JOIP.js.map
|