jinzd-ai-cli 0.4.319 → 0.4.321
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/dist/{batch-CQ542WJF.js → batch-EW6QTSE6.js} +2 -2
- package/dist/{chunk-D6BLSFYD.js → chunk-2GOBINY4.js} +4 -4
- package/dist/{chunk-A7YZLLWN.js → chunk-2R6Y5SJH.js} +1 -1
- package/dist/{chunk-2PAQOR2X.js → chunk-4MO2I5JB.js} +9 -9
- package/dist/{chunk-YHMQMP2I.js → chunk-6RCGQQFM.js} +57 -20
- package/dist/{chunk-Y3SEC5EC.js → chunk-6XXHAS5B.js} +1 -1
- package/dist/{chunk-GP24X3KJ.js → chunk-7QW3DFYV.js} +1 -1
- package/dist/{chunk-KEOMNFBH.js → chunk-COHKPZ2E.js} +1 -1
- package/dist/{chunk-GVAAGBHX.js → chunk-E2W2MI6H.js} +74 -5
- package/dist/{chunk-PGQ5OV3P.js → chunk-I3GEFCKP.js} +1 -1
- package/dist/{chunk-AC44E4CV.js → chunk-JHSULP5D.js} +1 -1
- package/dist/{chunk-OQZIP4LU.js → chunk-LEA4AMXE.js} +1 -1
- package/dist/{chunk-46OEMKQX.js → chunk-NFPJ6KUU.js} +8 -4
- package/dist/{chunk-DCQDFQDW.js → chunk-VEO2MILP.js} +1 -1
- package/dist/{chunk-662ZJPJE.js → chunk-VS43QMR2.js} +1 -1
- package/dist/{chunk-N2MHUA4B.js → chunk-XVB4RARX.js} +75 -2
- package/dist/{chunk-SZBSTSG4.js → chunk-ZDO3XCXL.js} +4 -4
- package/dist/{ci-HAPWBC37.js → ci-EZZD7CMS.js} +6 -6
- package/dist/{ci-format-NGWP4GTQ.js → ci-format-ZWECV6XI.js} +2 -2
- package/dist/{constants-GSUK6ILC.js → constants-F2ULZ6W2.js} +1 -1
- package/dist/{constants-CPHZLOCQ.js → constants-HF5AYLBR.js} +1 -1
- package/dist/{doctor-cli-L6AK2GJ7.js → doctor-cli-QAKYBWLG.js} +6 -6
- package/dist/electron-server.js +218 -35
- package/dist/{hub-TCZFG2ZG.js → hub-OYH2UGER.js} +1 -1
- package/dist/index.js +56 -26
- package/dist/{persistent-memory-IPNMYXU4.js → persistent-memory-EVZX7SLR.js} +2 -2
- package/dist/{persistent-memory-E3UYP7RJ.js → persistent-memory-WNPZSVE4.js} +2 -2
- package/dist/{pr-J5TSU5P6.js → pr-XPQVXG2G.js} +6 -6
- package/dist/{run-tests-PDD4KUQL.js → run-tests-3SYXE2PJ.js} +2 -2
- package/dist/{run-tests-FLGSE63O.js → run-tests-MFXQ76ZJ.js} +2 -2
- package/dist/{server-3C6IK3JE.js → server-AF5PIBUD.js} +6 -6
- package/dist/{server-XQ3IQUZ5.js → server-VOGBRYA5.js} +16 -16
- package/dist/{task-orchestrator-544JETWF.js → task-orchestrator-QHJEDRDS.js} +9 -7
- package/dist/{usage-MQTA7HHZ.js → usage-EZOW27TR.js} +3 -3
- package/package.json +1 -1
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-RS7SKYTS.js";
|
|
5
5
|
import {
|
|
6
6
|
ConfigManager
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-COHKPZ2E.js";
|
|
8
8
|
import "./chunk-TU3L3PW7.js";
|
|
9
9
|
import {
|
|
10
10
|
atomicWriteFileSync
|
|
11
11
|
} from "./chunk-BMLQPSHP.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-2R6Y5SJH.js";
|
|
13
13
|
|
|
14
14
|
// src/cli/batch.ts
|
|
15
15
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
listHooks,
|
|
9
9
|
listInstalledPlugins,
|
|
10
10
|
resetStats
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-VS43QMR2.js";
|
|
12
12
|
import {
|
|
13
13
|
ProviderRegistry
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XVB4RARX.js";
|
|
15
15
|
import {
|
|
16
16
|
ConfigManager
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-COHKPZ2E.js";
|
|
18
18
|
import {
|
|
19
19
|
getGitRoot
|
|
20
20
|
} from "./chunk-HOSJZMQS.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
MCP_PROJECT_CONFIG_NAME,
|
|
27
27
|
MEMORY_FILE_NAME,
|
|
28
28
|
VERSION
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-2R6Y5SJH.js";
|
|
30
30
|
|
|
31
31
|
// src/diagnostics/doctor-report.ts
|
|
32
32
|
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
computeCost,
|
|
4
4
|
formatCost
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NFPJ6KUU.js";
|
|
6
6
|
import {
|
|
7
7
|
clearRecentlyDeniedAutoActions,
|
|
8
8
|
estimateTokens,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
setPreferredAgentName,
|
|
17
17
|
truncateForPersist,
|
|
18
18
|
undoStack
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-6RCGQQFM.js";
|
|
20
20
|
import {
|
|
21
21
|
buildReviewPrompt,
|
|
22
22
|
buildSecurityReviewPrompt
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
touchMemoryReferences,
|
|
52
52
|
updateMemoryApproval,
|
|
53
53
|
updateMemoryEntry
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-I3GEFCKP.js";
|
|
55
55
|
import {
|
|
56
56
|
theme
|
|
57
57
|
} from "./chunk-5C76XDDF.js";
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
import {
|
|
66
66
|
buildDoctorReport,
|
|
67
67
|
formatDoctorReport
|
|
68
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-2GOBINY4.js";
|
|
69
69
|
import {
|
|
70
70
|
describePlugin,
|
|
71
71
|
getActivePluginAssets,
|
|
@@ -80,11 +80,11 @@ import {
|
|
|
80
80
|
trustHook,
|
|
81
81
|
trustPlugin,
|
|
82
82
|
untrustHook
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-VS43QMR2.js";
|
|
84
84
|
import {
|
|
85
85
|
isCleanDocumentBody,
|
|
86
86
|
stripOuterCodeFence
|
|
87
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-E2W2MI6H.js";
|
|
88
88
|
import {
|
|
89
89
|
formatGitContextForPrompt,
|
|
90
90
|
getGitContext
|
|
@@ -109,7 +109,7 @@ import {
|
|
|
109
109
|
MCP_TOOL_PREFIX,
|
|
110
110
|
REPO_URL,
|
|
111
111
|
VERSION
|
|
112
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-2R6Y5SJH.js";
|
|
113
113
|
|
|
114
114
|
// src/skills/manager.ts
|
|
115
115
|
import { existsSync, readdirSync, mkdirSync, statSync } from "fs";
|
|
@@ -2426,7 +2426,7 @@ No tools match "${filter}".
|
|
|
2426
2426
|
const { join: join6 } = await import("path");
|
|
2427
2427
|
const { existsSync: existsSync6 } = await import("fs");
|
|
2428
2428
|
const { getGitRoot } = await import("./git-context-EXOEHQSF.js");
|
|
2429
|
-
const { MCP_PROJECT_CONFIG_NAME } = await import("./constants-
|
|
2429
|
+
const { MCP_PROJECT_CONFIG_NAME } = await import("./constants-F2ULZ6W2.js");
|
|
2430
2430
|
const { approveProject, hashMcpFile } = await import("./project-trust-KGG2BWKC.js");
|
|
2431
2431
|
const cwd = process.cwd();
|
|
2432
2432
|
const projectRoot = getGitRoot(cwd) ?? cwd;
|
|
@@ -3534,7 +3534,7 @@ function createReviewCommands() {
|
|
|
3534
3534
|
usage: "/test [command|filter]",
|
|
3535
3535
|
async execute(args, ctx) {
|
|
3536
3536
|
try {
|
|
3537
|
-
const { executeTests } = await import("./run-tests-
|
|
3537
|
+
const { executeTests } = await import("./run-tests-MFXQ76ZJ.js");
|
|
3538
3538
|
const argStr = args.join(" ").trim();
|
|
3539
3539
|
let testArgs = {};
|
|
3540
3540
|
if (argStr) {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
searchPersistentMemories,
|
|
12
12
|
touchMemoryReferences,
|
|
13
13
|
updateMemoryEntry
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-I3GEFCKP.js";
|
|
15
15
|
import {
|
|
16
16
|
indexProject
|
|
17
17
|
} from "./chunk-RWRZVQCB.js";
|
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
runHook,
|
|
31
31
|
runLifecycleHooks,
|
|
32
32
|
runTool
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-VS43QMR2.js";
|
|
34
34
|
import {
|
|
35
35
|
buildTruncatedArgumentsError,
|
|
36
36
|
getDangerLevel,
|
|
37
37
|
isFileWriteTool,
|
|
38
38
|
runLeanAgentLoop
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-E2W2MI6H.js";
|
|
40
40
|
import {
|
|
41
41
|
EnvLoader,
|
|
42
42
|
NetworkError,
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
recoverGbkMojibake,
|
|
65
65
|
runTestsTool,
|
|
66
66
|
shellInvocation
|
|
67
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-6XXHAS5B.js";
|
|
68
68
|
import {
|
|
69
69
|
CONFIG_DIR_NAME,
|
|
70
70
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
SUBAGENT_ALLOWED_TOOLS,
|
|
74
74
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
75
75
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-2R6Y5SJH.js";
|
|
77
77
|
import {
|
|
78
78
|
getCurrentSessionKey,
|
|
79
79
|
getExecutionConfigDir,
|
|
@@ -6872,21 +6872,37 @@ function estimateTokens(text) {
|
|
|
6872
6872
|
return Math.ceil(tokens) + 4;
|
|
6873
6873
|
}
|
|
6874
6874
|
function estimateToolDefinitionTokens(def) {
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
if (param.enum) {
|
|
6879
|
-
charCount += param.enum.join(" ").length;
|
|
6880
|
-
}
|
|
6881
|
-
}
|
|
6882
|
-
const structuralOverhead = 40 + Object.keys(def.parameters).length * 30;
|
|
6883
|
-
return Math.ceil((charCount + structuralOverhead) * 0.25) + 10;
|
|
6875
|
+
const json = JSON.stringify({ name: def.name, description: def.description, parameters: def.parameters });
|
|
6876
|
+
const cjk = json.match(CJK_REGEX)?.length ?? 0;
|
|
6877
|
+
return Math.ceil((json.length - cjk + 40) / 3.7 + cjk * 1.5);
|
|
6884
6878
|
}
|
|
6885
6879
|
|
|
6886
6880
|
// src/tools/registry.ts
|
|
6887
6881
|
import { pathToFileURL } from "url";
|
|
6888
6882
|
import { existsSync as existsSync13, mkdirSync as mkdirSync3, readdirSync as readdirSync7 } from "fs";
|
|
6889
6883
|
import { join as join6 } from "path";
|
|
6884
|
+
var TRIMMABLE_BUILTIN_ORDER = [
|
|
6885
|
+
"google_search",
|
|
6886
|
+
"recall_memory",
|
|
6887
|
+
"search_code",
|
|
6888
|
+
"notebook_edit",
|
|
6889
|
+
"spawn_agent",
|
|
6890
|
+
"run_interactive",
|
|
6891
|
+
"save_memory",
|
|
6892
|
+
"find_references",
|
|
6893
|
+
"get_outline",
|
|
6894
|
+
"find_symbol",
|
|
6895
|
+
"task_stop",
|
|
6896
|
+
"task_list",
|
|
6897
|
+
"task_create",
|
|
6898
|
+
"git_log",
|
|
6899
|
+
"git_diff",
|
|
6900
|
+
"git_commit",
|
|
6901
|
+
"git_status",
|
|
6902
|
+
"web_fetch",
|
|
6903
|
+
"web_search",
|
|
6904
|
+
"run_tests"
|
|
6905
|
+
];
|
|
6890
6906
|
var ToolRegistry = class {
|
|
6891
6907
|
tools = /* @__PURE__ */ new Map();
|
|
6892
6908
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -6954,7 +6970,7 @@ var ToolRegistry = class {
|
|
|
6954
6970
|
totalTokens += estimateToolDefinitionTokens(def);
|
|
6955
6971
|
}
|
|
6956
6972
|
if (totalTokens <= tokenBudget) {
|
|
6957
|
-
return { definitions: allDefs, trimmedCount: 0, systemNote: null };
|
|
6973
|
+
return { definitions: allDefs, trimmedCount: 0, trimmedBuiltins: [], systemNote: null };
|
|
6958
6974
|
}
|
|
6959
6975
|
const builtinDefs = [];
|
|
6960
6976
|
const mcpDefs = [];
|
|
@@ -6966,10 +6982,19 @@ var ToolRegistry = class {
|
|
|
6966
6982
|
}
|
|
6967
6983
|
}
|
|
6968
6984
|
let budget = tokenBudget;
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
const
|
|
6985
|
+
const trimmedBuiltins = [];
|
|
6986
|
+
let builtinCost = builtinDefs.reduce((n, d) => n + estimateToolDefinitionTokens(d), 0);
|
|
6987
|
+
const keptBuiltins = new Set(builtinDefs.map((d) => d.name));
|
|
6988
|
+
for (const name of TRIMMABLE_BUILTIN_ORDER) {
|
|
6989
|
+
if (builtinCost <= budget) break;
|
|
6990
|
+
const def = builtinDefs.find((d) => d.name === name);
|
|
6991
|
+
if (!def || this.pluginToolNames.has(name)) continue;
|
|
6992
|
+
keptBuiltins.delete(name);
|
|
6993
|
+
trimmedBuiltins.push(name);
|
|
6994
|
+
builtinCost -= estimateToolDefinitionTokens(def);
|
|
6995
|
+
}
|
|
6996
|
+
budget -= builtinCost;
|
|
6997
|
+
const kept = builtinDefs.filter((d) => keptBuiltins.has(d.name));
|
|
6973
6998
|
const used = usedToolNames ?? /* @__PURE__ */ new Set();
|
|
6974
6999
|
const remaining = [];
|
|
6975
7000
|
for (const def of mcpDefs) {
|
|
@@ -6991,6 +7016,12 @@ var ToolRegistry = class {
|
|
|
6991
7016
|
trimmed++;
|
|
6992
7017
|
}
|
|
6993
7018
|
}
|
|
7019
|
+
const notes = [];
|
|
7020
|
+
if (trimmedBuiltins.length > 0) {
|
|
7021
|
+
notes.push(
|
|
7022
|
+
`[Tool Budget] To fit this model's context window, these tools are not available in this session: ${trimmedBuiltins.join(", ")}. Use bash for the equivalent (git commands, grep for symbols, curl for web pages) instead of calling them.`
|
|
7023
|
+
);
|
|
7024
|
+
}
|
|
6994
7025
|
let systemNote = null;
|
|
6995
7026
|
if (trimmed > 0) {
|
|
6996
7027
|
const trimmedServers = /* @__PURE__ */ new Set();
|
|
@@ -7003,7 +7034,13 @@ var ToolRegistry = class {
|
|
|
7003
7034
|
}
|
|
7004
7035
|
systemNote = `[MCP Tool Budget] ${trimmed} MCP tool(s) from server(s) [${[...trimmedServers].join(", ")}] were excluded to fit the context window. If you need a specific MCP tool that isn't listed, tell the user to ask for it explicitly.`;
|
|
7005
7036
|
}
|
|
7006
|
-
|
|
7037
|
+
if (systemNote) notes.push(systemNote);
|
|
7038
|
+
return {
|
|
7039
|
+
definitions: kept,
|
|
7040
|
+
trimmedCount: trimmed,
|
|
7041
|
+
trimmedBuiltins,
|
|
7042
|
+
systemNote: notes.length > 0 ? notes.join("\n\n") : null
|
|
7043
|
+
};
|
|
7007
7044
|
}
|
|
7008
7045
|
listAll() {
|
|
7009
7046
|
return [...this.tools.values()];
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
MEMORY_FILE_NAME,
|
|
13
13
|
MEMORY_MAX_CHARS,
|
|
14
14
|
MEMORY_STORE_FILE_NAME
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VEO2MILP.js";
|
|
16
16
|
|
|
17
17
|
// src/memory/persistent-memory.ts
|
|
18
18
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "fs";
|
|
@@ -14,6 +14,14 @@ var HALLUCINATION_PATTERNS = [
|
|
|
14
14
|
// 已创建!
|
|
15
15
|
/File\s+(?:written|saved|created)\s+(?:to|as|at)/i,
|
|
16
16
|
// File written to / saved as(要求介词)
|
|
17
|
+
// 2026-10-07 审计 B-02:中英文判据不对称。中文「已保存到」要求引号、英文只认 `File saved to`,
|
|
18
|
+
// 于是「已保存到 report.md。」「I saved the changes to src/index.ts.」双双漏检。
|
|
19
|
+
// 改为「后面跟一个带扩展名的文件名」——同样挡得住「已保存到数据库」「saved my time」这类误报。
|
|
20
|
+
/已(?:保存|写入)到?\s*[^\s,,。;;::]{1,120}\.\w{1,6}(?![\w/])/,
|
|
21
|
+
// 动词后直接跟文件名(中间只许冠词 / both / the file 之类),或「动词 … to/into 文件名」。
|
|
22
|
+
// 刻意不放宽成「同句任意文件名」:「I saved you some time by checking README.md」不是写入声明。
|
|
23
|
+
/\b(?:I|We)(?:'ve| have)?\s+(?:now\s+|just\s+|also\s+|successfully\s+)?(?:created|written|wrote|saved|generated)\s+(?:(?:the|a|an|both|all|two|three|new|following)\s+)*(?:(?:files?|documents?|reports?|scripts?)\s+)?[`'"]?[\w./-]+\.(?:md|txt|json|ya?ml|csv|html?|css|[jt]sx?|py|sh|go|rs|java|sql|xml|toml|ini|cfg|pdf|docx?|xlsx?|pptx?)\b/i,
|
|
24
|
+
/\b(?:I|We)(?:'ve| have)?\s+(?:now\s+|just\s+|also\s+|successfully\s+)?(?:created|written|wrote|saved|generated)\b[^.\n]{0,40}?\b(?:to|into)\s+[`'"]?[\w./-]+\.(?:md|txt|json|ya?ml|csv|html?|css|[jt]sx?|py|sh|go|rs|java|sql|xml|toml|ini|cfg|pdf|docx?|xlsx?|pptx?)\b/i,
|
|
17
25
|
/生成完成[!!]/,
|
|
18
26
|
// 生成完成!
|
|
19
27
|
/✅\s*(?:文件|已[生保写创]|第)\S*\.\w{1,5}/,
|
|
@@ -64,6 +72,8 @@ var QUICK_WRITE_KEYWORDS = [
|
|
|
64
72
|
"saved",
|
|
65
73
|
"written",
|
|
66
74
|
"created",
|
|
75
|
+
"wrote",
|
|
76
|
+
"generated",
|
|
67
77
|
"File",
|
|
68
78
|
"file",
|
|
69
79
|
// emoji 勾选标记
|
|
@@ -186,8 +196,13 @@ function extractClaimedFilePaths(content) {
|
|
|
186
196
|
}
|
|
187
197
|
const zhRe = /(?:已保存(?:到)?|已写入(?:到)?|已创建|已生成|文件路径[::]|保存为|写入到)\s*[`'”””]?([^\s`'”””,,。\n]+\.\w{1,6})/g;
|
|
188
198
|
while ((m = zhRe.exec(content)) !== null) add(m[1]);
|
|
189
|
-
const enRe =
|
|
199
|
+
const enRe = /\b(?:saved|written|wrote|created)\b[^.\n]{0,40}?\b(?:to|as|at|into)\s+[`'”]?([^\s`'”\n,]+\.\w{1,6})/gi;
|
|
190
200
|
while ((m = enRe.exec(content)) !== null) add(m[1]);
|
|
201
|
+
const enClaimRe = /\b(?:I|We)(?:'ve| have)?\s+(?:now\s+|just\s+|also\s+|successfully\s+)?(?:created|written|wrote|saved|generated)\b([^\n;]*)/gi;
|
|
202
|
+
while ((m = enClaimRe.exec(content)) !== null) {
|
|
203
|
+
const clause = m[1].split(/[.!?](?:\s|$)|\b(?:based on|from|using|per|according to|referencing|following|after|by|while|when|checking|reading|reviewing)\b/i)[0] ?? "";
|
|
204
|
+
for (const f of clause.matchAll(/[`'"]?([\w./-]+\.\w{1,6})\b/g)) add(f[1]);
|
|
205
|
+
}
|
|
191
206
|
const checkRe = /✅[^\n`]*?[`'”]?([^\s`'”\n,,。]+?\.\w{1,6})/g;
|
|
192
207
|
while ((m = checkRe.exec(content)) !== null) {
|
|
193
208
|
const pathStart = m.index + m[0].lastIndexOf(m[1]);
|
|
@@ -462,8 +477,27 @@ var META_NARRATION_HEURISTICS = [
|
|
|
462
477
|
/(?:我来|让我|容我)\s*(?:整理|梳理|归纳|想想|看看|重新|检查|核实)/u,
|
|
463
478
|
// 过渡语(需要 2+ hits):"以下是:" / "如下:" 作为独立行。文档中可作小节标题,
|
|
464
479
|
// 但配合另一个对话模式(如"好的")时是典型的 AI 内容包装。
|
|
465
|
-
/^.{0,60}(?:以下是|下面是|如下)\s*[::,,]\s*$/mu
|
|
480
|
+
/^.{0,60}(?:以下是|下面是|如下)\s*[::,,]\s*$/mu,
|
|
481
|
+
// 叙述自己要调用工具(2026-10-07 审计 B-05):文档正文不会这样写自己。
|
|
482
|
+
/(?:让我|我来|我将|我会|接下来)\s*(?:调用|使用|用)\s*`?\w+`?\s*工具/u,
|
|
483
|
+
/\bI(?:'ll| will| am going to)\s+(?:now\s+)?(?:use|call|invoke)\s+the\s+`?\w+`?\s+tool\b/i
|
|
484
|
+
];
|
|
485
|
+
var ANNOUNCEMENT_PATTERNS = [
|
|
486
|
+
/^\s*(?:好的|好|收到|明白|了解|嗯|OK|Okay|Sure|Certainly)[,,。.!!\s]/iu,
|
|
487
|
+
/(?:我(?:这就|马上|现在|立即|立刻|来|将|会)|让我|下面我|接下来我)\s*(?:为[您你])?\s*(?:开始|着手|撰写|写|生成|整理|输出|编写|准备|调用|使用)/u,
|
|
488
|
+
/(?:请稍[候等]|稍等|马上就好|这就开始)/u,
|
|
489
|
+
/\b(?:let me|I(?:'ll| will| am going to)|I'm going to)\s+(?:now\s+)?(?:write|draft|generate|create|start|prepare|produce|compose|use|call)\b/i,
|
|
490
|
+
/\b(?:please wait|one moment|stand by)\b/i
|
|
466
491
|
];
|
|
492
|
+
function looksLikeAnnouncementOnly(content) {
|
|
493
|
+
const text = content.trim();
|
|
494
|
+
if (!text || text.length > 300 || text.split("\n").length > 4) return null;
|
|
495
|
+
if (/^#{1,6}\s+\S/m.test(text)) return null;
|
|
496
|
+
for (const re of ANNOUNCEMENT_PATTERNS) {
|
|
497
|
+
if (re.test(text)) return re.source;
|
|
498
|
+
}
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
467
501
|
var CHINESE_TAIL_CLOSING_MARKERS = [
|
|
468
502
|
/(?:需要你|请你|麻烦你|你看)\s*(?:确认|决定|选择|帮忙|看[一看下]|过目|觉得|认为)/u,
|
|
469
503
|
/(?:随时|有问题|有需要|需要调整|需要修改)\s*(?:联系|找我|告诉|沟通|调整|修改)/u,
|
|
@@ -600,6 +634,15 @@ This fresh stream has NO tools \u2014 output is captured verbatim. STOP emitting
|
|
|
600
634
|
summary: `[save_last_response REJECTED] The content-only stream produced NO text at all (output budget likely spent on reasoning). ${saveToFile} was NOT saved.`
|
|
601
635
|
};
|
|
602
636
|
}
|
|
637
|
+
const announced = looksLikeAnnouncementOnly(rawContent);
|
|
638
|
+
if (announced) {
|
|
639
|
+
return fallback(`announcement only: ${announced}`) ?? {
|
|
640
|
+
kind: "reject",
|
|
641
|
+
reason: "not-a-document",
|
|
642
|
+
matched: announced,
|
|
643
|
+
summary: `[save_last_response REJECTED] The content-only stream produced only an announcement (e.g. "OK, I'll write the report now\u2026"), not the document itself. ${saveToFile} was NOT saved.`
|
|
644
|
+
};
|
|
645
|
+
}
|
|
603
646
|
const { lines, bytes } = teeFileStats(rawContent);
|
|
604
647
|
return {
|
|
605
648
|
kind: "ok",
|
|
@@ -621,6 +664,7 @@ function teeStreamErrorSummary(saveToFile, errMsg) {
|
|
|
621
664
|
// src/core/stalled-intent.ts
|
|
622
665
|
var WAITING_FOR_USER_RE = /[??]\s*$|是否|要不要|需不需要|需要我|要我|可以吗|好吗|行吗|您看|你看看|请确认|确认后|同意后|如果(?:你|您)|若(?:你|您)|如(?:你|您)需要|等你|等待(?:你|您)|待(?:你|您)|我可以|我也可以|还可以|(?:你|您)可以|告诉我|或者(?:让我|我)|可以为(?:你|您)|可以帮|随时|\bwould you\b|\bshall i\b|\bshould i\b|\bdo you want\b|\blet me know\b|\bif you(?:'d| would)? like\b|\bonce you confirm\b|\byour (?:approval|confirmation)\b|\bif you want\b|\bi can also\b/i;
|
|
623
666
|
var COMPLETED_RE = /已|完成|好了|完毕|写好|搞定|\b(?:done|finished|completed|have (?:written|created|generated))\b/i;
|
|
667
|
+
var NEGATION_RE = /没有?|未|无法|不(?:会|能|再|打算|需要|用|必|要)|n't\b|\b(?:not|never|no longer|cannot)\b/i;
|
|
624
668
|
var CN_VERBS = "\u5F00\u5199|\u52A8\u624B|\u7F16\u5199|\u5199\u5165|\u5199|\u751F\u6210|\u521B\u5EFA|\u5236\u4F5C|\u6267\u884C|\u8FD0\u884C|\u5B89\u88C5|\u4FEE\u6539|\u66F4\u65B0|\u4FEE\u590D|\u5B9E\u73B0|\u6784\u5EFA|\u8F93\u51FA|\u4FDD\u5B58|\u8BFB\u53D6|\u67E5\u770B|\u68C0\u67E5|\u8C03\u7528|\u5904\u7406|\u6574\u7406|\u8865\u5145|\u5B8C\u5584|\u91CD\u5199|\u6539\u5199|\u843D\u76D8";
|
|
625
669
|
var CN_FIRST_PERSON_INTENT_RE = new RegExp(
|
|
626
670
|
`(?:\u6211|\u54B1\u4EEC|\u6211\u4EEC|\u8BA9\u6211)(?:\u73B0\u5728|\u8FD9\u5C31|\u9A6C\u4E0A|\u7ACB\u5373|\u7ACB\u523B|\u76F4\u63A5|\u63A5\u4E0B\u6765|\u4E0B\u9762|\u5148|\u518D|\u968F\u540E|\u7136\u540E|\u5C31)*(?:\u5C06|\u4F1A|\u8981|\u6765|\u53BB|\u5F00\u59CB|\u7740\u624B|\u51C6\u5907)?(?:[^\uFF0C,\u3002\uFF01!\uFF1B;\uFF1A:\\n]{0,4})?(?:${CN_VERBS})`
|
|
@@ -642,11 +686,11 @@ function detectStalledIntent(content) {
|
|
|
642
686
|
if (WAITING_FOR_USER_RE.test(tail)) return null;
|
|
643
687
|
const clauses = tail.split(/[,,;;。!!::\n]/).map((c) => c.trim()).filter(Boolean);
|
|
644
688
|
for (const clause of clauses) {
|
|
645
|
-
if (COMPLETED_RE.test(clause)) continue;
|
|
689
|
+
if (COMPLETED_RE.test(clause) || NEGATION_RE.test(clause)) continue;
|
|
646
690
|
if (CN_FIRST_PERSON_INTENT_RE.test(clause) || CN_BARE_INTENT_RE.test(clause)) return clause;
|
|
647
691
|
}
|
|
648
692
|
for (const sentence of tail.split(/(?<=[.!])\s+|\n/)) {
|
|
649
|
-
if (COMPLETED_RE.test(sentence)) continue;
|
|
693
|
+
if (COMPLETED_RE.test(sentence) || NEGATION_RE.test(sentence)) continue;
|
|
650
694
|
const m = EN_INTENT_RE.exec(sentence);
|
|
651
695
|
if (m) return sentence.trim();
|
|
652
696
|
}
|
|
@@ -1094,7 +1138,12 @@ async function runAgentLoop(host) {
|
|
|
1094
1138
|
if (tc.name.startsWith("mcp__")) host.onMcpToolUsed?.(tc.name);
|
|
1095
1139
|
}
|
|
1096
1140
|
host.persistRound?.(toolCalls, toolResults, {
|
|
1097
|
-
|
|
1141
|
+
// 工具调用前模型说的话("A 方向汇报:…"、"先看一下配置")挂在 _streamedText 上:
|
|
1142
|
+
// 本轮上下文里有(buildToolResultMessages 会带上),但此前存档只取 outcome.content,
|
|
1143
|
+
// 而工具调用轮次的 outcome 没有 content——2026-10-07 审计会话 157 条带工具调用的
|
|
1144
|
+
// assistant 消息正文**全部为空**:下一条用户消息时模型看不到自己之前的阶段性汇报,
|
|
1145
|
+
// /session show、导出、回放也全缺这部分(审计 X-01)。
|
|
1146
|
+
assistantContent: outcome.content ?? toolCalls._streamedText,
|
|
1098
1147
|
reasoningContent: outcome.reasoningContent
|
|
1099
1148
|
});
|
|
1100
1149
|
host.afterToolRoundPersist?.(toolCalls, toolResults, extraMessages);
|
|
@@ -1149,6 +1198,7 @@ async function runLeanAgentLoop(opts) {
|
|
|
1149
1198
|
let roundsUsed = 0;
|
|
1150
1199
|
let toolCallCount = 0;
|
|
1151
1200
|
let stalledIntentNudges = 0;
|
|
1201
|
+
let hallucinationRetries = 0;
|
|
1152
1202
|
try {
|
|
1153
1203
|
for (let round = 0; round < opts.maxRounds; round++) {
|
|
1154
1204
|
roundsUsed = round + 1;
|
|
@@ -1164,6 +1214,7 @@ async function runLeanAgentLoop(opts) {
|
|
|
1164
1214
|
timeout: opts.chatParams?.timeout,
|
|
1165
1215
|
thinking: opts.chatParams?.thinking,
|
|
1166
1216
|
thinkingEffort: opts.chatParams?.thinkingEffort,
|
|
1217
|
+
thinkingBudget: opts.chatParams?.thinkingBudget,
|
|
1167
1218
|
...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
|
|
1168
1219
|
},
|
|
1169
1220
|
opts.toolDefsForRound(round)
|
|
@@ -1183,6 +1234,24 @@ async function runLeanAgentLoop(opts) {
|
|
|
1183
1234
|
);
|
|
1184
1235
|
continue;
|
|
1185
1236
|
}
|
|
1237
|
+
const nextTools = round < opts.maxRounds - 1 ? opts.toolDefsForRound(round + 1) : [];
|
|
1238
|
+
const canWrite = nextTools.some((t) => t.name === "write_file" || t.name === "edit_file");
|
|
1239
|
+
if (canWrite && hallucinationRetries < 1 && result.content) {
|
|
1240
|
+
const alreadyWrote = hadPreviousWriteToolCalls(extraMessages);
|
|
1241
|
+
const coarse = !alreadyWrote && detectsHallucinatedFileOp(result.content) && extractBashCommands(extraMessages).length === 0;
|
|
1242
|
+
const phantom = coarse || alreadyWrote ? findPhantomClaims(result.content, extraMessages) : [];
|
|
1243
|
+
if (phantom.length > 0 || coarse) {
|
|
1244
|
+
hallucinationRetries++;
|
|
1245
|
+
extraMessages.push(
|
|
1246
|
+
{ role: "assistant", content: result.content },
|
|
1247
|
+
{
|
|
1248
|
+
role: "user",
|
|
1249
|
+
content: phantom.length > 0 ? buildPhantomCorrectionMessage(phantom) : HALLUCINATION_CORRECTION_MESSAGE
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1186
1255
|
content = result.content;
|
|
1187
1256
|
break;
|
|
1188
1257
|
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
MEMORY_FILE_NAME,
|
|
14
14
|
MEMORY_MAX_CHARS,
|
|
15
15
|
MEMORY_STORE_FILE_NAME
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2R6Y5SJH.js";
|
|
17
17
|
|
|
18
18
|
// src/memory/persistent-memory.ts
|
|
19
19
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "fs";
|
|
@@ -108,17 +108,21 @@ var PRICING_TABLE = {
|
|
|
108
108
|
"glm-4.6": { input: 0.6, output: 2.2 },
|
|
109
109
|
"glm-4.6v": { input: 0.6, output: 2.2 },
|
|
110
110
|
"glm-5": { input: 0.85, output: 2.85 },
|
|
111
|
-
// GLM-5.3
|
|
111
|
+
// GLM-5.3:官方定价页(open.bigmodel.cn/pricing,2026-10-08 浏览器读取)——输入 8 元、
|
|
112
|
+
// 输出 28 元、缓存命中 2 元(每百万 token),按 ¥6.9/$1 换算。此前记的 $1.40/$4.40 是
|
|
113
|
+
// Z.ai 国际站的美元价,而本 provider 打的是国内站、按人民币计费,高报约 20%;更大的
|
|
114
|
+
// 偏差在**缓存**:没有 cacheRead 时缓存命中按输入全价算,2026-10-07 审计会话 1474 万
|
|
115
|
+
// 缓存 token 被记成 $21.34,按官方缓存价约 $4.90(2026-10-07 审计 X-03)。
|
|
112
116
|
// 注意它**始终思考**,思考 token 按输出价计费 —— 实际成本高度取决于 reasoning_effort。
|
|
113
|
-
"glm-5.3": { input:
|
|
117
|
+
"glm-5.3": { input: 8 / CNY_PER_USD, output: 28 / CNY_PER_USD, cacheRead: 2 / CNY_PER_USD },
|
|
114
118
|
// GLM-5.3-Flash:官方定价页(open.bigmodel.cn/pricing)一手数字——输入 0.8 元、
|
|
115
119
|
// 输出 2.8 元、缓存命中 0.23 元(每百万 token),按记账汇率 ¥6.9/$1 换算。
|
|
116
120
|
// **刻意记恢复价而非当前的 5 折限时价**(0.4/1.4/0.115 元):官方只说「限时两周」、
|
|
117
121
|
// 没公布截止日期,静态表表达不了这个时间维度;高估比低估安全——预算告警早触发
|
|
118
122
|
// 好过晚触发。折扣期内 `/cost` 会比实际偏高约一倍,这是刻意的取舍。
|
|
119
123
|
"glm-5.3-flash": { input: 0.116, output: 0.406, cacheRead: 0.033 },
|
|
120
|
-
// GLM-5.2
|
|
121
|
-
"glm-5.2": { input:
|
|
124
|
+
// GLM-5.2:与 GLM-5.3 同价(同一定价页:8 / 28 / 缓存命中 2 元),口径修正同上。
|
|
125
|
+
"glm-5.2": { input: 8 / CNY_PER_USD, output: 28 / CNY_PER_USD, cacheRead: 2 / CNY_PER_USD },
|
|
122
126
|
// GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
|
|
123
127
|
"glm-5v-turbo": { input: 1.2, output: 4 },
|
|
124
128
|
"glm-5.1": { input: 0.95, output: 3.15 },
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
detectsHallucinatedFileOp,
|
|
4
4
|
parseToolCallArguments,
|
|
5
5
|
schemaToJsonSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-E2W2MI6H.js";
|
|
7
7
|
import {
|
|
8
8
|
AuthError,
|
|
9
9
|
ProviderError,
|
|
@@ -2120,6 +2120,7 @@ function ollamaNativeBaseUrl(openAiBaseUrl) {
|
|
|
2120
2120
|
return openAiBaseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
2121
2121
|
}
|
|
2122
2122
|
var NATIVE_API_TIMEOUT_MS = 3e3;
|
|
2123
|
+
var SMALL_CONTEXT_WARN = 16384;
|
|
2123
2124
|
var OllamaProvider = class extends OpenAICompatibleProvider {
|
|
2124
2125
|
// 用 127.0.0.1 而非 localhost:Node.js 18+ 在 Windows 上优先解析 localhost 为 IPv6 (::1),
|
|
2125
2126
|
// 但 Ollama 默认只监听 IPv4,导致连接失败。
|
|
@@ -2198,12 +2199,13 @@ var OllamaProvider = class extends OpenAICompatibleProvider {
|
|
|
2198
2199
|
if (show?.capabilities?.includes("thinking")) thinkingModels.add(id);
|
|
2199
2200
|
if (running.size === 0 && !show) return;
|
|
2200
2201
|
const digest = digests.get(id);
|
|
2201
|
-
const { contextWindow } = resolveOllamaContextWindow({
|
|
2202
|
+
const { contextWindow, source } = resolveOllamaContextWindow({
|
|
2202
2203
|
runningContext: digest && running.get(digest) || running.get(id),
|
|
2203
2204
|
show,
|
|
2204
2205
|
fallback: this.estimateContextWindow(id)
|
|
2205
2206
|
});
|
|
2206
2207
|
result.set(id, contextWindow);
|
|
2208
|
+
this.contextSources.set(id, source);
|
|
2207
2209
|
}));
|
|
2208
2210
|
return { contextWindows: result, thinkingModels };
|
|
2209
2211
|
}
|
|
@@ -2299,6 +2301,77 @@ var OllamaProvider = class extends OpenAICompatibleProvider {
|
|
|
2299
2301
|
const target = norm(model);
|
|
2300
2302
|
return this.dynamicModels.some((m) => m.supportsThinking === true && norm(m.id) === target);
|
|
2301
2303
|
}
|
|
2304
|
+
// ── 加载后补读真实窗口(2026-10-07 审计 C-01)────────────────────────────
|
|
2305
|
+
//
|
|
2306
|
+
// 上下文窗口只在启动时读一次。模型当时若还没加载,`/api/ps` 里没有它,只能取训练上限
|
|
2307
|
+
// (Qwen3.8 是 262K);而服务端实际开的窗口可能只有它的几十分之一。第一次请求把模型
|
|
2308
|
+
// 加载进来之后若不再读,上下文压力监控、自动压缩与工具预算就整场按那个大数在算——
|
|
2309
|
+
// aicli 以为还有余量,Ollama 早已在悄悄截掉最早的消息(v0.4.311 那次说的正是这个后果)。
|
|
2310
|
+
// 所以每个模型在首次请求之后补读一次 `/api/ps`;读到的窗口若小得连工具定义都放不下,
|
|
2311
|
+
// 提示一次怎么调大。都是后台做,不拖慢这次请求。
|
|
2312
|
+
/** 每个模型的窗口是从哪一级读到的(只有 'running' 是服务端实际值) */
|
|
2313
|
+
contextSources = /* @__PURE__ */ new Map();
|
|
2314
|
+
refreshedAfterLoad = /* @__PURE__ */ new Set();
|
|
2315
|
+
warnedSmallContext = /* @__PURE__ */ new Set();
|
|
2316
|
+
async chat(request) {
|
|
2317
|
+
try {
|
|
2318
|
+
return await super.chat(request);
|
|
2319
|
+
} finally {
|
|
2320
|
+
this.scheduleContextRefresh(request.model);
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
async *chatStream(request) {
|
|
2324
|
+
try {
|
|
2325
|
+
yield* super.chatStream(request);
|
|
2326
|
+
} finally {
|
|
2327
|
+
this.scheduleContextRefresh(request.model);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
async chatWithTools(request, tools) {
|
|
2331
|
+
try {
|
|
2332
|
+
return await super.chatWithTools(request, tools);
|
|
2333
|
+
} finally {
|
|
2334
|
+
this.scheduleContextRefresh(request.model);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
async *chatWithToolsStream(request, tools) {
|
|
2338
|
+
try {
|
|
2339
|
+
yield* super.chatWithToolsStream(request, tools);
|
|
2340
|
+
} finally {
|
|
2341
|
+
this.scheduleContextRefresh(request.model);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
scheduleContextRefresh(model) {
|
|
2345
|
+
const key = model.replace(/:latest$/, "");
|
|
2346
|
+
if (this.refreshedAfterLoad.has(key)) return;
|
|
2347
|
+
this.refreshedAfterLoad.add(key);
|
|
2348
|
+
void this.refreshLoadedContext(model).catch(() => {
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
/** 补读已加载模型的实际窗口,更新同一份权重(digest 相同)的所有别名。测试可直接 await。 */
|
|
2352
|
+
async refreshLoadedContext(model) {
|
|
2353
|
+
const norm = (id2) => id2.replace(/:latest$/, "");
|
|
2354
|
+
const [running, digests] = await Promise.all([this.fetchRunningContexts(), this.fetchDigests()]);
|
|
2355
|
+
const self = this.dynamicModels.find((m) => norm(m.id) === norm(model));
|
|
2356
|
+
const id = self?.id ?? model;
|
|
2357
|
+
const digest = digests.get(id);
|
|
2358
|
+
const actual = digest && running.get(digest) || running.get(id);
|
|
2359
|
+
if (!actual) return;
|
|
2360
|
+
for (const m of this.dynamicModels) {
|
|
2361
|
+
if (m.id === id || digest && digests.get(m.id) === digest) {
|
|
2362
|
+
m.contextWindow = actual;
|
|
2363
|
+
this.contextSources.set(m.id, "running");
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
if (actual < SMALL_CONTEXT_WARN && !this.warnedSmallContext.has(norm(id))) {
|
|
2367
|
+
this.warnedSmallContext.add(norm(id));
|
|
2368
|
+
process.stderr.write(
|
|
2369
|
+
`
|
|
2370
|
+
[ollama] ${id} is loaded with a ${actual.toLocaleString()}-token context window. aicli's tool definitions alone take about 8K tokens, so earlier messages will be silently cut off. Raise it: set OLLAMA_CONTEXT_LENGTH=32768 (or more) for the Ollama server, or add "PARAMETER num_ctx 32768" to the model's Modelfile.
|
|
2371
|
+
`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2302
2375
|
/** 实时刷新模型列表 */
|
|
2303
2376
|
async listModels() {
|
|
2304
2377
|
await this.tryConnect();
|