reasonix 0.41.0 → 0.44.0-rc.5
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 +73 -12
- package/README.zh-CN.md +35 -7
- package/dashboard/app.css +225 -4
- package/dashboard/dist/app.js +6538 -6082
- package/dashboard/dist/app.js.map +1 -1
- package/data/deepseek-tokenizer.json.gz +0 -0
- package/dist/cli/{acp-64VQZLDJ.js → acp-TYZ2CTDL.js} +40 -37
- package/dist/cli/acp-TYZ2CTDL.js.map +1 -0
- package/dist/cli/chat-TH7VNNCJ.js +51 -0
- package/dist/cli/{chunk-E46ECXJD.js → chunk-2425HK6U.js} +2 -1
- package/dist/cli/{chunk-E46ECXJD.js.map → chunk-2425HK6U.js.map} +1 -1
- package/dist/cli/chunk-25T6CVUP.js +172 -0
- package/dist/cli/chunk-25T6CVUP.js.map +1 -0
- package/dist/cli/{chunk-3Q3C4W66.js → chunk-2UQP6H6T.js} +2 -1
- package/dist/cli/{chunk-3Q3C4W66.js.map → chunk-2UQP6H6T.js.map} +1 -1
- package/dist/cli/{chunk-SXLJBFIV.js → chunk-2V6EAEUW.js} +99 -31
- package/dist/cli/chunk-2V6EAEUW.js.map +1 -0
- package/dist/cli/{chunk-4H3ZRJ2U.js → chunk-4CTDEJUF.js} +4 -3
- package/dist/cli/{chunk-4H3ZRJ2U.js.map → chunk-4CTDEJUF.js.map} +1 -1
- package/dist/cli/{chunk-7VFNPMKG.js → chunk-4QUNBQQ2.js} +3 -2
- package/dist/cli/{chunk-7VFNPMKG.js.map → chunk-4QUNBQQ2.js.map} +1 -1
- package/dist/cli/chunk-5QCB62C4.js +25319 -0
- package/dist/cli/chunk-5QCB62C4.js.map +1 -0
- package/dist/cli/chunk-6OWJV3YW.js +390 -0
- package/dist/cli/chunk-6OWJV3YW.js.map +1 -0
- package/dist/cli/{chunk-YJFKFTAL.js → chunk-6PBZN4VI.js} +15 -3
- package/dist/cli/chunk-6PBZN4VI.js.map +1 -0
- package/dist/cli/{chunk-WE3YZULK.js → chunk-7CIGMZT3.js} +3 -2
- package/dist/cli/{chunk-WE3YZULK.js.map → chunk-7CIGMZT3.js.map} +1 -1
- package/dist/cli/{chunk-LTXADNCO.js → chunk-7UCMM425.js} +8 -3
- package/dist/cli/chunk-7UCMM425.js.map +1 -0
- package/dist/cli/{chunk-UV7XJUJH.js → chunk-AB2RED3C.js} +16 -9
- package/dist/cli/{chunk-UV7XJUJH.js.map → chunk-AB2RED3C.js.map} +1 -1
- package/dist/cli/chunk-AVFXO2EZ.js +2996 -0
- package/dist/cli/chunk-AVFXO2EZ.js.map +1 -0
- package/dist/cli/{chunk-7SPOFTMT.js → chunk-C53JQES5.js} +4 -3
- package/dist/cli/{chunk-7SPOFTMT.js.map → chunk-C53JQES5.js.map} +1 -1
- package/dist/cli/{chunk-MHGPBJ2T.js → chunk-CGDR2ELH.js} +131 -34
- package/dist/cli/chunk-CGDR2ELH.js.map +1 -0
- package/dist/cli/{chunk-4DCHFFEY.js → chunk-CWZKQ5FE.js} +8 -4
- package/dist/cli/chunk-CWZKQ5FE.js.map +1 -0
- package/dist/cli/chunk-FEZK652I.js +3644 -0
- package/dist/cli/chunk-FEZK652I.js.map +1 -0
- package/dist/cli/{chunk-A7VHMMDE.js → chunk-HNXDZGC6.js} +114 -5
- package/dist/cli/chunk-HNXDZGC6.js.map +1 -0
- package/dist/cli/{chunk-FWGEHRB7.js → chunk-J5XJHLWM.js} +2 -1
- package/dist/cli/{chunk-FWGEHRB7.js.map → chunk-J5XJHLWM.js.map} +1 -1
- package/dist/cli/{chunk-Y5XNV3NX.js → chunk-JMBMLOBP.js} +2 -1
- package/dist/cli/{chunk-Y5XNV3NX.js.map → chunk-JMBMLOBP.js.map} +1 -1
- package/dist/cli/{chunk-ARF3N2SY.js → chunk-JNAQYELD.js} +17 -8
- package/dist/cli/{chunk-ARF3N2SY.js.map → chunk-JNAQYELD.js.map} +1 -1
- package/dist/cli/{chunk-AT6GGIBV.js → chunk-KGBG6M2X.js} +25 -18
- package/dist/cli/chunk-KGBG6M2X.js.map +1 -0
- package/dist/cli/{chunk-A3LL4XDV.js → chunk-KLQTAZIY.js} +69 -5
- package/dist/cli/chunk-KLQTAZIY.js.map +1 -0
- package/dist/cli/{chunk-KZYLMMU5.js → chunk-KM465GST.js} +13 -12
- package/dist/cli/{chunk-KZYLMMU5.js.map → chunk-KM465GST.js.map} +1 -1
- package/dist/cli/{chunk-2CXPDAWX.js → chunk-LIR2HBQH.js} +3 -2
- package/dist/cli/{chunk-2CXPDAWX.js.map → chunk-LIR2HBQH.js.map} +1 -1
- package/dist/cli/{chunk-RAUPWSYA.js → chunk-MJ6W5UN3.js} +3 -2
- package/dist/cli/{chunk-RAUPWSYA.js.map → chunk-MJ6W5UN3.js.map} +1 -1
- package/dist/cli/{chunk-4W2CICFQ.js → chunk-MRHHQJAQ.js} +72 -60
- package/dist/cli/chunk-MRHHQJAQ.js.map +1 -0
- package/dist/cli/{chunk-BOFL3T45.js → chunk-NVURFF27.js} +23 -11
- package/dist/cli/chunk-NVURFF27.js.map +1 -0
- package/dist/cli/chunk-OPFUUYHL.js +11645 -0
- package/dist/cli/chunk-OPFUUYHL.js.map +1 -0
- package/dist/cli/{chunk-FM57FNPJ.js → chunk-PLHAZOLZ.js} +2 -1
- package/dist/cli/{chunk-FM57FNPJ.js.map → chunk-PLHAZOLZ.js.map} +1 -1
- package/dist/cli/{chunk-ZTLZO42A.js → chunk-R3CTO2HM.js} +3 -2
- package/dist/cli/{chunk-ZTLZO42A.js.map → chunk-R3CTO2HM.js.map} +1 -1
- package/dist/cli/{chunk-BYZGO3BX.js → chunk-RDRC3XDT.js} +145 -39
- package/dist/cli/chunk-RDRC3XDT.js.map +1 -0
- package/dist/cli/chunk-S4XVGLRW.js +499 -0
- package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
- package/dist/cli/{chunk-WJ3YX4PZ.js → chunk-SZ5XES2N.js} +3 -2
- package/dist/cli/{chunk-WJ3YX4PZ.js.map → chunk-SZ5XES2N.js.map} +1 -1
- package/dist/cli/{chunk-CD4SCQL4.js → chunk-TEUDEGX2.js} +64 -10
- package/dist/cli/chunk-TEUDEGX2.js.map +1 -0
- package/dist/cli/chunk-TKVXTQ3T.js +96 -0
- package/dist/cli/chunk-TKVXTQ3T.js.map +1 -0
- package/dist/cli/chunk-TUK7OWJA.js +51 -0
- package/dist/cli/chunk-TXJMRPIL.js +54 -0
- package/dist/cli/{chunk-L7W3HJZQ.js.map → chunk-TXJMRPIL.js.map} +1 -1
- package/dist/cli/{chunk-H4OLWRSX.js → chunk-V26WPN3J.js} +11 -7
- package/dist/cli/chunk-V26WPN3J.js.map +1 -0
- package/dist/cli/{chunk-VFG4GIT3.js → chunk-WK3UFQY3.js} +3 -2
- package/dist/cli/{chunk-VFG4GIT3.js.map → chunk-WK3UFQY3.js.map} +1 -1
- package/dist/cli/chunk-X53B3JIX.js +34320 -0
- package/dist/cli/chunk-X53B3JIX.js.map +1 -0
- package/dist/cli/{chunk-SOZE7V7V.js → chunk-XJXDHAES.js} +3 -2
- package/dist/cli/{chunk-SOZE7V7V.js.map → chunk-XJXDHAES.js.map} +1 -1
- package/dist/cli/{chunk-CFY2XLY6.js → chunk-XSU4QVFW.js} +7 -85
- package/dist/cli/chunk-XSU4QVFW.js.map +1 -0
- package/dist/cli/{chunk-CRPQUBP6.js → chunk-XXC2BYTV.js} +2 -1
- package/dist/cli/{chunk-CRPQUBP6.js.map → chunk-XXC2BYTV.js.map} +1 -1
- package/dist/cli/chunk-Z4S7EYXG.js +63813 -0
- package/dist/cli/chunk-Z4S7EYXG.js.map +1 -0
- package/dist/cli/chunk-ZZM6QJ4W.js +109 -0
- package/dist/cli/chunk-ZZM6QJ4W.js.map +1 -0
- package/dist/cli/{chunk-ORM6PK57.js → chunk-ZZYBBX5N.js} +14 -5
- package/dist/cli/chunk-ZZYBBX5N.js.map +1 -0
- package/dist/cli/code-PSVJ3KEN.js +166 -0
- package/dist/cli/code-PSVJ3KEN.js.map +1 -0
- package/dist/cli/{commands-QY7MSQG7.js → commands-OCU42XG4.js} +7 -5
- package/dist/cli/{commands-QY7MSQG7.js.map → commands-OCU42XG4.js.map} +1 -1
- package/dist/cli/{commit-BRCQ3OQO.js → commit-XCQIQCYG.js} +7 -4
- package/dist/cli/{commit-BRCQ3OQO.js.map → commit-XCQIQCYG.js.map} +1 -1
- package/dist/cli/{desktop-ZTMHQR2Y.js → desktop-KWGR4BNE.js} +327 -98
- package/dist/cli/desktop-KWGR4BNE.js.map +1 -0
- package/dist/cli/devtools-HW3WDT3Q.js +91 -0
- package/dist/cli/devtools-HW3WDT3Q.js.map +1 -0
- package/dist/cli/diff-NHANTNC3.js +165 -0
- package/dist/cli/{diff-YASCB7PU.js.map → diff-NHANTNC3.js.map} +1 -1
- package/dist/cli/doctor-CC5CLOGG.js +27 -0
- package/dist/cli/{events-2AJTXR7I.js → events-XEFAD5VX.js} +6 -4
- package/dist/cli/{events-2AJTXR7I.js.map → events-XEFAD5VX.js.map} +1 -1
- package/dist/cli/index.js +3266 -152
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-YMWBLRR7.js → mcp-MPVGBBJF.js} +6 -4
- package/dist/cli/{mcp-YMWBLRR7.js.map → mcp-MPVGBBJF.js.map} +1 -1
- package/dist/cli/{mcp-browse-XLDUE6SB.js → mcp-browse-4XOTC3FJ.js} +32 -21
- package/dist/cli/{mcp-browse-XLDUE6SB.js.map → mcp-browse-4XOTC3FJ.js.map} +1 -1
- package/dist/cli/{mcp-inspect-H4D2HSJP.js → mcp-inspect-CEMGKKAH.js} +18 -10
- package/dist/cli/mcp-inspect-CEMGKKAH.js.map +1 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/prompt-2D7ID24X.js +16 -0
- package/dist/cli/{prune-sessions-4N3BVST2.js → prune-sessions-3RWUBYRS.js} +4 -2
- package/dist/cli/{prune-sessions-4N3BVST2.js.map → prune-sessions-3RWUBYRS.js.map} +1 -1
- package/dist/cli/{replay-3GTWM75X.js → replay-SR44E6RS.js} +42 -30
- package/dist/cli/{replay-3GTWM75X.js.map → replay-SR44E6RS.js.map} +1 -1
- package/dist/cli/{run-BLZPTRDX.js → run-MDGL27WL.js} +42 -40
- package/dist/cli/run-MDGL27WL.js.map +1 -0
- package/dist/cli/{server-DRFPXXSH.js → server-27ARQXIZ.js} +95 -55
- package/dist/cli/server-27ARQXIZ.js.map +1 -0
- package/dist/cli/{sessions-BOWFPTXT.js → sessions-CKQXCYGP.js} +25 -22
- package/dist/cli/sessions-CKQXCYGP.js.map +1 -0
- package/dist/cli/setup-TPAGSVXO.js +618 -0
- package/dist/cli/setup-TPAGSVXO.js.map +1 -0
- package/dist/cli/stats-DPUBZNVX.js +16 -0
- package/dist/cli/update-6ITLPRDV.js +15 -0
- package/dist/cli/version-2X3BHVVK.js +33 -0
- package/dist/cli/{version-XQXYSJ5L.js.map → version-2X3BHVVK.js.map} +1 -1
- package/dist/index.d.ts +182 -45
- package/dist/index.js +1420 -546
- package/dist/index.js.map +1 -1
- package/package.json +21 -8
- package/dist/cli/.-3G6VX5S7.js +0 -327
- package/dist/cli/.-6YRPB2C7.js +0 -329
- package/dist/cli/.-EYSVINK3.js +0 -317
- package/dist/cli/acp-64VQZLDJ.js.map +0 -1
- package/dist/cli/chat-ZAGX52RV.js +0 -46
- package/dist/cli/chunk-4DCHFFEY.js.map +0 -1
- package/dist/cli/chunk-4W2CICFQ.js.map +0 -1
- package/dist/cli/chunk-5X7LZJDE.js +0 -36
- package/dist/cli/chunk-5X7LZJDE.js.map +0 -1
- package/dist/cli/chunk-65Q5HQ26.js +0 -892
- package/dist/cli/chunk-65Q5HQ26.js.map +0 -1
- package/dist/cli/chunk-A3LL4XDV.js.map +0 -1
- package/dist/cli/chunk-A7VHMMDE.js.map +0 -1
- package/dist/cli/chunk-AFFZF3MW.js +0 -36
- package/dist/cli/chunk-AFFZF3MW.js.map +0 -1
- package/dist/cli/chunk-AT6GGIBV.js.map +0 -1
- package/dist/cli/chunk-BOFL3T45.js.map +0 -1
- package/dist/cli/chunk-BYZGO3BX.js.map +0 -1
- package/dist/cli/chunk-CD4SCQL4.js.map +0 -1
- package/dist/cli/chunk-CFY2XLY6.js.map +0 -1
- package/dist/cli/chunk-CPOV2O73.js +0 -39
- package/dist/cli/chunk-CPOV2O73.js.map +0 -1
- package/dist/cli/chunk-DAEAAVDF.js +0 -199
- package/dist/cli/chunk-DAEAAVDF.js.map +0 -1
- package/dist/cli/chunk-F2AV2QDK.js +0 -16514
- package/dist/cli/chunk-F2AV2QDK.js.map +0 -1
- package/dist/cli/chunk-H4OLWRSX.js.map +0 -1
- package/dist/cli/chunk-IEA6JOIP.js +0 -5430
- package/dist/cli/chunk-IEA6JOIP.js.map +0 -1
- package/dist/cli/chunk-L7W3HJZQ.js +0 -46
- package/dist/cli/chunk-LN27AKV3.js +0 -26
- package/dist/cli/chunk-LN27AKV3.js.map +0 -1
- package/dist/cli/chunk-LTXADNCO.js.map +0 -1
- package/dist/cli/chunk-MHGPBJ2T.js.map +0 -1
- package/dist/cli/chunk-ORM6PK57.js.map +0 -1
- package/dist/cli/chunk-SXLJBFIV.js.map +0 -1
- package/dist/cli/chunk-YJFKFTAL.js.map +0 -1
- package/dist/cli/code-X3M6ENTQ.js +0 -150
- package/dist/cli/code-X3M6ENTQ.js.map +0 -1
- package/dist/cli/desktop-ZTMHQR2Y.js.map +0 -1
- package/dist/cli/diff-YASCB7PU.js +0 -153
- package/dist/cli/doctor-XCN5ETVP.js +0 -24
- package/dist/cli/mcp-inspect-H4D2HSJP.js.map +0 -1
- package/dist/cli/prompt-RSIHN62V.js +0 -14
- package/dist/cli/run-BLZPTRDX.js.map +0 -1
- package/dist/cli/server-DRFPXXSH.js.map +0 -1
- package/dist/cli/sessions-BOWFPTXT.js.map +0 -1
- package/dist/cli/setup-FQL2JJC2.js +0 -516
- package/dist/cli/setup-FQL2JJC2.js.map +0 -1
- package/dist/cli/stats-5RJCATCE.js +0 -12
- package/dist/cli/stats-5RJCATCE.js.map +0 -1
- package/dist/cli/update-GUCWB4UN.js +0 -13
- package/dist/cli/update-GUCWB4UN.js.map +0 -1
- package/dist/cli/version-XQXYSJ5L.js +0 -30
- /package/dist/cli/{.-3G6VX5S7.js.map → chat-TH7VNNCJ.js.map} +0 -0
- /package/dist/cli/{.-6YRPB2C7.js.map → chunk-TUK7OWJA.js.map} +0 -0
- /package/dist/cli/{.-EYSVINK3.js.map → doctor-CC5CLOGG.js.map} +0 -0
- /package/dist/cli/{chat-ZAGX52RV.js.map → prompt-2D7ID24X.js.map} +0 -0
- /package/dist/cli/{doctor-XCN5ETVP.js.map → stats-DPUBZNVX.js.map} +0 -0
- /package/dist/cli/{prompt-RSIHN62V.js.map → update-6ITLPRDV.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -47,8 +47,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
47
47
|
}
|
|
48
48
|
function sleep(ms, signal) {
|
|
49
49
|
if (ms <= 0) return Promise.resolve();
|
|
50
|
-
return new Promise((
|
|
51
|
-
const timer = setTimeout(
|
|
50
|
+
return new Promise((resolve13, reject) => {
|
|
51
|
+
const timer = setTimeout(resolve13, ms);
|
|
52
52
|
if (signal) {
|
|
53
53
|
const onAbort = () => {
|
|
54
54
|
clearTimeout(timer);
|
|
@@ -93,12 +93,15 @@ var Usage = class _Usage {
|
|
|
93
93
|
}
|
|
94
94
|
static fromApi(raw) {
|
|
95
95
|
const u = raw ?? {};
|
|
96
|
+
const promptTokens = u.prompt_tokens ?? 0;
|
|
97
|
+
const cacheHitTokens = u.prompt_cache_hit_tokens ?? 0;
|
|
98
|
+
const cacheMissTokens = u.prompt_cache_miss_tokens ?? Math.max(0, promptTokens - cacheHitTokens);
|
|
96
99
|
return new _Usage(
|
|
97
|
-
|
|
100
|
+
promptTokens,
|
|
98
101
|
u.completion_tokens ?? 0,
|
|
99
102
|
u.total_tokens ?? 0,
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
cacheHitTokens,
|
|
104
|
+
cacheMissTokens
|
|
102
105
|
);
|
|
103
106
|
}
|
|
104
107
|
};
|
|
@@ -309,10 +312,10 @@ var PauseGate = class {
|
|
|
309
312
|
`${kind}: no confirmation listener registered \u2014 cannot prompt the user. This tool can only be used inside an interactive Reasonix session.`
|
|
310
313
|
);
|
|
311
314
|
}
|
|
312
|
-
return new Promise((
|
|
315
|
+
return new Promise((resolve13) => {
|
|
313
316
|
const id = this._nextId++;
|
|
314
317
|
const request = { id, kind, payload };
|
|
315
|
-
this._pending.set(id, { resolve:
|
|
318
|
+
this._pending.set(id, { resolve: resolve13, request });
|
|
316
319
|
for (const fn of this._listeners) {
|
|
317
320
|
try {
|
|
318
321
|
fn(request);
|
|
@@ -427,12 +430,12 @@ import { join as join2 } from "path";
|
|
|
427
430
|
// src/config.ts
|
|
428
431
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
429
432
|
import { homedir } from "os";
|
|
430
|
-
import { dirname, join } from "path";
|
|
433
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
431
434
|
|
|
432
435
|
// src/cli/ui/theme/tokens.ts
|
|
433
436
|
function card(fg, tone) {
|
|
434
437
|
return {
|
|
435
|
-
user: { color:
|
|
438
|
+
user: { color: tone.brand, glyph: "\u25C7" },
|
|
436
439
|
reasoning: { color: tone.accent, glyph: "\u25C6" },
|
|
437
440
|
streaming: { color: tone.brand, glyph: "\u25C8" },
|
|
438
441
|
task: { color: tone.warn, glyph: "\u25B6" },
|
|
@@ -772,6 +775,85 @@ var DEFAULT_INDEX_EXCLUDES = {
|
|
|
772
775
|
};
|
|
773
776
|
var DEFAULT_MAX_FILE_BYTES = 256 * 1024;
|
|
774
777
|
|
|
778
|
+
// src/mcp/shell-split.ts
|
|
779
|
+
function shellSplit(input) {
|
|
780
|
+
const tokens = [];
|
|
781
|
+
let cur = "";
|
|
782
|
+
let quote = null;
|
|
783
|
+
let i = 0;
|
|
784
|
+
const s = input;
|
|
785
|
+
while (i < s.length) {
|
|
786
|
+
const ch = s[i];
|
|
787
|
+
if (quote) {
|
|
788
|
+
if (ch === quote) {
|
|
789
|
+
quote = null;
|
|
790
|
+
i++;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
if (ch === "\\" && quote === '"' && i + 1 < s.length) {
|
|
794
|
+
cur += s[i + 1];
|
|
795
|
+
i += 2;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
cur += ch;
|
|
799
|
+
i++;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (ch === '"' || ch === "'") {
|
|
803
|
+
quote = ch;
|
|
804
|
+
i++;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (ch === " " || ch === " ") {
|
|
808
|
+
if (cur.length > 0) {
|
|
809
|
+
tokens.push(cur);
|
|
810
|
+
cur = "";
|
|
811
|
+
}
|
|
812
|
+
i++;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
cur += ch;
|
|
816
|
+
i++;
|
|
817
|
+
}
|
|
818
|
+
if (quote) {
|
|
819
|
+
throw new Error(
|
|
820
|
+
`shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
if (cur.length > 0) tokens.push(cur);
|
|
824
|
+
return tokens;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/mcp/spec.ts
|
|
828
|
+
var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_-]*)=(.*)$/;
|
|
829
|
+
var HTTP_URL = /^https?:\/\//i;
|
|
830
|
+
var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
|
|
831
|
+
function parseMcpSpec(input) {
|
|
832
|
+
const trimmed = input.trim();
|
|
833
|
+
if (!trimmed) {
|
|
834
|
+
throw new Error("empty MCP spec");
|
|
835
|
+
}
|
|
836
|
+
const nameMatch = NAME_PREFIX.exec(trimmed);
|
|
837
|
+
const name = nameMatch ? nameMatch[1] : null;
|
|
838
|
+
const body = (nameMatch ? nameMatch[2] : trimmed).trim();
|
|
839
|
+
if (!body) {
|
|
840
|
+
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
841
|
+
}
|
|
842
|
+
const streamMatch = STREAMABLE_PREFIX.exec(body);
|
|
843
|
+
if (streamMatch) {
|
|
844
|
+
return { transport: "streamable-http", name, url: streamMatch[1] };
|
|
845
|
+
}
|
|
846
|
+
if (HTTP_URL.test(body)) {
|
|
847
|
+
return { transport: "sse", name, url: body };
|
|
848
|
+
}
|
|
849
|
+
const argv = shellSplit(body);
|
|
850
|
+
if (argv.length === 0) {
|
|
851
|
+
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
852
|
+
}
|
|
853
|
+
const [command, ...args] = argv;
|
|
854
|
+
return { transport: "stdio", name, command, args };
|
|
855
|
+
}
|
|
856
|
+
|
|
775
857
|
// src/config.ts
|
|
776
858
|
var BUILTIN_TYPE_DOCS = {
|
|
777
859
|
user: "role / skills / preferences",
|
|
@@ -809,6 +891,13 @@ function memoryTypeDefaults(typeName, cfg = readConfig()) {
|
|
|
809
891
|
if (found.expires) out.expires = found.expires;
|
|
810
892
|
return out;
|
|
811
893
|
}
|
|
894
|
+
var DEFAULT_METASO_API_KEY = "mk-E384C1DD5E8501BB7EFE27C949AFDE5B";
|
|
895
|
+
function loadMetasoApiKey(path2 = defaultConfigPath()) {
|
|
896
|
+
if (process.env.METASO_API_KEY) return process.env.METASO_API_KEY;
|
|
897
|
+
const cfg = readConfig(path2).metasoApiKey;
|
|
898
|
+
if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
|
|
899
|
+
return DEFAULT_METASO_API_KEY;
|
|
900
|
+
}
|
|
812
901
|
function defaultConfigPath() {
|
|
813
902
|
return join(homedir(), ".reasonix", "config.json");
|
|
814
903
|
}
|
|
@@ -850,9 +939,44 @@ function saveBaseUrl(url, path2 = defaultConfigPath()) {
|
|
|
850
939
|
}
|
|
851
940
|
writeConfig(cfg, path2);
|
|
852
941
|
}
|
|
942
|
+
function resolveSkillPath(raw, baseDir) {
|
|
943
|
+
const homeExpanded = expandCurrentUserHome(raw.trim());
|
|
944
|
+
return resolve(isAbsolute(homeExpanded) ? homeExpanded : join(baseDir, homeExpanded));
|
|
945
|
+
}
|
|
946
|
+
function normalizeSkillPathEntries(paths, baseDir) {
|
|
947
|
+
const out = [];
|
|
948
|
+
const seen = /* @__PURE__ */ new Set();
|
|
949
|
+
for (const value of paths) {
|
|
950
|
+
if (typeof value !== "string") continue;
|
|
951
|
+
const raw = value.trim();
|
|
952
|
+
if (!raw) continue;
|
|
953
|
+
const resolved = resolveSkillPath(raw, baseDir);
|
|
954
|
+
const key = skillPathKey(resolved);
|
|
955
|
+
if (seen.has(key)) continue;
|
|
956
|
+
seen.add(key);
|
|
957
|
+
out.push({ raw, resolved });
|
|
958
|
+
}
|
|
959
|
+
return out;
|
|
960
|
+
}
|
|
961
|
+
function resolveSkillPaths(paths, baseDir) {
|
|
962
|
+
return normalizeSkillPathEntries(paths, baseDir).map((entry) => entry.resolved);
|
|
963
|
+
}
|
|
964
|
+
function skillPathKey(path2) {
|
|
965
|
+
return process.platform === "win32" ? path2.toLowerCase() : path2;
|
|
966
|
+
}
|
|
967
|
+
function expandCurrentUserHome(path2) {
|
|
968
|
+
if (path2 === "~") return homedir();
|
|
969
|
+
if (path2.startsWith("~/") || path2.startsWith("~\\")) return join(homedir(), path2.slice(2));
|
|
970
|
+
return path2;
|
|
971
|
+
}
|
|
972
|
+
function loadResolvedSkillPaths(baseDir = process.cwd(), path2 = defaultConfigPath()) {
|
|
973
|
+
const raw = readConfig(path2).skills?.paths;
|
|
974
|
+
return Array.isArray(raw) ? resolveSkillPaths(raw, baseDir) : [];
|
|
975
|
+
}
|
|
853
976
|
function webSearchEngine(path2 = defaultConfigPath()) {
|
|
854
977
|
const cfg = readConfig(path2).webSearchEngine;
|
|
855
978
|
if (cfg === "searxng") return "searxng";
|
|
979
|
+
if (cfg === "metaso") return "metaso";
|
|
856
980
|
return "mojeek";
|
|
857
981
|
}
|
|
858
982
|
function webSearchEndpoint(path2 = defaultConfigPath()) {
|
|
@@ -952,6 +1076,16 @@ var EN = {
|
|
|
952
1076
|
update: "Check for a newer Reasonix and install it.",
|
|
953
1077
|
index: "Build (or incrementally refresh) a local semantic search index."
|
|
954
1078
|
},
|
|
1079
|
+
stats: {
|
|
1080
|
+
usageHint: "run `reasonix chat`, `reasonix code`, or `reasonix run <task>` \u2014 every turn",
|
|
1081
|
+
usageDetail: "appends one line to the log and `reasonix stats` will roll it up."
|
|
1082
|
+
},
|
|
1083
|
+
run: {
|
|
1084
|
+
missingApiKey: "DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
|
|
1085
|
+
},
|
|
1086
|
+
sessions: {
|
|
1087
|
+
emptyHint: "no saved sessions yet \u2014 run `reasonix chat` (sessions are auto-saved unless --no-session)."
|
|
1088
|
+
},
|
|
955
1089
|
ui: {
|
|
956
1090
|
welcome: "Run `reasonix` any time to start chatting \u2014 your settings are remembered.",
|
|
957
1091
|
taglineChat: "DeepSeek-native agent",
|
|
@@ -966,6 +1100,7 @@ var EN = {
|
|
|
966
1100
|
applied: "applied",
|
|
967
1101
|
rejected: "rejected",
|
|
968
1102
|
noDashboard: "Suppress the auto-launched embedded web dashboard.",
|
|
1103
|
+
openDashboardHint: "Open the dashboard URL in your default browser as soon as the server is ready. No-op when --no-dashboard is set.",
|
|
969
1104
|
dashboardPortHint: "Pin the dashboard to a fixed port (1\u201365535). Stable across restarts \u2014 required for SSH tunnels. Default: ephemeral.",
|
|
970
1105
|
dashboardPortInvalid: "\u25B2 ignoring --dashboard-port={value} (must be an integer 1\u201365535) \u2014 falling back to ephemeral",
|
|
971
1106
|
dashboardAutoStartFailed: "\u25B2 dashboard auto-start failed ({reason}) \u2014 try /dashboard, or pass --no-dashboard to silence",
|
|
@@ -1130,7 +1265,13 @@ var EN = {
|
|
|
1130
1265
|
jsonHintCatalog: "output as JSON",
|
|
1131
1266
|
jsonHintReport: "output the inspection report as JSON",
|
|
1132
1267
|
modelOverrideFlash: "override the model (default: deepseek-v4-flash)",
|
|
1133
|
-
skipConfirmHint: "skip the confirmation prompt"
|
|
1268
|
+
skipConfirmHint: "skip the confirmation prompt",
|
|
1269
|
+
yoloHint: "auto-approve plan checkpoints for this invocation (equivalent to editMode=yolo without mutating config)"
|
|
1270
|
+
},
|
|
1271
|
+
code: {
|
|
1272
|
+
workspaceConflict: "\u26A0 workspace contains another agent platform's files ({platforms}). Reasonix Code may read them as project content; relaunch with --dir <your-project> if that's not what you want.\n",
|
|
1273
|
+
systemAppendEmpty: "--system-append is empty \u2014 no prompt text will be appended\n",
|
|
1274
|
+
systemAppendFileReadError: 'Error: cannot read --system-append-file "{filePath}": {errorDetails}\n'
|
|
1134
1275
|
},
|
|
1135
1276
|
slash: {
|
|
1136
1277
|
help: { description: "show the full command reference" },
|
|
@@ -1176,8 +1317,8 @@ var EN = {
|
|
|
1176
1317
|
argsHint: "[list|show <name>|forget <name>|clear <scope> confirm]"
|
|
1177
1318
|
},
|
|
1178
1319
|
skill: {
|
|
1179
|
-
description: "list / run user skills (
|
|
1180
|
-
argsHint: "[list|show <name>|<name> [args]]"
|
|
1320
|
+
description: "list / run user skills (project + custom + global + builtin)",
|
|
1321
|
+
argsHint: "[list|paths|show <name>|<name> [args]]"
|
|
1181
1322
|
},
|
|
1182
1323
|
hooks: {
|
|
1183
1324
|
description: "list active hooks (settings.json under .reasonix/) \xB7 reload re-reads from disk",
|
|
@@ -1219,6 +1360,10 @@ var EN = {
|
|
|
1219
1360
|
argsHint: "[N]"
|
|
1220
1361
|
},
|
|
1221
1362
|
sessions: { description: "list saved sessions (current marked with \u25B8)" },
|
|
1363
|
+
qq: {
|
|
1364
|
+
description: "connect, inspect, or disconnect the QQ channel for this session",
|
|
1365
|
+
argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
|
|
1366
|
+
},
|
|
1222
1367
|
setup: { description: "reminds you to exit and run `reasonix setup`" },
|
|
1223
1368
|
semantic: {
|
|
1224
1369
|
description: "show semantic_search status \u2014 built? Ollama installed? how to enable"
|
|
@@ -1276,6 +1421,14 @@ var EN = {
|
|
|
1276
1421
|
logs: {
|
|
1277
1422
|
description: "tail a background job's output (default last 80 lines)",
|
|
1278
1423
|
argsHint: "<id> [lines]"
|
|
1424
|
+
},
|
|
1425
|
+
btw: {
|
|
1426
|
+
description: "ask a quick side question \u2014 answered from a blank slate, never added to the conversation context",
|
|
1427
|
+
argsHint: "<question>"
|
|
1428
|
+
},
|
|
1429
|
+
"search-engine": {
|
|
1430
|
+
description: "switch web search backend \u2014 mojeek (default, no deps), searxng (self-hosted), or metaso (free quota 100/d)",
|
|
1431
|
+
argsHint: "<mojeek|searxng|metaso> [<endpoint>]"
|
|
1279
1432
|
}
|
|
1280
1433
|
},
|
|
1281
1434
|
wizard: {
|
|
@@ -1469,16 +1622,15 @@ var EN = {
|
|
|
1469
1622
|
budgetExhausted: "session budget exhausted \u2014 spent ${spent} \u2265 cap ${cap}. Bump the cap with /budget <usd>, clear it with /budget off, or end the session.",
|
|
1470
1623
|
budget80Pct: "\u25B2 budget 80% used \u2014 ${spent} of ${cap}. Next turn or two likely trips the cap.",
|
|
1471
1624
|
proArmed: "\u21E7 /pro armed \u2014 this turn runs on deepseek-v4-pro (one-shot \xB7 disarms after turn)",
|
|
1472
|
-
abortedAtIter: "aborted at iter {iter}
|
|
1625
|
+
abortedAtIter: "aborted at iter {iter} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)",
|
|
1473
1626
|
toolUploadStatus: "tool result uploaded \xB7 model thinking before next response\u2026",
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
preflightNoFold: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) and nothing left to
|
|
1627
|
+
preflightTruncateStatus: "preflight: context near full, truncating oldest history\u2026",
|
|
1628
|
+
preflightTruncated: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) \u2014 truncated {beforeMessages} messages \u2192 {afterMessages}. Sending.",
|
|
1629
|
+
preflightTruncatedStillFull: "preflight: request still ~{estimate}/{ctxMax} tokens ({pct}%) after truncating {beforeMessages} messages \u2192 {afterMessages}. DeepSeek will likely 400. Run /clear or /new to start fresh.",
|
|
1630
|
+
preflightNoFold: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) and nothing left to truncate \u2014 DeepSeek will likely 400. Run /clear or /new to start fresh.",
|
|
1478
1631
|
flashEscalation: "\u21E7 flash requested escalation \u2014 retrying this turn on {model}{reasonSuffix}",
|
|
1479
1632
|
harvestStatus: "extracting plan state from reasoning\u2026",
|
|
1480
1633
|
autoEscalation: "\u21E7 auto-escalating to {model} for the rest of this turn \u2014 flash hit {breakdown}. Next turn falls back to {fallback} unless /pro is armed.",
|
|
1481
|
-
readOnlyLoopEscalation: "\u21E7 auto-escalating to {model} \u2014 flash made {n} consecutive read-only calls without producing an edit or final answer. Next turn falls back to {fallback} unless /pro is armed.",
|
|
1482
1634
|
repeatToolCallWarning: "Caught a repeated tool call \u2014 let the model see the issue and retry with a different approach.",
|
|
1483
1635
|
stormStuck: "Stopped a stuck retry loop \u2014 the model kept calling the same tool with identical args after a self-correction nudge. Try /retry, rephrase, or rule out the underlying blocker.",
|
|
1484
1636
|
stormSuppressed: "Suppressed {count} repeated tool call(s) \u2014 same name + args fired 3+ times.",
|
|
@@ -1504,11 +1656,9 @@ var EN = {
|
|
|
1504
1656
|
reasonAborted: "[aborted by user (Esc) \u2014 summarizing what I found so far]",
|
|
1505
1657
|
reasonContextGuard: "[context budget running low \u2014 summarizing before the next call would overflow]",
|
|
1506
1658
|
reasonStuck: "[stuck on a repeated tool call \u2014 explaining what was tried and what's blocking progress]",
|
|
1507
|
-
reasonBudget: "[tool-call budget ({iterCap}) reached \u2014 forcing summary from what I found]",
|
|
1508
1659
|
labelAborted: "aborted by user",
|
|
1509
1660
|
labelContextGuard: "context-guard triggered (prompt > 80% of window)",
|
|
1510
|
-
labelStuck: "stuck (repeated tool call suppressed by storm-breaker)"
|
|
1511
|
-
labelBudget: "tool-call budget ({iterCap}) reached"
|
|
1661
|
+
labelStuck: "stuck (repeated tool call suppressed by storm-breaker)"
|
|
1512
1662
|
},
|
|
1513
1663
|
handlers: {
|
|
1514
1664
|
basic: {
|
|
@@ -1818,11 +1968,13 @@ var EN = {
|
|
|
1818
1968
|
usageMojeek: " /search-engine mojeek use Mojeek (default, no external deps)",
|
|
1819
1969
|
usageSearxng: " /search-engine searxng use SearXNG at default endpoint",
|
|
1820
1970
|
usageSearxngUrl: " /search-engine searxng <url> use SearXNG at custom endpoint",
|
|
1971
|
+
usageMetaso: " /search-engine metaso use Metaso API (100/d free, configure your own API key for more)",
|
|
1821
1972
|
alias: "Alias: /se",
|
|
1822
1973
|
searxngInfo: "SearXNG is a self-hosted metasearch engine (https://github.com/searxng/searxng).",
|
|
1823
1974
|
searxngInstall: "Install it with: docker run -d -p 8080:8080 searxng/searxng",
|
|
1824
1975
|
switched: 'Switched web search engine to "{engine}".{note}',
|
|
1825
1976
|
switchedSearxngNote: " Make sure SearXNG is running at {endpoint}.",
|
|
1977
|
+
switchedMetasoNote: " There is a daily quota of 100 (configure your own API key for higher limits).",
|
|
1826
1978
|
confirmed: '\u2713 Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
|
|
1827
1979
|
confirmedDetail: " ({endpoint})"
|
|
1828
1980
|
},
|
|
@@ -1842,7 +1994,17 @@ var EN = {
|
|
|
1842
1994
|
runInfo: "\u25B8 running skill: {name}{args}",
|
|
1843
1995
|
newUsage: "usage: /skill new <name> [--global]",
|
|
1844
1996
|
newCreated: "\u25B8 created skill: {name}\n {path}\n edit it, then `/skill {name}` to invoke",
|
|
1845
|
-
newError: "\u25B2 /skill new failed: {reason}"
|
|
1997
|
+
newError: "\u25B2 /skill new failed: {reason}",
|
|
1998
|
+
pathsHeader: "Skill paths (priority order):",
|
|
1999
|
+
pathsPriority: "Priority: project > custom paths in config order > global > builtin. Changes affect the system prompt on next /new or new session.",
|
|
2000
|
+
pathsUsage: "usage: /skill paths [list]\n /skill paths add <path>\n /skill paths remove <path|N>",
|
|
2001
|
+
pathsAddUsage: "usage: /skill paths add <path>",
|
|
2002
|
+
pathsRemoveUsage: "usage: /skill paths remove <path|N>",
|
|
2003
|
+
pathsAdded: "\u25B8 added custom skills path: {path}",
|
|
2004
|
+
pathsAlready: "\u25B8 custom skills path already configured: {path}",
|
|
2005
|
+
pathsRemoved: "\u25B8 removed custom skills path: {path}",
|
|
2006
|
+
pathsRemoveNotFound: "\u25B8 no custom skills path matches: {target}",
|
|
2007
|
+
pathsRestartHint: "The current session's system prompt is unchanged; run /new or start a new session to refresh the skills index."
|
|
1846
2008
|
}
|
|
1847
2009
|
},
|
|
1848
2010
|
statusBar: {
|
|
@@ -1859,7 +2021,8 @@ var EN = {
|
|
|
1859
2021
|
mb: " MB",
|
|
1860
2022
|
evt: " evt",
|
|
1861
2023
|
editsLabel: "edits:",
|
|
1862
|
-
mcpLoading: "MCP"
|
|
2024
|
+
mcpLoading: "MCP",
|
|
2025
|
+
ctx: "ctx"
|
|
1863
2026
|
},
|
|
1864
2027
|
editMode: {
|
|
1865
2028
|
plan: "PLAN MODE",
|
|
@@ -1888,7 +2051,8 @@ var EN = {
|
|
|
1888
2051
|
editorNoRawMode: "external editor unavailable \u2014 stdin doesn't support raw-mode toggling on this terminal",
|
|
1889
2052
|
editorFailed: "external editor:",
|
|
1890
2053
|
editorMissing: "no $EDITOR / $VISUAL / $GIT_EDITOR set \u2014 export one (e.g. `export EDITOR=nano`) and retry",
|
|
1891
|
-
editorExited: "editor exited with code {code}"
|
|
2054
|
+
editorExited: "editor exited with code {code}",
|
|
2055
|
+
typeaheadStaged: "\u25B8 {count} line(s) staged \xB7 esc recall"
|
|
1892
2056
|
},
|
|
1893
2057
|
pathConfirm: {
|
|
1894
2058
|
title: "Outside-sandbox path",
|
|
@@ -2058,6 +2222,12 @@ var EN = {
|
|
|
2058
2222
|
endpointMustBeHttp: "web_search: SearXNG endpoint must be http(s), got {protocol} \u2014 try: set a valid URL with /search-endpoint http://host:port",
|
|
2059
2223
|
cannotReach: "web_search: Cannot reach SearXNG server at {endpoint} \u2014 try: install and start SearXNG (https://github.com/searxng/searxng, e.g. `docker run -d -p 8080:8080 searxng/searxng`), or switch to the default engine with /search-engine mojeek",
|
|
2060
2224
|
searxngNoResults: "web_search: 0 results but SearXNG response doesn't look like an empty results page ({chars} chars) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine mojeek",
|
|
2225
|
+
metasoDailyLimit: "web_search: daily search limit reached for the default API key \u2014 set your own METASO_API_KEY env var or get one at https://metaso.cn/search-api/playground",
|
|
2226
|
+
metasoUnauthorized: "web_search: Metaso API key rejected \u2014 check METASO_API_KEY or get one at https://metaso.cn/search-api/playground",
|
|
2227
|
+
metasoRateLimit: "web_search: Metaso rate-limited \u2014 wait and retry, or get your own API key at https://metaso.cn/search-api/playground",
|
|
2228
|
+
metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
|
|
2229
|
+
metasoParseError: "web_search: Metaso returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2230
|
+
metasoApiError: "web_search: Metaso API error (code {code}: {message}) \u2014 try again later",
|
|
2061
2231
|
fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
|
|
2062
2232
|
fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
|
|
2063
2233
|
fetchForbidden403: "web_fetch 403 for {url} \u2014 try: the host is blocking this client; the page may require login or block bots \u2014 use web_search snippets instead",
|
|
@@ -2232,7 +2402,9 @@ var EN = {
|
|
|
2232
2402
|
reconnect: "reconnect\u2026",
|
|
2233
2403
|
initDetail: "initialise \u2192 tools/list \u2192 resources/list",
|
|
2234
2404
|
reconnectDetail: "tearing down \xB7 re-handshake \xB7 listing tools",
|
|
2235
|
-
disabledDetail: "via /mcp disable {name}"
|
|
2405
|
+
disabledDetail: "via /mcp disable {name}",
|
|
2406
|
+
failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
|
|
2407
|
+
failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config."
|
|
2236
2408
|
},
|
|
2237
2409
|
checkpointPicker: {
|
|
2238
2410
|
title: "restore a checkpoint \u2014 {workspace}",
|
|
@@ -2316,6 +2488,16 @@ var zhCN = {
|
|
|
2316
2488
|
update: "\u68C0\u67E5\u8F83\u65B0\u7248\u672C\u7684 Reasonix \u5E76\u5B89\u88C5\u3002",
|
|
2317
2489
|
index: "\u6784\u5EFA\uFF08\u6216\u589E\u91CF\u5237\u65B0\uFF09\u672C\u5730\u8BED\u4E49\u641C\u7D22\u7D22\u5F15\u3002"
|
|
2318
2490
|
},
|
|
2491
|
+
stats: {
|
|
2492
|
+
usageHint: "\u8FD0\u884C `reasonix chat`\u3001`reasonix code` \u6216 `reasonix run <task>` \u2014 \u6BCF\u6B21\u5BF9\u8BDD\u90FD\u4F1A\u8BB0\u5F55",
|
|
2493
|
+
usageDetail: "\u6BCF\u6B21\u5BF9\u8BDD\u5728\u65E5\u5FD7\u4E2D\u8FFD\u52A0\u4E00\u884C\uFF0C`reasonix stats` \u4F1A\u5C06\u5176\u6C47\u603B\u7EDF\u8BA1\u3002"
|
|
2494
|
+
},
|
|
2495
|
+
run: {
|
|
2496
|
+
missingApiKey: "\u672A\u8BBE\u7F6E DEEPSEEK_API_KEY \u4E14\u6807\u51C6\u8F93\u5165\u4E0D\u662F TTY\uFF08\u65E0\u6CD5\u4EA4\u4E92\u5F0F\u8F93\u5165\uFF09\u3002\n\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5148\u8FD0\u884C `reasonix chat` \u4EA4\u4E92\u4E00\u6B21\u4EE5\u4FDD\u5B58\u5BC6\u94A5\u3002\n"
|
|
2497
|
+
},
|
|
2498
|
+
sessions: {
|
|
2499
|
+
emptyHint: "\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD \u2014 \u8FD0\u884C `reasonix chat`\uFF08\u4F1A\u8BDD\u4F1A\u81EA\u52A8\u4FDD\u5B58\uFF0C\u9664\u975E\u4F7F\u7528\u4E86 --no-session\uFF09\u3002"
|
|
2500
|
+
},
|
|
2319
2501
|
ui: {
|
|
2320
2502
|
welcome: "\u968F\u65F6\u8FD0\u884C `reasonix` \u5F00\u59CB\u804A\u5929 \u2014 \u60A8\u7684\u8BBE\u7F6E\u5C06\u88AB\u8BB0\u4F4F\u3002",
|
|
2321
2503
|
taglineChat: "DeepSeek \u539F\u751F\u667A\u80FD\u4F53",
|
|
@@ -2330,6 +2512,7 @@ var zhCN = {
|
|
|
2330
2512
|
applied: "\u5DF2\u5E94\u7528",
|
|
2331
2513
|
rejected: "\u5DF2\u62D2\u7EDD",
|
|
2332
2514
|
noDashboard: "\u7981\u6B62\u81EA\u52A8\u542F\u52A8\u5D4C\u5165\u5F0F Web \u4EEA\u8868\u677F\u3002",
|
|
2515
|
+
openDashboardHint: "\u670D\u52A1\u5C31\u7EEA\u540E\u7ACB\u5373\u5728\u9ED8\u8BA4\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EEA\u8868\u677F\u5730\u5740\u3002\u8BBE\u7F6E\u4E86 --no-dashboard \u65F6\u4E0D\u751F\u6548\u3002",
|
|
2333
2516
|
dashboardPortHint: "\u5C06\u4EEA\u8868\u677F\u7ED1\u5B9A\u5230\u56FA\u5B9A\u7AEF\u53E3 (1\u201365535)\u3002\u91CD\u542F\u540E\u4FDD\u6301\u7A33\u5B9A \u2014 SSH \u96A7\u9053\u8BBF\u95EE\u5FC5\u9700\u3002\u9ED8\u8BA4\u4E3A\u4E34\u65F6\u7AEF\u53E3\u3002",
|
|
2334
2517
|
dashboardPortInvalid: "\u25B2 \u5FFD\u7565 --dashboard-port={value} (\u5FC5\u987B\u4E3A 1\u201365535 \u4E4B\u95F4\u7684\u6574\u6570) \u2014 \u56DE\u9000\u5230\u4E34\u65F6\u7AEF\u53E3",
|
|
2335
2518
|
dashboardAutoStartFailed: "\u25B2 \u4EEA\u8868\u677F\u81EA\u52A8\u542F\u52A8\u5931\u8D25 ({reason}) \u2014 \u5C1D\u8BD5 /dashboard\uFF0C\u6216\u4F20\u9012 --no-dashboard \u4EE5\u9759\u9ED8",
|
|
@@ -2491,7 +2674,13 @@ var zhCN = {
|
|
|
2491
2674
|
jsonHintCatalog: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA",
|
|
2492
2675
|
jsonHintReport: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u68C0\u67E5\u62A5\u544A",
|
|
2493
2676
|
modelOverrideFlash: "\u8986\u76D6\u6A21\u578B\uFF08\u9ED8\u8BA4\uFF1Adeepseek-v4-flash\uFF09",
|
|
2494
|
-
skipConfirmHint: "\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A"
|
|
2677
|
+
skipConfirmHint: "\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A",
|
|
2678
|
+
yoloHint: "\u81EA\u52A8\u6279\u51C6\u672C\u6B21\u8C03\u7528\u7684\u8BA1\u5212\u68C0\u67E5\u70B9\uFF08\u7B49\u540C\u4E8E editMode=yolo\uFF0C\u4F46\u4E0D\u4FEE\u6539\u914D\u7F6E\u6587\u4EF6\uFF09"
|
|
2679
|
+
},
|
|
2680
|
+
code: {
|
|
2681
|
+
workspaceConflict: "\u26A0 \u5DE5\u4F5C\u533A\u5305\u542B\u53E6\u4E00\u4E2A\u667A\u80FD\u4F53\u5E73\u53F0\u7684\u6587\u4EF6 ({platforms})\u3002Reasonix Code \u53EF\u80FD\u4F1A\u5C06\u5176\u4F5C\u4E3A\u9879\u76EE\u5185\u5BB9\u8BFB\u53D6\uFF1B\u5982\u679C\u4E0D\u662F\u60A8\u60F3\u8981\u7684\uFF0C\u8BF7\u4F7F\u7528 --dir <your-project> \u91CD\u65B0\u542F\u52A8\u3002\n",
|
|
2682
|
+
systemAppendEmpty: "--system-append \u4E3A\u7A7A \u2014 \u4E0D\u4F1A\u8FFD\u52A0\u4EFB\u4F55\u63D0\u793A\u6587\u672C\n",
|
|
2683
|
+
systemAppendFileReadError: '\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 --system-append-file "{filePath}"\uFF1A{errorDetails}\n'
|
|
2495
2684
|
},
|
|
2496
2685
|
slash: {
|
|
2497
2686
|
help: { description: "\u663E\u793A\u5B8C\u6574\u547D\u4EE4\u53C2\u8003" },
|
|
@@ -2537,8 +2726,8 @@ var zhCN = {
|
|
|
2537
2726
|
argsHint: "[list|show <name>|forget <name>|clear <scope> confirm]"
|
|
2538
2727
|
},
|
|
2539
2728
|
skill: {
|
|
2540
|
-
description: "\u5217\u51FA / \u8FD0\u884C\u7528\u6237\u6280\u80FD\uFF08
|
|
2541
|
-
argsHint: "[list|show <name>|<name> [args]]"
|
|
2729
|
+
description: "\u5217\u51FA / \u8FD0\u884C\u7528\u6237\u6280\u80FD\uFF08\u9879\u76EE + \u81EA\u5B9A\u4E49 + \u5168\u5C40 + \u5185\u7F6E\uFF09",
|
|
2730
|
+
argsHint: "[list|paths|show <name>|<name> [args]]"
|
|
2542
2731
|
},
|
|
2543
2732
|
hooks: {
|
|
2544
2733
|
description: "\u5217\u51FA\u6D3B\u8DC3\u7684 hooks\uFF08.reasonix/ \u4E0B\u7684 settings.json\uFF09\xB7 reload \u4ECE\u78C1\u76D8\u91CD\u65B0\u8BFB\u53D6",
|
|
@@ -2582,6 +2771,10 @@ var zhCN = {
|
|
|
2582
2771
|
argsHint: "[N]"
|
|
2583
2772
|
},
|
|
2584
2773
|
sessions: { description: "\u5217\u51FA\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD\uFF08\u5F53\u524D\u6807\u8BB0\u4E3A \u25B8\uFF09" },
|
|
2774
|
+
qq: {
|
|
2775
|
+
description: "\u8FDE\u63A5\u3001\u67E5\u770B\u6216\u65AD\u5F00\u5F53\u524D\u4F1A\u8BDD\u7684 QQ \u901A\u9053",
|
|
2776
|
+
argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
|
|
2777
|
+
},
|
|
2585
2778
|
setup: { description: "\u63D0\u9192\u60A8\u9000\u51FA\u5E76\u8FD0\u884C `reasonix setup`" },
|
|
2586
2779
|
semantic: {
|
|
2587
2780
|
description: "\u663E\u793A semantic_search \u72B6\u6001 \u2014 \u5DF2\u6784\u5EFA\uFF1FOllama \u5DF2\u5B89\u88C5\uFF1F\u5982\u4F55\u542F\u7528"
|
|
@@ -2641,6 +2834,14 @@ var zhCN = {
|
|
|
2641
2834
|
logs: {
|
|
2642
2835
|
description: "\u8DDF\u8E2A\u540E\u53F0\u4F5C\u4E1A\u7684\u8F93\u51FA\uFF08\u9ED8\u8BA4\u6700\u540E 80 \u884C\uFF09",
|
|
2643
2836
|
argsHint: "<id> [lines]"
|
|
2837
|
+
},
|
|
2838
|
+
btw: {
|
|
2839
|
+
description: "\u987A\u4FBF\u95EE\u4E00\u4E0B \u2014 \u4ECE\u7A7A\u767D\u4E0A\u4E0B\u6587\u56DE\u7B54\uFF0C\u4E0D\u5199\u5165\u4F1A\u8BDD\u5386\u53F2",
|
|
2840
|
+
argsHint: "<question>"
|
|
2841
|
+
},
|
|
2842
|
+
"search-engine": {
|
|
2843
|
+
description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4F9D\u8D56\uFF09\u3001searxng\uFF08\u81EA\u6258\u7BA1\uFF09\u6216 metaso\uFF08\u6BCF\u65E5 100 \u6B21\u514D\u8D39\u989D\u5EA6\uFF09",
|
|
2844
|
+
argsHint: "<mojeek|searxng|metaso> [<endpoint>]"
|
|
2644
2845
|
}
|
|
2645
2846
|
},
|
|
2646
2847
|
wizard: {
|
|
@@ -2834,16 +3035,15 @@ var zhCN = {
|
|
|
2834
3035
|
budgetExhausted: "\u4F1A\u8BDD\u9884\u7B97\u5DF2\u7528\u5B8C \u2014 \u5DF2\u82B1\u8D39 ${spent} \u2265 \u4E0A\u9650 ${cap}\u3002\u7528 /budget <usd> \u63D0\u9AD8\u4E0A\u9650\uFF0C/budget off \u6E05\u9664\u4E0A\u9650\uFF0C\u6216\u7ED3\u675F\u4F1A\u8BDD\u3002",
|
|
2835
3036
|
budget80Pct: "\u25B2 \u9884\u7B97\u5DF2\u7528 80% \u2014 ${spent} / ${cap}\u3002\u4E0B\u4E00\u4E24\u8F6E\u53EF\u80FD\u5C31\u89E6\u9876\u3002",
|
|
2836
3037
|
proArmed: "\u21E7 /pro \u5DF2\u88C5\u5907 \u2014 \u672C\u8F6E\u4F7F\u7528 deepseek-v4-pro\uFF08\u4E00\u6B21\u6027 \xB7 \u672C\u8F6E\u540E\u81EA\u52A8\u89E3\u9664\uFF09",
|
|
2837
|
-
abortedAtIter: "\u5728\u7B2C {iter}
|
|
3038
|
+
abortedAtIter: "\u5728\u7B2C {iter} \u6B21\u5DE5\u5177\u8C03\u7528\u5904\u4E2D\u65AD \u2014 \u672A\u751F\u6210\u603B\u7ED3\u5373\u505C\u6B62\uFF08\u6309 \u2191 + Enter \u6216 /retry \u6062\u590D\uFF09",
|
|
2838
3039
|
toolUploadStatus: "\u5DE5\u5177\u7ED3\u679C\u5DF2\u4E0A\u4F20 \xB7 \u6A21\u578B\u5728\u751F\u6210\u4E0B\u4E00\u6761\u54CD\u5E94\u524D\u601D\u8003\u4E2D\u2026",
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
preflightNoFold: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u4E14\u6CA1\u6709\u53EF\
|
|
3040
|
+
preflightTruncateStatus: "\u9884\u68C0\uFF1A\u4E0A\u4E0B\u6587\u63A5\u8FD1\u4E0A\u9650\uFF0C\u6B63\u5728\u88C1\u526A\u6700\u65E9\u5386\u53F2\u2026",
|
|
3041
|
+
preflightTruncated: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u2014 \u5DF2\u88C1\u526A {beforeMessages} \u6761\u6D88\u606F \u2192 {afterMessages}\u3002\u53D1\u9001\u4E2D\u3002",
|
|
3042
|
+
preflightTruncatedStillFull: "\u9884\u68C0\uFF1A\u88C1\u526A {beforeMessages} \u6761\u6D88\u606F \u2192 {afterMessages} \u540E\uFF0C\u8BF7\u6C42\u4ECD\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
|
|
3043
|
+
preflightNoFold: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u4E14\u6CA1\u6709\u53EF\u88C1\u526A\u7684\u5185\u5BB9 \u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
|
|
2843
3044
|
flashEscalation: "\u21E7 flash \u8BF7\u6C42\u5347\u7EA7 \u2014 \u672C\u8F6E\u6539\u7528 {model}{reasonSuffix}",
|
|
2844
3045
|
harvestStatus: "\u6B63\u5728\u4ECE\u63A8\u7406\u8FC7\u7A0B\u63D0\u53D6\u8BA1\u5212\u72B6\u6001\u2026",
|
|
2845
3046
|
autoEscalation: "\u21E7 \u672C\u8F6E\u5269\u4F59\u8C03\u7528\u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u547D\u4E2D {breakdown}\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
|
|
2846
|
-
readOnlyLoopEscalation: "\u21E7 \u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u8FDE\u7EED {n} \u6B21\u53EA\u8BFB\u8C03\u7528\uFF0C\u672A\u4EA7\u51FA\u4FEE\u6539\u6216\u6700\u7EC8\u7B54\u6848\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
|
|
2847
3047
|
repeatToolCallWarning: "\u62E6\u622A\u5230\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u8BA9\u6A21\u578B\u5BDF\u89C9\u95EE\u9898\u5E76\u6362\u79CD\u65B9\u5F0F\u91CD\u8BD5\u3002",
|
|
2848
3048
|
stormStuck: "\u5DF2\u505C\u6B62\u5361\u6B7B\u7684\u91CD\u8BD5\u5FAA\u73AF \u2014 \u6A21\u578B\u5728\u81EA\u7EA0\u63D0\u793A\u540E\u4ECD\u4EE5\u76F8\u540C\u53C2\u6570\u91CD\u590D\u8C03\u7528\u540C\u4E00\u5DE5\u5177\u3002\u8BF7\u5C1D\u8BD5 /retry\u3001\u6362\u79CD\u8BF4\u6CD5\uFF0C\u6216\u6392\u67E5\u5E95\u5C42\u963B\u585E\u3002",
|
|
2849
3049
|
stormSuppressed: "\u5DF2\u6291\u5236 {count} \u6B21\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u540C\u4E00\u540D\u79F0 + \u53C2\u6570\u89E6\u53D1 3 \u6B21\u4EE5\u4E0A\u3002",
|
|
@@ -2869,11 +3069,9 @@ var zhCN = {
|
|
|
2869
3069
|
reasonAborted: "[\u7528\u6237\u5DF2\u4E2D\u65AD\uFF08Esc\uFF09 \u2014 \u6B63\u5728\u603B\u7ED3\u5230\u76EE\u524D\u4E3A\u6B62\u7684\u53D1\u73B0]",
|
|
2870
3070
|
reasonContextGuard: "[\u4E0A\u4E0B\u6587\u989D\u5EA6\u5373\u5C06\u8017\u5C3D \u2014 \u5728\u4E0B\u4E00\u6B21\u8C03\u7528\u6EA2\u51FA\u4E4B\u524D\u5148\u603B\u7ED3]",
|
|
2871
3071
|
reasonStuck: "[\u5361\u5728\u91CD\u590D\u7684\u5DE5\u5177\u8C03\u7528\u4E0A \u2014 \u8BF4\u660E\u5DF2\u5C1D\u8BD5\u7684\u65B9\u6CD5\u4EE5\u53CA\u963B\u585E\u70B9]",
|
|
2872
|
-
reasonBudget: "[\u5DE5\u5177\u8C03\u7528\u914D\u989D\uFF08{iterCap}\uFF09\u5DF2\u7528\u5C3D \u2014 \u57FA\u4E8E\u5DF2\u53D1\u73B0\u7684\u5185\u5BB9\u5F3A\u5236\u603B\u7ED3]",
|
|
2873
3072
|
labelAborted: "\u7528\u6237\u4E2D\u65AD",
|
|
2874
3073
|
labelContextGuard: "\u89E6\u53D1\u4E0A\u4E0B\u6587\u4FDD\u62A4\uFF08prompt > 80% \u7A97\u53E3\uFF09",
|
|
2875
|
-
labelStuck: "\u5361\u6B7B\uFF08\u91CD\u590D\u5DE5\u5177\u8C03\u7528\u88AB\u53CD\u98CE\u66B4\u673A\u5236\u6291\u5236\uFF09"
|
|
2876
|
-
labelBudget: "\u5DE5\u5177\u8C03\u7528\u914D\u989D\uFF08{iterCap}\uFF09\u5DF2\u7528\u5C3D"
|
|
3074
|
+
labelStuck: "\u5361\u6B7B\uFF08\u91CD\u590D\u5DE5\u5177\u8C03\u7528\u88AB\u53CD\u98CE\u66B4\u673A\u5236\u6291\u5236\uFF09"
|
|
2877
3075
|
},
|
|
2878
3076
|
handlers: {
|
|
2879
3077
|
basic: {
|
|
@@ -3183,11 +3381,13 @@ var zhCN = {
|
|
|
3183
3381
|
usageMojeek: " /search-engine mojeek \u4F7F\u7528 Mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u5916\u90E8\u4F9D\u8D56\uFF09",
|
|
3184
3382
|
usageSearxng: " /search-engine searxng \u4F7F\u7528 SearXNG \u9ED8\u8BA4\u7AEF\u70B9",
|
|
3185
3383
|
usageSearxngUrl: " /search-engine searxng <url> \u4F7F\u7528 SearXNG \u81EA\u5B9A\u4E49\u7AEF\u70B9",
|
|
3384
|
+
usageMetaso: " /search-engine metaso \u4F7F\u7528 Metaso API\uFF08\u6BCF\u5929 100 \u6B21\u514D\u8D39\uFF0C\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09",
|
|
3186
3385
|
alias: "\u522B\u540D\uFF1A/se",
|
|
3187
3386
|
searxngInfo: "SearXNG \u662F\u4E00\u4E2A\u81EA\u6258\u7BA1\u7684\u5143\u641C\u7D22\u5F15\u64CE\uFF08https://github.com/searxng/searxng\uFF09\u3002",
|
|
3188
3387
|
searxngInstall: "\u5B89\u88C5\u547D\u4EE4\uFF1A docker run -d -p 8080:8080 searxng/searxng",
|
|
3189
3388
|
switched: '\u5DF2\u5207\u6362\u7F51\u9875\u641C\u7D22\u5F15\u64CE\u4E3A "{engine}"\u3002{note}',
|
|
3190
3389
|
switchedSearxngNote: " \u8BF7\u786E\u4FDD SearXNG \u5728 {endpoint} \u8FD0\u884C\u3002",
|
|
3390
|
+
switchedMetasoNote: " \u6BCF\u65E5\u9650\u989D 100 \u6B21\uFF08\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09\u3002",
|
|
3191
3391
|
confirmed: '\u2713 \u7F51\u9875\u641C\u7D22\u5F15\u64CE\u5DF2\u8BBE\u4E3A "{engine}"{detail}\u3002\u4E0B\u4E00\u8F6E\u6A21\u578B\u8C03\u7528\u5C06\u751F\u6548\u3002',
|
|
3192
3392
|
confirmedDetail: "\uFF08{endpoint}\uFF09"
|
|
3193
3393
|
},
|
|
@@ -3207,7 +3407,17 @@ var zhCN = {
|
|
|
3207
3407
|
runInfo: "\u25B8 \u6B63\u5728\u8FD0\u884C\u6280\u80FD\uFF1A{name}{args}",
|
|
3208
3408
|
newUsage: "\u7528\u6CD5\uFF1A/skill new <name> [--global]",
|
|
3209
3409
|
newCreated: "\u25B8 \u5DF2\u521B\u5EFA\u6280\u80FD\uFF1A{name}\n {path}\n \u7F16\u8F91\u540E\u7528 `/skill {name}` \u8C03\u7528",
|
|
3210
|
-
newError: "\u25B2 /skill new \u5931\u8D25\uFF1A{reason}"
|
|
3410
|
+
newError: "\u25B2 /skill new \u5931\u8D25\uFF1A{reason}",
|
|
3411
|
+
pathsHeader: "\u6280\u80FD\u8DEF\u5F84\uFF08\u6309\u4F18\u5148\u7EA7\uFF09\uFF1A",
|
|
3412
|
+
pathsPriority: "\u4F18\u5148\u7EA7\uFF1A\u9879\u76EE > \u914D\u7F6E\u987A\u5E8F\u4E2D\u7684\u81EA\u5B9A\u4E49\u8DEF\u5F84 > \u5168\u5C40 > \u5185\u7F6E\u3002\u66F4\u6539\u4F1A\u5728\u4E0B\u6B21 /new \u6216\u65B0\u4F1A\u8BDD\u5237\u65B0\u7CFB\u7EDF\u63D0\u793A\u8BCD\u65F6\u751F\u6548\u3002",
|
|
3413
|
+
pathsUsage: "\u7528\u6CD5\uFF1A/skill paths [list]\n /skill paths add <path>\n /skill paths remove <path|N>",
|
|
3414
|
+
pathsAddUsage: "\u7528\u6CD5\uFF1A/skill paths add <path>",
|
|
3415
|
+
pathsRemoveUsage: "\u7528\u6CD5\uFF1A/skill paths remove <path|N>",
|
|
3416
|
+
pathsAdded: "\u25B8 \u5DF2\u6DFB\u52A0\u81EA\u5B9A\u4E49\u6280\u80FD\u8DEF\u5F84\uFF1A{path}",
|
|
3417
|
+
pathsAlready: "\u25B8 \u81EA\u5B9A\u4E49\u6280\u80FD\u8DEF\u5F84\u5DF2\u5B58\u5728\uFF1A{path}",
|
|
3418
|
+
pathsRemoved: "\u25B8 \u5DF2\u79FB\u9664\u81EA\u5B9A\u4E49\u6280\u80FD\u8DEF\u5F84\uFF1A{path}",
|
|
3419
|
+
pathsRemoveNotFound: "\u25B8 \u6CA1\u6709\u5339\u914D\u7684\u81EA\u5B9A\u4E49\u6280\u80FD\u8DEF\u5F84\uFF1A{target}",
|
|
3420
|
+
pathsRestartHint: "\u5F53\u524D\u4F1A\u8BDD\u7684\u7CFB\u7EDF\u63D0\u793A\u8BCD\u4E0D\u4F1A\u70ED\u66F4\u65B0\uFF1B\u8FD0\u884C /new \u6216\u542F\u52A8\u65B0\u4F1A\u8BDD\u4EE5\u5237\u65B0\u6280\u80FD\u7D22\u5F15\u3002"
|
|
3211
3421
|
}
|
|
3212
3422
|
},
|
|
3213
3423
|
statusBar: {
|
|
@@ -3224,7 +3434,8 @@ var zhCN = {
|
|
|
3224
3434
|
mb: " MB",
|
|
3225
3435
|
evt: " \u4E8B\u4EF6",
|
|
3226
3436
|
editsLabel: "\u7F16\u8F91:",
|
|
3227
|
-
mcpLoading: "MCP"
|
|
3437
|
+
mcpLoading: "MCP",
|
|
3438
|
+
ctx: "\u4E0A\u4E0B\u6587"
|
|
3228
3439
|
},
|
|
3229
3440
|
editMode: {
|
|
3230
3441
|
plan: "\u8BA1\u5212",
|
|
@@ -3253,7 +3464,8 @@ var zhCN = {
|
|
|
3253
3464
|
editorNoRawMode: "\u5916\u90E8\u7F16\u8F91\u5668\u4E0D\u53EF\u7528 \u2014 \u5F53\u524D\u7EC8\u7AEF\u4E0D\u652F\u6301 raw-mode \u5207\u6362",
|
|
3254
3465
|
editorFailed: "\u5916\u90E8\u7F16\u8F91\u5668\uFF1A",
|
|
3255
3466
|
editorMissing: "\u672A\u8BBE\u7F6E $EDITOR / $VISUAL / $GIT_EDITOR \u2014 \u8BF7\u5BFC\u51FA\u73AF\u5883\u53D8\u91CF\uFF08\u4F8B\u5982 `export EDITOR=nano`\uFF09\u540E\u91CD\u8BD5",
|
|
3256
|
-
editorExited: "\u7F16\u8F91\u5668\u5F02\u5E38\u9000\u51FA\uFF0C\u8FD4\u56DE\u7801 {code}"
|
|
3467
|
+
editorExited: "\u7F16\u8F91\u5668\u5F02\u5E38\u9000\u51FA\uFF0C\u8FD4\u56DE\u7801 {code}",
|
|
3468
|
+
typeaheadStaged: "\u25B8 {count} \u884C\u5DF2\u6682\u5B58 \xB7 esc \u53EC\u56DE"
|
|
3257
3469
|
},
|
|
3258
3470
|
pathConfirm: {
|
|
3259
3471
|
title: "\u6C99\u7BB1\u5916\u8DEF\u5F84",
|
|
@@ -3423,6 +3635,12 @@ var zhCN = {
|
|
|
3423
3635
|
endpointMustBeHttp: "web_search: SearXNG \u7AEF\u70B9\u5FC5\u987B\u662F http(s) \u534F\u8BAE\uFF0C\u5F53\u524D\u4E3A {protocol} \u2014 try: \u4F7F\u7528 /search-endpoint http://host:port \u8BBE\u7F6E\u6709\u6548\u7684 URL",
|
|
3424
3636
|
cannotReach: "web_search: \u65E0\u6CD5\u8BBF\u95EE SearXNG \u670D\u52A1\u5668 {endpoint} \u2014 try: \u5B89\u88C5\u5E76\u542F\u52A8 SearXNG\uFF08https://github.com/searxng/searxng\uFF0C\u4F8B\u5982 `docker run -d -p 8080:8080 searxng/searxng`\uFF09\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5230\u9ED8\u8BA4\u5F15\u64CE",
|
|
3425
3637
|
searxngNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46 SearXNG \u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
|
|
3638
|
+
metasoDailyLimit: "web_search: \u9ED8\u8BA4 API \u5BC6\u94A5\u7684\u6BCF\u65E5\u641C\u7D22\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 \u2014 \u8BBE\u7F6E METASO_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u81EA\u5DF1\u7684\u5BC6\u94A5",
|
|
3639
|
+
metasoUnauthorized: "web_search: Metaso API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 METASO_API_KEY\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u5BC6\u94A5",
|
|
3640
|
+
metasoRateLimit: "web_search: Metaso \u8BF7\u6C42\u9891\u7387\u9650\u5236 \u2014 \u7B49\u5F85\u540E\u91CD\u8BD5\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u81EA\u5DF1\u7684\u5BC6\u94A5",
|
|
3641
|
+
metasoServerError: "web_search: Metaso \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
|
|
3642
|
+
metasoParseError: "web_search: Metaso \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
3643
|
+
metasoApiError: "web_search: Metaso API \u9519\u8BEF\uFF08code {code}: {message}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
3426
3644
|
fetchStatus: "web_fetch {status} for {url} \u2014 try: \u5728\u6D4F\u89C8\u5668\u4E2D\u786E\u8BA4\u8BE5 URL \u80FD\u5426\u8BBF\u95EE\uFF1B\u8BE5\u72B6\u6001\u7801\u8868\u660E\u76EE\u6807\u4E3B\u673A\u8FD4\u56DE\u4E86\u9519\u8BEF\u9875\u9762",
|
|
3427
3645
|
fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: \u7B49\u5F85 10 \u79D2\u540E\u91CD\u8BD5\uFF1B\u76EE\u6807\u4E3B\u673A\u6B63\u5728\u5BF9\u8BE5\u5BA2\u6237\u7AEF\u8FDB\u884C\u9650\u6D41",
|
|
3428
3646
|
fetchForbidden403: "web_fetch 403 for {url} \u2014 try: \u76EE\u6807\u4E3B\u673A\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u8BE5\u9875\u9762\u53EF\u80FD\u9700\u8981\u767B\u5F55\u6216\u5C4F\u853D\u722C\u866B \u2014 \u6539\u7528 web_search \u6458\u8981",
|
|
@@ -3597,7 +3815,9 @@ var zhCN = {
|
|
|
3597
3815
|
reconnect: "\u91CD\u8FDE\u4E2D\u2026",
|
|
3598
3816
|
initDetail: "\u521D\u59CB\u5316 \u2192 tools/list \u2192 resources/list",
|
|
3599
3817
|
reconnectDetail: "\u65AD\u5F00\u65E7\u8FDE\u63A5 \xB7 \u91CD\u65B0\u63E1\u624B \xB7 \u5217\u51FA\u5DE5\u5177",
|
|
3600
|
-
disabledDetail: "\u901A\u8FC7 /mcp disable {name}"
|
|
3818
|
+
disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
|
|
3819
|
+
failedSetupHint: "\u2192 \u8FD0\u884C `reasonix setup` \u79FB\u9664\u6B64\u6761\u76EE\uFF0C\u6216\u4FEE\u590D\u5E95\u5C42\u95EE\u9898\uFF08\u7F3A\u5C11 npm \u5305\u3001\u7F51\u7EDC\u7B49\uFF09\u3002",
|
|
3820
|
+
failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002"
|
|
3601
3821
|
},
|
|
3602
3822
|
checkpointPicker: {
|
|
3603
3823
|
title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
|
|
@@ -3752,7 +3972,7 @@ function matchesTool(hook, toolName) {
|
|
|
3752
3972
|
}
|
|
3753
3973
|
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
3754
3974
|
function defaultSpawner(input) {
|
|
3755
|
-
return new Promise((
|
|
3975
|
+
return new Promise((resolve13) => {
|
|
3756
3976
|
const child = spawn(input.command, {
|
|
3757
3977
|
cwd: input.cwd,
|
|
3758
3978
|
shell: true,
|
|
@@ -3797,7 +4017,7 @@ function defaultSpawner(input) {
|
|
|
3797
4017
|
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
3798
4018
|
child.once("error", (err) => {
|
|
3799
4019
|
clearTimeout(timer);
|
|
3800
|
-
|
|
4020
|
+
resolve13({
|
|
3801
4021
|
exitCode: null,
|
|
3802
4022
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
3803
4023
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
@@ -3808,7 +4028,7 @@ function defaultSpawner(input) {
|
|
|
3808
4028
|
});
|
|
3809
4029
|
child.once("close", (code) => {
|
|
3810
4030
|
clearTimeout(timer);
|
|
3811
|
-
|
|
4031
|
+
resolve13({
|
|
3812
4032
|
exitCode: code,
|
|
3813
4033
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
3814
4034
|
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
@@ -4044,22 +4264,211 @@ function encode(text) {
|
|
|
4044
4264
|
function countTokens(text) {
|
|
4045
4265
|
return encode(text).length;
|
|
4046
4266
|
}
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4267
|
+
var DEFAULT_BOUNDED_TOKENIZE_CHARS = 2 * 1024;
|
|
4268
|
+
function countTokensBounded(text, maxChars = DEFAULT_BOUNDED_TOKENIZE_CHARS) {
|
|
4269
|
+
if (text.length === 0) return 0;
|
|
4270
|
+
const cap = Math.floor(maxChars);
|
|
4271
|
+
if (cap > 0 && text.length <= cap) return countTokens(text);
|
|
4272
|
+
if (cap <= 0) return Math.max(1, Math.ceil(text.length * 0.3));
|
|
4273
|
+
const headChars = Math.ceil(cap / 2);
|
|
4274
|
+
const tailChars = Math.floor(cap / 2);
|
|
4275
|
+
const head = text.slice(0, headChars);
|
|
4276
|
+
const tail = tailChars > 0 ? text.slice(-tailChars) : "";
|
|
4277
|
+
const sampleChars = head.length + tail.length;
|
|
4278
|
+
const sampleTokens = countTokens(head) + countTokens(tail);
|
|
4279
|
+
const ratio = sampleChars > 0 ? sampleTokens / sampleChars : 0.3;
|
|
4280
|
+
return Math.max(1, Math.ceil(text.length * ratio));
|
|
4281
|
+
}
|
|
4282
|
+
var BOS = "<\uFF5Cbegin\u2581of\u2581sentence\uFF5C>";
|
|
4283
|
+
var EOS = "<\uFF5Cend\u2581of\u2581sentence\uFF5C>";
|
|
4284
|
+
var USER_SP = "<\uFF5CUser\uFF5C>";
|
|
4285
|
+
var ASSISTANT_SP = "<\uFF5CAssistant\uFF5C>";
|
|
4286
|
+
var THINK_START = "<think>";
|
|
4287
|
+
var THINK_END = "</think>";
|
|
4288
|
+
var DSML = "\uFF5CDSML\uFF5C";
|
|
4289
|
+
var TC_BEGIN = `<${DSML}tool_calls>`;
|
|
4290
|
+
var TC_END = `</${DSML}tool_calls>`;
|
|
4291
|
+
var INVOKE_BEGIN = `<${DSML}invoke name="`;
|
|
4292
|
+
var INVOKE_END = `</${DSML}invoke>`;
|
|
4293
|
+
var PARAM_TEMPLATE = `<${DSML}parameter name="{key}" string="{is_str}">{value}</${DSML}parameter>`;
|
|
4294
|
+
var TOOL_RESULT_TEMPLATE = "<tool_result>{content}</tool_result>";
|
|
4295
|
+
var toolsTemplateCache = /* @__PURE__ */ new WeakMap();
|
|
4296
|
+
function renderTools(tools) {
|
|
4297
|
+
const cached2 = toolsTemplateCache.get(tools);
|
|
4298
|
+
if (cached2 !== void 0) return cached2;
|
|
4299
|
+
const schemas = tools.map((t2) => {
|
|
4300
|
+
const fn = t2.function ?? t2;
|
|
4301
|
+
return JSON.stringify(fn);
|
|
4302
|
+
}).join("\n");
|
|
4303
|
+
const rendered = `## Tools
|
|
4304
|
+
|
|
4305
|
+
You have access to a set of tools to help answer the user's question. You can invoke tools by writing a "<${DSML}tool_calls>" block like the following:
|
|
4306
|
+
|
|
4307
|
+
<${DSML}tool_calls>
|
|
4308
|
+
<${DSML}invoke name="$TOOL_NAME">
|
|
4309
|
+
<${DSML}parameter name="$PARAMETER_NAME" string="true|false">$PARAMETER_VALUE</${DSML}parameter>
|
|
4310
|
+
...
|
|
4311
|
+
</${DSML}invoke>
|
|
4312
|
+
<${DSML}invoke name="$TOOL_NAME2">
|
|
4313
|
+
...
|
|
4314
|
+
</${DSML}invoke>
|
|
4315
|
+
</${DSML}tool_calls>
|
|
4316
|
+
|
|
4317
|
+
String parameters should be specified as is and set \`string="true"\`. For all other types (numbers, booleans, arrays, objects), pass the value in JSON format and set \`string="false"\`.
|
|
4318
|
+
|
|
4319
|
+
If thinking_mode is enabled (triggered by ${THINK_START}), you MUST output your complete reasoning inside ${THINK_START}...${THINK_END} BEFORE any tool calls or final response.
|
|
4320
|
+
|
|
4321
|
+
Otherwise, output directly after ${THINK_END} with tool calls or final response.
|
|
4322
|
+
|
|
4323
|
+
### Available Tool Schemas
|
|
4324
|
+
|
|
4325
|
+
${schemas}
|
|
4326
|
+
|
|
4327
|
+
You MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.`;
|
|
4328
|
+
toolsTemplateCache.set(tools, rendered);
|
|
4329
|
+
return rendered;
|
|
4330
|
+
}
|
|
4331
|
+
function encodeArgumentsToDsml(argsJson) {
|
|
4332
|
+
let args;
|
|
4333
|
+
try {
|
|
4334
|
+
args = JSON.parse(argsJson);
|
|
4335
|
+
} catch {
|
|
4336
|
+
args = { arguments: argsJson };
|
|
4337
|
+
}
|
|
4338
|
+
return Object.entries(args).map(
|
|
4339
|
+
([k, v]) => PARAM_TEMPLATE.replace("{key}", k).replace("{is_str}", typeof v === "string" ? "true" : "false").replace("{value}", typeof v === "string" ? v : JSON.stringify(v))
|
|
4340
|
+
).join("\n");
|
|
4341
|
+
}
|
|
4342
|
+
function renderToolCallsDsml(toolCalls) {
|
|
4343
|
+
const invokes = toolCalls.map((tc) => {
|
|
4344
|
+
const name = tc.function?.name ?? "";
|
|
4345
|
+
const argsJson = tc.function?.arguments ?? "{}";
|
|
4346
|
+
return `${INVOKE_BEGIN + name}">
|
|
4347
|
+
${encodeArgumentsToDsml(argsJson)}
|
|
4348
|
+
${INVOKE_END}`;
|
|
4349
|
+
}).join("\n");
|
|
4350
|
+
return `
|
|
4351
|
+
|
|
4352
|
+
${TC_BEGIN}
|
|
4353
|
+
${invokes}
|
|
4354
|
+
${TC_END}`;
|
|
4355
|
+
}
|
|
4356
|
+
function mergeToolMessages(messages) {
|
|
4357
|
+
const merged = [];
|
|
4358
|
+
for (const msg of messages) {
|
|
4359
|
+
const role = msg.role ?? "user";
|
|
4360
|
+
if (role === "tool") {
|
|
4361
|
+
const toolBlock = TOOL_RESULT_TEMPLATE.replace("{content}", msg.content ?? "");
|
|
4362
|
+
const last = merged[merged.length - 1];
|
|
4363
|
+
if (last && last.role === "user" && Array.isArray(last._toolBlocks) && Array.isArray(last._textParts)) {
|
|
4364
|
+
last._toolBlocks.push(toolBlock);
|
|
4365
|
+
last.content = `${last._textParts.join("\n\n")}
|
|
4366
|
+
|
|
4367
|
+
${last._toolBlocks.join("\n")}`.replace(
|
|
4368
|
+
/^\n\n/,
|
|
4369
|
+
""
|
|
4370
|
+
);
|
|
4371
|
+
} else {
|
|
4372
|
+
merged.push({
|
|
4373
|
+
role: "user",
|
|
4374
|
+
content: toolBlock,
|
|
4375
|
+
_textParts: [],
|
|
4376
|
+
_toolBlocks: [toolBlock]
|
|
4377
|
+
});
|
|
4378
|
+
}
|
|
4379
|
+
} else if (role === "user") {
|
|
4380
|
+
const text = msg.content ?? "";
|
|
4381
|
+
const last = merged[merged.length - 1];
|
|
4382
|
+
if (last && last.role === "user" && Array.isArray(last._toolBlocks) && Array.isArray(last._textParts)) {
|
|
4383
|
+
last._textParts.push(text);
|
|
4384
|
+
last.content = `${last._textParts.join("\n\n")}
|
|
4385
|
+
|
|
4386
|
+
${last._toolBlocks.join("\n\n")}`.replace(
|
|
4387
|
+
/^\n\n/,
|
|
4388
|
+
""
|
|
4389
|
+
);
|
|
4390
|
+
} else {
|
|
4391
|
+
merged.push({
|
|
4392
|
+
...msg,
|
|
4393
|
+
role: "user",
|
|
4394
|
+
content: text,
|
|
4395
|
+
_textParts: [text],
|
|
4396
|
+
_toolBlocks: []
|
|
4397
|
+
});
|
|
4398
|
+
}
|
|
4399
|
+
} else {
|
|
4400
|
+
merged.push({ ...msg });
|
|
4052
4401
|
}
|
|
4053
|
-
|
|
4054
|
-
|
|
4402
|
+
}
|
|
4403
|
+
for (const m of merged) {
|
|
4404
|
+
m._textParts = void 0;
|
|
4405
|
+
m._toolBlocks = void 0;
|
|
4406
|
+
}
|
|
4407
|
+
return merged;
|
|
4408
|
+
}
|
|
4409
|
+
function dropThinkingMessages(messages) {
|
|
4410
|
+
let lastUserIdx = -1;
|
|
4411
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
4412
|
+
const role = messages[i].role;
|
|
4413
|
+
if (role === "user" || role === "developer") {
|
|
4414
|
+
lastUserIdx = i;
|
|
4415
|
+
break;
|
|
4055
4416
|
}
|
|
4056
4417
|
}
|
|
4057
|
-
return
|
|
4418
|
+
if (lastUserIdx < 0) return messages;
|
|
4419
|
+
const result = [];
|
|
4420
|
+
for (let i = 0; i < messages.length; i++) {
|
|
4421
|
+
const msg = messages[i];
|
|
4422
|
+
if (i < lastUserIdx && msg.role === "developer") continue;
|
|
4423
|
+
if (i < lastUserIdx && msg.role === "assistant") {
|
|
4424
|
+
result.push({ ...msg, reasoning_content: null });
|
|
4425
|
+
} else {
|
|
4426
|
+
result.push(msg);
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
return result;
|
|
4058
4430
|
}
|
|
4059
|
-
function
|
|
4060
|
-
|
|
4431
|
+
function formatDeepSeekPrompt(messages, drop_thinking = false) {
|
|
4432
|
+
if (messages.length === 0) return ASSISTANT_SP + THINK_END;
|
|
4433
|
+
let msgs = messages;
|
|
4434
|
+
if (drop_thinking) {
|
|
4435
|
+
msgs = dropThinkingMessages(msgs);
|
|
4436
|
+
}
|
|
4437
|
+
const merged = mergeToolMessages(msgs);
|
|
4438
|
+
let prompt = BOS;
|
|
4439
|
+
for (let i = 0; i < merged.length; i++) {
|
|
4440
|
+
const msg = merged[i];
|
|
4441
|
+
const role = msg.role ?? "user";
|
|
4442
|
+
const nextRole = i + 1 < merged.length ? merged[i + 1].role ?? "user" : null;
|
|
4443
|
+
if (role === "system") {
|
|
4444
|
+
prompt += msg.content ?? "";
|
|
4445
|
+
} else if (role === "user") {
|
|
4446
|
+
prompt += USER_SP + (msg.content ?? "");
|
|
4447
|
+
if (nextRole === "assistant" || nextRole === null) {
|
|
4448
|
+
prompt += ASSISTANT_SP + THINK_END;
|
|
4449
|
+
}
|
|
4450
|
+
} else if (role === "assistant") {
|
|
4451
|
+
if (msg.reasoning_content) {
|
|
4452
|
+
prompt += THINK_START + msg.reasoning_content + THINK_END;
|
|
4453
|
+
}
|
|
4454
|
+
if (msg.content) prompt += msg.content;
|
|
4455
|
+
const tcs = msg.tool_calls;
|
|
4456
|
+
if (Array.isArray(tcs) && tcs.length > 0) {
|
|
4457
|
+
prompt += renderToolCallsDsml(tcs);
|
|
4458
|
+
}
|
|
4459
|
+
prompt += EOS;
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
return prompt;
|
|
4463
|
+
}
|
|
4464
|
+
function estimateConversationTokens(messages, drop_thinking = false) {
|
|
4465
|
+
if (messages.length === 0) return 0;
|
|
4466
|
+
return countTokensBounded(formatDeepSeekPrompt(messages, drop_thinking));
|
|
4467
|
+
}
|
|
4468
|
+
function estimateRequestTokens(messages, toolSpecs, drop_thinking = false) {
|
|
4469
|
+
let total = estimateConversationTokens(messages, drop_thinking);
|
|
4061
4470
|
if (toolSpecs && toolSpecs.length > 0) {
|
|
4062
|
-
total +=
|
|
4471
|
+
total += countTokensBounded(renderTools(toolSpecs));
|
|
4063
4472
|
}
|
|
4064
4473
|
return total;
|
|
4065
4474
|
}
|
|
@@ -4412,12 +4821,12 @@ async function waitForReady(ready, timeoutMs, serverName, signal) {
|
|
|
4412
4821
|
let timer;
|
|
4413
4822
|
let onAbort;
|
|
4414
4823
|
try {
|
|
4415
|
-
await new Promise((
|
|
4824
|
+
await new Promise((resolve13, reject) => {
|
|
4416
4825
|
ready.then(
|
|
4417
4826
|
() => {
|
|
4418
4827
|
if (settled) return;
|
|
4419
4828
|
settled = true;
|
|
4420
|
-
|
|
4829
|
+
resolve13();
|
|
4421
4830
|
},
|
|
4422
4831
|
(err) => {
|
|
4423
4832
|
if (settled) return;
|
|
@@ -4566,6 +4975,40 @@ function blockToString(block) {
|
|
|
4566
4975
|
return `[unknown block: ${JSON.stringify(block)}]`;
|
|
4567
4976
|
}
|
|
4568
4977
|
|
|
4978
|
+
// src/loop/thinking.ts
|
|
4979
|
+
function isThinkingModeModel(model) {
|
|
4980
|
+
if (model.includes("reasoner")) return true;
|
|
4981
|
+
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
|
|
4982
|
+
return false;
|
|
4983
|
+
}
|
|
4984
|
+
function thinkingModeForModel(model) {
|
|
4985
|
+
if (model === "deepseek-chat") return "disabled";
|
|
4986
|
+
if (model.includes("reasoner")) return "enabled";
|
|
4987
|
+
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return "enabled";
|
|
4988
|
+
return void 0;
|
|
4989
|
+
}
|
|
4990
|
+
function stripHallucinatedToolMarkup(s) {
|
|
4991
|
+
let out = s;
|
|
4992
|
+
out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
|
|
4993
|
+
out = out.replace(/<\|DSML\|function_calls>[\s\S]*?<\/?\|DSML\|function_calls>/g, "");
|
|
4994
|
+
out = out.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "");
|
|
4995
|
+
out = out.replace(/<|DSML|[\s\S]*$/g, "");
|
|
4996
|
+
return out.trim();
|
|
4997
|
+
}
|
|
4998
|
+
|
|
4999
|
+
// src/loop/messages.ts
|
|
5000
|
+
function buildAssistantMessage(content, toolCalls, producingModel, reasoningContent) {
|
|
5001
|
+
const msg = { role: "assistant", content };
|
|
5002
|
+
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
5003
|
+
if (isThinkingModeModel(producingModel) || reasoningContent && reasoningContent.length > 0) {
|
|
5004
|
+
msg.reasoning_content = reasoningContent ?? "";
|
|
5005
|
+
}
|
|
5006
|
+
return msg;
|
|
5007
|
+
}
|
|
5008
|
+
function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
5009
|
+
return buildAssistantMessage(content, [], fallbackModel, "");
|
|
5010
|
+
}
|
|
5011
|
+
|
|
4569
5012
|
// src/memory/session.ts
|
|
4570
5013
|
import { execFileSync } from "child_process";
|
|
4571
5014
|
import {
|
|
@@ -4581,7 +5024,7 @@ import {
|
|
|
4581
5024
|
writeFileSync as writeFileSync2
|
|
4582
5025
|
} from "fs";
|
|
4583
5026
|
import { homedir as homedir3 } from "os";
|
|
4584
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
5027
|
+
import { dirname as dirname3, join as join4, posix as posixPath, win32 as win32Path } from "path";
|
|
4585
5028
|
function sessionsDir() {
|
|
4586
5029
|
return join4(homedir3(), ".reasonix", "sessions");
|
|
4587
5030
|
}
|
|
@@ -4735,11 +5178,11 @@ function countLines(path2) {
|
|
|
4735
5178
|
|
|
4736
5179
|
// src/telemetry/stats.ts
|
|
4737
5180
|
var DEEPSEEK_PRICING = {
|
|
4738
|
-
"deepseek-v4-flash": { inputCacheHit:
|
|
4739
|
-
"deepseek-v4-pro": { inputCacheHit:
|
|
5181
|
+
"deepseek-v4-flash": { inputCacheHit: 28e-4, inputCacheMiss: 0.14, output: 0.28 },
|
|
5182
|
+
"deepseek-v4-pro": { inputCacheHit: 3625e-6, inputCacheMiss: 0.435, output: 0.87 },
|
|
4740
5183
|
// Compat aliases — priced as v4-flash per the deprecation notice.
|
|
4741
|
-
"deepseek-chat": { inputCacheHit:
|
|
4742
|
-
"deepseek-reasoner": { inputCacheHit:
|
|
5184
|
+
"deepseek-chat": { inputCacheHit: 28e-4, inputCacheMiss: 0.14, output: 0.28 },
|
|
5185
|
+
"deepseek-reasoner": { inputCacheHit: 28e-4, inputCacheMiss: 0.14, output: 0.28 }
|
|
4743
5186
|
};
|
|
4744
5187
|
var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
|
|
4745
5188
|
var DEEPSEEK_CONTEXT_TOKENS = {
|
|
@@ -4801,6 +5244,14 @@ var SessionStats = class {
|
|
|
4801
5244
|
this._carryoverLastPromptTokens = opts.lastPromptTokens;
|
|
4802
5245
|
}
|
|
4803
5246
|
}
|
|
5247
|
+
reset() {
|
|
5248
|
+
this.turns.length = 0;
|
|
5249
|
+
this._carryoverCost = 0;
|
|
5250
|
+
this._carryoverTurns = 0;
|
|
5251
|
+
this._carryoverCacheHit = 0;
|
|
5252
|
+
this._carryoverCacheMiss = 0;
|
|
5253
|
+
this._carryoverLastPromptTokens = 0;
|
|
5254
|
+
}
|
|
4804
5255
|
record(turn, model, usage) {
|
|
4805
5256
|
const cost = costUsd(model, usage);
|
|
4806
5257
|
const stats = {
|
|
@@ -4867,7 +5318,26 @@ var HISTORY_FOLD_AGGRESSIVE_TAIL_FRACTION = 0.1;
|
|
|
4867
5318
|
var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
|
|
4868
5319
|
var FORCE_SUMMARY_THRESHOLD = 0.8;
|
|
4869
5320
|
var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
|
|
5321
|
+
var PREFLIGHT_MECHANICAL_TARGET_FRACTION = 0.7;
|
|
5322
|
+
var HISTORY_FOLD_SUMMARY_TIMEOUT_MS = 15e3;
|
|
4870
5323
|
var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
|
|
5324
|
+
var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
|
|
5325
|
+
var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
|
|
5326
|
+
function extractPinnedSkills(head) {
|
|
5327
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
5328
|
+
const stubbedHead = head.map((msg) => {
|
|
5329
|
+
if (typeof msg.content !== "string") return msg;
|
|
5330
|
+
let hit = false;
|
|
5331
|
+
const next = msg.content.replace(SKILL_PIN_REGEX, (full, name) => {
|
|
5332
|
+
pinned.delete(name);
|
|
5333
|
+
pinned.set(name, full);
|
|
5334
|
+
hit = true;
|
|
5335
|
+
return `[skill ${JSON.stringify(name)} memo \u2014 preserved separately, do not summarize.]`;
|
|
5336
|
+
});
|
|
5337
|
+
return hit ? { ...msg, content: next } : msg;
|
|
5338
|
+
});
|
|
5339
|
+
return { stubbedHead, pinnedBodies: [...pinned.values()] };
|
|
5340
|
+
}
|
|
4871
5341
|
var ContextManager = class {
|
|
4872
5342
|
constructor(deps) {
|
|
4873
5343
|
this.deps = deps;
|
|
@@ -4904,7 +5374,7 @@ var ContextManager = class {
|
|
|
4904
5374
|
/** Local-side preflight before sending a request — catches oversized payloads early. */
|
|
4905
5375
|
decidePreflight(messages, toolSpecs, model) {
|
|
4906
5376
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4907
|
-
const estimate = estimateRequestTokens(messages, toolSpecs ?? null);
|
|
5377
|
+
const estimate = estimateRequestTokens(messages, toolSpecs ?? null, true);
|
|
4908
5378
|
return {
|
|
4909
5379
|
needsAction: estimate / ctxMax > PREFLIGHT_EMERGENCY_THRESHOLD,
|
|
4910
5380
|
estimateTokens: estimate,
|
|
@@ -4923,7 +5393,7 @@ var ContextManager = class {
|
|
|
4923
5393
|
summaryChars: 0
|
|
4924
5394
|
};
|
|
4925
5395
|
if (all.length === 0) return noop;
|
|
4926
|
-
const tokenCounts = all.map((m) =>
|
|
5396
|
+
const tokenCounts = all.map((m) => countTokensBounded(m.content ?? ""));
|
|
4927
5397
|
const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);
|
|
4928
5398
|
let cumTokens = 0;
|
|
4929
5399
|
let boundary = all.length;
|
|
@@ -4937,12 +5407,20 @@ var ContextManager = class {
|
|
|
4937
5407
|
const tail = all.slice(boundary);
|
|
4938
5408
|
const headTokens = totalTokens - cumTokens;
|
|
4939
5409
|
if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
|
|
4940
|
-
const
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
5410
|
+
const { stubbedHead, pinnedBodies } = extractPinnedSkills(head);
|
|
5411
|
+
const summary = await this.summarizeForFold(stubbedHead);
|
|
5412
|
+
if (!summary.content) return noop;
|
|
5413
|
+
const memoTail = pinnedBodies.length > 0 ? `
|
|
5414
|
+
|
|
5415
|
+
${SKILL_PIN_MEMO_HEADER}
|
|
5416
|
+
|
|
5417
|
+
${pinnedBodies.join("\n\n")}` : "";
|
|
5418
|
+
const summaryMsg = buildAssistantMessage(
|
|
5419
|
+
HISTORY_FOLD_MARKER + summary.content + memoTail,
|
|
5420
|
+
[],
|
|
5421
|
+
model,
|
|
5422
|
+
summary.reasoningContent
|
|
5423
|
+
);
|
|
4946
5424
|
const replacement = [summaryMsg, ...tail];
|
|
4947
5425
|
this.deps.log.compactInPlace(replacement);
|
|
4948
5426
|
this.persistRewrite(replacement);
|
|
@@ -4950,7 +5428,51 @@ var ContextManager = class {
|
|
|
4950
5428
|
folded: true,
|
|
4951
5429
|
beforeMessages: all.length,
|
|
4952
5430
|
afterMessages: replacement.length,
|
|
4953
|
-
summaryChars: summary.length
|
|
5431
|
+
summaryChars: summary.content.length
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5434
|
+
/** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail. */
|
|
5435
|
+
mechanicalTruncate(model, opts) {
|
|
5436
|
+
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
5437
|
+
const targetTokens = opts?.targetTokens ?? Math.floor(ctxMax * PREFLIGHT_MECHANICAL_TARGET_FRACTION);
|
|
5438
|
+
const all = this.deps.log.toMessages();
|
|
5439
|
+
const noop = {
|
|
5440
|
+
folded: false,
|
|
5441
|
+
beforeMessages: all.length,
|
|
5442
|
+
afterMessages: all.length,
|
|
5443
|
+
summaryChars: 0
|
|
5444
|
+
};
|
|
5445
|
+
if (all.length === 0) return noop;
|
|
5446
|
+
const tokenCounts = all.map((m) => estimateConversationTokens([m], true));
|
|
5447
|
+
let latestUserBoundary = -1;
|
|
5448
|
+
for (let i = all.length - 1; i >= 0; i--) {
|
|
5449
|
+
if (all[i].role === "user") {
|
|
5450
|
+
latestUserBoundary = i;
|
|
5451
|
+
break;
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
let cumTokens = 0;
|
|
5455
|
+
let boundary = all.length;
|
|
5456
|
+
let foundSafeBoundary = false;
|
|
5457
|
+
for (let i = all.length - 1; i >= 0; i--) {
|
|
5458
|
+
const next = cumTokens + tokenCounts[i];
|
|
5459
|
+
if (next > targetTokens) break;
|
|
5460
|
+
cumTokens = next;
|
|
5461
|
+
if (all[i].role === "user") {
|
|
5462
|
+
boundary = i;
|
|
5463
|
+
foundSafeBoundary = true;
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
if (boundary <= 0) return noop;
|
|
5467
|
+
const replacement = foundSafeBoundary ? all.slice(boundary) : opts?.allowEmpty ? [] : latestUserBoundary >= 0 ? all.slice(latestUserBoundary) : all;
|
|
5468
|
+
if (replacement.length === all.length) return noop;
|
|
5469
|
+
this.deps.log.compactInPlace(replacement);
|
|
5470
|
+
this.persistRewrite(replacement);
|
|
5471
|
+
return {
|
|
5472
|
+
folded: true,
|
|
5473
|
+
beforeMessages: all.length,
|
|
5474
|
+
afterMessages: replacement.length,
|
|
5475
|
+
summaryChars: 0
|
|
4954
5476
|
};
|
|
4955
5477
|
}
|
|
4956
5478
|
/** Drop a trailing in-flight assistant-with-tool_calls before a forced summary. Tail-only mutation; prefix cache safe. */
|
|
@@ -4976,18 +5498,51 @@ var ContextManager = class {
|
|
|
4976
5498
|
content: "Summarize the conversation above as plain prose. This summary replaces the original turns to free context \u2014 make it self-contained."
|
|
4977
5499
|
}
|
|
4978
5500
|
];
|
|
5501
|
+
const turnSignal = this.deps.getAbortSignal();
|
|
5502
|
+
const foldCtrl = new AbortController();
|
|
5503
|
+
let cleanupAbort = () => {
|
|
5504
|
+
};
|
|
5505
|
+
let timeout;
|
|
4979
5506
|
try {
|
|
4980
|
-
const
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
5507
|
+
const abortPromise = new Promise((_, reject) => {
|
|
5508
|
+
const abort = () => {
|
|
5509
|
+
foldCtrl.abort();
|
|
5510
|
+
reject(new Error("fold-aborted"));
|
|
5511
|
+
};
|
|
5512
|
+
if (turnSignal.aborted) {
|
|
5513
|
+
abort();
|
|
5514
|
+
} else {
|
|
5515
|
+
turnSignal.addEventListener("abort", abort, { once: true });
|
|
5516
|
+
cleanupAbort = () => turnSignal.removeEventListener("abort", abort);
|
|
5517
|
+
}
|
|
4986
5518
|
});
|
|
5519
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
5520
|
+
timeout = setTimeout(() => {
|
|
5521
|
+
foldCtrl.abort();
|
|
5522
|
+
reject(new Error("fold-timeout"));
|
|
5523
|
+
}, HISTORY_FOLD_SUMMARY_TIMEOUT_MS);
|
|
5524
|
+
});
|
|
5525
|
+
const resp = await Promise.race([
|
|
5526
|
+
this.deps.client.chat({
|
|
5527
|
+
model: summaryModel,
|
|
5528
|
+
messages,
|
|
5529
|
+
signal: foldCtrl.signal,
|
|
5530
|
+
thinking: thinkingModeForModel(summaryModel),
|
|
5531
|
+
reasoningEffort: "high"
|
|
5532
|
+
}),
|
|
5533
|
+
abortPromise,
|
|
5534
|
+
timeoutPromise
|
|
5535
|
+
]);
|
|
4987
5536
|
this.deps.stats.record(this.deps.getCurrentTurn(), summaryModel, resp.usage ?? new Usage());
|
|
4988
|
-
return
|
|
5537
|
+
return {
|
|
5538
|
+
content: stripHallucinatedToolMarkup((resp.content ?? "").trim()),
|
|
5539
|
+
reasoningContent: resp.reasoningContent ?? ""
|
|
5540
|
+
};
|
|
4989
5541
|
} catch {
|
|
4990
|
-
return "";
|
|
5542
|
+
return { content: "", reasoningContent: "" };
|
|
5543
|
+
} finally {
|
|
5544
|
+
if (timeout) clearTimeout(timeout);
|
|
5545
|
+
cleanupAbort();
|
|
4991
5546
|
}
|
|
4992
5547
|
}
|
|
4993
5548
|
persistRewrite(messages) {
|
|
@@ -5079,17 +5634,15 @@ function formatDeepSeek5xx(status, probe) {
|
|
|
5079
5634
|
const action = probe?.reachable === false ? t("errors.deepseek5xxActionNetwork") : t("errors.deepseek5xxActionRetry");
|
|
5080
5635
|
return `${head}${probeNote}${action}`;
|
|
5081
5636
|
}
|
|
5082
|
-
function reasonPrefixFor(reason
|
|
5637
|
+
function reasonPrefixFor(reason) {
|
|
5083
5638
|
if (reason === "aborted") return t("errors.reasonAborted");
|
|
5084
5639
|
if (reason === "context-guard") return t("errors.reasonContextGuard");
|
|
5085
|
-
|
|
5086
|
-
return t("errors.reasonBudget", { iterCap });
|
|
5640
|
+
return t("errors.reasonStuck");
|
|
5087
5641
|
}
|
|
5088
|
-
function errorLabelFor(reason
|
|
5642
|
+
function errorLabelFor(reason) {
|
|
5089
5643
|
if (reason === "aborted") return t("errors.labelAborted");
|
|
5090
5644
|
if (reason === "context-guard") return t("errors.labelContextGuard");
|
|
5091
|
-
|
|
5092
|
-
return t("errors.labelBudget", { iterCap });
|
|
5645
|
+
return t("errors.labelStuck");
|
|
5093
5646
|
}
|
|
5094
5647
|
function extractDeepSeekErrorMessage(body) {
|
|
5095
5648
|
const trimmed = body.trim();
|
|
@@ -5131,48 +5684,14 @@ function looksLikePartialEscalationMarker(buf) {
|
|
|
5131
5684
|
return true;
|
|
5132
5685
|
}
|
|
5133
5686
|
|
|
5134
|
-
// src/loop/thinking.ts
|
|
5135
|
-
function isThinkingModeModel(model) {
|
|
5136
|
-
if (model.includes("reasoner")) return true;
|
|
5137
|
-
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
|
|
5138
|
-
return false;
|
|
5139
|
-
}
|
|
5140
|
-
function thinkingModeForModel(model) {
|
|
5141
|
-
if (model === "deepseek-chat") return "disabled";
|
|
5142
|
-
if (model.includes("reasoner")) return "enabled";
|
|
5143
|
-
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return "enabled";
|
|
5144
|
-
return void 0;
|
|
5145
|
-
}
|
|
5146
|
-
function stripHallucinatedToolMarkup(s) {
|
|
5147
|
-
let out = s;
|
|
5148
|
-
out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
|
|
5149
|
-
out = out.replace(/<\|DSML\|function_calls>[\s\S]*?<\/?\|DSML\|function_calls>/g, "");
|
|
5150
|
-
out = out.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "");
|
|
5151
|
-
out = out.replace(/<|DSML|[\s\S]*$/g, "");
|
|
5152
|
-
return out.trim();
|
|
5153
|
-
}
|
|
5154
|
-
|
|
5155
|
-
// src/loop/messages.ts
|
|
5156
|
-
function buildAssistantMessage(content, toolCalls, producingModel, reasoningContent) {
|
|
5157
|
-
const msg = { role: "assistant", content };
|
|
5158
|
-
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
5159
|
-
if (isThinkingModeModel(producingModel) || reasoningContent && reasoningContent.length > 0) {
|
|
5160
|
-
msg.reasoning_content = reasoningContent ?? "";
|
|
5161
|
-
}
|
|
5162
|
-
return msg;
|
|
5163
|
-
}
|
|
5164
|
-
function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
5165
|
-
return buildAssistantMessage(content, [], fallbackModel, "");
|
|
5166
|
-
}
|
|
5167
|
-
|
|
5168
5687
|
// src/loop/force-summary.ts
|
|
5169
|
-
async function* forceSummaryAfterIterLimit(ctx, opts
|
|
5688
|
+
async function* forceSummaryAfterIterLimit(ctx, opts) {
|
|
5170
5689
|
try {
|
|
5171
5690
|
yield { turn: ctx.turn, role: "status", content: t("summary.status") };
|
|
5172
5691
|
const messages = ctx.buildMessages();
|
|
5173
5692
|
messages.push({
|
|
5174
5693
|
role: "user",
|
|
5175
|
-
content: "
|
|
5694
|
+
content: "The turn is being force-summarized (context guard or stuck-state). Summarize in plain prose what you learned from the tool results above. Do NOT emit any tool calls, function-call markup, DSML invocations, or SEARCH/REPLACE edit blocks \u2014 they will be silently discarded. Just plain text."
|
|
5176
5695
|
});
|
|
5177
5696
|
const summaryModel = "deepseek-v4-flash";
|
|
5178
5697
|
const summaryEffort = "high";
|
|
@@ -5186,7 +5705,7 @@ async function* forceSummaryAfterIterLimit(ctx, opts = { reason: "budget" }) {
|
|
|
5186
5705
|
const rawContent = resp.content?.trim() ?? "";
|
|
5187
5706
|
const cleaned = stripHallucinatedToolMarkup(rawContent);
|
|
5188
5707
|
const summary = cleaned || t("summary.hallucinatedFallback");
|
|
5189
|
-
const reasonPrefix = reasonPrefixFor(opts.reason
|
|
5708
|
+
const reasonPrefix = reasonPrefixFor(opts.reason);
|
|
5190
5709
|
const annotated = `${reasonPrefix}
|
|
5191
5710
|
|
|
5192
5711
|
${summary}`;
|
|
@@ -5201,7 +5720,7 @@ ${summary}`;
|
|
|
5201
5720
|
};
|
|
5202
5721
|
yield { turn: ctx.turn, role: "done", content: summary };
|
|
5203
5722
|
} catch (err) {
|
|
5204
|
-
const label = errorLabelFor(opts.reason
|
|
5723
|
+
const label = errorLabelFor(opts.reason);
|
|
5205
5724
|
yield {
|
|
5206
5725
|
turn: ctx.turn,
|
|
5207
5726
|
role: "error",
|
|
@@ -5243,7 +5762,7 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
|
5243
5762
|
if (msg.role !== "tool") return msg;
|
|
5244
5763
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
5245
5764
|
if (content.length <= maxTokens) return msg;
|
|
5246
|
-
const beforeTokens =
|
|
5765
|
+
const beforeTokens = countTokensBounded(content);
|
|
5247
5766
|
if (beforeTokens <= maxTokens) return msg;
|
|
5248
5767
|
const truncated = truncateForModelByTokens(content, maxTokens);
|
|
5249
5768
|
const afterTokens = countTokens(truncated);
|
|
@@ -5348,32 +5867,6 @@ function* hookWarnings(outcomes, turn) {
|
|
|
5348
5867
|
}
|
|
5349
5868
|
}
|
|
5350
5869
|
|
|
5351
|
-
// src/loop/read-only-loop-tracker.ts
|
|
5352
|
-
var READONLY_LOOP_ESCALATION_THRESHOLD = 8;
|
|
5353
|
-
var ReadOnlyLoopTracker = class {
|
|
5354
|
-
streak = 0;
|
|
5355
|
-
threshold;
|
|
5356
|
-
constructor(threshold = READONLY_LOOP_ESCALATION_THRESHOLD) {
|
|
5357
|
-
this.threshold = Math.max(1, threshold);
|
|
5358
|
-
}
|
|
5359
|
-
reset() {
|
|
5360
|
-
this.streak = 0;
|
|
5361
|
-
}
|
|
5362
|
-
/** True ONLY on the call where the streak crosses the configured threshold. */
|
|
5363
|
-
noteAndCrossedThreshold(isReadOnly) {
|
|
5364
|
-
if (!isReadOnly) {
|
|
5365
|
-
this.streak = 0;
|
|
5366
|
-
return false;
|
|
5367
|
-
}
|
|
5368
|
-
const before = this.streak;
|
|
5369
|
-
this.streak += 1;
|
|
5370
|
-
return before < this.threshold && this.streak >= this.threshold;
|
|
5371
|
-
}
|
|
5372
|
-
get currentStreak() {
|
|
5373
|
-
return this.streak;
|
|
5374
|
-
}
|
|
5375
|
-
};
|
|
5376
|
-
|
|
5377
5870
|
// src/loop/turn-failure-tracker.ts
|
|
5378
5871
|
var FAILURE_ESCALATION_THRESHOLD = 3;
|
|
5379
5872
|
var TurnFailureTracker = class {
|
|
@@ -5703,11 +6196,16 @@ var StormBreaker = class {
|
|
|
5703
6196
|
function repairTruncatedJson(input) {
|
|
5704
6197
|
const notes = [];
|
|
5705
6198
|
if (!input || !input.trim()) {
|
|
5706
|
-
return {
|
|
6199
|
+
return {
|
|
6200
|
+
repaired: "{}",
|
|
6201
|
+
changed: input !== "{}",
|
|
6202
|
+
notes: ["empty input \u2192 {}"],
|
|
6203
|
+
fallback: false
|
|
6204
|
+
};
|
|
5707
6205
|
}
|
|
5708
6206
|
try {
|
|
5709
6207
|
JSON.parse(input);
|
|
5710
|
-
return { repaired: input, changed: false, notes: [] };
|
|
6208
|
+
return { repaired: input, changed: false, notes: [], fallback: false };
|
|
5711
6209
|
} catch {
|
|
5712
6210
|
}
|
|
5713
6211
|
const stack = [];
|
|
@@ -5762,10 +6260,12 @@ function repairTruncatedJson(input) {
|
|
|
5762
6260
|
}
|
|
5763
6261
|
try {
|
|
5764
6262
|
JSON.parse(s);
|
|
5765
|
-
return { repaired: s, changed:
|
|
6263
|
+
return { repaired: s, changed: s !== input, notes, fallback: false };
|
|
5766
6264
|
} catch (err) {
|
|
6265
|
+
const preview = input.length <= 500 ? input : `${input.slice(0, 500)} \u2026[+${input.length - 500} chars]`;
|
|
5767
6266
|
notes.push(`fallback to {}: ${err.message}`);
|
|
5768
|
-
|
|
6267
|
+
notes.push(`unrecoverable truncation \u2014 original args preview: ${preview}`);
|
|
6268
|
+
return { repaired: "{}", changed: true, notes, fallback: true };
|
|
5769
6269
|
}
|
|
5770
6270
|
}
|
|
5771
6271
|
|
|
@@ -5812,9 +6312,16 @@ var ToolCallRepair = class {
|
|
|
5812
6312
|
const args = call.function?.arguments ?? "";
|
|
5813
6313
|
const r = repairTruncatedJson(args);
|
|
5814
6314
|
if (r.changed) {
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
6315
|
+
if (r.fallback) {
|
|
6316
|
+
report.truncationsFixed++;
|
|
6317
|
+
report.notes.push(
|
|
6318
|
+
...r.notes.map((n) => `[${call.function?.name}] \u26A0\uFE0F TRUNCATION UNRECOVERABLE: ${n}`)
|
|
6319
|
+
);
|
|
6320
|
+
} else {
|
|
6321
|
+
call.function.arguments = r.repaired;
|
|
6322
|
+
report.truncationsFixed++;
|
|
6323
|
+
report.notes.push(...r.notes.map((n) => `[${call.function.name}] ${n}`));
|
|
6324
|
+
}
|
|
5818
6325
|
}
|
|
5819
6326
|
}
|
|
5820
6327
|
const filtered = [];
|
|
@@ -5836,12 +6343,10 @@ function signature(call) {
|
|
|
5836
6343
|
|
|
5837
6344
|
// src/loop.ts
|
|
5838
6345
|
var ESCALATION_MODEL = "deepseek-v4-pro";
|
|
5839
|
-
var PARENT_BUDGET_WARN_THRESHOLD = 5;
|
|
5840
6346
|
var CacheFirstLoop = class {
|
|
5841
6347
|
client;
|
|
5842
6348
|
prefix;
|
|
5843
6349
|
tools;
|
|
5844
|
-
maxToolIters;
|
|
5845
6350
|
log = new AppendOnlyLog();
|
|
5846
6351
|
scratch = new VolatileScratch();
|
|
5847
6352
|
stats = new SessionStats();
|
|
@@ -5869,13 +6374,25 @@ var CacheFirstLoop = class {
|
|
|
5869
6374
|
_turnAbort = new AbortController();
|
|
5870
6375
|
/** Authoritative running-id set — UI cards consult this instead of trusting end-event delivery. Insert at dispatch entry, delete in finally. */
|
|
5871
6376
|
_inflight = new InflightSet();
|
|
6377
|
+
/** Typeahead steer message set by the UI; step() consumes it at the next iter boundary. */
|
|
6378
|
+
_steer = null;
|
|
6379
|
+
/** Set true when a steer was consumed this turn; cleared on next step() entry. */
|
|
6380
|
+
_steerConsumed = false;
|
|
6381
|
+
/** UI calls this to inject a mid-turn steer message without aborting the current turn.
|
|
6382
|
+
* New text resets steerConsumed — a fresh steer hasn't been consumed yet. */
|
|
6383
|
+
steer(text) {
|
|
6384
|
+
this._steer = text;
|
|
6385
|
+
if (text !== null) this._steerConsumed = false;
|
|
6386
|
+
}
|
|
6387
|
+
/** True when a steer was consumed this turn (UI gate to avoid double-submit). */
|
|
6388
|
+
get steerConsumed() {
|
|
6389
|
+
return this._steerConsumed;
|
|
6390
|
+
}
|
|
5872
6391
|
_proArmedForNextTurn = false;
|
|
5873
6392
|
_escalateThisTurn = false;
|
|
5874
6393
|
_turnFailures;
|
|
5875
|
-
_readOnlyLoop;
|
|
5876
6394
|
_turnSelfCorrected = false;
|
|
5877
6395
|
_foldedThisTurn = false;
|
|
5878
|
-
_toolDispatchesThisStep = 0;
|
|
5879
6396
|
context;
|
|
5880
6397
|
/** Subscribe API so UI hooks can derive `running` from finally-guaranteed insertions. */
|
|
5881
6398
|
get inflight() {
|
|
@@ -5895,10 +6412,6 @@ var CacheFirstLoop = class {
|
|
|
5895
6412
|
this._turnFailures = new TurnFailureTracker(
|
|
5896
6413
|
resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
|
|
5897
6414
|
);
|
|
5898
|
-
this._readOnlyLoop = new ReadOnlyLoopTracker(
|
|
5899
|
-
parsePositiveIntEnv(process.env.REASONIX_READONLY_LOOP_THRESHOLD) ?? READONLY_LOOP_ESCALATION_THRESHOLD
|
|
5900
|
-
);
|
|
5901
|
-
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
5902
6415
|
this.hooks = opts.hooks ?? [];
|
|
5903
6416
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
5904
6417
|
this.confirmationGate = opts.confirmationGate ?? pauseGate;
|
|
@@ -5919,23 +6432,6 @@ var CacheFirstLoop = class {
|
|
|
5919
6432
|
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
5920
6433
|
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
5921
6434
|
});
|
|
5922
|
-
if (!this.tools.hasResultAugmenter) {
|
|
5923
|
-
this.tools.setResultAugmenter((_name, _args, result) => {
|
|
5924
|
-
this._toolDispatchesThisStep++;
|
|
5925
|
-
const remaining = this.maxToolIters - this._toolDispatchesThisStep;
|
|
5926
|
-
if (remaining <= 0) {
|
|
5927
|
-
return `${result}
|
|
5928
|
-
|
|
5929
|
-
[budget: 0 of ${this.maxToolIters} tool calls left this turn \u2014 finalize NOW; the next iter forces a summary]`;
|
|
5930
|
-
}
|
|
5931
|
-
if (remaining <= PARENT_BUDGET_WARN_THRESHOLD) {
|
|
5932
|
-
return `${result}
|
|
5933
|
-
|
|
5934
|
-
[budget: ${remaining} of ${this.maxToolIters} tool calls left this turn \u2014 wrap up soon]`;
|
|
5935
|
-
}
|
|
5936
|
-
return result;
|
|
5937
|
-
});
|
|
5938
|
-
}
|
|
5939
6435
|
this.sessionName = opts.session ?? null;
|
|
5940
6436
|
if (this.sessionName) {
|
|
5941
6437
|
const prior = loadSessionMessages(this.sessionName);
|
|
@@ -6021,6 +6517,10 @@ var CacheFirstLoop = class {
|
|
|
6021
6517
|
}
|
|
6022
6518
|
this.scratch.reset();
|
|
6023
6519
|
this._inflight.clear();
|
|
6520
|
+
this.stats.reset();
|
|
6521
|
+
this._turn = 0;
|
|
6522
|
+
this._turnFailures.reset();
|
|
6523
|
+
this._budgetWarned = false;
|
|
6024
6524
|
let systemRebuilt = false;
|
|
6025
6525
|
if (this._rebuildSystem) {
|
|
6026
6526
|
try {
|
|
@@ -6030,6 +6530,29 @@ var CacheFirstLoop = class {
|
|
|
6030
6530
|
}
|
|
6031
6531
|
return { dropped, archived, systemRebuilt };
|
|
6032
6532
|
}
|
|
6533
|
+
/** `/cwd` follow-through — archives the previous session, drops in-memory state, repoints sessionName, and rebuilds the system prompt against whatever the rebuilder closure now resolves (the caller is expected to have already updated the root the closure reads). */
|
|
6534
|
+
switchWorkspace(opts) {
|
|
6535
|
+
const dropped = this.log.length;
|
|
6536
|
+
let archived = null;
|
|
6537
|
+
if (this.sessionName) {
|
|
6538
|
+
try {
|
|
6539
|
+
archived = archiveSession(this.sessionName);
|
|
6540
|
+
if (archived === null) rewriteSession(this.sessionName, []);
|
|
6541
|
+
} catch {
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
this.log.compactInPlace([]);
|
|
6545
|
+
this.scratch.reset();
|
|
6546
|
+
this._inflight.clear();
|
|
6547
|
+
this.sessionName = opts.sessionName;
|
|
6548
|
+
if (this._rebuildSystem) {
|
|
6549
|
+
try {
|
|
6550
|
+
this.prefix.replaceSystem(this._rebuildSystem());
|
|
6551
|
+
} catch {
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
return { dropped, archived };
|
|
6555
|
+
}
|
|
6033
6556
|
configure(opts) {
|
|
6034
6557
|
if (opts.model !== void 0) this.model = opts.model;
|
|
6035
6558
|
if (opts.stream !== void 0) {
|
|
@@ -6074,14 +6597,6 @@ var CacheFirstLoop = class {
|
|
|
6074
6597
|
this._escalateThisTurn = true;
|
|
6075
6598
|
return true;
|
|
6076
6599
|
}
|
|
6077
|
-
/** Returns true ONLY on the call where the read-only streak crosses the threshold (#681). */
|
|
6078
|
-
noteReadOnlyToolCall(call) {
|
|
6079
|
-
const isReadOnly = !this.isMutating(call);
|
|
6080
|
-
if (!this._readOnlyLoop.noteAndCrossedThreshold(isReadOnly)) return false;
|
|
6081
|
-
if (this._escalateThisTurn || !this.autoEscalate) return false;
|
|
6082
|
-
this._escalateThisTurn = true;
|
|
6083
|
-
return true;
|
|
6084
|
-
}
|
|
6085
6600
|
/** A call counts as mutating when its definition reports `readOnly !== true` and any dynamic `readOnlyCheck` doesn't override that for these args. */
|
|
6086
6601
|
isMutating(call) {
|
|
6087
6602
|
const name = call.function?.name;
|
|
@@ -6193,6 +6708,7 @@ ${reason}`
|
|
|
6193
6708
|
return userText;
|
|
6194
6709
|
}
|
|
6195
6710
|
async *step(userInput) {
|
|
6711
|
+
this._steerConsumed = false;
|
|
6196
6712
|
if (this.budgetUsd !== null) {
|
|
6197
6713
|
const spent = this.stats.totalCost;
|
|
6198
6714
|
if (spent >= this.budgetUsd) {
|
|
@@ -6223,11 +6739,9 @@ ${reason}`
|
|
|
6223
6739
|
this.scratch.reset();
|
|
6224
6740
|
this.repair.resetStorm();
|
|
6225
6741
|
this._turnFailures.reset();
|
|
6226
|
-
this._readOnlyLoop.reset();
|
|
6227
6742
|
this._turnSelfCorrected = false;
|
|
6228
6743
|
this._escalateThisTurn = false;
|
|
6229
6744
|
this._foldedThisTurn = false;
|
|
6230
|
-
this._toolDispatchesThisStep = 0;
|
|
6231
6745
|
let armedConsumed = false;
|
|
6232
6746
|
if (this._proArmedForNextTurn) {
|
|
6233
6747
|
this._escalateThisTurn = true;
|
|
@@ -6245,16 +6759,15 @@ ${reason}`
|
|
|
6245
6759
|
content: t("loop.proArmed")
|
|
6246
6760
|
};
|
|
6247
6761
|
}
|
|
6248
|
-
|
|
6762
|
+
this.appendAndPersist({ role: "user", content: userInput });
|
|
6763
|
+
let pendingUser = null;
|
|
6249
6764
|
const toolSpecs = this.prefix.tools();
|
|
6250
|
-
|
|
6251
|
-
let warnedForIterBudget = false;
|
|
6252
|
-
for (let iter = 0; iter < this.maxToolIters; iter++) {
|
|
6765
|
+
for (let iter = 0; ; iter++) {
|
|
6253
6766
|
if (signal.aborted) {
|
|
6254
6767
|
yield {
|
|
6255
6768
|
turn: this._turn,
|
|
6256
6769
|
role: "warning",
|
|
6257
|
-
content: t("loop.abortedAtIter", { iter
|
|
6770
|
+
content: t("loop.abortedAtIter", { iter })
|
|
6258
6771
|
};
|
|
6259
6772
|
const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
|
|
6260
6773
|
this.appendAndPersist(buildSyntheticAssistantMessage(stoppedMsg, this.model));
|
|
@@ -6275,15 +6788,20 @@ ${reason}`
|
|
|
6275
6788
|
content: t("loop.toolUploadStatus")
|
|
6276
6789
|
};
|
|
6277
6790
|
}
|
|
6278
|
-
|
|
6279
|
-
|
|
6791
|
+
let messages = this.buildMessages(pendingUser);
|
|
6792
|
+
if (this._steer !== null) {
|
|
6793
|
+
const steer = this._steer;
|
|
6794
|
+
this._steer = null;
|
|
6795
|
+
this._steerConsumed = true;
|
|
6796
|
+
this.appendAndPersist({ role: "user", content: steer });
|
|
6797
|
+
messages = this.buildMessages(pendingUser);
|
|
6798
|
+
pendingUser = null;
|
|
6280
6799
|
yield {
|
|
6281
6800
|
turn: this._turn,
|
|
6282
|
-
role: "
|
|
6283
|
-
content:
|
|
6801
|
+
role: "steer",
|
|
6802
|
+
content: steer
|
|
6284
6803
|
};
|
|
6285
6804
|
}
|
|
6286
|
-
let messages = this.buildMessages(pendingUser);
|
|
6287
6805
|
{
|
|
6288
6806
|
const decision2 = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
6289
6807
|
if (decision2.needsAction) {
|
|
@@ -6291,23 +6809,29 @@ ${reason}`
|
|
|
6291
6809
|
yield {
|
|
6292
6810
|
turn: this._turn,
|
|
6293
6811
|
role: "status",
|
|
6294
|
-
content: t("loop.
|
|
6812
|
+
content: t("loop.preflightTruncateStatus")
|
|
6295
6813
|
};
|
|
6296
|
-
const result =
|
|
6814
|
+
const result = this.context.mechanicalTruncate(this.model, {
|
|
6815
|
+
allowEmpty: pendingUser !== null
|
|
6816
|
+
});
|
|
6297
6817
|
if (result.folded) {
|
|
6818
|
+
messages = this.buildMessages(pendingUser);
|
|
6819
|
+
const after = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
6820
|
+
const stillFull = after.needsAction;
|
|
6298
6821
|
yield {
|
|
6299
6822
|
turn: this._turn,
|
|
6300
6823
|
role: "warning",
|
|
6301
|
-
content: t(
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6824
|
+
content: t(
|
|
6825
|
+
stillFull ? "loop.preflightTruncatedStillFull" : "loop.preflightTruncated",
|
|
6826
|
+
{
|
|
6827
|
+
estimate: after.estimateTokens.toLocaleString(),
|
|
6828
|
+
ctxMax: after.ctxMax.toLocaleString(),
|
|
6829
|
+
pct: Math.round(after.estimateTokens / after.ctxMax * 100),
|
|
6830
|
+
beforeMessages: result.beforeMessages,
|
|
6831
|
+
afterMessages: result.afterMessages
|
|
6832
|
+
}
|
|
6833
|
+
)
|
|
6309
6834
|
};
|
|
6310
|
-
messages = this.buildMessages(pendingUser);
|
|
6311
6835
|
} else {
|
|
6312
6836
|
yield {
|
|
6313
6837
|
turn: this._turn,
|
|
@@ -6341,6 +6865,15 @@ ${reason}`
|
|
|
6341
6865
|
thinking: thinkingModeForModel(callModel),
|
|
6342
6866
|
reasoningEffort: this.reasoningEffort
|
|
6343
6867
|
})) {
|
|
6868
|
+
if (chunk.reasoningDelta) {
|
|
6869
|
+
reasoningContent += chunk.reasoningDelta;
|
|
6870
|
+
yield {
|
|
6871
|
+
turn: this._turn,
|
|
6872
|
+
role: "assistant_delta",
|
|
6873
|
+
content: "",
|
|
6874
|
+
reasoningDelta: chunk.reasoningDelta
|
|
6875
|
+
};
|
|
6876
|
+
}
|
|
6344
6877
|
if (chunk.contentDelta) {
|
|
6345
6878
|
assistantContent += chunk.contentDelta;
|
|
6346
6879
|
if (bufferForEscalation && !escalationBufFlushed) {
|
|
@@ -6365,15 +6898,6 @@ ${reason}`
|
|
|
6365
6898
|
};
|
|
6366
6899
|
}
|
|
6367
6900
|
}
|
|
6368
|
-
if (chunk.reasoningDelta) {
|
|
6369
|
-
reasoningContent += chunk.reasoningDelta;
|
|
6370
|
-
yield {
|
|
6371
|
-
turn: this._turn,
|
|
6372
|
-
role: "assistant_delta",
|
|
6373
|
-
content: "",
|
|
6374
|
-
reasoningDelta: chunk.reasoningDelta
|
|
6375
|
-
};
|
|
6376
|
-
}
|
|
6377
6901
|
if (chunk.toolCallDelta) {
|
|
6378
6902
|
const d = chunk.toolCallDelta;
|
|
6379
6903
|
const cur = callBuf.get(d.index) ?? {
|
|
@@ -6464,10 +6988,6 @@ ${reason}`
|
|
|
6464
6988
|
this.modelForCurrentCall(),
|
|
6465
6989
|
usage ?? new Usage()
|
|
6466
6990
|
);
|
|
6467
|
-
if (pendingUser !== null) {
|
|
6468
|
-
this.appendAndPersist({ role: "user", content: pendingUser });
|
|
6469
|
-
pendingUser = null;
|
|
6470
|
-
}
|
|
6471
6991
|
this.scratch.reasoning = reasoningContent || null;
|
|
6472
6992
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
6473
6993
|
toolCalls,
|
|
@@ -6637,26 +7157,15 @@ ${reason}`
|
|
|
6637
7157
|
role: "tool",
|
|
6638
7158
|
tool_call_id: call.id ?? "",
|
|
6639
7159
|
name,
|
|
6640
|
-
content: result
|
|
6641
|
-
});
|
|
6642
|
-
if (this.noteToolFailureSignal(result)) {
|
|
6643
|
-
yield {
|
|
6644
|
-
turn: this._turn,
|
|
6645
|
-
role: "warning",
|
|
6646
|
-
content: t("loop.autoEscalation", {
|
|
6647
|
-
model: ESCALATION_MODEL,
|
|
6648
|
-
breakdown: this._turnFailures.formatBreakdown(),
|
|
6649
|
-
fallback: this.model
|
|
6650
|
-
})
|
|
6651
|
-
};
|
|
6652
|
-
}
|
|
6653
|
-
if (this.noteReadOnlyToolCall(call)) {
|
|
7160
|
+
content: result
|
|
7161
|
+
});
|
|
7162
|
+
if (this.noteToolFailureSignal(result)) {
|
|
6654
7163
|
yield {
|
|
6655
7164
|
turn: this._turn,
|
|
6656
7165
|
role: "warning",
|
|
6657
|
-
content: t("loop.
|
|
7166
|
+
content: t("loop.autoEscalation", {
|
|
6658
7167
|
model: ESCALATION_MODEL,
|
|
6659
|
-
|
|
7168
|
+
breakdown: this._turnFailures.formatBreakdown(),
|
|
6660
7169
|
fallback: this.model
|
|
6661
7170
|
})
|
|
6662
7171
|
};
|
|
@@ -6672,7 +7181,6 @@ ${reason}`
|
|
|
6672
7181
|
}
|
|
6673
7182
|
}
|
|
6674
7183
|
}
|
|
6675
|
-
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "budget" });
|
|
6676
7184
|
}
|
|
6677
7185
|
summaryContext() {
|
|
6678
7186
|
return {
|
|
@@ -6681,8 +7189,7 @@ ${reason}`
|
|
|
6681
7189
|
buildMessages: () => this.buildMessages(null),
|
|
6682
7190
|
appendAndPersist: (m) => this.appendAndPersist(m),
|
|
6683
7191
|
recordStats: (model, usage) => this.stats.record(this._turn, model, usage),
|
|
6684
|
-
turn: this._turn
|
|
6685
|
-
maxToolIters: this.maxToolIters
|
|
7192
|
+
turn: this._turn
|
|
6686
7193
|
};
|
|
6687
7194
|
}
|
|
6688
7195
|
async run(userInput, onEvent) {
|
|
@@ -6717,7 +7224,7 @@ function resolveFailureThreshold(raw, fallback) {
|
|
|
6717
7224
|
// src/at-mentions.ts
|
|
6718
7225
|
import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
6719
7226
|
import { readdir, stat } from "fs/promises";
|
|
6720
|
-
import { isAbsolute, join as join5, relative, resolve } from "path";
|
|
7227
|
+
import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve2 } from "path";
|
|
6721
7228
|
|
|
6722
7229
|
// src/gitignore.ts
|
|
6723
7230
|
import { readFileSync as readFileSync5 } from "fs";
|
|
@@ -6772,7 +7279,7 @@ function listFilesSync(root, opts = {}) {
|
|
|
6772
7279
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
6773
7280
|
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
6774
7281
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
6775
|
-
const rootAbs =
|
|
7282
|
+
const rootAbs = resolve2(root);
|
|
6776
7283
|
const respectGi = opts.respectGitignore !== false;
|
|
6777
7284
|
const out = [];
|
|
6778
7285
|
const walk2 = (dirAbs, dirRel, layers) => {
|
|
@@ -6836,7 +7343,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
6836
7343
|
async function walkFilesStream(root, opts) {
|
|
6837
7344
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
6838
7345
|
const respectGi = opts.respectGitignore !== false;
|
|
6839
|
-
const rootAbs =
|
|
7346
|
+
const rootAbs = resolve2(root);
|
|
6840
7347
|
const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
|
|
6841
7348
|
let scanned = 0;
|
|
6842
7349
|
let halted = false;
|
|
@@ -6914,10 +7421,10 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
|
|
|
6914
7421
|
async function listDirectory(root, relDir, opts = {}) {
|
|
6915
7422
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
6916
7423
|
const respectGi = opts.respectGitignore !== false;
|
|
6917
|
-
const rootAbs =
|
|
6918
|
-
const dirAbs =
|
|
7424
|
+
const rootAbs = resolve2(root);
|
|
7425
|
+
const dirAbs = resolve2(rootAbs, relDir);
|
|
6919
7426
|
const rel = relative(rootAbs, dirAbs);
|
|
6920
|
-
if (rel.startsWith("..") ||
|
|
7427
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) return [];
|
|
6921
7428
|
const layers = [];
|
|
6922
7429
|
if (respectGi) {
|
|
6923
7430
|
const segs = rel ? rel.split(/[\\/]/) : [];
|
|
@@ -7080,7 +7587,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
7080
7587
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
7081
7588
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
7082
7589
|
const fs5 = opts.fs ?? defaultFs;
|
|
7083
|
-
const root =
|
|
7590
|
+
const root = resolve2(rootDir);
|
|
7084
7591
|
const seen = /* @__PURE__ */ new Map();
|
|
7085
7592
|
const expansions = [];
|
|
7086
7593
|
const dirListings = /* @__PURE__ */ new Map();
|
|
@@ -7124,12 +7631,12 @@ ${blocks.join("\n\n")}`;
|
|
|
7124
7631
|
return { text: augmented, expansions };
|
|
7125
7632
|
}
|
|
7126
7633
|
function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings) {
|
|
7127
|
-
if (
|
|
7634
|
+
if (isAbsolute2(rawPath)) {
|
|
7128
7635
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
7129
7636
|
}
|
|
7130
|
-
const resolved =
|
|
7637
|
+
const resolved = resolve2(root, rawPath);
|
|
7131
7638
|
const rel = relative(root, resolved);
|
|
7132
|
-
if (rel.startsWith("..") ||
|
|
7639
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
7133
7640
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
7134
7641
|
}
|
|
7135
7642
|
if (!fs5.exists(resolved)) {
|
|
@@ -7157,7 +7664,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings
|
|
|
7157
7664
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
7158
7665
|
}
|
|
7159
7666
|
function readSafe(root, rawPath, fs5) {
|
|
7160
|
-
const resolved =
|
|
7667
|
+
const resolved = resolve2(root, rawPath);
|
|
7161
7668
|
try {
|
|
7162
7669
|
return fs5.read(resolved);
|
|
7163
7670
|
} catch {
|
|
@@ -7267,7 +7774,7 @@ import {
|
|
|
7267
7774
|
writeFileSync as writeFileSync4
|
|
7268
7775
|
} from "fs";
|
|
7269
7776
|
import { homedir as homedir5 } from "os";
|
|
7270
|
-
import { join as join8, resolve as
|
|
7777
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
7271
7778
|
|
|
7272
7779
|
// src/frontmatter.ts
|
|
7273
7780
|
var KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/;
|
|
@@ -7317,9 +7824,18 @@ function parseFrontmatter(raw) {
|
|
|
7317
7824
|
}
|
|
7318
7825
|
|
|
7319
7826
|
// src/skills.ts
|
|
7320
|
-
import {
|
|
7827
|
+
import {
|
|
7828
|
+
constants,
|
|
7829
|
+
existsSync as existsSync6,
|
|
7830
|
+
mkdirSync as mkdirSync3,
|
|
7831
|
+
readFileSync as readFileSync8,
|
|
7832
|
+
readdirSync as readdirSync3,
|
|
7833
|
+
statSync as statSync4,
|
|
7834
|
+
writeFileSync as writeFileSync3
|
|
7835
|
+
} from "fs";
|
|
7836
|
+
import { accessSync } from "fs";
|
|
7321
7837
|
import { homedir as homedir4 } from "os";
|
|
7322
|
-
import { dirname as dirname4, join as join7, resolve as
|
|
7838
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join7, resolve as resolve3 } from "path";
|
|
7323
7839
|
|
|
7324
7840
|
// src/prompt-fragments.ts
|
|
7325
7841
|
var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown renderer):
|
|
@@ -7367,17 +7883,22 @@ function parseAllowedTools(raw) {
|
|
|
7367
7883
|
var SkillStore = class {
|
|
7368
7884
|
homeDir;
|
|
7369
7885
|
projectRoot;
|
|
7886
|
+
customSkillPaths;
|
|
7370
7887
|
disableBuiltins;
|
|
7371
7888
|
constructor(opts = {}) {
|
|
7372
7889
|
this.homeDir = opts.homeDir ?? homedir4();
|
|
7373
|
-
this.projectRoot = opts.projectRoot ?
|
|
7890
|
+
this.projectRoot = opts.projectRoot ? resolve3(opts.projectRoot) : void 0;
|
|
7891
|
+
const baseDir = this.projectRoot ?? process.cwd();
|
|
7892
|
+
this.customSkillPaths = dedupePaths(
|
|
7893
|
+
opts.customSkillPaths?.map((p) => resolveCustomSkillPath(p, baseDir, this.homeDir)) ?? []
|
|
7894
|
+
);
|
|
7374
7895
|
this.disableBuiltins = opts.disableBuiltins === true;
|
|
7375
7896
|
}
|
|
7376
7897
|
/** True iff this store was configured with a project root. */
|
|
7377
7898
|
hasProjectScope() {
|
|
7378
7899
|
return this.projectRoot !== void 0;
|
|
7379
7900
|
}
|
|
7380
|
-
/** Project scope first so per-repo skill overrides
|
|
7901
|
+
/** Project scope first so per-repo skill overrides custom/global entries with the same name. */
|
|
7381
7902
|
roots() {
|
|
7382
7903
|
const out = [];
|
|
7383
7904
|
if (this.projectRoot) {
|
|
@@ -7385,15 +7906,24 @@ var SkillStore = class {
|
|
|
7385
7906
|
dir: join7(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
|
|
7386
7907
|
scope: "project"
|
|
7387
7908
|
});
|
|
7909
|
+
out.push({
|
|
7910
|
+
dir: join7(this.projectRoot, ".agents", SKILLS_DIRNAME),
|
|
7911
|
+
scope: "project"
|
|
7912
|
+
});
|
|
7388
7913
|
}
|
|
7914
|
+
for (const dir of this.customSkillPaths) out.push({ dir, scope: "custom" });
|
|
7389
7915
|
out.push({ dir: join7(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
|
|
7390
|
-
|
|
7916
|
+
out.push({ dir: join7(this.homeDir, ".agents", SKILLS_DIRNAME), scope: "global" });
|
|
7917
|
+
return out.map((root, priority) => ({ ...root, priority, status: skillPathStatus(root.dir) }));
|
|
7918
|
+
}
|
|
7919
|
+
customRoots() {
|
|
7920
|
+
return this.roots().filter((root) => root.scope === "custom");
|
|
7391
7921
|
}
|
|
7392
|
-
/** Higher-priority root wins on collision (project > global > builtin); sorted for stable prefix hash. */
|
|
7922
|
+
/** Higher-priority root wins on collision (project > custom > global > builtin); sorted for stable prefix hash. */
|
|
7393
7923
|
list() {
|
|
7394
7924
|
const byName = /* @__PURE__ */ new Map();
|
|
7395
|
-
for (const { dir, scope } of this.roots()) {
|
|
7396
|
-
if (
|
|
7925
|
+
for (const { dir, scope, status } of this.roots()) {
|
|
7926
|
+
if (status !== "ok") continue;
|
|
7397
7927
|
let entries;
|
|
7398
7928
|
try {
|
|
7399
7929
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -7445,8 +7975,8 @@ var SkillStore = class {
|
|
|
7445
7975
|
/** Resolve one skill by name. Returns `null` if not found or malformed. */
|
|
7446
7976
|
read(name) {
|
|
7447
7977
|
if (!isValidSkillName(name)) return null;
|
|
7448
|
-
for (const { dir, scope } of this.roots()) {
|
|
7449
|
-
if (
|
|
7978
|
+
for (const { dir, scope, status } of this.roots()) {
|
|
7979
|
+
if (status !== "ok") continue;
|
|
7450
7980
|
const dirCandidate = join7(dir, name, SKILL_FILE);
|
|
7451
7981
|
if (existsSync6(dirCandidate) && statSync4(dirCandidate).isFile()) {
|
|
7452
7982
|
return this.parse(dirCandidate, name, scope);
|
|
@@ -7498,6 +8028,34 @@ var SkillStore = class {
|
|
|
7498
8028
|
};
|
|
7499
8029
|
}
|
|
7500
8030
|
};
|
|
8031
|
+
function dedupePaths(paths) {
|
|
8032
|
+
const out = [];
|
|
8033
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8034
|
+
for (const path2 of paths) {
|
|
8035
|
+
const key = process.platform === "win32" ? path2.toLowerCase() : path2;
|
|
8036
|
+
if (seen.has(key)) continue;
|
|
8037
|
+
seen.add(key);
|
|
8038
|
+
out.push(path2);
|
|
8039
|
+
}
|
|
8040
|
+
return out;
|
|
8041
|
+
}
|
|
8042
|
+
function resolveCustomSkillPath(path2, baseDir, homeDir) {
|
|
8043
|
+
const trimmed = path2.trim();
|
|
8044
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") || trimmed.startsWith("~\\") ? join7(homeDir, trimmed.slice(2)) : trimmed;
|
|
8045
|
+
return resolve3(isAbsolute3(expanded) ? expanded : join7(baseDir, expanded));
|
|
8046
|
+
}
|
|
8047
|
+
function skillPathStatus(dir) {
|
|
8048
|
+
try {
|
|
8049
|
+
const stat2 = statSync4(dir);
|
|
8050
|
+
if (!stat2.isDirectory()) return "not-directory";
|
|
8051
|
+
accessSync(dir, constants.R_OK);
|
|
8052
|
+
return "ok";
|
|
8053
|
+
} catch (err) {
|
|
8054
|
+
const code = err.code;
|
|
8055
|
+
if (code === "ENOENT") return "missing";
|
|
8056
|
+
return "unreadable";
|
|
8057
|
+
}
|
|
8058
|
+
}
|
|
7501
8059
|
function parseRunAs(raw) {
|
|
7502
8060
|
return raw?.trim() === "subagent" ? "subagent" : "inline";
|
|
7503
8061
|
}
|
|
@@ -7749,7 +8307,7 @@ function sanitizeMemoryName(raw) {
|
|
|
7749
8307
|
return trimmed;
|
|
7750
8308
|
}
|
|
7751
8309
|
function projectHash(rootDir) {
|
|
7752
|
-
const abs =
|
|
8310
|
+
const abs = resolve4(rootDir);
|
|
7753
8311
|
return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
|
|
7754
8312
|
}
|
|
7755
8313
|
function scopeDir(opts) {
|
|
@@ -7799,7 +8357,7 @@ var MemoryStore = class {
|
|
|
7799
8357
|
projectRoot;
|
|
7800
8358
|
constructor(opts = {}) {
|
|
7801
8359
|
this.homeDir = opts.homeDir ?? join8(homedir5(), ".reasonix");
|
|
7802
|
-
this.projectRoot = opts.projectRoot ?
|
|
8360
|
+
this.projectRoot = opts.projectRoot ? resolve4(opts.projectRoot) : void 0;
|
|
7803
8361
|
}
|
|
7804
8362
|
/** Directory this store writes `scope` files into, creating it if needed. */
|
|
7805
8363
|
dir(scope) {
|
|
@@ -8040,11 +8598,17 @@ function applyUserMemory(basePrompt, opts = {}) {
|
|
|
8040
8598
|
}
|
|
8041
8599
|
return parts.join("\n");
|
|
8042
8600
|
}
|
|
8043
|
-
function applyMemoryStack(basePrompt, rootDir) {
|
|
8601
|
+
function applyMemoryStack(basePrompt, rootDir, opts = {}) {
|
|
8602
|
+
const homeDir = opts.homeDir;
|
|
8603
|
+
const cfg = opts.cfg;
|
|
8044
8604
|
const withProject = applyProjectMemory(basePrompt, rootDir);
|
|
8045
|
-
const withGlobal = applyGlobalReasonixMemory(
|
|
8046
|
-
|
|
8047
|
-
|
|
8605
|
+
const withGlobal = applyGlobalReasonixMemory(
|
|
8606
|
+
withProject,
|
|
8607
|
+
homeDir ? join8(homeDir, ".reasonix") : void 0
|
|
8608
|
+
);
|
|
8609
|
+
const withMemory = applyUserMemory(withGlobal, { projectRoot: rootDir, homeDir, cfg });
|
|
8610
|
+
const customSkillPaths = cfg?.skills?.paths ? resolveSkillPaths(cfg.skills.paths, rootDir) : loadResolvedSkillPaths(rootDir);
|
|
8611
|
+
return applySkillsIndex(withMemory, { projectRoot: rootDir, homeDir, customSkillPaths });
|
|
8048
8612
|
}
|
|
8049
8613
|
|
|
8050
8614
|
// src/tools/filesystem.ts
|
|
@@ -8052,6 +8616,51 @@ import { promises as fs4 } from "fs";
|
|
|
8052
8616
|
import * as pathMod5 from "path";
|
|
8053
8617
|
import picomatch3 from "picomatch";
|
|
8054
8618
|
|
|
8619
|
+
// src/memory/subdir.ts
|
|
8620
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
8621
|
+
import { dirname as dirname5, join as join9, relative as relative2, resolve as resolve5 } from "path";
|
|
8622
|
+
function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
8623
|
+
const root = resolve5(rootDir);
|
|
8624
|
+
const target = resolve5(absPath);
|
|
8625
|
+
const rel = relative2(root, target);
|
|
8626
|
+
if (!rel || rel.startsWith("..")) return [];
|
|
8627
|
+
const found = [];
|
|
8628
|
+
let cur = dirname5(target);
|
|
8629
|
+
while (cur !== root) {
|
|
8630
|
+
const r = relative2(root, cur);
|
|
8631
|
+
if (!r || r.startsWith("..")) break;
|
|
8632
|
+
for (const name of PROJECT_MEMORY_FILES) {
|
|
8633
|
+
const path2 = join9(cur, name);
|
|
8634
|
+
if (existsSync8(path2)) {
|
|
8635
|
+
found.push(path2);
|
|
8636
|
+
break;
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
const parent = dirname5(cur);
|
|
8640
|
+
if (parent === cur) break;
|
|
8641
|
+
cur = parent;
|
|
8642
|
+
}
|
|
8643
|
+
return found;
|
|
8644
|
+
}
|
|
8645
|
+
function readSubdirMemoryContent(path2) {
|
|
8646
|
+
let raw;
|
|
8647
|
+
try {
|
|
8648
|
+
raw = readFileSync10(path2, "utf8");
|
|
8649
|
+
} catch {
|
|
8650
|
+
return null;
|
|
8651
|
+
}
|
|
8652
|
+
const trimmed = raw.trim();
|
|
8653
|
+
if (!trimmed) return null;
|
|
8654
|
+
if (trimmed.length <= PROJECT_MEMORY_MAX_CHARS) return trimmed;
|
|
8655
|
+
return `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
|
|
8656
|
+
\u2026 (truncated ${trimmed.length - PROJECT_MEMORY_MAX_CHARS} chars)`;
|
|
8657
|
+
}
|
|
8658
|
+
function formatSubdirMemorySection(displayPath, content) {
|
|
8659
|
+
return `[module memory: ${displayPath}]
|
|
8660
|
+
|
|
8661
|
+
${content}`;
|
|
8662
|
+
}
|
|
8663
|
+
|
|
8055
8664
|
// src/tools/fs/edit.ts
|
|
8056
8665
|
import { promises as fs } from "fs";
|
|
8057
8666
|
import * as pathMod from "path";
|
|
@@ -8282,6 +8891,18 @@ var RUST_DECL_RE = /^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|str
|
|
|
8282
8891
|
var RUST_IMPL_RE = /^(?:unsafe\s+)?impl(?:\s*<[^>]+>)?\s+(?:[^{]+\s+for\s+)?(\w+)/;
|
|
8283
8892
|
var MD_HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
8284
8893
|
var MD_FENCE_RE = /^```/;
|
|
8894
|
+
var PROTO_TOP_RE = /^(message|service|enum|extend)\s+(\w+)/;
|
|
8895
|
+
var PROTO_RPC_RE = /^\s+rpc\s+(\w+)/;
|
|
8896
|
+
var CN_NUM = "[\\d\u96F6\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D\u5341\u767E\u5343\u4E07\uFF10-\uFF19]+";
|
|
8897
|
+
var TXT_CHAPTER_PATTERNS = [
|
|
8898
|
+
new RegExp(`^\u7B2C${CN_NUM}[\u7AE0\u8282\u56DE].{0,80}$`),
|
|
8899
|
+
new RegExp(`^\u5377${CN_NUM}.{0,80}$`),
|
|
8900
|
+
/^(?:序章|楔子|番外篇?|前言|后记|尾声|引子)(?:[\s\u3000::、—\-.].{0,80})?$/,
|
|
8901
|
+
/^Chapter\s+(?:\d+|[IVXLCDMivxlcdm]+|[A-Za-z]+)\b.{0,80}$/,
|
|
8902
|
+
/^CHAPTER\s+.{1,80}$/,
|
|
8903
|
+
/^Part\s+(?:\d+|[IVXLCDMivxlcdm]+)\b.{0,80}$/,
|
|
8904
|
+
/^PART\s+.{1,80}$/
|
|
8905
|
+
];
|
|
8285
8906
|
var EXT_TO_LANG = {
|
|
8286
8907
|
".ts": "ts",
|
|
8287
8908
|
".tsx": "ts",
|
|
@@ -8297,7 +8918,10 @@ var EXT_TO_LANG = {
|
|
|
8297
8918
|
".rs": "rust",
|
|
8298
8919
|
".md": "md",
|
|
8299
8920
|
".markdown": "md",
|
|
8300
|
-
".mdx": "md"
|
|
8921
|
+
".mdx": "md",
|
|
8922
|
+
".proto": "proto",
|
|
8923
|
+
".txt": "txt",
|
|
8924
|
+
".text": "txt"
|
|
8301
8925
|
};
|
|
8302
8926
|
function extractOutline(filename, lines) {
|
|
8303
8927
|
const ext = pathMod3.extname(filename).toLowerCase();
|
|
@@ -8314,6 +8938,10 @@ function extractOutline(filename, lines) {
|
|
|
8314
8938
|
return extractRust(lines);
|
|
8315
8939
|
case "md":
|
|
8316
8940
|
return extractMarkdown(lines);
|
|
8941
|
+
case "proto":
|
|
8942
|
+
return extractProto(lines);
|
|
8943
|
+
case "txt":
|
|
8944
|
+
return extractText(lines);
|
|
8317
8945
|
}
|
|
8318
8946
|
}
|
|
8319
8947
|
function extractTs(lines) {
|
|
@@ -8365,6 +8993,36 @@ function extractRust(lines) {
|
|
|
8365
8993
|
}
|
|
8366
8994
|
return out;
|
|
8367
8995
|
}
|
|
8996
|
+
function extractProto(lines) {
|
|
8997
|
+
const out = [];
|
|
8998
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8999
|
+
const line = lines[i];
|
|
9000
|
+
if (!line.startsWith(" ") && !line.startsWith(" ")) {
|
|
9001
|
+
const m = PROTO_TOP_RE.exec(line);
|
|
9002
|
+
if (m) {
|
|
9003
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
9004
|
+
continue;
|
|
9005
|
+
}
|
|
9006
|
+
}
|
|
9007
|
+
const rpc = PROTO_RPC_RE.exec(line);
|
|
9008
|
+
if (rpc) out.push({ line: i + 1, text: `rpc ${rpc[1]}` });
|
|
9009
|
+
}
|
|
9010
|
+
return out;
|
|
9011
|
+
}
|
|
9012
|
+
function extractText(lines) {
|
|
9013
|
+
const out = [];
|
|
9014
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9015
|
+
const line = lines[i].trim();
|
|
9016
|
+
if (line.length === 0 || line.length > 100) continue;
|
|
9017
|
+
for (const re of TXT_CHAPTER_PATTERNS) {
|
|
9018
|
+
if (re.test(line)) {
|
|
9019
|
+
out.push({ line: i + 1, text: line });
|
|
9020
|
+
break;
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
return out;
|
|
9025
|
+
}
|
|
8368
9026
|
function extractMarkdown(lines) {
|
|
8369
9027
|
const out = [];
|
|
8370
9028
|
let inFence = false;
|
|
@@ -8589,8 +9247,8 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
8589
9247
|
for (let i = realStart; i <= winEnd; i++) {
|
|
8590
9248
|
const line = lines[i];
|
|
8591
9249
|
const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
|
|
8592
|
-
const
|
|
8593
|
-
if (!pushLine(`${rel}:${i + 1}${
|
|
9250
|
+
const sep3 = hitSet.has(i) ? ":" : "-";
|
|
9251
|
+
if (!pushLine(`${rel}:${i + 1}${sep3} ${display}`)) return;
|
|
8594
9252
|
}
|
|
8595
9253
|
prevWindowEnd = winEnd;
|
|
8596
9254
|
}
|
|
@@ -8612,17 +9270,25 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
8612
9270
|
}
|
|
8613
9271
|
|
|
8614
9272
|
// src/tools/filesystem.ts
|
|
8615
|
-
var
|
|
9273
|
+
var DEFAULT_OUTLINE_THRESHOLD_BYTES = 512 * 1024;
|
|
8616
9274
|
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
8617
|
-
var
|
|
8618
|
-
var
|
|
8619
|
-
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
8620
|
-
var OUTLINE_MAX_ENTRIES2 = 30;
|
|
9275
|
+
var HARD_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
9276
|
+
var OUTLINE_HEAD_LINES = 80;
|
|
8621
9277
|
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
8622
9278
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
8623
9279
|
function displayRel4(rootDir, full) {
|
|
8624
9280
|
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
8625
9281
|
}
|
|
9282
|
+
function looksLikeAbsoluteSystemPath(raw) {
|
|
9283
|
+
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
9284
|
+
return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
|
|
9285
|
+
raw
|
|
9286
|
+
);
|
|
9287
|
+
}
|
|
9288
|
+
function pathIsUnder(child, parent) {
|
|
9289
|
+
const rel = pathMod5.relative(parent, child);
|
|
9290
|
+
return rel === "" || !rel.startsWith("..") && !pathMod5.isAbsolute(rel);
|
|
9291
|
+
}
|
|
8626
9292
|
var GLOB_METACHARS = /[*?{[]/;
|
|
8627
9293
|
function compileNameFilter(filter) {
|
|
8628
9294
|
if (!filter) return null;
|
|
@@ -8639,24 +9305,45 @@ function isLikelyBinaryByName(name) {
|
|
|
8639
9305
|
if (dot < 0) return false;
|
|
8640
9306
|
return BINARY_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
8641
9307
|
}
|
|
9308
|
+
function looksBinary(buf) {
|
|
9309
|
+
const end = Math.min(buf.length, 8192);
|
|
9310
|
+
for (let i = 0; i < end; i++) {
|
|
9311
|
+
if (buf[i] === 0) return true;
|
|
9312
|
+
}
|
|
9313
|
+
return false;
|
|
9314
|
+
}
|
|
9315
|
+
function formatBytes(n) {
|
|
9316
|
+
if (n < 1024) return `${n} B`;
|
|
9317
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
|
9318
|
+
if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MiB`;
|
|
9319
|
+
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
9320
|
+
}
|
|
8642
9321
|
function registerFilesystemTools(registry, opts) {
|
|
8643
9322
|
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
8644
9323
|
const allowWriting = opts.allowWriting !== false;
|
|
8645
|
-
const
|
|
9324
|
+
const outlineThresholdBytes = opts.outlineThresholdBytes ?? DEFAULT_OUTLINE_THRESHOLD_BYTES;
|
|
8646
9325
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
8647
9326
|
const normRoot = pathMod5.resolve(rootDir);
|
|
8648
9327
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
8649
|
-
const
|
|
8650
|
-
function
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
9328
|
+
const shownSubdirMemory = /* @__PURE__ */ new Set();
|
|
9329
|
+
function withSubdirMemory(absPath, body) {
|
|
9330
|
+
if (!memoryEnabled()) return body;
|
|
9331
|
+
const ancestors = findSubdirMemoryAncestors(absPath, rootDir);
|
|
9332
|
+
if (ancestors.length === 0) return body;
|
|
9333
|
+
const sections = [];
|
|
9334
|
+
for (const memPath of [...ancestors].reverse()) {
|
|
9335
|
+
if (shownSubdirMemory.has(memPath)) continue;
|
|
9336
|
+
const content = readSubdirMemoryContent(memPath);
|
|
9337
|
+
if (!content) continue;
|
|
9338
|
+
shownSubdirMemory.add(memPath);
|
|
9339
|
+
sections.push(formatSubdirMemorySection(displayRel4(rootDir, memPath), content));
|
|
9340
|
+
}
|
|
9341
|
+
if (sections.length === 0) return body;
|
|
9342
|
+
return `${sections.join("\n\n")}
|
|
9343
|
+
|
|
9344
|
+
${body}`;
|
|
8659
9345
|
}
|
|
9346
|
+
const inflightGate = /* @__PURE__ */ new Map();
|
|
8660
9347
|
async function ensureOutsideSandboxAllowed(abs, intent, toolName, ctx) {
|
|
8661
9348
|
for (const dir of loadProjectPathAllowed(rootDir)) {
|
|
8662
9349
|
if (pathIsUnder(abs, dir)) return;
|
|
@@ -8721,11 +9408,11 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8721
9408
|
registry.register({
|
|
8722
9409
|
name: "read_file",
|
|
8723
9410
|
parallelSafe: true,
|
|
8724
|
-
description: `Read a file under the sandbox root.
|
|
8725
|
-
- head: N \u2192 first N lines (imports
|
|
8726
|
-
- tail: N \u2192 last N lines (
|
|
8727
|
-
- range: "A-B" \u2192 inclusive
|
|
8728
|
-
|
|
9411
|
+
description: `Read a file under the sandbox root. Default behaviour returns FULL CONTENT for files at or under ${Math.round(DEFAULT_OUTLINE_THRESHOLD_BYTES / 1024)} KiB \u2014 trust the prompt cache, don't pre-truncate. Optional scoping:
|
|
9412
|
+
- head: N \u2192 first N lines (cheap probe of imports / config head)
|
|
9413
|
+
- tail: N \u2192 last N lines (recent-tail of a log)
|
|
9414
|
+
- range: "A-B" \u2192 inclusive 1-indexed range (e.g. "120-180" around an edit site)
|
|
9415
|
+
Files OVER the threshold auto-switch to outline mode: file metadata + first ${OUTLINE_HEAD_LINES} lines + a top-level symbol outline (TS/JS exports, Python def/class, Go func/type, Rust fn/struct/impl/trait, Markdown headings, Protobuf message/service/rpc, plain-text chapter markers) + concrete next-step commands. No middle bytes \u2014 drill in with range / search_content. Files over ${Math.round(HARD_MAX_FILE_BYTES / (1024 * 1024))} MiB are refused entirely (use grep / range). Binary files are refused \u2014 use get_file_info if you only need stat.`,
|
|
8729
9416
|
readOnly: true,
|
|
8730
9417
|
stormExempt: true,
|
|
8731
9418
|
parameters: {
|
|
@@ -8743,22 +9430,31 @@ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_L
|
|
|
8743
9430
|
},
|
|
8744
9431
|
fn: async (args, ctx) => {
|
|
8745
9432
|
const abs = await safePath(args.path, "read_file", ctx);
|
|
9433
|
+
const rel = displayRel4(rootDir, abs);
|
|
8746
9434
|
const fh = await fs4.open(abs, "r");
|
|
8747
9435
|
let raw;
|
|
9436
|
+
let sizeBytes;
|
|
8748
9437
|
try {
|
|
8749
9438
|
const stat2 = await fh.stat();
|
|
8750
9439
|
if (stat2.isDirectory()) {
|
|
8751
9440
|
throw new Error(`not a file: ${args.path} (it's a directory)`);
|
|
8752
9441
|
}
|
|
9442
|
+
sizeBytes = stat2.size;
|
|
9443
|
+
if (sizeBytes > HARD_MAX_FILE_BYTES) {
|
|
9444
|
+
return [
|
|
9445
|
+
`[refused: ${rel} is ${formatBytes(sizeBytes)} (> ${formatBytes(HARD_MAX_FILE_BYTES)} hard ceiling) \u2014 too large to load]`,
|
|
9446
|
+
"Use one of:",
|
|
9447
|
+
` - search_content path:"${rel}" pattern:"<your regex>" \u2014 grep within the file`,
|
|
9448
|
+
` - read_file path:"${rel}" range:"A-B" \u2014 read a specific 1-indexed line range`,
|
|
9449
|
+
` - read_file path:"${rel}" head:N / tail:N \u2014 read N lines at the start or end`
|
|
9450
|
+
].join("\n");
|
|
9451
|
+
}
|
|
8753
9452
|
raw = await fh.readFile();
|
|
8754
9453
|
} finally {
|
|
8755
9454
|
await fh.close();
|
|
8756
9455
|
}
|
|
8757
|
-
if (raw
|
|
8758
|
-
|
|
8759
|
-
return `${headBytes}
|
|
8760
|
-
|
|
8761
|
-
[\u2026truncated ${raw.length - maxReadBytes} bytes \u2014 file is ${raw.length} B, cap ${maxReadBytes} B. Retry with head/tail/range for targeted view.]`;
|
|
9456
|
+
if (looksBinary(raw)) {
|
|
9457
|
+
return `[refused: ${rel} appears to be binary (${formatBytes(sizeBytes)}) \u2014 read_file returns text only. Use get_file_info for stat.]`;
|
|
8762
9458
|
}
|
|
8763
9459
|
const text = raw.toString("utf8");
|
|
8764
9460
|
let lines = text.split(/\r?\n/);
|
|
@@ -8770,8 +9466,8 @@ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_L
|
|
|
8770
9466
|
const end = Math.min(totalLines, Math.max(start, rawEnd ?? totalLines));
|
|
8771
9467
|
const slice = lines.slice(start - 1, end);
|
|
8772
9468
|
const label = `[range ${start}-${end} of ${totalLines} lines]`;
|
|
8773
|
-
return `${label}
|
|
8774
|
-
${slice.join("\n")}
|
|
9469
|
+
return withSubdirMemory(abs, `${label}
|
|
9470
|
+
${slice.join("\n")}`);
|
|
8775
9471
|
}
|
|
8776
9472
|
if (typeof args.head === "number" && args.head > 0) {
|
|
8777
9473
|
const count = Math.min(args.head, totalLines);
|
|
@@ -8779,7 +9475,7 @@ ${slice.join("\n")}`;
|
|
|
8779
9475
|
const marker = count < totalLines ? `
|
|
8780
9476
|
|
|
8781
9477
|
[\u2026head ${count} of ${totalLines} lines \u2014 call again with range / tail for more]` : "";
|
|
8782
|
-
return slice.join("\n") + marker;
|
|
9478
|
+
return withSubdirMemory(abs, slice.join("\n") + marker);
|
|
8783
9479
|
}
|
|
8784
9480
|
if (typeof args.tail === "number" && args.tail > 0) {
|
|
8785
9481
|
const count = Math.min(args.tail, totalLines);
|
|
@@ -8787,25 +9483,26 @@ ${slice.join("\n")}`;
|
|
|
8787
9483
|
const marker = count < totalLines ? `[\u2026tail ${count} of ${totalLines} lines \u2014 call again with range / head for more]
|
|
8788
9484
|
|
|
8789
9485
|
` : "";
|
|
8790
|
-
return marker + slice.join("\n");
|
|
9486
|
+
return withSubdirMemory(abs, marker + slice.join("\n"));
|
|
8791
9487
|
}
|
|
8792
|
-
if (
|
|
8793
|
-
const head = lines.slice(0,
|
|
8794
|
-
const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
|
|
8795
|
-
const omitted = totalLines - AUTO_PREVIEW_HEAD_LINES - AUTO_PREVIEW_TAIL_LINES;
|
|
9488
|
+
if (sizeBytes <= outlineThresholdBytes) return withSubdirMemory(abs, lines.join("\n"));
|
|
9489
|
+
const head = lines.slice(0, Math.min(OUTLINE_HEAD_LINES, totalLines)).join("\n");
|
|
8796
9490
|
const outline = formatOutline(extractOutline(abs, lines));
|
|
8797
9491
|
const parts = [
|
|
8798
|
-
`[
|
|
9492
|
+
`[large file: ${formatBytes(sizeBytes)}, ${totalLines} lines \u2014 outline mode (threshold ${formatBytes(outlineThresholdBytes)})]`,
|
|
9493
|
+
"",
|
|
9494
|
+
`[head ${Math.min(OUTLINE_HEAD_LINES, totalLines)} lines for orientation]`,
|
|
8799
9495
|
head
|
|
8800
9496
|
];
|
|
8801
9497
|
if (outline) parts.push("", outline);
|
|
8802
9498
|
parts.push(
|
|
8803
|
-
|
|
8804
|
-
[
|
|
8805
|
-
`,
|
|
8806
|
-
tail
|
|
9499
|
+
"",
|
|
9500
|
+
"[to read more, call one of:",
|
|
9501
|
+
` - read_file path:"${rel}" range:"A-B" \u2014 1-indexed line range`,
|
|
9502
|
+
` - read_file path:"${rel}" head:N / tail:N \u2014 first/last N lines`,
|
|
9503
|
+
` - search_content path:"${rel}" pattern:"..." \u2014 grep within this file]`
|
|
8807
9504
|
);
|
|
8808
|
-
return parts.join("\n");
|
|
9505
|
+
return withSubdirMemory(abs, parts.join("\n"));
|
|
8809
9506
|
}
|
|
8810
9507
|
});
|
|
8811
9508
|
registry.register({
|
|
@@ -9846,7 +10543,7 @@ var VERIFY_SYSTEM = `You are a verify subagent. Narrow check \u2014 return YES /
|
|
|
9846
10543
|
How to operate:
|
|
9847
10544
|
- Read only what's needed to verify the specific claim. No exploration past the claim.
|
|
9848
10545
|
- Use search_content / read_file to confirm the exact behavior, type, or call site in question.
|
|
9849
|
-
-
|
|
10546
|
+
- If a focused round of reads can't verify it, return INCONCLUSIVE plus what's missing \u2014 don't keep digging.
|
|
9850
10547
|
|
|
9851
10548
|
Final answer:
|
|
9852
10549
|
- Lead with VERIFIED / NOT VERIFIED / INCONCLUSIVE.
|
|
@@ -9857,8 +10554,8 @@ ${NEGATIVE_CLAIM_RULE}
|
|
|
9857
10554
|
|
|
9858
10555
|
${TUI_FORMATTING_RULES}`;
|
|
9859
10556
|
var TYPES = {
|
|
9860
|
-
explore: { system: EXPLORE_SYSTEM
|
|
9861
|
-
verify: { system: VERIFY_SYSTEM
|
|
10557
|
+
explore: { system: EXPLORE_SYSTEM },
|
|
10558
|
+
verify: { system: VERIFY_SYSTEM }
|
|
9862
10559
|
};
|
|
9863
10560
|
var SUBAGENT_TYPE_NAMES = Object.freeze(
|
|
9864
10561
|
Object.keys(TYPES)
|
|
@@ -9886,13 +10583,6 @@ ${NEGATIVE_CLAIM_RULE}
|
|
|
9886
10583
|
|
|
9887
10584
|
${TUI_FORMATTING_RULES}`;
|
|
9888
10585
|
var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
|
|
9889
|
-
var DEFAULT_MAX_ITERS = 16;
|
|
9890
|
-
var MIN_MAX_ITERS = 1;
|
|
9891
|
-
var MAX_MAX_ITERS = 32;
|
|
9892
|
-
var BUDGET_WARN_THRESHOLD = 3;
|
|
9893
|
-
function budgetParagraph(maxToolIters) {
|
|
9894
|
-
return `Tool budget: you have ${maxToolIters} tool call${maxToolIters === 1 ? "" : "s"} for this task. The cap is enforced from outside \u2014 the call after #${maxToolIters} is refused. Pace yourself: if you can't fully resolve the task within the budget, stop early and return what you have plus what's missing, rather than burning the budget on one branch.`;
|
|
9895
|
-
}
|
|
9896
10586
|
var DEFAULT_SUBAGENT_MODEL = "deepseek-v4-flash";
|
|
9897
10587
|
var DEFAULT_SUBAGENT_EFFORT = "high";
|
|
9898
10588
|
var SUBAGENT_TOOL_NAME = "spawn_subagent";
|
|
@@ -9911,12 +10601,12 @@ function subagentBudgetHint(spawnCount, totalTokens) {
|
|
|
9911
10601
|
}
|
|
9912
10602
|
async function spawnSubagent(opts) {
|
|
9913
10603
|
const model = opts.model ?? DEFAULT_SUBAGENT_MODEL;
|
|
9914
|
-
const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
|
|
9915
10604
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
9916
10605
|
const sink = opts.sink;
|
|
9917
10606
|
const skillName = opts.skillName;
|
|
9918
|
-
const startedAt = Date.now();
|
|
9919
10607
|
const runId = nextRunId();
|
|
10608
|
+
const sessionName = opts.resumeSession ?? `subagent-${runId}-${timestampSuffix()}`;
|
|
10609
|
+
const startedAt = Date.now();
|
|
9920
10610
|
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
9921
10611
|
sink?.current?.({
|
|
9922
10612
|
kind: "start",
|
|
@@ -9963,26 +10653,8 @@ async function spawnSubagent(opts) {
|
|
|
9963
10653
|
new Set(opts.allowedTools),
|
|
9964
10654
|
NEVER_INHERITED_TOOLS
|
|
9965
10655
|
) : forkRegistryExcluding(opts.parentRegistry, NEVER_INHERITED_TOOLS);
|
|
9966
|
-
let dispatchCount = 0;
|
|
9967
|
-
childTools.setResultAugmenter((_name, _args, result) => {
|
|
9968
|
-
dispatchCount++;
|
|
9969
|
-
const remaining = maxToolIters - dispatchCount;
|
|
9970
|
-
if (remaining <= 0) {
|
|
9971
|
-
return `${result}
|
|
9972
|
-
|
|
9973
|
-
[budget: 0 of ${maxToolIters} tool calls left \u2014 finalize NOW; the next tool call will be refused]`;
|
|
9974
|
-
}
|
|
9975
|
-
if (remaining <= BUDGET_WARN_THRESHOLD) {
|
|
9976
|
-
return `${result}
|
|
9977
|
-
|
|
9978
|
-
[budget: ${remaining} of ${maxToolIters} tool call${remaining === 1 ? "" : "s"} left \u2014 wrap up soon]`;
|
|
9979
|
-
}
|
|
9980
|
-
return result;
|
|
9981
|
-
});
|
|
9982
10656
|
const childPrefix = new ImmutablePrefix({
|
|
9983
|
-
system:
|
|
9984
|
-
|
|
9985
|
-
${budgetParagraph(maxToolIters)}`,
|
|
10657
|
+
system: opts.system,
|
|
9986
10658
|
toolSpecs: childTools.specs()
|
|
9987
10659
|
});
|
|
9988
10660
|
const childLoop = new CacheFirstLoop({
|
|
@@ -9994,12 +10666,9 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
9994
10666
|
// task is already narrow by construction, and `high` cuts output
|
|
9995
10667
|
// tokens substantially vs `max`.
|
|
9996
10668
|
reasoningEffort: DEFAULT_SUBAGENT_EFFORT,
|
|
9997
|
-
maxToolIters,
|
|
9998
10669
|
hooks: [],
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
// after the last tool result, before assistant_final lands).
|
|
10002
|
-
stream: true
|
|
10670
|
+
stream: true,
|
|
10671
|
+
session: sessionName
|
|
10003
10672
|
});
|
|
10004
10673
|
const onParentAbort = () => childLoop.abort();
|
|
10005
10674
|
if (opts.parentSignal?.aborted) {
|
|
@@ -10011,6 +10680,7 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
10011
10680
|
let errorMessage;
|
|
10012
10681
|
let toolIter = 0;
|
|
10013
10682
|
let summarisingEmitted = false;
|
|
10683
|
+
let forcedSummaryFired = false;
|
|
10014
10684
|
try {
|
|
10015
10685
|
for await (const ev of childLoop.step(opts.task)) {
|
|
10016
10686
|
sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
|
|
@@ -10042,7 +10712,12 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
10042
10712
|
}
|
|
10043
10713
|
if (ev.role === "assistant_final") {
|
|
10044
10714
|
if (ev.forcedSummary) {
|
|
10045
|
-
|
|
10715
|
+
if (opts.parentSignal?.aborted) {
|
|
10716
|
+
errorMessage = ev.content?.trim() || "subagent aborted before producing an answer";
|
|
10717
|
+
} else {
|
|
10718
|
+
final = ev.content ?? "";
|
|
10719
|
+
forcedSummaryFired = true;
|
|
10720
|
+
}
|
|
10046
10721
|
} else {
|
|
10047
10722
|
final = ev.content ?? "";
|
|
10048
10723
|
}
|
|
@@ -10081,7 +10756,7 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
10081
10756
|
usage
|
|
10082
10757
|
});
|
|
10083
10758
|
return {
|
|
10084
|
-
success: !errorMessage,
|
|
10759
|
+
success: !errorMessage && !forcedSummaryFired,
|
|
10085
10760
|
output: errorMessage ? "" : truncated,
|
|
10086
10761
|
error: errorMessage,
|
|
10087
10762
|
turns,
|
|
@@ -10090,7 +10765,8 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
10090
10765
|
costUsd: costUsd2,
|
|
10091
10766
|
model,
|
|
10092
10767
|
skillName,
|
|
10093
|
-
usage
|
|
10768
|
+
usage,
|
|
10769
|
+
forcedSummary: forcedSummaryFired || void 0
|
|
10094
10770
|
};
|
|
10095
10771
|
}
|
|
10096
10772
|
function aggregateChildUsage(loop) {
|
|
@@ -10105,6 +10781,18 @@ function aggregateChildUsage(loop) {
|
|
|
10105
10781
|
return agg;
|
|
10106
10782
|
}
|
|
10107
10783
|
function formatSubagentResult(r) {
|
|
10784
|
+
if (r.forcedSummary) {
|
|
10785
|
+
return JSON.stringify({
|
|
10786
|
+
success: false,
|
|
10787
|
+
partial: true,
|
|
10788
|
+
output: r.output,
|
|
10789
|
+
turns: r.turns,
|
|
10790
|
+
tool_iters: r.toolIters,
|
|
10791
|
+
elapsed_ms: r.elapsedMs,
|
|
10792
|
+
cost_usd: r.costUsd,
|
|
10793
|
+
note: "Subagent was force-summarized (storm-breaker or context-guard fired). `output` carries the partial synthesis the model produced before being stopped \u2014 useful but not a complete answer. Decide whether to accept the partial, narrow the task and re-spawn, or fall back to direct tools."
|
|
10794
|
+
});
|
|
10795
|
+
}
|
|
10108
10796
|
if (!r.success) {
|
|
10109
10797
|
return JSON.stringify({
|
|
10110
10798
|
success: false,
|
|
@@ -10127,7 +10815,6 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10127
10815
|
const baseSystem = opts.defaultSystem ?? SUBAGENT_BASE_SYSTEM;
|
|
10128
10816
|
const defaultSystemBase = opts.projectRoot ? applyProjectMemory(baseSystem, opts.projectRoot) : baseSystem;
|
|
10129
10817
|
const defaultModel = opts.defaultModel ?? DEFAULT_SUBAGENT_MODEL;
|
|
10130
|
-
const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
|
|
10131
10818
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
10132
10819
|
const sink = opts.sink;
|
|
10133
10820
|
let sessionSpawnCount = 0;
|
|
@@ -10135,33 +10822,31 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10135
10822
|
parentRegistry.register({
|
|
10136
10823
|
name: SUBAGENT_TOOL_NAME,
|
|
10137
10824
|
parallelSafe: true,
|
|
10138
|
-
description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. **Prefer direct tools.** Spawn primarily for parallel fan-out (2+ independent investigations issued in one tool batch) or when the work would otherwise need >10 file reads/searches whose trail you don't need to keep. Single greps, 1-3 file cross-references, and 'keep my context clean for one question' are NOT good reasons to spawn \u2014 direct tools are cheaper and let you reference the evidence later. Each spawn pays a
|
|
10825
|
+
description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. **Prefer direct tools.** Spawn primarily for parallel fan-out (2+ independent investigations issued in one tool batch) or when the work would otherwise need >10 file reads/searches whose trail you don't need to keep. Single greps, 1-3 file cross-references, and 'keep my context clean for one question' are NOT good reasons to spawn \u2014 direct tools are cheaper and let you reference the evidence later. Each fresh spawn pays a prefix-cache miss plus a full child loop. The subagent inherits your tools but runs in its own isolated message log; only the final assistant message comes back. The subagent runs to completion \u2014 same stops as top-level chat (token-context guard, storm breaker, parent Esc cascade).",
|
|
10139
10826
|
parameters: {
|
|
10140
10827
|
type: "object",
|
|
10141
10828
|
properties: {
|
|
10142
10829
|
task: {
|
|
10143
10830
|
type: "string",
|
|
10144
|
-
description:
|
|
10831
|
+
description: 'The subtask the subagent should perform. Be specific and self-contained \u2014 the subagent has none of your conversation context, only what you write here. When resuming via `resume_session`, this becomes a continuation nudge (e.g. "finish what you started" or a delta instruction).'
|
|
10145
10832
|
},
|
|
10146
10833
|
system: {
|
|
10147
10834
|
type: "string",
|
|
10148
|
-
description: "Optional override for the subagent's system prompt. The default tells it to stay focused and return a concise answer; override only when the subtask needs a specialized persona."
|
|
10835
|
+
description: "Optional override for the subagent's system prompt. The default tells it to stay focused and return a concise answer; override only when the subtask needs a specialized persona. Ignored on resume \u2014 the prior session keeps its original system prompt for cache stability."
|
|
10149
10836
|
},
|
|
10150
10837
|
model: {
|
|
10151
10838
|
type: "string",
|
|
10152
10839
|
enum: ["deepseek-v4-flash", "deepseek-v4-pro"],
|
|
10153
10840
|
description: "Which DeepSeek model the subagent runs on. Default is 'deepseek-v4-flash' \u2014 cheap and fast, fine for explore/research-style subtasks. Override to 'deepseek-v4-pro' (~12\xD7 more expensive) when the subtask genuinely needs the stronger model: cross-file architecture, subtle bug hunts, anything where flash has empirically underperformed."
|
|
10154
10841
|
},
|
|
10155
|
-
|
|
10156
|
-
type: "
|
|
10157
|
-
|
|
10158
|
-
maximum: MAX_MAX_ITERS,
|
|
10159
|
-
description: `Cap on the subagent's tool-call iterations. Default 16 (or the type's default when 'type' is set). Hard range: ${MIN_MAX_ITERS}-${MAX_MAX_ITERS}; out-of-range values are clamped to the nearest end.`
|
|
10842
|
+
resume_session: {
|
|
10843
|
+
type: "string",
|
|
10844
|
+
description: "Provide a previous subagent's session name to continue it. When set, prior messages are loaded from disk and the original system prompt is reused (cache-friendly). `task` becomes a continuation nudge."
|
|
10160
10845
|
},
|
|
10161
10846
|
type: {
|
|
10162
10847
|
type: "string",
|
|
10163
10848
|
enum: [...SUBAGENT_TYPE_NAMES],
|
|
10164
|
-
description: "Optional persona shaping the system prompt
|
|
10849
|
+
description: "Optional persona shaping the system prompt. 'explore' = wide-net read-only investigation, returns a distilled answer. 'verify' = narrow yes/no check with evidence. Omit when supplying your own 'system' or when the default generic persona fits."
|
|
10165
10850
|
}
|
|
10166
10851
|
},
|
|
10167
10852
|
required: ["task"]
|
|
@@ -10178,20 +10863,26 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10178
10863
|
const system = typeof args.system === "string" && args.system.trim().length > 0 ? args.system.trim() : typeSpec?.system ?? `${defaultSystemBase}
|
|
10179
10864
|
|
|
10180
10865
|
${escalationContract(model)}`;
|
|
10181
|
-
const
|
|
10866
|
+
const resumeSession = typeof args.resume_session === "string" && args.resume_session.trim().length > 0 ? args.resume_session.trim() : void 0;
|
|
10182
10867
|
const result = await spawnSubagent({
|
|
10183
10868
|
client: opts.client,
|
|
10184
10869
|
parentRegistry,
|
|
10185
10870
|
system,
|
|
10186
10871
|
task,
|
|
10187
10872
|
model,
|
|
10188
|
-
maxToolIters: callerIters ?? typeSpec?.maxToolIters ?? maxToolIters,
|
|
10189
10873
|
maxResultChars,
|
|
10190
10874
|
sink,
|
|
10191
|
-
parentSignal: ctx?.signal
|
|
10875
|
+
parentSignal: ctx?.signal,
|
|
10876
|
+
resumeSession
|
|
10192
10877
|
});
|
|
10193
10878
|
sessionSpawnCount++;
|
|
10194
10879
|
sessionSpawnTokens += result.usage.totalTokens;
|
|
10880
|
+
if (opts.onSpawnComplete) {
|
|
10881
|
+
try {
|
|
10882
|
+
opts.onSpawnComplete(result);
|
|
10883
|
+
} catch {
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10195
10886
|
const formatted = formatSubagentResult(result);
|
|
10196
10887
|
const hint = subagentBudgetHint(sessionSpawnCount, sessionSpawnTokens);
|
|
10197
10888
|
return hint ? `${formatted}
|
|
@@ -10200,13 +10891,6 @@ ${hint}` : formatted;
|
|
|
10200
10891
|
});
|
|
10201
10892
|
return parentRegistry;
|
|
10202
10893
|
}
|
|
10203
|
-
function clampMaxIters(raw) {
|
|
10204
|
-
if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
|
|
10205
|
-
const n = Math.floor(raw);
|
|
10206
|
-
if (n < MIN_MAX_ITERS) return MIN_MAX_ITERS;
|
|
10207
|
-
if (n > MAX_MAX_ITERS) return MAX_MAX_ITERS;
|
|
10208
|
-
return n;
|
|
10209
|
-
}
|
|
10210
10894
|
function forkRegistryExcluding(parent, exclude) {
|
|
10211
10895
|
const child = new ToolRegistry();
|
|
10212
10896
|
for (const spec of parent.specs()) {
|
|
@@ -10233,8 +10917,100 @@ function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
|
10233
10917
|
return child;
|
|
10234
10918
|
}
|
|
10235
10919
|
|
|
10920
|
+
// src/telemetry/subagent-distillation.ts
|
|
10921
|
+
function computeSpawnDistillation(result) {
|
|
10922
|
+
const outputTokens = countTokensBounded(result.output);
|
|
10923
|
+
const completionTokens = result.usage.completionTokens;
|
|
10924
|
+
const savingsTokens = Math.max(0, completionTokens - outputTokens);
|
|
10925
|
+
const compressionRatio = completionTokens > 0 ? outputTokens / completionTokens : 1;
|
|
10926
|
+
return {
|
|
10927
|
+
completionTokens,
|
|
10928
|
+
outputTokens,
|
|
10929
|
+
savingsTokens,
|
|
10930
|
+
compressionRatio,
|
|
10931
|
+
hasOutput: result.output.trim().length > 0,
|
|
10932
|
+
costUsd: result.costUsd
|
|
10933
|
+
};
|
|
10934
|
+
}
|
|
10935
|
+
function summarizeSubagentSession(spawns) {
|
|
10936
|
+
const spawnCount = spawns.length;
|
|
10937
|
+
if (spawnCount === 0) {
|
|
10938
|
+
return {
|
|
10939
|
+
spawnCount: 0,
|
|
10940
|
+
usefulSpawnCount: 0,
|
|
10941
|
+
successRate: 0,
|
|
10942
|
+
totalCompletionTokens: 0,
|
|
10943
|
+
totalOutputTokens: 0,
|
|
10944
|
+
totalSavingsTokens: 0,
|
|
10945
|
+
aggregateCompressionRatio: 1,
|
|
10946
|
+
totalCostUsd: 0
|
|
10947
|
+
};
|
|
10948
|
+
}
|
|
10949
|
+
let usefulSpawnCount = 0;
|
|
10950
|
+
let totalCompletionTokens = 0;
|
|
10951
|
+
let totalOutputTokens = 0;
|
|
10952
|
+
let totalSavingsTokens = 0;
|
|
10953
|
+
let totalCostUsd = 0;
|
|
10954
|
+
for (const s of spawns) {
|
|
10955
|
+
if (s.hasOutput) usefulSpawnCount++;
|
|
10956
|
+
totalCompletionTokens += s.completionTokens;
|
|
10957
|
+
totalOutputTokens += s.outputTokens;
|
|
10958
|
+
totalSavingsTokens += s.savingsTokens;
|
|
10959
|
+
totalCostUsd += s.costUsd;
|
|
10960
|
+
}
|
|
10961
|
+
const aggregateCompressionRatio = totalCompletionTokens > 0 ? totalOutputTokens / totalCompletionTokens : 1;
|
|
10962
|
+
return {
|
|
10963
|
+
spawnCount,
|
|
10964
|
+
usefulSpawnCount,
|
|
10965
|
+
successRate: usefulSpawnCount / spawnCount,
|
|
10966
|
+
totalCompletionTokens,
|
|
10967
|
+
totalOutputTokens,
|
|
10968
|
+
totalSavingsTokens,
|
|
10969
|
+
aggregateCompressionRatio,
|
|
10970
|
+
totalCostUsd
|
|
10971
|
+
};
|
|
10972
|
+
}
|
|
10973
|
+
var DEFAULT_SPAWN_STORM_THRESHOLD = 3;
|
|
10974
|
+
function countSpawnStorms(spawnsByTurn, threshold = DEFAULT_SPAWN_STORM_THRESHOLD) {
|
|
10975
|
+
let storms = 0;
|
|
10976
|
+
for (const turn of spawnsByTurn) {
|
|
10977
|
+
if (turn.length >= threshold) storms++;
|
|
10978
|
+
}
|
|
10979
|
+
return storms;
|
|
10980
|
+
}
|
|
10981
|
+
var SubagentTelemetry = class {
|
|
10982
|
+
_spawns = [];
|
|
10983
|
+
_byTurn = [];
|
|
10984
|
+
_currentTurn = 0;
|
|
10985
|
+
/** Bound for ergonomic use as a callback. */
|
|
10986
|
+
record = (result) => {
|
|
10987
|
+
const d = computeSpawnDistillation(result);
|
|
10988
|
+
this._spawns.push(d);
|
|
10989
|
+
while (this._byTurn.length <= this._currentTurn) this._byTurn.push([]);
|
|
10990
|
+
this._byTurn[this._currentTurn].push(d);
|
|
10991
|
+
return d;
|
|
10992
|
+
};
|
|
10993
|
+
/** Mark the start of a new parent turn so subsequent records group into a new bucket — call from the parent loop when its turn counter advances. */
|
|
10994
|
+
startTurn(turn) {
|
|
10995
|
+
if (turn < 0) return;
|
|
10996
|
+
this._currentTurn = turn;
|
|
10997
|
+
}
|
|
10998
|
+
get spawns() {
|
|
10999
|
+
return this._spawns;
|
|
11000
|
+
}
|
|
11001
|
+
get spawnsByTurn() {
|
|
11002
|
+
return this._byTurn;
|
|
11003
|
+
}
|
|
11004
|
+
get summary() {
|
|
11005
|
+
return summarizeSubagentSession(this._spawns);
|
|
11006
|
+
}
|
|
11007
|
+
stormCount(threshold = DEFAULT_SPAWN_STORM_THRESHOLD) {
|
|
11008
|
+
return countSpawnStorms(this._byTurn, threshold);
|
|
11009
|
+
}
|
|
11010
|
+
};
|
|
11011
|
+
|
|
10236
11012
|
// src/tools/shell.ts
|
|
10237
|
-
import * as
|
|
11013
|
+
import * as pathMod10 from "path";
|
|
10238
11014
|
|
|
10239
11015
|
// src/tools/jobs.ts
|
|
10240
11016
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -10483,16 +11259,16 @@ ${job.output.slice(start)}`;
|
|
|
10483
11259
|
let wakeOutput = null;
|
|
10484
11260
|
if (waitFor === "output-or-exit") {
|
|
10485
11261
|
racers.push(
|
|
10486
|
-
new Promise((
|
|
10487
|
-
wakeOutput =
|
|
10488
|
-
job.outputWaiters.add(
|
|
11262
|
+
new Promise((resolve13) => {
|
|
11263
|
+
wakeOutput = resolve13;
|
|
11264
|
+
job.outputWaiters.add(resolve13);
|
|
10489
11265
|
})
|
|
10490
11266
|
);
|
|
10491
11267
|
}
|
|
10492
11268
|
let timer = null;
|
|
10493
11269
|
racers.push(
|
|
10494
|
-
new Promise((
|
|
10495
|
-
timer = setTimeout(
|
|
11270
|
+
new Promise((resolve13) => {
|
|
11271
|
+
timer = setTimeout(resolve13, timeoutMs);
|
|
10496
11272
|
})
|
|
10497
11273
|
);
|
|
10498
11274
|
await Promise.race(racers);
|
|
@@ -10601,12 +11377,13 @@ function latestOutputSince(before, after) {
|
|
|
10601
11377
|
|
|
10602
11378
|
// src/tools/shell/exec.ts
|
|
10603
11379
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
10604
|
-
import { existsSync as
|
|
10605
|
-
import * as
|
|
11380
|
+
import { existsSync as existsSync9, statSync as statSync5 } from "fs";
|
|
11381
|
+
import * as pathMod9 from "path";
|
|
10606
11382
|
|
|
10607
11383
|
// src/tools/shell-chain.ts
|
|
10608
11384
|
import { spawn as spawn3 } from "child_process";
|
|
10609
11385
|
import { closeSync, openSync } from "fs";
|
|
11386
|
+
import { devNull } from "os";
|
|
10610
11387
|
import * as pathMod7 from "path";
|
|
10611
11388
|
var UnsupportedSyntaxError = class extends Error {
|
|
10612
11389
|
constructor(detail) {
|
|
@@ -10866,6 +11643,12 @@ async function runChain(chain, opts) {
|
|
|
10866
11643
|
[\u2026 truncated ${output.length - opts.maxOutputChars} chars \u2026]` : output;
|
|
10867
11644
|
return { exitCode: lastExit, output: truncated, timedOut };
|
|
10868
11645
|
}
|
|
11646
|
+
function isNullDeviceAlias(target) {
|
|
11647
|
+
const lower = target.toLowerCase();
|
|
11648
|
+
if (lower === "/dev/null") return true;
|
|
11649
|
+
if (process.platform === "win32" && lower === "nul") return true;
|
|
11650
|
+
return false;
|
|
11651
|
+
}
|
|
10869
11652
|
function openRedirects(redirects, cwd) {
|
|
10870
11653
|
let stdinFd = null;
|
|
10871
11654
|
let stdoutFd = null;
|
|
@@ -10874,7 +11657,7 @@ function openRedirects(redirects, cwd) {
|
|
|
10874
11657
|
let bothFd = null;
|
|
10875
11658
|
const toClose = [];
|
|
10876
11659
|
const open = (target, flags) => {
|
|
10877
|
-
const resolved = pathMod7.resolve(cwd, target);
|
|
11660
|
+
const resolved = isNullDeviceAlias(target) ? devNull : pathMod7.resolve(cwd, target);
|
|
10878
11661
|
const fd = openSync(resolved, flags);
|
|
10879
11662
|
toClose.push(fd);
|
|
10880
11663
|
return fd;
|
|
@@ -10928,6 +11711,10 @@ async function runPipeGroup(segments, opts) {
|
|
|
10928
11711
|
cwd: opts.cwd,
|
|
10929
11712
|
shell: false,
|
|
10930
11713
|
windowsHide: true,
|
|
11714
|
+
// POSIX: detach so the child becomes its own process-group leader,
|
|
11715
|
+
// allowing killProcessTree's neg-pid kill to terminate the whole
|
|
11716
|
+
// pipe chain subtree instead of just the direct child.
|
|
11717
|
+
detached: process.platform !== "win32",
|
|
10931
11718
|
env,
|
|
10932
11719
|
stdio: [stdinSpec, stdoutSpec, stderrSpec],
|
|
10933
11720
|
...spawnOverrides
|
|
@@ -10978,9 +11765,9 @@ async function runPipeGroup(segments, opts) {
|
|
|
10978
11765
|
}
|
|
10979
11766
|
const exits = await Promise.all(
|
|
10980
11767
|
children.map(
|
|
10981
|
-
(c) => new Promise((
|
|
10982
|
-
c.once("error", () =>
|
|
10983
|
-
c.once("close", (code) =>
|
|
11768
|
+
(c) => new Promise((resolve13) => {
|
|
11769
|
+
c.once("error", () => resolve13(null));
|
|
11770
|
+
c.once("close", (code) => resolve13(code));
|
|
10984
11771
|
})
|
|
10985
11772
|
)
|
|
10986
11773
|
);
|
|
@@ -11024,6 +11811,8 @@ var OutputBuffer = class {
|
|
|
11024
11811
|
};
|
|
11025
11812
|
|
|
11026
11813
|
// src/tools/shell/parse.ts
|
|
11814
|
+
import { homedir as homedir6 } from "os";
|
|
11815
|
+
import * as pathMod8 from "path";
|
|
11027
11816
|
var BUILTIN_ALLOWLIST = [
|
|
11028
11817
|
// Repo inspection
|
|
11029
11818
|
"git status",
|
|
@@ -11200,7 +11989,65 @@ function tailHasRisky(tail, risky) {
|
|
|
11200
11989
|
}
|
|
11201
11990
|
return false;
|
|
11202
11991
|
}
|
|
11203
|
-
|
|
11992
|
+
var DEFAULT_SENSITIVE_PREFIXES = [
|
|
11993
|
+
"~/.ssh",
|
|
11994
|
+
"~/.aws",
|
|
11995
|
+
"~/.gnupg",
|
|
11996
|
+
"~/.kube",
|
|
11997
|
+
"/etc/shadow",
|
|
11998
|
+
"/etc/sudoers"
|
|
11999
|
+
];
|
|
12000
|
+
var DEFAULT_SENSITIVE_PATTERNS = [
|
|
12001
|
+
"*.env",
|
|
12002
|
+
"*.env.*",
|
|
12003
|
+
"*.key",
|
|
12004
|
+
"*.pem",
|
|
12005
|
+
"id_rsa*",
|
|
12006
|
+
"id_ed25519*",
|
|
12007
|
+
"*credentials*",
|
|
12008
|
+
"*secret*"
|
|
12009
|
+
];
|
|
12010
|
+
function resolveSensitivePath(token, projectRoot) {
|
|
12011
|
+
if (!token || token.startsWith("-") || token.includes("://") || token.startsWith("$"))
|
|
12012
|
+
return null;
|
|
12013
|
+
let expanded = token;
|
|
12014
|
+
if (expanded.startsWith("~")) {
|
|
12015
|
+
expanded = pathMod8.join(homedir6(), expanded.slice(1));
|
|
12016
|
+
}
|
|
12017
|
+
return pathMod8.resolve(projectRoot, expanded);
|
|
12018
|
+
}
|
|
12019
|
+
function expandPrefix(prefix) {
|
|
12020
|
+
if (prefix.startsWith("~")) return pathMod8.join(homedir6(), prefix.slice(1));
|
|
12021
|
+
return pathMod8.resolve(prefix);
|
|
12022
|
+
}
|
|
12023
|
+
function pathStartsWithPrefix(normalized, prefix) {
|
|
12024
|
+
return normalized === prefix || normalized.startsWith(`${prefix}${pathMod8.sep}`);
|
|
12025
|
+
}
|
|
12026
|
+
function matchesGlob(name, pattern) {
|
|
12027
|
+
const regex = new RegExp(
|
|
12028
|
+
`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`,
|
|
12029
|
+
"i"
|
|
12030
|
+
);
|
|
12031
|
+
return regex.test(name);
|
|
12032
|
+
}
|
|
12033
|
+
function hasSensitivePathArgs(argv, projectRoot, extraPrefixes = [], extraPatterns = []) {
|
|
12034
|
+
const prefixes = [...DEFAULT_SENSITIVE_PREFIXES, ...extraPrefixes].map(expandPrefix);
|
|
12035
|
+
const patterns = [...DEFAULT_SENSITIVE_PATTERNS, ...extraPatterns];
|
|
12036
|
+
for (const token of argv) {
|
|
12037
|
+
const resolved = resolveSensitivePath(token, projectRoot);
|
|
12038
|
+
if (!resolved) continue;
|
|
12039
|
+
const normalized = pathMod8.normalize(resolved);
|
|
12040
|
+
for (const pfx of prefixes) {
|
|
12041
|
+
if (pathStartsWithPrefix(normalized, pfx)) return true;
|
|
12042
|
+
}
|
|
12043
|
+
const base = pathMod8.basename(normalized);
|
|
12044
|
+
for (const pat of patterns) {
|
|
12045
|
+
if (matchesGlob(base, pat)) return true;
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
return false;
|
|
12049
|
+
}
|
|
12050
|
+
function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
11204
12051
|
let argv;
|
|
11205
12052
|
try {
|
|
11206
12053
|
argv = tokenizeCommand(cmd);
|
|
@@ -11222,19 +12069,26 @@ function isAllowed(cmd, extra = []) {
|
|
|
11222
12069
|
if (!match) continue;
|
|
11223
12070
|
const risky = RISKY_ARGS[prefix];
|
|
11224
12071
|
if (risky && tailHasRisky(argv.slice(prefixTokens.length), risky)) return false;
|
|
12072
|
+
if (projectRoot && hasSensitivePathArgs(
|
|
12073
|
+
argv,
|
|
12074
|
+
projectRoot,
|
|
12075
|
+
sensitivePathConfig?.prefixes,
|
|
12076
|
+
sensitivePathConfig?.patterns
|
|
12077
|
+
))
|
|
12078
|
+
return false;
|
|
11225
12079
|
return true;
|
|
11226
12080
|
}
|
|
11227
12081
|
return false;
|
|
11228
12082
|
}
|
|
11229
|
-
function isCommandAllowed(cmd, extra = []) {
|
|
12083
|
+
function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
11230
12084
|
let chain;
|
|
11231
12085
|
try {
|
|
11232
12086
|
chain = parseCommandChain(cmd);
|
|
11233
12087
|
} catch {
|
|
11234
12088
|
return false;
|
|
11235
12089
|
}
|
|
11236
|
-
if (chain === null) return isAllowed(cmd, extra);
|
|
11237
|
-
return chainAllowed(chain, (seg) => isAllowed(seg, extra));
|
|
12090
|
+
if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig);
|
|
12091
|
+
return chainAllowed(chain, (seg) => isAllowed(seg, extra, projectRoot, sensitivePathConfig));
|
|
11238
12092
|
}
|
|
11239
12093
|
|
|
11240
12094
|
// src/tools/shell/exec.ts
|
|
@@ -11281,8 +12135,15 @@ async function runCommand(cmd, opts) {
|
|
|
11281
12135
|
const spawnOpts = {
|
|
11282
12136
|
cwd: opts.cwd,
|
|
11283
12137
|
shell: false,
|
|
11284
|
-
// no shell-expansion — see header comment
|
|
11285
12138
|
windowsHide: true,
|
|
12139
|
+
// POSIX: detach so the child becomes its own process-group leader.
|
|
12140
|
+
// Required for `process.kill(-pid, …)` in killProcessTree to
|
|
12141
|
+
// terminate the whole subtree (child + grandchildren) instead of
|
|
12142
|
+
// only the leader — without this grandchildren like npm→node→esbuild
|
|
12143
|
+
// become orphaned.
|
|
12144
|
+
// Windows: detached would spawn a new console window; leave the
|
|
12145
|
+
// default and use taskkill /T for tree termination (see killProcessTree).
|
|
12146
|
+
detached: process.platform !== "win32",
|
|
11286
12147
|
// PYTHONIOENCODING + PYTHONUTF8 force any spawned Python child
|
|
11287
12148
|
// (run_command running `python script.py`, etc.) to emit UTF-8
|
|
11288
12149
|
// on stdout/stderr. Without this, Chinese-Windows defaults
|
|
@@ -11295,7 +12156,7 @@ async function runCommand(cmd, opts) {
|
|
|
11295
12156
|
};
|
|
11296
12157
|
const { bin, args, spawnOverrides } = prepareSpawn(argv, { env: normalizedEnv });
|
|
11297
12158
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
11298
|
-
return await new Promise((
|
|
12159
|
+
return await new Promise((resolve13, reject) => {
|
|
11299
12160
|
let child;
|
|
11300
12161
|
try {
|
|
11301
12162
|
child = spawn4(bin, args, effectiveSpawnOpts);
|
|
@@ -11349,7 +12210,7 @@ async function runCommand(cmd, opts) {
|
|
|
11349
12210
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
11350
12211
|
|
|
11351
12212
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
11352
|
-
|
|
12213
|
+
resolve13({ exitCode: code, output, timedOut });
|
|
11353
12214
|
});
|
|
11354
12215
|
});
|
|
11355
12216
|
}
|
|
@@ -11371,16 +12232,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
11371
12232
|
const platform = opts.platform ?? process.platform;
|
|
11372
12233
|
if (platform !== "win32") return cmd;
|
|
11373
12234
|
if (!cmd) return cmd;
|
|
11374
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
11375
|
-
if (
|
|
12235
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod9.isAbsolute(cmd)) return cmd;
|
|
12236
|
+
if (pathMod9.extname(cmd)) return cmd;
|
|
11376
12237
|
const env = opts.env ?? process.env;
|
|
11377
12238
|
const pathExt = (getEnvCaseInsensitive(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
11378
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
12239
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod9.delimiter);
|
|
11379
12240
|
const pathDirs = (getEnvCaseInsensitive(env, "PATH") ?? "").split(delimiter2).filter(Boolean);
|
|
11380
12241
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
11381
12242
|
for (const dir of pathDirs) {
|
|
11382
12243
|
for (const ext of pathExt) {
|
|
11383
|
-
const full =
|
|
12244
|
+
const full = pathMod9.win32.join(dir, cmd + ext);
|
|
11384
12245
|
if (isFile(full)) return full;
|
|
11385
12246
|
}
|
|
11386
12247
|
}
|
|
@@ -11434,7 +12295,7 @@ function mergeWindowsPathLike(values, delimiter2) {
|
|
|
11434
12295
|
}
|
|
11435
12296
|
function defaultIsFile(full) {
|
|
11436
12297
|
try {
|
|
11437
|
-
return
|
|
12298
|
+
return existsSync9(full) && statSync5(full).isFile();
|
|
11438
12299
|
} catch {
|
|
11439
12300
|
return false;
|
|
11440
12301
|
}
|
|
@@ -11496,8 +12357,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
11496
12357
|
function isBareWindowsName(s) {
|
|
11497
12358
|
if (!s) return false;
|
|
11498
12359
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
11499
|
-
if (
|
|
11500
|
-
if (
|
|
12360
|
+
if (pathMod9.isAbsolute(s)) return false;
|
|
12361
|
+
if (pathMod9.extname(s)) return false;
|
|
11501
12362
|
return true;
|
|
11502
12363
|
}
|
|
11503
12364
|
function quoteForCmdExe(arg) {
|
|
@@ -11518,7 +12379,7 @@ var NeedsConfirmationError = class extends Error {
|
|
|
11518
12379
|
}
|
|
11519
12380
|
};
|
|
11520
12381
|
function registerShellTools(registry, opts) {
|
|
11521
|
-
const rootDir =
|
|
12382
|
+
const rootDir = pathMod10.resolve(opts.rootDir);
|
|
11522
12383
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
11523
12384
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
11524
12385
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -11538,7 +12399,7 @@ function registerShellTools(registry, opts) {
|
|
|
11538
12399
|
if (isAllowAll()) return true;
|
|
11539
12400
|
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
11540
12401
|
if (!cmd) return false;
|
|
11541
|
-
return isCommandAllowed(cmd, getExtraAllowed());
|
|
12402
|
+
return isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths);
|
|
11542
12403
|
},
|
|
11543
12404
|
parameters: {
|
|
11544
12405
|
type: "object",
|
|
@@ -11558,7 +12419,7 @@ function registerShellTools(registry, opts) {
|
|
|
11558
12419
|
const cmd = args.command.trim();
|
|
11559
12420
|
if (!cmd) throw new Error("run_command: empty command");
|
|
11560
12421
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
11561
|
-
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed())) {
|
|
12422
|
+
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths)) {
|
|
11562
12423
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
11563
12424
|
const choice = await gate.ask({
|
|
11564
12425
|
kind: "run_command",
|
|
@@ -11584,7 +12445,7 @@ function registerShellTools(registry, opts) {
|
|
|
11584
12445
|
});
|
|
11585
12446
|
registry.register({
|
|
11586
12447
|
name: "run_background",
|
|
11587
|
-
description: "Spawn a long-running process and detach. Waits up to `waitSec` for startup or a readiness signal ('Local:', 'listening on', 'compiled successfully'), then returns the job id + startup preview. Tail logs with `job_output`, block on completion with `wait_for_job`, kill with `stop_job`, list with `list_jobs`.\n\nSingle process only \u2014 chains / redirects
|
|
12448
|
+
description: "Spawn a long-running process and detach. Waits up to `waitSec` for startup or a readiness signal ('Local:', 'listening on', 'compiled successfully'), then returns the job id + startup preview. Tail logs with `job_output`, block on completion with `wait_for_job`, kill with `stop_job`, list with `list_jobs`.\n\nSingle process only \u2014 no chains / redirects. For subdirectories use the `cwd` parameter (workspace-relative or absolute, must stay inside the workspace root); do NOT write `cd X && cmd`, that gets rejected.\n\nUSE THIS \u2014 not run_command \u2014 for:\n- Dev servers / watchers: npm/yarn/pnpm dev, uvicorn / flask run, cargo watch, tsc --watch, webpack serve, anything with dev/serve/watch in the name.\n- One-shot long jobs: curl / wget large downloads, `huggingface-cli download`, multi-GB `pip install` / `npm install`, big `cargo build` / `docker build`. Start with `run_background`, then call `wait_for_job` once (default `waitFor: 'exit'`, timeoutMs up to 300_000) \u2014 the harness blocks server-side so a 5-minute download costs ONE tool call, not 30 polls.",
|
|
11588
12449
|
parameters: {
|
|
11589
12450
|
type: "object",
|
|
11590
12451
|
properties: {
|
|
@@ -11592,6 +12453,10 @@ function registerShellTools(registry, opts) {
|
|
|
11592
12453
|
type: "string",
|
|
11593
12454
|
description: "Full command line. Same quoting rules as run_command (no pipes / redirects / chaining)."
|
|
11594
12455
|
},
|
|
12456
|
+
cwd: {
|
|
12457
|
+
type: "string",
|
|
12458
|
+
description: "Working directory for the spawn. Workspace-relative or absolute. Defaults to the workspace root. Must resolve inside the workspace \u2014 paths escaping the root are rejected."
|
|
12459
|
+
},
|
|
11595
12460
|
waitSec: {
|
|
11596
12461
|
type: "integer",
|
|
11597
12462
|
description: "Max seconds to wait for startup before returning. 0..30, default 3. A ready-signal match short-circuits this."
|
|
@@ -11602,11 +12467,12 @@ function registerShellTools(registry, opts) {
|
|
|
11602
12467
|
fn: async (args, ctx) => {
|
|
11603
12468
|
const cmd = args.command.trim();
|
|
11604
12469
|
if (!cmd) throw new Error("run_background: empty command");
|
|
11605
|
-
|
|
12470
|
+
const cwd = resolveCwdInsideRoot(rootDir, args.cwd);
|
|
12471
|
+
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths)) {
|
|
11606
12472
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
11607
12473
|
const choice = await gate.ask({
|
|
11608
12474
|
kind: "run_background",
|
|
11609
|
-
payload: { command: cmd, cwd
|
|
12475
|
+
payload: { command: cmd, cwd, waitSec: args.waitSec }
|
|
11610
12476
|
});
|
|
11611
12477
|
if (choice.type === "deny") {
|
|
11612
12478
|
throw new Error(
|
|
@@ -11618,10 +12484,11 @@ function registerShellTools(registry, opts) {
|
|
|
11618
12484
|
}
|
|
11619
12485
|
}
|
|
11620
12486
|
const result = await jobs.start(cmd, {
|
|
11621
|
-
cwd
|
|
12487
|
+
cwd,
|
|
11622
12488
|
waitSec: args.waitSec,
|
|
11623
12489
|
signal: ctx?.signal
|
|
11624
12490
|
});
|
|
12491
|
+
opts.onJobsChanged?.();
|
|
11625
12492
|
return formatJobStart(result);
|
|
11626
12493
|
}
|
|
11627
12494
|
});
|
|
@@ -11683,6 +12550,7 @@ function registerShellTools(registry, opts) {
|
|
|
11683
12550
|
waitFor: args.waitFor
|
|
11684
12551
|
});
|
|
11685
12552
|
if (!out) return `job ${args.jobId}: not found (use list_jobs)`;
|
|
12553
|
+
if (out.exited) opts.onJobsChanged?.();
|
|
11686
12554
|
return {
|
|
11687
12555
|
jobId: args.jobId,
|
|
11688
12556
|
exited: out.exited,
|
|
@@ -11703,6 +12571,7 @@ function registerShellTools(registry, opts) {
|
|
|
11703
12571
|
},
|
|
11704
12572
|
fn: async (args) => {
|
|
11705
12573
|
const rec = await jobs.stop(args.jobId);
|
|
12574
|
+
opts.onJobsChanged?.();
|
|
11706
12575
|
if (!rec) return `job ${args.jobId}: not found`;
|
|
11707
12576
|
return formatJobStop(rec);
|
|
11708
12577
|
}
|
|
@@ -11722,6 +12591,18 @@ function registerShellTools(registry, opts) {
|
|
|
11722
12591
|
});
|
|
11723
12592
|
return registry;
|
|
11724
12593
|
}
|
|
12594
|
+
function resolveCwdInsideRoot(rootDir, raw) {
|
|
12595
|
+
const root = pathMod10.resolve(rootDir);
|
|
12596
|
+
if (!raw || !raw.trim()) return root;
|
|
12597
|
+
const resolved = pathMod10.resolve(root, raw);
|
|
12598
|
+
const rel = pathMod10.relative(root, resolved);
|
|
12599
|
+
if (rel.startsWith("..") || pathMod10.isAbsolute(rel)) {
|
|
12600
|
+
throw new Error(
|
|
12601
|
+
`run_background: cwd "${raw}" resolves outside the workspace root (${root}). Pass a workspace-relative path.`
|
|
12602
|
+
);
|
|
12603
|
+
}
|
|
12604
|
+
return resolved;
|
|
12605
|
+
}
|
|
11725
12606
|
function formatJobStart(r) {
|
|
11726
12607
|
const header = r.stillRunning ? `[job ${r.jobId} started \xB7 pid ${r.pid ?? "?"} \xB7 ${r.readyMatched ? "READY signal matched" : "running (no ready signal yet)"}]` : r.exitCode !== null ? `[job ${r.jobId} exited during startup \xB7 exit ${r.exitCode}]` : `[job ${r.jobId} failed to start]`;
|
|
11727
12608
|
return r.preview ? `${header}
|
|
@@ -11770,6 +12651,7 @@ var DEFAULT_TOPK = 5;
|
|
|
11770
12651
|
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
11771
12652
|
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";
|
|
11772
12653
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
12654
|
+
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
11773
12655
|
function searchStatusError(status) {
|
|
11774
12656
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
11775
12657
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -11783,6 +12665,9 @@ function fetchStatusError(status, url) {
|
|
|
11783
12665
|
return t("webErrors.fetchStatus", { status, url });
|
|
11784
12666
|
}
|
|
11785
12667
|
async function webSearch(query, opts = {}) {
|
|
12668
|
+
if (opts.engine === "metaso") {
|
|
12669
|
+
return searchMetaso(query, opts);
|
|
12670
|
+
}
|
|
11786
12671
|
if (opts.engine === "searxng") {
|
|
11787
12672
|
return searchSearxng(query, opts);
|
|
11788
12673
|
}
|
|
@@ -11858,6 +12743,68 @@ async function searchSearxng(query, opts = {}) {
|
|
|
11858
12743
|
}
|
|
11859
12744
|
return results;
|
|
11860
12745
|
}
|
|
12746
|
+
async function searchMetaso(query, opts = {}) {
|
|
12747
|
+
const topK = Math.max(1, Math.min(100, opts.topK ?? DEFAULT_TOPK));
|
|
12748
|
+
const apiKey = loadMetasoApiKey();
|
|
12749
|
+
let resp;
|
|
12750
|
+
try {
|
|
12751
|
+
resp = await fetch(`${METASO_ENDPOINT}/search`, {
|
|
12752
|
+
method: "POST",
|
|
12753
|
+
headers: {
|
|
12754
|
+
"Content-Type": "application/json",
|
|
12755
|
+
Accept: "application/json",
|
|
12756
|
+
Authorization: `Bearer ${apiKey}`
|
|
12757
|
+
},
|
|
12758
|
+
body: JSON.stringify({
|
|
12759
|
+
q: query,
|
|
12760
|
+
scope: "webpage",
|
|
12761
|
+
size: topK
|
|
12762
|
+
}),
|
|
12763
|
+
signal: opts.signal
|
|
12764
|
+
});
|
|
12765
|
+
} catch (err) {
|
|
12766
|
+
if (err instanceof TypeError && err.message.includes("fetch")) {
|
|
12767
|
+
throw new Error(t("webErrors.cannotReach", { endpoint: METASO_ENDPOINT }));
|
|
12768
|
+
}
|
|
12769
|
+
throw err;
|
|
12770
|
+
}
|
|
12771
|
+
const raw = await resp.text();
|
|
12772
|
+
let data;
|
|
12773
|
+
try {
|
|
12774
|
+
data = JSON.parse(raw);
|
|
12775
|
+
} catch {
|
|
12776
|
+
throw new Error(t("webErrors.metasoParseError", { status: resp.status }));
|
|
12777
|
+
}
|
|
12778
|
+
if (!resp.ok) {
|
|
12779
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
12780
|
+
throw new Error(t("webErrors.metasoUnauthorized"));
|
|
12781
|
+
}
|
|
12782
|
+
if (resp.status === 429) {
|
|
12783
|
+
throw new Error(t("webErrors.metasoRateLimit"));
|
|
12784
|
+
}
|
|
12785
|
+
throw new Error(t("webErrors.metasoServerError", { status: resp.status }));
|
|
12786
|
+
}
|
|
12787
|
+
if (data.code === 3003) {
|
|
12788
|
+
throw new Error(t("webErrors.metasoDailyLimit"));
|
|
12789
|
+
}
|
|
12790
|
+
if (data.code === 2005) {
|
|
12791
|
+
throw new Error(t("webErrors.metasoUnauthorized"));
|
|
12792
|
+
}
|
|
12793
|
+
if (data.code && data.code !== 0) {
|
|
12794
|
+
throw new Error(
|
|
12795
|
+
t("webErrors.metasoApiError", { code: data.code, message: data.message ?? "" })
|
|
12796
|
+
);
|
|
12797
|
+
}
|
|
12798
|
+
const webpages = data.webpages ?? [];
|
|
12799
|
+
if (webpages.length === 0) {
|
|
12800
|
+
return [];
|
|
12801
|
+
}
|
|
12802
|
+
return webpages.slice(0, topK).map((wp) => ({
|
|
12803
|
+
title: wp.title,
|
|
12804
|
+
url: wp.link,
|
|
12805
|
+
snippet: wp.snippet ?? wp.summary ?? ""
|
|
12806
|
+
}));
|
|
12807
|
+
}
|
|
11861
12808
|
function parseSearxngHtmlResults(html) {
|
|
11862
12809
|
const root = parseHtml(html);
|
|
11863
12810
|
const results = [];
|
|
@@ -12076,7 +13023,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
12076
13023
|
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
12077
13024
|
registry.register({
|
|
12078
13025
|
name: "web_search",
|
|
12079
|
-
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this. To change the backend, use /
|
|
13026
|
+
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this. To change the backend, use /search-engine mojeek|searxng|metaso.",
|
|
12080
13027
|
readOnly: true,
|
|
12081
13028
|
parallelSafe: true,
|
|
12082
13029
|
parameters: {
|
|
@@ -12141,12 +13088,12 @@ ${i + 1}. ${r.title}`);
|
|
|
12141
13088
|
}
|
|
12142
13089
|
|
|
12143
13090
|
// src/env.ts
|
|
12144
|
-
import { readFileSync as
|
|
12145
|
-
import { resolve as
|
|
13091
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
13092
|
+
import { resolve as resolve11 } from "path";
|
|
12146
13093
|
function loadDotenv(path2 = ".env") {
|
|
12147
13094
|
let raw;
|
|
12148
13095
|
try {
|
|
12149
|
-
raw =
|
|
13096
|
+
raw = readFileSync11(resolve11(process.cwd(), path2), "utf8");
|
|
12150
13097
|
} catch {
|
|
12151
13098
|
return;
|
|
12152
13099
|
}
|
|
@@ -12165,7 +13112,7 @@ function loadDotenv(path2 = ".env") {
|
|
|
12165
13112
|
}
|
|
12166
13113
|
|
|
12167
13114
|
// src/transcript/log.ts
|
|
12168
|
-
import { createWriteStream, readFileSync as
|
|
13115
|
+
import { createWriteStream, readFileSync as readFileSync12 } from "fs";
|
|
12169
13116
|
function recordFromLoopEvent(ev, extra) {
|
|
12170
13117
|
const rec = {
|
|
12171
13118
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12208,7 +13155,7 @@ function openTranscriptFile(path2, meta) {
|
|
|
12208
13155
|
return stream;
|
|
12209
13156
|
}
|
|
12210
13157
|
function readTranscript(path2) {
|
|
12211
|
-
const raw =
|
|
13158
|
+
const raw = readFileSync12(path2, "utf8");
|
|
12212
13159
|
return parseTranscript(raw);
|
|
12213
13160
|
}
|
|
12214
13161
|
function parseTranscript(raw) {
|
|
@@ -12595,25 +13542,25 @@ function truncate(s, n) {
|
|
|
12595
13542
|
}
|
|
12596
13543
|
|
|
12597
13544
|
// src/version.ts
|
|
12598
|
-
import { existsSync as
|
|
12599
|
-
import { homedir as
|
|
12600
|
-
import { dirname as
|
|
13545
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
13546
|
+
import { homedir as homedir7 } from "os";
|
|
13547
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
12601
13548
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12602
13549
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
12603
13550
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12604
13551
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
12605
13552
|
function readPackageVersion() {
|
|
12606
13553
|
try {
|
|
12607
|
-
let dir =
|
|
13554
|
+
let dir = dirname7(fileURLToPath2(import.meta.url));
|
|
12608
13555
|
for (let i = 0; i < 6; i++) {
|
|
12609
|
-
const p =
|
|
12610
|
-
if (
|
|
12611
|
-
const pkg = JSON.parse(
|
|
13556
|
+
const p = join14(dir, "package.json");
|
|
13557
|
+
if (existsSync10(p)) {
|
|
13558
|
+
const pkg = JSON.parse(readFileSync13(p, "utf8"));
|
|
12612
13559
|
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
12613
13560
|
return pkg.version;
|
|
12614
13561
|
}
|
|
12615
13562
|
}
|
|
12616
|
-
const parent =
|
|
13563
|
+
const parent = dirname7(dir);
|
|
12617
13564
|
if (parent === dir) break;
|
|
12618
13565
|
dir = parent;
|
|
12619
13566
|
}
|
|
@@ -12623,11 +13570,11 @@ function readPackageVersion() {
|
|
|
12623
13570
|
}
|
|
12624
13571
|
var VERSION = readPackageVersion();
|
|
12625
13572
|
function cachePath(homeDirOverride) {
|
|
12626
|
-
return
|
|
13573
|
+
return join14(homeDirOverride ?? homedir7(), ".reasonix", "version-cache.json");
|
|
12627
13574
|
}
|
|
12628
13575
|
function readCache(homeDirOverride) {
|
|
12629
13576
|
try {
|
|
12630
|
-
const raw =
|
|
13577
|
+
const raw = readFileSync13(cachePath(homeDirOverride), "utf8");
|
|
12631
13578
|
const parsed = JSON.parse(raw);
|
|
12632
13579
|
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
12633
13580
|
return parsed;
|
|
@@ -12639,7 +13586,7 @@ function readCache(homeDirOverride) {
|
|
|
12639
13586
|
function writeCache(entry, homeDirOverride) {
|
|
12640
13587
|
try {
|
|
12641
13588
|
const p = cachePath(homeDirOverride);
|
|
12642
|
-
mkdirSync5(
|
|
13589
|
+
mkdirSync5(dirname7(p), { recursive: true });
|
|
12643
13590
|
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
12644
13591
|
} catch {
|
|
12645
13592
|
}
|
|
@@ -12851,7 +13798,7 @@ var McpClient = class {
|
|
|
12851
13798
|
const id = this.nextId++;
|
|
12852
13799
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
12853
13800
|
let abortHandler = null;
|
|
12854
|
-
const promise = new Promise((
|
|
13801
|
+
const promise = new Promise((resolve13, reject) => {
|
|
12855
13802
|
const timeout = setTimeout(() => {
|
|
12856
13803
|
this.pending.delete(id);
|
|
12857
13804
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -12860,7 +13807,7 @@ var McpClient = class {
|
|
|
12860
13807
|
);
|
|
12861
13808
|
}, this.requestTimeoutMs);
|
|
12862
13809
|
this.pending.set(id, {
|
|
12863
|
-
resolve:
|
|
13810
|
+
resolve: resolve13,
|
|
12864
13811
|
reject,
|
|
12865
13812
|
timeout
|
|
12866
13813
|
});
|
|
@@ -12990,12 +13937,12 @@ var StdioTransport = class {
|
|
|
12990
13937
|
}
|
|
12991
13938
|
async send(message) {
|
|
12992
13939
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
12993
|
-
return new Promise((
|
|
13940
|
+
return new Promise((resolve13, reject) => {
|
|
12994
13941
|
const line = `${JSON.stringify(message)}
|
|
12995
13942
|
`;
|
|
12996
13943
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
12997
13944
|
if (err) reject(err);
|
|
12998
|
-
else
|
|
13945
|
+
else resolve13();
|
|
12999
13946
|
});
|
|
13000
13947
|
});
|
|
13001
13948
|
}
|
|
@@ -13006,8 +13953,8 @@ var StdioTransport = class {
|
|
|
13006
13953
|
continue;
|
|
13007
13954
|
}
|
|
13008
13955
|
if (this.closed) return;
|
|
13009
|
-
const next = await new Promise((
|
|
13010
|
-
this.waiters.push(
|
|
13956
|
+
const next = await new Promise((resolve13) => {
|
|
13957
|
+
this.waiters.push(resolve13);
|
|
13011
13958
|
});
|
|
13012
13959
|
if (next === null) return;
|
|
13013
13960
|
yield next;
|
|
@@ -13080,8 +14027,8 @@ var SseTransport = class {
|
|
|
13080
14027
|
constructor(opts) {
|
|
13081
14028
|
this.url = opts.url;
|
|
13082
14029
|
this.headers = opts.headers ?? {};
|
|
13083
|
-
this.endpointReady = new Promise((
|
|
13084
|
-
this.resolveEndpoint =
|
|
14030
|
+
this.endpointReady = new Promise((resolve13, reject) => {
|
|
14031
|
+
this.resolveEndpoint = resolve13;
|
|
13085
14032
|
this.rejectEndpoint = reject;
|
|
13086
14033
|
});
|
|
13087
14034
|
this.endpointReady.catch(() => void 0);
|
|
@@ -13108,8 +14055,8 @@ var SseTransport = class {
|
|
|
13108
14055
|
continue;
|
|
13109
14056
|
}
|
|
13110
14057
|
if (this.closed) return;
|
|
13111
|
-
const next = await new Promise((
|
|
13112
|
-
this.waiters.push(
|
|
14058
|
+
const next = await new Promise((resolve13) => {
|
|
14059
|
+
this.waiters.push(resolve13);
|
|
13113
14060
|
});
|
|
13114
14061
|
if (next === null) return;
|
|
13115
14062
|
yield next;
|
|
@@ -13295,8 +14242,8 @@ var StreamableHttpTransport = class {
|
|
|
13295
14242
|
continue;
|
|
13296
14243
|
}
|
|
13297
14244
|
if (this.closed) return;
|
|
13298
|
-
const next = await new Promise((
|
|
13299
|
-
this.waiters.push(
|
|
14245
|
+
const next = await new Promise((resolve13) => {
|
|
14246
|
+
this.waiters.push(resolve13);
|
|
13300
14247
|
});
|
|
13301
14248
|
if (next === null) return;
|
|
13302
14249
|
yield next;
|
|
@@ -13354,85 +14301,6 @@ var StreamableHttpTransport = class {
|
|
|
13354
14301
|
}
|
|
13355
14302
|
};
|
|
13356
14303
|
|
|
13357
|
-
// src/mcp/shell-split.ts
|
|
13358
|
-
function shellSplit(input) {
|
|
13359
|
-
const tokens = [];
|
|
13360
|
-
let cur = "";
|
|
13361
|
-
let quote = null;
|
|
13362
|
-
let i = 0;
|
|
13363
|
-
const s = input;
|
|
13364
|
-
while (i < s.length) {
|
|
13365
|
-
const ch = s[i];
|
|
13366
|
-
if (quote) {
|
|
13367
|
-
if (ch === quote) {
|
|
13368
|
-
quote = null;
|
|
13369
|
-
i++;
|
|
13370
|
-
continue;
|
|
13371
|
-
}
|
|
13372
|
-
if (ch === "\\" && quote === '"' && i + 1 < s.length) {
|
|
13373
|
-
cur += s[i + 1];
|
|
13374
|
-
i += 2;
|
|
13375
|
-
continue;
|
|
13376
|
-
}
|
|
13377
|
-
cur += ch;
|
|
13378
|
-
i++;
|
|
13379
|
-
continue;
|
|
13380
|
-
}
|
|
13381
|
-
if (ch === '"' || ch === "'") {
|
|
13382
|
-
quote = ch;
|
|
13383
|
-
i++;
|
|
13384
|
-
continue;
|
|
13385
|
-
}
|
|
13386
|
-
if (ch === " " || ch === " ") {
|
|
13387
|
-
if (cur.length > 0) {
|
|
13388
|
-
tokens.push(cur);
|
|
13389
|
-
cur = "";
|
|
13390
|
-
}
|
|
13391
|
-
i++;
|
|
13392
|
-
continue;
|
|
13393
|
-
}
|
|
13394
|
-
cur += ch;
|
|
13395
|
-
i++;
|
|
13396
|
-
}
|
|
13397
|
-
if (quote) {
|
|
13398
|
-
throw new Error(
|
|
13399
|
-
`shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
|
|
13400
|
-
);
|
|
13401
|
-
}
|
|
13402
|
-
if (cur.length > 0) tokens.push(cur);
|
|
13403
|
-
return tokens;
|
|
13404
|
-
}
|
|
13405
|
-
|
|
13406
|
-
// src/mcp/spec.ts
|
|
13407
|
-
var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_-]*)=(.*)$/;
|
|
13408
|
-
var HTTP_URL = /^https?:\/\//i;
|
|
13409
|
-
var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
|
|
13410
|
-
function parseMcpSpec(input) {
|
|
13411
|
-
const trimmed = input.trim();
|
|
13412
|
-
if (!trimmed) {
|
|
13413
|
-
throw new Error("empty MCP spec");
|
|
13414
|
-
}
|
|
13415
|
-
const nameMatch = NAME_PREFIX.exec(trimmed);
|
|
13416
|
-
const name = nameMatch ? nameMatch[1] : null;
|
|
13417
|
-
const body = (nameMatch ? nameMatch[2] : trimmed).trim();
|
|
13418
|
-
if (!body) {
|
|
13419
|
-
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
13420
|
-
}
|
|
13421
|
-
const streamMatch = STREAMABLE_PREFIX.exec(body);
|
|
13422
|
-
if (streamMatch) {
|
|
13423
|
-
return { transport: "streamable-http", name, url: streamMatch[1] };
|
|
13424
|
-
}
|
|
13425
|
-
if (HTTP_URL.test(body)) {
|
|
13426
|
-
return { transport: "sse", name, url: body };
|
|
13427
|
-
}
|
|
13428
|
-
const argv = shellSplit(body);
|
|
13429
|
-
if (argv.length === 0) {
|
|
13430
|
-
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
13431
|
-
}
|
|
13432
|
-
const [command, ...args] = argv;
|
|
13433
|
-
return { transport: "stdio", name, command, args };
|
|
13434
|
-
}
|
|
13435
|
-
|
|
13436
14304
|
// src/mcp/inspect.ts
|
|
13437
14305
|
async function inspectMcpServer(client) {
|
|
13438
14306
|
const t0 = Date.now();
|
|
@@ -13468,18 +14336,18 @@ async function trySection(load) {
|
|
|
13468
14336
|
// src/code/edit-blocks.ts
|
|
13469
14337
|
import {
|
|
13470
14338
|
closeSync as closeSync2,
|
|
13471
|
-
existsSync as
|
|
14339
|
+
existsSync as existsSync11,
|
|
13472
14340
|
fstatSync,
|
|
13473
14341
|
ftruncateSync,
|
|
13474
14342
|
mkdirSync as mkdirSync6,
|
|
13475
14343
|
openSync as openSync2,
|
|
13476
|
-
readFileSync as
|
|
14344
|
+
readFileSync as readFileSync14,
|
|
13477
14345
|
readSync,
|
|
13478
14346
|
unlinkSync as unlinkSync3,
|
|
13479
14347
|
writeFileSync as writeFileSync6,
|
|
13480
14348
|
writeSync
|
|
13481
14349
|
} from "fs";
|
|
13482
|
-
import { dirname as
|
|
14350
|
+
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
13483
14351
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
13484
14352
|
function parseEditBlocks(text) {
|
|
13485
14353
|
const out = [];
|
|
@@ -13497,9 +14365,9 @@ function parseEditBlocks(text) {
|
|
|
13497
14365
|
return out;
|
|
13498
14366
|
}
|
|
13499
14367
|
function applyEditBlock(block, rootDir) {
|
|
13500
|
-
const absRoot =
|
|
13501
|
-
const absTarget =
|
|
13502
|
-
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${
|
|
14368
|
+
const absRoot = resolve12(rootDir);
|
|
14369
|
+
const absTarget = resolve12(absRoot, block.path);
|
|
14370
|
+
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep2()}`)) {
|
|
13503
14371
|
return {
|
|
13504
14372
|
path: block.path,
|
|
13505
14373
|
status: "path-escape",
|
|
@@ -13509,7 +14377,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
13509
14377
|
const searchEmpty = block.search.length === 0;
|
|
13510
14378
|
if (searchEmpty) {
|
|
13511
14379
|
try {
|
|
13512
|
-
mkdirSync6(
|
|
14380
|
+
mkdirSync6(dirname8(absTarget), { recursive: true });
|
|
13513
14381
|
const fd = openSync2(absTarget, "wx");
|
|
13514
14382
|
try {
|
|
13515
14383
|
writeSync(fd, block.replace);
|
|
@@ -13585,19 +14453,19 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
13585
14453
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
13586
14454
|
}
|
|
13587
14455
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
13588
|
-
const absRoot =
|
|
14456
|
+
const absRoot = resolve12(rootDir);
|
|
13589
14457
|
const seen = /* @__PURE__ */ new Set();
|
|
13590
14458
|
const snapshots = [];
|
|
13591
14459
|
for (const b of blocks) {
|
|
13592
14460
|
if (seen.has(b.path)) continue;
|
|
13593
14461
|
seen.add(b.path);
|
|
13594
|
-
const abs =
|
|
13595
|
-
if (!
|
|
14462
|
+
const abs = resolve12(absRoot, b.path);
|
|
14463
|
+
if (!existsSync11(abs)) {
|
|
13596
14464
|
snapshots.push({ path: b.path, prevContent: null });
|
|
13597
14465
|
continue;
|
|
13598
14466
|
}
|
|
13599
14467
|
try {
|
|
13600
|
-
snapshots.push({ path: b.path, prevContent:
|
|
14468
|
+
snapshots.push({ path: b.path, prevContent: readFileSync14(abs, "utf8") });
|
|
13601
14469
|
} catch {
|
|
13602
14470
|
snapshots.push({ path: b.path, prevContent: null });
|
|
13603
14471
|
}
|
|
@@ -13605,10 +14473,10 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
13605
14473
|
return snapshots;
|
|
13606
14474
|
}
|
|
13607
14475
|
function restoreSnapshots(snapshots, rootDir) {
|
|
13608
|
-
const absRoot =
|
|
14476
|
+
const absRoot = resolve12(rootDir);
|
|
13609
14477
|
return snapshots.map((snap) => {
|
|
13610
|
-
const abs =
|
|
13611
|
-
if (abs !== absRoot && !abs.startsWith(`${absRoot}${
|
|
14478
|
+
const abs = resolve12(absRoot, snap.path);
|
|
14479
|
+
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep2()}`)) {
|
|
13612
14480
|
return {
|
|
13613
14481
|
path: snap.path,
|
|
13614
14482
|
status: "path-escape",
|
|
@@ -13617,7 +14485,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
13617
14485
|
}
|
|
13618
14486
|
try {
|
|
13619
14487
|
if (snap.prevContent === null) {
|
|
13620
|
-
if (
|
|
14488
|
+
if (existsSync11(abs)) unlinkSync3(abs);
|
|
13621
14489
|
return {
|
|
13622
14490
|
path: snap.path,
|
|
13623
14491
|
status: "applied",
|
|
@@ -13635,7 +14503,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
13635
14503
|
}
|
|
13636
14504
|
});
|
|
13637
14505
|
}
|
|
13638
|
-
function
|
|
14506
|
+
function sep2() {
|
|
13639
14507
|
return process.platform === "win32" ? "\\" : "/";
|
|
13640
14508
|
}
|
|
13641
14509
|
function lineEndingOf(text) {
|
|
@@ -13643,8 +14511,8 @@ function lineEndingOf(text) {
|
|
|
13643
14511
|
}
|
|
13644
14512
|
|
|
13645
14513
|
// src/code/prompt.ts
|
|
13646
|
-
import { existsSync as
|
|
13647
|
-
import { join as
|
|
14514
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
14515
|
+
import { join as join15 } from "path";
|
|
13648
14516
|
var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
|
|
13649
14517
|
function codeSystemBase(modelId) {
|
|
13650
14518
|
return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
|
|
@@ -13891,12 +14759,12 @@ function codeSystemPrompt(rootDir, opts = {}) {
|
|
|
13891
14759
|
const codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
|
|
13892
14760
|
const base = opts.hasSemanticSearch ? `${codeBase}${SEMANTIC_SEARCH_ROUTING}` : codeBase;
|
|
13893
14761
|
const withMemory = applyMemoryStack(base, rootDir);
|
|
13894
|
-
const gitignorePath =
|
|
14762
|
+
const gitignorePath = join15(rootDir, ".gitignore");
|
|
13895
14763
|
let result = withMemory;
|
|
13896
|
-
if (
|
|
14764
|
+
if (existsSync12(gitignorePath)) {
|
|
13897
14765
|
let content;
|
|
13898
14766
|
try {
|
|
13899
|
-
content =
|
|
14767
|
+
content = readFileSync15(gitignorePath, "utf8");
|
|
13900
14768
|
} catch {
|
|
13901
14769
|
}
|
|
13902
14770
|
if (content !== void 0) {
|
|
@@ -13930,21 +14798,21 @@ ${appendParts.join("\n\n")}`;
|
|
|
13930
14798
|
import {
|
|
13931
14799
|
appendFileSync as appendFileSync2,
|
|
13932
14800
|
closeSync as closeSync3,
|
|
13933
|
-
existsSync as
|
|
14801
|
+
existsSync as existsSync13,
|
|
13934
14802
|
fstatSync as fstatSync2,
|
|
13935
14803
|
mkdirSync as mkdirSync7,
|
|
13936
14804
|
openSync as openSync3,
|
|
13937
|
-
readFileSync as
|
|
14805
|
+
readFileSync as readFileSync16,
|
|
13938
14806
|
readSync as readSync2,
|
|
13939
14807
|
renameSync as renameSync2,
|
|
13940
14808
|
statSync as statSync6,
|
|
13941
14809
|
unlinkSync as unlinkSync4,
|
|
13942
14810
|
writeFileSync as writeFileSync7
|
|
13943
14811
|
} from "fs";
|
|
13944
|
-
import { homedir as
|
|
13945
|
-
import { dirname as
|
|
14812
|
+
import { homedir as homedir8 } from "os";
|
|
14813
|
+
import { dirname as dirname9, join as join16 } from "path";
|
|
13946
14814
|
function defaultUsageLogPath(homeDirOverride) {
|
|
13947
|
-
return
|
|
14815
|
+
return join16(homeDirOverride ?? homedir8(), ".reasonix", "usage.jsonl");
|
|
13948
14816
|
}
|
|
13949
14817
|
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
13950
14818
|
var USAGE_RETENTION_DAYS = 365;
|
|
@@ -14009,7 +14877,7 @@ function appendUsage(input) {
|
|
|
14009
14877
|
if (input.subagent) record.subagent = input.subagent;
|
|
14010
14878
|
const path2 = input.path ?? defaultUsageLogPath();
|
|
14011
14879
|
try {
|
|
14012
|
-
mkdirSync7(
|
|
14880
|
+
mkdirSync7(dirname9(path2), { recursive: true });
|
|
14013
14881
|
appendFileSync2(path2, `${JSON.stringify(record)}
|
|
14014
14882
|
`, "utf8");
|
|
14015
14883
|
compactUsageLogIfLarge(path2, record.ts);
|
|
@@ -14018,10 +14886,10 @@ function appendUsage(input) {
|
|
|
14018
14886
|
return record;
|
|
14019
14887
|
}
|
|
14020
14888
|
function readUsageLog(path2 = defaultUsageLogPath()) {
|
|
14021
|
-
if (!
|
|
14889
|
+
if (!existsSync13(path2)) return [];
|
|
14022
14890
|
let raw;
|
|
14023
14891
|
try {
|
|
14024
|
-
raw =
|
|
14892
|
+
raw = readFileSync16(path2, "utf8");
|
|
14025
14893
|
} catch {
|
|
14026
14894
|
return [];
|
|
14027
14895
|
}
|
|
@@ -14128,7 +14996,7 @@ function aggregateUsage(records, opts = {}) {
|
|
|
14128
14996
|
};
|
|
14129
14997
|
}
|
|
14130
14998
|
function formatLogSize(path2 = defaultUsageLogPath()) {
|
|
14131
|
-
if (!
|
|
14999
|
+
if (!existsSync13(path2)) return "";
|
|
14132
15000
|
try {
|
|
14133
15001
|
const s = statSync6(path2);
|
|
14134
15002
|
const bytes = s.size;
|
|
@@ -14151,6 +15019,7 @@ export {
|
|
|
14151
15019
|
DEFAULT_MAX_RESULT_CHARS,
|
|
14152
15020
|
DEFAULT_MAX_RESULT_TOKENS,
|
|
14153
15021
|
DEFAULT_PICKER_IGNORE_DIRS,
|
|
15022
|
+
DEFAULT_SPAWN_STORM_THRESHOLD,
|
|
14154
15023
|
DeepSeekClient,
|
|
14155
15024
|
HOOK_EVENTS,
|
|
14156
15025
|
HOOK_SETTINGS_DIRNAME,
|
|
@@ -14174,6 +15043,7 @@ export {
|
|
|
14174
15043
|
StdioTransport,
|
|
14175
15044
|
StormBreaker,
|
|
14176
15045
|
StreamableHttpTransport,
|
|
15046
|
+
SubagentTelemetry,
|
|
14177
15047
|
ToolCallRepair,
|
|
14178
15048
|
ToolRegistry,
|
|
14179
15049
|
USER_MEMORY_DIR,
|
|
@@ -14196,7 +15066,9 @@ export {
|
|
|
14196
15066
|
codeSystemPrompt,
|
|
14197
15067
|
compareVersions,
|
|
14198
15068
|
computeReplayStats,
|
|
15069
|
+
computeSpawnDistillation,
|
|
14199
15070
|
costUsd,
|
|
15071
|
+
countSpawnStorms,
|
|
14200
15072
|
decideOutcome,
|
|
14201
15073
|
defaultConfigPath,
|
|
14202
15074
|
defaultUsageLogPath,
|
|
@@ -14239,6 +15111,7 @@ export {
|
|
|
14239
15111
|
loadBaseUrl,
|
|
14240
15112
|
loadDotenv,
|
|
14241
15113
|
loadHooks,
|
|
15114
|
+
loadMetasoApiKey,
|
|
14242
15115
|
loadSessionMessages,
|
|
14243
15116
|
matchesTool,
|
|
14244
15117
|
memoryEnabled,
|
|
@@ -14289,6 +15162,7 @@ export {
|
|
|
14289
15162
|
similarity,
|
|
14290
15163
|
snapshotBeforeEdits,
|
|
14291
15164
|
stripHallucinatedToolMarkup,
|
|
15165
|
+
summarizeSubagentSession,
|
|
14292
15166
|
tokenizeCommand,
|
|
14293
15167
|
truncateForModel,
|
|
14294
15168
|
truncateForModelByTokens,
|