reasonix 0.43.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -11
- package/README.zh-CN.md +35 -7
- package/dashboard/app.css +225 -4
- package/dashboard/dist/app.js +6441 -6080
- package/dashboard/dist/app.js.map +1 -1
- package/data/deepseek-tokenizer.json.gz +0 -0
- package/dist/cli/{acp-DAGPCVFZ.js → acp-TYZ2CTDL.js} +28 -30
- package/dist/cli/acp-TYZ2CTDL.js.map +1 -0
- package/dist/cli/chat-TH7VNNCJ.js +51 -0
- package/dist/cli/chunk-2425HK6U.js +0 -0
- package/dist/cli/chunk-25T6CVUP.js +0 -0
- package/dist/cli/chunk-2UQP6H6T.js +0 -0
- package/dist/cli/{chunk-3Z6IBU3D.js → chunk-2V6EAEUW.js} +95 -31
- package/dist/cli/chunk-2V6EAEUW.js.map +1 -0
- package/dist/cli/{chunk-XCGGEJTI.js → chunk-4CTDEJUF.js} +2 -2
- package/dist/cli/chunk-4QUNBQQ2.js +0 -0
- package/dist/cli/{chunk-74EX7SUH.js → chunk-5QCB62C4.js} +33 -7
- package/dist/cli/{chunk-74EX7SUH.js.map → chunk-5QCB62C4.js.map} +1 -1
- package/dist/cli/chunk-6OWJV3YW.js +390 -0
- package/dist/cli/chunk-6OWJV3YW.js.map +1 -0
- package/dist/cli/chunk-6PBZN4VI.js +0 -0
- package/dist/cli/{chunk-7O5ALB4C.js → chunk-7CIGMZT3.js} +2 -2
- package/dist/cli/{chunk-H6PS7IUE.js → chunk-7UCMM425.js} +7 -3
- package/dist/cli/chunk-7UCMM425.js.map +1 -0
- package/dist/cli/{chunk-TJX6BFZZ.js → chunk-AB2RED3C.js} +3 -3
- package/dist/cli/{chunk-XPDVG52A.js → chunk-AVFXO2EZ.js} +361 -13
- package/dist/cli/chunk-AVFXO2EZ.js.map +1 -0
- package/dist/cli/{chunk-FHOGSSCH.js → chunk-C53JQES5.js} +3 -3
- package/dist/cli/{chunk-RE4RAVFF.js → chunk-CGDR2ELH.js} +92 -30
- package/dist/cli/chunk-CGDR2ELH.js.map +1 -0
- package/dist/cli/{chunk-OSZC7C6F.js → chunk-CWZKQ5FE.js} +7 -4
- package/dist/cli/chunk-CWZKQ5FE.js.map +1 -0
- package/dist/cli/{devtools-YECO25QO.js → chunk-FEZK652I.js} +10 -85
- package/dist/cli/chunk-FEZK652I.js.map +1 -0
- package/dist/cli/{chunk-45U62RI3.js → chunk-HNXDZGC6.js} +104 -2
- package/dist/cli/chunk-HNXDZGC6.js.map +1 -0
- package/dist/cli/chunk-J5XJHLWM.js +0 -0
- package/dist/cli/chunk-JMBMLOBP.js +0 -0
- package/dist/cli/{chunk-5JJRUIPA.js → chunk-JNAQYELD.js} +16 -8
- package/dist/cli/{chunk-5JJRUIPA.js.map → chunk-JNAQYELD.js.map} +1 -1
- package/dist/cli/{chunk-YFGF5NKA.js → chunk-KGBG6M2X.js} +19 -15
- package/dist/cli/chunk-KGBG6M2X.js.map +1 -0
- package/dist/cli/{chunk-3BXRZFWS.js → chunk-KLQTAZIY.js} +12 -4
- package/dist/cli/chunk-KLQTAZIY.js.map +1 -0
- package/dist/cli/{chunk-VK5HG73G.js → chunk-KM465GST.js} +9 -9
- package/dist/cli/{chunk-DOYHN4KB.js → chunk-LIR2HBQH.js} +2 -2
- package/dist/cli/{chunk-YYQAUTTN.js → chunk-MJ6W5UN3.js} +2 -2
- package/dist/cli/{chunk-6PZ3CXBP.js → chunk-MRHHQJAQ.js} +5 -4
- package/dist/cli/chunk-MRHHQJAQ.js.map +1 -0
- package/dist/cli/{chunk-PQXPXJBJ.js → chunk-NVURFF27.js} +16 -5
- package/dist/cli/chunk-NVURFF27.js.map +1 -0
- package/dist/cli/{chunk-2R4QCDOZ.js → chunk-OPFUUYHL.js} +540 -287
- package/dist/cli/chunk-OPFUUYHL.js.map +1 -0
- package/dist/cli/chunk-PLHAZOLZ.js +0 -0
- package/dist/cli/{chunk-HFEAY5DT.js → chunk-R3CTO2HM.js} +2 -2
- package/dist/cli/{chunk-O52OLQL3.js → chunk-RDRC3XDT.js} +136 -38
- package/dist/cli/chunk-RDRC3XDT.js.map +1 -0
- package/dist/cli/chunk-S4XVGLRW.js +0 -0
- package/dist/cli/chunk-SZ5XES2N.js +0 -0
- package/dist/cli/{chunk-2K65GZBT.js → chunk-TEUDEGX2.js} +64 -19
- package/dist/cli/chunk-TEUDEGX2.js.map +1 -0
- package/dist/cli/{chunk-2Z35JOA4.js → chunk-TKVXTQ3T.js} +4 -4
- package/dist/cli/{chunk-2Z35JOA4.js.map → chunk-TKVXTQ3T.js.map} +1 -1
- package/dist/cli/chunk-TUK7OWJA.js +0 -0
- package/dist/cli/{chunk-32TIKD5U.js → chunk-TXJMRPIL.js} +3 -3
- package/dist/cli/{chunk-2KDUS647.js → chunk-V26WPN3J.js} +7 -4
- package/dist/cli/chunk-V26WPN3J.js.map +1 -0
- package/dist/cli/{chunk-F3PXYSNN.js → chunk-WK3UFQY3.js} +2 -2
- package/dist/cli/{chunk-6G3CUUFG.js → chunk-X53B3JIX.js} +3 -3
- package/dist/cli/{chunk-6G3CUUFG.js.map → chunk-X53B3JIX.js.map} +1 -1
- package/dist/cli/chunk-XJXDHAES.js +0 -0
- package/dist/cli/{chunk-6AK4EY3D.js → chunk-XSU4QVFW.js} +1 -81
- package/dist/cli/chunk-XSU4QVFW.js.map +1 -0
- package/dist/cli/chunk-XXC2BYTV.js +0 -0
- package/dist/cli/{chunk-P7EKE5ZQ.js → chunk-Z4S7EYXG.js} +4482 -1310
- package/dist/cli/chunk-Z4S7EYXG.js.map +1 -0
- package/dist/cli/chunk-ZZM6QJ4W.js +0 -0
- package/dist/cli/{chunk-YQ6NTIIE.js → chunk-ZZYBBX5N.js} +13 -5
- package/dist/cli/chunk-ZZYBBX5N.js.map +1 -0
- package/dist/cli/{code-SMKEW6CD.js → code-PSVJ3KEN.js} +48 -36
- package/dist/cli/code-PSVJ3KEN.js.map +1 -0
- package/dist/cli/{commands-FVVB5FZF.js → commands-OCU42XG4.js} +4 -4
- package/dist/cli/{commit-HE4VSPZ7.js → commit-XCQIQCYG.js} +3 -3
- package/dist/cli/{desktop-Q7NDXCON.js → desktop-KWGR4BNE.js} +210 -69
- 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-435UTPC5.js → diff-NHANTNC3.js} +9 -9
- package/dist/cli/{doctor-OT7KH75K.js → doctor-CC5CLOGG.js} +10 -10
- package/dist/cli/events-XEFAD5VX.js +0 -0
- package/dist/cli/index.js +132 -94
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-WUL2WO75.js → mcp-MPVGBBJF.js} +2 -2
- package/dist/cli/{mcp-browse-RR7R4XET.js → mcp-browse-4XOTC3FJ.js} +3 -3
- package/dist/cli/{mcp-inspect-REGLYBWT.js → mcp-inspect-CEMGKKAH.js} +14 -9
- package/dist/cli/mcp-inspect-CEMGKKAH.js.map +1 -0
- package/dist/cli/{prompt-UW6EFLVR.js → prompt-2D7ID24X.js} +4 -4
- package/dist/cli/prune-sessions-3RWUBYRS.js +0 -0
- package/dist/cli/{replay-YOURXV4C.js → replay-SR44E6RS.js} +10 -10
- package/dist/cli/{run-Q6BUXV66.js → run-MDGL27WL.js} +35 -36
- package/dist/cli/run-MDGL27WL.js.map +1 -0
- package/dist/cli/{server-XGDBRWMB.js → server-27ARQXIZ.js} +67 -24
- package/dist/cli/server-27ARQXIZ.js.map +1 -0
- package/dist/cli/{sessions-FH7QVYSY.js → sessions-CKQXCYGP.js} +18 -18
- package/dist/cli/sessions-CKQXCYGP.js.map +1 -0
- package/dist/cli/{setup-VDS6SVEP.js → setup-TPAGSVXO.js} +6 -6
- package/dist/cli/{stats-MQVI2XQH.js → stats-DPUBZNVX.js} +6 -4
- package/dist/cli/update-6ITLPRDV.js +0 -0
- package/dist/cli/{version-DAHGZY5N.js → version-2X3BHVVK.js} +15 -15
- package/dist/index.d.ts +181 -53
- package/dist/index.js +1322 -533
- 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-DAGPCVFZ.js.map +0 -1
- package/dist/cli/chat-7ES4IBNH.js +0 -50
- package/dist/cli/chunk-2K65GZBT.js.map +0 -1
- package/dist/cli/chunk-2KDUS647.js.map +0 -1
- package/dist/cli/chunk-2R4QCDOZ.js.map +0 -1
- package/dist/cli/chunk-3BXRZFWS.js.map +0 -1
- package/dist/cli/chunk-3Z6IBU3D.js.map +0 -1
- package/dist/cli/chunk-45U62RI3.js.map +0 -1
- package/dist/cli/chunk-6AK4EY3D.js.map +0 -1
- package/dist/cli/chunk-6PZ3CXBP.js.map +0 -1
- package/dist/cli/chunk-H6PS7IUE.js.map +0 -1
- package/dist/cli/chunk-O52OLQL3.js.map +0 -1
- package/dist/cli/chunk-OSZC7C6F.js.map +0 -1
- package/dist/cli/chunk-P7EKE5ZQ.js.map +0 -1
- package/dist/cli/chunk-PQXPXJBJ.js.map +0 -1
- package/dist/cli/chunk-PV55UMTO.js +0 -200
- package/dist/cli/chunk-PV55UMTO.js.map +0 -1
- package/dist/cli/chunk-RE4RAVFF.js.map +0 -1
- package/dist/cli/chunk-XPDVG52A.js.map +0 -1
- package/dist/cli/chunk-YFGF5NKA.js.map +0 -1
- package/dist/cli/chunk-YQ6NTIIE.js.map +0 -1
- package/dist/cli/code-SMKEW6CD.js.map +0 -1
- package/dist/cli/desktop-Q7NDXCON.js.map +0 -1
- package/dist/cli/devtools-YECO25QO.js.map +0 -1
- package/dist/cli/doctor-OT7KH75K.js.map +0 -1
- package/dist/cli/mcp-inspect-REGLYBWT.js.map +0 -1
- package/dist/cli/prompt-UW6EFLVR.js.map +0 -1
- package/dist/cli/run-Q6BUXV66.js.map +0 -1
- package/dist/cli/server-XGDBRWMB.js.map +0 -1
- package/dist/cli/sessions-FH7QVYSY.js.map +0 -1
- package/dist/cli/stats-MQVI2XQH.js.map +0 -1
- /package/dist/cli/{.-3G6VX5S7.js.map → chat-TH7VNNCJ.js.map} +0 -0
- /package/dist/cli/{chunk-XCGGEJTI.js.map → chunk-4CTDEJUF.js.map} +0 -0
- /package/dist/cli/{chunk-7O5ALB4C.js.map → chunk-7CIGMZT3.js.map} +0 -0
- /package/dist/cli/{chunk-TJX6BFZZ.js.map → chunk-AB2RED3C.js.map} +0 -0
- /package/dist/cli/{chunk-FHOGSSCH.js.map → chunk-C53JQES5.js.map} +0 -0
- /package/dist/cli/{chunk-VK5HG73G.js.map → chunk-KM465GST.js.map} +0 -0
- /package/dist/cli/{chunk-DOYHN4KB.js.map → chunk-LIR2HBQH.js.map} +0 -0
- /package/dist/cli/{chunk-YYQAUTTN.js.map → chunk-MJ6W5UN3.js.map} +0 -0
- /package/dist/cli/{chunk-HFEAY5DT.js.map → chunk-R3CTO2HM.js.map} +0 -0
- /package/dist/cli/{chunk-32TIKD5U.js.map → chunk-TXJMRPIL.js.map} +0 -0
- /package/dist/cli/{chunk-F3PXYSNN.js.map → chunk-WK3UFQY3.js.map} +0 -0
- /package/dist/cli/{commands-FVVB5FZF.js.map → commands-OCU42XG4.js.map} +0 -0
- /package/dist/cli/{commit-HE4VSPZ7.js.map → commit-XCQIQCYG.js.map} +0 -0
- /package/dist/cli/{diff-435UTPC5.js.map → diff-NHANTNC3.js.map} +0 -0
- /package/dist/cli/{.-6YRPB2C7.js.map → doctor-CC5CLOGG.js.map} +0 -0
- /package/dist/cli/{mcp-WUL2WO75.js.map → mcp-MPVGBBJF.js.map} +0 -0
- /package/dist/cli/{mcp-browse-RR7R4XET.js.map → mcp-browse-4XOTC3FJ.js.map} +0 -0
- /package/dist/cli/{.-EYSVINK3.js.map → prompt-2D7ID24X.js.map} +0 -0
- /package/dist/cli/{replay-YOURXV4C.js.map → replay-SR44E6RS.js.map} +0 -0
- /package/dist/cli/{setup-VDS6SVEP.js.map → setup-TPAGSVXO.js.map} +0 -0
- /package/dist/cli/{chat-7ES4IBNH.js.map → stats-DPUBZNVX.js.map} +0 -0
- /package/dist/cli/{version-DAHGZY5N.js.map → version-2X3BHVVK.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",
|
|
@@ -1183,8 +1317,8 @@ var EN = {
|
|
|
1183
1317
|
argsHint: "[list|show <name>|forget <name>|clear <scope> confirm]"
|
|
1184
1318
|
},
|
|
1185
1319
|
skill: {
|
|
1186
|
-
description: "list / run user skills (
|
|
1187
|
-
argsHint: "[list|show <name>|<name> [args]]"
|
|
1320
|
+
description: "list / run user skills (project + custom + global + builtin)",
|
|
1321
|
+
argsHint: "[list|paths|show <name>|<name> [args]]"
|
|
1188
1322
|
},
|
|
1189
1323
|
hooks: {
|
|
1190
1324
|
description: "list active hooks (settings.json under .reasonix/) \xB7 reload re-reads from disk",
|
|
@@ -1226,6 +1360,10 @@ var EN = {
|
|
|
1226
1360
|
argsHint: "[N]"
|
|
1227
1361
|
},
|
|
1228
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
|
+
},
|
|
1229
1367
|
setup: { description: "reminds you to exit and run `reasonix setup`" },
|
|
1230
1368
|
semantic: {
|
|
1231
1369
|
description: "show semantic_search status \u2014 built? Ollama installed? how to enable"
|
|
@@ -1289,8 +1427,8 @@ var EN = {
|
|
|
1289
1427
|
argsHint: "<question>"
|
|
1290
1428
|
},
|
|
1291
1429
|
"search-engine": {
|
|
1292
|
-
description: "switch web search backend \u2014 mojeek (default, no deps)
|
|
1293
|
-
argsHint: "<mojeek|searxng> [<endpoint>]"
|
|
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>]"
|
|
1294
1432
|
}
|
|
1295
1433
|
},
|
|
1296
1434
|
wizard: {
|
|
@@ -1484,12 +1622,12 @@ var EN = {
|
|
|
1484
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.",
|
|
1485
1623
|
budget80Pct: "\u25B2 budget 80% used \u2014 ${spent} of ${cap}. Next turn or two likely trips the cap.",
|
|
1486
1624
|
proArmed: "\u21E7 /pro armed \u2014 this turn runs on deepseek-v4-pro (one-shot \xB7 disarms after turn)",
|
|
1487
|
-
abortedAtIter: "aborted at iter {iter}
|
|
1625
|
+
abortedAtIter: "aborted at iter {iter} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)",
|
|
1488
1626
|
toolUploadStatus: "tool result uploaded \xB7 model thinking before next response\u2026",
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
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.",
|
|
1493
1631
|
flashEscalation: "\u21E7 flash requested escalation \u2014 retrying this turn on {model}{reasonSuffix}",
|
|
1494
1632
|
harvestStatus: "extracting plan state from reasoning\u2026",
|
|
1495
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.",
|
|
@@ -1518,11 +1656,9 @@ var EN = {
|
|
|
1518
1656
|
reasonAborted: "[aborted by user (Esc) \u2014 summarizing what I found so far]",
|
|
1519
1657
|
reasonContextGuard: "[context budget running low \u2014 summarizing before the next call would overflow]",
|
|
1520
1658
|
reasonStuck: "[stuck on a repeated tool call \u2014 explaining what was tried and what's blocking progress]",
|
|
1521
|
-
reasonBudget: "[tool-call budget ({iterCap}) reached \u2014 forcing summary from what I found]",
|
|
1522
1659
|
labelAborted: "aborted by user",
|
|
1523
1660
|
labelContextGuard: "context-guard triggered (prompt > 80% of window)",
|
|
1524
|
-
labelStuck: "stuck (repeated tool call suppressed by storm-breaker)"
|
|
1525
|
-
labelBudget: "tool-call budget ({iterCap}) reached"
|
|
1661
|
+
labelStuck: "stuck (repeated tool call suppressed by storm-breaker)"
|
|
1526
1662
|
},
|
|
1527
1663
|
handlers: {
|
|
1528
1664
|
basic: {
|
|
@@ -1832,11 +1968,13 @@ var EN = {
|
|
|
1832
1968
|
usageMojeek: " /search-engine mojeek use Mojeek (default, no external deps)",
|
|
1833
1969
|
usageSearxng: " /search-engine searxng use SearXNG at default endpoint",
|
|
1834
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)",
|
|
1835
1972
|
alias: "Alias: /se",
|
|
1836
1973
|
searxngInfo: "SearXNG is a self-hosted metasearch engine (https://github.com/searxng/searxng).",
|
|
1837
1974
|
searxngInstall: "Install it with: docker run -d -p 8080:8080 searxng/searxng",
|
|
1838
1975
|
switched: 'Switched web search engine to "{engine}".{note}',
|
|
1839
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).",
|
|
1840
1978
|
confirmed: '\u2713 Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
|
|
1841
1979
|
confirmedDetail: " ({endpoint})"
|
|
1842
1980
|
},
|
|
@@ -1856,7 +1994,17 @@ var EN = {
|
|
|
1856
1994
|
runInfo: "\u25B8 running skill: {name}{args}",
|
|
1857
1995
|
newUsage: "usage: /skill new <name> [--global]",
|
|
1858
1996
|
newCreated: "\u25B8 created skill: {name}\n {path}\n edit it, then `/skill {name}` to invoke",
|
|
1859
|
-
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."
|
|
1860
2008
|
}
|
|
1861
2009
|
},
|
|
1862
2010
|
statusBar: {
|
|
@@ -1903,7 +2051,8 @@ var EN = {
|
|
|
1903
2051
|
editorNoRawMode: "external editor unavailable \u2014 stdin doesn't support raw-mode toggling on this terminal",
|
|
1904
2052
|
editorFailed: "external editor:",
|
|
1905
2053
|
editorMissing: "no $EDITOR / $VISUAL / $GIT_EDITOR set \u2014 export one (e.g. `export EDITOR=nano`) and retry",
|
|
1906
|
-
editorExited: "editor exited with code {code}"
|
|
2054
|
+
editorExited: "editor exited with code {code}",
|
|
2055
|
+
typeaheadStaged: "\u25B8 {count} line(s) staged \xB7 esc recall"
|
|
1907
2056
|
},
|
|
1908
2057
|
pathConfirm: {
|
|
1909
2058
|
title: "Outside-sandbox path",
|
|
@@ -2073,6 +2222,12 @@ var EN = {
|
|
|
2073
2222
|
endpointMustBeHttp: "web_search: SearXNG endpoint must be http(s), got {protocol} \u2014 try: set a valid URL with /search-endpoint http://host:port",
|
|
2074
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",
|
|
2075
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",
|
|
2076
2231
|
fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
|
|
2077
2232
|
fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
|
|
2078
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",
|
|
@@ -2333,6 +2488,16 @@ var zhCN = {
|
|
|
2333
2488
|
update: "\u68C0\u67E5\u8F83\u65B0\u7248\u672C\u7684 Reasonix \u5E76\u5B89\u88C5\u3002",
|
|
2334
2489
|
index: "\u6784\u5EFA\uFF08\u6216\u589E\u91CF\u5237\u65B0\uFF09\u672C\u5730\u8BED\u4E49\u641C\u7D22\u7D22\u5F15\u3002"
|
|
2335
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
|
+
},
|
|
2336
2501
|
ui: {
|
|
2337
2502
|
welcome: "\u968F\u65F6\u8FD0\u884C `reasonix` \u5F00\u59CB\u804A\u5929 \u2014 \u60A8\u7684\u8BBE\u7F6E\u5C06\u88AB\u8BB0\u4F4F\u3002",
|
|
2338
2503
|
taglineChat: "DeepSeek \u539F\u751F\u667A\u80FD\u4F53",
|
|
@@ -2561,8 +2726,8 @@ var zhCN = {
|
|
|
2561
2726
|
argsHint: "[list|show <name>|forget <name>|clear <scope> confirm]"
|
|
2562
2727
|
},
|
|
2563
2728
|
skill: {
|
|
2564
|
-
description: "\u5217\u51FA / \u8FD0\u884C\u7528\u6237\u6280\u80FD\uFF08
|
|
2565
|
-
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]]"
|
|
2566
2731
|
},
|
|
2567
2732
|
hooks: {
|
|
2568
2733
|
description: "\u5217\u51FA\u6D3B\u8DC3\u7684 hooks\uFF08.reasonix/ \u4E0B\u7684 settings.json\uFF09\xB7 reload \u4ECE\u78C1\u76D8\u91CD\u65B0\u8BFB\u53D6",
|
|
@@ -2606,6 +2771,10 @@ var zhCN = {
|
|
|
2606
2771
|
argsHint: "[N]"
|
|
2607
2772
|
},
|
|
2608
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
|
+
},
|
|
2609
2778
|
setup: { description: "\u63D0\u9192\u60A8\u9000\u51FA\u5E76\u8FD0\u884C `reasonix setup`" },
|
|
2610
2779
|
semantic: {
|
|
2611
2780
|
description: "\u663E\u793A semantic_search \u72B6\u6001 \u2014 \u5DF2\u6784\u5EFA\uFF1FOllama \u5DF2\u5B89\u88C5\uFF1F\u5982\u4F55\u542F\u7528"
|
|
@@ -2671,8 +2840,8 @@ var zhCN = {
|
|
|
2671
2840
|
argsHint: "<question>"
|
|
2672
2841
|
},
|
|
2673
2842
|
"search-engine": {
|
|
2674
|
-
description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4F9D\u8D56\uFF09\
|
|
2675
|
-
argsHint: "<mojeek|searxng> [<endpoint>]"
|
|
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>]"
|
|
2676
2845
|
}
|
|
2677
2846
|
},
|
|
2678
2847
|
wizard: {
|
|
@@ -2866,12 +3035,12 @@ var zhCN = {
|
|
|
2866
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",
|
|
2867
3036
|
budget80Pct: "\u25B2 \u9884\u7B97\u5DF2\u7528 80% \u2014 ${spent} / ${cap}\u3002\u4E0B\u4E00\u4E24\u8F6E\u53EF\u80FD\u5C31\u89E6\u9876\u3002",
|
|
2868
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",
|
|
2869
|
-
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",
|
|
2870
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",
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
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",
|
|
2875
3044
|
flashEscalation: "\u21E7 flash \u8BF7\u6C42\u5347\u7EA7 \u2014 \u672C\u8F6E\u6539\u7528 {model}{reasonSuffix}",
|
|
2876
3045
|
harvestStatus: "\u6B63\u5728\u4ECE\u63A8\u7406\u8FC7\u7A0B\u63D0\u53D6\u8BA1\u5212\u72B6\u6001\u2026",
|
|
2877
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",
|
|
@@ -2900,11 +3069,9 @@ var zhCN = {
|
|
|
2900
3069
|
reasonAborted: "[\u7528\u6237\u5DF2\u4E2D\u65AD\uFF08Esc\uFF09 \u2014 \u6B63\u5728\u603B\u7ED3\u5230\u76EE\u524D\u4E3A\u6B62\u7684\u53D1\u73B0]",
|
|
2901
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]",
|
|
2902
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]",
|
|
2903
|
-
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]",
|
|
2904
3072
|
labelAborted: "\u7528\u6237\u4E2D\u65AD",
|
|
2905
3073
|
labelContextGuard: "\u89E6\u53D1\u4E0A\u4E0B\u6587\u4FDD\u62A4\uFF08prompt > 80% \u7A97\u53E3\uFF09",
|
|
2906
|
-
labelStuck: "\u5361\u6B7B\uFF08\u91CD\u590D\u5DE5\u5177\u8C03\u7528\u88AB\u53CD\u98CE\u66B4\u673A\u5236\u6291\u5236\uFF09"
|
|
2907
|
-
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"
|
|
2908
3075
|
},
|
|
2909
3076
|
handlers: {
|
|
2910
3077
|
basic: {
|
|
@@ -3214,11 +3381,13 @@ var zhCN = {
|
|
|
3214
3381
|
usageMojeek: " /search-engine mojeek \u4F7F\u7528 Mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u5916\u90E8\u4F9D\u8D56\uFF09",
|
|
3215
3382
|
usageSearxng: " /search-engine searxng \u4F7F\u7528 SearXNG \u9ED8\u8BA4\u7AEF\u70B9",
|
|
3216
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",
|
|
3217
3385
|
alias: "\u522B\u540D\uFF1A/se",
|
|
3218
3386
|
searxngInfo: "SearXNG \u662F\u4E00\u4E2A\u81EA\u6258\u7BA1\u7684\u5143\u641C\u7D22\u5F15\u64CE\uFF08https://github.com/searxng/searxng\uFF09\u3002",
|
|
3219
3387
|
searxngInstall: "\u5B89\u88C5\u547D\u4EE4\uFF1A docker run -d -p 8080:8080 searxng/searxng",
|
|
3220
3388
|
switched: '\u5DF2\u5207\u6362\u7F51\u9875\u641C\u7D22\u5F15\u64CE\u4E3A "{engine}"\u3002{note}',
|
|
3221
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",
|
|
3222
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',
|
|
3223
3392
|
confirmedDetail: "\uFF08{endpoint}\uFF09"
|
|
3224
3393
|
},
|
|
@@ -3238,7 +3407,17 @@ var zhCN = {
|
|
|
3238
3407
|
runInfo: "\u25B8 \u6B63\u5728\u8FD0\u884C\u6280\u80FD\uFF1A{name}{args}",
|
|
3239
3408
|
newUsage: "\u7528\u6CD5\uFF1A/skill new <name> [--global]",
|
|
3240
3409
|
newCreated: "\u25B8 \u5DF2\u521B\u5EFA\u6280\u80FD\uFF1A{name}\n {path}\n \u7F16\u8F91\u540E\u7528 `/skill {name}` \u8C03\u7528",
|
|
3241
|
-
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"
|
|
3242
3421
|
}
|
|
3243
3422
|
},
|
|
3244
3423
|
statusBar: {
|
|
@@ -3285,7 +3464,8 @@ var zhCN = {
|
|
|
3285
3464
|
editorNoRawMode: "\u5916\u90E8\u7F16\u8F91\u5668\u4E0D\u53EF\u7528 \u2014 \u5F53\u524D\u7EC8\u7AEF\u4E0D\u652F\u6301 raw-mode \u5207\u6362",
|
|
3286
3465
|
editorFailed: "\u5916\u90E8\u7F16\u8F91\u5668\uFF1A",
|
|
3287
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",
|
|
3288
|
-
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"
|
|
3289
3469
|
},
|
|
3290
3470
|
pathConfirm: {
|
|
3291
3471
|
title: "\u6C99\u7BB1\u5916\u8DEF\u5F84",
|
|
@@ -3455,6 +3635,12 @@ var zhCN = {
|
|
|
3455
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",
|
|
3456
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",
|
|
3457
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",
|
|
3458
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",
|
|
3459
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",
|
|
3460
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",
|
|
@@ -3786,7 +3972,7 @@ function matchesTool(hook, toolName) {
|
|
|
3786
3972
|
}
|
|
3787
3973
|
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
3788
3974
|
function defaultSpawner(input) {
|
|
3789
|
-
return new Promise((
|
|
3975
|
+
return new Promise((resolve13) => {
|
|
3790
3976
|
const child = spawn(input.command, {
|
|
3791
3977
|
cwd: input.cwd,
|
|
3792
3978
|
shell: true,
|
|
@@ -3831,7 +4017,7 @@ function defaultSpawner(input) {
|
|
|
3831
4017
|
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
3832
4018
|
child.once("error", (err) => {
|
|
3833
4019
|
clearTimeout(timer);
|
|
3834
|
-
|
|
4020
|
+
resolve13({
|
|
3835
4021
|
exitCode: null,
|
|
3836
4022
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
3837
4023
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
@@ -3842,7 +4028,7 @@ function defaultSpawner(input) {
|
|
|
3842
4028
|
});
|
|
3843
4029
|
child.once("close", (code) => {
|
|
3844
4030
|
clearTimeout(timer);
|
|
3845
|
-
|
|
4031
|
+
resolve13({
|
|
3846
4032
|
exitCode: code,
|
|
3847
4033
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
3848
4034
|
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
@@ -4078,22 +4264,211 @@ function encode(text) {
|
|
|
4078
4264
|
function countTokens(text) {
|
|
4079
4265
|
return encode(text).length;
|
|
4080
4266
|
}
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
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 });
|
|
4086
4401
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
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;
|
|
4089
4416
|
}
|
|
4090
4417
|
}
|
|
4091
|
-
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;
|
|
4092
4430
|
}
|
|
4093
|
-
function
|
|
4094
|
-
|
|
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);
|
|
4095
4470
|
if (toolSpecs && toolSpecs.length > 0) {
|
|
4096
|
-
total +=
|
|
4471
|
+
total += countTokensBounded(renderTools(toolSpecs));
|
|
4097
4472
|
}
|
|
4098
4473
|
return total;
|
|
4099
4474
|
}
|
|
@@ -4446,12 +4821,12 @@ async function waitForReady(ready, timeoutMs, serverName, signal) {
|
|
|
4446
4821
|
let timer;
|
|
4447
4822
|
let onAbort;
|
|
4448
4823
|
try {
|
|
4449
|
-
await new Promise((
|
|
4824
|
+
await new Promise((resolve13, reject) => {
|
|
4450
4825
|
ready.then(
|
|
4451
4826
|
() => {
|
|
4452
4827
|
if (settled) return;
|
|
4453
4828
|
settled = true;
|
|
4454
|
-
|
|
4829
|
+
resolve13();
|
|
4455
4830
|
},
|
|
4456
4831
|
(err) => {
|
|
4457
4832
|
if (settled) return;
|
|
@@ -4600,6 +4975,40 @@ function blockToString(block) {
|
|
|
4600
4975
|
return `[unknown block: ${JSON.stringify(block)}]`;
|
|
4601
4976
|
}
|
|
4602
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
|
+
|
|
4603
5012
|
// src/memory/session.ts
|
|
4604
5013
|
import { execFileSync } from "child_process";
|
|
4605
5014
|
import {
|
|
@@ -4769,11 +5178,11 @@ function countLines(path2) {
|
|
|
4769
5178
|
|
|
4770
5179
|
// src/telemetry/stats.ts
|
|
4771
5180
|
var DEEPSEEK_PRICING = {
|
|
4772
|
-
"deepseek-v4-flash": { inputCacheHit:
|
|
4773
|
-
"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 },
|
|
4774
5183
|
// Compat aliases — priced as v4-flash per the deprecation notice.
|
|
4775
|
-
"deepseek-chat": { inputCacheHit:
|
|
4776
|
-
"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 }
|
|
4777
5186
|
};
|
|
4778
5187
|
var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
|
|
4779
5188
|
var DEEPSEEK_CONTEXT_TOKENS = {
|
|
@@ -4835,6 +5244,14 @@ var SessionStats = class {
|
|
|
4835
5244
|
this._carryoverLastPromptTokens = opts.lastPromptTokens;
|
|
4836
5245
|
}
|
|
4837
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
|
+
}
|
|
4838
5255
|
record(turn, model, usage) {
|
|
4839
5256
|
const cost = costUsd(model, usage);
|
|
4840
5257
|
const stats = {
|
|
@@ -4901,6 +5318,8 @@ var HISTORY_FOLD_AGGRESSIVE_TAIL_FRACTION = 0.1;
|
|
|
4901
5318
|
var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
|
|
4902
5319
|
var FORCE_SUMMARY_THRESHOLD = 0.8;
|
|
4903
5320
|
var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
|
|
5321
|
+
var PREFLIGHT_MECHANICAL_TARGET_FRACTION = 0.7;
|
|
5322
|
+
var HISTORY_FOLD_SUMMARY_TIMEOUT_MS = 15e3;
|
|
4904
5323
|
var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
|
|
4905
5324
|
var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
|
|
4906
5325
|
var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
|
|
@@ -4955,7 +5374,7 @@ var ContextManager = class {
|
|
|
4955
5374
|
/** Local-side preflight before sending a request — catches oversized payloads early. */
|
|
4956
5375
|
decidePreflight(messages, toolSpecs, model) {
|
|
4957
5376
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
4958
|
-
const estimate = estimateRequestTokens(messages, toolSpecs ?? null);
|
|
5377
|
+
const estimate = estimateRequestTokens(messages, toolSpecs ?? null, true);
|
|
4959
5378
|
return {
|
|
4960
5379
|
needsAction: estimate / ctxMax > PREFLIGHT_EMERGENCY_THRESHOLD,
|
|
4961
5380
|
estimateTokens: estimate,
|
|
@@ -4974,7 +5393,7 @@ var ContextManager = class {
|
|
|
4974
5393
|
summaryChars: 0
|
|
4975
5394
|
};
|
|
4976
5395
|
if (all.length === 0) return noop;
|
|
4977
|
-
const tokenCounts = all.map((m) =>
|
|
5396
|
+
const tokenCounts = all.map((m) => countTokensBounded(m.content ?? ""));
|
|
4978
5397
|
const totalTokens = tokenCounts.reduce((a, b) => a + b, 0);
|
|
4979
5398
|
let cumTokens = 0;
|
|
4980
5399
|
let boundary = all.length;
|
|
@@ -4990,16 +5409,18 @@ var ContextManager = class {
|
|
|
4990
5409
|
if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
|
|
4991
5410
|
const { stubbedHead, pinnedBodies } = extractPinnedSkills(head);
|
|
4992
5411
|
const summary = await this.summarizeForFold(stubbedHead);
|
|
4993
|
-
if (!summary) return noop;
|
|
5412
|
+
if (!summary.content) return noop;
|
|
4994
5413
|
const memoTail = pinnedBodies.length > 0 ? `
|
|
4995
5414
|
|
|
4996
5415
|
${SKILL_PIN_MEMO_HEADER}
|
|
4997
5416
|
|
|
4998
5417
|
${pinnedBodies.join("\n\n")}` : "";
|
|
4999
|
-
const summaryMsg =
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5418
|
+
const summaryMsg = buildAssistantMessage(
|
|
5419
|
+
HISTORY_FOLD_MARKER + summary.content + memoTail,
|
|
5420
|
+
[],
|
|
5421
|
+
model,
|
|
5422
|
+
summary.reasoningContent
|
|
5423
|
+
);
|
|
5003
5424
|
const replacement = [summaryMsg, ...tail];
|
|
5004
5425
|
this.deps.log.compactInPlace(replacement);
|
|
5005
5426
|
this.persistRewrite(replacement);
|
|
@@ -5007,7 +5428,51 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
5007
5428
|
folded: true,
|
|
5008
5429
|
beforeMessages: all.length,
|
|
5009
5430
|
afterMessages: replacement.length,
|
|
5010
|
-
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
|
|
5011
5476
|
};
|
|
5012
5477
|
}
|
|
5013
5478
|
/** Drop a trailing in-flight assistant-with-tool_calls before a forced summary. Tail-only mutation; prefix cache safe. */
|
|
@@ -5033,18 +5498,51 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
5033
5498
|
content: "Summarize the conversation above as plain prose. This summary replaces the original turns to free context \u2014 make it self-contained."
|
|
5034
5499
|
}
|
|
5035
5500
|
];
|
|
5501
|
+
const turnSignal = this.deps.getAbortSignal();
|
|
5502
|
+
const foldCtrl = new AbortController();
|
|
5503
|
+
let cleanupAbort = () => {
|
|
5504
|
+
};
|
|
5505
|
+
let timeout;
|
|
5036
5506
|
try {
|
|
5037
|
-
const
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
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
|
+
}
|
|
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);
|
|
5043
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
|
+
]);
|
|
5044
5536
|
this.deps.stats.record(this.deps.getCurrentTurn(), summaryModel, resp.usage ?? new Usage());
|
|
5045
|
-
return
|
|
5537
|
+
return {
|
|
5538
|
+
content: stripHallucinatedToolMarkup((resp.content ?? "").trim()),
|
|
5539
|
+
reasoningContent: resp.reasoningContent ?? ""
|
|
5540
|
+
};
|
|
5046
5541
|
} catch {
|
|
5047
|
-
return "";
|
|
5542
|
+
return { content: "", reasoningContent: "" };
|
|
5543
|
+
} finally {
|
|
5544
|
+
if (timeout) clearTimeout(timeout);
|
|
5545
|
+
cleanupAbort();
|
|
5048
5546
|
}
|
|
5049
5547
|
}
|
|
5050
5548
|
persistRewrite(messages) {
|
|
@@ -5136,17 +5634,15 @@ function formatDeepSeek5xx(status, probe) {
|
|
|
5136
5634
|
const action = probe?.reachable === false ? t("errors.deepseek5xxActionNetwork") : t("errors.deepseek5xxActionRetry");
|
|
5137
5635
|
return `${head}${probeNote}${action}`;
|
|
5138
5636
|
}
|
|
5139
|
-
function reasonPrefixFor(reason
|
|
5637
|
+
function reasonPrefixFor(reason) {
|
|
5140
5638
|
if (reason === "aborted") return t("errors.reasonAborted");
|
|
5141
5639
|
if (reason === "context-guard") return t("errors.reasonContextGuard");
|
|
5142
|
-
|
|
5143
|
-
return t("errors.reasonBudget", { iterCap });
|
|
5640
|
+
return t("errors.reasonStuck");
|
|
5144
5641
|
}
|
|
5145
|
-
function errorLabelFor(reason
|
|
5642
|
+
function errorLabelFor(reason) {
|
|
5146
5643
|
if (reason === "aborted") return t("errors.labelAborted");
|
|
5147
5644
|
if (reason === "context-guard") return t("errors.labelContextGuard");
|
|
5148
|
-
|
|
5149
|
-
return t("errors.labelBudget", { iterCap });
|
|
5645
|
+
return t("errors.labelStuck");
|
|
5150
5646
|
}
|
|
5151
5647
|
function extractDeepSeekErrorMessage(body) {
|
|
5152
5648
|
const trimmed = body.trim();
|
|
@@ -5188,50 +5684,14 @@ function looksLikePartialEscalationMarker(buf) {
|
|
|
5188
5684
|
return true;
|
|
5189
5685
|
}
|
|
5190
5686
|
|
|
5191
|
-
// src/loop/thinking.ts
|
|
5192
|
-
function isThinkingModeModel(model) {
|
|
5193
|
-
if (model.includes("reasoner")) return true;
|
|
5194
|
-
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
|
|
5195
|
-
return false;
|
|
5196
|
-
}
|
|
5197
|
-
function thinkingModeForModel(model) {
|
|
5198
|
-
if (model === "deepseek-chat") return "disabled";
|
|
5199
|
-
if (model.includes("reasoner")) return "enabled";
|
|
5200
|
-
if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return "enabled";
|
|
5201
|
-
return void 0;
|
|
5202
|
-
}
|
|
5203
|
-
function stripHallucinatedToolMarkup(s) {
|
|
5204
|
-
let out = s;
|
|
5205
|
-
out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
|
|
5206
|
-
out = out.replace(/<\|DSML\|function_calls>[\s\S]*?<\/?\|DSML\|function_calls>/g, "");
|
|
5207
|
-
out = out.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "");
|
|
5208
|
-
out = out.replace(/<|DSML|[\s\S]*$/g, "");
|
|
5209
|
-
return out.trim();
|
|
5210
|
-
}
|
|
5211
|
-
|
|
5212
|
-
// src/loop/messages.ts
|
|
5213
|
-
function buildAssistantMessage(content, toolCalls, producingModel, reasoningContent) {
|
|
5214
|
-
const msg = { role: "assistant", content };
|
|
5215
|
-
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
5216
|
-
if (isThinkingModeModel(producingModel) || reasoningContent && reasoningContent.length > 0) {
|
|
5217
|
-
msg.reasoning_content = reasoningContent ?? "";
|
|
5218
|
-
}
|
|
5219
|
-
return msg;
|
|
5220
|
-
}
|
|
5221
|
-
function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
5222
|
-
return buildAssistantMessage(content, [], fallbackModel, "");
|
|
5223
|
-
}
|
|
5224
|
-
|
|
5225
5687
|
// src/loop/force-summary.ts
|
|
5226
|
-
|
|
5227
|
-
var PAUSE_SUMMARY_EFFORT = "high";
|
|
5228
|
-
async function* forceSummaryAfterIterLimit(ctx, opts = { reason: "budget" }) {
|
|
5688
|
+
async function* forceSummaryAfterIterLimit(ctx, opts) {
|
|
5229
5689
|
try {
|
|
5230
5690
|
yield { turn: ctx.turn, role: "status", content: t("summary.status") };
|
|
5231
5691
|
const messages = ctx.buildMessages();
|
|
5232
5692
|
messages.push({
|
|
5233
5693
|
role: "user",
|
|
5234
|
-
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."
|
|
5235
5695
|
});
|
|
5236
5696
|
const summaryModel = "deepseek-v4-flash";
|
|
5237
5697
|
const summaryEffort = "high";
|
|
@@ -5245,7 +5705,7 @@ async function* forceSummaryAfterIterLimit(ctx, opts = { reason: "budget" }) {
|
|
|
5245
5705
|
const rawContent = resp.content?.trim() ?? "";
|
|
5246
5706
|
const cleaned = stripHallucinatedToolMarkup(rawContent);
|
|
5247
5707
|
const summary = cleaned || t("summary.hallucinatedFallback");
|
|
5248
|
-
const reasonPrefix = reasonPrefixFor(opts.reason
|
|
5708
|
+
const reasonPrefix = reasonPrefixFor(opts.reason);
|
|
5249
5709
|
const annotated = `${reasonPrefix}
|
|
5250
5710
|
|
|
5251
5711
|
${summary}`;
|
|
@@ -5260,7 +5720,7 @@ ${summary}`;
|
|
|
5260
5720
|
};
|
|
5261
5721
|
yield { turn: ctx.turn, role: "done", content: summary };
|
|
5262
5722
|
} catch (err) {
|
|
5263
|
-
const label = errorLabelFor(opts.reason
|
|
5723
|
+
const label = errorLabelFor(opts.reason);
|
|
5264
5724
|
yield {
|
|
5265
5725
|
turn: ctx.turn,
|
|
5266
5726
|
role: "error",
|
|
@@ -5270,28 +5730,6 @@ ${summary}`;
|
|
|
5270
5730
|
yield { turn: ctx.turn, role: "done", content: "" };
|
|
5271
5731
|
}
|
|
5272
5732
|
}
|
|
5273
|
-
async function summarizePartialProgress(ctx) {
|
|
5274
|
-
try {
|
|
5275
|
-
const messages = ctx.buildMessages();
|
|
5276
|
-
messages.push({
|
|
5277
|
-
role: "user",
|
|
5278
|
-
content: "You're being paused at a checkpoint, not stopped. In 3-6 sentences of plain prose, tell the parent agent: (1) what you accomplished so far, (2) what's still left, (3) any blockers or open questions. Be concrete \u2014 mention specific files / functions / tool results \u2014 so the parent can decide whether to resume you or take over. 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."
|
|
5279
|
-
});
|
|
5280
|
-
const resp = await ctx.client.chat({
|
|
5281
|
-
model: PAUSE_SUMMARY_MODEL,
|
|
5282
|
-
messages,
|
|
5283
|
-
signal: ctx.signal,
|
|
5284
|
-
thinking: thinkingModeForModel(PAUSE_SUMMARY_MODEL),
|
|
5285
|
-
reasoningEffort: PAUSE_SUMMARY_EFFORT
|
|
5286
|
-
});
|
|
5287
|
-
const cleaned = stripHallucinatedToolMarkup(resp.content?.trim() ?? "");
|
|
5288
|
-
if (!cleaned) return null;
|
|
5289
|
-
const stats = ctx.recordStats(PAUSE_SUMMARY_MODEL, resp.usage ?? new Usage());
|
|
5290
|
-
return { summary: cleaned, stats };
|
|
5291
|
-
} catch {
|
|
5292
|
-
return null;
|
|
5293
|
-
}
|
|
5294
|
-
}
|
|
5295
5733
|
|
|
5296
5734
|
// src/loop/shrink.ts
|
|
5297
5735
|
function looksLikeCompleteJson(s) {
|
|
@@ -5324,7 +5762,7 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
|
5324
5762
|
if (msg.role !== "tool") return msg;
|
|
5325
5763
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
5326
5764
|
if (content.length <= maxTokens) return msg;
|
|
5327
|
-
const beforeTokens =
|
|
5765
|
+
const beforeTokens = countTokensBounded(content);
|
|
5328
5766
|
if (beforeTokens <= maxTokens) return msg;
|
|
5329
5767
|
const truncated = truncateForModelByTokens(content, maxTokens);
|
|
5330
5768
|
const afterTokens = countTokens(truncated);
|
|
@@ -5758,11 +6196,16 @@ var StormBreaker = class {
|
|
|
5758
6196
|
function repairTruncatedJson(input) {
|
|
5759
6197
|
const notes = [];
|
|
5760
6198
|
if (!input || !input.trim()) {
|
|
5761
|
-
return {
|
|
6199
|
+
return {
|
|
6200
|
+
repaired: "{}",
|
|
6201
|
+
changed: input !== "{}",
|
|
6202
|
+
notes: ["empty input \u2192 {}"],
|
|
6203
|
+
fallback: false
|
|
6204
|
+
};
|
|
5762
6205
|
}
|
|
5763
6206
|
try {
|
|
5764
6207
|
JSON.parse(input);
|
|
5765
|
-
return { repaired: input, changed: false, notes: [] };
|
|
6208
|
+
return { repaired: input, changed: false, notes: [], fallback: false };
|
|
5766
6209
|
} catch {
|
|
5767
6210
|
}
|
|
5768
6211
|
const stack = [];
|
|
@@ -5817,10 +6260,12 @@ function repairTruncatedJson(input) {
|
|
|
5817
6260
|
}
|
|
5818
6261
|
try {
|
|
5819
6262
|
JSON.parse(s);
|
|
5820
|
-
return { repaired: s, changed:
|
|
6263
|
+
return { repaired: s, changed: s !== input, notes, fallback: false };
|
|
5821
6264
|
} catch (err) {
|
|
6265
|
+
const preview = input.length <= 500 ? input : `${input.slice(0, 500)} \u2026[+${input.length - 500} chars]`;
|
|
5822
6266
|
notes.push(`fallback to {}: ${err.message}`);
|
|
5823
|
-
|
|
6267
|
+
notes.push(`unrecoverable truncation \u2014 original args preview: ${preview}`);
|
|
6268
|
+
return { repaired: "{}", changed: true, notes, fallback: true };
|
|
5824
6269
|
}
|
|
5825
6270
|
}
|
|
5826
6271
|
|
|
@@ -5867,9 +6312,16 @@ var ToolCallRepair = class {
|
|
|
5867
6312
|
const args = call.function?.arguments ?? "";
|
|
5868
6313
|
const r = repairTruncatedJson(args);
|
|
5869
6314
|
if (r.changed) {
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
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
|
+
}
|
|
5873
6325
|
}
|
|
5874
6326
|
}
|
|
5875
6327
|
const filtered = [];
|
|
@@ -5891,12 +6343,10 @@ function signature(call) {
|
|
|
5891
6343
|
|
|
5892
6344
|
// src/loop.ts
|
|
5893
6345
|
var ESCALATION_MODEL = "deepseek-v4-pro";
|
|
5894
|
-
var PARENT_BUDGET_WARN_THRESHOLD = 5;
|
|
5895
6346
|
var CacheFirstLoop = class {
|
|
5896
6347
|
client;
|
|
5897
6348
|
prefix;
|
|
5898
6349
|
tools;
|
|
5899
|
-
maxToolIters;
|
|
5900
6350
|
log = new AppendOnlyLog();
|
|
5901
6351
|
scratch = new VolatileScratch();
|
|
5902
6352
|
stats = new SessionStats();
|
|
@@ -5911,7 +6361,6 @@ var CacheFirstLoop = class {
|
|
|
5911
6361
|
/** One-shot 80% warning latch — cleared by setBudget so a bump re-arms at the new boundary. */
|
|
5912
6362
|
_budgetWarned = false;
|
|
5913
6363
|
sessionName;
|
|
5914
|
-
onIterBudgetExhausted;
|
|
5915
6364
|
hooks;
|
|
5916
6365
|
hookCwd;
|
|
5917
6366
|
/** PauseGate bridge — defaults to singleton, injectable for tests. */
|
|
@@ -5925,12 +6374,25 @@ var CacheFirstLoop = class {
|
|
|
5925
6374
|
_turnAbort = new AbortController();
|
|
5926
6375
|
/** Authoritative running-id set — UI cards consult this instead of trusting end-event delivery. Insert at dispatch entry, delete in finally. */
|
|
5927
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
|
+
}
|
|
5928
6391
|
_proArmedForNextTurn = false;
|
|
5929
6392
|
_escalateThisTurn = false;
|
|
5930
6393
|
_turnFailures;
|
|
5931
6394
|
_turnSelfCorrected = false;
|
|
5932
6395
|
_foldedThisTurn = false;
|
|
5933
|
-
_toolDispatchesThisStep = 0;
|
|
5934
6396
|
context;
|
|
5935
6397
|
/** Subscribe API so UI hooks can derive `running` from finally-guaranteed insertions. */
|
|
5936
6398
|
get inflight() {
|
|
@@ -5950,8 +6412,6 @@ var CacheFirstLoop = class {
|
|
|
5950
6412
|
this._turnFailures = new TurnFailureTracker(
|
|
5951
6413
|
resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
|
|
5952
6414
|
);
|
|
5953
|
-
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
5954
|
-
this.onIterBudgetExhausted = opts.onIterBudgetExhausted ?? "summarize";
|
|
5955
6415
|
this.hooks = opts.hooks ?? [];
|
|
5956
6416
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
5957
6417
|
this.confirmationGate = opts.confirmationGate ?? pauseGate;
|
|
@@ -5972,23 +6432,6 @@ var CacheFirstLoop = class {
|
|
|
5972
6432
|
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
5973
6433
|
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
5974
6434
|
});
|
|
5975
|
-
if (!this.tools.hasResultAugmenter) {
|
|
5976
|
-
this.tools.setResultAugmenter((_name, _args, result) => {
|
|
5977
|
-
this._toolDispatchesThisStep++;
|
|
5978
|
-
const remaining = this.maxToolIters - this._toolDispatchesThisStep;
|
|
5979
|
-
if (remaining <= 0) {
|
|
5980
|
-
return `${result}
|
|
5981
|
-
|
|
5982
|
-
[budget: 0 of ${this.maxToolIters} tool calls left this turn \u2014 finalize NOW; the next iter forces a summary]`;
|
|
5983
|
-
}
|
|
5984
|
-
if (remaining <= PARENT_BUDGET_WARN_THRESHOLD) {
|
|
5985
|
-
return `${result}
|
|
5986
|
-
|
|
5987
|
-
[budget: ${remaining} of ${this.maxToolIters} tool calls left this turn \u2014 wrap up soon]`;
|
|
5988
|
-
}
|
|
5989
|
-
return result;
|
|
5990
|
-
});
|
|
5991
|
-
}
|
|
5992
6435
|
this.sessionName = opts.session ?? null;
|
|
5993
6436
|
if (this.sessionName) {
|
|
5994
6437
|
const prior = loadSessionMessages(this.sessionName);
|
|
@@ -6074,6 +6517,10 @@ var CacheFirstLoop = class {
|
|
|
6074
6517
|
}
|
|
6075
6518
|
this.scratch.reset();
|
|
6076
6519
|
this._inflight.clear();
|
|
6520
|
+
this.stats.reset();
|
|
6521
|
+
this._turn = 0;
|
|
6522
|
+
this._turnFailures.reset();
|
|
6523
|
+
this._budgetWarned = false;
|
|
6077
6524
|
let systemRebuilt = false;
|
|
6078
6525
|
if (this._rebuildSystem) {
|
|
6079
6526
|
try {
|
|
@@ -6083,6 +6530,29 @@ var CacheFirstLoop = class {
|
|
|
6083
6530
|
}
|
|
6084
6531
|
return { dropped, archived, systemRebuilt };
|
|
6085
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
|
+
}
|
|
6086
6556
|
configure(opts) {
|
|
6087
6557
|
if (opts.model !== void 0) this.model = opts.model;
|
|
6088
6558
|
if (opts.stream !== void 0) {
|
|
@@ -6238,6 +6708,7 @@ ${reason}`
|
|
|
6238
6708
|
return userText;
|
|
6239
6709
|
}
|
|
6240
6710
|
async *step(userInput) {
|
|
6711
|
+
this._steerConsumed = false;
|
|
6241
6712
|
if (this.budgetUsd !== null) {
|
|
6242
6713
|
const spent = this.stats.totalCost;
|
|
6243
6714
|
if (spent >= this.budgetUsd) {
|
|
@@ -6271,7 +6742,6 @@ ${reason}`
|
|
|
6271
6742
|
this._turnSelfCorrected = false;
|
|
6272
6743
|
this._escalateThisTurn = false;
|
|
6273
6744
|
this._foldedThisTurn = false;
|
|
6274
|
-
this._toolDispatchesThisStep = 0;
|
|
6275
6745
|
let armedConsumed = false;
|
|
6276
6746
|
if (this._proArmedForNextTurn) {
|
|
6277
6747
|
this._escalateThisTurn = true;
|
|
@@ -6289,16 +6759,15 @@ ${reason}`
|
|
|
6289
6759
|
content: t("loop.proArmed")
|
|
6290
6760
|
};
|
|
6291
6761
|
}
|
|
6292
|
-
|
|
6762
|
+
this.appendAndPersist({ role: "user", content: userInput });
|
|
6763
|
+
let pendingUser = null;
|
|
6293
6764
|
const toolSpecs = this.prefix.tools();
|
|
6294
|
-
|
|
6295
|
-
let warnedForIterBudget = false;
|
|
6296
|
-
for (let iter = 0; iter < this.maxToolIters; iter++) {
|
|
6765
|
+
for (let iter = 0; ; iter++) {
|
|
6297
6766
|
if (signal.aborted) {
|
|
6298
6767
|
yield {
|
|
6299
6768
|
turn: this._turn,
|
|
6300
6769
|
role: "warning",
|
|
6301
|
-
content: t("loop.abortedAtIter", { iter
|
|
6770
|
+
content: t("loop.abortedAtIter", { iter })
|
|
6302
6771
|
};
|
|
6303
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.]";
|
|
6304
6773
|
this.appendAndPersist(buildSyntheticAssistantMessage(stoppedMsg, this.model));
|
|
@@ -6319,15 +6788,20 @@ ${reason}`
|
|
|
6319
6788
|
content: t("loop.toolUploadStatus")
|
|
6320
6789
|
};
|
|
6321
6790
|
}
|
|
6322
|
-
|
|
6323
|
-
|
|
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;
|
|
6324
6799
|
yield {
|
|
6325
6800
|
turn: this._turn,
|
|
6326
|
-
role: "
|
|
6327
|
-
content:
|
|
6801
|
+
role: "steer",
|
|
6802
|
+
content: steer
|
|
6328
6803
|
};
|
|
6329
6804
|
}
|
|
6330
|
-
let messages = this.buildMessages(pendingUser);
|
|
6331
6805
|
{
|
|
6332
6806
|
const decision2 = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
6333
6807
|
if (decision2.needsAction) {
|
|
@@ -6335,23 +6809,29 @@ ${reason}`
|
|
|
6335
6809
|
yield {
|
|
6336
6810
|
turn: this._turn,
|
|
6337
6811
|
role: "status",
|
|
6338
|
-
content: t("loop.
|
|
6812
|
+
content: t("loop.preflightTruncateStatus")
|
|
6339
6813
|
};
|
|
6340
|
-
const result =
|
|
6814
|
+
const result = this.context.mechanicalTruncate(this.model, {
|
|
6815
|
+
allowEmpty: pendingUser !== null
|
|
6816
|
+
});
|
|
6341
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;
|
|
6342
6821
|
yield {
|
|
6343
|
-
turn: this._turn,
|
|
6344
|
-
role: "warning",
|
|
6345
|
-
content: t(
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6822
|
+
turn: this._turn,
|
|
6823
|
+
role: "warning",
|
|
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
|
+
)
|
|
6353
6834
|
};
|
|
6354
|
-
messages = this.buildMessages(pendingUser);
|
|
6355
6835
|
} else {
|
|
6356
6836
|
yield {
|
|
6357
6837
|
turn: this._turn,
|
|
@@ -6508,10 +6988,6 @@ ${reason}`
|
|
|
6508
6988
|
this.modelForCurrentCall(),
|
|
6509
6989
|
usage ?? new Usage()
|
|
6510
6990
|
);
|
|
6511
|
-
if (pendingUser !== null) {
|
|
6512
|
-
this.appendAndPersist({ role: "user", content: pendingUser });
|
|
6513
|
-
pendingUser = null;
|
|
6514
|
-
}
|
|
6515
6991
|
this.scratch.reasoning = reasoningContent || null;
|
|
6516
6992
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
6517
6993
|
toolCalls,
|
|
@@ -6705,20 +7181,6 @@ ${reason}`
|
|
|
6705
7181
|
}
|
|
6706
7182
|
}
|
|
6707
7183
|
}
|
|
6708
|
-
if (this.onIterBudgetExhausted === "pause") {
|
|
6709
|
-
const partial = await summarizePartialProgress(this.summaryContext());
|
|
6710
|
-
yield {
|
|
6711
|
-
turn: this._turn,
|
|
6712
|
-
role: "paused",
|
|
6713
|
-
content: "",
|
|
6714
|
-
sessionName: this.sessionName ?? void 0,
|
|
6715
|
-
pausedAtIter: this.maxToolIters,
|
|
6716
|
-
partialSummary: partial?.summary
|
|
6717
|
-
};
|
|
6718
|
-
yield { turn: this._turn, role: "done", content: "" };
|
|
6719
|
-
return;
|
|
6720
|
-
}
|
|
6721
|
-
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "budget" });
|
|
6722
7184
|
}
|
|
6723
7185
|
summaryContext() {
|
|
6724
7186
|
return {
|
|
@@ -6727,8 +7189,7 @@ ${reason}`
|
|
|
6727
7189
|
buildMessages: () => this.buildMessages(null),
|
|
6728
7190
|
appendAndPersist: (m) => this.appendAndPersist(m),
|
|
6729
7191
|
recordStats: (model, usage) => this.stats.record(this._turn, model, usage),
|
|
6730
|
-
turn: this._turn
|
|
6731
|
-
maxToolIters: this.maxToolIters
|
|
7192
|
+
turn: this._turn
|
|
6732
7193
|
};
|
|
6733
7194
|
}
|
|
6734
7195
|
async run(userInput, onEvent) {
|
|
@@ -6763,7 +7224,7 @@ function resolveFailureThreshold(raw, fallback) {
|
|
|
6763
7224
|
// src/at-mentions.ts
|
|
6764
7225
|
import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
6765
7226
|
import { readdir, stat } from "fs/promises";
|
|
6766
|
-
import { isAbsolute, join as join5, relative, resolve } from "path";
|
|
7227
|
+
import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve2 } from "path";
|
|
6767
7228
|
|
|
6768
7229
|
// src/gitignore.ts
|
|
6769
7230
|
import { readFileSync as readFileSync5 } from "fs";
|
|
@@ -6818,7 +7279,7 @@ function listFilesSync(root, opts = {}) {
|
|
|
6818
7279
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
6819
7280
|
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
6820
7281
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
6821
|
-
const rootAbs =
|
|
7282
|
+
const rootAbs = resolve2(root);
|
|
6822
7283
|
const respectGi = opts.respectGitignore !== false;
|
|
6823
7284
|
const out = [];
|
|
6824
7285
|
const walk2 = (dirAbs, dirRel, layers) => {
|
|
@@ -6882,7 +7343,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
6882
7343
|
async function walkFilesStream(root, opts) {
|
|
6883
7344
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
6884
7345
|
const respectGi = opts.respectGitignore !== false;
|
|
6885
|
-
const rootAbs =
|
|
7346
|
+
const rootAbs = resolve2(root);
|
|
6886
7347
|
const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
|
|
6887
7348
|
let scanned = 0;
|
|
6888
7349
|
let halted = false;
|
|
@@ -6960,10 +7421,10 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
|
|
|
6960
7421
|
async function listDirectory(root, relDir, opts = {}) {
|
|
6961
7422
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
6962
7423
|
const respectGi = opts.respectGitignore !== false;
|
|
6963
|
-
const rootAbs =
|
|
6964
|
-
const dirAbs =
|
|
7424
|
+
const rootAbs = resolve2(root);
|
|
7425
|
+
const dirAbs = resolve2(rootAbs, relDir);
|
|
6965
7426
|
const rel = relative(rootAbs, dirAbs);
|
|
6966
|
-
if (rel.startsWith("..") ||
|
|
7427
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) return [];
|
|
6967
7428
|
const layers = [];
|
|
6968
7429
|
if (respectGi) {
|
|
6969
7430
|
const segs = rel ? rel.split(/[\\/]/) : [];
|
|
@@ -7126,7 +7587,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
7126
7587
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
7127
7588
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
7128
7589
|
const fs5 = opts.fs ?? defaultFs;
|
|
7129
|
-
const root =
|
|
7590
|
+
const root = resolve2(rootDir);
|
|
7130
7591
|
const seen = /* @__PURE__ */ new Map();
|
|
7131
7592
|
const expansions = [];
|
|
7132
7593
|
const dirListings = /* @__PURE__ */ new Map();
|
|
@@ -7170,12 +7631,12 @@ ${blocks.join("\n\n")}`;
|
|
|
7170
7631
|
return { text: augmented, expansions };
|
|
7171
7632
|
}
|
|
7172
7633
|
function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings) {
|
|
7173
|
-
if (
|
|
7634
|
+
if (isAbsolute2(rawPath)) {
|
|
7174
7635
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
7175
7636
|
}
|
|
7176
|
-
const resolved =
|
|
7637
|
+
const resolved = resolve2(root, rawPath);
|
|
7177
7638
|
const rel = relative(root, resolved);
|
|
7178
|
-
if (rel.startsWith("..") ||
|
|
7639
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
7179
7640
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
7180
7641
|
}
|
|
7181
7642
|
if (!fs5.exists(resolved)) {
|
|
@@ -7203,7 +7664,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings
|
|
|
7203
7664
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
7204
7665
|
}
|
|
7205
7666
|
function readSafe(root, rawPath, fs5) {
|
|
7206
|
-
const resolved =
|
|
7667
|
+
const resolved = resolve2(root, rawPath);
|
|
7207
7668
|
try {
|
|
7208
7669
|
return fs5.read(resolved);
|
|
7209
7670
|
} catch {
|
|
@@ -7313,7 +7774,7 @@ import {
|
|
|
7313
7774
|
writeFileSync as writeFileSync4
|
|
7314
7775
|
} from "fs";
|
|
7315
7776
|
import { homedir as homedir5 } from "os";
|
|
7316
|
-
import { join as join8, resolve as
|
|
7777
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
7317
7778
|
|
|
7318
7779
|
// src/frontmatter.ts
|
|
7319
7780
|
var KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/;
|
|
@@ -7363,9 +7824,18 @@ function parseFrontmatter(raw) {
|
|
|
7363
7824
|
}
|
|
7364
7825
|
|
|
7365
7826
|
// src/skills.ts
|
|
7366
|
-
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";
|
|
7367
7837
|
import { homedir as homedir4 } from "os";
|
|
7368
|
-
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";
|
|
7369
7839
|
|
|
7370
7840
|
// src/prompt-fragments.ts
|
|
7371
7841
|
var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown renderer):
|
|
@@ -7410,26 +7880,25 @@ function parseAllowedTools(raw) {
|
|
|
7410
7880
|
const names = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7411
7881
|
return names.length > 0 ? Object.freeze(names) : void 0;
|
|
7412
7882
|
}
|
|
7413
|
-
function parseMaxToolIters(raw) {
|
|
7414
|
-
if (raw === void 0) return void 0;
|
|
7415
|
-
const n = Number.parseInt(raw.trim(), 10);
|
|
7416
|
-
if (!Number.isFinite(n) || n < 1) return void 0;
|
|
7417
|
-
return n;
|
|
7418
|
-
}
|
|
7419
7883
|
var SkillStore = class {
|
|
7420
7884
|
homeDir;
|
|
7421
7885
|
projectRoot;
|
|
7886
|
+
customSkillPaths;
|
|
7422
7887
|
disableBuiltins;
|
|
7423
7888
|
constructor(opts = {}) {
|
|
7424
7889
|
this.homeDir = opts.homeDir ?? homedir4();
|
|
7425
|
-
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
|
+
);
|
|
7426
7895
|
this.disableBuiltins = opts.disableBuiltins === true;
|
|
7427
7896
|
}
|
|
7428
7897
|
/** True iff this store was configured with a project root. */
|
|
7429
7898
|
hasProjectScope() {
|
|
7430
7899
|
return this.projectRoot !== void 0;
|
|
7431
7900
|
}
|
|
7432
|
-
/** Project scope first so per-repo skill overrides
|
|
7901
|
+
/** Project scope first so per-repo skill overrides custom/global entries with the same name. */
|
|
7433
7902
|
roots() {
|
|
7434
7903
|
const out = [];
|
|
7435
7904
|
if (this.projectRoot) {
|
|
@@ -7437,15 +7906,24 @@ var SkillStore = class {
|
|
|
7437
7906
|
dir: join7(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
|
|
7438
7907
|
scope: "project"
|
|
7439
7908
|
});
|
|
7909
|
+
out.push({
|
|
7910
|
+
dir: join7(this.projectRoot, ".agents", SKILLS_DIRNAME),
|
|
7911
|
+
scope: "project"
|
|
7912
|
+
});
|
|
7440
7913
|
}
|
|
7914
|
+
for (const dir of this.customSkillPaths) out.push({ dir, scope: "custom" });
|
|
7441
7915
|
out.push({ dir: join7(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
|
|
7442
|
-
|
|
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");
|
|
7443
7921
|
}
|
|
7444
|
-
/** 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. */
|
|
7445
7923
|
list() {
|
|
7446
7924
|
const byName = /* @__PURE__ */ new Map();
|
|
7447
|
-
for (const { dir, scope } of this.roots()) {
|
|
7448
|
-
if (
|
|
7925
|
+
for (const { dir, scope, status } of this.roots()) {
|
|
7926
|
+
if (status !== "ok") continue;
|
|
7449
7927
|
let entries;
|
|
7450
7928
|
try {
|
|
7451
7929
|
entries = readdirSync3(dir, { withFileTypes: true });
|
|
@@ -7497,8 +7975,8 @@ var SkillStore = class {
|
|
|
7497
7975
|
/** Resolve one skill by name. Returns `null` if not found or malformed. */
|
|
7498
7976
|
read(name) {
|
|
7499
7977
|
if (!isValidSkillName(name)) return null;
|
|
7500
|
-
for (const { dir, scope } of this.roots()) {
|
|
7501
|
-
if (
|
|
7978
|
+
for (const { dir, scope, status } of this.roots()) {
|
|
7979
|
+
if (status !== "ok") continue;
|
|
7502
7980
|
const dirCandidate = join7(dir, name, SKILL_FILE);
|
|
7503
7981
|
if (existsSync6(dirCandidate) && statSync4(dirCandidate).isFile()) {
|
|
7504
7982
|
return this.parse(dirCandidate, name, scope);
|
|
@@ -7546,11 +8024,38 @@ var SkillStore = class {
|
|
|
7546
8024
|
path: path2,
|
|
7547
8025
|
allowedTools: parseAllowedTools(data["allowed-tools"]),
|
|
7548
8026
|
runAs: parseRunAs(data.runAs),
|
|
7549
|
-
model: data.model?.startsWith("deepseek-") ? data.model : void 0
|
|
7550
|
-
maxToolIters: parseMaxToolIters(data["max-iters"])
|
|
8027
|
+
model: data.model?.startsWith("deepseek-") ? data.model : void 0
|
|
7551
8028
|
};
|
|
7552
8029
|
}
|
|
7553
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
|
+
}
|
|
7554
8059
|
function parseRunAs(raw) {
|
|
7555
8060
|
return raw?.trim() === "subagent" ? "subagent" : "inline";
|
|
7556
8061
|
}
|
|
@@ -7568,7 +8073,6 @@ Tips:
|
|
|
7568
8073
|
- Reference tools by name (run_command, edit_file, search_content, ...)
|
|
7569
8074
|
- Add \`runAs: subagent\` to frontmatter to spawn an isolated subagent loop
|
|
7570
8075
|
- Add \`allowed-tools: read_file, search_content\` to scope a subagent's tools
|
|
7571
|
-
- Add \`max-iters: N\` to change the subagent's pause cadence (default 16). This isn't a budget \u2014 the parent resumes on pause, so N is how often the parent gets a checkpoint, not how much total work the subagent gets.
|
|
7572
8076
|
`;
|
|
7573
8077
|
}
|
|
7574
8078
|
function skillIndexLine(s) {
|
|
@@ -7803,7 +8307,7 @@ function sanitizeMemoryName(raw) {
|
|
|
7803
8307
|
return trimmed;
|
|
7804
8308
|
}
|
|
7805
8309
|
function projectHash(rootDir) {
|
|
7806
|
-
const abs =
|
|
8310
|
+
const abs = resolve4(rootDir);
|
|
7807
8311
|
return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
|
|
7808
8312
|
}
|
|
7809
8313
|
function scopeDir(opts) {
|
|
@@ -7853,7 +8357,7 @@ var MemoryStore = class {
|
|
|
7853
8357
|
projectRoot;
|
|
7854
8358
|
constructor(opts = {}) {
|
|
7855
8359
|
this.homeDir = opts.homeDir ?? join8(homedir5(), ".reasonix");
|
|
7856
|
-
this.projectRoot = opts.projectRoot ?
|
|
8360
|
+
this.projectRoot = opts.projectRoot ? resolve4(opts.projectRoot) : void 0;
|
|
7857
8361
|
}
|
|
7858
8362
|
/** Directory this store writes `scope` files into, creating it if needed. */
|
|
7859
8363
|
dir(scope) {
|
|
@@ -8094,11 +8598,17 @@ function applyUserMemory(basePrompt, opts = {}) {
|
|
|
8094
8598
|
}
|
|
8095
8599
|
return parts.join("\n");
|
|
8096
8600
|
}
|
|
8097
|
-
function applyMemoryStack(basePrompt, rootDir) {
|
|
8601
|
+
function applyMemoryStack(basePrompt, rootDir, opts = {}) {
|
|
8602
|
+
const homeDir = opts.homeDir;
|
|
8603
|
+
const cfg = opts.cfg;
|
|
8098
8604
|
const withProject = applyProjectMemory(basePrompt, rootDir);
|
|
8099
|
-
const withGlobal = applyGlobalReasonixMemory(
|
|
8100
|
-
|
|
8101
|
-
|
|
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 });
|
|
8102
8612
|
}
|
|
8103
8613
|
|
|
8104
8614
|
// src/tools/filesystem.ts
|
|
@@ -8106,6 +8616,51 @@ import { promises as fs4 } from "fs";
|
|
|
8106
8616
|
import * as pathMod5 from "path";
|
|
8107
8617
|
import picomatch3 from "picomatch";
|
|
8108
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
|
+
|
|
8109
8664
|
// src/tools/fs/edit.ts
|
|
8110
8665
|
import { promises as fs } from "fs";
|
|
8111
8666
|
import * as pathMod from "path";
|
|
@@ -8336,6 +8891,18 @@ var RUST_DECL_RE = /^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|str
|
|
|
8336
8891
|
var RUST_IMPL_RE = /^(?:unsafe\s+)?impl(?:\s*<[^>]+>)?\s+(?:[^{]+\s+for\s+)?(\w+)/;
|
|
8337
8892
|
var MD_HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
8338
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
|
+
];
|
|
8339
8906
|
var EXT_TO_LANG = {
|
|
8340
8907
|
".ts": "ts",
|
|
8341
8908
|
".tsx": "ts",
|
|
@@ -8351,7 +8918,10 @@ var EXT_TO_LANG = {
|
|
|
8351
8918
|
".rs": "rust",
|
|
8352
8919
|
".md": "md",
|
|
8353
8920
|
".markdown": "md",
|
|
8354
|
-
".mdx": "md"
|
|
8921
|
+
".mdx": "md",
|
|
8922
|
+
".proto": "proto",
|
|
8923
|
+
".txt": "txt",
|
|
8924
|
+
".text": "txt"
|
|
8355
8925
|
};
|
|
8356
8926
|
function extractOutline(filename, lines) {
|
|
8357
8927
|
const ext = pathMod3.extname(filename).toLowerCase();
|
|
@@ -8368,6 +8938,10 @@ function extractOutline(filename, lines) {
|
|
|
8368
8938
|
return extractRust(lines);
|
|
8369
8939
|
case "md":
|
|
8370
8940
|
return extractMarkdown(lines);
|
|
8941
|
+
case "proto":
|
|
8942
|
+
return extractProto(lines);
|
|
8943
|
+
case "txt":
|
|
8944
|
+
return extractText(lines);
|
|
8371
8945
|
}
|
|
8372
8946
|
}
|
|
8373
8947
|
function extractTs(lines) {
|
|
@@ -8419,6 +8993,36 @@ function extractRust(lines) {
|
|
|
8419
8993
|
}
|
|
8420
8994
|
return out;
|
|
8421
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
|
+
}
|
|
8422
9026
|
function extractMarkdown(lines) {
|
|
8423
9027
|
const out = [];
|
|
8424
9028
|
let inFence = false;
|
|
@@ -8643,8 +9247,8 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
8643
9247
|
for (let i = realStart; i <= winEnd; i++) {
|
|
8644
9248
|
const line = lines[i];
|
|
8645
9249
|
const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
|
|
8646
|
-
const
|
|
8647
|
-
if (!pushLine(`${rel}:${i + 1}${
|
|
9250
|
+
const sep3 = hitSet.has(i) ? ":" : "-";
|
|
9251
|
+
if (!pushLine(`${rel}:${i + 1}${sep3} ${display}`)) return;
|
|
8648
9252
|
}
|
|
8649
9253
|
prevWindowEnd = winEnd;
|
|
8650
9254
|
}
|
|
@@ -8666,17 +9270,25 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
8666
9270
|
}
|
|
8667
9271
|
|
|
8668
9272
|
// src/tools/filesystem.ts
|
|
8669
|
-
var
|
|
9273
|
+
var DEFAULT_OUTLINE_THRESHOLD_BYTES = 512 * 1024;
|
|
8670
9274
|
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
8671
|
-
var
|
|
8672
|
-
var
|
|
8673
|
-
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
8674
|
-
var OUTLINE_MAX_ENTRIES2 = 30;
|
|
9275
|
+
var HARD_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
9276
|
+
var OUTLINE_HEAD_LINES = 80;
|
|
8675
9277
|
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
8676
9278
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
8677
9279
|
function displayRel4(rootDir, full) {
|
|
8678
9280
|
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
8679
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
|
+
}
|
|
8680
9292
|
var GLOB_METACHARS = /[*?{[]/;
|
|
8681
9293
|
function compileNameFilter(filter) {
|
|
8682
9294
|
if (!filter) return null;
|
|
@@ -8693,24 +9305,45 @@ function isLikelyBinaryByName(name) {
|
|
|
8693
9305
|
if (dot < 0) return false;
|
|
8694
9306
|
return BINARY_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
8695
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
|
+
}
|
|
8696
9321
|
function registerFilesystemTools(registry, opts) {
|
|
8697
9322
|
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
8698
9323
|
const allowWriting = opts.allowWriting !== false;
|
|
8699
|
-
const
|
|
9324
|
+
const outlineThresholdBytes = opts.outlineThresholdBytes ?? DEFAULT_OUTLINE_THRESHOLD_BYTES;
|
|
8700
9325
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
8701
9326
|
const normRoot = pathMod5.resolve(rootDir);
|
|
8702
9327
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
8703
|
-
const
|
|
8704
|
-
function
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
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}`;
|
|
8713
9345
|
}
|
|
9346
|
+
const inflightGate = /* @__PURE__ */ new Map();
|
|
8714
9347
|
async function ensureOutsideSandboxAllowed(abs, intent, toolName, ctx) {
|
|
8715
9348
|
for (const dir of loadProjectPathAllowed(rootDir)) {
|
|
8716
9349
|
if (pathIsUnder(abs, dir)) return;
|
|
@@ -8775,11 +9408,11 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8775
9408
|
registry.register({
|
|
8776
9409
|
name: "read_file",
|
|
8777
9410
|
parallelSafe: true,
|
|
8778
|
-
description: `Read a file under the sandbox root.
|
|
8779
|
-
- head: N \u2192 first N lines (imports
|
|
8780
|
-
- tail: N \u2192 last N lines (
|
|
8781
|
-
- range: "A-B" \u2192 inclusive
|
|
8782
|
-
|
|
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.`,
|
|
8783
9416
|
readOnly: true,
|
|
8784
9417
|
stormExempt: true,
|
|
8785
9418
|
parameters: {
|
|
@@ -8797,22 +9430,31 @@ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_L
|
|
|
8797
9430
|
},
|
|
8798
9431
|
fn: async (args, ctx) => {
|
|
8799
9432
|
const abs = await safePath(args.path, "read_file", ctx);
|
|
9433
|
+
const rel = displayRel4(rootDir, abs);
|
|
8800
9434
|
const fh = await fs4.open(abs, "r");
|
|
8801
9435
|
let raw;
|
|
9436
|
+
let sizeBytes;
|
|
8802
9437
|
try {
|
|
8803
9438
|
const stat2 = await fh.stat();
|
|
8804
9439
|
if (stat2.isDirectory()) {
|
|
8805
9440
|
throw new Error(`not a file: ${args.path} (it's a directory)`);
|
|
8806
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
|
+
}
|
|
8807
9452
|
raw = await fh.readFile();
|
|
8808
9453
|
} finally {
|
|
8809
9454
|
await fh.close();
|
|
8810
9455
|
}
|
|
8811
|
-
if (raw
|
|
8812
|
-
|
|
8813
|
-
return `${headBytes}
|
|
8814
|
-
|
|
8815
|
-
[\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.]`;
|
|
8816
9458
|
}
|
|
8817
9459
|
const text = raw.toString("utf8");
|
|
8818
9460
|
let lines = text.split(/\r?\n/);
|
|
@@ -8824,8 +9466,8 @@ When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_L
|
|
|
8824
9466
|
const end = Math.min(totalLines, Math.max(start, rawEnd ?? totalLines));
|
|
8825
9467
|
const slice = lines.slice(start - 1, end);
|
|
8826
9468
|
const label = `[range ${start}-${end} of ${totalLines} lines]`;
|
|
8827
|
-
return `${label}
|
|
8828
|
-
${slice.join("\n")}
|
|
9469
|
+
return withSubdirMemory(abs, `${label}
|
|
9470
|
+
${slice.join("\n")}`);
|
|
8829
9471
|
}
|
|
8830
9472
|
if (typeof args.head === "number" && args.head > 0) {
|
|
8831
9473
|
const count = Math.min(args.head, totalLines);
|
|
@@ -8833,7 +9475,7 @@ ${slice.join("\n")}`;
|
|
|
8833
9475
|
const marker = count < totalLines ? `
|
|
8834
9476
|
|
|
8835
9477
|
[\u2026head ${count} of ${totalLines} lines \u2014 call again with range / tail for more]` : "";
|
|
8836
|
-
return slice.join("\n") + marker;
|
|
9478
|
+
return withSubdirMemory(abs, slice.join("\n") + marker);
|
|
8837
9479
|
}
|
|
8838
9480
|
if (typeof args.tail === "number" && args.tail > 0) {
|
|
8839
9481
|
const count = Math.min(args.tail, totalLines);
|
|
@@ -8841,25 +9483,26 @@ ${slice.join("\n")}`;
|
|
|
8841
9483
|
const marker = count < totalLines ? `[\u2026tail ${count} of ${totalLines} lines \u2014 call again with range / head for more]
|
|
8842
9484
|
|
|
8843
9485
|
` : "";
|
|
8844
|
-
return marker + slice.join("\n");
|
|
9486
|
+
return withSubdirMemory(abs, marker + slice.join("\n"));
|
|
8845
9487
|
}
|
|
8846
|
-
if (
|
|
8847
|
-
const head = lines.slice(0,
|
|
8848
|
-
const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
|
|
8849
|
-
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");
|
|
8850
9490
|
const outline = formatOutline(extractOutline(abs, lines));
|
|
8851
9491
|
const parts = [
|
|
8852
|
-
`[
|
|
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]`,
|
|
8853
9495
|
head
|
|
8854
9496
|
];
|
|
8855
9497
|
if (outline) parts.push("", outline);
|
|
8856
9498
|
parts.push(
|
|
8857
|
-
|
|
8858
|
-
[
|
|
8859
|
-
`,
|
|
8860
|
-
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]`
|
|
8861
9504
|
);
|
|
8862
|
-
return parts.join("\n");
|
|
9505
|
+
return withSubdirMemory(abs, parts.join("\n"));
|
|
8863
9506
|
}
|
|
8864
9507
|
});
|
|
8865
9508
|
registry.register({
|
|
@@ -9900,7 +10543,7 @@ var VERIFY_SYSTEM = `You are a verify subagent. Narrow check \u2014 return YES /
|
|
|
9900
10543
|
How to operate:
|
|
9901
10544
|
- Read only what's needed to verify the specific claim. No exploration past the claim.
|
|
9902
10545
|
- Use search_content / read_file to confirm the exact behavior, type, or call site in question.
|
|
9903
|
-
-
|
|
10546
|
+
- If a focused round of reads can't verify it, return INCONCLUSIVE plus what's missing \u2014 don't keep digging.
|
|
9904
10547
|
|
|
9905
10548
|
Final answer:
|
|
9906
10549
|
- Lead with VERIFIED / NOT VERIFIED / INCONCLUSIVE.
|
|
@@ -9911,8 +10554,8 @@ ${NEGATIVE_CLAIM_RULE}
|
|
|
9911
10554
|
|
|
9912
10555
|
${TUI_FORMATTING_RULES}`;
|
|
9913
10556
|
var TYPES = {
|
|
9914
|
-
explore: { system: EXPLORE_SYSTEM
|
|
9915
|
-
verify: { system: VERIFY_SYSTEM
|
|
10557
|
+
explore: { system: EXPLORE_SYSTEM },
|
|
10558
|
+
verify: { system: VERIFY_SYSTEM }
|
|
9916
10559
|
};
|
|
9917
10560
|
var SUBAGENT_TYPE_NAMES = Object.freeze(
|
|
9918
10561
|
Object.keys(TYPES)
|
|
@@ -9940,11 +10583,6 @@ ${NEGATIVE_CLAIM_RULE}
|
|
|
9940
10583
|
|
|
9941
10584
|
${TUI_FORMATTING_RULES}`;
|
|
9942
10585
|
var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
|
|
9943
|
-
var DEFAULT_PAUSE_EVERY = 16;
|
|
9944
|
-
var BUDGET_WARN_THRESHOLD = 3;
|
|
9945
|
-
function budgetParagraph(maxToolIters) {
|
|
9946
|
-
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.`;
|
|
9947
|
-
}
|
|
9948
10586
|
var DEFAULT_SUBAGENT_MODEL = "deepseek-v4-flash";
|
|
9949
10587
|
var DEFAULT_SUBAGENT_EFFORT = "high";
|
|
9950
10588
|
var SUBAGENT_TOOL_NAME = "spawn_subagent";
|
|
@@ -9963,7 +10601,6 @@ function subagentBudgetHint(spawnCount, totalTokens) {
|
|
|
9963
10601
|
}
|
|
9964
10602
|
async function spawnSubagent(opts) {
|
|
9965
10603
|
const model = opts.model ?? DEFAULT_SUBAGENT_MODEL;
|
|
9966
|
-
const maxToolIters = opts.maxToolIters ?? DEFAULT_PAUSE_EVERY;
|
|
9967
10604
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
9968
10605
|
const sink = opts.sink;
|
|
9969
10606
|
const skillName = opts.skillName;
|
|
@@ -10016,26 +10653,8 @@ async function spawnSubagent(opts) {
|
|
|
10016
10653
|
new Set(opts.allowedTools),
|
|
10017
10654
|
NEVER_INHERITED_TOOLS
|
|
10018
10655
|
) : forkRegistryExcluding(opts.parentRegistry, NEVER_INHERITED_TOOLS);
|
|
10019
|
-
let dispatchCount = 0;
|
|
10020
|
-
childTools.setResultAugmenter((_name, _args, result) => {
|
|
10021
|
-
dispatchCount++;
|
|
10022
|
-
const remaining = maxToolIters - dispatchCount;
|
|
10023
|
-
if (remaining <= 0) {
|
|
10024
|
-
return `${result}
|
|
10025
|
-
|
|
10026
|
-
[budget: 0 of ${maxToolIters} tool calls left \u2014 finalize NOW; the next tool call will be refused]`;
|
|
10027
|
-
}
|
|
10028
|
-
if (remaining <= BUDGET_WARN_THRESHOLD) {
|
|
10029
|
-
return `${result}
|
|
10030
|
-
|
|
10031
|
-
[budget: ${remaining} of ${maxToolIters} tool call${remaining === 1 ? "" : "s"} left \u2014 wrap up soon]`;
|
|
10032
|
-
}
|
|
10033
|
-
return result;
|
|
10034
|
-
});
|
|
10035
10656
|
const childPrefix = new ImmutablePrefix({
|
|
10036
|
-
system:
|
|
10037
|
-
|
|
10038
|
-
${budgetParagraph(maxToolIters)}`,
|
|
10657
|
+
system: opts.system,
|
|
10039
10658
|
toolSpecs: childTools.specs()
|
|
10040
10659
|
});
|
|
10041
10660
|
const childLoop = new CacheFirstLoop({
|
|
@@ -10047,11 +10666,9 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
10047
10666
|
// task is already narrow by construction, and `high` cuts output
|
|
10048
10667
|
// tokens substantially vs `max`.
|
|
10049
10668
|
reasoningEffort: DEFAULT_SUBAGENT_EFFORT,
|
|
10050
|
-
maxToolIters,
|
|
10051
10669
|
hooks: [],
|
|
10052
10670
|
stream: true,
|
|
10053
|
-
session: sessionName
|
|
10054
|
-
onIterBudgetExhausted: "pause"
|
|
10671
|
+
session: sessionName
|
|
10055
10672
|
});
|
|
10056
10673
|
const onParentAbort = () => childLoop.abort();
|
|
10057
10674
|
if (opts.parentSignal?.aborted) {
|
|
@@ -10063,13 +10680,9 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
10063
10680
|
let errorMessage;
|
|
10064
10681
|
let toolIter = 0;
|
|
10065
10682
|
let summarisingEmitted = false;
|
|
10066
|
-
let
|
|
10067
|
-
let partialSummary;
|
|
10068
|
-
const taskForLoop = opts.resumeSession ? `[Resume: your tool-call budget has been refreshed with ${maxToolIters} new calls. Earlier "wrap up" / "finalize NOW" budget hints in this conversation referred to the previous window \u2014 they no longer apply. Continue the work you were given.]
|
|
10069
|
-
|
|
10070
|
-
${opts.task}` : opts.task;
|
|
10683
|
+
let forcedSummaryFired = false;
|
|
10071
10684
|
try {
|
|
10072
|
-
for await (const ev of childLoop.step(
|
|
10685
|
+
for await (const ev of childLoop.step(opts.task)) {
|
|
10073
10686
|
sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
|
|
10074
10687
|
if (ev.role === "tool") {
|
|
10075
10688
|
toolIter++;
|
|
@@ -10099,7 +10712,12 @@ ${opts.task}` : opts.task;
|
|
|
10099
10712
|
}
|
|
10100
10713
|
if (ev.role === "assistant_final") {
|
|
10101
10714
|
if (ev.forcedSummary) {
|
|
10102
|
-
|
|
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
|
+
}
|
|
10103
10721
|
} else {
|
|
10104
10722
|
final = ev.content ?? "";
|
|
10105
10723
|
}
|
|
@@ -10107,17 +10725,13 @@ ${opts.task}` : opts.task;
|
|
|
10107
10725
|
if (ev.role === "error") {
|
|
10108
10726
|
errorMessage = ev.error ?? "subagent error";
|
|
10109
10727
|
}
|
|
10110
|
-
if (ev.role === "paused") {
|
|
10111
|
-
paused = true;
|
|
10112
|
-
if (ev.partialSummary) partialSummary = ev.partialSummary;
|
|
10113
|
-
}
|
|
10114
10728
|
}
|
|
10115
10729
|
} catch (err) {
|
|
10116
10730
|
errorMessage = err.message;
|
|
10117
10731
|
} finally {
|
|
10118
10732
|
opts.parentSignal?.removeEventListener("abort", onParentAbort);
|
|
10119
10733
|
}
|
|
10120
|
-
if (!errorMessage && !final
|
|
10734
|
+
if (!errorMessage && !final) {
|
|
10121
10735
|
errorMessage = opts.parentSignal?.aborted ? "subagent aborted before producing an answer" : "subagent ended without producing an answer";
|
|
10122
10736
|
}
|
|
10123
10737
|
const elapsedMs = Date.now() - startedAt;
|
|
@@ -10142,7 +10756,7 @@ ${opts.task}` : opts.task;
|
|
|
10142
10756
|
usage
|
|
10143
10757
|
});
|
|
10144
10758
|
return {
|
|
10145
|
-
success: !errorMessage,
|
|
10759
|
+
success: !errorMessage && !forcedSummaryFired,
|
|
10146
10760
|
output: errorMessage ? "" : truncated,
|
|
10147
10761
|
error: errorMessage,
|
|
10148
10762
|
turns,
|
|
@@ -10152,9 +10766,7 @@ ${opts.task}` : opts.task;
|
|
|
10152
10766
|
model,
|
|
10153
10767
|
skillName,
|
|
10154
10768
|
usage,
|
|
10155
|
-
|
|
10156
|
-
pausedSession: paused ? sessionName : void 0,
|
|
10157
|
-
partialSummary: paused ? partialSummary : void 0
|
|
10769
|
+
forcedSummary: forcedSummaryFired || void 0
|
|
10158
10770
|
};
|
|
10159
10771
|
}
|
|
10160
10772
|
function aggregateChildUsage(loop) {
|
|
@@ -10169,16 +10781,16 @@ function aggregateChildUsage(loop) {
|
|
|
10169
10781
|
return agg;
|
|
10170
10782
|
}
|
|
10171
10783
|
function formatSubagentResult(r) {
|
|
10172
|
-
if (r.
|
|
10784
|
+
if (r.forcedSummary) {
|
|
10173
10785
|
return JSON.stringify({
|
|
10174
10786
|
success: false,
|
|
10175
|
-
|
|
10176
|
-
|
|
10787
|
+
partial: true,
|
|
10788
|
+
output: r.output,
|
|
10789
|
+
turns: r.turns,
|
|
10177
10790
|
tool_iters: r.toolIters,
|
|
10178
10791
|
elapsed_ms: r.elapsedMs,
|
|
10179
10792
|
cost_usd: r.costUsd,
|
|
10180
|
-
|
|
10181
|
-
note: `Subagent reached its pause-every interval (${r.toolIters} tool calls) without producing a final answer. Read partial_summary above to see what was done / left / blocked, then decide: resume by calling spawn_subagent again with resume_session="${r.pausedSession}" (the task arg becomes a continuation nudge \u2014 e.g. "finish what you started"), or accept the partial work and proceed with what you already know.`
|
|
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."
|
|
10182
10794
|
});
|
|
10183
10795
|
}
|
|
10184
10796
|
if (!r.success) {
|
|
@@ -10203,7 +10815,6 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10203
10815
|
const baseSystem = opts.defaultSystem ?? SUBAGENT_BASE_SYSTEM;
|
|
10204
10816
|
const defaultSystemBase = opts.projectRoot ? applyProjectMemory(baseSystem, opts.projectRoot) : baseSystem;
|
|
10205
10817
|
const defaultModel = opts.defaultModel ?? DEFAULT_SUBAGENT_MODEL;
|
|
10206
|
-
const maxToolIters = opts.maxToolIters ?? DEFAULT_PAUSE_EVERY;
|
|
10207
10818
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
10208
10819
|
const sink = opts.sink;
|
|
10209
10820
|
let sessionSpawnCount = 0;
|
|
@@ -10211,7 +10822,7 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10211
10822
|
parentRegistry.register({
|
|
10212
10823
|
name: SUBAGENT_TOOL_NAME,
|
|
10213
10824
|
parallelSafe: true,
|
|
10214
|
-
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.
|
|
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).",
|
|
10215
10826
|
parameters: {
|
|
10216
10827
|
type: "object",
|
|
10217
10828
|
properties: {
|
|
@@ -10228,18 +10839,14 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10228
10839
|
enum: ["deepseek-v4-flash", "deepseek-v4-pro"],
|
|
10229
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."
|
|
10230
10841
|
},
|
|
10231
|
-
max_iters: {
|
|
10232
|
-
type: "integer",
|
|
10233
|
-
description: "How many tool calls the subagent runs before pausing back to you for a checkpoint. Default 16. This is checkpoint cadence, not a budget \u2014 work continues across pauses via `resume_session`. Pick what feels natural for the granularity of decisions you want to make: small (4-8) when you want frequent control, larger (32-64) when the subagent should mostly run autonomously."
|
|
10234
|
-
},
|
|
10235
10842
|
resume_session: {
|
|
10236
10843
|
type: "string",
|
|
10237
|
-
description: "Provide
|
|
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."
|
|
10238
10845
|
},
|
|
10239
10846
|
type: {
|
|
10240
10847
|
type: "string",
|
|
10241
10848
|
enum: [...SUBAGENT_TYPE_NAMES],
|
|
10242
|
-
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."
|
|
10243
10850
|
}
|
|
10244
10851
|
},
|
|
10245
10852
|
required: ["task"]
|
|
@@ -10256,7 +10863,6 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
10256
10863
|
const system = typeof args.system === "string" && args.system.trim().length > 0 ? args.system.trim() : typeSpec?.system ?? `${defaultSystemBase}
|
|
10257
10864
|
|
|
10258
10865
|
${escalationContract(model)}`;
|
|
10259
|
-
const callerIters = parseMaxIters(args.max_iters);
|
|
10260
10866
|
const resumeSession = typeof args.resume_session === "string" && args.resume_session.trim().length > 0 ? args.resume_session.trim() : void 0;
|
|
10261
10867
|
const result = await spawnSubagent({
|
|
10262
10868
|
client: opts.client,
|
|
@@ -10264,7 +10870,6 @@ ${escalationContract(model)}`;
|
|
|
10264
10870
|
system,
|
|
10265
10871
|
task,
|
|
10266
10872
|
model,
|
|
10267
|
-
maxToolIters: callerIters ?? typeSpec?.maxToolIters ?? maxToolIters,
|
|
10268
10873
|
maxResultChars,
|
|
10269
10874
|
sink,
|
|
10270
10875
|
parentSignal: ctx?.signal,
|
|
@@ -10272,6 +10877,12 @@ ${escalationContract(model)}`;
|
|
|
10272
10877
|
});
|
|
10273
10878
|
sessionSpawnCount++;
|
|
10274
10879
|
sessionSpawnTokens += result.usage.totalTokens;
|
|
10880
|
+
if (opts.onSpawnComplete) {
|
|
10881
|
+
try {
|
|
10882
|
+
opts.onSpawnComplete(result);
|
|
10883
|
+
} catch {
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10275
10886
|
const formatted = formatSubagentResult(result);
|
|
10276
10887
|
const hint = subagentBudgetHint(sessionSpawnCount, sessionSpawnTokens);
|
|
10277
10888
|
return hint ? `${formatted}
|
|
@@ -10280,11 +10891,6 @@ ${hint}` : formatted;
|
|
|
10280
10891
|
});
|
|
10281
10892
|
return parentRegistry;
|
|
10282
10893
|
}
|
|
10283
|
-
function parseMaxIters(raw) {
|
|
10284
|
-
if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
|
|
10285
|
-
const n = Math.floor(raw);
|
|
10286
|
-
return n >= 1 ? n : void 0;
|
|
10287
|
-
}
|
|
10288
10894
|
function forkRegistryExcluding(parent, exclude) {
|
|
10289
10895
|
const child = new ToolRegistry();
|
|
10290
10896
|
for (const spec of parent.specs()) {
|
|
@@ -10311,8 +10917,100 @@ function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
|
10311
10917
|
return child;
|
|
10312
10918
|
}
|
|
10313
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
|
+
|
|
10314
11012
|
// src/tools/shell.ts
|
|
10315
|
-
import * as
|
|
11013
|
+
import * as pathMod10 from "path";
|
|
10316
11014
|
|
|
10317
11015
|
// src/tools/jobs.ts
|
|
10318
11016
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -10561,16 +11259,16 @@ ${job.output.slice(start)}`;
|
|
|
10561
11259
|
let wakeOutput = null;
|
|
10562
11260
|
if (waitFor === "output-or-exit") {
|
|
10563
11261
|
racers.push(
|
|
10564
|
-
new Promise((
|
|
10565
|
-
wakeOutput =
|
|
10566
|
-
job.outputWaiters.add(
|
|
11262
|
+
new Promise((resolve13) => {
|
|
11263
|
+
wakeOutput = resolve13;
|
|
11264
|
+
job.outputWaiters.add(resolve13);
|
|
10567
11265
|
})
|
|
10568
11266
|
);
|
|
10569
11267
|
}
|
|
10570
11268
|
let timer = null;
|
|
10571
11269
|
racers.push(
|
|
10572
|
-
new Promise((
|
|
10573
|
-
timer = setTimeout(
|
|
11270
|
+
new Promise((resolve13) => {
|
|
11271
|
+
timer = setTimeout(resolve13, timeoutMs);
|
|
10574
11272
|
})
|
|
10575
11273
|
);
|
|
10576
11274
|
await Promise.race(racers);
|
|
@@ -10679,8 +11377,8 @@ function latestOutputSince(before, after) {
|
|
|
10679
11377
|
|
|
10680
11378
|
// src/tools/shell/exec.ts
|
|
10681
11379
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
10682
|
-
import { existsSync as
|
|
10683
|
-
import * as
|
|
11380
|
+
import { existsSync as existsSync9, statSync as statSync5 } from "fs";
|
|
11381
|
+
import * as pathMod9 from "path";
|
|
10684
11382
|
|
|
10685
11383
|
// src/tools/shell-chain.ts
|
|
10686
11384
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -11013,6 +11711,10 @@ async function runPipeGroup(segments, opts) {
|
|
|
11013
11711
|
cwd: opts.cwd,
|
|
11014
11712
|
shell: false,
|
|
11015
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",
|
|
11016
11718
|
env,
|
|
11017
11719
|
stdio: [stdinSpec, stdoutSpec, stderrSpec],
|
|
11018
11720
|
...spawnOverrides
|
|
@@ -11063,9 +11765,9 @@ async function runPipeGroup(segments, opts) {
|
|
|
11063
11765
|
}
|
|
11064
11766
|
const exits = await Promise.all(
|
|
11065
11767
|
children.map(
|
|
11066
|
-
(c) => new Promise((
|
|
11067
|
-
c.once("error", () =>
|
|
11068
|
-
c.once("close", (code) =>
|
|
11768
|
+
(c) => new Promise((resolve13) => {
|
|
11769
|
+
c.once("error", () => resolve13(null));
|
|
11770
|
+
c.once("close", (code) => resolve13(code));
|
|
11069
11771
|
})
|
|
11070
11772
|
)
|
|
11071
11773
|
);
|
|
@@ -11109,6 +11811,8 @@ var OutputBuffer = class {
|
|
|
11109
11811
|
};
|
|
11110
11812
|
|
|
11111
11813
|
// src/tools/shell/parse.ts
|
|
11814
|
+
import { homedir as homedir6 } from "os";
|
|
11815
|
+
import * as pathMod8 from "path";
|
|
11112
11816
|
var BUILTIN_ALLOWLIST = [
|
|
11113
11817
|
// Repo inspection
|
|
11114
11818
|
"git status",
|
|
@@ -11285,7 +11989,65 @@ function tailHasRisky(tail, risky) {
|
|
|
11285
11989
|
}
|
|
11286
11990
|
return false;
|
|
11287
11991
|
}
|
|
11288
|
-
|
|
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) {
|
|
11289
12051
|
let argv;
|
|
11290
12052
|
try {
|
|
11291
12053
|
argv = tokenizeCommand(cmd);
|
|
@@ -11307,19 +12069,26 @@ function isAllowed(cmd, extra = []) {
|
|
|
11307
12069
|
if (!match) continue;
|
|
11308
12070
|
const risky = RISKY_ARGS[prefix];
|
|
11309
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;
|
|
11310
12079
|
return true;
|
|
11311
12080
|
}
|
|
11312
12081
|
return false;
|
|
11313
12082
|
}
|
|
11314
|
-
function isCommandAllowed(cmd, extra = []) {
|
|
12083
|
+
function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
11315
12084
|
let chain;
|
|
11316
12085
|
try {
|
|
11317
12086
|
chain = parseCommandChain(cmd);
|
|
11318
12087
|
} catch {
|
|
11319
12088
|
return false;
|
|
11320
12089
|
}
|
|
11321
|
-
if (chain === null) return isAllowed(cmd, extra);
|
|
11322
|
-
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));
|
|
11323
12092
|
}
|
|
11324
12093
|
|
|
11325
12094
|
// src/tools/shell/exec.ts
|
|
@@ -11366,8 +12135,15 @@ async function runCommand(cmd, opts) {
|
|
|
11366
12135
|
const spawnOpts = {
|
|
11367
12136
|
cwd: opts.cwd,
|
|
11368
12137
|
shell: false,
|
|
11369
|
-
// no shell-expansion — see header comment
|
|
11370
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",
|
|
11371
12147
|
// PYTHONIOENCODING + PYTHONUTF8 force any spawned Python child
|
|
11372
12148
|
// (run_command running `python script.py`, etc.) to emit UTF-8
|
|
11373
12149
|
// on stdout/stderr. Without this, Chinese-Windows defaults
|
|
@@ -11380,7 +12156,7 @@ async function runCommand(cmd, opts) {
|
|
|
11380
12156
|
};
|
|
11381
12157
|
const { bin, args, spawnOverrides } = prepareSpawn(argv, { env: normalizedEnv });
|
|
11382
12158
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
11383
|
-
return await new Promise((
|
|
12159
|
+
return await new Promise((resolve13, reject) => {
|
|
11384
12160
|
let child;
|
|
11385
12161
|
try {
|
|
11386
12162
|
child = spawn4(bin, args, effectiveSpawnOpts);
|
|
@@ -11434,7 +12210,7 @@ async function runCommand(cmd, opts) {
|
|
|
11434
12210
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
11435
12211
|
|
|
11436
12212
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
11437
|
-
|
|
12213
|
+
resolve13({ exitCode: code, output, timedOut });
|
|
11438
12214
|
});
|
|
11439
12215
|
});
|
|
11440
12216
|
}
|
|
@@ -11456,16 +12232,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
11456
12232
|
const platform = opts.platform ?? process.platform;
|
|
11457
12233
|
if (platform !== "win32") return cmd;
|
|
11458
12234
|
if (!cmd) return cmd;
|
|
11459
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
11460
|
-
if (
|
|
12235
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod9.isAbsolute(cmd)) return cmd;
|
|
12236
|
+
if (pathMod9.extname(cmd)) return cmd;
|
|
11461
12237
|
const env = opts.env ?? process.env;
|
|
11462
12238
|
const pathExt = (getEnvCaseInsensitive(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
11463
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
12239
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod9.delimiter);
|
|
11464
12240
|
const pathDirs = (getEnvCaseInsensitive(env, "PATH") ?? "").split(delimiter2).filter(Boolean);
|
|
11465
12241
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
11466
12242
|
for (const dir of pathDirs) {
|
|
11467
12243
|
for (const ext of pathExt) {
|
|
11468
|
-
const full =
|
|
12244
|
+
const full = pathMod9.win32.join(dir, cmd + ext);
|
|
11469
12245
|
if (isFile(full)) return full;
|
|
11470
12246
|
}
|
|
11471
12247
|
}
|
|
@@ -11519,7 +12295,7 @@ function mergeWindowsPathLike(values, delimiter2) {
|
|
|
11519
12295
|
}
|
|
11520
12296
|
function defaultIsFile(full) {
|
|
11521
12297
|
try {
|
|
11522
|
-
return
|
|
12298
|
+
return existsSync9(full) && statSync5(full).isFile();
|
|
11523
12299
|
} catch {
|
|
11524
12300
|
return false;
|
|
11525
12301
|
}
|
|
@@ -11581,8 +12357,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
11581
12357
|
function isBareWindowsName(s) {
|
|
11582
12358
|
if (!s) return false;
|
|
11583
12359
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
11584
|
-
if (
|
|
11585
|
-
if (
|
|
12360
|
+
if (pathMod9.isAbsolute(s)) return false;
|
|
12361
|
+
if (pathMod9.extname(s)) return false;
|
|
11586
12362
|
return true;
|
|
11587
12363
|
}
|
|
11588
12364
|
function quoteForCmdExe(arg) {
|
|
@@ -11603,7 +12379,7 @@ var NeedsConfirmationError = class extends Error {
|
|
|
11603
12379
|
}
|
|
11604
12380
|
};
|
|
11605
12381
|
function registerShellTools(registry, opts) {
|
|
11606
|
-
const rootDir =
|
|
12382
|
+
const rootDir = pathMod10.resolve(opts.rootDir);
|
|
11607
12383
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
11608
12384
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
11609
12385
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -11623,7 +12399,7 @@ function registerShellTools(registry, opts) {
|
|
|
11623
12399
|
if (isAllowAll()) return true;
|
|
11624
12400
|
const cmd = typeof args?.command === "string" ? args.command.trim() : "";
|
|
11625
12401
|
if (!cmd) return false;
|
|
11626
|
-
return isCommandAllowed(cmd, getExtraAllowed());
|
|
12402
|
+
return isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths);
|
|
11627
12403
|
},
|
|
11628
12404
|
parameters: {
|
|
11629
12405
|
type: "object",
|
|
@@ -11643,7 +12419,7 @@ function registerShellTools(registry, opts) {
|
|
|
11643
12419
|
const cmd = args.command.trim();
|
|
11644
12420
|
if (!cmd) throw new Error("run_command: empty command");
|
|
11645
12421
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
11646
|
-
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed())) {
|
|
12422
|
+
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths)) {
|
|
11647
12423
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
11648
12424
|
const choice = await gate.ask({
|
|
11649
12425
|
kind: "run_command",
|
|
@@ -11669,7 +12445,7 @@ function registerShellTools(registry, opts) {
|
|
|
11669
12445
|
});
|
|
11670
12446
|
registry.register({
|
|
11671
12447
|
name: "run_background",
|
|
11672
|
-
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.",
|
|
11673
12449
|
parameters: {
|
|
11674
12450
|
type: "object",
|
|
11675
12451
|
properties: {
|
|
@@ -11677,6 +12453,10 @@ function registerShellTools(registry, opts) {
|
|
|
11677
12453
|
type: "string",
|
|
11678
12454
|
description: "Full command line. Same quoting rules as run_command (no pipes / redirects / chaining)."
|
|
11679
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
|
+
},
|
|
11680
12460
|
waitSec: {
|
|
11681
12461
|
type: "integer",
|
|
11682
12462
|
description: "Max seconds to wait for startup before returning. 0..30, default 3. A ready-signal match short-circuits this."
|
|
@@ -11687,11 +12467,12 @@ function registerShellTools(registry, opts) {
|
|
|
11687
12467
|
fn: async (args, ctx) => {
|
|
11688
12468
|
const cmd = args.command.trim();
|
|
11689
12469
|
if (!cmd) throw new Error("run_background: empty command");
|
|
11690
|
-
|
|
12470
|
+
const cwd = resolveCwdInsideRoot(rootDir, args.cwd);
|
|
12471
|
+
if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths)) {
|
|
11691
12472
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
11692
12473
|
const choice = await gate.ask({
|
|
11693
12474
|
kind: "run_background",
|
|
11694
|
-
payload: { command: cmd, cwd
|
|
12475
|
+
payload: { command: cmd, cwd, waitSec: args.waitSec }
|
|
11695
12476
|
});
|
|
11696
12477
|
if (choice.type === "deny") {
|
|
11697
12478
|
throw new Error(
|
|
@@ -11703,10 +12484,11 @@ function registerShellTools(registry, opts) {
|
|
|
11703
12484
|
}
|
|
11704
12485
|
}
|
|
11705
12486
|
const result = await jobs.start(cmd, {
|
|
11706
|
-
cwd
|
|
12487
|
+
cwd,
|
|
11707
12488
|
waitSec: args.waitSec,
|
|
11708
12489
|
signal: ctx?.signal
|
|
11709
12490
|
});
|
|
12491
|
+
opts.onJobsChanged?.();
|
|
11710
12492
|
return formatJobStart(result);
|
|
11711
12493
|
}
|
|
11712
12494
|
});
|
|
@@ -11768,6 +12550,7 @@ function registerShellTools(registry, opts) {
|
|
|
11768
12550
|
waitFor: args.waitFor
|
|
11769
12551
|
});
|
|
11770
12552
|
if (!out) return `job ${args.jobId}: not found (use list_jobs)`;
|
|
12553
|
+
if (out.exited) opts.onJobsChanged?.();
|
|
11771
12554
|
return {
|
|
11772
12555
|
jobId: args.jobId,
|
|
11773
12556
|
exited: out.exited,
|
|
@@ -11788,6 +12571,7 @@ function registerShellTools(registry, opts) {
|
|
|
11788
12571
|
},
|
|
11789
12572
|
fn: async (args) => {
|
|
11790
12573
|
const rec = await jobs.stop(args.jobId);
|
|
12574
|
+
opts.onJobsChanged?.();
|
|
11791
12575
|
if (!rec) return `job ${args.jobId}: not found`;
|
|
11792
12576
|
return formatJobStop(rec);
|
|
11793
12577
|
}
|
|
@@ -11807,6 +12591,18 @@ function registerShellTools(registry, opts) {
|
|
|
11807
12591
|
});
|
|
11808
12592
|
return registry;
|
|
11809
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
|
+
}
|
|
11810
12606
|
function formatJobStart(r) {
|
|
11811
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]`;
|
|
11812
12608
|
return r.preview ? `${header}
|
|
@@ -11855,6 +12651,7 @@ var DEFAULT_TOPK = 5;
|
|
|
11855
12651
|
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
11856
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";
|
|
11857
12653
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
12654
|
+
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
11858
12655
|
function searchStatusError(status) {
|
|
11859
12656
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
11860
12657
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -11868,6 +12665,9 @@ function fetchStatusError(status, url) {
|
|
|
11868
12665
|
return t("webErrors.fetchStatus", { status, url });
|
|
11869
12666
|
}
|
|
11870
12667
|
async function webSearch(query, opts = {}) {
|
|
12668
|
+
if (opts.engine === "metaso") {
|
|
12669
|
+
return searchMetaso(query, opts);
|
|
12670
|
+
}
|
|
11871
12671
|
if (opts.engine === "searxng") {
|
|
11872
12672
|
return searchSearxng(query, opts);
|
|
11873
12673
|
}
|
|
@@ -11943,6 +12743,68 @@ async function searchSearxng(query, opts = {}) {
|
|
|
11943
12743
|
}
|
|
11944
12744
|
return results;
|
|
11945
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
|
+
}
|
|
11946
12808
|
function parseSearxngHtmlResults(html) {
|
|
11947
12809
|
const root = parseHtml(html);
|
|
11948
12810
|
const results = [];
|
|
@@ -12161,7 +13023,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
12161
13023
|
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
12162
13024
|
registry.register({
|
|
12163
13025
|
name: "web_search",
|
|
12164
|
-
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.",
|
|
12165
13027
|
readOnly: true,
|
|
12166
13028
|
parallelSafe: true,
|
|
12167
13029
|
parameters: {
|
|
@@ -12226,12 +13088,12 @@ ${i + 1}. ${r.title}`);
|
|
|
12226
13088
|
}
|
|
12227
13089
|
|
|
12228
13090
|
// src/env.ts
|
|
12229
|
-
import { readFileSync as
|
|
12230
|
-
import { resolve as
|
|
13091
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
13092
|
+
import { resolve as resolve11 } from "path";
|
|
12231
13093
|
function loadDotenv(path2 = ".env") {
|
|
12232
13094
|
let raw;
|
|
12233
13095
|
try {
|
|
12234
|
-
raw =
|
|
13096
|
+
raw = readFileSync11(resolve11(process.cwd(), path2), "utf8");
|
|
12235
13097
|
} catch {
|
|
12236
13098
|
return;
|
|
12237
13099
|
}
|
|
@@ -12250,7 +13112,7 @@ function loadDotenv(path2 = ".env") {
|
|
|
12250
13112
|
}
|
|
12251
13113
|
|
|
12252
13114
|
// src/transcript/log.ts
|
|
12253
|
-
import { createWriteStream, readFileSync as
|
|
13115
|
+
import { createWriteStream, readFileSync as readFileSync12 } from "fs";
|
|
12254
13116
|
function recordFromLoopEvent(ev, extra) {
|
|
12255
13117
|
const rec = {
|
|
12256
13118
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12293,7 +13155,7 @@ function openTranscriptFile(path2, meta) {
|
|
|
12293
13155
|
return stream;
|
|
12294
13156
|
}
|
|
12295
13157
|
function readTranscript(path2) {
|
|
12296
|
-
const raw =
|
|
13158
|
+
const raw = readFileSync12(path2, "utf8");
|
|
12297
13159
|
return parseTranscript(raw);
|
|
12298
13160
|
}
|
|
12299
13161
|
function parseTranscript(raw) {
|
|
@@ -12680,25 +13542,25 @@ function truncate(s, n) {
|
|
|
12680
13542
|
}
|
|
12681
13543
|
|
|
12682
13544
|
// src/version.ts
|
|
12683
|
-
import { existsSync as
|
|
12684
|
-
import { homedir as
|
|
12685
|
-
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";
|
|
12686
13548
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12687
13549
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
12688
13550
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12689
13551
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
12690
13552
|
function readPackageVersion() {
|
|
12691
13553
|
try {
|
|
12692
|
-
let dir =
|
|
13554
|
+
let dir = dirname7(fileURLToPath2(import.meta.url));
|
|
12693
13555
|
for (let i = 0; i < 6; i++) {
|
|
12694
|
-
const p =
|
|
12695
|
-
if (
|
|
12696
|
-
const pkg = JSON.parse(
|
|
13556
|
+
const p = join14(dir, "package.json");
|
|
13557
|
+
if (existsSync10(p)) {
|
|
13558
|
+
const pkg = JSON.parse(readFileSync13(p, "utf8"));
|
|
12697
13559
|
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
12698
13560
|
return pkg.version;
|
|
12699
13561
|
}
|
|
12700
13562
|
}
|
|
12701
|
-
const parent =
|
|
13563
|
+
const parent = dirname7(dir);
|
|
12702
13564
|
if (parent === dir) break;
|
|
12703
13565
|
dir = parent;
|
|
12704
13566
|
}
|
|
@@ -12708,11 +13570,11 @@ function readPackageVersion() {
|
|
|
12708
13570
|
}
|
|
12709
13571
|
var VERSION = readPackageVersion();
|
|
12710
13572
|
function cachePath(homeDirOverride) {
|
|
12711
|
-
return
|
|
13573
|
+
return join14(homeDirOverride ?? homedir7(), ".reasonix", "version-cache.json");
|
|
12712
13574
|
}
|
|
12713
13575
|
function readCache(homeDirOverride) {
|
|
12714
13576
|
try {
|
|
12715
|
-
const raw =
|
|
13577
|
+
const raw = readFileSync13(cachePath(homeDirOverride), "utf8");
|
|
12716
13578
|
const parsed = JSON.parse(raw);
|
|
12717
13579
|
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
12718
13580
|
return parsed;
|
|
@@ -12724,7 +13586,7 @@ function readCache(homeDirOverride) {
|
|
|
12724
13586
|
function writeCache(entry, homeDirOverride) {
|
|
12725
13587
|
try {
|
|
12726
13588
|
const p = cachePath(homeDirOverride);
|
|
12727
|
-
mkdirSync5(
|
|
13589
|
+
mkdirSync5(dirname7(p), { recursive: true });
|
|
12728
13590
|
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
12729
13591
|
} catch {
|
|
12730
13592
|
}
|
|
@@ -12936,7 +13798,7 @@ var McpClient = class {
|
|
|
12936
13798
|
const id = this.nextId++;
|
|
12937
13799
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
12938
13800
|
let abortHandler = null;
|
|
12939
|
-
const promise = new Promise((
|
|
13801
|
+
const promise = new Promise((resolve13, reject) => {
|
|
12940
13802
|
const timeout = setTimeout(() => {
|
|
12941
13803
|
this.pending.delete(id);
|
|
12942
13804
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -12945,7 +13807,7 @@ var McpClient = class {
|
|
|
12945
13807
|
);
|
|
12946
13808
|
}, this.requestTimeoutMs);
|
|
12947
13809
|
this.pending.set(id, {
|
|
12948
|
-
resolve:
|
|
13810
|
+
resolve: resolve13,
|
|
12949
13811
|
reject,
|
|
12950
13812
|
timeout
|
|
12951
13813
|
});
|
|
@@ -13075,12 +13937,12 @@ var StdioTransport = class {
|
|
|
13075
13937
|
}
|
|
13076
13938
|
async send(message) {
|
|
13077
13939
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
13078
|
-
return new Promise((
|
|
13940
|
+
return new Promise((resolve13, reject) => {
|
|
13079
13941
|
const line = `${JSON.stringify(message)}
|
|
13080
13942
|
`;
|
|
13081
13943
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
13082
13944
|
if (err) reject(err);
|
|
13083
|
-
else
|
|
13945
|
+
else resolve13();
|
|
13084
13946
|
});
|
|
13085
13947
|
});
|
|
13086
13948
|
}
|
|
@@ -13091,8 +13953,8 @@ var StdioTransport = class {
|
|
|
13091
13953
|
continue;
|
|
13092
13954
|
}
|
|
13093
13955
|
if (this.closed) return;
|
|
13094
|
-
const next = await new Promise((
|
|
13095
|
-
this.waiters.push(
|
|
13956
|
+
const next = await new Promise((resolve13) => {
|
|
13957
|
+
this.waiters.push(resolve13);
|
|
13096
13958
|
});
|
|
13097
13959
|
if (next === null) return;
|
|
13098
13960
|
yield next;
|
|
@@ -13165,8 +14027,8 @@ var SseTransport = class {
|
|
|
13165
14027
|
constructor(opts) {
|
|
13166
14028
|
this.url = opts.url;
|
|
13167
14029
|
this.headers = opts.headers ?? {};
|
|
13168
|
-
this.endpointReady = new Promise((
|
|
13169
|
-
this.resolveEndpoint =
|
|
14030
|
+
this.endpointReady = new Promise((resolve13, reject) => {
|
|
14031
|
+
this.resolveEndpoint = resolve13;
|
|
13170
14032
|
this.rejectEndpoint = reject;
|
|
13171
14033
|
});
|
|
13172
14034
|
this.endpointReady.catch(() => void 0);
|
|
@@ -13193,8 +14055,8 @@ var SseTransport = class {
|
|
|
13193
14055
|
continue;
|
|
13194
14056
|
}
|
|
13195
14057
|
if (this.closed) return;
|
|
13196
|
-
const next = await new Promise((
|
|
13197
|
-
this.waiters.push(
|
|
14058
|
+
const next = await new Promise((resolve13) => {
|
|
14059
|
+
this.waiters.push(resolve13);
|
|
13198
14060
|
});
|
|
13199
14061
|
if (next === null) return;
|
|
13200
14062
|
yield next;
|
|
@@ -13380,8 +14242,8 @@ var StreamableHttpTransport = class {
|
|
|
13380
14242
|
continue;
|
|
13381
14243
|
}
|
|
13382
14244
|
if (this.closed) return;
|
|
13383
|
-
const next = await new Promise((
|
|
13384
|
-
this.waiters.push(
|
|
14245
|
+
const next = await new Promise((resolve13) => {
|
|
14246
|
+
this.waiters.push(resolve13);
|
|
13385
14247
|
});
|
|
13386
14248
|
if (next === null) return;
|
|
13387
14249
|
yield next;
|
|
@@ -13439,85 +14301,6 @@ var StreamableHttpTransport = class {
|
|
|
13439
14301
|
}
|
|
13440
14302
|
};
|
|
13441
14303
|
|
|
13442
|
-
// src/mcp/shell-split.ts
|
|
13443
|
-
function shellSplit(input) {
|
|
13444
|
-
const tokens = [];
|
|
13445
|
-
let cur = "";
|
|
13446
|
-
let quote = null;
|
|
13447
|
-
let i = 0;
|
|
13448
|
-
const s = input;
|
|
13449
|
-
while (i < s.length) {
|
|
13450
|
-
const ch = s[i];
|
|
13451
|
-
if (quote) {
|
|
13452
|
-
if (ch === quote) {
|
|
13453
|
-
quote = null;
|
|
13454
|
-
i++;
|
|
13455
|
-
continue;
|
|
13456
|
-
}
|
|
13457
|
-
if (ch === "\\" && quote === '"' && i + 1 < s.length) {
|
|
13458
|
-
cur += s[i + 1];
|
|
13459
|
-
i += 2;
|
|
13460
|
-
continue;
|
|
13461
|
-
}
|
|
13462
|
-
cur += ch;
|
|
13463
|
-
i++;
|
|
13464
|
-
continue;
|
|
13465
|
-
}
|
|
13466
|
-
if (ch === '"' || ch === "'") {
|
|
13467
|
-
quote = ch;
|
|
13468
|
-
i++;
|
|
13469
|
-
continue;
|
|
13470
|
-
}
|
|
13471
|
-
if (ch === " " || ch === " ") {
|
|
13472
|
-
if (cur.length > 0) {
|
|
13473
|
-
tokens.push(cur);
|
|
13474
|
-
cur = "";
|
|
13475
|
-
}
|
|
13476
|
-
i++;
|
|
13477
|
-
continue;
|
|
13478
|
-
}
|
|
13479
|
-
cur += ch;
|
|
13480
|
-
i++;
|
|
13481
|
-
}
|
|
13482
|
-
if (quote) {
|
|
13483
|
-
throw new Error(
|
|
13484
|
-
`shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
|
|
13485
|
-
);
|
|
13486
|
-
}
|
|
13487
|
-
if (cur.length > 0) tokens.push(cur);
|
|
13488
|
-
return tokens;
|
|
13489
|
-
}
|
|
13490
|
-
|
|
13491
|
-
// src/mcp/spec.ts
|
|
13492
|
-
var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_-]*)=(.*)$/;
|
|
13493
|
-
var HTTP_URL = /^https?:\/\//i;
|
|
13494
|
-
var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
|
|
13495
|
-
function parseMcpSpec(input) {
|
|
13496
|
-
const trimmed = input.trim();
|
|
13497
|
-
if (!trimmed) {
|
|
13498
|
-
throw new Error("empty MCP spec");
|
|
13499
|
-
}
|
|
13500
|
-
const nameMatch = NAME_PREFIX.exec(trimmed);
|
|
13501
|
-
const name = nameMatch ? nameMatch[1] : null;
|
|
13502
|
-
const body = (nameMatch ? nameMatch[2] : trimmed).trim();
|
|
13503
|
-
if (!body) {
|
|
13504
|
-
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
13505
|
-
}
|
|
13506
|
-
const streamMatch = STREAMABLE_PREFIX.exec(body);
|
|
13507
|
-
if (streamMatch) {
|
|
13508
|
-
return { transport: "streamable-http", name, url: streamMatch[1] };
|
|
13509
|
-
}
|
|
13510
|
-
if (HTTP_URL.test(body)) {
|
|
13511
|
-
return { transport: "sse", name, url: body };
|
|
13512
|
-
}
|
|
13513
|
-
const argv = shellSplit(body);
|
|
13514
|
-
if (argv.length === 0) {
|
|
13515
|
-
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
13516
|
-
}
|
|
13517
|
-
const [command, ...args] = argv;
|
|
13518
|
-
return { transport: "stdio", name, command, args };
|
|
13519
|
-
}
|
|
13520
|
-
|
|
13521
14304
|
// src/mcp/inspect.ts
|
|
13522
14305
|
async function inspectMcpServer(client) {
|
|
13523
14306
|
const t0 = Date.now();
|
|
@@ -13553,18 +14336,18 @@ async function trySection(load) {
|
|
|
13553
14336
|
// src/code/edit-blocks.ts
|
|
13554
14337
|
import {
|
|
13555
14338
|
closeSync as closeSync2,
|
|
13556
|
-
existsSync as
|
|
14339
|
+
existsSync as existsSync11,
|
|
13557
14340
|
fstatSync,
|
|
13558
14341
|
ftruncateSync,
|
|
13559
14342
|
mkdirSync as mkdirSync6,
|
|
13560
14343
|
openSync as openSync2,
|
|
13561
|
-
readFileSync as
|
|
14344
|
+
readFileSync as readFileSync14,
|
|
13562
14345
|
readSync,
|
|
13563
14346
|
unlinkSync as unlinkSync3,
|
|
13564
14347
|
writeFileSync as writeFileSync6,
|
|
13565
14348
|
writeSync
|
|
13566
14349
|
} from "fs";
|
|
13567
|
-
import { dirname as
|
|
14350
|
+
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
13568
14351
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
13569
14352
|
function parseEditBlocks(text) {
|
|
13570
14353
|
const out = [];
|
|
@@ -13582,9 +14365,9 @@ function parseEditBlocks(text) {
|
|
|
13582
14365
|
return out;
|
|
13583
14366
|
}
|
|
13584
14367
|
function applyEditBlock(block, rootDir) {
|
|
13585
|
-
const absRoot =
|
|
13586
|
-
const absTarget =
|
|
13587
|
-
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()}`)) {
|
|
13588
14371
|
return {
|
|
13589
14372
|
path: block.path,
|
|
13590
14373
|
status: "path-escape",
|
|
@@ -13594,7 +14377,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
13594
14377
|
const searchEmpty = block.search.length === 0;
|
|
13595
14378
|
if (searchEmpty) {
|
|
13596
14379
|
try {
|
|
13597
|
-
mkdirSync6(
|
|
14380
|
+
mkdirSync6(dirname8(absTarget), { recursive: true });
|
|
13598
14381
|
const fd = openSync2(absTarget, "wx");
|
|
13599
14382
|
try {
|
|
13600
14383
|
writeSync(fd, block.replace);
|
|
@@ -13670,19 +14453,19 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
13670
14453
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
13671
14454
|
}
|
|
13672
14455
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
13673
|
-
const absRoot =
|
|
14456
|
+
const absRoot = resolve12(rootDir);
|
|
13674
14457
|
const seen = /* @__PURE__ */ new Set();
|
|
13675
14458
|
const snapshots = [];
|
|
13676
14459
|
for (const b of blocks) {
|
|
13677
14460
|
if (seen.has(b.path)) continue;
|
|
13678
14461
|
seen.add(b.path);
|
|
13679
|
-
const abs =
|
|
13680
|
-
if (!
|
|
14462
|
+
const abs = resolve12(absRoot, b.path);
|
|
14463
|
+
if (!existsSync11(abs)) {
|
|
13681
14464
|
snapshots.push({ path: b.path, prevContent: null });
|
|
13682
14465
|
continue;
|
|
13683
14466
|
}
|
|
13684
14467
|
try {
|
|
13685
|
-
snapshots.push({ path: b.path, prevContent:
|
|
14468
|
+
snapshots.push({ path: b.path, prevContent: readFileSync14(abs, "utf8") });
|
|
13686
14469
|
} catch {
|
|
13687
14470
|
snapshots.push({ path: b.path, prevContent: null });
|
|
13688
14471
|
}
|
|
@@ -13690,10 +14473,10 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
13690
14473
|
return snapshots;
|
|
13691
14474
|
}
|
|
13692
14475
|
function restoreSnapshots(snapshots, rootDir) {
|
|
13693
|
-
const absRoot =
|
|
14476
|
+
const absRoot = resolve12(rootDir);
|
|
13694
14477
|
return snapshots.map((snap) => {
|
|
13695
|
-
const abs =
|
|
13696
|
-
if (abs !== absRoot && !abs.startsWith(`${absRoot}${
|
|
14478
|
+
const abs = resolve12(absRoot, snap.path);
|
|
14479
|
+
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep2()}`)) {
|
|
13697
14480
|
return {
|
|
13698
14481
|
path: snap.path,
|
|
13699
14482
|
status: "path-escape",
|
|
@@ -13702,7 +14485,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
13702
14485
|
}
|
|
13703
14486
|
try {
|
|
13704
14487
|
if (snap.prevContent === null) {
|
|
13705
|
-
if (
|
|
14488
|
+
if (existsSync11(abs)) unlinkSync3(abs);
|
|
13706
14489
|
return {
|
|
13707
14490
|
path: snap.path,
|
|
13708
14491
|
status: "applied",
|
|
@@ -13720,7 +14503,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
13720
14503
|
}
|
|
13721
14504
|
});
|
|
13722
14505
|
}
|
|
13723
|
-
function
|
|
14506
|
+
function sep2() {
|
|
13724
14507
|
return process.platform === "win32" ? "\\" : "/";
|
|
13725
14508
|
}
|
|
13726
14509
|
function lineEndingOf(text) {
|
|
@@ -13728,8 +14511,8 @@ function lineEndingOf(text) {
|
|
|
13728
14511
|
}
|
|
13729
14512
|
|
|
13730
14513
|
// src/code/prompt.ts
|
|
13731
|
-
import { existsSync as
|
|
13732
|
-
import { join as
|
|
14514
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15 } from "fs";
|
|
14515
|
+
import { join as join15 } from "path";
|
|
13733
14516
|
var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
|
|
13734
14517
|
function codeSystemBase(modelId) {
|
|
13735
14518
|
return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
|
|
@@ -13976,12 +14759,12 @@ function codeSystemPrompt(rootDir, opts = {}) {
|
|
|
13976
14759
|
const codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
|
|
13977
14760
|
const base = opts.hasSemanticSearch ? `${codeBase}${SEMANTIC_SEARCH_ROUTING}` : codeBase;
|
|
13978
14761
|
const withMemory = applyMemoryStack(base, rootDir);
|
|
13979
|
-
const gitignorePath =
|
|
14762
|
+
const gitignorePath = join15(rootDir, ".gitignore");
|
|
13980
14763
|
let result = withMemory;
|
|
13981
|
-
if (
|
|
14764
|
+
if (existsSync12(gitignorePath)) {
|
|
13982
14765
|
let content;
|
|
13983
14766
|
try {
|
|
13984
|
-
content =
|
|
14767
|
+
content = readFileSync15(gitignorePath, "utf8");
|
|
13985
14768
|
} catch {
|
|
13986
14769
|
}
|
|
13987
14770
|
if (content !== void 0) {
|
|
@@ -14015,21 +14798,21 @@ ${appendParts.join("\n\n")}`;
|
|
|
14015
14798
|
import {
|
|
14016
14799
|
appendFileSync as appendFileSync2,
|
|
14017
14800
|
closeSync as closeSync3,
|
|
14018
|
-
existsSync as
|
|
14801
|
+
existsSync as existsSync13,
|
|
14019
14802
|
fstatSync as fstatSync2,
|
|
14020
14803
|
mkdirSync as mkdirSync7,
|
|
14021
14804
|
openSync as openSync3,
|
|
14022
|
-
readFileSync as
|
|
14805
|
+
readFileSync as readFileSync16,
|
|
14023
14806
|
readSync as readSync2,
|
|
14024
14807
|
renameSync as renameSync2,
|
|
14025
14808
|
statSync as statSync6,
|
|
14026
14809
|
unlinkSync as unlinkSync4,
|
|
14027
14810
|
writeFileSync as writeFileSync7
|
|
14028
14811
|
} from "fs";
|
|
14029
|
-
import { homedir as
|
|
14030
|
-
import { dirname as
|
|
14812
|
+
import { homedir as homedir8 } from "os";
|
|
14813
|
+
import { dirname as dirname9, join as join16 } from "path";
|
|
14031
14814
|
function defaultUsageLogPath(homeDirOverride) {
|
|
14032
|
-
return
|
|
14815
|
+
return join16(homeDirOverride ?? homedir8(), ".reasonix", "usage.jsonl");
|
|
14033
14816
|
}
|
|
14034
14817
|
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
14035
14818
|
var USAGE_RETENTION_DAYS = 365;
|
|
@@ -14094,7 +14877,7 @@ function appendUsage(input) {
|
|
|
14094
14877
|
if (input.subagent) record.subagent = input.subagent;
|
|
14095
14878
|
const path2 = input.path ?? defaultUsageLogPath();
|
|
14096
14879
|
try {
|
|
14097
|
-
mkdirSync7(
|
|
14880
|
+
mkdirSync7(dirname9(path2), { recursive: true });
|
|
14098
14881
|
appendFileSync2(path2, `${JSON.stringify(record)}
|
|
14099
14882
|
`, "utf8");
|
|
14100
14883
|
compactUsageLogIfLarge(path2, record.ts);
|
|
@@ -14103,10 +14886,10 @@ function appendUsage(input) {
|
|
|
14103
14886
|
return record;
|
|
14104
14887
|
}
|
|
14105
14888
|
function readUsageLog(path2 = defaultUsageLogPath()) {
|
|
14106
|
-
if (!
|
|
14889
|
+
if (!existsSync13(path2)) return [];
|
|
14107
14890
|
let raw;
|
|
14108
14891
|
try {
|
|
14109
|
-
raw =
|
|
14892
|
+
raw = readFileSync16(path2, "utf8");
|
|
14110
14893
|
} catch {
|
|
14111
14894
|
return [];
|
|
14112
14895
|
}
|
|
@@ -14213,7 +14996,7 @@ function aggregateUsage(records, opts = {}) {
|
|
|
14213
14996
|
};
|
|
14214
14997
|
}
|
|
14215
14998
|
function formatLogSize(path2 = defaultUsageLogPath()) {
|
|
14216
|
-
if (!
|
|
14999
|
+
if (!existsSync13(path2)) return "";
|
|
14217
15000
|
try {
|
|
14218
15001
|
const s = statSync6(path2);
|
|
14219
15002
|
const bytes = s.size;
|
|
@@ -14236,6 +15019,7 @@ export {
|
|
|
14236
15019
|
DEFAULT_MAX_RESULT_CHARS,
|
|
14237
15020
|
DEFAULT_MAX_RESULT_TOKENS,
|
|
14238
15021
|
DEFAULT_PICKER_IGNORE_DIRS,
|
|
15022
|
+
DEFAULT_SPAWN_STORM_THRESHOLD,
|
|
14239
15023
|
DeepSeekClient,
|
|
14240
15024
|
HOOK_EVENTS,
|
|
14241
15025
|
HOOK_SETTINGS_DIRNAME,
|
|
@@ -14259,6 +15043,7 @@ export {
|
|
|
14259
15043
|
StdioTransport,
|
|
14260
15044
|
StormBreaker,
|
|
14261
15045
|
StreamableHttpTransport,
|
|
15046
|
+
SubagentTelemetry,
|
|
14262
15047
|
ToolCallRepair,
|
|
14263
15048
|
ToolRegistry,
|
|
14264
15049
|
USER_MEMORY_DIR,
|
|
@@ -14281,7 +15066,9 @@ export {
|
|
|
14281
15066
|
codeSystemPrompt,
|
|
14282
15067
|
compareVersions,
|
|
14283
15068
|
computeReplayStats,
|
|
15069
|
+
computeSpawnDistillation,
|
|
14284
15070
|
costUsd,
|
|
15071
|
+
countSpawnStorms,
|
|
14285
15072
|
decideOutcome,
|
|
14286
15073
|
defaultConfigPath,
|
|
14287
15074
|
defaultUsageLogPath,
|
|
@@ -14324,6 +15111,7 @@ export {
|
|
|
14324
15111
|
loadBaseUrl,
|
|
14325
15112
|
loadDotenv,
|
|
14326
15113
|
loadHooks,
|
|
15114
|
+
loadMetasoApiKey,
|
|
14327
15115
|
loadSessionMessages,
|
|
14328
15116
|
matchesTool,
|
|
14329
15117
|
memoryEnabled,
|
|
@@ -14374,6 +15162,7 @@ export {
|
|
|
14374
15162
|
similarity,
|
|
14375
15163
|
snapshotBeforeEdits,
|
|
14376
15164
|
stripHallucinatedToolMarkup,
|
|
15165
|
+
summarizeSubagentSession,
|
|
14377
15166
|
tokenizeCommand,
|
|
14378
15167
|
truncateForModel,
|
|
14379
15168
|
truncateForModelByTokens,
|