@zhin.js/agent 0.1.20 → 0.1.22
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 +37 -23
- package/lib/agent-prompt/format.d.ts +6 -0
- package/lib/agent-prompt/format.d.ts.map +1 -0
- package/lib/agent-prompt/format.js +40 -0
- package/lib/agent-prompt/format.js.map +1 -0
- package/lib/agent-prompt/index.d.ts +5 -0
- package/lib/agent-prompt/index.d.ts.map +1 -0
- package/lib/agent-prompt/index.js +4 -0
- package/lib/agent-prompt/index.js.map +1 -0
- package/lib/agent-prompt/registry.d.ts +7 -0
- package/lib/agent-prompt/registry.d.ts.map +1 -0
- package/lib/agent-prompt/registry.js +23 -0
- package/lib/agent-prompt/registry.js.map +1 -0
- package/lib/agent-prompt/resolver.d.ts +12 -0
- package/lib/agent-prompt/resolver.d.ts.map +1 -0
- package/lib/agent-prompt/resolver.js +71 -0
- package/lib/agent-prompt/resolver.js.map +1 -0
- package/lib/builtin/run-deferred-task-tool.d.ts +20 -0
- package/lib/builtin/run-deferred-task-tool.d.ts.map +1 -0
- package/lib/builtin/run-deferred-task-tool.js +39 -0
- package/lib/builtin/run-deferred-task-tool.js.map +1 -0
- package/lib/builtin/tool-search-tool.d.ts +19 -0
- package/lib/builtin/tool-search-tool.d.ts.map +1 -0
- package/lib/builtin/tool-search-tool.js +54 -0
- package/lib/builtin/tool-search-tool.js.map +1 -0
- package/lib/builtin-tools.d.ts +1 -1
- package/lib/builtin-tools.d.ts.map +1 -1
- package/lib/builtin-tools.js +1 -5
- package/lib/builtin-tools.js.map +1 -1
- package/lib/cron-engine.d.ts.map +1 -1
- package/lib/cron-engine.js +3 -3
- package/lib/cron-engine.js.map +1 -1
- package/lib/deferred-worker-runner.d.ts +30 -0
- package/lib/deferred-worker-runner.d.ts.map +1 -0
- package/lib/deferred-worker-runner.js +159 -0
- package/lib/deferred-worker-runner.js.map +1 -0
- package/lib/deferred-worker-tool-load.d.ts +6 -0
- package/lib/deferred-worker-tool-load.d.ts.map +1 -0
- package/lib/deferred-worker-tool-load.js +5 -0
- package/lib/deferred-worker-tool-load.js.map +1 -0
- package/lib/hooks.d.ts +13 -0
- package/lib/hooks.d.ts.map +1 -1
- package/lib/hooks.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -3
- package/lib/index.js.map +1 -1
- package/lib/init/create-zhin-agent.d.ts.map +1 -1
- package/lib/init/create-zhin-agent.js +9 -7
- package/lib/init/create-zhin-agent.js.map +1 -1
- package/lib/init/register-ai-service.d.ts.map +1 -1
- package/lib/init/register-ai-service.js +5 -5
- package/lib/init/register-ai-service.js.map +1 -1
- package/lib/init/register-ai-trigger.d.ts.map +1 -1
- package/lib/init/register-ai-trigger.js +25 -9
- package/lib/init/register-ai-trigger.js.map +1 -1
- package/lib/init/register-builtin-tools.d.ts.map +1 -1
- package/lib/init/register-builtin-tools.js +33 -29
- package/lib/init/register-builtin-tools.js.map +1 -1
- package/lib/init/register-mcp-from-config.d.ts +2 -0
- package/lib/init/register-mcp-from-config.d.ts.map +1 -0
- package/lib/init/register-mcp-from-config.js +85 -0
- package/lib/init/register-mcp-from-config.js.map +1 -0
- package/lib/init/register-mcp-memory-default.d.ts +2 -0
- package/lib/init/register-mcp-memory-default.d.ts.map +1 -0
- package/lib/init/register-mcp-memory-default.js +34 -0
- package/lib/init/register-mcp-memory-default.js.map +1 -0
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +4 -0
- package/lib/init.js.map +1 -1
- package/lib/mcp-client/index.d.ts +1 -2
- package/lib/mcp-client/index.d.ts.map +1 -1
- package/lib/mcp-client/index.js +1 -2
- package/lib/mcp-client/index.js.map +1 -1
- package/lib/orchestrator/mcp-lifecycle.d.ts +6 -0
- package/lib/orchestrator/mcp-lifecycle.d.ts.map +1 -0
- package/lib/orchestrator/mcp-lifecycle.js +7 -0
- package/lib/orchestrator/mcp-lifecycle.js.map +1 -0
- package/lib/orchestrator/mcp-registry.d.ts +5 -1
- package/lib/orchestrator/mcp-registry.d.ts.map +1 -1
- package/lib/orchestrator/mcp-registry.js +36 -20
- package/lib/orchestrator/mcp-registry.js.map +1 -1
- package/lib/orchestrator/owner-confirm-orchestration.d.ts +4 -1
- package/lib/orchestrator/owner-confirm-orchestration.d.ts.map +1 -1
- package/lib/orchestrator/owner-confirm-orchestration.js.map +1 -1
- package/lib/orchestrator/tool-selection.d.ts +2 -0
- package/lib/orchestrator/tool-selection.d.ts.map +1 -1
- package/lib/orchestrator/tool-selection.js +40 -4
- package/lib/orchestrator/tool-selection.js.map +1 -1
- package/lib/reserved-tools.js +2 -2
- package/lib/reserved-tools.js.map +1 -1
- package/lib/security/bash-tool-context.d.ts +17 -2
- package/lib/security/bash-tool-context.d.ts.map +1 -1
- package/lib/security/bash-tool-context.js +9 -3
- package/lib/security/bash-tool-context.js.map +1 -1
- package/lib/security/exec-policy.d.ts.map +1 -1
- package/lib/security/exec-policy.js +4 -1
- package/lib/security/exec-policy.js.map +1 -1
- package/lib/subagent.d.ts +7 -1
- package/lib/subagent.d.ts.map +1 -1
- package/lib/subagent.js +22 -8
- package/lib/subagent.js.map +1 -1
- package/lib/utils/config-env.d.ts +5 -0
- package/lib/utils/config-env.d.ts.map +1 -0
- package/lib/utils/config-env.js +34 -0
- package/lib/utils/config-env.js.map +1 -0
- package/lib/zhin-agent/config.d.ts +23 -0
- package/lib/zhin-agent/config.d.ts.map +1 -1
- package/lib/zhin-agent/config.js +20 -0
- package/lib/zhin-agent/config.js.map +1 -1
- package/lib/zhin-agent/index.d.ts +21 -0
- package/lib/zhin-agent/index.d.ts.map +1 -1
- package/lib/zhin-agent/index.js +257 -35
- package/lib/zhin-agent/index.js.map +1 -1
- package/lib/zhin-agent/prompt.d.ts +6 -0
- package/lib/zhin-agent/prompt.d.ts.map +1 -1
- package/lib/zhin-agent/prompt.js +70 -15
- package/lib/zhin-agent/prompt.js.map +1 -1
- package/lib/zhin-agent/tool-catalog.d.ts +15 -0
- package/lib/zhin-agent/tool-catalog.d.ts.map +1 -0
- package/lib/zhin-agent/tool-catalog.js +52 -0
- package/lib/zhin-agent/tool-catalog.js.map +1 -0
- package/lib/zhin-agent/tool-runtime.d.ts +3 -1
- package/lib/zhin-agent/tool-runtime.d.ts.map +1 -1
- package/lib/zhin-agent/tool-runtime.js +14 -0
- package/lib/zhin-agent/tool-runtime.js.map +1 -1
- package/lib/zhin-agent/tool-search-orchestrator.d.ts +23 -0
- package/lib/zhin-agent/tool-search-orchestrator.d.ts.map +1 -0
- package/lib/zhin-agent/tool-search-orchestrator.js +86 -0
- package/lib/zhin-agent/tool-search-orchestrator.js.map +1 -0
- package/lib/zhin-agent/turn-metrics.d.ts +20 -0
- package/lib/zhin-agent/turn-metrics.d.ts.map +1 -0
- package/lib/zhin-agent/turn-metrics.js +28 -0
- package/lib/zhin-agent/turn-metrics.js.map +1 -0
- package/package.json +14 -4
- package/lib/builtin/read-memory-tool.d.ts +0 -14
- package/lib/builtin/read-memory-tool.d.ts.map +0 -1
- package/lib/builtin/read-memory-tool.js +0 -49
- package/lib/builtin/read-memory-tool.js.map +0 -1
- package/lib/builtin/write-memory-tool.d.ts +0 -14
- package/lib/builtin/write-memory-tool.d.ts.map +0 -1
- package/lib/builtin/write-memory-tool.js +0 -50
- package/lib/builtin/write-memory-tool.js.map +0 -1
package/lib/zhin-agent/index.js
CHANGED
|
@@ -15,28 +15,36 @@
|
|
|
15
15
|
* 11. 定时任务:cron_add 持久化定时任务
|
|
16
16
|
* 12. 多模态输入:图片/音频直接传给视觉模型
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { getPlugin, Logger } from '@zhin.js/core';
|
|
19
|
+
import { formatCompact, truncatePreview } from '@zhin.js/logger';
|
|
20
|
+
import { ensureMcpConnections } from '../orchestrator/mcp-lifecycle.js';
|
|
19
21
|
import { createAgent } from '@zhin.js/ai';
|
|
20
22
|
import { SessionManager, createMemorySessionManager } from '@zhin.js/ai';
|
|
21
23
|
import { ConversationMemory } from '@zhin.js/ai';
|
|
22
24
|
import { parseOutput } from '@zhin.js/ai';
|
|
25
|
+
import { addUsage, EMPTY_USAGE, } from './turn-metrics.js';
|
|
23
26
|
import { UserProfileStore } from '../user-profile.js';
|
|
24
27
|
import { WEB_SEARCH_LOCALE_EXTRA_KEY, normalizeWebSearchLocaleHint, } from '../builtin/web-search-locale.js';
|
|
25
28
|
import { RateLimiter } from '@zhin.js/ai';
|
|
26
29
|
import { detectTone } from '@zhin.js/ai';
|
|
27
30
|
import { SubagentManager } from '../subagent.js';
|
|
28
31
|
import { triggerAIHook, createAIHookEvent } from '../hooks.js';
|
|
32
|
+
import { resolveAgentPromptMarkdown } from '../agent-prompt/index.js';
|
|
29
33
|
// ── Sub-modules ─────────────────────────────────────────────────────
|
|
30
34
|
import { DEFAULT_CONFIG, isPhaseTraceEnabled, } from './config.js';
|
|
31
35
|
import { applyExecPolicyToTools } from '../security/exec-policy.js';
|
|
32
36
|
import { runWithBashToolContext } from '../security/bash-tool-context.js';
|
|
33
|
-
import { buildEnhancedPersona, buildContextHint, buildRichSystemPrompt, buildUserMessageWithHistory, FIXED_DISCIPLINE_RULES, } from './prompt.js';
|
|
37
|
+
import { buildEnhancedPersona, buildContextHint, buildRichSystemPrompt, buildLiteSystemPromptWithPlatform, buildUserMessageWithHistory, FIXED_DISCIPLINE_RULES, } from './prompt.js';
|
|
34
38
|
import { buildPreExecFastPathPrompt, collectRuntimeTools, planToolRun, } from './tool-runtime.js';
|
|
39
|
+
import { DeferredWorkerRunner } from '../deferred-worker-runner.js';
|
|
40
|
+
import { buildOrchestratorAgentTools } from './tool-search-orchestrator.js';
|
|
41
|
+
import { filterToolsForToolSearchCatalog } from './tool-catalog.js';
|
|
35
42
|
import { stripHallucinatedToolCalls, stripThinkBlocks } from './text-sanitize.js';
|
|
36
43
|
import { pruneHistoryWithBudget } from './context-budget.js';
|
|
37
44
|
import { resolveModelHarness } from './model-harness.js';
|
|
38
45
|
import { RESERVED_TOOL_NAMES, RESERVED_TOOL_NAME_PREFIXES } from '../reserved-tools.js';
|
|
39
46
|
import { createOwnerOrchestratedToolResultTransform } from '../orchestrator/owner-confirm-orchestration.js';
|
|
47
|
+
export { formatAiHandlerCompleteLog, formatZhinAgentTurnUsage } from './turn-metrics.js';
|
|
40
48
|
const logger = new Logger(null, 'ZhinAgent');
|
|
41
49
|
const now = () => performance.now();
|
|
42
50
|
// ============================================================================
|
|
@@ -46,6 +54,7 @@ export class ZhinAgent {
|
|
|
46
54
|
provider;
|
|
47
55
|
config;
|
|
48
56
|
skillRegistry = null;
|
|
57
|
+
orchestrator = null;
|
|
49
58
|
sessions;
|
|
50
59
|
contextManager = null;
|
|
51
60
|
memory;
|
|
@@ -60,6 +69,12 @@ export class ZhinAgent {
|
|
|
60
69
|
phaseTraceEnabled;
|
|
61
70
|
/** 根插件(createZhinAgentContext 注入);避免在 Agent.run 路径依赖 AsyncLocalStorage 上的 getPlugin() */
|
|
62
71
|
hostPlugin = null;
|
|
72
|
+
deferredCatalog = [];
|
|
73
|
+
deferredWorkerRunner = new DeferredWorkerRunner();
|
|
74
|
+
lastToolSearchDeferredStats;
|
|
75
|
+
lastTurnMetrics = null;
|
|
76
|
+
activeTurnSubagentUsage = null;
|
|
77
|
+
activeTurnSubagentWaits = [];
|
|
63
78
|
constructor(provider, config) {
|
|
64
79
|
this.provider = provider;
|
|
65
80
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
@@ -79,6 +94,10 @@ export class ZhinAgent {
|
|
|
79
94
|
this.skillRegistry = registry;
|
|
80
95
|
logger.debug(`SkillRegistry connected (${registry.size} skills)`);
|
|
81
96
|
}
|
|
97
|
+
setOrchestrator(orchestrator) {
|
|
98
|
+
this.orchestrator = orchestrator;
|
|
99
|
+
logger.debug('AgentOrchestrator connected for MCP and resources');
|
|
100
|
+
}
|
|
82
101
|
setSessionManager(manager) {
|
|
83
102
|
this.sessions.dispose();
|
|
84
103
|
this.sessions = manager;
|
|
@@ -110,6 +129,8 @@ export class ZhinAgent {
|
|
|
110
129
|
maxIterations: this.config.maxSubagentIterations,
|
|
111
130
|
execPolicyConfig: this.config,
|
|
112
131
|
modelRegistry: this.modelRegistry,
|
|
132
|
+
onSubagentUsage: (usage) => this.addActiveTurnSubagentUsage(usage),
|
|
133
|
+
registerSubagentTask: (done) => this.trackActiveTurnSubagent(done),
|
|
113
134
|
});
|
|
114
135
|
logger.debug('SubagentManager initialized');
|
|
115
136
|
}
|
|
@@ -170,6 +191,51 @@ export class ZhinAgent {
|
|
|
170
191
|
setSkillsSummaryXML(xml) {
|
|
171
192
|
this.skillsSummaryXML = xml || '';
|
|
172
193
|
}
|
|
194
|
+
/** 上一轮 `process` / `processMultimodal` 的 token 等指标(供 AI Handler 打汇总日志) */
|
|
195
|
+
getLastTurnMetrics() {
|
|
196
|
+
return this.lastTurnMetrics;
|
|
197
|
+
}
|
|
198
|
+
beginActiveTurn() {
|
|
199
|
+
this.activeTurnSubagentUsage = { ...EMPTY_USAGE };
|
|
200
|
+
this.activeTurnSubagentWaits = [];
|
|
201
|
+
}
|
|
202
|
+
addActiveTurnSubagentUsage(usage) {
|
|
203
|
+
if (!this.activeTurnSubagentUsage)
|
|
204
|
+
return;
|
|
205
|
+
addUsage(this.activeTurnSubagentUsage, usage);
|
|
206
|
+
}
|
|
207
|
+
trackActiveTurnSubagent(done) {
|
|
208
|
+
if (!this.activeTurnSubagentUsage)
|
|
209
|
+
return;
|
|
210
|
+
this.activeTurnSubagentWaits.push(done);
|
|
211
|
+
}
|
|
212
|
+
async finalizeActiveTurn(partial) {
|
|
213
|
+
const waitMs = this.config.subagentTurnWaitMs;
|
|
214
|
+
if (this.activeTurnSubagentWaits.length > 0 && waitMs > 0) {
|
|
215
|
+
await Promise.race([
|
|
216
|
+
Promise.allSettled(this.activeTurnSubagentWaits),
|
|
217
|
+
new Promise(resolve => setTimeout(resolve, waitMs)),
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
const mainUsage = { ...partial.usage };
|
|
221
|
+
const subagentUsage = this.activeTurnSubagentUsage
|
|
222
|
+
&& (this.activeTurnSubagentUsage.total_tokens > 0
|
|
223
|
+
|| this.activeTurnSubagentUsage.prompt_tokens > 0
|
|
224
|
+
|| this.activeTurnSubagentUsage.completion_tokens > 0)
|
|
225
|
+
? { ...this.activeTurnSubagentUsage }
|
|
226
|
+
: undefined;
|
|
227
|
+
const totalUsage = { ...mainUsage };
|
|
228
|
+
if (subagentUsage)
|
|
229
|
+
addUsage(totalUsage, subagentUsage);
|
|
230
|
+
this.lastTurnMetrics = {
|
|
231
|
+
...partial,
|
|
232
|
+
usage: totalUsage,
|
|
233
|
+
mainUsage,
|
|
234
|
+
...(subagentUsage ? { subagentUsage } : {}),
|
|
235
|
+
};
|
|
236
|
+
this.activeTurnSubagentUsage = null;
|
|
237
|
+
this.activeTurnSubagentWaits = [];
|
|
238
|
+
}
|
|
173
239
|
// ── Core processing ─────────────────────────────────────────────────
|
|
174
240
|
async process(content, context, externalTools = [], onChunk) {
|
|
175
241
|
const t0 = now();
|
|
@@ -185,8 +251,10 @@ export class ZhinAgent {
|
|
|
185
251
|
if (!rateCheck.allowed) {
|
|
186
252
|
this.logPhase('turn.rate_limited', sessionId, { userId });
|
|
187
253
|
logger.debug(`[速率限制] 用户 ${userId} 被限制: ${rateCheck.message}`);
|
|
254
|
+
await this.finalizeActiveTurn({ usage: EMPTY_USAGE, path: 'rate_limited' });
|
|
188
255
|
return parseOutput(rateCheck.message || '请稍后再试');
|
|
189
256
|
}
|
|
257
|
+
this.beginActiveTurn();
|
|
190
258
|
// 0.5 工具上下文:web_search 语言(档案 preferred_language / language,否则默认中文)
|
|
191
259
|
const contextForTools = await this.attachWebSearchLocale(context, userId);
|
|
192
260
|
triggerAIHook(createAIHookEvent('message', 'received', sessionId, {
|
|
@@ -194,8 +262,13 @@ export class ZhinAgent {
|
|
|
194
262
|
content,
|
|
195
263
|
platform: platform || '',
|
|
196
264
|
})).catch(() => { });
|
|
265
|
+
// 0.9 Lazy-connect configured MCP servers before tool collection
|
|
266
|
+
if (this.orchestrator) {
|
|
267
|
+
await ensureMcpConnections(this.orchestrator.mcps);
|
|
268
|
+
}
|
|
197
269
|
// 1. Collect tools
|
|
198
270
|
const tFilter = now();
|
|
271
|
+
const mcpTools = this.orchestrator?.mcps.getAllMcpTools() ?? [];
|
|
199
272
|
const allTools = collectRuntimeTools({
|
|
200
273
|
content,
|
|
201
274
|
context: contextForTools,
|
|
@@ -208,10 +281,17 @@ export class ZhinAgent {
|
|
|
208
281
|
memory: this.memory,
|
|
209
282
|
userProfiles: this.userProfiles,
|
|
210
283
|
subagentManager: this.subagentManager,
|
|
284
|
+
mcpTools,
|
|
211
285
|
});
|
|
286
|
+
const { tools: resolvedTools, deferredStats } = this.resolveAgentToolsForTurn(allTools, contextForTools);
|
|
287
|
+
this.lastToolSearchDeferredStats = deferredStats;
|
|
212
288
|
const filterMs = (now() - tFilter).toFixed(0);
|
|
213
|
-
this.logPhase('tools.collected', sessionId, { count:
|
|
214
|
-
logger.
|
|
289
|
+
this.logPhase('tools.collected', sessionId, { count: resolvedTools.length });
|
|
290
|
+
logger.debug(formatCompact({
|
|
291
|
+
tools: resolvedTools.length,
|
|
292
|
+
tool_search: this.config.toolSearch || undefined,
|
|
293
|
+
names: resolvedTools.map(t => t.name).join(',') || '(none)',
|
|
294
|
+
}));
|
|
215
295
|
// 2. History + profile (parallel)
|
|
216
296
|
const tMem = now();
|
|
217
297
|
const [rawHistoryMessages, profileSummary] = await Promise.all([
|
|
@@ -239,16 +319,33 @@ export class ZhinAgent {
|
|
|
239
319
|
this.logPhase('path.chat', sessionId, { toolCount: 0 });
|
|
240
320
|
const liteModel = this.config.chatLiteModel || undefined;
|
|
241
321
|
const chatSystemPrompt = this.buildDisciplinedPrompt(personaEnhanced);
|
|
242
|
-
logger.
|
|
243
|
-
|
|
322
|
+
logger.debug(formatCompact({
|
|
323
|
+
mode: 'chat',
|
|
324
|
+
prompt_chars: chatSystemPrompt.length,
|
|
325
|
+
model: liteModel || chatCandidates[0] || undefined,
|
|
326
|
+
}));
|
|
244
327
|
const tLLM = now();
|
|
245
328
|
this.logPhase('chat.llm.start', sessionId, { model: liteModel || chatCandidates[0] || '' });
|
|
246
|
-
|
|
247
|
-
reply = stripHallucinatedToolCalls(
|
|
329
|
+
const chatResult = await this.streamChatWithHistory(content, chatSystemPrompt, historyMessages, onChunk, liteModel);
|
|
330
|
+
let reply = stripHallucinatedToolCalls(chatResult.content);
|
|
248
331
|
const llmMs = (now() - tLLM).toFixed(0);
|
|
249
|
-
this.logPhase('chat.llm.end', sessionId, {
|
|
250
|
-
|
|
332
|
+
this.logPhase('chat.llm.end', sessionId, {
|
|
333
|
+
durationMs: Number(llmMs),
|
|
334
|
+
...this.usageLogFields(chatResult.usage ?? undefined),
|
|
335
|
+
});
|
|
336
|
+
logger.debug(formatCompact({
|
|
337
|
+
mode: 'chat',
|
|
338
|
+
filter_ms: filterMs,
|
|
339
|
+
mem_ms: memMs,
|
|
340
|
+
llm_ms: llmMs,
|
|
341
|
+
total_ms: Math.round(now() - t0),
|
|
342
|
+
}));
|
|
251
343
|
await this.saveToSession(sessionId, content, reply, sceneId);
|
|
344
|
+
await this.finalizeActiveTurn({
|
|
345
|
+
usage: chatResult.usage ?? EMPTY_USAGE,
|
|
346
|
+
path: 'chat',
|
|
347
|
+
model: chatResult.model,
|
|
348
|
+
});
|
|
252
349
|
this.logPhase('turn.end', sessionId, { path: 'chat' });
|
|
253
350
|
return parseOutput(reply);
|
|
254
351
|
}
|
|
@@ -259,7 +356,7 @@ export class ZhinAgent {
|
|
|
259
356
|
if (preExecCandidates.length > 0) {
|
|
260
357
|
logger.debug(`预执行: ${preExecCandidates.map(t => t.name).join(', ')}`);
|
|
261
358
|
}
|
|
262
|
-
const toolRun = await planToolRun(
|
|
359
|
+
const toolRun = await planToolRun(resolvedTools, this.config.preExecTimeout);
|
|
263
360
|
this.logPhase('preexec.done', sessionId, {
|
|
264
361
|
mode: toolRun.mode,
|
|
265
362
|
preExecutedTools: toolRun.preExecution.tools.length,
|
|
@@ -275,30 +372,52 @@ export class ZhinAgent {
|
|
|
275
372
|
// Fast path
|
|
276
373
|
const tLLM = now();
|
|
277
374
|
const prompt = this.buildDisciplinedPrompt(buildPreExecFastPathPrompt(personaEnhanced, preData));
|
|
278
|
-
logger.
|
|
375
|
+
logger.debug(formatCompact({ mode: 'fast', prompt_chars: prompt.length }));
|
|
279
376
|
this.logPhase('fast.llm.start', sessionId, { model: chatCandidates[0] || '' });
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
377
|
+
const fastResult = await this.streamChatWithHistory(content, prompt, historyMessages, onChunk);
|
|
378
|
+
reply = fastResult.content;
|
|
379
|
+
this.logPhase('fast.llm.end', sessionId, {
|
|
380
|
+
durationMs: Math.round(now() - tLLM),
|
|
381
|
+
...this.usageLogFields(fastResult.usage ?? undefined),
|
|
382
|
+
});
|
|
383
|
+
logger.debug(`[快速路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, LLM=${(now() - tLLM).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
|
|
384
|
+
await this.finalizeActiveTurn({
|
|
385
|
+
usage: fastResult.usage ?? EMPTY_USAGE,
|
|
386
|
+
path: 'fast',
|
|
387
|
+
model: fastResult.model,
|
|
388
|
+
});
|
|
283
389
|
}
|
|
284
390
|
else {
|
|
285
391
|
this.logPhase('path.agent', sessionId, { toolCount: allTools.length });
|
|
286
392
|
const tAgent = now();
|
|
287
393
|
logger.debug(`Agent 路径: ${allTools.length} 个工具`);
|
|
288
394
|
const contextHint = buildContextHint(context, content);
|
|
395
|
+
const platformMarkdown = await resolveAgentPromptMarkdown({
|
|
396
|
+
ctx: {
|
|
397
|
+
slot: 'orchestrator',
|
|
398
|
+
toolContext: context,
|
|
399
|
+
toolSearch: !!this.config.toolSearch,
|
|
400
|
+
userMessagePreview: content.slice(0, 500),
|
|
401
|
+
deferred: deferredStats ? { goal: content, domainStats: deferredStats } : undefined,
|
|
402
|
+
},
|
|
403
|
+
config: this.config,
|
|
404
|
+
sessionId,
|
|
405
|
+
});
|
|
289
406
|
const richPrompt = buildRichSystemPrompt({
|
|
290
407
|
config: this.config,
|
|
291
408
|
skillRegistry: this.skillRegistry,
|
|
292
409
|
skillsSummaryXML: this.skillsSummaryXML,
|
|
293
410
|
activeSkillsContext: this.activeSkillsContext,
|
|
294
411
|
bootstrapContext: this.bootstrapContext,
|
|
412
|
+
toolSearchDeferredStats: deferredStats,
|
|
413
|
+
platformSections: platformMarkdown,
|
|
295
414
|
});
|
|
296
415
|
const systemPrompt = `${richPrompt}
|
|
297
416
|
${contextHint}
|
|
298
417
|
${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
299
|
-
logger.
|
|
418
|
+
logger.debug(formatCompact({ mode: 'agent', prompt_chars: systemPrompt.length }));
|
|
300
419
|
logger.debug(`[System Prompt Full]\n${systemPrompt}\n---END---`);
|
|
301
|
-
const agentTools = applyExecPolicyToTools(this.config,
|
|
420
|
+
const agentTools = applyExecPolicyToTools(this.config, resolvedTools);
|
|
302
421
|
// Adaptive maxIterations: boost when skills are active (multi-step skill flows)
|
|
303
422
|
const SKILL_ITERATION_BOOST = 3;
|
|
304
423
|
const hasSkillActivation = agentTools.some(t => t.name === 'activate_skill' || t.name === 'install_skill');
|
|
@@ -318,7 +437,7 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
318
437
|
orchestrationPlugin = getPlugin().root ?? getPlugin();
|
|
319
438
|
}
|
|
320
439
|
catch {
|
|
321
|
-
logger.warn(
|
|
440
|
+
logger.warn(formatCompact({ warn: 'no_host_plugin' }));
|
|
322
441
|
}
|
|
323
442
|
}
|
|
324
443
|
const agent = createAgent(this.provider, {
|
|
@@ -342,13 +461,23 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
342
461
|
try {
|
|
343
462
|
this.logPhase('agent.run.start', sessionId, { model: chatCandidates[0] || '' });
|
|
344
463
|
result = await runWithBashToolContext(contextForTools, () => agent.run(userMessageWithHistory, []));
|
|
345
|
-
this.logPhase('agent.run.end', sessionId, {
|
|
464
|
+
this.logPhase('agent.run.end', sessionId, {
|
|
465
|
+
iterations: result.iterations,
|
|
466
|
+
durationMs: Math.round(now() - tAgent),
|
|
467
|
+
...this.usageLogFields(result.usage),
|
|
468
|
+
});
|
|
346
469
|
}
|
|
347
470
|
finally {
|
|
348
471
|
agent.dispose();
|
|
349
472
|
}
|
|
350
473
|
reply = stripHallucinatedToolCalls(stripThinkBlocks(result.content)) || this.fallbackFormat(result.toolCalls);
|
|
351
|
-
logger.
|
|
474
|
+
logger.debug(`[Agent 路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, Agent=${(now() - tAgent).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
|
|
475
|
+
await this.finalizeActiveTurn({
|
|
476
|
+
usage: result.usage,
|
|
477
|
+
path: 'agent',
|
|
478
|
+
iterations: result.iterations,
|
|
479
|
+
model: chatCandidates[0] || undefined,
|
|
480
|
+
});
|
|
352
481
|
}
|
|
353
482
|
await this.saveToSession(sessionId, content, reply, sceneId);
|
|
354
483
|
triggerAIHook(createAIHookEvent('message', 'sent', sessionId, {
|
|
@@ -359,14 +488,71 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
359
488
|
this.logPhase('turn.end', sessionId, { path: toolRun.mode === 'pre-exec-fast-path' ? 'fast' : 'agent' });
|
|
360
489
|
return parseOutput(reply);
|
|
361
490
|
}
|
|
491
|
+
resolveAgentToolsForTurn(allTools, context) {
|
|
492
|
+
if (!this.config.toolSearch) {
|
|
493
|
+
return { tools: allTools };
|
|
494
|
+
}
|
|
495
|
+
const toolSearchPool = filterToolsForToolSearchCatalog(allTools);
|
|
496
|
+
const built = buildOrchestratorAgentTools({
|
|
497
|
+
allTools: toolSearchPool,
|
|
498
|
+
config: this.config,
|
|
499
|
+
context,
|
|
500
|
+
getDeferredCatalog: () => this.deferredCatalog,
|
|
501
|
+
runWorker: (goal, toolQuery) => this.runDeferredWorker(goal, toolQuery, context, toolSearchPool),
|
|
502
|
+
});
|
|
503
|
+
this.deferredCatalog = built.deferred;
|
|
504
|
+
logger.debug(formatCompact({
|
|
505
|
+
tool_search: `${built.orchestratorTools.length}+${built.deferred.length}`,
|
|
506
|
+
stats: built.domainStats,
|
|
507
|
+
}));
|
|
508
|
+
return { tools: built.orchestratorTools, deferredStats: built.domainStats };
|
|
509
|
+
}
|
|
510
|
+
async runDeferredWorker(goal, toolQuery, context, allTools) {
|
|
511
|
+
const allByName = new Map(allTools.map(t => [t.name, t]));
|
|
512
|
+
const workerBase = [];
|
|
513
|
+
for (const name of this.config.toolSearchWorkerBaseTools) {
|
|
514
|
+
const t = allByName.get(name);
|
|
515
|
+
if (t)
|
|
516
|
+
workerBase.push(t);
|
|
517
|
+
}
|
|
518
|
+
const result = await this.deferredWorkerRunner.runSync({
|
|
519
|
+
goal,
|
|
520
|
+
toolQuery,
|
|
521
|
+
deferredCatalog: this.deferredCatalog,
|
|
522
|
+
workerBaseTools: workerBase,
|
|
523
|
+
allToolsByName: allByName,
|
|
524
|
+
origin: context,
|
|
525
|
+
maxToolResults: this.config.toolSearchMaxResults,
|
|
526
|
+
execPolicyConfig: this.config,
|
|
527
|
+
modelRegistry: this.modelRegistry,
|
|
528
|
+
provider: this.provider,
|
|
529
|
+
maxIterations: this.config.maxSubagentIterations,
|
|
530
|
+
});
|
|
531
|
+
return result.summary;
|
|
532
|
+
}
|
|
362
533
|
buildDisciplinedPrompt(basePrompt) {
|
|
363
534
|
const discipline = `# Discipline\n${FIXED_DISCIPLINE_RULES.map(rule => `- ${rule}`).join('\n')}`;
|
|
364
535
|
return `${discipline}\n\n${basePrompt}`;
|
|
365
536
|
}
|
|
537
|
+
usageLogFields(usage) {
|
|
538
|
+
if (!usage)
|
|
539
|
+
return {};
|
|
540
|
+
return {
|
|
541
|
+
promptTokens: usage.prompt_tokens,
|
|
542
|
+
completionTokens: usage.completion_tokens,
|
|
543
|
+
totalTokens: usage.total_tokens,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
366
546
|
logPhase(phase, sessionId, extra = {}) {
|
|
367
547
|
if (!this.phaseTraceEnabled)
|
|
368
548
|
return;
|
|
369
|
-
|
|
549
|
+
const flat = { phase, session: sessionId };
|
|
550
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
551
|
+
if (v === undefined || v === null)
|
|
552
|
+
continue;
|
|
553
|
+
flat[k] = typeof v === 'object' ? JSON.stringify(v) : v;
|
|
554
|
+
}
|
|
555
|
+
logger.info(formatCompact(flat));
|
|
370
556
|
}
|
|
371
557
|
async processMultimodal(parts, context, onChunk) {
|
|
372
558
|
const { senderId, sceneId, platform } = context;
|
|
@@ -374,8 +560,10 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
374
560
|
const userId = senderId || 'unknown';
|
|
375
561
|
const rateCheck = this.rateLimiter.check(userId);
|
|
376
562
|
if (!rateCheck.allowed) {
|
|
563
|
+
await this.finalizeActiveTurn({ usage: EMPTY_USAGE, path: 'rate_limited' });
|
|
377
564
|
return parseOutput(rateCheck.message || '请稍后再试');
|
|
378
565
|
}
|
|
566
|
+
this.beginActiveTurn();
|
|
379
567
|
const rawHistoryMessages = await this.buildHistoryMessages(sessionId);
|
|
380
568
|
const profileSummary = await this.userProfiles.buildProfileSummary(userId);
|
|
381
569
|
const personaEnhanced = this.buildDisciplinedPrompt(buildEnhancedPersona(this.config, profileSummary, ''));
|
|
@@ -408,6 +596,17 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
408
596
|
}
|
|
409
597
|
}
|
|
410
598
|
const textContent = textFragments.join(' ') || '[多模态消息]';
|
|
599
|
+
const platformMarkdown = await resolveAgentPromptMarkdown({
|
|
600
|
+
ctx: {
|
|
601
|
+
slot: 'orchestrator',
|
|
602
|
+
toolContext: context,
|
|
603
|
+
toolSearch: !!this.config.toolSearch,
|
|
604
|
+
userMessagePreview: textContent.slice(0, 500),
|
|
605
|
+
},
|
|
606
|
+
config: this.config,
|
|
607
|
+
sessionId,
|
|
608
|
+
});
|
|
609
|
+
const visionSystemPrompt = buildLiteSystemPromptWithPlatform(personaEnhanced, platformMarkdown, buildContextHint(context, textContent));
|
|
411
610
|
const visionCandidates = this.resolveModelCandidates('vision');
|
|
412
611
|
const { messages: historyMessages, result: pruneResult } = pruneHistoryWithBudget({
|
|
413
612
|
messages: rawHistoryMessages,
|
|
@@ -420,16 +619,21 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
420
619
|
logger.debug(`[多模态上下文窗口] 丢弃 ${pruneResult.droppedCount} 条历史消息 (${pruneResult.droppedTokens} tokens)`);
|
|
421
620
|
}
|
|
422
621
|
const messages = [
|
|
423
|
-
{ role: 'system', content:
|
|
622
|
+
{ role: 'system', content: visionSystemPrompt },
|
|
424
623
|
...historyMessages,
|
|
425
624
|
{ role: 'user', content: llmParts },
|
|
426
625
|
];
|
|
427
626
|
let reply = '';
|
|
627
|
+
let lastUsage = null;
|
|
628
|
+
let usedVisionModel = visionCandidates[0] || '';
|
|
428
629
|
for (let i = 0; i < visionCandidates.length; i++) {
|
|
429
630
|
const visionModel = visionCandidates[i];
|
|
631
|
+
usedVisionModel = visionModel;
|
|
430
632
|
try {
|
|
431
633
|
reply = '';
|
|
432
634
|
for await (const chunk of this.provider.chatStream({ model: visionModel, messages })) {
|
|
635
|
+
if (chunk.usage)
|
|
636
|
+
lastUsage = chunk.usage;
|
|
433
637
|
const delta = chunk.choices?.[0]?.delta;
|
|
434
638
|
if (!delta)
|
|
435
639
|
continue;
|
|
@@ -442,8 +646,10 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
442
646
|
}
|
|
443
647
|
reply = stripThinkBlocks(reply);
|
|
444
648
|
if (!reply) {
|
|
445
|
-
logger.warn(
|
|
649
|
+
logger.warn(formatCompact({ mode: 'multimodal', fallback: visionModel, reason: 'empty_stream' }));
|
|
446
650
|
const response = await this.provider.chat({ model: visionModel, messages });
|
|
651
|
+
if (response.usage)
|
|
652
|
+
lastUsage = response.usage;
|
|
447
653
|
const msg = response.choices[0]?.message?.content;
|
|
448
654
|
reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
449
655
|
}
|
|
@@ -455,13 +661,19 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
455
661
|
if (isLast) {
|
|
456
662
|
try {
|
|
457
663
|
const response = await this.provider.chat({ model: visionModel, messages });
|
|
664
|
+
if (response.usage)
|
|
665
|
+
lastUsage = response.usage;
|
|
458
666
|
const msg = response.choices[0]?.message?.content;
|
|
459
667
|
reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
460
668
|
}
|
|
461
669
|
catch { /* all candidates exhausted */ }
|
|
462
670
|
}
|
|
463
671
|
else {
|
|
464
|
-
logger.warn(
|
|
672
|
+
logger.warn(formatCompact({
|
|
673
|
+
mode: 'multimodal',
|
|
674
|
+
fallback: `${visionModel}→${visionCandidates[i + 1]}`,
|
|
675
|
+
error: truncatePreview(err.message, 80),
|
|
676
|
+
}));
|
|
465
677
|
}
|
|
466
678
|
}
|
|
467
679
|
}
|
|
@@ -469,6 +681,11 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
469
681
|
reply = '抱歉,我无法理解这条消息。';
|
|
470
682
|
reply = stripHallucinatedToolCalls(reply);
|
|
471
683
|
await this.saveToSession(sessionId, textContent, reply, sceneId);
|
|
684
|
+
await this.finalizeActiveTurn({
|
|
685
|
+
usage: lastUsage ?? EMPTY_USAGE,
|
|
686
|
+
path: 'multimodal',
|
|
687
|
+
model: usedVisionModel,
|
|
688
|
+
});
|
|
472
689
|
return parseOutput(reply);
|
|
473
690
|
}
|
|
474
691
|
// ── Internal helpers ────────────────────────────────────────────────
|
|
@@ -500,6 +717,7 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
500
717
|
}
|
|
501
718
|
async streamChatWithHistory(content, systemPrompt, history, onChunk, preferredModel) {
|
|
502
719
|
const candidates = this.resolveModelCandidates('chat', preferredModel);
|
|
720
|
+
const empty = (model) => ({ content: '', usage: null, model });
|
|
503
721
|
const turnTimeout = this.config.timeout;
|
|
504
722
|
const userContent = history.length > 0
|
|
505
723
|
? buildUserMessageWithHistory(history, content)
|
|
@@ -535,21 +753,19 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
535
753
|
}
|
|
536
754
|
}
|
|
537
755
|
})());
|
|
538
|
-
if (lastUsage) {
|
|
539
|
-
logger.info(`[闲聊] token 用量: prompt=${lastUsage.prompt_tokens}, completion=${lastUsage.completion_tokens}, total=${lastUsage.total_tokens} (model=${model})`);
|
|
540
|
-
}
|
|
541
756
|
result = stripThinkBlocks(result);
|
|
542
|
-
if (result)
|
|
543
|
-
return result;
|
|
757
|
+
if (result) {
|
|
758
|
+
return { content: result, usage: lastUsage, model };
|
|
759
|
+
}
|
|
544
760
|
// Streaming returned empty content — try non-streaming with same model
|
|
545
|
-
logger.warn(
|
|
761
|
+
logger.warn(formatCompact({ stream: 'empty', model }));
|
|
546
762
|
const response = await withTurnTimeout(this.provider.chat({ model, messages }));
|
|
547
763
|
const msg = response.choices?.[0]?.message?.content;
|
|
548
764
|
result = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
549
765
|
if (result) {
|
|
550
766
|
if (onChunk)
|
|
551
767
|
onChunk(result, result);
|
|
552
|
-
return result;
|
|
768
|
+
return { content: result, usage: response.usage ?? null, model };
|
|
553
769
|
}
|
|
554
770
|
}
|
|
555
771
|
catch (err) {
|
|
@@ -562,16 +778,22 @@ ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
|
|
|
562
778
|
let result = stripThinkBlocks(typeof msg === 'string' ? msg : '');
|
|
563
779
|
if (onChunk && result)
|
|
564
780
|
onChunk(result, result);
|
|
565
|
-
|
|
781
|
+
if (result) {
|
|
782
|
+
return { content: result, usage: response.usage ?? null, model };
|
|
783
|
+
}
|
|
784
|
+
return empty(model);
|
|
566
785
|
}
|
|
567
786
|
catch {
|
|
568
|
-
return
|
|
787
|
+
return empty(model);
|
|
569
788
|
}
|
|
570
789
|
}
|
|
571
|
-
logger.warn(
|
|
790
|
+
logger.warn(formatCompact({
|
|
791
|
+
fallback: `${model}→${candidates[i + 1]}`,
|
|
792
|
+
error: truncatePreview(err.message, 80),
|
|
793
|
+
}));
|
|
572
794
|
}
|
|
573
795
|
}
|
|
574
|
-
return '';
|
|
796
|
+
return empty(candidates[0] || '');
|
|
575
797
|
}
|
|
576
798
|
async saveToSession(sessionId, userContent, assistantContent, sceneId) {
|
|
577
799
|
await this.memory.saveRound(sessionId, userContent, assistantContent);
|