@zqagent/core 0.2.4 → 0.2.6
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 +125 -43
- package/README.oss.en.md +5 -0
- package/README.oss.md +5 -0
- package/README.oss.zh-TW.md +5 -0
- package/dist/cli/index.cjs +1135 -599
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1141 -605
- package/dist/cli/index.js.map +1 -1
- package/dist/{explore-D4lCKXbA.d.ts → explore-B_bIPzmB.d.ts} +49 -1
- package/dist/{explore-Dj1j_GyN.d.cts → explore-COqHKDoK.d.cts} +49 -1
- package/dist/index.cjs +654 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +654 -258
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +974 -442
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +980 -448
- package/dist/server/index.js.map +1 -1
- package/dist/tools/explore.cjs +528 -141
- package/dist/tools/explore.cjs.map +1 -1
- package/dist/tools/explore.d.cts +1 -1
- package/dist/tools/explore.d.ts +1 -1
- package/dist/tools/explore.js +528 -141
- package/dist/tools/explore.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -388,8 +388,11 @@ function formatTokens(tokens) {
|
|
|
388
388
|
return `${tokens}`;
|
|
389
389
|
}
|
|
390
390
|
var KEEP_RECENT_FRACTION = 0.25;
|
|
391
|
-
var
|
|
392
|
-
var
|
|
391
|
+
var SOFT_THRESHOLD_RATIO = 0.75;
|
|
392
|
+
var ELIDE_KEEP_FRACTION = 0.15;
|
|
393
|
+
var SUMMARY_MAX_TOKENS = 3e3;
|
|
394
|
+
var SPAN_TOOL_RESULT_CLIP = 200;
|
|
395
|
+
var SPAN_BUDGET_CHARS = 8e4;
|
|
393
396
|
var USER_MESSAGE_CLIP = 600;
|
|
394
397
|
var USER_MESSAGES_MAX = 40;
|
|
395
398
|
var TRIM_FRACTION = 0.1;
|
|
@@ -412,9 +415,22 @@ function triggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capT
|
|
|
412
415
|
}
|
|
413
416
|
return base;
|
|
414
417
|
}
|
|
418
|
+
function softTriggerTokens(contextWindow, thresholdPct = DEFAULT_THRESHOLD_PCT, capTokens, softThresholdPct) {
|
|
419
|
+
const hard = triggerTokens(contextWindow, thresholdPct, capTokens);
|
|
420
|
+
if (softThresholdPct != null && Number.isFinite(softThresholdPct) && softThresholdPct > 0) {
|
|
421
|
+
return Math.min(
|
|
422
|
+
triggerTokens(contextWindow, softThresholdPct, capTokens),
|
|
423
|
+
Math.floor(hard * 0.95)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return Math.floor(hard * SOFT_THRESHOLD_RATIO);
|
|
427
|
+
}
|
|
415
428
|
function shouldCompact(signal, contextWindow, thresholdPct, capTokens) {
|
|
416
429
|
return signal >= triggerTokens(contextWindow, thresholdPct, capTokens);
|
|
417
430
|
}
|
|
431
|
+
function elisionKeepChars(hardTriggerTokens, fraction = ELIDE_KEEP_FRACTION) {
|
|
432
|
+
return Math.max(4e3, Math.floor(hardTriggerTokens * fraction * 4));
|
|
433
|
+
}
|
|
418
434
|
function turnStarts(messages, start) {
|
|
419
435
|
const users = [];
|
|
420
436
|
const assistants = [];
|
|
@@ -564,10 +580,11 @@ var SUMMARY_SYSTEM_PROMPT = `\u4F60\u6B63\u5728\u538B\u7F29\u4E00\u4E2A AI \u534
|
|
|
564
580
|
|
|
565
581
|
\u89C4\u5219\uFF1A
|
|
566
582
|
- \u4E0D\u8981\u5C06\u5B8C\u6574\u7684\u6587\u4EF6\u5185\u5BB9\u5F53\u4F5C\u771F\u7406\u3002\u53EA\u8BB0\u5F55\u67D0\u4E2A\u6587\u4EF6\u88AB\u8BFB\u53D6/\u7F16\u8F91\u8FC7\u8FD9\u4E00\u4E8B\u5B9E\uFF1B\u534F\u4F5C\u8005\u5982\u679C\u9700\u8981\u518D\u6B21\u83B7\u53D6\u5185\u5BB9\u4F1A\u91CD\u65B0\u8BFB\u53D6\u3002\u9648\u65E7\u7684\u6587\u4EF6\u8BB0\u5FC6\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\u7CD5\u3002
|
|
583
|
+
- \u82E5\u63D0\u4F9B\u4E86\u4E0A\u4E00\u7248\u6458\u8981\uFF1A\u53EA\u628A\u5B83\u4ECD\u7136\u6709\u6548\u7684\u7ED3\u8BBA\u5E76\u5165\u5BF9\u5E94\u7AE0\u8282\uFF0C\u4E0D\u8981\u6574\u6BB5\u590D\u8FF0\u6216\u9010\u6761\u8F6C\u6284\u3002
|
|
567
584
|
- \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
|
|
568
585
|
- \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`;
|
|
569
586
|
var CONTINUATION_CONTRACT = "\u4ECE\u4F60\u4E4B\u524D\u4E2D\u65AD\u7684\u5730\u65B9\u7EE7\u7EED\uFF1A\u5B8C\u5168\u6309\u7167\u63CF\u8FF0\u63A5\u7EED\u5F53\u524D\u5DE5\u4F5C\u548C\u4E0B\u4E00\u6B65\u3002\u4E0D\u8981\u91CD\u65B0\u8BE2\u95EE\u5DF2\u56DE\u7B54\u7684\u95EE\u9898\uFF0C\u4E0D\u8981\u56DE\u987E\uFF0C\u4E0D\u8981\u63D0\u53CA\u4E0A\u4E0B\u6587\u5DF2\u88AB\u538B\u7F29\u3002\u5982\u679C\u4F60\u9700\u8981\u4E0A\u8FF0\u63D0\u5230\u7684\u6587\u4EF6\u7684\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u5B83\u3002";
|
|
570
|
-
function renderSpan(span,
|
|
587
|
+
function renderSpan(span, budgetChars2 = SPAN_BUDGET_CHARS) {
|
|
571
588
|
const lines = [];
|
|
572
589
|
for (const m of span) {
|
|
573
590
|
const role = m.role;
|
|
@@ -591,8 +608,8 @@ function renderSpan(span, budgetChars = SPAN_BUDGET_CHARS) {
|
|
|
591
608
|
if (text) lines.push(`[${role}] ${text}`);
|
|
592
609
|
}
|
|
593
610
|
let rendered = lines.join("\n");
|
|
594
|
-
if (rendered.length >
|
|
595
|
-
rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-
|
|
611
|
+
if (rendered.length > budgetChars2) {
|
|
612
|
+
rendered = "(\u2026oldest turns elided\u2026)\n" + rendered.slice(-budgetChars2);
|
|
596
613
|
}
|
|
597
614
|
return rendered;
|
|
598
615
|
}
|
|
@@ -606,9 +623,17 @@ function summarizerMessages(span, priorSummary = "") {
|
|
|
606
623
|
{ role: "user", content: body }
|
|
607
624
|
];
|
|
608
625
|
}
|
|
626
|
+
function summarizerReasoningEffort() {
|
|
627
|
+
const v = process.env.COMPACTION_REASONING_EFFORT?.toLowerCase();
|
|
628
|
+
if (v === "low" || v === "medium" || v === "high") return v;
|
|
629
|
+
return "low";
|
|
630
|
+
}
|
|
609
631
|
async function summarizeSpan(provider, model, span, priorSummary = "", opts) {
|
|
610
632
|
const msgs = summarizerMessages(span, priorSummary);
|
|
611
|
-
const chunks = await provider.stream(model, msgs, [],
|
|
633
|
+
const chunks = await provider.stream(model, msgs, [], {
|
|
634
|
+
...opts,
|
|
635
|
+
maxTokens: SUMMARY_MAX_TOKENS
|
|
636
|
+
});
|
|
612
637
|
let text = "";
|
|
613
638
|
for await (const chunk of chunks) {
|
|
614
639
|
if (chunk.content) text += chunk.content;
|
|
@@ -719,6 +744,166 @@ function applyToOutbound(messages, state) {
|
|
|
719
744
|
head.push({ role: "user", content: compactedBlock(state) });
|
|
720
745
|
return [...head, ...messages.slice(boundary)];
|
|
721
746
|
}
|
|
747
|
+
var ELIDE_EXEMPT_TOOLS = /* @__PURE__ */ new Set([
|
|
748
|
+
"explore",
|
|
749
|
+
"ask_user",
|
|
750
|
+
"propose_plan",
|
|
751
|
+
"request_directory",
|
|
752
|
+
"todo_write"
|
|
753
|
+
]);
|
|
754
|
+
function elidedToolContent(m) {
|
|
755
|
+
const name = m.tool_call_name || "tool";
|
|
756
|
+
const size = m.content.length;
|
|
757
|
+
const saved = /full output saved to ([^\]]+)/.exec(m.content)?.[1]?.trim();
|
|
758
|
+
return saved ? `[old tool result elided: ${name}, ${size} chars; full output saved to ${saved}]` : `[old tool result elided: ${name}, ${size} chars; re-run the tool if its content is still needed]`;
|
|
759
|
+
}
|
|
760
|
+
function isPlaceholder(content) {
|
|
761
|
+
return content.startsWith("[old tool result elided") || content.startsWith("[superseded by a later read_file");
|
|
762
|
+
}
|
|
763
|
+
var ONE_SHOT_TOOLS = /* @__PURE__ */ new Set(["git_diff"]);
|
|
764
|
+
var ONE_SHOT_KEEP_RECENT = 1;
|
|
765
|
+
function applyOneShotElision(messages, keepRecent = ONE_SHOT_KEEP_RECENT, tools = ONE_SHOT_TOOLS) {
|
|
766
|
+
const out = [...messages];
|
|
767
|
+
let seen = 0;
|
|
768
|
+
let elided = 0;
|
|
769
|
+
let freed = 0;
|
|
770
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
771
|
+
const m = messages[i];
|
|
772
|
+
if (m.role !== "tool" || !tools.has(m.tool_call_name ?? "")) continue;
|
|
773
|
+
if (isPlaceholder(m.content)) continue;
|
|
774
|
+
seen++;
|
|
775
|
+
if (seen <= keepRecent) continue;
|
|
776
|
+
const placeholder = elidedToolContent(m);
|
|
777
|
+
freed += m.content.length - placeholder.length;
|
|
778
|
+
out[i] = { ...m, content: placeholder };
|
|
779
|
+
elided++;
|
|
780
|
+
}
|
|
781
|
+
return elided > 0 ? { messages: out, elided, freedChars: freed } : { messages, elided: 0, freedChars: 0 };
|
|
782
|
+
}
|
|
783
|
+
function applyToolResultElision(messages, keepChars, exempt = ELIDE_EXEMPT_TOOLS) {
|
|
784
|
+
let total = 0;
|
|
785
|
+
for (const m of messages) {
|
|
786
|
+
if (m.role === "tool" && !exempt.has(m.tool_call_name ?? "")) {
|
|
787
|
+
total += m.content.length;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (total <= keepChars) return { messages, elided: 0, freedChars: 0 };
|
|
791
|
+
const out = [...messages];
|
|
792
|
+
let budget = 0;
|
|
793
|
+
let elided = 0;
|
|
794
|
+
let freed = 0;
|
|
795
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
796
|
+
const m = messages[i];
|
|
797
|
+
if (m.role !== "tool") continue;
|
|
798
|
+
if (exempt.has(m.tool_call_name ?? "")) continue;
|
|
799
|
+
if (elided === 0 && budget === 0 && m.content.length > keepChars) {
|
|
800
|
+
const kept = m.content.slice(0, keepChars) + "\n\u2026(truncated)";
|
|
801
|
+
freed += m.content.length - kept.length;
|
|
802
|
+
out[i] = { ...m, content: kept };
|
|
803
|
+
budget = keepChars;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (budget + m.content.length <= keepChars) {
|
|
807
|
+
budget += m.content.length;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const placeholder = elidedToolContent(m);
|
|
811
|
+
freed += m.content.length - placeholder.length;
|
|
812
|
+
out[i] = { ...m, content: placeholder };
|
|
813
|
+
elided++;
|
|
814
|
+
}
|
|
815
|
+
return { messages: out, elided, freedChars: freed };
|
|
816
|
+
}
|
|
817
|
+
function normalizeToolPath(p) {
|
|
818
|
+
let s = p.trim().replace(/\\/g, "/");
|
|
819
|
+
while (s.startsWith("./")) s = s.slice(2);
|
|
820
|
+
return s.replace(/\/{2,}/g, "/");
|
|
821
|
+
}
|
|
822
|
+
var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
823
|
+
"write_file",
|
|
824
|
+
"replace_in_file",
|
|
825
|
+
"apply_patch",
|
|
826
|
+
"apply_unified_diff",
|
|
827
|
+
"run_shell"
|
|
828
|
+
]);
|
|
829
|
+
function parseReadFileSpan(content) {
|
|
830
|
+
const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
|
|
831
|
+
let path16;
|
|
832
|
+
let start;
|
|
833
|
+
let end;
|
|
834
|
+
try {
|
|
835
|
+
const obj = JSON.parse(body);
|
|
836
|
+
path16 = obj.path;
|
|
837
|
+
start = obj.start_line;
|
|
838
|
+
end = obj.end_line;
|
|
839
|
+
} catch {
|
|
840
|
+
const str2 = (key) => new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`).exec(body)?.[1];
|
|
841
|
+
const num = (key) => {
|
|
842
|
+
const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
|
|
843
|
+
return m ? Number(m[1]) : void 0;
|
|
844
|
+
};
|
|
845
|
+
path16 = str2("path");
|
|
846
|
+
start = num("start_line");
|
|
847
|
+
end = num("end_line");
|
|
848
|
+
}
|
|
849
|
+
if (typeof path16 !== "string" || !path16) return null;
|
|
850
|
+
if (typeof start !== "number" || typeof end !== "number") return null;
|
|
851
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
|
|
852
|
+
return { path: path16, start, end };
|
|
853
|
+
}
|
|
854
|
+
function addSpan(spans, start, end) {
|
|
855
|
+
spans.push([start, end]);
|
|
856
|
+
spans.sort((a, b) => a[0] - b[0]);
|
|
857
|
+
const merged = [];
|
|
858
|
+
for (const cur of spans) {
|
|
859
|
+
const last = merged[merged.length - 1];
|
|
860
|
+
if (last && cur[0] <= last[1] + 1) {
|
|
861
|
+
last[1] = Math.max(last[1], cur[1]);
|
|
862
|
+
} else {
|
|
863
|
+
merged.push([...cur]);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
spans.length = 0;
|
|
867
|
+
spans.push(...merged);
|
|
868
|
+
}
|
|
869
|
+
function coversSpans(spans, start, end) {
|
|
870
|
+
return spans.some(([a, b]) => a <= start && b >= end);
|
|
871
|
+
}
|
|
872
|
+
function applyReadFileDedup(messages) {
|
|
873
|
+
let candidates = 0;
|
|
874
|
+
for (const m of messages) {
|
|
875
|
+
if (m.role === "tool" && m.tool_call_name === "read_file") candidates++;
|
|
876
|
+
}
|
|
877
|
+
if (candidates < 2) return { messages, elided: 0 };
|
|
878
|
+
const out = [...messages];
|
|
879
|
+
const covered = /* @__PURE__ */ new Map();
|
|
880
|
+
let elided = 0;
|
|
881
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
882
|
+
const m = messages[i];
|
|
883
|
+
if (m.role === "assistant") {
|
|
884
|
+
if (m.tool_calls?.some((tc) => MUTATING_TOOLS.has(tc.function.name))) {
|
|
885
|
+
covered.clear();
|
|
886
|
+
}
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (m.role !== "tool" || m.tool_call_name !== "read_file") continue;
|
|
890
|
+
const span = parseReadFileSpan(m.content);
|
|
891
|
+
if (!span) continue;
|
|
892
|
+
const key = normalizeToolPath(span.path);
|
|
893
|
+
const seen = covered.get(key) ?? [];
|
|
894
|
+
if (coversSpans(seen, span.start, span.end)) {
|
|
895
|
+
out[i] = {
|
|
896
|
+
...m,
|
|
897
|
+
content: `[superseded by a later read_file of ${span.path} (lines ${span.start}-${span.end})]`
|
|
898
|
+
};
|
|
899
|
+
elided++;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
addSpan(seen, span.start, span.end);
|
|
903
|
+
covered.set(key, seen);
|
|
904
|
+
}
|
|
905
|
+
return { messages: elided > 0 ? out : messages, elided };
|
|
906
|
+
}
|
|
722
907
|
var OVERFLOW_MARKERS = [
|
|
723
908
|
"context_length_exceeded",
|
|
724
909
|
"maximum context length",
|
|
@@ -734,9 +919,108 @@ function isContextOverflow(exc) {
|
|
|
734
919
|
return OVERFLOW_MARKERS.some((m) => text.includes(m));
|
|
735
920
|
}
|
|
736
921
|
|
|
922
|
+
// src/engine/toolResultBudget.ts
|
|
923
|
+
import * as fs from "fs";
|
|
924
|
+
import * as path2 from "path";
|
|
925
|
+
|
|
926
|
+
// src/paths.ts
|
|
927
|
+
import * as os from "os";
|
|
928
|
+
import * as path from "path";
|
|
929
|
+
var APP_DIR_NAME = "zqagent";
|
|
930
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
931
|
+
function homeDir() {
|
|
932
|
+
return process.env.HOME || process.env.USERPROFILE || os.homedir() || os.tmpdir();
|
|
933
|
+
}
|
|
934
|
+
function stateDir() {
|
|
935
|
+
const base = process.env.XDG_STATE_HOME || (IS_WINDOWS ? process.env.LOCALAPPDATA : void 0) || path.join(homeDir(), ".local", "state");
|
|
936
|
+
return path.join(base, APP_DIR_NAME);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/engine/toolResultBudget.ts
|
|
940
|
+
var DEFAULT_TOOL_RESULT_MAX_CHARS = 24e3;
|
|
941
|
+
var HEAD_RATIO = 0.6;
|
|
942
|
+
function budgetChars() {
|
|
943
|
+
const n = Number(process.env.TOOL_RESULT_MAX_CHARS);
|
|
944
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_TOOL_RESULT_MAX_CHARS;
|
|
945
|
+
}
|
|
946
|
+
function toolResultDir() {
|
|
947
|
+
const env = process.env.TOOL_RESULT_DIR?.trim();
|
|
948
|
+
return env || path2.join(stateDir(), "tool-results");
|
|
949
|
+
}
|
|
950
|
+
function clipText(text, budget) {
|
|
951
|
+
if (text.length <= budget) return text;
|
|
952
|
+
const room = Math.max(
|
|
953
|
+
0,
|
|
954
|
+
budget - `
|
|
955
|
+
|
|
956
|
+
\u2026 [${text.length} characters omitted] \u2026
|
|
957
|
+
|
|
958
|
+
`.length
|
|
959
|
+
);
|
|
960
|
+
const headLen = Math.floor(room * HEAD_RATIO);
|
|
961
|
+
const tailLen = room - headLen;
|
|
962
|
+
const omitted = text.length - headLen - tailLen;
|
|
963
|
+
return text.slice(0, headLen) + `
|
|
964
|
+
|
|
965
|
+
\u2026 [${omitted} characters omitted] \u2026
|
|
966
|
+
|
|
967
|
+
` + text.slice(-tailLen);
|
|
968
|
+
}
|
|
969
|
+
function clipDeep(value, perField) {
|
|
970
|
+
if (typeof value === "string") {
|
|
971
|
+
return value.length <= perField ? value : clipText(value, perField);
|
|
972
|
+
}
|
|
973
|
+
if (Array.isArray(value)) {
|
|
974
|
+
return value.map((v) => clipDeep(v, perField));
|
|
975
|
+
}
|
|
976
|
+
if (value && typeof value === "object") {
|
|
977
|
+
const out = {};
|
|
978
|
+
for (const [k, v] of Object.entries(value)) out[k] = clipDeep(v, perField);
|
|
979
|
+
return out;
|
|
980
|
+
}
|
|
981
|
+
return value;
|
|
982
|
+
}
|
|
983
|
+
function saveRaw(tool, raw, dir) {
|
|
984
|
+
try {
|
|
985
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
986
|
+
const safe = tool.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40) || "tool";
|
|
987
|
+
const name = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}-${safe}.txt`;
|
|
988
|
+
const file = path2.join(dir, name);
|
|
989
|
+
fs.writeFileSync(file, raw, "utf-8");
|
|
990
|
+
return file;
|
|
991
|
+
} catch {
|
|
992
|
+
return void 0;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function applyToolResultBudget(tool, result, opts = {}) {
|
|
996
|
+
const budget = opts.maxChars ?? budgetChars();
|
|
997
|
+
const raw = typeof result === "string" ? result : JSON.stringify(result, null, 2) ?? String(result);
|
|
998
|
+
if (raw.length <= budget) {
|
|
999
|
+
return { content: raw, truncated: false };
|
|
1000
|
+
}
|
|
1001
|
+
let content = raw;
|
|
1002
|
+
if (typeof result === "object" && result !== null) {
|
|
1003
|
+
let perField = Math.floor(budget * 0.7);
|
|
1004
|
+
let clipped = JSON.stringify(clipDeep(result, perField), null, 2);
|
|
1005
|
+
for (let i = 0; i < 6 && clipped && clipped.length > budget && perField > 128; i++) {
|
|
1006
|
+
perField = Math.floor(perField / 2);
|
|
1007
|
+
clipped = JSON.stringify(clipDeep(result, perField), null, 2);
|
|
1008
|
+
}
|
|
1009
|
+
if (clipped && clipped.length <= budget) content = clipped;
|
|
1010
|
+
}
|
|
1011
|
+
if (content.length > budget) content = clipText(content, budget);
|
|
1012
|
+
const savedPath = saveRaw(tool, raw, opts.dir ?? toolResultDir());
|
|
1013
|
+
if (savedPath) {
|
|
1014
|
+
content = `[tool output truncated: ${raw.length} chars, full output saved to ${savedPath}]
|
|
1015
|
+
|
|
1016
|
+
` + content;
|
|
1017
|
+
}
|
|
1018
|
+
return { content, truncated: true, savedPath };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
737
1021
|
// src/engine/TurnEngine.ts
|
|
738
1022
|
import * as crypto from "crypto";
|
|
739
|
-
import * as
|
|
1023
|
+
import * as fs2 from "fs";
|
|
740
1024
|
import * as _path from "path";
|
|
741
1025
|
|
|
742
1026
|
// src/prompts/index.ts
|
|
@@ -863,13 +1147,17 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
863
1147
|
"propose_plan",
|
|
864
1148
|
"request_directory"
|
|
865
1149
|
]);
|
|
866
|
-
|
|
867
|
-
|
|
1150
|
+
var MAX_COMPACT_ROUNDS = 4;
|
|
1151
|
+
var OVERFLOW_TARGET_RATIO = 0.5;
|
|
1152
|
+
var OVERFLOW_TRIM_FRACTION = 0.5;
|
|
1153
|
+
var COMPACT_STALL_RETRY_DELTA = 20;
|
|
1154
|
+
function toolResultMessage(tc, result, taskId, toolResultDir2) {
|
|
1155
|
+
const budgeted = applyToolResultBudget(tc.name, result, { dir: toolResultDir2 });
|
|
868
1156
|
return {
|
|
869
1157
|
role: "tool",
|
|
870
1158
|
tool_call_id: tc.id,
|
|
871
1159
|
tool_call_name: tc.name,
|
|
872
|
-
content,
|
|
1160
|
+
content: budgeted.content,
|
|
873
1161
|
taskId
|
|
874
1162
|
};
|
|
875
1163
|
}
|
|
@@ -980,12 +1268,22 @@ var TurnEngine = class _TurnEngine {
|
|
|
980
1268
|
// 上下文压缩
|
|
981
1269
|
compactionState = null;
|
|
982
1270
|
_lastContextTokens = null;
|
|
1271
|
+
/** 压缩无进展时的消息条数快照;-1 表示无冷却 */
|
|
1272
|
+
_compactStalledAt = -1;
|
|
983
1273
|
/** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
|
|
984
1274
|
_contextWindow;
|
|
985
1275
|
/** 压缩触发阈值比例(默认 0.8) */
|
|
986
1276
|
_thresholdPct;
|
|
1277
|
+
/** 软阈值比例(undefined = 取硬阈值的 75%) */
|
|
1278
|
+
_softThresholdPct;
|
|
1279
|
+
/** 是否启用软阈值淘汰(COMPACTION_ELIDE=0 可关闭) */
|
|
1280
|
+
_elisionEnabled;
|
|
1281
|
+
/** 是否启用重复读取去重(COMPACTION_DEDUP=0 可关闭) */
|
|
1282
|
+
_dedupEnabled;
|
|
987
1283
|
/** 压缩触发阈值绝对上限(null = 不限制) */
|
|
988
1284
|
_capTokens;
|
|
1285
|
+
/** 超预算工具结果的落盘目录(undefined = 使用默认状态目录) */
|
|
1286
|
+
_toolResultDir;
|
|
989
1287
|
// 产出文件追踪(本轮 Turn 中成功写入的文件)
|
|
990
1288
|
_producedFiles = [];
|
|
991
1289
|
_turnStartTime = 0;
|
|
@@ -1037,7 +1335,15 @@ var TurnEngine = class _TurnEngine {
|
|
|
1037
1335
|
this._hook = opts.hook;
|
|
1038
1336
|
this._contextWindow = opts.contextWindow;
|
|
1039
1337
|
this._thresholdPct = opts.thresholdPct ?? DEFAULT_THRESHOLD_PCT;
|
|
1338
|
+
this._softThresholdPct = opts.softThresholdPct;
|
|
1339
|
+
this._elisionEnabled = !/^(0|false|off|no)$/i.test(
|
|
1340
|
+
(process.env.COMPACTION_ELIDE ?? "").trim()
|
|
1341
|
+
);
|
|
1342
|
+
this._dedupEnabled = !/^(0|false|off|no)$/i.test(
|
|
1343
|
+
(process.env.COMPACTION_DEDUP ?? "").trim()
|
|
1344
|
+
);
|
|
1040
1345
|
this._capTokens = opts.capTokens ?? null;
|
|
1346
|
+
this._toolResultDir = opts.toolResultDir;
|
|
1041
1347
|
this._reasoningEffort = opts.reasoningEffort;
|
|
1042
1348
|
if (this.messages.length === 0 || this.messages[0].role !== "system") {
|
|
1043
1349
|
this.messages.unshift({ role: "system", content: this.instructions });
|
|
@@ -1093,6 +1399,7 @@ var TurnEngine = class _TurnEngine {
|
|
|
1093
1399
|
this._cancel = false;
|
|
1094
1400
|
this._producedFiles = [];
|
|
1095
1401
|
this._turnStartTime = Date.now();
|
|
1402
|
+
this._compactStalledAt = -1;
|
|
1096
1403
|
this._emitHook((h) => h.onTurnStart?.(userInput));
|
|
1097
1404
|
yield {
|
|
1098
1405
|
type: "turn_start" /* TURN_START */,
|
|
@@ -1809,7 +2116,9 @@ var TurnEngine = class _TurnEngine {
|
|
|
1809
2116
|
for (let i = 0; i < concurrent.length; i++) {
|
|
1810
2117
|
const tc = concurrent[i];
|
|
1811
2118
|
const { result, status } = results[i];
|
|
1812
|
-
this.messages.push(
|
|
2119
|
+
this.messages.push(
|
|
2120
|
+
toolResultMessage(tc, result, this.taskId, this._toolResultDir)
|
|
2121
|
+
);
|
|
1813
2122
|
this._trackProducedFile(
|
|
1814
2123
|
tc,
|
|
1815
2124
|
result,
|
|
@@ -1872,7 +2181,9 @@ var TurnEngine = class _TurnEngine {
|
|
|
1872
2181
|
};
|
|
1873
2182
|
status = "error";
|
|
1874
2183
|
}
|
|
1875
|
-
this.messages.push(
|
|
2184
|
+
this.messages.push(
|
|
2185
|
+
toolResultMessage(tc, result, this.taskId, this._toolResultDir)
|
|
2186
|
+
);
|
|
1876
2187
|
this._trackProducedFile(
|
|
1877
2188
|
tc,
|
|
1878
2189
|
result,
|
|
@@ -1907,13 +2218,41 @@ var TurnEngine = class _TurnEngine {
|
|
|
1907
2218
|
* 过滤 notice 消息;tool 消息只保留必要字段;
|
|
1908
2219
|
* 其余消息剥离 display-only sidecar 字段(source/_display/ts/
|
|
1909
2220
|
* reasoning/usage),避免污染模型上下文。
|
|
2221
|
+
*
|
|
2222
|
+
* 达到软阈值时先淘汰旧工具结果(零 LLM 成本),再产出最终视图。
|
|
1910
2223
|
*/
|
|
1911
2224
|
_outboundMessages() {
|
|
1912
|
-
const
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
|
|
2225
|
+
const base = applyToOutbound(this.messages, this.compactionState);
|
|
2226
|
+
const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
|
|
2227
|
+
let view = this._finalizeOutbound(deduped);
|
|
2228
|
+
if (!this._elisionEnabled) return view;
|
|
2229
|
+
if (estimateTokens(view) < this._softTriggerTokens()) {
|
|
2230
|
+
return view;
|
|
2231
|
+
}
|
|
2232
|
+
const oneShot = applyOneShotElision(deduped);
|
|
2233
|
+
if (oneShot.freedChars > 0) {
|
|
2234
|
+
view = this._finalizeOutbound(oneShot.messages);
|
|
2235
|
+
if (estimateTokens(view) < this._softTriggerTokens()) {
|
|
2236
|
+
return view;
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
const keepChars = elisionKeepChars(
|
|
2240
|
+
triggerTokens(
|
|
2241
|
+
this._getContextWindow(),
|
|
2242
|
+
this._thresholdPct,
|
|
2243
|
+
this._capTokens
|
|
2244
|
+
)
|
|
2245
|
+
);
|
|
2246
|
+
const elided = applyToolResultElision(
|
|
2247
|
+
oneShot.messages,
|
|
2248
|
+
keepChars
|
|
1916
2249
|
);
|
|
2250
|
+
if (elided.freedChars === 0) return view;
|
|
2251
|
+
return this._finalizeOutbound(elided.messages);
|
|
2252
|
+
}
|
|
2253
|
+
/** 过滤 notice + 剥离 display-only 字段,得到最终发送视图 */
|
|
2254
|
+
_finalizeOutbound(sourceMessages) {
|
|
2255
|
+
const _STRIP = /* @__PURE__ */ new Set(["source", "_display", "ts", "reasoning", "usage"]);
|
|
1917
2256
|
return sourceMessages.filter((m) => m.role !== "notice").map((m) => {
|
|
1918
2257
|
if (m.role === "tool") {
|
|
1919
2258
|
return {
|
|
@@ -1931,6 +2270,15 @@ var TurnEngine = class _TurnEngine {
|
|
|
1931
2270
|
return stripped;
|
|
1932
2271
|
});
|
|
1933
2272
|
}
|
|
2273
|
+
/** 软阈值(token 数):达到它只淘汰旧工具结果 */
|
|
2274
|
+
_softTriggerTokens() {
|
|
2275
|
+
return softTriggerTokens(
|
|
2276
|
+
this._getContextWindow(),
|
|
2277
|
+
this._thresholdPct,
|
|
2278
|
+
this._capTokens,
|
|
2279
|
+
this._softThresholdPct
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
1934
2282
|
/** 通知消息:仅用于日志/展示,不发给模型 */
|
|
1935
2283
|
_appendNotice(kind, text) {
|
|
1936
2284
|
this.messages.push({
|
|
@@ -1972,18 +2320,36 @@ var TurnEngine = class _TurnEngine {
|
|
|
1972
2320
|
return false;
|
|
1973
2321
|
}
|
|
1974
2322
|
// ===================== 自动压缩 =====================
|
|
1975
|
-
/**
|
|
2323
|
+
/**
|
|
2324
|
+
* 当前上下文 token 用量。
|
|
2325
|
+
*
|
|
2326
|
+
* API 返回的 `usage.input` 是精确值,但反映的是「上一次请求」——工具结果
|
|
2327
|
+
* 与淘汰/压缩都会让它过时。因此同时估算当前 outbound 视图,取较大者:
|
|
2328
|
+
* 宁可早一点压缩,也不要漏判导致溢出。
|
|
2329
|
+
*/
|
|
1976
2330
|
_compactionTokens() {
|
|
1977
|
-
|
|
1978
|
-
|
|
2331
|
+
const estimated = estimateTokens(this._outboundMessages());
|
|
2332
|
+
if (this._lastContextTokens == null) {
|
|
2333
|
+
return { tokens: estimated, estimated: true };
|
|
1979
2334
|
}
|
|
1980
|
-
|
|
1981
|
-
tokens:
|
|
1982
|
-
|
|
1983
|
-
};
|
|
2335
|
+
if (estimated > this._lastContextTokens) {
|
|
2336
|
+
return { tokens: estimated, estimated: true };
|
|
2337
|
+
}
|
|
2338
|
+
return { tokens: this._lastContextTokens, estimated: false };
|
|
2339
|
+
}
|
|
2340
|
+
/** 压缩后重置精确计数:后续按估算值判断 */
|
|
2341
|
+
_resetContextTokens() {
|
|
2342
|
+
this._lastContextTokens = null;
|
|
2343
|
+
}
|
|
2344
|
+
/** 当前 outbound 视图的估算 token 数 */
|
|
2345
|
+
_estimateContextTokens() {
|
|
2346
|
+
return estimateTokens(this._outboundMessages());
|
|
1984
2347
|
}
|
|
1985
2348
|
/** 检查是否需要压缩(每轮 LLM 调用前) */
|
|
1986
2349
|
_compactionDue() {
|
|
2350
|
+
if (this._compactStalledAt >= 0 && this.messages.length < this._compactStalledAt + COMPACT_STALL_RETRY_DELTA) {
|
|
2351
|
+
return false;
|
|
2352
|
+
}
|
|
1987
2353
|
const { tokens } = this._compactionTokens();
|
|
1988
2354
|
return shouldCompact(
|
|
1989
2355
|
tokens,
|
|
@@ -2030,63 +2396,82 @@ var TurnEngine = class _TurnEngine {
|
|
|
2030
2396
|
async _compactNow(reqId, force = false) {
|
|
2031
2397
|
const contextWindow = this._getContextWindow();
|
|
2032
2398
|
const messagesBefore = this._outboundMessages().length;
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
this._capTokens
|
|
2038
|
-
)
|
|
2399
|
+
const trigger = triggerTokens(
|
|
2400
|
+
contextWindow,
|
|
2401
|
+
this._thresholdPct,
|
|
2402
|
+
this._capTokens
|
|
2039
2403
|
);
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2404
|
+
const target = Math.floor(
|
|
2405
|
+
trigger * (force ? OVERFLOW_TARGET_RATIO : 0.9)
|
|
2406
|
+
);
|
|
2407
|
+
const model = process.env.COMPACTION_MODEL?.trim() || this.model;
|
|
2408
|
+
const reasoningEffort = summarizerReasoningEffort();
|
|
2409
|
+
let keep = Math.floor(KEEP_RECENT_FRACTION * trigger);
|
|
2410
|
+
let method = null;
|
|
2411
|
+
let lastState = null;
|
|
2412
|
+
let progressed = false;
|
|
2413
|
+
for (let round = 0; round < MAX_COMPACT_ROUNDS; round++) {
|
|
2414
|
+
if (round > 0 && this._estimateContextTokens() <= target) break;
|
|
2415
|
+
let state = null;
|
|
2416
|
+
for (let attempt = 0; attempt < 2 && !state; attempt++) {
|
|
2417
|
+
try {
|
|
2418
|
+
const logTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
2419
|
+
state = await buildState(
|
|
2420
|
+
this.messages,
|
|
2421
|
+
this.provider,
|
|
2422
|
+
model,
|
|
2423
|
+
keep,
|
|
2424
|
+
this.compactionState,
|
|
2425
|
+
{ reqId, logTime, reasoningEffort }
|
|
2426
|
+
);
|
|
2427
|
+
} catch {
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
if (state) {
|
|
2431
|
+
this.compactionState = state;
|
|
2432
|
+
lastState = state;
|
|
2433
|
+
method = "summarized";
|
|
2434
|
+
progressed = true;
|
|
2435
|
+
} else {
|
|
2436
|
+
const trimmed = trimState(
|
|
2047
2437
|
this.messages,
|
|
2048
|
-
this.provider,
|
|
2049
|
-
model,
|
|
2050
|
-
keep,
|
|
2051
2438
|
this.compactionState,
|
|
2052
|
-
|
|
2439
|
+
force ? OVERFLOW_TRIM_FRACTION : 0.25
|
|
2053
2440
|
);
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
this.
|
|
2062
|
-
|
|
2441
|
+
if (!trimmed) break;
|
|
2442
|
+
this.compactionState = trimmed;
|
|
2443
|
+
lastState = trimmed;
|
|
2444
|
+
method = "trimmed";
|
|
2445
|
+
progressed = true;
|
|
2446
|
+
}
|
|
2447
|
+
this._resetContextTokens();
|
|
2448
|
+
if (this._estimateContextTokens() <= trigger) break;
|
|
2449
|
+
keep = Math.max(Math.floor(trigger * 0.05), Math.floor(keep / 2));
|
|
2450
|
+
}
|
|
2451
|
+
if (!progressed || !lastState) return { notice: null, method: null };
|
|
2452
|
+
this._compactStalledAt = this._estimateContextTokens() >= trigger ? this.messages.length : -1;
|
|
2453
|
+
const messagesAfter = this._outboundMessages().length;
|
|
2454
|
+
const common = {
|
|
2455
|
+
boundaryIndex: lastState.boundaryIndex,
|
|
2456
|
+
messagesBefore,
|
|
2457
|
+
messagesAfter
|
|
2458
|
+
};
|
|
2459
|
+
if (method === "summarized") {
|
|
2063
2460
|
return {
|
|
2064
2461
|
notice: "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29 \u2014 \u65E9\u671F\u8F6E\u6B21\u5DF2\u6458\u8981",
|
|
2065
2462
|
method: "summarized",
|
|
2066
|
-
modelUsed:
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
messagesBefore,
|
|
2072
|
-
messagesAfter: this._outboundMessages().length
|
|
2463
|
+
modelUsed: lastState.modelUsed,
|
|
2464
|
+
summaryPreview: lastState.summaryText.slice(0, 200),
|
|
2465
|
+
keptUserMessages: lastState.userMessages.length,
|
|
2466
|
+
droppedUserMessages: lastState.userMessagesDropped,
|
|
2467
|
+
...common
|
|
2073
2468
|
};
|
|
2074
2469
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
return {
|
|
2081
|
-
notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
|
|
2082
|
-
method: "trimmed",
|
|
2083
|
-
boundaryIndex: trimmed.boundaryIndex,
|
|
2084
|
-
messagesBefore,
|
|
2085
|
-
messagesAfter: this._outboundMessages().length
|
|
2086
|
-
};
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
return { notice: null, method: null };
|
|
2470
|
+
return {
|
|
2471
|
+
notice: "\u4E0A\u4E0B\u6587\u5DF2\u88C1\u526A \u2014 \u6700\u65E7\u7684\u8F6E\u6B21\u5DF2\u4E22\u5F03\uFF08\u6458\u8981\u4E0D\u53EF\u7528\uFF09",
|
|
2472
|
+
method: "trimmed",
|
|
2473
|
+
...common
|
|
2474
|
+
};
|
|
2090
2475
|
}
|
|
2091
2476
|
/** 追踪文件写入工具成功产生的文件 */
|
|
2092
2477
|
_trackProducedFile(tc, result, status) {
|
|
@@ -2096,9 +2481,9 @@ var TurnEngine = class _TurnEngine {
|
|
|
2096
2481
|
return;
|
|
2097
2482
|
}
|
|
2098
2483
|
if (result && typeof result === "object") {
|
|
2099
|
-
const
|
|
2100
|
-
if (
|
|
2101
|
-
this._producedFiles.push(
|
|
2484
|
+
const path16 = result.path;
|
|
2485
|
+
if (path16) {
|
|
2486
|
+
this._producedFiles.push(path16);
|
|
2102
2487
|
return;
|
|
2103
2488
|
}
|
|
2104
2489
|
const changed = result.changed_files;
|
|
@@ -2122,7 +2507,7 @@ var TurnEngine = class _TurnEngine {
|
|
|
2122
2507
|
_fileExists(f, ws) {
|
|
2123
2508
|
if (!ws && !_path.isAbsolute(f)) return true;
|
|
2124
2509
|
try {
|
|
2125
|
-
return
|
|
2510
|
+
return fs2.existsSync(_path.isAbsolute(f) ? f : _path.join(ws, f));
|
|
2126
2511
|
} catch {
|
|
2127
2512
|
return false;
|
|
2128
2513
|
}
|
|
@@ -2145,7 +2530,7 @@ var TurnEngine = class _TurnEngine {
|
|
|
2145
2530
|
if (depth > 4 || results.length >= 50) return;
|
|
2146
2531
|
let entries;
|
|
2147
2532
|
try {
|
|
2148
|
-
entries =
|
|
2533
|
+
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
2149
2534
|
} catch {
|
|
2150
2535
|
return;
|
|
2151
2536
|
}
|
|
@@ -2160,7 +2545,7 @@ var TurnEngine = class _TurnEngine {
|
|
|
2160
2545
|
const ext = dotIdx !== -1 ? e.name.slice(dotIdx).toLowerCase() : "";
|
|
2161
2546
|
if (!_TurnEngine.SCAN_SUFFIXES.has(ext)) continue;
|
|
2162
2547
|
try {
|
|
2163
|
-
const st =
|
|
2548
|
+
const st = fs2.statSync(full);
|
|
2164
2549
|
if (st.mtimeMs >= since) {
|
|
2165
2550
|
results.push(
|
|
2166
2551
|
_path.relative(_path.resolve(ws), _path.resolve(full))
|
|
@@ -2181,8 +2566,8 @@ var TurnEngine = class _TurnEngine {
|
|
|
2181
2566
|
};
|
|
2182
2567
|
|
|
2183
2568
|
// src/providers/OpenAIProvider.ts
|
|
2184
|
-
import * as
|
|
2185
|
-
import * as
|
|
2569
|
+
import * as fs3 from "fs";
|
|
2570
|
+
import * as path3 from "path";
|
|
2186
2571
|
import OpenAI from "openai";
|
|
2187
2572
|
|
|
2188
2573
|
// src/terminal.ts
|
|
@@ -2360,7 +2745,7 @@ var OpenAIProvider = class {
|
|
|
2360
2745
|
});
|
|
2361
2746
|
if (opts.logDir) {
|
|
2362
2747
|
this._logDir = opts.logDir;
|
|
2363
|
-
|
|
2748
|
+
fs3.mkdirSync(opts.logDir, { recursive: true });
|
|
2364
2749
|
}
|
|
2365
2750
|
}
|
|
2366
2751
|
// =====================================================================
|
|
@@ -2513,6 +2898,8 @@ var OpenAIProvider = class {
|
|
|
2513
2898
|
tools: this._toResponsesTools(tools, model),
|
|
2514
2899
|
// 推理强度:DeepSeek 不支持,跳过
|
|
2515
2900
|
...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning: { effort: opts.reasoningEffort } } : {},
|
|
2901
|
+
// 限制输出长度(摘要器等轻量请求)
|
|
2902
|
+
...opts.maxTokens ? { max_output_tokens: opts.maxTokens } : {},
|
|
2516
2903
|
stream: true
|
|
2517
2904
|
}, requestOptions);
|
|
2518
2905
|
return this._responsesStream(reqId, startTime, rawStream, {
|
|
@@ -2611,6 +2998,8 @@ var OpenAIProvider = class {
|
|
|
2611
2998
|
})),
|
|
2612
2999
|
// 推理强度:DeepSeek 不支持,跳过
|
|
2613
3000
|
...opts.reasoningEffort && !this._isDeepSeek(model) ? { reasoning_effort: opts.reasoningEffort } : {},
|
|
3001
|
+
// 限制输出长度:o 系列 / gpt-5 推理模型仅支持 max_completion_tokens
|
|
3002
|
+
...opts.maxTokens ? isReasoningModel(model) ? { max_completion_tokens: opts.maxTokens } : { max_tokens: opts.maxTokens } : {},
|
|
2614
3003
|
stream: true,
|
|
2615
3004
|
// 要求返回 Token 用量统计(在流结束前的一个 chunk 中携带)
|
|
2616
3005
|
stream_options: { include_usage: true }
|
|
@@ -2724,8 +3113,8 @@ var OpenAIProvider = class {
|
|
|
2724
3113
|
if (!this._logDir) return;
|
|
2725
3114
|
try {
|
|
2726
3115
|
const today = logTime?.split("T")[0];
|
|
2727
|
-
const dir =
|
|
2728
|
-
|
|
3116
|
+
const dir = path3.join(this._logDir, today);
|
|
3117
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
2729
3118
|
const entry = {
|
|
2730
3119
|
reqId,
|
|
2731
3120
|
ts: logTime,
|
|
@@ -2737,8 +3126,8 @@ var OpenAIProvider = class {
|
|
|
2737
3126
|
toolNames: tools?.map((t) => t.function.name) ?? []
|
|
2738
3127
|
}
|
|
2739
3128
|
};
|
|
2740
|
-
|
|
2741
|
-
|
|
3129
|
+
fs3.writeFileSync(
|
|
3130
|
+
path3.join(dir, `${reqId}.json`),
|
|
2742
3131
|
JSON.stringify(entry, null, 2),
|
|
2743
3132
|
"utf-8"
|
|
2744
3133
|
);
|
|
@@ -2757,9 +3146,9 @@ var OpenAIProvider = class {
|
|
|
2757
3146
|
if (!this._logDir) return;
|
|
2758
3147
|
try {
|
|
2759
3148
|
const today = logTime?.split("T")[0];
|
|
2760
|
-
const filePath =
|
|
2761
|
-
if (!
|
|
2762
|
-
const entry = JSON.parse(
|
|
3149
|
+
const filePath = path3.join(this._logDir, today, `${reqId}.json`);
|
|
3150
|
+
if (!fs3.existsSync(filePath)) return;
|
|
3151
|
+
const entry = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
2763
3152
|
entry.elapsedMs = Date.now() - startTime;
|
|
2764
3153
|
if (data.response) {
|
|
2765
3154
|
entry.response = data.response;
|
|
@@ -2767,7 +3156,7 @@ var OpenAIProvider = class {
|
|
|
2767
3156
|
if (data.error) {
|
|
2768
3157
|
entry.error = data.error;
|
|
2769
3158
|
}
|
|
2770
|
-
|
|
3159
|
+
fs3.writeFileSync(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
2771
3160
|
} catch (e) {
|
|
2772
3161
|
console.error(e);
|
|
2773
3162
|
}
|
|
@@ -3232,62 +3621,62 @@ var ToolRegistry = class {
|
|
|
3232
3621
|
};
|
|
3233
3622
|
|
|
3234
3623
|
// src/tools/pathSafety.ts
|
|
3235
|
-
import * as
|
|
3236
|
-
import * as
|
|
3624
|
+
import * as fs4 from "fs";
|
|
3625
|
+
import * as path4 from "path";
|
|
3237
3626
|
function isInsideRelative(rel) {
|
|
3238
|
-
return !
|
|
3627
|
+
return !path4.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path4.sep}`);
|
|
3239
3628
|
}
|
|
3240
3629
|
function isFilesystemRoot(p) {
|
|
3241
|
-
return
|
|
3630
|
+
return path4.parse(p).root === p;
|
|
3242
3631
|
}
|
|
3243
3632
|
function resolveWithinRoot(workspace, target) {
|
|
3244
|
-
const abs =
|
|
3633
|
+
const abs = path4.resolve(workspace, target);
|
|
3245
3634
|
let rootReal;
|
|
3246
3635
|
try {
|
|
3247
|
-
rootReal =
|
|
3636
|
+
rootReal = fs4.realpathSync(workspace);
|
|
3248
3637
|
} catch {
|
|
3249
|
-
const rootAbs =
|
|
3250
|
-
if (!isInsideRelative(
|
|
3638
|
+
const rootAbs = path4.resolve(workspace);
|
|
3639
|
+
if (!isInsideRelative(path4.relative(rootAbs, abs))) return null;
|
|
3251
3640
|
return { root: rootAbs, real: abs };
|
|
3252
3641
|
}
|
|
3253
3642
|
if (isFilesystemRoot(rootReal)) return null;
|
|
3254
3643
|
let real;
|
|
3255
3644
|
try {
|
|
3256
|
-
real =
|
|
3645
|
+
real = fs4.realpathSync(abs);
|
|
3257
3646
|
} catch {
|
|
3258
3647
|
let probe = abs;
|
|
3259
3648
|
const tail = [];
|
|
3260
3649
|
for (; ; ) {
|
|
3261
|
-
const exists =
|
|
3650
|
+
const exists = fs4.existsSync(probe) || fs4.lstatSync(probe, { throwIfNoEntry: false }) !== void 0;
|
|
3262
3651
|
if (exists) {
|
|
3263
3652
|
try {
|
|
3264
|
-
real =
|
|
3653
|
+
real = path4.join(fs4.realpathSync(probe), ...tail);
|
|
3265
3654
|
break;
|
|
3266
3655
|
} catch {
|
|
3267
3656
|
return null;
|
|
3268
3657
|
}
|
|
3269
3658
|
}
|
|
3270
|
-
tail.unshift(
|
|
3271
|
-
const parent =
|
|
3659
|
+
tail.unshift(path4.basename(probe));
|
|
3660
|
+
const parent = path4.dirname(probe);
|
|
3272
3661
|
if (parent === probe) return null;
|
|
3273
3662
|
probe = parent;
|
|
3274
3663
|
}
|
|
3275
3664
|
}
|
|
3276
|
-
if (!isInsideRelative(
|
|
3665
|
+
if (!isInsideRelative(path4.relative(rootReal, real))) return null;
|
|
3277
3666
|
return { root: rootReal, real };
|
|
3278
3667
|
}
|
|
3279
3668
|
function isWithinRoot(workspace, target) {
|
|
3280
3669
|
return resolveWithinRoot(workspace, target) !== null;
|
|
3281
3670
|
}
|
|
3282
3671
|
function resolveEntryWithinRoot(workspace, target) {
|
|
3283
|
-
const abs =
|
|
3284
|
-
const parent = resolveWithinRoot(workspace,
|
|
3672
|
+
const abs = path4.resolve(workspace, target);
|
|
3673
|
+
const parent = resolveWithinRoot(workspace, path4.dirname(abs));
|
|
3285
3674
|
if (!parent) return null;
|
|
3286
|
-
return { root: parent.root, real:
|
|
3675
|
+
return { root: parent.root, real: path4.join(parent.real, path4.basename(abs)) };
|
|
3287
3676
|
}
|
|
3288
3677
|
|
|
3289
3678
|
// src/permissions/PermissionEngine.ts
|
|
3290
|
-
import * as
|
|
3679
|
+
import * as path5 from "path";
|
|
3291
3680
|
var Mode = /* @__PURE__ */ ((Mode2) => {
|
|
3292
3681
|
Mode2["DISCUSS"] = "discuss";
|
|
3293
3682
|
Mode2["PLAN"] = "plan";
|
|
@@ -3344,7 +3733,7 @@ var PermissionEngine = class {
|
|
|
3344
3733
|
/** 会话级「总是允许此命令」集合 */
|
|
3345
3734
|
sessionAllowCommands;
|
|
3346
3735
|
constructor(opts) {
|
|
3347
|
-
this.workspaceRoot =
|
|
3736
|
+
this.workspaceRoot = path5.resolve(opts.workspaceRoot);
|
|
3348
3737
|
this.mode = opts.mode;
|
|
3349
3738
|
this.allowedCommands = Array.isArray(opts.allowedCommands) ? opts.allowedCommands : [];
|
|
3350
3739
|
this.sessionAllowTools = /* @__PURE__ */ new Set();
|
|
@@ -4259,14 +4648,14 @@ function approvalToHtmlTable(input, maxLen = APPROVAL_VALUE_MAX) {
|
|
|
4259
4648
|
}
|
|
4260
4649
|
|
|
4261
4650
|
// src/engine/FileLogHook.ts
|
|
4262
|
-
import * as
|
|
4263
|
-
import * as
|
|
4651
|
+
import * as fs5 from "fs";
|
|
4652
|
+
import * as path6 from "path";
|
|
4264
4653
|
var FileLogHook = class {
|
|
4265
|
-
constructor(sessionId,
|
|
4654
|
+
constructor(sessionId, stateDir2) {
|
|
4266
4655
|
this.sessionId = sessionId;
|
|
4267
|
-
|
|
4268
|
-
this._logPath =
|
|
4269
|
-
this._statePath =
|
|
4656
|
+
fs5.mkdirSync(stateDir2, { recursive: true });
|
|
4657
|
+
this._logPath = path6.join(stateDir2, `${sessionId}.jsonl`);
|
|
4658
|
+
this._statePath = path6.join(stateDir2, `${sessionId}_state.json`);
|
|
4270
4659
|
}
|
|
4271
4660
|
sessionId;
|
|
4272
4661
|
_logPath;
|
|
@@ -4360,13 +4749,13 @@ var FileLogHook = class {
|
|
|
4360
4749
|
...meta?.nameSource !== void 0 ? { nameSource: meta.nameSource } : {},
|
|
4361
4750
|
...meta?.taskCount !== void 0 ? { taskCount: meta.taskCount } : {}
|
|
4362
4751
|
};
|
|
4363
|
-
|
|
4752
|
+
fs5.writeFileSync(this._statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
4364
4753
|
}
|
|
4365
4754
|
/** 读取保存的引擎状态 */
|
|
4366
|
-
static loadState(sessionId,
|
|
4367
|
-
const p =
|
|
4755
|
+
static loadState(sessionId, stateDir2) {
|
|
4756
|
+
const p = path6.join(stateDir2, `${sessionId}_state.json`);
|
|
4368
4757
|
try {
|
|
4369
|
-
const raw =
|
|
4758
|
+
const raw = fs5.readFileSync(p, "utf-8");
|
|
4370
4759
|
return JSON.parse(raw);
|
|
4371
4760
|
} catch (e) {
|
|
4372
4761
|
console.error(e);
|
|
@@ -4374,10 +4763,10 @@ var FileLogHook = class {
|
|
|
4374
4763
|
}
|
|
4375
4764
|
}
|
|
4376
4765
|
/** 读取事件日志 */
|
|
4377
|
-
static loadEvents(sessionId,
|
|
4378
|
-
const p =
|
|
4766
|
+
static loadEvents(sessionId, stateDir2) {
|
|
4767
|
+
const p = path6.join(stateDir2, `${sessionId}.jsonl`);
|
|
4379
4768
|
try {
|
|
4380
|
-
const raw =
|
|
4769
|
+
const raw = fs5.readFileSync(p, "utf-8");
|
|
4381
4770
|
return raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
4382
4771
|
} catch (e) {
|
|
4383
4772
|
console.error(e);
|
|
@@ -4388,7 +4777,7 @@ var FileLogHook = class {
|
|
|
4388
4777
|
_append(entry) {
|
|
4389
4778
|
entry["ts"] = (/* @__PURE__ */ new Date()).toISOString();
|
|
4390
4779
|
try {
|
|
4391
|
-
|
|
4780
|
+
fs5.appendFileSync(this._logPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
4392
4781
|
} catch (e) {
|
|
4393
4782
|
console.error(e);
|
|
4394
4783
|
}
|
|
@@ -4812,12 +5201,12 @@ var McpManager = class {
|
|
|
4812
5201
|
};
|
|
4813
5202
|
|
|
4814
5203
|
// src/tools/files.ts
|
|
4815
|
-
import * as
|
|
4816
|
-
import * as
|
|
5204
|
+
import * as fs6 from "fs";
|
|
5205
|
+
import * as path7 from "path";
|
|
4817
5206
|
var DEFAULT_MAX_LINES = 2e3;
|
|
4818
5207
|
var MAX_LINE_CHARS = 500;
|
|
4819
5208
|
function makeReadFile(workspace) {
|
|
4820
|
-
const root =
|
|
5209
|
+
const root = path7.resolve(workspace);
|
|
4821
5210
|
return function read_file(args) {
|
|
4822
5211
|
const filePath = String(args.path ?? "");
|
|
4823
5212
|
const startLine = Math.max(1, Number(args.start_line ?? 1));
|
|
@@ -4827,13 +5216,13 @@ function makeReadFile(workspace) {
|
|
|
4827
5216
|
return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
|
|
4828
5217
|
}
|
|
4829
5218
|
const target = resolved.real;
|
|
4830
|
-
if (!
|
|
5219
|
+
if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
|
|
4831
5220
|
return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
|
|
4832
5221
|
}
|
|
4833
5222
|
const selected = [];
|
|
4834
5223
|
let total = 0;
|
|
4835
5224
|
try {
|
|
4836
|
-
const content =
|
|
5225
|
+
const content = fs6.readFileSync(target, "utf-8");
|
|
4837
5226
|
const lines = content.split("\n");
|
|
4838
5227
|
total = lines.length;
|
|
4839
5228
|
for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
|
|
@@ -4848,7 +5237,7 @@ function makeReadFile(workspace) {
|
|
|
4848
5237
|
}
|
|
4849
5238
|
const end = startLine + selected.length - 1;
|
|
4850
5239
|
const result = {
|
|
4851
|
-
path:
|
|
5240
|
+
path: path7.relative(resolved.root, target),
|
|
4852
5241
|
start_line: startLine,
|
|
4853
5242
|
end_line: end,
|
|
4854
5243
|
total_lines: total,
|
|
@@ -4861,7 +5250,7 @@ function makeReadFile(workspace) {
|
|
|
4861
5250
|
};
|
|
4862
5251
|
}
|
|
4863
5252
|
function makeWriteFile(workspace) {
|
|
4864
|
-
const root =
|
|
5253
|
+
const root = path7.resolve(workspace);
|
|
4865
5254
|
return function write_file(args) {
|
|
4866
5255
|
const filePath = String(args.path ?? "");
|
|
4867
5256
|
const content = String(args.content ?? "");
|
|
@@ -4871,34 +5260,34 @@ function makeWriteFile(workspace) {
|
|
|
4871
5260
|
return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
|
|
4872
5261
|
}
|
|
4873
5262
|
const target = resolved.real;
|
|
4874
|
-
if (
|
|
5263
|
+
if (fs6.existsSync(target) && fs6.statSync(target).isDirectory()) {
|
|
4875
5264
|
return { error: "\u8DEF\u5F84\u662F\u76EE\u5F55\uFF0C\u4E0D\u662F\u6587\u4EF6" };
|
|
4876
5265
|
}
|
|
4877
|
-
if (
|
|
5266
|
+
if (fs6.existsSync(target) && !overwrite) {
|
|
4878
5267
|
return { error: "\u6587\u4EF6\u5DF2\u5B58\u5728 (overwrite=false)" };
|
|
4879
5268
|
}
|
|
4880
5269
|
try {
|
|
4881
|
-
|
|
4882
|
-
|
|
5270
|
+
fs6.mkdirSync(path7.dirname(target), { recursive: true });
|
|
5271
|
+
fs6.writeFileSync(target, content, "utf-8");
|
|
4883
5272
|
} catch (exc) {
|
|
4884
5273
|
return { error: `\u5199\u5165\u5931\u8D25: ${String(exc)}` };
|
|
4885
5274
|
}
|
|
4886
|
-
return
|
|
5275
|
+
return path7.relative(resolved.root, target);
|
|
4887
5276
|
};
|
|
4888
5277
|
}
|
|
4889
5278
|
function makeListFiles(workspace) {
|
|
4890
|
-
const root =
|
|
5279
|
+
const root = path7.resolve(workspace);
|
|
4891
5280
|
return function list_files(args) {
|
|
4892
5281
|
const dirPath = String(args.path ?? ".");
|
|
4893
5282
|
const maxResults = Math.min(Number(args.max_results ?? 100), 100);
|
|
4894
5283
|
const resolved = resolveWithinRoot(root, dirPath);
|
|
4895
5284
|
if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
|
|
4896
5285
|
const target = resolved.real;
|
|
4897
|
-
if (!
|
|
5286
|
+
if (!fs6.existsSync(target) || !fs6.statSync(target).isDirectory()) {
|
|
4898
5287
|
return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
|
|
4899
5288
|
}
|
|
4900
5289
|
try {
|
|
4901
|
-
const entries =
|
|
5290
|
+
const entries = fs6.readdirSync(target, { withFileTypes: true });
|
|
4902
5291
|
const results = [];
|
|
4903
5292
|
for (const e of entries) {
|
|
4904
5293
|
if (results.length >= maxResults) break;
|
|
@@ -4907,14 +5296,14 @@ function makeListFiles(workspace) {
|
|
|
4907
5296
|
results.push(e.isDirectory() ? `${e.name}/` : e.name);
|
|
4908
5297
|
}
|
|
4909
5298
|
results.sort();
|
|
4910
|
-
return { path:
|
|
5299
|
+
return { path: path7.relative(resolved.root, target), entries: results.slice(0, maxResults) };
|
|
4911
5300
|
} catch (exc) {
|
|
4912
5301
|
return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
|
|
4913
5302
|
}
|
|
4914
5303
|
};
|
|
4915
5304
|
}
|
|
4916
5305
|
function makeReplaceInFile(workspace) {
|
|
4917
|
-
const root =
|
|
5306
|
+
const root = path7.resolve(workspace);
|
|
4918
5307
|
return function replace_in_file(args) {
|
|
4919
5308
|
const filePath = String(args.path ?? "");
|
|
4920
5309
|
const oldStr = String(args.old ?? "");
|
|
@@ -4924,19 +5313,19 @@ function makeReplaceInFile(workspace) {
|
|
|
4924
5313
|
const resolved = resolveWithinRoot(root, filePath);
|
|
4925
5314
|
if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
|
|
4926
5315
|
const target = resolved.real;
|
|
4927
|
-
if (!
|
|
5316
|
+
if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
|
|
4928
5317
|
return { error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
|
|
4929
5318
|
}
|
|
4930
5319
|
try {
|
|
4931
|
-
const content =
|
|
5320
|
+
const content = fs6.readFileSync(target, "utf-8");
|
|
4932
5321
|
const count = content.split(oldStr).length - 1;
|
|
4933
5322
|
if (count !== expectedReplacements) {
|
|
4934
5323
|
return { error: `\u671F\u671B ${expectedReplacements} \u5904\u66FF\u6362\uFF0C\u5B9E\u9645\u627E\u5230 ${count} \u5904: ${filePath}` };
|
|
4935
5324
|
}
|
|
4936
5325
|
const updated = content.replaceAll(oldStr, newStr);
|
|
4937
|
-
|
|
5326
|
+
fs6.writeFileSync(target, updated, "utf-8");
|
|
4938
5327
|
return {
|
|
4939
|
-
path:
|
|
5328
|
+
path: path7.relative(resolved.root, target),
|
|
4940
5329
|
replacements: count,
|
|
4941
5330
|
chars_before: content.length,
|
|
4942
5331
|
chars_after: updated.length
|
|
@@ -5036,8 +5425,8 @@ function registerFileTools(registry, workspace) {
|
|
|
5036
5425
|
}
|
|
5037
5426
|
|
|
5038
5427
|
// src/tools/shell.ts
|
|
5039
|
-
import * as
|
|
5040
|
-
import * as
|
|
5428
|
+
import * as fs8 from "fs";
|
|
5429
|
+
import * as path9 from "path";
|
|
5041
5430
|
|
|
5042
5431
|
// src/tools/execAsync.ts
|
|
5043
5432
|
import { spawn } from "child_process";
|
|
@@ -5199,15 +5588,15 @@ function execAsync(file, argv, opts = {}) {
|
|
|
5199
5588
|
}
|
|
5200
5589
|
|
|
5201
5590
|
// src/tools/shellRuntime.ts
|
|
5202
|
-
import * as
|
|
5203
|
-
import * as
|
|
5591
|
+
import * as fs7 from "fs";
|
|
5592
|
+
import * as path8 from "path";
|
|
5204
5593
|
var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
|
|
5205
5594
|
function pathApi(platform) {
|
|
5206
|
-
return platform === "win32" ?
|
|
5595
|
+
return platform === "win32" ? path8.win32 : path8.posix;
|
|
5207
5596
|
}
|
|
5208
5597
|
function defaultFileExists(p) {
|
|
5209
5598
|
try {
|
|
5210
|
-
return
|
|
5599
|
+
return fs7.statSync(p).isFile();
|
|
5211
5600
|
} catch {
|
|
5212
5601
|
return false;
|
|
5213
5602
|
}
|
|
@@ -5238,7 +5627,7 @@ function kindFromBasename(base) {
|
|
|
5238
5627
|
return "sh";
|
|
5239
5628
|
}
|
|
5240
5629
|
function busyboxArgs(file, command) {
|
|
5241
|
-
const base = (file ?
|
|
5630
|
+
const base = (file ? path8.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
|
|
5242
5631
|
return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
|
|
5243
5632
|
}
|
|
5244
5633
|
function argsFor(kind, command) {
|
|
@@ -5504,7 +5893,7 @@ var MAX_STDERR = 5e3;
|
|
|
5504
5893
|
var MAX_BUFFER = 1024 * 1024 * 5;
|
|
5505
5894
|
var shellSessions = /* @__PURE__ */ new Map();
|
|
5506
5895
|
function sessionFor(workspace) {
|
|
5507
|
-
const key =
|
|
5896
|
+
const key = path9.resolve(workspace);
|
|
5508
5897
|
let s = shellSessions.get(key);
|
|
5509
5898
|
if (!s) {
|
|
5510
5899
|
s = { cwd: key };
|
|
@@ -5561,8 +5950,8 @@ function makeRunShell(workspace, spec, tasks, scope) {
|
|
|
5561
5950
|
let toRun = command;
|
|
5562
5951
|
if (target) {
|
|
5563
5952
|
try {
|
|
5564
|
-
const resolved =
|
|
5565
|
-
if (
|
|
5953
|
+
const resolved = path9.resolve(session.cwd, target);
|
|
5954
|
+
if (fs8.existsSync(resolved) && fs8.statSync(resolved).isDirectory()) {
|
|
5566
5955
|
session.cwd = resolved;
|
|
5567
5956
|
}
|
|
5568
5957
|
} catch {
|
|
@@ -5687,8 +6076,8 @@ function registerShellTool(registry, workspace, scope) {
|
|
|
5687
6076
|
}
|
|
5688
6077
|
|
|
5689
6078
|
// src/tools/nodeGrep.ts
|
|
5690
|
-
import * as
|
|
5691
|
-
import * as
|
|
6079
|
+
import * as fs9 from "fs";
|
|
6080
|
+
import * as path10 from "path";
|
|
5692
6081
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5693
6082
|
".git",
|
|
5694
6083
|
"node_modules",
|
|
@@ -5713,27 +6102,27 @@ function nodeGrep(opts) {
|
|
|
5713
6102
|
} catch (err) {
|
|
5714
6103
|
throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
|
|
5715
6104
|
}
|
|
5716
|
-
const target =
|
|
5717
|
-
if (!
|
|
6105
|
+
const target = path10.resolve(opts.workspace, opts.target);
|
|
6106
|
+
if (!fs9.existsSync(target)) {
|
|
5718
6107
|
throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
|
|
5719
6108
|
}
|
|
5720
6109
|
const matches = [];
|
|
5721
6110
|
const scanFile = (file) => {
|
|
5722
6111
|
let stat2;
|
|
5723
6112
|
try {
|
|
5724
|
-
stat2 =
|
|
6113
|
+
stat2 = fs9.statSync(file);
|
|
5725
6114
|
} catch {
|
|
5726
6115
|
return false;
|
|
5727
6116
|
}
|
|
5728
6117
|
if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
|
|
5729
6118
|
let buf;
|
|
5730
6119
|
try {
|
|
5731
|
-
buf =
|
|
6120
|
+
buf = fs9.readFileSync(file);
|
|
5732
6121
|
} catch {
|
|
5733
6122
|
return false;
|
|
5734
6123
|
}
|
|
5735
6124
|
if (looksBinary(buf)) return false;
|
|
5736
|
-
const rel =
|
|
6125
|
+
const rel = path10.relative(opts.workspace, file) || path10.basename(file);
|
|
5737
6126
|
const lines = buf.toString("utf-8").split("\n");
|
|
5738
6127
|
for (let i = 0; i < lines.length; i++) {
|
|
5739
6128
|
const raw = lines[i].replace(/\r$/, "");
|
|
@@ -5744,7 +6133,7 @@ function nodeGrep(opts) {
|
|
|
5744
6133
|
}
|
|
5745
6134
|
return false;
|
|
5746
6135
|
};
|
|
5747
|
-
const stat =
|
|
6136
|
+
const stat = fs9.statSync(target);
|
|
5748
6137
|
if (stat.isFile()) {
|
|
5749
6138
|
scanFile(target);
|
|
5750
6139
|
return matches;
|
|
@@ -5754,12 +6143,12 @@ function nodeGrep(opts) {
|
|
|
5754
6143
|
const dir = stack.pop();
|
|
5755
6144
|
let entries;
|
|
5756
6145
|
try {
|
|
5757
|
-
entries =
|
|
6146
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
5758
6147
|
} catch {
|
|
5759
6148
|
continue;
|
|
5760
6149
|
}
|
|
5761
6150
|
for (const entry of entries) {
|
|
5762
|
-
const full =
|
|
6151
|
+
const full = path10.join(dir, entry.name);
|
|
5763
6152
|
if (entry.isDirectory()) {
|
|
5764
6153
|
if (!SKIP_DIRS.has(entry.name)) stack.push(full);
|
|
5765
6154
|
} else if (entry.isFile()) {
|
|
@@ -5771,22 +6160,22 @@ function nodeGrep(opts) {
|
|
|
5771
6160
|
}
|
|
5772
6161
|
|
|
5773
6162
|
// src/tools/ripgrep.ts
|
|
5774
|
-
import * as
|
|
5775
|
-
import * as
|
|
5776
|
-
import * as
|
|
6163
|
+
import * as fs10 from "fs";
|
|
6164
|
+
import * as os2 from "os";
|
|
6165
|
+
import * as path11 from "path";
|
|
5777
6166
|
import { createRequire } from "module";
|
|
5778
6167
|
var cached2;
|
|
5779
6168
|
function extractBundledRipgrep() {
|
|
5780
6169
|
try {
|
|
5781
|
-
const req = typeof __require === "function" ? __require : createRequire(
|
|
6170
|
+
const req = typeof __require === "function" ? __require : createRequire(path11.join(process.cwd(), "__zqagent__.js"));
|
|
5782
6171
|
const sea = req("node:sea");
|
|
5783
6172
|
if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
|
|
5784
6173
|
const asset = sea.getAsset("rg");
|
|
5785
6174
|
if (!asset) return null;
|
|
5786
|
-
const dir =
|
|
5787
|
-
const file =
|
|
5788
|
-
|
|
5789
|
-
|
|
6175
|
+
const dir = fs10.mkdtempSync(path11.join(os2.tmpdir(), "zqagent-rg-"));
|
|
6176
|
+
const file = path11.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
|
|
6177
|
+
fs10.writeFileSync(file, Buffer.from(asset));
|
|
6178
|
+
fs10.chmodSync(file, 493);
|
|
5790
6179
|
return file;
|
|
5791
6180
|
} catch {
|
|
5792
6181
|
return null;
|
|
@@ -5795,7 +6184,7 @@ function extractBundledRipgrep() {
|
|
|
5795
6184
|
function resolveRipgrep() {
|
|
5796
6185
|
if (cached2 !== void 0) return cached2;
|
|
5797
6186
|
const explicit = (process.env.ZQAGENT_RG ?? "").trim();
|
|
5798
|
-
if (explicit &&
|
|
6187
|
+
if (explicit && fs10.existsSync(explicit)) {
|
|
5799
6188
|
cached2 = explicit;
|
|
5800
6189
|
return cached2;
|
|
5801
6190
|
}
|
|
@@ -5809,7 +6198,7 @@ function resolveRipgrep() {
|
|
|
5809
6198
|
env: process.env,
|
|
5810
6199
|
fileExists: (p) => {
|
|
5811
6200
|
try {
|
|
5812
|
-
return
|
|
6201
|
+
return fs10.statSync(p).isFile();
|
|
5813
6202
|
} catch {
|
|
5814
6203
|
return false;
|
|
5815
6204
|
}
|
|
@@ -6356,8 +6745,8 @@ function registerWebTools(registry) {
|
|
|
6356
6745
|
}
|
|
6357
6746
|
|
|
6358
6747
|
// src/tools/git.ts
|
|
6359
|
-
import * as
|
|
6360
|
-
import * as
|
|
6748
|
+
import * as fs11 from "fs";
|
|
6749
|
+
import * as path12 from "path";
|
|
6361
6750
|
async function runGit(repoDir, args, timeout = 15e3) {
|
|
6362
6751
|
const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
|
|
6363
6752
|
cwd: repoDir,
|
|
@@ -6374,9 +6763,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
|
|
|
6374
6763
|
async function resolveRepoRoot(workspace, pathArg) {
|
|
6375
6764
|
let dir = workspace;
|
|
6376
6765
|
if (pathArg) {
|
|
6377
|
-
const abs =
|
|
6766
|
+
const abs = path12.resolve(workspace, pathArg);
|
|
6378
6767
|
try {
|
|
6379
|
-
dir =
|
|
6768
|
+
dir = fs11.statSync(abs).isDirectory() ? abs : path12.dirname(abs);
|
|
6380
6769
|
} catch {
|
|
6381
6770
|
dir = abs;
|
|
6382
6771
|
}
|
|
@@ -6389,24 +6778,24 @@ function repoDirArg(workspace, raw) {
|
|
|
6389
6778
|
if (typeof raw !== "string") return void 0;
|
|
6390
6779
|
const v = raw.trim();
|
|
6391
6780
|
if (!v) return void 0;
|
|
6392
|
-
const base =
|
|
6393
|
-
const abs =
|
|
6394
|
-
const rel =
|
|
6395
|
-
if (rel.startsWith("..") ||
|
|
6781
|
+
const base = path12.resolve(workspace);
|
|
6782
|
+
const abs = path12.resolve(base, v);
|
|
6783
|
+
const rel = path12.relative(base, abs);
|
|
6784
|
+
if (rel.startsWith("..") || path12.isAbsolute(rel)) return void 0;
|
|
6396
6785
|
return abs;
|
|
6397
6786
|
}
|
|
6398
6787
|
function toPathspec(repoRoot, workspace, pathArg) {
|
|
6399
6788
|
if (!pathArg) return null;
|
|
6400
|
-
const abs =
|
|
6401
|
-
let rel =
|
|
6789
|
+
const abs = path12.resolve(workspace, pathArg);
|
|
6790
|
+
let rel = path12.relative(repoRoot, abs);
|
|
6402
6791
|
if (rel === "") return ".";
|
|
6403
|
-
if (rel.startsWith("..") ||
|
|
6792
|
+
if (rel.startsWith("..") || path12.isAbsolute(rel)) {
|
|
6404
6793
|
try {
|
|
6405
|
-
const realRepo =
|
|
6406
|
-
const realAbs =
|
|
6407
|
-
const rel2 =
|
|
6794
|
+
const realRepo = fs11.realpathSync(repoRoot);
|
|
6795
|
+
const realAbs = fs11.realpathSync(abs);
|
|
6796
|
+
const rel2 = path12.relative(realRepo, realAbs);
|
|
6408
6797
|
if (rel2 === "") return ".";
|
|
6409
|
-
if (!rel2.startsWith("..") && !
|
|
6798
|
+
if (!rel2.startsWith("..") && !path12.isAbsolute(rel2)) return rel2;
|
|
6410
6799
|
} catch {
|
|
6411
6800
|
}
|
|
6412
6801
|
return null;
|
|
@@ -6551,10 +6940,10 @@ function registerGitTools(registry, workspace) {
|
|
|
6551
6940
|
}
|
|
6552
6941
|
|
|
6553
6942
|
// src/tools/diff.ts
|
|
6554
|
-
import * as
|
|
6555
|
-
import * as
|
|
6943
|
+
import * as fs12 from "fs";
|
|
6944
|
+
import * as path13 from "path";
|
|
6556
6945
|
function makeApplyPatch(workspace) {
|
|
6557
|
-
const root =
|
|
6946
|
+
const root = path13.resolve(workspace);
|
|
6558
6947
|
return function apply_patch(args) {
|
|
6559
6948
|
const patch = String(args.patch ?? "");
|
|
6560
6949
|
if (!patch.trim()) return { error: "\u8865\u4E01\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
@@ -6593,12 +6982,12 @@ function makeApplyPatch(workspace) {
|
|
|
6593
6982
|
contentLines.push(current.slice(1));
|
|
6594
6983
|
i++;
|
|
6595
6984
|
}
|
|
6596
|
-
if (
|
|
6985
|
+
if (fs12.existsSync(target)) {
|
|
6597
6986
|
return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${filePath}` };
|
|
6598
6987
|
}
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
const rel =
|
|
6988
|
+
fs12.mkdirSync(path13.dirname(target), { recursive: true });
|
|
6989
|
+
fs12.writeFileSync(target, contentLines.join("\n") + "\n", "utf-8");
|
|
6990
|
+
const rel = path13.relative(resolved.root, target);
|
|
6602
6991
|
changedFiles.push(rel);
|
|
6603
6992
|
addedFiles.push(rel);
|
|
6604
6993
|
fileCount++;
|
|
@@ -6611,11 +7000,11 @@ function makeApplyPatch(workspace) {
|
|
|
6611
7000
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
6612
7001
|
}
|
|
6613
7002
|
const target = entry.real;
|
|
6614
|
-
if (!
|
|
7003
|
+
if (!fs12.existsSync(target)) {
|
|
6615
7004
|
return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
|
|
6616
7005
|
}
|
|
6617
|
-
|
|
6618
|
-
deletedFiles.push(
|
|
7006
|
+
fs12.unlinkSync(target);
|
|
7007
|
+
deletedFiles.push(path13.relative(entry.root, target));
|
|
6619
7008
|
fileCount++;
|
|
6620
7009
|
i++;
|
|
6621
7010
|
continue;
|
|
@@ -6651,10 +7040,10 @@ function makeApplyPatch(workspace) {
|
|
|
6651
7040
|
if (!chunkLines.length) {
|
|
6652
7041
|
return { error: `\u8865\u4E01\u66F4\u65B0\u6CA1\u6709 hunks: ${filePath}` };
|
|
6653
7042
|
}
|
|
6654
|
-
if (!
|
|
7043
|
+
if (!fs12.existsSync(target)) {
|
|
6655
7044
|
return { error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}` };
|
|
6656
7045
|
}
|
|
6657
|
-
const oldLines =
|
|
7046
|
+
const oldLines = fs12.readFileSync(target, "utf-8").split("\n");
|
|
6658
7047
|
const newLines = applyPatchHunks(oldLines, chunkLines, filePath);
|
|
6659
7048
|
let finalResolved = resolved;
|
|
6660
7049
|
if (moveTo) {
|
|
@@ -6665,20 +7054,20 @@ function makeApplyPatch(workspace) {
|
|
|
6665
7054
|
finalResolved = moved;
|
|
6666
7055
|
}
|
|
6667
7056
|
const finalTarget = finalResolved.real;
|
|
6668
|
-
if (moveTo &&
|
|
7057
|
+
if (moveTo && fs12.existsSync(finalTarget) && finalTarget !== target) {
|
|
6669
7058
|
return { error: `\u6587\u4EF6\u5DF2\u5B58\u5728: ${moveTo}` };
|
|
6670
7059
|
}
|
|
6671
|
-
|
|
6672
|
-
|
|
7060
|
+
fs12.mkdirSync(path13.dirname(finalTarget), { recursive: true });
|
|
7061
|
+
fs12.writeFileSync(finalTarget, newLines.join("\n"), "utf-8");
|
|
6673
7062
|
if (moveTo && finalTarget !== target) {
|
|
6674
7063
|
const entry = resolveEntryWithinRoot(root, filePath);
|
|
6675
7064
|
if (!entry) {
|
|
6676
7065
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${filePath}` };
|
|
6677
7066
|
}
|
|
6678
|
-
|
|
6679
|
-
deletedFiles.push(
|
|
7067
|
+
fs12.unlinkSync(entry.real);
|
|
7068
|
+
deletedFiles.push(path13.relative(resolved.root, target));
|
|
6680
7069
|
}
|
|
6681
|
-
changedFiles.push(
|
|
7070
|
+
changedFiles.push(path13.relative(finalResolved.root, finalTarget));
|
|
6682
7071
|
fileCount++;
|
|
6683
7072
|
continue;
|
|
6684
7073
|
}
|
|
@@ -6690,18 +7079,18 @@ function makeApplyPatch(workspace) {
|
|
|
6690
7079
|
return { changed_files: changedFiles, added_files: addedFiles, deleted_files: deletedFiles, file_count: fileCount };
|
|
6691
7080
|
};
|
|
6692
7081
|
}
|
|
6693
|
-
function applyPatchHunks(oldLines, chunks,
|
|
7082
|
+
function applyPatchHunks(oldLines, chunks, path16) {
|
|
6694
7083
|
let newLines = [...oldLines];
|
|
6695
7084
|
let cursor = 0;
|
|
6696
7085
|
for (const chunk of chunks) {
|
|
6697
7086
|
const oldBlock = chunk.filter((l) => l[0] === " " || l[0] === "-").map((l) => l.slice(1));
|
|
6698
7087
|
const newBlock = chunk.filter((l) => l[0] === " " || l[0] === "+").map((l) => l.slice(1));
|
|
6699
7088
|
if (!oldBlock.length) {
|
|
6700
|
-
throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${
|
|
7089
|
+
throw new Error(`\u8865\u4E01 hunk \u5FC5\u987B\u5305\u542B\u4E0A\u4E0B\u6587\u6216\u5220\u9664\u884C: ${path16}`);
|
|
6701
7090
|
}
|
|
6702
7091
|
const matchStart = findBlockMatch(newLines, oldBlock, cursor);
|
|
6703
7092
|
if (matchStart === -1) {
|
|
6704
|
-
throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${
|
|
7093
|
+
throw new Error(`\u8865\u4E01\u4E0A\u4E0B\u6587\u4E0D\u5339\u914D: ${path16}`);
|
|
6705
7094
|
}
|
|
6706
7095
|
const matchEnd = matchStart + oldBlock.length;
|
|
6707
7096
|
newLines.splice(matchStart, oldBlock.length, ...newBlock);
|
|
@@ -6718,7 +7107,7 @@ function findBlockMatch(lines, block, start) {
|
|
|
6718
7107
|
return -1;
|
|
6719
7108
|
}
|
|
6720
7109
|
function makeApplyUnifiedDiff(workspace) {
|
|
6721
|
-
const root =
|
|
7110
|
+
const root = path13.resolve(workspace);
|
|
6722
7111
|
return function apply_unified_diff(args) {
|
|
6723
7112
|
const diff = String(args.diff ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
6724
7113
|
if (!diff.trim()) return { error: "diff \u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
@@ -6737,26 +7126,26 @@ function makeApplyUnifiedDiff(workspace) {
|
|
|
6737
7126
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
|
|
6738
7127
|
}
|
|
6739
7128
|
const target = resolved.real;
|
|
6740
|
-
const oldLines = oldIsDevNull ? [] :
|
|
7129
|
+
const oldLines = oldIsDevNull ? [] : fs12.existsSync(target) ? fs12.readFileSync(target, "utf-8").split("\n") : [];
|
|
6741
7130
|
const newLines = applyDiffHunks(oldLines, patch.hunks, targetPath);
|
|
6742
7131
|
hunkCount += patch.hunks.length;
|
|
6743
7132
|
if (newIsDevNull) {
|
|
6744
|
-
if (
|
|
6745
|
-
if (!
|
|
7133
|
+
if (fs12.existsSync(target)) {
|
|
7134
|
+
if (!fs12.statSync(target).isFile()) {
|
|
6746
7135
|
return { error: `\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${targetPath}` };
|
|
6747
7136
|
}
|
|
6748
7137
|
const entry = resolveEntryWithinRoot(root, targetPath);
|
|
6749
7138
|
if (!entry) {
|
|
6750
7139
|
return { error: `\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916: ${targetPath}` };
|
|
6751
7140
|
}
|
|
6752
|
-
|
|
7141
|
+
fs12.unlinkSync(entry.real);
|
|
6753
7142
|
}
|
|
6754
|
-
deletedFiles.push(
|
|
7143
|
+
deletedFiles.push(path13.relative(resolved.root, target));
|
|
6755
7144
|
continue;
|
|
6756
7145
|
}
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
const rel =
|
|
7146
|
+
fs12.mkdirSync(path13.dirname(target), { recursive: true });
|
|
7147
|
+
fs12.writeFileSync(target, newLines.join("\n"), "utf-8");
|
|
7148
|
+
const rel = path13.relative(resolved.root, target);
|
|
6760
7149
|
changedFiles.push(rel);
|
|
6761
7150
|
if (oldIsDevNull) addedFiles.push(rel);
|
|
6762
7151
|
}
|
|
@@ -7001,7 +7390,8 @@ function buildExplorerEngine(opts) {
|
|
|
7001
7390
|
model: opts.model,
|
|
7002
7391
|
instructions: EXPLORER_INSTRUCTIONS,
|
|
7003
7392
|
maxIterations: parseMaxIterations(process.env.EXPLORE_MAX_ITERATIONS, 30),
|
|
7004
|
-
hook: opts.hook
|
|
7393
|
+
hook: opts.hook,
|
|
7394
|
+
toolResultDir: opts.toolResultDir
|
|
7005
7395
|
});
|
|
7006
7396
|
}
|
|
7007
7397
|
function makeExploreTool(opts) {
|
|
@@ -7013,7 +7403,8 @@ function makeExploreTool(opts) {
|
|
|
7013
7403
|
provider: opts.provider,
|
|
7014
7404
|
model: opts.model,
|
|
7015
7405
|
readonlyRegistrars: opts.readonlyRegistrars,
|
|
7016
|
-
hook: opts.hookFactory?.()
|
|
7406
|
+
hook: opts.hookFactory?.(),
|
|
7407
|
+
toolResultDir: opts.toolResultDir
|
|
7017
7408
|
});
|
|
7018
7409
|
let report = "";
|
|
7019
7410
|
let status = "unknown";
|
|
@@ -7073,13 +7464,13 @@ function registerExploreTool(registry, opts) {
|
|
|
7073
7464
|
}
|
|
7074
7465
|
|
|
7075
7466
|
// src/tools/media.ts
|
|
7076
|
-
import * as
|
|
7077
|
-
import * as
|
|
7467
|
+
import * as fs14 from "fs";
|
|
7468
|
+
import * as path15 from "path";
|
|
7078
7469
|
|
|
7079
7470
|
// src/tools/ffmpegRuntime.ts
|
|
7080
|
-
import * as
|
|
7081
|
-
import * as
|
|
7082
|
-
import * as
|
|
7471
|
+
import * as fs13 from "fs";
|
|
7472
|
+
import * as os3 from "os";
|
|
7473
|
+
import * as path14 from "path";
|
|
7083
7474
|
import { createRequire as createRequire2 } from "module";
|
|
7084
7475
|
import { createHash } from "crypto";
|
|
7085
7476
|
import { pipeline } from "stream/promises";
|
|
@@ -7102,29 +7493,29 @@ if (PROXY_ENV2) {
|
|
|
7102
7493
|
}
|
|
7103
7494
|
var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
7104
7495
|
function agentBinDir(workspace) {
|
|
7105
|
-
return
|
|
7496
|
+
return path14.join(path14.resolve(workspace), ".agent", "bin");
|
|
7106
7497
|
}
|
|
7107
7498
|
function exeName(name, platform) {
|
|
7108
7499
|
return platform === "win32" ? `${name}.exe` : name;
|
|
7109
7500
|
}
|
|
7110
7501
|
function defaultFileExists2(p) {
|
|
7111
7502
|
try {
|
|
7112
|
-
return
|
|
7503
|
+
return fs13.statSync(p).isFile();
|
|
7113
7504
|
} catch {
|
|
7114
7505
|
return false;
|
|
7115
7506
|
}
|
|
7116
7507
|
}
|
|
7117
7508
|
function extractBundled(name, platform) {
|
|
7118
7509
|
try {
|
|
7119
|
-
const req = typeof __require === "function" ? __require : createRequire2(
|
|
7510
|
+
const req = typeof __require === "function" ? __require : createRequire2(path14.join(process.cwd(), "__zqagent__.js"));
|
|
7120
7511
|
const sea = req("node:sea");
|
|
7121
7512
|
if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
|
|
7122
7513
|
const asset = sea.getAsset(name);
|
|
7123
7514
|
if (!asset) return null;
|
|
7124
|
-
const dir =
|
|
7125
|
-
const file =
|
|
7126
|
-
|
|
7127
|
-
|
|
7515
|
+
const dir = fs13.mkdtempSync(path14.join(os3.tmpdir(), `zqagent-${name}-`));
|
|
7516
|
+
const file = path14.join(dir, exeName(name, platform));
|
|
7517
|
+
fs13.writeFileSync(file, Buffer.from(asset));
|
|
7518
|
+
fs13.chmodSync(file, 493);
|
|
7128
7519
|
return file;
|
|
7129
7520
|
} catch {
|
|
7130
7521
|
return null;
|
|
@@ -7134,7 +7525,7 @@ function resolveOne(name, workspace, o) {
|
|
|
7134
7525
|
const envKey = name === "ffmpeg" ? ENV_FFMPEG : ENV_FFPROBE;
|
|
7135
7526
|
const explicit = (o.env[envKey] ?? "").trim();
|
|
7136
7527
|
if (explicit && o.fileExists(explicit)) return { path: explicit, source: "env" };
|
|
7137
|
-
const local =
|
|
7528
|
+
const local = path14.join(agentBinDir(workspace), exeName(name, o.platform));
|
|
7138
7529
|
if (o.fileExists(local)) return { path: local, source: "agent-bin" };
|
|
7139
7530
|
const bundled = extractBundled(name, o.platform);
|
|
7140
7531
|
if (bundled) return { path: bundled, source: "bundled" };
|
|
@@ -7148,7 +7539,7 @@ function resolveOne(name, workspace, o) {
|
|
|
7148
7539
|
var cache = /* @__PURE__ */ new Map();
|
|
7149
7540
|
function resolveMedia(workspace, opts = {}) {
|
|
7150
7541
|
const platform = opts.platform ?? process.platform;
|
|
7151
|
-
const key = `${platform}:${
|
|
7542
|
+
const key = `${platform}:${path14.resolve(workspace)}`;
|
|
7152
7543
|
const hit = cache.get(key);
|
|
7153
7544
|
if (hit) return hit;
|
|
7154
7545
|
const ctx = {
|
|
@@ -7254,7 +7645,7 @@ function parseChecksumFile(text, filename) {
|
|
|
7254
7645
|
if (!m) continue;
|
|
7255
7646
|
const hash = m[1].toLowerCase();
|
|
7256
7647
|
const name = m[2]?.trim() ?? "";
|
|
7257
|
-
if (!name || name === filename ||
|
|
7648
|
+
if (!name || name === filename || path14.basename(name) === filename) return hash;
|
|
7258
7649
|
}
|
|
7259
7650
|
const single = lines[0]?.trim();
|
|
7260
7651
|
if (lines.length === 1 && single && /^[0-9a-f]{32,128}$/i.test(single)) {
|
|
@@ -7264,7 +7655,7 @@ function parseChecksumFile(text, filename) {
|
|
|
7264
7655
|
}
|
|
7265
7656
|
async function computeChecksum(file, algo) {
|
|
7266
7657
|
const hash = createHash(algo);
|
|
7267
|
-
await pipeline(
|
|
7658
|
+
await pipeline(fs13.createReadStream(file), hash);
|
|
7268
7659
|
return hash.digest("hex");
|
|
7269
7660
|
}
|
|
7270
7661
|
function fetchInit() {
|
|
@@ -7279,7 +7670,7 @@ async function downloadToFile(url, dest) {
|
|
|
7279
7670
|
const resp = await nodeFetch3(url, fetchInit());
|
|
7280
7671
|
if (!resp.ok) throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
|
|
7281
7672
|
if (!resp.body) throw new Error("\u54CD\u5E94\u6CA1\u6709\u5185\u5BB9");
|
|
7282
|
-
await pipeline(resp.body,
|
|
7673
|
+
await pipeline(resp.body, fs13.createWriteStream(dest));
|
|
7283
7674
|
}
|
|
7284
7675
|
async function resolveComponentUrl(c) {
|
|
7285
7676
|
if (c.url) return c.url;
|
|
@@ -7296,12 +7687,12 @@ function findBinaryInTree(dir, names) {
|
|
|
7296
7687
|
const cur = stack.pop();
|
|
7297
7688
|
let entries;
|
|
7298
7689
|
try {
|
|
7299
|
-
entries =
|
|
7690
|
+
entries = fs13.readdirSync(cur, { withFileTypes: true });
|
|
7300
7691
|
} catch {
|
|
7301
7692
|
continue;
|
|
7302
7693
|
}
|
|
7303
7694
|
for (const e of entries) {
|
|
7304
|
-
const full =
|
|
7695
|
+
const full = path14.join(cur, e.name);
|
|
7305
7696
|
if (e.isDirectory()) {
|
|
7306
7697
|
stack.push(full);
|
|
7307
7698
|
} else if (names.includes(e.name)) {
|
|
@@ -7312,7 +7703,7 @@ function findBinaryInTree(dir, names) {
|
|
|
7312
7703
|
return null;
|
|
7313
7704
|
}
|
|
7314
7705
|
async function extractArchive(archive, dest, kind, platform) {
|
|
7315
|
-
|
|
7706
|
+
fs13.mkdirSync(dest, { recursive: true });
|
|
7316
7707
|
let res;
|
|
7317
7708
|
if (kind === "tar.xz") {
|
|
7318
7709
|
res = await execAsync("tar", ["-xJf", archive, "-C", dest]);
|
|
@@ -7348,7 +7739,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
|
|
|
7348
7739
|
error: `\u6682\u4E0D\u652F\u6301\u81EA\u52A8\u4E0B\u8F7D\u7684\u5E73\u53F0: ${platform}/${arch}`
|
|
7349
7740
|
};
|
|
7350
7741
|
}
|
|
7351
|
-
const tmp =
|
|
7742
|
+
const tmp = fs13.mkdtempSync(path14.join(os3.tmpdir(), "zqagent-ffmpeg-"));
|
|
7352
7743
|
const binDir = agentBinDir(workspace);
|
|
7353
7744
|
const installed = {};
|
|
7354
7745
|
try {
|
|
@@ -7362,7 +7753,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
|
|
|
7362
7753
|
}
|
|
7363
7754
|
let archive = fetched.get(url);
|
|
7364
7755
|
if (!archive) {
|
|
7365
|
-
const archivePath =
|
|
7756
|
+
const archivePath = path14.join(tmp, path14.basename(new URL(url).pathname) || `${c.name}.${c.archive}`);
|
|
7366
7757
|
log(`\u4E0B\u8F7D ${url}`);
|
|
7367
7758
|
try {
|
|
7368
7759
|
await downloadToFile(url, archivePath);
|
|
@@ -7372,7 +7763,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
|
|
|
7372
7763
|
if (c.checksumUrl) {
|
|
7373
7764
|
const algo = c.checksumAlgo ?? "sha256";
|
|
7374
7765
|
try {
|
|
7375
|
-
const expected = parseChecksumFile(await fetchText(c.checksumUrl),
|
|
7766
|
+
const expected = parseChecksumFile(await fetchText(c.checksumUrl), path14.basename(archivePath));
|
|
7376
7767
|
if (!expected) {
|
|
7377
7768
|
notes.push(`\u672A\u80FD\u4ECE ${c.checksumUrl} \u89E3\u6790\u51FA\u6821\u9A8C\u548C\uFF0C\u5DF2\u8DF3\u8FC7\u6821\u9A8C`);
|
|
7378
7769
|
} else {
|
|
@@ -7389,24 +7780,24 @@ async function downloadFfmpeg(workspace, opts = {}) {
|
|
|
7389
7780
|
notes.push(`\u6821\u9A8C\u548C\u83B7\u53D6\u5931\u8D25 (${c.checksumUrl}): ${String(e instanceof Error ? e.message : e)}`);
|
|
7390
7781
|
}
|
|
7391
7782
|
} else {
|
|
7392
|
-
notes.push(`${
|
|
7783
|
+
notes.push(`${path14.basename(archivePath)} \u672A\u63D0\u4F9B\u6821\u9A8C\u548C\uFF08\u4EC5 HTTPS \u4F20\u8F93\u4FDD\u62A4\uFF09`);
|
|
7393
7784
|
}
|
|
7394
7785
|
archive = { file: archivePath, kind: c.archive };
|
|
7395
7786
|
fetched.set(url, archive);
|
|
7396
7787
|
}
|
|
7397
|
-
const extractDir =
|
|
7788
|
+
const extractDir = fs13.mkdtempSync(path14.join(tmp, "x-"));
|
|
7398
7789
|
const err = await extractArchive(archive.file, extractDir, archive.kind, platform);
|
|
7399
7790
|
if (err) return { ok: false, error: err };
|
|
7400
7791
|
const found = findBinaryInTree(extractDir, [exeName(c.name, platform), c.name]);
|
|
7401
7792
|
if (!found) {
|
|
7402
|
-
return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${
|
|
7793
|
+
return { ok: false, error: `\u5F52\u6863\u5185\u672A\u627E\u5230 ${c.name}: ${path14.basename(archive.file)}` };
|
|
7403
7794
|
}
|
|
7404
|
-
|
|
7405
|
-
const target =
|
|
7795
|
+
fs13.mkdirSync(binDir, { recursive: true });
|
|
7796
|
+
const target = path14.join(binDir, exeName(c.name, platform));
|
|
7406
7797
|
const staging = `${target}.tmp-${process.pid}`;
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7798
|
+
fs13.copyFileSync(found, staging);
|
|
7799
|
+
fs13.chmodSync(staging, 493);
|
|
7800
|
+
fs13.renameSync(staging, target);
|
|
7410
7801
|
installed[c.name] = target;
|
|
7411
7802
|
}
|
|
7412
7803
|
resetFfmpegCache();
|
|
@@ -7415,7 +7806,7 @@ async function downloadFfmpeg(workspace, opts = {}) {
|
|
|
7415
7806
|
} catch (e) {
|
|
7416
7807
|
return { ok: false, error: `\u5B89\u88C5\u5931\u8D25: ${String(e instanceof Error ? e.message : e)}` };
|
|
7417
7808
|
} finally {
|
|
7418
|
-
|
|
7809
|
+
fs13.rmSync(tmp, { recursive: true, force: true });
|
|
7419
7810
|
}
|
|
7420
7811
|
}
|
|
7421
7812
|
function canAutoDownload(platform, arch) {
|
|
@@ -7680,13 +8071,13 @@ function resolveTarget(workspace, value) {
|
|
|
7680
8071
|
return { abs: resolved.real, root: resolved.root };
|
|
7681
8072
|
}
|
|
7682
8073
|
function relOut(workspace, abs) {
|
|
7683
|
-
return
|
|
8074
|
+
return path15.relative(workspace, abs) || abs;
|
|
7684
8075
|
}
|
|
7685
8076
|
function registerMediaTool(registry, workspace, opts = {}) {
|
|
7686
8077
|
const platform = opts.platform ?? process.platform;
|
|
7687
8078
|
const arch = process.arch;
|
|
7688
8079
|
const media = async (args) => {
|
|
7689
|
-
const root =
|
|
8080
|
+
const root = path15.resolve(workspace);
|
|
7690
8081
|
const op = str(args["op"]);
|
|
7691
8082
|
if (!op) {
|
|
7692
8083
|
return { error: `\u7F3A\u5C11\u53C2\u6570 op\u3002\u53EF\u9009\u503C\uFF1A${OPS.join(" / ")}` };
|
|
@@ -7731,7 +8122,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
|
|
|
7731
8122
|
if (!input) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 input\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
|
|
7732
8123
|
const r = resolveTarget(root, input);
|
|
7733
8124
|
if ("error" in r) return { error: r.error };
|
|
7734
|
-
if (!
|
|
8125
|
+
if (!fs14.existsSync(r.abs) || !fs14.statSync(r.abs).isFile()) {
|
|
7735
8126
|
return { error: `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${input}` };
|
|
7736
8127
|
}
|
|
7737
8128
|
inputAbs = r.abs;
|
|
@@ -7743,7 +8134,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
|
|
|
7743
8134
|
if (!output) return { error: `\u64CD\u4F5C ${op} \u9700\u8981\u53C2\u6570 output\uFF08\u5DE5\u4F5C\u533A\u5185\u76F8\u5BF9\u8DEF\u5F84\uFF09` };
|
|
7744
8135
|
const r = resolveTarget(root, output);
|
|
7745
8136
|
if ("error" in r) return { error: r.error };
|
|
7746
|
-
|
|
8137
|
+
fs14.mkdirSync(path15.dirname(r.abs), { recursive: true });
|
|
7747
8138
|
outputAbs = r.abs;
|
|
7748
8139
|
relRoot = r.root;
|
|
7749
8140
|
}
|
|
@@ -7793,7 +8184,7 @@ function registerMediaTool(registry, workspace, opts = {}) {
|
|
|
7793
8184
|
};
|
|
7794
8185
|
if (outputAbs) {
|
|
7795
8186
|
try {
|
|
7796
|
-
result["size_bytes"] =
|
|
8187
|
+
result["size_bytes"] = fs14.statSync(outputAbs).size;
|
|
7797
8188
|
} catch {
|
|
7798
8189
|
}
|
|
7799
8190
|
}
|
|
@@ -8033,7 +8424,9 @@ function createDefaultEngine(opts) {
|
|
|
8033
8424
|
subagentHookFactory,
|
|
8034
8425
|
contextWindow,
|
|
8035
8426
|
thresholdPct,
|
|
8427
|
+
softThresholdPct,
|
|
8036
8428
|
capTokens,
|
|
8429
|
+
toolResultDir: toolResultDir2,
|
|
8037
8430
|
logDir
|
|
8038
8431
|
} = opts;
|
|
8039
8432
|
const envEffort = process.env.OPENAI_REASONING_EFFORT?.toLowerCase();
|
|
@@ -8056,7 +8449,8 @@ function createDefaultEngine(opts) {
|
|
|
8056
8449
|
workspace,
|
|
8057
8450
|
provider: effectiveProvider,
|
|
8058
8451
|
model,
|
|
8059
|
-
hookFactory: subagentHookFactory
|
|
8452
|
+
hookFactory: subagentHookFactory,
|
|
8453
|
+
toolResultDir: toolResultDir2
|
|
8060
8454
|
});
|
|
8061
8455
|
const permissions = new PermissionEngine({
|
|
8062
8456
|
workspaceRoot: workspace,
|
|
@@ -8078,7 +8472,9 @@ function createDefaultEngine(opts) {
|
|
|
8078
8472
|
hook,
|
|
8079
8473
|
contextWindow,
|
|
8080
8474
|
thresholdPct,
|
|
8081
|
-
|
|
8475
|
+
softThresholdPct,
|
|
8476
|
+
capTokens,
|
|
8477
|
+
toolResultDir: toolResultDir2
|
|
8082
8478
|
});
|
|
8083
8479
|
return engine;
|
|
8084
8480
|
}
|