reasonix 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dashboard/dist/app.js +123 -16
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/{acp-4ROCGYNH.js → acp-WFQIC6SO.js} +52 -135
- package/dist/cli/acp-WFQIC6SO.js.map +1 -0
- package/dist/cli/chat-D32JGNVH.js +51 -0
- package/dist/cli/{chunk-S2RMQULY.js → chunk-23ZPCIPR.js} +12 -9
- package/dist/cli/chunk-23ZPCIPR.js.map +1 -0
- package/dist/cli/{chunk-TKVXTQ3T.js → chunk-3ZZXQ3CZ.js} +27 -27
- package/dist/cli/chunk-3ZZXQ3CZ.js.map +1 -0
- package/dist/cli/{chunk-5OHHAQ4W.js → chunk-7AST3QQ3.js} +2 -2
- package/dist/cli/{chunk-MRZG4GBF.js → chunk-7JTKBJ2G.js} +3 -3
- package/dist/cli/{chunk-X53B3JIX.js → chunk-7X4JJOO7.js} +2 -61
- package/dist/cli/{chunk-X53B3JIX.js.map → chunk-7X4JJOO7.js.map} +1 -1
- package/dist/cli/{chunk-MOJYKO2A.js → chunk-ASOLXV67.js} +3 -3
- package/dist/cli/{chunk-7M4YYMKW.js → chunk-AWEULQG6.js} +49 -56
- package/dist/cli/{chunk-7M4YYMKW.js.map → chunk-AWEULQG6.js.map} +1 -1
- package/dist/cli/{chunk-HR5NBKEM.js → chunk-DFX5ZH5L.js} +2 -2
- package/dist/cli/{chunk-3WGTGXO4.js → chunk-GNS7BAT2.js} +4 -4
- package/dist/cli/chunk-GNS7BAT2.js.map +1 -0
- package/dist/cli/{chunk-TE5UIIFL.js → chunk-J2IHQGPQ.js} +12 -6
- package/dist/cli/chunk-J2IHQGPQ.js.map +1 -0
- package/dist/cli/{chunk-I4M5QJNL.js → chunk-JGTX4RRQ.js} +3 -3
- package/dist/cli/{chunk-FY4S7TJZ.js → chunk-JNTMOX7G.js} +10 -2
- package/dist/cli/chunk-JNTMOX7G.js.map +1 -0
- package/dist/cli/{chunk-OB4BUJBL.js → chunk-MGTBP7GG.js} +5 -2
- package/dist/cli/chunk-MGTBP7GG.js.map +1 -0
- package/dist/cli/{chunk-OPYALNTT.js → chunk-MQWO32ZD.js} +387 -184
- package/dist/cli/chunk-MQWO32ZD.js.map +1 -0
- package/dist/cli/{chunk-2QSTA2QV.js → chunk-O5LIHAMP.js} +8 -4
- package/dist/cli/chunk-O5LIHAMP.js.map +1 -0
- package/dist/cli/{chunk-NMQSUNLB.js → chunk-PB3MAFEI.js} +6 -3
- package/dist/cli/chunk-PB3MAFEI.js.map +1 -0
- package/dist/cli/{chunk-H4CCXMDD.js → chunk-PEMG6CUB.js} +2 -2
- package/dist/cli/{chunk-RUDBUHO4.js → chunk-PXBQ6IZ7.js} +3 -3
- package/dist/cli/{chunk-J2TQAWOM.js → chunk-Q46B3Z7H.js} +25 -10
- package/dist/cli/{chunk-J2TQAWOM.js.map → chunk-Q46B3Z7H.js.map} +1 -1
- package/dist/cli/{chunk-6MZTZO7A.js → chunk-QF32ROX2.js} +2152 -2613
- package/dist/cli/chunk-QF32ROX2.js.map +1 -0
- package/dist/cli/{chunk-OG5JANQ4.js → chunk-QX5TWXRZ.js} +2 -2
- package/dist/cli/{chunk-V4Y732RQ.js → chunk-TAIKVL35.js} +2 -2
- package/dist/cli/{chunk-B5CZL2SE.js → chunk-TEDWJKEI.js} +4 -9
- package/dist/cli/chunk-TEDWJKEI.js.map +1 -0
- package/dist/cli/{chunk-EMMENC4O.js → chunk-U5XQDCK7.js} +5 -5
- package/dist/cli/{chunk-DOWEOA6E.js → chunk-W46ZMNKO.js} +3 -3
- package/dist/cli/{chunk-CDVSFSAK.js → chunk-WMTMMSXU.js} +184 -8
- package/dist/cli/chunk-WMTMMSXU.js.map +1 -0
- package/dist/cli/{chunk-YW63N3ZR.js → chunk-YEF7C4XI.js} +270 -96
- package/dist/cli/chunk-YEF7C4XI.js.map +1 -0
- package/dist/cli/{chunk-JMDE6IO3.js → chunk-ZAEJWKXB.js} +2 -2
- package/dist/cli/chunk-ZWHSHFDP.js +6173 -0
- package/dist/cli/chunk-ZWHSHFDP.js.map +1 -0
- package/dist/cli/{code-PMPJWXEO.js → code-R4IHI7SR.js} +30 -30
- package/dist/cli/{commands-QS6TG4G3.js → commands-DRHFCYMO.js} +4 -4
- package/dist/cli/{commit-XPRSKUBF.js → commit-AG5KB4YP.js} +3 -3
- package/dist/cli/{desktop-562OPWIU.js → desktop-JGL6GORA.js} +60 -23
- package/dist/cli/desktop-JGL6GORA.js.map +1 -0
- package/dist/cli/{diff-I6W4AUWJ.js → diff-4Z7ETWZO.js} +9 -9
- package/dist/cli/{doctor-6XVZKT4U.js → doctor-VA3RHQLB.js} +9 -9
- package/dist/cli/index.js +37 -36
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{mcp-7W7ANO2Y.js → mcp-LZO4HXFA.js} +34 -23
- package/dist/cli/mcp-LZO4HXFA.js.map +1 -0
- package/dist/cli/{mcp-browse-LA4I4YIZ.js → mcp-browse-C3GXVMYZ.js} +3 -3
- package/dist/cli/{mcp-inspect-LWXXU7BY.js → mcp-inspect-ZMYUNFDS.js} +2 -2
- package/dist/cli/{prompt-RKZD4X6Y.js → prompt-MC3U5KRP.js} +5 -4
- package/dist/cli/{prune-sessions-SEWX7GP6.js → prune-sessions-OEPFH4N6.js} +11 -7
- package/dist/cli/prune-sessions-OEPFH4N6.js.map +1 -0
- package/dist/cli/{replay-2X7MVXOI.js → replay-4TP7ZUMZ.js} +10 -10
- package/dist/cli/{run-TPKXIJ27.js → run-6MXQYBOE.js} +16 -15
- package/dist/cli/run-6MXQYBOE.js.map +1 -0
- package/dist/cli/{server-NHQ3QXOZ.js → server-Z3IMJNNI.js} +65 -12
- package/dist/cli/server-Z3IMJNNI.js.map +1 -0
- package/dist/cli/{sessions-2A4DGSHA.js → sessions-NXQ5SAV7.js} +18 -18
- package/dist/cli/sessions-NXQ5SAV7.js.map +1 -0
- package/dist/cli/{setup-GOLP7J4C.js → setup-LHZELI6I.js} +6 -6
- package/dist/cli/{stats-CGDAFDKI.js → stats-SUIJ3QWY.js} +6 -6
- package/dist/cli/{version-FIL4ZFOS.js → version-BIFONEUB.js} +13 -13
- package/dist/index.d.ts +71 -17
- package/dist/index.js +1040 -391
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/dist/cli/acp-4ROCGYNH.js.map +0 -1
- package/dist/cli/chat-GZNB5625.js +0 -51
- package/dist/cli/chunk-2QSTA2QV.js.map +0 -1
- package/dist/cli/chunk-3WGTGXO4.js.map +0 -1
- package/dist/cli/chunk-6MZTZO7A.js.map +0 -1
- package/dist/cli/chunk-B5CZL2SE.js.map +0 -1
- package/dist/cli/chunk-CDVSFSAK.js.map +0 -1
- package/dist/cli/chunk-FY4S7TJZ.js.map +0 -1
- package/dist/cli/chunk-NMQSUNLB.js.map +0 -1
- package/dist/cli/chunk-OB4BUJBL.js.map +0 -1
- package/dist/cli/chunk-OPYALNTT.js.map +0 -1
- package/dist/cli/chunk-S2RMQULY.js.map +0 -1
- package/dist/cli/chunk-TE5UIIFL.js.map +0 -1
- package/dist/cli/chunk-TKVXTQ3T.js.map +0 -1
- package/dist/cli/chunk-WZGNXR6E.js +0 -2020
- package/dist/cli/chunk-WZGNXR6E.js.map +0 -1
- package/dist/cli/chunk-YW63N3ZR.js.map +0 -1
- package/dist/cli/desktop-562OPWIU.js.map +0 -1
- package/dist/cli/mcp-7W7ANO2Y.js.map +0 -1
- package/dist/cli/prune-sessions-SEWX7GP6.js.map +0 -1
- package/dist/cli/run-TPKXIJ27.js.map +0 -1
- package/dist/cli/server-NHQ3QXOZ.js.map +0 -1
- package/dist/cli/sessions-2A4DGSHA.js.map +0 -1
- /package/dist/cli/{chat-GZNB5625.js.map → chat-D32JGNVH.js.map} +0 -0
- /package/dist/cli/{chunk-5OHHAQ4W.js.map → chunk-7AST3QQ3.js.map} +0 -0
- /package/dist/cli/{chunk-MRZG4GBF.js.map → chunk-7JTKBJ2G.js.map} +0 -0
- /package/dist/cli/{chunk-MOJYKO2A.js.map → chunk-ASOLXV67.js.map} +0 -0
- /package/dist/cli/{chunk-HR5NBKEM.js.map → chunk-DFX5ZH5L.js.map} +0 -0
- /package/dist/cli/{chunk-I4M5QJNL.js.map → chunk-JGTX4RRQ.js.map} +0 -0
- /package/dist/cli/{chunk-H4CCXMDD.js.map → chunk-PEMG6CUB.js.map} +0 -0
- /package/dist/cli/{chunk-RUDBUHO4.js.map → chunk-PXBQ6IZ7.js.map} +0 -0
- /package/dist/cli/{chunk-OG5JANQ4.js.map → chunk-QX5TWXRZ.js.map} +0 -0
- /package/dist/cli/{chunk-V4Y732RQ.js.map → chunk-TAIKVL35.js.map} +0 -0
- /package/dist/cli/{chunk-EMMENC4O.js.map → chunk-U5XQDCK7.js.map} +0 -0
- /package/dist/cli/{chunk-DOWEOA6E.js.map → chunk-W46ZMNKO.js.map} +0 -0
- /package/dist/cli/{chunk-JMDE6IO3.js.map → chunk-ZAEJWKXB.js.map} +0 -0
- /package/dist/cli/{code-PMPJWXEO.js.map → code-R4IHI7SR.js.map} +0 -0
- /package/dist/cli/{commands-QS6TG4G3.js.map → commands-DRHFCYMO.js.map} +0 -0
- /package/dist/cli/{commit-XPRSKUBF.js.map → commit-AG5KB4YP.js.map} +0 -0
- /package/dist/cli/{diff-I6W4AUWJ.js.map → diff-4Z7ETWZO.js.map} +0 -0
- /package/dist/cli/{doctor-6XVZKT4U.js.map → doctor-VA3RHQLB.js.map} +0 -0
- /package/dist/cli/{mcp-browse-LA4I4YIZ.js.map → mcp-browse-C3GXVMYZ.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-LWXXU7BY.js.map → mcp-inspect-ZMYUNFDS.js.map} +0 -0
- /package/dist/cli/{prompt-RKZD4X6Y.js.map → prompt-MC3U5KRP.js.map} +0 -0
- /package/dist/cli/{replay-2X7MVXOI.js.map → replay-4TP7ZUMZ.js.map} +0 -0
- /package/dist/cli/{setup-GOLP7J4C.js.map → setup-LHZELI6I.js.map} +0 -0
- /package/dist/cli/{stats-CGDAFDKI.js.map → stats-SUIJ3QWY.js.map} +0 -0
- /package/dist/cli/{version-FIL4ZFOS.js.map → version-BIFONEUB.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
import { createParser } from "eventsource-parser";
|
|
3
3
|
|
|
4
4
|
// src/config.ts
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
appendFileSync,
|
|
7
|
+
chmodSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
writeFileSync
|
|
12
|
+
} from "fs";
|
|
6
13
|
import { homedir } from "os";
|
|
7
14
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
8
15
|
import { z } from "zod";
|
|
@@ -429,6 +436,117 @@ function parseMcpSpec(input) {
|
|
|
429
436
|
return { transport: "stdio", name, command, args };
|
|
430
437
|
}
|
|
431
438
|
|
|
439
|
+
// src/tools/rate-limit.ts
|
|
440
|
+
var DEFAULT_TOOL_RATE_LIMIT = {
|
|
441
|
+
aggregate: { maxCalls: 200, windowSeconds: 60 },
|
|
442
|
+
tools: {
|
|
443
|
+
run_command: { maxCalls: 60, windowSeconds: 60 },
|
|
444
|
+
run_background: { maxCalls: 10, windowSeconds: 60 }
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
var ToolRateLimiter = class {
|
|
448
|
+
config;
|
|
449
|
+
clock;
|
|
450
|
+
aggregate = [];
|
|
451
|
+
tools = /* @__PURE__ */ new Map();
|
|
452
|
+
constructor(config = {}, clock = () => Date.now()) {
|
|
453
|
+
this.config = normalizeToolRateLimitConfig(config);
|
|
454
|
+
this.clock = clock;
|
|
455
|
+
}
|
|
456
|
+
get policy() {
|
|
457
|
+
return this.config;
|
|
458
|
+
}
|
|
459
|
+
consume(tool) {
|
|
460
|
+
if (this.config === false) return { allowed: true };
|
|
461
|
+
const now = this.clock();
|
|
462
|
+
const toolBucket = this.config.tools[tool];
|
|
463
|
+
if (toolBucket !== false && toolBucket !== void 0) {
|
|
464
|
+
const timestamps = this.timestampsFor(tool);
|
|
465
|
+
const blocked = inspectBucket(tool, timestamps, toolBucket, now);
|
|
466
|
+
if (blocked) return { allowed: false, result: blocked };
|
|
467
|
+
}
|
|
468
|
+
const aggregateBlocked = inspectBucket(
|
|
469
|
+
tool,
|
|
470
|
+
this.aggregate,
|
|
471
|
+
this.config.aggregate,
|
|
472
|
+
now,
|
|
473
|
+
"all_tools"
|
|
474
|
+
);
|
|
475
|
+
if (aggregateBlocked) return { allowed: false, result: aggregateBlocked };
|
|
476
|
+
this.aggregate.push(now);
|
|
477
|
+
if (toolBucket !== false && toolBucket !== void 0) this.timestampsFor(tool).push(now);
|
|
478
|
+
return { allowed: true };
|
|
479
|
+
}
|
|
480
|
+
timestampsFor(tool) {
|
|
481
|
+
const existing = this.tools.get(tool);
|
|
482
|
+
if (existing) return existing;
|
|
483
|
+
const created = [];
|
|
484
|
+
this.tools.set(tool, created);
|
|
485
|
+
return created;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
function normalizeToolRateLimitConfig(config) {
|
|
489
|
+
if (config === false || config?.enabled === false) return false;
|
|
490
|
+
const aggregate = normalizeBucket(config?.aggregate, DEFAULT_TOOL_RATE_LIMIT.aggregate);
|
|
491
|
+
const tools = {
|
|
492
|
+
...DEFAULT_TOOL_RATE_LIMIT.tools
|
|
493
|
+
};
|
|
494
|
+
for (const [name, value] of Object.entries(config?.tools ?? {})) {
|
|
495
|
+
if (value === false) {
|
|
496
|
+
tools[name] = false;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const fallback = DEFAULT_TOOL_RATE_LIMIT.tools[name];
|
|
500
|
+
tools[name] = normalizeBucket(
|
|
501
|
+
value,
|
|
502
|
+
fallback === false || fallback === void 0 ? DEFAULT_TOOL_RATE_LIMIT.aggregate : fallback
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return { aggregate, tools };
|
|
506
|
+
}
|
|
507
|
+
function parseRateLimitedToolResult(result) {
|
|
508
|
+
try {
|
|
509
|
+
const parsed = JSON.parse(result);
|
|
510
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
511
|
+
const value = parsed;
|
|
512
|
+
if (value.error !== "rate_limited") return null;
|
|
513
|
+
if (typeof value.tool !== "string" || typeof value.scope !== "string") return null;
|
|
514
|
+
if (typeof value.limit !== "number" || typeof value.windowSeconds !== "number") return null;
|
|
515
|
+
if (typeof value.retryAfterMs !== "number" || typeof value.message !== "string") return null;
|
|
516
|
+
return value;
|
|
517
|
+
} catch {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function normalizeBucket(raw, fallback) {
|
|
522
|
+
return {
|
|
523
|
+
maxCalls: positiveInteger(raw?.maxCalls) ?? fallback.maxCalls,
|
|
524
|
+
windowSeconds: positiveInteger(raw?.windowSeconds) ?? fallback.windowSeconds
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function positiveInteger(value) {
|
|
528
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
529
|
+
}
|
|
530
|
+
function inspectBucket(tool, timestamps, bucket, now, scope = tool) {
|
|
531
|
+
const windowMs = bucket.windowSeconds * 1e3;
|
|
532
|
+
while (timestamps.length > 0 && now - timestamps[0] >= windowMs) timestamps.shift();
|
|
533
|
+
if (timestamps.length < bucket.maxCalls) return null;
|
|
534
|
+
const retryAfterMs = Math.max(0, timestamps[0] + windowMs - now);
|
|
535
|
+
return {
|
|
536
|
+
error: "rate_limited",
|
|
537
|
+
tool,
|
|
538
|
+
scope,
|
|
539
|
+
limit: bucket.maxCalls,
|
|
540
|
+
windowSeconds: bucket.windowSeconds,
|
|
541
|
+
retryAfterMs,
|
|
542
|
+
message: `${scope} rate-limited: ${bucket.maxCalls} calls / ${bucket.windowSeconds}s. Wait ${formatWait(retryAfterMs)} or summarize what you know.`
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function formatWait(ms) {
|
|
546
|
+
const seconds = ms / 1e3;
|
|
547
|
+
return `${Number.isInteger(seconds) ? seconds.toFixed(0) : seconds.toFixed(1)}s`;
|
|
548
|
+
}
|
|
549
|
+
|
|
432
550
|
// src/config.ts
|
|
433
551
|
var BUILTIN_TYPE_DOCS = {
|
|
434
552
|
user: "role / skills / preferences",
|
|
@@ -466,12 +584,11 @@ function memoryTypeDefaults(typeName, cfg = readConfig()) {
|
|
|
466
584
|
if (found.expires) out.expires = found.expires;
|
|
467
585
|
return out;
|
|
468
586
|
}
|
|
469
|
-
var DEFAULT_METASO_API_KEY = "mk-E384C1DD5E8501BB7EFE27C949AFDE5B";
|
|
470
587
|
function loadMetasoApiKey(path2 = defaultConfigPath()) {
|
|
471
|
-
if (process.env.METASO_API_KEY) return process.env.METASO_API_KEY;
|
|
588
|
+
if (process.env.METASO_API_KEY) return process.env.METASO_API_KEY.trim();
|
|
472
589
|
const cfg = readConfig(path2).metasoApiKey;
|
|
473
590
|
if (cfg && typeof cfg === "string" && cfg.trim()) return cfg.trim();
|
|
474
|
-
return
|
|
591
|
+
return void 0;
|
|
475
592
|
}
|
|
476
593
|
function loadTavilyApiKey(path2 = defaultConfigPath()) {
|
|
477
594
|
if (process.env.TAVILY_API_KEY) return process.env.TAVILY_API_KEY.trim();
|
|
@@ -543,10 +660,30 @@ function readConfig(path2 = defaultConfigPath()) {
|
|
|
543
660
|
return {};
|
|
544
661
|
}
|
|
545
662
|
function writeConfig(cfg, path2 = defaultConfigPath()) {
|
|
663
|
+
debugLogConfigWrite(cfg, path2);
|
|
546
664
|
mkdirSync(dirname(path2), { recursive: true });
|
|
547
|
-
|
|
665
|
+
const tmp = `${path2}.${process.pid}.tmp`;
|
|
666
|
+
writeFileSync(tmp, JSON.stringify(cfg, null, 2), "utf8");
|
|
667
|
+
try {
|
|
668
|
+
chmodSync(tmp, 384);
|
|
669
|
+
} catch {
|
|
670
|
+
}
|
|
671
|
+
renameSync(tmp, path2);
|
|
672
|
+
}
|
|
673
|
+
function debugLogConfigWrite(cfg, configPath) {
|
|
674
|
+
const debugPath = process.env.REASONIX_DEBUG_PRESET;
|
|
675
|
+
if (!debugPath) return;
|
|
548
676
|
try {
|
|
549
|
-
|
|
677
|
+
const stack = new Error("trace").stack ?? "";
|
|
678
|
+
const keys = Object.keys(cfg).sort().join(",");
|
|
679
|
+
const presetField = cfg.preset === void 0 ? "(absent)" : JSON.stringify(cfg.preset);
|
|
680
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} writeConfig pid=${process.pid} \u2192 ${configPath}
|
|
681
|
+
keys=[${keys}]
|
|
682
|
+
preset=${presetField}
|
|
683
|
+
${stack.split("\n").slice(1, 10).map((l) => ` ${l.trim()}`).join("\n")}
|
|
684
|
+
|
|
685
|
+
`;
|
|
686
|
+
appendFileSync(debugPath, line);
|
|
550
687
|
} catch {
|
|
551
688
|
}
|
|
552
689
|
}
|
|
@@ -634,7 +771,7 @@ function webSearchEngine(path2 = defaultConfigPath()) {
|
|
|
634
771
|
if (cfg === "tavily") return "tavily";
|
|
635
772
|
if (cfg === "perplexity") return "perplexity";
|
|
636
773
|
if (cfg === "exa") return "exa";
|
|
637
|
-
return "
|
|
774
|
+
return "bing";
|
|
638
775
|
}
|
|
639
776
|
function webSearchEndpoint(path2 = defaultConfigPath()) {
|
|
640
777
|
const cfg = readConfig(path2).webSearchEndpoint;
|
|
@@ -669,6 +806,11 @@ function addProjectShellAllowed(rootDir, prefix, path2 = defaultConfigPath()) {
|
|
|
669
806
|
cfg.projects[key].shellAllowed = [...existing, trimmed];
|
|
670
807
|
writeConfig(cfg, path2);
|
|
671
808
|
}
|
|
809
|
+
function projectHooksTrusted(rootDir, path2 = defaultConfigPath()) {
|
|
810
|
+
const cfg = readConfig(path2);
|
|
811
|
+
const key = findProjectKey(cfg, rootDir);
|
|
812
|
+
return key !== void 0 && cfg.projects?.[key]?.hooksTrusted === true;
|
|
813
|
+
}
|
|
672
814
|
function loadProjectPathAllowed(rootDir, path2 = defaultConfigPath()) {
|
|
673
815
|
const cfg = readConfig(path2);
|
|
674
816
|
const key = findProjectKey(cfg, rootDir);
|
|
@@ -749,8 +891,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
749
891
|
}
|
|
750
892
|
function sleep(ms, signal) {
|
|
751
893
|
if (ms <= 0) return Promise.resolve();
|
|
752
|
-
return new Promise((
|
|
753
|
-
const timer = setTimeout(
|
|
894
|
+
return new Promise((resolve14, reject) => {
|
|
895
|
+
const timer = setTimeout(resolve14, ms);
|
|
754
896
|
if (signal) {
|
|
755
897
|
const onAbort = () => {
|
|
756
898
|
clearTimeout(timer);
|
|
@@ -838,8 +980,8 @@ var DeepSeekClient = class {
|
|
|
838
980
|
const waitMs = Math.max(0, this.nextChatRequestAt - now);
|
|
839
981
|
this.nextChatRequestAt = Math.max(now, this.nextChatRequestAt) + this.minChatIntervalMs;
|
|
840
982
|
if (waitMs <= 0) return;
|
|
841
|
-
await new Promise((
|
|
842
|
-
const timer = setTimeout(
|
|
983
|
+
await new Promise((resolve14, reject) => {
|
|
984
|
+
const timer = setTimeout(resolve14, waitMs);
|
|
843
985
|
signal?.addEventListener(
|
|
844
986
|
"abort",
|
|
845
987
|
() => {
|
|
@@ -913,8 +1055,11 @@ var DeepSeekClient = class {
|
|
|
913
1055
|
}
|
|
914
1056
|
async chat(opts) {
|
|
915
1057
|
const ctrl = new AbortController();
|
|
916
|
-
const timer = setTimeout(
|
|
917
|
-
|
|
1058
|
+
const timer = setTimeout(
|
|
1059
|
+
() => ctrl.abort(new Error(`DeepSeek request timed out after ${this.timeoutMs}ms`)),
|
|
1060
|
+
this.timeoutMs
|
|
1061
|
+
);
|
|
1062
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, ctrl.signal]) : ctrl.signal;
|
|
918
1063
|
try {
|
|
919
1064
|
await this.waitForChatRateLimit(signal);
|
|
920
1065
|
const resp = await fetchWithRetry(
|
|
@@ -949,8 +1094,11 @@ var DeepSeekClient = class {
|
|
|
949
1094
|
}
|
|
950
1095
|
async *stream(opts) {
|
|
951
1096
|
const ctrl = new AbortController();
|
|
952
|
-
const timer = setTimeout(
|
|
953
|
-
|
|
1097
|
+
const timer = setTimeout(
|
|
1098
|
+
() => ctrl.abort(new Error(`DeepSeek stream timed out after ${this.timeoutMs}ms`)),
|
|
1099
|
+
this.timeoutMs
|
|
1100
|
+
);
|
|
1101
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, ctrl.signal]) : ctrl.signal;
|
|
954
1102
|
let resp;
|
|
955
1103
|
try {
|
|
956
1104
|
await this.waitForChatRateLimit(signal);
|
|
@@ -1049,10 +1197,10 @@ var PauseGate = class {
|
|
|
1049
1197
|
`${kind}: no confirmation listener registered \u2014 cannot prompt the user. This tool can only be used inside an interactive Reasonix session.`
|
|
1050
1198
|
);
|
|
1051
1199
|
}
|
|
1052
|
-
return new Promise((
|
|
1200
|
+
return new Promise((resolve14) => {
|
|
1053
1201
|
const id = this._nextId++;
|
|
1054
1202
|
const request = { id, kind, payload };
|
|
1055
|
-
this._pending.set(id, { resolve:
|
|
1203
|
+
this._pending.set(id, { resolve: resolve14, request });
|
|
1056
1204
|
for (const fn of this._listeners) {
|
|
1057
1205
|
try {
|
|
1058
1206
|
fn(request);
|
|
@@ -1207,7 +1355,17 @@ var EN = {
|
|
|
1207
1355
|
missingApiKey: "DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
|
|
1208
1356
|
},
|
|
1209
1357
|
sessions: {
|
|
1210
|
-
emptyHint: "no saved sessions yet \u2014 run `reasonix chat` (sessions are auto-saved unless --no-session)."
|
|
1358
|
+
emptyHint: "no saved sessions yet \u2014 run `reasonix chat` (sessions are auto-saved unless --no-session).",
|
|
1359
|
+
listHeader: "Saved sessions (~/.reasonix/sessions/):",
|
|
1360
|
+
inspectHint: "Inspect: reasonix sessions <name>",
|
|
1361
|
+
resumeHint: "Resume: reasonix chat --session <name>",
|
|
1362
|
+
noSession: 'no session named "{name}" (or it\u2019s empty).',
|
|
1363
|
+
lookedAt: "looked at: {path}",
|
|
1364
|
+
noIdleSessions: "no sessions idle \u2265{days} days. Nothing pruned.",
|
|
1365
|
+
wouldPrune: "would prune {count} session(s) idle \u2265{days} days:",
|
|
1366
|
+
dryRunHint: "re-run without --dry-run to actually delete.",
|
|
1367
|
+
prunedCount: "pruned {count} session(s) idle \u2265{days} days:",
|
|
1368
|
+
daysInvalid: "--days must be a positive integer (got {days})."
|
|
1211
1369
|
},
|
|
1212
1370
|
ui: {
|
|
1213
1371
|
welcome: "Run `reasonix` any time to start chatting \u2014 your settings are remembered.",
|
|
@@ -1311,10 +1469,6 @@ var EN = {
|
|
|
1311
1469
|
title: "copy / paste",
|
|
1312
1470
|
rows: [
|
|
1313
1471
|
{ key: "select text", text: "drag to select \u2014 terminal-native (no modifier needed)" },
|
|
1314
|
-
{
|
|
1315
|
-
key: "/copy",
|
|
1316
|
-
text: "vim/tmux-style copy mode \u2014 works in SSH/mosh/tmux where drag-select can't extend past the viewport"
|
|
1317
|
-
},
|
|
1318
1472
|
{
|
|
1319
1473
|
key: "copy",
|
|
1320
1474
|
text: "Ctrl+Shift+C (Win/Linux) \xB7 Cmd+C (macOS) \u2014 or auto-copy-on-select if your terminal does it"
|
|
@@ -1405,9 +1559,6 @@ var EN = {
|
|
|
1405
1559
|
},
|
|
1406
1560
|
slash: {
|
|
1407
1561
|
help: { description: "show the full command reference" },
|
|
1408
|
-
copy: {
|
|
1409
|
-
description: "open vim/tmux-style copy mode \u2014 j/k navigate, v select, y yank to clipboard"
|
|
1410
|
-
},
|
|
1411
1562
|
status: { description: "current model, flags, context, session" },
|
|
1412
1563
|
preset: {
|
|
1413
1564
|
description: "model bundle \u2014 auto escalates flash \u2192 pro, flash/pro lock",
|
|
@@ -1559,8 +1710,8 @@ var EN = {
|
|
|
1559
1710
|
argsHint: "<question>"
|
|
1560
1711
|
},
|
|
1561
1712
|
"search-engine": {
|
|
1562
|
-
description: "switch web search backend \u2014
|
|
1563
|
-
argsHint: "<
|
|
1713
|
+
description: "switch web search backend \u2014 bing (default, works from CN without proxy), searxng (self-hosted), metaso (free 100/d), tavily (free 1000/mo), perplexity (AI-native), or exa (AI-native)",
|
|
1714
|
+
argsHint: "<bing|searxng|metaso|tavily|perplexity|exa> [<key>]"
|
|
1564
1715
|
}
|
|
1565
1716
|
},
|
|
1566
1717
|
wizard: {
|
|
@@ -1723,6 +1874,8 @@ var EN = {
|
|
|
1723
1874
|
verboseOn: "\u25B8 verbose mode on \u2014 full reasoning + tool output",
|
|
1724
1875
|
verboseOff: "\u25B8 verbose mode off \u2014 head/tail elision restored",
|
|
1725
1876
|
commandFailed: "! command failed",
|
|
1877
|
+
steerInjected: "\u25B8 steering queued \u2014 will be added after the current step",
|
|
1878
|
+
steerCommandRejected: "\u25B8 commands are disabled while steering a busy turn",
|
|
1726
1879
|
btwUsage: "\u25B8 /btw <question> \u2014 ask a side question without polluting the conversation context.",
|
|
1727
1880
|
btwHeader: "\u226B btw",
|
|
1728
1881
|
btwFailed: "/btw failed",
|
|
@@ -1766,7 +1919,11 @@ var EN = {
|
|
|
1766
1919
|
editHistoryHelpShow: "/show <id> \u2192 per-file summary \xB7 /show <id> <path> \u2192 full diff of one file",
|
|
1767
1920
|
editHistoryHelpUndo: "/undo \u2192 newest non-undone \xB7 /undo <id> [path] \u2192 target a specific batch or file",
|
|
1768
1921
|
editHistoryAlreadyReverted: "(already reverted \u2014 /history shows the batch-level status)",
|
|
1769
|
-
editHistoryRevertFile: "/undo {id} {path} \u2192 revert just this file"
|
|
1922
|
+
editHistoryRevertFile: "/undo {id} {path} \u2192 revert just this file",
|
|
1923
|
+
mcpFailed: "MCP {name} failed",
|
|
1924
|
+
mcpWarn: "MCP {name} warn",
|
|
1925
|
+
unknownTheme: "unknown theme: {name}\navailable: {choices}",
|
|
1926
|
+
themeSaved: "theme saved: {name}\nactive on next launch: {active}"
|
|
1770
1927
|
},
|
|
1771
1928
|
hooks: {
|
|
1772
1929
|
head: "hook {tag} `{cmd}` {decision}{truncTag}",
|
|
@@ -1789,9 +1946,9 @@ var EN = {
|
|
|
1789
1946
|
abortedAtIter: "aborted at iter {iter} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)",
|
|
1790
1947
|
toolUploadStatus: "tool result uploaded \xB7 model thinking before next response\u2026",
|
|
1791
1948
|
preflightTruncateStatus: "preflight: context near full, truncating oldest history\u2026",
|
|
1792
|
-
preflightTruncated: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) \u2014 truncated {beforeMessages} messages \u2192 {afterMessages}. Sending.",
|
|
1793
|
-
preflightTruncatedStillFull: "preflight: request still ~{estimate}/{ctxMax} tokens ({pct}%) after truncating {beforeMessages} messages \u2192 {afterMessages}. DeepSeek will likely 400. Run /clear or /new to start fresh.",
|
|
1794
|
-
preflightNoFold: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) and nothing left to truncate \u2014 DeepSeek will likely 400. Run /clear or /new to start fresh.",
|
|
1949
|
+
preflightTruncated: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) \xB7 body {bodyKB} KB \u2014 truncated {beforeMessages} messages \u2192 {afterMessages}. Sending.",
|
|
1950
|
+
preflightTruncatedStillFull: "preflight: request still ~{estimate}/{ctxMax} tokens ({pct}%) \xB7 body {bodyKB} KB after truncating {beforeMessages} messages \u2192 {afterMessages}. DeepSeek will likely 400. Run /clear or /new to start fresh.",
|
|
1951
|
+
preflightNoFold: "preflight: request ~{estimate}/{ctxMax} tokens ({pct}%) \xB7 body {bodyKB} KB and nothing left to truncate \u2014 DeepSeek will likely 400. Run /clear or /new to start fresh.",
|
|
1795
1952
|
flashEscalation: "\u21E7 flash requested escalation \u2014 retrying this turn on {model}{reasonSuffix}",
|
|
1796
1953
|
harvestStatus: "extracting plan state from reasoning\u2026",
|
|
1797
1954
|
repeatToolCallWarning: "Caught a repeated tool call \u2014 let the model see the issue and retry with a different approach.",
|
|
@@ -1810,6 +1967,7 @@ var EN = {
|
|
|
1810
1967
|
balance402: "Out of balance (DeepSeek 402): {inner}. Top up at https://platform.deepseek.com/top_up \u2014 the panel header shows your balance once it's non-zero.",
|
|
1811
1968
|
badparam422: "Invalid parameter (DeepSeek 422): {inner}",
|
|
1812
1969
|
badrequest400: "Bad request (DeepSeek 400): {inner}",
|
|
1970
|
+
concurrency429: "DeepSeek concurrency limit hit (429): {inner}. The account has too many in-flight requests (cap: 500 for v4-pro, 2500 for v4-flash, summed across API keys account-wide). Usually means another Reasonix process is sharing the same key, or a parallel subagent fan-out overshot. Wait a few seconds and retry, reduce parallelism, or request a higher cap at https://platform.deepseek.com.",
|
|
1813
1971
|
deepseek5xxHead: "DeepSeek service unavailable ({status}) \u2014 this is a DeepSeek-side problem, not Reasonix. Already retried 4\xD7 with backoff.",
|
|
1814
1972
|
deepseek5xxReachable: " DeepSeek's main API answered our health check, but /chat/completions is failing \u2014 partial outage on their side.",
|
|
1815
1973
|
deepseek5xxUnreachable: " DeepSeek API is unreachable from your network \u2014 could be a wider DS outage or a local network issue.",
|
|
@@ -2078,6 +2236,12 @@ var EN = {
|
|
|
2078
2236
|
statusMcp: " mcp {servers} server(s), {tools} tool(s) in registry",
|
|
2079
2237
|
statusEdits: " edits {count} pending (/apply to commit, /discard to drop)",
|
|
2080
2238
|
statusPlan: " plan ON \u2014 writes gated (submit_plan + approval)",
|
|
2239
|
+
statusLifecycle: " lifecycle {mode}/{state} \xB7 {progress}{evidence}",
|
|
2240
|
+
lifecycleNoPlan: "no plan",
|
|
2241
|
+
lifecycleEvidencePending: "evidence pending",
|
|
2242
|
+
lifecycleRejected: "lifecycle: {tool} blocked in {state} \u2014 next: {next}",
|
|
2243
|
+
lifecycleEvidenceRejected: "lifecycle: step {stepId} needs evidence \u2014 next: {next}",
|
|
2244
|
+
lifecycleRepeatedRejected: "lifecycle: repeated {tool} rejection \u2014 do not retry identical args",
|
|
2081
2245
|
statusModeYolo: " mode YOLO \u2014 edits + shell auto-run with no prompt (/undo still rolls back \xB7 Shift+Tab to flip)",
|
|
2082
2246
|
statusModeAuto: " mode AUTO \u2014 edits apply immediately (u to undo within 5s \xB7 Shift+Tab to flip)",
|
|
2083
2247
|
statusModeReview: " mode review \u2014 edits queue for /apply or y (Shift+Tab to flip)",
|
|
@@ -2089,6 +2253,9 @@ var EN = {
|
|
|
2089
2253
|
activeNone: "\u25B8 active plan: (none)",
|
|
2090
2254
|
noArchives: "no archived plans yet for this session \u2014 they auto-archive when every step is done",
|
|
2091
2255
|
archivedHeader: "Archived ({count}):",
|
|
2256
|
+
evidencePending: " ! evidence pending \u2014 current step needs verification/diff/checkpoint/manual evidence",
|
|
2257
|
+
evidenceLine: " evidence {stepId}: {summary}",
|
|
2258
|
+
archivedEvidenceLine: " evidence: {summary}",
|
|
2092
2259
|
replayNoSession: "no session attached \u2014 `/replay` is per-session. Run `reasonix code` in a project to get a session.",
|
|
2093
2260
|
replayNoArchives: "no archived plans yet for this session \u2014 `/replay` lights up once a plan completes (auto-archives when every step is done).",
|
|
2094
2261
|
replayInvalidIndex: "invalid index \u2014 `/replay` takes 1..{max} (newest = 1). Use `/plans` to see the list.",
|
|
@@ -2183,7 +2350,7 @@ var EN = {
|
|
|
2183
2350
|
currentEngine: "Current web search engine: {engine}",
|
|
2184
2351
|
endpoint: "SearXNG endpoint: {url}",
|
|
2185
2352
|
usageHeader: "Usage:",
|
|
2186
|
-
|
|
2353
|
+
usageBing: " /search-engine bing use Bing (default, works from CN without proxy)",
|
|
2187
2354
|
usageSearxng: " /search-engine searxng use SearXNG at default endpoint",
|
|
2188
2355
|
usageSearxngUrl: " /search-engine searxng <url> use SearXNG at custom endpoint",
|
|
2189
2356
|
usageMetaso: " /search-engine metaso use Metaso API (100/d free, configure your own API key for more)",
|
|
@@ -2248,7 +2415,8 @@ var EN = {
|
|
|
2248
2415
|
evt: " evt",
|
|
2249
2416
|
editsLabel: "edits:",
|
|
2250
2417
|
mcpLoading: "MCP",
|
|
2251
|
-
ctx: "ctx"
|
|
2418
|
+
ctx: "ctx",
|
|
2419
|
+
shortcutsHint: "Ctrl+P shortcuts"
|
|
2252
2420
|
},
|
|
2253
2421
|
editMode: {
|
|
2254
2422
|
plan: "PLAN MODE",
|
|
@@ -2278,7 +2446,9 @@ var EN = {
|
|
|
2278
2446
|
editorFailed: "external editor:",
|
|
2279
2447
|
editorMissing: "no $EDITOR / $VISUAL / $GIT_EDITOR set \u2014 export one (e.g. `export EDITOR=nano`) and retry",
|
|
2280
2448
|
editorExited: "editor exited with code {code}",
|
|
2281
|
-
typeaheadStaged: "\u25B8 {count} line(s) staged \xB7 esc recall"
|
|
2449
|
+
typeaheadStaged: "\u25B8 {count} line(s) staged \xB7 esc recall",
|
|
2450
|
+
steerPlaceholder: "type to steer the current task \u2014 commands are disabled while busy",
|
|
2451
|
+
steerHint: "send \u2014 injected mid-turn"
|
|
2282
2452
|
},
|
|
2283
2453
|
pathConfirm: {
|
|
2284
2454
|
title: "Outside-sandbox path",
|
|
@@ -2297,7 +2467,13 @@ var EN = {
|
|
|
2297
2467
|
denyDesc: "press Tab to add context telling the model why",
|
|
2298
2468
|
pathLabel: "path",
|
|
2299
2469
|
sandboxLabel: "sandbox",
|
|
2300
|
-
allowPrefixLabel: "prefix"
|
|
2470
|
+
allowPrefixLabel: "prefix",
|
|
2471
|
+
promptTitleRead: "Access path \u2014 read",
|
|
2472
|
+
promptTitleWrite: "Access path \u2014 write",
|
|
2473
|
+
actionAllowRead: "Allow read",
|
|
2474
|
+
actionAllowWrite: "Allow write",
|
|
2475
|
+
actionAlwaysAllow: "Always allow \u2014 {prefix}",
|
|
2476
|
+
actionDeny: "Deny"
|
|
2301
2477
|
},
|
|
2302
2478
|
shellConfirm: {
|
|
2303
2479
|
title: "Shell command",
|
|
@@ -2319,7 +2495,12 @@ var EN = {
|
|
|
2319
2495
|
timeoutLabel: "timeout",
|
|
2320
2496
|
waitLabel: "wait",
|
|
2321
2497
|
previewMore: "\u2026 {n} more line hidden \u2014 press esc, ask the model to split it",
|
|
2322
|
-
previewMorePlural: "\u2026 {n} more lines hidden \u2014 press esc, ask the model to split it"
|
|
2498
|
+
previewMorePlural: "\u2026 {n} more lines hidden \u2014 press esc, ask the model to split it",
|
|
2499
|
+
promptTitleRunCommand: "Run command",
|
|
2500
|
+
promptTitleRunBackground: "Run background command",
|
|
2501
|
+
actionRunOnce: "Run once",
|
|
2502
|
+
actionAlwaysAllow: "Always allow \u2014 {prefix}",
|
|
2503
|
+
actionDeny: "Deny"
|
|
2323
2504
|
},
|
|
2324
2505
|
editConfirm: {
|
|
2325
2506
|
footer: "[y/Enter] apply \xB7 [n] reject with reason \xB7 [a] apply rest \xB7 [A] flip AUTO \xB7 [\u2191\u2193/Space] scroll \xB7 [Esc] abort",
|
|
@@ -2460,36 +2641,37 @@ var EN = {
|
|
|
2460
2641
|
probeFailed: "probe failed \u2014 {message}"
|
|
2461
2642
|
},
|
|
2462
2643
|
webErrors: {
|
|
2463
|
-
status: "web_search {status} \u2014 try: the search backend returned an error; rephrase the query, or switch engine with /search-engine
|
|
2644
|
+
status: "web_search {status} \u2014 try: the search backend returned an error; rephrase the query, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2464
2645
|
rateLimit429: "web_search 429 \u2014 try: wait 10s before retrying, or rephrase the query; the search backend is rate-limiting this client",
|
|
2465
|
-
forbidden403: "web_search 403 \u2014 try: the search backend is blocking this client; switch engine with /search-engine
|
|
2646
|
+
forbidden403: "web_search 403 \u2014 try: the search backend is blocking this client; switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa, or wait and retry later",
|
|
2466
2647
|
serverError5xx: "web_search {status} \u2014 try: open the search URL in a browser; if it loads this is transient and a retry in 30s may help",
|
|
2467
|
-
|
|
2468
|
-
|
|
2648
|
+
bingBlocked: "web_search: Bing anti-bot page \u2014 rate-limited or blocked \u2014 try: wait 30s and retry, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2649
|
+
bingNoResults: "web_search: 0 results but response doesn't look like a real empty page ({chars} chars, first 120: {preview}) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2469
2650
|
invalidEndpoint: 'web_search: invalid SearXNG endpoint "{endpoint}" \u2014 try: set a valid URL with /search-endpoint http://host:port',
|
|
2470
2651
|
endpointMustBeHttp: "web_search: SearXNG endpoint must be http(s), got {protocol} \u2014 try: set a valid URL with /search-endpoint http://host:port",
|
|
2471
|
-
cannotReach: "web_search: Cannot reach SearXNG server at {endpoint} \u2014 try: install and start SearXNG (https://github.com/searxng/searxng, e.g. `docker run -d -p 8080:8080 searxng/searxng`), or switch to another engine with /search-engine
|
|
2472
|
-
searxngNoResults: "web_search: 0 results but SearXNG response doesn't look like an empty results page ({chars} chars) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine
|
|
2473
|
-
|
|
2652
|
+
cannotReach: "web_search: Cannot reach SearXNG server at {endpoint} \u2014 try: install and start SearXNG (https://github.com/searxng/searxng, e.g. `docker run -d -p 8080:8080 searxng/searxng`), or switch to another engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2653
|
+
searxngNoResults: "web_search: 0 results but SearXNG response doesn't look like an empty results page ({chars} chars) \u2014 try: rephrase the query with simpler terms, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2654
|
+
metasoMissingKey: "web_search: Metaso requires an API key \u2014 set METASO_API_KEY or configure one with /search-engine metaso <key>. Get one at https://metaso.cn/search-api/playground",
|
|
2655
|
+
metasoDailyLimit: "web_search: Metaso daily search limit reached \u2014 set METASO_API_KEY or get a key at https://metaso.cn/search-api/playground",
|
|
2474
2656
|
metasoUnauthorized: "web_search: Metaso API key rejected \u2014 check METASO_API_KEY or get one at https://metaso.cn/search-api/playground",
|
|
2475
2657
|
metasoRateLimit: "web_search: Metaso rate-limited \u2014 wait and retry, or get your own API key at https://metaso.cn/search-api/playground",
|
|
2476
|
-
metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine
|
|
2658
|
+
metasoServerError: "web_search: Metaso server error ({status}) \u2014 try again later, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2477
2659
|
metasoParseError: "web_search: Metaso returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2478
2660
|
metasoApiError: "web_search: Metaso API error (code {code}: {message}) \u2014 try again later",
|
|
2479
2661
|
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",
|
|
2480
2662
|
tavilyUnauthorized: "web_search: Tavily API key rejected \u2014 check TAVILY_API_KEY or get one at https://tavily.com",
|
|
2481
|
-
tavilyRateLimit: "web_search: Tavily rate-limited or monthly quota exceeded \u2014 wait, switch engine with /search-engine
|
|
2482
|
-
tavilyServerError: "web_search: Tavily server error ({status}) \u2014 try again later, or switch engine with /search-engine
|
|
2663
|
+
tavilyRateLimit: "web_search: Tavily rate-limited or monthly quota exceeded \u2014 wait, switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa, or upgrade your Tavily plan",
|
|
2664
|
+
tavilyServerError: "web_search: Tavily server error ({status}) \u2014 try again later, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2483
2665
|
tavilyParseError: "web_search: Tavily returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2484
2666
|
perplexityMissingKey: "web_search: Perplexity backend requires an API key \u2014 set PERPLEXITY_API_KEY env var or `perplexityApiKey` in ~/.reasonix/config.json; get one at https://perplexity.ai/settings/api",
|
|
2485
2667
|
perplexityUnauthorized: "web_search: Perplexity API key rejected \u2014 check PERPLEXITY_API_KEY or get one at https://perplexity.ai/settings/api",
|
|
2486
|
-
perplexityRateLimit: "web_search: Perplexity rate-limited \u2014 wait and retry, or switch engine with /search-engine
|
|
2487
|
-
perplexityServerError: "web_search: Perplexity server error ({status}) \u2014 try again later, or switch engine with /search-engine
|
|
2668
|
+
perplexityRateLimit: "web_search: Perplexity rate-limited \u2014 wait and retry, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2669
|
+
perplexityServerError: "web_search: Perplexity server error ({status}) \u2014 try again later, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2488
2670
|
perplexityParseError: "web_search: Perplexity returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2489
2671
|
exaMissingKey: "web_search: Exa backend requires an API key \u2014 set EXA_API_KEY env var or `exaApiKey` in ~/.reasonix/config.json; free 1000/mo signup at https://exa.ai",
|
|
2490
2672
|
exaUnauthorized: "web_search: Exa API key rejected \u2014 check EXA_API_KEY or get one at https://exa.ai",
|
|
2491
2673
|
exaRateLimit: "web_search: Exa API rate-limited or monthly quota exceeded \u2014 wait or upgrade at https://exa.ai/pricing",
|
|
2492
|
-
exaServerError: "web_search: Exa server error ({status}) \u2014 try again later, or switch engine with /search-engine
|
|
2674
|
+
exaServerError: "web_search: Exa server error ({status}) \u2014 try again later, or switch engine with /search-engine bing|searxng|metaso|tavily|perplexity|exa",
|
|
2493
2675
|
exaParseError: "web_search: Exa returned unparseable response (HTTP {status}) \u2014 try again later",
|
|
2494
2676
|
fetchStatus: "web_fetch {status} for {url} \u2014 try: confirm the URL resolves in a browser; status suggests the host returned an error page",
|
|
2495
2677
|
fetchRateLimit429: "web_fetch 429 for {url} \u2014 try: wait 10s before retrying; the host is rate-limiting this client",
|
|
@@ -2581,19 +2763,6 @@ var EN = {
|
|
|
2581
2763
|
categoryProject: "project",
|
|
2582
2764
|
categoryReference: "reference"
|
|
2583
2765
|
},
|
|
2584
|
-
copyMode: {
|
|
2585
|
-
title: "\u2500\u2500 COPY MODE \u2500\u2500",
|
|
2586
|
-
help: "j/k or \u2191/\u2193 move \xB7 v select \xB7 y yank \xB7 g/G top/bottom \xB7 q quit",
|
|
2587
|
-
statusBar: "line {cur}/{total} \xB7 selection: {sel}",
|
|
2588
|
-
statusYanked: "yanked {size} chars (osc52={osc52})",
|
|
2589
|
-
statusEmpty: "nothing selected",
|
|
2590
|
-
empty: "(no chat content yet \u2014 say something to the model first)",
|
|
2591
|
-
labelUser: "you",
|
|
2592
|
-
labelAssistant: "assistant",
|
|
2593
|
-
labelReasoning: "reasoning",
|
|
2594
|
-
yankedToast: "\u25B8 copied {size} chars to clipboard (osc52)",
|
|
2595
|
-
yankedToastFile: "\u25B8 copied {size} chars \xB7 file: {path}"
|
|
2596
|
-
},
|
|
2597
2766
|
mcpHealth: {
|
|
2598
2767
|
noData: "no inspect data",
|
|
2599
2768
|
healthy: "healthy \xB7 {ms}ms",
|
|
@@ -2662,7 +2831,13 @@ var EN = {
|
|
|
2662
2831
|
noResources: "No resources on any connected MCP server (or no servers connected). `/mcp` shows the current set.",
|
|
2663
2832
|
readOne: "Read one: `/resource <uri>` \u2014 or use Tab in the picker.",
|
|
2664
2833
|
noPrompts: "No prompts on any connected MCP server (or no servers connected). `/mcp` shows the current set.",
|
|
2665
|
-
fetchOne: "Fetch one: `/prompt <name>` \u2014 args are not supported yet; prompts with required args will surface an error from the server."
|
|
2834
|
+
fetchOne: "Fetch one: `/prompt <name>` \u2014 args are not supported yet; prompts with required args will surface an error from the server.",
|
|
2835
|
+
noServerForResource: 'no server exposes resource "{name}"',
|
|
2836
|
+
resourceHint: "`/resource` with no arg lists what's available.",
|
|
2837
|
+
readFailed: "readResource failed",
|
|
2838
|
+
noServerForPrompt: 'no server exposes prompt "{name}"',
|
|
2839
|
+
promptHint: "`/prompt` with no arg lists what's available.",
|
|
2840
|
+
fetchFailed: "getPrompt failed"
|
|
2666
2841
|
},
|
|
2667
2842
|
mcpLifecycle: {
|
|
2668
2843
|
handshake: "handshake\u2026",
|
|
@@ -2675,7 +2850,9 @@ var EN = {
|
|
|
2675
2850
|
disabledDetail: "via /mcp disable {name}",
|
|
2676
2851
|
failedSetupHint: "\u2192 run `reasonix setup` to remove this entry, or fix the underlying issue (missing npm package, network, etc.).",
|
|
2677
2852
|
failedSetupConfigHint: "\u2192 run `reasonix setup` to remove broken entries from your saved config.",
|
|
2678
|
-
abortedHint: "MCP startup aborted \u2014 {count} server(s) skipped. Run /mcp to retry once you've fixed the underlying issue."
|
|
2853
|
+
abortedHint: "MCP startup aborted \u2014 {count} server(s) skipped. Run /mcp to retry once you've fixed the underlying issue.",
|
|
2854
|
+
toolsReady: "tools ready",
|
|
2855
|
+
warnLabel: "warn"
|
|
2679
2856
|
},
|
|
2680
2857
|
checkpointPicker: {
|
|
2681
2858
|
title: "restore a checkpoint \u2014 {workspace}",
|
|
@@ -2721,6 +2898,64 @@ var EN = {
|
|
|
2721
2898
|
noRecords: "no records",
|
|
2722
2899
|
untracked: "(untracked)",
|
|
2723
2900
|
churned: "(churned \xD7{count})"
|
|
2901
|
+
},
|
|
2902
|
+
builtinSkills: {
|
|
2903
|
+
explore: "Explore the codebase in an isolated subagent \u2014 wide-net read-only investigation that returns one distilled answer. Best for: 'find all places that\u2026', 'how does X work across the project', 'survey the code for Y'.",
|
|
2904
|
+
research: "Research a question by combining web search + code reading in an isolated subagent. Best for: 'is X feature supported by lib Y', 'what\u2019s the canonical way to do Z', 'compare our impl against the spec'.",
|
|
2905
|
+
review: "Review the pending changes (current branch diff by default) in an isolated subagent \u2014 flags correctness, security, missing tests, hidden behavior changes; reports verdict + per-issue file:line. Read-only; the parent decides what to act on.",
|
|
2906
|
+
securityReview: "Security-focused review of the current branch diff in an isolated subagent \u2014 flags injection/authz/secrets/deserialization/path-traversal/crypto issues, severity-tagged. Read-only. Use when shipping changes that touch auth, input parsing, file IO, or external requests.",
|
|
2907
|
+
test: "Run the project\u2019s test suite, diagnose failures, propose SEARCH/REPLACE fixes, re-run until green (or stop after 2 fix attempts on the same failure). Inlined \u2014 runs in the parent loop so you see the edit blocks and can /apply them. Detects npm/pnpm/yarn/pytest/go/cargo."
|
|
2908
|
+
},
|
|
2909
|
+
shortcutsHelp: {
|
|
2910
|
+
title: "Shortcuts",
|
|
2911
|
+
groupInput: "Input",
|
|
2912
|
+
groupNavigation: "Navigation",
|
|
2913
|
+
groupSession: "Session",
|
|
2914
|
+
groupSystem: "System",
|
|
2915
|
+
descEnter: "Send message",
|
|
2916
|
+
descShiftEnter: "New line",
|
|
2917
|
+
descCtrlEnter: "New line",
|
|
2918
|
+
descCtrlJ: "New line",
|
|
2919
|
+
descCtrlU: "Clear input",
|
|
2920
|
+
descCtrlW: "Delete word",
|
|
2921
|
+
descCtrlP: "Show/hide shortcuts",
|
|
2922
|
+
descCtrlX: "Open in editor",
|
|
2923
|
+
descArrows: "Input history",
|
|
2924
|
+
descPgUpDown: "Scroll page",
|
|
2925
|
+
descCtrlL: "Clear screen",
|
|
2926
|
+
descCtrlB: "Toggle sidebar",
|
|
2927
|
+
descNewSession: "New session",
|
|
2928
|
+
descListSessions: "List sessions",
|
|
2929
|
+
descSwitchModel: "Switch model",
|
|
2930
|
+
descSwitchPreset: "Switch preset",
|
|
2931
|
+
descSwitchTheme: "Switch theme",
|
|
2932
|
+
descCtrlC: "Quit",
|
|
2933
|
+
descEsc: "Stop / Cancel",
|
|
2934
|
+
descCtrlR: "Toggle verbose",
|
|
2935
|
+
descCtrlO: "Expand reply (streaming only)",
|
|
2936
|
+
descHelp: "Show all commands",
|
|
2937
|
+
descShiftTab: "Switch edit mode"
|
|
2938
|
+
},
|
|
2939
|
+
mcpCli: {
|
|
2940
|
+
bundledCatalog: "Bundled MCP servers (offline catalog):",
|
|
2941
|
+
justFetched: "just fetched",
|
|
2942
|
+
cachedAge: "cached, {age}",
|
|
2943
|
+
moreAvailable: "more available",
|
|
2944
|
+
allLoaded: "all loaded",
|
|
2945
|
+
morePagesAvailable: "\u25B8 more pages available \u2014 `reasonix mcp list --pages <n>` or --all",
|
|
2946
|
+
installHint: "Install: reasonix mcp install <name>",
|
|
2947
|
+
usageSearch: "usage: reasonix mcp search <query>",
|
|
2948
|
+
usageInstall: "usage: reasonix mcp install <name>",
|
|
2949
|
+
noMatchesFor: 'No matches for "{q}" across {count} loaded entries ({source})',
|
|
2950
|
+
matchCount: '{count} match(es) for "{q}" in {source} registry ({loaded} entries scanned):',
|
|
2951
|
+
moreLoaded: "\u2026 {count} more loaded \u2014 use `reasonix mcp search <query>` to filter",
|
|
2952
|
+
moreMatches: "\u2026 {count} more matches",
|
|
2953
|
+
installed: "Installed: {spec}",
|
|
2954
|
+
noServerFound: 'No MCP server named "{target}" found after walking {pages} page(s) of the {source} registry.',
|
|
2955
|
+
noServerTryMore: "Try: reasonix mcp install {target} --max-pages 100",
|
|
2956
|
+
noInstallMeta: 'Could not derive install metadata for "{name}" \u2014 try `npx -y @smithery/cli install {name}` directly.',
|
|
2957
|
+
buildSpecFailed: "Cannot build install spec for {name}: {message}",
|
|
2958
|
+
alreadyInstalled: "Already installed: {spec}"
|
|
2724
2959
|
}
|
|
2725
2960
|
};
|
|
2726
2961
|
|
|
@@ -2767,7 +3002,17 @@ var zhCN = {
|
|
|
2767
3002
|
missingApiKey: "\u672A\u8BBE\u7F6E DEEPSEEK_API_KEY \u4E14\u6807\u51C6\u8F93\u5165\u4E0D\u662F TTY\uFF08\u65E0\u6CD5\u4EA4\u4E92\u5F0F\u8F93\u5165\uFF09\u3002\n\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5148\u8FD0\u884C `reasonix chat` \u4EA4\u4E92\u4E00\u6B21\u4EE5\u4FDD\u5B58\u5BC6\u94A5\u3002\n"
|
|
2768
3003
|
},
|
|
2769
3004
|
sessions: {
|
|
2770
|
-
emptyHint: "\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD \u2014 \u8FD0\u884C `reasonix chat`\uFF08\u4F1A\u8BDD\u4F1A\u81EA\u52A8\u4FDD\u5B58\uFF0C\u9664\u975E\u4F7F\u7528\u4E86 --no-session\uFF09\u3002"
|
|
3005
|
+
emptyHint: "\u6682\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD \u2014 \u8FD0\u884C `reasonix chat`\uFF08\u4F1A\u8BDD\u4F1A\u81EA\u52A8\u4FDD\u5B58\uFF0C\u9664\u975E\u4F7F\u7528\u4E86 --no-session\uFF09\u3002",
|
|
3006
|
+
listHeader: "\u4FDD\u5B58\u7684\u4F1A\u8BDD (~/.reasonix/sessions/)\uFF1A",
|
|
3007
|
+
inspectHint: "\u67E5\u770B\uFF1Areasonix sessions <name>",
|
|
3008
|
+
resumeHint: "\u6062\u590D\uFF1Areasonix chat --session <name>",
|
|
3009
|
+
noSession: '\u627E\u4E0D\u5230\u4F1A\u8BDD "{name}"\uFF08\u6216\u4E3A\u7A7A\uFF09\u3002',
|
|
3010
|
+
lookedAt: "\u4F4D\u7F6E\uFF1A{path}",
|
|
3011
|
+
noIdleSessions: "\u6CA1\u6709\u95F2\u7F6E \u2265{days} \u5929\u7684\u4F1A\u8BDD\u3002\u65E0\u9700\u6E05\u7406\u3002",
|
|
3012
|
+
wouldPrune: "\u5C06\u6E05\u7406 {count} \u4E2A\u95F2\u7F6E \u2265{days} \u5929\u7684\u4F1A\u8BDD\uFF1A",
|
|
3013
|
+
dryRunHint: "\u53BB\u6389 --dry-run \u53EF\u5B9E\u9645\u6267\u884C\u5220\u9664\u3002",
|
|
3014
|
+
prunedCount: "\u5DF2\u6E05\u7406 {count} \u4E2A\u95F2\u7F6E \u2265{days} \u5929\u7684\u4F1A\u8BDD\uFF1A",
|
|
3015
|
+
daysInvalid: "--days \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08\u4F20\u5165\u4E86 {days}\uFF09\u3002"
|
|
2771
3016
|
},
|
|
2772
3017
|
ui: {
|
|
2773
3018
|
welcome: "\u968F\u65F6\u8FD0\u884C `reasonix` \u5F00\u59CB\u804A\u5929 \u2014 \u60A8\u7684\u8BBE\u7F6E\u5C06\u88AB\u8BB0\u4F4F\u3002",
|
|
@@ -2865,10 +3110,6 @@ var zhCN = {
|
|
|
2865
3110
|
title: "\u590D\u5236 / \u7C98\u8D34",
|
|
2866
3111
|
rows: [
|
|
2867
3112
|
{ key: "\u9009\u4E2D\u6587\u5B57", text: "\u76F4\u63A5\u62D6\u52A8 \u2014 \u7EC8\u7AEF\u539F\u751F\uFF08\u4E0D\u9700\u8981\u4EFB\u4F55\u4FEE\u9970\u952E\uFF09" },
|
|
2868
|
-
{
|
|
2869
|
-
key: "/copy",
|
|
2870
|
-
text: "vim/tmux \u98CE\u683C\u590D\u5236\u6A21\u5F0F \u2014 SSH / mosh / tmux \u4E0B\u62D6\u9009\u8D8A\u8FC7\u53EF\u89C6\u533A\u65E0\u6548\u65F6\u7528\u8FD9\u4E2A"
|
|
2871
|
-
},
|
|
2872
3113
|
{
|
|
2873
3114
|
key: "\u590D\u5236",
|
|
2874
3115
|
text: "Ctrl+Shift+C\uFF08Win/Linux\uFF09\xB7 Cmd+C\uFF08macOS\uFF09\u2014 \u6216\u9009\u4E2D\u5373\u590D\u5236\uFF08\u770B\u7EC8\u7AEF\u8BBE\u7F6E\uFF09"
|
|
@@ -2959,9 +3200,6 @@ var zhCN = {
|
|
|
2959
3200
|
},
|
|
2960
3201
|
slash: {
|
|
2961
3202
|
help: { description: "\u663E\u793A\u5B8C\u6574\u547D\u4EE4\u53C2\u8003" },
|
|
2962
|
-
copy: {
|
|
2963
|
-
description: "\u8FDB\u5165 vim/tmux \u98CE\u683C\u590D\u5236\u6A21\u5F0F \u2014 j/k \u79FB\u52A8\u3001v \u8D77\u9009\u533A\u3001y \u590D\u5236\u5230\u526A\u8D34\u677F"
|
|
2964
|
-
},
|
|
2965
3203
|
status: { description: "\u5F53\u524D\u6A21\u578B\u3001\u6807\u5FD7\u3001\u4E0A\u4E0B\u6587\u3001\u4F1A\u8BDD" },
|
|
2966
3204
|
preset: {
|
|
2967
3205
|
description: "\u6A21\u578B\u7EC4\u5408 \u2014 \u81EA\u52A8\u5728 flash \u2192 pro \u4E4B\u95F4\u5207\u6362\uFF0C\u6216\u9501\u5B9A flash/pro",
|
|
@@ -3117,8 +3355,8 @@ var zhCN = {
|
|
|
3117
3355
|
argsHint: "<question>"
|
|
3118
3356
|
},
|
|
3119
3357
|
"search-engine": {
|
|
3120
|
-
description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014
|
|
3121
|
-
argsHint: "<
|
|
3358
|
+
description: "\u5207\u6362\u7F51\u7EDC\u641C\u7D22\u540E\u7AEF \u2014 bing\uFF08\u9ED8\u8BA4\uFF0C\u56FD\u5185\u88F8 IP \u76F4\u8FDE\uFF09\u3001searxng\uFF08\u81EA\u6258\u7BA1\uFF09\u3001metaso\uFF08\u6BCF\u65E5 100 \u6B21\uFF09\u3001tavily\uFF08\u6BCF\u6708 1000 \u6B21\u514D\u8D39\uFF09\u3001perplexity\uFF08AI \u76F4\u63A5\u56DE\u7B54\uFF09\u6216 exa\uFF08AI \u76F4\u63A5\u56DE\u7B54\uFF09",
|
|
3359
|
+
argsHint: "<bing|searxng|metaso|tavily|perplexity|exa> [<key>]"
|
|
3122
3360
|
}
|
|
3123
3361
|
},
|
|
3124
3362
|
wizard: {
|
|
@@ -3281,6 +3519,8 @@ var zhCN = {
|
|
|
3281
3519
|
verboseOn: "\u25B8 \u8BE6\u7EC6\u6A21\u5F0F\u5DF2\u5F00 \u2014 \u663E\u793A\u5B8C\u6574\u63A8\u7406 + \u5DE5\u5177\u8F93\u51FA",
|
|
3282
3520
|
verboseOff: "\u25B8 \u8BE6\u7EC6\u6A21\u5F0F\u5DF2\u5173 \u2014 \u6062\u590D\u5934\u5C3E\u7701\u7565",
|
|
3283
3521
|
commandFailed: "! \u547D\u4EE4\u5931\u8D25",
|
|
3522
|
+
steerInjected: "\u25B8 \u5DF2\u52A0\u5165\u5F15\u5BFC\u961F\u5217 \u2014 \u5C06\u5728\u5F53\u524D\u6B65\u9AA4\u540E\u6CE8\u5165",
|
|
3523
|
+
steerCommandRejected: "\u25B8 \u5F53\u524D\u8F6E\u6B21\u5FD9\u788C\u65F6\u4E0D\u80FD\u63D0\u4EA4\u547D\u4EE4\uFF0C\u53EA\u80FD\u8F93\u5165\u666E\u901A\u5F15\u5BFC\u6D88\u606F",
|
|
3284
3524
|
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",
|
|
3285
3525
|
btwHeader: "\u226B btw",
|
|
3286
3526
|
btwFailed: "/btw \u8C03\u7528\u5931\u8D25",
|
|
@@ -3324,7 +3564,11 @@ var zhCN = {
|
|
|
3324
3564
|
editHistoryHelpShow: "/show <id> \u2192 \u6587\u4EF6\u6458\u8981 \xB7 /show <id> <path> \u2192 \u67D0\u4E2A\u6587\u4EF6\u7684\u5B8C\u6574 diff",
|
|
3325
3565
|
editHistoryHelpUndo: "/undo \u2192 \u6700\u65B0\u7684\u672A\u64A4\u9500\u9879 \xB7 /undo <id> [path] \u2192 \u6307\u5B9A\u6279\u6B21\u6216\u6587\u4EF6",
|
|
3326
3566
|
editHistoryAlreadyReverted: "\uFF08\u5DF2\u64A4\u9500 \u2014 /history \u663E\u793A\u6279\u6B21\u7EA7\u72B6\u6001\uFF09",
|
|
3327
|
-
editHistoryRevertFile: "/undo {id} {path} \u2192 \u4EC5\u8FD8\u539F\u6B64\u6587\u4EF6"
|
|
3567
|
+
editHistoryRevertFile: "/undo {id} {path} \u2192 \u4EC5\u8FD8\u539F\u6B64\u6587\u4EF6",
|
|
3568
|
+
mcpFailed: "MCP {name} \u5931\u8D25",
|
|
3569
|
+
mcpWarn: "MCP {name} \u8B66\u544A",
|
|
3570
|
+
unknownTheme: "\u672A\u77E5\u4E3B\u9898\uFF1A{name}\n\u53EF\u7528\u4E3B\u9898\uFF1A{choices}",
|
|
3571
|
+
themeSaved: "\u4E3B\u9898\u5DF2\u4FDD\u5B58\uFF1A{name}\n\u4E0B\u6B21\u542F\u52A8\u751F\u6548\uFF1A{active}"
|
|
3328
3572
|
},
|
|
3329
3573
|
hooks: {
|
|
3330
3574
|
head: "\u94A9\u5B50 {tag} `{cmd}` {decision}{truncTag}",
|
|
@@ -3347,9 +3591,9 @@ var zhCN = {
|
|
|
3347
3591
|
abortedAtIter: "\u5728\u7B2C {iter} \u6B21\u5DE5\u5177\u8C03\u7528\u5904\u4E2D\u65AD \u2014 \u672A\u751F\u6210\u603B\u7ED3\u5373\u505C\u6B62\uFF08\u6309 \u2191 + Enter \u6216 /retry \u6062\u590D\uFF09",
|
|
3348
3592
|
toolUploadStatus: "\u5DE5\u5177\u7ED3\u679C\u5DF2\u4E0A\u4F20 \xB7 \u6A21\u578B\u5728\u751F\u6210\u4E0B\u4E00\u6761\u54CD\u5E94\u524D\u601D\u8003\u4E2D\u2026",
|
|
3349
3593
|
preflightTruncateStatus: "\u9884\u68C0\uFF1A\u4E0A\u4E0B\u6587\u63A5\u8FD1\u4E0A\u9650\uFF0C\u6B63\u5728\u88C1\u526A\u6700\u65E9\u5386\u53F2\u2026",
|
|
3350
|
-
preflightTruncated: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u2014 \u5DF2\u88C1\u526A {beforeMessages} \u6761\u6D88\u606F \u2192 {afterMessages}\u3002\u53D1\u9001\u4E2D\u3002",
|
|
3351
|
-
preflightTruncatedStillFull: "\u9884\u68C0\uFF1A\u88C1\u526A {beforeMessages} \u6761\u6D88\u606F \u2192 {afterMessages} \u540E\uFF0C\u8BF7\u6C42\u4ECD\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
|
|
3352
|
-
preflightNoFold: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\u4E14\u6CA1\u6709\u53EF\u88C1\u526A\u7684\u5185\u5BB9 \u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
|
|
3594
|
+
preflightTruncated: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\xB7 body {bodyKB} KB \u2014 \u5DF2\u88C1\u526A {beforeMessages} \u6761\u6D88\u606F \u2192 {afterMessages}\u3002\u53D1\u9001\u4E2D\u3002",
|
|
3595
|
+
preflightTruncatedStillFull: "\u9884\u68C0\uFF1A\u88C1\u526A {beforeMessages} \u6761\u6D88\u606F \u2192 {afterMessages} \u540E\uFF0C\u8BF7\u6C42\u4ECD\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\xB7 body {bodyKB} KB \u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
|
|
3596
|
+
preflightNoFold: "\u9884\u68C0\uFF1A\u8BF7\u6C42\u7EA6 {estimate}/{ctxMax} tokens\uFF08{pct}%\uFF09\xB7 body {bodyKB} KB \u4E14\u6CA1\u6709\u53EF\u88C1\u526A\u7684\u5185\u5BB9 \u2014 DeepSeek \u5927\u6982\u7387\u4F1A\u8FD4\u56DE 400\u3002\u8BF7\u8FD0\u884C /clear \u6216 /new \u91CD\u65B0\u5F00\u59CB\u3002",
|
|
3353
3597
|
flashEscalation: "\u21E7 flash \u8BF7\u6C42\u5347\u7EA7 \u2014 \u672C\u8F6E\u6539\u7528 {model}{reasonSuffix}",
|
|
3354
3598
|
harvestStatus: "\u6B63\u5728\u4ECE\u63A8\u7406\u8FC7\u7A0B\u63D0\u53D6\u8BA1\u5212\u72B6\u6001\u2026",
|
|
3355
3599
|
repeatToolCallWarning: "\u62E6\u622A\u5230\u91CD\u590D\u5DE5\u5177\u8C03\u7528 \u2014 \u8BA9\u6A21\u578B\u5BDF\u89C9\u95EE\u9898\u5E76\u6362\u79CD\u65B9\u5F0F\u91CD\u8BD5\u3002",
|
|
@@ -3368,6 +3612,7 @@ var zhCN = {
|
|
|
3368
3612
|
balance402: "\u4F59\u989D\u4E0D\u8DB3\uFF08DeepSeek 402\uFF09\uFF1A{inner}\u3002\u5728 https://platform.deepseek.com/top_up \u5145\u503C \u2014 \u4F59\u989D\u975E\u96F6\u65F6\u9762\u677F\u9876\u680F\u4F1A\u663E\u793A\u3002",
|
|
3369
3613
|
badparam422: "\u53C2\u6570\u9519\u8BEF\uFF08DeepSeek 422\uFF09\uFF1A{inner}",
|
|
3370
3614
|
badrequest400: "\u8BF7\u6C42\u9519\u8BEF\uFF08DeepSeek 400\uFF09\uFF1A{inner}",
|
|
3615
|
+
concurrency429: "DeepSeek \u5E76\u53D1\u8D85\u9650\uFF08429\uFF09\uFF1A{inner}\u3002\u8D26\u53F7\u5728\u8DD1\u7684\u8BF7\u6C42\u8D85\u8FC7\u4E0A\u9650\uFF08v4-pro 500\u3001v4-flash 2500\uFF0C\u8D26\u53F7\u4E0B\u6240\u6709 API key \u7D2F\u52A0\uFF09\u3002\u901A\u5E38\u662F\u540C\u4E00\u8D26\u53F7\u5F00\u4E86\u591A\u4E2A Reasonix \u8FDB\u7A0B\uFF0C\u6216\u8005\u5E76\u884C subagent \u4E00\u6B21\u53D1\u592A\u591A\u3002\u7B49\u51E0\u79D2\u91CD\u8BD5\u3001\u51CF\u5C11\u5E76\u884C\uFF0C\u6216\u5728 https://platform.deepseek.com \u7533\u8BF7\u6269\u5BB9\u3002",
|
|
3371
3616
|
deepseek5xxHead: "DeepSeek \u670D\u52A1\u4E0D\u53EF\u7528\uFF08{status}\uFF09 \u2014 \u8FD9\u662F DeepSeek \u670D\u52A1\u7AEF\u95EE\u9898\uFF0C\u4E0D\u662F Reasonix \u6545\u969C\u3002\u5DF2\u6309\u6307\u6570\u9000\u907F\u91CD\u8BD5 4 \u6B21\u3002",
|
|
3372
3617
|
deepseek5xxReachable: " DeepSeek \u4E3B API \u5065\u5EB7\u68C0\u67E5\u901A\u8FC7\uFF0C\u4F46 /chat/completions \u5728\u6302 \u2014 \u4ED6\u4EEC\u90A3\u8FB9\u90E8\u5206\u670D\u52A1\u5F02\u5E38\u3002",
|
|
3373
3618
|
deepseek5xxUnreachable: " \u65E0\u6CD5\u4ECE\u4F60\u7684\u7F51\u7EDC\u8BBF\u95EE DeepSeek API \u2014 \u53EF\u80FD\u662F DS \u6574\u4F53\u6545\u969C\uFF0C\u4E5F\u53EF\u80FD\u662F\u672C\u5730\u7F51\u7EDC\u95EE\u9898\u3002",
|
|
@@ -3636,6 +3881,12 @@ var zhCN = {
|
|
|
3636
3881
|
statusMcp: " MCP {servers} \u4E2A\u670D\u52A1\u5668\uFF0C\u6CE8\u518C\u8868\u4E2D {tools} \u4E2A\u5DE5\u5177",
|
|
3637
3882
|
statusEdits: " \u7F16\u8F91 {count} \u4E2A\u5F85\u5904\u7406\uFF08/apply \u63D0\u4EA4\uFF0C/discard \u4E22\u5F03\uFF09",
|
|
3638
3883
|
statusPlan: " \u8BA1\u5212 \u5F00\u542F \u2014 \u5199\u5165\u53D7\u9650\uFF08submit_plan + \u5BA1\u6279\uFF09",
|
|
3884
|
+
statusLifecycle: " \u751F\u547D\u5468\u671F {mode}/{state} \xB7 {progress}{evidence}",
|
|
3885
|
+
lifecycleNoPlan: "\u6682\u65E0\u8BA1\u5212",
|
|
3886
|
+
lifecycleEvidencePending: "\u7B49\u5F85 evidence",
|
|
3887
|
+
lifecycleRejected: "lifecycle\uFF1A{tool} \u5728 {state} \u72B6\u6001\u88AB\u62E6\u622A \u2014 \u4E0B\u4E00\u6B65\uFF1A{next}",
|
|
3888
|
+
lifecycleEvidenceRejected: "lifecycle\uFF1A\u6B65\u9AA4 {stepId} \u9700\u8981 evidence \u2014 \u4E0B\u4E00\u6B65\uFF1A{next}",
|
|
3889
|
+
lifecycleRepeatedRejected: "lifecycle\uFF1A{tool} \u88AB\u91CD\u590D\u62E6\u622A \u2014 \u4E0D\u8981\u7528\u76F8\u540C\u53C2\u6570\u53CD\u590D\u91CD\u8BD5",
|
|
3639
3890
|
statusModeYolo: " \u6A21\u5F0F YOLO \u2014 \u7F16\u8F91 + shell \u81EA\u52A8\u8FD0\u884C\uFF0C\u65E0\u63D0\u793A\uFF08/undo \u4ECD\u53EF\u56DE\u6EDA \xB7 Shift+Tab \u5207\u6362\uFF09",
|
|
3640
3891
|
statusModeAuto: " \u6A21\u5F0F AUTO \u2014 \u7F16\u8F91\u7ACB\u5373\u5E94\u7528\uFF085 \u79D2\u5185\u6309 u \u64A4\u6D88 \xB7 Shift+Tab \u5207\u6362\uFF09",
|
|
3641
3892
|
statusModeReview: " \u6A21\u5F0F review \u2014 \u7F16\u8F91\u6392\u961F\u7B49\u5F85 /apply \u6216 y\uFF08Shift+Tab \u5207\u6362\uFF09",
|
|
@@ -3647,6 +3898,9 @@ var zhCN = {
|
|
|
3647
3898
|
activeNone: "\u25B8 \u6D3B\u8DC3\u8BA1\u5212\uFF1A\uFF08\u65E0\uFF09",
|
|
3648
3899
|
noArchives: "\u6B64\u4F1A\u8BDD\u5C1A\u65E0\u5F52\u6863\u8BA1\u5212 \u2014 \u5F53\u6BCF\u4E2A\u6B65\u9AA4\u5B8C\u6210\u65F6\u81EA\u52A8\u5F52\u6863",
|
|
3649
3900
|
archivedHeader: "\u5DF2\u5F52\u6863\uFF08{count}\uFF09\uFF1A",
|
|
3901
|
+
evidencePending: " ! \u7B49\u5F85 evidence \u2014 \u5F53\u524D\u6B65\u9AA4\u9700\u8981 verification/diff/checkpoint/manual evidence",
|
|
3902
|
+
evidenceLine: " evidence {stepId}: {summary}",
|
|
3903
|
+
archivedEvidenceLine: " evidence: {summary}",
|
|
3650
3904
|
replayNoSession: "\u672A\u9644\u52A0\u4F1A\u8BDD \u2014 `/replay` \u662F\u6309\u4F1A\u8BDD\u7684\u3002\u5728\u9879\u76EE\u4E2D\u8FD0\u884C `reasonix code` \u4EE5\u83B7\u53D6\u4F1A\u8BDD\u3002",
|
|
3651
3905
|
replayNoArchives: "\u6B64\u4F1A\u8BDD\u5C1A\u65E0\u5F52\u6863\u8BA1\u5212 \u2014 `/replay` \u5728\u8BA1\u5212\u5B8C\u6210\u540E\u542F\u7528\uFF08\u6BCF\u4E2A\u6B65\u9AA4\u5B8C\u6210\u65F6\u81EA\u52A8\u5F52\u6863\uFF09\u3002",
|
|
3652
3906
|
replayInvalidIndex: "\u65E0\u6548\u7D22\u5F15 \u2014 `/replay` \u63A5\u53D7 1..{max}\uFF08\u6700\u65B0 = 1\uFF09\u3002\u4F7F\u7528 `/plans` \u67E5\u770B\u5217\u8868\u3002",
|
|
@@ -3741,7 +3995,7 @@ var zhCN = {
|
|
|
3741
3995
|
currentEngine: "\u5F53\u524D\u7F51\u9875\u641C\u7D22\u5F15\u64CE\uFF1A{engine}",
|
|
3742
3996
|
endpoint: "SearXNG \u7AEF\u70B9\uFF1A{url}",
|
|
3743
3997
|
usageHeader: "\u7528\u6CD5\uFF1A",
|
|
3744
|
-
|
|
3998
|
+
usageBing: " /search-engine bing \u4F7F\u7528 Bing\uFF08\u9ED8\u8BA4\uFF0C\u56FD\u5185\u88F8 IP \u76F4\u8FDE\uFF0C\u65E0\u9700\u4EE3\u7406\uFF09",
|
|
3745
3999
|
usageSearxng: " /search-engine searxng \u4F7F\u7528 SearXNG \u9ED8\u8BA4\u7AEF\u70B9",
|
|
3746
4000
|
usageSearxngUrl: " /search-engine searxng <url> \u4F7F\u7528 SearXNG \u81EA\u5B9A\u4E49\u7AEF\u70B9",
|
|
3747
4001
|
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",
|
|
@@ -3806,7 +4060,8 @@ var zhCN = {
|
|
|
3806
4060
|
evt: " \u4E8B\u4EF6",
|
|
3807
4061
|
editsLabel: "\u7F16\u8F91:",
|
|
3808
4062
|
mcpLoading: "MCP",
|
|
3809
|
-
ctx: "\u4E0A\u4E0B\u6587"
|
|
4063
|
+
ctx: "\u4E0A\u4E0B\u6587",
|
|
4064
|
+
shortcutsHint: "Ctrl+P \u5FEB\u6377\u952E"
|
|
3810
4065
|
},
|
|
3811
4066
|
editMode: {
|
|
3812
4067
|
plan: "\u8BA1\u5212",
|
|
@@ -3836,7 +4091,9 @@ var zhCN = {
|
|
|
3836
4091
|
editorFailed: "\u5916\u90E8\u7F16\u8F91\u5668\uFF1A",
|
|
3837
4092
|
editorMissing: "\u672A\u8BBE\u7F6E $EDITOR / $VISUAL / $GIT_EDITOR \u2014 \u8BF7\u5BFC\u51FA\u73AF\u5883\u53D8\u91CF\uFF08\u4F8B\u5982 `export EDITOR=nano`\uFF09\u540E\u91CD\u8BD5",
|
|
3838
4093
|
editorExited: "\u7F16\u8F91\u5668\u5F02\u5E38\u9000\u51FA\uFF0C\u8FD4\u56DE\u7801 {code}",
|
|
3839
|
-
typeaheadStaged: "\u25B8 {count} \u884C\u5DF2\u6682\u5B58 \xB7 esc \u53EC\u56DE"
|
|
4094
|
+
typeaheadStaged: "\u25B8 {count} \u884C\u5DF2\u6682\u5B58 \xB7 esc \u53EC\u56DE",
|
|
4095
|
+
steerPlaceholder: "\u8F93\u5165\u6D88\u606F\u4EE5\u5F15\u5BFC\u5F53\u524D\u4EFB\u52A1 \u2014 \u5FD9\u788C\u65F6\u4E0D\u652F\u6301\u547D\u4EE4",
|
|
4096
|
+
steerHint: "\u53D1\u9001 \u2014 \u56DE\u5408\u5185\u6CE8\u5165"
|
|
3840
4097
|
},
|
|
3841
4098
|
pathConfirm: {
|
|
3842
4099
|
title: "\u6C99\u7BB1\u5916\u8DEF\u5F84",
|
|
@@ -3855,7 +4112,13 @@ var zhCN = {
|
|
|
3855
4112
|
denyDesc: "\u6309 Tab \u6DFB\u52A0\u8BF4\u660E\uFF0C\u544A\u8BC9\u6A21\u578B\u539F\u56E0",
|
|
3856
4113
|
pathLabel: "\u8DEF\u5F84",
|
|
3857
4114
|
sandboxLabel: "\u6C99\u7BB1",
|
|
3858
|
-
allowPrefixLabel: "\u524D\u7F00"
|
|
4115
|
+
allowPrefixLabel: "\u524D\u7F00",
|
|
4116
|
+
promptTitleRead: "\u8BBF\u95EE\u8DEF\u5F84 \u2014 \u8BFB\u53D6",
|
|
4117
|
+
promptTitleWrite: "\u8BBF\u95EE\u8DEF\u5F84 \u2014 \u5199\u5165",
|
|
4118
|
+
actionAllowRead: "\u5141\u8BB8\u8BFB\u53D6",
|
|
4119
|
+
actionAllowWrite: "\u5141\u8BB8\u5199\u5165",
|
|
4120
|
+
actionAlwaysAllow: "\u59CB\u7EC8\u5141\u8BB8 \u2014 {prefix}",
|
|
4121
|
+
actionDeny: "\u62D2\u7EDD"
|
|
3859
4122
|
},
|
|
3860
4123
|
shellConfirm: {
|
|
3861
4124
|
title: "Shell \u547D\u4EE4",
|
|
@@ -3877,7 +4140,12 @@ var zhCN = {
|
|
|
3877
4140
|
timeoutLabel: "\u8D85\u65F6",
|
|
3878
4141
|
waitLabel: "\u7B49\u5F85",
|
|
3879
4142
|
previewMore: "\u2026 \u8FD8\u6709 {n} \u884C\u672A\u663E\u793A \u2014 \u6309 esc \u53D6\u6D88\uFF0C\u8BA9\u6A21\u578B\u62C6\u5206\u540E\u518D\u8BD5",
|
|
3880
|
-
previewMorePlural: "\u2026 \u8FD8\u6709 {n} \u884C\u672A\u663E\u793A \u2014 \u6309 esc \u53D6\u6D88\uFF0C\u8BA9\u6A21\u578B\u62C6\u5206\u540E\u518D\u8BD5"
|
|
4143
|
+
previewMorePlural: "\u2026 \u8FD8\u6709 {n} \u884C\u672A\u663E\u793A \u2014 \u6309 esc \u53D6\u6D88\uFF0C\u8BA9\u6A21\u578B\u62C6\u5206\u540E\u518D\u8BD5",
|
|
4144
|
+
promptTitleRunCommand: "\u8FD0\u884C\u547D\u4EE4",
|
|
4145
|
+
promptTitleRunBackground: "\u8FD0\u884C\u540E\u53F0\u547D\u4EE4",
|
|
4146
|
+
actionRunOnce: "\u8FD0\u884C\u4E00\u6B21",
|
|
4147
|
+
actionAlwaysAllow: "\u59CB\u7EC8\u5141\u8BB8 \u2014 {prefix}",
|
|
4148
|
+
actionDeny: "\u62D2\u7EDD"
|
|
3881
4149
|
},
|
|
3882
4150
|
editConfirm: {
|
|
3883
4151
|
footer: "[y/Enter] \u5E94\u7528 \xB7 [n] \u62D2\u7EDD\u5E76\u8BF4\u660E \xB7 [a] \u5E94\u7528\u5269\u4F59 \xB7 [A] \u5207\u6362 AUTO \xB7 [\u2191\u2193/Space] \u6EDA\u52A8 \xB7 [Esc] \u4E2D\u6B62",
|
|
@@ -4018,36 +4286,37 @@ var zhCN = {
|
|
|
4018
4286
|
probeFailed: "\u63A2\u6D4B\u5931\u8D25 \u2014 {message}"
|
|
4019
4287
|
},
|
|
4020
4288
|
webErrors: {
|
|
4021
|
-
status: "web_search {status} \u2014 try: \u641C\u7D22\u540E\u7AEF\u8FD4\u56DE\u9519\u8BEF\uFF1B\u8BF7\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4289
|
+
status: "web_search {status} \u2014 try: \u641C\u7D22\u540E\u7AEF\u8FD4\u56DE\u9519\u8BEF\uFF1B\u8BF7\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4022
4290
|
rateLimit429: "web_search 429 \u2014 try: \u7B49\u5F85 10 \u79D2\u540E\u91CD\u8BD5\uFF0C\u6216\u6539\u5199\u67E5\u8BE2\uFF1B\u641C\u7D22\u540E\u7AEF\u6B63\u5728\u5BF9\u8BE5\u5BA2\u6237\u7AEF\u8FDB\u884C\u9650\u6D41",
|
|
4023
|
-
forbidden403: "web_search 403 \u2014 try: \u641C\u7D22\u540E\u7AEF\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u4F7F\u7528 /search-engine
|
|
4291
|
+
forbidden403: "web_search 403 \u2014 try: \u641C\u7D22\u540E\u7AEF\u62D2\u7EDD\u8BE5\u5BA2\u6237\u7AEF\u8BBF\u95EE\uFF1B\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u7A0D\u540E\u91CD\u8BD5",
|
|
4024
4292
|
serverError5xx: "web_search {status} \u2014 try: \u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u641C\u7D22 URL\uFF1B\u82E5\u80FD\u52A0\u8F7D\u5219\u5C5E\u4E34\u65F6\u6545\u969C\uFF0C\u7B49 30 \u79D2\u91CD\u8BD5\u5373\u53EF",
|
|
4025
|
-
|
|
4026
|
-
|
|
4293
|
+
bingBlocked: "web_search: Bing \u53CD\u722C\u9875\u9762 \u2014 \u9891\u7387\u9650\u5236\u6216\u88AB\u5C4F\u853D \u2014 try: \u7B49\u5F85 30 \u79D2\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4294
|
+
bingNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46\u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF0C\u524D 120 \u5B57\u7B26\uFF1A{preview}\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4027
4295
|
invalidEndpoint: 'web_search: \u65E0\u6548\u7684 SearXNG \u7AEF\u70B9 "{endpoint}" \u2014 try: \u4F7F\u7528 /search-endpoint http://host:port \u8BBE\u7F6E\u6709\u6548\u7684 URL',
|
|
4028
4296
|
endpointMustBeHttp: "web_search: SearXNG \u7AEF\u70B9\u5FC5\u987B\u662F http(s) \u534F\u8BAE\uFF0C\u5F53\u524D\u4E3A {protocol} \u2014 try: \u4F7F\u7528 /search-endpoint http://host:port \u8BBE\u7F6E\u6709\u6548\u7684 URL",
|
|
4029
|
-
cannotReach: "web_search: \u65E0\u6CD5\u8BBF\u95EE SearXNG \u670D\u52A1\u5668 {endpoint} \u2014 try: \u5B89\u88C5\u5E76\u542F\u52A8 SearXNG\uFF08https://github.com/searxng/searxng\uFF0C\u4F8B\u5982 `docker run -d -p 8080:8080 searxng/searxng`\uFF09\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4030
|
-
searxngNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46 SearXNG \u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4031
|
-
|
|
4297
|
+
cannotReach: "web_search: \u65E0\u6CD5\u8BBF\u95EE SearXNG \u670D\u52A1\u5668 {endpoint} \u2014 try: \u5B89\u88C5\u5E76\u542F\u52A8 SearXNG\uFF08https://github.com/searxng/searxng\uFF0C\u4F8B\u5982 `docker run -d -p 8080:8080 searxng/searxng`\uFF09\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4298
|
+
searxngNoResults: "web_search: \u8FD4\u56DE 0 \u6761\u7ED3\u679C\u4F46 SearXNG \u54CD\u5E94\u770B\u8D77\u6765\u4E0D\u662F\u6B63\u5E38\u7A7A\u7ED3\u679C\u9875\uFF08{chars} \u5B57\u7B26\uFF09\u2014 try: \u4F7F\u7528\u66F4\u7B80\u5355\u7684\u5173\u952E\u8BCD\u6539\u5199\u67E5\u8BE2\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4299
|
+
metasoMissingKey: "web_search: Metaso \u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E METASO_API_KEY\uFF0C\u6216\u4F7F\u7528 /search-engine metaso <key> \u914D\u7F6E\uFF1B\u53EF\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u5BC6\u94A5",
|
|
4300
|
+
metasoDailyLimit: "web_search: Metaso \u6BCF\u65E5\u641C\u7D22\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650 \u2014 \u8BBE\u7F6E METASO_API_KEY\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u5BC6\u94A5",
|
|
4032
4301
|
metasoUnauthorized: "web_search: Metaso API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 METASO_API_KEY\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u5BC6\u94A5",
|
|
4033
4302
|
metasoRateLimit: "web_search: Metaso \u8BF7\u6C42\u9891\u7387\u9650\u5236 \u2014 \u7B49\u5F85\u540E\u91CD\u8BD5\uFF0C\u6216\u5728 https://metaso.cn/search-api/playground \u83B7\u53D6\u81EA\u5DF1\u7684\u5BC6\u94A5",
|
|
4034
|
-
metasoServerError: "web_search: Metaso \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4303
|
+
metasoServerError: "web_search: Metaso \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4035
4304
|
metasoParseError: "web_search: Metaso \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
4036
4305
|
metasoApiError: "web_search: Metaso API \u9519\u8BEF\uFF08code {code}: {message}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
4037
4306
|
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",
|
|
4038
4307
|
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",
|
|
4039
|
-
tavilyRateLimit: "web_search: Tavily \u8BF7\u6C42\u9891\u7387\u9650\u5236\u6216\u6708\u5EA6\u914D\u989D\u7528\u5C3D \u2014 \u7B49\u5F85\u3001\u7528 /search-engine
|
|
4040
|
-
tavilyServerError: "web_search: Tavily \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4308
|
+
tavilyRateLimit: "web_search: Tavily \u8BF7\u6C42\u9891\u7387\u9650\u5236\u6216\u6708\u5EA6\u914D\u989D\u7528\u5C3D \u2014 \u7B49\u5F85\u3001\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE\uFF0C\u6216\u5347\u7EA7 Tavily \u8BA1\u5212",
|
|
4309
|
+
tavilyServerError: "web_search: Tavily \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4041
4310
|
tavilyParseError: "web_search: Tavily \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
4042
4311
|
perplexityMissingKey: "web_search: Perplexity \u540E\u7AEF\u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E PERPLEXITY_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 ~/.reasonix/config.json \u4E2D\u914D\u7F6E `perplexityApiKey`\uFF1B\u5728 https://perplexity.ai/settings/api \u83B7\u53D6\u5BC6\u94A5",
|
|
4043
4312
|
perplexityUnauthorized: "web_search: Perplexity API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 PERPLEXITY_API_KEY\uFF0C\u6216\u5728 https://perplexity.ai/settings/api \u83B7\u53D6\u5BC6\u94A5",
|
|
4044
|
-
perplexityRateLimit: "web_search: Perplexity \u8BF7\u6C42\u9891\u7387\u9650\u5236 \u2014 \u7B49\u5F85\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4045
|
-
perplexityServerError: "web_search: Perplexity \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4313
|
+
perplexityRateLimit: "web_search: Perplexity \u8BF7\u6C42\u9891\u7387\u9650\u5236 \u2014 \u7B49\u5F85\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4314
|
+
perplexityServerError: "web_search: Perplexity \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4046
4315
|
perplexityParseError: "web_search: Perplexity \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
4047
4316
|
exaMissingKey: "web_search: Exa \u540E\u7AEF\u9700\u8981 API \u5BC6\u94A5 \u2014 \u8BBE\u7F6E EXA_API_KEY \u73AF\u5883\u53D8\u91CF\uFF0C\u6216\u5728 ~/.reasonix/config.json \u4E2D\u914D\u7F6E `exaApiKey`\uFF1Bhttps://exa.ai \u6BCF\u6708 1000 \u6B21\u514D\u8D39",
|
|
4048
4317
|
exaUnauthorized: "web_search: Exa API \u5BC6\u94A5\u88AB\u62D2\u7EDD \u2014 \u68C0\u67E5 EXA_API_KEY\uFF0C\u6216\u5728 https://exa.ai \u83B7\u53D6\u5BC6\u94A5",
|
|
4049
4318
|
exaRateLimit: "web_search: Exa \u8BF7\u6C42\u9891\u7387\u9650\u5236\u6216\u6708\u5EA6\u914D\u989D\u7528\u5C3D \u2014 \u7B49\u5F85\u5347\u7EA7\uFF0C\u6216\u5728 https://exa.ai/pricing \u67E5\u770B\u8BA1\u5212",
|
|
4050
|
-
exaServerError: "web_search: Exa \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine
|
|
4319
|
+
exaServerError: "web_search: Exa \u670D\u52A1\u5668\u9519\u8BEF\uFF08{status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u4F7F\u7528 /search-engine bing|searxng|metaso|tavily|perplexity|exa \u5207\u6362\u5F15\u64CE",
|
|
4051
4320
|
exaParseError: "web_search: Exa \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790\u7684\u54CD\u5E94\uFF08HTTP {status}\uFF09\u2014 \u7A0D\u540E\u91CD\u8BD5",
|
|
4052
4321
|
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",
|
|
4053
4322
|
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",
|
|
@@ -4139,19 +4408,6 @@ var zhCN = {
|
|
|
4139
4408
|
categoryProject: "\u9879\u76EE",
|
|
4140
4409
|
categoryReference: "\u53C2\u8003"
|
|
4141
4410
|
},
|
|
4142
|
-
copyMode: {
|
|
4143
|
-
title: "\u2500\u2500 \u590D\u5236\u6A21\u5F0F \u2500\u2500",
|
|
4144
|
-
help: "j/k \u6216 \u2191/\u2193 \u79FB\u52A8 \xB7 v \u8D77\u9009\u533A \xB7 y \u590D\u5236 \xB7 g/G \u9876/\u5E95 \xB7 q \u9000\u51FA",
|
|
4145
|
-
statusBar: "\u7B2C {cur}/{total} \u884C \xB7 \u9009\u533A\uFF1A{sel}",
|
|
4146
|
-
statusYanked: "\u5DF2\u590D\u5236 {size} \u5B57\u7B26\uFF08osc52={osc52}\uFF09",
|
|
4147
|
-
statusEmpty: "\u672A\u9009\u4E2D\u5185\u5BB9",
|
|
4148
|
-
empty: "\uFF08\u8FD8\u6CA1\u6709\u804A\u5929\u5185\u5BB9 \u2014 \u5148\u548C\u6A21\u578B\u8BF4\u70B9\u4EC0\u4E48\uFF09",
|
|
4149
|
-
labelUser: "\u4F60",
|
|
4150
|
-
labelAssistant: "\u52A9\u624B",
|
|
4151
|
-
labelReasoning: "\u63A8\u7406",
|
|
4152
|
-
yankedToast: "\u25B8 \u5DF2\u590D\u5236 {size} \u5B57\u7B26\u5230\u526A\u8D34\u677F (osc52)",
|
|
4153
|
-
yankedToastFile: "\u25B8 \u5DF2\u590D\u5236 {size} \u5B57\u7B26 \xB7 \u6587\u4EF6\uFF1A{path}"
|
|
4154
|
-
},
|
|
4155
4411
|
mcpHealth: {
|
|
4156
4412
|
noData: "\u65E0\u68C0\u67E5\u6570\u636E",
|
|
4157
4413
|
healthy: "\u6B63\u5E38 \xB7 {ms}ms",
|
|
@@ -4220,7 +4476,13 @@ var zhCN = {
|
|
|
4220
4476
|
noResources: "\u6CA1\u6709\u4EFB\u4F55\u5DF2\u8FDE\u63A5 MCP \u670D\u52A1\u5668\u4E0A\u7684\u8D44\u6E90\uFF08\u6216\u65E0\u670D\u52A1\u5668\u8FDE\u63A5\uFF09\u3002`/mcp` \u663E\u793A\u5F53\u524D\u5217\u8868\u3002",
|
|
4221
4477
|
readOne: "\u8BFB\u53D6\uFF1A`/resource <uri>` \u2014 \u6216\u5728\u9009\u62E9\u5668\u4E2D\u4F7F\u7528 Tab \u952E\u3002",
|
|
4222
4478
|
noPrompts: "\u6CA1\u6709\u4EFB\u4F55\u5DF2\u8FDE\u63A5 MCP \u670D\u52A1\u5668\u4E0A\u7684\u63D0\u793A\uFF08\u6216\u65E0\u670D\u52A1\u5668\u8FDE\u63A5\uFF09\u3002`/mcp` \u663E\u793A\u5F53\u524D\u5217\u8868\u3002",
|
|
4223
|
-
fetchOne: "\u83B7\u53D6\uFF1A`/prompt <name>` \u2014 \u6682\u4E0D\u652F\u6301\u53C2\u6570\uFF1B\u5E26\u5FC5\u9700\u53C2\u6570\u7684\u63D0\u793A\u5C06\u8FD4\u56DE\u670D\u52A1\u5668\u9519\u8BEF\u3002"
|
|
4479
|
+
fetchOne: "\u83B7\u53D6\uFF1A`/prompt <name>` \u2014 \u6682\u4E0D\u652F\u6301\u53C2\u6570\uFF1B\u5E26\u5FC5\u9700\u53C2\u6570\u7684\u63D0\u793A\u5C06\u8FD4\u56DE\u670D\u52A1\u5668\u9519\u8BEF\u3002",
|
|
4480
|
+
noServerForResource: '\u6CA1\u6709\u670D\u52A1\u5668\u66B4\u9732\u8D44\u6E90 "{name}"',
|
|
4481
|
+
resourceHint: "`/resource` \u4E0D\u5E26\u53C2\u6570\u53EF\u67E5\u770B\u53EF\u7528\u5217\u8868\u3002",
|
|
4482
|
+
readFailed: "\u8BFB\u53D6\u8D44\u6E90\u5931\u8D25",
|
|
4483
|
+
noServerForPrompt: '\u6CA1\u6709\u670D\u52A1\u5668\u66B4\u9732 prompt "{name}"',
|
|
4484
|
+
promptHint: "`/prompt` \u4E0D\u5E26\u53C2\u6570\u53EF\u67E5\u770B\u53EF\u7528\u5217\u8868\u3002",
|
|
4485
|
+
fetchFailed: "\u83B7\u53D6 prompt \u5931\u8D25"
|
|
4224
4486
|
},
|
|
4225
4487
|
mcpLifecycle: {
|
|
4226
4488
|
handshake: "\u63E1\u624B\u4E2D\u2026",
|
|
@@ -4233,7 +4495,9 @@ var zhCN = {
|
|
|
4233
4495
|
disabledDetail: "\u901A\u8FC7 /mcp disable {name}",
|
|
4234
4496
|
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",
|
|
4235
4497
|
failedSetupConfigHint: "\u2192 \u8FD0\u884C `reasonix setup` \u4ECE\u5DF2\u4FDD\u5B58\u914D\u7F6E\u4E2D\u79FB\u9664\u635F\u574F\u7684\u6761\u76EE\u3002",
|
|
4236
|
-
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"
|
|
4498
|
+
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",
|
|
4499
|
+
toolsReady: "\u5DE5\u5177\u5C31\u7EEA",
|
|
4500
|
+
warnLabel: "\u8B66\u544A"
|
|
4237
4501
|
},
|
|
4238
4502
|
checkpointPicker: {
|
|
4239
4503
|
title: "\u6062\u590D\u68C0\u67E5\u70B9 \u2014 {workspace}",
|
|
@@ -4279,6 +4543,64 @@ var zhCN = {
|
|
|
4279
4543
|
noRecords: "\u65E0\u8BB0\u5F55",
|
|
4280
4544
|
untracked: "\uFF08\u672A\u8FFD\u8E2A\uFF09",
|
|
4281
4545
|
churned: "\uFF08\u5DF2\u53D8\u66F4 \xD7{count}\uFF09"
|
|
4546
|
+
},
|
|
4547
|
+
builtinSkills: {
|
|
4548
|
+
explore: "\u5728\u9694\u79BB\u5B50 agent \u4E2D\u63A2\u7D22\u4EE3\u7801\u5E93 \u2014 \u53EA\u8BFB\u5BBD\u7F51\u8C03\u67E5\uFF0C\u8FD4\u56DE\u4E00\u4E2A\u7CBE\u70BC\u7ED3\u8BBA",
|
|
4549
|
+
research: "\u7ED3\u5408\u4EE3\u7801\u9605\u8BFB\u4E0E\u7F51\u7EDC\u641C\u7D22\u8FDB\u884C\u8C03\u7814 \u2014 \u5728\u9694\u79BB\u5B50 agent \u4E2D\u7EFC\u5408\u4FE1\u606F\u5E76\u8FD4\u56DE\u7ED3\u8BBA",
|
|
4550
|
+
review: "\u5BA1\u67E5\u5F53\u524D\u5206\u652F\u53D8\u66F4 \u2014 \u68C0\u67E5\u6B63\u786E\u6027\u3001\u5B89\u5168\u6027\u3001\u7F3A\u5931\u6D4B\u8BD5\u3001\u9690\u85CF\u884C\u4E3A\u53D8\u66F4",
|
|
4551
|
+
securityReview: "\u5B89\u5168\u4E13\u9879\u5BA1\u67E5 \u2014 \u6807\u8BB0\u6CE8\u5165/\u8BA4\u8BC1/\u5BC6\u94A5/\u53CD\u5E8F\u5217\u5316/\u8DEF\u5F84\u7A7F\u8D8A/\u52A0\u5BC6\u95EE\u9898",
|
|
4552
|
+
test: "\u8FD0\u884C\u6D4B\u8BD5\u5957\u4EF6\u5E76\u8BCA\u65AD\u5931\u8D25 \u2014 \u81EA\u52A8\u8BC6\u522B\u6D4B\u8BD5\u6846\u67B6\uFF0C\u4FEE\u590D\u540E\u91CD\u8DD1\u76F4\u81F3\u901A\u8FC7"
|
|
4553
|
+
},
|
|
4554
|
+
shortcutsHelp: {
|
|
4555
|
+
title: "\u5FEB\u6377\u952E",
|
|
4556
|
+
groupInput: "\u8F93\u5165",
|
|
4557
|
+
groupNavigation: "\u5BFC\u822A",
|
|
4558
|
+
groupSession: "\u4F1A\u8BDD",
|
|
4559
|
+
groupSystem: "\u7CFB\u7EDF",
|
|
4560
|
+
descEnter: "\u53D1\u9001\u6D88\u606F",
|
|
4561
|
+
descShiftEnter: "\u6362\u884C",
|
|
4562
|
+
descCtrlEnter: "\u6362\u884C",
|
|
4563
|
+
descCtrlJ: "\u6362\u884C",
|
|
4564
|
+
descCtrlU: "\u6E05\u7A7A\u8F93\u5165",
|
|
4565
|
+
descCtrlW: "\u5220\u9664\u5355\u8BCD",
|
|
4566
|
+
descCtrlP: "\u663E\u793A/\u9690\u85CF\u5FEB\u6377\u952E",
|
|
4567
|
+
descCtrlX: "\u5728\u7F16\u8F91\u5668\u4E2D\u6253\u5F00",
|
|
4568
|
+
descArrows: "\u6D4F\u89C8\u8F93\u5165\u5386\u53F2",
|
|
4569
|
+
descPgUpDown: "\u7FFB\u9875",
|
|
4570
|
+
descCtrlL: "\u6E05\u5C4F",
|
|
4571
|
+
descCtrlB: "\u5207\u6362\u4FA7\u8FB9\u680F",
|
|
4572
|
+
descNewSession: "\u65B0\u5EFA\u4F1A\u8BDD",
|
|
4573
|
+
descListSessions: "\u5217\u51FA\u4F1A\u8BDD",
|
|
4574
|
+
descSwitchModel: "\u5207\u6362\u6A21\u578B",
|
|
4575
|
+
descSwitchPreset: "\u5207\u6362\u9884\u8BBE",
|
|
4576
|
+
descSwitchTheme: "\u5207\u6362\u4E3B\u9898",
|
|
4577
|
+
descCtrlC: "\u9000\u51FA",
|
|
4578
|
+
descEsc: "\u505C\u6B62/\u53D6\u6D88",
|
|
4579
|
+
descCtrlR: "\u5207\u6362\u8BE6\u7EC6\u6A21\u5F0F",
|
|
4580
|
+
descCtrlO: "\u5C55\u5F00\u56DE\u590D\uFF08\u4EC5\u6D41\u5F0F\u8F93\u51FA\u671F\u95F4\uFF09",
|
|
4581
|
+
descHelp: "\u663E\u793A\u6240\u6709\u547D\u4EE4",
|
|
4582
|
+
descShiftTab: "\u5207\u6362\u7F16\u8F91\u6A21\u5F0F"
|
|
4583
|
+
},
|
|
4584
|
+
mcpCli: {
|
|
4585
|
+
bundledCatalog: "\u5DF2\u6253\u5305\u7684 MCP \u670D\u52A1\u5668\uFF08\u79BB\u7EBF\u76EE\u5F55\uFF09\uFF1A",
|
|
4586
|
+
justFetched: "\u521A\u521A\u83B7\u53D6",
|
|
4587
|
+
cachedAge: "\u7F13\u5B58\uFF0C{age}",
|
|
4588
|
+
moreAvailable: "\u8FD8\u6709\u66F4\u591A",
|
|
4589
|
+
allLoaded: "\u5DF2\u5168\u90E8\u52A0\u8F7D",
|
|
4590
|
+
morePagesAvailable: "\u25B8 \u8FD8\u6709\u66F4\u591A\u9875\u53EF\u7528 \u2014 `reasonix mcp list --pages <n>` \u6216 --all",
|
|
4591
|
+
installHint: "\u5B89\u88C5\uFF1Areasonix mcp install <name>",
|
|
4592
|
+
usageSearch: "\u7528\u6CD5\uFF1Areasonix mcp search <query>",
|
|
4593
|
+
usageInstall: "\u7528\u6CD5\uFF1Areasonix mcp install <name>",
|
|
4594
|
+
noMatchesFor: '\u672A\u627E\u5230 "{q}" \u7684\u5339\u914D\u9879\uFF08\u5DF2\u68C0\u7D22 {count} \u6761\u8BB0\u5F55\uFF0C\u6765\u6E90\uFF1A{source}\uFF09',
|
|
4595
|
+
matchCount: '\u5728 {source} \u4E2D\u627E\u5230 {count} \u6761 "{q}" \u7684\u5339\u914D\u9879\uFF08\u5DF2\u626B\u63CF {loaded} \u6761\u8BB0\u5F55\uFF09\uFF1A',
|
|
4596
|
+
moreLoaded: "\u2026 \u8FD8\u6709 {count} \u6761\u5DF2\u52A0\u8F7D \u2014 \u4F7F\u7528 `reasonix mcp search <query>` \u7B5B\u9009",
|
|
4597
|
+
moreMatches: "\u2026 \u8FD8\u6709 {count} \u6761\u5339\u914D\u9879",
|
|
4598
|
+
installed: "\u5DF2\u5B89\u88C5\uFF1A{spec}",
|
|
4599
|
+
noServerFound: '\u5728 {source} \u4E2D\u904D\u5386\u4E86 {pages} \u9875\u540E\u672A\u627E\u5230\u540D\u4E3A "{target}" \u7684 MCP \u670D\u52A1\u5668\u3002',
|
|
4600
|
+
noServerTryMore: "\u8BD5\u8BD5\uFF1Areasonix mcp install {target} --max-pages 100",
|
|
4601
|
+
noInstallMeta: '\u65E0\u6CD5\u4E3A "{name}" \u83B7\u53D6\u5B89\u88C5\u5143\u6570\u636E \u2014 \u8BD5\u8BD5 `npx -y @smithery/cli install {name}`\u3002',
|
|
4602
|
+
buildSpecFailed: "\u65E0\u6CD5\u4E3A {name} \u6784\u5EFA\u5B89\u88C5 spec\uFF1A{message}",
|
|
4603
|
+
alreadyInstalled: "\u5DF2\u5B89\u88C5\uFF1A{spec}"
|
|
4282
4604
|
}
|
|
4283
4605
|
};
|
|
4284
4606
|
|
|
@@ -4354,7 +4676,7 @@ function readSettingsFile(path2) {
|
|
|
4354
4676
|
}
|
|
4355
4677
|
function loadHooks(opts = {}) {
|
|
4356
4678
|
const out = [];
|
|
4357
|
-
if (opts.projectRoot) {
|
|
4679
|
+
if (opts.projectRoot && (opts.trustProjectHooks === true || projectHooksTrusted(opts.projectRoot, opts.configPath))) {
|
|
4358
4680
|
const projPath = projectSettingsPath(opts.projectRoot);
|
|
4359
4681
|
const settings2 = readSettingsFile(projPath);
|
|
4360
4682
|
if (settings2) appendResolved(out, settings2, "project", projPath);
|
|
@@ -4388,7 +4710,7 @@ function matchesTool(hook, toolName) {
|
|
|
4388
4710
|
}
|
|
4389
4711
|
var HOOK_OUTPUT_CAP_BYTES = 256 * 1024;
|
|
4390
4712
|
function defaultSpawner(input) {
|
|
4391
|
-
return new Promise((
|
|
4713
|
+
return new Promise((resolve14) => {
|
|
4392
4714
|
const child = spawn(input.command, {
|
|
4393
4715
|
cwd: input.cwd,
|
|
4394
4716
|
shell: true,
|
|
@@ -4433,7 +4755,7 @@ function defaultSpawner(input) {
|
|
|
4433
4755
|
child.stderr.on("data", (chunk) => onChunk("stderr", chunk));
|
|
4434
4756
|
child.once("error", (err) => {
|
|
4435
4757
|
clearTimeout(timer);
|
|
4436
|
-
|
|
4758
|
+
resolve14({
|
|
4437
4759
|
exitCode: null,
|
|
4438
4760
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
4439
4761
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
@@ -4444,7 +4766,7 @@ function defaultSpawner(input) {
|
|
|
4444
4766
|
});
|
|
4445
4767
|
child.once("close", (code) => {
|
|
4446
4768
|
clearTimeout(timer);
|
|
4447
|
-
|
|
4769
|
+
resolve14({
|
|
4448
4770
|
exitCode: code,
|
|
4449
4771
|
stdout: Buffer.concat(stdoutChunks).toString("utf8").trim(),
|
|
4450
4772
|
stderr: Buffer.concat(stderrChunks).toString("utf8").trim(),
|
|
@@ -4966,12 +5288,14 @@ var ToolRegistry = class {
|
|
|
4966
5288
|
_interceptors = [];
|
|
4967
5289
|
_auditListener = null;
|
|
4968
5290
|
_resultAugmenter = null;
|
|
5291
|
+
_rateLimiter;
|
|
4969
5292
|
/** Per-tool fingerprint of the last call that failed schema validation. Cleared by any successful validation for that tool. */
|
|
4970
5293
|
_lastMalformed = /* @__PURE__ */ new Map();
|
|
4971
5294
|
/** Per-tool fingerprint of the last host-side gate rejection. */
|
|
4972
5295
|
_lastGateRejection = /* @__PURE__ */ new Map();
|
|
4973
5296
|
constructor(opts = {}) {
|
|
4974
5297
|
this._autoFlatten = opts.autoFlatten !== false;
|
|
5298
|
+
this._rateLimiter = new ToolRateLimiter(opts.rateLimit);
|
|
4975
5299
|
}
|
|
4976
5300
|
/** Enable / disable plan-mode enforcement at dispatch. */
|
|
4977
5301
|
setPlanMode(on) {
|
|
@@ -5008,6 +5332,9 @@ var ToolRegistry = class {
|
|
|
5008
5332
|
get hasResultAugmenter() {
|
|
5009
5333
|
return this._resultAugmenter !== null;
|
|
5010
5334
|
}
|
|
5335
|
+
get rateLimitPolicy() {
|
|
5336
|
+
return this._rateLimiter.policy;
|
|
5337
|
+
}
|
|
5011
5338
|
register(def) {
|
|
5012
5339
|
if (!def.name) throw new Error("tool requires a name");
|
|
5013
5340
|
const internal = { ...def };
|
|
@@ -5106,6 +5433,10 @@ var ToolRegistry = class {
|
|
|
5106
5433
|
rejectedReason: "aborted"
|
|
5107
5434
|
});
|
|
5108
5435
|
}
|
|
5436
|
+
const rateLimit = this._rateLimiter.consume(name);
|
|
5437
|
+
if (!rateLimit.allowed) {
|
|
5438
|
+
return JSON.stringify(rateLimit.result);
|
|
5439
|
+
}
|
|
5109
5440
|
let finalResult;
|
|
5110
5441
|
try {
|
|
5111
5442
|
try {
|
|
@@ -5114,7 +5445,8 @@ var ToolRegistry = class {
|
|
|
5114
5445
|
}
|
|
5115
5446
|
const result = await tool.fn(args, {
|
|
5116
5447
|
signal: opts.signal,
|
|
5117
|
-
confirmationGate: opts.confirmationGate
|
|
5448
|
+
confirmationGate: opts.confirmationGate,
|
|
5449
|
+
readTracker: opts.readTracker
|
|
5118
5450
|
});
|
|
5119
5451
|
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
5120
5452
|
let clipped = str;
|
|
@@ -5199,6 +5531,9 @@ function plainTextRejectedReason(name, result) {
|
|
|
5199
5531
|
if ((name === "edit_file" || name === "write_file") && /rejected this edit/i.test(result)) {
|
|
5200
5532
|
return "edit-gate";
|
|
5201
5533
|
}
|
|
5534
|
+
if ((name === "edit_file" || name === "multi_edit") && /read_file first/i.test(result)) {
|
|
5535
|
+
return "read-before-edit";
|
|
5536
|
+
}
|
|
5202
5537
|
if ((name === "run_command" || name === "run_background") && /\buser denied:/i.test(result)) {
|
|
5203
5538
|
return "shell-gate";
|
|
5204
5539
|
}
|
|
@@ -5208,6 +5543,8 @@ function rejectionRecoveryHint(reason) {
|
|
|
5208
5543
|
switch (reason) {
|
|
5209
5544
|
case "edit-gate":
|
|
5210
5545
|
return "Do not re-emit the same edit. Try a genuinely different edit or ask the user how to proceed.";
|
|
5546
|
+
case "read-before-edit":
|
|
5547
|
+
return "Call read_file on the target path first, then re-issue the edit.";
|
|
5211
5548
|
case "shell-gate":
|
|
5212
5549
|
return "Do not retry the same command. Use an allowlisted/read-only command, wait for approval, or ask the user how to proceed.";
|
|
5213
5550
|
case "engineering-lifecycle":
|
|
@@ -5336,12 +5673,12 @@ async function waitForReady(ready, timeoutMs, serverName, signal) {
|
|
|
5336
5673
|
let timer;
|
|
5337
5674
|
let onAbort;
|
|
5338
5675
|
try {
|
|
5339
|
-
await new Promise((
|
|
5676
|
+
await new Promise((resolve14, reject) => {
|
|
5340
5677
|
ready.then(
|
|
5341
5678
|
() => {
|
|
5342
5679
|
if (settled) return;
|
|
5343
5680
|
settled = true;
|
|
5344
|
-
|
|
5681
|
+
resolve14();
|
|
5345
5682
|
},
|
|
5346
5683
|
(err) => {
|
|
5347
5684
|
if (settled) return;
|
|
@@ -5558,14 +5895,14 @@ function buildSyntheticAssistantMessage(content, fallbackModel) {
|
|
|
5558
5895
|
// src/memory/session.ts
|
|
5559
5896
|
import { execFileSync } from "child_process";
|
|
5560
5897
|
import {
|
|
5561
|
-
appendFileSync,
|
|
5898
|
+
appendFileSync as appendFileSync2,
|
|
5562
5899
|
chmodSync as chmodSync2,
|
|
5563
5900
|
copyFileSync,
|
|
5564
5901
|
existsSync as existsSync3,
|
|
5565
5902
|
mkdirSync as mkdirSync2,
|
|
5566
5903
|
readFileSync as readFileSync4,
|
|
5567
5904
|
readdirSync,
|
|
5568
|
-
renameSync,
|
|
5905
|
+
renameSync as renameSync2,
|
|
5569
5906
|
statSync,
|
|
5570
5907
|
unlinkSync,
|
|
5571
5908
|
writeFileSync as writeFileSync2
|
|
@@ -5622,7 +5959,7 @@ function readSessionMessages(path2) {
|
|
|
5622
5959
|
function appendSessionMessage(name, message) {
|
|
5623
5960
|
const path2 = sessionPath(name);
|
|
5624
5961
|
mkdirSync2(dirname3(path2), { recursive: true });
|
|
5625
|
-
|
|
5962
|
+
appendFileSync2(path2, `${JSON.stringify(message)}
|
|
5626
5963
|
`, "utf8");
|
|
5627
5964
|
try {
|
|
5628
5965
|
chmodSync2(path2, 384);
|
|
@@ -5681,13 +6018,13 @@ function renameSession(oldName, newName) {
|
|
|
5681
6018
|
const oldJsonl = sessionPath(oldName);
|
|
5682
6019
|
const newJsonl = sessionPath(newName);
|
|
5683
6020
|
if (!existsSync3(oldJsonl) || existsSync3(newJsonl)) return false;
|
|
5684
|
-
|
|
6021
|
+
renameSync2(oldJsonl, newJsonl);
|
|
5685
6022
|
for (const ext of SESSION_SIDECAR_EXTS) {
|
|
5686
6023
|
const oldP = oldJsonl.replace(/\.jsonl$/, ext);
|
|
5687
6024
|
const newP = newJsonl.replace(/\.jsonl$/, ext);
|
|
5688
6025
|
if (existsSync3(oldP)) {
|
|
5689
6026
|
try {
|
|
5690
|
-
|
|
6027
|
+
renameSync2(oldP, newP);
|
|
5691
6028
|
} catch {
|
|
5692
6029
|
}
|
|
5693
6030
|
}
|
|
@@ -5724,7 +6061,7 @@ function rewriteSession(name, messages) {
|
|
|
5724
6061
|
copyFileSync(path2, backup);
|
|
5725
6062
|
chmodPrivate(backup);
|
|
5726
6063
|
}
|
|
5727
|
-
|
|
6064
|
+
renameSync2(tmp, path2);
|
|
5728
6065
|
chmodPrivate(path2);
|
|
5729
6066
|
} catch (err) {
|
|
5730
6067
|
try {
|
|
@@ -5916,32 +6253,43 @@ function round(n, digits) {
|
|
|
5916
6253
|
}
|
|
5917
6254
|
|
|
5918
6255
|
// src/context-manager.ts
|
|
5919
|
-
|
|
6256
|
+
function extractPinnedConstraints(systemPrompt) {
|
|
6257
|
+
const pattern = /# (?:HIGH PRIORITY constraints|User memory|Project memory)[\s\S]*?(?=\n# |\n---|$)/g;
|
|
6258
|
+
return Array.from(systemPrompt.matchAll(pattern), (m) => m[0]).join("\n\n");
|
|
6259
|
+
}
|
|
6260
|
+
var HISTORY_FOLD_THRESHOLD = 0.75;
|
|
5920
6261
|
var HISTORY_FOLD_TAIL_FRACTION = 0.2;
|
|
5921
|
-
var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.
|
|
6262
|
+
var HISTORY_FOLD_AGGRESSIVE_THRESHOLD = 0.78;
|
|
5922
6263
|
var HISTORY_FOLD_AGGRESSIVE_TAIL_FRACTION = 0.1;
|
|
5923
6264
|
var HISTORY_FOLD_MIN_SAVINGS_FRACTION = 0.3;
|
|
5924
6265
|
var FORCE_SUMMARY_THRESHOLD = 0.8;
|
|
5925
6266
|
var PREFLIGHT_EMERGENCY_THRESHOLD = 0.95;
|
|
5926
6267
|
var PREFLIGHT_MECHANICAL_TARGET_FRACTION = 0.7;
|
|
6268
|
+
var MAX_BODY_BYTES = 7e5;
|
|
6269
|
+
var MAX_BODY_BYTES_TARGET = 5e5;
|
|
5927
6270
|
var HISTORY_FOLD_SUMMARY_TIMEOUT_MS = 15e3;
|
|
5928
6271
|
var HISTORY_FOLD_MARKER = "[CONVERSATION HISTORY SUMMARY \u2014 earlier turns folded for context efficiency]\n\n";
|
|
5929
6272
|
var SKILL_PIN_MEMO_HEADER = "[Active skill memos \u2014 preserved verbatim across the fold:]";
|
|
5930
6273
|
var SKILL_PIN_REGEX = /<skill-pin name="([^"]+)">\n[\s\S]*?\n<\/skill-pin>/g;
|
|
5931
|
-
function
|
|
6274
|
+
function buildFoldSummaryInstruction(pinnedSkillNames) {
|
|
6275
|
+
const base = "Summarize the conversation above as one self-contained prose recap. Preserve the user's ORIGINAL OBJECTIVE (never paraphrase away negative constraints like 'do NOT do X'), all 'do not' / 'never' / 'avoid' instructions, decisions reached, files inspected or modified, tool results still relevant, and any open todos. Skip turn-by-turn play-by-play. Output plain prose only \u2014 no tool calls, no markdown headings, no SEARCH/REPLACE blocks.";
|
|
6276
|
+
if (pinnedSkillNames.length === 0) return base;
|
|
6277
|
+
const list = pinnedSkillNames.map((n) => `"${n}"`).join(", ");
|
|
6278
|
+
return `${base} The following skill memos are pinned verbatim and appended after your summary \u2014 do NOT quote or paraphrase their bodies: ${list}.`;
|
|
6279
|
+
}
|
|
6280
|
+
function collectPinnedSkills(head) {
|
|
5932
6281
|
const pinned = /* @__PURE__ */ new Map();
|
|
5933
|
-
const
|
|
5934
|
-
if (typeof msg.content !== "string")
|
|
5935
|
-
|
|
5936
|
-
const
|
|
6282
|
+
for (const msg of head) {
|
|
6283
|
+
if (typeof msg.content !== "string") continue;
|
|
6284
|
+
SKILL_PIN_REGEX.lastIndex = 0;
|
|
6285
|
+
for (const match of msg.content.matchAll(SKILL_PIN_REGEX)) {
|
|
6286
|
+
const name = match[1];
|
|
6287
|
+
const full = match[0];
|
|
5937
6288
|
pinned.delete(name);
|
|
5938
6289
|
pinned.set(name, full);
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
return hit ? { ...msg, content: next } : msg;
|
|
5943
|
-
});
|
|
5944
|
-
return { stubbedHead, pinnedBodies: [...pinned.values()] };
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
return { names: [...pinned.keys()], bodies: [...pinned.values()] };
|
|
5945
6293
|
}
|
|
5946
6294
|
var ContextManager = class {
|
|
5947
6295
|
constructor(deps) {
|
|
@@ -5990,14 +6338,25 @@ var ContextManager = class {
|
|
|
5990
6338
|
}
|
|
5991
6339
|
return { kind: "none", ...base };
|
|
5992
6340
|
}
|
|
5993
|
-
/** Local-side preflight before sending a request — catches oversized payloads early.
|
|
6341
|
+
/** Local-side preflight before sending a request — catches oversized payloads early.
|
|
6342
|
+
* Two independent signals trip mechanical truncate: token estimate above the context-window
|
|
6343
|
+
* fraction, OR JSON body bytes above the gateway limit (see `MAX_BODY_BYTES`). */
|
|
5994
6344
|
decidePreflight(messages, toolSpecs, model) {
|
|
5995
6345
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
5996
6346
|
const estimate = estimateRequestTokens(messages, toolSpecs ?? null, true);
|
|
6347
|
+
const estimateBytes = Buffer.byteLength(JSON.stringify(messages), "utf8");
|
|
6348
|
+
const tokensOver = estimate / ctxMax > PREFLIGHT_EMERGENCY_THRESHOLD;
|
|
6349
|
+
const bytesOver = estimateBytes > MAX_BODY_BYTES;
|
|
6350
|
+
let trigger = "none";
|
|
6351
|
+
if (tokensOver && bytesOver) trigger = "both";
|
|
6352
|
+
else if (tokensOver) trigger = "tokens";
|
|
6353
|
+
else if (bytesOver) trigger = "bytes";
|
|
5997
6354
|
return {
|
|
5998
|
-
needsAction:
|
|
6355
|
+
needsAction: tokensOver || bytesOver,
|
|
5999
6356
|
estimateTokens: estimate,
|
|
6000
|
-
|
|
6357
|
+
estimateBytes,
|
|
6358
|
+
ctxMax,
|
|
6359
|
+
trigger
|
|
6001
6360
|
};
|
|
6002
6361
|
}
|
|
6003
6362
|
/** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
|
|
@@ -6026,16 +6385,22 @@ var ContextManager = class {
|
|
|
6026
6385
|
const tail = all.slice(boundary);
|
|
6027
6386
|
const headTokens = totalTokens - cumTokens;
|
|
6028
6387
|
if (headTokens < totalTokens * HISTORY_FOLD_MIN_SAVINGS_FRACTION) return noop;
|
|
6029
|
-
const {
|
|
6030
|
-
const summary = await this.summarizeForFold(
|
|
6388
|
+
const { names: pinnedNames, bodies: pinnedBodies } = collectPinnedSkills(head);
|
|
6389
|
+
const summary = await this.summarizeForFold(head, pinnedNames);
|
|
6031
6390
|
if (!summary.content) return noop;
|
|
6032
6391
|
const memoTail = pinnedBodies.length > 0 ? `
|
|
6033
6392
|
|
|
6034
6393
|
${SKILL_PIN_MEMO_HEADER}
|
|
6035
6394
|
|
|
6036
6395
|
${pinnedBodies.join("\n\n")}` : "";
|
|
6396
|
+
const constraints = extractPinnedConstraints(this.deps.getSystemPrompt());
|
|
6397
|
+
const constraintTail = constraints ? `
|
|
6398
|
+
|
|
6399
|
+
[PINNED CONSTRAINTS \u2014 preserved verbatim]
|
|
6400
|
+
|
|
6401
|
+
${constraints}` : "";
|
|
6037
6402
|
const summaryMsg = buildAssistantMessage(
|
|
6038
|
-
HISTORY_FOLD_MARKER + summary.content + memoTail,
|
|
6403
|
+
HISTORY_FOLD_MARKER + summary.content + memoTail + constraintTail,
|
|
6039
6404
|
[],
|
|
6040
6405
|
model,
|
|
6041
6406
|
summary.reasoningContent
|
|
@@ -6043,6 +6408,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6043
6408
|
const replacement = [summaryMsg, ...tail];
|
|
6044
6409
|
this.deps.log.compactInPlace(replacement);
|
|
6045
6410
|
this.persistRewrite(replacement);
|
|
6411
|
+
this.deps.onLogRewrite?.();
|
|
6046
6412
|
return {
|
|
6047
6413
|
folded: true,
|
|
6048
6414
|
beforeMessages: all.length,
|
|
@@ -6050,10 +6416,13 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6050
6416
|
summaryChars: summary.content.length
|
|
6051
6417
|
};
|
|
6052
6418
|
}
|
|
6053
|
-
/** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail.
|
|
6419
|
+
/** Pure local emergency compaction for preflight: drop oldest log entries and keep a valid tail.
|
|
6420
|
+
* Bounded by tokens AND bytes — bytes matter because DeepSeek's gateway 400s on bodies past
|
|
6421
|
+
* `MAX_BODY_BYTES` even when the token budget is far from exhausted. */
|
|
6054
6422
|
mechanicalTruncate(model, opts) {
|
|
6055
6423
|
const ctxMax = DEEPSEEK_CONTEXT_TOKENS[model] ?? DEFAULT_CONTEXT_TOKENS;
|
|
6056
6424
|
const targetTokens = opts?.targetTokens ?? Math.floor(ctxMax * PREFLIGHT_MECHANICAL_TARGET_FRACTION);
|
|
6425
|
+
const targetBytes = opts?.targetBytes ?? MAX_BODY_BYTES_TARGET;
|
|
6057
6426
|
const all = this.deps.log.toMessages();
|
|
6058
6427
|
const noop = {
|
|
6059
6428
|
folded: false,
|
|
@@ -6063,6 +6432,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6063
6432
|
};
|
|
6064
6433
|
if (all.length === 0) return noop;
|
|
6065
6434
|
const tokenCounts = all.map((m) => estimateConversationTokens([m], true));
|
|
6435
|
+
const byteCounts = all.map((m) => Buffer.byteLength(JSON.stringify(m), "utf8"));
|
|
6066
6436
|
let latestUserBoundary = -1;
|
|
6067
6437
|
for (let i = all.length - 1; i >= 0; i--) {
|
|
6068
6438
|
if (all[i].role === "user") {
|
|
@@ -6071,12 +6441,15 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6071
6441
|
}
|
|
6072
6442
|
}
|
|
6073
6443
|
let cumTokens = 0;
|
|
6444
|
+
let cumBytes = 0;
|
|
6074
6445
|
let boundary = all.length;
|
|
6075
6446
|
let foundSafeBoundary = false;
|
|
6076
6447
|
for (let i = all.length - 1; i >= 0; i--) {
|
|
6077
|
-
const
|
|
6078
|
-
|
|
6079
|
-
|
|
6448
|
+
const nextTokens = cumTokens + tokenCounts[i];
|
|
6449
|
+
const nextBytes = cumBytes + byteCounts[i];
|
|
6450
|
+
if (nextTokens > targetTokens || nextBytes > targetBytes) break;
|
|
6451
|
+
cumTokens = nextTokens;
|
|
6452
|
+
cumBytes = nextBytes;
|
|
6080
6453
|
if (all[i].role === "user") {
|
|
6081
6454
|
boundary = i;
|
|
6082
6455
|
foundSafeBoundary = true;
|
|
@@ -6087,6 +6460,7 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6087
6460
|
if (replacement.length === all.length) return noop;
|
|
6088
6461
|
this.deps.log.compactInPlace(replacement);
|
|
6089
6462
|
this.persistRewrite(replacement);
|
|
6463
|
+
this.deps.onLogRewrite?.();
|
|
6090
6464
|
return {
|
|
6091
6465
|
folded: true,
|
|
6092
6466
|
beforeMessages: all.length,
|
|
@@ -6105,17 +6479,18 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6105
6479
|
this.persistRewrite([...kept]);
|
|
6106
6480
|
return true;
|
|
6107
6481
|
}
|
|
6108
|
-
async summarizeForFold(messagesToSummarize) {
|
|
6482
|
+
async summarizeForFold(messagesToSummarize, pinnedSkillNames) {
|
|
6109
6483
|
const summaryModel = "deepseek-v4-flash";
|
|
6110
|
-
const systemPrompt = "You compress conversation history for a coding agent. Output one prose recap that preserves: the user's overall goal, decisions and conclusions reached, files inspected or modified, important tool results still relevant to ongoing work, and any open todos. Skip turn-by-turn play-by-play. No tool calls, no markdown headings, no SEARCH/REPLACE blocks \u2014 plain prose only.";
|
|
6111
6484
|
const healed = healLoadedMessages(messagesToSummarize, DEFAULT_MAX_RESULT_CHARS).messages;
|
|
6485
|
+
const agentSystem = this.deps.getSystemPrompt();
|
|
6486
|
+
const fewShots = this.deps.getFewShots?.() ?? [];
|
|
6487
|
+
const tools = this.deps.getToolSpecs?.() ?? [];
|
|
6488
|
+
const instruction = buildFoldSummaryInstruction(pinnedSkillNames);
|
|
6112
6489
|
const messages = [
|
|
6113
|
-
{ role: "system", content:
|
|
6490
|
+
{ role: "system", content: agentSystem },
|
|
6491
|
+
...fewShots.map((m) => ({ ...m })),
|
|
6114
6492
|
...healed,
|
|
6115
|
-
{
|
|
6116
|
-
role: "user",
|
|
6117
|
-
content: "Summarize the conversation above as plain prose. This summary replaces the original turns to free context \u2014 make it self-contained."
|
|
6118
|
-
}
|
|
6493
|
+
{ role: "user", content: instruction }
|
|
6119
6494
|
];
|
|
6120
6495
|
const turnSignal = this.deps.getAbortSignal();
|
|
6121
6496
|
const foldCtrl = new AbortController();
|
|
@@ -6145,9 +6520,9 @@ ${pinnedBodies.join("\n\n")}` : "";
|
|
|
6145
6520
|
this.deps.client.chat({
|
|
6146
6521
|
model: summaryModel,
|
|
6147
6522
|
messages,
|
|
6523
|
+
tools: tools.length ? tools : void 0,
|
|
6148
6524
|
signal: foldCtrl.signal,
|
|
6149
|
-
thinking:
|
|
6150
|
-
reasoningEffort: "high"
|
|
6525
|
+
thinking: "disabled"
|
|
6151
6526
|
}),
|
|
6152
6527
|
abortPromise,
|
|
6153
6528
|
timeoutPromise
|
|
@@ -6232,6 +6607,7 @@ function formatLoopError(err, probe) {
|
|
|
6232
6607
|
if (status === "402") return t("errors.balance402", { inner });
|
|
6233
6608
|
if (status === "422") return t("errors.badparam422", { inner });
|
|
6234
6609
|
if (status === "400") return t("errors.badrequest400", { inner });
|
|
6610
|
+
if (status === "429") return t("errors.concurrency429", { inner });
|
|
6235
6611
|
if (is5xxStatus(status)) return formatDeepSeek5xx(status, probe);
|
|
6236
6612
|
return msg;
|
|
6237
6613
|
}
|
|
@@ -6922,8 +7298,34 @@ function signature(call) {
|
|
|
6922
7298
|
return `${call.function?.name ?? ""}::${call.function?.arguments ?? ""}`;
|
|
6923
7299
|
}
|
|
6924
7300
|
|
|
7301
|
+
// src/tools/read-tracker.ts
|
|
7302
|
+
import * as pathMod from "path";
|
|
7303
|
+
var ReadTracker = class _ReadTracker {
|
|
7304
|
+
_seen = /* @__PURE__ */ new Set();
|
|
7305
|
+
static norm(abs) {
|
|
7306
|
+
const resolved = pathMod.resolve(abs);
|
|
7307
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7308
|
+
}
|
|
7309
|
+
markRead(abs) {
|
|
7310
|
+
this._seen.add(_ReadTracker.norm(abs));
|
|
7311
|
+
}
|
|
7312
|
+
hasRead(abs) {
|
|
7313
|
+
return this._seen.has(_ReadTracker.norm(abs));
|
|
7314
|
+
}
|
|
7315
|
+
reset() {
|
|
7316
|
+
this._seen.clear();
|
|
7317
|
+
}
|
|
7318
|
+
get size() {
|
|
7319
|
+
return this._seen.size;
|
|
7320
|
+
}
|
|
7321
|
+
};
|
|
7322
|
+
|
|
6925
7323
|
// src/loop.ts
|
|
6926
7324
|
var ESCALATION_MODEL = "deepseek-v4-pro";
|
|
7325
|
+
var MID_TURN_STEER_WRAPPER = "[Mid-turn steer queued by the user. Do not treat this as a new task; use it only as additional guidance for the current task after completing the current step.]";
|
|
7326
|
+
function formatSteerUserMessage(content) {
|
|
7327
|
+
return [MID_TURN_STEER_WRAPPER, content].join("\n");
|
|
7328
|
+
}
|
|
6927
7329
|
var CacheFirstLoop = class {
|
|
6928
7330
|
client;
|
|
6929
7331
|
prefix;
|
|
@@ -6932,6 +7334,8 @@ var CacheFirstLoop = class {
|
|
|
6932
7334
|
scratch = new VolatileScratch();
|
|
6933
7335
|
stats = new SessionStats();
|
|
6934
7336
|
repair;
|
|
7337
|
+
/** Files the model has read this session; gates edit_file / multi_edit so SEARCH text matches on-disk bytes. Cleared on fold / mechanical truncate (the model's byte-level view of the elided history is gone). In-memory only — naturally empty on resume. */
|
|
7338
|
+
readTracker = new ReadTracker();
|
|
6935
7339
|
// Mutable via configure() — slash commands in the TUI / library callers tweak
|
|
6936
7340
|
// these mid-session so users don't have to restart.
|
|
6937
7341
|
model;
|
|
@@ -6955,15 +7359,19 @@ var CacheFirstLoop = class {
|
|
|
6955
7359
|
_turnAbort = new AbortController();
|
|
6956
7360
|
/** Authoritative running-id set — UI cards consult this instead of trusting end-event delivery. Insert at dispatch entry, delete in finally. */
|
|
6957
7361
|
_inflight = new InflightSet();
|
|
6958
|
-
/** Typeahead steer
|
|
6959
|
-
|
|
7362
|
+
/** Typeahead steer messages set by the UI; step() consumes one at each iter boundary. */
|
|
7363
|
+
_steerQueue = [];
|
|
6960
7364
|
/** Set true when a steer was consumed this turn; cleared on next step() entry. */
|
|
6961
7365
|
_steerConsumed = false;
|
|
6962
7366
|
/** UI calls this to inject a mid-turn steer message without aborting the current turn.
|
|
6963
|
-
* New text resets steerConsumed
|
|
7367
|
+
* New text resets steerConsumed because a fresh steer is queued. */
|
|
6964
7368
|
steer(text) {
|
|
6965
|
-
|
|
6966
|
-
|
|
7369
|
+
if (text === null) {
|
|
7370
|
+
this._steerQueue.length = 0;
|
|
7371
|
+
return;
|
|
7372
|
+
}
|
|
7373
|
+
this._steerQueue.push(text);
|
|
7374
|
+
this._steerConsumed = false;
|
|
6967
7375
|
}
|
|
6968
7376
|
/** True when a steer was consumed this turn (UI gate to avoid double-submit). */
|
|
6969
7377
|
get steerConsumed() {
|
|
@@ -7049,7 +7457,11 @@ var CacheFirstLoop = class {
|
|
|
7049
7457
|
stats: this.stats,
|
|
7050
7458
|
sessionName: this.sessionName,
|
|
7051
7459
|
getAbortSignal: () => this._turnAbort.signal,
|
|
7052
|
-
getCurrentTurn: () => this._turn
|
|
7460
|
+
getCurrentTurn: () => this._turn,
|
|
7461
|
+
getSystemPrompt: () => this.prefix.system,
|
|
7462
|
+
getToolSpecs: () => this.prefix.toolSpecs,
|
|
7463
|
+
getFewShots: () => this.prefix.fewShots,
|
|
7464
|
+
onLogRewrite: () => this.readTracker.reset()
|
|
7053
7465
|
});
|
|
7054
7466
|
}
|
|
7055
7467
|
/** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
|
|
@@ -7220,7 +7632,8 @@ ${reason}`
|
|
|
7220
7632
|
const result = await this.tools.dispatch(name, args, {
|
|
7221
7633
|
signal,
|
|
7222
7634
|
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
7223
|
-
confirmationGate: this.confirmationGate
|
|
7635
|
+
confirmationGate: this.confirmationGate,
|
|
7636
|
+
readTracker: this.readTracker
|
|
7224
7637
|
});
|
|
7225
7638
|
const postReport = await runHooks({
|
|
7226
7639
|
hooks: this.hooks,
|
|
@@ -7248,11 +7661,9 @@ ${reason}`
|
|
|
7248
7661
|
return generated;
|
|
7249
7662
|
}
|
|
7250
7663
|
_inflightCounter = 0;
|
|
7251
|
-
buildMessages(
|
|
7664
|
+
buildMessages() {
|
|
7252
7665
|
const healedMessages = this.healActiveLogBeforeSend();
|
|
7253
|
-
|
|
7254
|
-
if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
|
|
7255
|
-
return msgs;
|
|
7666
|
+
return [...this.prefix.toMessages(), ...healedMessages];
|
|
7256
7667
|
}
|
|
7257
7668
|
healActiveLogBeforeSend() {
|
|
7258
7669
|
const current = this.log.toMessages();
|
|
@@ -7333,6 +7744,7 @@ ${reason}`
|
|
|
7333
7744
|
cap: this.budgetUsd.toFixed(2)
|
|
7334
7745
|
})
|
|
7335
7746
|
};
|
|
7747
|
+
this._steerQueue.length = 0;
|
|
7336
7748
|
return;
|
|
7337
7749
|
}
|
|
7338
7750
|
if (!this._budgetWarned && spent >= this.budgetUsd * 0.8) {
|
|
@@ -7371,8 +7783,8 @@ ${reason}`
|
|
|
7371
7783
|
};
|
|
7372
7784
|
}
|
|
7373
7785
|
this.appendAndPersist({ role: "user", content: userInput });
|
|
7374
|
-
let pendingUser = null;
|
|
7375
7786
|
const toolSpecs = this.prefix.tools();
|
|
7787
|
+
let rateLimitWarningShown = false;
|
|
7376
7788
|
for (let iter = 0; ; iter++) {
|
|
7377
7789
|
if (signal.aborted) {
|
|
7378
7790
|
try {
|
|
@@ -7393,6 +7805,7 @@ ${reason}`
|
|
|
7393
7805
|
} finally {
|
|
7394
7806
|
this._turnAbort = new AbortController();
|
|
7395
7807
|
}
|
|
7808
|
+
this._steerQueue.length = 0;
|
|
7396
7809
|
return;
|
|
7397
7810
|
}
|
|
7398
7811
|
if (iter > 0) {
|
|
@@ -7402,14 +7815,15 @@ ${reason}`
|
|
|
7402
7815
|
content: t("loop.toolUploadStatus")
|
|
7403
7816
|
};
|
|
7404
7817
|
}
|
|
7405
|
-
let messages = this.buildMessages(
|
|
7406
|
-
if (this.
|
|
7407
|
-
const steer = this.
|
|
7408
|
-
this.
|
|
7409
|
-
this.
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7818
|
+
let messages = this.buildMessages();
|
|
7819
|
+
if (this._steerQueue.length > 0) {
|
|
7820
|
+
const steer = this._steerQueue.shift();
|
|
7821
|
+
this._steerConsumed = this._steerQueue.length === 0;
|
|
7822
|
+
this.appendAndPersist({
|
|
7823
|
+
role: "user",
|
|
7824
|
+
content: formatSteerUserMessage(steer)
|
|
7825
|
+
});
|
|
7826
|
+
messages = this.buildMessages();
|
|
7413
7827
|
yield {
|
|
7414
7828
|
turn: this._turn,
|
|
7415
7829
|
role: "steer",
|
|
@@ -7419,17 +7833,17 @@ ${reason}`
|
|
|
7419
7833
|
{
|
|
7420
7834
|
const decision2 = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
7421
7835
|
if (decision2.needsAction) {
|
|
7422
|
-
const { estimateTokens: estimate, ctxMax } = decision2;
|
|
7836
|
+
const { estimateTokens: estimate, estimateBytes, ctxMax } = decision2;
|
|
7423
7837
|
yield {
|
|
7424
7838
|
turn: this._turn,
|
|
7425
7839
|
role: "status",
|
|
7426
7840
|
content: t("loop.preflightTruncateStatus")
|
|
7427
7841
|
};
|
|
7428
7842
|
const result = this.context.mechanicalTruncate(this.model, {
|
|
7429
|
-
allowEmpty:
|
|
7843
|
+
allowEmpty: false
|
|
7430
7844
|
});
|
|
7431
7845
|
if (result.folded) {
|
|
7432
|
-
messages = this.buildMessages(
|
|
7846
|
+
messages = this.buildMessages();
|
|
7433
7847
|
const after = this.context.decidePreflight(messages, this.prefix.toolSpecs, this.model);
|
|
7434
7848
|
const stillFull = after.needsAction;
|
|
7435
7849
|
yield {
|
|
@@ -7441,6 +7855,7 @@ ${reason}`
|
|
|
7441
7855
|
estimate: after.estimateTokens.toLocaleString(),
|
|
7442
7856
|
ctxMax: after.ctxMax.toLocaleString(),
|
|
7443
7857
|
pct: Math.round(after.estimateTokens / after.ctxMax * 100),
|
|
7858
|
+
bodyKB: Math.round(after.estimateBytes / 1024).toLocaleString(),
|
|
7444
7859
|
beforeMessages: result.beforeMessages,
|
|
7445
7860
|
afterMessages: result.afterMessages
|
|
7446
7861
|
}
|
|
@@ -7453,7 +7868,8 @@ ${reason}`
|
|
|
7453
7868
|
content: t("loop.preflightNoFold", {
|
|
7454
7869
|
estimate: estimate.toLocaleString(),
|
|
7455
7870
|
ctxMax: ctxMax.toLocaleString(),
|
|
7456
|
-
pct: Math.round(estimate / ctxMax * 100)
|
|
7871
|
+
pct: Math.round(estimate / ctxMax * 100),
|
|
7872
|
+
bodyKB: Math.round(estimateBytes / 1024).toLocaleString()
|
|
7457
7873
|
})
|
|
7458
7874
|
};
|
|
7459
7875
|
}
|
|
@@ -7573,6 +7989,7 @@ ${reason}`
|
|
|
7573
7989
|
} finally {
|
|
7574
7990
|
this._turnAbort = new AbortController();
|
|
7575
7991
|
}
|
|
7992
|
+
this._steerQueue.length = 0;
|
|
7576
7993
|
return;
|
|
7577
7994
|
}
|
|
7578
7995
|
const probe = is5xxError(err) ? await probeDeepSeekReachable(this.client) : void 0;
|
|
@@ -7582,6 +7999,7 @@ ${reason}`
|
|
|
7582
7999
|
content: "",
|
|
7583
8000
|
error: formatLoopError(err, probe)
|
|
7584
8001
|
};
|
|
8002
|
+
this._steerQueue.length = 0;
|
|
7585
8003
|
return;
|
|
7586
8004
|
}
|
|
7587
8005
|
if (this.autoEscalate && this.modelForCurrentCall() !== ESCALATION_MODEL && isEscalationRequest(assistantContent)) {
|
|
@@ -7662,11 +8080,16 @@ ${reason}`
|
|
|
7662
8080
|
};
|
|
7663
8081
|
}
|
|
7664
8082
|
if (repairedCalls.length === 0) {
|
|
8083
|
+
if (this._steerQueue.length > 0) {
|
|
8084
|
+
continue;
|
|
8085
|
+
}
|
|
7665
8086
|
if (allSuppressed) {
|
|
7666
8087
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "stuck" });
|
|
8088
|
+
this._steerQueue.length = 0;
|
|
7667
8089
|
return;
|
|
7668
8090
|
}
|
|
7669
8091
|
yield { turn: this._turn, role: "done", content: assistantContent };
|
|
8092
|
+
this._steerQueue.length = 0;
|
|
7670
8093
|
return;
|
|
7671
8094
|
}
|
|
7672
8095
|
const decision = this.context.decideAfterUsage(usage, this.model, this._foldedThisTurn);
|
|
@@ -7712,6 +8135,7 @@ ${reason}`
|
|
|
7712
8135
|
};
|
|
7713
8136
|
this.context.trimTrailingToolCalls();
|
|
7714
8137
|
yield* forceSummaryAfterIterLimit(this.summaryContext(), { reason: "context-guard" });
|
|
8138
|
+
this._steerQueue.length = 0;
|
|
7715
8139
|
return;
|
|
7716
8140
|
}
|
|
7717
8141
|
const dispatchSerial = (process.env.REASONIX_TOOL_DISPATCH ?? "auto").toLowerCase() === "serial";
|
|
@@ -7759,6 +8183,15 @@ ${reason}`
|
|
|
7759
8183
|
}
|
|
7760
8184
|
for (const w of preWarnings) yield w;
|
|
7761
8185
|
for (const w of postWarnings) yield w;
|
|
8186
|
+
const rateLimited = parseRateLimitedToolResult(result);
|
|
8187
|
+
if (rateLimited && !rateLimitWarningShown) {
|
|
8188
|
+
rateLimitWarningShown = true;
|
|
8189
|
+
yield {
|
|
8190
|
+
turn: this._turn,
|
|
8191
|
+
role: "warning",
|
|
8192
|
+
content: rateLimited.message
|
|
8193
|
+
};
|
|
8194
|
+
}
|
|
7762
8195
|
this.appendAndPersist({
|
|
7763
8196
|
role: "tool",
|
|
7764
8197
|
tool_call_id: call.id ?? "",
|
|
@@ -7781,7 +8214,7 @@ ${reason}`
|
|
|
7781
8214
|
return {
|
|
7782
8215
|
client: this.client,
|
|
7783
8216
|
signal: this._turnAbort.signal,
|
|
7784
|
-
buildMessages: () => this.buildMessages(
|
|
8217
|
+
buildMessages: () => this.buildMessages(),
|
|
7785
8218
|
appendAndPersist: (m) => this.appendAndPersist(m),
|
|
7786
8219
|
recordStats: (model, usage) => this.stats.record(this._turn, model, usage),
|
|
7787
8220
|
turn: this._turn
|
|
@@ -7806,7 +8239,7 @@ function parsePositiveIntEnv(raw) {
|
|
|
7806
8239
|
// src/at-mentions.ts
|
|
7807
8240
|
import { existsSync as existsSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
7808
8241
|
import { readdir, stat } from "fs/promises";
|
|
7809
|
-
import { isAbsolute as isAbsolute2, join as join5, relative, resolve as
|
|
8242
|
+
import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve3 } from "path";
|
|
7810
8243
|
|
|
7811
8244
|
// src/gitignore.ts
|
|
7812
8245
|
import { readFileSync as readFileSync5 } from "fs";
|
|
@@ -7861,7 +8294,7 @@ function listFilesSync(root, opts = {}) {
|
|
|
7861
8294
|
function listFilesWithStatsSync(root, opts = {}) {
|
|
7862
8295
|
const maxResults = Math.max(1, opts.maxResults ?? 2e3);
|
|
7863
8296
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
7864
|
-
const rootAbs =
|
|
8297
|
+
const rootAbs = resolve3(root);
|
|
7865
8298
|
const respectGi = opts.respectGitignore !== false;
|
|
7866
8299
|
const out = [];
|
|
7867
8300
|
const walk2 = (dirAbs, dirRel, layers) => {
|
|
@@ -7925,7 +8358,7 @@ async function listFilesWithStatsAsync(root, opts = {}) {
|
|
|
7925
8358
|
async function walkFilesStream(root, opts) {
|
|
7926
8359
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
7927
8360
|
const respectGi = opts.respectGitignore !== false;
|
|
7928
|
-
const rootAbs =
|
|
8361
|
+
const rootAbs = resolve3(root);
|
|
7929
8362
|
const progressGap = Math.max(0, opts.progressIntervalMs ?? 100);
|
|
7930
8363
|
let scanned = 0;
|
|
7931
8364
|
let halted = false;
|
|
@@ -8003,8 +8436,8 @@ async function flushFiles(ents, dirAbs, dirRel, layers, emit) {
|
|
|
8003
8436
|
async function listDirectory(root, relDir, opts = {}) {
|
|
8004
8437
|
const ignoreDirs = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
|
|
8005
8438
|
const respectGi = opts.respectGitignore !== false;
|
|
8006
|
-
const rootAbs =
|
|
8007
|
-
const dirAbs =
|
|
8439
|
+
const rootAbs = resolve3(root);
|
|
8440
|
+
const dirAbs = resolve3(rootAbs, relDir);
|
|
8008
8441
|
const rel = relative(rootAbs, dirAbs);
|
|
8009
8442
|
if (rel.startsWith("..") || isAbsolute2(rel)) return [];
|
|
8010
8443
|
const layers = [];
|
|
@@ -8169,7 +8602,7 @@ function expandAtMentions(text, rootDir, opts = {}) {
|
|
|
8169
8602
|
const maxBytes = opts.maxBytes ?? DEFAULT_AT_MENTION_MAX_BYTES;
|
|
8170
8603
|
const maxDirEntries = Math.max(1, opts.maxDirEntries ?? DEFAULT_AT_DIR_MAX_ENTRIES);
|
|
8171
8604
|
const fs5 = opts.fs ?? defaultFs;
|
|
8172
|
-
const root =
|
|
8605
|
+
const root = resolve3(rootDir);
|
|
8173
8606
|
const seen = /* @__PURE__ */ new Map();
|
|
8174
8607
|
const expansions = [];
|
|
8175
8608
|
const dirListings = /* @__PURE__ */ new Map();
|
|
@@ -8216,7 +8649,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings
|
|
|
8216
8649
|
if (isAbsolute2(rawPath)) {
|
|
8217
8650
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
8218
8651
|
}
|
|
8219
|
-
const resolved =
|
|
8652
|
+
const resolved = resolve3(root, rawPath);
|
|
8220
8653
|
const rel = relative(root, resolved);
|
|
8221
8654
|
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
8222
8655
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "escape" };
|
|
@@ -8246,7 +8679,7 @@ function resolveMention(rawPath, root, maxBytes, maxDirEntries, fs5, dirListings
|
|
|
8246
8679
|
return { token: `@${rawPath}`, path: rawPath, ok: false, skip: "not-file" };
|
|
8247
8680
|
}
|
|
8248
8681
|
function readSafe(root, rawPath, fs5) {
|
|
8249
|
-
const resolved =
|
|
8682
|
+
const resolved = resolve3(root, rawPath);
|
|
8250
8683
|
try {
|
|
8251
8684
|
return fs5.read(resolved);
|
|
8252
8685
|
} catch {
|
|
@@ -8356,7 +8789,7 @@ import {
|
|
|
8356
8789
|
writeFileSync as writeFileSync4
|
|
8357
8790
|
} from "fs";
|
|
8358
8791
|
import { homedir as homedir5 } from "os";
|
|
8359
|
-
import { join as join8, resolve as
|
|
8792
|
+
import { join as join8, resolve as resolve5 } from "path";
|
|
8360
8793
|
|
|
8361
8794
|
// src/frontmatter.ts
|
|
8362
8795
|
var KEY_RE = /^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/;
|
|
@@ -8417,7 +8850,7 @@ import {
|
|
|
8417
8850
|
} from "fs";
|
|
8418
8851
|
import { accessSync } from "fs";
|
|
8419
8852
|
import { homedir as homedir4 } from "os";
|
|
8420
|
-
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join7, resolve as
|
|
8853
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join7, resolve as resolve4 } from "path";
|
|
8421
8854
|
|
|
8422
8855
|
// src/prompt-fragments.ts
|
|
8423
8856
|
var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown renderer):
|
|
@@ -8469,7 +8902,7 @@ var SkillStore = class {
|
|
|
8469
8902
|
disableBuiltins;
|
|
8470
8903
|
constructor(opts = {}) {
|
|
8471
8904
|
this.homeDir = opts.homeDir ?? homedir4();
|
|
8472
|
-
this.projectRoot = opts.projectRoot ?
|
|
8905
|
+
this.projectRoot = opts.projectRoot ? resolve4(opts.projectRoot) : void 0;
|
|
8473
8906
|
const baseDir = this.projectRoot ?? process.cwd();
|
|
8474
8907
|
this.customSkillPaths = dedupePaths(
|
|
8475
8908
|
opts.customSkillPaths?.map((p) => resolveCustomSkillPath(p, baseDir, this.homeDir)) ?? []
|
|
@@ -8635,7 +9068,7 @@ function dedupePaths(paths) {
|
|
|
8635
9068
|
function resolveCustomSkillPath(path2, baseDir, homeDir) {
|
|
8636
9069
|
const trimmed = path2.trim();
|
|
8637
9070
|
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") || trimmed.startsWith("~\\") ? join7(homeDir, trimmed.slice(2)) : trimmed;
|
|
8638
|
-
return
|
|
9071
|
+
return resolve4(isAbsolute3(expanded) ? expanded : join7(baseDir, expanded));
|
|
8639
9072
|
}
|
|
8640
9073
|
function skillPathStatus(dir) {
|
|
8641
9074
|
try {
|
|
@@ -8671,8 +9104,13 @@ Tips:
|
|
|
8671
9104
|
- Add \`allowed-tools: read_file, search_content\` to scope a subagent's tools
|
|
8672
9105
|
`;
|
|
8673
9106
|
}
|
|
9107
|
+
function skillDescription(s) {
|
|
9108
|
+
if (s.scope !== "builtin") return s.description;
|
|
9109
|
+
const key = s.name === "security-review" ? "securityReview" : s.name;
|
|
9110
|
+
return t(`builtinSkills.${key}`);
|
|
9111
|
+
}
|
|
8674
9112
|
function skillIndexLine(s) {
|
|
8675
|
-
const safeDesc = s.
|
|
9113
|
+
const safeDesc = skillDescription(s).replace(/\n/g, " ").trim();
|
|
8676
9114
|
const tag = s.runAs === "subagent" ? " [\u{1F9EC} subagent]" : "";
|
|
8677
9115
|
const max = 130 - s.name.length - tag.length;
|
|
8678
9116
|
const clipped = safeDesc.length > max ? `${safeDesc.slice(0, Math.max(1, max - 1))}\u2026` : safeDesc;
|
|
@@ -8903,7 +9341,7 @@ function sanitizeMemoryName(raw) {
|
|
|
8903
9341
|
return trimmed;
|
|
8904
9342
|
}
|
|
8905
9343
|
function projectHash(rootDir) {
|
|
8906
|
-
const abs =
|
|
9344
|
+
const abs = resolve5(rootDir);
|
|
8907
9345
|
return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
|
|
8908
9346
|
}
|
|
8909
9347
|
function scopeDir(opts) {
|
|
@@ -8953,7 +9391,7 @@ var MemoryStore = class {
|
|
|
8953
9391
|
projectRoot;
|
|
8954
9392
|
constructor(opts = {}) {
|
|
8955
9393
|
this.homeDir = opts.homeDir ?? join8(homedir5(), ".reasonix");
|
|
8956
|
-
this.projectRoot = opts.projectRoot ?
|
|
9394
|
+
this.projectRoot = opts.projectRoot ? resolve5(opts.projectRoot) : void 0;
|
|
8957
9395
|
}
|
|
8958
9396
|
/** Directory this store writes `scope` files into, creating it if needed. */
|
|
8959
9397
|
dir(scope) {
|
|
@@ -9209,15 +9647,44 @@ function applyMemoryStack(basePrompt, rootDir, opts = {}) {
|
|
|
9209
9647
|
|
|
9210
9648
|
// src/tools/filesystem.ts
|
|
9211
9649
|
import { promises as fs4 } from "fs";
|
|
9212
|
-
import * as
|
|
9650
|
+
import * as pathMod6 from "path";
|
|
9213
9651
|
import picomatch3 from "picomatch";
|
|
9214
9652
|
|
|
9653
|
+
// src/code/file-encoding.ts
|
|
9654
|
+
import { promises as fsp, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
9655
|
+
import iconv from "iconv-lite";
|
|
9656
|
+
var UTF8_BOM = Buffer.from([239, 187, 191]);
|
|
9657
|
+
function decodeFileBuffer(buf) {
|
|
9658
|
+
if (buf.length >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
|
|
9659
|
+
return { text: buf.subarray(3).toString("utf8"), encoding: "utf8-bom" };
|
|
9660
|
+
}
|
|
9661
|
+
try {
|
|
9662
|
+
return { text: new TextDecoder("utf-8", { fatal: true }).decode(buf), encoding: "utf8" };
|
|
9663
|
+
} catch {
|
|
9664
|
+
}
|
|
9665
|
+
try {
|
|
9666
|
+
return {
|
|
9667
|
+
text: new TextDecoder("gb18030", { fatal: true }).decode(buf),
|
|
9668
|
+
encoding: "gb18030"
|
|
9669
|
+
};
|
|
9670
|
+
} catch {
|
|
9671
|
+
}
|
|
9672
|
+
return { text: buf.toString("utf8"), encoding: "utf8" };
|
|
9673
|
+
}
|
|
9674
|
+
function encodeFile(text, encoding) {
|
|
9675
|
+
if (encoding === "utf8") return Buffer.from(text, "utf8");
|
|
9676
|
+
if (encoding === "utf8-bom") {
|
|
9677
|
+
return Buffer.concat([UTF8_BOM, Buffer.from(text, "utf8")]);
|
|
9678
|
+
}
|
|
9679
|
+
return iconv.encode(text, "gb18030");
|
|
9680
|
+
}
|
|
9681
|
+
|
|
9215
9682
|
// src/memory/subdir.ts
|
|
9216
|
-
import { existsSync as existsSync8, readFileSync as
|
|
9217
|
-
import { dirname as dirname5, join as join9, relative as relative2, resolve as
|
|
9683
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
9684
|
+
import { dirname as dirname5, join as join9, relative as relative2, resolve as resolve6 } from "path";
|
|
9218
9685
|
function findDirMemory(absDir, rootDir) {
|
|
9219
|
-
const root =
|
|
9220
|
-
const target =
|
|
9686
|
+
const root = resolve6(rootDir);
|
|
9687
|
+
const target = resolve6(absDir);
|
|
9221
9688
|
const rel = relative2(root, target);
|
|
9222
9689
|
if (rel.startsWith("..")) return [];
|
|
9223
9690
|
const found = [];
|
|
@@ -9239,12 +9706,12 @@ function findDirMemory(absDir, rootDir) {
|
|
|
9239
9706
|
return found;
|
|
9240
9707
|
}
|
|
9241
9708
|
function findSubdirMemoryAncestors(absPath, rootDir) {
|
|
9242
|
-
return findDirMemory(dirname5(
|
|
9709
|
+
return findDirMemory(dirname5(resolve6(absPath)), rootDir);
|
|
9243
9710
|
}
|
|
9244
9711
|
function readSubdirMemoryContent(path2) {
|
|
9245
9712
|
let raw;
|
|
9246
9713
|
try {
|
|
9247
|
-
raw =
|
|
9714
|
+
raw = readFileSync11(path2, "utf8");
|
|
9248
9715
|
} catch {
|
|
9249
9716
|
return null;
|
|
9250
9717
|
}
|
|
@@ -9262,15 +9729,22 @@ ${content}`;
|
|
|
9262
9729
|
|
|
9263
9730
|
// src/tools/fs/edit.ts
|
|
9264
9731
|
import { promises as fs } from "fs";
|
|
9265
|
-
import * as
|
|
9732
|
+
import * as pathMod2 from "path";
|
|
9266
9733
|
function displayRel(rootDir, full) {
|
|
9267
|
-
return
|
|
9734
|
+
return pathMod2.relative(rootDir, full).replaceAll("\\", "/");
|
|
9268
9735
|
}
|
|
9269
|
-
|
|
9736
|
+
var READ_BEFORE_EDIT_MARKER = "read_file first";
|
|
9737
|
+
async function applyEdit(rootDir, abs, args, hasRead) {
|
|
9270
9738
|
if (args.search.length === 0) {
|
|
9271
9739
|
throw new Error("edit_file: search cannot be empty");
|
|
9272
9740
|
}
|
|
9273
|
-
|
|
9741
|
+
if (hasRead && !hasRead(abs)) {
|
|
9742
|
+
throw new Error(
|
|
9743
|
+
`edit_file: ${displayRel(rootDir, abs)} was not read this session \u2014 ${READ_BEFORE_EDIT_MARKER} so your SEARCH matches the bytes on disk.`
|
|
9744
|
+
);
|
|
9745
|
+
}
|
|
9746
|
+
const beforeBuf = await fs.readFile(abs);
|
|
9747
|
+
const { text: before, encoding } = decodeFileBuffer(beforeBuf);
|
|
9274
9748
|
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
9275
9749
|
const adaptedSearch = args.search.replace(/\r?\n/g, le);
|
|
9276
9750
|
const adaptedReplace = args.replace.replace(/\r?\n/g, le);
|
|
@@ -9285,7 +9759,7 @@ async function applyEdit(rootDir, abs, args) {
|
|
|
9285
9759
|
);
|
|
9286
9760
|
}
|
|
9287
9761
|
const after = before.slice(0, firstIdx) + adaptedReplace + before.slice(firstIdx + adaptedSearch.length);
|
|
9288
|
-
await fs.writeFile(abs, after,
|
|
9762
|
+
await fs.writeFile(abs, encodeFile(after, encoding));
|
|
9289
9763
|
const rel = displayRel(rootDir, abs);
|
|
9290
9764
|
const header = `edited ${rel} (${adaptedSearch.length}\u2192${adaptedReplace.length} chars)`;
|
|
9291
9765
|
const startLine = before.slice(0, firstIdx).split(/\r?\n/).length;
|
|
@@ -9293,7 +9767,7 @@ async function applyEdit(rootDir, abs, args) {
|
|
|
9293
9767
|
return `${header}
|
|
9294
9768
|
${diff}`;
|
|
9295
9769
|
}
|
|
9296
|
-
async function applyMultiEdit(rootDir, edits) {
|
|
9770
|
+
async function applyMultiEdit(rootDir, edits, hasRead) {
|
|
9297
9771
|
if (edits.length === 0) {
|
|
9298
9772
|
throw new Error("multi_edit: edits must contain at least one entry");
|
|
9299
9773
|
}
|
|
@@ -9319,16 +9793,23 @@ async function applyMultiEdit(rootDir, edits) {
|
|
|
9319
9793
|
}
|
|
9320
9794
|
let state = filesByPath.get(e.abs);
|
|
9321
9795
|
if (!state) {
|
|
9796
|
+
if (hasRead && !hasRead(e.abs)) {
|
|
9797
|
+
throw new Error(
|
|
9798
|
+
`multi_edit: edit #${i + 1} target ${rel} was not read this session \u2014 ${READ_BEFORE_EDIT_MARKER} (no edits applied)`
|
|
9799
|
+
);
|
|
9800
|
+
}
|
|
9322
9801
|
let before;
|
|
9802
|
+
let encoding;
|
|
9323
9803
|
try {
|
|
9324
|
-
|
|
9804
|
+
const buf = await fs.readFile(e.abs);
|
|
9805
|
+
({ text: before, encoding } = decodeFileBuffer(buf));
|
|
9325
9806
|
} catch (err) {
|
|
9326
9807
|
throw new Error(
|
|
9327
9808
|
`multi_edit: edit #${i + 1} cannot read ${rel}: ${err.message} (no edits applied)`
|
|
9328
9809
|
);
|
|
9329
9810
|
}
|
|
9330
9811
|
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
9331
|
-
state = { before, buf: before, le, hunks: [], deltaChars: 0, touched: 0 };
|
|
9812
|
+
state = { before, buf: before, le, hunks: [], deltaChars: 0, touched: 0, encoding };
|
|
9332
9813
|
filesByPath.set(e.abs, state);
|
|
9333
9814
|
}
|
|
9334
9815
|
const adaptedSearch = e.search.replace(/\r?\n/g, state.le);
|
|
@@ -9355,14 +9836,14 @@ ${renderEditDiff(adaptedSearch, adaptedReplace, startLine)}`);
|
|
|
9355
9836
|
const attempted = [];
|
|
9356
9837
|
try {
|
|
9357
9838
|
for (const [abs, state] of filesByPath) {
|
|
9358
|
-
attempted.push({ abs, before: state.before });
|
|
9359
|
-
await fs.writeFile(abs, state.buf,
|
|
9839
|
+
attempted.push({ abs, before: state.before, encoding: state.encoding });
|
|
9840
|
+
await fs.writeFile(abs, encodeFile(state.buf, state.encoding));
|
|
9360
9841
|
}
|
|
9361
9842
|
} catch (writeErr) {
|
|
9362
9843
|
const rollbackFailures = [];
|
|
9363
9844
|
for (const item of [...attempted].reverse()) {
|
|
9364
9845
|
try {
|
|
9365
|
-
await fs.writeFile(item.abs, item.before,
|
|
9846
|
+
await fs.writeFile(item.abs, encodeFile(item.before, item.encoding));
|
|
9366
9847
|
} catch (restoreErr) {
|
|
9367
9848
|
rollbackFailures.push(`${displayRel(rootDir, item.abs)}: ${restoreErr.message}`);
|
|
9368
9849
|
}
|
|
@@ -9439,10 +9920,10 @@ function lineDiff(a, b) {
|
|
|
9439
9920
|
|
|
9440
9921
|
// src/tools/fs/glob.ts
|
|
9441
9922
|
import { promises as fs2 } from "fs";
|
|
9442
|
-
import * as
|
|
9923
|
+
import * as pathMod3 from "path";
|
|
9443
9924
|
import picomatch2 from "picomatch";
|
|
9444
9925
|
function displayRel2(rootDir, full) {
|
|
9445
|
-
return
|
|
9926
|
+
return pathMod3.relative(rootDir, full).replaceAll("\\", "/");
|
|
9446
9927
|
}
|
|
9447
9928
|
async function globFiles(ctx, startAbs, args) {
|
|
9448
9929
|
if (args.signal?.aborted) {
|
|
@@ -9464,7 +9945,7 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
9464
9945
|
return;
|
|
9465
9946
|
}
|
|
9466
9947
|
for (const e of entries) {
|
|
9467
|
-
const full =
|
|
9948
|
+
const full = pathMod3.join(dir, e.name);
|
|
9468
9949
|
if (e.isDirectory()) {
|
|
9469
9950
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
9470
9951
|
await walk2(full);
|
|
@@ -9501,7 +9982,7 @@ async function globFiles(ctx, startAbs, args) {
|
|
|
9501
9982
|
}
|
|
9502
9983
|
|
|
9503
9984
|
// src/tools/fs/outline.ts
|
|
9504
|
-
import * as
|
|
9985
|
+
import * as pathMod4 from "path";
|
|
9505
9986
|
var OUTLINE_MAX_ENTRIES = 30;
|
|
9506
9987
|
var OUTLINE_TAIL_KEEP = 5;
|
|
9507
9988
|
var TS_EXPORT_RE = /^export\s+(?:default\s+)?(?:async\s+)?(function|class|const|let|var|interface|type|enum)\s+\*?\s*(\w+)/;
|
|
@@ -9544,7 +10025,7 @@ var EXT_TO_LANG = {
|
|
|
9544
10025
|
".text": "txt"
|
|
9545
10026
|
};
|
|
9546
10027
|
function extractOutline(filename, lines) {
|
|
9547
|
-
const ext =
|
|
10028
|
+
const ext = pathMod4.extname(filename).toLowerCase();
|
|
9548
10029
|
const lang = EXT_TO_LANG[ext];
|
|
9549
10030
|
if (!lang) return [];
|
|
9550
10031
|
switch (lang) {
|
|
@@ -9685,7 +10166,7 @@ function formatOutline(entries) {
|
|
|
9685
10166
|
|
|
9686
10167
|
// src/tools/fs/search.ts
|
|
9687
10168
|
import { promises as fs3 } from "fs";
|
|
9688
|
-
import * as
|
|
10169
|
+
import * as pathMod5 from "path";
|
|
9689
10170
|
|
|
9690
10171
|
// src/tools/fs/regex-runner.ts
|
|
9691
10172
|
import { Worker } from "worker_threads";
|
|
@@ -9718,7 +10199,7 @@ var RegexRunner = class {
|
|
|
9718
10199
|
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
9719
10200
|
}
|
|
9720
10201
|
testLines(text, source, flags, opts = {}) {
|
|
9721
|
-
return new Promise((
|
|
10202
|
+
return new Promise((resolve14, reject) => {
|
|
9722
10203
|
if (opts.signal?.aborted) {
|
|
9723
10204
|
reject(new Error("regex evaluation aborted"));
|
|
9724
10205
|
return;
|
|
@@ -9731,7 +10212,7 @@ var RegexRunner = class {
|
|
|
9731
10212
|
this.killWorker();
|
|
9732
10213
|
reject(new Error(`regex evaluation exceeded ${timeoutMs}ms`));
|
|
9733
10214
|
}, timeoutMs);
|
|
9734
|
-
const entry = { resolve:
|
|
10215
|
+
const entry = { resolve: resolve14, reject, timer };
|
|
9735
10216
|
if (opts.signal) {
|
|
9736
10217
|
entry.signal = opts.signal;
|
|
9737
10218
|
entry.onAbort = () => {
|
|
@@ -9814,7 +10295,7 @@ function throwIfAborted(signal) {
|
|
|
9814
10295
|
throw new DOMException("search aborted by user", "AbortError");
|
|
9815
10296
|
}
|
|
9816
10297
|
function displayRel3(rootDir, full) {
|
|
9817
|
-
return
|
|
10298
|
+
return pathMod5.relative(rootDir, full).replaceAll("\\", "/");
|
|
9818
10299
|
}
|
|
9819
10300
|
async function searchFiles(ctx, startAbs, args) {
|
|
9820
10301
|
throwIfAborted(args.signal);
|
|
@@ -9838,7 +10319,7 @@ async function searchFiles(ctx, startAbs, args) {
|
|
|
9838
10319
|
}
|
|
9839
10320
|
for (const e of entries) {
|
|
9840
10321
|
throwIfAborted(args.signal);
|
|
9841
|
-
const full =
|
|
10322
|
+
const full = pathMod5.join(dir, e.name);
|
|
9842
10323
|
const lower = e.name.toLowerCase();
|
|
9843
10324
|
const hit = re ? re.test(e.name) : lower.includes(needle);
|
|
9844
10325
|
if (hit) {
|
|
@@ -9930,11 +10411,11 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
9930
10411
|
throwIfTimedOut();
|
|
9931
10412
|
if (e.isDirectory()) {
|
|
9932
10413
|
if (!includeDeps && ctx.skipDirNames.has(e.name)) continue;
|
|
9933
|
-
await walk2(
|
|
10414
|
+
await walk2(pathMod5.join(dir, e.name));
|
|
9934
10415
|
continue;
|
|
9935
10416
|
}
|
|
9936
10417
|
if (!e.isFile()) continue;
|
|
9937
|
-
const full =
|
|
10418
|
+
const full = pathMod5.join(dir, e.name);
|
|
9938
10419
|
if (ctx.nameMatch && !ctx.nameMatch(e.name, displayRel3(ctx.rootDir, full))) continue;
|
|
9939
10420
|
if (ctx.isBinaryByName(e.name)) continue;
|
|
9940
10421
|
let fh;
|
|
@@ -10015,8 +10496,8 @@ async function searchContent(ctx, startAbs, args) {
|
|
|
10015
10496
|
for (let i = realStart; i <= winEnd; i++) {
|
|
10016
10497
|
const line = lines[i];
|
|
10017
10498
|
const display = line.length > 200 ? `${line.slice(0, 200)}\u2026` : line;
|
|
10018
|
-
const
|
|
10019
|
-
if (!pushLine(`${rel}:${i + 1}${
|
|
10499
|
+
const sep2 = hitSet.has(i) ? ":" : "-";
|
|
10500
|
+
if (!pushLine(`${rel}:${i + 1}${sep2} ${display}`)) return;
|
|
10020
10501
|
}
|
|
10021
10502
|
prevWindowEnd = winEnd;
|
|
10022
10503
|
}
|
|
@@ -10052,7 +10533,7 @@ var SKIP_DIR_NAMES = new Set(
|
|
|
10052
10533
|
);
|
|
10053
10534
|
var BINARY_EXTENSIONS = new Set(DEFAULT_INDEX_EXCLUDES.exts);
|
|
10054
10535
|
function displayRel4(rootDir, full) {
|
|
10055
|
-
return
|
|
10536
|
+
return pathMod6.relative(rootDir, full).replaceAll("\\", "/");
|
|
10056
10537
|
}
|
|
10057
10538
|
function looksLikeAbsoluteSystemPath(raw) {
|
|
10058
10539
|
if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
|
|
@@ -10061,8 +10542,8 @@ function looksLikeAbsoluteSystemPath(raw) {
|
|
|
10061
10542
|
);
|
|
10062
10543
|
}
|
|
10063
10544
|
function pathIsUnder(child, parent) {
|
|
10064
|
-
const rel =
|
|
10065
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10545
|
+
const rel = pathMod6.relative(parent, child);
|
|
10546
|
+
return rel === "" || !rel.startsWith("..") && !pathMod6.isAbsolute(rel);
|
|
10066
10547
|
}
|
|
10067
10548
|
var GLOB_METACHARS = /[*?{[]/;
|
|
10068
10549
|
function compileNameFilter(filter) {
|
|
@@ -10094,11 +10575,11 @@ function formatBytes(n) {
|
|
|
10094
10575
|
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
10095
10576
|
}
|
|
10096
10577
|
function registerFilesystemTools(registry, opts) {
|
|
10097
|
-
const rootDir =
|
|
10578
|
+
const rootDir = pathMod6.resolve(opts.rootDir);
|
|
10098
10579
|
const allowWriting = opts.allowWriting !== false;
|
|
10099
10580
|
const outlineThresholdBytes = opts.outlineThresholdBytes ?? DEFAULT_OUTLINE_THRESHOLD_BYTES;
|
|
10100
10581
|
const maxListBytes = opts.maxListBytes ?? DEFAULT_MAX_LIST_BYTES;
|
|
10101
|
-
const normRoot =
|
|
10582
|
+
const normRoot = pathMod6.resolve(rootDir);
|
|
10102
10583
|
const sessionApproved = /* @__PURE__ */ new Set();
|
|
10103
10584
|
const shownSubdirMemory = /* @__PURE__ */ new Set();
|
|
10104
10585
|
function withSubdirMemory(absPath, body) {
|
|
@@ -10131,7 +10612,7 @@ ${body}`;
|
|
|
10131
10612
|
if (pathIsUnder(abs, dir)) return;
|
|
10132
10613
|
}
|
|
10133
10614
|
const stat2 = await safeLstat(abs);
|
|
10134
|
-
const allowPrefix = stat2?.isDirectory() ? abs :
|
|
10615
|
+
const allowPrefix = stat2?.isDirectory() ? abs : pathMod6.dirname(abs);
|
|
10135
10616
|
let pending = inflightGate.get(allowPrefix);
|
|
10136
10617
|
if (!pending) {
|
|
10137
10618
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
@@ -10159,7 +10640,7 @@ ${body}`;
|
|
|
10159
10640
|
throw new Error("path must be a non-empty string");
|
|
10160
10641
|
}
|
|
10161
10642
|
if (looksLikeAbsoluteSystemPath(raw)) {
|
|
10162
|
-
const abs =
|
|
10643
|
+
const abs = pathMod6.resolve(raw);
|
|
10163
10644
|
if (pathIsUnder(abs, normRoot)) return abs;
|
|
10164
10645
|
await ensureOutsideSandboxAllowed(abs, intent, toolName, ctx);
|
|
10165
10646
|
return abs;
|
|
@@ -10169,7 +10650,7 @@ ${body}`;
|
|
|
10169
10650
|
normalized = normalized.slice(1);
|
|
10170
10651
|
}
|
|
10171
10652
|
if (normalized.length === 0) normalized = ".";
|
|
10172
|
-
const resolved =
|
|
10653
|
+
const resolved = pathMod6.resolve(rootDir, normalized);
|
|
10173
10654
|
if (!pathIsUnder(resolved, normRoot)) {
|
|
10174
10655
|
throw new Error(
|
|
10175
10656
|
`path escapes sandbox root (${normRoot}): ${raw} \u2014 use an absolute system path like /Users/foo or C:\\Users\\foo to request approved outside-sandbox access`
|
|
@@ -10231,7 +10712,8 @@ ${body}`;
|
|
|
10231
10712
|
if (looksBinary(raw)) {
|
|
10232
10713
|
return `[refused: ${rel} appears to be binary (${formatBytes(sizeBytes)}) \u2014 read_file returns text only. Use get_file_info for stat.]`;
|
|
10233
10714
|
}
|
|
10234
|
-
const text = raw
|
|
10715
|
+
const { text } = decodeFileBuffer(raw);
|
|
10716
|
+
ctx?.readTracker?.markRead(abs);
|
|
10235
10717
|
let lines = text.split(/\r?\n/);
|
|
10236
10718
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
10237
10719
|
const totalLines = lines.length;
|
|
@@ -10369,7 +10851,7 @@ ${slice.join("\n")}`);
|
|
|
10369
10851
|
lines.push(line);
|
|
10370
10852
|
emitted++;
|
|
10371
10853
|
if (e.isDirectory() && !skip) {
|
|
10372
|
-
await walk2(
|
|
10854
|
+
await walk2(pathMod6.join(dir, e.name), depth + 1);
|
|
10373
10855
|
}
|
|
10374
10856
|
}
|
|
10375
10857
|
};
|
|
@@ -10529,14 +11011,20 @@ ${slice.join("\n")}`);
|
|
|
10529
11011
|
},
|
|
10530
11012
|
fn: async (args, ctx) => {
|
|
10531
11013
|
const abs = await safePath(args.path, "write_file", ctx, "write");
|
|
10532
|
-
await fs4.mkdir(
|
|
10533
|
-
|
|
11014
|
+
await fs4.mkdir(pathMod6.dirname(abs), { recursive: true });
|
|
11015
|
+
let encoding = "utf8";
|
|
11016
|
+
try {
|
|
11017
|
+
encoding = decodeFileBuffer(await fs4.readFile(abs)).encoding;
|
|
11018
|
+
} catch {
|
|
11019
|
+
}
|
|
11020
|
+
await fs4.writeFile(abs, encodeFile(args.content, encoding));
|
|
11021
|
+
ctx?.readTracker?.markRead(abs);
|
|
10534
11022
|
return `wrote ${args.content.length} chars to ${displayRel4(rootDir, abs)}`;
|
|
10535
11023
|
}
|
|
10536
11024
|
});
|
|
10537
11025
|
registry.register({
|
|
10538
11026
|
name: "edit_file",
|
|
10539
|
-
description: "Apply a SEARCH/REPLACE edit to an existing file. `
|
|
11027
|
+
description: "Apply a SEARCH/REPLACE edit to an existing file. Call `read_file` on this path first this session \u2014 the tool refuses otherwise, since SEARCH must match on-disk bytes exactly. `search` is whitespace-sensitive plain text (no regex) and must be unique in the file; otherwise the edit is refused to avoid surprise rewrites.",
|
|
10540
11028
|
parameters: {
|
|
10541
11029
|
type: "object",
|
|
10542
11030
|
properties: {
|
|
@@ -10546,11 +11034,16 @@ ${slice.join("\n")}`);
|
|
|
10546
11034
|
},
|
|
10547
11035
|
required: ["path", "search", "replace"]
|
|
10548
11036
|
},
|
|
10549
|
-
fn: async (args, ctx) => applyEdit(
|
|
11037
|
+
fn: async (args, ctx) => applyEdit(
|
|
11038
|
+
rootDir,
|
|
11039
|
+
await safePath(args.path, "edit_file", ctx, "write"),
|
|
11040
|
+
args,
|
|
11041
|
+
ctx?.readTracker ? (abs) => ctx.readTracker.hasRead(abs) : void 0
|
|
11042
|
+
)
|
|
10550
11043
|
});
|
|
10551
11044
|
registry.register({
|
|
10552
11045
|
name: "multi_edit",
|
|
10553
|
-
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.",
|
|
11046
|
+
description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in one call. Every target file must have been `read_file`'d this session \u2014 the tool refuses the whole batch otherwise. 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.",
|
|
10554
11047
|
parameters: {
|
|
10555
11048
|
type: "object",
|
|
10556
11049
|
properties: {
|
|
@@ -10584,7 +11077,11 @@ ${slice.join("\n")}`);
|
|
|
10584
11077
|
replace: e?.replace
|
|
10585
11078
|
}))
|
|
10586
11079
|
);
|
|
10587
|
-
return applyMultiEdit(
|
|
11080
|
+
return applyMultiEdit(
|
|
11081
|
+
rootDir,
|
|
11082
|
+
resolved,
|
|
11083
|
+
ctx?.readTracker ? (abs) => ctx.readTracker.hasRead(abs) : void 0
|
|
11084
|
+
);
|
|
10588
11085
|
}
|
|
10589
11086
|
});
|
|
10590
11087
|
registry.register({
|
|
@@ -10615,7 +11112,7 @@ ${slice.join("\n")}`);
|
|
|
10615
11112
|
fn: async (args, ctx) => {
|
|
10616
11113
|
const src = await safePath(args.source, "move_file", ctx, "write");
|
|
10617
11114
|
const dst = await safePath(args.destination, "move_file", ctx, "write");
|
|
10618
|
-
await fs4.mkdir(
|
|
11115
|
+
await fs4.mkdir(pathMod6.dirname(dst), { recursive: true });
|
|
10619
11116
|
await fs4.rename(src, dst);
|
|
10620
11117
|
return `moved ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
|
|
10621
11118
|
}
|
|
@@ -10683,7 +11180,7 @@ ${slice.join("\n")}`);
|
|
|
10683
11180
|
fn: async (args, ctx) => {
|
|
10684
11181
|
const src = await safePath(args.source, "copy_file", ctx);
|
|
10685
11182
|
const dst = await safePath(args.destination, "copy_file", ctx, "write");
|
|
10686
|
-
await fs4.mkdir(
|
|
11183
|
+
await fs4.mkdir(pathMod6.dirname(dst), { recursive: true });
|
|
10687
11184
|
await fs4.cp(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
10688
11185
|
return `copied ${displayRel4(rootDir, src)} \u2192 ${displayRel4(rootDir, dst)}`;
|
|
10689
11186
|
}
|
|
@@ -11783,7 +12280,7 @@ ${hint}` : formatted;
|
|
|
11783
12280
|
return parentRegistry;
|
|
11784
12281
|
}
|
|
11785
12282
|
function forkRegistryExcluding(parent, exclude) {
|
|
11786
|
-
const child = new ToolRegistry();
|
|
12283
|
+
const child = new ToolRegistry({ rateLimit: parent.rateLimitPolicy });
|
|
11787
12284
|
for (const spec of parent.specs()) {
|
|
11788
12285
|
const name = spec.function.name;
|
|
11789
12286
|
if (exclude.has(name)) continue;
|
|
@@ -11795,7 +12292,7 @@ function forkRegistryExcluding(parent, exclude) {
|
|
|
11795
12292
|
return child;
|
|
11796
12293
|
}
|
|
11797
12294
|
function forkRegistryWithAllowList(parent, allow, alsoExclude) {
|
|
11798
|
-
const child = new ToolRegistry();
|
|
12295
|
+
const child = new ToolRegistry({ rateLimit: parent.rateLimitPolicy });
|
|
11799
12296
|
for (const spec of parent.specs()) {
|
|
11800
12297
|
const name = spec.function.name;
|
|
11801
12298
|
if (!allow.has(name)) continue;
|
|
@@ -11901,11 +12398,11 @@ var SubagentTelemetry = class {
|
|
|
11901
12398
|
};
|
|
11902
12399
|
|
|
11903
12400
|
// src/tools/shell.ts
|
|
11904
|
-
import * as
|
|
12401
|
+
import * as pathMod11 from "path";
|
|
11905
12402
|
|
|
11906
12403
|
// src/tools/jobs.ts
|
|
11907
12404
|
import { spawn as spawn2 } from "child_process";
|
|
11908
|
-
import * as
|
|
12405
|
+
import * as pathMod7 from "path";
|
|
11909
12406
|
function killProcessTree(pid, signal) {
|
|
11910
12407
|
if (process.platform === "win32") {
|
|
11911
12408
|
const args = ["/pid", String(pid), "/T"];
|
|
@@ -11965,7 +12462,7 @@ var JobRegistry = class {
|
|
|
11965
12462
|
const maxBytes = opts.maxBufferBytes ?? DEFAULT_OUTPUT_CAP_BYTES;
|
|
11966
12463
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
11967
12464
|
const spawnOpts = {
|
|
11968
|
-
cwd:
|
|
12465
|
+
cwd: pathMod7.resolve(opts.cwd),
|
|
11969
12466
|
shell: false,
|
|
11970
12467
|
windowsHide: true,
|
|
11971
12468
|
env: process.env,
|
|
@@ -12150,16 +12647,16 @@ ${job.output.slice(start)}`;
|
|
|
12150
12647
|
let wakeOutput = null;
|
|
12151
12648
|
if (waitFor === "output-or-exit") {
|
|
12152
12649
|
racers.push(
|
|
12153
|
-
new Promise((
|
|
12154
|
-
wakeOutput =
|
|
12155
|
-
job.outputWaiters.add(
|
|
12650
|
+
new Promise((resolve14) => {
|
|
12651
|
+
wakeOutput = resolve14;
|
|
12652
|
+
job.outputWaiters.add(resolve14);
|
|
12156
12653
|
})
|
|
12157
12654
|
);
|
|
12158
12655
|
}
|
|
12159
12656
|
let timer = null;
|
|
12160
12657
|
racers.push(
|
|
12161
|
-
new Promise((
|
|
12162
|
-
timer = setTimeout(
|
|
12658
|
+
new Promise((resolve14) => {
|
|
12659
|
+
timer = setTimeout(resolve14, timeoutMs);
|
|
12163
12660
|
})
|
|
12164
12661
|
);
|
|
12165
12662
|
await Promise.race(racers);
|
|
@@ -12269,13 +12766,13 @@ function latestOutputSince(before, after) {
|
|
|
12269
12766
|
// src/tools/shell/exec.ts
|
|
12270
12767
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
12271
12768
|
import { existsSync as existsSync9, statSync as statSync5 } from "fs";
|
|
12272
|
-
import * as
|
|
12769
|
+
import * as pathMod10 from "path";
|
|
12273
12770
|
|
|
12274
12771
|
// src/tools/shell-chain.ts
|
|
12275
12772
|
import { spawn as spawn3 } from "child_process";
|
|
12276
|
-
import { closeSync, openSync } from "fs";
|
|
12773
|
+
import { constants as constants2, closeSync, lstatSync, openSync, realpathSync } from "fs";
|
|
12277
12774
|
import { devNull } from "os";
|
|
12278
|
-
import * as
|
|
12775
|
+
import * as pathMod8 from "path";
|
|
12279
12776
|
var UnsupportedSyntaxError = class extends Error {
|
|
12280
12777
|
constructor(detail) {
|
|
12281
12778
|
super(`run_command: ${detail}`);
|
|
@@ -12540,7 +13037,49 @@ function isNullDeviceAlias(target) {
|
|
|
12540
13037
|
if (process.platform === "win32" && lower === "nul") return true;
|
|
12541
13038
|
return false;
|
|
12542
13039
|
}
|
|
13040
|
+
function pathIsUnder2(child, parent) {
|
|
13041
|
+
const rel = pathMod8.relative(parent, child);
|
|
13042
|
+
return rel === "" || !rel.startsWith("..") && !pathMod8.isAbsolute(rel);
|
|
13043
|
+
}
|
|
13044
|
+
function openFlags(mode) {
|
|
13045
|
+
const noFollow = "O_NOFOLLOW" in constants2 ? constants2.O_NOFOLLOW : 0;
|
|
13046
|
+
if (mode === "r") return constants2.O_RDONLY | noFollow;
|
|
13047
|
+
if (mode === "w") return constants2.O_WRONLY | constants2.O_CREAT | constants2.O_TRUNC | noFollow;
|
|
13048
|
+
return constants2.O_WRONLY | constants2.O_CREAT | constants2.O_APPEND | noFollow;
|
|
13049
|
+
}
|
|
13050
|
+
function ensureUnderSandbox(path2, sandboxRoot, target) {
|
|
13051
|
+
if (!pathIsUnder2(path2, sandboxRoot)) {
|
|
13052
|
+
throw new Error(
|
|
13053
|
+
`redirect target "${target}" resolves outside the workspace sandbox (${sandboxRoot})`
|
|
13054
|
+
);
|
|
13055
|
+
}
|
|
13056
|
+
}
|
|
13057
|
+
function resolveRedirectTarget(target, cwd) {
|
|
13058
|
+
const lexicalRoot = pathMod8.resolve(cwd);
|
|
13059
|
+
const sandboxRoot = realpathSync(lexicalRoot);
|
|
13060
|
+
const resolved = pathMod8.resolve(lexicalRoot, target);
|
|
13061
|
+
ensureUnderSandbox(resolved, lexicalRoot, target);
|
|
13062
|
+
try {
|
|
13063
|
+
const stat2 = lstatSync(resolved);
|
|
13064
|
+
if (stat2.isSymbolicLink()) {
|
|
13065
|
+
throw new Error(`redirect target "${target}" is a symbolic link`);
|
|
13066
|
+
}
|
|
13067
|
+
ensureUnderSandbox(realpathSync(resolved), sandboxRoot, target);
|
|
13068
|
+
} catch (err) {
|
|
13069
|
+
const code = err.code;
|
|
13070
|
+
if (code !== "ENOENT") throw err;
|
|
13071
|
+
ensureUnderSandbox(realpathSync(pathMod8.dirname(resolved)), sandboxRoot, target);
|
|
13072
|
+
}
|
|
13073
|
+
return resolved;
|
|
13074
|
+
}
|
|
13075
|
+
function validateRedirectTargets(redirects, cwd) {
|
|
13076
|
+
for (const r of redirects) {
|
|
13077
|
+
if (r.kind === "2>&1" || !r.target || isNullDeviceAlias(r.target)) continue;
|
|
13078
|
+
resolveRedirectTarget(r.target, cwd);
|
|
13079
|
+
}
|
|
13080
|
+
}
|
|
12543
13081
|
function openRedirects(redirects, cwd) {
|
|
13082
|
+
validateRedirectTargets(redirects, cwd);
|
|
12544
13083
|
let stdinFd = null;
|
|
12545
13084
|
let stdoutFd = null;
|
|
12546
13085
|
let stderrFd = null;
|
|
@@ -12548,8 +13087,8 @@ function openRedirects(redirects, cwd) {
|
|
|
12548
13087
|
let bothFd = null;
|
|
12549
13088
|
const toClose = [];
|
|
12550
13089
|
const open = (target, flags) => {
|
|
12551
|
-
const resolved = isNullDeviceAlias(target) ? devNull :
|
|
12552
|
-
const fd = openSync(resolved, flags);
|
|
13090
|
+
const resolved = isNullDeviceAlias(target) ? devNull : resolveRedirectTarget(target, cwd);
|
|
13091
|
+
const fd = openSync(resolved, openFlags(flags), 438);
|
|
12553
13092
|
toClose.push(fd);
|
|
12554
13093
|
return fd;
|
|
12555
13094
|
};
|
|
@@ -12656,9 +13195,9 @@ async function runPipeGroup(segments, opts) {
|
|
|
12656
13195
|
}
|
|
12657
13196
|
const exits = await Promise.all(
|
|
12658
13197
|
children.map(
|
|
12659
|
-
(c) => new Promise((
|
|
12660
|
-
c.once("error", () =>
|
|
12661
|
-
c.once("close", (code) =>
|
|
13198
|
+
(c) => new Promise((resolve14) => {
|
|
13199
|
+
c.once("error", () => resolve14(null));
|
|
13200
|
+
c.once("close", (code) => resolve14(code));
|
|
12662
13201
|
})
|
|
12663
13202
|
)
|
|
12664
13203
|
);
|
|
@@ -12703,7 +13242,7 @@ var OutputBuffer = class {
|
|
|
12703
13242
|
|
|
12704
13243
|
// src/tools/shell/parse.ts
|
|
12705
13244
|
import { homedir as homedir7 } from "os";
|
|
12706
|
-
import * as
|
|
13245
|
+
import * as pathMod9 from "path";
|
|
12707
13246
|
|
|
12708
13247
|
// packages/core-utils/src/tildeify.ts
|
|
12709
13248
|
import { homedir as homedir6 } from "os";
|
|
@@ -12908,16 +13447,16 @@ function resolveSensitivePath(token, projectRoot) {
|
|
|
12908
13447
|
return null;
|
|
12909
13448
|
let expanded = token;
|
|
12910
13449
|
if (expanded.startsWith("~")) {
|
|
12911
|
-
expanded =
|
|
13450
|
+
expanded = pathMod9.join(homedir7(), expanded.slice(1));
|
|
12912
13451
|
}
|
|
12913
|
-
return
|
|
13452
|
+
return pathMod9.resolve(projectRoot, expanded);
|
|
12914
13453
|
}
|
|
12915
13454
|
function expandPrefix(prefix) {
|
|
12916
|
-
if (prefix.startsWith("~")) return
|
|
12917
|
-
return
|
|
13455
|
+
if (prefix.startsWith("~")) return pathMod9.join(homedir7(), prefix.slice(1));
|
|
13456
|
+
return pathMod9.resolve(prefix);
|
|
12918
13457
|
}
|
|
12919
13458
|
function pathStartsWithPrefix(normalized, prefix) {
|
|
12920
|
-
return normalized === prefix || normalized.startsWith(`${prefix}${
|
|
13459
|
+
return normalized === prefix || normalized.startsWith(`${prefix}${pathMod9.sep}`);
|
|
12921
13460
|
}
|
|
12922
13461
|
function matchesGlob(name, pattern) {
|
|
12923
13462
|
const regex = new RegExp(
|
|
@@ -12932,17 +13471,39 @@ function hasSensitivePathArgs(argv, projectRoot, extraPrefixes = [], extraPatter
|
|
|
12932
13471
|
for (const token of argv) {
|
|
12933
13472
|
const resolved = resolveSensitivePath(token, projectRoot);
|
|
12934
13473
|
if (!resolved) continue;
|
|
12935
|
-
const normalized =
|
|
13474
|
+
const normalized = pathMod9.normalize(resolved);
|
|
12936
13475
|
for (const pfx of prefixes) {
|
|
12937
13476
|
if (pathStartsWithPrefix(normalized, pfx)) return true;
|
|
12938
13477
|
}
|
|
12939
|
-
const base =
|
|
13478
|
+
const base = pathMod9.basename(normalized);
|
|
12940
13479
|
for (const pat of patterns) {
|
|
12941
13480
|
if (matchesGlob(base, pat)) return true;
|
|
12942
13481
|
}
|
|
12943
13482
|
}
|
|
12944
13483
|
return false;
|
|
12945
13484
|
}
|
|
13485
|
+
function pathIsUnder3(child, parent) {
|
|
13486
|
+
const rel = pathMod9.relative(parent, child);
|
|
13487
|
+
return rel === "" || !rel.startsWith("..") && !pathMod9.isAbsolute(rel);
|
|
13488
|
+
}
|
|
13489
|
+
function redirectTargets(chain) {
|
|
13490
|
+
const targets = [];
|
|
13491
|
+
for (const seg of chain.segments) {
|
|
13492
|
+
for (const r of seg.redirects) {
|
|
13493
|
+
if (r.kind === "2>&1" || !r.target || isNullDeviceAlias(r.target)) continue;
|
|
13494
|
+
targets.push(r.target);
|
|
13495
|
+
}
|
|
13496
|
+
}
|
|
13497
|
+
return targets;
|
|
13498
|
+
}
|
|
13499
|
+
function redirectsEscapeSandbox(chain, projectRoot) {
|
|
13500
|
+
const root = pathMod9.resolve(projectRoot);
|
|
13501
|
+
for (const target of redirectTargets(chain)) {
|
|
13502
|
+
const resolved = pathMod9.resolve(root, target);
|
|
13503
|
+
if (!pathIsUnder3(resolved, root)) return true;
|
|
13504
|
+
}
|
|
13505
|
+
return false;
|
|
13506
|
+
}
|
|
12946
13507
|
function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
12947
13508
|
let argv;
|
|
12948
13509
|
try {
|
|
@@ -12984,6 +13545,18 @@ function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
|
12984
13545
|
return false;
|
|
12985
13546
|
}
|
|
12986
13547
|
if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig);
|
|
13548
|
+
const targets = redirectTargets(chain);
|
|
13549
|
+
if (targets.length > 0 && !projectRoot) return false;
|
|
13550
|
+
if (projectRoot) {
|
|
13551
|
+
if (redirectsEscapeSandbox(chain, projectRoot)) return false;
|
|
13552
|
+
if (hasSensitivePathArgs(
|
|
13553
|
+
targets,
|
|
13554
|
+
projectRoot,
|
|
13555
|
+
sensitivePathConfig?.prefixes,
|
|
13556
|
+
sensitivePathConfig?.patterns
|
|
13557
|
+
))
|
|
13558
|
+
return false;
|
|
13559
|
+
}
|
|
12987
13560
|
return chainAllowed(chain, (seg) => isAllowed(seg, extra, projectRoot, sensitivePathConfig));
|
|
12988
13561
|
}
|
|
12989
13562
|
|
|
@@ -13052,7 +13625,7 @@ async function runCommand(cmd, opts) {
|
|
|
13052
13625
|
};
|
|
13053
13626
|
const { bin, args, spawnOverrides } = prepareSpawn(argv, { env: normalizedEnv });
|
|
13054
13627
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
13055
|
-
return await new Promise((
|
|
13628
|
+
return await new Promise((resolve14, reject) => {
|
|
13056
13629
|
let child;
|
|
13057
13630
|
try {
|
|
13058
13631
|
child = spawn4(bin, args, effectiveSpawnOpts);
|
|
@@ -13106,7 +13679,7 @@ async function runCommand(cmd, opts) {
|
|
|
13106
13679
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
13107
13680
|
|
|
13108
13681
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
13109
|
-
|
|
13682
|
+
resolve14({ exitCode: code, output, timedOut });
|
|
13110
13683
|
});
|
|
13111
13684
|
});
|
|
13112
13685
|
}
|
|
@@ -13128,16 +13701,16 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
13128
13701
|
const platform = opts.platform ?? process.platform;
|
|
13129
13702
|
if (platform !== "win32") return cmd;
|
|
13130
13703
|
if (!cmd) return cmd;
|
|
13131
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
13132
|
-
if (
|
|
13704
|
+
if (cmd.includes("/") || cmd.includes("\\") || pathMod10.isAbsolute(cmd)) return cmd;
|
|
13705
|
+
if (pathMod10.extname(cmd)) return cmd;
|
|
13133
13706
|
const env = opts.env ?? process.env;
|
|
13134
13707
|
const pathExt = (getEnvCaseInsensitive(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
13135
|
-
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" :
|
|
13708
|
+
const delimiter2 = opts.pathDelimiter ?? (platform === "win32" ? ";" : pathMod10.delimiter);
|
|
13136
13709
|
const pathDirs = (getEnvCaseInsensitive(env, "PATH") ?? "").split(delimiter2).filter(Boolean);
|
|
13137
13710
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
13138
13711
|
for (const dir of pathDirs) {
|
|
13139
13712
|
for (const ext of pathExt) {
|
|
13140
|
-
const full =
|
|
13713
|
+
const full = pathMod10.win32.join(dir, cmd + ext);
|
|
13141
13714
|
if (isFile(full)) return full;
|
|
13142
13715
|
}
|
|
13143
13716
|
}
|
|
@@ -13253,8 +13826,8 @@ function withUtf8Codepage(cmdline) {
|
|
|
13253
13826
|
function isBareWindowsName(s) {
|
|
13254
13827
|
if (!s) return false;
|
|
13255
13828
|
if (s.includes("/") || s.includes("\\")) return false;
|
|
13256
|
-
if (
|
|
13257
|
-
if (
|
|
13829
|
+
if (pathMod10.isAbsolute(s)) return false;
|
|
13830
|
+
if (pathMod10.extname(s)) return false;
|
|
13258
13831
|
return true;
|
|
13259
13832
|
}
|
|
13260
13833
|
function quoteForCmdExe(arg) {
|
|
@@ -13275,7 +13848,7 @@ var NeedsConfirmationError = class extends Error {
|
|
|
13275
13848
|
}
|
|
13276
13849
|
};
|
|
13277
13850
|
function registerShellTools(registry, opts) {
|
|
13278
|
-
const rootDir =
|
|
13851
|
+
const rootDir = pathMod11.resolve(opts.rootDir);
|
|
13279
13852
|
const timeoutSec = opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC;
|
|
13280
13853
|
const maxOutputChars = opts.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
|
|
13281
13854
|
const jobs = opts.jobs ?? new JobRegistry();
|
|
@@ -13286,7 +13859,7 @@ function registerShellTools(registry, opts) {
|
|
|
13286
13859
|
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
13287
13860
|
registry.register({
|
|
13288
13861
|
name: "run_command",
|
|
13289
|
-
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.',
|
|
13862
|
+
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\nDO NOT use run_command for file operations \u2014 use write_file, edit_file, multi_edit, copy_file, move_file, or delete_file instead. Shell utilities (echo, cp, sed, cat, tee, perl, python -c, etc.) bypass validation, lack rollback, and will trigger user confirmation gates that waste turns.\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.',
|
|
13290
13863
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
13291
13864
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
13292
13865
|
// during planning. Anything that would otherwise trigger a
|
|
@@ -13488,11 +14061,11 @@ function registerShellTools(registry, opts) {
|
|
|
13488
14061
|
return registry;
|
|
13489
14062
|
}
|
|
13490
14063
|
function resolveCwdInsideRoot(rootDir, raw) {
|
|
13491
|
-
const root =
|
|
14064
|
+
const root = pathMod11.resolve(rootDir);
|
|
13492
14065
|
if (!raw || !raw.trim()) return root;
|
|
13493
|
-
const resolved =
|
|
13494
|
-
const rel =
|
|
13495
|
-
if (rel.startsWith("..") ||
|
|
14066
|
+
const resolved = pathMod11.resolve(root, raw);
|
|
14067
|
+
const rel = pathMod11.relative(root, resolved);
|
|
14068
|
+
if (rel.startsWith("..") || pathMod11.isAbsolute(rel)) {
|
|
13496
14069
|
throw new Error(
|
|
13497
14070
|
`run_background: cwd "${raw}" resolves outside the workspace root (${root}). Pass a workspace-relative path.`
|
|
13498
14071
|
);
|
|
@@ -13540,17 +14113,20 @@ ${r.output}` : header;
|
|
|
13540
14113
|
}
|
|
13541
14114
|
|
|
13542
14115
|
// src/tools/web.ts
|
|
14116
|
+
import { lookup } from "dns/promises";
|
|
14117
|
+
import { isIP } from "net";
|
|
13543
14118
|
import { parse as parseHtml } from "node-html-parser";
|
|
13544
14119
|
var DEFAULT_FETCH_MAX_CHARS = 32e3;
|
|
13545
14120
|
var DEFAULT_FETCH_TIMEOUT_MS = 15e3;
|
|
13546
14121
|
var DEFAULT_TOPK = 5;
|
|
13547
14122
|
var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
13548
14123
|
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";
|
|
13549
|
-
var
|
|
14124
|
+
var BING_ENDPOINT = "https://cn.bing.com/search";
|
|
13550
14125
|
var METASO_ENDPOINT = "https://metaso.cn/api/v1";
|
|
13551
14126
|
var TAVILY_ENDPOINT = "https://api.tavily.com/search";
|
|
13552
14127
|
var PERPLEXITY_ENDPOINT = "https://api.perplexity.ai/chat/completions";
|
|
13553
14128
|
var EXA_ENDPOINT = "https://api.exa.ai/answer";
|
|
14129
|
+
var FETCH_MAX_REDIRECTS = 5;
|
|
13554
14130
|
function searchStatusError(status) {
|
|
13555
14131
|
if (status === 429) return t("webErrors.rateLimit429");
|
|
13556
14132
|
if (status === 403) return t("webErrors.forbidden403");
|
|
@@ -13563,6 +14139,63 @@ function fetchStatusError(status, url) {
|
|
|
13563
14139
|
if (status >= 500 && status <= 599) return t("webErrors.fetchServerError5xx", { status, url });
|
|
13564
14140
|
return t("webErrors.fetchStatus", { status, url });
|
|
13565
14141
|
}
|
|
14142
|
+
function parseIpv4(address) {
|
|
14143
|
+
const parts = address.split(".");
|
|
14144
|
+
if (parts.length !== 4) return null;
|
|
14145
|
+
let out = 0;
|
|
14146
|
+
for (const part of parts) {
|
|
14147
|
+
if (!/^\d+$/.test(part)) return null;
|
|
14148
|
+
const n = Number(part);
|
|
14149
|
+
if (!Number.isInteger(n) || n < 0 || n > 255) return null;
|
|
14150
|
+
out = (out << 8) + n;
|
|
14151
|
+
}
|
|
14152
|
+
return out >>> 0;
|
|
14153
|
+
}
|
|
14154
|
+
function ipv4InRange(value, base, bits) {
|
|
14155
|
+
const parsed = parseIpv4(base);
|
|
14156
|
+
if (parsed === null) return false;
|
|
14157
|
+
const mask = bits === 0 ? 0 : 4294967295 << 32 - bits >>> 0;
|
|
14158
|
+
return (value & mask) === (parsed & mask);
|
|
14159
|
+
}
|
|
14160
|
+
function isPrivateIpv4(address) {
|
|
14161
|
+
const value = parseIpv4(address);
|
|
14162
|
+
if (value === null) return false;
|
|
14163
|
+
return ipv4InRange(value, "0.0.0.0", 8) || ipv4InRange(value, "10.0.0.0", 8) || ipv4InRange(value, "100.64.0.0", 10) || ipv4InRange(value, "127.0.0.0", 8) || ipv4InRange(value, "169.254.0.0", 16) || ipv4InRange(value, "172.16.0.0", 12) || ipv4InRange(value, "192.0.0.0", 24) || ipv4InRange(value, "192.0.2.0", 24) || ipv4InRange(value, "192.168.0.0", 16) || ipv4InRange(value, "198.18.0.0", 15) || ipv4InRange(value, "198.51.100.0", 24) || ipv4InRange(value, "203.0.113.0", 24) || ipv4InRange(value, "224.0.0.0", 4) || ipv4InRange(value, "240.0.0.0", 4);
|
|
14164
|
+
}
|
|
14165
|
+
function normalizeIpv6(address) {
|
|
14166
|
+
return address.toLowerCase().replace(/(^|:)0+([0-9a-f])/g, "$1$2");
|
|
14167
|
+
}
|
|
14168
|
+
function isPrivateIpv6(address) {
|
|
14169
|
+
const normalized = normalizeIpv6(address);
|
|
14170
|
+
const mapped = /^::ffff:(?:0+:)?(\d+\.\d+\.\d+\.\d+)$/i.exec(normalized);
|
|
14171
|
+
if (mapped) return isPrivateIpv4(mapped[1]);
|
|
14172
|
+
return normalized === "::" || normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb") || normalized.startsWith("ff");
|
|
14173
|
+
}
|
|
14174
|
+
function isInternalAddress(address) {
|
|
14175
|
+
const family = isIP(address);
|
|
14176
|
+
if (family === 4) return isPrivateIpv4(address);
|
|
14177
|
+
if (family === 6) return isPrivateIpv6(address);
|
|
14178
|
+
return false;
|
|
14179
|
+
}
|
|
14180
|
+
async function assertPublicHttpUrl(rawUrl) {
|
|
14181
|
+
const url = new URL(rawUrl);
|
|
14182
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
14183
|
+
throw new Error(`web_fetch refuses non-HTTP URL: ${url.protocol}`);
|
|
14184
|
+
}
|
|
14185
|
+
const host = url.hostname;
|
|
14186
|
+
const literal = isIP(host);
|
|
14187
|
+
const addresses = literal ? [host] : (await lookup(host, { all: true, verbatim: true })).map((entry) => entry.address);
|
|
14188
|
+
if (addresses.length === 0 || addresses.some(isInternalAddress)) {
|
|
14189
|
+
throw new Error(`web_fetch refuses internal or reserved host: ${host}`);
|
|
14190
|
+
}
|
|
14191
|
+
return url;
|
|
14192
|
+
}
|
|
14193
|
+
function redirectLocation(resp, currentUrl) {
|
|
14194
|
+
if (resp.status < 300 || resp.status > 399) return null;
|
|
14195
|
+
const location = resp.headers.get("location");
|
|
14196
|
+
if (!location) return null;
|
|
14197
|
+
return new URL(location, currentUrl).toString();
|
|
14198
|
+
}
|
|
13566
14199
|
async function webSearch(query, opts = {}) {
|
|
13567
14200
|
if (opts.engine === "metaso") {
|
|
13568
14201
|
return searchMetaso(query, opts);
|
|
@@ -13579,29 +14212,29 @@ async function webSearch(query, opts = {}) {
|
|
|
13579
14212
|
if (opts.engine === "exa") {
|
|
13580
14213
|
return searchExa(query, opts);
|
|
13581
14214
|
}
|
|
13582
|
-
return
|
|
14215
|
+
return searchBing(query, opts);
|
|
13583
14216
|
}
|
|
13584
|
-
async function
|
|
14217
|
+
async function searchBing(query, opts = {}) {
|
|
13585
14218
|
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
13586
|
-
const resp = await fetch(`${
|
|
14219
|
+
const resp = await fetch(`${BING_ENDPOINT}?q=${encodeURIComponent(query)}`, {
|
|
13587
14220
|
headers: {
|
|
13588
14221
|
"User-Agent": USER_AGENT,
|
|
13589
14222
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9",
|
|
13590
|
-
"Accept-Language": "
|
|
14223
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
|
|
13591
14224
|
},
|
|
13592
14225
|
signal: opts.signal,
|
|
13593
14226
|
redirect: "follow"
|
|
13594
14227
|
});
|
|
13595
14228
|
if (!resp.ok) throw new Error(searchStatusError(resp.status));
|
|
13596
14229
|
const html = await resp.text();
|
|
13597
|
-
const results =
|
|
14230
|
+
const results = parseBingResults(html).slice(0, topK);
|
|
13598
14231
|
if (results.length === 0) {
|
|
13599
14232
|
if (/no results found|did not match any documents/i.test(html)) return [];
|
|
13600
14233
|
if (/captcha|verify you are human|access denied|forbidden/i.test(html)) {
|
|
13601
|
-
throw new Error(t("webErrors.
|
|
14234
|
+
throw new Error(t("webErrors.bingBlocked"));
|
|
13602
14235
|
}
|
|
13603
14236
|
throw new Error(
|
|
13604
|
-
t("webErrors.
|
|
14237
|
+
t("webErrors.bingNoResults", {
|
|
13605
14238
|
chars: html.length,
|
|
13606
14239
|
preview: html.slice(0, 120).replace(/\s+/g, " ")
|
|
13607
14240
|
})
|
|
@@ -13654,6 +14287,7 @@ async function searchSearxng(query, opts = {}) {
|
|
|
13654
14287
|
async function searchMetaso(query, opts = {}) {
|
|
13655
14288
|
const topK = Math.max(1, Math.min(100, opts.topK ?? DEFAULT_TOPK));
|
|
13656
14289
|
const apiKey = loadMetasoApiKey();
|
|
14290
|
+
if (!apiKey) throw new Error(t("webErrors.metasoMissingKey"));
|
|
13657
14291
|
let resp;
|
|
13658
14292
|
try {
|
|
13659
14293
|
resp = await fetch(`${METASO_ENDPOINT}/search`, {
|
|
@@ -13917,35 +14551,19 @@ function parseSearxngHtmlResults(html) {
|
|
|
13917
14551
|
}
|
|
13918
14552
|
return results;
|
|
13919
14553
|
}
|
|
13920
|
-
function
|
|
13921
|
-
const
|
|
13922
|
-
const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
|
|
13923
|
-
let m;
|
|
13924
|
-
while (true) {
|
|
13925
|
-
m = titleAnchorRe.exec(html);
|
|
13926
|
-
if (m === null) break;
|
|
13927
|
-
titles.push(m[0]);
|
|
13928
|
-
}
|
|
13929
|
-
const snippets = [];
|
|
13930
|
-
const snippetRe = /<p\b[^>]*\bclass="s"[^>]*>([\s\S]*?)<\/p>/g;
|
|
13931
|
-
while (true) {
|
|
13932
|
-
m = snippetRe.exec(html);
|
|
13933
|
-
if (m === null) break;
|
|
13934
|
-
snippets.push(m[1] ?? "");
|
|
13935
|
-
}
|
|
13936
|
-
const hrefRe = /href="([^"]+)"/;
|
|
13937
|
-
const innerRe = /<a\b[^>]*>([\s\S]*?)<\/a>/;
|
|
14554
|
+
function parseBingResults(html) {
|
|
14555
|
+
const root = parseHtml(html);
|
|
13938
14556
|
const results = [];
|
|
13939
|
-
for (
|
|
13940
|
-
const anchor =
|
|
13941
|
-
|
|
13942
|
-
const
|
|
13943
|
-
if (!
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
});
|
|
14557
|
+
for (const li of root.querySelectorAll("li.b_algo")) {
|
|
14558
|
+
const anchor = li.querySelector("h2 a[href]");
|
|
14559
|
+
if (!anchor) continue;
|
|
14560
|
+
const href = anchor.getAttribute("href");
|
|
14561
|
+
if (!href) continue;
|
|
14562
|
+
const title = anchor.textContent.trim();
|
|
14563
|
+
if (!title) continue;
|
|
14564
|
+
const cap = li.querySelector("div.b_caption p");
|
|
14565
|
+
const snippet = cap ? cap.textContent.trim().replace(/\s+/g, " ") : "";
|
|
14566
|
+
results.push({ title, url: href, snippet });
|
|
13949
14567
|
}
|
|
13950
14568
|
return results;
|
|
13951
14569
|
}
|
|
@@ -13961,12 +14579,23 @@ async function webFetch(url, opts = {}) {
|
|
|
13961
14579
|
const cancel = () => ctl.abort();
|
|
13962
14580
|
opts.signal?.addEventListener("abort", cancel, { once: true });
|
|
13963
14581
|
let resp;
|
|
14582
|
+
let currentUrl = url;
|
|
13964
14583
|
try {
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
14584
|
+
for (let redirects = 0; ; redirects++) {
|
|
14585
|
+
const parsed = await assertPublicHttpUrl(currentUrl);
|
|
14586
|
+
if (ctl.signal.aborted) throw new DOMException("aborted", "AbortError");
|
|
14587
|
+
resp = await fetch(parsed, {
|
|
14588
|
+
headers: { "User-Agent": USER_AGENT, Accept: "text/html,text/plain,*/*" },
|
|
14589
|
+
signal: ctl.signal,
|
|
14590
|
+
redirect: "manual"
|
|
14591
|
+
});
|
|
14592
|
+
const nextUrl = redirectLocation(resp, parsed.toString());
|
|
14593
|
+
if (!nextUrl) break;
|
|
14594
|
+
if (redirects >= FETCH_MAX_REDIRECTS) {
|
|
14595
|
+
throw new Error(`web_fetch redirect limit exceeded for ${url}`);
|
|
14596
|
+
}
|
|
14597
|
+
currentUrl = nextUrl;
|
|
14598
|
+
}
|
|
13970
14599
|
} catch (err) {
|
|
13971
14600
|
if (timedOut) {
|
|
13972
14601
|
throw new Error(t("webErrors.fetchTimeout", { ms: timeoutMs, url }));
|
|
@@ -13989,7 +14618,7 @@ async function webFetch(url, opts = {}) {
|
|
|
13989
14618
|
const finalText = truncated ? `${text.slice(0, maxChars)}
|
|
13990
14619
|
|
|
13991
14620
|
[\u2026 truncated ${text.length - maxChars} chars \u2026]` : text;
|
|
13992
|
-
return { url, title, text: finalText, truncated };
|
|
14621
|
+
return { url: currentUrl, title, text: finalText, truncated };
|
|
13993
14622
|
}
|
|
13994
14623
|
async function readBodyCapped(resp, maxBytes) {
|
|
13995
14624
|
if (!resp.body) return await resp.text();
|
|
@@ -14061,9 +14690,6 @@ function walkExtract(node, out) {
|
|
|
14061
14690
|
for (const child of node.childNodes) walkExtract(child, out);
|
|
14062
14691
|
if (isBreak) out.push("\n");
|
|
14063
14692
|
}
|
|
14064
|
-
function stripHtml(s) {
|
|
14065
|
-
return parseHtml(s).text;
|
|
14066
|
-
}
|
|
14067
14693
|
var HTML_ENTITIES = {
|
|
14068
14694
|
amp: "&",
|
|
14069
14695
|
lt: "<",
|
|
@@ -14176,12 +14802,12 @@ ${i + 1}. ${r.title}`);
|
|
|
14176
14802
|
}
|
|
14177
14803
|
|
|
14178
14804
|
// src/env.ts
|
|
14179
|
-
import { readFileSync as
|
|
14180
|
-
import { resolve as
|
|
14805
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
14806
|
+
import { resolve as resolve12 } from "path";
|
|
14181
14807
|
function loadDotenv(path2 = ".env") {
|
|
14182
14808
|
let raw;
|
|
14183
14809
|
try {
|
|
14184
|
-
raw =
|
|
14810
|
+
raw = readFileSync12(resolve12(process.cwd(), path2), "utf8");
|
|
14185
14811
|
} catch {
|
|
14186
14812
|
return;
|
|
14187
14813
|
}
|
|
@@ -14200,7 +14826,7 @@ function loadDotenv(path2 = ".env") {
|
|
|
14200
14826
|
}
|
|
14201
14827
|
|
|
14202
14828
|
// src/transcript/log.ts
|
|
14203
|
-
import { createWriteStream, readFileSync as
|
|
14829
|
+
import { createWriteStream, readFileSync as readFileSync13 } from "fs";
|
|
14204
14830
|
function recordFromLoopEvent(ev, extra) {
|
|
14205
14831
|
const rec = {
|
|
14206
14832
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -14243,7 +14869,7 @@ function openTranscriptFile(path2, meta) {
|
|
|
14243
14869
|
return stream;
|
|
14244
14870
|
}
|
|
14245
14871
|
function readTranscript(path2) {
|
|
14246
|
-
const raw =
|
|
14872
|
+
const raw = readFileSync13(path2, "utf8");
|
|
14247
14873
|
return parseTranscript(raw);
|
|
14248
14874
|
}
|
|
14249
14875
|
function parseTranscript(raw) {
|
|
@@ -14630,25 +15256,25 @@ function truncate(s, n) {
|
|
|
14630
15256
|
}
|
|
14631
15257
|
|
|
14632
15258
|
// src/version.ts
|
|
14633
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as
|
|
15259
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
14634
15260
|
import { homedir as homedir8 } from "os";
|
|
14635
|
-
import { dirname as
|
|
15261
|
+
import { dirname as dirname8, join as join14 } from "path";
|
|
14636
15262
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14637
15263
|
var REGISTRY_URL = "https://registry.npmjs.org/reasonix/latest";
|
|
14638
15264
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
14639
15265
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
14640
15266
|
function readPackageVersion() {
|
|
14641
15267
|
try {
|
|
14642
|
-
let dir =
|
|
15268
|
+
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
14643
15269
|
for (let i = 0; i < 6; i++) {
|
|
14644
15270
|
const p = join14(dir, "package.json");
|
|
14645
15271
|
if (existsSync10(p)) {
|
|
14646
|
-
const pkg = JSON.parse(
|
|
15272
|
+
const pkg = JSON.parse(readFileSync14(p, "utf8"));
|
|
14647
15273
|
if (pkg?.name === "reasonix" && typeof pkg.version === "string") {
|
|
14648
15274
|
return pkg.version;
|
|
14649
15275
|
}
|
|
14650
15276
|
}
|
|
14651
|
-
const parent =
|
|
15277
|
+
const parent = dirname8(dir);
|
|
14652
15278
|
if (parent === dir) break;
|
|
14653
15279
|
dir = parent;
|
|
14654
15280
|
}
|
|
@@ -14662,7 +15288,7 @@ function cachePath(homeDirOverride) {
|
|
|
14662
15288
|
}
|
|
14663
15289
|
function readCache(homeDirOverride) {
|
|
14664
15290
|
try {
|
|
14665
|
-
const raw =
|
|
15291
|
+
const raw = readFileSync14(cachePath(homeDirOverride), "utf8");
|
|
14666
15292
|
const parsed = JSON.parse(raw);
|
|
14667
15293
|
if (parsed && typeof parsed.version === "string" && typeof parsed.checkedAt === "number") {
|
|
14668
15294
|
return parsed;
|
|
@@ -14674,8 +15300,8 @@ function readCache(homeDirOverride) {
|
|
|
14674
15300
|
function writeCache(entry, homeDirOverride) {
|
|
14675
15301
|
try {
|
|
14676
15302
|
const p = cachePath(homeDirOverride);
|
|
14677
|
-
mkdirSync5(
|
|
14678
|
-
|
|
15303
|
+
mkdirSync5(dirname8(p), { recursive: true });
|
|
15304
|
+
writeFileSync6(p, JSON.stringify(entry), "utf8");
|
|
14679
15305
|
} catch {
|
|
14680
15306
|
}
|
|
14681
15307
|
}
|
|
@@ -14890,7 +15516,7 @@ var McpClient = class {
|
|
|
14890
15516
|
const id = this.nextId++;
|
|
14891
15517
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
14892
15518
|
let abortHandler = null;
|
|
14893
|
-
const promise = new Promise((
|
|
15519
|
+
const promise = new Promise((resolve14, reject) => {
|
|
14894
15520
|
const timeout = setTimeout(() => {
|
|
14895
15521
|
this.pending.delete(id);
|
|
14896
15522
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -14899,7 +15525,7 @@ var McpClient = class {
|
|
|
14899
15525
|
);
|
|
14900
15526
|
}, this.requestTimeoutMs);
|
|
14901
15527
|
this.pending.set(id, {
|
|
14902
|
-
resolve:
|
|
15528
|
+
resolve: resolve14,
|
|
14903
15529
|
reject,
|
|
14904
15530
|
timeout
|
|
14905
15531
|
});
|
|
@@ -15031,12 +15657,12 @@ var StdioTransport = class {
|
|
|
15031
15657
|
}
|
|
15032
15658
|
async send(message) {
|
|
15033
15659
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
15034
|
-
return new Promise((
|
|
15660
|
+
return new Promise((resolve14, reject) => {
|
|
15035
15661
|
const line = `${JSON.stringify(message)}
|
|
15036
15662
|
`;
|
|
15037
15663
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
15038
15664
|
if (err) reject(err);
|
|
15039
|
-
else
|
|
15665
|
+
else resolve14();
|
|
15040
15666
|
});
|
|
15041
15667
|
});
|
|
15042
15668
|
}
|
|
@@ -15047,8 +15673,8 @@ var StdioTransport = class {
|
|
|
15047
15673
|
continue;
|
|
15048
15674
|
}
|
|
15049
15675
|
if (this.closed) return;
|
|
15050
|
-
const next = await new Promise((
|
|
15051
|
-
this.waiters.push(
|
|
15676
|
+
const next = await new Promise((resolve14) => {
|
|
15677
|
+
this.waiters.push(resolve14);
|
|
15052
15678
|
});
|
|
15053
15679
|
if (next === null) return;
|
|
15054
15680
|
yield next;
|
|
@@ -15128,8 +15754,8 @@ var SseTransport = class {
|
|
|
15128
15754
|
constructor(opts) {
|
|
15129
15755
|
this.url = opts.url;
|
|
15130
15756
|
this.headers = opts.headers ?? {};
|
|
15131
|
-
this.endpointReady = new Promise((
|
|
15132
|
-
this.resolveEndpoint =
|
|
15757
|
+
this.endpointReady = new Promise((resolve14, reject) => {
|
|
15758
|
+
this.resolveEndpoint = resolve14;
|
|
15133
15759
|
this.rejectEndpoint = reject;
|
|
15134
15760
|
});
|
|
15135
15761
|
this.endpointReady.catch(() => void 0);
|
|
@@ -15156,8 +15782,8 @@ var SseTransport = class {
|
|
|
15156
15782
|
continue;
|
|
15157
15783
|
}
|
|
15158
15784
|
if (this.closed) return;
|
|
15159
|
-
const next = await new Promise((
|
|
15160
|
-
this.waiters.push(
|
|
15785
|
+
const next = await new Promise((resolve14) => {
|
|
15786
|
+
this.waiters.push(resolve14);
|
|
15161
15787
|
});
|
|
15162
15788
|
if (next === null) return;
|
|
15163
15789
|
yield next;
|
|
@@ -15343,8 +15969,8 @@ var StreamableHttpTransport = class {
|
|
|
15343
15969
|
continue;
|
|
15344
15970
|
}
|
|
15345
15971
|
if (this.closed) return;
|
|
15346
|
-
const next = await new Promise((
|
|
15347
|
-
this.waiters.push(
|
|
15972
|
+
const next = await new Promise((resolve14) => {
|
|
15973
|
+
this.waiters.push(resolve14);
|
|
15348
15974
|
});
|
|
15349
15975
|
if (next === null) return;
|
|
15350
15976
|
yield next;
|
|
@@ -15442,13 +16068,13 @@ import {
|
|
|
15442
16068
|
ftruncateSync,
|
|
15443
16069
|
mkdirSync as mkdirSync6,
|
|
15444
16070
|
openSync as openSync2,
|
|
15445
|
-
readFileSync as
|
|
16071
|
+
readFileSync as readFileSync15,
|
|
15446
16072
|
readSync,
|
|
15447
16073
|
unlinkSync as unlinkSync3,
|
|
15448
|
-
writeFileSync as
|
|
16074
|
+
writeFileSync as writeFileSync7,
|
|
15449
16075
|
writeSync
|
|
15450
16076
|
} from "fs";
|
|
15451
|
-
import { dirname as
|
|
16077
|
+
import { dirname as dirname9, isAbsolute as isAbsolute9, relative as relative10, resolve as resolve13 } from "path";
|
|
15452
16078
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
15453
16079
|
function parseEditBlocks(text) {
|
|
15454
16080
|
const out = [];
|
|
@@ -15465,10 +16091,30 @@ function parseEditBlocks(text) {
|
|
|
15465
16091
|
}
|
|
15466
16092
|
return out;
|
|
15467
16093
|
}
|
|
16094
|
+
function resolveEditPath(rootDir, rawPath) {
|
|
16095
|
+
const absRoot = resolve13(rootDir);
|
|
16096
|
+
if (/^[A-Za-z]:[\\/]/.test(rawPath) || looksLikeAbsoluteSystemPath2(rawPath)) {
|
|
16097
|
+
return resolve13(rawPath);
|
|
16098
|
+
}
|
|
16099
|
+
let rooted = rawPath;
|
|
16100
|
+
while (rooted.startsWith("/") || rooted.startsWith("\\")) {
|
|
16101
|
+
rooted = rooted.slice(1);
|
|
16102
|
+
}
|
|
16103
|
+
return resolve13(absRoot, rooted || ".");
|
|
16104
|
+
}
|
|
16105
|
+
function looksLikeAbsoluteSystemPath2(rawPath) {
|
|
16106
|
+
return /^\/(?:home|Users|etc|var|opt|tmp|usr|mnt|Library|Volumes|proc|sys|dev|run|srv|media|Applications|System|root|boot|private)(?:[/\\]|$)/.test(
|
|
16107
|
+
rawPath
|
|
16108
|
+
);
|
|
16109
|
+
}
|
|
16110
|
+
function pathIsUnder4(child, parent) {
|
|
16111
|
+
const rel = relative10(parent, child);
|
|
16112
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute9(rel);
|
|
16113
|
+
}
|
|
15468
16114
|
function applyEditBlock(block, rootDir) {
|
|
15469
|
-
const absRoot =
|
|
15470
|
-
const absTarget =
|
|
15471
|
-
if (absTarget
|
|
16115
|
+
const absRoot = resolve13(rootDir);
|
|
16116
|
+
const absTarget = resolveEditPath(rootDir, block.path);
|
|
16117
|
+
if (!pathIsUnder4(absTarget, absRoot)) {
|
|
15472
16118
|
return {
|
|
15473
16119
|
path: block.path,
|
|
15474
16120
|
status: "path-escape",
|
|
@@ -15478,7 +16124,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
15478
16124
|
const searchEmpty = block.search.length === 0;
|
|
15479
16125
|
if (searchEmpty) {
|
|
15480
16126
|
try {
|
|
15481
|
-
mkdirSync6(
|
|
16127
|
+
mkdirSync6(dirname9(absTarget), { recursive: true });
|
|
15482
16128
|
const fd = openSync2(absTarget, "wx");
|
|
15483
16129
|
try {
|
|
15484
16130
|
writeSync(fd, block.replace);
|
|
@@ -15521,7 +16167,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
15521
16167
|
if (n <= 0) break;
|
|
15522
16168
|
readBytes += n;
|
|
15523
16169
|
}
|
|
15524
|
-
const content = inBuf.
|
|
16170
|
+
const { text: content, encoding } = decodeFileBuffer(inBuf.subarray(0, readBytes));
|
|
15525
16171
|
const le = lineEndingOf(content);
|
|
15526
16172
|
const adaptedSearch = block.search.replace(/\r?\n/g, le);
|
|
15527
16173
|
const adaptedReplace = block.replace.replace(/\r?\n/g, le);
|
|
@@ -15542,7 +16188,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
15542
16188
|
};
|
|
15543
16189
|
}
|
|
15544
16190
|
const replaced = `${content.slice(0, idx)}${adaptedReplace}${content.slice(idx + adaptedSearch.length)}`;
|
|
15545
|
-
const outBuf =
|
|
16191
|
+
const outBuf = encodeFile(replaced, encoding);
|
|
15546
16192
|
ftruncateSync(fd, outBuf.length);
|
|
15547
16193
|
let written = 0;
|
|
15548
16194
|
while (written < outBuf.length) {
|
|
@@ -15562,19 +16208,21 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
15562
16208
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
15563
16209
|
}
|
|
15564
16210
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
15565
|
-
const absRoot =
|
|
16211
|
+
const absRoot = resolve13(rootDir);
|
|
15566
16212
|
const seen = /* @__PURE__ */ new Set();
|
|
15567
16213
|
const snapshots = [];
|
|
15568
16214
|
for (const b of blocks) {
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
16215
|
+
const abs = resolveEditPath(rootDir, b.path);
|
|
16216
|
+
if (!pathIsUnder4(abs, absRoot)) continue;
|
|
16217
|
+
if (seen.has(abs)) continue;
|
|
16218
|
+
seen.add(abs);
|
|
15572
16219
|
if (!existsSync11(abs)) {
|
|
15573
16220
|
snapshots.push({ path: b.path, prevContent: null });
|
|
15574
16221
|
continue;
|
|
15575
16222
|
}
|
|
15576
16223
|
try {
|
|
15577
|
-
|
|
16224
|
+
const { text, encoding } = decodeFileBuffer(readFileSync15(abs));
|
|
16225
|
+
snapshots.push({ path: b.path, prevContent: text, prevEncoding: encoding });
|
|
15578
16226
|
} catch {
|
|
15579
16227
|
snapshots.push({ path: b.path, prevContent: null });
|
|
15580
16228
|
}
|
|
@@ -15582,10 +16230,10 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
15582
16230
|
return snapshots;
|
|
15583
16231
|
}
|
|
15584
16232
|
function restoreSnapshots(snapshots, rootDir) {
|
|
15585
|
-
const absRoot =
|
|
16233
|
+
const absRoot = resolve13(rootDir);
|
|
15586
16234
|
return snapshots.map((snap) => {
|
|
15587
|
-
const abs =
|
|
15588
|
-
if (abs
|
|
16235
|
+
const abs = resolveEditPath(rootDir, snap.path);
|
|
16236
|
+
if (!pathIsUnder4(abs, absRoot)) {
|
|
15589
16237
|
return {
|
|
15590
16238
|
path: snap.path,
|
|
15591
16239
|
status: "path-escape",
|
|
@@ -15601,7 +16249,7 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
15601
16249
|
message: "removed (the edit had created it)"
|
|
15602
16250
|
};
|
|
15603
16251
|
}
|
|
15604
|
-
|
|
16252
|
+
writeFileSync7(abs, encodeFile(snap.prevContent, snap.prevEncoding ?? "utf8"));
|
|
15605
16253
|
return {
|
|
15606
16254
|
path: snap.path,
|
|
15607
16255
|
status: "applied",
|
|
@@ -15612,15 +16260,12 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
15612
16260
|
}
|
|
15613
16261
|
});
|
|
15614
16262
|
}
|
|
15615
|
-
function sep2() {
|
|
15616
|
-
return process.platform === "win32" ? "\\" : "/";
|
|
15617
|
-
}
|
|
15618
16263
|
function lineEndingOf(text) {
|
|
15619
16264
|
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
15620
16265
|
}
|
|
15621
16266
|
|
|
15622
16267
|
// src/code/prompt.ts
|
|
15623
|
-
import { existsSync as existsSync12, readFileSync as
|
|
16268
|
+
import { existsSync as existsSync12, readFileSync as readFileSync16 } from "fs";
|
|
15624
16269
|
import { join as join15 } from "path";
|
|
15625
16270
|
var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
|
|
15626
16271
|
function codeSystemBase(modelId) {
|
|
@@ -15684,7 +16329,7 @@ the new lines
|
|
|
15684
16329
|
>>>>>>> REPLACE
|
|
15685
16330
|
|
|
15686
16331
|
Rules:
|
|
15687
|
-
- read_file
|
|
16332
|
+
- **Read before edit (enforced).** You MUST call \`read_file\` on the target this session before \`edit_file\` / \`multi_edit\` will accept it \u2014 the tool refuses unread targets up front, so SEARCH text is grounded in on-disk bytes, not a guess. A fold / mechanical truncate clears the tracker, so re-read after one of those before mutating. \`write_file\` counts as a read for that path (the content is what you just wrote).
|
|
15688
16333
|
- One edit per block; multiple blocks per response are fine.
|
|
15689
16334
|
- Create a new file with empty SEARCH:
|
|
15690
16335
|
path/to/new.ts
|
|
@@ -15727,6 +16372,10 @@ When the user says run / start / launch / serve / boot up: start it, verify it c
|
|
|
15727
16372
|
- One short paragraph explaining *why*, then the blocks.
|
|
15728
16373
|
- Silence during exploration is fine \u2014 tool calls first, prose after.
|
|
15729
16374
|
|
|
16375
|
+
# Task integrity \u2014 non-negotiable
|
|
16376
|
+
|
|
16377
|
+
The user's original objective and ALL constraints (especially "do NOT do X", "avoid Y", "never Z") remain in force for the entire session. You may NOT unilaterally simplify, narrow, or change the objective to save tokens, time, or steps. If you believe the objective needs adjustment, ask the user \u2014 do NOT decide on your own.
|
|
16378
|
+
|
|
15730
16379
|
__ESCALATION_CONTRACT__
|
|
15731
16380
|
|
|
15732
16381
|
${TUI_FORMATTING_RULES}
|
|
@@ -15751,7 +16400,7 @@ function codeSystemPrompt(rootDir, opts = {}) {
|
|
|
15751
16400
|
if (existsSync12(gitignorePath)) {
|
|
15752
16401
|
let content;
|
|
15753
16402
|
try {
|
|
15754
|
-
content =
|
|
16403
|
+
content = readFileSync16(gitignorePath, "utf8");
|
|
15755
16404
|
} catch {
|
|
15756
16405
|
}
|
|
15757
16406
|
if (content !== void 0) {
|
|
@@ -15783,21 +16432,21 @@ ${appendParts.join("\n\n")}`;
|
|
|
15783
16432
|
|
|
15784
16433
|
// src/telemetry/usage.ts
|
|
15785
16434
|
import {
|
|
15786
|
-
appendFileSync as
|
|
16435
|
+
appendFileSync as appendFileSync3,
|
|
15787
16436
|
closeSync as closeSync3,
|
|
15788
16437
|
existsSync as existsSync13,
|
|
15789
16438
|
fstatSync as fstatSync2,
|
|
15790
16439
|
mkdirSync as mkdirSync7,
|
|
15791
16440
|
openSync as openSync3,
|
|
15792
|
-
readFileSync as
|
|
16441
|
+
readFileSync as readFileSync17,
|
|
15793
16442
|
readSync as readSync2,
|
|
15794
|
-
renameSync as
|
|
16443
|
+
renameSync as renameSync3,
|
|
15795
16444
|
statSync as statSync6,
|
|
15796
16445
|
unlinkSync as unlinkSync4,
|
|
15797
|
-
writeFileSync as
|
|
16446
|
+
writeFileSync as writeFileSync8
|
|
15798
16447
|
} from "fs";
|
|
15799
16448
|
import { homedir as homedir9 } from "os";
|
|
15800
|
-
import { dirname as
|
|
16449
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
15801
16450
|
function defaultUsageLogPath(homeDirOverride) {
|
|
15802
16451
|
return join16(homeDirOverride ?? homedir9(), ".reasonix", "usage.jsonl");
|
|
15803
16452
|
}
|
|
@@ -15838,9 +16487,9 @@ function compactUsageLogIfLarge(path2, now) {
|
|
|
15838
16487
|
if (kept.length === lines.filter((l) => l.trim()).length) return;
|
|
15839
16488
|
const tmp = `${path2}.compacting`;
|
|
15840
16489
|
try {
|
|
15841
|
-
|
|
16490
|
+
writeFileSync8(tmp, kept.length > 0 ? `${kept.join("\n")}
|
|
15842
16491
|
` : "", "utf8");
|
|
15843
|
-
|
|
16492
|
+
renameSync3(tmp, path2);
|
|
15844
16493
|
} catch {
|
|
15845
16494
|
try {
|
|
15846
16495
|
unlinkSync4(tmp);
|
|
@@ -15864,8 +16513,8 @@ function appendUsage(input) {
|
|
|
15864
16513
|
if (input.subagent) record.subagent = input.subagent;
|
|
15865
16514
|
const path2 = input.path ?? defaultUsageLogPath();
|
|
15866
16515
|
try {
|
|
15867
|
-
mkdirSync7(
|
|
15868
|
-
|
|
16516
|
+
mkdirSync7(dirname10(path2), { recursive: true });
|
|
16517
|
+
appendFileSync3(path2, `${JSON.stringify(record)}
|
|
15869
16518
|
`, "utf8");
|
|
15870
16519
|
compactUsageLogIfLarge(path2, record.ts);
|
|
15871
16520
|
} catch {
|
|
@@ -15876,7 +16525,7 @@ function readUsageLog(path2 = defaultUsageLogPath()) {
|
|
|
15876
16525
|
if (!existsSync13(path2)) return [];
|
|
15877
16526
|
let raw;
|
|
15878
16527
|
try {
|
|
15879
|
-
raw =
|
|
16528
|
+
raw = readFileSync17(path2, "utf8");
|
|
15880
16529
|
} catch {
|
|
15881
16530
|
return [];
|
|
15882
16531
|
}
|
|
@@ -16108,9 +16757,9 @@ export {
|
|
|
16108
16757
|
openTranscriptFile,
|
|
16109
16758
|
outputCostUsd,
|
|
16110
16759
|
parseAtQuery,
|
|
16760
|
+
parseBingResults,
|
|
16111
16761
|
parseEditBlocks,
|
|
16112
16762
|
parseMcpSpec,
|
|
16113
|
-
parseMojeekResults,
|
|
16114
16763
|
parseSearxngHtmlResults,
|
|
16115
16764
|
parseTranscript,
|
|
16116
16765
|
prepareSpawn,
|