reasonix 0.48.1 → 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/dashboard/dist/app.js +123 -16
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{acp-QJGGHQLA.js → acp-WFQIC6SO.js} +18 -18
- package/dist/cli/chat-D32JGNVH.js +51 -0
- package/dist/cli/{chunk-TIJ4ZHD6.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-H2F4LDNH.js → chunk-7AST3QQ3.js} +2 -2
- package/dist/cli/{chunk-XMR2VCGT.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-WKOXKCF3.js → chunk-ASOLXV67.js} +3 -3
- package/dist/cli/{chunk-43ROGEX2.js → chunk-AWEULQG6.js} +10 -9
- package/dist/cli/{chunk-43ROGEX2.js.map → chunk-AWEULQG6.js.map} +1 -1
- package/dist/cli/{chunk-5AW6NIHU.js → chunk-DFX5ZH5L.js} +2 -2
- package/dist/cli/{chunk-4E2BHJU4.js → chunk-GNS7BAT2.js} +2 -2
- package/dist/cli/{chunk-5U5LMMFF.js → chunk-J2IHQGPQ.js} +12 -6
- package/dist/cli/chunk-J2IHQGPQ.js.map +1 -0
- package/dist/cli/{chunk-O3AGYTG2.js → chunk-JGTX4RRQ.js} +3 -3
- package/dist/cli/{chunk-SLAFMXAZ.js → chunk-JNTMOX7G.js} +2 -2
- package/dist/cli/{chunk-RCLS63KE.js → chunk-MGTBP7GG.js} +2 -2
- package/dist/cli/{chunk-JFBGSWQB.js → chunk-MQWO32ZD.js} +323 -159
- package/dist/cli/chunk-MQWO32ZD.js.map +1 -0
- package/dist/cli/{chunk-FD7SNDWW.js → chunk-O5LIHAMP.js} +8 -4
- package/dist/cli/chunk-O5LIHAMP.js.map +1 -0
- package/dist/cli/{chunk-PJIQIYTV.js → chunk-PB3MAFEI.js} +3 -3
- package/dist/cli/{chunk-NQZ5U37J.js → chunk-PEMG6CUB.js} +2 -2
- package/dist/cli/{chunk-KH5JIJJD.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-DABAOQSV.js → chunk-QF32ROX2.js} +1260 -1754
- package/dist/cli/chunk-QF32ROX2.js.map +1 -0
- package/dist/cli/{chunk-IKSYVBBZ.js → chunk-QX5TWXRZ.js} +2 -2
- package/dist/cli/{chunk-R7U44O3Y.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-EO6RPTJG.js → chunk-U5XQDCK7.js} +5 -5
- package/dist/cli/{chunk-SWUMD2LX.js → chunk-W46ZMNKO.js} +3 -3
- package/dist/cli/{chunk-FPME5QOO.js → chunk-WMTMMSXU.js} +166 -5
- package/dist/cli/chunk-WMTMMSXU.js.map +1 -0
- package/dist/cli/{chunk-PIC5HJRD.js → chunk-YEF7C4XI.js} +154 -86
- package/dist/cli/chunk-YEF7C4XI.js.map +1 -0
- package/dist/cli/{chunk-3FULTFRB.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-OKA5YPOH.js → code-R4IHI7SR.js} +30 -30
- package/dist/cli/{commands-3U6PUVSS.js → commands-DRHFCYMO.js} +4 -4
- package/dist/cli/{commit-HFB7SRBU.js → commit-AG5KB4YP.js} +3 -3
- package/dist/cli/{desktop-G7UMW3CJ.js → desktop-JGL6GORA.js} +19 -19
- package/dist/cli/{diff-PGXW4YZD.js → diff-4Z7ETWZO.js} +9 -9
- package/dist/cli/{doctor-WWJFLVCB.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-Y3VKIK3T.js → mcp-LZO4HXFA.js} +34 -23
- package/dist/cli/mcp-LZO4HXFA.js.map +1 -0
- package/dist/cli/{mcp-browse-4IN2QIFR.js → mcp-browse-C3GXVMYZ.js} +3 -3
- package/dist/cli/{mcp-inspect-BUXFXDHB.js → mcp-inspect-ZMYUNFDS.js} +2 -2
- package/dist/cli/{prompt-US57R6BA.js → prompt-MC3U5KRP.js} +5 -5
- 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-EQJMZRB3.js → replay-4TP7ZUMZ.js} +10 -10
- package/dist/cli/{run-KVEI66TR.js → run-6MXQYBOE.js} +16 -15
- package/dist/cli/run-6MXQYBOE.js.map +1 -0
- package/dist/cli/{server-D6C4GHWN.js → server-Z3IMJNNI.js} +63 -12
- package/dist/cli/server-Z3IMJNNI.js.map +1 -0
- package/dist/cli/{sessions-TGVS2RQZ.js → sessions-NXQ5SAV7.js} +18 -18
- package/dist/cli/sessions-NXQ5SAV7.js.map +1 -0
- package/dist/cli/{setup-WLKX6GGG.js → setup-LHZELI6I.js} +6 -6
- package/dist/cli/{stats-TCD7Q6MB.js → stats-SUIJ3QWY.js} +6 -6
- package/dist/cli/{version-BCWWS2OU.js → version-BIFONEUB.js} +13 -13
- package/dist/index.d.ts +63 -15
- package/dist/index.js +855 -360
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/cli/chat-ZXF227MP.js +0 -51
- package/dist/cli/chunk-5U5LMMFF.js.map +0 -1
- package/dist/cli/chunk-6FRNXWDZ.js +0 -2265
- package/dist/cli/chunk-6FRNXWDZ.js.map +0 -1
- package/dist/cli/chunk-B5CZL2SE.js.map +0 -1
- package/dist/cli/chunk-DABAOQSV.js.map +0 -1
- package/dist/cli/chunk-FD7SNDWW.js.map +0 -1
- package/dist/cli/chunk-FPME5QOO.js.map +0 -1
- package/dist/cli/chunk-JFBGSWQB.js.map +0 -1
- package/dist/cli/chunk-PIC5HJRD.js.map +0 -1
- package/dist/cli/chunk-TIJ4ZHD6.js.map +0 -1
- package/dist/cli/chunk-TKVXTQ3T.js.map +0 -1
- package/dist/cli/mcp-Y3VKIK3T.js.map +0 -1
- package/dist/cli/prune-sessions-SEWX7GP6.js.map +0 -1
- package/dist/cli/run-KVEI66TR.js.map +0 -1
- package/dist/cli/server-D6C4GHWN.js.map +0 -1
- package/dist/cli/sessions-TGVS2RQZ.js.map +0 -1
- /package/dist/cli/{acp-QJGGHQLA.js.map → acp-WFQIC6SO.js.map} +0 -0
- /package/dist/cli/{chat-ZXF227MP.js.map → chat-D32JGNVH.js.map} +0 -0
- /package/dist/cli/{chunk-H2F4LDNH.js.map → chunk-7AST3QQ3.js.map} +0 -0
- /package/dist/cli/{chunk-XMR2VCGT.js.map → chunk-7JTKBJ2G.js.map} +0 -0
- /package/dist/cli/{chunk-WKOXKCF3.js.map → chunk-ASOLXV67.js.map} +0 -0
- /package/dist/cli/{chunk-5AW6NIHU.js.map → chunk-DFX5ZH5L.js.map} +0 -0
- /package/dist/cli/{chunk-4E2BHJU4.js.map → chunk-GNS7BAT2.js.map} +0 -0
- /package/dist/cli/{chunk-O3AGYTG2.js.map → chunk-JGTX4RRQ.js.map} +0 -0
- /package/dist/cli/{chunk-SLAFMXAZ.js.map → chunk-JNTMOX7G.js.map} +0 -0
- /package/dist/cli/{chunk-RCLS63KE.js.map → chunk-MGTBP7GG.js.map} +0 -0
- /package/dist/cli/{chunk-PJIQIYTV.js.map → chunk-PB3MAFEI.js.map} +0 -0
- /package/dist/cli/{chunk-NQZ5U37J.js.map → chunk-PEMG6CUB.js.map} +0 -0
- /package/dist/cli/{chunk-KH5JIJJD.js.map → chunk-PXBQ6IZ7.js.map} +0 -0
- /package/dist/cli/{chunk-IKSYVBBZ.js.map → chunk-QX5TWXRZ.js.map} +0 -0
- /package/dist/cli/{chunk-R7U44O3Y.js.map → chunk-TAIKVL35.js.map} +0 -0
- /package/dist/cli/{chunk-EO6RPTJG.js.map → chunk-U5XQDCK7.js.map} +0 -0
- /package/dist/cli/{chunk-SWUMD2LX.js.map → chunk-W46ZMNKO.js.map} +0 -0
- /package/dist/cli/{chunk-3FULTFRB.js.map → chunk-ZAEJWKXB.js.map} +0 -0
- /package/dist/cli/{code-OKA5YPOH.js.map → code-R4IHI7SR.js.map} +0 -0
- /package/dist/cli/{commands-3U6PUVSS.js.map → commands-DRHFCYMO.js.map} +0 -0
- /package/dist/cli/{commit-HFB7SRBU.js.map → commit-AG5KB4YP.js.map} +0 -0
- /package/dist/cli/{desktop-G7UMW3CJ.js.map → desktop-JGL6GORA.js.map} +0 -0
- /package/dist/cli/{diff-PGXW4YZD.js.map → diff-4Z7ETWZO.js.map} +0 -0
- /package/dist/cli/{doctor-WWJFLVCB.js.map → doctor-VA3RHQLB.js.map} +0 -0
- /package/dist/cli/{mcp-browse-4IN2QIFR.js.map → mcp-browse-C3GXVMYZ.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-BUXFXDHB.js.map → mcp-inspect-ZMYUNFDS.js.map} +0 -0
- /package/dist/cli/{prompt-US57R6BA.js.map → prompt-MC3U5KRP.js.map} +0 -0
- /package/dist/cli/{replay-EQJMZRB3.js.map → replay-4TP7ZUMZ.js.map} +0 -0
- /package/dist/cli/{setup-WLKX6GGG.js.map → setup-LHZELI6I.js.map} +0 -0
- /package/dist/cli/{stats-TCD7Q6MB.js.map → stats-SUIJ3QWY.js.map} +0 -0
- /package/dist/cli/{version-BCWWS2OU.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,
|
|
@@ -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,6 +6776,10 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
|
6757
6776
|
}
|
|
6758
6777
|
|
|
6759
6778
|
// src/context-manager.ts
|
|
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
|
+
}
|
|
6760
6783
|
var HISTORY_FOLD_THRESHOLD = 0.75;
|
|
6761
6784
|
var HISTORY_FOLD_TAIL_FRACTION = 0.2;
|
|
6762
6785
|
var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.78;
|
|
@@ -6771,20 +6794,25 @@ var HISTORY_FOLD_SUMMARY_TIMEOUT_MS = 15e3;
|
|
|
6771
6794
|
var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
|
|
6772
6795
|
var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
|
|
6773
6796
|
var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
|
|
6774
|
-
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) {
|
|
6775
6804
|
const pinned = /* @__PURE__ */ new Map();
|
|
6776
|
-
const
|
|
6777
|
-
if (typeof msg.content !== "string")
|
|
6778
|
-
|
|
6779
|
-
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];
|
|
6780
6811
|
pinned.delete(name);
|
|
6781
6812
|
pinned.set(name, full);
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
return hit ? { ...msg, content: next } : msg;
|
|
6786
|
-
});
|
|
6787
|
-
return { stubbedHead, pinnedBodies: [...pinned.values()] };
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
return { names: [...pinned.keys()], bodies: [...pinned.values()] };
|
|
6788
6816
|
}
|
|
6789
6817
|
var ContextManager = class {
|
|
6790
6818
|
constructor(deps) {
|
|
@@ -6880,16 +6908,22 @@ var ContextManager = class {
|
|
|
6880
6908
|
const tail = all.slice(boundary);
|
|
6881
6909
|
const headTokens = totalTokens - cumTokens;
|
|
6882
6910
|
if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
|
|
6883
|
-
const {
|
|
6884
|
-
const summary = await this.summarizeForFold(
|
|
6911
|
+
const { names: pinnedNames, bodies: pinnedBodies } = collectPinnedSkills(head);
|
|
6912
|
+
const summary = await this.summarizeForFold(head, pinnedNames);
|
|
6885
6913
|
if (!summary.content) return noop;
|
|
6886
6914
|
const memoTail = pinnedBodies.length > 0 ? `
|
|
6887
6915
|
|
|
6888
6916
|
${SKILL_PIN_MEMO_HEADER}
|
|
6889
6917
|
|
|
6890
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}` : "";
|
|
6891
6925
|
const summaryMsg = buildAssistantMessage(
|
|
6892
|
-
HISTORY_FOLD_MARKER + summary.content + memoTail,
|
|
6926
|
+
HISTORY_FOLD_MARKER + summary.content + memoTail + constraintTail,
|
|
6893
6927
|
[],
|
|
6894
6928
|
model,
|
|
6895
6929
|
summary.reasoningContent
|
|
@@ -6897,6 +6931,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6897
6931
|
const replacement = [summaryMsg, ...tail];
|
|
6898
6932
|
this.deps.log.compactInPlace(replacement);
|
|
6899
6933
|
this.persistRewrite(replacement);
|
|
6934
|
+
this.deps.onLogRewrite?.();
|
|
6900
6935
|
return {
|
|
6901
6936
|
folded: true,
|
|
6902
6937
|
beforeMessages: all.length,
|
|
@@ -6948,6 +6983,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6948
6983
|
if (replacement.length === all.length) return noop;
|
|
6949
6984
|
this.deps.log.compactInPlace(replacement);
|
|
6950
6985
|
this.persistRewrite(replacement);
|
|
6986
|
+
this.deps.onLogRewrite?.();
|
|
6951
6987
|
return {
|
|
6952
6988
|
folded: true,
|
|
6953
6989
|
beforeMessages: all.length,
|
|
@@ -6966,17 +7002,18 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6966
7002
|
this.persistRewrite([...kept]);
|
|
6967
7003
|
return true;
|
|
6968
7004
|
}
|
|
6969
|
-
async summarizeForFold(messagesToSummarize) {
|
|
7005
|
+
async summarizeForFold(messagesToSummarize, pinnedSkillNames) {
|
|
6970
7006
|
const summaryModel = "deepseek-v4-flash";
|
|
6971
|
-
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.";
|
|
6972
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);
|
|
6973
7012
|
const messages = [
|
|
6974
|
-
{ role: "system", content:
|
|
7013
|
+
{ role: "system", content: agentSystem },
|
|
7014
|
+
...fewShots.map((m) => ({ ...m })),
|
|
6975
7015
|
...healed,
|
|
6976
|
-
{
|
|
6977
|
-
role: "user",
|
|
6978
|
-
content: "Summarize the conversation above as plain prose. This summary replaces the original turns to free context \u2014 make it self-contained."
|
|
6979
|
-
}
|
|
7016
|
+
{ role: "user", content: instruction }
|
|
6980
7017
|
];
|
|
6981
7018
|
const turnSignal = this.deps.getAbortSignal();
|
|
6982
7019
|
const foldCtrl = new AbortController();
|
|
@@ -7006,9 +7043,9 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
7006
7043
|
this.deps.client.chat({
|
|
7007
7044
|
model: summaryModel,
|
|
7008
7045
|
messages,
|
|
7046
|
+
tools: tools.length ? tools : void 0,
|
|
7009
7047
|
signal: foldCtrl.signal,
|
|
7010
|
-
thinking:
|
|
7011
|
-
reasoningEffort: "high"
|
|
7048
|
+
thinking: "disabled"
|
|
7012
7049
|
}),
|
|
7013
7050
|
abortPromise,
|
|
7014
7051
|
timeoutPromise
|
|
@@ -7093,6 +7130,7 @@ function formatLoopError(err, probe) {
|
|
|
7093
7130
|
if (status === "402") return t("errors.balance402", { inner });
|
|
7094
7131
|
if (status === "422") return t("errors.badparam422", { inner });
|
|
7095
7132
|
if (status === "400") return t("errors.badrequest400", { inner });
|
|
7133
|
+
if (status === "429") return t("errors.concurrency429", { inner });
|
|
7096
7134
|
if (is5xxStatus(status)) return formatDeepSeek5xx(status, probe);
|
|
7097
7135
|
return msg;
|
|
7098
7136
|
}
|
|
@@ -7675,8 +7713,34 @@ function signature(call) {
|
|
|
7675
7713
|
return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
|
|
7676
7714
|
}
|
|
7677
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
|
+
|
|
7678
7738
|
// src/loop.ts
|
|
7679
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
|
+
}
|
|
7680
7744
|
var CacheFirstLoop = class {
|
|
7681
7745
|
client;
|
|
7682
7746
|
prefix;
|
|
@@ -7685,6 +7749,8 @@ var CacheFirstLoop = class {
|
|
|
7685
7749
|
scratch = new VolatileScratch();
|
|
7686
7750
|
stats = new SessionStats();
|
|
7687
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();
|
|
7688
7754
|
// Mutable via configure() — slash commands in the TUI / library callers tweak
|
|
7689
7755
|
// these mid-session so users don't have to restart.
|
|
7690
7756
|
model;
|
|
@@ -7708,15 +7774,19 @@ var CacheFirstLoop = class {
|
|
|
7708
7774
|
_turnAbort = new AbortController();
|
|
7709
7775
|
/** Authoritative running-id set — UI cards consult this instead of trusting end-event delivery. Insert at dispatch entry, delete in finally. */
|
|
7710
7776
|
_inflight = new InflightSet();
|
|
7711
|
-
/** Typeahead steer
|
|
7712
|
-
|
|
7777
|
+
/** Typeahead steer messages set by the UI; step() consumes one at each iter boundary. */
|
|
7778
|
+
_steerQueue = [];
|
|
7713
7779
|
/** Set true when a steer was consumed this turn; cleared on next step() entry. */
|
|
7714
7780
|
_steerConsumed = false;
|
|
7715
7781
|
/** UI calls this to inject a mid-turn steer message without aborting the current turn.
|
|
7716
|
-
* New text resets steerConsumed
|
|
7782
|
+
* New text resets steerConsumed because a fresh steer is queued. */
|
|
7717
7783
|
steer(text) {
|
|
7718
|
-
|
|
7719
|
-
|
|
7784
|
+
if (text === null) {
|
|
7785
|
+
this._steerQueue.length = 0;
|
|
7786
|
+
return;
|
|
7787
|
+
}
|
|
7788
|
+
this._steerQueue.push(text);
|
|
7789
|
+
this._steerConsumed = false;
|
|
7720
7790
|
}
|
|
7721
7791
|
/** True when a steer was consumed this turn (UI gate to avoid double-submit). */
|
|
7722
7792
|
get steerConsumed() {
|
|
@@ -7802,7 +7872,11 @@ var CacheFirstLoop = class {
|
|
|
7802
7872
|
stats: this.stats,
|
|
7803
7873
|
sessionName: this.sessionName,
|
|
7804
7874
|
getAbortSignal: () => this._turnAbort.signal,
|
|
7805
|
-
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()
|
|
7806
7880
|
});
|
|
7807
7881
|
}
|
|
7808
7882
|
/** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
|
|
@@ -7973,7 +8047,8 @@ ${reason}`
|
|
|
7973
8047
|
const result = await this.tools.dispatch(name, args, {
|
|
7974
8048
|
signal,
|
|
7975
8049
|
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
7976
|
-
confirmationGate: this.confirmationGate
|
|
8050
|
+
confirmationGate: this.confirmationGate,
|
|
8051
|
+
readTracker: this.readTracker
|
|
7977
8052
|
});
|
|
7978
8053
|
const postReport = await runHooks({
|
|
7979
8054
|
hooks: this.hooks,
|
|
@@ -8001,11 +8076,9 @@ ${reason}`
|
|
|
8001
8076
|
return generated;
|
|
8002
8077
|
}
|
|
8003
8078
|
_inflightCounter = 0;
|
|
8004
|
-
buildMessages(
|
|
8079
|
+
buildMessages() {
|
|
8005
8080
|
const healedMessages = this.healActiveLogBeforeSend();
|
|
8006
|
-
|
|
8007
|
-
if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
|
|
8008
|
-
return msgs;
|
|
8081
|
+
return [...this.prefix.toMessages(), ...healedMessages];
|
|
8009
8082
|
}
|
|
8010
8083
|
healActiveLogBeforeSend() {
|
|
8011
8084
|
const current = this.log.toMessages();
|
|
@@ -8086,6 +8159,7 @@ ${reason}`
|
|
|
8086
8159
|
cap: this.budgetUsd.toFixed(2)
|
|
8087
8160
|
})
|
|
8088
8161
|
};
|
|
8162
|
+
this._steerQueue.length = 0;
|
|
8089
8163
|
return;
|
|
8090
8164
|
}
|
|
8091
8165
|
if (!this._budgetWarned && spent >= this.budgetUsd * 0.8) {
|
|
@@ -8124,8 +8198,8 @@ ${reason}`
|
|
|
8124
8198
|
};
|
|
8125
8199
|
}
|
|
8126
8200
|
this.appendAndPersist({ role: "user", content: userInput });
|
|
8127
|
-
let pendingUser = null;
|
|
8128
8201
|
const toolSpecs = this.prefix.tools();
|
|
8202
|
+
let rateLimitWarningShown = false;
|
|
8129
8203
|
for (let iter = 0; ; iter++) {
|
|
8130
8204
|
if (signal.aborted) {
|
|
8131
8205
|
try {
|
|
@@ -8146,6 +8220,7 @@ ${reason}`
|
|
|
8146
8220
|
} finally {
|
|
8147
8221
|
this._turnAbort = new AbortController();
|
|
8148
8222
|
}
|
|
8223
|
+
this._steerQueue.length = 0;
|
|
8149
8224
|
return;
|
|
8150
8225
|
}
|
|
8151
8226
|
if (iter > 0) {
|
|
@@ -8155,14 +8230,15 @@ ${reason}`
|
|
|
8155
8230
|
content: t("loop.toolUploadStatus")
|
|
8156
8231
|
};
|
|
8157
8232
|
}
|
|
8158
|
-
let messages = this.buildMessages(
|
|
8159
|
-
if (this.
|
|
8160
|
-
const steer = this.
|
|
8161
|
-
this.
|
|
8162
|
-
this.
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
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();
|
|
8166
8242
|
yield {
|
|
8167
8243
|
turn: this._turn,
|
|
8168
8244
|
role: "steer",
|
|
@@ -8179,10 +8255,10 @@ ${reason}`
|
|
|
8179
8255
|
content: t("loop.preflightTruncateStatus")
|
|
8180
8256
|
};
|
|
8181
8257
|
const result = this.context.mechanicalTruncate(this.model, {
|
|
8182
|
-
allowEmpty:
|
|
8258
|
+
allowEmpty: false
|
|
8183
8259
|
});
|
|
8184
8260
|
if (result.folded) {
|
|
8185
|
-
messages = this.buildMessages(
|
|
8261
|
+
messages = this.buildMessages();
|
|
8186
8262
|
const after = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
8187
8263
|
const stillFull = after.needsAction;
|
|
8188
8264
|
yield {
|
|
@@ -8328,6 +8404,7 @@ ${reason}`
|
|
|
8328
8404
|
} finally {
|
|
8329
8405
|
this._turnAbort = new AbortController();
|
|
8330
8406
|
}
|
|
8407
|
+
this._steerQueue.length = 0;
|
|
8331
8408
|
return;
|
|
8332
8409
|
}
|
|
8333
8410
|
const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
|
|
@@ -8337,6 +8414,7 @@ ${reason}`
|
|
|
8337
8414
|
content: "",
|
|
8338
8415
|
error: formatLoopError(err, probe)
|
|
8339
8416
|
};
|
|
8417
|
+
this._steerQueue.length = 0;
|
|
8340
8418
|
return;
|
|
8341
8419
|
}
|
|
8342
8420
|
if (this.autoEscalate && this.modelForCurrentCall() !== ESCALATION_MODEL && isEscalationRequest(assistantContent)) {
|
|
@@ -8417,11 +8495,16 @@ ${reason}`
|
|
|
8417
8495
|
};
|
|
8418
8496
|
}
|
|
8419
8497
|
if (repairedCalls.length === 0) {
|
|
8498
|
+
if (this._steerQueue.length > 0) {
|
|
8499
|
+
continue;
|
|
8500
|
+
}
|
|
8420
8501
|
if (allSuppressed) {
|
|
8421
8502
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "stuck" });
|
|
8503
|
+
this._steerQueue.length = 0;
|
|
8422
8504
|
return;
|
|
8423
8505
|
}
|
|
8424
8506
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
8507
|
+
this._steerQueue.length = 0;
|
|
8425
8508
|
return;
|
|
8426
8509
|
}
|
|
8427
8510
|
const decision = this.context.decideAfterUsage(usage, this.model, this._foldedThisTurn);
|
|
@@ -8467,6 +8550,7 @@ ${reason}`
|
|
|
8467
8550
|
};
|
|
8468
8551
|
this.context.trimTrailingToolCalls();
|
|
8469
8552
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "context-guard" });
|
|
8553
|
+
this._steerQueue.length = 0;
|
|
8470
8554
|
return;
|
|
8471
8555
|
}
|
|
8472
8556
|
const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
|
|
@@ -8514,6 +8598,15 @@ ${reason}`
|
|
|
8514
8598
|
}
|
|
8515
8599
|
for (const w of preWarnings) yield w;
|
|
8516
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
|
+
}
|
|
8517
8610
|
this.appendAndPersist({
|
|
8518
8611
|
role: "tool",
|
|
8519
8612
|
tool_call_id: call.id ?? "",
|
|
@@ -8536,7 +8629,7 @@ ${reason}`
|
|
|
8536
8629
|
return {
|
|
8537
8630
|
client: this.client,
|
|
8538
8631
|
signal: this._turnAbort.signal,
|
|
8539
|
-
buildMessages: () => this.buildMessages(
|
|
8632
|
+
buildMessages: () => this.buildMessages(),
|
|
8540
8633
|
appendAndPersist: (m) => this.appendAndPersist(m),
|
|
8541
8634
|
recordStats: (model, usage) => this.stats.record(this._turn, model, usage),
|
|
8542
8635
|
turn: this._turn
|
|
@@ -8561,7 +8654,7 @@ function parsePositiveIntEnv(raw) {
|
|
|
8561
8654
|
// src/at-mentions.ts
|
|
8562
8655
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
8563
8656
|
import { readdir, stat } from "fs/promises";
|
|
8564
|
-
import { isAbsolute, join, relative, resolve } from "path";
|
|
8657
|
+
import { isAbsolute, join, relative, resolve as resolve2 } from "path";
|
|
8565
8658
|
|
|
8566
8659
|
// src/at-mentions-url.ts
|
|
8567
8660
|
var AT_URL_PATTERN = /(?<=^|\s)@(https?:\/\/\S+)/g;
|
|
@@ -8692,7 +8785,7 @@ function listFilesSync(root, opts = {}) {
|
|
|
8692
8785
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
8693
8786
|
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
8694
8787
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8695
|
-
const rootAbs =
|
|
8788
|
+
const rootAbs = resolve2(root);
|
|
8696
8789
|
const respectGi = opts.respectGitignore !== false;
|
|
8697
8790
|
const out = [];
|
|
8698
8791
|
const walk2 = (dirAbs, dirRel, layers) => {
|
|
@@ -8756,7 +8849,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
8756
8849
|
async function walkFilesStream(root, opts) {
|
|
8757
8850
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8758
8851
|
const respectGi = opts.respectGitignore !== false;
|
|
8759
|
-
const rootAbs =
|
|
8852
|
+
const rootAbs = resolve2(root);
|
|
8760
8853
|
const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
|
|
8761
8854
|
let scanned = 0;
|
|
8762
8855
|
let halted = false;
|
|
@@ -8834,8 +8927,8 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
|
|
|
8834
8927
|
async function listDirectory(root, relDir, opts = {}) {
|
|
8835
8928
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8836
8929
|
const respectGi = opts.respectGitignore !== false;
|
|
8837
|
-
const rootAbs =
|
|
8838
|
-
const dirAbs =
|
|
8930
|
+
const rootAbs = resolve2(root);
|
|
8931
|
+
const dirAbs = resolve2(rootAbs, relDir);
|
|
8839
8932
|
const rel = relative(rootAbs, dirAbs);
|
|
8840
8933
|
if (rel.startsWith("..") || isAbsolute(rel)) return [];
|
|
8841
8934
|
const layers = [];
|
|
@@ -9000,7 +9093,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
9000
9093
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
9001
9094
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
9002
9095
|
const fs4 = opts.fs ?? defaultFs;
|
|
9003
|
-
const root =
|
|
9096
|
+
const root = resolve2(rootDir);
|
|
9004
9097
|
const seen = /* @__PURE__ */ new Map();
|
|
9005
9098
|
const expansions = [];
|
|
9006
9099
|
const dirListings = /* @__PURE__ */ new Map();
|
|
@@ -9047,7 +9140,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs4, dirListings
|
|
|
9047
9140
|
if (isAbsolute(rawPath)) {
|
|
9048
9141
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
9049
9142
|
}
|
|
9050
|
-
const resolved =
|
|
9143
|
+
const resolved = resolve2(root, rawPath);
|
|
9051
9144
|
const rel = relative(root, resolved);
|
|
9052
9145
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
9053
9146
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
@@ -9077,7 +9170,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs4, dirListings
|
|
|
9077
9170
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
9078
9171
|
}
|
|
9079
9172
|
function readSafe(root, rawPath, fs4) {
|
|
9080
|
-
const resolved =
|
|
9173
|
+
const resolved = resolve2(root, rawPath);
|
|
9081
9174
|
try {
|
|
9082
9175
|
return fs4.read(resolved);
|
|
9083
9176
|
} catch {
|
|
@@ -9353,16 +9446,19 @@ function registerChoiceTool(registry, opts = {}) {
|
|
|
9353
9446
|
|
|
9354
9447
|
// src/tools/web.ts
|
|
9355
9448
|
var import_node_html_parser = __toESM(require_dist(), 1);
|
|
9449
|
+
import { lookup } from "dns/promises";
|
|
9450
|
+
import { isIP } from "net";
|
|
9356
9451
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
9357
9452
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
9358
9453
|
var DEFAULT_TOPK = 5;
|
|
9359
9454
|
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
9360
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";
|
|
9361
|
-
var
|
|
9456
|
+
var BING_ENDPOINT = "https://cn.bing.com/search";
|
|
9362
9457
|
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
9363
9458
|
var TAVILY_ENDPOINT = "https://api.tavily.com/search";
|
|
9364
9459
|
var PERPLEXITY_ENDPOINT = "https://api.perplexity.ai/chat/completions";
|
|
9365
9460
|
var EXA_ENDPOINT = "https://api.exa.ai/answer";
|
|
9461
|
+
var FETCH_MAX_REDIRECTS = 5;
|
|
9366
9462
|
function searchStatusError(status) {
|
|
9367
9463
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
9368
9464
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -9375,6 +9471,63 @@ function fetchStatusError(status, url) {
|
|
|
9375
9471
|
if (status >= 500 && status <= 599) return t("webErrors.fetchServerError5xx", { status, url });
|
|
9376
9472
|
return t("webErrors.fetchStatus", { status, url });
|
|
9377
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
|
+
}
|
|
9378
9531
|
async function webSearch(query, opts = {}) {
|
|
9379
9532
|
if (opts.engine === "metaso") {
|
|
9380
9533
|
return searchMetaso(query, opts);
|
|
@@ -9391,29 +9544,29 @@ async function webSearch(query, opts = {}) {
|
|
|
9391
9544
|
if (opts.engine === "exa") {
|
|
9392
9545
|
return searchExa(query, opts);
|
|
9393
9546
|
}
|
|
9394
|
-
return
|
|
9547
|
+
return searchBing(query, opts);
|
|
9395
9548
|
}
|
|
9396
|
-
async function
|
|
9549
|
+
async function searchBing(query, opts = {}) {
|
|
9397
9550
|
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
9398
|
-
const resp = await fetch(`${
|
|
9551
|
+
const resp = await fetch(`${BING_ENDPOINT}?q=${encodeURIComponent(query)}`, {
|
|
9399
9552
|
headers: {
|
|
9400
9553
|
"User-Agent": USER_AGENT,
|
|
9401
9554
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
9402
|
-
"Accept-Language": "
|
|
9555
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
|
|
9403
9556
|
},
|
|
9404
9557
|
signal: opts.signal,
|
|
9405
9558
|
redirect: "follow"
|
|
9406
9559
|
});
|
|
9407
9560
|
if (!resp.ok) throw new Error(searchStatusError(resp.status));
|
|
9408
9561
|
const html = await resp.text();
|
|
9409
|
-
const results =
|
|
9562
|
+
const results = parseBingResults(html).slice(0, topK);
|
|
9410
9563
|
if (results.length === 0) {
|
|
9411
9564
|
if (/no results found|did not match any documents/i.test(html)) return [];
|
|
9412
9565
|
if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
|
|
9413
|
-
throw new Error(t("webErrors.
|
|
9566
|
+
throw new Error(t("webErrors.bingBlocked"));
|
|
9414
9567
|
}
|
|
9415
9568
|
throw new Error(
|
|
9416
|
-
t("webErrors.
|
|
9569
|
+
t("webErrors.bingNoResults", {
|
|
9417
9570
|
chars: html.length,
|
|
9418
9571
|
preview: html.slice(0, 120).replace(/\s+/g, " ")
|
|
9419
9572
|
})
|
|
@@ -9730,35 +9883,19 @@ function parseSearxngHtmlResults(html) {
|
|
|
9730
9883
|
}
|
|
9731
9884
|
return results;
|
|
9732
9885
|
}
|
|
9733
|
-
function
|
|
9734
|
-
const
|
|
9735
|
-
const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
|
|
9736
|
-
let m;
|
|
9737
|
-
while (true) {
|
|
9738
|
-
m = titleAnchorRe.exec(html);
|
|
9739
|
-
if (m === null) break;
|
|
9740
|
-
titles.push(m[0]);
|
|
9741
|
-
}
|
|
9742
|
-
const snippets = [];
|
|
9743
|
-
const snippetRe = /<p\b[^>]*\bclass="s"[^>]*>([\s\S]*?)<\/p>/g;
|
|
9744
|
-
while (true) {
|
|
9745
|
-
m = snippetRe.exec(html);
|
|
9746
|
-
if (m === null) break;
|
|
9747
|
-
snippets.push(m[1] ?? "");
|
|
9748
|
-
}
|
|
9749
|
-
const hrefRe = /href="([^"]+)"/;
|
|
9750
|
-
const innerRe = /<a\b[^>]*>([\s\S]*?)<\/a>/;
|
|
9886
|
+
function parseBingResults(html) {
|
|
9887
|
+
const root = (0, import_node_html_parser.parse)(html);
|
|
9751
9888
|
const results = [];
|
|
9752
|
-
for (
|
|
9753
|
-
const anchor =
|
|
9754
|
-
|
|
9755
|
-
const
|
|
9756
|
-
if (!
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
});
|
|
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 });
|
|
9762
9899
|
}
|
|
9763
9900
|
return results;
|
|
9764
9901
|
}
|
|
@@ -9774,12 +9911,23 @@ async function webFetch(url, opts = {}) {
|
|
|
9774
9911
|
const cancel = () => ctl.abort();
|
|
9775
9912
|
opts.signal?.addEventListener("abort", cancel, { once: true });
|
|
9776
9913
|
let resp;
|
|
9914
|
+
let currentUrl = url;
|
|
9777
9915
|
try {
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
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
|
+
}
|
|
9783
9931
|
} catch (err) {
|
|
9784
9932
|
if (timedOut) {
|
|
9785
9933
|
throw new Error(t("webErrors.fetchTimeout", { ms: timeoutMs, url }));
|
|
@@ -9802,7 +9950,7 @@ async function webFetch(url, opts = {}) {
|
|
|
9802
9950
|
const finalText = truncated ? `${text.slice(0, maxChars)}
|
|
9803
9951
|
|
|
9804
9952
|
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
9805
|
-
return { url, title, text: finalText, truncated };
|
|
9953
|
+
return { url: currentUrl, title, text: finalText, truncated };
|
|
9806
9954
|
}
|
|
9807
9955
|
async function readBodyCapped(resp, maxBytes) {
|
|
9808
9956
|
if (!resp.body) return await resp.text();
|
|
@@ -9874,9 +10022,6 @@ function walkExtract(node, out) {
|
|
|
9874
10022
|
for (const child of node.childNodes) walkExtract(child, out);
|
|
9875
10023
|
if (isBreak) out.push("\n");
|
|
9876
10024
|
}
|
|
9877
|
-
function stripHtml(s) {
|
|
9878
|
-
return (0, import_node_html_parser.parse)(s).text;
|
|
9879
|
-
}
|
|
9880
10025
|
var HTML_ENTITIES = {
|
|
9881
10026
|
amp: "&",
|
|
9882
10027
|
lt: "<",
|
|
@@ -9991,14 +10136,14 @@ ${i + 1}. ${r.title}`);
|
|
|
9991
10136
|
// src/tools/filesystem.ts
|
|
9992
10137
|
var import_picomatch2 = __toESM(require_picomatch(), 1);
|
|
9993
10138
|
import { promises as fs3 } from "fs";
|
|
9994
|
-
import * as
|
|
10139
|
+
import * as pathMod5 from "path";
|
|
9995
10140
|
|
|
9996
10141
|
// src/memory/subdir.ts
|
|
9997
10142
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
9998
|
-
import { dirname, join as join2, relative as relative2, resolve as
|
|
10143
|
+
import { dirname, join as join2, relative as relative2, resolve as resolve3 } from "path";
|
|
9999
10144
|
function findDirMemory(absDir, rootDir) {
|
|
10000
|
-
const root =
|
|
10001
|
-
const target =
|
|
10145
|
+
const root = resolve3(rootDir);
|
|
10146
|
+
const target = resolve3(absDir);
|
|
10002
10147
|
const rel = relative2(root, target);
|
|
10003
10148
|
if (rel.startsWith("..")) return [];
|
|
10004
10149
|
const found = [];
|
|
@@ -10020,7 +10165,7 @@ function findDirMemory(absDir, rootDir) {
|
|
|
10020
10165
|
return found;
|
|
10021
10166
|
}
|
|
10022
10167
|
function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
10023
|
-
return findDirMemory(dirname(
|
|
10168
|
+
return findDirMemory(dirname(resolve3(absPath)), rootDir);
|
|
10024
10169
|
}
|
|
10025
10170
|
function readSubdirMemoryContent(path) {
|
|
10026
10171
|
let raw;
|
|
@@ -10044,9 +10189,9 @@ ${content}`;
|
|
|
10044
10189
|
// src/tools/fs/glob.ts
|
|
10045
10190
|
var import_picomatch = __toESM(require_picomatch(), 1);
|
|
10046
10191
|
import { promises as fs } from "fs";
|
|
10047
|
-
import * as
|
|
10192
|
+
import * as pathMod2 from "path";
|
|
10048
10193
|
function displayRel(rootDir, full) {
|
|
10049
|
-
return
|
|
10194
|
+
return pathMod2.relative(rootDir, full).replaceAll("\\", "/");
|
|
10050
10195
|
}
|
|
10051
10196
|
async function globFiles(ctx, startAbs, args) {
|
|
10052
10197
|
if (args.signal?.aborted) {
|
|
@@ -10068,7 +10213,7 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
10068
10213
|
return;
|
|
10069
10214
|
}
|
|
10070
10215
|
for (const e of entries) {
|
|
10071
|
-
const full =
|
|
10216
|
+
const full = pathMod2.join(dir, e.name);
|
|
10072
10217
|
if (e.isDirectory()) {
|
|
10073
10218
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
10074
10219
|
await walk2(full);
|
|
@@ -10105,7 +10250,7 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
10105
10250
|
}
|
|
10106
10251
|
|
|
10107
10252
|
// src/tools/fs/outline.ts
|
|
10108
|
-
import * as
|
|
10253
|
+
import * as pathMod3 from "path";
|
|
10109
10254
|
var OUTLINE_MAX_ENTRIES = 30;
|
|
10110
10255
|
var OUTLINE_TAIL_KEEP = 5;
|
|
10111
10256
|
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
@@ -10148,7 +10293,7 @@ var EXT_TO_LANG = {
|
|
|
10148
10293
|
".text": "txt"
|
|
10149
10294
|
};
|
|
10150
10295
|
function extractOutline(filename, lines) {
|
|
10151
|
-
const ext =
|
|
10296
|
+
const ext = pathMod3.extname(filename).toLowerCase();
|
|
10152
10297
|
const lang = EXT_TO_LANG[ext];
|
|
10153
10298
|
if (!lang) return [];
|
|
10154
10299
|
switch (lang) {
|
|
@@ -10289,7 +10434,7 @@ function formatOutline(entries) {
|
|
|
10289
10434
|
|
|
10290
10435
|
// src/tools/fs/search.ts
|
|
10291
10436
|
import { promises as fs2 } from "fs";
|
|
10292
|
-
import * as
|
|
10437
|
+
import * as pathMod4 from "path";
|
|
10293
10438
|
|
|
10294
10439
|
// src/tools/fs/regex-runner.ts
|
|
10295
10440
|
import { Worker } from "worker_threads";
|
|
@@ -10322,7 +10467,7 @@ var RegexRunner = class {
|
|
|
10322
10467
|
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
10323
10468
|
}
|
|
10324
10469
|
testLines(text, source, flags, opts = {}) {
|
|
10325
|
-
return new Promise((
|
|
10470
|
+
return new Promise((resolve6, reject) => {
|
|
10326
10471
|
if (opts.signal?.aborted) {
|
|
10327
10472
|
reject(new Error("regex evaluation aborted"));
|
|
10328
10473
|
return;
|
|
@@ -10335,7 +10480,7 @@ var RegexRunner = class {
|
|
|
10335
10480
|
this.killWorker();
|
|
10336
10481
|
reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
|
|
10337
10482
|
}, timeoutMs);
|
|
10338
|
-
const entry = { resolve:
|
|
10483
|
+
const entry = { resolve: resolve6, reject, timer };
|
|
10339
10484
|
if (opts.signal) {
|
|
10340
10485
|
entry.signal = opts.signal;
|
|
10341
10486
|
entry.onAbort = () => {
|
|
@@ -10418,7 +10563,7 @@ function throwIfAborted(signal) {
|
|
|
10418
10563
|
throw new DOMException("search aborted by user", "AbortError");
|
|
10419
10564
|
}
|
|
10420
10565
|
function displayRel2(rootDir, full) {
|
|
10421
|
-
return
|
|
10566
|
+
return pathMod4.relative(rootDir, full).replaceAll("\\", "/");
|
|
10422
10567
|
}
|
|
10423
10568
|
async function searchFiles(ctx, startAbs, args) {
|
|
10424
10569
|
throwIfAborted(args.signal);
|
|
@@ -10442,7 +10587,7 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
10442
10587
|
}
|
|
10443
10588
|
for (const e of entries) {
|
|
10444
10589
|
throwIfAborted(args.signal);
|
|
10445
|
-
const full =
|
|
10590
|
+
const full = pathMod4.join(dir, e.name);
|
|
10446
10591
|
const lower = e.name.toLowerCase();
|
|
10447
10592
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
10448
10593
|
if (hit) {
|
|
@@ -10534,11 +10679,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10534
10679
|
throwIfTimedOut();
|
|
10535
10680
|
if (e.isDirectory()) {
|
|
10536
10681
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
10537
|
-
await walk2(
|
|
10682
|
+
await walk2(pathMod4.join(dir, e.name));
|
|
10538
10683
|
continue;
|
|
10539
10684
|
}
|
|
10540
10685
|
if (!e.isFile()) continue;
|
|
10541
|
-
const full =
|
|
10686
|
+
const full = pathMod4.join(dir, e.name);
|
|
10542
10687
|
if (ctx.nameMatch && !ctx.nameMatch(e.name, displayRel2(ctx.rootDir, full))) continue;
|
|
10543
10688
|
if (ctx.isBinaryByName(e.name)) continue;
|
|
10544
10689
|
let fh;
|
|
@@ -10656,7 +10801,7 @@ var SKIP_DIR_NAMES = new Set(
|
|
|
10656
10801
|
);
|
|
10657
10802
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
10658
10803
|
function displayRel3(rootDir, full) {
|
|
10659
|
-
return
|
|
10804
|
+
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
10660
10805
|
}
|
|
10661
10806
|
function looksLikeAbsoluteSystemPath(raw) {
|
|
10662
10807
|
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
@@ -10665,8 +10810,8 @@ function looksLikeAbsoluteSystemPath(raw) {
|
|
|
10665
10810
|
);
|
|
10666
10811
|
}
|
|
10667
10812
|
function pathIsUnder(child, parent) {
|
|
10668
|
-
const rel =
|
|
10669
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10813
|
+
const rel = pathMod5.relative(parent, child);
|
|
10814
|
+
return rel === "" || !rel.startsWith("..") && !pathMod5.isAbsolute(rel);
|
|
10670
10815
|
}
|
|
10671
10816
|
var GLOB_METACHARS = /[*?{[]/;
|
|
10672
10817
|
function compileNameFilter(filter) {
|
|
@@ -10698,11 +10843,11 @@ function formatBytes(n) {
|
|
|
10698
10843
|
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
10699
10844
|
}
|
|
10700
10845
|
function registerFilesystemTools(registry, opts) {
|
|
10701
|
-
const rootDir =
|
|
10846
|
+
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
10702
10847
|
const allowWriting = opts.allowWriting !== false;
|
|
10703
10848
|
const outlineThresholdBytes = opts.outlineThresholdBytes ?? DEFAULT_OUTLINE_THRESHOLD_BYTES;
|
|
10704
10849
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
10705
|
-
const normRoot =
|
|
10850
|
+
const normRoot = pathMod5.resolve(rootDir);
|
|
10706
10851
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
10707
10852
|
const shownSubdirMemory = /* @__PURE__ */ new Set();
|
|
10708
10853
|
function withSubdirMemory(absPath, body) {
|
|
@@ -10735,7 +10880,7 @@ ${body}`;
|
|
|
10735
10880
|
if (pathIsUnder(abs, dir)) return;
|
|
10736
10881
|
}
|
|
10737
10882
|
const stat2 = await safeLstat(abs);
|
|
10738
|
-
const allowPrefix = stat2?.isDirectory() ? abs :
|
|
10883
|
+
const allowPrefix = stat2?.isDirectory() ? abs : pathMod5.dirname(abs);
|
|
10739
10884
|
let pending = inflightGate.get(allowPrefix);
|
|
10740
10885
|
if (!pending) {
|
|
10741
10886
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
@@ -10763,7 +10908,7 @@ ${body}`;
|
|
|
10763
10908
|
throw new Error("path must be a non-empty string");
|
|
10764
10909
|
}
|
|
10765
10910
|
if (looksLikeAbsoluteSystemPath(raw)) {
|
|
10766
|
-
const abs =
|
|
10911
|
+
const abs = pathMod5.resolve(raw);
|
|
10767
10912
|
if (pathIsUnder(abs, normRoot)) return abs;
|
|
10768
10913
|
await ensureOutsideSandboxAllowed(abs, intent, toolName, ctx);
|
|
10769
10914
|
return abs;
|
|
@@ -10773,7 +10918,7 @@ ${body}`;
|
|
|
10773
10918
|
normalized = normalized.slice(1);
|
|
10774
10919
|
}
|
|
10775
10920
|
if (normalized.length === 0) normalized = ".";
|
|
10776
|
-
const resolved =
|
|
10921
|
+
const resolved = pathMod5.resolve(rootDir, normalized);
|
|
10777
10922
|
if (!pathIsUnder(resolved, normRoot)) {
|
|
10778
10923
|
throw new Error(
|
|
10779
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`
|
|
@@ -10835,7 +10980,8 @@ ${body}`;
|
|
|
10835
10980
|
if (looksBinary(raw)) {
|
|
10836
10981
|
return `[refused: ${rel} appears to be binary (${formatBytes(sizeBytes)}) \u2014 read_file returns text only. Use get_file_info for stat.]`;
|
|
10837
10982
|
}
|
|
10838
|
-
const text = raw
|
|
10983
|
+
const { text } = decodeFileBuffer(raw);
|
|
10984
|
+
ctx?.readTracker?.markRead(abs);
|
|
10839
10985
|
let lines = text.split(/\r?\n/);
|
|
10840
10986
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
10841
10987
|
const totalLines = lines.length;
|
|
@@ -10973,7 +11119,7 @@ ${slice.join("\n")}`);
|
|
|
10973
11119
|
lines.push(line);
|
|
10974
11120
|
emitted++;
|
|
10975
11121
|
if (e.isDirectory() && !skip) {
|
|
10976
|
-
await walk2(
|
|
11122
|
+
await walk2(pathMod5.join(dir, e.name), depth + 1);
|
|
10977
11123
|
}
|
|
10978
11124
|
}
|
|
10979
11125
|
};
|
|
@@ -11133,14 +11279,20 @@ ${slice.join("\n")}`);
|
|
|
11133
11279
|
},
|
|
11134
11280
|
fn: async (args, ctx) => {
|
|
11135
11281
|
const abs = await safePath(args.path, "write_file", ctx, "write");
|
|
11136
|
-
await fs3.mkdir(
|
|
11137
|
-
|
|
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);
|
|
11138
11290
|
return `wrote ${args.content.length} chars to ${displayRel3(rootDir, abs)}`;
|
|
11139
11291
|
}
|
|
11140
11292
|
});
|
|
11141
11293
|
registry.register({
|
|
11142
11294
|
name: "edit_file",
|
|
11143
|
-
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.",
|
|
11144
11296
|
parameters: {
|
|
11145
11297
|
type: "object",
|
|
11146
11298
|
properties: {
|
|
@@ -11150,11 +11302,16 @@ ${slice.join("\n")}`);
|
|
|
11150
11302
|
},
|
|
11151
11303
|
required: ["path", "search", "replace"]
|
|
11152
11304
|
},
|
|
11153
|
-
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
|
+
)
|
|
11154
11311
|
});
|
|
11155
11312
|
registry.register({
|
|
11156
11313
|
name: "multi_edit",
|
|
11157
|
-
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.",
|
|
11158
11315
|
parameters: {
|
|
11159
11316
|
type: "object",
|
|
11160
11317
|
properties: {
|
|
@@ -11188,7 +11345,11 @@ ${slice.join("\n")}`);
|
|
|
11188
11345
|
replace: e?.replace
|
|
11189
11346
|
}))
|
|
11190
11347
|
);
|
|
11191
|
-
return applyMultiEdit(
|
|
11348
|
+
return applyMultiEdit(
|
|
11349
|
+
rootDir,
|
|
11350
|
+
resolved,
|
|
11351
|
+
ctx?.readTracker ? (abs) => ctx.readTracker.hasRead(abs) : void 0
|
|
11352
|
+
);
|
|
11192
11353
|
}
|
|
11193
11354
|
});
|
|
11194
11355
|
registry.register({
|
|
@@ -11219,7 +11380,7 @@ ${slice.join("\n")}`);
|
|
|
11219
11380
|
fn: async (args, ctx) => {
|
|
11220
11381
|
const src = await safePath(args.source, "move_file", ctx, "write");
|
|
11221
11382
|
const dst = await safePath(args.destination, "move_file", ctx, "write");
|
|
11222
|
-
await fs3.mkdir(
|
|
11383
|
+
await fs3.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
11223
11384
|
await fs3.rename(src, dst);
|
|
11224
11385
|
return `moved ${displayRel3(rootDir, src)} \u2192 ${displayRel3(rootDir, dst)}`;
|
|
11225
11386
|
}
|
|
@@ -11287,7 +11448,7 @@ ${slice.join("\n")}`);
|
|
|
11287
11448
|
fn: async (args, ctx) => {
|
|
11288
11449
|
const src = await safePath(args.source, "copy_file", ctx);
|
|
11289
11450
|
const dst = await safePath(args.destination, "copy_file", ctx, "write");
|
|
11290
|
-
await fs3.mkdir(
|
|
11451
|
+
await fs3.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
11291
11452
|
await fs3.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
11292
11453
|
return `copied ${displayRel3(rootDir, src)} \u2192 ${displayRel3(rootDir, dst)}`;
|
|
11293
11454
|
}
|
|
@@ -11992,7 +12153,7 @@ function formatSubagentResult(r) {
|
|
|
11992
12153
|
});
|
|
11993
12154
|
}
|
|
11994
12155
|
function forkRegistryExcluding(parent, exclude) {
|
|
11995
|
-
const child = new ToolRegistry();
|
|
12156
|
+
const child = new ToolRegistry({ rateLimit: parent.rateLimitPolicy });
|
|
11996
12157
|
for (const spec of parent.specs()) {
|
|
11997
12158
|
const name = spec.function.name;
|
|
11998
12159
|
if (exclude.has(name)) continue;
|
|
@@ -12004,7 +12165,7 @@ function forkRegistryExcluding(parent, exclude) {
|
|
|
12004
12165
|
return child;
|
|
12005
12166
|
}
|
|
12006
12167
|
function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
12007
|
-
const child = new ToolRegistry();
|
|
12168
|
+
const child = new ToolRegistry({ rateLimit: parent.rateLimitPolicy });
|
|
12008
12169
|
for (const spec of parent.specs()) {
|
|
12009
12170
|
const name = spec.function.name;
|
|
12010
12171
|
if (!allow.has(name)) continue;
|
|
@@ -12031,7 +12192,7 @@ import {
|
|
|
12031
12192
|
writeFileSync,
|
|
12032
12193
|
writeSync
|
|
12033
12194
|
} from "fs";
|
|
12034
|
-
import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative6, resolve as
|
|
12195
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative6, resolve as resolve5 } from "path";
|
|
12035
12196
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
12036
12197
|
function parseEditBlocks(text) {
|
|
12037
12198
|
const out = [];
|
|
@@ -12049,15 +12210,15 @@ function parseEditBlocks(text) {
|
|
|
12049
12210
|
return out;
|
|
12050
12211
|
}
|
|
12051
12212
|
function resolveEditPath(rootDir, rawPath) {
|
|
12052
|
-
const absRoot =
|
|
12213
|
+
const absRoot = resolve5(rootDir);
|
|
12053
12214
|
if (/^[A-Za-z]:[\\/]/.test(rawPath) || looksLikeAbsoluteSystemPath2(rawPath)) {
|
|
12054
|
-
return
|
|
12215
|
+
return resolve5(rawPath);
|
|
12055
12216
|
}
|
|
12056
12217
|
let rooted = rawPath;
|
|
12057
12218
|
while (rooted.startsWith("/") || rooted.startsWith("\\")) {
|
|
12058
12219
|
rooted = rooted.slice(1);
|
|
12059
12220
|
}
|
|
12060
|
-
return
|
|
12221
|
+
return resolve5(absRoot, rooted || ".");
|
|
12061
12222
|
}
|
|
12062
12223
|
function looksLikeAbsoluteSystemPath2(rawPath) {
|
|
12063
12224
|
return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
|
|
@@ -12069,7 +12230,7 @@ function pathIsUnder2(child, parent) {
|
|
|
12069
12230
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
12070
12231
|
}
|
|
12071
12232
|
function applyEditBlock(block, rootDir) {
|
|
12072
|
-
const absRoot =
|
|
12233
|
+
const absRoot = resolve5(rootDir);
|
|
12073
12234
|
const absTarget = resolveEditPath(rootDir, block.path);
|
|
12074
12235
|
if (!pathIsUnder2(absTarget, absRoot)) {
|
|
12075
12236
|
return {
|
|
@@ -12124,7 +12285,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
12124
12285
|
if (n <= 0) break;
|
|
12125
12286
|
readBytes += n;
|
|
12126
12287
|
}
|
|
12127
|
-
const content = inBuf.
|
|
12288
|
+
const { text: content, encoding } = decodeFileBuffer(inBuf.subarray(0, readBytes));
|
|
12128
12289
|
const le = lineEndingOf(content);
|
|
12129
12290
|
const adaptedSearch = block.search.replace(/\r?\n/g, le);
|
|
12130
12291
|
const adaptedReplace = block.replace.replace(/\r?\n/g, le);
|
|
@@ -12145,7 +12306,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
12145
12306
|
};
|
|
12146
12307
|
}
|
|
12147
12308
|
const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
|
|
12148
|
-
const outBuf =
|
|
12309
|
+
const outBuf = encodeFile(replaced, encoding);
|
|
12149
12310
|
ftruncateSync(fd, outBuf.length);
|
|
12150
12311
|
let written = 0;
|
|
12151
12312
|
while (written < outBuf.length) {
|
|
@@ -12169,7 +12330,7 @@ function toWholeFileEditBlock(path, content, rootDir) {
|
|
|
12169
12330
|
let search = "";
|
|
12170
12331
|
if (existsSync3(abs)) {
|
|
12171
12332
|
try {
|
|
12172
|
-
search = readFileSync3(abs
|
|
12333
|
+
search = decodeFileBuffer(readFileSync3(abs)).text;
|
|
12173
12334
|
} catch {
|
|
12174
12335
|
search = "";
|
|
12175
12336
|
}
|
|
@@ -12177,10 +12338,12 @@ function toWholeFileEditBlock(path, content, rootDir) {
|
|
|
12177
12338
|
return { path, search, replace: content, offset: 0 };
|
|
12178
12339
|
}
|
|
12179
12340
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
12341
|
+
const absRoot = resolve5(rootDir);
|
|
12180
12342
|
const seen = /* @__PURE__ */ new Set();
|
|
12181
12343
|
const snapshots = [];
|
|
12182
12344
|
for (const b of blocks) {
|
|
12183
12345
|
const abs = resolveEditPath(rootDir, b.path);
|
|
12346
|
+
if (!pathIsUnder2(abs, absRoot)) continue;
|
|
12184
12347
|
if (seen.has(abs)) continue;
|
|
12185
12348
|
seen.add(abs);
|
|
12186
12349
|
if (!existsSync3(abs)) {
|
|
@@ -12188,7 +12351,8 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
12188
12351
|
continue;
|
|
12189
12352
|
}
|
|
12190
12353
|
try {
|
|
12191
|
-
|
|
12354
|
+
const { text, encoding } = decodeFileBuffer(readFileSync3(abs));
|
|
12355
|
+
snapshots.push({ path: b.path, prevContent: text, prevEncoding: encoding });
|
|
12192
12356
|
} catch {
|
|
12193
12357
|
snapshots.push({ path: b.path, prevContent: null });
|
|
12194
12358
|
}
|
|
@@ -12196,7 +12360,7 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
12196
12360
|
return snapshots;
|
|
12197
12361
|
}
|
|
12198
12362
|
function restoreSnapshots(snapshots, rootDir) {
|
|
12199
|
-
const absRoot =
|
|
12363
|
+
const absRoot = resolve5(rootDir);
|
|
12200
12364
|
return snapshots.map((snap) => {
|
|
12201
12365
|
const abs = resolveEditPath(rootDir, snap.path);
|
|
12202
12366
|
if (!pathIsUnder2(abs, absRoot)) {
|
|
@@ -12215,7 +12379,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
12215
12379
|
message: "removed (the edit had created it)"
|
|
12216
12380
|
};
|
|
12217
12381
|
}
|
|
12218
|
-
writeFileSync(abs, snap.prevContent, "utf8");
|
|
12382
|
+
writeFileSync(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
|
|
12219
12383
|
return {
|
|
12220
12384
|
path: snap.path,
|
|
12221
12385
|
status: "applied",
|
|
@@ -12267,4 +12431,4 @@ export {
|
|
|
12267
12431
|
he/he.js:
|
|
12268
12432
|
(*! https://mths.be/he v1.2.0 by @mathias | MIT license *)
|
|
12269
12433
|
*/
|
|
12270
|
-
//# sourceMappingURL=chunk-
|
|
12434
|
+
//# sourceMappingURL=chunk-MQWO32ZD.js.map
|