@cicctencent/agent-core 1.0.34 → 1.0.37
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/cjs/a2a/rest-runner.d.ts.map +1 -1
- package/dist/cjs/a2a/rest-runner.js +24 -5
- package/dist/cjs/a2a/rest-runner.js.map +1 -1
- package/dist/cjs/a2a/runner.d.ts.map +1 -1
- package/dist/cjs/a2a/runner.js +33 -8
- package/dist/cjs/a2a/runner.js.map +1 -1
- package/dist/cjs/agent/compaction.d.ts.map +1 -1
- package/dist/cjs/agent/compaction.js +1 -4
- package/dist/cjs/agent/compaction.js.map +1 -1
- package/dist/cjs/agent/constants.d.ts +14 -0
- package/dist/cjs/agent/constants.d.ts.map +1 -0
- package/dist/cjs/agent/constants.js +30 -0
- package/dist/cjs/agent/constants.js.map +1 -0
- package/dist/cjs/agent/engine.d.ts +57 -2
- package/dist/cjs/agent/engine.d.ts.map +1 -1
- package/dist/cjs/agent/engine.js +139 -379
- package/dist/cjs/agent/engine.js.map +1 -1
- package/dist/cjs/agent/guard-controller.d.ts +64 -0
- package/dist/cjs/agent/guard-controller.d.ts.map +1 -0
- package/dist/cjs/agent/guard-controller.js +265 -0
- package/dist/cjs/agent/guard-controller.js.map +1 -0
- package/dist/cjs/agent/llm-caller.d.ts +22 -5
- package/dist/cjs/agent/llm-caller.d.ts.map +1 -1
- package/dist/cjs/agent/llm-caller.js +119 -30
- package/dist/cjs/agent/llm-caller.js.map +1 -1
- package/dist/cjs/agent/rate-limit-manager.d.ts +8 -0
- package/dist/cjs/agent/rate-limit-manager.d.ts.map +1 -1
- package/dist/cjs/agent/rate-limit-manager.js +36 -0
- package/dist/cjs/agent/rate-limit-manager.js.map +1 -1
- package/dist/cjs/agent/runtime-builder.d.ts +44 -0
- package/dist/cjs/agent/runtime-builder.d.ts.map +1 -1
- package/dist/cjs/agent/runtime-builder.js +78 -20
- package/dist/cjs/agent/runtime-builder.js.map +1 -1
- package/dist/cjs/agent/session-state-manager.d.ts +47 -0
- package/dist/cjs/agent/session-state-manager.d.ts.map +1 -0
- package/dist/cjs/agent/session-state-manager.js +85 -0
- package/dist/cjs/agent/session-state-manager.js.map +1 -0
- package/dist/cjs/agent/tool-executor.d.ts +26 -3
- package/dist/cjs/agent/tool-executor.d.ts.map +1 -1
- package/dist/cjs/agent/tool-executor.js +101 -20
- package/dist/cjs/agent/tool-executor.js.map +1 -1
- package/dist/cjs/agent/tool-section-builder.d.ts +55 -0
- package/dist/cjs/agent/tool-section-builder.d.ts.map +1 -0
- package/dist/cjs/agent/tool-section-builder.js +191 -0
- package/dist/cjs/agent/tool-section-builder.js.map +1 -0
- package/dist/cjs/agent/types.d.ts +6 -2
- package/dist/cjs/agent/types.d.ts.map +1 -1
- package/dist/cjs/agent/types.js.map +1 -1
- package/dist/cjs/index.d.ts +7 -3
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +20 -11
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/llm/anthropic-provider.d.ts +5 -0
- package/dist/cjs/llm/anthropic-provider.d.ts.map +1 -1
- package/dist/cjs/llm/anthropic-provider.js +25 -1
- package/dist/cjs/llm/anthropic-provider.js.map +1 -1
- package/dist/cjs/llm/ollama-provider.d.ts +8 -1
- package/dist/cjs/llm/ollama-provider.d.ts.map +1 -1
- package/dist/cjs/llm/ollama-provider.js +19 -0
- package/dist/cjs/llm/ollama-provider.js.map +1 -1
- package/dist/cjs/llm/openai-provider.d.ts.map +1 -1
- package/dist/cjs/llm/openai-provider.js +24 -0
- package/dist/cjs/llm/openai-provider.js.map +1 -1
- package/dist/cjs/llm/types.d.ts +40 -0
- package/dist/cjs/llm/types.d.ts.map +1 -1
- package/dist/cjs/llm/types.js +83 -0
- package/dist/cjs/llm/types.js.map +1 -1
- package/dist/cjs/mcp/base-client.js +2 -2
- package/dist/cjs/mcp/base-client.js.map +1 -1
- package/dist/cjs/mcp/client.js +4 -4
- package/dist/cjs/mcp/client.js.map +1 -1
- package/dist/cjs/mcp/factory.d.ts +6 -1
- package/dist/cjs/mcp/factory.d.ts.map +1 -1
- package/dist/cjs/mcp/factory.js +7 -2
- package/dist/cjs/mcp/factory.js.map +1 -1
- package/dist/cjs/mcp/http-client.js +5 -5
- package/dist/cjs/mcp/http-client.js.map +1 -1
- package/dist/cjs/observability/logger.d.ts.map +1 -1
- package/dist/cjs/observability/logger.js +56 -2
- package/dist/cjs/observability/logger.js.map +1 -1
- package/dist/cjs/pool/engine-pool.d.ts +4 -4
- package/dist/cjs/pool/engine-pool.d.ts.map +1 -1
- package/dist/cjs/pool/engine-pool.js +21 -50
- package/dist/cjs/pool/engine-pool.js.map +1 -1
- package/dist/cjs/runtime/run-registry-interface.d.ts +6 -0
- package/dist/cjs/runtime/run-registry-interface.d.ts.map +1 -1
- package/dist/cjs/runtime/run-registry.d.ts +8 -0
- package/dist/cjs/runtime/run-registry.d.ts.map +1 -1
- package/dist/cjs/runtime/run-registry.js +25 -0
- package/dist/cjs/runtime/run-registry.js.map +1 -1
- package/dist/cjs/security/guard.d.ts +12 -1
- package/dist/cjs/security/guard.d.ts.map +1 -1
- package/dist/cjs/security/guard.js +42 -10
- package/dist/cjs/security/guard.js.map +1 -1
- package/dist/cjs/security/types.d.ts +11 -0
- package/dist/cjs/security/types.d.ts.map +1 -1
- package/dist/cjs/tool/ask-user.js +3 -3
- package/dist/cjs/tool/delegate.d.ts.map +1 -1
- package/dist/cjs/tool/delegate.js +4 -2
- package/dist/cjs/tool/delegate.js.map +1 -1
- package/dist/cjs/types.d.ts +15 -1
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/dsml-parser.d.ts +1 -1
- package/dist/cjs/utils/dsml-parser.js +1 -1
- package/dist/cjs/utils/event-adapter.d.ts.map +1 -1
- package/dist/cjs/utils/event-adapter.js +6 -0
- package/dist/cjs/utils/event-adapter.js.map +1 -1
- package/dist/cjs/utils/ttl-lru-cache.d.ts +49 -0
- package/dist/cjs/utils/ttl-lru-cache.d.ts.map +1 -0
- package/dist/cjs/utils/ttl-lru-cache.js +108 -0
- package/dist/cjs/utils/ttl-lru-cache.js.map +1 -0
- package/dist/esm/a2a/rest-runner.d.ts.map +1 -1
- package/dist/esm/a2a/rest-runner.js +24 -5
- package/dist/esm/a2a/rest-runner.js.map +1 -1
- package/dist/esm/a2a/runner.d.ts.map +1 -1
- package/dist/esm/a2a/runner.js +33 -8
- package/dist/esm/a2a/runner.js.map +1 -1
- package/dist/esm/agent/compaction.d.ts.map +1 -1
- package/dist/esm/agent/compaction.js +1 -4
- package/dist/esm/agent/compaction.js.map +1 -1
- package/dist/esm/agent/constants.d.ts +14 -0
- package/dist/esm/agent/constants.d.ts.map +1 -0
- package/dist/esm/agent/constants.js +27 -0
- package/dist/esm/agent/constants.js.map +1 -0
- package/dist/esm/agent/engine.d.ts +57 -2
- package/dist/esm/agent/engine.d.ts.map +1 -1
- package/dist/esm/agent/engine.js +140 -380
- package/dist/esm/agent/engine.js.map +1 -1
- package/dist/esm/agent/guard-controller.d.ts +64 -0
- package/dist/esm/agent/guard-controller.d.ts.map +1 -0
- package/dist/esm/agent/guard-controller.js +256 -0
- package/dist/esm/agent/guard-controller.js.map +1 -0
- package/dist/esm/agent/llm-caller.d.ts +22 -5
- package/dist/esm/agent/llm-caller.d.ts.map +1 -1
- package/dist/esm/agent/llm-caller.js +119 -30
- package/dist/esm/agent/llm-caller.js.map +1 -1
- package/dist/esm/agent/rate-limit-manager.d.ts +8 -0
- package/dist/esm/agent/rate-limit-manager.d.ts.map +1 -1
- package/dist/esm/agent/rate-limit-manager.js +36 -0
- package/dist/esm/agent/rate-limit-manager.js.map +1 -1
- package/dist/esm/agent/runtime-builder.d.ts +44 -0
- package/dist/esm/agent/runtime-builder.d.ts.map +1 -1
- package/dist/esm/agent/runtime-builder.js +75 -20
- package/dist/esm/agent/runtime-builder.js.map +1 -1
- package/dist/esm/agent/session-state-manager.d.ts +47 -0
- package/dist/esm/agent/session-state-manager.d.ts.map +1 -0
- package/dist/esm/agent/session-state-manager.js +78 -0
- package/dist/esm/agent/session-state-manager.js.map +1 -0
- package/dist/esm/agent/tool-executor.d.ts +26 -3
- package/dist/esm/agent/tool-executor.d.ts.map +1 -1
- package/dist/esm/agent/tool-executor.js +101 -20
- package/dist/esm/agent/tool-executor.js.map +1 -1
- package/dist/esm/agent/tool-section-builder.d.ts +55 -0
- package/dist/esm/agent/tool-section-builder.d.ts.map +1 -0
- package/dist/esm/agent/tool-section-builder.js +182 -0
- package/dist/esm/agent/tool-section-builder.js.map +1 -0
- package/dist/esm/agent/types.d.ts +6 -2
- package/dist/esm/agent/types.d.ts.map +1 -1
- package/dist/esm/agent/types.js.map +1 -1
- package/dist/esm/index.d.ts +7 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/llm/anthropic-provider.d.ts +5 -0
- package/dist/esm/llm/anthropic-provider.d.ts.map +1 -1
- package/dist/esm/llm/anthropic-provider.js +25 -1
- package/dist/esm/llm/anthropic-provider.js.map +1 -1
- package/dist/esm/llm/ollama-provider.d.ts +8 -1
- package/dist/esm/llm/ollama-provider.d.ts.map +1 -1
- package/dist/esm/llm/ollama-provider.js +19 -0
- package/dist/esm/llm/ollama-provider.js.map +1 -1
- package/dist/esm/llm/openai-provider.d.ts.map +1 -1
- package/dist/esm/llm/openai-provider.js +24 -0
- package/dist/esm/llm/openai-provider.js.map +1 -1
- package/dist/esm/llm/types.d.ts +40 -0
- package/dist/esm/llm/types.d.ts.map +1 -1
- package/dist/esm/llm/types.js +80 -0
- package/dist/esm/llm/types.js.map +1 -1
- package/dist/esm/mcp/base-client.js +2 -2
- package/dist/esm/mcp/base-client.js.map +1 -1
- package/dist/esm/mcp/client.js +4 -4
- package/dist/esm/mcp/client.js.map +1 -1
- package/dist/esm/mcp/factory.d.ts +6 -1
- package/dist/esm/mcp/factory.d.ts.map +1 -1
- package/dist/esm/mcp/factory.js +7 -2
- package/dist/esm/mcp/factory.js.map +1 -1
- package/dist/esm/mcp/http-client.js +5 -5
- package/dist/esm/mcp/http-client.js.map +1 -1
- package/dist/esm/observability/logger.d.ts.map +1 -1
- package/dist/esm/observability/logger.js +56 -2
- package/dist/esm/observability/logger.js.map +1 -1
- package/dist/esm/pool/engine-pool.d.ts +4 -4
- package/dist/esm/pool/engine-pool.d.ts.map +1 -1
- package/dist/esm/pool/engine-pool.js +21 -50
- package/dist/esm/pool/engine-pool.js.map +1 -1
- package/dist/esm/runtime/run-registry-interface.d.ts +6 -0
- package/dist/esm/runtime/run-registry-interface.d.ts.map +1 -1
- package/dist/esm/runtime/run-registry.d.ts +8 -0
- package/dist/esm/runtime/run-registry.d.ts.map +1 -1
- package/dist/esm/runtime/run-registry.js +25 -0
- package/dist/esm/runtime/run-registry.js.map +1 -1
- package/dist/esm/security/guard.d.ts +12 -1
- package/dist/esm/security/guard.d.ts.map +1 -1
- package/dist/esm/security/guard.js +42 -10
- package/dist/esm/security/guard.js.map +1 -1
- package/dist/esm/security/types.d.ts +11 -0
- package/dist/esm/security/types.d.ts.map +1 -1
- package/dist/esm/tool/ask-user.js +3 -3
- package/dist/esm/tool/delegate.d.ts.map +1 -1
- package/dist/esm/tool/delegate.js +4 -2
- package/dist/esm/tool/delegate.js.map +1 -1
- package/dist/esm/types.d.ts +15 -1
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/utils/dsml-parser.d.ts +1 -1
- package/dist/esm/utils/dsml-parser.js +1 -1
- package/dist/esm/utils/event-adapter.d.ts.map +1 -1
- package/dist/esm/utils/event-adapter.js +6 -0
- package/dist/esm/utils/event-adapter.js.map +1 -1
- package/dist/esm/utils/ttl-lru-cache.d.ts +49 -0
- package/dist/esm/utils/ttl-lru-cache.d.ts.map +1 -0
- package/dist/esm/utils/ttl-lru-cache.js +104 -0
- package/dist/esm/utils/ttl-lru-cache.js.map +1 -0
- package/package.json +1 -1
package/dist/esm/agent/engine.js
CHANGED
|
@@ -9,31 +9,12 @@ import { LLMCaller } from "./llm-caller.js";
|
|
|
9
9
|
import { AgentToolExecutor } from "./tool-executor.js";
|
|
10
10
|
import { logEvent } from "../observability/logger.js";
|
|
11
11
|
import { rateLimitManager } from "./rate-limit-manager.js";
|
|
12
|
-
import { summarizeToolCall
|
|
12
|
+
import { summarizeToolCall } from "./compaction.js";
|
|
13
|
+
import { resolveToolDefs as resolveToolDefsFn, summarizeToolDefImpl, buildToolContext as buildToolContextFn, buildDynamicToolSection as buildDynamicToolSectionFn, renderToolRefs as renderToolRefsFn, refreshToolSection as refreshToolSectionFn, createNoopShell, } from "./tool-section-builder.js";
|
|
14
|
+
import { injectProgressNudge as injectProgressNudgeFn, injectIterationGuard as injectIterationGuardFn, disableDataFetchTools as disableDataFetchToolsFn, checkDataFetchLoop as checkDataFetchLoopFn, injectDataFetchGuard as injectDataFetchGuardFn, injectContinuationNudge as injectContinuationNudgeFn, injectProgressiveNudge as injectProgressiveNudgeFn, } from "./guard-controller.js";
|
|
15
|
+
import { steer as steerFn, drainSteering as drainSteeringFn, abortSession as abortSessionFn, abortCascade as abortCascadeFn, cleanupSessionRelations as cleanupSessionRelationsFn, } from "./session-state-manager.js";
|
|
13
16
|
/** 默认系统提示词 */
|
|
14
17
|
const DEFAULT_BASE_PROMPT = `You are a helpful AI assistant with access to tools. Use tools when needed to accomplish tasks. Always explain your actions clearly.`;
|
|
15
|
-
/**
|
|
16
|
-
* 核心工具集(summary-all 模式下保持全量 schema 的工具)。
|
|
17
|
-
*
|
|
18
|
-
* 这些工具是 Agent 执行绝大多数任务的基础(文件读写、代码搜索、命令执行、
|
|
19
|
-
* 工具发现、委托派发),必须让 LLM 看到完整参数 schema 以保证调用可靠性。
|
|
20
|
-
* 非核心工具(通讯、日历、自动化、文档生成等)仅以摘要形式呈现,
|
|
21
|
-
* LLM 需通过 read_tool_doc 获取完整 schema 后再调用。
|
|
22
|
-
*/
|
|
23
|
-
const CORE_TOOL_NAMES = new Set([
|
|
24
|
-
'read_file',
|
|
25
|
-
'write_file',
|
|
26
|
-
'edit_file',
|
|
27
|
-
'append_file',
|
|
28
|
-
'search_code',
|
|
29
|
-
'execute_command',
|
|
30
|
-
'run_script',
|
|
31
|
-
'read_terminal',
|
|
32
|
-
'read_tool_doc',
|
|
33
|
-
'read_tool_result',
|
|
34
|
-
'read_offload',
|
|
35
|
-
'delegate_task',
|
|
36
|
-
]);
|
|
37
18
|
/** 进度叙述规则(追加到 system prompt,引导 LLM 在工具调用间输出可见文本)— 可通过 AgentConfig.progressNarrationRules 覆盖 */
|
|
38
19
|
const DEFAULT_PROGRESS_NARRATION_RULES = `
|
|
39
20
|
|
|
@@ -99,10 +80,14 @@ export class AgentEngine {
|
|
|
99
80
|
abortMap = new Map();
|
|
100
81
|
/** sessionId → steering messages(实时转向队列) */
|
|
101
82
|
steeringMap = new Map();
|
|
83
|
+
/** sessionId -> pause state (user pause: agent loop suspends at turn boundary until resume) */
|
|
84
|
+
pauseMap = new Map();
|
|
102
85
|
/** parentSessionId → Set<childSessionId>(父子会话关系跟踪,用于级联 abort) */
|
|
103
86
|
childSessionMap = new Map();
|
|
104
87
|
/** childSessionId → parentSessionId(反向索引,用于快速查找父会话) */
|
|
105
88
|
parentSessionMap = new Map();
|
|
89
|
+
/** 被摘要化的工具名集合(progressive loading:这些工具的 prompt 不注入 system prompt) */
|
|
90
|
+
summarizedToolNames = new Set();
|
|
106
91
|
/** 子模块:历史管理器 */
|
|
107
92
|
historyManager;
|
|
108
93
|
/** 子模块:LLM 调用器 */
|
|
@@ -123,6 +108,33 @@ export class AgentEngine {
|
|
|
123
108
|
guardConfig;
|
|
124
109
|
/** 错误恢复策略配置 */
|
|
125
110
|
errorRecovery;
|
|
111
|
+
/** 为 ToolSectionBuilder 提供依赖 */
|
|
112
|
+
get toolSectionDeps() {
|
|
113
|
+
return { config: this.config, summarizedToolNames: this.summarizedToolNames };
|
|
114
|
+
}
|
|
115
|
+
/** 为 GuardController 提供依赖 */
|
|
116
|
+
get guardControllerDeps() {
|
|
117
|
+
return {
|
|
118
|
+
historyManager: this.historyManager,
|
|
119
|
+
dataFetchTools: this.dataFetchTools,
|
|
120
|
+
outputTools: this.outputTools,
|
|
121
|
+
guardConfig: this.guardConfig,
|
|
122
|
+
maxIterations: this.config.maxIterations,
|
|
123
|
+
hasTool: (name) => this.hasTool(name),
|
|
124
|
+
runHooks: (event, ctx) => this.runHooks(event, ctx),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** 为 SessionStateManager 提供依赖 */
|
|
128
|
+
get sessionStateDeps() {
|
|
129
|
+
return {
|
|
130
|
+
abortMap: this.abortMap,
|
|
131
|
+
steeringMap: this.steeringMap,
|
|
132
|
+
statusMap: this.statusMap,
|
|
133
|
+
childSessionMap: this.childSessionMap,
|
|
134
|
+
parentSessionMap: this.parentSessionMap,
|
|
135
|
+
historyManager: this.historyManager,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
126
138
|
constructor(config) {
|
|
127
139
|
this.config = {
|
|
128
140
|
...config,
|
|
@@ -160,6 +172,9 @@ export class AgentEngine {
|
|
|
160
172
|
createEvent: this.createEvent.bind(this),
|
|
161
173
|
historyManager: this.historyManager,
|
|
162
174
|
getMiddleware: () => this.config.middleware,
|
|
175
|
+
// 流式工具早分发:存在 before_tool hook / onToolBefore middleware 时禁用(防 block/modify 语义被绕过)
|
|
176
|
+
hasBeforeToolInterceptors: () => (this.config.hooks ?? []).some((h) => h.event === 'before_tool') ||
|
|
177
|
+
(this.config.middleware ?? []).some((m) => m.onToolBefore !== undefined),
|
|
163
178
|
});
|
|
164
179
|
// 初始化工具分类集合(可通过 config 覆盖默认值)
|
|
165
180
|
this.dataFetchTools = new Set(config.dataFetchTools ?? [
|
|
@@ -173,8 +188,7 @@ export class AgentEngine {
|
|
|
173
188
|
// 模型在收尾阶段无法再用它读取 data/ 下的结果文件。
|
|
174
189
|
// 若某 agent 仍需将其纳入数据获取循环检测,可在 AgentConfig.dataFetchTools 中显式加回。
|
|
175
190
|
this.outputTools = new Set(config.outputTools ?? [
|
|
176
|
-
'delegate_task', '
|
|
177
|
-
'create_pdf', 'create_report', 'show_widget', 'save_artifact',
|
|
191
|
+
'delegate_task', 'create_document', 'show_widget', 'save_artifact',
|
|
178
192
|
]);
|
|
179
193
|
// 外部数据源获取工具(用于数据出处水印判定,仅含真正访问外部/实时数据源的工具,
|
|
180
194
|
// 不含 read_file 等本地读取——本地读取不构成「实时数据核验」但也不代表数据源失败)
|
|
@@ -368,6 +382,8 @@ export class AgentEngine {
|
|
|
368
382
|
abortSignal.removeEventListener('abort', onExternalAbort);
|
|
369
383
|
this.abortMap.delete(sessionId);
|
|
370
384
|
this.steeringMap.delete(sessionId);
|
|
385
|
+
// 清理暂停状态(run 结束后不再保留标志)
|
|
386
|
+
this.pauseMap.delete(sessionId);
|
|
371
387
|
// 注销主会话,清理限流状态和子会话映射
|
|
372
388
|
rateLimitManager.unregisterMainSession(sessionId);
|
|
373
389
|
}
|
|
@@ -691,67 +707,19 @@ ${textHistory.join('\n')}`;
|
|
|
691
707
|
// ---- 工具定义 ----
|
|
692
708
|
/** 获取工具定义列表(含预过滤 + 渐进式发现) */
|
|
693
709
|
resolveToolDefs(options) {
|
|
694
|
-
|
|
695
|
-
let defs = toolNames.length > 0
|
|
696
|
-
? this.config.toolRegistry.getDefinitions({ names: toolNames })
|
|
697
|
-
: this.config.toolRegistry.getDefinitions();
|
|
698
|
-
if (this.config.deniedTools?.length) {
|
|
699
|
-
const denied = new Set(this.config.deniedTools);
|
|
700
|
-
defs = defs.filter(d => !denied.has(d.function.name));
|
|
701
|
-
}
|
|
702
|
-
const discovery = this.config.toolDiscovery;
|
|
703
|
-
// summary 模式:仅 MCP 工具摘要
|
|
704
|
-
if (discovery === 'summary') {
|
|
705
|
-
defs = defs.map(d => this.summarizeToolDef(d, 'mcp'));
|
|
706
|
-
}
|
|
707
|
-
// summary-all 模式:所有非核心工具摘要(MCP + 非核心内置工具)
|
|
708
|
-
if (discovery === 'summary-all') {
|
|
709
|
-
const coreOverride = this.config.coreToolNames
|
|
710
|
-
? new Set(this.config.coreToolNames)
|
|
711
|
-
: CORE_TOOL_NAMES;
|
|
712
|
-
defs = defs.map(d => {
|
|
713
|
-
const isCore = coreOverride.has(d.function.name);
|
|
714
|
-
return this.summarizeToolDef(d, isCore ? null : 'all');
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
return defs;
|
|
710
|
+
return resolveToolDefsFn(this.toolSectionDeps, options);
|
|
718
711
|
}
|
|
719
712
|
/**
|
|
720
713
|
* 将工具定义转为摘要形式(截断 description + 清空 parameters)。
|
|
721
|
-
* 实现抽离至 ./compaction.ts,保持单一来源。
|
|
714
|
+
* 实现抽离至 ./tool-section-builder.ts -> compaction.ts,保持单一来源。
|
|
722
715
|
*/
|
|
723
716
|
summarizeToolDef(def, scope) {
|
|
724
|
-
return
|
|
725
|
-
scope,
|
|
726
|
-
maxDesc: this.config.toolSummaryMaxDesc ?? 100,
|
|
727
|
-
getCategory: (name) => this.config.toolRegistry.get(name)?.category,
|
|
728
|
-
});
|
|
717
|
+
return summarizeToolDefImpl(this.toolSectionDeps, def, scope);
|
|
729
718
|
}
|
|
730
719
|
// ---- 准备阶段(续) ----
|
|
731
720
|
/** 构建 ToolContext */
|
|
732
721
|
buildToolContext(options, internalAbort) {
|
|
733
|
-
|
|
734
|
-
return {
|
|
735
|
-
sessionId: options.sessionId,
|
|
736
|
-
shell: custom?.shell || this.createNoopShell(),
|
|
737
|
-
fs: custom?.fs,
|
|
738
|
-
env: options.context?.env,
|
|
739
|
-
abortSignal: internalAbort.signal,
|
|
740
|
-
custom: {
|
|
741
|
-
...custom,
|
|
742
|
-
// Fix A:始终使用引擎授权注册表(已按 selectedTools/disabledTools 过滤),
|
|
743
|
-
// 不接受 custom.toolRegistry 覆盖——防止 read_tool_doc/diagnose 泄漏被禁工具 schema。
|
|
744
|
-
toolRegistry: this.config.toolRegistry,
|
|
745
|
-
skillManager: custom?.skillManager ?? this.config.skillManager,
|
|
746
|
-
},
|
|
747
|
-
emitSubEvent: custom?.emitSubEvent,
|
|
748
|
-
emitWidget: custom?.emitWidget,
|
|
749
|
-
emitArtifact: custom?.emitArtifact,
|
|
750
|
-
emitToolLog: custom?.emitToolLog,
|
|
751
|
-
emitToolHeartbeat: custom?.emitToolHeartbeat,
|
|
752
|
-
toolTimeout: this.config.toolTimeout,
|
|
753
|
-
maxScriptTimeoutMs: this.config.maxScriptTimeoutMs,
|
|
754
|
-
};
|
|
722
|
+
return buildToolContextFn(this.toolSectionDeps, options, internalAbort);
|
|
755
723
|
}
|
|
756
724
|
/** 汇总准备阶段,生成 LoopState */
|
|
757
725
|
async prepareRun(options, internalAbort) {
|
|
@@ -785,33 +753,14 @@ ${textHistory.join('\n')}`;
|
|
|
785
753
|
hadFatalError: false,
|
|
786
754
|
fatalErrorMsg: undefined,
|
|
787
755
|
stableSystemPrompt,
|
|
756
|
+
// 请求级覆盖 Profile 级(AgentConfig.responseFormat)
|
|
757
|
+
responseFormat: options.responseFormat ?? this.config.responseFormat,
|
|
788
758
|
};
|
|
789
759
|
}
|
|
790
760
|
/** 动态构建工具区 prompt(Available Tools + Tool Usage Guidelines)。
|
|
791
761
|
* 从 state.toolDefs 现场拼装,确保与可执行工具集一致,消除静态穿透。 */
|
|
792
762
|
buildDynamicToolSection(toolDefs) {
|
|
793
|
-
|
|
794
|
-
return this.config.buildToolSection(toolDefs, this.config.toolRegistry);
|
|
795
|
-
}
|
|
796
|
-
if (toolDefs.length === 0)
|
|
797
|
-
return '';
|
|
798
|
-
// 可用工具名集合:用于占位符 {{TOOL:name}} 渲染时判定引用是否真实可用
|
|
799
|
-
const available = new Set(toolDefs.map(d => d.function.name));
|
|
800
|
-
const lines = toolDefs.map(d => `- \`${d.function.name}\`: ${this.renderToolRefs(d.function.description || '', available)}`);
|
|
801
|
-
let section = `\n\n## Available Tools\n${lines.join('\n')}`;
|
|
802
|
-
// 收集工具自带的使用引导(tool.prompt),并对其中对其他工具的引用做可用裁剪。
|
|
803
|
-
// tool.relatedTools 是声明式的「本工具引用的其它工具」清单,
|
|
804
|
-
// 用于:若某占位符 {{TOOL:x}} 未注册,则整段引用被剔除(见 renderToolRefs)。
|
|
805
|
-
const prompts = [];
|
|
806
|
-
for (const def of toolDefs) {
|
|
807
|
-
const tool = this.config.toolRegistry.get(def.function.name);
|
|
808
|
-
if (tool?.prompt)
|
|
809
|
-
prompts.push(this.renderToolRefs(tool.prompt, available, tool.relatedTools));
|
|
810
|
-
}
|
|
811
|
-
if (prompts.length > 0) {
|
|
812
|
-
section += `\n\n## Tool Usage Guidelines\n\n${prompts.join('\n\n')}`;
|
|
813
|
-
}
|
|
814
|
-
return section;
|
|
763
|
+
return buildDynamicToolSectionFn(this.toolSectionDeps, toolDefs);
|
|
815
764
|
}
|
|
816
765
|
/**
|
|
817
766
|
* 渲染工具提示中对其它工具的引用,避免「提示穿透」:
|
|
@@ -826,51 +775,29 @@ ${textHistory.join('\n')}`;
|
|
|
826
775
|
* 显式声明在 relatedTools 中的工具名也可被识别为「已知引用」参与裁剪。
|
|
827
776
|
*/
|
|
828
777
|
renderToolRefs(text, available, relatedTools) {
|
|
829
|
-
|
|
830
|
-
// - name 确实注册(available)时才渲染为 `name`;
|
|
831
|
-
// - 否则用哨兵标记,后续做子句级清除,避免留下 "do NOT call after" 这类残缺。
|
|
832
|
-
// 不在 relatedTools 且未注册的占位符视为误写,保留原文。
|
|
833
|
-
const knownRefs = relatedTools && relatedTools.length > 0 ? new Set(relatedTools) : null;
|
|
834
|
-
const SENTINEL = '\x00'; // 哨兵:标记被剔除的占位符位置
|
|
835
|
-
// 1) 替换:可用 -> name;不可用且为已知引用 -> 哨兵;其余保留原文
|
|
836
|
-
let out = text.replace(/\{\{TOOL:([a-zA-Z0-9_-]+)\}\}/g, (m, name) => {
|
|
837
|
-
if (available.has(name))
|
|
838
|
-
return `\`${name}\``;
|
|
839
|
-
if (knownRefs && !knownRefs.has(name))
|
|
840
|
-
return m;
|
|
841
|
-
return SENTINEL;
|
|
842
|
-
});
|
|
843
|
-
// 2) 子句级清除:以 " - " 或 ", " 分隔的子句中含哨兵,则移除整个子句
|
|
844
|
-
out = out
|
|
845
|
-
.split(/(?<=\S)\s+-\s+|(?<=\S),\s+/)
|
|
846
|
-
.filter(clause => !clause.includes(SENTINEL))
|
|
847
|
-
.join(', ');
|
|
848
|
-
// 3) 行级清除:多行 prompt 中,整行仅剩哨兵/标点/空白的予以移除
|
|
849
|
-
out = out
|
|
850
|
-
.split('\n')
|
|
851
|
-
.filter(line => {
|
|
852
|
-
const trimmed = line.trim();
|
|
853
|
-
if (trimmed === '')
|
|
854
|
-
return true;
|
|
855
|
-
const meaningful = trimmed.replace(/[`.,;:()\-]+/g, '').replace(/\s+/g, '');
|
|
856
|
-
return meaningful.length > 0 && !trimmed.includes(SENTINEL);
|
|
857
|
-
})
|
|
858
|
-
.join('\n');
|
|
859
|
-
// 4) 清理残留哨兵 + 收缩多余空格
|
|
860
|
-
out = out.replace(/\x00/g, '').replace(/ {2,}/g, ' ').replace(/\s+\n/g, '\n').trim();
|
|
861
|
-
return out;
|
|
778
|
+
return renderToolRefsFn(text, available, relatedTools);
|
|
862
779
|
}
|
|
863
780
|
/** 刷新 system prompt 中的工具区,确保与 state.toolDefs 一致 */
|
|
864
781
|
refreshToolSection(state) {
|
|
865
|
-
|
|
866
|
-
state.messages[0].content = state.stableSystemPrompt + this.buildDynamicToolSection(state.toolDefs);
|
|
867
|
-
}
|
|
782
|
+
refreshToolSectionFn(this.toolSectionDeps, state);
|
|
868
783
|
}
|
|
869
784
|
// ---- Agent Loop ----
|
|
870
785
|
/** 主循环:迭代调用 LLM → 执行工具 → 直到完成或达到上限 */
|
|
871
786
|
async *agentLoop(state) {
|
|
872
787
|
while (state.iteration < this.config.maxIterations) {
|
|
873
788
|
state.iteration++;
|
|
789
|
+
// Pause checkpoint: when user pauses, the current tool/LLM call has finished;
|
|
790
|
+
// the agent loop exits gracefully before the next LLM call (stream ends normally).
|
|
791
|
+
// Resume is implemented by starting a NEW SSE stream that rebuilds context from
|
|
792
|
+
// persisted message history - no in-memory suspension (survives process restart).
|
|
793
|
+
if (this.pauseMap.get(state.sessionId)?.paused === true) {
|
|
794
|
+
// 清理暂停标志(恢复由新建流完成,不再唤醒本循环)
|
|
795
|
+
this.pauseMap.delete(state.sessionId);
|
|
796
|
+
yield this.createEvent(state.sessionId, { type: 'paused' });
|
|
797
|
+
// 正常结束 SSE 流(run 状态 ok,与 abort 的 error 收尾区分)
|
|
798
|
+
yield this.emitDone(state);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
874
801
|
// 中断检查
|
|
875
802
|
if (state.internalAbort.signal.aborted) {
|
|
876
803
|
// 外部中断(客户端断开 / 用户主动 abort / 看门狗超时):标记致命错误,
|
|
@@ -967,8 +894,38 @@ ${textHistory.join('\n')}`;
|
|
|
967
894
|
});
|
|
968
895
|
// 调用 LLM(流式,含重试与降级)
|
|
969
896
|
this.statusMap.set(state.sessionId, 'thinking');
|
|
897
|
+
// 流式工具早分发:本轮 LLM 调用的预执行缓存(重试/降级时由 onToolCallReset 重置)
|
|
898
|
+
state.earlyToolResults = new Map();
|
|
899
|
+
state.earlyExecutions = [];
|
|
970
900
|
const llmStart = Date.now();
|
|
971
|
-
|
|
901
|
+
// 透传 LLMCaller 事件流,同时捕获 error 事件的原始报文(服务商返回的 429/5xx 等),
|
|
902
|
+
// 用于 llm_exhausted 分支向用户透传服务商原始异常信息。
|
|
903
|
+
// 采用 next() 循环:转发所有 yield 事件,并在 done 时拿到 callWithRetry 的 return 值(assistant/usage/observability)。
|
|
904
|
+
let lastLlmErrorMsg;
|
|
905
|
+
const llmGen = this.llmCaller.callWithRetry(state.sessionId, state.messages, state.toolDefs, state.internalAbort.signal, {
|
|
906
|
+
// 工具调用参数聚合完成即预执行(纯优化:执行结果缓存后由 executeToolCalls 命中复用)
|
|
907
|
+
onToolCallReady: (tc) => this.toolExecutor.startEarlyExecution(state, tc),
|
|
908
|
+
onToolCallReset: () => {
|
|
909
|
+
state.earlyToolResults = new Map();
|
|
910
|
+
state.earlyExecutions = [];
|
|
911
|
+
},
|
|
912
|
+
}, state.responseFormat);
|
|
913
|
+
let llmResult = { assistant: null, usage: undefined, observability: undefined };
|
|
914
|
+
while (true) {
|
|
915
|
+
const { value: ev, done } = await llmGen.next();
|
|
916
|
+
if (done) {
|
|
917
|
+
if (ev)
|
|
918
|
+
llmResult = ev;
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
if (ev && ev.type === 'error' && ev.error) {
|
|
922
|
+
lastLlmErrorMsg = ev.error;
|
|
923
|
+
}
|
|
924
|
+
yield ev;
|
|
925
|
+
}
|
|
926
|
+
const { assistant, usage, observability } = llmResult;
|
|
927
|
+
// 等待流中启动的预执行收尾(abort 场景同样先收尾再继续,保证资源不泄漏)
|
|
928
|
+
await this.toolExecutor.awaitEarlyExecutions(state);
|
|
972
929
|
const llmDuration = Date.now() - llmStart;
|
|
973
930
|
// 累加 Token 用量
|
|
974
931
|
if (usage) {
|
|
@@ -978,6 +935,9 @@ ${textHistory.join('\n')}`;
|
|
|
978
935
|
state.totalUsage.promptTokens += usage.promptTokens || 0;
|
|
979
936
|
state.totalUsage.completionTokens += usage.completionTokens || 0;
|
|
980
937
|
state.totalUsage.totalTokens += usage.totalTokens || 0;
|
|
938
|
+
state.totalUsage.cacheReadTokens = (state.totalUsage.cacheReadTokens || 0) + (usage.cacheReadTokens || 0);
|
|
939
|
+
state.totalUsage.cacheWriteTokens = (state.totalUsage.cacheWriteTokens || 0) + (usage.cacheWriteTokens || 0);
|
|
940
|
+
state.totalUsage.reasoningTokens = (state.totalUsage.reasoningTokens || 0) + (usage.reasoningTokens || 0);
|
|
981
941
|
}
|
|
982
942
|
if (!assistant) {
|
|
983
943
|
// 所有 LLM provider 均已失败(callWithRetry 内部已重试+降级)。
|
|
@@ -986,7 +946,9 @@ ${textHistory.join('\n')}`;
|
|
|
986
946
|
// 前端 onDone 回调不触发(无法更新 UI 状态)。
|
|
987
947
|
// error 和 done 是不同的 SSE 事件类型,前端分别用 onError 和 onDone 处理,
|
|
988
948
|
// done 事件携带 lastContent(之前成功迭代的输出)或兜底总结,不会覆盖 error 消息。
|
|
989
|
-
|
|
949
|
+
// 优先透传服务商原始异常报文(LLMCaller 经 error 事件发出的 429/5xx 原文,含 code 与重置时间等),
|
|
950
|
+
// 缺失时回退到固定概括文案,向后兼容。
|
|
951
|
+
const errorMsg = lastLlmErrorMsg || 'All LLM providers exhausted (all calls failed including retries and fallback)';
|
|
990
952
|
logEvent({ event: 'engine.llm_exhausted', sessionId: state.sessionId, level: 'warn', data: { iteration: state.iteration } });
|
|
991
953
|
this.statusMap.set(state.sessionId, 'idle');
|
|
992
954
|
// 致命错误 → 自动 abort + 级联清理(级联清理子 Agent 运行中的子任务)
|
|
@@ -1184,6 +1146,9 @@ ${textHistory.join('\n')}`;
|
|
|
1184
1146
|
state.totalUsage.promptTokens += finalUsage.promptTokens || 0;
|
|
1185
1147
|
state.totalUsage.completionTokens += finalUsage.completionTokens || 0;
|
|
1186
1148
|
state.totalUsage.totalTokens += finalUsage.totalTokens || 0;
|
|
1149
|
+
state.totalUsage.cacheReadTokens = (state.totalUsage.cacheReadTokens || 0) + (finalUsage.cacheReadTokens || 0);
|
|
1150
|
+
state.totalUsage.cacheWriteTokens = (state.totalUsage.cacheWriteTokens || 0) + (finalUsage.cacheWriteTokens || 0);
|
|
1151
|
+
state.totalUsage.reasoningTokens = (state.totalUsage.reasoningTokens || 0) + (finalUsage.reasoningTokens || 0);
|
|
1187
1152
|
}
|
|
1188
1153
|
if (finalAssistant) {
|
|
1189
1154
|
state.messages.push(finalAssistant);
|
|
@@ -1275,27 +1240,11 @@ ${textHistory.join('\n')}`;
|
|
|
1275
1240
|
* 消息将在下次 LLM 调用前注入对话上下文
|
|
1276
1241
|
*/
|
|
1277
1242
|
steer(sessionId, message) {
|
|
1278
|
-
|
|
1279
|
-
return false;
|
|
1280
|
-
if (!this.steeringMap.has(sessionId)) {
|
|
1281
|
-
this.steeringMap.set(sessionId, []);
|
|
1282
|
-
}
|
|
1283
|
-
this.steeringMap.get(sessionId).push(message);
|
|
1284
|
-
return true;
|
|
1243
|
+
return steerFn(this.sessionStateDeps, sessionId, message);
|
|
1285
1244
|
}
|
|
1286
1245
|
/** 消费当前会话的 steering 消息,注入到 messages 中 */
|
|
1287
1246
|
drainSteering(state) {
|
|
1288
|
-
|
|
1289
|
-
if (!msgs || msgs.length === 0)
|
|
1290
|
-
return null;
|
|
1291
|
-
const drained = [...msgs];
|
|
1292
|
-
msgs.length = 0;
|
|
1293
|
-
for (const msg of drained) {
|
|
1294
|
-
const userMsg = { role: 'user', content: msg };
|
|
1295
|
-
state.messages.push(userMsg);
|
|
1296
|
-
this.historyManager.addHistory(state.sessionId, userMsg);
|
|
1297
|
-
}
|
|
1298
|
-
return { messages: drained, count: drained.length };
|
|
1247
|
+
return drainSteeringFn(this.sessionStateDeps, state);
|
|
1299
1248
|
}
|
|
1300
1249
|
/**
|
|
1301
1250
|
* 进度叙述提醒
|
|
@@ -1303,23 +1252,7 @@ ${textHistory.join('\n')}`;
|
|
|
1303
1252
|
* 确保用户在对话中有可见的文字输出,而不是所有内容都收在执行步骤中。
|
|
1304
1253
|
*/
|
|
1305
1254
|
injectProgressNudge(state) {
|
|
1306
|
-
|
|
1307
|
-
role: 'user',
|
|
1308
|
-
content: [
|
|
1309
|
-
'[SYSTEM: PROGRESS NARRATION]',
|
|
1310
|
-
'',
|
|
1311
|
-
'You have been calling tools without providing visible text to the user.',
|
|
1312
|
-
'Please provide a brief progress update in the user\'s language:',
|
|
1313
|
-
'- What have you accomplished so far?',
|
|
1314
|
-
'- What are you working on next?',
|
|
1315
|
-
'- If you found important data, summarize the key findings.',
|
|
1316
|
-
'',
|
|
1317
|
-
'Keep it to 1-3 sentences. This text is displayed directly to the user.',
|
|
1318
|
-
].join('\n'),
|
|
1319
|
-
};
|
|
1320
|
-
state.messages.push(msg);
|
|
1321
|
-
this.historyManager.addHistory(state.sessionId, msg);
|
|
1322
|
-
logEvent({ event: 'engine.progress_nudge', sessionId: state.sessionId, data: { iteration: state.iteration } });
|
|
1255
|
+
injectProgressNudgeFn(this.guardControllerDeps, state);
|
|
1323
1256
|
}
|
|
1324
1257
|
/**
|
|
1325
1258
|
* 迭代预算提醒
|
|
@@ -1327,49 +1260,7 @@ ${textHistory.join('\n')}`;
|
|
|
1327
1260
|
* 解决 agent 在数据查询循环中耗尽迭代、永远无法进入文档生成/委托阶段的问题。
|
|
1328
1261
|
*/
|
|
1329
1262
|
injectIterationGuard(state) {
|
|
1330
|
-
|
|
1331
|
-
return;
|
|
1332
|
-
const max = this.config.maxIterations;
|
|
1333
|
-
const cur = state.iteration;
|
|
1334
|
-
const remaining = max - cur;
|
|
1335
|
-
const ratio = cur / max;
|
|
1336
|
-
if (ratio >= this.guardConfig.iterationGuardThreshold && remaining > 0) {
|
|
1337
|
-
state.iterationGuardInjected = true;
|
|
1338
|
-
const toolCallCount = state.toolCallRecords.length;
|
|
1339
|
-
const targets = this.guardConfig.delegateTargets;
|
|
1340
|
-
const reportAgent = targets.report ? `"${targets.report}"` : 'the appropriate report agent';
|
|
1341
|
-
const docAgent = targets.document ? `"${targets.document}"` : 'the appropriate document agent';
|
|
1342
|
-
const pptAgent = targets.presentation ? `"${targets.presentation}"` : docAgent;
|
|
1343
|
-
// P1 #6: 使用可配置的提示文本,默认为通用英文提示(不绑定业务 Agent 名称)
|
|
1344
|
-
// 仅引用当前 registry 实际注册的工具,避免引导 LLM 调用未绑定工具
|
|
1345
|
-
const hasDelegate = this.hasTool('delegate_task');
|
|
1346
|
-
const delegateLines = [];
|
|
1347
|
-
if (hasDelegate) {
|
|
1348
|
-
delegateLines.push(` - For reports/dashboards: call \`delegate_task\` with agent ${reportAgent}.`);
|
|
1349
|
-
delegateLines.push(` - For documents: call \`delegate_task\` with agent ${docAgent}.`);
|
|
1350
|
-
delegateLines.push(` - For PPT: call \`delegate_task\` with agent ${pptAgent}.`);
|
|
1351
|
-
}
|
|
1352
|
-
const promptText = this.guardConfig.iterationGuardPrompt || [
|
|
1353
|
-
`[SYSTEM ITERATION BUDGET: iteration ${cur}/${max}, ${remaining} remaining]`,
|
|
1354
|
-
'',
|
|
1355
|
-
`⚠️ You have used ${cur} of ${max} iterations (${toolCallCount} tool calls). Budget is running low.`,
|
|
1356
|
-
'',
|
|
1357
|
-
'**MANDATORY rules for remaining iterations — violating these will result in task failure:**',
|
|
1358
|
-
'1. **STOP all data gathering immediately.** You already have enough data from previous tool calls.',
|
|
1359
|
-
'2. **DO NOT call web_fetch, execute_command, write_file, or run_script again.**',
|
|
1360
|
-
...(hasDelegate ? [`3. **NOW delegate remaining work (only if the user explicitly requested file/document generation):**`, ...delegateLines] : []),
|
|
1361
|
-
'4. If you cannot delegate, produce the final output NOW with the data you have.',
|
|
1362
|
-
'5. **Do NOT proactively generate files (PPT/PDF/HTML/Word) unless the user explicitly asked for them.** If the user only asked for analysis or information, present your findings directly in the conversation as text/markdown.',
|
|
1363
|
-
'',
|
|
1364
|
-
'Do NOT write any more scripts or fetch any more data. Your data collection phase is OVER.',
|
|
1365
|
-
].join('\n');
|
|
1366
|
-
const msg = { role: 'user', content: promptText };
|
|
1367
|
-
state.messages.push(msg);
|
|
1368
|
-
this.historyManager.addHistory(state.sessionId, msg);
|
|
1369
|
-
logEvent({ event: 'engine.iteration_guard', sessionId: state.sessionId, data: { iteration: cur, max } });
|
|
1370
|
-
// 硬性禁用数据获取工具,防止 LLM 无视文字提示继续调用
|
|
1371
|
-
this.disableDataFetchTools(state);
|
|
1372
|
-
}
|
|
1263
|
+
injectIterationGuardFn(this.guardControllerDeps, state);
|
|
1373
1264
|
}
|
|
1374
1265
|
/**
|
|
1375
1266
|
* 从可用工具列表中移除数据获取类工具,强制 LLM 转向产出。
|
|
@@ -1377,20 +1268,7 @@ ${textHistory.join('\n')}`;
|
|
|
1377
1268
|
* 仅移除尚未执行的 toolDefs,不影响正在执行的工具。
|
|
1378
1269
|
*/
|
|
1379
1270
|
disableDataFetchTools(state) {
|
|
1380
|
-
|
|
1381
|
-
state.toolDefs = state.toolDefs.filter(t => !this.dataFetchTools.has(t.function.name));
|
|
1382
|
-
const removed = before - state.toolDefs.length;
|
|
1383
|
-
// 同步将禁用的工具名加入 blockedTools 集合,供 tool executor 在执行层拦截
|
|
1384
|
-
// LLM 基于对话历史的幻觉调用(toolDefs 仅控制 LLM 可见的工具列表,
|
|
1385
|
-
// 但 tool executor 检查的是 toolRegistry,不会拦截已注册但被禁用的工具)
|
|
1386
|
-
if (!state.blockedTools)
|
|
1387
|
-
state.blockedTools = new Set();
|
|
1388
|
-
for (const name of this.dataFetchTools) {
|
|
1389
|
-
state.blockedTools.add(name);
|
|
1390
|
-
}
|
|
1391
|
-
if (removed > 0) {
|
|
1392
|
-
logEvent({ event: 'engine.tools_disabled', sessionId: state.sessionId, data: { removed, remaining: state.toolDefs.length, blocked: state.blockedTools.size } });
|
|
1393
|
-
}
|
|
1271
|
+
disableDataFetchToolsFn(this.guardControllerDeps, state);
|
|
1394
1272
|
}
|
|
1395
1273
|
/**
|
|
1396
1274
|
* 数据获取循环检测 + 连续失败熔断
|
|
@@ -1400,90 +1278,11 @@ ${textHistory.join('\n')}`;
|
|
|
1400
1278
|
* 2. 连续失败熔断:最近 5+ 条工具调用中 4+ 条失败,强制停止试错、转向产出
|
|
1401
1279
|
*/
|
|
1402
1280
|
checkDataFetchLoop(state) {
|
|
1403
|
-
|
|
1404
|
-
return;
|
|
1405
|
-
// 数据获取类工具(非产出型)— 使用实例配置
|
|
1406
|
-
const DATA_FETCH_TOOLS = this.dataFetchTools;
|
|
1407
|
-
// 产出型工具(有这些就不算数据获取循环)— 使用实例配置
|
|
1408
|
-
const OUTPUT_TOOLS = this.outputTools;
|
|
1409
|
-
const hasOutput = state.toolCallRecords.some(r => OUTPUT_TOOLS.has(r.tool));
|
|
1410
|
-
if (hasOutput)
|
|
1411
|
-
return; // 已调用过产出型工具,不再干预
|
|
1412
|
-
const recent = state.toolCallRecords.slice(-6);
|
|
1413
|
-
// --- 条件 1:数据获取循环(最近 N+ 条全是数据获取类) ---
|
|
1414
|
-
const loopMin = this.guardConfig.dataFetchLoopMinCount;
|
|
1415
|
-
if (recent.length >= loopMin) {
|
|
1416
|
-
const lastN = recent.slice(-loopMin);
|
|
1417
|
-
const allDataFetch = lastN.every(r => DATA_FETCH_TOOLS.has(r.tool));
|
|
1418
|
-
if (allDataFetch) {
|
|
1419
|
-
this.injectDataFetchGuard(state, recent, 'loop');
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
// --- 条件 2:连续失败熔断(最近 N+ 条中 M+ 条失败) ---
|
|
1424
|
-
const failMin = this.guardConfig.consecutiveFailMinCount;
|
|
1425
|
-
if (recent.length >= failMin) {
|
|
1426
|
-
const errorCount = recent.filter(r => r.isError).length;
|
|
1427
|
-
if (errorCount >= this.guardConfig.consecutiveFailErrorThreshold) {
|
|
1428
|
-
this.injectDataFetchGuard(state, recent, 'failures');
|
|
1429
|
-
return;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
// --- 条件 3:跨迭代累计失败(总调用 >=8 且失败率 >=70%) ---
|
|
1433
|
-
// 解决 LLM 在不同迭代中切换工具类型(web_fetch -> http_request -> browser_navigate)
|
|
1434
|
-
// 导致每种工具的连续失败计数都不够触发条件 2 的问题。
|
|
1435
|
-
const totalCalls = state.toolCallRecords.length;
|
|
1436
|
-
if (totalCalls >= 8) {
|
|
1437
|
-
const totalErrors = state.toolCallRecords.filter(r => r.isError).length;
|
|
1438
|
-
if (totalErrors >= Math.floor(totalCalls * 0.7)) {
|
|
1439
|
-
this.injectDataFetchGuard(state, state.toolCallRecords.slice(-6), 'cumulative_failures');
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1281
|
+
checkDataFetchLoopFn(this.guardControllerDeps, state);
|
|
1443
1282
|
}
|
|
1444
1283
|
/** 注入数据获取循环/连续失败熔断提示 */
|
|
1445
1284
|
injectDataFetchGuard(state, recent, reason) {
|
|
1446
|
-
|
|
1447
|
-
// 触发 guardrail 可观测事件(fire-and-forget,不阻塞主流程)
|
|
1448
|
-
void this.runHooks('on_data_fetch_loop', {
|
|
1449
|
-
sessionId: state.sessionId,
|
|
1450
|
-
iteration: state.iteration,
|
|
1451
|
-
traceId: state.toolContext?.custom?.traceId,
|
|
1452
|
-
});
|
|
1453
|
-
const toolSummary = recent.map(r => `${r.tool}(${r.isError ? 'err' : 'ok'})`).join(', ');
|
|
1454
|
-
const errorCount = recent.filter(r => r.isError).length;
|
|
1455
|
-
const reasonText = reason === 'loop'
|
|
1456
|
-
? `Your last ${recent.length} tool calls are all data-fetching without any output: ${toolSummary}`
|
|
1457
|
-
: `${errorCount} of your last ${recent.length} tool calls FAILED: ${toolSummary}`;
|
|
1458
|
-
const msg = {
|
|
1459
|
-
role: 'user',
|
|
1460
|
-
content: [
|
|
1461
|
-
`[SYSTEM: ${reason === 'loop' ? 'DATA GATHERING LOOP' : 'CONSECUTIVE FAILURES'} DETECTED]`,
|
|
1462
|
-
'',
|
|
1463
|
-
reasonText,
|
|
1464
|
-
'',
|
|
1465
|
-
'⛔ **You MUST stop trying to fetch more data NOW.**',
|
|
1466
|
-
'',
|
|
1467
|
-
'**Required action — do ONE of the following immediately:**',
|
|
1468
|
-
...(this.hasTool('delegate_task')
|
|
1469
|
-
? ['1. Call `delegate_task` with whatever data/context you have to let a specialist generate the output.']
|
|
1470
|
-
: []),
|
|
1471
|
-
'2. If delegation is not suitable, produce the final output NOW with available data.',
|
|
1472
|
-
'3. At minimum, provide a clear text summary to the user explaining what you found and what failed.',
|
|
1473
|
-
'',
|
|
1474
|
-
'**Do NOT:**',
|
|
1475
|
-
'- Write another script to fetch data',
|
|
1476
|
-
'- Try another API, URL, or npm package',
|
|
1477
|
-
'- Debug the current failure — accept it and move on',
|
|
1478
|
-
'',
|
|
1479
|
-
'The user is waiting for OUTPUT, not more failed attempts.',
|
|
1480
|
-
].join('\n'),
|
|
1481
|
-
};
|
|
1482
|
-
state.messages.push(msg);
|
|
1483
|
-
this.historyManager.addHistory(state.sessionId, msg);
|
|
1484
|
-
logEvent({ event: 'engine.data_fetch_guard', sessionId: state.sessionId, data: { reason, iteration: state.iteration, recent: toolSummary } });
|
|
1485
|
-
// 硬性禁用数据获取工具,防止 LLM 无视文字提示继续调用
|
|
1486
|
-
this.disableDataFetchTools(state);
|
|
1285
|
+
injectDataFetchGuardFn(this.guardControllerDeps, state, recent, reason);
|
|
1487
1286
|
}
|
|
1488
1287
|
/**
|
|
1489
1288
|
* 任务继续提示
|
|
@@ -1491,28 +1290,7 @@ ${textHistory.join('\n')}`;
|
|
|
1491
1290
|
* 典型场景:多步委托任务中,第一步数据采集完成后 LLM 未生成后续步骤的 delegate_task。
|
|
1492
1291
|
*/
|
|
1493
1292
|
injectContinuationNudge(state) {
|
|
1494
|
-
|
|
1495
|
-
const msg = {
|
|
1496
|
-
role: 'user',
|
|
1497
|
-
content: [
|
|
1498
|
-
'[SYSTEM: TASK CONTINUATION]',
|
|
1499
|
-
'',
|
|
1500
|
-
`You have completed ${toolCallCount} tool call(s) so far, but the original user request has NOT been fully fulfilled.`,
|
|
1501
|
-
'',
|
|
1502
|
-
'Please review the original request and continue with the remaining steps:',
|
|
1503
|
-
'- If you planned multi-step tasks (e.g., data gathering → PPT/report generation), **NOW execute the remaining steps**.',
|
|
1504
|
-
...(this.hasTool('delegate_task')
|
|
1505
|
-
? ['- Use `delegate_task` to delegate remaining work to appropriate specialists.']
|
|
1506
|
-
: []),
|
|
1507
|
-
'- Only if the user **explicitly requested** document/PPT/report generation, call the relevant tools now. Do NOT proactively generate files the user did not ask for.',
|
|
1508
|
-
'- Do NOT re-gather data you already have. Use the results from previous tool calls.',
|
|
1509
|
-
'',
|
|
1510
|
-
'Continue your work now.',
|
|
1511
|
-
].join('\n'),
|
|
1512
|
-
};
|
|
1513
|
-
state.messages.push(msg);
|
|
1514
|
-
this.historyManager.addHistory(state.sessionId, msg);
|
|
1515
|
-
logEvent({ event: 'engine.continuation_nudge_injected', sessionId: state.sessionId, data: { iteration: state.iteration, nudge: state.continuationNudges } });
|
|
1293
|
+
injectContinuationNudgeFn(this.guardControllerDeps, state);
|
|
1516
1294
|
}
|
|
1517
1295
|
/**
|
|
1518
1296
|
* 渐进式结果交付
|
|
@@ -1520,28 +1298,7 @@ ${textHistory.join('\n')}`;
|
|
|
1520
1298
|
* 而不是等所有结果全部返回后才开始输出。减少用户感知等待时间。
|
|
1521
1299
|
*/
|
|
1522
1300
|
injectProgressiveNudge(state, toolCalls) {
|
|
1523
|
-
|
|
1524
|
-
if (delegateCalls.length < 2)
|
|
1525
|
-
return;
|
|
1526
|
-
const msg = {
|
|
1527
|
-
role: 'user',
|
|
1528
|
-
content: [
|
|
1529
|
-
'[SYSTEM: PROGRESSIVE DELIVERY]',
|
|
1530
|
-
'',
|
|
1531
|
-
`You dispatched ${delegateCalls.length} sub-tasks in parallel. Some may succeed while others fail or take longer.`,
|
|
1532
|
-
'',
|
|
1533
|
-
'When presenting results to the user:',
|
|
1534
|
-
'1. **Present each completed result as it becomes available** — do NOT wait for all tasks to finish.',
|
|
1535
|
-
'2. If a sub-agent reports network/sandbox restrictions or fails, **acknowledge it briefly and move on** with available data.',
|
|
1536
|
-
'3. **Do NOT re-dispatch** a failed task to the same or different agent — use what you already have.',
|
|
1537
|
-
'4. Start your final analysis/report with whatever data is available, noting which parts are missing.',
|
|
1538
|
-
'',
|
|
1539
|
-
'The user values SPEED over COMPLETENESS. A partial result delivered in 2 minutes is better than a perfect result delivered in 15 minutes.',
|
|
1540
|
-
].join('\n'),
|
|
1541
|
-
};
|
|
1542
|
-
state.messages.push(msg);
|
|
1543
|
-
this.historyManager.addHistory(state.sessionId, msg);
|
|
1544
|
-
logEvent({ event: 'engine.progressive_nudge', sessionId: state.sessionId, data: { iteration: state.iteration, delegates: delegateCalls.length } });
|
|
1301
|
+
injectProgressiveNudgeFn(this.guardControllerDeps, state, toolCalls);
|
|
1545
1302
|
}
|
|
1546
1303
|
// ---- Hooks ----
|
|
1547
1304
|
/** 执行指定事件类型的所有 Hook */
|
|
@@ -1661,13 +1418,34 @@ ${textHistory.join('\n')}`;
|
|
|
1661
1418
|
}
|
|
1662
1419
|
/** 中断指定会话的 Agent 执行 */
|
|
1663
1420
|
abort(sessionId) {
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1421
|
+
abortSessionFn(this.sessionStateDeps, sessionId);
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* 暂停指定会话的 Agent 执行:当前工具/LLM 调用继续完成,
|
|
1425
|
+
* agent loop 在下一轮 turn 边界优雅退出(发送 paused 事件后正常结束流)。
|
|
1426
|
+
* 恢复由新建 SSE 流重建上下文实现(不唤醒本循环)。
|
|
1427
|
+
* 对不存在的会话调用是幂等 no-op。
|
|
1428
|
+
*/
|
|
1429
|
+
pause(sessionId) {
|
|
1430
|
+
const entry = this.pauseMap.get(sessionId);
|
|
1431
|
+
if (entry) {
|
|
1432
|
+
entry.paused = true;
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
this.pauseMap.set(sessionId, { paused: true, waiters: [] });
|
|
1669
1436
|
}
|
|
1670
1437
|
}
|
|
1438
|
+
/**
|
|
1439
|
+
* 清理指定会话的暂停标志(恢复由新建 SSE 流完成,不再唤醒挂起循环)。
|
|
1440
|
+
* 对未暂停的会话调用是幂等 no-op。
|
|
1441
|
+
*/
|
|
1442
|
+
resume(sessionId) {
|
|
1443
|
+
this.pauseMap.delete(sessionId);
|
|
1444
|
+
}
|
|
1445
|
+
/** 查询指定会话是否处于暂停状态 */
|
|
1446
|
+
isPaused(sessionId) {
|
|
1447
|
+
return this.pauseMap.get(sessionId)?.paused === true;
|
|
1448
|
+
}
|
|
1671
1449
|
/**
|
|
1672
1450
|
* 中断指定会话及其所有子会话的 Agent 执行(级联 abort)。
|
|
1673
1451
|
*
|
|
@@ -1675,14 +1453,7 @@ ${textHistory.join('\n')}`;
|
|
|
1675
1453
|
* 用于确保父会话异常终止时,所有 delegate_task 派生的子任务也被终止。
|
|
1676
1454
|
*/
|
|
1677
1455
|
abortCascade(sessionId) {
|
|
1678
|
-
this.
|
|
1679
|
-
// 遍历直接子会话,递归 abort
|
|
1680
|
-
const children = this.getChildSessions(sessionId);
|
|
1681
|
-
for (const childId of children) {
|
|
1682
|
-
this.abortCascade(childId);
|
|
1683
|
-
}
|
|
1684
|
-
// 清理父子关系
|
|
1685
|
-
this.cleanupSessionRelations(sessionId);
|
|
1456
|
+
abortCascadeFn(this.sessionStateDeps, sessionId);
|
|
1686
1457
|
}
|
|
1687
1458
|
/** 查询指定会话的 Agent 状态 */
|
|
1688
1459
|
getStatus(sessionId) {
|
|
@@ -1762,25 +1533,14 @@ ${textHistory.join('\n')}`;
|
|
|
1762
1533
|
* - 如果是子会话:从父会话的子集中移除自身
|
|
1763
1534
|
*/
|
|
1764
1535
|
cleanupSessionRelations(sessionId) {
|
|
1765
|
-
|
|
1766
|
-
const parentId = this.parentSessionMap.get(sessionId);
|
|
1767
|
-
if (parentId) {
|
|
1768
|
-
this.childSessionMap.get(parentId)?.delete(sessionId);
|
|
1769
|
-
this.parentSessionMap.delete(sessionId);
|
|
1770
|
-
}
|
|
1771
|
-
// 删除自身作为父会话的所有子关系记录(但保留子会话本身仍在运行)
|
|
1772
|
-
this.childSessionMap.delete(sessionId);
|
|
1536
|
+
cleanupSessionRelationsFn(this.sessionStateDeps, sessionId);
|
|
1773
1537
|
}
|
|
1774
1538
|
// ---- 内部辅助 ----
|
|
1775
1539
|
createEvent(sessionId, event) {
|
|
1776
1540
|
return { ...event, timestamp: Date.now(), sessionId };
|
|
1777
1541
|
}
|
|
1778
1542
|
createNoopShell() {
|
|
1779
|
-
return
|
|
1780
|
-
exec: async () => 'Error: Shell executor not configured',
|
|
1781
|
-
write: () => { },
|
|
1782
|
-
read: () => '',
|
|
1783
|
-
};
|
|
1543
|
+
return createNoopShell();
|
|
1784
1544
|
}
|
|
1785
1545
|
}
|
|
1786
1546
|
//# sourceMappingURL=engine.js.map
|