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
|
@@ -3,7 +3,7 @@ import { createRequire as __cr } from 'node:module'; if (typeof globalThis.requi
|
|
|
3
3
|
import {
|
|
4
4
|
MemoryStore,
|
|
5
5
|
sanitizeMemoryName
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BQ6HC66J.js";
|
|
7
7
|
import {
|
|
8
8
|
countTokens,
|
|
9
9
|
countTokensBounded,
|
|
@@ -12,23 +12,23 @@ import {
|
|
|
12
12
|
} from "./chunk-6OWJV3YW.js";
|
|
13
13
|
import {
|
|
14
14
|
Usage
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-DWPAKZTY.js";
|
|
16
16
|
import {
|
|
17
17
|
applyEdit,
|
|
18
18
|
applyMultiEdit,
|
|
19
19
|
pauseGate
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-TRSAHHCL.js";
|
|
21
21
|
import {
|
|
22
22
|
NEGATIVE_CLAIM_RULE,
|
|
23
23
|
PROJECT_MEMORY_FILES,
|
|
24
24
|
PROJECT_MEMORY_MAX_CHARS,
|
|
25
25
|
TUI_FORMATTING_RULES,
|
|
26
26
|
memoryEnabled
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-FY4S7TJZ.js";
|
|
28
28
|
import {
|
|
29
29
|
formatHookOutcomeMessage,
|
|
30
30
|
runHooks
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GH7DC2Y5.js";
|
|
32
32
|
import {
|
|
33
33
|
ignoredByLayers,
|
|
34
34
|
loadGitignoreAt,
|
|
@@ -41,25 +41,26 @@ import {
|
|
|
41
41
|
loadSessionMeta,
|
|
42
42
|
rewriteSession,
|
|
43
43
|
timestampSuffix
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-RRXUIPWG.js";
|
|
45
45
|
import {
|
|
46
46
|
DEEPSEEK_CONTEXT_TOKENS,
|
|
47
47
|
DEFAULT_CONTEXT_TOKENS,
|
|
48
48
|
SessionStats
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-QCFLPSPH.js";
|
|
50
50
|
import {
|
|
51
51
|
t
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-NRQ5UP5T.js";
|
|
53
53
|
import {
|
|
54
54
|
DEFAULT_INDEX_EXCLUDES,
|
|
55
55
|
addProjectPathAllowed,
|
|
56
56
|
loadMemoryTypeRegistry,
|
|
57
57
|
loadMetasoApiKey,
|
|
58
58
|
loadProjectPathAllowed,
|
|
59
|
+
loadTavilyApiKey,
|
|
59
60
|
require_picomatch,
|
|
60
61
|
webSearchEndpoint,
|
|
61
62
|
webSearchEngine
|
|
62
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-6CRPCJAU.js";
|
|
63
64
|
import {
|
|
64
65
|
__commonJS,
|
|
65
66
|
__esm,
|
|
@@ -6124,11 +6125,42 @@ async function bridgeMcpTools(client, opts = {}) {
|
|
|
6124
6125
|
return { ...result, env };
|
|
6125
6126
|
}
|
|
6126
6127
|
function flattenMcpResult(result, opts = {}) {
|
|
6128
|
+
validateResultShape(result);
|
|
6127
6129
|
const parts = result.content.map(blockToString);
|
|
6128
6130
|
const joined = parts.join("\n").trim();
|
|
6129
6131
|
const prefixed = result.isError ? `ERROR: ${joined || "(no error message from server)"}` : joined;
|
|
6130
6132
|
return opts.maxChars ? truncateForModel(prefixed, opts.maxChars) : prefixed;
|
|
6131
6133
|
}
|
|
6134
|
+
function validateResultShape(result) {
|
|
6135
|
+
if (typeof result !== "object" || !result)
|
|
6136
|
+
throw new Error(`MCP server returned non-object result: ${typeof result}`);
|
|
6137
|
+
const { content, isError: _isError } = result;
|
|
6138
|
+
if (!Array.isArray(content))
|
|
6139
|
+
throw new Error(`MCP server returned result with non-array content: ${typeof content}`);
|
|
6140
|
+
for (let i = 0; i < content.length; i++) {
|
|
6141
|
+
const block = content[i];
|
|
6142
|
+
if (typeof block !== "object" || !block)
|
|
6143
|
+
throw new Error(`MCP server returned result.content[${i}] is not an object`);
|
|
6144
|
+
if (block.type !== "text" && block.type !== "image")
|
|
6145
|
+
throw new Error(
|
|
6146
|
+
`MCP server returned result.content[${i}] with unknown type ${JSON.stringify(block.type)}`
|
|
6147
|
+
);
|
|
6148
|
+
if (block.type === "text" && typeof block.text !== "string")
|
|
6149
|
+
throw new Error(
|
|
6150
|
+
`MCP server returned result.content[${i}] with non-string text (${typeof block.text})`
|
|
6151
|
+
);
|
|
6152
|
+
if (block.type === "image") {
|
|
6153
|
+
if (typeof block.data !== "string")
|
|
6154
|
+
throw new Error(
|
|
6155
|
+
`MCP server returned result.content[${i}] with non-string data (${typeof block.data})`
|
|
6156
|
+
);
|
|
6157
|
+
if (typeof block.mimeType !== "string")
|
|
6158
|
+
throw new Error(
|
|
6159
|
+
`MCP server returned result.content[${i}] with non-string mimeType (${typeof block.mimeType})`
|
|
6160
|
+
);
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6132
6164
|
function truncateForModel(s, maxChars) {
|
|
6133
6165
|
if (s.length <= maxChars) return s;
|
|
6134
6166
|
const tailBudget = Math.min(1024, Math.floor(maxChars * 0.1));
|
|
@@ -6277,10 +6309,13 @@ var ToolRegistry = class {
|
|
|
6277
6309
|
_autoFlatten;
|
|
6278
6310
|
_planMode = false;
|
|
6279
6311
|
_interceptor = null;
|
|
6312
|
+
_interceptors = [];
|
|
6280
6313
|
_auditListener = null;
|
|
6281
6314
|
_resultAugmenter = null;
|
|
6282
6315
|
/** Per-tool fingerprint of the last call that failed schema validation. Cleared by any successful validation for that tool. */
|
|
6283
6316
|
_lastMalformed = /* @__PURE__ */ new Map();
|
|
6317
|
+
/** Per-tool fingerprint of the last host-side interceptor rejection. */
|
|
6318
|
+
_lastInterceptorRejection = /* @__PURE__ */ new Map();
|
|
6284
6319
|
constructor(opts = {}) {
|
|
6285
6320
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
6286
6321
|
}
|
|
@@ -6296,6 +6331,18 @@ var ToolRegistry = class {
|
|
|
6296
6331
|
setToolInterceptor(fn) {
|
|
6297
6332
|
this._interceptor = fn;
|
|
6298
6333
|
}
|
|
6334
|
+
/** Ordered host-side interceptors. They run before the legacy single interceptor. */
|
|
6335
|
+
addToolInterceptor(id, fn) {
|
|
6336
|
+
const normalized = id.trim();
|
|
6337
|
+
if (!normalized) throw new Error("tool interceptor requires a non-empty id");
|
|
6338
|
+
const existing = this._interceptors.findIndex((entry) => entry.id === normalized);
|
|
6339
|
+
if (existing >= 0) this._interceptors.splice(existing, 1);
|
|
6340
|
+
this._interceptors.push({ id: normalized, fn });
|
|
6341
|
+
return () => {
|
|
6342
|
+
const idx = this._interceptors.findIndex((entry) => entry.id === normalized);
|
|
6343
|
+
if (idx >= 0) this._interceptors.splice(idx, 1);
|
|
6344
|
+
};
|
|
6345
|
+
}
|
|
6299
6346
|
setAuditListener(fn) {
|
|
6300
6347
|
this._auditListener = fn;
|
|
6301
6348
|
}
|
|
@@ -6384,16 +6431,27 @@ var ToolRegistry = class {
|
|
|
6384
6431
|
rejectedReason: "plan-mode"
|
|
6385
6432
|
});
|
|
6386
6433
|
}
|
|
6387
|
-
|
|
6434
|
+
const chain = this._interceptor ? [...this._interceptors.map((entry) => entry.fn), this._interceptor] : this._interceptors.map((entry) => entry.fn);
|
|
6435
|
+
for (const interceptor of chain) {
|
|
6388
6436
|
try {
|
|
6389
|
-
const short = await
|
|
6390
|
-
if (typeof short === "string")
|
|
6437
|
+
const short = await interceptor(name, args);
|
|
6438
|
+
if (typeof short === "string") {
|
|
6439
|
+
const guarded = this._noteInterceptorRejection(name, fingerprint, short);
|
|
6440
|
+
return this._augmentResult(name, args, guarded);
|
|
6441
|
+
}
|
|
6391
6442
|
} catch (err) {
|
|
6392
6443
|
return JSON.stringify({
|
|
6393
6444
|
error: `${name}: interceptor failed \u2014 ${err.message}`
|
|
6394
6445
|
});
|
|
6395
6446
|
}
|
|
6396
6447
|
}
|
|
6448
|
+
this._lastInterceptorRejection.delete(name);
|
|
6449
|
+
if (opts.signal?.aborted) {
|
|
6450
|
+
return JSON.stringify({
|
|
6451
|
+
error: `${name}: aborted before dispatch (user interrupt)`,
|
|
6452
|
+
rejectedReason: "aborted"
|
|
6453
|
+
});
|
|
6454
|
+
}
|
|
6397
6455
|
let finalResult;
|
|
6398
6456
|
try {
|
|
6399
6457
|
try {
|
|
@@ -6425,13 +6483,16 @@ var ToolRegistry = class {
|
|
|
6425
6483
|
finalResult = JSON.stringify({ error: `${e.name}: ${e.message}` });
|
|
6426
6484
|
}
|
|
6427
6485
|
}
|
|
6486
|
+
return this._augmentResult(name, args, finalResult);
|
|
6487
|
+
}
|
|
6488
|
+
_augmentResult(name, args, result) {
|
|
6428
6489
|
if (this._resultAugmenter) {
|
|
6429
6490
|
try {
|
|
6430
|
-
return this._resultAugmenter(name, args,
|
|
6491
|
+
return this._resultAugmenter(name, args, result);
|
|
6431
6492
|
} catch {
|
|
6432
6493
|
}
|
|
6433
6494
|
}
|
|
6434
|
-
return
|
|
6495
|
+
return result;
|
|
6435
6496
|
}
|
|
6436
6497
|
/** 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. */
|
|
6437
6498
|
_noteMalformed(name, fingerprint, detail) {
|
|
@@ -6445,7 +6506,35 @@ var ToolRegistry = class {
|
|
|
6445
6506
|
}
|
|
6446
6507
|
return JSON.stringify({ error: `${name}: ${detail}` });
|
|
6447
6508
|
}
|
|
6509
|
+
_noteInterceptorRejection(name, fingerprint, result) {
|
|
6510
|
+
const reason = rejectedReason(result);
|
|
6511
|
+
if (!reason) {
|
|
6512
|
+
this._lastInterceptorRejection.delete(name);
|
|
6513
|
+
return result;
|
|
6514
|
+
}
|
|
6515
|
+
const key = `${reason}:${fingerprint}`;
|
|
6516
|
+
const prev = this._lastInterceptorRejection.get(name);
|
|
6517
|
+
this._lastInterceptorRejection.set(name, key);
|
|
6518
|
+
if (prev === key) {
|
|
6519
|
+
return JSON.stringify({
|
|
6520
|
+
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.`,
|
|
6521
|
+
rejectedReason: reason,
|
|
6522
|
+
consecutiveInterceptorRejection: true
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6525
|
+
return result;
|
|
6526
|
+
}
|
|
6448
6527
|
};
|
|
6528
|
+
function rejectedReason(result) {
|
|
6529
|
+
try {
|
|
6530
|
+
const parsed = JSON.parse(result);
|
|
6531
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
6532
|
+
const reason = parsed.rejectedReason;
|
|
6533
|
+
return typeof reason === "string" && reason ? reason : null;
|
|
6534
|
+
} catch {
|
|
6535
|
+
return null;
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6449
6538
|
function isReadOnlyCall(tool, args) {
|
|
6450
6539
|
if (tool.readOnlyCheck) {
|
|
6451
6540
|
try {
|
|
@@ -7877,6 +7966,32 @@ ${reason}`
|
|
|
7877
7966
|
}
|
|
7878
7967
|
return userText;
|
|
7879
7968
|
}
|
|
7969
|
+
/** Rewind to the N-th user turn (0-indexed). Drops that turn + everything after. */
|
|
7970
|
+
rewindToUserTurn(userTurnIndex) {
|
|
7971
|
+
const entries = this.log.entries;
|
|
7972
|
+
let count = 0;
|
|
7973
|
+
let targetIdx = -1;
|
|
7974
|
+
for (let i = 0; i < entries.length; i++) {
|
|
7975
|
+
if (entries[i].role !== "user") continue;
|
|
7976
|
+
if (count === userTurnIndex) {
|
|
7977
|
+
targetIdx = i;
|
|
7978
|
+
break;
|
|
7979
|
+
}
|
|
7980
|
+
count++;
|
|
7981
|
+
}
|
|
7982
|
+
if (targetIdx < 0) return null;
|
|
7983
|
+
const raw = entries[targetIdx].content;
|
|
7984
|
+
const userText = typeof raw === "string" ? raw : "";
|
|
7985
|
+
const preserved = entries.slice(0, targetIdx).map((m) => ({ ...m }));
|
|
7986
|
+
this.log.compactInPlace(preserved);
|
|
7987
|
+
if (this.sessionName) {
|
|
7988
|
+
try {
|
|
7989
|
+
rewriteSession(this.sessionName, preserved);
|
|
7990
|
+
} catch {
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
return userText;
|
|
7994
|
+
}
|
|
7880
7995
|
async *step(userInput) {
|
|
7881
7996
|
this._steerConsumed = false;
|
|
7882
7997
|
if (this.budgetUsd !== null) {
|
|
@@ -8934,7 +9049,7 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8934
9049
|
}
|
|
8935
9050
|
registry.register({
|
|
8936
9051
|
name: "remember",
|
|
8937
|
-
description: "Save a memory for future sessions
|
|
9052
|
+
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.",
|
|
8938
9053
|
parameters: {
|
|
8939
9054
|
type: "object",
|
|
8940
9055
|
properties: {
|
|
@@ -8945,29 +9060,29 @@ function registerMemoryTools(registry, opts = {}) {
|
|
|
8945
9060
|
scope: {
|
|
8946
9061
|
type: "string",
|
|
8947
9062
|
enum: ["global", "project"],
|
|
8948
|
-
description: "
|
|
9063
|
+
description: "global = across all projects; project = current sandbox only (needs `reasonix code`)."
|
|
8949
9064
|
},
|
|
8950
9065
|
name: {
|
|
8951
9066
|
type: "string",
|
|
8952
|
-
description: "
|
|
9067
|
+
description: "Filename-safe id, 3-40 chars, alnum + _ - . (no separators, no leading dot)."
|
|
8953
9068
|
},
|
|
8954
9069
|
description: {
|
|
8955
9070
|
type: "string",
|
|
8956
|
-
description: "
|
|
9071
|
+
description: "\u2264150 char one-liner shown in MEMORY.md."
|
|
8957
9072
|
},
|
|
8958
9073
|
content: {
|
|
8959
9074
|
type: "string",
|
|
8960
|
-
description: "
|
|
9075
|
+
description: "Markdown body. For feedback/project, structure as rule + **Why:** + **How to apply:**."
|
|
8961
9076
|
},
|
|
8962
9077
|
priority: {
|
|
8963
9078
|
type: "string",
|
|
8964
9079
|
enum: ["low", "medium", "high"],
|
|
8965
|
-
description: "
|
|
9080
|
+
description: "`high` injects entry into HIGH PRIORITY block \u2014 use sparingly."
|
|
8966
9081
|
},
|
|
8967
9082
|
expires: {
|
|
8968
9083
|
type: "string",
|
|
8969
9084
|
enum: ["project_end"],
|
|
8970
|
-
description: "
|
|
9085
|
+
description: "`project_end` lets /memory clear project remove this even at global scope."
|
|
8971
9086
|
}
|
|
8972
9087
|
},
|
|
8973
9088
|
required: ["type", "scope", "name", "description", "content"]
|
|
@@ -9084,26 +9199,26 @@ function sanitizeOptions(raw) {
|
|
|
9084
9199
|
function registerChoiceTool(registry, opts = {}) {
|
|
9085
9200
|
registry.register({
|
|
9086
9201
|
name: "ask_choice",
|
|
9087
|
-
description: "
|
|
9202
|
+
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.",
|
|
9088
9203
|
readOnly: true,
|
|
9089
9204
|
parameters: {
|
|
9090
9205
|
type: "object",
|
|
9091
9206
|
properties: {
|
|
9092
9207
|
question: {
|
|
9093
9208
|
type: "string",
|
|
9094
|
-
description: "
|
|
9209
|
+
description: "One-sentence question. Don't repeat the options here \u2014 the picker renders them."
|
|
9095
9210
|
},
|
|
9096
9211
|
options: {
|
|
9097
9212
|
type: "array",
|
|
9098
|
-
description: "2\
|
|
9213
|
+
description: "2\u20136 alternatives. Each: stable id + short title; summary optional.",
|
|
9099
9214
|
items: {
|
|
9100
9215
|
type: "object",
|
|
9101
9216
|
properties: {
|
|
9102
|
-
id: { type: "string", description: "
|
|
9103
|
-
title: { type: "string", description: "One-line
|
|
9217
|
+
id: { type: "string", description: "Stable id (A, B, C or option-1)." },
|
|
9218
|
+
title: { type: "string", description: "One-line label." },
|
|
9104
9219
|
summary: {
|
|
9105
9220
|
type: "string",
|
|
9106
|
-
description: "Optional
|
|
9221
|
+
description: "Optional dimmed second line, \u226480 chars."
|
|
9107
9222
|
}
|
|
9108
9223
|
},
|
|
9109
9224
|
required: ["id", "title"]
|
|
@@ -9111,7 +9226,7 @@ function registerChoiceTool(registry, opts = {}) {
|
|
|
9111
9226
|
},
|
|
9112
9227
|
allowCustom: {
|
|
9113
9228
|
type: "boolean",
|
|
9114
|
-
description: "
|
|
9229
|
+
description: "Shows a 'type my own answer' escape hatch. Default false."
|
|
9115
9230
|
}
|
|
9116
9231
|
},
|
|
9117
9232
|
required: ["question", "options"]
|
|
@@ -9157,6 +9272,7 @@ var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
9157
9272
|
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";
|
|
9158
9273
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
9159
9274
|
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
9275
|
+
var TAVILY_ENDPOINT = "https://api.tavily.com/search";
|
|
9160
9276
|
function searchStatusError(status) {
|
|
9161
9277
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
9162
9278
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -9176,6 +9292,9 @@ async function webSearch(query, opts = {}) {
|
|
|
9176
9292
|
if (opts.engine === "searxng") {
|
|
9177
9293
|
return searchSearxng(query, opts);
|
|
9178
9294
|
}
|
|
9295
|
+
if (opts.engine === "tavily") {
|
|
9296
|
+
return searchTavily(query, opts);
|
|
9297
|
+
}
|
|
9179
9298
|
return searchMojeek(query, opts);
|
|
9180
9299
|
}
|
|
9181
9300
|
async function searchMojeek(query, opts = {}) {
|
|
@@ -9310,6 +9429,55 @@ async function searchMetaso(query, opts = {}) {
|
|
|
9310
9429
|
snippet: wp.snippet ?? wp.summary ?? ""
|
|
9311
9430
|
}));
|
|
9312
9431
|
}
|
|
9432
|
+
async function searchTavily(query, opts = {}) {
|
|
9433
|
+
const topK = Math.max(1, Math.min(20, opts.topK ?? DEFAULT_TOPK));
|
|
9434
|
+
const apiKey = loadTavilyApiKey();
|
|
9435
|
+
if (!apiKey) throw new Error(t("webErrors.tavilyMissingKey"));
|
|
9436
|
+
let resp;
|
|
9437
|
+
try {
|
|
9438
|
+
resp = await fetch(TAVILY_ENDPOINT, {
|
|
9439
|
+
method: "POST",
|
|
9440
|
+
headers: {
|
|
9441
|
+
"Content-Type": "application/json",
|
|
9442
|
+
Accept: "application/json"
|
|
9443
|
+
},
|
|
9444
|
+
body: JSON.stringify({
|
|
9445
|
+
api_key: apiKey,
|
|
9446
|
+
query,
|
|
9447
|
+
search_depth: "basic",
|
|
9448
|
+
max_results: topK,
|
|
9449
|
+
include_answer: false,
|
|
9450
|
+
include_raw_content: false,
|
|
9451
|
+
include_images: false
|
|
9452
|
+
}),
|
|
9453
|
+
signal: opts.signal
|
|
9454
|
+
});
|
|
9455
|
+
} catch (err) {
|
|
9456
|
+
if (err instanceof TypeError && err.message.includes("fetch")) {
|
|
9457
|
+
throw new Error(t("webErrors.cannotReach", { endpoint: TAVILY_ENDPOINT }));
|
|
9458
|
+
}
|
|
9459
|
+
throw err;
|
|
9460
|
+
}
|
|
9461
|
+
if (!resp.ok) {
|
|
9462
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
9463
|
+
throw new Error(t("webErrors.tavilyUnauthorized"));
|
|
9464
|
+
}
|
|
9465
|
+
if (resp.status === 429) throw new Error(t("webErrors.tavilyRateLimit"));
|
|
9466
|
+
throw new Error(t("webErrors.tavilyServerError", { status: resp.status }));
|
|
9467
|
+
}
|
|
9468
|
+
let data;
|
|
9469
|
+
try {
|
|
9470
|
+
data = await resp.json();
|
|
9471
|
+
} catch {
|
|
9472
|
+
throw new Error(t("webErrors.tavilyParseError", { status: resp.status }));
|
|
9473
|
+
}
|
|
9474
|
+
const results = data.results ?? [];
|
|
9475
|
+
return results.slice(0, topK).map((r) => ({
|
|
9476
|
+
title: r.title,
|
|
9477
|
+
url: r.url,
|
|
9478
|
+
snippet: r.content ?? ""
|
|
9479
|
+
}));
|
|
9480
|
+
}
|
|
9313
9481
|
function parseSearxngHtmlResults(html) {
|
|
9314
9482
|
const root = (0, import_node_html_parser.parse)(html);
|
|
9315
9483
|
const results = [];
|
|
@@ -9528,7 +9696,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
9528
9696
|
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
9529
9697
|
registry.register({
|
|
9530
9698
|
name: "web_search",
|
|
9531
|
-
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.",
|
|
9699
|
+
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.",
|
|
9532
9700
|
readOnly: true,
|
|
9533
9701
|
parallelSafe: true,
|
|
9534
9702
|
parameters: {
|
|
@@ -9543,8 +9711,8 @@ function registerWebTools(registry, opts = {}) {
|
|
|
9543
9711
|
required: ["query"]
|
|
9544
9712
|
},
|
|
9545
9713
|
fn: async (args, ctx) => {
|
|
9546
|
-
const engine =
|
|
9547
|
-
const endpoint =
|
|
9714
|
+
const engine = webSearchEngine();
|
|
9715
|
+
const endpoint = webSearchEndpoint();
|
|
9548
9716
|
const results = await webSearch(args.query, {
|
|
9549
9717
|
topK: args.topK ?? defaultTopK,
|
|
9550
9718
|
signal: ctx?.signal,
|
|
@@ -9600,13 +9768,13 @@ import * as pathMod4 from "path";
|
|
|
9600
9768
|
// src/memory/subdir.ts
|
|
9601
9769
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
9602
9770
|
import { dirname, join as join2, relative as relative2, resolve as resolve2 } from "path";
|
|
9603
|
-
function
|
|
9771
|
+
function findDirMemory(absDir, rootDir) {
|
|
9604
9772
|
const root = resolve2(rootDir);
|
|
9605
|
-
const target = resolve2(
|
|
9773
|
+
const target = resolve2(absDir);
|
|
9606
9774
|
const rel = relative2(root, target);
|
|
9607
|
-
if (
|
|
9775
|
+
if (rel.startsWith("..")) return [];
|
|
9608
9776
|
const found = [];
|
|
9609
|
-
let cur =
|
|
9777
|
+
let cur = target;
|
|
9610
9778
|
while (cur !== root) {
|
|
9611
9779
|
const r = relative2(root, cur);
|
|
9612
9780
|
if (!r || r.startsWith("..")) break;
|
|
@@ -9623,6 +9791,9 @@ function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
|
9623
9791
|
}
|
|
9624
9792
|
return found;
|
|
9625
9793
|
}
|
|
9794
|
+
function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
9795
|
+
return findDirMemory(dirname(resolve2(absPath)), rootDir);
|
|
9796
|
+
}
|
|
9626
9797
|
function readSubdirMemoryContent(path) {
|
|
9627
9798
|
let raw;
|
|
9628
9799
|
try {
|
|
@@ -9891,6 +10062,129 @@ function formatOutline(entries) {
|
|
|
9891
10062
|
// src/tools/fs/search.ts
|
|
9892
10063
|
import { promises as fs2 } from "fs";
|
|
9893
10064
|
import * as pathMod3 from "path";
|
|
10065
|
+
|
|
10066
|
+
// src/tools/fs/regex-runner.ts
|
|
10067
|
+
import { Worker } from "worker_threads";
|
|
10068
|
+
var WORKER_SOURCE = `
|
|
10069
|
+
const { parentPort } = require("node:worker_threads");
|
|
10070
|
+
parentPort.on("message", (msg) => {
|
|
10071
|
+
const { id, text, source, flags } = msg;
|
|
10072
|
+
let re;
|
|
10073
|
+
try {
|
|
10074
|
+
re = new RegExp(source, flags);
|
|
10075
|
+
} catch (err) {
|
|
10076
|
+
parentPort.postMessage({ id, error: (err && err.message) ? err.message : String(err) });
|
|
10077
|
+
return;
|
|
10078
|
+
}
|
|
10079
|
+
const lines = text.split(/\\r?\\n/);
|
|
10080
|
+
const hits = [];
|
|
10081
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10082
|
+
if (re.test(lines[i])) hits.push(i);
|
|
10083
|
+
}
|
|
10084
|
+
parentPort.postMessage({ id, hits });
|
|
10085
|
+
});
|
|
10086
|
+
`;
|
|
10087
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
10088
|
+
var RegexRunner = class {
|
|
10089
|
+
worker = null;
|
|
10090
|
+
pending = /* @__PURE__ */ new Map();
|
|
10091
|
+
nextId = 1;
|
|
10092
|
+
defaultTimeoutMs;
|
|
10093
|
+
constructor(opts = {}) {
|
|
10094
|
+
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
10095
|
+
}
|
|
10096
|
+
testLines(text, source, flags, opts = {}) {
|
|
10097
|
+
return new Promise((resolve5, reject) => {
|
|
10098
|
+
if (opts.signal?.aborted) {
|
|
10099
|
+
reject(new Error("regex evaluation aborted"));
|
|
10100
|
+
return;
|
|
10101
|
+
}
|
|
10102
|
+
if (!this.worker) this.worker = this.spawn();
|
|
10103
|
+
const id = this.nextId++;
|
|
10104
|
+
const timeoutMs = opts.timeoutMs ?? this.defaultTimeoutMs;
|
|
10105
|
+
const timer = setTimeout(() => {
|
|
10106
|
+
this.pending.delete(id);
|
|
10107
|
+
this.killWorker();
|
|
10108
|
+
reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
|
|
10109
|
+
}, timeoutMs);
|
|
10110
|
+
const entry = { resolve: resolve5, reject, timer };
|
|
10111
|
+
if (opts.signal) {
|
|
10112
|
+
entry.signal = opts.signal;
|
|
10113
|
+
entry.onAbort = () => {
|
|
10114
|
+
this.pending.delete(id);
|
|
10115
|
+
clearTimeout(timer);
|
|
10116
|
+
this.killWorker();
|
|
10117
|
+
reject(new Error("regex evaluation aborted"));
|
|
10118
|
+
};
|
|
10119
|
+
opts.signal.addEventListener("abort", entry.onAbort, { once: true });
|
|
10120
|
+
}
|
|
10121
|
+
this.pending.set(id, entry);
|
|
10122
|
+
this.worker.postMessage({ id, text, source, flags });
|
|
10123
|
+
});
|
|
10124
|
+
}
|
|
10125
|
+
async shutdown() {
|
|
10126
|
+
if (this.worker) {
|
|
10127
|
+
const w = this.worker;
|
|
10128
|
+
this.worker = null;
|
|
10129
|
+
await w.terminate();
|
|
10130
|
+
}
|
|
10131
|
+
for (const entry of this.pending.values()) {
|
|
10132
|
+
clearTimeout(entry.timer);
|
|
10133
|
+
if (entry.onAbort && entry.signal) {
|
|
10134
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
10135
|
+
}
|
|
10136
|
+
entry.reject(new Error("regex runner shut down"));
|
|
10137
|
+
}
|
|
10138
|
+
this.pending.clear();
|
|
10139
|
+
}
|
|
10140
|
+
spawn() {
|
|
10141
|
+
const w = new Worker(WORKER_SOURCE, { eval: true });
|
|
10142
|
+
w.on("message", (msg) => {
|
|
10143
|
+
const entry = this.pending.get(msg.id);
|
|
10144
|
+
if (!entry) return;
|
|
10145
|
+
clearTimeout(entry.timer);
|
|
10146
|
+
if (entry.onAbort && entry.signal) {
|
|
10147
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
10148
|
+
}
|
|
10149
|
+
this.pending.delete(msg.id);
|
|
10150
|
+
if (msg.error !== void 0) entry.reject(new Error(msg.error));
|
|
10151
|
+
else entry.resolve(msg.hits ?? []);
|
|
10152
|
+
});
|
|
10153
|
+
w.on("error", (err) => {
|
|
10154
|
+
if (this.worker !== w) return;
|
|
10155
|
+
this.failPending(err);
|
|
10156
|
+
});
|
|
10157
|
+
w.on("exit", () => {
|
|
10158
|
+
if (this.worker !== w) return;
|
|
10159
|
+
this.worker = null;
|
|
10160
|
+
if (this.pending.size > 0) this.failPending(new Error("regex worker exited"));
|
|
10161
|
+
});
|
|
10162
|
+
return w;
|
|
10163
|
+
}
|
|
10164
|
+
killWorker() {
|
|
10165
|
+
if (!this.worker) return;
|
|
10166
|
+
const w = this.worker;
|
|
10167
|
+
this.worker = null;
|
|
10168
|
+
void w.terminate();
|
|
10169
|
+
}
|
|
10170
|
+
failPending(err) {
|
|
10171
|
+
for (const entry of this.pending.values()) {
|
|
10172
|
+
clearTimeout(entry.timer);
|
|
10173
|
+
if (entry.onAbort && entry.signal) {
|
|
10174
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
10175
|
+
}
|
|
10176
|
+
entry.reject(err);
|
|
10177
|
+
}
|
|
10178
|
+
this.pending.clear();
|
|
10179
|
+
}
|
|
10180
|
+
};
|
|
10181
|
+
var _runner = null;
|
|
10182
|
+
function getRegexRunner() {
|
|
10183
|
+
if (!_runner) _runner = new RegexRunner();
|
|
10184
|
+
return _runner;
|
|
10185
|
+
}
|
|
10186
|
+
|
|
10187
|
+
// src/tools/fs/search.ts
|
|
9894
10188
|
function throwIfAborted(signal) {
|
|
9895
10189
|
if (!signal?.aborted) return;
|
|
9896
10190
|
throw new DOMException("search aborted by user", "AbortError");
|
|
@@ -9943,17 +10237,20 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
9943
10237
|
}
|
|
9944
10238
|
var MAX_HITS_PER_FILE = 30;
|
|
9945
10239
|
var SUMMARY_MODE_TRIGGER_RATIO = 0.8;
|
|
10240
|
+
var WALK_DEADLINE_MS = 12e4;
|
|
9946
10241
|
async function searchContent(ctx, startAbs, args) {
|
|
9947
10242
|
throwIfAborted(args.signal);
|
|
9948
10243
|
const caseSensitive = args.case_sensitive === true;
|
|
9949
10244
|
const includeDeps = args.include_deps === true;
|
|
9950
10245
|
const ctxLines = Math.max(0, Math.min(20, Math.floor(args.context ?? 0)));
|
|
9951
10246
|
const summaryOnly = args.summary_only === true;
|
|
9952
|
-
|
|
10247
|
+
const reFlags = caseSensitive ? "" : "i";
|
|
10248
|
+
let reSource = null;
|
|
9953
10249
|
try {
|
|
9954
|
-
|
|
10250
|
+
new RegExp(args.pattern, reFlags);
|
|
10251
|
+
reSource = args.pattern;
|
|
9955
10252
|
} catch {
|
|
9956
|
-
|
|
10253
|
+
reSource = null;
|
|
9957
10254
|
}
|
|
9958
10255
|
const needle = caseSensitive ? args.pattern : args.pattern.toLowerCase();
|
|
9959
10256
|
const matches = [];
|
|
@@ -9963,6 +10260,15 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9963
10260
|
let summaryMode = summaryOnly;
|
|
9964
10261
|
let summaryNoticeEmitted = false;
|
|
9965
10262
|
const fileHitCounts = /* @__PURE__ */ new Map();
|
|
10263
|
+
const regexSkippedFiles = [];
|
|
10264
|
+
const t0 = Date.now();
|
|
10265
|
+
const throwIfTimedOut = () => {
|
|
10266
|
+
if (Date.now() - t0 > WALK_DEADLINE_MS) {
|
|
10267
|
+
throw new Error(
|
|
10268
|
+
`search_content exceeded ${WALK_DEADLINE_MS}ms \u2014 narrow the scope (path/glob) or simplify the pattern`
|
|
10269
|
+
);
|
|
10270
|
+
}
|
|
10271
|
+
};
|
|
9966
10272
|
const pushLine = (out) => {
|
|
9967
10273
|
if (totalBytes + out.length + 1 > ctx.maxListBytes) {
|
|
9968
10274
|
matches.push(`[\u2026 truncated at ${ctx.maxListBytes} bytes \u2014 refine pattern or path \u2026]`);
|
|
@@ -9997,6 +10303,7 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9997
10303
|
for (const e of entries) {
|
|
9998
10304
|
if (truncated) return;
|
|
9999
10305
|
throwIfAborted(args.signal);
|
|
10306
|
+
throwIfTimedOut();
|
|
10000
10307
|
if (e.isDirectory()) {
|
|
10001
10308
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
10002
10309
|
await walk2(pathMod3.join(dir, e.name));
|
|
@@ -10033,13 +10340,25 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10033
10340
|
const text = raw.toString("utf8");
|
|
10034
10341
|
const rel = displayRel2(ctx.rootDir, full);
|
|
10035
10342
|
const lines = text.split(/\r?\n/);
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10343
|
+
let hits;
|
|
10344
|
+
if (reSource !== null) {
|
|
10345
|
+
try {
|
|
10346
|
+
hits = await getRegexRunner().testLines(text, reSource, reFlags, {
|
|
10347
|
+
signal: args.signal
|
|
10348
|
+
});
|
|
10349
|
+
} catch (err) {
|
|
10350
|
+
const reason = err.message;
|
|
10351
|
+
if (reason.includes("aborted")) throw err;
|
|
10352
|
+
regexSkippedFiles.push({ rel, reason });
|
|
10353
|
+
continue;
|
|
10354
|
+
}
|
|
10355
|
+
} else {
|
|
10356
|
+
hits = [];
|
|
10357
|
+
for (let li = 0; li < lines.length; li++) {
|
|
10358
|
+
throwIfAborted(args.signal);
|
|
10359
|
+
const lineForCheck = caseSensitive ? lines[li] : lines[li].toLowerCase();
|
|
10360
|
+
if (lineForCheck.includes(needle)) hits.push(li);
|
|
10361
|
+
}
|
|
10043
10362
|
}
|
|
10044
10363
|
scanned++;
|
|
10045
10364
|
if (hits.length === 0) continue;
|
|
@@ -10088,6 +10407,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10088
10407
|
}
|
|
10089
10408
|
};
|
|
10090
10409
|
await walk2(startAbs);
|
|
10410
|
+
if (regexSkippedFiles.length > 0) {
|
|
10411
|
+
pushLine(
|
|
10412
|
+
`[regex timed out on ${regexSkippedFiles.length} file${regexSkippedFiles.length === 1 ? "" : "s"} \u2014 pattern may have catastrophic backtracking; first: ${regexSkippedFiles[0].rel}]`
|
|
10413
|
+
);
|
|
10414
|
+
}
|
|
10091
10415
|
if (matches.length === 0) {
|
|
10092
10416
|
return scanned === 0 ? "(no files scanned \u2014 path empty or all files filtered out)" : `(no matches across ${scanned} file${scanned === 1 ? "" : "s"})`;
|
|
10093
10417
|
}
|
|
@@ -10095,7 +10419,7 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10095
10419
|
}
|
|
10096
10420
|
|
|
10097
10421
|
// src/tools/filesystem.ts
|
|
10098
|
-
var DEFAULT_OUTLINE_THRESHOLD_BYTES =
|
|
10422
|
+
var DEFAULT_OUTLINE_THRESHOLD_BYTES = 64 * 1024;
|
|
10099
10423
|
var DEFAULT_MAX_LIST_BYTES = 256 * 1024;
|
|
10100
10424
|
var HARD_MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
10101
10425
|
var OUTLINE_HEAD_LINES = 80;
|
|
@@ -10152,11 +10476,15 @@ function registerFilesystemTools(registry, opts) {
|
|
|
10152
10476
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
10153
10477
|
const shownSubdirMemory = /* @__PURE__ */ new Set();
|
|
10154
10478
|
function withSubdirMemory(absPath, body) {
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10479
|
+
return prependMemorySections(findSubdirMemoryAncestors(absPath, rootDir), body);
|
|
10480
|
+
}
|
|
10481
|
+
function withDirMemory(absDir, body) {
|
|
10482
|
+
return prependMemorySections(findDirMemory(absDir, rootDir), body);
|
|
10483
|
+
}
|
|
10484
|
+
function prependMemorySections(memPaths, body) {
|
|
10485
|
+
if (!memoryEnabled() || memPaths.length === 0) return body;
|
|
10158
10486
|
const sections = [];
|
|
10159
|
-
for (const memPath of [...
|
|
10487
|
+
for (const memPath of [...memPaths].reverse()) {
|
|
10160
10488
|
if (shownSubdirMemory.has(memPath)) continue;
|
|
10161
10489
|
const content = readSubdirMemoryContent(memPath);
|
|
10162
10490
|
if (!content) continue;
|
|
@@ -10233,11 +10561,7 @@ ${body}`;
|
|
|
10233
10561
|
registry.register({
|
|
10234
10562
|
name: "read_file",
|
|
10235
10563
|
parallelSafe: true,
|
|
10236
|
-
description: `Read a file under the sandbox root. Default
|
|
10237
|
-
- head: N \u2192 first N lines (cheap probe of imports / config head)
|
|
10238
|
-
- tail: N \u2192 last N lines (recent-tail of a log)
|
|
10239
|
-
- range: "A-B" \u2192 inclusive 1-indexed range (e.g. "120-180" around an edit site)
|
|
10240
|
-
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.`,
|
|
10564
|
+
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.`,
|
|
10241
10565
|
readOnly: true,
|
|
10242
10566
|
stormExempt: true,
|
|
10243
10567
|
parameters: {
|
|
@@ -10349,17 +10673,13 @@ ${slice.join("\n")}`);
|
|
|
10349
10673
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
10350
10674
|
lines.push(e.isDirectory() ? `${e.name}/` : e.name);
|
|
10351
10675
|
}
|
|
10352
|
-
return lines.join("\n") || "(empty directory)";
|
|
10676
|
+
return withDirMemory(abs, lines.join("\n") || "(empty directory)");
|
|
10353
10677
|
}
|
|
10354
10678
|
});
|
|
10355
10679
|
registry.register({
|
|
10356
10680
|
name: "directory_tree",
|
|
10357
10681
|
parallelSafe: true,
|
|
10358
|
-
description: `Recursively list entries
|
|
10359
|
-
- 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.
|
|
10360
|
-
- Skips ${[...SKIP_DIR_NAMES].sort().join(", ")} unless include_deps:true. Traversing into node_modules / .git / dist is almost always token-waste.
|
|
10361
|
-
- 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.
|
|
10362
|
-
Prefer \`list_directory\` for a single-level view, \`search_files\` to find specific paths, and \`search_content\` to find code.`,
|
|
10682
|
+
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.`,
|
|
10363
10683
|
readOnly: true,
|
|
10364
10684
|
parameters: {
|
|
10365
10685
|
type: "object",
|
|
@@ -10460,38 +10780,38 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
10460
10780
|
registry.register({
|
|
10461
10781
|
name: "search_content",
|
|
10462
10782
|
parallelSafe: true,
|
|
10463
|
-
description: "Recursively grep file CONTENTS for a substring or regex
|
|
10783
|
+
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.",
|
|
10464
10784
|
readOnly: true,
|
|
10465
10785
|
parameters: {
|
|
10466
10786
|
type: "object",
|
|
10467
10787
|
properties: {
|
|
10468
10788
|
pattern: {
|
|
10469
10789
|
type: "string",
|
|
10470
|
-
description: "Substring
|
|
10790
|
+
description: "Substring or regex."
|
|
10471
10791
|
},
|
|
10472
10792
|
path: {
|
|
10473
10793
|
type: "string",
|
|
10474
|
-
description: "
|
|
10794
|
+
description: "Search root (default: sandbox root)."
|
|
10475
10795
|
},
|
|
10476
10796
|
glob: {
|
|
10477
10797
|
type: "string",
|
|
10478
|
-
description: "
|
|
10798
|
+
description: "Filename filter. Glob when it contains `*`/`?`/`{`/`[`; otherwise substring. Patterns with `/` match the path relative to the search root."
|
|
10479
10799
|
},
|
|
10480
10800
|
case_sensitive: {
|
|
10481
10801
|
type: "boolean",
|
|
10482
|
-
description: "
|
|
10802
|
+
description: "Default false."
|
|
10483
10803
|
},
|
|
10484
10804
|
include_deps: {
|
|
10485
10805
|
type: "boolean",
|
|
10486
|
-
description: "
|
|
10806
|
+
description: "Also search node_modules / .git / dist / build / etc. Default off."
|
|
10487
10807
|
},
|
|
10488
10808
|
context: {
|
|
10489
10809
|
type: "integer",
|
|
10490
|
-
description: "Lines of context
|
|
10810
|
+
description: "Lines of context around each match (both sides). Default 0, capped 20. Ripgrep-style output."
|
|
10491
10811
|
},
|
|
10492
10812
|
summary_only: {
|
|
10493
10813
|
type: "boolean",
|
|
10494
|
-
description: "
|
|
10814
|
+
description: "Skip line content, return `rel: N matches` per file. Use for 'where does this exist at all' before drilling in."
|
|
10495
10815
|
}
|
|
10496
10816
|
},
|
|
10497
10817
|
required: ["pattern"]
|
|
@@ -10604,7 +10924,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
10604
10924
|
});
|
|
10605
10925
|
registry.register({
|
|
10606
10926
|
name: "multi_edit",
|
|
10607
|
-
description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in
|
|
10927
|
+
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.",
|
|
10608
10928
|
parameters: {
|
|
10609
10929
|
type: "object",
|
|
10610
10930
|
properties: {
|
|
@@ -10746,19 +11066,33 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
10746
11066
|
}
|
|
10747
11067
|
|
|
10748
11068
|
// src/tools/plan-core.ts
|
|
10749
|
-
var SUBMIT_PLAN_DESCRIPTION = "Submit ONE concrete plan
|
|
10750
|
-
var MARK_STEP_COMPLETE_DESCRIPTION = "Mark one
|
|
10751
|
-
var REVISE_PLAN_DESCRIPTION = "
|
|
11069
|
+
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.";
|
|
11070
|
+
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.";
|
|
11071
|
+
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.";
|
|
10752
11072
|
var STEP_ITEM_SCHEMA = {
|
|
10753
11073
|
type: "object",
|
|
10754
11074
|
properties: {
|
|
10755
11075
|
id: { type: "string", description: "Stable id, e.g. step-1." },
|
|
10756
11076
|
title: { type: "string", description: "Short imperative title." },
|
|
10757
|
-
action: { type: "string", description: "One-sentence
|
|
11077
|
+
action: { type: "string", description: "One-sentence concrete action." },
|
|
10758
11078
|
risk: {
|
|
10759
11079
|
type: "string",
|
|
10760
11080
|
enum: ["low", "med", "high"],
|
|
10761
|
-
description: "
|
|
11081
|
+
description: "high = hard-to-undo / prod / API break; med = reversible multi-file; low = safe local. Omit if unsure."
|
|
11082
|
+
},
|
|
11083
|
+
targets: {
|
|
11084
|
+
type: "array",
|
|
11085
|
+
description: "Optional. Files/dirs/modules this step touches.",
|
|
11086
|
+
items: { type: "string" }
|
|
11087
|
+
},
|
|
11088
|
+
acceptance: {
|
|
11089
|
+
type: "string",
|
|
11090
|
+
description: "Optional. One-sentence completion criterion."
|
|
11091
|
+
},
|
|
11092
|
+
verification: {
|
|
11093
|
+
type: "array",
|
|
11094
|
+
description: "Optional. Verification commands/checks for this step.",
|
|
11095
|
+
items: { type: "string" }
|
|
10762
11096
|
}
|
|
10763
11097
|
},
|
|
10764
11098
|
required: ["id", "title", "action"]
|
|
@@ -10780,10 +11114,42 @@ function sanitizeSteps(raw) {
|
|
|
10780
11114
|
const step = { id, title, action };
|
|
10781
11115
|
const risk = sanitizeRisk(e.risk);
|
|
10782
11116
|
if (risk) step.risk = risk;
|
|
11117
|
+
const targets = sanitizeStringList(e.targets);
|
|
11118
|
+
if (targets) step.targets = targets;
|
|
11119
|
+
const acceptance = typeof e.acceptance === "string" ? e.acceptance.trim() : "";
|
|
11120
|
+
if (acceptance) step.acceptance = acceptance;
|
|
11121
|
+
const verification = sanitizeStringList(e.verification);
|
|
11122
|
+
if (verification) step.verification = verification;
|
|
10783
11123
|
steps.push(step);
|
|
10784
11124
|
}
|
|
10785
11125
|
return steps.length > 0 ? steps : void 0;
|
|
10786
11126
|
}
|
|
11127
|
+
function sanitizeStringList(raw) {
|
|
11128
|
+
if (!Array.isArray(raw)) return void 0;
|
|
11129
|
+
const out = raw.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0);
|
|
11130
|
+
return out.length > 0 ? out : void 0;
|
|
11131
|
+
}
|
|
11132
|
+
function sanitizeEvidence(raw) {
|
|
11133
|
+
if (!Array.isArray(raw)) return void 0;
|
|
11134
|
+
const out = [];
|
|
11135
|
+
for (const item of raw) {
|
|
11136
|
+
if (!item || typeof item !== "object") continue;
|
|
11137
|
+
const e = item;
|
|
11138
|
+
const kind = e.kind;
|
|
11139
|
+
if (kind !== "verification" && kind !== "diff" && kind !== "checkpoint" && kind !== "manual") {
|
|
11140
|
+
continue;
|
|
11141
|
+
}
|
|
11142
|
+
const summary = typeof e.summary === "string" ? e.summary.trim() : "";
|
|
11143
|
+
if (!summary) continue;
|
|
11144
|
+
const evidence = { kind, summary };
|
|
11145
|
+
const command = typeof e.command === "string" ? e.command.trim() : "";
|
|
11146
|
+
if (command) evidence.command = command;
|
|
11147
|
+
const paths = sanitizeStringList(e.paths);
|
|
11148
|
+
if (paths) evidence.paths = paths;
|
|
11149
|
+
out.push(evidence);
|
|
11150
|
+
}
|
|
11151
|
+
return out.length > 0 ? out : void 0;
|
|
11152
|
+
}
|
|
10787
11153
|
function registerSubmitPlan(registry, opts) {
|
|
10788
11154
|
registry.register({
|
|
10789
11155
|
name: "submit_plan",
|
|
@@ -10794,16 +11160,16 @@ function registerSubmitPlan(registry, opts) {
|
|
|
10794
11160
|
properties: {
|
|
10795
11161
|
plan: {
|
|
10796
11162
|
type: "string",
|
|
10797
|
-
description: "Markdown
|
|
11163
|
+
description: "Markdown plan: one-line summary, file-by-file breakdown, risks/open questions."
|
|
10798
11164
|
},
|
|
10799
11165
|
steps: {
|
|
10800
11166
|
type: "array",
|
|
10801
|
-
description: "Structured step list
|
|
11167
|
+
description: "Structured step list \u2014 strongly recommended for >1 step. Stable ids (step-1, step-2, ...).",
|
|
10802
11168
|
items: STEP_ITEM_SCHEMA
|
|
10803
11169
|
},
|
|
10804
11170
|
summary: {
|
|
10805
11171
|
type: "string",
|
|
10806
|
-
description: "Optional
|
|
11172
|
+
description: "Optional ~80-char plan title for the picker header and /plans listings."
|
|
10807
11173
|
}
|
|
10808
11174
|
},
|
|
10809
11175
|
required: ["plan"]
|
|
@@ -10841,19 +11207,33 @@ function registerMarkStepComplete(registry, opts) {
|
|
|
10841
11207
|
properties: {
|
|
10842
11208
|
stepId: {
|
|
10843
11209
|
type: "string",
|
|
10844
|
-
description: "
|
|
11210
|
+
description: "Step id from submit_plan's steps array."
|
|
10845
11211
|
},
|
|
10846
11212
|
title: {
|
|
10847
11213
|
type: "string",
|
|
10848
|
-
description: "Optional.
|
|
11214
|
+
description: "Optional. Echoed for the UI; falls back to id."
|
|
10849
11215
|
},
|
|
10850
11216
|
result: {
|
|
10851
11217
|
type: "string",
|
|
10852
|
-
description: "One-sentence summary of what was done
|
|
11218
|
+
description: "One-sentence summary of what was done."
|
|
10853
11219
|
},
|
|
10854
11220
|
notes: {
|
|
10855
11221
|
type: "string",
|
|
10856
|
-
description: "Optional.
|
|
11222
|
+
description: "Optional. Surprises \u2014 blockers, revised assumptions, follow-ups."
|
|
11223
|
+
},
|
|
11224
|
+
evidence: {
|
|
11225
|
+
type: "array",
|
|
11226
|
+
description: "Optional. Verification summary / diff / checkpoint ref / manual note.",
|
|
11227
|
+
items: {
|
|
11228
|
+
type: "object",
|
|
11229
|
+
properties: {
|
|
11230
|
+
kind: { type: "string", enum: ["verification", "diff", "checkpoint", "manual"] },
|
|
11231
|
+
summary: { type: "string" },
|
|
11232
|
+
command: { type: "string" },
|
|
11233
|
+
paths: { type: "array", items: { type: "string" } }
|
|
11234
|
+
},
|
|
11235
|
+
required: ["kind", "summary"]
|
|
11236
|
+
}
|
|
10857
11237
|
}
|
|
10858
11238
|
},
|
|
10859
11239
|
required: ["stepId", "result"]
|
|
@@ -10871,9 +11251,15 @@ function registerMarkStepComplete(registry, opts) {
|
|
|
10871
11251
|
}
|
|
10872
11252
|
const title = typeof args?.title === "string" ? args.title.trim() || void 0 : void 0;
|
|
10873
11253
|
const notes = typeof args?.notes === "string" ? args.notes.trim() || void 0 : void 0;
|
|
11254
|
+
const evidence = sanitizeEvidence(args?.evidence);
|
|
11255
|
+
const evidenceReason = opts.requireStepEvidence?.({ stepId, title });
|
|
11256
|
+
if (evidenceReason && (!evidence || evidence.length === 0)) {
|
|
11257
|
+
throw new Error(`mark_step_complete: evidence required \u2014 ${evidenceReason}`);
|
|
11258
|
+
}
|
|
10874
11259
|
const update = { kind: "step_completed", stepId, result };
|
|
10875
11260
|
if (title) update.title = title;
|
|
10876
11261
|
if (notes) update.notes = notes;
|
|
11262
|
+
if (evidence) update.evidence = evidence;
|
|
10877
11263
|
opts.onStepCompleted?.(update);
|
|
10878
11264
|
const verdict = await (ctx?.confirmationGate ?? pauseGate).ask({
|
|
10879
11265
|
kind: "plan_checkpoint",
|
|
@@ -10898,16 +11284,16 @@ function registerRevisePlan(registry, opts) {
|
|
|
10898
11284
|
properties: {
|
|
10899
11285
|
reason: {
|
|
10900
11286
|
type: "string",
|
|
10901
|
-
description: "One sentence
|
|
11287
|
+
description: "One sentence \u2014 why you're revising / what the user asked for."
|
|
10902
11288
|
},
|
|
10903
11289
|
remainingSteps: {
|
|
10904
11290
|
type: "array",
|
|
10905
|
-
description: "
|
|
11291
|
+
description: "New tail of the plan. Reuse old ids when adjusting; new ids for new steps.",
|
|
10906
11292
|
items: STEP_ITEM_SCHEMA
|
|
10907
11293
|
},
|
|
10908
11294
|
summary: {
|
|
10909
11295
|
type: "string",
|
|
10910
|
-
description: "Optional. Updated one-line
|
|
11296
|
+
description: "Optional. Updated one-line summary when framing has shifted."
|
|
10911
11297
|
}
|
|
10912
11298
|
},
|
|
10913
11299
|
required: ["reason", "remainingSteps"]
|
|
@@ -10945,7 +11331,7 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
10945
11331
|
}
|
|
10946
11332
|
|
|
10947
11333
|
// src/tools/todo.ts
|
|
10948
|
-
var DESCRIPTION =
|
|
11334
|
+
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.";
|
|
10949
11335
|
function validateTodos(raw) {
|
|
10950
11336
|
if (!Array.isArray(raw)) {
|
|
10951
11337
|
throw new Error("todo_write: `todos` must be an array");
|
|
@@ -11571,4 +11957,4 @@ export {
|
|
|
11571
11957
|
he/he.js:
|
|
11572
11958
|
(*! https://mths.be/he v1.2.0 by @mathias | MIT license *)
|
|
11573
11959
|
*/
|
|
11574
|
-
//# sourceMappingURL=chunk-
|
|
11960
|
+
//# sourceMappingURL=chunk-JBH5RM7X.js.map
|