reasonix 0.40.0 → 0.41.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 +21 -13
- package/README.zh-CN.md +19 -13
- package/dashboard/app.css +8 -4
- package/dashboard/dist/app.js +279 -224
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/acp-64VQZLDJ.js +708 -0
- package/dist/cli/acp-64VQZLDJ.js.map +1 -0
- package/dist/cli/chat-ZAGX52RV.js +46 -0
- package/dist/cli/{chunk-UCMTWZKU.js → chunk-2CXPDAWX.js} +2 -2
- package/dist/cli/{chunk-CLAN6PVH.js → chunk-4H3ZRJ2U.js} +19 -7
- package/dist/cli/chunk-4H3ZRJ2U.js.map +1 -0
- package/dist/cli/{chunk-A5LSGEEK.js → chunk-4W2CICFQ.js} +21 -10
- package/dist/cli/{chunk-A5LSGEEK.js.map → chunk-4W2CICFQ.js.map} +1 -1
- package/dist/cli/{chunk-CZSJILQP.js → chunk-65Q5HQ26.js} +39 -1
- package/dist/cli/chunk-65Q5HQ26.js.map +1 -0
- package/dist/cli/{chunk-XHQIK7B6.js → chunk-7SPOFTMT.js} +2 -2
- package/dist/cli/{chunk-5GKJLNP2.js → chunk-7VFNPMKG.js} +2 -2
- package/dist/cli/{chunk-UVRXTSK3.js → chunk-A3LL4XDV.js} +8 -2
- package/dist/cli/chunk-A3LL4XDV.js.map +1 -0
- package/dist/cli/{chunk-VLNRQMCI.js → chunk-A7VHMMDE.js} +2 -2
- package/dist/cli/{chunk-R4YTW7PR.js → chunk-ARF3N2SY.js} +56 -12
- package/dist/cli/chunk-ARF3N2SY.js.map +1 -0
- package/dist/cli/{chunk-AVB3WZWU.js → chunk-AT6GGIBV.js} +10 -10
- package/dist/cli/{chunk-RFX7TYVV.js → chunk-BOFL3T45.js} +14 -1
- package/dist/cli/chunk-BOFL3T45.js.map +1 -0
- package/dist/cli/{chunk-SZH34P45.js → chunk-BYZGO3BX.js} +43 -17
- package/dist/cli/chunk-BYZGO3BX.js.map +1 -0
- package/dist/cli/{chunk-7DLHHBGN.js → chunk-CD4SCQL4.js} +6 -4
- package/dist/cli/chunk-CD4SCQL4.js.map +1 -0
- package/dist/cli/{chunk-HCC42PEI.js → chunk-CFY2XLY6.js} +6 -2
- package/dist/cli/chunk-CFY2XLY6.js.map +1 -0
- package/dist/cli/{chunk-26UDIXLD.js → chunk-F2AV2QDK.js} +493 -460
- package/dist/cli/chunk-F2AV2QDK.js.map +1 -0
- package/dist/cli/{chunk-KMWKGPFZ.js → chunk-H4OLWRSX.js} +10 -1
- package/dist/cli/chunk-H4OLWRSX.js.map +1 -0
- package/dist/cli/{chunk-4YV2GBYG.js → chunk-IEA6JOIP.js} +291 -98
- package/dist/cli/chunk-IEA6JOIP.js.map +1 -0
- package/dist/cli/{chunk-WKOMCPXP.js → chunk-KZYLMMU5.js} +21 -13
- package/dist/cli/chunk-KZYLMMU5.js.map +1 -0
- package/dist/cli/{chunk-JWCTX5S4.js → chunk-L7W3HJZQ.js} +2 -2
- package/dist/cli/{chunk-MRLXEMZ7.js → chunk-LN27AKV3.js} +1 -1
- package/dist/cli/chunk-LN27AKV3.js.map +1 -0
- package/dist/cli/{chunk-IYF36OCJ.js → chunk-LTXADNCO.js} +2 -2
- package/dist/cli/{chunk-H7PHYVPM.js → chunk-MHGPBJ2T.js} +44 -8
- package/dist/cli/chunk-MHGPBJ2T.js.map +1 -0
- package/dist/cli/{chunk-ULBW7DYL.js → chunk-RAUPWSYA.js} +2 -2
- package/dist/cli/chunk-SXLJBFIV.js +245 -0
- package/dist/cli/chunk-SXLJBFIV.js.map +1 -0
- package/dist/cli/{chunk-4X3NY5ZM.js → chunk-UV7XJUJH.js} +2 -2
- package/dist/cli/{chunk-XJLZ4HKU.js → chunk-VFG4GIT3.js} +2 -2
- package/dist/cli/{chunk-FFNOMR32.js → chunk-WE3YZULK.js} +2 -2
- package/dist/cli/chunk-Y5XNV3NX.js +25 -0
- package/dist/cli/chunk-Y5XNV3NX.js.map +1 -0
- package/dist/cli/{chunk-XST7BSZJ.js → chunk-YJFKFTAL.js} +7 -1
- package/dist/cli/chunk-YJFKFTAL.js.map +1 -0
- package/dist/cli/{code-YQGVLIT2.js → code-X3M6ENTQ.js} +38 -35
- package/dist/cli/{code-YQGVLIT2.js.map → code-X3M6ENTQ.js.map} +1 -1
- package/dist/cli/{commands-FQZOBLLZ.js → commands-QY7MSQG7.js} +4 -4
- package/dist/cli/{commit-ZS24SHPG.js → commit-BRCQ3OQO.js} +3 -3
- package/dist/cli/{desktop-6OLENOOO.js → desktop-ZTMHQR2Y.js} +247 -28
- package/dist/cli/desktop-ZTMHQR2Y.js.map +1 -0
- package/dist/cli/{diff-2VUKNGEI.js → diff-YASCB7PU.js} +7 -7
- package/dist/cli/{doctor-JO2WNN6C.js → doctor-XCN5ETVP.js} +9 -9
- package/dist/cli/{events-APSVNROZ.js → events-2AJTXR7I.js} +3 -3
- package/dist/cli/index.js +69 -35
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-DCKOE5RF.js → mcp-YMWBLRR7.js} +2 -2
- package/dist/cli/{mcp-browse-D6GBP5RQ.js → mcp-browse-XLDUE6SB.js} +7 -3
- package/dist/cli/mcp-browse-XLDUE6SB.js.map +1 -0
- package/dist/cli/{mcp-inspect-KFGFPJ3E.js → mcp-inspect-H4D2HSJP.js} +5 -7
- package/dist/cli/{mcp-inspect-KFGFPJ3E.js.map → mcp-inspect-H4D2HSJP.js.map} +1 -1
- package/dist/cli/{prompt-PKCCLLAD.js → prompt-RSIHN62V.js} +4 -3
- package/dist/cli/{prune-sessions-LV33R47N.js → prune-sessions-4N3BVST2.js} +2 -2
- package/dist/cli/{replay-WFCYX7XF.js → replay-3GTWM75X.js} +8 -8
- package/dist/cli/{run-IUJYEPMT.js → run-BLZPTRDX.js} +19 -21
- package/dist/cli/{run-IUJYEPMT.js.map → run-BLZPTRDX.js.map} +1 -1
- package/dist/cli/{server-CN4QPPVJ.js → server-DRFPXXSH.js} +16 -12
- package/dist/cli/{server-CN4QPPVJ.js.map → server-DRFPXXSH.js.map} +1 -1
- package/dist/cli/{sessions-F5GPGTJN.js → sessions-BOWFPTXT.js} +13 -13
- package/dist/cli/{setup-WWMDBPSB.js → setup-FQL2JJC2.js} +5 -5
- package/dist/cli/version-XQXYSJ5L.js +30 -0
- package/dist/index.d.ts +148 -103
- package/dist/index.js +468 -134
- 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.map +0 -1
- package/dist/cli/chunk-4YV2GBYG.js.map +0 -1
- package/dist/cli/chunk-7DLHHBGN.js.map +0 -1
- package/dist/cli/chunk-CLAN6PVH.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.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-KMWKGPFZ.js.map +0 -1
- 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-WKOMCPXP.js.map +0 -1
- package/dist/cli/chunk-XST7BSZJ.js.map +0 -1
- package/dist/cli/desktop-6OLENOOO.js.map +0 -1
- package/dist/cli/mcp-browse-D6GBP5RQ.js.map +0 -1
- package/dist/cli/version-KQUPV6T5.js +0 -30
- /package/dist/cli/{chat-G7CUW4ZI.js.map → chat-ZAGX52RV.js.map} +0 -0
- /package/dist/cli/{chunk-UCMTWZKU.js.map → chunk-2CXPDAWX.js.map} +0 -0
- /package/dist/cli/{chunk-XHQIK7B6.js.map → chunk-7SPOFTMT.js.map} +0 -0
- /package/dist/cli/{chunk-5GKJLNP2.js.map → chunk-7VFNPMKG.js.map} +0 -0
- /package/dist/cli/{chunk-VLNRQMCI.js.map → chunk-A7VHMMDE.js.map} +0 -0
- /package/dist/cli/{chunk-AVB3WZWU.js.map → chunk-AT6GGIBV.js.map} +0 -0
- /package/dist/cli/{chunk-JWCTX5S4.js.map → chunk-L7W3HJZQ.js.map} +0 -0
- /package/dist/cli/{chunk-IYF36OCJ.js.map → chunk-LTXADNCO.js.map} +0 -0
- /package/dist/cli/{chunk-ULBW7DYL.js.map → chunk-RAUPWSYA.js.map} +0 -0
- /package/dist/cli/{chunk-4X3NY5ZM.js.map → chunk-UV7XJUJH.js.map} +0 -0
- /package/dist/cli/{chunk-XJLZ4HKU.js.map → chunk-VFG4GIT3.js.map} +0 -0
- /package/dist/cli/{chunk-FFNOMR32.js.map → chunk-WE3YZULK.js.map} +0 -0
- /package/dist/cli/{commands-FQZOBLLZ.js.map → commands-QY7MSQG7.js.map} +0 -0
- /package/dist/cli/{commit-ZS24SHPG.js.map → commit-BRCQ3OQO.js.map} +0 -0
- /package/dist/cli/{diff-2VUKNGEI.js.map → diff-YASCB7PU.js.map} +0 -0
- /package/dist/cli/{doctor-JO2WNN6C.js.map → doctor-XCN5ETVP.js.map} +0 -0
- /package/dist/cli/{events-APSVNROZ.js.map → events-2AJTXR7I.js.map} +0 -0
- /package/dist/cli/{mcp-DCKOE5RF.js.map → mcp-YMWBLRR7.js.map} +0 -0
- /package/dist/cli/{prompt-PKCCLLAD.js.map → prompt-RSIHN62V.js.map} +0 -0
- /package/dist/cli/{prune-sessions-LV33R47N.js.map → prune-sessions-4N3BVST2.js.map} +0 -0
- /package/dist/cli/{replay-WFCYX7XF.js.map → replay-3GTWM75X.js.map} +0 -0
- /package/dist/cli/{sessions-F5GPGTJN.js.map → sessions-BOWFPTXT.js.map} +0 -0
- /package/dist/cli/{setup-WWMDBPSB.js.map → setup-FQL2JJC2.js.map} +0 -0
- /package/dist/cli/{version-KQUPV6T5.js.map → version-XQXYSJ5L.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.",
|
|
@@ -1363,9 +1402,18 @@ var EN = {
|
|
|
1363
1402
|
counterDone: "{done}/{total} done ({pct}%) \xB7 {total} steps",
|
|
1364
1403
|
counterDoneSingular: "{done}/{total} done ({pct}%) \xB7 {total} step"
|
|
1365
1404
|
},
|
|
1405
|
+
noPlanSummary: "No plan body submitted yet.",
|
|
1406
|
+
detailCollapsedHint: "Ctrl+P expands full plan details.",
|
|
1407
|
+
detailExpandedHint: "Ctrl+P collapses details.",
|
|
1408
|
+
detailHeader: "Plan details",
|
|
1409
|
+
detailWindow: "showing lines {start}-{end} of {total}",
|
|
1410
|
+
detailScrollHint: "PgUp/PgDn scroll details \xB7 Home/End jump",
|
|
1366
1411
|
reviseTitle: "Revise plan",
|
|
1367
1412
|
reviseSteps: "{count} steps",
|
|
1368
|
-
reviseFooter: "\u2191\u2193 focus \xB7 space toggle skip \xB7 k/j move \xB7 \u23CE accept \xB7 esc cancel"
|
|
1413
|
+
reviseFooter: "\u2191\u2193 focus \xB7 space toggle skip \xB7 k/j move \xB7 \u23CE accept \xB7 esc cancel",
|
|
1414
|
+
riskMed: " med",
|
|
1415
|
+
riskHigh: " high",
|
|
1416
|
+
completeMsg: "\u25B8 plan complete \u2014 all {total} step{s} done \xB7 archived"
|
|
1369
1417
|
},
|
|
1370
1418
|
app: {
|
|
1371
1419
|
walkCancelledRemaining: "\u25B8 walk cancelled \u2014 {count} block(s) still pending.",
|
|
@@ -1385,6 +1433,9 @@ var EN = {
|
|
|
1385
1433
|
notedVerbAppended: "appended to",
|
|
1386
1434
|
memoryWriteFailed: "# memory write failed",
|
|
1387
1435
|
commandFailed: "! command failed",
|
|
1436
|
+
btwUsage: "\u25B8 /btw <question> \u2014 ask a side question without polluting the conversation context.",
|
|
1437
|
+
btwHeader: "\u226B btw",
|
|
1438
|
+
btwFailed: "/btw failed",
|
|
1388
1439
|
restoreCodeOnly: "\u25B8 /restore is code-mode only",
|
|
1389
1440
|
hookUserPromptSubmit: "UserPromptSubmit hook",
|
|
1390
1441
|
hookStop: "Stop hook",
|
|
@@ -1427,6 +1478,7 @@ var EN = {
|
|
|
1427
1478
|
flashEscalation: "\u21E7 flash requested escalation \u2014 retrying this turn on {model}{reasonSuffix}",
|
|
1428
1479
|
harvestStatus: "extracting plan state from reasoning\u2026",
|
|
1429
1480
|
autoEscalation: "\u21E7 auto-escalating to {model} for the rest of this turn \u2014 flash hit {breakdown}. Next turn falls back to {fallback} unless /pro is armed.",
|
|
1481
|
+
readOnlyLoopEscalation: "\u21E7 auto-escalating to {model} \u2014 flash made {n} consecutive read-only calls without producing an edit or final answer. Next turn falls back to {fallback} unless /pro is armed.",
|
|
1430
1482
|
repeatToolCallWarning: "Caught a repeated tool call \u2014 let the model see the issue and retry with a different approach.",
|
|
1431
1483
|
stormStuck: "Stopped a stuck retry loop \u2014 the model kept calling the same tool with identical args after a self-correction nudge. Try /retry, rephrase, or rule out the underlying blocker.",
|
|
1432
1484
|
stormSuppressed: "Suppressed {count} repeated tool call(s) \u2014 same name + args fired 3+ times.",
|
|
@@ -1462,6 +1514,7 @@ var EN = {
|
|
|
1462
1514
|
basic: {
|
|
1463
1515
|
newInfo: "\u25B8 new conversation \u2014 dropped {count} message(s) from context. Same session, fresh slate.",
|
|
1464
1516
|
newInfoArchived: '\u25B8 new conversation \u2014 dropped {count} message(s) from context. Prior transcript archived as "{archived}" (visible under Sessions).',
|
|
1517
|
+
newInfoSystemReloaded: " \xB7 REASONIX.md / project memory reloaded (next turn pays one cache miss)",
|
|
1465
1518
|
helpTitle: "Commands:",
|
|
1466
1519
|
helpShellTitle: "Shell shortcut:",
|
|
1467
1520
|
helpShell: " !<cmd> run <cmd> in the sandbox root; output goes into",
|
|
@@ -2113,7 +2166,8 @@ var EN = {
|
|
|
2113
2166
|
healthy: "healthy \xB7 {ms}ms",
|
|
2114
2167
|
slow: "slow \xB7 {ms}ms",
|
|
2115
2168
|
verySlow: "very slow \xB7 {ms}ms",
|
|
2116
|
-
slowToast: "\u26A0 MCP `{name}` slow \xB7 {seconds}s p95 over the last {sampleSize} calls"
|
|
2169
|
+
slowToast: "\u26A0 MCP `{name}` slow \xB7 {seconds}s p95 over the last {sampleSize} calls",
|
|
2170
|
+
emptyHint: "\u2139 no MCP servers configured \u2014 try: `reasonix setup` to re-pick, or `reasonix mcp install filesystem`"
|
|
2117
2171
|
},
|
|
2118
2172
|
denyContextInput: {
|
|
2119
2173
|
description: "Tell the agent why you denied this. The next attempt will see your reason as additional context."
|
|
@@ -2237,7 +2291,10 @@ var zhCN = {
|
|
|
2237
2291
|
cancel: "\u53D6\u6D88",
|
|
2238
2292
|
confirm: "\u786E\u8BA4",
|
|
2239
2293
|
back: "\u8FD4\u56DE",
|
|
2240
|
-
next: "\u4E0B\u4E00\u6B65"
|
|
2294
|
+
next: "\u4E0B\u4E00\u6B65",
|
|
2295
|
+
tool: "\u5DE5\u5177",
|
|
2296
|
+
running: "\u8FD0\u884C\u4E2D",
|
|
2297
|
+
noTurns: "(\u6682\u65E0\u5BF9\u8BDD)"
|
|
2241
2298
|
},
|
|
2242
2299
|
cli: {
|
|
2243
2300
|
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",
|
|
@@ -2710,9 +2767,18 @@ var zhCN = {
|
|
|
2710
2767
|
counterDone: "{done}/{total} \u5DF2\u5B8C\u6210\uFF08{pct}%\uFF09 \xB7 \u5171 {total} \u6B65",
|
|
2711
2768
|
counterDoneSingular: "{done}/{total} \u5DF2\u5B8C\u6210\uFF08{pct}%\uFF09 \xB7 \u5171 {total} \u6B65"
|
|
2712
2769
|
},
|
|
2770
|
+
noPlanSummary: "\u5C1A\u672A\u63D0\u4EA4\u8BA1\u5212\u5185\u5BB9\u3002",
|
|
2771
|
+
detailCollapsedHint: "Ctrl+P \u5C55\u5F00\u5B8C\u6574\u8BA1\u5212\u8BE6\u60C5\u3002",
|
|
2772
|
+
detailExpandedHint: "Ctrl+P \u6536\u8D77\u8BE6\u60C5\u3002",
|
|
2773
|
+
detailHeader: "\u8BA1\u5212\u8BE6\u60C5",
|
|
2774
|
+
detailWindow: "\u663E\u793A\u7B2C {start}-{end} \u884C\uFF0C\u5171 {total} \u884C",
|
|
2775
|
+
detailScrollHint: "PgUp/PgDn \u6EDA\u52A8\u8BE6\u60C5 \xB7 Home/End \u8DF3\u8F6C",
|
|
2713
2776
|
reviseTitle: "\u4FEE\u6539\u8BA1\u5212",
|
|
2714
2777
|
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"
|
|
2778
|
+
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",
|
|
2779
|
+
riskMed: " \u4E2D",
|
|
2780
|
+
riskHigh: " \u9AD8",
|
|
2781
|
+
completeMsg: "\u25B8 \u8BA1\u5212\u5B8C\u6210 \u2014 \u5168\u90E8 {total} \u4E2A\u6B65\u9AA4\u5DF2\u5B8C\u6210 \xB7 \u5DF2\u5F52\u6863"
|
|
2716
2782
|
},
|
|
2717
2783
|
app: {
|
|
2718
2784
|
walkCancelledRemaining: "\u25B8 \u6D4F\u89C8\u5DF2\u53D6\u6D88 \u2014 \u8FD8\u6709 {count} \u4E2A\u5F85\u5904\u7406\u7F16\u8F91\u5757\u3002",
|
|
@@ -2732,6 +2798,9 @@ var zhCN = {
|
|
|
2732
2798
|
notedVerbAppended: "\u8FFD\u52A0\u5230",
|
|
2733
2799
|
memoryWriteFailed: "# \u8BB0\u5FC6\u5199\u5165\u5931\u8D25",
|
|
2734
2800
|
commandFailed: "! \u547D\u4EE4\u5931\u8D25",
|
|
2801
|
+
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",
|
|
2802
|
+
btwHeader: "\u226B btw",
|
|
2803
|
+
btwFailed: "/btw \u8C03\u7528\u5931\u8D25",
|
|
2735
2804
|
restoreCodeOnly: "\u25B8 /restore \u4EC5\u5728\u4EE3\u7801\u6A21\u5F0F\u53EF\u7528",
|
|
2736
2805
|
hookUserPromptSubmit: "UserPromptSubmit \u94A9\u5B50",
|
|
2737
2806
|
hookStop: "Stop \u94A9\u5B50",
|
|
@@ -2774,6 +2843,7 @@ var zhCN = {
|
|
|
2774
2843
|
flashEscalation: "\u21E7 flash \u8BF7\u6C42\u5347\u7EA7 \u2014 \u672C\u8F6E\u6539\u7528 {model}{reasonSuffix}",
|
|
2775
2844
|
harvestStatus: "\u6B63\u5728\u4ECE\u63A8\u7406\u8FC7\u7A0B\u63D0\u53D6\u8BA1\u5212\u72B6\u6001\u2026",
|
|
2776
2845
|
autoEscalation: "\u21E7 \u672C\u8F6E\u5269\u4F59\u8C03\u7528\u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u547D\u4E2D {breakdown}\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
|
|
2846
|
+
readOnlyLoopEscalation: "\u21E7 \u81EA\u52A8\u5347\u7EA7\u5230 {model} \u2014 flash \u8FDE\u7EED {n} \u6B21\u53EA\u8BFB\u8C03\u7528\uFF0C\u672A\u4EA7\u51FA\u4FEE\u6539\u6216\u6700\u7EC8\u7B54\u6848\u3002\u4E0B\u4E00\u8F6E\u56DE\u9000\u5230 {fallback}\uFF0C\u9664\u975E\u5DF2\u88C5\u5907 /pro\u3002",
|
|
2777
2847
|
repeatToolCallWarning: "\u62E6\u622A\u5230\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u8BA9\u6A21\u578B\u5BDF\u89C9\u95EE\u9898\u5E76\u6362\u79CD\u65B9\u5F0F\u91CD\u8BD5\u3002",
|
|
2778
2848
|
stormStuck: "\u5DF2\u505C\u6B62\u5361\u6B7B\u7684\u91CD\u8BD5\u5FAA\u73AF \u2014 \u6A21\u578B\u5728\u81EA\u7EA0\u63D0\u793A\u540E\u4ECD\u4EE5\u76F8\u540C\u53C2\u6570\u91CD\u590D\u8C03\u7528\u540C\u4E00\u5DE5\u5177\u3002\u8BF7\u5C1D\u8BD5 /retry\u3001\u6362\u79CD\u8BF4\u6CD5\uFF0C\u6216\u6392\u67E5\u5E95\u5C42\u963B\u585E\u3002",
|
|
2779
2849
|
stormSuppressed: "\u5DF2\u6291\u5236 {count} \u6B21\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u540C\u4E00\u540D\u79F0 + \u53C2\u6570\u89E6\u53D1 3 \u6B21\u4EE5\u4E0A\u3002",
|
|
@@ -2809,6 +2879,7 @@ var zhCN = {
|
|
|
2809
2879
|
basic: {
|
|
2810
2880
|
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
2881
|
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",
|
|
2882
|
+
newInfoSystemReloaded: " \xB7 REASONIX.md / \u9879\u76EE\u8BB0\u5FC6\u5DF2\u91CD\u65B0\u52A0\u8F7D\uFF08\u4E0B\u4E00\u8F6E\u4E00\u6B21\u6027 cache miss\uFF09",
|
|
2812
2883
|
helpTitle: "\u547D\u4EE4\uFF1A",
|
|
2813
2884
|
helpShellTitle: "Shell \u5FEB\u6377\u65B9\u5F0F\uFF1A",
|
|
2814
2885
|
helpShell: " !<cmd> \u5728\u6C99\u7BB1\u6839\u76EE\u5F55\u8FD0\u884C <cmd>\uFF1B\u8F93\u51FA\u8FDB\u5165\u5BF9\u8BDD",
|
|
@@ -3460,7 +3531,8 @@ var zhCN = {
|
|
|
3460
3531
|
healthy: "\u6B63\u5E38 \xB7 {ms}ms",
|
|
3461
3532
|
slow: "\u7F13\u6162 \xB7 {ms}ms",
|
|
3462
3533
|
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"
|
|
3534
|
+
slowToast: "\u26A0 MCP `{name}` \u54CD\u5E94\u7F13\u6162 \xB7 P95 {seconds}s \xB7 \u6700\u8FD1 {sampleSize} \u6B21\u8C03\u7528",
|
|
3535
|
+
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
3536
|
},
|
|
3465
3537
|
denyContextInput: {
|
|
3466
3538
|
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"
|
|
@@ -5184,6 +5256,10 @@ function shrinkOversizedToolResultsByTokens(messages, maxTokens) {
|
|
|
5184
5256
|
}
|
|
5185
5257
|
|
|
5186
5258
|
// src/loop/healing.ts
|
|
5259
|
+
var _stampSeq = 0;
|
|
5260
|
+
function stampMissingIds(calls) {
|
|
5261
|
+
return calls.map((c) => c.id ? c : { ...c, id: `z-ext-${Date.now()}-${_stampSeq++}` });
|
|
5262
|
+
}
|
|
5187
5263
|
function fixToolCallPairing(messages) {
|
|
5188
5264
|
const out = [];
|
|
5189
5265
|
let droppedAssistantCalls = 0;
|
|
@@ -5191,9 +5267,10 @@ function fixToolCallPairing(messages) {
|
|
|
5191
5267
|
for (let i = 0; i < messages.length; i++) {
|
|
5192
5268
|
const msg = messages[i];
|
|
5193
5269
|
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
5270
|
+
const calls = stampMissingIds(msg.tool_calls);
|
|
5194
5271
|
const needed = /* @__PURE__ */ new Set();
|
|
5195
|
-
for (const call of
|
|
5196
|
-
if (call
|
|
5272
|
+
for (const call of calls) {
|
|
5273
|
+
if (call.id) needed.add(call.id);
|
|
5197
5274
|
}
|
|
5198
5275
|
const candidates = [];
|
|
5199
5276
|
let j = i + 1;
|
|
@@ -5207,7 +5284,7 @@ function fixToolCallPairing(messages) {
|
|
|
5207
5284
|
j++;
|
|
5208
5285
|
}
|
|
5209
5286
|
if (needed.size === 0) {
|
|
5210
|
-
out.push(msg);
|
|
5287
|
+
out.push({ ...msg, tool_calls: calls });
|
|
5211
5288
|
for (const r of candidates) out.push(r);
|
|
5212
5289
|
i = j - 1;
|
|
5213
5290
|
} else {
|
|
@@ -5271,6 +5348,32 @@ function* hookWarnings(outcomes, turn) {
|
|
|
5271
5348
|
}
|
|
5272
5349
|
}
|
|
5273
5350
|
|
|
5351
|
+
// src/loop/read-only-loop-tracker.ts
|
|
5352
|
+
var READONLY_LOOP_ESCALATION_THRESHOLD = 8;
|
|
5353
|
+
var ReadOnlyLoopTracker = class {
|
|
5354
|
+
streak = 0;
|
|
5355
|
+
threshold;
|
|
5356
|
+
constructor(threshold = READONLY_LOOP_ESCALATION_THRESHOLD) {
|
|
5357
|
+
this.threshold = Math.max(1, threshold);
|
|
5358
|
+
}
|
|
5359
|
+
reset() {
|
|
5360
|
+
this.streak = 0;
|
|
5361
|
+
}
|
|
5362
|
+
/** True ONLY on the call where the streak crosses the configured threshold. */
|
|
5363
|
+
noteAndCrossedThreshold(isReadOnly) {
|
|
5364
|
+
if (!isReadOnly) {
|
|
5365
|
+
this.streak = 0;
|
|
5366
|
+
return false;
|
|
5367
|
+
}
|
|
5368
|
+
const before = this.streak;
|
|
5369
|
+
this.streak += 1;
|
|
5370
|
+
return before < this.threshold && this.streak >= this.threshold;
|
|
5371
|
+
}
|
|
5372
|
+
get currentStreak() {
|
|
5373
|
+
return this.streak;
|
|
5374
|
+
}
|
|
5375
|
+
};
|
|
5376
|
+
|
|
5274
5377
|
// src/loop/turn-failure-tracker.ts
|
|
5275
5378
|
var FAILURE_ESCALATION_THRESHOLD = 3;
|
|
5276
5379
|
var TurnFailureTracker = class {
|
|
@@ -5310,17 +5413,25 @@ var TurnFailureTracker = class {
|
|
|
5310
5413
|
// src/memory/runtime.ts
|
|
5311
5414
|
import { createHash } from "crypto";
|
|
5312
5415
|
var ImmutablePrefix = class {
|
|
5416
|
+
/** Stable across turns; rebuilt only on /new when REASONIX.md changed on disk. */
|
|
5313
5417
|
system;
|
|
5314
5418
|
/** Each `addTool` costs one cache-miss turn — DeepSeek's prefix cache is keyed by full tool list. */
|
|
5315
5419
|
_toolSpecs;
|
|
5316
5420
|
fewShots;
|
|
5317
|
-
/** Invalidated
|
|
5421
|
+
/** Invalidated by addTool / removeTool / replaceSystem; bypassing any of those leaves cache stale → fingerprint diverges from sent prefix. */
|
|
5318
5422
|
_fingerprintCache = null;
|
|
5319
5423
|
constructor(opts) {
|
|
5320
5424
|
this.system = opts.system;
|
|
5321
5425
|
this._toolSpecs = [...opts.toolSpecs ?? []];
|
|
5322
5426
|
this.fewShots = Object.freeze([...opts.fewShots ?? []]);
|
|
5323
5427
|
}
|
|
5428
|
+
/** Replaces the system prompt; returns true iff the string actually changed. Caller must accept a cache miss on the next turn. */
|
|
5429
|
+
replaceSystem(s) {
|
|
5430
|
+
if (this.system === s) return false;
|
|
5431
|
+
this.system = s;
|
|
5432
|
+
this._fingerprintCache = null;
|
|
5433
|
+
return true;
|
|
5434
|
+
}
|
|
5324
5435
|
get toolSpecs() {
|
|
5325
5436
|
return this._toolSpecs;
|
|
5326
5437
|
}
|
|
@@ -5751,6 +5862,7 @@ var CacheFirstLoop = class {
|
|
|
5751
5862
|
confirmationGate;
|
|
5752
5863
|
/** Number of messages that were pre-loaded from the session file. */
|
|
5753
5864
|
resumedMessageCount;
|
|
5865
|
+
_rebuildSystem;
|
|
5754
5866
|
_turn = 0;
|
|
5755
5867
|
_streamPreference;
|
|
5756
5868
|
/** Threaded through HTTP + every tool dispatch so Esc cancels in-flight work, not after. */
|
|
@@ -5760,6 +5872,7 @@ var CacheFirstLoop = class {
|
|
|
5760
5872
|
_proArmedForNextTurn = false;
|
|
5761
5873
|
_escalateThisTurn = false;
|
|
5762
5874
|
_turnFailures;
|
|
5875
|
+
_readOnlyLoop;
|
|
5763
5876
|
_turnSelfCorrected = false;
|
|
5764
5877
|
_foldedThisTurn = false;
|
|
5765
5878
|
_toolDispatchesThisStep = 0;
|
|
@@ -5782,34 +5895,18 @@ var CacheFirstLoop = class {
|
|
|
5782
5895
|
this._turnFailures = new TurnFailureTracker(
|
|
5783
5896
|
resolveFailureThreshold(opts.failureThreshold, FAILURE_ESCALATION_THRESHOLD)
|
|
5784
5897
|
);
|
|
5898
|
+
this._readOnlyLoop = new ReadOnlyLoopTracker(
|
|
5899
|
+
parsePositiveIntEnv(process.env.REASONIX_READONLY_LOOP_THRESHOLD) ?? READONLY_LOOP_ESCALATION_THRESHOLD
|
|
5900
|
+
);
|
|
5785
5901
|
this.maxToolIters = opts.maxToolIters ?? 64;
|
|
5786
5902
|
this.hooks = opts.hooks ?? [];
|
|
5787
5903
|
this.hookCwd = opts.hookCwd ?? process.cwd();
|
|
5788
5904
|
this.confirmationGate = opts.confirmationGate ?? pauseGate;
|
|
5905
|
+
this._rebuildSystem = opts.rebuildSystem ?? null;
|
|
5789
5906
|
this._streamPreference = opts.stream ?? true;
|
|
5790
5907
|
this.stream = this._streamPreference;
|
|
5791
5908
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
5792
5909
|
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
5910
|
const isStormExempt = (call) => {
|
|
5814
5911
|
const name = call.function?.name;
|
|
5815
5912
|
if (!name) return false;
|
|
@@ -5817,7 +5914,7 @@ var CacheFirstLoop = class {
|
|
|
5817
5914
|
};
|
|
5818
5915
|
this.repair = new ToolCallRepair({
|
|
5819
5916
|
allowedToolNames: allowedNames,
|
|
5820
|
-
isMutating,
|
|
5917
|
+
isMutating: (call) => this.isMutating(call),
|
|
5821
5918
|
isStormExempt,
|
|
5822
5919
|
stormThreshold: parsePositiveIntEnv(process.env.REASONIX_STORM_THRESHOLD),
|
|
5823
5920
|
stormWindow: parsePositiveIntEnv(process.env.REASONIX_STORM_WINDOW)
|
|
@@ -5910,7 +6007,7 @@ var CacheFirstLoop = class {
|
|
|
5910
6007
|
}
|
|
5911
6008
|
}
|
|
5912
6009
|
}
|
|
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. */
|
|
6010
|
+
/** "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
6011
|
clearLog() {
|
|
5915
6012
|
const dropped = this.log.length;
|
|
5916
6013
|
this.log.compactInPlace([]);
|
|
@@ -5924,7 +6021,14 @@ var CacheFirstLoop = class {
|
|
|
5924
6021
|
}
|
|
5925
6022
|
this.scratch.reset();
|
|
5926
6023
|
this._inflight.clear();
|
|
5927
|
-
|
|
6024
|
+
let systemRebuilt = false;
|
|
6025
|
+
if (this._rebuildSystem) {
|
|
6026
|
+
try {
|
|
6027
|
+
systemRebuilt = this.prefix.replaceSystem(this._rebuildSystem());
|
|
6028
|
+
} catch {
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
return { dropped, archived, systemRebuilt };
|
|
5928
6032
|
}
|
|
5929
6033
|
configure(opts) {
|
|
5930
6034
|
if (opts.model !== void 0) this.model = opts.model;
|
|
@@ -5970,6 +6074,35 @@ var CacheFirstLoop = class {
|
|
|
5970
6074
|
this._escalateThisTurn = true;
|
|
5971
6075
|
return true;
|
|
5972
6076
|
}
|
|
6077
|
+
/** Returns true ONLY on the call where the read-only streak crosses the threshold (#681). */
|
|
6078
|
+
noteReadOnlyToolCall(call) {
|
|
6079
|
+
const isReadOnly = !this.isMutating(call);
|
|
6080
|
+
if (!this._readOnlyLoop.noteAndCrossedThreshold(isReadOnly)) return false;
|
|
6081
|
+
if (this._escalateThisTurn || !this.autoEscalate) return false;
|
|
6082
|
+
this._escalateThisTurn = true;
|
|
6083
|
+
return true;
|
|
6084
|
+
}
|
|
6085
|
+
/** A call counts as mutating when its definition reports `readOnly !== true` and any dynamic `readOnlyCheck` doesn't override that for these args. */
|
|
6086
|
+
isMutating(call) {
|
|
6087
|
+
const name = call.function?.name;
|
|
6088
|
+
if (!name) return false;
|
|
6089
|
+
const def = this.tools.get(name);
|
|
6090
|
+
if (!def) return false;
|
|
6091
|
+
if (def.readOnlyCheck) {
|
|
6092
|
+
let args = {};
|
|
6093
|
+
try {
|
|
6094
|
+
args = JSON.parse(call.function?.arguments ?? "{}") ?? {};
|
|
6095
|
+
} catch {
|
|
6096
|
+
}
|
|
6097
|
+
try {
|
|
6098
|
+
if (def.readOnlyCheck(args)) return false;
|
|
6099
|
+
} catch (err) {
|
|
6100
|
+
process.stderr.write(`readOnlyCheck for ${name} threw: ${err.message}
|
|
6101
|
+
`);
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
return def.readOnly !== true;
|
|
6105
|
+
}
|
|
5973
6106
|
async runOneToolCall(call, signal) {
|
|
5974
6107
|
const name = call.function?.name ?? "";
|
|
5975
6108
|
const args = call.function?.arguments ?? "{}";
|
|
@@ -6090,6 +6223,7 @@ ${reason}`
|
|
|
6090
6223
|
this.scratch.reset();
|
|
6091
6224
|
this.repair.resetStorm();
|
|
6092
6225
|
this._turnFailures.reset();
|
|
6226
|
+
this._readOnlyLoop.reset();
|
|
6093
6227
|
this._turnSelfCorrected = false;
|
|
6094
6228
|
this._escalateThisTurn = false;
|
|
6095
6229
|
this._foldedThisTurn = false;
|
|
@@ -6516,6 +6650,17 @@ ${reason}`
|
|
|
6516
6650
|
})
|
|
6517
6651
|
};
|
|
6518
6652
|
}
|
|
6653
|
+
if (this.noteReadOnlyToolCall(call)) {
|
|
6654
|
+
yield {
|
|
6655
|
+
turn: this._turn,
|
|
6656
|
+
role: "warning",
|
|
6657
|
+
content: t("loop.readOnlyLoopEscalation", {
|
|
6658
|
+
model: ESCALATION_MODEL,
|
|
6659
|
+
n: this._readOnlyLoop.currentStreak,
|
|
6660
|
+
fallback: this.model
|
|
6661
|
+
})
|
|
6662
|
+
};
|
|
6663
|
+
}
|
|
6519
6664
|
yield {
|
|
6520
6665
|
turn: this._turn,
|
|
6521
6666
|
role: "tool",
|
|
@@ -6844,7 +6989,7 @@ function parseAtQuery(query) {
|
|
|
6844
6989
|
trailingSlash: false
|
|
6845
6990
|
};
|
|
6846
6991
|
}
|
|
6847
|
-
var AT_PICKER_PREFIX = /(?:^|\s)@([
|
|
6992
|
+
var AT_PICKER_PREFIX = /(?:^|\s)@([\p{L}\p{N}_./\\-]*)$/u;
|
|
6848
6993
|
function detectAtPicker(input) {
|
|
6849
6994
|
const m = AT_PICKER_PREFIX.exec(input);
|
|
6850
6995
|
if (!m) return null;
|
|
@@ -6930,7 +7075,7 @@ function fuzzySubseqScore(needle, target) {
|
|
|
6930
7075
|
const lengthPenalty = Math.floor(target.length / 4);
|
|
6931
7076
|
return quality + lengthPenalty;
|
|
6932
7077
|
}
|
|
6933
|
-
var AT_MENTION_PATTERN = /(?<=^|\s)@([
|
|
7078
|
+
var AT_MENTION_PATTERN = /(?<=^|\s)@([\p{L}\p{N}_./\\-]+)/gu;
|
|
6934
7079
|
function expandAtMentions(text, rootDir, opts = {}) {
|
|
6935
7080
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
6936
7081
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
@@ -7620,16 +7765,24 @@ function ensureDir(p) {
|
|
|
7620
7765
|
if (!existsSync7(p)) mkdirSync4(p, { recursive: true });
|
|
7621
7766
|
}
|
|
7622
7767
|
function formatFrontmatter(e) {
|
|
7623
|
-
|
|
7768
|
+
const lines = [
|
|
7624
7769
|
"---",
|
|
7625
7770
|
`name: ${e.name}`,
|
|
7626
7771
|
`description: ${e.description.replace(/\n/g, " ")}`,
|
|
7627
7772
|
`type: ${e.type}`,
|
|
7628
7773
|
`scope: ${e.scope}`,
|
|
7629
|
-
`created: ${e.createdAt}
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7774
|
+
`created: ${e.createdAt}`
|
|
7775
|
+
];
|
|
7776
|
+
if (e.priority) lines.push(`priority: ${e.priority}`);
|
|
7777
|
+
if (e.expires) lines.push(`expires: ${e.expires}`);
|
|
7778
|
+
lines.push("---", "");
|
|
7779
|
+
return lines.join("\n");
|
|
7780
|
+
}
|
|
7781
|
+
function coercePriority(v) {
|
|
7782
|
+
return v === "low" || v === "medium" || v === "high" ? v : void 0;
|
|
7783
|
+
}
|
|
7784
|
+
function coerceExpires(v) {
|
|
7785
|
+
return v === "project_end" ? v : void 0;
|
|
7633
7786
|
}
|
|
7634
7787
|
function todayIso() {
|
|
7635
7788
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -7691,7 +7844,7 @@ var MemoryStore = class {
|
|
|
7691
7844
|
}
|
|
7692
7845
|
const raw = readFileSync9(file, "utf8");
|
|
7693
7846
|
const { data, body } = parseFrontmatter(raw);
|
|
7694
|
-
|
|
7847
|
+
const entry = {
|
|
7695
7848
|
name: data.name ?? name,
|
|
7696
7849
|
type: data.type ?? "project",
|
|
7697
7850
|
scope: data.scope ?? scope,
|
|
@@ -7699,6 +7852,11 @@ var MemoryStore = class {
|
|
|
7699
7852
|
body: body.trim(),
|
|
7700
7853
|
createdAt: data.created ?? ""
|
|
7701
7854
|
};
|
|
7855
|
+
const priority = coercePriority(data.priority);
|
|
7856
|
+
if (priority) entry.priority = priority;
|
|
7857
|
+
const expires = coerceExpires(data.expires);
|
|
7858
|
+
if (expires) entry.expires = expires;
|
|
7859
|
+
return entry;
|
|
7702
7860
|
}
|
|
7703
7861
|
/** Skips malformed files — index stays queryable even if one file is hand-edited into nonsense. */
|
|
7704
7862
|
list() {
|
|
@@ -7741,6 +7899,8 @@ var MemoryStore = class {
|
|
|
7741
7899
|
body,
|
|
7742
7900
|
createdAt: todayIso()
|
|
7743
7901
|
};
|
|
7902
|
+
if (input.priority) entry.priority = input.priority;
|
|
7903
|
+
if (input.expires) entry.expires = input.expires;
|
|
7744
7904
|
const dir = this.dir(input.scope);
|
|
7745
7905
|
const file = join8(dir, `${name}.md`);
|
|
7746
7906
|
const content = `${formatFrontmatter(entry)}${body}
|
|
@@ -7824,13 +7984,36 @@ function applyGlobalReasonixMemory(basePrompt, homeDir) {
|
|
|
7824
7984
|
"```"
|
|
7825
7985
|
].join("\n");
|
|
7826
7986
|
}
|
|
7987
|
+
function effectivePriority(entry, cfg) {
|
|
7988
|
+
if (entry.priority) return entry.priority;
|
|
7989
|
+
return memoryTypeDefaults(entry.type, cfg).priority;
|
|
7990
|
+
}
|
|
7991
|
+
function highPriorityBlock(entries, cfg) {
|
|
7992
|
+
const high = entries.filter((e) => effectivePriority(e, cfg) === "high");
|
|
7993
|
+
if (high.length === 0) return null;
|
|
7994
|
+
const lines = [
|
|
7995
|
+
"# HIGH PRIORITY constraints (must observe)",
|
|
7996
|
+
"",
|
|
7997
|
+
"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.",
|
|
7998
|
+
""
|
|
7999
|
+
];
|
|
8000
|
+
for (const e of high) {
|
|
8001
|
+
const head = `!!! [${e.scope}/${e.type}/${e.name}] ${e.description || "(no description)"}`;
|
|
8002
|
+
lines.push(head);
|
|
8003
|
+
if (e.body) lines.push("", e.body);
|
|
8004
|
+
lines.push("");
|
|
8005
|
+
}
|
|
8006
|
+
return lines.join("\n").trimEnd();
|
|
8007
|
+
}
|
|
7827
8008
|
function applyUserMemory(basePrompt, opts = {}) {
|
|
7828
8009
|
if (!memoryEnabled()) return basePrompt;
|
|
7829
8010
|
const store = new MemoryStore(opts);
|
|
7830
8011
|
const global = store.loadIndex("global");
|
|
7831
8012
|
const project = store.hasProjectScope() ? store.loadIndex("project") : null;
|
|
7832
|
-
|
|
8013
|
+
const high = highPriorityBlock(store.list(), opts.cfg);
|
|
8014
|
+
if (!global && !project && !high) return basePrompt;
|
|
7833
8015
|
const parts = [basePrompt];
|
|
8016
|
+
if (high) parts.push("", high);
|
|
7834
8017
|
if (global) {
|
|
7835
8018
|
parts.push(
|
|
7836
8019
|
"",
|
|
@@ -7866,7 +8049,7 @@ function applyMemoryStack(basePrompt, rootDir) {
|
|
|
7866
8049
|
|
|
7867
8050
|
// src/tools/filesystem.ts
|
|
7868
8051
|
import { promises as fs4 } from "fs";
|
|
7869
|
-
import * as
|
|
8052
|
+
import * as pathMod5 from "path";
|
|
7870
8053
|
import picomatch3 from "picomatch";
|
|
7871
8054
|
|
|
7872
8055
|
// src/tools/fs/edit.ts
|
|
@@ -8088,15 +8271,149 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
8088
8271
|
return lines.join("\n");
|
|
8089
8272
|
}
|
|
8090
8273
|
|
|
8274
|
+
// src/tools/fs/outline.ts
|
|
8275
|
+
import * as pathMod3 from "path";
|
|
8276
|
+
var OUTLINE_MAX_ENTRIES = 30;
|
|
8277
|
+
var OUTLINE_TAIL_KEEP = 5;
|
|
8278
|
+
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
8279
|
+
var PY_DECL_RE = /^(?:async\s+)?(def|class)\s+(\w+)/;
|
|
8280
|
+
var GO_DECL_RE = /^(func|type|var|const)\s+(?:\([^)]+\)\s+)?(\w+)/;
|
|
8281
|
+
var RUST_DECL_RE = /^(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|mod|type|const|static|union)\s+(\w+)/;
|
|
8282
|
+
var RUST_IMPL_RE = /^(?:unsafe\s+)?impl(?:\s*<[^>]+>)?\s+(?:[^{]+\s+for\s+)?(\w+)/;
|
|
8283
|
+
var MD_HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
8284
|
+
var MD_FENCE_RE = /^```/;
|
|
8285
|
+
var EXT_TO_LANG = {
|
|
8286
|
+
".ts": "ts",
|
|
8287
|
+
".tsx": "ts",
|
|
8288
|
+
".mts": "ts",
|
|
8289
|
+
".cts": "ts",
|
|
8290
|
+
".js": "ts",
|
|
8291
|
+
".jsx": "ts",
|
|
8292
|
+
".mjs": "ts",
|
|
8293
|
+
".cjs": "ts",
|
|
8294
|
+
".py": "py",
|
|
8295
|
+
".pyi": "py",
|
|
8296
|
+
".go": "go",
|
|
8297
|
+
".rs": "rust",
|
|
8298
|
+
".md": "md",
|
|
8299
|
+
".markdown": "md",
|
|
8300
|
+
".mdx": "md"
|
|
8301
|
+
};
|
|
8302
|
+
function extractOutline(filename, lines) {
|
|
8303
|
+
const ext = pathMod3.extname(filename).toLowerCase();
|
|
8304
|
+
const lang = EXT_TO_LANG[ext];
|
|
8305
|
+
if (!lang) return [];
|
|
8306
|
+
switch (lang) {
|
|
8307
|
+
case "ts":
|
|
8308
|
+
return extractTs(lines);
|
|
8309
|
+
case "py":
|
|
8310
|
+
return extractPython(lines);
|
|
8311
|
+
case "go":
|
|
8312
|
+
return extractGo(lines);
|
|
8313
|
+
case "rust":
|
|
8314
|
+
return extractRust(lines);
|
|
8315
|
+
case "md":
|
|
8316
|
+
return extractMarkdown(lines);
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
function extractTs(lines) {
|
|
8320
|
+
const out = [];
|
|
8321
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8322
|
+
const line = lines[i];
|
|
8323
|
+
if (!line.startsWith("export ")) continue;
|
|
8324
|
+
const m = TS_EXPORT_RE.exec(line);
|
|
8325
|
+
if (!m) continue;
|
|
8326
|
+
out.push({ line: i + 1, text: `export ${m[1]} ${m[2]}` });
|
|
8327
|
+
}
|
|
8328
|
+
return out;
|
|
8329
|
+
}
|
|
8330
|
+
function extractPython(lines) {
|
|
8331
|
+
const out = [];
|
|
8332
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8333
|
+
const line = lines[i];
|
|
8334
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
8335
|
+
const m = PY_DECL_RE.exec(line);
|
|
8336
|
+
if (!m) continue;
|
|
8337
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8338
|
+
}
|
|
8339
|
+
return out;
|
|
8340
|
+
}
|
|
8341
|
+
function extractGo(lines) {
|
|
8342
|
+
const out = [];
|
|
8343
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8344
|
+
const line = lines[i];
|
|
8345
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
8346
|
+
const m = GO_DECL_RE.exec(line);
|
|
8347
|
+
if (!m) continue;
|
|
8348
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8349
|
+
}
|
|
8350
|
+
return out;
|
|
8351
|
+
}
|
|
8352
|
+
function extractRust(lines) {
|
|
8353
|
+
const out = [];
|
|
8354
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8355
|
+
const line = lines[i];
|
|
8356
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
8357
|
+
const implMatch = RUST_IMPL_RE.exec(line);
|
|
8358
|
+
if (implMatch) {
|
|
8359
|
+
out.push({ line: i + 1, text: `impl ${implMatch[1]}` });
|
|
8360
|
+
continue;
|
|
8361
|
+
}
|
|
8362
|
+
const m = RUST_DECL_RE.exec(line);
|
|
8363
|
+
if (!m) continue;
|
|
8364
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8365
|
+
}
|
|
8366
|
+
return out;
|
|
8367
|
+
}
|
|
8368
|
+
function extractMarkdown(lines) {
|
|
8369
|
+
const out = [];
|
|
8370
|
+
let inFence = false;
|
|
8371
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8372
|
+
const line = lines[i];
|
|
8373
|
+
if (MD_FENCE_RE.test(line)) {
|
|
8374
|
+
inFence = !inFence;
|
|
8375
|
+
continue;
|
|
8376
|
+
}
|
|
8377
|
+
if (inFence) continue;
|
|
8378
|
+
const m = MD_HEADING_RE.exec(line);
|
|
8379
|
+
if (!m) continue;
|
|
8380
|
+
out.push({ line: i + 1, text: `${m[1]} ${m[2]}` });
|
|
8381
|
+
}
|
|
8382
|
+
return out;
|
|
8383
|
+
}
|
|
8384
|
+
function formatOutline(entries) {
|
|
8385
|
+
const total = entries.length;
|
|
8386
|
+
if (total === 0) return "";
|
|
8387
|
+
const lastEntry = entries[total - 1];
|
|
8388
|
+
const width = String(lastEntry.line).length;
|
|
8389
|
+
const fmt = (e) => ` L${String(e.line).padStart(width, " ")} ${e.text}`;
|
|
8390
|
+
const header = `[outline: ${total} symbol${total === 1 ? "" : "s"}]`;
|
|
8391
|
+
if (total <= OUTLINE_MAX_ENTRIES) {
|
|
8392
|
+
return [header, ...entries.map(fmt)].join("\n");
|
|
8393
|
+
}
|
|
8394
|
+
const headCount = OUTLINE_MAX_ENTRIES - OUTLINE_TAIL_KEEP;
|
|
8395
|
+
const headEntries = entries.slice(0, headCount);
|
|
8396
|
+
const tailEntries = entries.slice(-OUTLINE_TAIL_KEEP);
|
|
8397
|
+
const omitted = total - OUTLINE_MAX_ENTRIES;
|
|
8398
|
+
const gapStart = headEntries[headEntries.length - 1].line;
|
|
8399
|
+
const gapEnd = tailEntries[0].line;
|
|
8400
|
+
return [
|
|
8401
|
+
header,
|
|
8402
|
+
...headEntries.map(fmt),
|
|
8403
|
+
` [\u2026 ${omitted} more symbol${omitted === 1 ? "" : "s"} between L${gapStart} and L${gapEnd} \u2026]`,
|
|
8404
|
+
...tailEntries.map(fmt)
|
|
8405
|
+
].join("\n");
|
|
8406
|
+
}
|
|
8407
|
+
|
|
8091
8408
|
// src/tools/fs/search.ts
|
|
8092
8409
|
import { promises as fs3 } from "fs";
|
|
8093
|
-
import * as
|
|
8410
|
+
import * as pathMod4 from "path";
|
|
8094
8411
|
function throwIfAborted(signal) {
|
|
8095
8412
|
if (!signal?.aborted) return;
|
|
8096
8413
|
throw new DOMException("search aborted by user", "AbortError");
|
|
8097
8414
|
}
|
|
8098
8415
|
function displayRel3(rootDir, full) {
|
|
8099
|
-
return
|
|
8416
|
+
return pathMod4.relative(rootDir, full).replaceAll("\\", "/");
|
|
8100
8417
|
}
|
|
8101
8418
|
async function searchFiles(ctx, startAbs, args) {
|
|
8102
8419
|
throwIfAborted(args.signal);
|
|
@@ -8120,7 +8437,7 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
8120
8437
|
}
|
|
8121
8438
|
for (const e of entries) {
|
|
8122
8439
|
throwIfAborted(args.signal);
|
|
8123
|
-
const full =
|
|
8440
|
+
const full = pathMod4.join(dir, e.name);
|
|
8124
8441
|
const lower = e.name.toLowerCase();
|
|
8125
8442
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
8126
8443
|
if (hit) {
|
|
@@ -8199,11 +8516,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
8199
8516
|
throwIfAborted(args.signal);
|
|
8200
8517
|
if (e.isDirectory()) {
|
|
8201
8518
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
8202
|
-
await walk2(
|
|
8519
|
+
await walk2(pathMod4.join(dir, e.name));
|
|
8203
8520
|
continue;
|
|
8204
8521
|
}
|
|
8205
8522
|
if (!e.isFile()) continue;
|
|
8206
|
-
const full =
|
|
8523
|
+
const full = pathMod4.join(dir, e.name);
|
|
8207
8524
|
if (ctx.nameMatch && !ctx.nameMatch(e.name, displayRel3(ctx.rootDir, full))) continue;
|
|
8208
8525
|
if (ctx.isBinaryByName(e.name)) continue;
|
|
8209
8526
|
let fh;
|
|
@@ -8300,47 +8617,11 @@ var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
|
8300
8617
|
var DEFAULT_AUTO_PREVIEW_LINES = 200;
|
|
8301
8618
|
var AUTO_PREVIEW_HEAD_LINES = 80;
|
|
8302
8619
|
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
|
-
}
|
|
8620
|
+
var OUTLINE_MAX_ENTRIES2 = 30;
|
|
8340
8621
|
var SKIP_DIR_NAMES = new Set(DEFAULT_INDEX_EXCLUDES.dirs);
|
|
8341
8622
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
8342
8623
|
function displayRel4(rootDir, full) {
|
|
8343
|
-
return
|
|
8624
|
+
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
8344
8625
|
}
|
|
8345
8626
|
var GLOB_METACHARS = /[*?{[]/;
|
|
8346
8627
|
function compileNameFilter(filter) {
|
|
@@ -8359,16 +8640,16 @@ function isLikelyBinaryByName(name) {
|
|
|
8359
8640
|
return BINARY_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
8360
8641
|
}
|
|
8361
8642
|
function registerFilesystemTools(registry, opts) {
|
|
8362
|
-
const rootDir =
|
|
8643
|
+
const rootDir = pathMod5.resolve(opts.rootDir);
|
|
8363
8644
|
const allowWriting = opts.allowWriting !== false;
|
|
8364
8645
|
const maxReadBytes = opts.maxReadBytes ?? DEFAULT_MAX_READ_BYTES;
|
|
8365
8646
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
8366
|
-
const normRoot =
|
|
8647
|
+
const normRoot = pathMod5.resolve(rootDir);
|
|
8367
8648
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
8368
8649
|
const inflightGate = /* @__PURE__ */ new Map();
|
|
8369
8650
|
function pathIsUnder(child, parent) {
|
|
8370
|
-
const rel =
|
|
8371
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8651
|
+
const rel = pathMod5.relative(parent, child);
|
|
8652
|
+
return rel === "" || !rel.startsWith("..") && !pathMod5.isAbsolute(rel);
|
|
8372
8653
|
}
|
|
8373
8654
|
function looksLikeAbsoluteSystemPath(raw) {
|
|
8374
8655
|
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
@@ -8384,7 +8665,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8384
8665
|
if (pathIsUnder(abs, dir)) return;
|
|
8385
8666
|
}
|
|
8386
8667
|
const stat2 = await safeLstat(abs);
|
|
8387
|
-
const allowPrefix = stat2?.isDirectory() ? abs :
|
|
8668
|
+
const allowPrefix = stat2?.isDirectory() ? abs : pathMod5.dirname(abs);
|
|
8388
8669
|
let pending = inflightGate.get(allowPrefix);
|
|
8389
8670
|
if (!pending) {
|
|
8390
8671
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
@@ -8412,7 +8693,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8412
8693
|
throw new Error("path must be a non-empty string");
|
|
8413
8694
|
}
|
|
8414
8695
|
if (looksLikeAbsoluteSystemPath(raw)) {
|
|
8415
|
-
const abs =
|
|
8696
|
+
const abs = pathMod5.resolve(raw);
|
|
8416
8697
|
if (pathIsUnder(abs, normRoot)) return abs;
|
|
8417
8698
|
await ensureOutsideSandboxAllowed(abs, intent, toolName, ctx);
|
|
8418
8699
|
return abs;
|
|
@@ -8422,7 +8703,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8422
8703
|
normalized = normalized.slice(1);
|
|
8423
8704
|
}
|
|
8424
8705
|
if (normalized.length === 0) normalized = ".";
|
|
8425
|
-
const resolved =
|
|
8706
|
+
const resolved = pathMod5.resolve(rootDir, normalized);
|
|
8426
8707
|
if (!pathIsUnder(resolved, normRoot)) {
|
|
8427
8708
|
throw new Error(
|
|
8428
8709
|
`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 +8725,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
8444
8725
|
- head: N \u2192 first N lines (imports, public API, small configs)
|
|
8445
8726
|
- tail: N \u2192 last N lines (recently-added code, log tails)
|
|
8446
8727
|
- 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
|
|
8728
|
+
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
8729
|
readOnly: true,
|
|
8449
8730
|
stormExempt: true,
|
|
8450
8731
|
parameters: {
|
|
@@ -8512,7 +8793,7 @@ ${slice.join("\n")}`;
|
|
|
8512
8793
|
const head = lines.slice(0, AUTO_PREVIEW_HEAD_LINES).join("\n");
|
|
8513
8794
|
const tail = lines.slice(totalLines - AUTO_PREVIEW_TAIL_LINES).join("\n");
|
|
8514
8795
|
const omitted = totalLines - AUTO_PREVIEW_HEAD_LINES - AUTO_PREVIEW_TAIL_LINES;
|
|
8515
|
-
const outline = formatOutline(
|
|
8796
|
+
const outline = formatOutline(extractOutline(abs, lines));
|
|
8516
8797
|
const parts = [
|
|
8517
8798
|
`[auto-preview: head ${AUTO_PREVIEW_HEAD_LINES} + tail ${AUTO_PREVIEW_TAIL_LINES} of ${totalLines} lines]`,
|
|
8518
8799
|
head
|
|
@@ -8620,7 +8901,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8620
8901
|
lines.push(line);
|
|
8621
8902
|
emitted++;
|
|
8622
8903
|
if (e.isDirectory() && !skip) {
|
|
8623
|
-
await walk2(
|
|
8904
|
+
await walk2(pathMod5.join(dir, e.name), depth + 1);
|
|
8624
8905
|
}
|
|
8625
8906
|
}
|
|
8626
8907
|
};
|
|
@@ -8780,7 +9061,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8780
9061
|
},
|
|
8781
9062
|
fn: async (args, ctx) => {
|
|
8782
9063
|
const abs = await safePath(args.path, "write_file", ctx, "write");
|
|
8783
|
-
await fs4.mkdir(
|
|
9064
|
+
await fs4.mkdir(pathMod5.dirname(abs), { recursive: true });
|
|
8784
9065
|
await fs4.writeFile(abs, args.content, "utf8");
|
|
8785
9066
|
return `wrote ${args.content.length} chars to ${displayRel4(rootDir, abs)}`;
|
|
8786
9067
|
}
|
|
@@ -8866,7 +9147,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8866
9147
|
fn: async (args, ctx) => {
|
|
8867
9148
|
const src = await safePath(args.source, "move_file", ctx, "write");
|
|
8868
9149
|
const dst = await safePath(args.destination, "move_file", ctx, "write");
|
|
8869
|
-
await fs4.mkdir(
|
|
9150
|
+
await fs4.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
8870
9151
|
await fs4.rename(src, dst);
|
|
8871
9152
|
return `moved ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
|
|
8872
9153
|
}
|
|
@@ -8934,7 +9215,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8934
9215
|
fn: async (args, ctx) => {
|
|
8935
9216
|
const src = await safePath(args.source, "copy_file", ctx);
|
|
8936
9217
|
const dst = await safePath(args.destination, "copy_file", ctx, "write");
|
|
8937
|
-
await fs4.mkdir(
|
|
9218
|
+
await fs4.mkdir(pathMod5.dirname(dst), { recursive: true });
|
|
8938
9219
|
await fs4.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
8939
9220
|
return `copied ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
|
|
8940
9221
|
}
|
|
@@ -8946,6 +9227,16 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
8946
9227
|
function registerMemoryTools(registry, opts = {}) {
|
|
8947
9228
|
const store = new MemoryStore({ homeDir: opts.homeDir, projectRoot: opts.projectRoot });
|
|
8948
9229
|
const hasProject = store.hasProjectScope();
|
|
9230
|
+
const registry_types = loadMemoryTypeRegistry();
|
|
9231
|
+
const customTypeNames = registry_types.filter((r) => !r.builtin).map((r) => r.name);
|
|
9232
|
+
const typeDescParts = [
|
|
9233
|
+
"'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
9234
|
+
];
|
|
9235
|
+
if (customTypeNames.length > 0) {
|
|
9236
|
+
typeDescParts.push(
|
|
9237
|
+
`Custom types declared in config: ${customTypeNames.join(", ")}. Any string is accepted; unknown types are stored verbatim and treated as 'reference' priority.`
|
|
9238
|
+
);
|
|
9239
|
+
}
|
|
8949
9240
|
registry.register({
|
|
8950
9241
|
name: "remember",
|
|
8951
9242
|
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 +9245,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8954
9245
|
properties: {
|
|
8955
9246
|
type: {
|
|
8956
9247
|
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."
|
|
9248
|
+
description: typeDescParts.join(" ")
|
|
8959
9249
|
},
|
|
8960
9250
|
scope: {
|
|
8961
9251
|
type: "string",
|
|
@@ -8973,6 +9263,16 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8973
9263
|
content: {
|
|
8974
9264
|
type: "string",
|
|
8975
9265
|
description: "Full memory body in markdown. For feedback/project types, structure as: rule/fact, then **Why:** line, then **How to apply:** line."
|
|
9266
|
+
},
|
|
9267
|
+
priority: {
|
|
9268
|
+
type: "string",
|
|
9269
|
+
enum: ["low", "medium", "high"],
|
|
9270
|
+
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."
|
|
9271
|
+
},
|
|
9272
|
+
expires: {
|
|
9273
|
+
type: "string",
|
|
9274
|
+
enum: ["project_end"],
|
|
9275
|
+
description: "Optional lifecycle hint. `project_end` causes `/memory clear project` to also remove this entry even when it's stored at global scope."
|
|
8976
9276
|
}
|
|
8977
9277
|
},
|
|
8978
9278
|
required: ["type", "scope", "name", "description", "content"]
|
|
@@ -8989,7 +9289,9 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8989
9289
|
type: args.type,
|
|
8990
9290
|
scope: args.scope,
|
|
8991
9291
|
description: args.description,
|
|
8992
|
-
body: args.content
|
|
9292
|
+
body: args.content,
|
|
9293
|
+
...args.priority ? { priority: args.priority } : {},
|
|
9294
|
+
...args.expires ? { expires: args.expires } : {}
|
|
8993
9295
|
});
|
|
8994
9296
|
const key = sanitizeMemoryName(args.name);
|
|
8995
9297
|
return [
|
|
@@ -9932,11 +10234,11 @@ function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
|
9932
10234
|
}
|
|
9933
10235
|
|
|
9934
10236
|
// src/tools/shell.ts
|
|
9935
|
-
import * as
|
|
10237
|
+
import * as pathMod9 from "path";
|
|
9936
10238
|
|
|
9937
10239
|
// src/tools/jobs.ts
|
|
9938
10240
|
import { spawn as spawn2 } from "child_process";
|
|
9939
|
-
import * as
|
|
10241
|
+
import * as pathMod6 from "path";
|
|
9940
10242
|
function killProcessTree(pid, signal) {
|
|
9941
10243
|
if (process.platform === "win32") {
|
|
9942
10244
|
const args = ["/pid", String(pid), "/T"];
|
|
@@ -9996,7 +10298,7 @@ var JobRegistry = class {
|
|
|
9996
10298
|
const maxBytes = opts.maxBufferBytes ?? DEFAULT_OUTPUT_CAP_BYTES;
|
|
9997
10299
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
9998
10300
|
const spawnOpts = {
|
|
9999
|
-
cwd:
|
|
10301
|
+
cwd: pathMod6.resolve(opts.cwd),
|
|
10000
10302
|
shell: false,
|
|
10001
10303
|
windowsHide: true,
|
|
10002
10304
|
env: process.env,
|
|
@@ -10109,12 +10411,15 @@ ${job.output.slice(start)}`;
|
|
|
10109
10411
|
job.signalReady();
|
|
10110
10412
|
job.signalClosed();
|
|
10111
10413
|
});
|
|
10112
|
-
|
|
10414
|
+
const settleClosed = (code) => {
|
|
10415
|
+
if (!job.running && job.exitCode !== null) return;
|
|
10113
10416
|
job.running = false;
|
|
10114
10417
|
job.exitCode = code;
|
|
10115
10418
|
job.signalReady();
|
|
10116
10419
|
job.signalClosed();
|
|
10117
|
-
}
|
|
10420
|
+
};
|
|
10421
|
+
child.on("exit", settleClosed);
|
|
10422
|
+
child.on("close", settleClosed);
|
|
10118
10423
|
const onAbort = () => this.stop(id, { graceMs: 100 });
|
|
10119
10424
|
if (opts.signal?.aborted) {
|
|
10120
10425
|
onAbort();
|
|
@@ -10171,21 +10476,26 @@ ${job.output.slice(start)}`;
|
|
|
10171
10476
|
latestOutput: job.output
|
|
10172
10477
|
};
|
|
10173
10478
|
}
|
|
10174
|
-
const timeoutMs = Math.max(0, Math.min(
|
|
10479
|
+
const timeoutMs = Math.max(0, Math.min(3e5, opts.timeoutMs ?? 5e3));
|
|
10480
|
+
const waitFor = opts.waitFor ?? "exit";
|
|
10175
10481
|
const startOutput = job.output;
|
|
10482
|
+
const racers = [job.closedPromise];
|
|
10176
10483
|
let wakeOutput = null;
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10484
|
+
if (waitFor === "output-or-exit") {
|
|
10485
|
+
racers.push(
|
|
10486
|
+
new Promise((resolve10) => {
|
|
10487
|
+
wakeOutput = resolve10;
|
|
10488
|
+
job.outputWaiters.add(resolve10);
|
|
10489
|
+
})
|
|
10490
|
+
);
|
|
10491
|
+
}
|
|
10181
10492
|
let timer = null;
|
|
10182
|
-
|
|
10183
|
-
job.closedPromise,
|
|
10184
|
-
outputPromise,
|
|
10493
|
+
racers.push(
|
|
10185
10494
|
new Promise((resolve10) => {
|
|
10186
10495
|
timer = setTimeout(resolve10, timeoutMs);
|
|
10187
10496
|
})
|
|
10188
|
-
|
|
10497
|
+
);
|
|
10498
|
+
await Promise.race(racers);
|
|
10189
10499
|
if (timer) clearTimeout(timer);
|
|
10190
10500
|
if (wakeOutput) job.outputWaiters.delete(wakeOutput);
|
|
10191
10501
|
return {
|
|
@@ -10219,6 +10529,10 @@ ${job.output.slice(start)}`;
|
|
|
10219
10529
|
}
|
|
10220
10530
|
}
|
|
10221
10531
|
await Promise.race([job.closedPromise, new Promise((res) => setTimeout(res, 5e3))]);
|
|
10532
|
+
if (job.running) {
|
|
10533
|
+
job.running = false;
|
|
10534
|
+
job.signalClosed();
|
|
10535
|
+
}
|
|
10222
10536
|
}
|
|
10223
10537
|
return snapshot(job);
|
|
10224
10538
|
}
|
|
@@ -10252,6 +10566,12 @@ ${job.output.slice(start)}`;
|
|
|
10252
10566
|
}
|
|
10253
10567
|
const remaining = Math.max(800, deadlineMs - elapsed());
|
|
10254
10568
|
await Promise.race([allClose, new Promise((res) => setTimeout(res, remaining))]);
|
|
10569
|
+
for (const job of runningJobs) {
|
|
10570
|
+
if (job.running) {
|
|
10571
|
+
job.running = false;
|
|
10572
|
+
job.signalClosed();
|
|
10573
|
+
}
|
|
10574
|
+
}
|
|
10255
10575
|
}
|
|
10256
10576
|
/** Count of still-running jobs — drives the TUI status-bar indicator. */
|
|
10257
10577
|
runningCount() {
|
|
@@ -10282,12 +10602,12 @@ function latestOutputSince(before, after) {
|
|
|
10282
10602
|
// src/tools/shell/exec.ts
|
|
10283
10603
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
10284
10604
|
import { existsSync as existsSync8, statSync as statSync5 } from "fs";
|
|
10285
|
-
import * as
|
|
10605
|
+
import * as pathMod8 from "path";
|
|
10286
10606
|
|
|
10287
10607
|
// src/tools/shell-chain.ts
|
|
10288
10608
|
import { spawn as spawn3 } from "child_process";
|
|
10289
10609
|
import { closeSync, openSync } from "fs";
|
|
10290
|
-
import * as
|
|
10610
|
+
import * as pathMod7 from "path";
|
|
10291
10611
|
var UnsupportedSyntaxError = class extends Error {
|
|
10292
10612
|
constructor(detail) {
|
|
10293
10613
|
super(`run_command: ${detail}`);
|
|
@@ -10554,7 +10874,7 @@ function openRedirects(redirects, cwd) {
|
|
|
10554
10874
|
let bothFd = null;
|
|
10555
10875
|
const toClose = [];
|
|
10556
10876
|
const open = (target, flags) => {
|
|
10557
|
-
const resolved =
|
|
10877
|
+
const resolved = pathMod7.resolve(cwd, target);
|
|
10558
10878
|
const fd = openSync(resolved, flags);
|
|
10559
10879
|
toClose.push(fd);
|
|
10560
10880
|
return fd;
|
|
@@ -11051,16 +11371,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
11051
11371
|
const platform = opts.platform ?? process.platform;
|
|
11052
11372
|
if (platform !== "win32") return cmd;
|
|
11053
11373
|
if (!cmd) return cmd;
|
|
11054
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
11055
|
-
if (
|
|
11374
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod8.isAbsolute(cmd)) return cmd;
|
|
11375
|
+
if (pathMod8.extname(cmd)) return cmd;
|
|
11056
11376
|
const env = opts.env ?? process.env;
|
|
11057
11377
|
const pathExt = (getEnvCaseInsensitive(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
11058
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
11378
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod8.delimiter);
|
|
11059
11379
|
const pathDirs = (getEnvCaseInsensitive(env, "PATH") ?? "").split(delimiter2).filter(Boolean);
|
|
11060
11380
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
11061
11381
|
for (const dir of pathDirs) {
|
|
11062
11382
|
for (const ext of pathExt) {
|
|
11063
|
-
const full =
|
|
11383
|
+
const full = pathMod8.win32.join(dir, cmd + ext);
|
|
11064
11384
|
if (isFile(full)) return full;
|
|
11065
11385
|
}
|
|
11066
11386
|
}
|
|
@@ -11176,8 +11496,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
11176
11496
|
function isBareWindowsName(s) {
|
|
11177
11497
|
if (!s) return false;
|
|
11178
11498
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
11179
|
-
if (
|
|
11180
|
-
if (
|
|
11499
|
+
if (pathMod8.isAbsolute(s)) return false;
|
|
11500
|
+
if (pathMod8.extname(s)) return false;
|
|
11181
11501
|
return true;
|
|
11182
11502
|
}
|
|
11183
11503
|
function quoteForCmdExe(arg) {
|
|
@@ -11198,7 +11518,7 @@ var NeedsConfirmationError = class extends Error {
|
|
|
11198
11518
|
}
|
|
11199
11519
|
};
|
|
11200
11520
|
function registerShellTools(registry, opts) {
|
|
11201
|
-
const rootDir =
|
|
11521
|
+
const rootDir = pathMod9.resolve(opts.rootDir);
|
|
11202
11522
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
11203
11523
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
11204
11524
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -11264,7 +11584,7 @@ function registerShellTools(registry, opts) {
|
|
|
11264
11584
|
});
|
|
11265
11585
|
registry.register({
|
|
11266
11586
|
name: "run_background",
|
|
11267
|
-
description: "Spawn a long-running process
|
|
11587
|
+
description: "Spawn a long-running process and detach. Waits up to `waitSec` for startup or a readiness signal ('Local:', 'listening on', 'compiled successfully'), then returns the job id + startup preview. Tail logs with `job_output`, block on completion with `wait_for_job`, kill with `stop_job`, list with `list_jobs`.\n\nSingle process only \u2014 chains / redirects / `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
11588
|
parameters: {
|
|
11269
11589
|
type: "object",
|
|
11270
11590
|
properties: {
|
|
@@ -11337,7 +11657,7 @@ function registerShellTools(registry, opts) {
|
|
|
11337
11657
|
});
|
|
11338
11658
|
registry.register({
|
|
11339
11659
|
name: "wait_for_job",
|
|
11340
|
-
description: "Block until a background job
|
|
11660
|
+
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
11661
|
readOnly: true,
|
|
11342
11662
|
parallelSafe: true,
|
|
11343
11663
|
stormExempt: true,
|
|
@@ -11347,13 +11667,21 @@ function registerShellTools(registry, opts) {
|
|
|
11347
11667
|
jobId: { type: "integer", description: "Job id returned by run_background." },
|
|
11348
11668
|
timeoutMs: {
|
|
11349
11669
|
type: "integer",
|
|
11350
|
-
description: "Max time to block before returning if
|
|
11670
|
+
description: "Max time to block before returning if the wake condition hasn't fired. Clamped to 0..300000. Default 5000."
|
|
11671
|
+
},
|
|
11672
|
+
waitFor: {
|
|
11673
|
+
type: "string",
|
|
11674
|
+
enum: ["exit", "output-or-exit"],
|
|
11675
|
+
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
11676
|
}
|
|
11352
11677
|
},
|
|
11353
11678
|
required: ["jobId"]
|
|
11354
11679
|
},
|
|
11355
11680
|
fn: async (args) => {
|
|
11356
|
-
const out = await jobs.waitForJob(args.jobId, {
|
|
11681
|
+
const out = await jobs.waitForJob(args.jobId, {
|
|
11682
|
+
timeoutMs: args.timeoutMs,
|
|
11683
|
+
waitFor: args.waitFor
|
|
11684
|
+
});
|
|
11357
11685
|
if (!out) return `job ${args.jobId}: not found (use list_jobs)`;
|
|
11358
11686
|
return {
|
|
11359
11687
|
jobId: args.jobId,
|
|
@@ -12558,8 +12886,12 @@ var McpClient = class {
|
|
|
12558
12886
|
}
|
|
12559
12887
|
});
|
|
12560
12888
|
promise.catch(() => void 0);
|
|
12889
|
+
const promiseSettled = promise.then(
|
|
12890
|
+
() => void 0,
|
|
12891
|
+
() => void 0
|
|
12892
|
+
);
|
|
12561
12893
|
try {
|
|
12562
|
-
await Promise.race([this.transport.send(frame),
|
|
12894
|
+
await Promise.race([this.transport.send(frame), promiseSettled]);
|
|
12563
12895
|
} catch (err) {
|
|
12564
12896
|
const pending = this.pending.get(id);
|
|
12565
12897
|
if (pending) clearTimeout(pending.timeout);
|
|
@@ -13502,13 +13834,15 @@ Do NOT try to switch via \`run_command\` (\`cd\`, \`pushd\`, etc.) \u2014 your t
|
|
|
13502
13834
|
You have TWO tools for running shell commands, and picking the right one is non-negotiable:
|
|
13503
13835
|
|
|
13504
13836
|
- \`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:
|
|
13837
|
+
- \`run_background\` \u2014 spawns and detaches after a brief startup window. Use for:
|
|
13838
|
+
- **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\`.
|
|
13839
|
+
- **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
13840
|
|
|
13507
|
-
**Never use run_command for a dev server.** It will block
|
|
13841
|
+
**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
13842
|
|
|
13509
13843
|
After \`run_background\`, tools available to you:
|
|
13510
13844
|
- \`job_output(jobId, tailLines?)\` \u2014 read recent logs to verify startup / debug errors.
|
|
13511
|
-
- \`wait_for_job(jobId, timeoutMs?)\` \u2014 block until the job
|
|
13845
|
+
- \`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
13846
|
- \`list_jobs\` \u2014 see every job this session (running + exited).
|
|
13513
13847
|
- \`stop_job(jobId)\` \u2014 SIGTERM \u2192 SIGKILL after grace. Stop before switching port / config.
|
|
13514
13848
|
|