reasonix 0.46.1 → 0.47.1
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 +62 -13
- package/README.zh-CN.md +52 -10
- package/dashboard/dist/app.js +217 -60
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{acp-LKJU5DZX.js → acp-GEOAKSTU.js} +26 -54
- package/dist/cli/acp-GEOAKSTU.js.map +1 -0
- package/dist/cli/chat-YTPATMMG.js +51 -0
- package/dist/cli/{chunk-R2ASNSEO.js → chunk-2XY77LW7.js} +8 -8
- package/dist/cli/{chunk-SE7C5ZSI.js → chunk-4MFCAZ2W.js} +3 -3
- package/dist/cli/{chunk-DGA5QYFM.js → chunk-6CRPCJAU.js} +55 -13
- package/dist/cli/chunk-6CRPCJAU.js.map +1 -0
- package/dist/cli/{chunk-TDSBASOF.js → chunk-6QC5RQLE.js} +2 -2
- package/dist/cli/chunk-BQ6HC66J.js +530 -0
- package/dist/cli/chunk-BQ6HC66J.js.map +1 -0
- package/dist/cli/{chunk-7SGGXNB2.js → chunk-CCJAP7G3.js} +2 -2
- package/dist/cli/{chunk-3AAG2CUT.js → chunk-CNG32VAB.js} +2 -2
- package/dist/cli/{chunk-WRONKNIH.js → chunk-DN4B5S6Y.js} +2 -2
- package/dist/cli/{chunk-NCBP5D6E.js → chunk-DQ6K5ZQ7.js} +2 -2
- package/dist/cli/{chunk-MIIZJD5O.js → chunk-DWPAKZTY.js} +14 -3
- package/dist/cli/chunk-DWPAKZTY.js.map +1 -0
- package/dist/cli/{chunk-IYQ325V7.js → chunk-E5WCLUIU.js} +2 -2
- package/dist/cli/{chunk-YRLC2EDF.js → chunk-EQATK2L2.js} +2 -2
- package/dist/cli/{chunk-TEUDEGX2.js → chunk-FY4S7TJZ.js} +19 -5
- package/dist/cli/chunk-FY4S7TJZ.js.map +1 -0
- package/dist/cli/{chunk-C72TNHDE.js → chunk-GH7DC2Y5.js} +2 -2
- package/dist/cli/{chunk-WQ6ZRDQM.js → chunk-HIYTRCSW.js} +16 -12
- package/dist/cli/chunk-HIYTRCSW.js.map +1 -0
- package/dist/cli/{chunk-EAOL43HB.js → chunk-HUILPCYX.js} +3 -3
- package/dist/cli/{chunk-ZOQHVQON.js → chunk-JBH5RM7X.js} +473 -87
- package/dist/cli/chunk-JBH5RM7X.js.map +1 -0
- package/dist/cli/{chunk-XPAUNFOL.js → chunk-KVZZ5U75.js} +3 -2
- package/dist/cli/chunk-KVZZ5U75.js.map +1 -0
- package/dist/cli/{chunk-2AASOSD5.js → chunk-KYQVQ5X4.js} +85 -10
- package/dist/cli/chunk-KYQVQ5X4.js.map +1 -0
- package/dist/cli/{chunk-2425HK6U.js → chunk-LGEKVMMV.js} +7 -2
- package/dist/cli/{chunk-2425HK6U.js.map → chunk-LGEKVMMV.js.map} +1 -1
- package/dist/cli/{chunk-6VANO7KB.js → chunk-NRQ5UP5T.js} +165 -24
- package/dist/cli/chunk-NRQ5UP5T.js.map +1 -0
- package/dist/cli/{chunk-M4E5JK6S.js → chunk-QCFLPSPH.js} +2 -2
- package/dist/cli/{chunk-E7TAHQ4A.js → chunk-RRXUIPWG.js} +19 -18
- package/dist/cli/chunk-RRXUIPWG.js.map +1 -0
- package/dist/cli/{chunk-JLQDNLZF.js → chunk-T5A7EY6B.js} +26 -14
- package/dist/cli/chunk-T5A7EY6B.js.map +1 -0
- package/dist/cli/{chunk-7LOJS3LV.js → chunk-TDHXB2ER.js} +2 -2
- package/dist/cli/{chunk-CXVWUPA3.js → chunk-TKVXTQ3T.js} +26 -26
- package/dist/cli/chunk-TKVXTQ3T.js.map +1 -0
- package/dist/cli/{chunk-JVFEJAJX.js → chunk-TRSAHHCL.js} +107 -11
- package/dist/cli/chunk-TRSAHHCL.js.map +1 -0
- package/dist/cli/{chunk-K3AIFMI6.js → chunk-TRWHTFG7.js} +2 -2
- package/dist/cli/{chunk-7YW6TPXK.js → chunk-XD6P7AFH.js} +28 -31
- package/dist/cli/chunk-XD6P7AFH.js.map +1 -0
- package/dist/cli/{chunk-SPXN5JIT.js → chunk-XMHP7BEE.js} +1787 -1081
- package/dist/cli/chunk-XMHP7BEE.js.map +1 -0
- package/dist/cli/{chunk-JVQT5IYP.js → chunk-YFP3MYMY.js} +19 -9
- package/dist/cli/chunk-YFP3MYMY.js.map +1 -0
- package/dist/cli/{chunk-HNXDZGC6.js → chunk-ZXSCAODE.js} +9 -9
- package/dist/cli/{chunk-HNXDZGC6.js.map → chunk-ZXSCAODE.js.map} +1 -1
- package/dist/cli/{code-2JIHL5M2.js → code-Q4NRVEDG.js} +42 -35
- package/dist/cli/code-Q4NRVEDG.js.map +1 -0
- package/dist/cli/{commands-OPT5AJNH.js → commands-4CDI4GFM.js} +4 -4
- package/dist/cli/{commit-KA37H6GM.js → commit-GW7LDQP5.js} +3 -3
- package/dist/cli/{desktop-5ONTRU3C.js → desktop-EG6P5SF2.js} +321 -36
- package/dist/cli/desktop-EG6P5SF2.js.map +1 -0
- package/dist/cli/{diff-SOIA7AKH.js → diff-VI2YX4FN.js} +8 -8
- package/dist/cli/{doctor-RCUP4XRV.js → doctor-CQTTZP27.js} +9 -9
- package/dist/cli/{events-6KHITNX4.js → events-VRYXOSKI.js} +3 -3
- package/dist/cli/index.js +94 -45
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-JP5OWD6R.js → mcp-J2UCD4RZ.js} +2 -2
- package/dist/cli/{mcp-browse-ONCJJPJN.js → mcp-browse-GSX34JEK.js} +2 -2
- package/dist/cli/{mcp-inspect-TPLHW5JA.js → mcp-inspect-RRFYF4ZV.js} +4 -4
- package/dist/cli/{prompt-RJDNCQAP.js → prompt-5TQPIVHV.js} +4 -4
- package/dist/cli/{prune-sessions-MKEATRVL.js → prune-sessions-SEWX7GP6.js} +2 -2
- package/dist/cli/{replay-4NILJG4U.js → replay-MJCEMODU.js} +9 -9
- package/dist/cli/{run-WFGXB4SB.js → run-P4D5VDYE.js} +17 -17
- package/dist/cli/{server-5VFQP3PV.js → server-C25JNNZV.js} +82 -34
- package/dist/cli/server-C25JNNZV.js.map +1 -0
- package/dist/cli/{sessions-5XDJDALO.js → sessions-QIONZJQ6.js} +15 -15
- package/dist/cli/{setup-F6XSWLRA.js → setup-NLQ6G5G4.js} +7 -7
- package/dist/cli/setup-NLQ6G5G4.js.map +1 -0
- package/dist/cli/{stats-ALHBZICE.js → stats-DFZEXHP4.js} +6 -6
- package/dist/cli/{version-JVRAHBMM.js → version-GR3X3MPI.js} +15 -15
- package/dist/index.d.ts +69 -56
- package/dist/index.js +791 -303
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/cli/acp-LKJU5DZX.js.map +0 -1
- package/dist/cli/chat-W7LAWEN6.js +0 -51
- package/dist/cli/chunk-2AASOSD5.js.map +0 -1
- package/dist/cli/chunk-6VANO7KB.js.map +0 -1
- package/dist/cli/chunk-7YW6TPXK.js.map +0 -1
- package/dist/cli/chunk-CXVWUPA3.js.map +0 -1
- package/dist/cli/chunk-DGA5QYFM.js.map +0 -1
- package/dist/cli/chunk-DHRVZJ2D.js +0 -642
- package/dist/cli/chunk-DHRVZJ2D.js.map +0 -1
- package/dist/cli/chunk-E7TAHQ4A.js.map +0 -1
- package/dist/cli/chunk-JLQDNLZF.js.map +0 -1
- package/dist/cli/chunk-JVFEJAJX.js.map +0 -1
- package/dist/cli/chunk-JVQT5IYP.js.map +0 -1
- package/dist/cli/chunk-MIIZJD5O.js.map +0 -1
- package/dist/cli/chunk-SPXN5JIT.js.map +0 -1
- package/dist/cli/chunk-TEUDEGX2.js.map +0 -1
- package/dist/cli/chunk-WQ6ZRDQM.js.map +0 -1
- package/dist/cli/chunk-XPAUNFOL.js.map +0 -1
- package/dist/cli/chunk-ZOQHVQON.js.map +0 -1
- package/dist/cli/code-2JIHL5M2.js.map +0 -1
- package/dist/cli/desktop-5ONTRU3C.js.map +0 -1
- package/dist/cli/server-5VFQP3PV.js.map +0 -1
- package/dist/cli/setup-F6XSWLRA.js.map +0 -1
- /package/dist/cli/{chat-W7LAWEN6.js.map → chat-YTPATMMG.js.map} +0 -0
- /package/dist/cli/{chunk-R2ASNSEO.js.map → chunk-2XY77LW7.js.map} +0 -0
- /package/dist/cli/{chunk-SE7C5ZSI.js.map → chunk-4MFCAZ2W.js.map} +0 -0
- /package/dist/cli/{chunk-TDSBASOF.js.map → chunk-6QC5RQLE.js.map} +0 -0
- /package/dist/cli/{chunk-7SGGXNB2.js.map → chunk-CCJAP7G3.js.map} +0 -0
- /package/dist/cli/{chunk-3AAG2CUT.js.map → chunk-CNG32VAB.js.map} +0 -0
- /package/dist/cli/{chunk-WRONKNIH.js.map → chunk-DN4B5S6Y.js.map} +0 -0
- /package/dist/cli/{chunk-NCBP5D6E.js.map → chunk-DQ6K5ZQ7.js.map} +0 -0
- /package/dist/cli/{chunk-IYQ325V7.js.map → chunk-E5WCLUIU.js.map} +0 -0
- /package/dist/cli/{chunk-YRLC2EDF.js.map → chunk-EQATK2L2.js.map} +0 -0
- /package/dist/cli/{chunk-C72TNHDE.js.map → chunk-GH7DC2Y5.js.map} +0 -0
- /package/dist/cli/{chunk-EAOL43HB.js.map → chunk-HUILPCYX.js.map} +0 -0
- /package/dist/cli/{chunk-M4E5JK6S.js.map → chunk-QCFLPSPH.js.map} +0 -0
- /package/dist/cli/{chunk-7LOJS3LV.js.map → chunk-TDHXB2ER.js.map} +0 -0
- /package/dist/cli/{chunk-K3AIFMI6.js.map → chunk-TRWHTFG7.js.map} +0 -0
- /package/dist/cli/{commands-OPT5AJNH.js.map → commands-4CDI4GFM.js.map} +0 -0
- /package/dist/cli/{commit-KA37H6GM.js.map → commit-GW7LDQP5.js.map} +0 -0
- /package/dist/cli/{diff-SOIA7AKH.js.map → diff-VI2YX4FN.js.map} +0 -0
- /package/dist/cli/{doctor-RCUP4XRV.js.map → doctor-CQTTZP27.js.map} +0 -0
- /package/dist/cli/{events-6KHITNX4.js.map → events-VRYXOSKI.js.map} +0 -0
- /package/dist/cli/{mcp-JP5OWD6R.js.map → mcp-J2UCD4RZ.js.map} +0 -0
- /package/dist/cli/{mcp-browse-ONCJJPJN.js.map → mcp-browse-GSX34JEK.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-TPLHW5JA.js.map → mcp-inspect-RRFYF4ZV.js.map} +0 -0
- /package/dist/cli/{prompt-RJDNCQAP.js.map → prompt-5TQPIVHV.js.map} +0 -0
- /package/dist/cli/{prune-sessions-MKEATRVL.js.map → prune-sessions-SEWX7GP6.js.map} +0 -0
- /package/dist/cli/{replay-4NILJG4U.js.map → replay-MJCEMODU.js.map} +0 -0
- /package/dist/cli/{run-WFGXB4SB.js.map → run-P4D5VDYE.js.map} +0 -0
- /package/dist/cli/{sessions-5XDJDALO.js.map → sessions-QIONZJQ6.js.map} +0 -0
- /package/dist/cli/{stats-ALHBZICE.js.map → stats-DFZEXHP4.js.map} +0 -0
- /package/dist/cli/{version-JVRAHBMM.js.map → version-GR3X3MPI.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -472,6 +472,12 @@ function loadMetasoApiKey(path2 = defaultConfigPath()) {
|
|
|
472
472
|
if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
|
|
473
473
|
return DEFAULT_METASO_API_KEY;
|
|
474
474
|
}
|
|
475
|
+
function loadTavilyApiKey(path2 = defaultConfigPath()) {
|
|
476
|
+
if (process.env.TAVILY_API_KEY) return process.env.TAVILY_API_KEY.trim();
|
|
477
|
+
const cfg = readConfig(path2).tavilyApiKey;
|
|
478
|
+
if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
|
|
479
|
+
return void 0;
|
|
480
|
+
}
|
|
475
481
|
function defaultConfigPath() {
|
|
476
482
|
return join(homedir(), ".reasonix", "config.json");
|
|
477
483
|
}
|
|
@@ -573,6 +579,7 @@ function webSearchEngine(path2 = defaultConfigPath()) {
|
|
|
573
579
|
const cfg = readConfig(path2).webSearchEngine;
|
|
574
580
|
if (cfg === "searxng") return "searxng";
|
|
575
581
|
if (cfg === "metaso") return "metaso";
|
|
582
|
+
if (cfg === "tavily") return "tavily";
|
|
576
583
|
return "mojeek";
|
|
577
584
|
}
|
|
578
585
|
function webSearchEndpoint(path2 = defaultConfigPath()) {
|
|
@@ -799,7 +806,7 @@ var DeepSeekClient = class {
|
|
|
799
806
|
if (opts.temperature !== void 0) payload.temperature = opts.temperature;
|
|
800
807
|
if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
|
|
801
808
|
if (opts.responseFormat) payload.response_format = opts.responseFormat;
|
|
802
|
-
if (opts.thinking) {
|
|
809
|
+
if (opts.thinking && !this._isAzureEndpoint()) {
|
|
803
810
|
payload.extra_body = { thinking: { type: opts.thinking } };
|
|
804
811
|
}
|
|
805
812
|
if (opts.reasoningEffort) {
|
|
@@ -807,6 +814,17 @@ var DeepSeekClient = class {
|
|
|
807
814
|
}
|
|
808
815
|
return payload;
|
|
809
816
|
}
|
|
817
|
+
/** Azure OpenAI-compatible endpoints do not accept DeepSeek's proprietary
|
|
818
|
+
* `extra_body.thinking` field (they reject the request with 400). We still
|
|
819
|
+
* send `reasoning_effort`, which Azure *does* support. */
|
|
820
|
+
_isAzureEndpoint() {
|
|
821
|
+
try {
|
|
822
|
+
const host = new URL(this.baseUrl).hostname;
|
|
823
|
+
return host === "azure.com" || host.endsWith(".azure.com");
|
|
824
|
+
} catch {
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
810
828
|
/** Returns null on failure so callers can degrade — session must keep working without balance UI. */
|
|
811
829
|
async getBalance(opts = {}) {
|
|
812
830
|
try {
|
|
@@ -1191,8 +1209,9 @@ var EN = {
|
|
|
1191
1209
|
{ key: "wheel", text: "scrolls chat history (works on web/cloud/SSH terminals too)" },
|
|
1192
1210
|
{
|
|
1193
1211
|
key: "\u2191 / \u2193",
|
|
1194
|
-
text: "
|
|
1195
|
-
}
|
|
1212
|
+
text: "prompt history (or per-line cursor in a multi-line draft) \u2014 Ctrl+P / Ctrl+N alias"
|
|
1213
|
+
},
|
|
1214
|
+
{ key: "PgUp / PgDn", text: "scroll chat history (mouse wheel routes here too)" }
|
|
1196
1215
|
]
|
|
1197
1216
|
}
|
|
1198
1217
|
],
|
|
@@ -1206,11 +1225,11 @@ var EN = {
|
|
|
1206
1225
|
rows: [
|
|
1207
1226
|
{ key: "Enter", text: "submit the prompt" },
|
|
1208
1227
|
{ key: "Shift+Enter", text: "insert a newline in the prompt" },
|
|
1209
|
-
{ key: "\u2191 / \u2193", text: "scroll chat history (mouse wheel routes here too)" },
|
|
1210
1228
|
{
|
|
1211
|
-
key: "
|
|
1229
|
+
key: "\u2191 / \u2193",
|
|
1212
1230
|
text: "previous / next prompt history \xB7 cursor up / down in a multi-line draft"
|
|
1213
1231
|
},
|
|
1232
|
+
{ key: "Ctrl+P / Ctrl+N", text: "readline alias for \u2191 / \u2193" },
|
|
1214
1233
|
{ key: "Ctrl+A / Ctrl+E", text: "jump to start / end of the current line" },
|
|
1215
1234
|
{ key: "Ctrl+W", text: "delete the word before the cursor" },
|
|
1216
1235
|
{ key: "Ctrl+U", text: "clear the entire prompt buffer" },
|
|
@@ -1219,7 +1238,11 @@ var EN = {
|
|
|
1219
1238
|
{ key: "Esc", text: "dismiss picker \xB7 abort the running model turn" },
|
|
1220
1239
|
{ key: "Ctrl+C", text: "abort the running model turn (NOT copy \u2014 see clipboard)" },
|
|
1221
1240
|
{ key: "PgUp / PgDn", text: "scroll chat history a page at a time" },
|
|
1222
|
-
{ key: "End", text: "jump chat to the most recent line" }
|
|
1241
|
+
{ key: "End", text: "jump chat to the most recent line" },
|
|
1242
|
+
{
|
|
1243
|
+
key: "Ctrl+R",
|
|
1244
|
+
text: "toggle verbose mode \u2014 full reasoning + tool output, no head/tail elision"
|
|
1245
|
+
}
|
|
1223
1246
|
]
|
|
1224
1247
|
},
|
|
1225
1248
|
{
|
|
@@ -1258,7 +1281,7 @@ var EN = {
|
|
|
1258
1281
|
]
|
|
1259
1282
|
}
|
|
1260
1283
|
],
|
|
1261
|
-
footer: "Wheel
|
|
1284
|
+
footer: "Wheel scrolls chat on most terminals (web/cloud/SSH included) \u2014 SGR mouse tracking is on by default and stays out of the way of native drag-select and right-click. Pass --no-mouse to opt out."
|
|
1262
1285
|
},
|
|
1263
1286
|
tipShownOnce: "shown once",
|
|
1264
1287
|
modelOverride: "override the default model",
|
|
@@ -1413,7 +1436,7 @@ var EN = {
|
|
|
1413
1436
|
sessions: { description: "list saved sessions (current marked with \u25B8)" },
|
|
1414
1437
|
title: { description: "ask the model to rename this session from the conversation" },
|
|
1415
1438
|
qq: {
|
|
1416
|
-
description: "connect, inspect, or disconnect the QQ channel for this session",
|
|
1439
|
+
description: "connect, inspect, or disconnect the QQ channel for this session (first connect guides App ID / App Secret setup)",
|
|
1417
1440
|
argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
|
|
1418
1441
|
},
|
|
1419
1442
|
setup: { description: "reminds you to exit and run `reasonix setup`" },
|
|
@@ -1532,6 +1555,7 @@ var EN = {
|
|
|
1532
1555
|
reviewSaveError: "Could not save config: {message}",
|
|
1533
1556
|
reviewFooter: "[Enter] save \xB7 [Esc] cancel",
|
|
1534
1557
|
savedTitle: "\u25B8 Saved.",
|
|
1558
|
+
savedShellHint: "Shell commands the model wants to run ask each time \u2014 pick `allow always` on the prompt to whitelist that exact command for this project. No global allow-all flag by design.",
|
|
1535
1559
|
savedFooter: "[Enter] to exit",
|
|
1536
1560
|
selectFooter: "[\u2191\u2193] navigate \xB7 [Enter] confirm \xB7 [Esc] cancel",
|
|
1537
1561
|
stepCounter: "Step {step}/{total} \xB7 ",
|
|
@@ -1596,6 +1620,8 @@ var EN = {
|
|
|
1596
1620
|
title: "Checkpoint \u2014 step done",
|
|
1597
1621
|
continue: "Continue \u2014 run the next step",
|
|
1598
1622
|
continueHint: "Model resumes with the next step.",
|
|
1623
|
+
finish: "Finish \u2014 summarize and close",
|
|
1624
|
+
finishHint: "Model records the final step and summarizes the completed plan.",
|
|
1599
1625
|
revise: "Revise \u2014 give feedback before the next step",
|
|
1600
1626
|
reviseHint: "Stay paused, type guidance; model adjusts the remaining plan.",
|
|
1601
1627
|
stop: "Stop \u2014 end the plan here",
|
|
@@ -1637,6 +1663,8 @@ var EN = {
|
|
|
1637
1663
|
notedVerbCreated: "created",
|
|
1638
1664
|
notedVerbAppended: "appended to",
|
|
1639
1665
|
memoryWriteFailed: "# memory write failed",
|
|
1666
|
+
verboseOn: "\u25B8 verbose mode on \u2014 full reasoning + tool output",
|
|
1667
|
+
verboseOff: "\u25B8 verbose mode off \u2014 head/tail elision restored",
|
|
1640
1668
|
commandFailed: "! command failed",
|
|
1641
1669
|
btwUsage: "\u25B8 /btw <question> \u2014 ask a side question without polluting the conversation context.",
|
|
1642
1670
|
btwHeader: "\u226B btw",
|
|
@@ -1734,6 +1762,10 @@ var EN = {
|
|
|
1734
1762
|
helpShellDetail: " the conversation so the model sees it next turn.",
|
|
1735
1763
|
helpShellConsent: " No allowlist gate \u2014 user-typed = explicit consent.",
|
|
1736
1764
|
helpShellExample: " Example: !git status !ls src/ !npm test",
|
|
1765
|
+
helpShellGateTitle: "Model-invoked shell commands (per-call approval):",
|
|
1766
|
+
helpShellGate: " \u2191\u2193 + \u23CE each call shows a prompt with `allow once` / `allow always`",
|
|
1767
|
+
helpShellGateDetail: " / `deny`. Pick `allow always` to whitelist that exact",
|
|
1768
|
+
helpShellGatePolicy: " command prefix for this project. No global allow-all flag.",
|
|
1737
1769
|
helpMemoryTitle: "Quick memory:",
|
|
1738
1770
|
helpMemoryPin: " #<note> append <note> to <project>/REASONIX.md (committable).",
|
|
1739
1771
|
helpMemoryPinEx: " Example: #findByEmail must be case-insensitive",
|
|
@@ -1771,6 +1803,48 @@ var EN = {
|
|
|
1771
1803
|
titleStarted: "\u25B8 naming session\u2026",
|
|
1772
1804
|
titleFailed: "\u25B8 session title failed: {reason}"
|
|
1773
1805
|
},
|
|
1806
|
+
qq: {
|
|
1807
|
+
unavailable: "/qq is not available in this session.",
|
|
1808
|
+
connecting: "QQ: connecting\u2026",
|
|
1809
|
+
connectFailed: "QQ connect failed: {reason}",
|
|
1810
|
+
disconnecting: "QQ: disconnecting\u2026",
|
|
1811
|
+
disconnectFailed: "QQ disconnect failed: {reason}",
|
|
1812
|
+
usage: "Usage: /qq connect [appId appSecret [sandbox]] | /qq status | /qq disconnect",
|
|
1813
|
+
promptAppId: "QQ setup: enter your QQ Open Platform App ID, then press Enter. Type /cancel to abort.",
|
|
1814
|
+
promptAppSecret: "QQ setup: enter your QQ Open Platform App Secret, then press Enter. Type /cancel to abort.",
|
|
1815
|
+
setupWaitingAppId: "waiting for App ID",
|
|
1816
|
+
setupWaitingAppSecret: "waiting for App Secret",
|
|
1817
|
+
setupCancelled: "QQ setup cancelled.",
|
|
1818
|
+
credentialsRequired: "QQ App ID and App Secret are required.",
|
|
1819
|
+
connected: "QQ connected in {mode} mode. It will auto-start on future launches.",
|
|
1820
|
+
alreadyConnected: "QQ is already connected in {mode} mode. Auto-start is enabled.",
|
|
1821
|
+
disconnected: "QQ disconnected. Auto-start is disabled.",
|
|
1822
|
+
status: "QQ: {connected}, auto-start {enabled}, credentials {configured}, appId {appId}, {sandbox}, access {access}, current mode {mode}.",
|
|
1823
|
+
statusSetup: "QQ: setup in progress \u2014 {step}",
|
|
1824
|
+
stateConnected: "connected",
|
|
1825
|
+
stateDisconnected: "disconnected",
|
|
1826
|
+
stateEnabled: "enabled",
|
|
1827
|
+
stateDisabled: "disabled",
|
|
1828
|
+
stateConfigured: "configured",
|
|
1829
|
+
stateNotConfigured: "not configured",
|
|
1830
|
+
sandbox: "sandbox",
|
|
1831
|
+
production: "production",
|
|
1832
|
+
none: "none",
|
|
1833
|
+
modeChat: "chat",
|
|
1834
|
+
modeCode: "code",
|
|
1835
|
+
accessOwner: "owner {owner}",
|
|
1836
|
+
accessOwnerWithAllowlist: "owner {owner}, allowlist {count}",
|
|
1837
|
+
accessAllowlist: "allowlist {count}",
|
|
1838
|
+
accessRuntime: "first-sender (runtime only, {owner})",
|
|
1839
|
+
accessOpen: "open (unbound)",
|
|
1840
|
+
lockAlreadyRunning: "QQ channel is already running in process {pid}. Stop that process before starting another QQ channel.",
|
|
1841
|
+
unauthorizedMessage: "QQ ignored message from unauthorized openid {openid}. Current access: {access}.",
|
|
1842
|
+
runtimeBound: "QQ temporarily bound this run to first sender {openid}. Set `qq.ownerOpenId` in config to persist access.",
|
|
1843
|
+
missingAppId: "QQ App ID is required. Run `/qq connect` to configure.",
|
|
1844
|
+
missingAppSecret: "QQ App Secret is required. Run `/qq connect` to configure.",
|
|
1845
|
+
authFailed: "QQ bot authentication failed \u2014 check your App ID and App Secret.",
|
|
1846
|
+
readyTimeout: "QQ bot did not receive READY within 15s \u2014 check your App ID and App Secret."
|
|
1847
|
+
},
|
|
1774
1848
|
admin: {
|
|
1775
1849
|
doctorNeedsTui: "/doctor needs a TUI context (postDoctor wired).",
|
|
1776
1850
|
doctorRunning: "\u2695 Doctor \u2014 running health checks\u2026",
|
|
@@ -2003,7 +2077,7 @@ var EN = {
|
|
|
2003
2077
|
changesNoteShort: "Changes take effect on next /new or launch. Subcommands: /memory list | show | forget | clear"
|
|
2004
2078
|
},
|
|
2005
2079
|
mcp: {
|
|
2006
|
-
noServers: 'no MCP servers attached. Run `reasonix setup` to pick some, or launch with --mcp "<spec>". `reasonix mcp list` shows the catalog.',
|
|
2080
|
+
noServers: 'no MCP servers attached. Run `reasonix setup` to pick some, or launch with --mcp "<spec>". `reasonix mcp list` shows the catalog. Note: model-invoked shell commands are gated per-call (allow once / allow always / deny) \u2014 no global allow-all flag.',
|
|
2007
2081
|
toolsLabel: " tools {count}",
|
|
2008
2082
|
resourcesHint: "`/resource` to browse+read",
|
|
2009
2083
|
promptsHint: "`/prompt` to browse+fetch",
|
|
@@ -2037,12 +2111,14 @@ var EN = {
|
|
|
2037
2111
|
usageSearxng: " /search-engine searxng use SearXNG at default endpoint",
|
|
2038
2112
|
usageSearxngUrl: " /search-engine searxng <url> use SearXNG at custom endpoint",
|
|
2039
2113
|
usageMetaso: " /search-engine metaso use Metaso API (100/d free, configure your own API key for more)",
|
|
2114
|
+
usageTavily: " /search-engine tavily use Tavily API (LLM-friendly, free 1000/mo \u2014 set TAVILY_API_KEY or tavilyApiKey in config; get one at https://tavily.com)",
|
|
2040
2115
|
alias: "Alias: /se",
|
|
2041
2116
|
searxngInfo: "SearXNG is a self-hosted metasearch engine (https://github.com/searxng/searxng).",
|
|
2042
2117
|
searxngInstall: "Install it with: docker run -d -p 8080:8080 searxng/searxng",
|
|
2043
2118
|
switched: 'Switched web search engine to "{engine}".{note}',
|
|
2044
2119
|
switchedSearxngNote: " Make sure SearXNG is running at {endpoint}.",
|
|
2045
2120
|
switchedMetasoNote: " There is a daily quota of 100 (configure your own API key for higher limits).",
|
|
2121
|
+
switchedTavilyNote: " Set TAVILY_API_KEY or `tavilyApiKey` in config; free 1000/mo at https://tavily.com.",
|
|
2046
2122
|
confirmed: '\u2713 Web search engine set to "{engine}"{detail}. Next assistant turn will pick up the change.',
|
|
2047
2123
|
confirmedDetail: " ({endpoint})"
|
|
2048
2124
|
},
|
|
@@ -2178,6 +2254,13 @@ var EN = {
|
|
|
2178
2254
|
linesBelow: " \u2193 {count} line below (\u2193/j or Space/PgDn)",
|
|
2179
2255
|
linesBelowPlural: " \u2193 {count} lines below (\u2193/j or Space/PgDn)"
|
|
2180
2256
|
},
|
|
2257
|
+
editPicker: {
|
|
2258
|
+
title: "edit a previous message",
|
|
2259
|
+
hint: "\u2191\u2193 pick \xB7 Enter to load into composer \xB7 Esc to cancel",
|
|
2260
|
+
empty: "no user turns yet \u2014 nothing to edit",
|
|
2261
|
+
dismiss: "Esc to dismiss",
|
|
2262
|
+
forked: "\u25B8 forked at turn #{turn} \u2014 buffer holds the original text"
|
|
2263
|
+
},
|
|
2181
2264
|
sessionPicker: {
|
|
2182
2265
|
header: " \u25C8 REASONIX \xB7 pick a session ",
|
|
2183
2266
|
title: "pick a session \u2014 {workspace}",
|
|
@@ -2311,6 +2394,11 @@ var EN = {
|
|
|
2311
2394
|
metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
|
|
2312
2395
|
metasoParseError: "web_search: Metaso returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2313
2396
|
metasoApiError: "web_search: Metaso API error (code {code}: {message}) \u2014 try again later",
|
|
2397
|
+
tavilyMissingKey: "web_search: Tavily backend requires an API key \u2014 set TAVILY_API_KEY env var or `tavilyApiKey` in ~/.reasonix/config.json; free 1000/mo signup at https://tavily.com",
|
|
2398
|
+
tavilyUnauthorized: "web_search: Tavily API key rejected \u2014 check TAVILY_API_KEY or get one at https://tavily.com",
|
|
2399
|
+
tavilyRateLimit: "web_search: Tavily rate-limited or monthly quota exceeded \u2014 wait, switch engine with /search-engine mojeek, or upgrade your Tavily plan",
|
|
2400
|
+
tavilyServerError: "web_search: Tavily server error ({status}) \u2014 try again later, or switch engine with /search-engine mojeek",
|
|
2401
|
+
tavilyParseError: "web_search: Tavily returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2314
2402
|
fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
|
|
2315
2403
|
fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
|
|
2316
2404
|
fetchForbidden403: "web_fetch 403 for {url} \u2014 try: the host is blocking this client; the page may require login or block bots \u2014 use web_search snippets instead",
|
|
@@ -2420,7 +2508,7 @@ var EN = {
|
|
|
2420
2508
|
slow: "slow \xB7 {ms}ms",
|
|
2421
2509
|
verySlow: "very slow \xB7 {ms}ms",
|
|
2422
2510
|
slowToast: "\u26A0 MCP `{name}` slow \xB7 {seconds}s p95 over the last {sampleSize} calls",
|
|
2423
|
-
emptyHint: "\u2139 no MCP servers configured \u2014 try: `reasonix setup` to re-pick, or `reasonix mcp install filesystem`"
|
|
2511
|
+
emptyHint: "\u2139 no MCP servers configured \u2014 try: `reasonix setup` to re-pick, or `reasonix mcp install filesystem` \xB7 shell commands gate per-call (allow once / allow always / deny), no global allow-all"
|
|
2424
2512
|
},
|
|
2425
2513
|
denyContextInput: {
|
|
2426
2514
|
description: "Tell the agent why you denied this. The next attempt will see your reason as additional context."
|
|
@@ -2429,7 +2517,8 @@ var EN = {
|
|
|
2429
2517
|
scrollAbove: " \u2191 {scroll} / {max} row above",
|
|
2430
2518
|
scrollAbovePlural: " \u2191 {scroll} / {max} rows above",
|
|
2431
2519
|
scrollMore: " \u2014 {remaining} more",
|
|
2432
|
-
scrollPgUp: " \xB7 PgUp / wheel
|
|
2520
|
+
scrollPgUp: " \xB7 PgUp / wheel",
|
|
2521
|
+
scrollCopy: " \xB7 /copy enters copy mode"
|
|
2433
2522
|
},
|
|
2434
2523
|
slashArgPicker: {
|
|
2435
2524
|
noMatch: 'no match for "{partial}"',
|
|
@@ -2487,7 +2576,8 @@ var EN = {
|
|
|
2487
2576
|
reconnectDetail: "tearing down \xB7 re-handshake \xB7 listing tools",
|
|
2488
2577
|
disabledDetail: "via /mcp disable {name}",
|
|
2489
2578
|
failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
|
|
2490
|
-
failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config."
|
|
2579
|
+
failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config.",
|
|
2580
|
+
abortedHint: "MCP startup aborted \u2014 {count} server(s) skipped. Run /mcp to retry once you've fixed the underlying issue."
|
|
2491
2581
|
},
|
|
2492
2582
|
checkpointPicker: {
|
|
2493
2583
|
title: "restore a checkpoint \u2014 {workspace}",
|
|
@@ -2632,8 +2722,9 @@ var zhCN = {
|
|
|
2632
2722
|
{ key: "\u6EDA\u8F6E", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08Web / \u4E91\u7AEF / SSH \u7EC8\u7AEF\u4E5F\u80FD\u7528\uFF09" },
|
|
2633
2723
|
{
|
|
2634
2724
|
key: "\u2191 / \u2193",
|
|
2635
|
-
text: "\
|
|
2636
|
-
}
|
|
2725
|
+
text: "\u8F93\u5165\u5386\u53F2\uFF08\u591A\u884C\u8349\u7A3F\u65F6\u6309\u884C\u79FB\u52A8\u5149\u6807\uFF09\u2014 Ctrl+P / Ctrl+N \u540C\u4E49"
|
|
2726
|
+
},
|
|
2727
|
+
{ key: "PgUp / PgDn", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08\u9F20\u6807\u6EDA\u8F6E\u4E5F\u8D70\u8FD9\u6761\u8DEF\u5F84\uFF09" }
|
|
2637
2728
|
]
|
|
2638
2729
|
}
|
|
2639
2730
|
],
|
|
@@ -2647,11 +2738,11 @@ var zhCN = {
|
|
|
2647
2738
|
rows: [
|
|
2648
2739
|
{ key: "Enter", text: "\u63D0\u4EA4\u8F93\u5165" },
|
|
2649
2740
|
{ key: "Shift+Enter", text: "\u5728\u8F93\u5165\u6846\u4E2D\u63D2\u5165\u6362\u884C" },
|
|
2650
|
-
{ key: "\u2191 / \u2193", text: "\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55\uFF08\u9F20\u6807\u6EDA\u8F6E\u4E5F\u8D70\u8FD9\u6761\u8DEF\u5F84\uFF09" },
|
|
2651
2741
|
{
|
|
2652
|
-
key: "
|
|
2742
|
+
key: "\u2191 / \u2193",
|
|
2653
2743
|
text: "\u4E0A\u4E00\u6761 / \u4E0B\u4E00\u6761\u8F93\u5165\u5386\u53F2 \xB7 \u591A\u884C\u8349\u7A3F\u4E2D\u6309\u884C\u79FB\u52A8\u5149\u6807"
|
|
2654
2744
|
},
|
|
2745
|
+
{ key: "Ctrl+P / Ctrl+N", text: "\u2191 / \u2193 \u7684 readline \u540C\u4E49\u952E" },
|
|
2655
2746
|
{ key: "Ctrl+A / Ctrl+E", text: "\u8DF3\u5230\u5F53\u524D\u884C\u7684\u5F00\u5934 / \u7ED3\u5C3E" },
|
|
2656
2747
|
{ key: "Ctrl+W", text: "\u5220\u9664\u5149\u6807\u524D\u7684\u4E00\u4E2A\u8BCD" },
|
|
2657
2748
|
{ key: "Ctrl+U", text: "\u6E05\u7A7A\u6574\u4E2A\u8F93\u5165\u7F13\u51B2\u533A" },
|
|
@@ -2660,7 +2751,8 @@ var zhCN = {
|
|
|
2660
2751
|
{ key: "Esc", text: "\u5173\u95ED\u5F39\u51FA\u9009\u62E9\u5668 \xB7 \u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408" },
|
|
2661
2752
|
{ key: "Ctrl+C", text: "\u4E2D\u6B62\u5F53\u524D\u6A21\u578B\u56DE\u5408\uFF08\u4E0D\u662F\u590D\u5236 \u2014 \u89C1\u526A\u8D34\u677F\u6BB5\uFF09" },
|
|
2662
2753
|
{ key: "PgUp / PgDn", text: "\u6574\u9875\u6EDA\u52A8\u804A\u5929\u8BB0\u5F55" },
|
|
2663
|
-
{ key: "End", text: "\u8DF3\u5230\u804A\u5929\u7684\u6700\u65B0\u4E00\u884C" }
|
|
2754
|
+
{ key: "End", text: "\u8DF3\u5230\u804A\u5929\u7684\u6700\u65B0\u4E00\u884C" },
|
|
2755
|
+
{ key: "Ctrl+R", text: "\u5207\u6362\u8BE6\u7EC6\u6A21\u5F0F \u2014 \u663E\u793A\u5B8C\u6574\u63A8\u7406 + \u5DE5\u5177\u8F93\u51FA\uFF0C\u4E0D\u7701\u7565" }
|
|
2664
2756
|
]
|
|
2665
2757
|
},
|
|
2666
2758
|
{
|
|
@@ -2699,7 +2791,7 @@ var zhCN = {
|
|
|
2699
2791
|
]
|
|
2700
2792
|
}
|
|
2701
2793
|
],
|
|
2702
|
-
footer: "\
|
|
2794
|
+
footer: "\u6EDA\u8F6E\u5728\u5927\u591A\u6570\u7EC8\u7AEF\uFF08\u542B Web / \u4E91\u7AEF / SSH\uFF09\u90FD\u80FD\u6EDA\u804A\u5929 \u2014 \u9ED8\u8BA4\u5F00\u542F SGR \u9F20\u6807\u8DDF\u8E2A\uFF0C\u4F46\u4E0D\u4F1A\u5F71\u54CD\u7EC8\u7AEF\u539F\u751F\u62D6\u9009\u548C\u53F3\u952E\u83DC\u5355\u3002\u76F4\u63A5\u62D6\u52A8\u9009\u4E2D\u6587\u672C\u65E0\u9700 Shift\u3002\u4F20\u5165 --no-mouse \u53EF\u5173\u95ED\u3002"
|
|
2703
2795
|
},
|
|
2704
2796
|
tipShownOnce: "\u4EC5\u663E\u793A\u4E00\u6B21",
|
|
2705
2797
|
modelOverride: "\u8986\u76D6\u9ED8\u8BA4\u6A21\u578B",
|
|
@@ -2856,7 +2948,7 @@ var zhCN = {
|
|
|
2856
2948
|
sessions: { description: "\u5217\u51FA\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD\uFF08\u5F53\u524D\u6807\u8BB0\u4E3A \u25B8\uFF09" },
|
|
2857
2949
|
title: { description: "\u8BA9\u6A21\u578B\u6839\u636E\u5F53\u524D\u5BF9\u8BDD\u91CD\u547D\u540D\u6B64\u4F1A\u8BDD" },
|
|
2858
2950
|
qq: {
|
|
2859
|
-
description: "\u8FDE\u63A5\u3001\u67E5\u770B\u6216\u65AD\u5F00\u5F53\u524D\u4F1A\u8BDD\u7684 QQ \u901A\u9053",
|
|
2951
|
+
description: "\u8FDE\u63A5\u3001\u67E5\u770B\u6216\u65AD\u5F00\u5F53\u524D\u4F1A\u8BDD\u7684 QQ \u901A\u9053\uFF08\u9996\u6B21\u8FDE\u63A5\u4F1A\u5F15\u5BFC\u5F55\u5165 App ID / App Secret\uFF09",
|
|
2860
2952
|
argsHint: "[connect [appId appSecret [sandbox]]|status|disconnect]"
|
|
2861
2953
|
},
|
|
2862
2954
|
setup: { description: "\u63D0\u9192\u60A8\u9000\u51FA\u5E76\u8FD0\u884C `reasonix setup`" },
|
|
@@ -2977,6 +3069,7 @@ var zhCN = {
|
|
|
2977
3069
|
reviewSaveError: "\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25\uFF1A{message}",
|
|
2978
3070
|
reviewFooter: "[Enter] \u4FDD\u5B58 \xB7 [Esc] \u53D6\u6D88",
|
|
2979
3071
|
savedTitle: "\u25B8 \u5DF2\u4FDD\u5B58\u3002",
|
|
3072
|
+
savedShellHint: "\u6A21\u578B\u53D1\u8D77\u7684 shell \u547D\u4EE4\u6BCF\u6B21\u90FD\u4F1A\u5F39\u51FA\u786E\u8BA4 \u2014\u2014 \u5728\u63D0\u793A\u6846\u91CC\u9009 `allow always` \u53EF\u5C06\u8BE5\u547D\u4EE4\u524D\u7F00\u52A0\u5165\u672C\u9879\u76EE\u767D\u540D\u5355\u3002\u8BBE\u8BA1\u4E0A\u6CA1\u6709\u300C\u5168\u5C40\u653E\u884C\u300D\u5F00\u5173\u3002",
|
|
2980
3073
|
savedFooter: "[Enter] \u9000\u51FA",
|
|
2981
3074
|
selectFooter: "[\u2191\u2193] \u79FB\u52A8 \xB7 [Enter] \u786E\u8BA4 \xB7 [Esc] \u53D6\u6D88",
|
|
2982
3075
|
stepCounter: "\u6B65\u9AA4 {step}/{total} \xB7 ",
|
|
@@ -3041,6 +3134,8 @@ var zhCN = {
|
|
|
3041
3134
|
title: "\u68C0\u67E5\u70B9 \u2014\u2014 \u5F53\u524D\u6B65\u9AA4\u5DF2\u5B8C\u6210",
|
|
3042
3135
|
continue: "\u7EE7\u7EED \u2014\u2014 \u6267\u884C\u4E0B\u4E00\u6B65",
|
|
3043
3136
|
continueHint: "\u6A21\u578B\u4ECE\u4E0B\u4E00\u6B65\u7EE7\u7EED\u3002",
|
|
3137
|
+
finish: "\u5B8C\u6210 \u2014\u2014 \u603B\u7ED3\u5E76\u6536\u5C3E",
|
|
3138
|
+
finishHint: "\u6A21\u578B\u8BB0\u5F55\u6700\u540E\u4E00\u6B65\uFF0C\u7136\u540E\u603B\u7ED3\u5DF2\u5B8C\u6210\u7684\u8BA1\u5212\u3002",
|
|
3044
3139
|
revise: "\u8C03\u6574 \u2014\u2014 \u5728\u4E0B\u4E00\u6B65\u524D\u7ED9\u53CD\u9988",
|
|
3045
3140
|
reviseHint: "\u5148\u6682\u505C\uFF0C\u8F93\u5165\u6307\u5F15\uFF1B\u6A21\u578B\u4F1A\u8C03\u6574\u5269\u4F59\u8BA1\u5212\u3002",
|
|
3046
3141
|
stop: "\u505C\u6B62 \u2014\u2014 \u5728\u6B64\u7ED3\u675F\u8BA1\u5212",
|
|
@@ -3082,6 +3177,8 @@ var zhCN = {
|
|
|
3082
3177
|
notedVerbCreated: "\u521B\u5EFA",
|
|
3083
3178
|
notedVerbAppended: "\u8FFD\u52A0\u5230",
|
|
3084
3179
|
memoryWriteFailed: "# \u8BB0\u5FC6\u5199\u5165\u5931\u8D25",
|
|
3180
|
+
verboseOn: "\u25B8 \u8BE6\u7EC6\u6A21\u5F0F\u5DF2\u5F00 \u2014 \u663E\u793A\u5B8C\u6574\u63A8\u7406 + \u5DE5\u5177\u8F93\u51FA",
|
|
3181
|
+
verboseOff: "\u25B8 \u8BE6\u7EC6\u6A21\u5F0F\u5DF2\u5173 \u2014 \u6062\u590D\u5934\u5C3E\u7701\u7565",
|
|
3085
3182
|
commandFailed: "! \u547D\u4EE4\u5931\u8D25",
|
|
3086
3183
|
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",
|
|
3087
3184
|
btwHeader: "\u226B btw",
|
|
@@ -3179,6 +3276,10 @@ var zhCN = {
|
|
|
3179
3276
|
helpShellDetail: " \u4EE5\u4FBF\u6A21\u578B\u5728\u4E0B\u4E00\u8F6E\u770B\u5230\u3002\u65E0\u5141\u8BB8\u5217\u8868\u9650\u5236\u3002",
|
|
3180
3277
|
helpShellConsent: " \u7528\u6237\u8F93\u5165 = \u660E\u786E\u540C\u610F\u3002",
|
|
3181
3278
|
helpShellExample: " \u793A\u4F8B\uFF1A!git status !ls src/ !npm test",
|
|
3279
|
+
helpShellGateTitle: "\u6A21\u578B\u53D1\u8D77\u7684 shell \u547D\u4EE4\uFF08\u6309\u6B21\u5BA1\u6279\uFF09\uFF1A",
|
|
3280
|
+
helpShellGate: " \u2191\u2193 + \u23CE \u6BCF\u6B21\u90FD\u4F1A\u5F39\u51FA `allow once` / `allow always` /",
|
|
3281
|
+
helpShellGateDetail: " `deny` \u4E09\u9009\u4E00\u3002\u9009 `allow always` \u53EF\u5C06\u8BE5\u547D\u4EE4\u524D\u7F00",
|
|
3282
|
+
helpShellGatePolicy: " \u52A0\u5165\u672C\u9879\u76EE\u767D\u540D\u5355\u3002\u8BBE\u8BA1\u4E0A\u6CA1\u6709\u300C\u5168\u5C40\u653E\u884C\u300D\u5F00\u5173\u3002",
|
|
3182
3283
|
helpMemoryTitle: "\u5FEB\u901F\u8BB0\u5FC6\uFF1A",
|
|
3183
3284
|
helpMemoryPin: " #<note> \u5C06 <note> \u8FFD\u52A0\u5230 <project>/REASONIX.md\uFF08\u53EF\u63D0\u4EA4\uFF09\u3002",
|
|
3184
3285
|
helpMemoryPinEx: " \u793A\u4F8B\uFF1A#findByEmail \u5FC5\u987B\u533A\u5206\u5927\u5C0F\u5199",
|
|
@@ -3216,6 +3317,48 @@ var zhCN = {
|
|
|
3216
3317
|
titleStarted: "\u25B8 \u6B63\u5728\u547D\u540D\u4F1A\u8BDD\u2026",
|
|
3217
3318
|
titleFailed: "\u25B8 \u4F1A\u8BDD\u547D\u540D\u5931\u8D25\uFF1A{reason}"
|
|
3218
3319
|
},
|
|
3320
|
+
qq: {
|
|
3321
|
+
unavailable: "/qq \u5728\u5F53\u524D\u4F1A\u8BDD\u4E2D\u4E0D\u53EF\u7528\u3002",
|
|
3322
|
+
connecting: "QQ\uFF1A\u6B63\u5728\u8FDE\u63A5\u2026",
|
|
3323
|
+
connectFailed: "QQ \u8FDE\u63A5\u5931\u8D25\uFF1A{reason}",
|
|
3324
|
+
disconnecting: "QQ\uFF1A\u6B63\u5728\u65AD\u5F00\u2026",
|
|
3325
|
+
disconnectFailed: "QQ \u65AD\u5F00\u5931\u8D25\uFF1A{reason}",
|
|
3326
|
+
usage: "\u7528\u6CD5\uFF1A/qq connect [appId appSecret [sandbox]] | /qq status | /qq disconnect",
|
|
3327
|
+
promptAppId: "QQ \u9996\u6B21\u914D\u7F6E\uFF1A\u8BF7\u8F93\u5165 QQ \u5F00\u653E\u5E73\u53F0 App ID \u540E\u56DE\u8F66\u3002\u8F93\u5165 /cancel \u53EF\u53D6\u6D88\u3002",
|
|
3328
|
+
promptAppSecret: "QQ \u9996\u6B21\u914D\u7F6E\uFF1A\u8BF7\u8F93\u5165 QQ \u5F00\u653E\u5E73\u53F0 App Secret \u540E\u56DE\u8F66\u3002\u8F93\u5165 /cancel \u53EF\u53D6\u6D88\u3002",
|
|
3329
|
+
setupWaitingAppId: "\u7B49\u5F85\u8F93\u5165 App ID",
|
|
3330
|
+
setupWaitingAppSecret: "\u7B49\u5F85\u8F93\u5165 App Secret",
|
|
3331
|
+
setupCancelled: "QQ \u9996\u6B21\u914D\u7F6E\u5DF2\u53D6\u6D88\u3002",
|
|
3332
|
+
credentialsRequired: "QQ App ID \u548C App Secret \u4E0D\u80FD\u4E3A\u7A7A\u3002",
|
|
3333
|
+
connected: "QQ \u5DF2\u5728{mode}\u6A21\u5F0F\u4E0B\u8FDE\u63A5\u6210\u529F\uFF0C\u540E\u7EED\u542F\u52A8\u4F1A\u81EA\u52A8\u542F\u7528\u3002",
|
|
3334
|
+
alreadyConnected: "QQ \u5DF2\u5728{mode}\u6A21\u5F0F\u4E0B\u8FDE\u63A5\uFF0C\u81EA\u52A8\u542F\u52A8\u5DF2\u542F\u7528\u3002",
|
|
3335
|
+
disconnected: "QQ \u5DF2\u65AD\u5F00\u8FDE\u63A5\uFF0C\u81EA\u52A8\u542F\u52A8\u5DF2\u5173\u95ED\u3002",
|
|
3336
|
+
status: "QQ\uFF1A{connected}\uFF0C\u81EA\u52A8\u542F\u52A8{enabled}\uFF0C\u51ED\u636E{configured}\uFF0CappId {appId}\uFF0C{sandbox}\uFF0C\u8BBF\u95EE\u63A7\u5236 {access}\uFF0C\u5F53\u524D\u6A21\u5F0F {mode}\u3002",
|
|
3337
|
+
statusSetup: "QQ\uFF1A\u9996\u6B21\u914D\u7F6E\u8FDB\u884C\u4E2D \u2014\u2014 {step}",
|
|
3338
|
+
stateConnected: "\u5DF2\u8FDE\u63A5",
|
|
3339
|
+
stateDisconnected: "\u672A\u8FDE\u63A5",
|
|
3340
|
+
stateEnabled: "\u5DF2\u542F\u7528",
|
|
3341
|
+
stateDisabled: "\u672A\u542F\u7528",
|
|
3342
|
+
stateConfigured: "\u5DF2\u914D\u7F6E",
|
|
3343
|
+
stateNotConfigured: "\u672A\u914D\u7F6E",
|
|
3344
|
+
sandbox: "\u6C99\u7BB1\u73AF\u5883",
|
|
3345
|
+
production: "\u6B63\u5F0F\u73AF\u5883",
|
|
3346
|
+
none: "\u65E0",
|
|
3347
|
+
modeChat: "\u804A\u5929",
|
|
3348
|
+
modeCode: "\u4EE3\u7801",
|
|
3349
|
+
accessOwner: "\u6240\u6709\u8005 {owner}",
|
|
3350
|
+
accessOwnerWithAllowlist: "\u6240\u6709\u8005 {owner}\uFF0C\u767D\u540D\u5355 {count}",
|
|
3351
|
+
accessAllowlist: "\u767D\u540D\u5355 {count}",
|
|
3352
|
+
accessRuntime: "\u9996\u4E2A\u79C1\u804A\u7528\u6237\uFF08\u4EC5\u672C\u6B21\u8FD0\u884C\uFF0C{owner}\uFF09",
|
|
3353
|
+
accessOpen: "\u5F00\u653E\uFF08\u672A\u7ED1\u5B9A\uFF09",
|
|
3354
|
+
lockAlreadyRunning: "QQ \u901A\u9053\u5DF2\u5728\u8FDB\u7A0B {pid} \u4E2D\u8FD0\u884C\u3002\u8BF7\u5148\u505C\u6B62\u8BE5\u8FDB\u7A0B\uFF0C\u518D\u542F\u52A8\u65B0\u7684 QQ \u901A\u9053\u3002",
|
|
3355
|
+
unauthorizedMessage: "QQ \u5FFD\u7565\u4E86\u672A\u6388\u6743 openid {openid} \u7684\u6D88\u606F\u3002\u5F53\u524D\u8BBF\u95EE\u63A7\u5236\uFF1A{access}\u3002",
|
|
3356
|
+
runtimeBound: "QQ \u5DF2\u5728\u672C\u6B21\u8FD0\u884C\u4E2D\u4E34\u65F6\u7ED1\u5B9A\u5230\u9996\u4E2A\u53D1\u9001\u8005 {openid}\u3002\u5982\u9700\u6301\u4E45\u5316\uFF0C\u8BF7\u5728\u914D\u7F6E\u4E2D\u8BBE\u7F6E `qq.ownerOpenId`\u3002",
|
|
3357
|
+
missingAppId: "\u7F3A\u5C11 QQ App ID\u3002\u8BF7\u5148\u8FD0\u884C `/qq connect` \u5B8C\u6210\u914D\u7F6E\u3002",
|
|
3358
|
+
missingAppSecret: "\u7F3A\u5C11 QQ App Secret\u3002\u8BF7\u5148\u8FD0\u884C `/qq connect` \u5B8C\u6210\u914D\u7F6E\u3002",
|
|
3359
|
+
authFailed: "QQ \u673A\u5668\u4EBA\u9274\u6743\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5 App ID \u548C App Secret\u3002",
|
|
3360
|
+
readyTimeout: "QQ \u673A\u5668\u4EBA 15 \u79D2\u5185\u672A\u6536\u5230 READY\uFF0C\u8BF7\u68C0\u67E5 App ID \u548C App Secret\u3002"
|
|
3361
|
+
},
|
|
3219
3362
|
admin: {
|
|
3220
3363
|
doctorNeedsTui: "/doctor \u9700\u8981 TUI \u4E0A\u4E0B\u6587\uFF08postDoctor \u5DF2\u8FDE\u63A5\uFF09\u3002",
|
|
3221
3364
|
doctorRunning: "\u2695 \u5065\u5EB7\u68C0\u67E5 \u2014 \u6B63\u5728\u8FD0\u884C\u2026",
|
|
@@ -3448,7 +3591,7 @@ var zhCN = {
|
|
|
3448
3591
|
changesNoteShort: "\u66F4\u6539\u5728\u4E0B\u6B21 /new \u6216\u542F\u52A8\u65F6\u751F\u6548\u3002\u5B50\u547D\u4EE4\uFF1A/memory list | show | forget | clear"
|
|
3449
3592
|
},
|
|
3450
3593
|
mcp: {
|
|
3451
|
-
noServers: '\u672A\u9644\u52A0 MCP \u670D\u52A1\u5668\u3002\u8FD0\u884C `reasonix setup` \u9009\u62E9\u4E00\u4E9B\uFF0C\u6216\u4F7F\u7528 --mcp "<spec>" \u542F\u52A8\u3002`reasonix mcp list` \u663E\u793A\u76EE\u5F55\u3002',
|
|
3594
|
+
noServers: '\u672A\u9644\u52A0 MCP \u670D\u52A1\u5668\u3002\u8FD0\u884C `reasonix setup` \u9009\u62E9\u4E00\u4E9B\uFF0C\u6216\u4F7F\u7528 --mcp "<spec>" \u542F\u52A8\u3002`reasonix mcp list` \u663E\u793A\u76EE\u5F55\u3002\u6CE8\uFF1A\u6A21\u578B\u53D1\u8D77\u7684 shell \u547D\u4EE4\u6309\u6B21\u5BA1\u6279\uFF08allow once / allow always / deny\uFF09\uFF0C\u8BBE\u8BA1\u4E0A\u6CA1\u6709\u300C\u5168\u5C40\u653E\u884C\u300D\u5F00\u5173\u3002',
|
|
3452
3595
|
toolsLabel: " \u5DE5\u5177 {count}",
|
|
3453
3596
|
resourcesHint: "`/resource` \u6D4F\u89C8+\u8BFB\u53D6",
|
|
3454
3597
|
promptsHint: "`/prompt` \u6D4F\u89C8+\u83B7\u53D6",
|
|
@@ -3482,12 +3625,14 @@ var zhCN = {
|
|
|
3482
3625
|
usageSearxng: " /search-engine searxng \u4F7F\u7528 SearXNG \u9ED8\u8BA4\u7AEF\u70B9",
|
|
3483
3626
|
usageSearxngUrl: " /search-engine searxng <url> \u4F7F\u7528 SearXNG \u81EA\u5B9A\u4E49\u7AEF\u70B9",
|
|
3484
3627
|
usageMetaso: " /search-engine metaso \u4F7F\u7528 Metaso API\uFF08\u6BCF\u5929 100 \u6B21\u514D\u8D39\uFF0C\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09",
|
|
3628
|
+
usageTavily: " /search-engine tavily \u4F7F\u7528 Tavily API\uFF08LLM \u53CB\u597D\uFF0C\u6BCF\u6708 1000 \u6B21\u514D\u8D39 \u2014 \u8BBE\u7F6E TAVILY_API_KEY \u6216 config \u7684 tavilyApiKey\uFF1B\u6CE8\u518C https://tavily.com\uFF09",
|
|
3485
3629
|
alias: "\u522B\u540D\uFF1A/se",
|
|
3486
3630
|
searxngInfo: "SearXNG \u662F\u4E00\u4E2A\u81EA\u6258\u7BA1\u7684\u5143\u641C\u7D22\u5F15\u64CE\uFF08https://github.com/searxng/searxng\uFF09\u3002",
|
|
3487
3631
|
searxngInstall: "\u5B89\u88C5\u547D\u4EE4\uFF1A docker run -d -p 8080:8080 searxng/searxng",
|
|
3488
3632
|
switched: '\u5DF2\u5207\u6362\u7F51\u9875\u641C\u7D22\u5F15\u64CE\u4E3A "{engine}"\u3002{note}',
|
|
3489
3633
|
switchedSearxngNote: " \u8BF7\u786E\u4FDD SearXNG \u5728 {endpoint} \u8FD0\u884C\u3002",
|
|
3490
3634
|
switchedMetasoNote: " \u6BCF\u65E5\u9650\u989D 100 \u6B21\uFF08\u914D\u7F6E\u4F60\u81EA\u5DF1\u7684 API \u5BC6\u94A5\u53EF\u63D0\u5347\u9650\u989D\uFF09\u3002",
|
|
3635
|
+
switchedTavilyNote: " \u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF TAVILY_API_KEY \u6216 config \u4E2D\u7684 `tavilyApiKey`\uFF1Bhttps://tavily.com \u6BCF\u6708 1000 \u6B21\u514D\u8D39\u3002",
|
|
3491
3636
|
confirmed: '\u2713 \u7F51\u9875\u641C\u7D22\u5F15\u64CE\u5DF2\u8BBE\u4E3A "{engine}"{detail}\u3002\u4E0B\u4E00\u8F6E\u6A21\u578B\u8C03\u7528\u5C06\u751F\u6548\u3002',
|
|
3492
3637
|
confirmedDetail: "\uFF08{endpoint}\uFF09"
|
|
3493
3638
|
},
|
|
@@ -3623,6 +3768,13 @@ var zhCN = {
|
|
|
3623
3768
|
linesBelow: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09",
|
|
3624
3769
|
linesBelowPlural: " \u2193 \u4E0B\u65B9 {count} \u884C\uFF08\u2193/j \u6216 Space/PgDn\uFF09"
|
|
3625
3770
|
},
|
|
3771
|
+
editPicker: {
|
|
3772
|
+
title: "\u7F16\u8F91\u4E4B\u524D\u7684\u6D88\u606F",
|
|
3773
|
+
hint: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u52A0\u8F7D\u5230\u8F93\u5165\u6846 \xB7 Esc \u53D6\u6D88",
|
|
3774
|
+
empty: "\u8FD8\u6CA1\u6709\u7528\u6237\u53D1\u8A00 \u2014 \u6CA1\u4EC0\u4E48\u53EF\u4EE5\u7F16\u8F91\u7684",
|
|
3775
|
+
dismiss: "Esc \u5173\u95ED",
|
|
3776
|
+
forked: "\u25B8 \u4ECE\u7B2C #{turn} \u8F6E\u5206\u53C9 \u2014 \u539F\u6587\u5DF2\u586B\u56DE\u8F93\u5165\u6846"
|
|
3777
|
+
},
|
|
3626
3778
|
sessionPicker: {
|
|
3627
3779
|
header: " \u25C8 REASONIX \xB7 \u9009\u62E9\u4F1A\u8BDD ",
|
|
3628
3780
|
title: "\u9009\u62E9\u4F1A\u8BDD \u2014 {workspace}",
|
|
@@ -3756,6 +3908,11 @@ var zhCN = {
|
|
|
3756
3908
|
metasoServerError: "web_search: Metaso \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
|
|
3757
3909
|
metasoParseError: "web_search: Metaso \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
3758
3910
|
metasoApiError: "web_search: Metaso API \u9519\u8BEF\uFF08code {code}: {message}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
3911
|
+
tavilyMissingKey: "web_search: Tavily \u540E\u7AEF\u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E TAVILY_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 ~/.reasonix/config.json \u4E2D\u914D\u7F6E `tavilyApiKey`\uFF1Bhttps://tavily.com \u6BCF\u6708 1000 \u6B21\u514D\u8D39",
|
|
3912
|
+
tavilyUnauthorized: "web_search: Tavily API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 TAVILY_API_KEY\uFF0C\u6216\u5728 https://tavily.com \u83B7\u53D6\u5BC6\u94A5",
|
|
3913
|
+
tavilyRateLimit: "web_search: Tavily \u8BF7\u6C42\u9891\u7387\u9650\u5236\u6216\u6708\u5EA6\u914D\u989D\u7528\u5C3D \u2014 \u7B49\u5F85\u3001\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u5347\u7EA7 Tavily \u8BA1\u5212",
|
|
3914
|
+
tavilyServerError: "web_search: Tavily \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine mojeek \u5207\u6362\u5F15\u64CE",
|
|
3915
|
+
tavilyParseError: "web_search: Tavily \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
3759
3916
|
fetchStatus: "web_fetch {status} for {url} \u2014 try: \u5728\u6D4F\u89C8\u5668\u4E2D\u786E\u8BA4\u8BE5 URL \u80FD\u5426\u8BBF\u95EE\uFF1B\u8BE5\u72B6\u6001\u7801\u8868\u660E\u76EE\u6807\u4E3B\u673A\u8FD4\u56DE\u4E86\u9519\u8BEF\u9875\u9762",
|
|
3760
3917
|
fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: \u7B49\u5F85 10 \u79D2\u540E\u91CD\u8BD5\uFF1B\u76EE\u6807\u4E3B\u673A\u6B63\u5728\u5BF9\u8BE5\u5BA2\u6237\u7AEF\u8FDB\u884C\u9650\u6D41",
|
|
3761
3918
|
fetchForbidden403: "web_fetch 403 for {url} \u2014 try: \u76EE\u6807\u4E3B\u673A\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u8BE5\u9875\u9762\u53EF\u80FD\u9700\u8981\u767B\u5F55\u6216\u5C4F\u853D\u722C\u866B \u2014 \u6539\u7528 web_search \u6458\u8981",
|
|
@@ -3865,7 +4022,7 @@ var zhCN = {
|
|
|
3865
4022
|
slow: "\u7F13\u6162 \xB7 {ms}ms",
|
|
3866
4023
|
verySlow: "\u975E\u5E38\u6162 \xB7 {ms}ms",
|
|
3867
4024
|
slowToast: "\u26A0 MCP `{name}` \u54CD\u5E94\u7F13\u6162 \xB7 P95 {seconds}s \xB7 \u6700\u8FD1 {sampleSize} \u6B21\u8C03\u7528",
|
|
3868
|
-
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`"
|
|
4025
|
+
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` \xB7 shell \u547D\u4EE4\u6309\u6B21\u5BA1\u6279\uFF08allow once / allow always / deny\uFF09\uFF0C\u65E0\u5168\u5C40\u653E\u884C"
|
|
3869
4026
|
},
|
|
3870
4027
|
denyContextInput: {
|
|
3871
4028
|
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"
|
|
@@ -3874,7 +4031,8 @@ var zhCN = {
|
|
|
3874
4031
|
scrollAbove: " \u2191 {scroll}/{max} \u884C",
|
|
3875
4032
|
scrollAbovePlural: " \u2191 {scroll}/{max} \u884C",
|
|
3876
4033
|
scrollMore: " \u2014 \u8FD8\u6709 {remaining} \u884C",
|
|
3877
|
-
scrollPgUp: " \xB7 PgUp/\u6EDA\u8F6E
|
|
4034
|
+
scrollPgUp: " \xB7 PgUp/\u6EDA\u8F6E",
|
|
4035
|
+
scrollCopy: " \xB7 /copy \u8FDB\u5165\u590D\u5236\u6A21\u5F0F"
|
|
3878
4036
|
},
|
|
3879
4037
|
slashArgPicker: {
|
|
3880
4038
|
noMatch: '\u6CA1\u6709\u5339\u914D "{partial}"',
|
|
@@ -3932,7 +4090,8 @@ var zhCN = {
|
|
|
3932
4090
|
reconnectDetail: "\u65AD\u5F00\u65E7\u8FDE\u63A5 \xB7 \u91CD\u65B0\u63E1\u624B \xB7 \u5217\u51FA\u5DE5\u5177",
|
|
3933
4091
|
disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
|
|
3934
4092
|
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",
|
|
3935
|
-
failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002"
|
|
4093
|
+
failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002",
|
|
4094
|
+
abortedHint: "\u5DF2\u4E2D\u65AD MCP \u542F\u52A8 \u2014 \u8DF3\u8FC7 {count} \u4E2A\u670D\u52A1\u5668\u3002\u95EE\u9898\u4FEE\u590D\u540E\u7528 /mcp \u91CD\u65B0\u8FDE\u63A5\u3002"
|
|
3936
4095
|
},
|
|
3937
4096
|
checkpointPicker: {
|
|
3938
4097
|
title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
|
|
@@ -4662,10 +4821,13 @@ var ToolRegistry = class {
|
|
|
4662
4821
|
_autoFlatten;
|
|
4663
4822
|
_planMode = false;
|
|
4664
4823
|
_interceptor = null;
|
|
4824
|
+
_interceptors = [];
|
|
4665
4825
|
_auditListener = null;
|
|
4666
4826
|
_resultAugmenter = null;
|
|
4667
4827
|
/** Per-tool fingerprint of the last call that failed schema validation. Cleared by any successful validation for that tool. */
|
|
4668
4828
|
_lastMalformed = /* @__PURE__ */ new Map();
|
|
4829
|
+
/** Per-tool fingerprint of the last host-side interceptor rejection. */
|
|
4830
|
+
_lastInterceptorRejection = /* @__PURE__ */ new Map();
|
|
4669
4831
|
constructor(opts = {}) {
|
|
4670
4832
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
4671
4833
|
}
|
|
@@ -4681,6 +4843,18 @@ var ToolRegistry = class {
|
|
|
4681
4843
|
setToolInterceptor(fn) {
|
|
4682
4844
|
this._interceptor = fn;
|
|
4683
4845
|
}
|
|
4846
|
+
/** Ordered host-side interceptors. They run before the legacy single interceptor. */
|
|
4847
|
+
addToolInterceptor(id, fn) {
|
|
4848
|
+
const normalized = id.trim();
|
|
4849
|
+
if (!normalized) throw new Error("tool interceptor requires a non-empty id");
|
|
4850
|
+
const existing = this._interceptors.findIndex((entry) => entry.id === normalized);
|
|
4851
|
+
if (existing >= 0) this._interceptors.splice(existing, 1);
|
|
4852
|
+
this._interceptors.push({ id: normalized, fn });
|
|
4853
|
+
return () => {
|
|
4854
|
+
const idx = this._interceptors.findIndex((entry) => entry.id === normalized);
|
|
4855
|
+
if (idx >= 0) this._interceptors.splice(idx, 1);
|
|
4856
|
+
};
|
|
4857
|
+
}
|
|
4684
4858
|
setAuditListener(fn) {
|
|
4685
4859
|
this._auditListener = fn;
|
|
4686
4860
|
}
|
|
@@ -4769,16 +4943,27 @@ var ToolRegistry = class {
|
|
|
4769
4943
|
rejectedReason: "plan-mode"
|
|
4770
4944
|
});
|
|
4771
4945
|
}
|
|
4772
|
-
|
|
4946
|
+
const chain = this._interceptor ? [...this._interceptors.map((entry) => entry.fn), this._interceptor] : this._interceptors.map((entry) => entry.fn);
|
|
4947
|
+
for (const interceptor of chain) {
|
|
4773
4948
|
try {
|
|
4774
|
-
const short = await
|
|
4775
|
-
if (typeof short === "string")
|
|
4949
|
+
const short = await interceptor(name, args);
|
|
4950
|
+
if (typeof short === "string") {
|
|
4951
|
+
const guarded = this._noteInterceptorRejection(name, fingerprint, short);
|
|
4952
|
+
return this._augmentResult(name, args, guarded);
|
|
4953
|
+
}
|
|
4776
4954
|
} catch (err) {
|
|
4777
4955
|
return JSON.stringify({
|
|
4778
4956
|
error: `${name}: interceptor failed \u2014 ${err.message}`
|
|
4779
4957
|
});
|
|
4780
4958
|
}
|
|
4781
4959
|
}
|
|
4960
|
+
this._lastInterceptorRejection.delete(name);
|
|
4961
|
+
if (opts.signal?.aborted) {
|
|
4962
|
+
return JSON.stringify({
|
|
4963
|
+
error: `${name}: aborted before dispatch (user interrupt)`,
|
|
4964
|
+
rejectedReason: "aborted"
|
|
4965
|
+
});
|
|
4966
|
+
}
|
|
4782
4967
|
let finalResult;
|
|
4783
4968
|
try {
|
|
4784
4969
|
try {
|
|
@@ -4810,13 +4995,16 @@ var ToolRegistry = class {
|
|
|
4810
4995
|
finalResult = JSON.stringify({ error: `${e.name}: ${e.message}` });
|
|
4811
4996
|
}
|
|
4812
4997
|
}
|
|
4998
|
+
return this._augmentResult(name, args, finalResult);
|
|
4999
|
+
}
|
|
5000
|
+
_augmentResult(name, args, result) {
|
|
4813
5001
|
if (this._resultAugmenter) {
|
|
4814
5002
|
try {
|
|
4815
|
-
return this._resultAugmenter(name, args,
|
|
5003
|
+
return this._resultAugmenter(name, args, result);
|
|
4816
5004
|
} catch {
|
|
4817
5005
|
}
|
|
4818
5006
|
}
|
|
4819
|
-
return
|
|
5007
|
+
return result;
|
|
4820
5008
|
}
|
|
4821
5009
|
/** Records the failed call's fingerprint; on the 2nd consecutive identical malformed call to the same tool, returns a sharper error that tells the model to stop retrying. */
|
|
4822
5010
|
_noteMalformed(name, fingerprint, detail) {
|
|
@@ -4830,7 +5018,35 @@ var ToolRegistry = class {
|
|
|
4830
5018
|
}
|
|
4831
5019
|
return JSON.stringify({ error: `${name}: ${detail}` });
|
|
4832
5020
|
}
|
|
5021
|
+
_noteInterceptorRejection(name, fingerprint, result) {
|
|
5022
|
+
const reason = rejectedReason(result);
|
|
5023
|
+
if (!reason) {
|
|
5024
|
+
this._lastInterceptorRejection.delete(name);
|
|
5025
|
+
return result;
|
|
5026
|
+
}
|
|
5027
|
+
const key = `${reason}:${fingerprint}`;
|
|
5028
|
+
const prev = this._lastInterceptorRejection.get(name);
|
|
5029
|
+
this._lastInterceptorRejection.set(name, key);
|
|
5030
|
+
if (prev === key) {
|
|
5031
|
+
return JSON.stringify({
|
|
5032
|
+
error: `${name}: same call was just rejected by ${reason} \u2014 do not retry identical args. Switch to read-only exploration, submit or revise the plan, or choose a different tool call.`,
|
|
5033
|
+
rejectedReason: reason,
|
|
5034
|
+
consecutiveInterceptorRejection: true
|
|
5035
|
+
});
|
|
5036
|
+
}
|
|
5037
|
+
return result;
|
|
5038
|
+
}
|
|
4833
5039
|
};
|
|
5040
|
+
function rejectedReason(result) {
|
|
5041
|
+
try {
|
|
5042
|
+
const parsed = JSON.parse(result);
|
|
5043
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
5044
|
+
const reason = parsed.rejectedReason;
|
|
5045
|
+
return typeof reason === "string" && reason ? reason : null;
|
|
5046
|
+
} catch {
|
|
5047
|
+
return null;
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
4834
5050
|
function isReadOnlyCall(tool, args) {
|
|
4835
5051
|
if (tool.readOnlyCheck) {
|
|
4836
5052
|
try {
|
|
@@ -5011,11 +5227,42 @@ async function bridgeMcpTools(client, opts = {}) {
|
|
|
5011
5227
|
return { ...result, env };
|
|
5012
5228
|
}
|
|
5013
5229
|
function flattenMcpResult(result, opts = {}) {
|
|
5230
|
+
validateResultShape(result);
|
|
5014
5231
|
const parts = result.content.map(blockToString);
|
|
5015
5232
|
const joined = parts.join("\n").trim();
|
|
5016
5233
|
const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
|
|
5017
5234
|
return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
|
|
5018
5235
|
}
|
|
5236
|
+
function validateResultShape(result) {
|
|
5237
|
+
if (typeof result !== "object" || !result)
|
|
5238
|
+
throw new Error(`MCP server returned non-object result: ${typeof result}`);
|
|
5239
|
+
const { content, isError: _isError } = result;
|
|
5240
|
+
if (!Array.isArray(content))
|
|
5241
|
+
throw new Error(`MCP server returned result with non-array content: ${typeof content}`);
|
|
5242
|
+
for (let i = 0; i < content.length; i++) {
|
|
5243
|
+
const block = content[i];
|
|
5244
|
+
if (typeof block !== "object" || !block)
|
|
5245
|
+
throw new Error(`MCP server returned result.content[${i}] is not an object`);
|
|
5246
|
+
if (block.type !== "text" && block.type !== "image")
|
|
5247
|
+
throw new Error(
|
|
5248
|
+
`MCP server returned result.content[${i}] with unknown type ${JSON.stringify(block.type)}`
|
|
5249
|
+
);
|
|
5250
|
+
if (block.type === "text" && typeof block.text !== "string")
|
|
5251
|
+
throw new Error(
|
|
5252
|
+
`MCP server returned result.content[${i}] with non-string text (${typeof block.text})`
|
|
5253
|
+
);
|
|
5254
|
+
if (block.type === "image") {
|
|
5255
|
+
if (typeof block.data !== "string")
|
|
5256
|
+
throw new Error(
|
|
5257
|
+
`MCP server returned result.content[${i}] with non-string data (${typeof block.data})`
|
|
5258
|
+
);
|
|
5259
|
+
if (typeof block.mimeType !== "string")
|
|
5260
|
+
throw new Error(
|
|
5261
|
+
`MCP server returned result.content[${i}] with non-string mimeType (${typeof block.mimeType})`
|
|
5262
|
+
);
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5019
5266
|
function truncateForModel(s, maxChars) {
|
|
5020
5267
|
if (s.length <= maxChars) return s;
|
|
5021
5268
|
const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
|
|
@@ -5198,31 +5445,38 @@ function appendSessionMessage(name, message) {
|
|
|
5198
5445
|
} catch {
|
|
5199
5446
|
}
|
|
5200
5447
|
}
|
|
5201
|
-
function listSessions() {
|
|
5448
|
+
function listSessions(opts) {
|
|
5202
5449
|
const dir = sessionsDir();
|
|
5203
5450
|
if (!existsSync3(dir)) return [];
|
|
5451
|
+
const want = opts?.workspaceFilter ? normalizeWorkspace(opts.workspaceFilter) : null;
|
|
5204
5452
|
try {
|
|
5205
5453
|
const files = readdirSync(dir).filter(
|
|
5206
5454
|
(f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl")
|
|
5207
5455
|
);
|
|
5208
|
-
return files.
|
|
5456
|
+
return files.flatMap((file) => {
|
|
5209
5457
|
const path2 = join4(dir, file);
|
|
5210
|
-
const stat2 = statSync(path2);
|
|
5211
5458
|
const name = file.replace(/\.jsonl$/, "");
|
|
5459
|
+
const meta = loadSessionMeta(name);
|
|
5460
|
+
if (want !== null) {
|
|
5461
|
+
if (typeof meta.workspace !== "string") return [];
|
|
5462
|
+
if (normalizeWorkspace(meta.workspace) !== want) return [];
|
|
5463
|
+
}
|
|
5464
|
+
const stat2 = statSync(path2);
|
|
5212
5465
|
const messageCount = countLines(path2);
|
|
5213
|
-
return {
|
|
5214
|
-
name,
|
|
5215
|
-
path: path2,
|
|
5216
|
-
size: stat2.size,
|
|
5217
|
-
messageCount,
|
|
5218
|
-
mtime: stat2.mtime,
|
|
5219
|
-
meta: loadSessionMeta(name)
|
|
5220
|
-
};
|
|
5466
|
+
return [{ name, path: path2, size: stat2.size, messageCount, mtime: stat2.mtime, meta }];
|
|
5221
5467
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
5222
5468
|
} catch {
|
|
5223
5469
|
return [];
|
|
5224
5470
|
}
|
|
5225
5471
|
}
|
|
5472
|
+
function normalizeWorkspace(p, platform = process.platform) {
|
|
5473
|
+
if (typeof p !== "string" || p.length === 0) return "";
|
|
5474
|
+
if (platform === "win32") {
|
|
5475
|
+
const resolved = win32Path.resolve(p);
|
|
5476
|
+
return resolved.replace(/\\/g, "/").replace(/^([A-Z]):/i, (_, d) => `${d.toLowerCase()}:`);
|
|
5477
|
+
}
|
|
5478
|
+
return posixPath.resolve(p);
|
|
5479
|
+
}
|
|
5226
5480
|
function metaPath(name) {
|
|
5227
5481
|
return join4(sessionsDir(), `${sanitizeName(name)}.meta.json`);
|
|
5228
5482
|
}
|
|
@@ -5312,8 +5566,13 @@ function archiveSession(name) {
|
|
|
5312
5566
|
}
|
|
5313
5567
|
function countLines(path2) {
|
|
5314
5568
|
try {
|
|
5315
|
-
const
|
|
5316
|
-
|
|
5569
|
+
const buf = readFileSync4(path2);
|
|
5570
|
+
let count = 0;
|
|
5571
|
+
for (let i = 0; i < buf.length; i++) {
|
|
5572
|
+
if (buf[i] === 10) count++;
|
|
5573
|
+
}
|
|
5574
|
+
if (buf.length > 0 && buf[buf.length - 1] !== 10) count++;
|
|
5575
|
+
return count;
|
|
5317
5576
|
} catch {
|
|
5318
5577
|
return 0;
|
|
5319
5578
|
}
|
|
@@ -6834,6 +7093,32 @@ ${reason}`
|
|
|
6834
7093
|
}
|
|
6835
7094
|
return userText;
|
|
6836
7095
|
}
|
|
7096
|
+
/** Rewind to the N-th user turn (0-indexed). Drops that turn + everything after. */
|
|
7097
|
+
rewindToUserTurn(userTurnIndex) {
|
|
7098
|
+
const entries = this.log.entries;
|
|
7099
|
+
let count = 0;
|
|
7100
|
+
let targetIdx = -1;
|
|
7101
|
+
for (let i = 0; i < entries.length; i++) {
|
|
7102
|
+
if (entries[i].role !== "user") continue;
|
|
7103
|
+
if (count === userTurnIndex) {
|
|
7104
|
+
targetIdx = i;
|
|
7105
|
+
break;
|
|
7106
|
+
}
|
|
7107
|
+
count++;
|
|
7108
|
+
}
|
|
7109
|
+
if (targetIdx < 0) return null;
|
|
7110
|
+
const raw = entries[targetIdx].content;
|
|
7111
|
+
const userText = typeof raw === "string" ? raw : "";
|
|
7112
|
+
const preserved = entries.slice(0, targetIdx).map((m) => ({ ...m }));
|
|
7113
|
+
this.log.compactInPlace(preserved);
|
|
7114
|
+
if (this.sessionName) {
|
|
7115
|
+
try {
|
|
7116
|
+
rewriteSession(this.sessionName, preserved);
|
|
7117
|
+
} catch {
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
return userText;
|
|
7121
|
+
}
|
|
6837
7122
|
async *step(userInput) {
|
|
6838
7123
|
this._steerConsumed = false;
|
|
6839
7124
|
if (this.budgetUsd !== null) {
|
|
@@ -8001,10 +8286,15 @@ var SkillStore = class {
|
|
|
8001
8286
|
dir: join7(this.projectRoot, ".agents", SKILLS_DIRNAME),
|
|
8002
8287
|
scope: "project"
|
|
8003
8288
|
});
|
|
8289
|
+
out.push({
|
|
8290
|
+
dir: join7(this.projectRoot, ".claude", SKILLS_DIRNAME),
|
|
8291
|
+
scope: "project"
|
|
8292
|
+
});
|
|
8004
8293
|
}
|
|
8005
8294
|
for (const dir of this.customSkillPaths) out.push({ dir, scope: "custom" });
|
|
8006
8295
|
out.push({ dir: join7(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
|
|
8007
8296
|
out.push({ dir: join7(this.homeDir, ".agents", SKILLS_DIRNAME), scope: "global" });
|
|
8297
|
+
out.push({ dir: join7(this.homeDir, ".claude", SKILLS_DIRNAME), scope: "global" });
|
|
8008
8298
|
return out.map((root, priority) => ({ ...root, priority, status: skillPathStatus(root.dir) }));
|
|
8009
8299
|
}
|
|
8010
8300
|
customRoots() {
|
|
@@ -8107,14 +8397,20 @@ var SkillStore = class {
|
|
|
8107
8397
|
}
|
|
8108
8398
|
const { data, body } = parseFrontmatter(raw);
|
|
8109
8399
|
const name = data.name && isValidSkillName(data.name) ? data.name : stem;
|
|
8400
|
+
const description = (data.description ?? "").trim();
|
|
8401
|
+
if (!description) {
|
|
8402
|
+
console.warn(
|
|
8403
|
+
`[skills] "${name}" at ${path2} has no description: \u2014 it will be loaded but won't appear in the skills index.`
|
|
8404
|
+
);
|
|
8405
|
+
}
|
|
8110
8406
|
return {
|
|
8111
8407
|
name,
|
|
8112
|
-
description
|
|
8408
|
+
description,
|
|
8113
8409
|
body: body.trim(),
|
|
8114
8410
|
scope,
|
|
8115
8411
|
path: path2,
|
|
8116
8412
|
allowedTools: parseAllowedTools(data["allowed-tools"]),
|
|
8117
|
-
runAs: parseRunAs(data.runAs),
|
|
8413
|
+
runAs: parseRunAs(data.runAs, data.context, data.agent),
|
|
8118
8414
|
model: data.model?.startsWith("deepseek-") ? data.model : void 0
|
|
8119
8415
|
};
|
|
8120
8416
|
}
|
|
@@ -8147,8 +8443,11 @@ function skillPathStatus(dir) {
|
|
|
8147
8443
|
return "unreadable";
|
|
8148
8444
|
}
|
|
8149
8445
|
}
|
|
8150
|
-
function parseRunAs(raw) {
|
|
8151
|
-
|
|
8446
|
+
function parseRunAs(raw, context, agent) {
|
|
8447
|
+
if (raw?.trim() === "subagent") return "subagent";
|
|
8448
|
+
if (context?.trim().toLowerCase() === "fork") return "subagent";
|
|
8449
|
+
if (agent?.trim()) return "subagent";
|
|
8450
|
+
return "inline";
|
|
8152
8451
|
}
|
|
8153
8452
|
function skillStubBody(name) {
|
|
8154
8453
|
return `---
|
|
@@ -8710,13 +9009,13 @@ import picomatch3 from "picomatch";
|
|
|
8710
9009
|
// src/memory/subdir.ts
|
|
8711
9010
|
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
8712
9011
|
import { dirname as dirname5, join as join9, relative as relative2, resolve as resolve5 } from "path";
|
|
8713
|
-
function
|
|
9012
|
+
function findDirMemory(absDir, rootDir) {
|
|
8714
9013
|
const root = resolve5(rootDir);
|
|
8715
|
-
const target = resolve5(
|
|
9014
|
+
const target = resolve5(absDir);
|
|
8716
9015
|
const rel = relative2(root, target);
|
|
8717
|
-
if (
|
|
9016
|
+
if (rel.startsWith("..")) return [];
|
|
8718
9017
|
const found = [];
|
|
8719
|
-
let cur =
|
|
9018
|
+
let cur = target;
|
|
8720
9019
|
while (cur !== root) {
|
|
8721
9020
|
const r = relative2(root, cur);
|
|
8722
9021
|
if (!r || r.startsWith("..")) break;
|
|
@@ -8733,6 +9032,9 @@ function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
|
8733
9032
|
}
|
|
8734
9033
|
return found;
|
|
8735
9034
|
}
|
|
9035
|
+
function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
9036
|
+
return findDirMemory(dirname5(resolve5(absPath)), rootDir);
|
|
9037
|
+
}
|
|
8736
9038
|
function readSubdirMemoryContent(path2) {
|
|
8737
9039
|
let raw;
|
|
8738
9040
|
try {
|
|
@@ -8820,7 +9122,7 @@ async function applyMultiEdit(rootDir, edits) {
|
|
|
8820
9122
|
);
|
|
8821
9123
|
}
|
|
8822
9124
|
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
8823
|
-
state = { buf: before, le, hunks: [], deltaChars: 0, touched: 0 };
|
|
9125
|
+
state = { before, buf: before, le, hunks: [], deltaChars: 0, touched: 0 };
|
|
8824
9126
|
filesByPath.set(e.abs, state);
|
|
8825
9127
|
}
|
|
8826
9128
|
const adaptedSearch = e.search.replace(/\r?\n/g, state.le);
|
|
@@ -8828,7 +9130,7 @@ async function applyMultiEdit(rootDir, edits) {
|
|
|
8828
9130
|
const firstIdx = state.buf.indexOf(adaptedSearch);
|
|
8829
9131
|
if (firstIdx < 0) {
|
|
8830
9132
|
throw new Error(
|
|
8831
|
-
`multi_edit: edit #${i + 1} search text not found in ${rel} \u2014 no edits applied
|
|
9133
|
+
`multi_edit: edit #${i + 1} search text not found in ${rel} \u2014 no edits applied`
|
|
8832
9134
|
);
|
|
8833
9135
|
}
|
|
8834
9136
|
const nextIdx = state.buf.indexOf(adaptedSearch, firstIdx + 1);
|
|
@@ -8844,8 +9146,29 @@ ${renderEditDiff(adaptedSearch, adaptedReplace, startLine)}`);
|
|
|
8844
9146
|
state.deltaChars += adaptedReplace.length - adaptedSearch.length;
|
|
8845
9147
|
state.touched++;
|
|
8846
9148
|
}
|
|
8847
|
-
|
|
8848
|
-
|
|
9149
|
+
const attempted = [];
|
|
9150
|
+
try {
|
|
9151
|
+
for (const [abs, state] of filesByPath) {
|
|
9152
|
+
attempted.push({ abs, before: state.before });
|
|
9153
|
+
await fs.writeFile(abs, state.buf, "utf8");
|
|
9154
|
+
}
|
|
9155
|
+
} catch (writeErr) {
|
|
9156
|
+
const rollbackFailures = [];
|
|
9157
|
+
for (const item of [...attempted].reverse()) {
|
|
9158
|
+
try {
|
|
9159
|
+
await fs.writeFile(item.abs, item.before, "utf8");
|
|
9160
|
+
} catch (restoreErr) {
|
|
9161
|
+
rollbackFailures.push(`${displayRel(rootDir, item.abs)}: ${restoreErr.message}`);
|
|
9162
|
+
}
|
|
9163
|
+
}
|
|
9164
|
+
if (rollbackFailures.length > 0) {
|
|
9165
|
+
throw new Error(
|
|
9166
|
+
`multi_edit: write failed after partial application: ${writeErr.message}; rollback failed for ${rollbackFailures.join("; ")}`
|
|
9167
|
+
);
|
|
9168
|
+
}
|
|
9169
|
+
throw new Error(
|
|
9170
|
+
`multi_edit: write failed: ${writeErr.message}; rolled back all files that may have been modified`
|
|
9171
|
+
);
|
|
8849
9172
|
}
|
|
8850
9173
|
const fileCount = filesByPath.size;
|
|
8851
9174
|
const editCount = edits.length;
|
|
@@ -9157,6 +9480,129 @@ function formatOutline(entries) {
|
|
|
9157
9480
|
// src/tools/fs/search.ts
|
|
9158
9481
|
import { promises as fs3 } from "fs";
|
|
9159
9482
|
import * as pathMod4 from "path";
|
|
9483
|
+
|
|
9484
|
+
// src/tools/fs/regex-runner.ts
|
|
9485
|
+
import { Worker } from "worker_threads";
|
|
9486
|
+
var WORKER_SOURCE = `
|
|
9487
|
+
const { parentPort } = require("node:worker_threads");
|
|
9488
|
+
parentPort.on("message", (msg) => {
|
|
9489
|
+
const { id, text, source, flags } = msg;
|
|
9490
|
+
let re;
|
|
9491
|
+
try {
|
|
9492
|
+
re = new RegExp(source, flags);
|
|
9493
|
+
} catch (err) {
|
|
9494
|
+
parentPort.postMessage({ id, error: (err && err.message) ? err.message : String(err) });
|
|
9495
|
+
return;
|
|
9496
|
+
}
|
|
9497
|
+
const lines = text.split(/\\r?\\n/);
|
|
9498
|
+
const hits = [];
|
|
9499
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9500
|
+
if (re.test(lines[i])) hits.push(i);
|
|
9501
|
+
}
|
|
9502
|
+
parentPort.postMessage({ id, hits });
|
|
9503
|
+
});
|
|
9504
|
+
`;
|
|
9505
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
9506
|
+
var RegexRunner = class {
|
|
9507
|
+
worker = null;
|
|
9508
|
+
pending = /* @__PURE__ */ new Map();
|
|
9509
|
+
nextId = 1;
|
|
9510
|
+
defaultTimeoutMs;
|
|
9511
|
+
constructor(opts = {}) {
|
|
9512
|
+
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
9513
|
+
}
|
|
9514
|
+
testLines(text, source, flags, opts = {}) {
|
|
9515
|
+
return new Promise((resolve13, reject) => {
|
|
9516
|
+
if (opts.signal?.aborted) {
|
|
9517
|
+
reject(new Error("regex evaluation aborted"));
|
|
9518
|
+
return;
|
|
9519
|
+
}
|
|
9520
|
+
if (!this.worker) this.worker = this.spawn();
|
|
9521
|
+
const id = this.nextId++;
|
|
9522
|
+
const timeoutMs = opts.timeoutMs ?? this.defaultTimeoutMs;
|
|
9523
|
+
const timer = setTimeout(() => {
|
|
9524
|
+
this.pending.delete(id);
|
|
9525
|
+
this.killWorker();
|
|
9526
|
+
reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
|
|
9527
|
+
}, timeoutMs);
|
|
9528
|
+
const entry = { resolve: resolve13, reject, timer };
|
|
9529
|
+
if (opts.signal) {
|
|
9530
|
+
entry.signal = opts.signal;
|
|
9531
|
+
entry.onAbort = () => {
|
|
9532
|
+
this.pending.delete(id);
|
|
9533
|
+
clearTimeout(timer);
|
|
9534
|
+
this.killWorker();
|
|
9535
|
+
reject(new Error("regex evaluation aborted"));
|
|
9536
|
+
};
|
|
9537
|
+
opts.signal.addEventListener("abort", entry.onAbort, { once: true });
|
|
9538
|
+
}
|
|
9539
|
+
this.pending.set(id, entry);
|
|
9540
|
+
this.worker.postMessage({ id, text, source, flags });
|
|
9541
|
+
});
|
|
9542
|
+
}
|
|
9543
|
+
async shutdown() {
|
|
9544
|
+
if (this.worker) {
|
|
9545
|
+
const w = this.worker;
|
|
9546
|
+
this.worker = null;
|
|
9547
|
+
await w.terminate();
|
|
9548
|
+
}
|
|
9549
|
+
for (const entry of this.pending.values()) {
|
|
9550
|
+
clearTimeout(entry.timer);
|
|
9551
|
+
if (entry.onAbort && entry.signal) {
|
|
9552
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
9553
|
+
}
|
|
9554
|
+
entry.reject(new Error("regex runner shut down"));
|
|
9555
|
+
}
|
|
9556
|
+
this.pending.clear();
|
|
9557
|
+
}
|
|
9558
|
+
spawn() {
|
|
9559
|
+
const w = new Worker(WORKER_SOURCE, { eval: true });
|
|
9560
|
+
w.on("message", (msg) => {
|
|
9561
|
+
const entry = this.pending.get(msg.id);
|
|
9562
|
+
if (!entry) return;
|
|
9563
|
+
clearTimeout(entry.timer);
|
|
9564
|
+
if (entry.onAbort && entry.signal) {
|
|
9565
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
9566
|
+
}
|
|
9567
|
+
this.pending.delete(msg.id);
|
|
9568
|
+
if (msg.error !== void 0) entry.reject(new Error(msg.error));
|
|
9569
|
+
else entry.resolve(msg.hits ?? []);
|
|
9570
|
+
});
|
|
9571
|
+
w.on("error", (err) => {
|
|
9572
|
+
if (this.worker !== w) return;
|
|
9573
|
+
this.failPending(err);
|
|
9574
|
+
});
|
|
9575
|
+
w.on("exit", () => {
|
|
9576
|
+
if (this.worker !== w) return;
|
|
9577
|
+
this.worker = null;
|
|
9578
|
+
if (this.pending.size > 0) this.failPending(new Error("regex worker exited"));
|
|
9579
|
+
});
|
|
9580
|
+
return w;
|
|
9581
|
+
}
|
|
9582
|
+
killWorker() {
|
|
9583
|
+
if (!this.worker) return;
|
|
9584
|
+
const w = this.worker;
|
|
9585
|
+
this.worker = null;
|
|
9586
|
+
void w.terminate();
|
|
9587
|
+
}
|
|
9588
|
+
failPending(err) {
|
|
9589
|
+
for (const entry of this.pending.values()) {
|
|
9590
|
+
clearTimeout(entry.timer);
|
|
9591
|
+
if (entry.onAbort && entry.signal) {
|
|
9592
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
9593
|
+
}
|
|
9594
|
+
entry.reject(err);
|
|
9595
|
+
}
|
|
9596
|
+
this.pending.clear();
|
|
9597
|
+
}
|
|
9598
|
+
};
|
|
9599
|
+
var _runner = null;
|
|
9600
|
+
function getRegexRunner() {
|
|
9601
|
+
if (!_runner) _runner = new RegexRunner();
|
|
9602
|
+
return _runner;
|
|
9603
|
+
}
|
|
9604
|
+
|
|
9605
|
+
// src/tools/fs/search.ts
|
|
9160
9606
|
function throwIfAborted(signal) {
|
|
9161
9607
|
if (!signal?.aborted) return;
|
|
9162
9608
|
throw new DOMException("search aborted by user", "AbortError");
|
|
@@ -9209,17 +9655,20 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
9209
9655
|
}
|
|
9210
9656
|
var MAX_HITS_PER_FILE = 30;
|
|
9211
9657
|
var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
|
|
9658
|
+
var WALK_DEADLINE_MS = 12e4;
|
|
9212
9659
|
async function searchContent(ctx, startAbs, args) {
|
|
9213
9660
|
throwIfAborted(args.signal);
|
|
9214
9661
|
const caseSensitive = args.case_sensitive === true;
|
|
9215
9662
|
const includeDeps = args.include_deps === true;
|
|
9216
9663
|
const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
|
|
9217
9664
|
const summaryOnly = args.summary_only === true;
|
|
9218
|
-
|
|
9665
|
+
const reFlags = caseSensitive ? "" : "i";
|
|
9666
|
+
let reSource = null;
|
|
9219
9667
|
try {
|
|
9220
|
-
|
|
9668
|
+
new RegExp(args.pattern, reFlags);
|
|
9669
|
+
reSource = args.pattern;
|
|
9221
9670
|
} catch {
|
|
9222
|
-
|
|
9671
|
+
reSource = null;
|
|
9223
9672
|
}
|
|
9224
9673
|
const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
|
|
9225
9674
|
const matches = [];
|
|
@@ -9229,6 +9678,15 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9229
9678
|
let summaryMode = summaryOnly;
|
|
9230
9679
|
let summaryNoticeEmitted = false;
|
|
9231
9680
|
const fileHitCounts = /* @__PURE__ */ new Map();
|
|
9681
|
+
const regexSkippedFiles = [];
|
|
9682
|
+
const t0 = Date.now();
|
|
9683
|
+
const throwIfTimedOut = () => {
|
|
9684
|
+
if (Date.now() - t0 > WALK_DEADLINE_MS) {
|
|
9685
|
+
throw new Error(
|
|
9686
|
+
`search_content exceeded ${WALK_DEADLINE_MS}ms \u2014 narrow the scope (path/glob) or simplify the pattern`
|
|
9687
|
+
);
|
|
9688
|
+
}
|
|
9689
|
+
};
|
|
9232
9690
|
const pushLine = (out) => {
|
|
9233
9691
|
if (totalBytes + out.length + 1 > ctx.maxListBytes) {
|
|
9234
9692
|
matches.push(`[\u2026 truncated at ${ctx.maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
|
|
@@ -9263,6 +9721,7 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9263
9721
|
for (const e of entries) {
|
|
9264
9722
|
if (truncated) return;
|
|
9265
9723
|
throwIfAborted(args.signal);
|
|
9724
|
+
throwIfTimedOut();
|
|
9266
9725
|
if (e.isDirectory()) {
|
|
9267
9726
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
9268
9727
|
await walk2(pathMod4.join(dir, e.name));
|
|
@@ -9299,13 +9758,25 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9299
9758
|
const text = raw.toString("utf8");
|
|
9300
9759
|
const rel = displayRel3(ctx.rootDir, full);
|
|
9301
9760
|
const lines = text.split(/\r?\n/);
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9761
|
+
let hits;
|
|
9762
|
+
if (reSource !== null) {
|
|
9763
|
+
try {
|
|
9764
|
+
hits = await getRegexRunner().testLines(text, reSource, reFlags, {
|
|
9765
|
+
signal: args.signal
|
|
9766
|
+
});
|
|
9767
|
+
} catch (err) {
|
|
9768
|
+
const reason = err.message;
|
|
9769
|
+
if (reason.includes("aborted")) throw err;
|
|
9770
|
+
regexSkippedFiles.push({ rel, reason });
|
|
9771
|
+
continue;
|
|
9772
|
+
}
|
|
9773
|
+
} else {
|
|
9774
|
+
hits = [];
|
|
9775
|
+
for (let li = 0; li < lines.length; li++) {
|
|
9776
|
+
throwIfAborted(args.signal);
|
|
9777
|
+
const lineForCheck = caseSensitive ? lines[li] : lines[li].toLowerCase();
|
|
9778
|
+
if (lineForCheck.includes(needle)) hits.push(li);
|
|
9779
|
+
}
|
|
9309
9780
|
}
|
|
9310
9781
|
scanned++;
|
|
9311
9782
|
if (hits.length === 0) continue;
|
|
@@ -9354,6 +9825,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9354
9825
|
}
|
|
9355
9826
|
};
|
|
9356
9827
|
await walk2(startAbs);
|
|
9828
|
+
if (regexSkippedFiles.length > 0) {
|
|
9829
|
+
pushLine(
|
|
9830
|
+
`[regex timed out on ${regexSkippedFiles.length} file${regexSkippedFiles.length === 1 ? "" : "s"} \u2014 pattern may have catastrophic backtracking; first: ${regexSkippedFiles[0].rel}]`
|
|
9831
|
+
);
|
|
9832
|
+
}
|
|
9357
9833
|
if (matches.length === 0) {
|
|
9358
9834
|
return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
|
|
9359
9835
|
}
|
|
@@ -9361,7 +9837,7 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9361
9837
|
}
|
|
9362
9838
|
|
|
9363
9839
|
// src/tools/filesystem.ts
|
|
9364
|
-
var DEFAULT_OUTLINE_THRESHOLD_BYTES =
|
|
9840
|
+
var DEFAULT_OUTLINE_THRESHOLD_BYTES = 64 * 1024;
|
|
9365
9841
|
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
9366
9842
|
var HARD_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
9367
9843
|
var OUTLINE_HEAD_LINES = 80;
|
|
@@ -9418,11 +9894,15 @@ function registerFilesystemTools(registry, opts) {
|
|
|
9418
9894
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
9419
9895
|
const shownSubdirMemory = /* @__PURE__ */ new Set();
|
|
9420
9896
|
function withSubdirMemory(absPath, body) {
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9897
|
+
return prependMemorySections(findSubdirMemoryAncestors(absPath, rootDir), body);
|
|
9898
|
+
}
|
|
9899
|
+
function withDirMemory(absDir, body) {
|
|
9900
|
+
return prependMemorySections(findDirMemory(absDir, rootDir), body);
|
|
9901
|
+
}
|
|
9902
|
+
function prependMemorySections(memPaths, body) {
|
|
9903
|
+
if (!memoryEnabled() || memPaths.length === 0) return body;
|
|
9424
9904
|
const sections = [];
|
|
9425
|
-
for (const memPath of [...
|
|
9905
|
+
for (const memPath of [...memPaths].reverse()) {
|
|
9426
9906
|
if (shownSubdirMemory.has(memPath)) continue;
|
|
9427
9907
|
const content = readSubdirMemoryContent(memPath);
|
|
9428
9908
|
if (!content) continue;
|
|
@@ -9499,11 +9979,7 @@ ${body}`;
|
|
|
9499
9979
|
registry.register({
|
|
9500
9980
|
name: "read_file",
|
|
9501
9981
|
parallelSafe: true,
|
|
9502
|
-
description: `Read a file under the sandbox root. Default
|
|
9503
|
-
- head: N \u2192 first N lines (cheap probe of imports / config head)
|
|
9504
|
-
- tail: N \u2192 last N lines (recent-tail of a log)
|
|
9505
|
-
- range: "A-B" \u2192 inclusive 1-indexed range (e.g. "120-180" around an edit site)
|
|
9506
|
-
Files OVER the threshold auto-switch to outline mode: file metadata + first ${OUTLINE_HEAD_LINES} lines + a top-level symbol outline (TS/JS exports, Python def/class, Go func/type, Rust fn/struct/impl/trait, Markdown headings, Protobuf message/service/rpc, plain-text chapter markers) + concrete next-step commands. No middle bytes \u2014 drill in with range / search_content. Files over ${Math.round(HARD_MAX_FILE_BYTES / (1024 * 1024))} MiB are refused entirely (use grep / range). Binary files are refused \u2014 use get_file_info if you only need stat.`,
|
|
9982
|
+
description: `Read a file under the sandbox root. Default returns FULL CONTENT for files \u2264 ${Math.round(DEFAULT_OUTLINE_THRESHOLD_BYTES / 1024)} KiB. Optional scoping: head/tail (N lines), range "A-B" (1-indexed inclusive). Larger files auto-switch to outline mode (metadata + head + symbol outline for TS/JS/Python/Go/Rust/Markdown/Protobuf/text) \u2014 drill in with range or search_content. Files over ${Math.round(HARD_MAX_FILE_BYTES / (1024 * 1024))} MiB and binaries are refused \u2014 use get_file_info for stat.`,
|
|
9507
9983
|
readOnly: true,
|
|
9508
9984
|
stormExempt: true,
|
|
9509
9985
|
parameters: {
|
|
@@ -9615,17 +10091,13 @@ ${slice.join("\n")}`);
|
|
|
9615
10091
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
9616
10092
|
lines.push(e.isDirectory() ? `${e.name}/` : e.name);
|
|
9617
10093
|
}
|
|
9618
|
-
return lines.join("\n") || "(empty directory)";
|
|
10094
|
+
return withDirMemory(abs, lines.join("\n") || "(empty directory)");
|
|
9619
10095
|
}
|
|
9620
10096
|
});
|
|
9621
10097
|
registry.register({
|
|
9622
10098
|
name: "directory_tree",
|
|
9623
10099
|
parallelSafe: true,
|
|
9624
|
-
description: `Recursively list entries
|
|
9625
|
-
- maxDepth defaults to 2 (root + one level). A depth-4 tree on a real repo blew ~5K tokens in one call. If you truly need deeper, pass maxDepth:N explicitly.
|
|
9626
|
-
- Skips ${[...SKIP_DIR_NAMES].sort().join(", ")} unless include_deps:true. Traversing into node_modules / .git / dist is almost always token-waste.
|
|
9627
|
-
- Large subtrees (>50 children) auto-collapse to "[N files, M dirs hidden \u2014 list_directory <path> to inspect]" so one huge folder can't dominate the output.
|
|
9628
|
-
Prefer \`list_directory\` for a single-level view, \`search_files\` to find specific paths, and \`search_content\` to find code.`,
|
|
10100
|
+
description: `Recursively list entries with indented tree structure (dirs marked '/'). Budget-aware: maxDepth defaults to 2, large subtrees (>50 children) auto-collapse to "[N hidden \u2014 list_directory to inspect]", and ${[...SKIP_DIR_NAMES].sort().join(" / ")} are skipped unless include_deps:true. For single-level use list_directory; for path lookups use search_files; for code lookups use search_content.`,
|
|
9629
10101
|
readOnly: true,
|
|
9630
10102
|
parameters: {
|
|
9631
10103
|
type: "object",
|
|
@@ -9726,38 +10198,38 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
9726
10198
|
registry.register({
|
|
9727
10199
|
name: "search_content",
|
|
9728
10200
|
parallelSafe: true,
|
|
9729
|
-
description: "Recursively grep file CONTENTS for a substring or regex
|
|
10201
|
+
description: "Recursively grep file CONTENTS for a substring or regex \u2014 'where is X called', 'what files contain Y'. Returns one match per line as `path:line: text`. Per-file hit cap 30; when the byte budget is mostly spent, remaining files switch to a `rel: N matches` histogram. Pass `summary_only:true` for just the histogram. Skips dependency / VCS / build dirs and binary files. For file NAMES use search_files.",
|
|
9730
10202
|
readOnly: true,
|
|
9731
10203
|
parameters: {
|
|
9732
10204
|
type: "object",
|
|
9733
10205
|
properties: {
|
|
9734
10206
|
pattern: {
|
|
9735
10207
|
type: "string",
|
|
9736
|
-
description: "Substring
|
|
10208
|
+
description: "Substring or regex."
|
|
9737
10209
|
},
|
|
9738
10210
|
path: {
|
|
9739
10211
|
type: "string",
|
|
9740
|
-
description: "
|
|
10212
|
+
description: "Search root (default: sandbox root)."
|
|
9741
10213
|
},
|
|
9742
10214
|
glob: {
|
|
9743
10215
|
type: "string",
|
|
9744
|
-
description: "
|
|
10216
|
+
description: "Filename filter. Glob when it contains `*`/`?`/`{`/`[`; otherwise substring. Patterns with `/` match the path relative to the search root."
|
|
9745
10217
|
},
|
|
9746
10218
|
case_sensitive: {
|
|
9747
10219
|
type: "boolean",
|
|
9748
|
-
description: "
|
|
10220
|
+
description: "Default false."
|
|
9749
10221
|
},
|
|
9750
10222
|
include_deps: {
|
|
9751
10223
|
type: "boolean",
|
|
9752
|
-
description: "
|
|
10224
|
+
description: "Also search node_modules / .git / dist / build / etc. Default off."
|
|
9753
10225
|
},
|
|
9754
10226
|
context: {
|
|
9755
10227
|
type: "integer",
|
|
9756
|
-
description: "Lines of context
|
|
10228
|
+
description: "Lines of context around each match (both sides). Default 0, capped 20. Ripgrep-style output."
|
|
9757
10229
|
},
|
|
9758
10230
|
summary_only: {
|
|
9759
10231
|
type: "boolean",
|
|
9760
|
-
description: "
|
|
10232
|
+
description: "Skip line content, return `rel: N matches` per file. Use for 'where does this exist at all' before drilling in."
|
|
9761
10233
|
}
|
|
9762
10234
|
},
|
|
9763
10235
|
required: ["pattern"]
|
|
@@ -9870,7 +10342,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
9870
10342
|
});
|
|
9871
10343
|
registry.register({
|
|
9872
10344
|
name: "multi_edit",
|
|
9873
|
-
description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in
|
|
10345
|
+
description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in one call. Edits validate across the full batch before writing. Validation failures leave all files untouched; disk write failures trigger best-effort rollback of files that may have been modified. Per-file edits run in array order, so a later edit can match text inserted by an earlier one. Same per-edit rules as edit_file: `search` is exact text (whitespace sensitive, no regex) and must be unique in its target file at the moment that edit applies. Use this for renames spanning multiple files, cross-file refactors, or any batch where you'd otherwise loop edit_file.",
|
|
9874
10346
|
parameters: {
|
|
9875
10347
|
type: "object",
|
|
9876
10348
|
properties: {
|
|
@@ -10027,7 +10499,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
10027
10499
|
}
|
|
10028
10500
|
registry.register({
|
|
10029
10501
|
name: "remember",
|
|
10030
|
-
description: "Save a memory for future sessions
|
|
10502
|
+
description: "Save a memory for future sessions \u2014 preferences, corrections, non-obvious project facts. Not for transient task state. Loads into the system prompt on next `/new` or launch.",
|
|
10031
10503
|
parameters: {
|
|
10032
10504
|
type: "object",
|
|
10033
10505
|
properties: {
|
|
@@ -10038,29 +10510,29 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
10038
10510
|
scope: {
|
|
10039
10511
|
type: "string",
|
|
10040
10512
|
enum: ["global", "project"],
|
|
10041
|
-
description: "
|
|
10513
|
+
description: "global = across all projects; project = current sandbox only (needs `reasonix code`)."
|
|
10042
10514
|
},
|
|
10043
10515
|
name: {
|
|
10044
10516
|
type: "string",
|
|
10045
|
-
description: "
|
|
10517
|
+
description: "Filename-safe id, 3-40 chars, alnum + _ - . (no separators, no leading dot)."
|
|
10046
10518
|
},
|
|
10047
10519
|
description: {
|
|
10048
10520
|
type: "string",
|
|
10049
|
-
description: "
|
|
10521
|
+
description: "\u2264150 char one-liner shown in MEMORY.md."
|
|
10050
10522
|
},
|
|
10051
10523
|
content: {
|
|
10052
10524
|
type: "string",
|
|
10053
|
-
description: "
|
|
10525
|
+
description: "Markdown body. For feedback/project, structure as rule + **Why:** + **How to apply:**."
|
|
10054
10526
|
},
|
|
10055
10527
|
priority: {
|
|
10056
10528
|
type: "string",
|
|
10057
10529
|
enum: ["low", "medium", "high"],
|
|
10058
|
-
description: "
|
|
10530
|
+
description: "`high` injects entry into HIGH PRIORITY block \u2014 use sparingly."
|
|
10059
10531
|
},
|
|
10060
10532
|
expires: {
|
|
10061
10533
|
type: "string",
|
|
10062
10534
|
enum: ["project_end"],
|
|
10063
|
-
description: "
|
|
10535
|
+
description: "`project_end` lets /memory clear project remove this even at global scope."
|
|
10064
10536
|
}
|
|
10065
10537
|
},
|
|
10066
10538
|
required: ["type", "scope", "name", "description", "content"]
|
|
@@ -10199,26 +10671,26 @@ function sanitizeOptions(raw) {
|
|
|
10199
10671
|
function registerChoiceTool(registry, opts = {}) {
|
|
10200
10672
|
registry.register({
|
|
10201
10673
|
name: "ask_choice",
|
|
10202
|
-
description: "
|
|
10674
|
+
description: "Render an arrow-key picker with 2\u20136 alternatives. Use when the user is supposed to pick \u2014 never enumerate choices as prose. Skip when one option is clearly best (just do it) or a free-form text answer fits. Max 6 options; set `allowCustom:true` when their real answer might not fit.",
|
|
10203
10675
|
readOnly: true,
|
|
10204
10676
|
parameters: {
|
|
10205
10677
|
type: "object",
|
|
10206
10678
|
properties: {
|
|
10207
10679
|
question: {
|
|
10208
10680
|
type: "string",
|
|
10209
|
-
description: "
|
|
10681
|
+
description: "One-sentence question. Don't repeat the options here \u2014 the picker renders them."
|
|
10210
10682
|
},
|
|
10211
10683
|
options: {
|
|
10212
10684
|
type: "array",
|
|
10213
|
-
description: "2\
|
|
10685
|
+
description: "2\u20136 alternatives. Each: stable id + short title; summary optional.",
|
|
10214
10686
|
items: {
|
|
10215
10687
|
type: "object",
|
|
10216
10688
|
properties: {
|
|
10217
|
-
id: { type: "string", description: "
|
|
10218
|
-
title: { type: "string", description: "One-line
|
|
10689
|
+
id: { type: "string", description: "Stable id (A, B, C or option-1)." },
|
|
10690
|
+
title: { type: "string", description: "One-line label." },
|
|
10219
10691
|
summary: {
|
|
10220
10692
|
type: "string",
|
|
10221
|
-
description: "Optional
|
|
10693
|
+
description: "Optional dimmed second line, \u226480 chars."
|
|
10222
10694
|
}
|
|
10223
10695
|
},
|
|
10224
10696
|
required: ["id", "title"]
|
|
@@ -10226,7 +10698,7 @@ function registerChoiceTool(registry, opts = {}) {
|
|
|
10226
10698
|
},
|
|
10227
10699
|
allowCustom: {
|
|
10228
10700
|
type: "boolean",
|
|
10229
|
-
description: "
|
|
10701
|
+
description: "Shows a 'type my own answer' escape hatch. Default false."
|
|
10230
10702
|
}
|
|
10231
10703
|
},
|
|
10232
10704
|
required: ["question", "options"]
|
|
@@ -10312,19 +10784,33 @@ var PlanRevisionProposedError = class extends Error {
|
|
|
10312
10784
|
};
|
|
10313
10785
|
|
|
10314
10786
|
// src/tools/plan-core.ts
|
|
10315
|
-
var SUBMIT_PLAN_DESCRIPTION = "Submit ONE concrete plan
|
|
10316
|
-
var MARK_STEP_COMPLETE_DESCRIPTION = "Mark one
|
|
10317
|
-
var REVISE_PLAN_DESCRIPTION = "
|
|
10787
|
+
var SUBMIT_PLAN_DESCRIPTION = "Submit ONE concrete plan for review. The user approves / refines / cancels \u2014 write a markdown plan body and (strongly preferred) a structured `steps` array. Use for multi-file refactors, architecture changes, anything expensive to undo. Skip for small fixes. Do NOT use for A/B/C menus \u2014 the picker has no branch selector, so a menu plan strands the user; call `ask_choice` for branching decisions. See the system prompt for fuller guidance.";
|
|
10788
|
+
var MARK_STEP_COMPLETE_DESCRIPTION = "Mark one approved-plan step as done. Call exactly once after finishing each step, before starting the next. After the FINAL step, write a brief reply summarizing what was done and end the turn. Skip if the plan didn't include structured steps.";
|
|
10789
|
+
var REVISE_PLAN_DESCRIPTION = "Replace the REMAINING steps of an in-flight plan when checkpoint feedback warrants a structural change. Pass `reason`, the new `remainingSteps` tail (done steps are untouched \u2014 keep them out), and optional updated `summary`. Don't call submit_plan for revisions \u2014 it resets the whole plan.";
|
|
10318
10790
|
var STEP_ITEM_SCHEMA = {
|
|
10319
10791
|
type: "object",
|
|
10320
10792
|
properties: {
|
|
10321
10793
|
id: { type: "string", description: "Stable id, e.g. step-1." },
|
|
10322
10794
|
title: { type: "string", description: "Short imperative title." },
|
|
10323
|
-
action: { type: "string", description: "One-sentence
|
|
10795
|
+
action: { type: "string", description: "One-sentence concrete action." },
|
|
10324
10796
|
risk: {
|
|
10325
10797
|
type: "string",
|
|
10326
10798
|
enum: ["low", "med", "high"],
|
|
10327
|
-
description: "
|
|
10799
|
+
description: "high = hard-to-undo / prod / API break; med = reversible multi-file; low = safe local. Omit if unsure."
|
|
10800
|
+
},
|
|
10801
|
+
targets: {
|
|
10802
|
+
type: "array",
|
|
10803
|
+
description: "Optional. Files/dirs/modules this step touches.",
|
|
10804
|
+
items: { type: "string" }
|
|
10805
|
+
},
|
|
10806
|
+
acceptance: {
|
|
10807
|
+
type: "string",
|
|
10808
|
+
description: "Optional. One-sentence completion criterion."
|
|
10809
|
+
},
|
|
10810
|
+
verification: {
|
|
10811
|
+
type: "array",
|
|
10812
|
+
description: "Optional. Verification commands/checks for this step.",
|
|
10813
|
+
items: { type: "string" }
|
|
10328
10814
|
}
|
|
10329
10815
|
},
|
|
10330
10816
|
required: ["id", "title", "action"]
|
|
@@ -10346,10 +10832,42 @@ function sanitizeSteps(raw) {
|
|
|
10346
10832
|
const step = { id, title, action };
|
|
10347
10833
|
const risk = sanitizeRisk(e.risk);
|
|
10348
10834
|
if (risk) step.risk = risk;
|
|
10835
|
+
const targets = sanitizeStringList(e.targets);
|
|
10836
|
+
if (targets) step.targets = targets;
|
|
10837
|
+
const acceptance = typeof e.acceptance === "string" ? e.acceptance.trim() : "";
|
|
10838
|
+
if (acceptance) step.acceptance = acceptance;
|
|
10839
|
+
const verification = sanitizeStringList(e.verification);
|
|
10840
|
+
if (verification) step.verification = verification;
|
|
10349
10841
|
steps.push(step);
|
|
10350
10842
|
}
|
|
10351
10843
|
return steps.length > 0 ? steps : void 0;
|
|
10352
10844
|
}
|
|
10845
|
+
function sanitizeStringList(raw) {
|
|
10846
|
+
if (!Array.isArray(raw)) return void 0;
|
|
10847
|
+
const out = raw.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0);
|
|
10848
|
+
return out.length > 0 ? out : void 0;
|
|
10849
|
+
}
|
|
10850
|
+
function sanitizeEvidence(raw) {
|
|
10851
|
+
if (!Array.isArray(raw)) return void 0;
|
|
10852
|
+
const out = [];
|
|
10853
|
+
for (const item of raw) {
|
|
10854
|
+
if (!item || typeof item !== "object") continue;
|
|
10855
|
+
const e = item;
|
|
10856
|
+
const kind = e.kind;
|
|
10857
|
+
if (kind !== "verification" && kind !== "diff" && kind !== "checkpoint" && kind !== "manual") {
|
|
10858
|
+
continue;
|
|
10859
|
+
}
|
|
10860
|
+
const summary = typeof e.summary === "string" ? e.summary.trim() : "";
|
|
10861
|
+
if (!summary) continue;
|
|
10862
|
+
const evidence = { kind, summary };
|
|
10863
|
+
const command = typeof e.command === "string" ? e.command.trim() : "";
|
|
10864
|
+
if (command) evidence.command = command;
|
|
10865
|
+
const paths = sanitizeStringList(e.paths);
|
|
10866
|
+
if (paths) evidence.paths = paths;
|
|
10867
|
+
out.push(evidence);
|
|
10868
|
+
}
|
|
10869
|
+
return out.length > 0 ? out : void 0;
|
|
10870
|
+
}
|
|
10353
10871
|
function registerSubmitPlan(registry, opts) {
|
|
10354
10872
|
registry.register({
|
|
10355
10873
|
name: "submit_plan",
|
|
@@ -10360,16 +10878,16 @@ function registerSubmitPlan(registry, opts) {
|
|
|
10360
10878
|
properties: {
|
|
10361
10879
|
plan: {
|
|
10362
10880
|
type: "string",
|
|
10363
|
-
description: "Markdown
|
|
10881
|
+
description: "Markdown plan: one-line summary, file-by-file breakdown, risks/open questions."
|
|
10364
10882
|
},
|
|
10365
10883
|
steps: {
|
|
10366
10884
|
type: "array",
|
|
10367
|
-
description: "Structured step list
|
|
10885
|
+
description: "Structured step list \u2014 strongly recommended for >1 step. Stable ids (step-1, step-2, ...).",
|
|
10368
10886
|
items: STEP_ITEM_SCHEMA
|
|
10369
10887
|
},
|
|
10370
10888
|
summary: {
|
|
10371
10889
|
type: "string",
|
|
10372
|
-
description: "Optional
|
|
10890
|
+
description: "Optional ~80-char plan title for the picker header and /plans listings."
|
|
10373
10891
|
}
|
|
10374
10892
|
},
|
|
10375
10893
|
required: ["plan"]
|
|
@@ -10407,19 +10925,33 @@ function registerMarkStepComplete(registry, opts) {
|
|
|
10407
10925
|
properties: {
|
|
10408
10926
|
stepId: {
|
|
10409
10927
|
type: "string",
|
|
10410
|
-
description: "
|
|
10928
|
+
description: "Step id from submit_plan's steps array."
|
|
10411
10929
|
},
|
|
10412
10930
|
title: {
|
|
10413
10931
|
type: "string",
|
|
10414
|
-
description: "Optional.
|
|
10932
|
+
description: "Optional. Echoed for the UI; falls back to id."
|
|
10415
10933
|
},
|
|
10416
10934
|
result: {
|
|
10417
10935
|
type: "string",
|
|
10418
|
-
description: "One-sentence summary of what was done
|
|
10936
|
+
description: "One-sentence summary of what was done."
|
|
10419
10937
|
},
|
|
10420
10938
|
notes: {
|
|
10421
10939
|
type: "string",
|
|
10422
|
-
description: "Optional.
|
|
10940
|
+
description: "Optional. Surprises \u2014 blockers, revised assumptions, follow-ups."
|
|
10941
|
+
},
|
|
10942
|
+
evidence: {
|
|
10943
|
+
type: "array",
|
|
10944
|
+
description: "Optional. Verification summary / diff / checkpoint ref / manual note.",
|
|
10945
|
+
items: {
|
|
10946
|
+
type: "object",
|
|
10947
|
+
properties: {
|
|
10948
|
+
kind: { type: "string", enum: ["verification", "diff", "checkpoint", "manual"] },
|
|
10949
|
+
summary: { type: "string" },
|
|
10950
|
+
command: { type: "string" },
|
|
10951
|
+
paths: { type: "array", items: { type: "string" } }
|
|
10952
|
+
},
|
|
10953
|
+
required: ["kind", "summary"]
|
|
10954
|
+
}
|
|
10423
10955
|
}
|
|
10424
10956
|
},
|
|
10425
10957
|
required: ["stepId", "result"]
|
|
@@ -10437,9 +10969,15 @@ function registerMarkStepComplete(registry, opts) {
|
|
|
10437
10969
|
}
|
|
10438
10970
|
const title = typeof args?.title === "string" ? args.title.trim() || void 0 : void 0;
|
|
10439
10971
|
const notes = typeof args?.notes === "string" ? args.notes.trim() || void 0 : void 0;
|
|
10972
|
+
const evidence = sanitizeEvidence(args?.evidence);
|
|
10973
|
+
const evidenceReason = opts.requireStepEvidence?.({ stepId, title });
|
|
10974
|
+
if (evidenceReason && (!evidence || evidence.length === 0)) {
|
|
10975
|
+
throw new Error(`mark_step_complete: evidence required \u2014 ${evidenceReason}`);
|
|
10976
|
+
}
|
|
10440
10977
|
const update = { kind: "step_completed", stepId, result };
|
|
10441
10978
|
if (title) update.title = title;
|
|
10442
10979
|
if (notes) update.notes = notes;
|
|
10980
|
+
if (evidence) update.evidence = evidence;
|
|
10443
10981
|
opts.onStepCompleted?.(update);
|
|
10444
10982
|
const verdict = await (ctx?.confirmationGate ?? pauseGate).ask({
|
|
10445
10983
|
kind: "plan_checkpoint",
|
|
@@ -10464,16 +11002,16 @@ function registerRevisePlan(registry, opts) {
|
|
|
10464
11002
|
properties: {
|
|
10465
11003
|
reason: {
|
|
10466
11004
|
type: "string",
|
|
10467
|
-
description: "One sentence
|
|
11005
|
+
description: "One sentence \u2014 why you're revising / what the user asked for."
|
|
10468
11006
|
},
|
|
10469
11007
|
remainingSteps: {
|
|
10470
11008
|
type: "array",
|
|
10471
|
-
description: "
|
|
11009
|
+
description: "New tail of the plan. Reuse old ids when adjusting; new ids for new steps.",
|
|
10472
11010
|
items: STEP_ITEM_SCHEMA
|
|
10473
11011
|
},
|
|
10474
11012
|
summary: {
|
|
10475
11013
|
type: "string",
|
|
10476
|
-
description: "Optional. Updated one-line
|
|
11014
|
+
description: "Optional. Updated one-line summary when framing has shifted."
|
|
10477
11015
|
}
|
|
10478
11016
|
},
|
|
10479
11017
|
required: ["reason", "remainingSteps"]
|
|
@@ -10511,7 +11049,7 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
10511
11049
|
}
|
|
10512
11050
|
|
|
10513
11051
|
// src/tools/todo.ts
|
|
10514
|
-
var DESCRIPTION =
|
|
11052
|
+
var DESCRIPTION = "In-session task tracker for 3+ step work. NOT a plan \u2014 no approval gate, no checkpoint, no files touched. Each call REPLACES the entire list (set semantics) \u2014 pass the FULL list. Exactly one item may be in_progress at a time; flip to completed the moment that step's done. Pass `[]` to clear. For approval gates use submit_plan; for branching choices use ask_choice.";
|
|
10515
11053
|
function validateTodos(raw) {
|
|
10516
11054
|
if (!Array.isArray(raw)) {
|
|
10517
11055
|
throw new Error("todo_write: `todos` must be an array");
|
|
@@ -11902,8 +12440,13 @@ var OutputBuffer = class {
|
|
|
11902
12440
|
};
|
|
11903
12441
|
|
|
11904
12442
|
// src/tools/shell/parse.ts
|
|
11905
|
-
import { homedir as
|
|
12443
|
+
import { homedir as homedir7 } from "os";
|
|
11906
12444
|
import * as pathMod8 from "path";
|
|
12445
|
+
|
|
12446
|
+
// packages/core-utils/src/tildeify.ts
|
|
12447
|
+
import { homedir as homedir6 } from "os";
|
|
12448
|
+
|
|
12449
|
+
// src/tools/shell/parse.ts
|
|
11907
12450
|
var BUILTIN_ALLOWLIST = [
|
|
11908
12451
|
// Repo inspection
|
|
11909
12452
|
"git status",
|
|
@@ -12103,12 +12646,12 @@ function resolveSensitivePath(token, projectRoot) {
|
|
|
12103
12646
|
return null;
|
|
12104
12647
|
let expanded = token;
|
|
12105
12648
|
if (expanded.startsWith("~")) {
|
|
12106
|
-
expanded = pathMod8.join(
|
|
12649
|
+
expanded = pathMod8.join(homedir7(), expanded.slice(1));
|
|
12107
12650
|
}
|
|
12108
12651
|
return pathMod8.resolve(projectRoot, expanded);
|
|
12109
12652
|
}
|
|
12110
12653
|
function expandPrefix(prefix) {
|
|
12111
|
-
if (prefix.startsWith("~")) return pathMod8.join(
|
|
12654
|
+
if (prefix.startsWith("~")) return pathMod8.join(homedir7(), prefix.slice(1));
|
|
12112
12655
|
return pathMod8.resolve(prefix);
|
|
12113
12656
|
}
|
|
12114
12657
|
function pathStartsWithPrefix(normalized, prefix) {
|
|
@@ -12481,7 +13024,7 @@ function registerShellTools(registry, opts) {
|
|
|
12481
13024
|
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
12482
13025
|
registry.register({
|
|
12483
13026
|
name: "run_command",
|
|
12484
|
-
description:
|
|
13027
|
+
description: 'Run a shell command in the project root; returns combined stdout+stderr. Allowlisted read-only / test / lint / typecheck commands run immediately; mutating / network / install commands gate on user confirmation.\n\nNo real shell \u2014 argv parsed natively for cross-platform parity:\n\u2022 Supported: chains `|`/`||`/`&&`/`;` (each segment allowlist-checked) and file redirects `>`/`>>`/`<`/`2>`/`2>>`/`2>&1`/`&>`.\n\u2022 Rejected: background `&`, heredoc `<<`, `$(\u2026)`, subshells, `$VAR` expansion, glob expansion. Quote operator chars as literals (`grep "a|b" file`).\n\u2022 `cd` does NOT persist \u2014 between calls OR within a chain. Use `npm --prefix <dir>`, `git -C <dir>`, `cargo -C <dir>` instead.\n\u2022 Filter at source \u2014 `grep -c` / `wc -l` / narrower paths over unbounded dumps.',
|
|
12485
13028
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
12486
13029
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
12487
13030
|
// during planning. Anything that would otherwise trigger a
|
|
@@ -12536,7 +13079,7 @@ function registerShellTools(registry, opts) {
|
|
|
12536
13079
|
});
|
|
12537
13080
|
registry.register({
|
|
12538
13081
|
name: "run_background",
|
|
12539
|
-
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
|
|
13082
|
+
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 job id + startup preview. Companion tools: `job_output`, `wait_for_job`, `stop_job`, `list_jobs`. Single process only \u2014 no chains/redirects. Use `cwd` (not `cd X && cmd`) for subdirs.\n\nUSE THIS \u2014 not run_command \u2014 for: dev servers / watchers (`npm dev`, `uvicorn`, `tsc --watch`, anything with dev/serve/watch in the name) AND one-shot long jobs (large `curl`, `pip install`, `cargo build`, `docker build`). Pair with `wait_for_job` for server-side blocking \u2014 one tool call regardless of duration.",
|
|
12540
13083
|
parameters: {
|
|
12541
13084
|
type: "object",
|
|
12542
13085
|
properties: {
|
|
@@ -12743,6 +13286,7 @@ var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
12743
13286
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
12744
13287
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
12745
13288
|
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
13289
|
+
var TAVILY_ENDPOINT = "https://api.tavily.com/search";
|
|
12746
13290
|
function searchStatusError(status) {
|
|
12747
13291
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
12748
13292
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -12762,6 +13306,9 @@ async function webSearch(query, opts = {}) {
|
|
|
12762
13306
|
if (opts.engine === "searxng") {
|
|
12763
13307
|
return searchSearxng(query, opts);
|
|
12764
13308
|
}
|
|
13309
|
+
if (opts.engine === "tavily") {
|
|
13310
|
+
return searchTavily(query, opts);
|
|
13311
|
+
}
|
|
12765
13312
|
return searchMojeek(query, opts);
|
|
12766
13313
|
}
|
|
12767
13314
|
async function searchMojeek(query, opts = {}) {
|
|
@@ -12896,6 +13443,55 @@ async function searchMetaso(query, opts = {}) {
|
|
|
12896
13443
|
snippet: wp.snippet ?? wp.summary ?? ""
|
|
12897
13444
|
}));
|
|
12898
13445
|
}
|
|
13446
|
+
async function searchTavily(query, opts = {}) {
|
|
13447
|
+
const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
|
|
13448
|
+
const apiKey = loadTavilyApiKey();
|
|
13449
|
+
if (!apiKey) throw new Error(t("webErrors.tavilyMissingKey"));
|
|
13450
|
+
let resp;
|
|
13451
|
+
try {
|
|
13452
|
+
resp = await fetch(TAVILY_ENDPOINT, {
|
|
13453
|
+
method: "POST",
|
|
13454
|
+
headers: {
|
|
13455
|
+
"Content-Type": "application/json",
|
|
13456
|
+
Accept: "application/json"
|
|
13457
|
+
},
|
|
13458
|
+
body: JSON.stringify({
|
|
13459
|
+
api_key: apiKey,
|
|
13460
|
+
query,
|
|
13461
|
+
search_depth: "basic",
|
|
13462
|
+
max_results: topK,
|
|
13463
|
+
include_answer: false,
|
|
13464
|
+
include_raw_content: false,
|
|
13465
|
+
include_images: false
|
|
13466
|
+
}),
|
|
13467
|
+
signal: opts.signal
|
|
13468
|
+
});
|
|
13469
|
+
} catch (err) {
|
|
13470
|
+
if (err instanceof TypeError && err.message.includes("fetch")) {
|
|
13471
|
+
throw new Error(t("webErrors.cannotReach", { endpoint: TAVILY_ENDPOINT }));
|
|
13472
|
+
}
|
|
13473
|
+
throw err;
|
|
13474
|
+
}
|
|
13475
|
+
if (!resp.ok) {
|
|
13476
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
13477
|
+
throw new Error(t("webErrors.tavilyUnauthorized"));
|
|
13478
|
+
}
|
|
13479
|
+
if (resp.status === 429) throw new Error(t("webErrors.tavilyRateLimit"));
|
|
13480
|
+
throw new Error(t("webErrors.tavilyServerError", { status: resp.status }));
|
|
13481
|
+
}
|
|
13482
|
+
let data;
|
|
13483
|
+
try {
|
|
13484
|
+
data = await resp.json();
|
|
13485
|
+
} catch {
|
|
13486
|
+
throw new Error(t("webErrors.tavilyParseError", { status: resp.status }));
|
|
13487
|
+
}
|
|
13488
|
+
const results = data.results ?? [];
|
|
13489
|
+
return results.slice(0, topK).map((r) => ({
|
|
13490
|
+
title: r.title,
|
|
13491
|
+
url: r.url,
|
|
13492
|
+
snippet: r.content ?? ""
|
|
13493
|
+
}));
|
|
13494
|
+
}
|
|
12899
13495
|
function parseSearxngHtmlResults(html) {
|
|
12900
13496
|
const root = parseHtml(html);
|
|
12901
13497
|
const results = [];
|
|
@@ -13114,7 +13710,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
13114
13710
|
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
13115
13711
|
registry.register({
|
|
13116
13712
|
name: "web_search",
|
|
13117
|
-
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this. To change the backend, use /search-engine mojeek|searxng|metaso.",
|
|
13713
|
+
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this. To change the backend, use /search-engine mojeek|searxng|metaso|tavily.",
|
|
13118
13714
|
readOnly: true,
|
|
13119
13715
|
parallelSafe: true,
|
|
13120
13716
|
parameters: {
|
|
@@ -13129,8 +13725,8 @@ function registerWebTools(registry, opts = {}) {
|
|
|
13129
13725
|
required: ["query"]
|
|
13130
13726
|
},
|
|
13131
13727
|
fn: async (args, ctx) => {
|
|
13132
|
-
const engine =
|
|
13133
|
-
const endpoint =
|
|
13728
|
+
const engine = webSearchEngine();
|
|
13729
|
+
const endpoint = webSearchEndpoint();
|
|
13134
13730
|
const results = await webSearch(args.query, {
|
|
13135
13731
|
topK: args.topK ?? defaultTopK,
|
|
13136
13732
|
signal: ctx?.signal,
|
|
@@ -13634,7 +14230,7 @@ function truncate(s, n) {
|
|
|
13634
14230
|
|
|
13635
14231
|
// src/version.ts
|
|
13636
14232
|
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
13637
|
-
import { homedir as
|
|
14233
|
+
import { homedir as homedir8 } from "os";
|
|
13638
14234
|
import { dirname as dirname7, join as join14 } from "path";
|
|
13639
14235
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13640
14236
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
@@ -13661,7 +14257,7 @@ function readPackageVersion() {
|
|
|
13661
14257
|
}
|
|
13662
14258
|
var VERSION = readPackageVersion();
|
|
13663
14259
|
function cachePath(homeDirOverride) {
|
|
13664
|
-
return join14(homeDirOverride ??
|
|
14260
|
+
return join14(homeDirOverride ?? homedir8(), ".reasonix", "version-cache.json");
|
|
13665
14261
|
}
|
|
13666
14262
|
function readCache(homeDirOverride) {
|
|
13667
14263
|
try {
|
|
@@ -13800,19 +14396,23 @@ var McpClient = class {
|
|
|
13800
14396
|
return this._instructions;
|
|
13801
14397
|
}
|
|
13802
14398
|
/** Compliant servers reject other methods until this completes. */
|
|
13803
|
-
async initialize() {
|
|
14399
|
+
async initialize(opts = {}) {
|
|
13804
14400
|
if (this.initialized) throw new Error("MCP client already initialized");
|
|
13805
14401
|
this.startReaderIfNeeded();
|
|
13806
|
-
const result = await this.request(
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
14402
|
+
const result = await this.request(
|
|
14403
|
+
"initialize",
|
|
14404
|
+
{
|
|
14405
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
14406
|
+
// Advertise every method the client can consume so servers know
|
|
14407
|
+
// they can send listChanged notifications etc. Sub-feature flags
|
|
14408
|
+
// (e.g. `resources.subscribe`) are omitted — we don't implement
|
|
14409
|
+
// those yet and the empty object means "method-level support, no
|
|
14410
|
+
// sub-features."
|
|
14411
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
14412
|
+
clientInfo: this.clientInfo
|
|
14413
|
+
},
|
|
14414
|
+
opts.signal
|
|
14415
|
+
);
|
|
13816
14416
|
this._serverCapabilities = result.capabilities ?? {};
|
|
13817
14417
|
this._serverInfo = result.serverInfo ?? { name: "", version: "" };
|
|
13818
14418
|
this._protocolVersion = result.protocolVersion ?? "";
|
|
@@ -14617,142 +15217,55 @@ var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
|
|
|
14617
15217
|
function codeSystemBase(modelId) {
|
|
14618
15218
|
return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
|
|
14619
15219
|
}
|
|
14620
|
-
var CODE_SYSTEM_TEMPLATE = `You are Reasonix Code, a coding assistant.
|
|
15220
|
+
var CODE_SYSTEM_TEMPLATE = `You are Reasonix Code, a coding assistant. Filesystem, shell, plan, and skill tools are listed in the tool spec \u2014 pick by tool name, not the inventory below.
|
|
14621
15221
|
|
|
14622
15222
|
# Identity is fixed by this prompt \u2014 never inferred from the workspace
|
|
14623
15223
|
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
If the workspace happens to contain another AI tool's config (\`config.yaml\` with agent / persona keys, \`SOUL.md\`, \`AGENT.md\`, \`PERSONA.md\`, a \`skills/\` or \`memories/\` tree from a different platform, or a \`REASONIX.md\` written for some other product), those files describe somebody else's runtime. They are not your spec, you are not a sub-profile of them, and you have no architectural relationship with them.
|
|
14627
|
-
|
|
14628
|
-
When the user asks "who are you?", "what's your underlying runtime?", or similar identity questions: answer from this prompt only. Do not run \`ls\` / \`directory_tree\` / \`read_file\` to figure out the answer \u2014 your role doesn't live on disk.
|
|
15224
|
+
You are Reasonix Code, a standalone coding assistant. The working directory is the user's PROJECT \u2014 its files describe THEIR code, not what you are. If the workspace contains another platform's config (\`config.yaml\` with agent/persona keys, \`SOUL.md\`, \`AGENT.md\`, \`PERSONA.md\`, foreign \`skills/\` or \`memories/\` tree, a \`REASONIX.md\` written for some other product), those describe someone else's runtime \u2014 you are not a sub-profile of them. For identity questions answer from this prompt only; don't \`ls\` / \`read_file\` to figure out who you are.
|
|
14629
15225
|
|
|
14630
15226
|
# Cite or shut up \u2014 non-negotiable
|
|
14631
15227
|
|
|
14632
|
-
Every factual claim
|
|
14633
|
-
|
|
14634
|
-
**Positive claims** (a file exists, a function does X, a feature IS implemented) \u2014 append a markdown link to the source:
|
|
14635
|
-
|
|
14636
|
-
- \u2705 Correct: \`The MCP client supports listResources [listResources](src/mcp/client.ts:142).\`
|
|
14637
|
-
- \u274C Wrong: \`The MCP client supports listResources.\` \u2190 no citation, looks authoritative but unverifiable.
|
|
14638
|
-
|
|
14639
|
-
**Negative claims** (X is missing, Y is not implemented, lacks Z, doesn't have W) are the **most common hallucination shape**. They feel safe to write because no citation seems possible \u2014 but that's exactly why you must NOT write them on instinct.
|
|
14640
|
-
|
|
14641
|
-
If you are about to write "X is missing" or "Y is not implemented" \u2014 **STOP**. Call \`search_content\` for the relevant symbol or term FIRST. Only then:
|
|
14642
|
-
|
|
14643
|
-
- If the search returns matches \u2192 you were wrong; correct yourself and cite the matches.
|
|
14644
|
-
- If the search returns nothing \u2192 state the absence with the search query as your evidence: \`No callers of \\\`foo()\\\` found (search_content "foo").\`
|
|
14645
|
-
|
|
14646
|
-
Asserting absence without a search is the #1 way evaluative answers go wrong. Treat the urge to write "missing" as a red flag in your own reasoning.
|
|
15228
|
+
Every factual claim about THIS codebase needs evidence \u2014 Reasonix VALIDATES citations and broken paths render in **red strikethrough with \u274C**. **Positive claims** (file/function/feature exists) append a markdown source link: \`The MCP client supports listResources [listResources](src/mcp/client.ts:142).\` **Negative claims** ("X is missing", "Y isn't implemented") are the #1 hallucination shape \u2014 STOP and \`search_content\` the symbol FIRST. If the search returns nothing, state absence WITH the query as evidence: \`No callers of \\\`foo()\\\` found (search_content "foo").\`
|
|
14647
15229
|
|
|
14648
15230
|
# When auditing or reviewing this codebase
|
|
14649
15231
|
|
|
14650
|
-
When
|
|
14651
|
-
|
|
14652
|
-
- **Auto-preview is for locating, not auditing.** Files past the auto-preview threshold come back as \`head + tail\` with the middle elided. Don't conclude what's in the elided section \u2014 runtime behavior, current architectural state, whether a plan doc is still accurate \u2014 off the preview. Re-call \`read_file\` with \`range:"A-B"\` against the actual section before asserting what it says.
|
|
14653
|
-
- **Flag \u2192 consumer trace.** Reading a type field (\`parallelSafe?: boolean\`, \`stormExempt?: boolean\`) is not understanding behavior. Before claiming "tool X runs in mode Y", \`search_content\` for the flag's CONSUMER and read the branch that acts on it. **For inventory claims** ("which tools have flag F?"), grep the flag \u2014 don't enumerate from memory; the field is set per-tool and easily mis-recalled.
|
|
14654
|
-
- **No fabricated percentages.** "Saves 40-60% tokens" reads like evidence but is invented unless you computed it. Ground numbers in a cited transcript / token count, or use hedged language ("small but non-zero", "may compound") \u2014 never present an unmeasured number as a measured one.
|
|
14655
|
-
- **Schema cost is real.** Every tool's description ships in every request. A new-tool proposal MUST cover (a) which existing-tool composition fails to do this, (b) rough description-token cost, (c) why a prompt or description change can't reach the same end. Default to "tighten prompt / existing tool" before "add tool".
|
|
14656
|
-
- **MEMORY.md is part of the design space.** The pinned memory blocks above are loaded user feedback \u2014 recommendations contradicting them ("auto-commit checkpoints", "free-credit messaging", anything the user has explicitly ruled out) are wrong by construction. Cross-check before proposing.
|
|
14657
|
-
- **User-facing \u2260 model-facing \u2260 library-facing.** Reasonix has four action surfaces: slash commands (user), tools (model), UI (user), and library exports (\`src/index.ts\`). Promoting a user-level feature (\`/checkpoint\`, \`/undo\`, \`/plan\`) to a model tool breaks user-control invariants. Treating a library export as "dead code" because the CLI doesn't register it to the model misreads the design \u2014 embedders consume \`src/index.ts\` directly.
|
|
14658
|
-
|
|
14659
|
-
# When to propose a plan (submit_plan)
|
|
14660
|
-
|
|
14661
|
-
You have a \`submit_plan\` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:
|
|
14662
|
-
|
|
14663
|
-
- Multi-file refactors or renames.
|
|
14664
|
-
- Architecture changes (moving modules, splitting / merging files, new abstractions).
|
|
14665
|
-
- Anything where "undo" after the fact would be expensive \u2014 migrations, destructive cleanups, API shape changes.
|
|
14666
|
-
- When the user's request is ambiguous and multiple reasonable interpretations exist \u2014 propose your reading as a plan and let them confirm.
|
|
14667
|
-
|
|
14668
|
-
Skip submit_plan for small, obvious changes: one-line typo, clear bug with a clear fix, adding a missing import, renaming a local variable. Just do those.
|
|
14669
|
-
|
|
14670
|
-
Plan body: one-sentence summary, then a file-by-file breakdown of what you'll change and why, and any risks or open questions. If some decisions are genuinely up to the user (naming, tradeoffs, out-of-scope possibilities), list them in an "Open questions" section \u2014 the user sees the plan in a picker and has a text input to answer your questions before approving. Don't pretend certainty you don't have; flagged questions are how the user tells you what they care about. After calling submit_plan, STOP \u2014 don't call any more tools, wait for the user's verdict.
|
|
15232
|
+
When asked to audit/review/critique Reasonix itself, the failure mode is building confident proposals on factually wrong premises. Six rails:
|
|
14671
15233
|
|
|
14672
|
-
**
|
|
15234
|
+
- **Auto-preview is for locating, not auditing.** Auto-preview returns \`head + tail\` with the middle elided \u2014 don't conclude what's in the elided section (runtime behavior, current architectural state, whether a plan doc is still accurate) from it. Re-call \`read_file\` with \`range:"A-B"\` before asserting.
|
|
15235
|
+
- **Flag \u2192 consumer trace.** Reading a type field (\`parallelSafe?: boolean\`, \`stormExempt?: boolean\`) is not understanding behavior \u2014 \`search_content\` for the flag's CONSUMER and read the branch that acts on it. **For inventory claims** ("which tools have flag F?"), grep the flag \u2014 don't enumerate from memory; the field is set per-tool and easily mis-recalled.
|
|
15236
|
+
- **No fabricated percentages.** "Saves 40-60% tokens" is invented unless you computed it. Ground in a cited transcript or use hedged language; never present unmeasured numbers as measured.
|
|
15237
|
+
- **Schema cost is real.** Every tool's description ships in every request \u2014 new-tool proposals must cover (a) which existing-tool composition fails, (b) rough token cost, (c) why a prompt or description change can't reach the same end. Default to "tighten prompt / existing tool".
|
|
15238
|
+
- **MEMORY.md is part of the design space.** Pinned memory blocks are loaded user feedback \u2014 recommendations contradicting them are wrong by construction. Cross-check before proposing.
|
|
15239
|
+
- **User-facing \u2260 model-facing \u2260 library-facing.** Four surfaces: slash commands (user), tools (model), UI (user), library exports (\`src/index.ts\`). Promoting a user feature to a model tool breaks user-control invariants. Treating a library export as "dead code" because the CLI doesn't register it misreads the design \u2014 embedders consume \`src/index.ts\` directly.
|
|
14673
15240
|
|
|
14674
|
-
#
|
|
15241
|
+
# Picking the right tool: submit_plan / ask_choice / todo_write
|
|
14675
15242
|
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
- The user has asked for options / doesn't want a recommendation / wants to decide.
|
|
14680
|
-
- You've analyzed multiple approaches and the final call is theirs.
|
|
14681
|
-
- It's a preference fork you can't resolve without them (deployment target, team convention, taste).
|
|
14682
|
-
|
|
14683
|
-
Skip it when one option is clearly correct (just do it, or submit_plan) or a free-form text answer fits (ask in prose).
|
|
14684
|
-
|
|
14685
|
-
Each option: short stable id (A/B/C), one-line title, optional summary. \`allowCustom: true\` when their real answer might not fit. Max 6. A ~1-sentence lead-in before the call is fine ("I see three directions \u2014 letting you pick"); don't repeat the options in it. After the call, STOP.
|
|
14686
|
-
|
|
14687
|
-
# When to track multi-step intent (todo_write)
|
|
14688
|
-
|
|
14689
|
-
\`todo_write\` is a lightweight in-session task tracker \u2014 NOT a plan. No approval gate, no checkpoint pauses, doesn't touch files. Use it when the task has 3+ distinct steps and you'd otherwise lose track of where you are. Each call REPLACES the entire list (set semantics). Exactly one item may be \`in_progress\` at a time \u2014 flip it to \`completed\` the moment that step's done, before starting the next.
|
|
14690
|
-
|
|
14691
|
-
Use it for:
|
|
14692
|
-
- Multi-part user requests ("do A, then B, then C") \u2014 record the parts so you don't drop one.
|
|
14693
|
-
- Long refactors where you've finished step 2 of 5 and want a visible record.
|
|
14694
|
-
- Any moment where you'd otherwise enumerate "1. ... 2. ... 3. ..." in prose \u2014 the tool is strictly better, the UI shows progress live.
|
|
14695
|
-
|
|
14696
|
-
Skip it for: one-shot edits, single-question answers, anything that fits in one tool call. Don't \`todo_write\` and \`submit_plan\` for the same work \u2014 \`submit_plan\` is for tasks that need a review gate; \`todo_write\` is for personal bookkeeping after the user has already given you the green light.
|
|
14697
|
-
|
|
14698
|
-
Call shape: \`{ todos: [{ content, activeForm, status }, ...] }\` \u2014 \`content\` is imperative ("Add tests"), \`activeForm\` is gerund ("Adding tests") shown while \`in_progress\`. Pass the FULL list every call, not a delta. Pass \`todos: []\` to clear when work's done.
|
|
15243
|
+
- **submit_plan** \u2014 review-gate for multi-file refactors, architecture changes, anything expensive to undo. Markdown body + structured \`steps\`. After calling, STOP and wait. Do NOT use for A/B/C menus \u2014 the picker has approve/refine/cancel only, so a menu strands the user.
|
|
15244
|
+
- **ask_choice** \u2014 when the user is supposed to pick between alternatives, the TOOL picks; never enumerate choices as prose. Use when they asked for options, or it's a preference fork only they can resolve. Skip when one option is clearly correct (just do it). After calling, STOP.
|
|
15245
|
+
- **todo_write** \u2014 in-session tracker for 3+ step work. NOT a plan (no approval gate, no files touched). One \`in_progress\` at a time; flip to \`completed\` immediately. For approval gates use submit_plan; for branching use ask_choice.
|
|
14699
15246
|
|
|
14700
15247
|
# Plan mode (/plan)
|
|
14701
15248
|
|
|
14702
|
-
|
|
14703
|
-
- Write tools (edit_file, multi_edit, write_file, create_directory, move_file, copy_file, delete_file, delete_directory) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
|
|
14704
|
-
- Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work \u2014 use them to investigate.
|
|
14705
|
-
- You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
|
|
14706
|
-
|
|
15249
|
+
Stronger constraint than submit_plan: writes + non-allowlisted run_command are bounced at dispatch ("unavailable in plan mode" \u2014 don't retry). Read tools and allowlisted shell commands still work. You MUST call submit_plan before anything will execute.
|
|
14707
15250
|
|
|
14708
15251
|
# Delegating to subagents via Skills
|
|
14709
15252
|
|
|
14710
|
-
The pinned Skills index below lists
|
|
14711
|
-
|
|
14712
|
-
**When you call \`run_skill\`, the \`name\` is ONLY the identifier before the tag** \u2014 e.g. \`run_skill({ name: "explore", arguments: "..." })\`, NOT \`"[\u{1F9EC} subagent] explore"\` and NOT \`"explore [\u{1F9EC} subagent]"\`. The tag is display sugar; the name argument is just the bare identifier.
|
|
14713
|
-
|
|
14714
|
-
Two built-ins ship by default:
|
|
14715
|
-
- **explore** \`[\u{1F9EC} subagent]\` \u2014 read-only investigation across the codebase. Use when the user says things like "find all places that...", "how does X work across the project", "survey the code for Y". Pass \`arguments\` describing the concrete question.
|
|
14716
|
-
- **research** \`[\u{1F9EC} subagent]\` \u2014 combines web search + code reading. Use for "is X supported by lib Y", "what's the canonical way to Z", "compare our impl to the spec".
|
|
14717
|
-
|
|
14718
|
-
**Default: don't delegate.** Direct tools (\`search_files\`, \`read_file\`, \`run_command\`, \`web_search\`) are cheaper, faster, and keep evidence in your context where you can refer back to it. A subagent spawn pays a fresh prefix-cache miss and a full child loop \u2014 hundreds of ms of overhead and full input pricing for the child's first turn. For most questions the spawn costs more than it saves.
|
|
15253
|
+
The pinned Skills index below lists every available playbook (built-ins + user-installed). Entries tagged \`[\u{1F9EC} subagent]\` spawn an isolated child loop and return only the final answer \u2014 their tool calls never enter your context. Pass \`name\` as the BARE identifier (e.g. \`"explore"\`), not the \`[\u{1F9EC} subagent]\` tag.
|
|
14719
15254
|
|
|
14720
|
-
Spawn ONLY in
|
|
14721
|
-
1. **True parallelism** \u2014 you have 2+ independent investigations that can run concurrently in the same tool batch. The wall-time win is real and only achievable via fan-out.
|
|
14722
|
-
2. **Context blow-up** \u2014 the work would otherwise need >10 file reads/searches and you only need the conclusion. Keeping the trail out of your context is the actual saving.
|
|
14723
|
-
|
|
14724
|
-
Anti-patterns \u2014 do NOT spawn for any of these:
|
|
14725
|
-
- single grep / single file read \u2192 call the tool directly
|
|
14726
|
-
- 1-3 file cross-reference \u2192 read them directly
|
|
14727
|
-
- "to keep my context clean for one question" \u2192 not enough saving to justify the spawn
|
|
14728
|
-
- anything that needs user interaction (subagents can't submit plans or ask for clarification)
|
|
14729
|
-
- anything where you need to track intermediate results yourself (planning, multi-step edits)
|
|
14730
|
-
|
|
14731
|
-
Always pass a clear, self-contained \`arguments\` \u2014 that text is the **only** context the subagent gets.
|
|
15255
|
+
**Default: don't delegate.** Direct tools are cheaper and keep evidence in your context. Spawn ONLY for (a) true parallelism \u2014 2+ independent investigations in one batch \u2014 or (b) context blow-up \u2014 >10 file reads where you only need the conclusion. Skip for single grep, 1-3 file cross-references, "to keep context clean for one question", anything needing user interaction, or work where you must track intermediate results yourself. Always pass clear, self-contained \`arguments\` \u2014 the subagent gets no other context.
|
|
14732
15256
|
|
|
14733
15257
|
# When to edit vs. when to explore
|
|
14734
15258
|
|
|
14735
|
-
Only propose edits when the user explicitly
|
|
14736
|
-
- analyze, read, explore, describe, or summarize a project
|
|
14737
|
-
- explain how something works
|
|
14738
|
-
- answer a question about the code
|
|
14739
|
-
|
|
14740
|
-
In those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.
|
|
14741
|
-
|
|
14742
|
-
When you do propose edits, the user will review them and decide whether to \`/apply\` or \`/discard\`. Don't assume they'll accept \u2014 write as if each edit will be audited, because it will.
|
|
15259
|
+
Only propose edits when the user explicitly says change / fix / add / remove / refactor / write. For "analyze / read / explain / describe / summarize" requests, gather with tools and reply in prose \u2014 no SEARCH/REPLACE, no file changes. If unclear, ask.
|
|
14743
15260
|
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
-
|
|
14747
|
-
-
|
|
14748
|
-
- \`"edit blocks: 1/1 applied"\` \u2014 user approved it. Continue as normal.
|
|
14749
|
-
- \`"User rejected this edit to <path>. Don't retry the same SEARCH/REPLACE\u2026"\` \u2014 user said no to THIS specific edit. Do NOT re-emit the same block, do NOT switch tools to sneak it past the gate (write_file \u2192 edit_file, or text-form SEARCH/REPLACE). Either take a clearly different approach or stop and ask the user what they want instead.
|
|
14750
|
-
- Text-form SEARCH/REPLACE blocks in your assistant reply queue for end-of-turn /apply \u2014 same "don't retry on rejection" rule.
|
|
14751
|
-
- If the user presses Esc mid-prompt the whole turn is aborted; you won't get another tool response. Don't keep spamming tool calls after an abort.
|
|
15261
|
+
The **edit gate** routes \`edit_file\` / \`write_file\` based on the user's mode (\`review\` or \`auto\`) \u2014 you don't see which is active, write the same way in both. Responses:
|
|
15262
|
+
- \`"edit blocks: 1/1 applied"\` \u2014 proceed.
|
|
15263
|
+
- \`"User rejected this edit to <path>. Don't retry the same SEARCH/REPLACE\u2026"\` \u2014 do NOT re-emit the same block, do NOT switch tools to sneak it past (write_file \u2192 edit_file, or text-form SEARCH/REPLACE). Take a clearly different approach or ask.
|
|
15264
|
+
- Esc mid-prompt aborts the whole turn \u2014 don't keep calling tools after.
|
|
14752
15265
|
|
|
14753
15266
|
# Editing files
|
|
14754
15267
|
|
|
14755
|
-
|
|
15268
|
+
Output one or more SEARCH/REPLACE blocks in this exact format:
|
|
14756
15269
|
|
|
14757
15270
|
path/to/file.ext
|
|
14758
15271
|
<<<<<<< SEARCH
|
|
@@ -14762,83 +15275,48 @@ the new lines
|
|
|
14762
15275
|
>>>>>>> REPLACE
|
|
14763
15276
|
|
|
14764
15277
|
Rules:
|
|
14765
|
-
-
|
|
14766
|
-
- One edit per block
|
|
14767
|
-
-
|
|
15278
|
+
- read_file first so your SEARCH matches byte-for-byte.
|
|
15279
|
+
- One edit per block; multiple blocks per response are fine.
|
|
15280
|
+
- Create a new file with empty SEARCH:
|
|
14768
15281
|
path/to/new.ts
|
|
14769
15282
|
<<<<<<< SEARCH
|
|
14770
15283
|
=======
|
|
14771
15284
|
(whole file content here)
|
|
14772
15285
|
>>>>>>> REPLACE
|
|
14773
|
-
-
|
|
14774
|
-
- Paths are relative to the working directory.
|
|
14775
|
-
- For multi-site changes
|
|
15286
|
+
- Don't use write_file to change existing files \u2014 the user reviews edits as SEARCH/REPLACE. write_file is for wholesale overwrites only.
|
|
15287
|
+
- Paths are relative to the working directory.
|
|
15288
|
+
- For multi-site changes use \`multi_edit\` \u2014 validation runs before any write; validation failures leave all files untouched. Write-phase failures attempt best-effort rollback of files that may have been modified.
|
|
14776
15289
|
|
|
14777
15290
|
# Trust what you already know
|
|
14778
15291
|
|
|
14779
|
-
Before exploring
|
|
15292
|
+
Before exploring to answer a factual question, check context first: the user's message, prior turns (including \`remember\` results), the pinned memory blocks above. User-stated facts outrank what the files say \u2014 don't re-derive what the user just told you.
|
|
14780
15293
|
|
|
14781
15294
|
# Exploration
|
|
14782
15295
|
|
|
14783
|
-
|
|
14784
|
-
- Prefer \`search_files\` over \`list_directory\` when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees. Note: \`search_files\` matches file NAMES; for searching file CONTENTS use \`search_content\`.
|
|
14785
|
-
- Available exploration tools: \`read_file\`, \`list_directory\`, \`directory_tree\`, \`search_files\` (filename match), \`glob\` (mtime-sorted glob \u2014 use for "what changed lately", "all *.ts under src/"), \`search_content\` (content grep \u2014 use for "where is X called", "find all references to Y"; pass \`context:N\` for grep -C N around hits), \`get_file_info\`. Don't call \`grep\` or other tools that aren't in this list \u2014 they don't exist as functions.
|
|
15296
|
+
Skip dependency, build, and VCS directories unless asked (the pinned .gitignore below is your denylist). \`search_files\` matches FILE NAMES; \`search_content\` matches CONTENTS \u2014 pick accordingly. Use \`glob\` for "what changed lately" / "all *.ts under src/", \`search_content\` with \`context:N\` for grep -C around hits.
|
|
14786
15297
|
|
|
14787
15298
|
# Path conventions
|
|
14788
15299
|
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
- **Filesystem tools** (\`read_file\`, \`list_directory\`, \`search_files\`, \`edit_file\`, etc.): paths resolve against the sandbox root. Relative (\`src/foo.ts\`), POSIX-absolute (\`/src/foo.ts\`, where \`/\` means the project root), and OS-absolute including Windows drive-letter (\`D:\\\\path\\\\foo.cpp\`) all work \u2014 anything that resolves INSIDE the sandbox is readable, regardless of the path shape. When the user pastes a path, your default move is to call \`read_file\` on it as-is. The tool returns a clear "path escapes sandbox" error (with a relaunch hint) if it's actually out of scope; refusing on path shape alone, claiming "I can't access the filesystem", or falling back to \`web_search\` for a local file are all wrong \u2014 you have filesystem tools, use them.
|
|
14792
|
-
- **\`run_command\`**: the command runs in a real OS shell with cwd pinned to the project root. Paths inside the shell command are interpreted by THAT shell, not by us. **Never use leading \`/\` in run_command arguments** \u2014 Windows treats \`/tests\` as drive-root \`F:\\tests\` (non-existent), POSIX shells treat it as filesystem root. Use plain relative paths (\`tests\`, \`./tests\`, \`src/loop.ts\`) instead.
|
|
14793
|
-
|
|
14794
|
-
# When the user wants to switch project / working directory
|
|
14795
|
-
|
|
14796
|
-
You can't. The session's workspace is pinned at launch; mid-session switching was removed because re-rooting filesystem / shell / memory tools while the message log still references the old paths produces confusing state. Tell the user to quit and relaunch with the new directory (e.g. \`cd ../other-project && reasonix code\`).
|
|
15300
|
+
- **Filesystem tools** (\`read_file\`, \`list_directory\`, \`edit_file\`, etc.): paths resolve against the sandbox root. Relative, POSIX-absolute (\`/\` = project root), and OS-absolute (e.g. \`D:\\\\path\\\\foo.cpp\`) all work as long as they resolve INSIDE the sandbox. Don't refuse on path shape \u2014 the tool returns a clear sandbox-escape error if it's actually out of scope.
|
|
15301
|
+
- **\`run_command\`**: cwd pinned to project root. Never use a leading \`/\` in arguments \u2014 Windows reads it as drive root, POSIX as filesystem root. Use relative paths.
|
|
14797
15302
|
|
|
14798
|
-
|
|
15303
|
+
# Workspace is pinned
|
|
14799
15304
|
|
|
14800
|
-
|
|
15305
|
+
You can't switch project / working directory mid-session \u2014 tell the user to quit and relaunch (e.g. \`cd ../other-project && reasonix code\`). Don't try \`cd\` via \`run_command\` either; the sandbox is pinned and \`cd\` doesn't carry between calls.
|
|
14801
15306
|
|
|
14802
|
-
|
|
15307
|
+
# Foreground vs background
|
|
14803
15308
|
|
|
14804
|
-
|
|
14805
|
-
- \`run_background\` \u2014 spawns and detaches after a brief startup window. Use for:
|
|
14806
|
-
- **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\`.
|
|
14807
|
-
- **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.
|
|
14808
|
-
|
|
14809
|
-
**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\`.
|
|
14810
|
-
|
|
14811
|
-
After \`run_background\`, tools available to you:
|
|
14812
|
-
- \`job_output(jobId, tailLines?)\` \u2014 read recent logs to verify startup / debug errors.
|
|
14813
|
-
- \`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\`.
|
|
14814
|
-
- \`list_jobs\` \u2014 see every job this session (running + exited).
|
|
14815
|
-
- \`stop_job(jobId)\` \u2014 SIGTERM \u2192 SIGKILL after grace. Stop before switching port / config.
|
|
14816
|
-
|
|
14817
|
-
Don't re-start an already-running dev server \u2014 call \`list_jobs\` first when in doubt.
|
|
15309
|
+
\`run_command\` blocks until exit \u2014 use for tests / builds / lints / typechecks / git / one-shot scripts under a minute. \`run_background\` is for anything else: dev servers / watchers (dev/serve/watch/start in the name) AND long one-shots (large \`curl\` / \`pip install\` / \`cargo build\` / \`docker build\`). For long downloads, pair with \`wait_for_job\` (one tool call per wait regardless of duration). Don't restart a running dev server \u2014 \`list_jobs\` first.
|
|
14818
15310
|
|
|
14819
15311
|
# Scope discipline on "run it" / "start it" requests
|
|
14820
15312
|
|
|
14821
|
-
When the user
|
|
14822
|
-
|
|
14823
|
-
1. Start it (\`run_background\` for dev servers, \`run_command\` for one-shots).
|
|
14824
|
-
2. Verify it came up (read a ready signal via \`job_output\`, or fetch the URL with \`web_fetch\` if they want you to confirm).
|
|
14825
|
-
3. Report what's running, where (URL / port / pid), and STOP.
|
|
14826
|
-
|
|
14827
|
-
Do NOT, in the same turn:
|
|
14828
|
-
- Run \`tsc\` / type-checkers / linters unless the user asked for it.
|
|
14829
|
-
- Scan for bugs to "proactively" fix. The page rendering is success.
|
|
14830
|
-
- Clean up unused imports, dead code, or refactor "while you're here."
|
|
14831
|
-
- Edit files to improve anything the user didn't mention.
|
|
14832
|
-
|
|
14833
|
-
If you notice an obvious issue, MENTION it in one sentence and wait for the user to say "fix it." The cost of over-eagerness is real: you burn tokens, make surprise edits the user didn't want, and chain into cascading "fix the new error I just introduced" loops. The storm-breaker will cut you off, but the user still sees the mess.
|
|
14834
|
-
|
|
14835
|
-
"It works" is the end state. Resist the urge to polish.
|
|
15313
|
+
When the user says run / start / launch / serve / boot up: start it, verify it came up, report what's running and STOP. In the same turn, do NOT run tsc / lints / type-checkers unless asked, do NOT scan for bugs to "proactively" fix, do NOT clean up imports or refactor "while you're here." If you notice an issue, mention in one sentence and wait. "It works" is the end state \u2014 resist the urge to polish.
|
|
14836
15314
|
|
|
14837
15315
|
# Style
|
|
14838
15316
|
|
|
14839
15317
|
- Show edits; don't narrate them in prose. "Here's the fix:" is enough.
|
|
14840
15318
|
- One short paragraph explaining *why*, then the blocks.
|
|
14841
|
-
-
|
|
15319
|
+
- Silence during exploration is fine \u2014 tool calls first, prose after.
|
|
14842
15320
|
|
|
14843
15321
|
__ESCALATION_CONTRACT__
|
|
14844
15322
|
|
|
@@ -14855,8 +15333,18 @@ You have BOTH \`semantic_search\` (vector index) and \`search_content\` (literal
|
|
|
14855
15333
|
- **Exact-token queries** (a specific identifier, regex, or "find every call to foo") \u2192 call \`search_content\`.
|
|
14856
15334
|
|
|
14857
15335
|
If \`semantic_search\` returns nothing useful (low scores, off-topic), THEN fall back to \`search_content\`. Don't go the other way \u2014 grepping a paraphrased question wastes turns.`;
|
|
15336
|
+
var ENGINEERING_LIFECYCLE_CONTRACT = `
|
|
15337
|
+
|
|
15338
|
+
# Engineering lifecycle contract
|
|
15339
|
+
|
|
15340
|
+
Reasonix may enforce a prefix-stable Engineering Lifecycle for explicitly enabled high-risk engineering work. The runtime keeps lifecycle state outside the system prompt and tool list, so do not expect stage-specific prompt changes or new tools to appear. Treat any lifecycle block as a host constraint, not as a suggestion.
|
|
15341
|
+
|
|
15342
|
+
When high-risk mutations are bounced with \`rejectedReason: "engineering-lifecycle"\`, switch to read-only exploration, then call \`submit_plan\` with concrete steps before trying the mutation again. Add optional per-step \`targets\`, \`acceptance\`, and \`verification\` fields when they clarify scope or success criteria. For medium/high-risk steps, steps with verification criteria, or steps that changed code, \`mark_step_complete\` requires \`evidence\` entries such as verification output, diff summary, checkpoint id, or manual rationale.`;
|
|
14858
15343
|
function codeSystemPrompt(rootDir, opts = {}) {
|
|
14859
|
-
|
|
15344
|
+
let codeBase = codeSystemBase(opts.modelId ?? DEFAULT_CODE_MODEL);
|
|
15345
|
+
if (opts.engineeringLifecycleMode === "strict") {
|
|
15346
|
+
codeBase = `${codeBase}${ENGINEERING_LIFECYCLE_CONTRACT}`;
|
|
15347
|
+
}
|
|
14860
15348
|
const base = opts.hasSemanticSearch ? `${codeBase}${SEMANTIC_SEARCH_ROUTING}` : codeBase;
|
|
14861
15349
|
const withMemory = applyMemoryStack(base, rootDir);
|
|
14862
15350
|
const gitignorePath = join15(rootDir, ".gitignore");
|
|
@@ -14909,10 +15397,10 @@ import {
|
|
|
14909
15397
|
unlinkSync as unlinkSync4,
|
|
14910
15398
|
writeFileSync as writeFileSync7
|
|
14911
15399
|
} from "fs";
|
|
14912
|
-
import { homedir as
|
|
15400
|
+
import { homedir as homedir9 } from "os";
|
|
14913
15401
|
import { dirname as dirname9, join as join16 } from "path";
|
|
14914
15402
|
function defaultUsageLogPath(homeDirOverride) {
|
|
14915
|
-
return join16(homeDirOverride ??
|
|
15403
|
+
return join16(homeDirOverride ?? homedir9(), ".reasonix", "usage.jsonl");
|
|
14916
15404
|
}
|
|
14917
15405
|
var USAGE_COMPACTION_THRESHOLD_BYTES = 5 * 1024 * 1024;
|
|
14918
15406
|
var USAGE_RETENTION_DAYS = 365;
|