reasonix 0.40.0 → 0.43.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 +47 -16
- package/README.zh-CN.md +19 -13
- package/dashboard/app.css +8 -4
- package/dashboard/dist/app.js +377 -227
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/acp-DAGPCVFZ.js +713 -0
- package/dist/cli/acp-DAGPCVFZ.js.map +1 -0
- package/dist/cli/chat-7ES4IBNH.js +50 -0
- package/dist/cli/{chunk-E46ECXJD.js → chunk-2425HK6U.js} +2 -1
- package/dist/cli/{chunk-E46ECXJD.js.map → chunk-2425HK6U.js.map} +1 -1
- package/dist/cli/chunk-25T6CVUP.js +172 -0
- package/dist/cli/chunk-25T6CVUP.js.map +1 -0
- package/dist/cli/{chunk-7DLHHBGN.js → chunk-2K65GZBT.js} +16 -5
- package/dist/cli/chunk-2K65GZBT.js.map +1 -0
- package/dist/cli/{chunk-KMWKGPFZ.js → chunk-2KDUS647.js} +14 -4
- package/dist/cli/chunk-2KDUS647.js.map +1 -0
- package/dist/cli/chunk-2R4QCDOZ.js +11392 -0
- package/dist/cli/chunk-2R4QCDOZ.js.map +1 -0
- package/dist/cli/{chunk-3Q3C4W66.js → chunk-2UQP6H6T.js} +2 -1
- package/dist/cli/{chunk-3Q3C4W66.js.map → chunk-2UQP6H6T.js.map} +1 -1
- package/dist/cli/chunk-2Z35JOA4.js +96 -0
- package/dist/cli/chunk-2Z35JOA4.js.map +1 -0
- package/dist/cli/chunk-32TIKD5U.js +54 -0
- package/dist/cli/{chunk-JWCTX5S4.js.map → chunk-32TIKD5U.js.map} +1 -1
- package/dist/cli/{chunk-UVRXTSK3.js → chunk-3BXRZFWS.js} +65 -3
- package/dist/cli/chunk-3BXRZFWS.js.map +1 -0
- package/dist/cli/chunk-3Z6IBU3D.js +249 -0
- package/dist/cli/chunk-3Z6IBU3D.js.map +1 -0
- package/dist/cli/{chunk-VLNRQMCI.js → chunk-45U62RI3.js} +12 -5
- package/dist/cli/chunk-45U62RI3.js.map +1 -0
- package/dist/cli/{chunk-5GKJLNP2.js → chunk-4QUNBQQ2.js} +3 -2
- package/dist/cli/{chunk-5GKJLNP2.js.map → chunk-4QUNBQQ2.js.map} +1 -1
- package/dist/cli/{chunk-R4YTW7PR.js → chunk-5JJRUIPA.js} +57 -12
- package/dist/cli/chunk-5JJRUIPA.js.map +1 -0
- package/dist/cli/{chunk-HCC42PEI.js → chunk-6AK4EY3D.js} +12 -6
- package/dist/cli/chunk-6AK4EY3D.js.map +1 -0
- package/dist/cli/chunk-6G3CUUFG.js +34320 -0
- package/dist/cli/chunk-6G3CUUFG.js.map +1 -0
- package/dist/cli/{chunk-XST7BSZJ.js → chunk-6PBZN4VI.js} +21 -3
- package/dist/cli/chunk-6PBZN4VI.js.map +1 -0
- package/dist/cli/{chunk-A5LSGEEK.js → chunk-6PZ3CXBP.js} +88 -66
- package/dist/cli/chunk-6PZ3CXBP.js.map +1 -0
- package/dist/cli/chunk-74EX7SUH.js +25293 -0
- package/dist/cli/chunk-74EX7SUH.js.map +1 -0
- package/dist/cli/{chunk-FFNOMR32.js → chunk-7O5ALB4C.js} +3 -2
- package/dist/cli/{chunk-FFNOMR32.js.map → chunk-7O5ALB4C.js.map} +1 -1
- package/dist/cli/{chunk-UCMTWZKU.js → chunk-DOYHN4KB.js} +3 -2
- package/dist/cli/{chunk-UCMTWZKU.js.map → chunk-DOYHN4KB.js.map} +1 -1
- package/dist/cli/{chunk-XJLZ4HKU.js → chunk-F3PXYSNN.js} +3 -2
- package/dist/cli/{chunk-XJLZ4HKU.js.map → chunk-F3PXYSNN.js.map} +1 -1
- package/dist/cli/{chunk-XHQIK7B6.js → chunk-FHOGSSCH.js} +4 -3
- package/dist/cli/{chunk-XHQIK7B6.js.map → chunk-FHOGSSCH.js.map} +1 -1
- package/dist/cli/{chunk-IYF36OCJ.js → chunk-H6PS7IUE.js} +3 -2
- package/dist/cli/{chunk-IYF36OCJ.js.map → chunk-H6PS7IUE.js.map} +1 -1
- package/dist/cli/{chunk-ZTLZO42A.js → chunk-HFEAY5DT.js} +3 -2
- package/dist/cli/{chunk-ZTLZO42A.js.map → chunk-HFEAY5DT.js.map} +1 -1
- package/dist/cli/{chunk-FWGEHRB7.js → chunk-J5XJHLWM.js} +2 -1
- package/dist/cli/{chunk-FWGEHRB7.js.map → chunk-J5XJHLWM.js.map} +1 -1
- package/dist/cli/chunk-JMBMLOBP.js +26 -0
- package/dist/cli/chunk-JMBMLOBP.js.map +1 -0
- package/dist/cli/{chunk-SZH34P45.js → chunk-O52OLQL3.js} +52 -18
- package/dist/cli/chunk-O52OLQL3.js.map +1 -0
- package/dist/cli/{chunk-4DCHFFEY.js → chunk-OSZC7C6F.js} +3 -2
- package/dist/cli/{chunk-4DCHFFEY.js.map → chunk-OSZC7C6F.js.map} +1 -1
- package/dist/cli/chunk-P7EKE5ZQ.js +60641 -0
- package/dist/cli/chunk-P7EKE5ZQ.js.map +1 -0
- package/dist/cli/{chunk-FM57FNPJ.js → chunk-PLHAZOLZ.js} +2 -1
- package/dist/cli/{chunk-FM57FNPJ.js.map → chunk-PLHAZOLZ.js.map} +1 -1
- package/dist/cli/{chunk-RFX7TYVV.js → chunk-PQXPXJBJ.js} +16 -2
- package/dist/cli/chunk-PQXPXJBJ.js.map +1 -0
- package/dist/cli/{chunk-DAEAAVDF.js → chunk-PV55UMTO.js} +2 -1
- package/dist/cli/{chunk-DAEAAVDF.js.map → chunk-PV55UMTO.js.map} +1 -1
- package/dist/cli/{chunk-H7PHYVPM.js → chunk-RE4RAVFF.js} +85 -14
- package/dist/cli/chunk-RE4RAVFF.js.map +1 -0
- package/dist/cli/chunk-S4XVGLRW.js +499 -0
- package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
- package/dist/cli/{chunk-WJ3YX4PZ.js → chunk-SZ5XES2N.js} +3 -2
- package/dist/cli/{chunk-WJ3YX4PZ.js.map → chunk-SZ5XES2N.js.map} +1 -1
- package/dist/cli/{chunk-4X3NY5ZM.js → chunk-TJX6BFZZ.js} +16 -9
- package/dist/cli/{chunk-4X3NY5ZM.js.map → chunk-TJX6BFZZ.js.map} +1 -1
- package/dist/cli/chunk-TUK7OWJA.js +51 -0
- package/dist/cli/{chunk-WKOMCPXP.js → chunk-VK5HG73G.js} +26 -17
- package/dist/cli/chunk-VK5HG73G.js.map +1 -0
- package/dist/cli/{chunk-CLAN6PVH.js → chunk-XCGGEJTI.js} +21 -8
- package/dist/cli/chunk-XCGGEJTI.js.map +1 -0
- package/dist/cli/{chunk-SOZE7V7V.js → chunk-XJXDHAES.js} +3 -2
- package/dist/cli/{chunk-SOZE7V7V.js.map → chunk-XJXDHAES.js.map} +1 -1
- package/dist/cli/chunk-XPDVG52A.js +2648 -0
- package/dist/cli/chunk-XPDVG52A.js.map +1 -0
- package/dist/cli/{chunk-CRPQUBP6.js → chunk-XXC2BYTV.js} +2 -1
- package/dist/cli/{chunk-CRPQUBP6.js.map → chunk-XXC2BYTV.js.map} +1 -1
- package/dist/cli/{chunk-AVB3WZWU.js → chunk-YFGF5NKA.js} +17 -14
- package/dist/cli/{chunk-AVB3WZWU.js.map → chunk-YFGF5NKA.js.map} +1 -1
- package/dist/cli/{chunk-ORM6PK57.js → chunk-YQ6NTIIE.js} +2 -1
- package/dist/cli/{chunk-ORM6PK57.js.map → chunk-YQ6NTIIE.js.map} +1 -1
- package/dist/cli/{chunk-ULBW7DYL.js → chunk-YYQAUTTN.js} +3 -2
- package/dist/cli/{chunk-ULBW7DYL.js.map → chunk-YYQAUTTN.js.map} +1 -1
- package/dist/cli/chunk-ZZM6QJ4W.js +109 -0
- package/dist/cli/chunk-ZZM6QJ4W.js.map +1 -0
- package/dist/cli/code-SMKEW6CD.js +154 -0
- package/dist/cli/code-SMKEW6CD.js.map +1 -0
- package/dist/cli/{commands-FQZOBLLZ.js → commands-FVVB5FZF.js} +7 -5
- package/dist/cli/{commands-FQZOBLLZ.js.map → commands-FVVB5FZF.js.map} +1 -1
- package/dist/cli/{commit-ZS24SHPG.js → commit-HE4VSPZ7.js} +7 -4
- package/dist/cli/{commit-ZS24SHPG.js.map → commit-HE4VSPZ7.js.map} +1 -1
- package/dist/cli/{desktop-6OLENOOO.js → desktop-Q7NDXCON.js} +379 -72
- package/dist/cli/desktop-Q7NDXCON.js.map +1 -0
- package/dist/cli/devtools-YECO25QO.js +3719 -0
- package/dist/cli/devtools-YECO25QO.js.map +1 -0
- package/dist/cli/diff-435UTPC5.js +165 -0
- package/dist/cli/{diff-2VUKNGEI.js.map → diff-435UTPC5.js.map} +1 -1
- package/dist/cli/doctor-OT7KH75K.js +27 -0
- package/dist/cli/{events-APSVNROZ.js → events-XEFAD5VX.js} +6 -4
- package/dist/cli/{events-APSVNROZ.js.map → events-XEFAD5VX.js.map} +1 -1
- package/dist/cli/index.js +3233 -123
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-DCKOE5RF.js → mcp-WUL2WO75.js} +6 -4
- package/dist/cli/{mcp-DCKOE5RF.js.map → mcp-WUL2WO75.js.map} +1 -1
- package/dist/cli/{mcp-browse-D6GBP5RQ.js → mcp-browse-RR7R4XET.js} +34 -19
- package/dist/cli/mcp-browse-RR7R4XET.js.map +1 -0
- package/dist/cli/{mcp-inspect-KFGFPJ3E.js → mcp-inspect-REGLYBWT.js} +9 -8
- package/dist/cli/{mcp-inspect-KFGFPJ3E.js.map → mcp-inspect-REGLYBWT.js.map} +1 -1
- package/dist/cli/package.json +3 -0
- package/dist/cli/prompt-UW6EFLVR.js +16 -0
- package/dist/cli/{prune-sessions-LV33R47N.js → prune-sessions-3RWUBYRS.js} +4 -2
- package/dist/cli/{prune-sessions-LV33R47N.js.map → prune-sessions-3RWUBYRS.js.map} +1 -1
- package/dist/cli/{replay-WFCYX7XF.js → replay-YOURXV4C.js} +42 -30
- package/dist/cli/{replay-WFCYX7XF.js.map → replay-YOURXV4C.js.map} +1 -1
- package/dist/cli/{run-IUJYEPMT.js → run-Q6BUXV66.js} +28 -27
- package/dist/cli/{run-IUJYEPMT.js.map → run-Q6BUXV66.js.map} +1 -1
- package/dist/cli/{server-CN4QPPVJ.js → server-XGDBRWMB.js} +44 -43
- package/dist/cli/server-XGDBRWMB.js.map +1 -0
- package/dist/cli/{sessions-F5GPGTJN.js → sessions-FH7QVYSY.js} +22 -19
- package/dist/cli/{sessions-F5GPGTJN.js.map → sessions-FH7QVYSY.js.map} +1 -1
- package/dist/cli/setup-VDS6SVEP.js +618 -0
- package/dist/cli/setup-VDS6SVEP.js.map +1 -0
- package/dist/cli/stats-MQVI2XQH.js +14 -0
- package/dist/cli/update-6ITLPRDV.js +15 -0
- package/dist/cli/update-6ITLPRDV.js.map +1 -0
- package/dist/cli/version-DAHGZY5N.js +33 -0
- package/dist/cli/{version-KQUPV6T5.js.map → version-DAHGZY5N.js.map} +1 -1
- package/dist/index.d.ts +157 -103
- package/dist/index.js +597 -178
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/cli/chat-G7CUW4ZI.js +0 -45
- package/dist/cli/chunk-26UDIXLD.js +0 -16481
- package/dist/cli/chunk-26UDIXLD.js.map +0 -1
- package/dist/cli/chunk-4YV2GBYG.js +0 -5237
- package/dist/cli/chunk-4YV2GBYG.js.map +0 -1
- package/dist/cli/chunk-5X7LZJDE.js +0 -36
- package/dist/cli/chunk-5X7LZJDE.js.map +0 -1
- package/dist/cli/chunk-7DLHHBGN.js.map +0 -1
- package/dist/cli/chunk-A5LSGEEK.js.map +0 -1
- package/dist/cli/chunk-AFFZF3MW.js +0 -36
- package/dist/cli/chunk-AFFZF3MW.js.map +0 -1
- package/dist/cli/chunk-CLAN6PVH.js.map +0 -1
- package/dist/cli/chunk-CPOV2O73.js +0 -39
- package/dist/cli/chunk-CPOV2O73.js.map +0 -1
- package/dist/cli/chunk-CPTZ5OHX.js +0 -18
- package/dist/cli/chunk-CPTZ5OHX.js.map +0 -1
- package/dist/cli/chunk-CZSJILQP.js +0 -854
- package/dist/cli/chunk-CZSJILQP.js.map +0 -1
- package/dist/cli/chunk-H7PHYVPM.js.map +0 -1
- package/dist/cli/chunk-HCC42PEI.js.map +0 -1
- package/dist/cli/chunk-JWCTX5S4.js +0 -46
- package/dist/cli/chunk-KMWKGPFZ.js.map +0 -1
- package/dist/cli/chunk-MRLXEMZ7.js +0 -26
- package/dist/cli/chunk-MRLXEMZ7.js.map +0 -1
- package/dist/cli/chunk-R4YTW7PR.js.map +0 -1
- package/dist/cli/chunk-RFX7TYVV.js.map +0 -1
- package/dist/cli/chunk-SZH34P45.js.map +0 -1
- package/dist/cli/chunk-UVRXTSK3.js.map +0 -1
- package/dist/cli/chunk-VLNRQMCI.js.map +0 -1
- package/dist/cli/chunk-WKOMCPXP.js.map +0 -1
- package/dist/cli/chunk-XST7BSZJ.js.map +0 -1
- package/dist/cli/code-YQGVLIT2.js +0 -147
- package/dist/cli/code-YQGVLIT2.js.map +0 -1
- package/dist/cli/desktop-6OLENOOO.js.map +0 -1
- package/dist/cli/diff-2VUKNGEI.js +0 -153
- package/dist/cli/doctor-JO2WNN6C.js +0 -24
- package/dist/cli/mcp-browse-D6GBP5RQ.js.map +0 -1
- package/dist/cli/prompt-PKCCLLAD.js +0 -13
- package/dist/cli/server-CN4QPPVJ.js.map +0 -1
- package/dist/cli/setup-WWMDBPSB.js +0 -516
- package/dist/cli/setup-WWMDBPSB.js.map +0 -1
- package/dist/cli/stats-5RJCATCE.js +0 -12
- package/dist/cli/update-GUCWB4UN.js +0 -13
- package/dist/cli/version-KQUPV6T5.js +0 -30
- /package/dist/cli/{chat-G7CUW4ZI.js.map → chat-7ES4IBNH.js.map} +0 -0
- /package/dist/cli/{doctor-JO2WNN6C.js.map → chunk-TUK7OWJA.js.map} +0 -0
- /package/dist/cli/{prompt-PKCCLLAD.js.map → doctor-OT7KH75K.js.map} +0 -0
- /package/dist/cli/{stats-5RJCATCE.js.map → prompt-UW6EFLVR.js.map} +0 -0
- /package/dist/cli/{update-GUCWB4UN.js.map → stats-MQVI2XQH.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -773,6 +773,42 @@ var DEFAULT_INDEX_EXCLUDES = {
|
|
|
773
773
|
var DEFAULT_MAX_FILE_BYTES = 256 * 1024;
|
|
774
774
|
|
|
775
775
|
// src/config.ts
|
|
776
|
+
var BUILTIN_TYPE_DOCS = {
|
|
777
|
+
user: "role / skills / preferences",
|
|
778
|
+
feedback: "corrections or confirmed approaches",
|
|
779
|
+
project: "facts / decisions about the current work",
|
|
780
|
+
reference: "pointers to external systems the user uses"
|
|
781
|
+
};
|
|
782
|
+
function loadMemoryTypeRegistry(cfg = readConfig()) {
|
|
783
|
+
const out = [];
|
|
784
|
+
for (const name of ["user", "feedback", "project", "reference"]) {
|
|
785
|
+
out.push({ name, builtin: true, description: BUILTIN_TYPE_DOCS[name] });
|
|
786
|
+
}
|
|
787
|
+
const seen = new Set(out.map((e) => e.name));
|
|
788
|
+
for (const raw of cfg.memory?.customTypes ?? []) {
|
|
789
|
+
if (!raw || typeof raw.name !== "string") continue;
|
|
790
|
+
const name = raw.name.trim();
|
|
791
|
+
if (!name || !/^[a-zA-Z][a-zA-Z0-9_-]{0,31}$/.test(name)) continue;
|
|
792
|
+
if (seen.has(name)) continue;
|
|
793
|
+
seen.add(name);
|
|
794
|
+
const entry = { name, builtin: false };
|
|
795
|
+
if (typeof raw.description === "string") entry.description = raw.description;
|
|
796
|
+
if (raw.priority === "low" || raw.priority === "medium" || raw.priority === "high") {
|
|
797
|
+
entry.priority = raw.priority;
|
|
798
|
+
}
|
|
799
|
+
if (raw.expires === "project_end") entry.expires = raw.expires;
|
|
800
|
+
out.push(entry);
|
|
801
|
+
}
|
|
802
|
+
return out;
|
|
803
|
+
}
|
|
804
|
+
function memoryTypeDefaults(typeName, cfg = readConfig()) {
|
|
805
|
+
const found = loadMemoryTypeRegistry(cfg).find((e) => e.name === typeName);
|
|
806
|
+
if (!found) return {};
|
|
807
|
+
const out = {};
|
|
808
|
+
if (found.priority) out.priority = found.priority;
|
|
809
|
+
if (found.expires) out.expires = found.expires;
|
|
810
|
+
return out;
|
|
811
|
+
}
|
|
776
812
|
function defaultConfigPath() {
|
|
777
813
|
return join(homedir(), ".reasonix", "config.json");
|
|
778
814
|
}
|
|
@@ -891,7 +927,10 @@ var EN = {
|
|
|
891
927
|
cancel: "Cancel",
|
|
892
928
|
confirm: "Confirm",
|
|
893
929
|
back: "Back",
|
|
894
|
-
next: "Next"
|
|
930
|
+
next: "Next",
|
|
931
|
+
tool: "tool",
|
|
932
|
+
running: "running",
|
|
933
|
+
noTurns: "(no turns yet)"
|
|
895
934
|
},
|
|
896
935
|
cli: {
|
|
897
936
|
description: "DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.",
|
|
@@ -927,6 +966,7 @@ var EN = {
|
|
|
927
966
|
applied: "applied",
|
|
928
967
|
rejected: "rejected",
|
|
929
968
|
noDashboard: "Suppress the auto-launched embedded web dashboard.",
|
|
969
|
+
openDashboardHint: "Open the dashboard URL in your default browser as soon as the server is ready. No-op when --no-dashboard is set.",
|
|
930
970
|
dashboardPortHint: "Pin the dashboard to a fixed port (1\u201365535). Stable across restarts \u2014 required for SSH tunnels. Default: ephemeral.",
|
|
931
971
|
dashboardPortInvalid: "\u25B2 ignoring --dashboard-port={value} (must be an integer 1\u201365535) \u2014 falling back to ephemeral",
|
|
932
972
|
dashboardAutoStartFailed: "\u25B2 dashboard auto-start failed ({reason}) \u2014 try /dashboard, or pass --no-dashboard to silence",
|
|
@@ -1091,7 +1131,13 @@ var EN = {
|
|
|
1091
1131
|
jsonHintCatalog: "output as JSON",
|
|
1092
1132
|
jsonHintReport: "output the inspection report as JSON",
|
|
1093
1133
|
modelOverrideFlash: "override the model (default: deepseek-v4-flash)",
|
|
1094
|
-
skipConfirmHint: "skip the confirmation prompt"
|
|
1134
|
+
skipConfirmHint: "skip the confirmation prompt",
|
|
1135
|
+
yoloHint: "auto-approve plan checkpoints for this invocation (equivalent to editMode=yolo without mutating config)"
|
|
1136
|
+
},
|
|
1137
|
+
code: {
|
|
1138
|
+
workspaceConflict: "\u26A0 workspace contains another agent platform's files ({platforms}). Reasonix Code may read them as project content; relaunch with --dir <your-project> if that's not what you want.\n",
|
|
1139
|
+
systemAppendEmpty: "--system-append is empty \u2014 no prompt text will be appended\n",
|
|
1140
|
+
systemAppendFileReadError: 'Error: cannot read --system-append-file "{filePath}": {errorDetails}\n'
|
|
1095
1141
|
},
|
|
1096
1142
|
slash: {
|
|
1097
1143
|
help: { description: "show the full command reference" },
|
|
@@ -1237,6 +1283,14 @@ var EN = {
|
|
|
1237
1283
|
logs: {
|
|
1238
1284
|
description: "tail a background job's output (default last 80 lines)",
|
|
1239
1285
|
argsHint: "<id> [lines]"
|
|
1286
|
+
},
|
|
1287
|
+
btw: {
|
|
1288
|
+
description: "ask a quick side question \u2014 answered from a blank slate, never added to the conversation context",
|
|
1289
|
+
argsHint: "<question>"
|
|
1290
|
+
},
|
|
1291
|
+
"search-engine": {
|
|
1292
|
+
description: "switch web search backend \u2014 mojeek (default, no deps) or searxng (self-hosted)",
|
|
1293
|
+
argsHint: "<mojeek|searxng> [<endpoint>]"
|
|
1240
1294
|
}
|
|
1241
1295
|
},
|
|
1242
1296
|
wizard: {
|
|
@@ -1363,9 +1417,18 @@ var EN = {
|
|
|
1363
1417
|
counterDone: "{done}/{total} done ({pct}%) \xB7 {total} steps",
|
|
1364
1418
|
counterDoneSingular: "{done}/{total} done ({pct}%) \xB7 {total} step"
|
|
1365
1419
|
},
|
|
1420
|
+
noPlanSummary: "No plan body submitted yet.",
|
|
1421
|
+
detailCollapsedHint: "Ctrl+P expands full plan details.",
|
|
1422
|
+
detailExpandedHint: "Ctrl+P collapses details.",
|
|
1423
|
+
detailHeader: "Plan details",
|
|
1424
|
+
detailWindow: "showing lines {start}-{end} of {total}",
|
|
1425
|
+
detailScrollHint: "PgUp/PgDn scroll details \xB7 Home/End jump",
|
|
1366
1426
|
reviseTitle: "Revise plan",
|
|
1367
1427
|
reviseSteps: "{count} steps",
|
|
1368
|
-
reviseFooter: "\u2191\u2193 focus \xB7 space toggle skip \xB7 k/j move \xB7 \u23CE accept \xB7 esc cancel"
|
|
1428
|
+
reviseFooter: "\u2191\u2193 focus \xB7 space toggle skip \xB7 k/j move \xB7 \u23CE accept \xB7 esc cancel",
|
|
1429
|
+
riskMed: " med",
|
|
1430
|
+
riskHigh: " high",
|
|
1431
|
+
completeMsg: "\u25B8 plan complete \u2014 all {total} step{s} done \xB7 archived"
|
|
1369
1432
|
},
|
|
1370
1433
|
app: {
|
|
1371
1434
|
walkCancelledRemaining: "\u25B8 walk cancelled \u2014 {count} block(s) still pending.",
|
|
@@ -1385,6 +1448,9 @@ var EN = {
|
|
|
1385
1448
|
notedVerbAppended: "appended to",
|
|
1386
1449
|
memoryWriteFailed: "# memory write failed",
|
|
1387
1450
|
commandFailed: "! command failed",
|
|
1451
|
+
btwUsage: "\u25B8 /btw <question> \u2014 ask a side question without polluting the conversation context.",
|
|
1452
|
+
btwHeader: "\u226B btw",
|
|
1453
|
+
btwFailed: "/btw failed",
|
|
1388
1454
|
restoreCodeOnly: "\u25B8 /restore is code-mode only",
|
|
1389
1455
|
hookUserPromptSubmit: "UserPromptSubmit hook",
|
|
1390
1456
|
hookStop: "Stop hook",
|
|
@@ -1462,6 +1528,7 @@ var EN = {
|
|
|
1462
1528
|
basic: {
|
|
1463
1529
|
newInfo: "\u25B8 new conversation \u2014 dropped {count} message(s) from context. Same session, fresh slate.",
|
|
1464
1530
|
newInfoArchived: '\u25B8 new conversation \u2014 dropped {count} message(s) from context. Prior transcript archived as "{archived}" (visible under Sessions).',
|
|
1531
|
+
newInfoSystemReloaded: " \xB7 REASONIX.md / project memory reloaded (next turn pays one cache miss)",
|
|
1465
1532
|
helpTitle: "Commands:",
|
|
1466
1533
|
helpShellTitle: "Shell shortcut:",
|
|
1467
1534
|
helpShell: " !<cmd> run <cmd> in the sandbox root; output goes into",
|
|
@@ -1806,7 +1873,8 @@ var EN = {
|
|
|
1806
1873
|
mb: " MB",
|
|
1807
1874
|
evt: " evt",
|
|
1808
1875
|
editsLabel: "edits:",
|
|
1809
|
-
mcpLoading: "MCP"
|
|
1876
|
+
mcpLoading: "MCP",
|
|
1877
|
+
ctx: "ctx"
|
|
1810
1878
|
},
|
|
1811
1879
|
editMode: {
|
|
1812
1880
|
plan: "PLAN MODE",
|
|
@@ -2113,7 +2181,8 @@ var EN = {
|
|
|
2113
2181
|
healthy: "healthy \xB7 {ms}ms",
|
|
2114
2182
|
slow: "slow \xB7 {ms}ms",
|
|
2115
2183
|
verySlow: "very slow \xB7 {ms}ms",
|
|
2116
|
-
slowToast: "\u26A0 MCP `{name}` slow \xB7 {seconds}s p95 over the last {sampleSize} calls"
|
|
2184
|
+
slowToast: "\u26A0 MCP `{name}` slow \xB7 {seconds}s p95 over the last {sampleSize} calls",
|
|
2185
|
+
emptyHint: "\u2139 no MCP servers configured \u2014 try: `reasonix setup` to re-pick, or `reasonix mcp install filesystem`"
|
|
2117
2186
|
},
|
|
2118
2187
|
denyContextInput: {
|
|
2119
2188
|
description: "Tell the agent why you denied this. The next attempt will see your reason as additional context."
|
|
@@ -2178,7 +2247,9 @@ var EN = {
|
|
|
2178
2247
|
reconnect: "reconnect\u2026",
|
|
2179
2248
|
initDetail: "initialise \u2192 tools/list \u2192 resources/list",
|
|
2180
2249
|
reconnectDetail: "tearing down \xB7 re-handshake \xB7 listing tools",
|
|
2181
|
-
disabledDetail: "via /mcp disable {name}"
|
|
2250
|
+
disabledDetail: "via /mcp disable {name}",
|
|
2251
|
+
failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
|
|
2252
|
+
failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config."
|
|
2182
2253
|
},
|
|
2183
2254
|
checkpointPicker: {
|
|
2184
2255
|
title: "restore a checkpoint \u2014 {workspace}",
|
|
@@ -2237,7 +2308,10 @@ var zhCN = {
|
|
|
2237
2308
|
cancel: "\u53D6\u6D88",
|
|
2238
2309
|
confirm: "\u786E\u8BA4",
|
|
2239
2310
|
back: "\u8FD4\u56DE",
|
|
2240
|
-
next: "\u4E0B\u4E00\u6B65"
|
|
2311
|
+
next: "\u4E0B\u4E00\u6B65",
|
|
2312
|
+
tool: "\u5DE5\u5177",
|
|
2313
|
+
running: "\u8FD0\u884C\u4E2D",
|
|
2314
|
+
noTurns: "(\u6682\u65E0\u5BF9\u8BDD)"
|
|
2241
2315
|
},
|
|
2242
2316
|
cli: {
|
|
2243
2317
|
description: "DeepSeek \u539F\u751F\u667A\u80FD\u4F53\u6846\u67B6 \u2014 \u4E13\u4E3A\u7F13\u5B58\u547D\u4E2D\u548C\u4F4E\u6210\u672C\u4EE4\u724C\u6784\u5EFA\u3002",
|
|
@@ -2273,6 +2347,7 @@ var zhCN = {
|
|
|
2273
2347
|
applied: "\u5DF2\u5E94\u7528",
|
|
2274
2348
|
rejected: "\u5DF2\u62D2\u7EDD",
|
|
2275
2349
|
noDashboard: "\u7981\u6B62\u81EA\u52A8\u542F\u52A8\u5D4C\u5165\u5F0F Web \u4EEA\u8868\u677F\u3002",
|
|
2350
|
+
openDashboardHint: "\u670D\u52A1\u5C31\u7EEA\u540E\u7ACB\u5373\u5728\u9ED8\u8BA4\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u4EEA\u8868\u677F\u5730\u5740\u3002\u8BBE\u7F6E\u4E86 --no-dashboard \u65F6\u4E0D\u751F\u6548\u3002",
|
|
2276
2351
|
dashboardPortHint: "\u5C06\u4EEA\u8868\u677F\u7ED1\u5B9A\u5230\u56FA\u5B9A\u7AEF\u53E3 (1\u201365535)\u3002\u91CD\u542F\u540E\u4FDD\u6301\u7A33\u5B9A \u2014 SSH \u96A7\u9053\u8BBF\u95EE\u5FC5\u9700\u3002\u9ED8\u8BA4\u4E3A\u4E34\u65F6\u7AEF\u53E3\u3002",
|
|
2277
2352
|
dashboardPortInvalid: "\u25B2 \u5FFD\u7565 --dashboard-port={value} (\u5FC5\u987B\u4E3A 1\u201365535 \u4E4B\u95F4\u7684\u6574\u6570) \u2014 \u56DE\u9000\u5230\u4E34\u65F6\u7AEF\u53E3",
|
|
2278
2353
|
dashboardAutoStartFailed: "\u25B2 \u4EEA\u8868\u677F\u81EA\u52A8\u542F\u52A8\u5931\u8D25 ({reason}) \u2014 \u5C1D\u8BD5 /dashboard\uFF0C\u6216\u4F20\u9012 --no-dashboard \u4EE5\u9759\u9ED8",
|
|
@@ -2434,7 +2509,13 @@ var zhCN = {
|
|
|
2434
2509
|
jsonHintCatalog: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA",
|
|
2435
2510
|
jsonHintReport: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u68C0\u67E5\u62A5\u544A",
|
|
2436
2511
|
modelOverrideFlash: "\u8986\u76D6\u6A21\u578B\uFF08\u9ED8\u8BA4\uFF1Adeepseek-v4-flash\uFF09",
|
|
2437
|
-
skipConfirmHint: "\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A"
|
|
2512
|
+
skipConfirmHint: "\u8DF3\u8FC7\u786E\u8BA4\u63D0\u793A",
|
|
2513
|
+
yoloHint: "\u81EA\u52A8\u6279\u51C6\u672C\u6B21\u8C03\u7528\u7684\u8BA1\u5212\u68C0\u67E5\u70B9\uFF08\u7B49\u540C\u4E8E editMode=yolo\uFF0C\u4F46\u4E0D\u4FEE\u6539\u914D\u7F6E\u6587\u4EF6\uFF09"
|
|
2514
|
+
},
|
|
2515
|
+
code: {
|
|
2516
|
+
workspaceConflict: "\u26A0 \u5DE5\u4F5C\u533A\u5305\u542B\u53E6\u4E00\u4E2A\u667A\u80FD\u4F53\u5E73\u53F0\u7684\u6587\u4EF6 ({platforms})\u3002Reasonix Code \u53EF\u80FD\u4F1A\u5C06\u5176\u4F5C\u4E3A\u9879\u76EE\u5185\u5BB9\u8BFB\u53D6\uFF1B\u5982\u679C\u4E0D\u662F\u60A8\u60F3\u8981\u7684\uFF0C\u8BF7\u4F7F\u7528 --dir <your-project> \u91CD\u65B0\u542F\u52A8\u3002\n",
|
|
2517
|
+
systemAppendEmpty: "--system-append \u4E3A\u7A7A \u2014 \u4E0D\u4F1A\u8FFD\u52A0\u4EFB\u4F55\u63D0\u793A\u6587\u672C\n",
|
|
2518
|
+
systemAppendFileReadError: '\u9519\u8BEF\uFF1A\u65E0\u6CD5\u8BFB\u53D6 --system-append-file "{filePath}"\uFF1A{errorDetails}\n'
|
|
2438
2519
|
},
|
|
2439
2520
|
slash: {
|
|
2440
2521
|
help: { description: "\u663E\u793A\u5B8C\u6574\u547D\u4EE4\u53C2\u8003" },
|
|
@@ -2584,6 +2665,14 @@ var zhCN = {
|
|
|
2584
2665
|
logs: {
|
|
2585
2666
|
description: "\u8DDF\u8E2A\u540E\u53F0\u4F5C\u4E1A\u7684\u8F93\u51FA\uFF08\u9ED8\u8BA4\u6700\u540E 80 \u884C\uFF09",
|
|
2586
2667
|
argsHint: "<id> [lines]"
|
|
2668
|
+
},
|
|
2669
|
+
btw: {
|
|
2670
|
+
description: "\u987A\u4FBF\u95EE\u4E00\u4E0B \u2014 \u4ECE\u7A7A\u767D\u4E0A\u4E0B\u6587\u56DE\u7B54\uFF0C\u4E0D\u5199\u5165\u4F1A\u8BDD\u5386\u53F2",
|
|
2671
|
+
argsHint: "<question>"
|
|
2672
|
+
},
|
|
2673
|
+
"search-engine": {
|
|
2674
|
+
description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 mojeek\uFF08\u9ED8\u8BA4\uFF0C\u65E0\u4F9D\u8D56\uFF09\u6216 searxng\uFF08\u81EA\u6258\u7BA1\uFF09",
|
|
2675
|
+
argsHint: "<mojeek|searxng> [<endpoint>]"
|
|
2587
2676
|
}
|
|
2588
2677
|
},
|
|
2589
2678
|
wizard: {
|
|
@@ -2710,9 +2799,18 @@ var zhCN = {
|
|
|
2710
2799
|
counterDone: "{done}/{total} \u5DF2\u5B8C\u6210\uFF08{pct}%\uFF09 \xB7 \u5171 {total} \u6B65",
|
|
2711
2800
|
counterDoneSingular: "{done}/{total} \u5DF2\u5B8C\u6210\uFF08{pct}%\uFF09 \xB7 \u5171 {total} \u6B65"
|
|
2712
2801
|
},
|
|
2802
|
+
noPlanSummary: "\u5C1A\u672A\u63D0\u4EA4\u8BA1\u5212\u5185\u5BB9\u3002",
|
|
2803
|
+
detailCollapsedHint: "Ctrl+P \u5C55\u5F00\u5B8C\u6574\u8BA1\u5212\u8BE6\u60C5\u3002",
|
|
2804
|
+
detailExpandedHint: "Ctrl+P \u6536\u8D77\u8BE6\u60C5\u3002",
|
|
2805
|
+
detailHeader: "\u8BA1\u5212\u8BE6\u60C5",
|
|
2806
|
+
detailWindow: "\u663E\u793A\u7B2C {start}-{end} \u884C\uFF0C\u5171 {total} \u884C",
|
|
2807
|
+
detailScrollHint: "PgUp/PgDn \u6EDA\u52A8\u8BE6\u60C5 \xB7 Home/End \u8DF3\u8F6C",
|
|
2713
2808
|
reviseTitle: "\u4FEE\u6539\u8BA1\u5212",
|
|
2714
2809
|
reviseSteps: "{count} \u4E2A\u6B65\u9AA4",
|
|
2715
|
-
reviseFooter: "\u2191\u2193 \u7126\u70B9 \xB7 \u7A7A\u683C\u5207\u6362\u8DF3\u8FC7 \xB7 k/j \u79FB\u52A8 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88"
|
|
2810
|
+
reviseFooter: "\u2191\u2193 \u7126\u70B9 \xB7 \u7A7A\u683C\u5207\u6362\u8DF3\u8FC7 \xB7 k/j \u79FB\u52A8 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88",
|
|
2811
|
+
riskMed: " \u4E2D",
|
|
2812
|
+
riskHigh: " \u9AD8",
|
|
2813
|
+
completeMsg: "\u25B8 \u8BA1\u5212\u5B8C\u6210 \u2014 \u5168\u90E8 {total} \u4E2A\u6B65\u9AA4\u5DF2\u5B8C\u6210 \xB7 \u5DF2\u5F52\u6863"
|
|
2716
2814
|
},
|
|
2717
2815
|
app: {
|
|
2718
2816
|
walkCancelledRemaining: "\u25B8 \u6D4F\u89C8\u5DF2\u53D6\u6D88 \u2014 \u8FD8\u6709 {count} \u4E2A\u5F85\u5904\u7406\u7F16\u8F91\u5757\u3002",
|
|
@@ -2732,6 +2830,9 @@ var zhCN = {
|
|
|
2732
2830
|
notedVerbAppended: "\u8FFD\u52A0\u5230",
|
|
2733
2831
|
memoryWriteFailed: "# \u8BB0\u5FC6\u5199\u5165\u5931\u8D25",
|
|
2734
2832
|
commandFailed: "! \u547D\u4EE4\u5931\u8D25",
|
|
2833
|
+
btwUsage: "\u25B8 /btw <\u95EE\u9898> \u2014 \u987A\u4FBF\u95EE\u4E2A\u9898\u5916\u8BDD\uFF0C\u4E0D\u4F1A\u5199\u5165\u5F53\u524D\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u3002",
|
|
2834
|
+
btwHeader: "\u226B btw",
|
|
2835
|
+
btwFailed: "/btw \u8C03\u7528\u5931\u8D25",
|
|
2735
2836
|
restoreCodeOnly: "\u25B8 /restore \u4EC5\u5728\u4EE3\u7801\u6A21\u5F0F\u53EF\u7528",
|
|
2736
2837
|
hookUserPromptSubmit: "UserPromptSubmit \u94A9\u5B50",
|
|
2737
2838
|
hookStop: "Stop \u94A9\u5B50",
|
|
@@ -2809,6 +2910,7 @@ var zhCN = {
|
|
|
2809
2910
|
basic: {
|
|
2810
2911
|
newInfo: "\u25B8 \u65B0\u5BF9\u8BDD \u2014 \u5DF2\u4ECE\u4E0A\u4E0B\u6587\u4E2D\u4E22\u5F03 {count} \u6761\u6D88\u606F\u3002\u540C\u4E00\u4F1A\u8BDD\uFF0C\u5168\u65B0\u5F00\u59CB\u3002",
|
|
2811
2912
|
newInfoArchived: "\u25B8 \u65B0\u5BF9\u8BDD \u2014 \u5DF2\u4ECE\u4E0A\u4E0B\u6587\u4E2D\u4E22\u5F03 {count} \u6761\u6D88\u606F\u3002\u539F\u5BF9\u8BDD\u5DF2\u5F52\u6863\u4E3A\u300C{archived}\u300D\uFF0C\u53EF\u5728 Sessions \u9762\u677F\u67E5\u770B\u3002",
|
|
2913
|
+
newInfoSystemReloaded: " \xB7 REASONIX.md / \u9879\u76EE\u8BB0\u5FC6\u5DF2\u91CD\u65B0\u52A0\u8F7D\uFF08\u4E0B\u4E00\u8F6E\u4E00\u6B21\u6027 cache miss\uFF09",
|
|
2812
2914
|
helpTitle: "\u547D\u4EE4\uFF1A",
|
|
2813
2915
|
helpShellTitle: "Shell \u5FEB\u6377\u65B9\u5F0F\uFF1A",
|
|
2814
2916
|
helpShell: " !<cmd> \u5728\u6C99\u7BB1\u6839\u76EE\u5F55\u8FD0\u884C <cmd>\uFF1B\u8F93\u51FA\u8FDB\u5165\u5BF9\u8BDD",
|
|
@@ -3153,7 +3255,8 @@ var zhCN = {
|
|
|
3153
3255
|
mb: " MB",
|
|
3154
3256
|
evt: " \u4E8B\u4EF6",
|
|
3155
3257
|
editsLabel: "\u7F16\u8F91:",
|
|
3156
|
-
mcpLoading: "MCP"
|
|
3258
|
+
mcpLoading: "MCP",
|
|
3259
|
+
ctx: "\u4E0A\u4E0B\u6587"
|
|
3157
3260
|
},
|
|
3158
3261
|
editMode: {
|
|
3159
3262
|
plan: "\u8BA1\u5212",
|
|
@@ -3460,7 +3563,8 @@ var zhCN = {
|
|
|
3460
3563
|
healthy: "\u6B63\u5E38 \xB7 {ms}ms",
|
|
3461
3564
|
slow: "\u7F13\u6162 \xB7 {ms}ms",
|
|
3462
3565
|
verySlow: "\u975E\u5E38\u6162 \xB7 {ms}ms",
|
|
3463
|
-
slowToast: "\u26A0 MCP `{name}` \u54CD\u5E94\u7F13\u6162 \xB7 P95 {seconds}s \xB7 \u6700\u8FD1 {sampleSize} \u6B21\u8C03\u7528"
|
|
3566
|
+
slowToast: "\u26A0 MCP `{name}` \u54CD\u5E94\u7F13\u6162 \xB7 P95 {seconds}s \xB7 \u6700\u8FD1 {sampleSize} \u6B21\u8C03\u7528",
|
|
3567
|
+
emptyHint: "\u2139 \u672A\u914D\u7F6E MCP \u670D\u52A1\u5668 \u2014\u2014 \u53EF\u5C1D\u8BD5\uFF1A`reasonix setup` \u91CD\u65B0\u9009\u62E9\uFF0C\u6216 `reasonix mcp install filesystem`"
|
|
3464
3568
|
},
|
|
3465
3569
|
denyContextInput: {
|
|
3466
3570
|
description: "\u544A\u8BC9\u6A21\u578B\u4F60\u4E3A\u4EC0\u4E48\u62D2\u7EDD\u4E86\u3002\u6A21\u578B\u4E0B\u6B21\u4F1A\u770B\u5230\u4F60\u7684\u7406\u7531\u4F5C\u4E3A\u989D\u5916\u7684\u4E0A\u4E0B\u6587\u3002"
|
|
@@ -3525,7 +3629,9 @@ var zhCN = {
|
|
|
3525
3629
|
reconnect: "\u91CD\u8FDE\u4E2D\u2026",
|
|
3526
3630
|
initDetail: "\u521D\u59CB\u5316 \u2192 tools/list \u2192 resources/list",
|
|
3527
3631
|
reconnectDetail: "\u65AD\u5F00\u65E7\u8FDE\u63A5 \xB7 \u91CD\u65B0\u63E1\u624B \xB7 \u5217\u51FA\u5DE5\u5177",
|
|
3528
|
-
disabledDetail: "\u901A\u8FC7 /mcp disable {name}"
|
|
3632
|
+
disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
|
|
3633
|
+
failedSetupHint: "\u2192 \u8FD0\u884C `reasonix setup` \u79FB\u9664\u6B64\u6761\u76EE\uFF0C\u6216\u4FEE\u590D\u5E95\u5C42\u95EE\u9898\uFF08\u7F3A\u5C11 npm \u5305\u3001\u7F51\u7EDC\u7B49\uFF09\u3002",
|
|
3634
|
+
failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002"
|
|
3529
3635
|
},
|
|
3530
3636
|
checkpointPicker: {
|
|
3531
3637
|
title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
|
|
@@ -4509,7 +4615,7 @@ import {
|
|
|
4509
4615
|
writeFileSync as writeFileSync2
|
|
4510
4616
|
} from "fs";
|
|
4511
4617
|
import { homedir as homedir3 } from "os";
|
|
4512
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
4618
|
+
import { dirname as dirname3, join as join4, posix as posixPath, win32 as win32Path } from "path";
|
|
4513
4619
|
function sessionsDir() {
|
|
4514
4620
|
return join4(homedir3(), ".reasonix", "sessions");
|
|
4515
4621
|
}
|
|
@@ -4796,6 +4902,23 @@ var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
|
|
|
4796
4902
|
var FORCE_SUMMARY_THRESHOLD = 0.8;
|
|
4797
4903
|
var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
|
|
4798
4904
|
var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
|
|
4905
|
+
var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
|
|
4906
|
+
var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
|
|
4907
|
+
function extractPinnedSkills(head) {
|
|
4908
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
4909
|
+
const stubbedHead = head.map((msg) => {
|
|
4910
|
+
if (typeof msg.content !== "string") return msg;
|
|
4911
|
+
let hit = false;
|
|
4912
|
+
const next = msg.content.replace(SKILL_PIN_REGEX, (full, name) => {
|
|
4913
|
+
pinned.delete(name);
|
|
4914
|
+
pinned.set(name, full);
|
|
4915
|
+
hit = true;
|
|
4916
|
+
return `[skill ${JSON.stringify(name)} memo \u2014 preserved separately, do not summarize.]`;
|
|
4917
|
+
});
|
|
4918
|
+
return hit ? { ...msg, content: next } : msg;
|
|
4919
|
+
});
|
|
4920
|
+
return { stubbedHead, pinnedBodies: [...pinned.values()] };
|
|
4921
|
+
}
|
|
4799
4922
|
var ContextManager = class {
|
|
4800
4923
|
constructor(deps) {
|
|
4801
4924
|
this.deps = deps;
|
|
@@ -4865,11 +4988,17 @@ var ContextManager = class {
|
|
|
4865
4988
|
const tail = all.slice(boundary);
|
|
4866
4989
|
const headTokens = totalTokens - cumTokens;
|
|
4867
4990
|
if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
|
|
4868
|
-
const
|
|
4991
|
+
const { stubbedHead, pinnedBodies } = extractPinnedSkills(head);
|
|
4992
|
+
const summary = await this.summarizeForFold(stubbedHead);
|
|
4869
4993
|
if (!summary) return noop;
|
|
4994
|
+
const memoTail = pinnedBodies.length > 0 ? `
|
|
4995
|
+
|
|
4996
|
+
${SKILL_PIN_MEMO_HEADER}
|
|
4997
|
+
|
|
4998
|
+
${pinnedBodies.join("\n\n")}` : "";
|
|
4870
4999
|
const summaryMsg = {
|
|
4871
5000
|
role: "assistant",
|
|
4872
|
-
content: HISTORY_FOLD_MARKER + summary
|
|
5001
|
+
content: HISTORY_FOLD_MARKER + summary + memoTail
|
|
4873
5002
|
};
|
|
4874
5003
|
const replacement = [summaryMsg, ...tail];
|
|
4875
5004
|
this.deps.log.compactInPlace(replacement);
|
|
@@ -5094,6 +5223,8 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
|
5094
5223
|
}
|
|
5095
5224
|
|
|
5096
5225
|
// src/loop/force-summary.ts
|
|
5226
|
+
var PAUSE_SUMMARY_MODEL = "deepseek-v4-flash";
|
|
5227
|
+
var PAUSE_SUMMARY_EFFORT = "high";
|
|
5097
5228
|
async function* forceSummaryAfterIterLimit(ctx, opts = { reason: "budget" }) {
|
|
5098
5229
|
try {
|
|
5099
5230
|
yield { turn: ctx.turn, role: "status", content: t("summary.status") };
|
|
@@ -5139,6 +5270,28 @@ ${summary}`;
|
|
|
5139
5270
|
yield { turn: ctx.turn, role: "done", content: "" };
|
|
5140
5271
|
}
|
|
5141
5272
|
}
|
|
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
|
+
}
|
|
5142
5295
|
|
|
5143
5296
|
// src/loop/shrink.ts
|
|
5144
5297
|
function looksLikeCompleteJson(s) {
|
|
@@ -5184,6 +5337,10 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
|
5184
5337
|
}
|
|
5185
5338
|
|
|
5186
5339
|
// src/loop/healing.ts
|
|
5340
|
+
var _stampSeq = 0;
|
|
5341
|
+
function stampMissingIds(calls) {
|
|
5342
|
+
return calls.map((c) => c.id ? c : { ...c, id: `z-ext-${Date.now()}-${_stampSeq++}` });
|
|
5343
|
+
}
|
|
5187
5344
|
function fixToolCallPairing(messages) {
|
|
5188
5345
|
const out = [];
|
|
5189
5346
|
let droppedAssistantCalls = 0;
|
|
@@ -5191,9 +5348,10 @@ function fixToolCallPairing(messages) {
|
|
|
5191
5348
|
for (let i = 0; i < messages.length; i++) {
|
|
5192
5349
|
const msg = messages[i];
|
|
5193
5350
|
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
5351
|
+
const calls = stampMissingIds(msg.tool_calls);
|
|
5194
5352
|
const needed = /* @__PURE__ */ new Set();
|
|
5195
|
-
for (const call of
|
|
5196
|
-
if (call
|
|
5353
|
+
for (const call of calls) {
|
|
5354
|
+
if (call.id) needed.add(call.id);
|
|
5197
5355
|
}
|
|
5198
5356
|
const candidates = [];
|
|
5199
5357
|
let j = i + 1;
|
|
@@ -5207,7 +5365,7 @@ function fixToolCallPairing(messages) {
|
|
|
5207
5365
|
j++;
|
|
5208
5366
|
}
|
|
5209
5367
|
if (needed.size === 0) {
|
|
5210
|
-
out.push(msg);
|
|
5368
|
+
out.push({ ...msg, tool_calls: calls });
|
|
5211
5369
|
for (const r of candidates) out.push(r);
|
|
5212
5370
|
i = j - 1;
|
|
5213
5371
|
} else {
|
|
@@ -5310,17 +5468,25 @@ var TurnFailureTracker = class {
|
|
|
5310
5468
|
// src/memory/runtime.ts
|
|
5311
5469
|
import { createHash } from "crypto";
|
|
5312
5470
|
var ImmutablePrefix = class {
|
|
5471
|
+
/** Stable across turns; rebuilt only on /new when REASONIX.md changed on disk. */
|
|
5313
5472
|
system;
|
|
5314
5473
|
/** Each `addTool` costs one cache-miss turn — DeepSeek's prefix cache is keyed by full tool list. */
|
|
5315
5474
|
_toolSpecs;
|
|
5316
5475
|
fewShots;
|
|
5317
|
-
/** Invalidated
|
|
5476
|
+
/** Invalidated by addTool / removeTool / replaceSystem; bypassing any of those leaves cache stale → fingerprint diverges from sent prefix. */
|
|
5318
5477
|
_fingerprintCache = null;
|
|
5319
5478
|
constructor(opts) {
|
|
5320
5479
|
this.system = opts.system;
|
|
5321
5480
|
this._toolSpecs = [...opts.toolSpecs ?? []];
|
|
5322
5481
|
this.fewShots = Object.freeze([...opts.fewShots ?? []]);
|
|
5323
5482
|
}
|
|
5483
|
+
/** Replaces the system prompt; returns true iff the string actually changed. Caller must accept a cache miss on the next turn. */
|
|
5484
|
+
replaceSystem(s) {
|
|
5485
|
+
if (this.system === s) return false;
|
|
5486
|
+
this.system = s;
|
|
5487
|
+
this._fingerprintCache = null;
|
|
5488
|
+
return true;
|
|
5489
|
+
}
|
|
5324
5490
|
get toolSpecs() {
|
|
5325
5491
|
return this._toolSpecs;
|
|
5326
5492
|
}
|
|
@@ -5745,12 +5911,14 @@ var CacheFirstLoop = class {
|
|
|
5745
5911
|
/** One-shot 80% warning latch — cleared by setBudget so a bump re-arms at the new boundary. */
|
|
5746
5912
|
_budgetWarned = false;
|
|
5747
5913
|
sessionName;
|
|
5914
|
+
onIterBudgetExhausted;
|
|
5748
5915
|
hooks;
|
|
5749
5916
|
hookCwd;
|
|
5750
5917
|
/** PauseGate bridge — defaults to singleton, injectable for tests. */
|
|
5751
5918
|
confirmationGate;
|
|
5752
5919
|
/** Number of messages that were pre-loaded from the session file. */
|
|
5753
5920
|
resumedMessageCount;
|
|
5921
|
+
_rebuildSystem;
|
|
5754
5922
|
_turn = 0;
|
|
5755
5923
|
_streamPreference;
|
|
5756
5924
|
/** Threaded through HTTP + every tool dispatch so Esc cancels in-flight work, not after. */
|
|
@@ -5783,33 +5951,15 @@ var CacheFirstLoop = class {
|
|
|
5783
5951
|
resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
|
|
5784
5952
|
);
|
|
5785
5953
|
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
5954
|
+
this.onIterBudgetExhausted = opts.onIterBudgetExhausted ?? "summarize";
|
|
5786
5955
|
this.hooks = opts.hooks ?? [];
|
|
5787
5956
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
5788
5957
|
this.confirmationGate = opts.confirmationGate ?? pauseGate;
|
|
5958
|
+
this._rebuildSystem = opts.rebuildSystem ?? null;
|
|
5789
5959
|
this._streamPreference = opts.stream ?? true;
|
|
5790
5960
|
this.stream = this._streamPreference;
|
|
5791
5961
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
5792
5962
|
const registry = this.tools;
|
|
5793
|
-
const isMutating = (call) => {
|
|
5794
|
-
const name = call.function?.name;
|
|
5795
|
-
if (!name) return false;
|
|
5796
|
-
const def = registry.get(name);
|
|
5797
|
-
if (!def) return false;
|
|
5798
|
-
if (def.readOnlyCheck) {
|
|
5799
|
-
let args = {};
|
|
5800
|
-
try {
|
|
5801
|
-
args = JSON.parse(call.function?.arguments ?? "{}") ?? {};
|
|
5802
|
-
} catch {
|
|
5803
|
-
}
|
|
5804
|
-
try {
|
|
5805
|
-
if (def.readOnlyCheck(args)) return false;
|
|
5806
|
-
} catch (err) {
|
|
5807
|
-
process.stderr.write(`readOnlyCheck for ${name} threw: ${err.message}
|
|
5808
|
-
`);
|
|
5809
|
-
}
|
|
5810
|
-
}
|
|
5811
|
-
return def.readOnly !== true;
|
|
5812
|
-
};
|
|
5813
5963
|
const isStormExempt = (call) => {
|
|
5814
5964
|
const name = call.function?.name;
|
|
5815
5965
|
if (!name) return false;
|
|
@@ -5817,7 +5967,7 @@ var CacheFirstLoop = class {
|
|
|
5817
5967
|
};
|
|
5818
5968
|
this.repair = new ToolCallRepair({
|
|
5819
5969
|
allowedToolNames: allowedNames,
|
|
5820
|
-
isMutating,
|
|
5970
|
+
isMutating: (call) => this.isMutating(call),
|
|
5821
5971
|
isStormExempt,
|
|
5822
5972
|
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
5823
5973
|
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
@@ -5910,7 +6060,7 @@ var CacheFirstLoop = class {
|
|
|
5910
6060
|
}
|
|
5911
6061
|
}
|
|
5912
6062
|
}
|
|
5913
|
-
/** "New chat" — drops in-memory messages, archives the on-disk transcript so it survives in Sessions, keeps sessionName so the prefix cache stays warm. */
|
|
6063
|
+
/** "New chat" — drops in-memory messages, archives the on-disk transcript so it survives in Sessions, keeps sessionName so the prefix cache stays warm. Re-runs the system-prompt builder if one was wired (issue #778: REASONIX.md edits otherwise need a restart). */
|
|
5914
6064
|
clearLog() {
|
|
5915
6065
|
const dropped = this.log.length;
|
|
5916
6066
|
this.log.compactInPlace([]);
|
|
@@ -5924,7 +6074,14 @@ var CacheFirstLoop = class {
|
|
|
5924
6074
|
}
|
|
5925
6075
|
this.scratch.reset();
|
|
5926
6076
|
this._inflight.clear();
|
|
5927
|
-
|
|
6077
|
+
let systemRebuilt = false;
|
|
6078
|
+
if (this._rebuildSystem) {
|
|
6079
|
+
try {
|
|
6080
|
+
systemRebuilt = this.prefix.replaceSystem(this._rebuildSystem());
|
|
6081
|
+
} catch {
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
return { dropped, archived, systemRebuilt };
|
|
5928
6085
|
}
|
|
5929
6086
|
configure(opts) {
|
|
5930
6087
|
if (opts.model !== void 0) this.model = opts.model;
|
|
@@ -5970,6 +6127,27 @@ var CacheFirstLoop = class {
|
|
|
5970
6127
|
this._escalateThisTurn = true;
|
|
5971
6128
|
return true;
|
|
5972
6129
|
}
|
|
6130
|
+
/** A call counts as mutating when its definition reports `readOnly !== true` and any dynamic `readOnlyCheck` doesn't override that for these args. */
|
|
6131
|
+
isMutating(call) {
|
|
6132
|
+
const name = call.function?.name;
|
|
6133
|
+
if (!name) return false;
|
|
6134
|
+
const def = this.tools.get(name);
|
|
6135
|
+
if (!def) return false;
|
|
6136
|
+
if (def.readOnlyCheck) {
|
|
6137
|
+
let args = {};
|
|
6138
|
+
try {
|
|
6139
|
+
args = JSON.parse(call.function?.arguments ?? "{}") ?? {};
|
|
6140
|
+
} catch {
|
|
6141
|
+
}
|
|
6142
|
+
try {
|
|
6143
|
+
if (def.readOnlyCheck(args)) return false;
|
|
6144
|
+
} catch (err) {
|
|
6145
|
+
process.stderr.write(`readOnlyCheck for ${name} threw: ${err.message}
|
|
6146
|
+
`);
|
|
6147
|
+
}
|
|
6148
|
+
}
|
|
6149
|
+
return def.readOnly !== true;
|
|
6150
|
+
}
|
|
5973
6151
|
async runOneToolCall(call, signal) {
|
|
5974
6152
|
const name = call.function?.name ?? "";
|
|
5975
6153
|
const args = call.function?.arguments ?? "{}";
|
|
@@ -6207,6 +6385,15 @@ ${reason}`
|
|
|
6207
6385
|
thinking: thinkingModeForModel(callModel),
|
|
6208
6386
|
reasoningEffort: this.reasoningEffort
|
|
6209
6387
|
})) {
|
|
6388
|
+
if (chunk.reasoningDelta) {
|
|
6389
|
+
reasoningContent += chunk.reasoningDelta;
|
|
6390
|
+
yield {
|
|
6391
|
+
turn: this._turn,
|
|
6392
|
+
role: "assistant_delta",
|
|
6393
|
+
content: "",
|
|
6394
|
+
reasoningDelta: chunk.reasoningDelta
|
|
6395
|
+
};
|
|
6396
|
+
}
|
|
6210
6397
|
if (chunk.contentDelta) {
|
|
6211
6398
|
assistantContent += chunk.contentDelta;
|
|
6212
6399
|
if (bufferForEscalation && !escalationBufFlushed) {
|
|
@@ -6231,15 +6418,6 @@ ${reason}`
|
|
|
6231
6418
|
};
|
|
6232
6419
|
}
|
|
6233
6420
|
}
|
|
6234
|
-
if (chunk.reasoningDelta) {
|
|
6235
|
-
reasoningContent += chunk.reasoningDelta;
|
|
6236
|
-
yield {
|
|
6237
|
-
turn: this._turn,
|
|
6238
|
-
role: "assistant_delta",
|
|
6239
|
-
content: "",
|
|
6240
|
-
reasoningDelta: chunk.reasoningDelta
|
|
6241
|
-
};
|
|
6242
|
-
}
|
|
6243
6421
|
if (chunk.toolCallDelta) {
|
|
6244
6422
|
const d = chunk.toolCallDelta;
|
|
6245
6423
|
const cur = callBuf.get(d.index) ?? {
|
|
@@ -6527,6 +6705,19 @@ ${reason}`
|
|
|
6527
6705
|
}
|
|
6528
6706
|
}
|
|
6529
6707
|
}
|
|
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
|
+
}
|
|
6530
6721
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "budget" });
|
|
6531
6722
|
}
|
|
6532
6723
|
summaryContext() {
|
|
@@ -6844,7 +7035,7 @@ function parseAtQuery(query) {
|
|
|
6844
7035
|
trailingSlash: false
|
|
6845
7036
|
};
|
|
6846
7037
|
}
|
|
6847
|
-
var AT_PICKER_PREFIX = /(?:^|\s)@([
|
|
7038
|
+
var AT_PICKER_PREFIX = /(?:^|\s)@([\p{L}\p{N}_./\\-]*)$/u;
|
|
6848
7039
|
function detectAtPicker(input) {
|
|
6849
7040
|
const m = AT_PICKER_PREFIX.exec(input);
|
|
6850
7041
|
if (!m) return null;
|
|
@@ -6930,7 +7121,7 @@ function fuzzySubseqScore(needle, target) {
|
|
|
6930
7121
|
const lengthPenalty = Math.floor(target.length / 4);
|
|
6931
7122
|
return quality + lengthPenalty;
|
|
6932
7123
|
}
|
|
6933
|
-
var AT_MENTION_PATTERN = /(?<=^|\s)@([
|
|
7124
|
+
var AT_MENTION_PATTERN = /(?<=^|\s)@([\p{L}\p{N}_./\\-]+)/gu;
|
|
6934
7125
|
function expandAtMentions(text, rootDir, opts = {}) {
|
|
6935
7126
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
6936
7127
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
@@ -7219,6 +7410,12 @@ function parseAllowedTools(raw) {
|
|
|
7219
7410
|
const names = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7220
7411
|
return names.length > 0 ? Object.freeze(names) : void 0;
|
|
7221
7412
|
}
|
|
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
|
+
}
|
|
7222
7419
|
var SkillStore = class {
|
|
7223
7420
|
homeDir;
|
|
7224
7421
|
projectRoot;
|
|
@@ -7349,7 +7546,8 @@ var SkillStore = class {
|
|
|
7349
7546
|
path: path2,
|
|
7350
7547
|
allowedTools: parseAllowedTools(data["allowed-tools"]),
|
|
7351
7548
|
runAs: parseRunAs(data.runAs),
|
|
7352
|
-
model: data.model?.startsWith("deepseek-") ? data.model : void 0
|
|
7549
|
+
model: data.model?.startsWith("deepseek-") ? data.model : void 0,
|
|
7550
|
+
maxToolIters: parseMaxToolIters(data["max-iters"])
|
|
7353
7551
|
};
|
|
7354
7552
|
}
|
|
7355
7553
|
};
|
|
@@ -7370,6 +7568,7 @@ Tips:
|
|
|
7370
7568
|
- Reference tools by name (run_command, edit_file, search_content, ...)
|
|
7371
7569
|
- Add \`runAs: subagent\` to frontmatter to spawn an isolated subagent loop
|
|
7372
7570
|
- 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.
|
|
7373
7572
|
`;
|
|
7374
7573
|
}
|
|
7375
7574
|
function skillIndexLine(s) {
|
|
@@ -7620,16 +7819,24 @@ function ensureDir(p) {
|
|
|
7620
7819
|
if (!existsSync7(p)) mkdirSync4(p, { recursive: true });
|
|
7621
7820
|
}
|
|
7622
7821
|
function formatFrontmatter(e) {
|
|
7623
|
-
|
|
7822
|
+
const lines = [
|
|
7624
7823
|
"---",
|
|
7625
7824
|
`name: ${e.name}`,
|
|
7626
7825
|
`description: ${e.description.replace(/\n/g, " ")}`,
|
|
7627
7826
|
`type: ${e.type}`,
|
|
7628
7827
|
`scope: ${e.scope}`,
|
|
7629
|
-
`created: ${e.createdAt}
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7828
|
+
`created: ${e.createdAt}`
|
|
7829
|
+
];
|
|
7830
|
+
if (e.priority) lines.push(`priority: ${e.priority}`);
|
|
7831
|
+
if (e.expires) lines.push(`expires: ${e.expires}`);
|
|
7832
|
+
lines.push("---", "");
|
|
7833
|
+
return lines.join("\n");
|
|
7834
|
+
}
|
|
7835
|
+
function coercePriority(v) {
|
|
7836
|
+
return v === "low" || v === "medium" || v === "high" ? v : void 0;
|
|
7837
|
+
}
|
|
7838
|
+
function coerceExpires(v) {
|
|
7839
|
+
return v === "project_end" ? v : void 0;
|
|
7633
7840
|
}
|
|
7634
7841
|
function todayIso() {
|
|
7635
7842
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -7691,7 +7898,7 @@ var MemoryStore = class {
|
|
|
7691
7898
|
}
|
|
7692
7899
|
const raw = readFileSync9(file, "utf8");
|
|
7693
7900
|
const { data, body } = parseFrontmatter(raw);
|
|
7694
|
-
|
|
7901
|
+
const entry = {
|
|
7695
7902
|
name: data.name ?? name,
|
|
7696
7903
|
type: data.type ?? "project",
|
|
7697
7904
|
scope: data.scope ?? scope,
|
|
@@ -7699,6 +7906,11 @@ var MemoryStore = class {
|
|
|
7699
7906
|
body: body.trim(),
|
|
7700
7907
|
createdAt: data.created ?? ""
|
|
7701
7908
|
};
|
|
7909
|
+
const priority = coercePriority(data.priority);
|
|
7910
|
+
if (priority) entry.priority = priority;
|
|
7911
|
+
const expires = coerceExpires(data.expires);
|
|
7912
|
+
if (expires) entry.expires = expires;
|
|
7913
|
+
return entry;
|
|
7702
7914
|
}
|
|
7703
7915
|
/** Skips malformed files — index stays queryable even if one file is hand-edited into nonsense. */
|
|
7704
7916
|
list() {
|
|
@@ -7741,6 +7953,8 @@ var MemoryStore = class {
|
|
|
7741
7953
|
body,
|
|
7742
7954
|
createdAt: todayIso()
|
|
7743
7955
|
};
|
|
7956
|
+
if (input.priority) entry.priority = input.priority;
|
|
7957
|
+
if (input.expires) entry.expires = input.expires;
|
|
7744
7958
|
const dir = this.dir(input.scope);
|
|
7745
7959
|
const file = join8(dir, `${name}.md`);
|
|
7746
7960
|
const content = `${formatFrontmatter(entry)}${body}
|
|
@@ -7824,13 +8038,36 @@ function applyGlobalReasonixMemory(basePrompt, homeDir) {
|
|
|
7824
8038
|
"```"
|
|
7825
8039
|
].join("\n");
|
|
7826
8040
|
}
|
|
8041
|
+
function effectivePriority(entry, cfg) {
|
|
8042
|
+
if (entry.priority) return entry.priority;
|
|
8043
|
+
return memoryTypeDefaults(entry.type, cfg).priority;
|
|
8044
|
+
}
|
|
8045
|
+
function highPriorityBlock(entries, cfg) {
|
|
8046
|
+
const high = entries.filter((e) => effectivePriority(e, cfg) === "high");
|
|
8047
|
+
if (high.length === 0) return null;
|
|
8048
|
+
const lines = [
|
|
8049
|
+
"# HIGH PRIORITY constraints (must observe)",
|
|
8050
|
+
"",
|
|
8051
|
+
"These memories were declared `priority: high` (via config.memory.customTypes or the memory file itself). Treat them as hard rules \u2014 violations override any other guidance below.",
|
|
8052
|
+
""
|
|
8053
|
+
];
|
|
8054
|
+
for (const e of high) {
|
|
8055
|
+
const head = `!!! [${e.scope}/${e.type}/${e.name}] ${e.description || "(no description)"}`;
|
|
8056
|
+
lines.push(head);
|
|
8057
|
+
if (e.body) lines.push("", e.body);
|
|
8058
|
+
lines.push("");
|
|
8059
|
+
}
|
|
8060
|
+
return lines.join("\n").trimEnd();
|
|
8061
|
+
}
|
|
7827
8062
|
function applyUserMemory(basePrompt, opts = {}) {
|
|
7828
8063
|
if (!memoryEnabled()) return basePrompt;
|
|
7829
8064
|
const store = new MemoryStore(opts);
|
|
7830
8065
|
const global = store.loadIndex("global");
|
|
7831
8066
|
const project = store.hasProjectScope() ? store.loadIndex("project") : null;
|
|
7832
|
-
|
|
8067
|
+
const high = highPriorityBlock(store.list(), opts.cfg);
|
|
8068
|
+
if (!global && !project && !high) return basePrompt;
|
|
7833
8069
|
const parts = [basePrompt];
|
|
8070
|
+
if (high) parts.push("", high);
|
|
7834
8071
|
if (global) {
|
|
7835
8072
|
parts.push(
|
|
7836
8073
|
"",
|
|
@@ -7866,7 +8103,7 @@ function applyMemoryStack(basePrompt, rootDir) {
|
|
|
7866
8103
|
|
|
7867
8104
|
// src/tools/filesystem.ts
|
|
7868
8105
|
import { promises as fs4 } from "fs";
|
|
7869
|
-
import * as
|
|
8106
|
+
import * as pathMod5 from "path";
|
|
7870
8107
|
import picomatch3 from "picomatch";
|
|
7871
8108
|
|
|
7872
8109
|
// src/tools/fs/edit.ts
|
|
@@ -8088,15 +8325,149 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
8088
8325
|
return lines.join("\n");
|
|
8089
8326
|
}
|
|
8090
8327
|
|
|
8328
|
+
// src/tools/fs/outline.ts
|
|
8329
|
+
import * as pathMod3 from "path";
|
|
8330
|
+
var OUTLINE_MAX_ENTRIES = 30;
|
|
8331
|
+
var OUTLINE_TAIL_KEEP = 5;
|
|
8332
|
+
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
8333
|
+
var PY_DECL_RE = /^(?:async\s+)?(def|class)\s+(\w+)/;
|
|
8334
|
+
var GO_DECL_RE = /^(func|type|var|const)\s+(?:\([^)]+\)\s+)?(\w+)/;
|
|
8335
|
+
var RUST_DECL_RE = /^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|mod|type|const|static|union)\s+(\w+)/;
|
|
8336
|
+
var RUST_IMPL_RE = /^(?:unsafe\s+)?impl(?:\s*<[^>]+>)?\s+(?:[^{]+\s+for\s+)?(\w+)/;
|
|
8337
|
+
var MD_HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
8338
|
+
var MD_FENCE_RE = /^```/;
|
|
8339
|
+
var EXT_TO_LANG = {
|
|
8340
|
+
".ts": "ts",
|
|
8341
|
+
".tsx": "ts",
|
|
8342
|
+
".mts": "ts",
|
|
8343
|
+
".cts": "ts",
|
|
8344
|
+
".js": "ts",
|
|
8345
|
+
".jsx": "ts",
|
|
8346
|
+
".mjs": "ts",
|
|
8347
|
+
".cjs": "ts",
|
|
8348
|
+
".py": "py",
|
|
8349
|
+
".pyi": "py",
|
|
8350
|
+
".go": "go",
|
|
8351
|
+
".rs": "rust",
|
|
8352
|
+
".md": "md",
|
|
8353
|
+
".markdown": "md",
|
|
8354
|
+
".mdx": "md"
|
|
8355
|
+
};
|
|
8356
|
+
function extractOutline(filename, lines) {
|
|
8357
|
+
const ext = pathMod3.extname(filename).toLowerCase();
|
|
8358
|
+
const lang = EXT_TO_LANG[ext];
|
|
8359
|
+
if (!lang) return [];
|
|
8360
|
+
switch (lang) {
|
|
8361
|
+
case "ts":
|
|
8362
|
+
return extractTs(lines);
|
|
8363
|
+
case "py":
|
|
8364
|
+
return extractPython(lines);
|
|
8365
|
+
case "go":
|
|
8366
|
+
return extractGo(lines);
|
|
8367
|
+
case "rust":
|
|
8368
|
+
return extractRust(lines);
|
|
8369
|
+
case "md":
|
|
8370
|
+
return extractMarkdown(lines);
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
8373
|
+
function extractTs(lines) {
|
|
8374
|
+
const out = [];
|
|
8375
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8376
|
+
const line = lines[i];
|
|
8377
|
+
if (!line.startsWith("export ")) continue;
|
|
8378
|
+
const m = TS_EXPORT_RE.exec(line);
|
|
8379
|
+
if (!m) continue;
|
|
8380
|
+
out.push({ line: i + 1, text: `export ${m[1]} ${m[2]}` });
|
|
8381
|
+
}
|
|
8382
|
+
return out;
|
|
8383
|
+
}
|
|
8384
|
+
function extractPython(lines) {
|
|
8385
|
+
const out = [];
|
|
8386
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8387
|
+
const line = lines[i];
|
|
8388
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
8389
|
+
const m = PY_DECL_RE.exec(line);
|
|
8390
|
+
if (!m) continue;
|
|
8391
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8392
|
+
}
|
|
8393
|
+
return out;
|
|
8394
|
+
}
|
|
8395
|
+
function extractGo(lines) {
|
|
8396
|
+
const out = [];
|
|
8397
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8398
|
+
const line = lines[i];
|
|
8399
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
8400
|
+
const m = GO_DECL_RE.exec(line);
|
|
8401
|
+
if (!m) continue;
|
|
8402
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8403
|
+
}
|
|
8404
|
+
return out;
|
|
8405
|
+
}
|
|
8406
|
+
function extractRust(lines) {
|
|
8407
|
+
const out = [];
|
|
8408
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8409
|
+
const line = lines[i];
|
|
8410
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
8411
|
+
const implMatch = RUST_IMPL_RE.exec(line);
|
|
8412
|
+
if (implMatch) {
|
|
8413
|
+
out.push({ line: i + 1, text: `impl ${implMatch[1]}` });
|
|
8414
|
+
continue;
|
|
8415
|
+
}
|
|
8416
|
+
const m = RUST_DECL_RE.exec(line);
|
|
8417
|
+
if (!m) continue;
|
|
8418
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8419
|
+
}
|
|
8420
|
+
return out;
|
|
8421
|
+
}
|
|
8422
|
+
function extractMarkdown(lines) {
|
|
8423
|
+
const out = [];
|
|
8424
|
+
let inFence = false;
|
|
8425
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8426
|
+
const line = lines[i];
|
|
8427
|
+
if (MD_FENCE_RE.test(line)) {
|
|
8428
|
+
inFence = !inFence;
|
|
8429
|
+
continue;
|
|
8430
|
+
}
|
|
8431
|
+
if (inFence) continue;
|
|
8432
|
+
const m = MD_HEADING_RE.exec(line);
|
|
8433
|
+
if (!m) continue;
|
|
8434
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8435
|
+
}
|
|
8436
|
+
return out;
|
|
8437
|
+
}
|
|
8438
|
+
function formatOutline(entries) {
|
|
8439
|
+
const total = entries.length;
|
|
8440
|
+
if (total === 0) return "";
|
|
8441
|
+
const lastEntry = entries[total - 1];
|
|
8442
|
+
const width = String(lastEntry.line).length;
|
|
8443
|
+
const fmt = (e) => ` L${String(e.line).padStart(width, " ")} ${e.text}`;
|
|
8444
|
+
const header = `[outline: ${total} symbol${total === 1 ? "" : "s"}]`;
|
|
8445
|
+
if (total <= OUTLINE_MAX_ENTRIES) {
|
|
8446
|
+
return [header, ...entries.map(fmt)].join("\n");
|
|
8447
|
+
}
|
|
8448
|
+
const headCount = OUTLINE_MAX_ENTRIES - OUTLINE_TAIL_KEEP;
|
|
8449
|
+
const headEntries = entries.slice(0, headCount);
|
|
8450
|
+
const tailEntries = entries.slice(-OUTLINE_TAIL_KEEP);
|
|
8451
|
+
const omitted = total - OUTLINE_MAX_ENTRIES;
|
|
8452
|
+
const gapStart = headEntries[headEntries.length - 1].line;
|
|
8453
|
+
const gapEnd = tailEntries[0].line;
|
|
8454
|
+
return [
|
|
8455
|
+
header,
|
|
8456
|
+
...headEntries.map(fmt),
|
|
8457
|
+
` [\u2026 ${omitted} more symbol${omitted === 1 ? "" : "s"} between L${gapStart} and L${gapEnd} \u2026]`,
|
|
8458
|
+
...tailEntries.map(fmt)
|
|
8459
|
+
].join("\n");
|
|
8460
|
+
}
|
|
8461
|
+
|
|
8091
8462
|
// src/tools/fs/search.ts
|
|
8092
8463
|
import { promises as fs3 } from "fs";
|
|
8093
|
-
import * as
|
|
8464
|
+
import * as pathMod4 from "path";
|
|
8094
8465
|
function throwIfAborted(signal) {
|
|
8095
8466
|
if (!signal?.aborted) return;
|
|
8096
8467
|
throw new DOMException("search aborted by user", "AbortError");
|
|
8097
8468
|
}
|
|
8098
8469
|
function displayRel3(rootDir, full) {
|
|
8099
|
-
return
|
|
8470
|
+
return pathMod4.relative(rootDir, full).replaceAll("\\", "/");
|
|
8100
8471
|
}
|
|
8101
8472
|
async function searchFiles(ctx, startAbs, args) {
|
|
8102
8473
|
throwIfAborted(args.signal);
|
|
@@ -8120,7 +8491,7 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
8120
8491
|
}
|
|
8121
8492
|
for (const e of entries) {
|
|
8122
8493
|
throwIfAborted(args.signal);
|
|
8123
|
-
const full =
|
|
8494
|
+
const full = pathMod4.join(dir, e.name);
|
|
8124
8495
|
const lower = e.name.toLowerCase();
|
|
8125
8496
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
8126
8497
|
if (hit) {
|
|
@@ -8199,11 +8570,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
8199
8570
|
throwIfAborted(args.signal);
|
|
8200
8571
|
if (e.isDirectory()) {
|
|
8201
8572
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
8202
|
-
await walk2(
|
|
8573
|
+
await walk2(pathMod4.join(dir, e.name));
|
|
8203
8574
|
continue;
|
|
8204
8575
|
}
|
|
8205
8576
|
if (!e.isFile()) continue;
|
|
8206
|
-
const full =
|
|
8577
|
+
const full = pathMod4.join(dir, e.name);
|
|
8207
8578
|
if (ctx.nameMatch && !ctx.nameMatch(e.name, displayRel3(ctx.rootDir, full))) continue;
|
|
8208
8579
|
if (ctx.isBinaryByName(e.name)) continue;
|
|
8209
8580
|
let fh;
|
|
@@ -8300,47 +8671,11 @@ var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
|
8300
8671
|
var DEFAULT_AUTO_PREVIEW_LINES = 200;
|
|
8301
8672
|
var AUTO_PREVIEW_HEAD_LINES = 80;
|
|
8302
8673
|
var AUTO_PREVIEW_TAIL_LINES = 40;
|
|
8303
|
-
var
|
|
8304
|
-
var OUTLINE_TAIL_KEEP = 5;
|
|
8305
|
-
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
8306
|
-
function extractTsExportOutline(lines) {
|
|
8307
|
-
const out = [];
|
|
8308
|
-
for (let i = 0; i < lines.length; i++) {
|
|
8309
|
-
const line = lines[i];
|
|
8310
|
-
if (!line.startsWith("export ")) continue;
|
|
8311
|
-
const m = TS_EXPORT_RE.exec(line);
|
|
8312
|
-
if (!m) continue;
|
|
8313
|
-
out.push({ line: i + 1, kind: m[1], name: m[2] });
|
|
8314
|
-
}
|
|
8315
|
-
return out;
|
|
8316
|
-
}
|
|
8317
|
-
function formatOutline(entries) {
|
|
8318
|
-
const total = entries.length;
|
|
8319
|
-
if (total === 0) return "";
|
|
8320
|
-
const lastEntry = entries[total - 1];
|
|
8321
|
-
const width = String(lastEntry.line).length;
|
|
8322
|
-
const fmt = (e) => ` L${String(e.line).padStart(width, " ")} export ${e.kind} ${e.name}`;
|
|
8323
|
-
const header = `[outline: ${total} top-level export${total === 1 ? "" : "s"}]`;
|
|
8324
|
-
if (total <= OUTLINE_MAX_ENTRIES) {
|
|
8325
|
-
return [header, ...entries.map(fmt)].join("\n");
|
|
8326
|
-
}
|
|
8327
|
-
const headCount = OUTLINE_MAX_ENTRIES - OUTLINE_TAIL_KEEP;
|
|
8328
|
-
const headEntries = entries.slice(0, headCount);
|
|
8329
|
-
const tailEntries = entries.slice(-OUTLINE_TAIL_KEEP);
|
|
8330
|
-
const omitted = total - OUTLINE_MAX_ENTRIES;
|
|
8331
|
-
const gapStart = headEntries[headEntries.length - 1].line;
|
|
8332
|
-
const gapEnd = tailEntries[0].line;
|
|
8333
|
-
return [
|
|
8334
|
-
header,
|
|
8335
|
-
...headEntries.map(fmt),
|
|
8336
|
-
` [\u2026 ${omitted} more export${omitted === 1 ? "" : "s"} between L${gapStart} and L${gapEnd} \u2026]`,
|
|
8337
|
-
...tailEntries.map(fmt)
|
|
8338
|
-
].join("\n");
|
|
8339
|
-
}
|
|
8674
|
+
var OUTLINE_MAX_ENTRIES2 = 30;
|
|
8340
8675
|
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
8341
8676
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
8342
8677
|
function displayRel4(rootDir, full) {
|
|
8343
|
-
return
|
|
8678
|
+
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
8344
8679
|
}
|
|
8345
8680
|
var GLOB_METACHARS = /[*?{[]/;
|
|
8346
8681
|
function compileNameFilter(filter) {
|
|
@@ -8359,16 +8694,16 @@ function isLikelyBinaryByName(name) {
|
|
|
8359
8694
|
return BINARY_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
8360
8695
|
}
|
|
8361
8696
|
function registerFilesystemTools(registry, opts) {
|
|
8362
|
-
const rootDir =
|
|
8697
|
+
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
8363
8698
|
const allowWriting = opts.allowWriting !== false;
|
|
8364
8699
|
const maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
8365
8700
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
8366
|
-
const normRoot =
|
|
8701
|
+
const normRoot = pathMod5.resolve(rootDir);
|
|
8367
8702
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
8368
8703
|
const inflightGate = /* @__PURE__ */ new Map();
|
|
8369
8704
|
function pathIsUnder(child, parent) {
|
|
8370
|
-
const rel =
|
|
8371
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8705
|
+
const rel = pathMod5.relative(parent, child);
|
|
8706
|
+
return rel === "" || !rel.startsWith("..") && !pathMod5.isAbsolute(rel);
|
|
8372
8707
|
}
|
|
8373
8708
|
function looksLikeAbsoluteSystemPath(raw) {
|
|
8374
8709
|
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
@@ -8384,7 +8719,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8384
8719
|
if (pathIsUnder(abs, dir)) return;
|
|
8385
8720
|
}
|
|
8386
8721
|
const stat2 = await safeLstat(abs);
|
|
8387
|
-
const allowPrefix = stat2?.isDirectory() ? abs :
|
|
8722
|
+
const allowPrefix = stat2?.isDirectory() ? abs : pathMod5.dirname(abs);
|
|
8388
8723
|
let pending = inflightGate.get(allowPrefix);
|
|
8389
8724
|
if (!pending) {
|
|
8390
8725
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
@@ -8412,7 +8747,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8412
8747
|
throw new Error("path must be a non-empty string");
|
|
8413
8748
|
}
|
|
8414
8749
|
if (looksLikeAbsoluteSystemPath(raw)) {
|
|
8415
|
-
const abs =
|
|
8750
|
+
const abs = pathMod5.resolve(raw);
|
|
8416
8751
|
if (pathIsUnder(abs, normRoot)) return abs;
|
|
8417
8752
|
await ensureOutsideSandboxAllowed(abs, intent, toolName, ctx);
|
|
8418
8753
|
return abs;
|
|
@@ -8422,7 +8757,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8422
8757
|
normalized = normalized.slice(1);
|
|
8423
8758
|
}
|
|
8424
8759
|
if (normalized.length === 0) normalized = ".";
|
|
8425
|
-
const resolved =
|
|
8760
|
+
const resolved = pathMod5.resolve(rootDir, normalized);
|
|
8426
8761
|
if (!pathIsUnder(resolved, normRoot)) {
|
|
8427
8762
|
throw new Error(
|
|
8428
8763
|
`path escapes sandbox root (${normRoot}): ${raw} \u2014 use an absolute system path like /Users/foo or C:\\Users\\foo to request approved outside-sandbox access`
|
|
@@ -8444,7 +8779,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8444
8779
|
- head: N \u2192 first N lines (imports, public API, small configs)
|
|
8445
8780
|
- tail: N \u2192 last N lines (recently-added code, log tails)
|
|
8446
8781
|
- range: "A-B" \u2192 inclusive line range A..B, 1-indexed (e.g. "120-180" around an edit site)
|
|
8447
|
-
When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker, plus a top-level
|
|
8782
|
+
When none of these is given AND the file is longer than ${DEFAULT_AUTO_PREVIEW_LINES} lines, the tool auto-returns a head+tail preview with an "N lines omitted" marker, plus a top-level symbol outline (TS/JS exports, Python def/class, Go func/type, Rust fn/struct/impl/trait, Markdown headings, with line numbers, capped at ${OUTLINE_MAX_ENTRIES2}) so you can pick a smart range without a follow-up grep. If you need the middle, re-call with a range. Prefer search_content to locate a symbol first only when the outline doesn't have what you want \u2014 one scoped read beats three full-file reads.`,
|
|
8448
8783
|
readOnly: true,
|
|
8449
8784
|
stormExempt: true,
|
|
8450
8785
|
parameters: {
|
|
@@ -8512,7 +8847,7 @@ ${slice.join("\n")}`;
|
|
|
8512
8847
|
const head = lines.slice(0, AUTO_PREVIEW_HEAD_LINES).join("\n");
|
|
8513
8848
|
const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
|
|
8514
8849
|
const omitted = totalLines - AUTO_PREVIEW_HEAD_LINES - AUTO_PREVIEW_TAIL_LINES;
|
|
8515
|
-
const outline = formatOutline(
|
|
8850
|
+
const outline = formatOutline(extractOutline(abs, lines));
|
|
8516
8851
|
const parts = [
|
|
8517
8852
|
`[auto-preview: head ${AUTO_PREVIEW_HEAD_LINES} + tail ${AUTO_PREVIEW_TAIL_LINES} of ${totalLines} lines]`,
|
|
8518
8853
|
head
|
|
@@ -8620,7 +8955,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8620
8955
|
lines.push(line);
|
|
8621
8956
|
emitted++;
|
|
8622
8957
|
if (e.isDirectory() && !skip) {
|
|
8623
|
-
await walk2(
|
|
8958
|
+
await walk2(pathMod5.join(dir, e.name), depth + 1);
|
|
8624
8959
|
}
|
|
8625
8960
|
}
|
|
8626
8961
|
};
|
|
@@ -8780,7 +9115,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8780
9115
|
},
|
|
8781
9116
|
fn: async (args, ctx) => {
|
|
8782
9117
|
const abs = await safePath(args.path, "write_file", ctx, "write");
|
|
8783
|
-
await fs4.mkdir(
|
|
9118
|
+
await fs4.mkdir(pathMod5.dirname(abs), { recursive: true });
|
|
8784
9119
|
await fs4.writeFile(abs, args.content, "utf8");
|
|
8785
9120
|
return `wrote ${args.content.length} chars to ${displayRel4(rootDir, abs)}`;
|
|
8786
9121
|
}
|
|
@@ -8866,7 +9201,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8866
9201
|
fn: async (args, ctx) => {
|
|
8867
9202
|
const src = await safePath(args.source, "move_file", ctx, "write");
|
|
8868
9203
|
const dst = await safePath(args.destination, "move_file", ctx, "write");
|
|
8869
|
-
await fs4.mkdir(
|
|
9204
|
+
await fs4.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
8870
9205
|
await fs4.rename(src, dst);
|
|
8871
9206
|
return `moved ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
|
|
8872
9207
|
}
|
|
@@ -8934,7 +9269,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8934
9269
|
fn: async (args, ctx) => {
|
|
8935
9270
|
const src = await safePath(args.source, "copy_file", ctx);
|
|
8936
9271
|
const dst = await safePath(args.destination, "copy_file", ctx, "write");
|
|
8937
|
-
await fs4.mkdir(
|
|
9272
|
+
await fs4.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
8938
9273
|
await fs4.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
8939
9274
|
return `copied ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
|
|
8940
9275
|
}
|
|
@@ -8946,6 +9281,16 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8946
9281
|
function registerMemoryTools(registry, opts = {}) {
|
|
8947
9282
|
const store = new MemoryStore({ homeDir: opts.homeDir, projectRoot: opts.projectRoot });
|
|
8948
9283
|
const hasProject = store.hasProjectScope();
|
|
9284
|
+
const registry_types = loadMemoryTypeRegistry();
|
|
9285
|
+
const customTypeNames = registry_types.filter((r) => !r.builtin).map((r) => r.name);
|
|
9286
|
+
const typeDescParts = [
|
|
9287
|
+
"'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
9288
|
+
];
|
|
9289
|
+
if (customTypeNames.length > 0) {
|
|
9290
|
+
typeDescParts.push(
|
|
9291
|
+
`Custom types declared in config: ${customTypeNames.join(", ")}. Any string is accepted; unknown types are stored verbatim and treated as 'reference' priority.`
|
|
9292
|
+
);
|
|
9293
|
+
}
|
|
8949
9294
|
registry.register({
|
|
8950
9295
|
name: "remember",
|
|
8951
9296
|
description: "Save a memory for future sessions. Use when the user states a preference, corrects your approach, shares a non-obvious fact about this project, or explicitly asks you to remember something. Don't remember transient task state \u2014 only things worth recalling next session. The memory is written now but won't re-load into the system prompt until the next `/new` or launch.",
|
|
@@ -8954,8 +9299,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8954
9299
|
properties: {
|
|
8955
9300
|
type: {
|
|
8956
9301
|
type: "string",
|
|
8957
|
-
|
|
8958
|
-
description: "'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
9302
|
+
description: typeDescParts.join(" ")
|
|
8959
9303
|
},
|
|
8960
9304
|
scope: {
|
|
8961
9305
|
type: "string",
|
|
@@ -8973,6 +9317,16 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8973
9317
|
content: {
|
|
8974
9318
|
type: "string",
|
|
8975
9319
|
description: "Full memory body in markdown. For feedback/project types, structure as: rule/fact, then **Why:** line, then **How to apply:** line."
|
|
9320
|
+
},
|
|
9321
|
+
priority: {
|
|
9322
|
+
type: "string",
|
|
9323
|
+
enum: ["low", "medium", "high"],
|
|
9324
|
+
description: "Optional per-memory priority. `high` injects the entry into a `# HIGH PRIORITY constraints` block at the top of the system prompt \u2014 use sparingly, only for hard rules the model must never violate."
|
|
9325
|
+
},
|
|
9326
|
+
expires: {
|
|
9327
|
+
type: "string",
|
|
9328
|
+
enum: ["project_end"],
|
|
9329
|
+
description: "Optional lifecycle hint. `project_end` causes `/memory clear project` to also remove this entry even when it's stored at global scope."
|
|
8976
9330
|
}
|
|
8977
9331
|
},
|
|
8978
9332
|
required: ["type", "scope", "name", "description", "content"]
|
|
@@ -8989,7 +9343,9 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8989
9343
|
type: args.type,
|
|
8990
9344
|
scope: args.scope,
|
|
8991
9345
|
description: args.description,
|
|
8992
|
-
body: args.content
|
|
9346
|
+
body: args.content,
|
|
9347
|
+
...args.priority ? { priority: args.priority } : {},
|
|
9348
|
+
...args.expires ? { expires: args.expires } : {}
|
|
8993
9349
|
});
|
|
8994
9350
|
const key = sanitizeMemoryName(args.name);
|
|
8995
9351
|
return [
|
|
@@ -9584,9 +9940,7 @@ ${NEGATIVE_CLAIM_RULE}
|
|
|
9584
9940
|
|
|
9585
9941
|
${TUI_FORMATTING_RULES}`;
|
|
9586
9942
|
var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
|
|
9587
|
-
var
|
|
9588
|
-
var MIN_MAX_ITERS = 1;
|
|
9589
|
-
var MAX_MAX_ITERS = 32;
|
|
9943
|
+
var DEFAULT_PAUSE_EVERY = 16;
|
|
9590
9944
|
var BUDGET_WARN_THRESHOLD = 3;
|
|
9591
9945
|
function budgetParagraph(maxToolIters) {
|
|
9592
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.`;
|
|
@@ -9609,12 +9963,13 @@ function subagentBudgetHint(spawnCount, totalTokens) {
|
|
|
9609
9963
|
}
|
|
9610
9964
|
async function spawnSubagent(opts) {
|
|
9611
9965
|
const model = opts.model ?? DEFAULT_SUBAGENT_MODEL;
|
|
9612
|
-
const maxToolIters = opts.maxToolIters ??
|
|
9966
|
+
const maxToolIters = opts.maxToolIters ?? DEFAULT_PAUSE_EVERY;
|
|
9613
9967
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
9614
9968
|
const sink = opts.sink;
|
|
9615
9969
|
const skillName = opts.skillName;
|
|
9616
|
-
const startedAt = Date.now();
|
|
9617
9970
|
const runId = nextRunId();
|
|
9971
|
+
const sessionName = opts.resumeSession ?? `subagent-${runId}-${timestampSuffix()}`;
|
|
9972
|
+
const startedAt = Date.now();
|
|
9618
9973
|
const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
|
|
9619
9974
|
sink?.current?.({
|
|
9620
9975
|
kind: "start",
|
|
@@ -9694,10 +10049,9 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
9694
10049
|
reasoningEffort: DEFAULT_SUBAGENT_EFFORT,
|
|
9695
10050
|
maxToolIters,
|
|
9696
10051
|
hooks: [],
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
stream: true
|
|
10052
|
+
stream: true,
|
|
10053
|
+
session: sessionName,
|
|
10054
|
+
onIterBudgetExhausted: "pause"
|
|
9701
10055
|
});
|
|
9702
10056
|
const onParentAbort = () => childLoop.abort();
|
|
9703
10057
|
if (opts.parentSignal?.aborted) {
|
|
@@ -9709,8 +10063,13 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
9709
10063
|
let errorMessage;
|
|
9710
10064
|
let toolIter = 0;
|
|
9711
10065
|
let summarisingEmitted = false;
|
|
10066
|
+
let paused = false;
|
|
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;
|
|
9712
10071
|
try {
|
|
9713
|
-
for await (const ev of childLoop.step(
|
|
10072
|
+
for await (const ev of childLoop.step(taskForLoop)) {
|
|
9714
10073
|
sink?.current?.({ kind: "inner", runId, task: taskPreview, skillName, model, inner: ev });
|
|
9715
10074
|
if (ev.role === "tool") {
|
|
9716
10075
|
toolIter++;
|
|
@@ -9748,13 +10107,17 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
9748
10107
|
if (ev.role === "error") {
|
|
9749
10108
|
errorMessage = ev.error ?? "subagent error";
|
|
9750
10109
|
}
|
|
10110
|
+
if (ev.role === "paused") {
|
|
10111
|
+
paused = true;
|
|
10112
|
+
if (ev.partialSummary) partialSummary = ev.partialSummary;
|
|
10113
|
+
}
|
|
9751
10114
|
}
|
|
9752
10115
|
} catch (err) {
|
|
9753
10116
|
errorMessage = err.message;
|
|
9754
10117
|
} finally {
|
|
9755
10118
|
opts.parentSignal?.removeEventListener("abort", onParentAbort);
|
|
9756
10119
|
}
|
|
9757
|
-
if (!errorMessage && !final) {
|
|
10120
|
+
if (!errorMessage && !final && !paused) {
|
|
9758
10121
|
errorMessage = opts.parentSignal?.aborted ? "subagent aborted before producing an answer" : "subagent ended without producing an answer";
|
|
9759
10122
|
}
|
|
9760
10123
|
const elapsedMs = Date.now() - startedAt;
|
|
@@ -9788,7 +10151,10 @@ ${budgetParagraph(maxToolIters)}`,
|
|
|
9788
10151
|
costUsd: costUsd2,
|
|
9789
10152
|
model,
|
|
9790
10153
|
skillName,
|
|
9791
|
-
usage
|
|
10154
|
+
usage,
|
|
10155
|
+
paused: paused || void 0,
|
|
10156
|
+
pausedSession: paused ? sessionName : void 0,
|
|
10157
|
+
partialSummary: paused ? partialSummary : void 0
|
|
9792
10158
|
};
|
|
9793
10159
|
}
|
|
9794
10160
|
function aggregateChildUsage(loop) {
|
|
@@ -9803,6 +10169,18 @@ function aggregateChildUsage(loop) {
|
|
|
9803
10169
|
return agg;
|
|
9804
10170
|
}
|
|
9805
10171
|
function formatSubagentResult(r) {
|
|
10172
|
+
if (r.paused) {
|
|
10173
|
+
return JSON.stringify({
|
|
10174
|
+
success: false,
|
|
10175
|
+
paused: true,
|
|
10176
|
+
resume_session: r.pausedSession,
|
|
10177
|
+
tool_iters: r.toolIters,
|
|
10178
|
+
elapsed_ms: r.elapsedMs,
|
|
10179
|
+
cost_usd: r.costUsd,
|
|
10180
|
+
partial_summary: r.partialSummary,
|
|
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.`
|
|
10182
|
+
});
|
|
10183
|
+
}
|
|
9806
10184
|
if (!r.success) {
|
|
9807
10185
|
return JSON.stringify({
|
|
9808
10186
|
success: false,
|
|
@@ -9825,7 +10203,7 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
9825
10203
|
const baseSystem = opts.defaultSystem ?? SUBAGENT_BASE_SYSTEM;
|
|
9826
10204
|
const defaultSystemBase = opts.projectRoot ? applyProjectMemory(baseSystem, opts.projectRoot) : baseSystem;
|
|
9827
10205
|
const defaultModel = opts.defaultModel ?? DEFAULT_SUBAGENT_MODEL;
|
|
9828
|
-
const maxToolIters = opts.maxToolIters ??
|
|
10206
|
+
const maxToolIters = opts.maxToolIters ?? DEFAULT_PAUSE_EVERY;
|
|
9829
10207
|
const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
|
|
9830
10208
|
const sink = opts.sink;
|
|
9831
10209
|
let sessionSpawnCount = 0;
|
|
@@ -9833,17 +10211,17 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
9833
10211
|
parentRegistry.register({
|
|
9834
10212
|
name: SUBAGENT_TOOL_NAME,
|
|
9835
10213
|
parallelSafe: true,
|
|
9836
|
-
description: "Spawn an isolated subagent to handle a self-contained subtask in a fresh context, returning only its final answer. **Prefer direct tools.** Spawn primarily for parallel fan-out (2+ independent investigations issued in one tool batch) or when the work would otherwise need >10 file reads/searches whose trail you don't need to keep. Single greps, 1-3 file cross-references, and 'keep my context clean for one question' are NOT good reasons to spawn \u2014 direct tools are cheaper and let you reference the evidence later. Each spawn pays a
|
|
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. **Pause/resume**: subagents yield back to you every `max_iters` tool calls. A paused result returns `{ paused: true, resume_session: \"...\" }` \u2014 you can either accept the partial state, or call spawn_subagent again with `resume_session` set to continue where it left off (cheap: the prior prefix is cached). Tasks expected to run much longer than 16 tool calls don't need a huge `max_iters` \u2014 just resume.",
|
|
9837
10215
|
parameters: {
|
|
9838
10216
|
type: "object",
|
|
9839
10217
|
properties: {
|
|
9840
10218
|
task: {
|
|
9841
10219
|
type: "string",
|
|
9842
|
-
description:
|
|
10220
|
+
description: 'The subtask the subagent should perform. Be specific and self-contained \u2014 the subagent has none of your conversation context, only what you write here. When resuming via `resume_session`, this becomes a continuation nudge (e.g. "finish what you started" or a delta instruction).'
|
|
9843
10221
|
},
|
|
9844
10222
|
system: {
|
|
9845
10223
|
type: "string",
|
|
9846
|
-
description: "Optional override for the subagent's system prompt. The default tells it to stay focused and return a concise answer; override only when the subtask needs a specialized persona."
|
|
10224
|
+
description: "Optional override for the subagent's system prompt. The default tells it to stay focused and return a concise answer; override only when the subtask needs a specialized persona. Ignored on resume \u2014 the prior session keeps its original system prompt for cache stability."
|
|
9847
10225
|
},
|
|
9848
10226
|
model: {
|
|
9849
10227
|
type: "string",
|
|
@@ -9852,9 +10230,11 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
9852
10230
|
},
|
|
9853
10231
|
max_iters: {
|
|
9854
10232
|
type: "integer",
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
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
|
+
resume_session: {
|
|
10236
|
+
type: "string",
|
|
10237
|
+
description: "Provide the `resume_session` value returned by a previous paused spawn to continue that subagent. When set, prior messages are loaded from disk and the original system prompt is reused (cache-friendly). `task` becomes a continuation nudge."
|
|
9858
10238
|
},
|
|
9859
10239
|
type: {
|
|
9860
10240
|
type: "string",
|
|
@@ -9876,7 +10256,8 @@ function registerSubagentTool(parentRegistry, opts) {
|
|
|
9876
10256
|
const system = typeof args.system === "string" && args.system.trim().length > 0 ? args.system.trim() : typeSpec?.system ?? `${defaultSystemBase}
|
|
9877
10257
|
|
|
9878
10258
|
${escalationContract(model)}`;
|
|
9879
|
-
const callerIters =
|
|
10259
|
+
const callerIters = parseMaxIters(args.max_iters);
|
|
10260
|
+
const resumeSession = typeof args.resume_session === "string" && args.resume_session.trim().length > 0 ? args.resume_session.trim() : void 0;
|
|
9880
10261
|
const result = await spawnSubagent({
|
|
9881
10262
|
client: opts.client,
|
|
9882
10263
|
parentRegistry,
|
|
@@ -9886,7 +10267,8 @@ ${escalationContract(model)}`;
|
|
|
9886
10267
|
maxToolIters: callerIters ?? typeSpec?.maxToolIters ?? maxToolIters,
|
|
9887
10268
|
maxResultChars,
|
|
9888
10269
|
sink,
|
|
9889
|
-
parentSignal: ctx?.signal
|
|
10270
|
+
parentSignal: ctx?.signal,
|
|
10271
|
+
resumeSession
|
|
9890
10272
|
});
|
|
9891
10273
|
sessionSpawnCount++;
|
|
9892
10274
|
sessionSpawnTokens += result.usage.totalTokens;
|
|
@@ -9898,12 +10280,10 @@ ${hint}` : formatted;
|
|
|
9898
10280
|
});
|
|
9899
10281
|
return parentRegistry;
|
|
9900
10282
|
}
|
|
9901
|
-
function
|
|
10283
|
+
function parseMaxIters(raw) {
|
|
9902
10284
|
if (typeof raw !== "number" || !Number.isFinite(raw)) return void 0;
|
|
9903
10285
|
const n = Math.floor(raw);
|
|
9904
|
-
|
|
9905
|
-
if (n > MAX_MAX_ITERS) return MAX_MAX_ITERS;
|
|
9906
|
-
return n;
|
|
10286
|
+
return n >= 1 ? n : void 0;
|
|
9907
10287
|
}
|
|
9908
10288
|
function forkRegistryExcluding(parent, exclude) {
|
|
9909
10289
|
const child = new ToolRegistry();
|
|
@@ -9932,11 +10312,11 @@ function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
|
9932
10312
|
}
|
|
9933
10313
|
|
|
9934
10314
|
// src/tools/shell.ts
|
|
9935
|
-
import * as
|
|
10315
|
+
import * as pathMod9 from "path";
|
|
9936
10316
|
|
|
9937
10317
|
// src/tools/jobs.ts
|
|
9938
10318
|
import { spawn as spawn2 } from "child_process";
|
|
9939
|
-
import * as
|
|
10319
|
+
import * as pathMod6 from "path";
|
|
9940
10320
|
function killProcessTree(pid, signal) {
|
|
9941
10321
|
if (process.platform === "win32") {
|
|
9942
10322
|
const args = ["/pid", String(pid), "/T"];
|
|
@@ -9996,7 +10376,7 @@ var JobRegistry = class {
|
|
|
9996
10376
|
const maxBytes = opts.maxBufferBytes ?? DEFAULT_OUTPUT_CAP_BYTES;
|
|
9997
10377
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
9998
10378
|
const spawnOpts = {
|
|
9999
|
-
cwd:
|
|
10379
|
+
cwd: pathMod6.resolve(opts.cwd),
|
|
10000
10380
|
shell: false,
|
|
10001
10381
|
windowsHide: true,
|
|
10002
10382
|
env: process.env,
|
|
@@ -10109,12 +10489,15 @@ ${job.output.slice(start)}`;
|
|
|
10109
10489
|
job.signalReady();
|
|
10110
10490
|
job.signalClosed();
|
|
10111
10491
|
});
|
|
10112
|
-
|
|
10492
|
+
const settleClosed = (code) => {
|
|
10493
|
+
if (!job.running && job.exitCode !== null) return;
|
|
10113
10494
|
job.running = false;
|
|
10114
10495
|
job.exitCode = code;
|
|
10115
10496
|
job.signalReady();
|
|
10116
10497
|
job.signalClosed();
|
|
10117
|
-
}
|
|
10498
|
+
};
|
|
10499
|
+
child.on("exit", settleClosed);
|
|
10500
|
+
child.on("close", settleClosed);
|
|
10118
10501
|
const onAbort = () => this.stop(id, { graceMs: 100 });
|
|
10119
10502
|
if (opts.signal?.aborted) {
|
|
10120
10503
|
onAbort();
|
|
@@ -10171,21 +10554,26 @@ ${job.output.slice(start)}`;
|
|
|
10171
10554
|
latestOutput: job.output
|
|
10172
10555
|
};
|
|
10173
10556
|
}
|
|
10174
|
-
const timeoutMs = Math.max(0, Math.min(
|
|
10557
|
+
const timeoutMs = Math.max(0, Math.min(3e5, opts.timeoutMs ?? 5e3));
|
|
10558
|
+
const waitFor = opts.waitFor ?? "exit";
|
|
10175
10559
|
const startOutput = job.output;
|
|
10560
|
+
const racers = [job.closedPromise];
|
|
10176
10561
|
let wakeOutput = null;
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10562
|
+
if (waitFor === "output-or-exit") {
|
|
10563
|
+
racers.push(
|
|
10564
|
+
new Promise((resolve10) => {
|
|
10565
|
+
wakeOutput = resolve10;
|
|
10566
|
+
job.outputWaiters.add(resolve10);
|
|
10567
|
+
})
|
|
10568
|
+
);
|
|
10569
|
+
}
|
|
10181
10570
|
let timer = null;
|
|
10182
|
-
|
|
10183
|
-
job.closedPromise,
|
|
10184
|
-
outputPromise,
|
|
10571
|
+
racers.push(
|
|
10185
10572
|
new Promise((resolve10) => {
|
|
10186
10573
|
timer = setTimeout(resolve10, timeoutMs);
|
|
10187
10574
|
})
|
|
10188
|
-
|
|
10575
|
+
);
|
|
10576
|
+
await Promise.race(racers);
|
|
10189
10577
|
if (timer) clearTimeout(timer);
|
|
10190
10578
|
if (wakeOutput) job.outputWaiters.delete(wakeOutput);
|
|
10191
10579
|
return {
|
|
@@ -10219,6 +10607,10 @@ ${job.output.slice(start)}`;
|
|
|
10219
10607
|
}
|
|
10220
10608
|
}
|
|
10221
10609
|
await Promise.race([job.closedPromise, new Promise((res) => setTimeout(res, 5e3))]);
|
|
10610
|
+
if (job.running) {
|
|
10611
|
+
job.running = false;
|
|
10612
|
+
job.signalClosed();
|
|
10613
|
+
}
|
|
10222
10614
|
}
|
|
10223
10615
|
return snapshot(job);
|
|
10224
10616
|
}
|
|
@@ -10252,6 +10644,12 @@ ${job.output.slice(start)}`;
|
|
|
10252
10644
|
}
|
|
10253
10645
|
const remaining = Math.max(800, deadlineMs - elapsed());
|
|
10254
10646
|
await Promise.race([allClose, new Promise((res) => setTimeout(res, remaining))]);
|
|
10647
|
+
for (const job of runningJobs) {
|
|
10648
|
+
if (job.running) {
|
|
10649
|
+
job.running = false;
|
|
10650
|
+
job.signalClosed();
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10255
10653
|
}
|
|
10256
10654
|
/** Count of still-running jobs — drives the TUI status-bar indicator. */
|
|
10257
10655
|
runningCount() {
|
|
@@ -10282,12 +10680,13 @@ function latestOutputSince(before, after) {
|
|
|
10282
10680
|
// src/tools/shell/exec.ts
|
|
10283
10681
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
10284
10682
|
import { existsSync as existsSync8, statSync as statSync5 } from "fs";
|
|
10285
|
-
import * as
|
|
10683
|
+
import * as pathMod8 from "path";
|
|
10286
10684
|
|
|
10287
10685
|
// src/tools/shell-chain.ts
|
|
10288
10686
|
import { spawn as spawn3 } from "child_process";
|
|
10289
10687
|
import { closeSync, openSync } from "fs";
|
|
10290
|
-
import
|
|
10688
|
+
import { devNull } from "os";
|
|
10689
|
+
import * as pathMod7 from "path";
|
|
10291
10690
|
var UnsupportedSyntaxError = class extends Error {
|
|
10292
10691
|
constructor(detail) {
|
|
10293
10692
|
super(`run_command: ${detail}`);
|
|
@@ -10546,6 +10945,12 @@ async function runChain(chain, opts) {
|
|
|
10546
10945
|
[\u2026 truncated ${output.length - opts.maxOutputChars} chars \u2026]` : output;
|
|
10547
10946
|
return { exitCode: lastExit, output: truncated, timedOut };
|
|
10548
10947
|
}
|
|
10948
|
+
function isNullDeviceAlias(target) {
|
|
10949
|
+
const lower = target.toLowerCase();
|
|
10950
|
+
if (lower === "/dev/null") return true;
|
|
10951
|
+
if (process.platform === "win32" && lower === "nul") return true;
|
|
10952
|
+
return false;
|
|
10953
|
+
}
|
|
10549
10954
|
function openRedirects(redirects, cwd) {
|
|
10550
10955
|
let stdinFd = null;
|
|
10551
10956
|
let stdoutFd = null;
|
|
@@ -10554,7 +10959,7 @@ function openRedirects(redirects, cwd) {
|
|
|
10554
10959
|
let bothFd = null;
|
|
10555
10960
|
const toClose = [];
|
|
10556
10961
|
const open = (target, flags) => {
|
|
10557
|
-
const resolved =
|
|
10962
|
+
const resolved = isNullDeviceAlias(target) ? devNull : pathMod7.resolve(cwd, target);
|
|
10558
10963
|
const fd = openSync(resolved, flags);
|
|
10559
10964
|
toClose.push(fd);
|
|
10560
10965
|
return fd;
|
|
@@ -11051,16 +11456,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
11051
11456
|
const platform = opts.platform ?? process.platform;
|
|
11052
11457
|
if (platform !== "win32") return cmd;
|
|
11053
11458
|
if (!cmd) return cmd;
|
|
11054
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
11055
|
-
if (
|
|
11459
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod8.isAbsolute(cmd)) return cmd;
|
|
11460
|
+
if (pathMod8.extname(cmd)) return cmd;
|
|
11056
11461
|
const env = opts.env ?? process.env;
|
|
11057
11462
|
const pathExt = (getEnvCaseInsensitive(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
11058
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
11463
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod8.delimiter);
|
|
11059
11464
|
const pathDirs = (getEnvCaseInsensitive(env, "PATH") ?? "").split(delimiter2).filter(Boolean);
|
|
11060
11465
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
11061
11466
|
for (const dir of pathDirs) {
|
|
11062
11467
|
for (const ext of pathExt) {
|
|
11063
|
-
const full =
|
|
11468
|
+
const full = pathMod8.win32.join(dir, cmd + ext);
|
|
11064
11469
|
if (isFile(full)) return full;
|
|
11065
11470
|
}
|
|
11066
11471
|
}
|
|
@@ -11176,8 +11581,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
11176
11581
|
function isBareWindowsName(s) {
|
|
11177
11582
|
if (!s) return false;
|
|
11178
11583
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
11179
|
-
if (
|
|
11180
|
-
if (
|
|
11584
|
+
if (pathMod8.isAbsolute(s)) return false;
|
|
11585
|
+
if (pathMod8.extname(s)) return false;
|
|
11181
11586
|
return true;
|
|
11182
11587
|
}
|
|
11183
11588
|
function quoteForCmdExe(arg) {
|
|
@@ -11198,7 +11603,7 @@ var NeedsConfirmationError = class extends Error {
|
|
|
11198
11603
|
}
|
|
11199
11604
|
};
|
|
11200
11605
|
function registerShellTools(registry, opts) {
|
|
11201
|
-
const rootDir =
|
|
11606
|
+
const rootDir = pathMod9.resolve(opts.rootDir);
|
|
11202
11607
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
11203
11608
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
11204
11609
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -11264,7 +11669,7 @@ function registerShellTools(registry, opts) {
|
|
|
11264
11669
|
});
|
|
11265
11670
|
registry.register({
|
|
11266
11671
|
name: "run_background",
|
|
11267
|
-
description: "Spawn a long-running process
|
|
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 / `cd` work as in run_command, but a typical invocation is one binary. Use the binary's own --cwd / --prefix flag for subdirectories. Vite gotcha: npm's `--prefix` only finds package.json; vite's server root still uses process cwd \u2014 pass `vite <project-dir>` instead.\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.",
|
|
11268
11673
|
parameters: {
|
|
11269
11674
|
type: "object",
|
|
11270
11675
|
properties: {
|
|
@@ -11337,7 +11742,7 @@ function registerShellTools(registry, opts) {
|
|
|
11337
11742
|
});
|
|
11338
11743
|
registry.register({
|
|
11339
11744
|
name: "wait_for_job",
|
|
11340
|
-
description: "Block until a background job
|
|
11745
|
+
description: "Block server-side until a background job finishes (or, opt-in, until it produces new output), bounded by `timeoutMs`. Costs ONE tool call regardless of how long the wait runs \u2014 use this instead of polling `job_output` in a loop. Returns JSON with `exited`, `exitCode`, and `latestOutput`.\n\n`waitFor` controls the wake condition:\n- `'exit'` (default) \u2014 only wake on the job exiting (or the timeout). Right for downloads, installs, builds, anything one-shot. Chatty progress bars do NOT wake the wait.\n- `'output-or-exit'` \u2014 also wake whenever the job writes a new line. Right for tailing a dev server / watcher and reacting to a specific log line.\n\nFor a download or install, set `timeoutMs` to the slowest reasonable end-to-end (e.g. 300_000 for a 5-min wheel install).",
|
|
11341
11746
|
readOnly: true,
|
|
11342
11747
|
parallelSafe: true,
|
|
11343
11748
|
stormExempt: true,
|
|
@@ -11347,13 +11752,21 @@ function registerShellTools(registry, opts) {
|
|
|
11347
11752
|
jobId: { type: "integer", description: "Job id returned by run_background." },
|
|
11348
11753
|
timeoutMs: {
|
|
11349
11754
|
type: "integer",
|
|
11350
|
-
description: "Max time to block before returning if
|
|
11755
|
+
description: "Max time to block before returning if the wake condition hasn't fired. Clamped to 0..300000. Default 5000."
|
|
11756
|
+
},
|
|
11757
|
+
waitFor: {
|
|
11758
|
+
type: "string",
|
|
11759
|
+
enum: ["exit", "output-or-exit"],
|
|
11760
|
+
description: "Wake condition. 'exit' = only on job exit (right for downloads / installs / builds). 'output-or-exit' = also on any new output (right for tailing a dev server). Default 'exit'."
|
|
11351
11761
|
}
|
|
11352
11762
|
},
|
|
11353
11763
|
required: ["jobId"]
|
|
11354
11764
|
},
|
|
11355
11765
|
fn: async (args) => {
|
|
11356
|
-
const out = await jobs.waitForJob(args.jobId, {
|
|
11766
|
+
const out = await jobs.waitForJob(args.jobId, {
|
|
11767
|
+
timeoutMs: args.timeoutMs,
|
|
11768
|
+
waitFor: args.waitFor
|
|
11769
|
+
});
|
|
11357
11770
|
if (!out) return `job ${args.jobId}: not found (use list_jobs)`;
|
|
11358
11771
|
return {
|
|
11359
11772
|
jobId: args.jobId,
|
|
@@ -12558,8 +12971,12 @@ var McpClient = class {
|
|
|
12558
12971
|
}
|
|
12559
12972
|
});
|
|
12560
12973
|
promise.catch(() => void 0);
|
|
12974
|
+
const promiseSettled = promise.then(
|
|
12975
|
+
() => void 0,
|
|
12976
|
+
() => void 0
|
|
12977
|
+
);
|
|
12561
12978
|
try {
|
|
12562
|
-
await Promise.race([this.transport.send(frame),
|
|
12979
|
+
await Promise.race([this.transport.send(frame), promiseSettled]);
|
|
12563
12980
|
} catch (err) {
|
|
12564
12981
|
const pending = this.pending.get(id);
|
|
12565
12982
|
if (pending) clearTimeout(pending.timeout);
|
|
@@ -13502,13 +13919,15 @@ Do NOT try to switch via \`run_command\` (\`cd\`, \`pushd\`, etc.) \u2014 your t
|
|
|
13502
13919
|
You have TWO tools for running shell commands, and picking the right one is non-negotiable:
|
|
13503
13920
|
|
|
13504
13921
|
- \`run_command\` \u2014 blocks until the process exits. Use for: **tests, builds, lints, typechecks, git operations, one-shot scripts**. Anything that naturally returns in under a minute.
|
|
13505
|
-
- \`run_background\` \u2014 spawns and detaches after a brief startup window. Use for:
|
|
13922
|
+
- \`run_background\` \u2014 spawns and detaches after a brief startup window. Use for:
|
|
13923
|
+
- **Dev servers / watchers / anything with "dev" / "serve" / "watch" / "start" in the name.** Examples: \`npm run dev\`, \`pnpm dev\`, \`yarn start\`, \`vite\`, \`next dev\`, \`uvicorn app:app --reload\`, \`flask run\`, \`python -m http.server\`, \`cargo watch\`, \`tsc --watch\`, \`webpack serve\`.
|
|
13924
|
+
- **One-shot long jobs that would blow run_command's 60s ceiling.** Examples: \`curl -L -O <big-url>\`, \`wget\`, \`huggingface-cli download\`, multi-GB \`pip install\` / \`npm install\`, big \`cargo build\` / \`docker build\`. Start with \`run_background\`, then call \`wait_for_job\` ONCE with a long \`timeoutMs\` \u2014 that costs one tool call total, not one per poll.
|
|
13506
13925
|
|
|
13507
|
-
**Never use run_command for a dev server.** It will block
|
|
13926
|
+
**Never use run_command for a dev server or a download likely to exceed a minute.** It will block, time out, and the user will see a frozen tool call while the work was actually running fine. Always \`run_background\` + \`wait_for_job\` / \`job_output\`.
|
|
13508
13927
|
|
|
13509
13928
|
After \`run_background\`, tools available to you:
|
|
13510
13929
|
- \`job_output(jobId, tailLines?)\` \u2014 read recent logs to verify startup / debug errors.
|
|
13511
|
-
- \`wait_for_job(jobId, timeoutMs?)\` \u2014 block until the job
|
|
13930
|
+
- \`wait_for_job(jobId, timeoutMs?, waitFor?)\` \u2014 block server-side until the job finishes (or, with \`waitFor: 'output-or-exit'\`, until it writes a new line). ONE tool call per wait regardless of duration. \`timeoutMs\` clamps at 300_000. For downloads / installs / builds: leave \`waitFor\` at the default \`'exit'\` and set \`timeoutMs\` to the slowest reasonable end-to-end. For tailing a dev server and reacting to a specific log line: pass \`waitFor: 'output-or-exit'\` with a short \`timeoutMs\`.
|
|
13512
13931
|
- \`list_jobs\` \u2014 see every job this session (running + exited).
|
|
13513
13932
|
- \`stop_job(jobId)\` \u2014 SIGTERM \u2192 SIGKILL after grace. Stop before switching port / config.
|
|
13514
13933
|
|