reasonix 0.48.0 → 0.49.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 +9 -0
- package/dashboard/dist/app.js +123 -16
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{acp-4ROCGYNH.js → acp-WFQIC6SO.js} +52 -135
- package/dist/cli/acp-WFQIC6SO.js.map +1 -0
- package/dist/cli/chat-D32JGNVH.js +51 -0
- package/dist/cli/{chunk-S2RMQULY.js → chunk-23ZPCIPR.js} +12 -9
- package/dist/cli/chunk-23ZPCIPR.js.map +1 -0
- package/dist/cli/{chunk-TKVXTQ3T.js → chunk-3ZZXQ3CZ.js} +27 -27
- package/dist/cli/chunk-3ZZXQ3CZ.js.map +1 -0
- package/dist/cli/{chunk-5OHHAQ4W.js → chunk-7AST3QQ3.js} +2 -2
- package/dist/cli/{chunk-MRZG4GBF.js → chunk-7JTKBJ2G.js} +3 -3
- package/dist/cli/{chunk-X53B3JIX.js → chunk-7X4JJOO7.js} +2 -61
- package/dist/cli/{chunk-X53B3JIX.js.map → chunk-7X4JJOO7.js.map} +1 -1
- package/dist/cli/{chunk-MOJYKO2A.js → chunk-ASOLXV67.js} +3 -3
- package/dist/cli/{chunk-7M4YYMKW.js → chunk-AWEULQG6.js} +49 -56
- package/dist/cli/{chunk-7M4YYMKW.js.map → chunk-AWEULQG6.js.map} +1 -1
- package/dist/cli/{chunk-HR5NBKEM.js → chunk-DFX5ZH5L.js} +2 -2
- package/dist/cli/{chunk-3WGTGXO4.js → chunk-GNS7BAT2.js} +4 -4
- package/dist/cli/chunk-GNS7BAT2.js.map +1 -0
- package/dist/cli/{chunk-TE5UIIFL.js → chunk-J2IHQGPQ.js} +12 -6
- package/dist/cli/chunk-J2IHQGPQ.js.map +1 -0
- package/dist/cli/{chunk-I4M5QJNL.js → chunk-JGTX4RRQ.js} +3 -3
- package/dist/cli/{chunk-FY4S7TJZ.js → chunk-JNTMOX7G.js} +10 -2
- package/dist/cli/chunk-JNTMOX7G.js.map +1 -0
- package/dist/cli/{chunk-OB4BUJBL.js → chunk-MGTBP7GG.js} +5 -2
- package/dist/cli/chunk-MGTBP7GG.js.map +1 -0
- package/dist/cli/{chunk-OPYALNTT.js → chunk-MQWO32ZD.js} +387 -184
- package/dist/cli/chunk-MQWO32ZD.js.map +1 -0
- package/dist/cli/{chunk-2QSTA2QV.js → chunk-O5LIHAMP.js} +8 -4
- package/dist/cli/chunk-O5LIHAMP.js.map +1 -0
- package/dist/cli/{chunk-NMQSUNLB.js → chunk-PB3MAFEI.js} +6 -3
- package/dist/cli/chunk-PB3MAFEI.js.map +1 -0
- package/dist/cli/{chunk-H4CCXMDD.js → chunk-PEMG6CUB.js} +2 -2
- package/dist/cli/{chunk-RUDBUHO4.js → chunk-PXBQ6IZ7.js} +3 -3
- package/dist/cli/{chunk-J2TQAWOM.js → chunk-Q46B3Z7H.js} +25 -10
- package/dist/cli/{chunk-J2TQAWOM.js.map → chunk-Q46B3Z7H.js.map} +1 -1
- package/dist/cli/{chunk-6MZTZO7A.js → chunk-QF32ROX2.js} +2152 -2613
- package/dist/cli/chunk-QF32ROX2.js.map +1 -0
- package/dist/cli/{chunk-OG5JANQ4.js → chunk-QX5TWXRZ.js} +2 -2
- package/dist/cli/{chunk-V4Y732RQ.js → chunk-TAIKVL35.js} +2 -2
- package/dist/cli/{chunk-B5CZL2SE.js → chunk-TEDWJKEI.js} +4 -9
- package/dist/cli/chunk-TEDWJKEI.js.map +1 -0
- package/dist/cli/{chunk-EMMENC4O.js → chunk-U5XQDCK7.js} +5 -5
- package/dist/cli/{chunk-DOWEOA6E.js → chunk-W46ZMNKO.js} +3 -3
- package/dist/cli/{chunk-CDVSFSAK.js → chunk-WMTMMSXU.js} +184 -8
- package/dist/cli/chunk-WMTMMSXU.js.map +1 -0
- package/dist/cli/{chunk-YW63N3ZR.js → chunk-YEF7C4XI.js} +270 -96
- package/dist/cli/chunk-YEF7C4XI.js.map +1 -0
- package/dist/cli/{chunk-JMDE6IO3.js → chunk-ZAEJWKXB.js} +2 -2
- package/dist/cli/chunk-ZWHSHFDP.js +6173 -0
- package/dist/cli/chunk-ZWHSHFDP.js.map +1 -0
- package/dist/cli/{code-PMPJWXEO.js → code-R4IHI7SR.js} +30 -30
- package/dist/cli/{commands-QS6TG4G3.js → commands-DRHFCYMO.js} +4 -4
- package/dist/cli/{commit-XPRSKUBF.js → commit-AG5KB4YP.js} +3 -3
- package/dist/cli/{desktop-562OPWIU.js → desktop-JGL6GORA.js} +60 -23
- package/dist/cli/desktop-JGL6GORA.js.map +1 -0
- package/dist/cli/{diff-I6W4AUWJ.js → diff-4Z7ETWZO.js} +9 -9
- package/dist/cli/{doctor-6XVZKT4U.js → doctor-VA3RHQLB.js} +9 -9
- package/dist/cli/index.js +37 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-7W7ANO2Y.js → mcp-LZO4HXFA.js} +34 -23
- package/dist/cli/mcp-LZO4HXFA.js.map +1 -0
- package/dist/cli/{mcp-browse-LA4I4YIZ.js → mcp-browse-C3GXVMYZ.js} +3 -3
- package/dist/cli/{mcp-inspect-LWXXU7BY.js → mcp-inspect-ZMYUNFDS.js} +2 -2
- package/dist/cli/{prompt-RKZD4X6Y.js → prompt-MC3U5KRP.js} +5 -4
- package/dist/cli/{prune-sessions-SEWX7GP6.js → prune-sessions-OEPFH4N6.js} +11 -7
- package/dist/cli/prune-sessions-OEPFH4N6.js.map +1 -0
- package/dist/cli/{replay-2X7MVXOI.js → replay-4TP7ZUMZ.js} +10 -10
- package/dist/cli/{run-TPKXIJ27.js → run-6MXQYBOE.js} +16 -15
- package/dist/cli/run-6MXQYBOE.js.map +1 -0
- package/dist/cli/{server-NHQ3QXOZ.js → server-Z3IMJNNI.js} +65 -12
- package/dist/cli/server-Z3IMJNNI.js.map +1 -0
- package/dist/cli/{sessions-2A4DGSHA.js → sessions-NXQ5SAV7.js} +18 -18
- package/dist/cli/sessions-NXQ5SAV7.js.map +1 -0
- package/dist/cli/{setup-GOLP7J4C.js → setup-LHZELI6I.js} +6 -6
- package/dist/cli/{stats-CGDAFDKI.js → stats-SUIJ3QWY.js} +6 -6
- package/dist/cli/{version-FIL4ZFOS.js → version-BIFONEUB.js} +13 -13
- package/dist/index.d.ts +71 -17
- package/dist/index.js +1040 -391
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/dist/cli/acp-4ROCGYNH.js.map +0 -1
- package/dist/cli/chat-GZNB5625.js +0 -51
- package/dist/cli/chunk-2QSTA2QV.js.map +0 -1
- package/dist/cli/chunk-3WGTGXO4.js.map +0 -1
- package/dist/cli/chunk-6MZTZO7A.js.map +0 -1
- package/dist/cli/chunk-B5CZL2SE.js.map +0 -1
- package/dist/cli/chunk-CDVSFSAK.js.map +0 -1
- package/dist/cli/chunk-FY4S7TJZ.js.map +0 -1
- package/dist/cli/chunk-NMQSUNLB.js.map +0 -1
- package/dist/cli/chunk-OB4BUJBL.js.map +0 -1
- package/dist/cli/chunk-OPYALNTT.js.map +0 -1
- package/dist/cli/chunk-S2RMQULY.js.map +0 -1
- package/dist/cli/chunk-TE5UIIFL.js.map +0 -1
- package/dist/cli/chunk-TKVXTQ3T.js.map +0 -1
- package/dist/cli/chunk-WZGNXR6E.js +0 -2020
- package/dist/cli/chunk-WZGNXR6E.js.map +0 -1
- package/dist/cli/chunk-YW63N3ZR.js.map +0 -1
- package/dist/cli/desktop-562OPWIU.js.map +0 -1
- package/dist/cli/mcp-7W7ANO2Y.js.map +0 -1
- package/dist/cli/prune-sessions-SEWX7GP6.js.map +0 -1
- package/dist/cli/run-TPKXIJ27.js.map +0 -1
- package/dist/cli/server-NHQ3QXOZ.js.map +0 -1
- package/dist/cli/sessions-2A4DGSHA.js.map +0 -1
- /package/dist/cli/{chat-GZNB5625.js.map → chat-D32JGNVH.js.map} +0 -0
- /package/dist/cli/{chunk-5OHHAQ4W.js.map → chunk-7AST3QQ3.js.map} +0 -0
- /package/dist/cli/{chunk-MRZG4GBF.js.map → chunk-7JTKBJ2G.js.map} +0 -0
- /package/dist/cli/{chunk-MOJYKO2A.js.map → chunk-ASOLXV67.js.map} +0 -0
- /package/dist/cli/{chunk-HR5NBKEM.js.map → chunk-DFX5ZH5L.js.map} +0 -0
- /package/dist/cli/{chunk-I4M5QJNL.js.map → chunk-JGTX4RRQ.js.map} +0 -0
- /package/dist/cli/{chunk-H4CCXMDD.js.map → chunk-PEMG6CUB.js.map} +0 -0
- /package/dist/cli/{chunk-RUDBUHO4.js.map → chunk-PXBQ6IZ7.js.map} +0 -0
- /package/dist/cli/{chunk-OG5JANQ4.js.map → chunk-QX5TWXRZ.js.map} +0 -0
- /package/dist/cli/{chunk-V4Y732RQ.js.map → chunk-TAIKVL35.js.map} +0 -0
- /package/dist/cli/{chunk-EMMENC4O.js.map → chunk-U5XQDCK7.js.map} +0 -0
- /package/dist/cli/{chunk-DOWEOA6E.js.map → chunk-W46ZMNKO.js.map} +0 -0
- /package/dist/cli/{chunk-JMDE6IO3.js.map → chunk-ZAEJWKXB.js.map} +0 -0
- /package/dist/cli/{code-PMPJWXEO.js.map → code-R4IHI7SR.js.map} +0 -0
- /package/dist/cli/{commands-QS6TG4G3.js.map → commands-DRHFCYMO.js.map} +0 -0
- /package/dist/cli/{commit-XPRSKUBF.js.map → commit-AG5KB4YP.js.map} +0 -0
- /package/dist/cli/{diff-I6W4AUWJ.js.map → diff-4Z7ETWZO.js.map} +0 -0
- /package/dist/cli/{doctor-6XVZKT4U.js.map → doctor-VA3RHQLB.js.map} +0 -0
- /package/dist/cli/{mcp-browse-LA4I4YIZ.js.map → mcp-browse-C3GXVMYZ.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-LWXXU7BY.js.map → mcp-inspect-ZMYUNFDS.js.map} +0 -0
- /package/dist/cli/{prompt-RKZD4X6Y.js.map → prompt-MC3U5KRP.js.map} +0 -0
- /package/dist/cli/{replay-2X7MVXOI.js.map → replay-4TP7ZUMZ.js.map} +0 -0
- /package/dist/cli/{setup-GOLP7J4C.js.map → setup-LHZELI6I.js.map} +0 -0
- /package/dist/cli/{stats-CGDAFDKI.js.map → stats-SUIJ3QWY.js.map} +0 -0
- /package/dist/cli/{version-FIL4ZFOS.js.map → version-BIFONEUB.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import { createRequire as __cr } from 'node:module'; if (typeof globalThis.requi
|
|
|
3
3
|
import {
|
|
4
4
|
MemoryStore,
|
|
5
5
|
sanitizeMemoryName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-O5LIHAMP.js";
|
|
7
7
|
import {
|
|
8
8
|
countTokens,
|
|
9
9
|
countTokensBounded,
|
|
@@ -12,23 +12,25 @@ import {
|
|
|
12
12
|
} from "./chunk-6OWJV3YW.js";
|
|
13
13
|
import {
|
|
14
14
|
Usage
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-J2IHQGPQ.js";
|
|
16
16
|
import {
|
|
17
17
|
applyEdit,
|
|
18
18
|
applyMultiEdit,
|
|
19
|
+
decodeFileBuffer,
|
|
20
|
+
encodeFile,
|
|
19
21
|
pauseGate
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-ZWHSHFDP.js";
|
|
21
23
|
import {
|
|
22
24
|
NEGATIVE_CLAIM_RULE,
|
|
23
25
|
PROJECT_MEMORY_FILES,
|
|
24
26
|
PROJECT_MEMORY_MAX_CHARS,
|
|
25
27
|
TUI_FORMATTING_RULES,
|
|
26
28
|
memoryEnabled
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-JNTMOX7G.js";
|
|
28
30
|
import {
|
|
29
31
|
formatHookOutcomeMessage,
|
|
30
32
|
runHooks
|
|
31
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-PB3MAFEI.js";
|
|
32
34
|
import {
|
|
33
35
|
ignoredByLayers,
|
|
34
36
|
loadGitignoreAt,
|
|
@@ -46,12 +48,13 @@ import {
|
|
|
46
48
|
DEEPSEEK_CONTEXT_TOKENS,
|
|
47
49
|
DEFAULT_CONTEXT_TOKENS,
|
|
48
50
|
SessionStats
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-QX5TWXRZ.js";
|
|
50
52
|
import {
|
|
51
53
|
t
|
|
52
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-YEF7C4XI.js";
|
|
53
55
|
import {
|
|
54
56
|
DEFAULT_INDEX_EXCLUDES,
|
|
57
|
+
ToolRateLimiter,
|
|
55
58
|
addProjectPathAllowed,
|
|
56
59
|
loadExaApiKey,
|
|
57
60
|
loadMemoryTypeRegistry,
|
|
@@ -59,10 +62,11 @@ import {
|
|
|
59
62
|
loadPerplexityApiKey,
|
|
60
63
|
loadProjectPathAllowed,
|
|
61
64
|
loadTavilyApiKey,
|
|
65
|
+
parseRateLimitedToolResult,
|
|
62
66
|
require_picomatch,
|
|
63
67
|
webSearchEndpoint,
|
|
64
68
|
webSearchEngine
|
|
65
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-WMTMMSXU.js";
|
|
66
70
|
import {
|
|
67
71
|
__commonJS,
|
|
68
72
|
__esm,
|
|
@@ -2599,10 +2603,10 @@ var require_helpers = __commonJS({
|
|
|
2599
2603
|
return !arr.includes(node, i + 1);
|
|
2600
2604
|
});
|
|
2601
2605
|
nodes.sort(function(a, b) {
|
|
2602
|
-
var
|
|
2603
|
-
if (
|
|
2606
|
+
var relative7 = compareDocumentPosition(a, b);
|
|
2607
|
+
if (relative7 & DocumentPosition.PRECEDING) {
|
|
2604
2608
|
return -1;
|
|
2605
|
-
} else if (
|
|
2609
|
+
} else if (relative7 & DocumentPosition.FOLLOWING) {
|
|
2606
2610
|
return 1;
|
|
2607
2611
|
}
|
|
2608
2612
|
return 0;
|
|
@@ -6052,12 +6056,12 @@ async function waitForReady(ready, timeoutMs, serverName, signal) {
|
|
|
6052
6056
|
let timer;
|
|
6053
6057
|
let onAbort;
|
|
6054
6058
|
try {
|
|
6055
|
-
await new Promise((
|
|
6059
|
+
await new Promise((resolve6, reject) => {
|
|
6056
6060
|
ready.then(
|
|
6057
6061
|
() => {
|
|
6058
6062
|
if (settled) return;
|
|
6059
6063
|
settled = true;
|
|
6060
|
-
|
|
6064
|
+
resolve6();
|
|
6061
6065
|
},
|
|
6062
6066
|
(err) => {
|
|
6063
6067
|
if (settled) return;
|
|
@@ -6314,12 +6318,14 @@ var ToolRegistry = class {
|
|
|
6314
6318
|
_interceptors = [];
|
|
6315
6319
|
_auditListener = null;
|
|
6316
6320
|
_resultAugmenter = null;
|
|
6321
|
+
_rateLimiter;
|
|
6317
6322
|
/** Per-tool fingerprint of the last call that failed schema validation. Cleared by any successful validation for that tool. */
|
|
6318
6323
|
_lastMalformed = /* @__PURE__ */ new Map();
|
|
6319
6324
|
/** Per-tool fingerprint of the last host-side gate rejection. */
|
|
6320
6325
|
_lastGateRejection = /* @__PURE__ */ new Map();
|
|
6321
6326
|
constructor(opts = {}) {
|
|
6322
6327
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
6328
|
+
this._rateLimiter = new ToolRateLimiter(opts.rateLimit);
|
|
6323
6329
|
}
|
|
6324
6330
|
/** Enable / disable plan-mode enforcement at dispatch. */
|
|
6325
6331
|
setPlanMode(on) {
|
|
@@ -6356,6 +6362,9 @@ var ToolRegistry = class {
|
|
|
6356
6362
|
get hasResultAugmenter() {
|
|
6357
6363
|
return this._resultAugmenter !== null;
|
|
6358
6364
|
}
|
|
6365
|
+
get rateLimitPolicy() {
|
|
6366
|
+
return this._rateLimiter.policy;
|
|
6367
|
+
}
|
|
6359
6368
|
register(def) {
|
|
6360
6369
|
if (!def.name) throw new Error("tool requires a name");
|
|
6361
6370
|
const internal = { ...def };
|
|
@@ -6454,6 +6463,10 @@ var ToolRegistry = class {
|
|
|
6454
6463
|
rejectedReason: "aborted"
|
|
6455
6464
|
});
|
|
6456
6465
|
}
|
|
6466
|
+
const rateLimit = this._rateLimiter.consume(name);
|
|
6467
|
+
if (!rateLimit.allowed) {
|
|
6468
|
+
return JSON.stringify(rateLimit.result);
|
|
6469
|
+
}
|
|
6457
6470
|
let finalResult;
|
|
6458
6471
|
try {
|
|
6459
6472
|
try {
|
|
@@ -6462,7 +6475,8 @@ var ToolRegistry = class {
|
|
|
6462
6475
|
}
|
|
6463
6476
|
const result = await tool.fn(args, {
|
|
6464
6477
|
signal: opts.signal,
|
|
6465
|
-
confirmationGate: opts.confirmationGate
|
|
6478
|
+
confirmationGate: opts.confirmationGate,
|
|
6479
|
+
readTracker: opts.readTracker
|
|
6466
6480
|
});
|
|
6467
6481
|
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
6468
6482
|
let clipped = str;
|
|
@@ -6547,6 +6561,9 @@ function plainTextRejectedReason(name, result) {
|
|
|
6547
6561
|
if ((name === "edit_file" || name === "write_file") && /rejected this edit/i.test(result)) {
|
|
6548
6562
|
return "edit-gate";
|
|
6549
6563
|
}
|
|
6564
|
+
if ((name === "edit_file" || name === "multi_edit") && /read_file first/i.test(result)) {
|
|
6565
|
+
return "read-before-edit";
|
|
6566
|
+
}
|
|
6550
6567
|
if ((name === "run_command" || name === "run_background") && /\buser denied:/i.test(result)) {
|
|
6551
6568
|
return "shell-gate";
|
|
6552
6569
|
}
|
|
@@ -6556,6 +6573,8 @@ function rejectionRecoveryHint(reason) {
|
|
|
6556
6573
|
switch (reason) {
|
|
6557
6574
|
case "edit-gate":
|
|
6558
6575
|
return "Do not re-emit the same edit. Try a genuinely different edit or ask the user how to proceed.";
|
|
6576
|
+
case "read-before-edit":
|
|
6577
|
+
return "Call read_file on the target path first, then re-issue the edit.";
|
|
6559
6578
|
case "shell-gate":
|
|
6560
6579
|
return "Do not retry the same command. Use an allowlisted/read-only command, wait for approval, or ask the user how to proceed.";
|
|
6561
6580
|
case "engineering-lifecycle":
|
|
@@ -6757,32 +6776,43 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
|
6757
6776
|
}
|
|
6758
6777
|
|
|
6759
6778
|
// src/context-manager.ts
|
|
6760
|
-
|
|
6779
|
+
function extractPinnedConstraints(systemPrompt) {
|
|
6780
|
+
const pattern = /# (?:HIGH PRIORITY constraints|User memory|Project memory)[\s\S]*?(?=\n# |\n---|$)/g;
|
|
6781
|
+
return Array.from(systemPrompt.matchAll(pattern), (m) => m[0]).join("\n\n");
|
|
6782
|
+
}
|
|
6783
|
+
var HISTORY_FOLD_THRESHOLD = 0.75;
|
|
6761
6784
|
var HISTORY_FOLD_TAIL_FRACTION = 0.2;
|
|
6762
|
-
var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.
|
|
6785
|
+
var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.78;
|
|
6763
6786
|
var HISTORY_FOLD_AGGRESSIVE_TAIL_FRACTION = 0.1;
|
|
6764
6787
|
var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
|
|
6765
6788
|
var FORCE_SUMMARY_THRESHOLD = 0.8;
|
|
6766
6789
|
var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
|
|
6767
6790
|
var PREFLIGHT_MECHANICAL_TARGET_FRACTION = 0.7;
|
|
6791
|
+
var MAX_BODY_BYTES = 7e5;
|
|
6792
|
+
var MAX_BODY_BYTES_TARGET = 5e5;
|
|
6768
6793
|
var HISTORY_FOLD_SUMMARY_TIMEOUT_MS = 15e3;
|
|
6769
6794
|
var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
|
|
6770
6795
|
var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
|
|
6771
6796
|
var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
|
|
6772
|
-
function
|
|
6797
|
+
function buildFoldSummaryInstruction(pinnedSkillNames) {
|
|
6798
|
+
const base = "Summarize the conversation above as one self-contained prose recap. Preserve the user's ORIGINAL OBJECTIVE (never paraphrase away negative constraints like 'do NOT do X'), all 'do not' / 'never' / 'avoid' instructions, decisions reached, files inspected or modified, tool results still relevant, and any open todos. Skip turn-by-turn play-by-play. Output plain prose only \u2014 no tool calls, no markdown headings, no SEARCH/REPLACE blocks.";
|
|
6799
|
+
if (pinnedSkillNames.length === 0) return base;
|
|
6800
|
+
const list = pinnedSkillNames.map((n) => `"${n}"`).join(", ");
|
|
6801
|
+
return `${base} The following skill memos are pinned verbatim and appended after your summary \u2014 do NOT quote or paraphrase their bodies: ${list}.`;
|
|
6802
|
+
}
|
|
6803
|
+
function collectPinnedSkills(head) {
|
|
6773
6804
|
const pinned = /* @__PURE__ */ new Map();
|
|
6774
|
-
const
|
|
6775
|
-
if (typeof msg.content !== "string")
|
|
6776
|
-
|
|
6777
|
-
const
|
|
6805
|
+
for (const msg of head) {
|
|
6806
|
+
if (typeof msg.content !== "string") continue;
|
|
6807
|
+
SKILL_PIN_REGEX.lastIndex = 0;
|
|
6808
|
+
for (const match of msg.content.matchAll(SKILL_PIN_REGEX)) {
|
|
6809
|
+
const name = match[1];
|
|
6810
|
+
const full = match[0];
|
|
6778
6811
|
pinned.delete(name);
|
|
6779
6812
|
pinned.set(name, full);
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
return hit ? { ...msg, content: next } : msg;
|
|
6784
|
-
});
|
|
6785
|
-
return { stubbedHead, pinnedBodies: [...pinned.values()] };
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
return { names: [...pinned.keys()], bodies: [...pinned.values()] };
|
|
6786
6816
|
}
|
|
6787
6817
|
var ContextManager = class {
|
|
6788
6818
|
constructor(deps) {
|
|
@@ -6831,14 +6861,25 @@ var ContextManager = class {
|
|
|
6831
6861
|
}
|
|
6832
6862
|
return { kind: "none", ...base };
|
|
6833
6863
|
}
|
|
6834
|
-
/** Local-side preflight before sending a request — catches oversized payloads early.
|
|
6864
|
+
/** Local-side preflight before sending a request — catches oversized payloads early.
|
|
6865
|
+
* Two independent signals trip mechanical truncate: token estimate above the context-window
|
|
6866
|
+
* fraction, OR JSON body bytes above the gateway limit (see `MAX_BODY_BYTES`). */
|
|
6835
6867
|
decidePreflight(messages, toolSpecs, model) {
|
|
6836
6868
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
6837
6869
|
const estimate = estimateRequestTokens(messages, toolSpecs ?? null, true);
|
|
6870
|
+
const estimateBytes = Buffer.byteLength(JSON.stringify(messages), "utf8");
|
|
6871
|
+
const tokensOver = estimate / ctxMax > PREFLIGHT_EMERGENCY_THRESHOLD;
|
|
6872
|
+
const bytesOver = estimateBytes > MAX_BODY_BYTES;
|
|
6873
|
+
let trigger = "none";
|
|
6874
|
+
if (tokensOver && bytesOver) trigger = "both";
|
|
6875
|
+
else if (tokensOver) trigger = "tokens";
|
|
6876
|
+
else if (bytesOver) trigger = "bytes";
|
|
6838
6877
|
return {
|
|
6839
|
-
needsAction:
|
|
6878
|
+
needsAction: tokensOver || bytesOver,
|
|
6840
6879
|
estimateTokens: estimate,
|
|
6841
|
-
|
|
6880
|
+
estimateBytes,
|
|
6881
|
+
ctxMax,
|
|
6882
|
+
trigger
|
|
6842
6883
|
};
|
|
6843
6884
|
}
|
|
6844
6885
|
/** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
|
|
@@ -6867,16 +6908,22 @@ var ContextManager = class {
|
|
|
6867
6908
|
const tail = all.slice(boundary);
|
|
6868
6909
|
const headTokens = totalTokens - cumTokens;
|
|
6869
6910
|
if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
|
|
6870
|
-
const {
|
|
6871
|
-
const summary = await this.summarizeForFold(
|
|
6911
|
+
const { names: pinnedNames, bodies: pinnedBodies } = collectPinnedSkills(head);
|
|
6912
|
+
const summary = await this.summarizeForFold(head, pinnedNames);
|
|
6872
6913
|
if (!summary.content) return noop;
|
|
6873
6914
|
const memoTail = pinnedBodies.length > 0 ? `
|
|
6874
6915
|
|
|
6875
6916
|
${SKILL_PIN_MEMO_HEADER}
|
|
6876
6917
|
|
|
6877
6918
|
${pinnedBodies.join("\n\n")}` : "";
|
|
6919
|
+
const constraints = extractPinnedConstraints(this.deps.getSystemPrompt());
|
|
6920
|
+
const constraintTail = constraints ? `
|
|
6921
|
+
|
|
6922
|
+
[PINNED CONSTRAINTS \u2014 preserved verbatim]
|
|
6923
|
+
|
|
6924
|
+
${constraints}` : "";
|
|
6878
6925
|
const summaryMsg = buildAssistantMessage(
|
|
6879
|
-
HISTORY_FOLD_MARKER + summary.content + memoTail,
|
|
6926
|
+
HISTORY_FOLD_MARKER + summary.content + memoTail + constraintTail,
|
|
6880
6927
|
[],
|
|
6881
6928
|
model,
|
|
6882
6929
|
summary.reasoningContent
|
|
@@ -6884,6 +6931,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6884
6931
|
const replacement = [summaryMsg, ...tail];
|
|
6885
6932
|
this.deps.log.compactInPlace(replacement);
|
|
6886
6933
|
this.persistRewrite(replacement);
|
|
6934
|
+
this.deps.onLogRewrite?.();
|
|
6887
6935
|
return {
|
|
6888
6936
|
folded: true,
|
|
6889
6937
|
beforeMessages: all.length,
|
|
@@ -6891,10 +6939,13 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6891
6939
|
summaryChars: summary.content.length
|
|
6892
6940
|
};
|
|
6893
6941
|
}
|
|
6894
|
-
/** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail.
|
|
6942
|
+
/** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail.
|
|
6943
|
+
* Bounded by tokens AND bytes — bytes matter because DeepSeek's gateway 400s on bodies past
|
|
6944
|
+
* `MAX_BODY_BYTES` even when the token budget is far from exhausted. */
|
|
6895
6945
|
mechanicalTruncate(model, opts) {
|
|
6896
6946
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
6897
6947
|
const targetTokens = opts?.targetTokens ?? Math.floor(ctxMax * PREFLIGHT_MECHANICAL_TARGET_FRACTION);
|
|
6948
|
+
const targetBytes = opts?.targetBytes ?? MAX_BODY_BYTES_TARGET;
|
|
6898
6949
|
const all = this.deps.log.toMessages();
|
|
6899
6950
|
const noop = {
|
|
6900
6951
|
folded: false,
|
|
@@ -6904,6 +6955,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6904
6955
|
};
|
|
6905
6956
|
if (all.length === 0) return noop;
|
|
6906
6957
|
const tokenCounts = all.map((m) => estimateConversationTokens([m], true));
|
|
6958
|
+
const byteCounts = all.map((m) => Buffer.byteLength(JSON.stringify(m), "utf8"));
|
|
6907
6959
|
let latestUserBoundary = -1;
|
|
6908
6960
|
for (let i = all.length - 1; i >= 0; i--) {
|
|
6909
6961
|
if (all[i].role === "user") {
|
|
@@ -6912,12 +6964,15 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6912
6964
|
}
|
|
6913
6965
|
}
|
|
6914
6966
|
let cumTokens = 0;
|
|
6967
|
+
let cumBytes = 0;
|
|
6915
6968
|
let boundary = all.length;
|
|
6916
6969
|
let foundSafeBoundary = false;
|
|
6917
6970
|
for (let i = all.length - 1; i >= 0; i--) {
|
|
6918
|
-
const
|
|
6919
|
-
|
|
6920
|
-
|
|
6971
|
+
const nextTokens = cumTokens + tokenCounts[i];
|
|
6972
|
+
const nextBytes = cumBytes + byteCounts[i];
|
|
6973
|
+
if (nextTokens > targetTokens || nextBytes > targetBytes) break;
|
|
6974
|
+
cumTokens = nextTokens;
|
|
6975
|
+
cumBytes = nextBytes;
|
|
6921
6976
|
if (all[i].role === "user") {
|
|
6922
6977
|
boundary = i;
|
|
6923
6978
|
foundSafeBoundary = true;
|
|
@@ -6928,6 +6983,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6928
6983
|
if (replacement.length === all.length) return noop;
|
|
6929
6984
|
this.deps.log.compactInPlace(replacement);
|
|
6930
6985
|
this.persistRewrite(replacement);
|
|
6986
|
+
this.deps.onLogRewrite?.();
|
|
6931
6987
|
return {
|
|
6932
6988
|
folded: true,
|
|
6933
6989
|
beforeMessages: all.length,
|
|
@@ -6946,17 +7002,18 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6946
7002
|
this.persistRewrite([...kept]);
|
|
6947
7003
|
return true;
|
|
6948
7004
|
}
|
|
6949
|
-
async summarizeForFold(messagesToSummarize) {
|
|
7005
|
+
async summarizeForFold(messagesToSummarize, pinnedSkillNames) {
|
|
6950
7006
|
const summaryModel = "deepseek-v4-flash";
|
|
6951
|
-
const systemPrompt = "You compress conversation history for a coding agent. Output one prose recap that preserves: the user's overall goal, decisions and conclusions reached, files inspected or modified, important tool results still relevant to ongoing work, and any open todos. Skip turn-by-turn play-by-play. No tool calls, no markdown headings, no SEARCH/REPLACE blocks \u2014 plain prose only.";
|
|
6952
7007
|
const healed = healLoadedMessages(messagesToSummarize, DEFAULT_MAX_RESULT_CHARS).messages;
|
|
7008
|
+
const agentSystem = this.deps.getSystemPrompt();
|
|
7009
|
+
const fewShots = this.deps.getFewShots?.() ?? [];
|
|
7010
|
+
const tools = this.deps.getToolSpecs?.() ?? [];
|
|
7011
|
+
const instruction = buildFoldSummaryInstruction(pinnedSkillNames);
|
|
6953
7012
|
const messages = [
|
|
6954
|
-
{ role: "system", content:
|
|
7013
|
+
{ role: "system", content: agentSystem },
|
|
7014
|
+
...fewShots.map((m) => ({ ...m })),
|
|
6955
7015
|
...healed,
|
|
6956
|
-
{
|
|
6957
|
-
role: "user",
|
|
6958
|
-
content: "Summarize the conversation above as plain prose. This summary replaces the original turns to free context \u2014 make it self-contained."
|
|
6959
|
-
}
|
|
7016
|
+
{ role: "user", content: instruction }
|
|
6960
7017
|
];
|
|
6961
7018
|
const turnSignal = this.deps.getAbortSignal();
|
|
6962
7019
|
const foldCtrl = new AbortController();
|
|
@@ -6986,9 +7043,9 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6986
7043
|
this.deps.client.chat({
|
|
6987
7044
|
model: summaryModel,
|
|
6988
7045
|
messages,
|
|
7046
|
+
tools: tools.length ? tools : void 0,
|
|
6989
7047
|
signal: foldCtrl.signal,
|
|
6990
|
-
thinking:
|
|
6991
|
-
reasoningEffort: "high"
|
|
7048
|
+
thinking: "disabled"
|
|
6992
7049
|
}),
|
|
6993
7050
|
abortPromise,
|
|
6994
7051
|
timeoutPromise
|
|
@@ -7073,6 +7130,7 @@ function formatLoopError(err, probe) {
|
|
|
7073
7130
|
if (status === "402") return t("errors.balance402", { inner });
|
|
7074
7131
|
if (status === "422") return t("errors.badparam422", { inner });
|
|
7075
7132
|
if (status === "400") return t("errors.badrequest400", { inner });
|
|
7133
|
+
if (status === "429") return t("errors.concurrency429", { inner });
|
|
7076
7134
|
if (is5xxStatus(status)) return formatDeepSeek5xx(status, probe);
|
|
7077
7135
|
return msg;
|
|
7078
7136
|
}
|
|
@@ -7655,8 +7713,34 @@ function signature(call) {
|
|
|
7655
7713
|
return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
|
|
7656
7714
|
}
|
|
7657
7715
|
|
|
7716
|
+
// src/tools/read-tracker.ts
|
|
7717
|
+
import * as pathMod from "path";
|
|
7718
|
+
var ReadTracker = class _ReadTracker {
|
|
7719
|
+
_seen = /* @__PURE__ */ new Set();
|
|
7720
|
+
static norm(abs) {
|
|
7721
|
+
const resolved = pathMod.resolve(abs);
|
|
7722
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7723
|
+
}
|
|
7724
|
+
markRead(abs) {
|
|
7725
|
+
this._seen.add(_ReadTracker.norm(abs));
|
|
7726
|
+
}
|
|
7727
|
+
hasRead(abs) {
|
|
7728
|
+
return this._seen.has(_ReadTracker.norm(abs));
|
|
7729
|
+
}
|
|
7730
|
+
reset() {
|
|
7731
|
+
this._seen.clear();
|
|
7732
|
+
}
|
|
7733
|
+
get size() {
|
|
7734
|
+
return this._seen.size;
|
|
7735
|
+
}
|
|
7736
|
+
};
|
|
7737
|
+
|
|
7658
7738
|
// src/loop.ts
|
|
7659
7739
|
var ESCALATION_MODEL = "deepseek-v4-pro";
|
|
7740
|
+
var MID_TURN_STEER_WRAPPER = "[Mid-turn steer queued by the user. Do not treat this as a new task; use it only as additional guidance for the current task after completing the current step.]";
|
|
7741
|
+
function formatSteerUserMessage(content) {
|
|
7742
|
+
return [MID_TURN_STEER_WRAPPER, content].join("\n");
|
|
7743
|
+
}
|
|
7660
7744
|
var CacheFirstLoop = class {
|
|
7661
7745
|
client;
|
|
7662
7746
|
prefix;
|
|
@@ -7665,6 +7749,8 @@ var CacheFirstLoop = class {
|
|
|
7665
7749
|
scratch = new VolatileScratch();
|
|
7666
7750
|
stats = new SessionStats();
|
|
7667
7751
|
repair;
|
|
7752
|
+
/** Files the model has read this session; gates edit_file / multi_edit so SEARCH text matches on-disk bytes. Cleared on fold / mechanical truncate (the model's byte-level view of the elided history is gone). In-memory only — naturally empty on resume. */
|
|
7753
|
+
readTracker = new ReadTracker();
|
|
7668
7754
|
// Mutable via configure() — slash commands in the TUI / library callers tweak
|
|
7669
7755
|
// these mid-session so users don't have to restart.
|
|
7670
7756
|
model;
|
|
@@ -7688,15 +7774,19 @@ var CacheFirstLoop = class {
|
|
|
7688
7774
|
_turnAbort = new AbortController();
|
|
7689
7775
|
/** Authoritative running-id set — UI cards consult this instead of trusting end-event delivery. Insert at dispatch entry, delete in finally. */
|
|
7690
7776
|
_inflight = new InflightSet();
|
|
7691
|
-
/** Typeahead steer
|
|
7692
|
-
|
|
7777
|
+
/** Typeahead steer messages set by the UI; step() consumes one at each iter boundary. */
|
|
7778
|
+
_steerQueue = [];
|
|
7693
7779
|
/** Set true when a steer was consumed this turn; cleared on next step() entry. */
|
|
7694
7780
|
_steerConsumed = false;
|
|
7695
7781
|
/** UI calls this to inject a mid-turn steer message without aborting the current turn.
|
|
7696
|
-
* New text resets steerConsumed
|
|
7782
|
+
* New text resets steerConsumed because a fresh steer is queued. */
|
|
7697
7783
|
steer(text) {
|
|
7698
|
-
|
|
7699
|
-
|
|
7784
|
+
if (text === null) {
|
|
7785
|
+
this._steerQueue.length = 0;
|
|
7786
|
+
return;
|
|
7787
|
+
}
|
|
7788
|
+
this._steerQueue.push(text);
|
|
7789
|
+
this._steerConsumed = false;
|
|
7700
7790
|
}
|
|
7701
7791
|
/** True when a steer was consumed this turn (UI gate to avoid double-submit). */
|
|
7702
7792
|
get steerConsumed() {
|
|
@@ -7782,7 +7872,11 @@ var CacheFirstLoop = class {
|
|
|
7782
7872
|
stats: this.stats,
|
|
7783
7873
|
sessionName: this.sessionName,
|
|
7784
7874
|
getAbortSignal: () => this._turnAbort.signal,
|
|
7785
|
-
getCurrentTurn: () => this._turn
|
|
7875
|
+
getCurrentTurn: () => this._turn,
|
|
7876
|
+
getSystemPrompt: () => this.prefix.system,
|
|
7877
|
+
getToolSpecs: () => this.prefix.toolSpecs,
|
|
7878
|
+
getFewShots: () => this.prefix.fewShots,
|
|
7879
|
+
onLogRewrite: () => this.readTracker.reset()
|
|
7786
7880
|
});
|
|
7787
7881
|
}
|
|
7788
7882
|
/** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
|
|
@@ -7953,7 +8047,8 @@ ${reason}`
|
|
|
7953
8047
|
const result = await this.tools.dispatch(name, args, {
|
|
7954
8048
|
signal,
|
|
7955
8049
|
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
7956
|
-
confirmationGate: this.confirmationGate
|
|
8050
|
+
confirmationGate: this.confirmationGate,
|
|
8051
|
+
readTracker: this.readTracker
|
|
7957
8052
|
});
|
|
7958
8053
|
const postReport = await runHooks({
|
|
7959
8054
|
hooks: this.hooks,
|
|
@@ -7981,11 +8076,9 @@ ${reason}`
|
|
|
7981
8076
|
return generated;
|
|
7982
8077
|
}
|
|
7983
8078
|
_inflightCounter = 0;
|
|
7984
|
-
buildMessages(
|
|
8079
|
+
buildMessages() {
|
|
7985
8080
|
const healedMessages = this.healActiveLogBeforeSend();
|
|
7986
|
-
|
|
7987
|
-
if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
|
|
7988
|
-
return msgs;
|
|
8081
|
+
return [...this.prefix.toMessages(), ...healedMessages];
|
|
7989
8082
|
}
|
|
7990
8083
|
healActiveLogBeforeSend() {
|
|
7991
8084
|
const current = this.log.toMessages();
|
|
@@ -8066,6 +8159,7 @@ ${reason}`
|
|
|
8066
8159
|
cap: this.budgetUsd.toFixed(2)
|
|
8067
8160
|
})
|
|
8068
8161
|
};
|
|
8162
|
+
this._steerQueue.length = 0;
|
|
8069
8163
|
return;
|
|
8070
8164
|
}
|
|
8071
8165
|
if (!this._budgetWarned && spent >= this.budgetUsd * 0.8) {
|
|
@@ -8104,8 +8198,8 @@ ${reason}`
|
|
|
8104
8198
|
};
|
|
8105
8199
|
}
|
|
8106
8200
|
this.appendAndPersist({ role: "user", content: userInput });
|
|
8107
|
-
let pendingUser = null;
|
|
8108
8201
|
const toolSpecs = this.prefix.tools();
|
|
8202
|
+
let rateLimitWarningShown = false;
|
|
8109
8203
|
for (let iter = 0; ; iter++) {
|
|
8110
8204
|
if (signal.aborted) {
|
|
8111
8205
|
try {
|
|
@@ -8126,6 +8220,7 @@ ${reason}`
|
|
|
8126
8220
|
} finally {
|
|
8127
8221
|
this._turnAbort = new AbortController();
|
|
8128
8222
|
}
|
|
8223
|
+
this._steerQueue.length = 0;
|
|
8129
8224
|
return;
|
|
8130
8225
|
}
|
|
8131
8226
|
if (iter > 0) {
|
|
@@ -8135,14 +8230,15 @@ ${reason}`
|
|
|
8135
8230
|
content: t("loop.toolUploadStatus")
|
|
8136
8231
|
};
|
|
8137
8232
|
}
|
|
8138
|
-
let messages = this.buildMessages(
|
|
8139
|
-
if (this.
|
|
8140
|
-
const steer = this.
|
|
8141
|
-
this.
|
|
8142
|
-
this.
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8233
|
+
let messages = this.buildMessages();
|
|
8234
|
+
if (this._steerQueue.length > 0) {
|
|
8235
|
+
const steer = this._steerQueue.shift();
|
|
8236
|
+
this._steerConsumed = this._steerQueue.length === 0;
|
|
8237
|
+
this.appendAndPersist({
|
|
8238
|
+
role: "user",
|
|
8239
|
+
content: formatSteerUserMessage(steer)
|
|
8240
|
+
});
|
|
8241
|
+
messages = this.buildMessages();
|
|
8146
8242
|
yield {
|
|
8147
8243
|
turn: this._turn,
|
|
8148
8244
|
role: "steer",
|
|
@@ -8152,17 +8248,17 @@ ${reason}`
|
|
|
8152
8248
|
{
|
|
8153
8249
|
const decision2 = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
8154
8250
|
if (decision2.needsAction) {
|
|
8155
|
-
const { estimateTokens: estimate, ctxMax } = decision2;
|
|
8251
|
+
const { estimateTokens: estimate, estimateBytes, ctxMax } = decision2;
|
|
8156
8252
|
yield {
|
|
8157
8253
|
turn: this._turn,
|
|
8158
8254
|
role: "status",
|
|
8159
8255
|
content: t("loop.preflightTruncateStatus")
|
|
8160
8256
|
};
|
|
8161
8257
|
const result = this.context.mechanicalTruncate(this.model, {
|
|
8162
|
-
allowEmpty:
|
|
8258
|
+
allowEmpty: false
|
|
8163
8259
|
});
|
|
8164
8260
|
if (result.folded) {
|
|
8165
|
-
messages = this.buildMessages(
|
|
8261
|
+
messages = this.buildMessages();
|
|
8166
8262
|
const after = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
8167
8263
|
const stillFull = after.needsAction;
|
|
8168
8264
|
yield {
|
|
@@ -8174,6 +8270,7 @@ ${reason}`
|
|
|
8174
8270
|
estimate: after.estimateTokens.toLocaleString(),
|
|
8175
8271
|
ctxMax: after.ctxMax.toLocaleString(),
|
|
8176
8272
|
pct: Math.round(after.estimateTokens / after.ctxMax * 100),
|
|
8273
|
+
bodyKB: Math.round(after.estimateBytes / 1024).toLocaleString(),
|
|
8177
8274
|
beforeMessages: result.beforeMessages,
|
|
8178
8275
|
afterMessages: result.afterMessages
|
|
8179
8276
|
}
|
|
@@ -8186,7 +8283,8 @@ ${reason}`
|
|
|
8186
8283
|
content: t("loop.preflightNoFold", {
|
|
8187
8284
|
estimate: estimate.toLocaleString(),
|
|
8188
8285
|
ctxMax: ctxMax.toLocaleString(),
|
|
8189
|
-
pct: Math.round(estimate / ctxMax * 100)
|
|
8286
|
+
pct: Math.round(estimate / ctxMax * 100),
|
|
8287
|
+
bodyKB: Math.round(estimateBytes / 1024).toLocaleString()
|
|
8190
8288
|
})
|
|
8191
8289
|
};
|
|
8192
8290
|
}
|
|
@@ -8306,6 +8404,7 @@ ${reason}`
|
|
|
8306
8404
|
} finally {
|
|
8307
8405
|
this._turnAbort = new AbortController();
|
|
8308
8406
|
}
|
|
8407
|
+
this._steerQueue.length = 0;
|
|
8309
8408
|
return;
|
|
8310
8409
|
}
|
|
8311
8410
|
const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
|
|
@@ -8315,6 +8414,7 @@ ${reason}`
|
|
|
8315
8414
|
content: "",
|
|
8316
8415
|
error: formatLoopError(err, probe)
|
|
8317
8416
|
};
|
|
8417
|
+
this._steerQueue.length = 0;
|
|
8318
8418
|
return;
|
|
8319
8419
|
}
|
|
8320
8420
|
if (this.autoEscalate && this.modelForCurrentCall() !== ESCALATION_MODEL && isEscalationRequest(assistantContent)) {
|
|
@@ -8395,11 +8495,16 @@ ${reason}`
|
|
|
8395
8495
|
};
|
|
8396
8496
|
}
|
|
8397
8497
|
if (repairedCalls.length === 0) {
|
|
8498
|
+
if (this._steerQueue.length > 0) {
|
|
8499
|
+
continue;
|
|
8500
|
+
}
|
|
8398
8501
|
if (allSuppressed) {
|
|
8399
8502
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "stuck" });
|
|
8503
|
+
this._steerQueue.length = 0;
|
|
8400
8504
|
return;
|
|
8401
8505
|
}
|
|
8402
8506
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
8507
|
+
this._steerQueue.length = 0;
|
|
8403
8508
|
return;
|
|
8404
8509
|
}
|
|
8405
8510
|
const decision = this.context.decideAfterUsage(usage, this.model, this._foldedThisTurn);
|
|
@@ -8445,6 +8550,7 @@ ${reason}`
|
|
|
8445
8550
|
};
|
|
8446
8551
|
this.context.trimTrailingToolCalls();
|
|
8447
8552
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "context-guard" });
|
|
8553
|
+
this._steerQueue.length = 0;
|
|
8448
8554
|
return;
|
|
8449
8555
|
}
|
|
8450
8556
|
const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
|
|
@@ -8492,6 +8598,15 @@ ${reason}`
|
|
|
8492
8598
|
}
|
|
8493
8599
|
for (const w of preWarnings) yield w;
|
|
8494
8600
|
for (const w of postWarnings) yield w;
|
|
8601
|
+
const rateLimited = parseRateLimitedToolResult(result);
|
|
8602
|
+
if (rateLimited && !rateLimitWarningShown) {
|
|
8603
|
+
rateLimitWarningShown = true;
|
|
8604
|
+
yield {
|
|
8605
|
+
turn: this._turn,
|
|
8606
|
+
role: "warning",
|
|
8607
|
+
content: rateLimited.message
|
|
8608
|
+
};
|
|
8609
|
+
}
|
|
8495
8610
|
this.appendAndPersist({
|
|
8496
8611
|
role: "tool",
|
|
8497
8612
|
tool_call_id: call.id ?? "",
|
|
@@ -8514,7 +8629,7 @@ ${reason}`
|
|
|
8514
8629
|
return {
|
|
8515
8630
|
client: this.client,
|
|
8516
8631
|
signal: this._turnAbort.signal,
|
|
8517
|
-
buildMessages: () => this.buildMessages(
|
|
8632
|
+
buildMessages: () => this.buildMessages(),
|
|
8518
8633
|
appendAndPersist: (m) => this.appendAndPersist(m),
|
|
8519
8634
|
recordStats: (model, usage) => this.stats.record(this._turn, model, usage),
|
|
8520
8635
|
turn: this._turn
|
|
@@ -8539,7 +8654,7 @@ function parsePositiveIntEnv(raw) {
|
|
|
8539
8654
|
// src/at-mentions.ts
|
|
8540
8655
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
8541
8656
|
import { readdir, stat } from "fs/promises";
|
|
8542
|
-
import { isAbsolute, join, relative, resolve } from "path";
|
|
8657
|
+
import { isAbsolute, join, relative, resolve as resolve2 } from "path";
|
|
8543
8658
|
|
|
8544
8659
|
// src/at-mentions-url.ts
|
|
8545
8660
|
var AT_URL_PATTERN = /(?<=^|\s)@(https?:\/\/\S+)/g;
|
|
@@ -8670,7 +8785,7 @@ function listFilesSync(root, opts = {}) {
|
|
|
8670
8785
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
8671
8786
|
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
8672
8787
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8673
|
-
const rootAbs =
|
|
8788
|
+
const rootAbs = resolve2(root);
|
|
8674
8789
|
const respectGi = opts.respectGitignore !== false;
|
|
8675
8790
|
const out = [];
|
|
8676
8791
|
const walk2 = (dirAbs, dirRel, layers) => {
|
|
@@ -8734,7 +8849,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
8734
8849
|
async function walkFilesStream(root, opts) {
|
|
8735
8850
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8736
8851
|
const respectGi = opts.respectGitignore !== false;
|
|
8737
|
-
const rootAbs =
|
|
8852
|
+
const rootAbs = resolve2(root);
|
|
8738
8853
|
const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
|
|
8739
8854
|
let scanned = 0;
|
|
8740
8855
|
let halted = false;
|
|
@@ -8812,8 +8927,8 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
|
|
|
8812
8927
|
async function listDirectory(root, relDir, opts = {}) {
|
|
8813
8928
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8814
8929
|
const respectGi = opts.respectGitignore !== false;
|
|
8815
|
-
const rootAbs =
|
|
8816
|
-
const dirAbs =
|
|
8930
|
+
const rootAbs = resolve2(root);
|
|
8931
|
+
const dirAbs = resolve2(rootAbs, relDir);
|
|
8817
8932
|
const rel = relative(rootAbs, dirAbs);
|
|
8818
8933
|
if (rel.startsWith("..") || isAbsolute(rel)) return [];
|
|
8819
8934
|
const layers = [];
|
|
@@ -8978,7 +9093,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
8978
9093
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
8979
9094
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
8980
9095
|
const fs4 = opts.fs ?? defaultFs;
|
|
8981
|
-
const root =
|
|
9096
|
+
const root = resolve2(rootDir);
|
|
8982
9097
|
const seen = /* @__PURE__ */ new Map();
|
|
8983
9098
|
const expansions = [];
|
|
8984
9099
|
const dirListings = /* @__PURE__ */ new Map();
|
|
@@ -9025,7 +9140,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs4, dirListings
|
|
|
9025
9140
|
if (isAbsolute(rawPath)) {
|
|
9026
9141
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
9027
9142
|
}
|
|
9028
|
-
const resolved =
|
|
9143
|
+
const resolved = resolve2(root, rawPath);
|
|
9029
9144
|
const rel = relative(root, resolved);
|
|
9030
9145
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
9031
9146
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
@@ -9055,7 +9170,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs4, dirListings
|
|
|
9055
9170
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
9056
9171
|
}
|
|
9057
9172
|
function readSafe(root, rawPath, fs4) {
|
|
9058
|
-
const resolved =
|
|
9173
|
+
const resolved = resolve2(root, rawPath);
|
|
9059
9174
|
try {
|
|
9060
9175
|
return fs4.read(resolved);
|
|
9061
9176
|
} catch {
|
|
@@ -9331,16 +9446,19 @@ function registerChoiceTool(registry, opts = {}) {
|
|
|
9331
9446
|
|
|
9332
9447
|
// src/tools/web.ts
|
|
9333
9448
|
var import_node_html_parser = __toESM(require_dist(), 1);
|
|
9449
|
+
import { lookup } from "dns/promises";
|
|
9450
|
+
import { isIP } from "net";
|
|
9334
9451
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
9335
9452
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
9336
9453
|
var DEFAULT_TOPK = 5;
|
|
9337
9454
|
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
9338
9455
|
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";
|
|
9339
|
-
var
|
|
9456
|
+
var BING_ENDPOINT = "https://cn.bing.com/search";
|
|
9340
9457
|
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
9341
9458
|
var TAVILY_ENDPOINT = "https://api.tavily.com/search";
|
|
9342
9459
|
var PERPLEXITY_ENDPOINT = "https://api.perplexity.ai/chat/completions";
|
|
9343
9460
|
var EXA_ENDPOINT = "https://api.exa.ai/answer";
|
|
9461
|
+
var FETCH_MAX_REDIRECTS = 5;
|
|
9344
9462
|
function searchStatusError(status) {
|
|
9345
9463
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
9346
9464
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -9353,6 +9471,63 @@ function fetchStatusError(status, url) {
|
|
|
9353
9471
|
if (status >= 500 && status <= 599) return t("webErrors.fetchServerError5xx", { status, url });
|
|
9354
9472
|
return t("webErrors.fetchStatus", { status, url });
|
|
9355
9473
|
}
|
|
9474
|
+
function parseIpv4(address) {
|
|
9475
|
+
const parts = address.split(".");
|
|
9476
|
+
if (parts.length !== 4) return null;
|
|
9477
|
+
let out = 0;
|
|
9478
|
+
for (const part of parts) {
|
|
9479
|
+
if (!/^\d+$/.test(part)) return null;
|
|
9480
|
+
const n = Number(part);
|
|
9481
|
+
if (!Number.isInteger(n) || n < 0 || n > 255) return null;
|
|
9482
|
+
out = (out << 8) + n;
|
|
9483
|
+
}
|
|
9484
|
+
return out >>> 0;
|
|
9485
|
+
}
|
|
9486
|
+
function ipv4InRange(value, base, bits) {
|
|
9487
|
+
const parsed = parseIpv4(base);
|
|
9488
|
+
if (parsed === null) return false;
|
|
9489
|
+
const mask = bits === 0 ? 0 : 4294967295 << 32 - bits >>> 0;
|
|
9490
|
+
return (value & mask) === (parsed & mask);
|
|
9491
|
+
}
|
|
9492
|
+
function isPrivateIpv4(address) {
|
|
9493
|
+
const value = parseIpv4(address);
|
|
9494
|
+
if (value === null) return false;
|
|
9495
|
+
return ipv4InRange(value, "0.0.0.0", 8) || ipv4InRange(value, "10.0.0.0", 8) || ipv4InRange(value, "100.64.0.0", 10) || ipv4InRange(value, "127.0.0.0", 8) || ipv4InRange(value, "169.254.0.0", 16) || ipv4InRange(value, "172.16.0.0", 12) || ipv4InRange(value, "192.0.0.0", 24) || ipv4InRange(value, "192.0.2.0", 24) || ipv4InRange(value, "192.168.0.0", 16) || ipv4InRange(value, "198.18.0.0", 15) || ipv4InRange(value, "198.51.100.0", 24) || ipv4InRange(value, "203.0.113.0", 24) || ipv4InRange(value, "224.0.0.0", 4) || ipv4InRange(value, "240.0.0.0", 4);
|
|
9496
|
+
}
|
|
9497
|
+
function normalizeIpv6(address) {
|
|
9498
|
+
return address.toLowerCase().replace(/(^|:)0+([0-9a-f])/g, "$1$2");
|
|
9499
|
+
}
|
|
9500
|
+
function isPrivateIpv6(address) {
|
|
9501
|
+
const normalized = normalizeIpv6(address);
|
|
9502
|
+
const mapped = /^::ffff:(?:0+:)?(\d+\.\d+\.\d+\.\d+)$/i.exec(normalized);
|
|
9503
|
+
if (mapped) return isPrivateIpv4(mapped[1]);
|
|
9504
|
+
return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb") || normalized.startsWith("ff");
|
|
9505
|
+
}
|
|
9506
|
+
function isInternalAddress(address) {
|
|
9507
|
+
const family = isIP(address);
|
|
9508
|
+
if (family === 4) return isPrivateIpv4(address);
|
|
9509
|
+
if (family === 6) return isPrivateIpv6(address);
|
|
9510
|
+
return false;
|
|
9511
|
+
}
|
|
9512
|
+
async function assertPublicHttpUrl(rawUrl) {
|
|
9513
|
+
const url = new URL(rawUrl);
|
|
9514
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
9515
|
+
throw new Error(`web_fetch refuses non-HTTP URL: ${url.protocol}`);
|
|
9516
|
+
}
|
|
9517
|
+
const host = url.hostname;
|
|
9518
|
+
const literal = isIP(host);
|
|
9519
|
+
const addresses = literal ? [host] : (await lookup(host, { all: true, verbatim: true })).map((entry) => entry.address);
|
|
9520
|
+
if (addresses.length === 0 || addresses.some(isInternalAddress)) {
|
|
9521
|
+
throw new Error(`web_fetch refuses internal or reserved host: ${host}`);
|
|
9522
|
+
}
|
|
9523
|
+
return url;
|
|
9524
|
+
}
|
|
9525
|
+
function redirectLocation(resp, currentUrl) {
|
|
9526
|
+
if (resp.status < 300 || resp.status > 399) return null;
|
|
9527
|
+
const location = resp.headers.get("location");
|
|
9528
|
+
if (!location) return null;
|
|
9529
|
+
return new URL(location, currentUrl).toString();
|
|
9530
|
+
}
|
|
9356
9531
|
async function webSearch(query, opts = {}) {
|
|
9357
9532
|
if (opts.engine === "metaso") {
|
|
9358
9533
|
return searchMetaso(query, opts);
|
|
@@ -9369,29 +9544,29 @@ async function webSearch(query, opts = {}) {
|
|
|
9369
9544
|
if (opts.engine === "exa") {
|
|
9370
9545
|
return searchExa(query, opts);
|
|
9371
9546
|
}
|
|
9372
|
-
return
|
|
9547
|
+
return searchBing(query, opts);
|
|
9373
9548
|
}
|
|
9374
|
-
async function
|
|
9549
|
+
async function searchBing(query, opts = {}) {
|
|
9375
9550
|
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
9376
|
-
const resp = await fetch(`${
|
|
9551
|
+
const resp = await fetch(`${BING_ENDPOINT}?q=${encodeURIComponent(query)}`, {
|
|
9377
9552
|
headers: {
|
|
9378
9553
|
"User-Agent": USER_AGENT,
|
|
9379
9554
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
9380
|
-
"Accept-Language": "
|
|
9555
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
|
|
9381
9556
|
},
|
|
9382
9557
|
signal: opts.signal,
|
|
9383
9558
|
redirect: "follow"
|
|
9384
9559
|
});
|
|
9385
9560
|
if (!resp.ok) throw new Error(searchStatusError(resp.status));
|
|
9386
9561
|
const html = await resp.text();
|
|
9387
|
-
const results =
|
|
9562
|
+
const results = parseBingResults(html).slice(0, topK);
|
|
9388
9563
|
if (results.length === 0) {
|
|
9389
9564
|
if (/no results found|did not match any documents/i.test(html)) return [];
|
|
9390
9565
|
if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
|
|
9391
|
-
throw new Error(t("webErrors.
|
|
9566
|
+
throw new Error(t("webErrors.bingBlocked"));
|
|
9392
9567
|
}
|
|
9393
9568
|
throw new Error(
|
|
9394
|
-
t("webErrors.
|
|
9569
|
+
t("webErrors.bingNoResults", {
|
|
9395
9570
|
chars: html.length,
|
|
9396
9571
|
preview: html.slice(0, 120).replace(/\s+/g, " ")
|
|
9397
9572
|
})
|
|
@@ -9444,6 +9619,7 @@ async function searchSearxng(query, opts = {}) {
|
|
|
9444
9619
|
async function searchMetaso(query, opts = {}) {
|
|
9445
9620
|
const topK = Math.max(1, Math.min(100, opts.topK ?? DEFAULT_TOPK));
|
|
9446
9621
|
const apiKey = loadMetasoApiKey();
|
|
9622
|
+
if (!apiKey) throw new Error(t("webErrors.metasoMissingKey"));
|
|
9447
9623
|
let resp;
|
|
9448
9624
|
try {
|
|
9449
9625
|
resp = await fetch(`${METASO_ENDPOINT}/search`, {
|
|
@@ -9707,35 +9883,19 @@ function parseSearxngHtmlResults(html) {
|
|
|
9707
9883
|
}
|
|
9708
9884
|
return results;
|
|
9709
9885
|
}
|
|
9710
|
-
function
|
|
9711
|
-
const
|
|
9712
|
-
const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
|
|
9713
|
-
let m;
|
|
9714
|
-
while (true) {
|
|
9715
|
-
m = titleAnchorRe.exec(html);
|
|
9716
|
-
if (m === null) break;
|
|
9717
|
-
titles.push(m[0]);
|
|
9718
|
-
}
|
|
9719
|
-
const snippets = [];
|
|
9720
|
-
const snippetRe = /<p\b[^>]*\bclass="s"[^>]*>([\s\S]*?)<\/p>/g;
|
|
9721
|
-
while (true) {
|
|
9722
|
-
m = snippetRe.exec(html);
|
|
9723
|
-
if (m === null) break;
|
|
9724
|
-
snippets.push(m[1] ?? "");
|
|
9725
|
-
}
|
|
9726
|
-
const hrefRe = /href="([^"]+)"/;
|
|
9727
|
-
const innerRe = /<a\b[^>]*>([\s\S]*?)<\/a>/;
|
|
9886
|
+
function parseBingResults(html) {
|
|
9887
|
+
const root = (0, import_node_html_parser.parse)(html);
|
|
9728
9888
|
const results = [];
|
|
9729
|
-
for (
|
|
9730
|
-
const anchor =
|
|
9731
|
-
|
|
9732
|
-
const
|
|
9733
|
-
if (!
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
});
|
|
9889
|
+
for (const li of root.querySelectorAll("li.b_algo")) {
|
|
9890
|
+
const anchor = li.querySelector("h2 a[href]");
|
|
9891
|
+
if (!anchor) continue;
|
|
9892
|
+
const href = anchor.getAttribute("href");
|
|
9893
|
+
if (!href) continue;
|
|
9894
|
+
const title = anchor.textContent.trim();
|
|
9895
|
+
if (!title) continue;
|
|
9896
|
+
const cap = li.querySelector("div.b_caption p");
|
|
9897
|
+
const snippet = cap ? cap.textContent.trim().replace(/\s+/g, " ") : "";
|
|
9898
|
+
results.push({ title, url: href, snippet });
|
|
9739
9899
|
}
|
|
9740
9900
|
return results;
|
|
9741
9901
|
}
|
|
@@ -9751,12 +9911,23 @@ async function webFetch(url, opts = {}) {
|
|
|
9751
9911
|
const cancel = () => ctl.abort();
|
|
9752
9912
|
opts.signal?.addEventListener("abort", cancel, { once: true });
|
|
9753
9913
|
let resp;
|
|
9914
|
+
let currentUrl = url;
|
|
9754
9915
|
try {
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9916
|
+
for (let redirects = 0; ; redirects++) {
|
|
9917
|
+
const parsed = await assertPublicHttpUrl(currentUrl);
|
|
9918
|
+
if (ctl.signal.aborted) throw new DOMException("aborted", "AbortError");
|
|
9919
|
+
resp = await fetch(parsed, {
|
|
9920
|
+
headers: { "User-Agent": USER_AGENT, Accept: "text/html,text/plain,*/*" },
|
|
9921
|
+
signal: ctl.signal,
|
|
9922
|
+
redirect: "manual"
|
|
9923
|
+
});
|
|
9924
|
+
const nextUrl = redirectLocation(resp, parsed.toString());
|
|
9925
|
+
if (!nextUrl) break;
|
|
9926
|
+
if (redirects >= FETCH_MAX_REDIRECTS) {
|
|
9927
|
+
throw new Error(`web_fetch redirect limit exceeded for ${url}`);
|
|
9928
|
+
}
|
|
9929
|
+
currentUrl = nextUrl;
|
|
9930
|
+
}
|
|
9760
9931
|
} catch (err) {
|
|
9761
9932
|
if (timedOut) {
|
|
9762
9933
|
throw new Error(t("webErrors.fetchTimeout", { ms: timeoutMs, url }));
|
|
@@ -9779,7 +9950,7 @@ async function webFetch(url, opts = {}) {
|
|
|
9779
9950
|
const finalText = truncated ? `${text.slice(0, maxChars)}
|
|
9780
9951
|
|
|
9781
9952
|
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
9782
|
-
return { url, title, text: finalText, truncated };
|
|
9953
|
+
return { url: currentUrl, title, text: finalText, truncated };
|
|
9783
9954
|
}
|
|
9784
9955
|
async function readBodyCapped(resp, maxBytes) {
|
|
9785
9956
|
if (!resp.body) return await resp.text();
|
|
@@ -9851,9 +10022,6 @@ function walkExtract(node, out) {
|
|
|
9851
10022
|
for (const child of node.childNodes) walkExtract(child, out);
|
|
9852
10023
|
if (isBreak) out.push("\n");
|
|
9853
10024
|
}
|
|
9854
|
-
function stripHtml(s) {
|
|
9855
|
-
return (0, import_node_html_parser.parse)(s).text;
|
|
9856
|
-
}
|
|
9857
10025
|
var HTML_ENTITIES = {
|
|
9858
10026
|
amp: "&",
|
|
9859
10027
|
lt: "<",
|
|
@@ -9968,14 +10136,14 @@ ${i + 1}. ${r.title}`);
|
|
|
9968
10136
|
// src/tools/filesystem.ts
|
|
9969
10137
|
var import_picomatch2 = __toESM(require_picomatch(), 1);
|
|
9970
10138
|
import { promises as fs3 } from "fs";
|
|
9971
|
-
import * as
|
|
10139
|
+
import * as pathMod5 from "path";
|
|
9972
10140
|
|
|
9973
10141
|
// src/memory/subdir.ts
|
|
9974
10142
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
9975
|
-
import { dirname, join as join2, relative as relative2, resolve as
|
|
10143
|
+
import { dirname, join as join2, relative as relative2, resolve as resolve3 } from "path";
|
|
9976
10144
|
function findDirMemory(absDir, rootDir) {
|
|
9977
|
-
const root =
|
|
9978
|
-
const target =
|
|
10145
|
+
const root = resolve3(rootDir);
|
|
10146
|
+
const target = resolve3(absDir);
|
|
9979
10147
|
const rel = relative2(root, target);
|
|
9980
10148
|
if (rel.startsWith("..")) return [];
|
|
9981
10149
|
const found = [];
|
|
@@ -9997,7 +10165,7 @@ function findDirMemory(absDir, rootDir) {
|
|
|
9997
10165
|
return found;
|
|
9998
10166
|
}
|
|
9999
10167
|
function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
10000
|
-
return findDirMemory(dirname(
|
|
10168
|
+
return findDirMemory(dirname(resolve3(absPath)), rootDir);
|
|
10001
10169
|
}
|
|
10002
10170
|
function readSubdirMemoryContent(path) {
|
|
10003
10171
|
let raw;
|
|
@@ -10021,9 +10189,9 @@ ${content}`;
|
|
|
10021
10189
|
// src/tools/fs/glob.ts
|
|
10022
10190
|
var import_picomatch = __toESM(require_picomatch(), 1);
|
|
10023
10191
|
import { promises as fs } from "fs";
|
|
10024
|
-
import * as
|
|
10192
|
+
import * as pathMod2 from "path";
|
|
10025
10193
|
function displayRel(rootDir, full) {
|
|
10026
|
-
return
|
|
10194
|
+
return pathMod2.relative(rootDir, full).replaceAll("\\", "/");
|
|
10027
10195
|
}
|
|
10028
10196
|
async function globFiles(ctx, startAbs, args) {
|
|
10029
10197
|
if (args.signal?.aborted) {
|
|
@@ -10045,7 +10213,7 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
10045
10213
|
return;
|
|
10046
10214
|
}
|
|
10047
10215
|
for (const e of entries) {
|
|
10048
|
-
const full =
|
|
10216
|
+
const full = pathMod2.join(dir, e.name);
|
|
10049
10217
|
if (e.isDirectory()) {
|
|
10050
10218
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
10051
10219
|
await walk2(full);
|
|
@@ -10082,7 +10250,7 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
10082
10250
|
}
|
|
10083
10251
|
|
|
10084
10252
|
// src/tools/fs/outline.ts
|
|
10085
|
-
import * as
|
|
10253
|
+
import * as pathMod3 from "path";
|
|
10086
10254
|
var OUTLINE_MAX_ENTRIES = 30;
|
|
10087
10255
|
var OUTLINE_TAIL_KEEP = 5;
|
|
10088
10256
|
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
@@ -10125,7 +10293,7 @@ var EXT_TO_LANG = {
|
|
|
10125
10293
|
".text": "txt"
|
|
10126
10294
|
};
|
|
10127
10295
|
function extractOutline(filename, lines) {
|
|
10128
|
-
const ext =
|
|
10296
|
+
const ext = pathMod3.extname(filename).toLowerCase();
|
|
10129
10297
|
const lang = EXT_TO_LANG[ext];
|
|
10130
10298
|
if (!lang) return [];
|
|
10131
10299
|
switch (lang) {
|
|
@@ -10266,7 +10434,7 @@ function formatOutline(entries) {
|
|
|
10266
10434
|
|
|
10267
10435
|
// src/tools/fs/search.ts
|
|
10268
10436
|
import { promises as fs2 } from "fs";
|
|
10269
|
-
import * as
|
|
10437
|
+
import * as pathMod4 from "path";
|
|
10270
10438
|
|
|
10271
10439
|
// src/tools/fs/regex-runner.ts
|
|
10272
10440
|
import { Worker } from "worker_threads";
|
|
@@ -10299,7 +10467,7 @@ var RegexRunner = class {
|
|
|
10299
10467
|
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
10300
10468
|
}
|
|
10301
10469
|
testLines(text, source, flags, opts = {}) {
|
|
10302
|
-
return new Promise((
|
|
10470
|
+
return new Promise((resolve6, reject) => {
|
|
10303
10471
|
if (opts.signal?.aborted) {
|
|
10304
10472
|
reject(new Error("regex evaluation aborted"));
|
|
10305
10473
|
return;
|
|
@@ -10312,7 +10480,7 @@ var RegexRunner = class {
|
|
|
10312
10480
|
this.killWorker();
|
|
10313
10481
|
reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
|
|
10314
10482
|
}, timeoutMs);
|
|
10315
|
-
const entry = { resolve:
|
|
10483
|
+
const entry = { resolve: resolve6, reject, timer };
|
|
10316
10484
|
if (opts.signal) {
|
|
10317
10485
|
entry.signal = opts.signal;
|
|
10318
10486
|
entry.onAbort = () => {
|
|
@@ -10395,7 +10563,7 @@ function throwIfAborted(signal) {
|
|
|
10395
10563
|
throw new DOMException("search aborted by user", "AbortError");
|
|
10396
10564
|
}
|
|
10397
10565
|
function displayRel2(rootDir, full) {
|
|
10398
|
-
return
|
|
10566
|
+
return pathMod4.relative(rootDir, full).replaceAll("\\", "/");
|
|
10399
10567
|
}
|
|
10400
10568
|
async function searchFiles(ctx, startAbs, args) {
|
|
10401
10569
|
throwIfAborted(args.signal);
|
|
@@ -10419,7 +10587,7 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
10419
10587
|
}
|
|
10420
10588
|
for (const e of entries) {
|
|
10421
10589
|
throwIfAborted(args.signal);
|
|
10422
|
-
const full =
|
|
10590
|
+
const full = pathMod4.join(dir, e.name);
|
|
10423
10591
|
const lower = e.name.toLowerCase();
|
|
10424
10592
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
10425
10593
|
if (hit) {
|
|
@@ -10511,11 +10679,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10511
10679
|
throwIfTimedOut();
|
|
10512
10680
|
if (e.isDirectory()) {
|
|
10513
10681
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
10514
|
-
await walk2(
|
|
10682
|
+
await walk2(pathMod4.join(dir, e.name));
|
|
10515
10683
|
continue;
|
|
10516
10684
|
}
|
|
10517
10685
|
if (!e.isFile()) continue;
|
|
10518
|
-
const full =
|
|
10686
|
+
const full = pathMod4.join(dir, e.name);
|
|
10519
10687
|
if (ctx.nameMatch && !ctx.nameMatch(e.name, displayRel2(ctx.rootDir, full))) continue;
|
|
10520
10688
|
if (ctx.isBinaryByName(e.name)) continue;
|
|
10521
10689
|
let fh;
|
|
@@ -10596,8 +10764,8 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10596
10764
|
for (let i = realStart; i <= winEnd; i++) {
|
|
10597
10765
|
const line = lines[i];
|
|
10598
10766
|
const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
|
|
10599
|
-
const
|
|
10600
|
-
if (!pushLine(`${rel}:${i + 1}${
|
|
10767
|
+
const sep = hitSet.has(i) ? ":" : "-";
|
|
10768
|
+
if (!pushLine(`${rel}:${i + 1}${sep} ${display}`)) return;
|
|
10601
10769
|
}
|
|
10602
10770
|
prevWindowEnd = winEnd;
|
|
10603
10771
|
}
|
|
@@ -10633,7 +10801,7 @@ var SKIP_DIR_NAMES = new Set(
|
|
|
10633
10801
|
);
|
|
10634
10802
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
10635
10803
|
function displayRel3(rootDir, full) {
|
|
10636
|
-
return
|
|
10804
|
+
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
10637
10805
|
}
|
|
10638
10806
|
function looksLikeAbsoluteSystemPath(raw) {
|
|
10639
10807
|
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
@@ -10642,8 +10810,8 @@ function looksLikeAbsoluteSystemPath(raw) {
|
|
|
10642
10810
|
);
|
|
10643
10811
|
}
|
|
10644
10812
|
function pathIsUnder(child, parent) {
|
|
10645
|
-
const rel =
|
|
10646
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10813
|
+
const rel = pathMod5.relative(parent, child);
|
|
10814
|
+
return rel === "" || !rel.startsWith("..") && !pathMod5.isAbsolute(rel);
|
|
10647
10815
|
}
|
|
10648
10816
|
var GLOB_METACHARS = /[*?{[]/;
|
|
10649
10817
|
function compileNameFilter(filter) {
|
|
@@ -10675,11 +10843,11 @@ function formatBytes(n) {
|
|
|
10675
10843
|
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
10676
10844
|
}
|
|
10677
10845
|
function registerFilesystemTools(registry, opts) {
|
|
10678
|
-
const rootDir =
|
|
10846
|
+
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
10679
10847
|
const allowWriting = opts.allowWriting !== false;
|
|
10680
10848
|
const outlineThresholdBytes = opts.outlineThresholdBytes ?? DEFAULT_OUTLINE_THRESHOLD_BYTES;
|
|
10681
10849
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
10682
|
-
const normRoot =
|
|
10850
|
+
const normRoot = pathMod5.resolve(rootDir);
|
|
10683
10851
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
10684
10852
|
const shownSubdirMemory = /* @__PURE__ */ new Set();
|
|
10685
10853
|
function withSubdirMemory(absPath, body) {
|
|
@@ -10712,7 +10880,7 @@ ${body}`;
|
|
|
10712
10880
|
if (pathIsUnder(abs, dir)) return;
|
|
10713
10881
|
}
|
|
10714
10882
|
const stat2 = await safeLstat(abs);
|
|
10715
|
-
const allowPrefix = stat2?.isDirectory() ? abs :
|
|
10883
|
+
const allowPrefix = stat2?.isDirectory() ? abs : pathMod5.dirname(abs);
|
|
10716
10884
|
let pending = inflightGate.get(allowPrefix);
|
|
10717
10885
|
if (!pending) {
|
|
10718
10886
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
@@ -10740,7 +10908,7 @@ ${body}`;
|
|
|
10740
10908
|
throw new Error("path must be a non-empty string");
|
|
10741
10909
|
}
|
|
10742
10910
|
if (looksLikeAbsoluteSystemPath(raw)) {
|
|
10743
|
-
const abs =
|
|
10911
|
+
const abs = pathMod5.resolve(raw);
|
|
10744
10912
|
if (pathIsUnder(abs, normRoot)) return abs;
|
|
10745
10913
|
await ensureOutsideSandboxAllowed(abs, intent, toolName, ctx);
|
|
10746
10914
|
return abs;
|
|
@@ -10750,7 +10918,7 @@ ${body}`;
|
|
|
10750
10918
|
normalized = normalized.slice(1);
|
|
10751
10919
|
}
|
|
10752
10920
|
if (normalized.length === 0) normalized = ".";
|
|
10753
|
-
const resolved =
|
|
10921
|
+
const resolved = pathMod5.resolve(rootDir, normalized);
|
|
10754
10922
|
if (!pathIsUnder(resolved, normRoot)) {
|
|
10755
10923
|
throw new Error(
|
|
10756
10924
|
`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`
|
|
@@ -10812,7 +10980,8 @@ ${body}`;
|
|
|
10812
10980
|
if (looksBinary(raw)) {
|
|
10813
10981
|
return `[refused: ${rel} appears to be binary (${formatBytes(sizeBytes)}) \u2014 read_file returns text only. Use get_file_info for stat.]`;
|
|
10814
10982
|
}
|
|
10815
|
-
const text = raw
|
|
10983
|
+
const { text } = decodeFileBuffer(raw);
|
|
10984
|
+
ctx?.readTracker?.markRead(abs);
|
|
10816
10985
|
let lines = text.split(/\r?\n/);
|
|
10817
10986
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
10818
10987
|
const totalLines = lines.length;
|
|
@@ -10950,7 +11119,7 @@ ${slice.join("\n")}`);
|
|
|
10950
11119
|
lines.push(line);
|
|
10951
11120
|
emitted++;
|
|
10952
11121
|
if (e.isDirectory() && !skip) {
|
|
10953
|
-
await walk2(
|
|
11122
|
+
await walk2(pathMod5.join(dir, e.name), depth + 1);
|
|
10954
11123
|
}
|
|
10955
11124
|
}
|
|
10956
11125
|
};
|
|
@@ -11110,14 +11279,20 @@ ${slice.join("\n")}`);
|
|
|
11110
11279
|
},
|
|
11111
11280
|
fn: async (args, ctx) => {
|
|
11112
11281
|
const abs = await safePath(args.path, "write_file", ctx, "write");
|
|
11113
|
-
await fs3.mkdir(
|
|
11114
|
-
|
|
11282
|
+
await fs3.mkdir(pathMod5.dirname(abs), { recursive: true });
|
|
11283
|
+
let encoding = "utf8";
|
|
11284
|
+
try {
|
|
11285
|
+
encoding = decodeFileBuffer(await fs3.readFile(abs)).encoding;
|
|
11286
|
+
} catch {
|
|
11287
|
+
}
|
|
11288
|
+
await fs3.writeFile(abs, encodeFile(args.content, encoding));
|
|
11289
|
+
ctx?.readTracker?.markRead(abs);
|
|
11115
11290
|
return `wrote ${args.content.length} chars to ${displayRel3(rootDir, abs)}`;
|
|
11116
11291
|
}
|
|
11117
11292
|
});
|
|
11118
11293
|
registry.register({
|
|
11119
11294
|
name: "edit_file",
|
|
11120
|
-
description: "Apply a SEARCH/REPLACE edit to an existing file. `
|
|
11295
|
+
description: "Apply a SEARCH/REPLACE edit to an existing file. Call `read_file` on this path first this session \u2014 the tool refuses otherwise, since SEARCH must match on-disk bytes exactly. `search` is whitespace-sensitive plain text (no regex) and must be unique in the file; otherwise the edit is refused to avoid surprise rewrites.",
|
|
11121
11296
|
parameters: {
|
|
11122
11297
|
type: "object",
|
|
11123
11298
|
properties: {
|
|
@@ -11127,11 +11302,16 @@ ${slice.join("\n")}`);
|
|
|
11127
11302
|
},
|
|
11128
11303
|
required: ["path", "search", "replace"]
|
|
11129
11304
|
},
|
|
11130
|
-
fn: async (args, ctx) => applyEdit(
|
|
11305
|
+
fn: async (args, ctx) => applyEdit(
|
|
11306
|
+
rootDir,
|
|
11307
|
+
await safePath(args.path, "edit_file", ctx, "write"),
|
|
11308
|
+
args,
|
|
11309
|
+
ctx?.readTracker ? (abs) => ctx.readTracker.hasRead(abs) : void 0
|
|
11310
|
+
)
|
|
11131
11311
|
});
|
|
11132
11312
|
registry.register({
|
|
11133
11313
|
name: "multi_edit",
|
|
11134
|
-
description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in one call. Edits validate across the full batch before writing. Validation failures leave all files untouched; disk write failures trigger best-effort rollback of files that may have been modified. Per-file edits run in array order, so a later edit can match text inserted by an earlier one. Same per-edit rules as edit_file: `search` is exact text (whitespace sensitive, no regex) and must be unique in its target file at the moment that edit applies. Use this for renames spanning multiple files, cross-file refactors, or any batch where you'd otherwise loop edit_file.",
|
|
11314
|
+
description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in one call. Every target file must have been `read_file`'d this session \u2014 the tool refuses the whole batch otherwise. Edits validate across the full batch before writing. Validation failures leave all files untouched; disk write failures trigger best-effort rollback of files that may have been modified. Per-file edits run in array order, so a later edit can match text inserted by an earlier one. Same per-edit rules as edit_file: `search` is exact text (whitespace sensitive, no regex) and must be unique in its target file at the moment that edit applies. Use this for renames spanning multiple files, cross-file refactors, or any batch where you'd otherwise loop edit_file.",
|
|
11135
11315
|
parameters: {
|
|
11136
11316
|
type: "object",
|
|
11137
11317
|
properties: {
|
|
@@ -11165,7 +11345,11 @@ ${slice.join("\n")}`);
|
|
|
11165
11345
|
replace: e?.replace
|
|
11166
11346
|
}))
|
|
11167
11347
|
);
|
|
11168
|
-
return applyMultiEdit(
|
|
11348
|
+
return applyMultiEdit(
|
|
11349
|
+
rootDir,
|
|
11350
|
+
resolved,
|
|
11351
|
+
ctx?.readTracker ? (abs) => ctx.readTracker.hasRead(abs) : void 0
|
|
11352
|
+
);
|
|
11169
11353
|
}
|
|
11170
11354
|
});
|
|
11171
11355
|
registry.register({
|
|
@@ -11196,7 +11380,7 @@ ${slice.join("\n")}`);
|
|
|
11196
11380
|
fn: async (args, ctx) => {
|
|
11197
11381
|
const src = await safePath(args.source, "move_file", ctx, "write");
|
|
11198
11382
|
const dst = await safePath(args.destination, "move_file", ctx, "write");
|
|
11199
|
-
await fs3.mkdir(
|
|
11383
|
+
await fs3.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
11200
11384
|
await fs3.rename(src, dst);
|
|
11201
11385
|
return `moved ${displayRel3(rootDir, src)} \u2192 ${displayRel3(rootDir, dst)}`;
|
|
11202
11386
|
}
|
|
@@ -11264,7 +11448,7 @@ ${slice.join("\n")}`);
|
|
|
11264
11448
|
fn: async (args, ctx) => {
|
|
11265
11449
|
const src = await safePath(args.source, "copy_file", ctx);
|
|
11266
11450
|
const dst = await safePath(args.destination, "copy_file", ctx, "write");
|
|
11267
|
-
await fs3.mkdir(
|
|
11451
|
+
await fs3.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
11268
11452
|
await fs3.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
11269
11453
|
return `copied ${displayRel3(rootDir, src)} \u2192 ${displayRel3(rootDir, dst)}`;
|
|
11270
11454
|
}
|
|
@@ -11969,7 +12153,7 @@ function formatSubagentResult(r) {
|
|
|
11969
12153
|
});
|
|
11970
12154
|
}
|
|
11971
12155
|
function forkRegistryExcluding(parent, exclude) {
|
|
11972
|
-
const child = new ToolRegistry();
|
|
12156
|
+
const child = new ToolRegistry({ rateLimit: parent.rateLimitPolicy });
|
|
11973
12157
|
for (const spec of parent.specs()) {
|
|
11974
12158
|
const name = spec.function.name;
|
|
11975
12159
|
if (exclude.has(name)) continue;
|
|
@@ -11981,7 +12165,7 @@ function forkRegistryExcluding(parent, exclude) {
|
|
|
11981
12165
|
return child;
|
|
11982
12166
|
}
|
|
11983
12167
|
function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
11984
|
-
const child = new ToolRegistry();
|
|
12168
|
+
const child = new ToolRegistry({ rateLimit: parent.rateLimitPolicy });
|
|
11985
12169
|
for (const spec of parent.specs()) {
|
|
11986
12170
|
const name = spec.function.name;
|
|
11987
12171
|
if (!allow.has(name)) continue;
|
|
@@ -12008,7 +12192,7 @@ import {
|
|
|
12008
12192
|
writeFileSync,
|
|
12009
12193
|
writeSync
|
|
12010
12194
|
} from "fs";
|
|
12011
|
-
import { dirname as dirname3, resolve as
|
|
12195
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative6, resolve as resolve5 } from "path";
|
|
12012
12196
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
12013
12197
|
function parseEditBlocks(text) {
|
|
12014
12198
|
const out = [];
|
|
@@ -12025,10 +12209,30 @@ function parseEditBlocks(text) {
|
|
|
12025
12209
|
}
|
|
12026
12210
|
return out;
|
|
12027
12211
|
}
|
|
12212
|
+
function resolveEditPath(rootDir, rawPath) {
|
|
12213
|
+
const absRoot = resolve5(rootDir);
|
|
12214
|
+
if (/^[A-Za-z]:[\\/]/.test(rawPath) || looksLikeAbsoluteSystemPath2(rawPath)) {
|
|
12215
|
+
return resolve5(rawPath);
|
|
12216
|
+
}
|
|
12217
|
+
let rooted = rawPath;
|
|
12218
|
+
while (rooted.startsWith("/") || rooted.startsWith("\\")) {
|
|
12219
|
+
rooted = rooted.slice(1);
|
|
12220
|
+
}
|
|
12221
|
+
return resolve5(absRoot, rooted || ".");
|
|
12222
|
+
}
|
|
12223
|
+
function looksLikeAbsoluteSystemPath2(rawPath) {
|
|
12224
|
+
return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
|
|
12225
|
+
rawPath
|
|
12226
|
+
);
|
|
12227
|
+
}
|
|
12228
|
+
function pathIsUnder2(child, parent) {
|
|
12229
|
+
const rel = relative6(parent, child);
|
|
12230
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
12231
|
+
}
|
|
12028
12232
|
function applyEditBlock(block, rootDir) {
|
|
12029
|
-
const absRoot =
|
|
12030
|
-
const absTarget =
|
|
12031
|
-
if (absTarget
|
|
12233
|
+
const absRoot = resolve5(rootDir);
|
|
12234
|
+
const absTarget = resolveEditPath(rootDir, block.path);
|
|
12235
|
+
if (!pathIsUnder2(absTarget, absRoot)) {
|
|
12032
12236
|
return {
|
|
12033
12237
|
path: block.path,
|
|
12034
12238
|
status: "path-escape",
|
|
@@ -12081,7 +12285,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
12081
12285
|
if (n <= 0) break;
|
|
12082
12286
|
readBytes += n;
|
|
12083
12287
|
}
|
|
12084
|
-
const content = inBuf.
|
|
12288
|
+
const { text: content, encoding } = decodeFileBuffer(inBuf.subarray(0, readBytes));
|
|
12085
12289
|
const le = lineEndingOf(content);
|
|
12086
12290
|
const adaptedSearch = block.search.replace(/\r?\n/g, le);
|
|
12087
12291
|
const adaptedReplace = block.replace.replace(/\r?\n/g, le);
|
|
@@ -12102,7 +12306,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
12102
12306
|
};
|
|
12103
12307
|
}
|
|
12104
12308
|
const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
|
|
12105
|
-
const outBuf =
|
|
12309
|
+
const outBuf = encodeFile(replaced, encoding);
|
|
12106
12310
|
ftruncateSync(fd, outBuf.length);
|
|
12107
12311
|
let written = 0;
|
|
12108
12312
|
while (written < outBuf.length) {
|
|
@@ -12122,11 +12326,11 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
12122
12326
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
12123
12327
|
}
|
|
12124
12328
|
function toWholeFileEditBlock(path, content, rootDir) {
|
|
12125
|
-
const abs =
|
|
12329
|
+
const abs = resolveEditPath(rootDir, path);
|
|
12126
12330
|
let search = "";
|
|
12127
12331
|
if (existsSync3(abs)) {
|
|
12128
12332
|
try {
|
|
12129
|
-
search = readFileSync3(abs
|
|
12333
|
+
search = decodeFileBuffer(readFileSync3(abs)).text;
|
|
12130
12334
|
} catch {
|
|
12131
12335
|
search = "";
|
|
12132
12336
|
}
|
|
@@ -12134,19 +12338,21 @@ function toWholeFileEditBlock(path, content, rootDir) {
|
|
|
12134
12338
|
return { path, search, replace: content, offset: 0 };
|
|
12135
12339
|
}
|
|
12136
12340
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
12137
|
-
const absRoot =
|
|
12341
|
+
const absRoot = resolve5(rootDir);
|
|
12138
12342
|
const seen = /* @__PURE__ */ new Set();
|
|
12139
12343
|
const snapshots = [];
|
|
12140
12344
|
for (const b of blocks) {
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12345
|
+
const abs = resolveEditPath(rootDir, b.path);
|
|
12346
|
+
if (!pathIsUnder2(abs, absRoot)) continue;
|
|
12347
|
+
if (seen.has(abs)) continue;
|
|
12348
|
+
seen.add(abs);
|
|
12144
12349
|
if (!existsSync3(abs)) {
|
|
12145
12350
|
snapshots.push({ path: b.path, prevContent: null });
|
|
12146
12351
|
continue;
|
|
12147
12352
|
}
|
|
12148
12353
|
try {
|
|
12149
|
-
|
|
12354
|
+
const { text, encoding } = decodeFileBuffer(readFileSync3(abs));
|
|
12355
|
+
snapshots.push({ path: b.path, prevContent: text, prevEncoding: encoding });
|
|
12150
12356
|
} catch {
|
|
12151
12357
|
snapshots.push({ path: b.path, prevContent: null });
|
|
12152
12358
|
}
|
|
@@ -12154,10 +12360,10 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
12154
12360
|
return snapshots;
|
|
12155
12361
|
}
|
|
12156
12362
|
function restoreSnapshots(snapshots, rootDir) {
|
|
12157
|
-
const absRoot =
|
|
12363
|
+
const absRoot = resolve5(rootDir);
|
|
12158
12364
|
return snapshots.map((snap) => {
|
|
12159
|
-
const abs =
|
|
12160
|
-
if (abs
|
|
12365
|
+
const abs = resolveEditPath(rootDir, snap.path);
|
|
12366
|
+
if (!pathIsUnder2(abs, absRoot)) {
|
|
12161
12367
|
return {
|
|
12162
12368
|
path: snap.path,
|
|
12163
12369
|
status: "path-escape",
|
|
@@ -12173,7 +12379,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
12173
12379
|
message: "removed (the edit had created it)"
|
|
12174
12380
|
};
|
|
12175
12381
|
}
|
|
12176
|
-
writeFileSync(abs, snap.prevContent, "utf8");
|
|
12382
|
+
writeFileSync(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
|
|
12177
12383
|
return {
|
|
12178
12384
|
path: snap.path,
|
|
12179
12385
|
status: "applied",
|
|
@@ -12184,9 +12390,6 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
12184
12390
|
}
|
|
12185
12391
|
});
|
|
12186
12392
|
}
|
|
12187
|
-
function sep() {
|
|
12188
|
-
return process.platform === "win32" ? "\\" : "/";
|
|
12189
|
-
}
|
|
12190
12393
|
function lineEndingOf(text) {
|
|
12191
12394
|
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
12192
12395
|
}
|
|
@@ -12228,4 +12431,4 @@ export {
|
|
|
12228
12431
|
he/he.js:
|
|
12229
12432
|
(*! https://mths.be/he v1.2.0 by @mathias | MIT license *)
|
|
12230
12433
|
*/
|
|
12231
|
-
//# sourceMappingURL=chunk-
|
|
12434
|
+
//# sourceMappingURL=chunk-MQWO32ZD.js.map
|