@zhin.js/agent 0.1.25 → 0.1.26

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.
Files changed (307) hide show
  1. package/lib/agent-prompt/registry.js +1 -1
  2. package/lib/agent-prompt/registry.js.map +1 -1
  3. package/lib/ai-event-bus.d.ts +19 -0
  4. package/lib/ai-event-bus.d.ts.map +1 -0
  5. package/lib/ai-event-bus.js +75 -0
  6. package/lib/ai-event-bus.js.map +1 -0
  7. package/lib/ai-event-subscriber.d.ts +38 -0
  8. package/lib/ai-event-subscriber.d.ts.map +1 -0
  9. package/lib/ai-event-subscriber.js +61 -0
  10. package/lib/ai-event-subscriber.js.map +1 -0
  11. package/lib/builtin/ask-user-tool.d.ts +2 -0
  12. package/lib/builtin/ask-user-tool.d.ts.map +1 -1
  13. package/lib/builtin/ask-user-tool.js +2 -0
  14. package/lib/builtin/ask-user-tool.js.map +1 -1
  15. package/lib/builtin/bash-tool.d.ts +7 -3
  16. package/lib/builtin/bash-tool.d.ts.map +1 -1
  17. package/lib/builtin/bash-tool.js +51 -9
  18. package/lib/builtin/bash-tool.js.map +1 -1
  19. package/lib/builtin/bing-search-html.d.ts.map +1 -1
  20. package/lib/builtin/bing-search-html.js +14 -7
  21. package/lib/builtin/bing-search-html.js.map +1 -1
  22. package/lib/builtin/builtin-base-tool.d.ts +2 -0
  23. package/lib/builtin/builtin-base-tool.d.ts.map +1 -1
  24. package/lib/builtin/builtin-base-tool.js +5 -0
  25. package/lib/builtin/builtin-base-tool.js.map +1 -1
  26. package/lib/builtin/edit-file-tool.d.ts +1 -1
  27. package/lib/builtin/edit-file-tool.d.ts.map +1 -1
  28. package/lib/builtin/edit-file-tool.js +33 -1
  29. package/lib/builtin/edit-file-tool.js.map +1 -1
  30. package/lib/builtin/file-edit-quote-utils.d.ts +1 -3
  31. package/lib/builtin/file-edit-quote-utils.d.ts.map +1 -1
  32. package/lib/builtin/file-edit-quote-utils.js +1 -3
  33. package/lib/builtin/file-edit-quote-utils.js.map +1 -1
  34. package/lib/builtin/glob-tool.d.ts +1 -1
  35. package/lib/builtin/glob-tool.d.ts.map +1 -1
  36. package/lib/builtin/glob-tool.js +15 -3
  37. package/lib/builtin/glob-tool.js.map +1 -1
  38. package/lib/builtin/grep-tool.d.ts +1 -1
  39. package/lib/builtin/grep-tool.d.ts.map +1 -1
  40. package/lib/builtin/grep-tool.js +16 -3
  41. package/lib/builtin/grep-tool.js.map +1 -1
  42. package/lib/builtin/list-dir-tool.d.ts +1 -1
  43. package/lib/builtin/list-dir-tool.d.ts.map +1 -1
  44. package/lib/builtin/list-dir-tool.js +14 -3
  45. package/lib/builtin/list-dir-tool.js.map +1 -1
  46. package/lib/builtin/read-file-tool.d.ts +1 -1
  47. package/lib/builtin/read-file-tool.d.ts.map +1 -1
  48. package/lib/builtin/read-file-tool.js +24 -3
  49. package/lib/builtin/read-file-tool.js.map +1 -1
  50. package/lib/builtin/run-deferred-task-tool.d.ts +3 -0
  51. package/lib/builtin/run-deferred-task-tool.d.ts.map +1 -1
  52. package/lib/builtin/run-deferred-task-tool.js +2 -0
  53. package/lib/builtin/run-deferred-task-tool.js.map +1 -1
  54. package/lib/builtin/spawn-task-tool.d.ts.map +1 -1
  55. package/lib/builtin/spawn-task-tool.js +2 -0
  56. package/lib/builtin/spawn-task-tool.js.map +1 -1
  57. package/lib/builtin/web-fetch-tool.d.ts +2 -2
  58. package/lib/builtin/web-fetch-tool.d.ts.map +1 -1
  59. package/lib/builtin/web-fetch-tool.js +12 -18
  60. package/lib/builtin/web-fetch-tool.js.map +1 -1
  61. package/lib/builtin/write-file-tool.d.ts +1 -1
  62. package/lib/builtin/write-file-tool.d.ts.map +1 -1
  63. package/lib/builtin/write-file-tool.js +33 -1
  64. package/lib/builtin/write-file-tool.js.map +1 -1
  65. package/lib/common-adapter-tools.d.ts +8 -41
  66. package/lib/common-adapter-tools.d.ts.map +1 -1
  67. package/lib/common-adapter-tools.js +23 -182
  68. package/lib/common-adapter-tools.js.map +1 -1
  69. package/lib/deferred-worker-runner.d.ts +13 -2
  70. package/lib/deferred-worker-runner.d.ts.map +1 -1
  71. package/lib/deferred-worker-runner.js +91 -13
  72. package/lib/deferred-worker-runner.js.map +1 -1
  73. package/lib/discovery/agents.js +1 -1
  74. package/lib/discovery/agents.js.map +1 -1
  75. package/lib/discovery/skills.js +1 -1
  76. package/lib/discovery/skills.js.map +1 -1
  77. package/lib/discovery/tools.d.ts +1 -1
  78. package/lib/discovery/tools.d.ts.map +1 -1
  79. package/lib/discovery/tools.js +17 -4
  80. package/lib/discovery/tools.js.map +1 -1
  81. package/lib/discovery/utils.js +1 -1
  82. package/lib/discovery/utils.js.map +1 -1
  83. package/lib/hooks.d.ts.map +1 -1
  84. package/lib/hooks.js +2 -0
  85. package/lib/hooks.js.map +1 -1
  86. package/lib/index.d.ts +9 -1
  87. package/lib/index.d.ts.map +1 -1
  88. package/lib/index.js +6 -0
  89. package/lib/index.js.map +1 -1
  90. package/lib/init/message-media.d.ts +3 -1
  91. package/lib/init/message-media.d.ts.map +1 -1
  92. package/lib/init/message-media.js +20 -0
  93. package/lib/init/message-media.js.map +1 -1
  94. package/lib/init/register-ai-service.js.map +1 -1
  95. package/lib/init/register-ai-trigger.d.ts.map +1 -1
  96. package/lib/init/register-ai-trigger.js +71 -10
  97. package/lib/init/register-ai-trigger.js.map +1 -1
  98. package/lib/init/register-builtin-tools.js +7 -7
  99. package/lib/init/register-builtin-tools.js.map +1 -1
  100. package/lib/init/register-typing-indicator.d.ts +9 -0
  101. package/lib/init/register-typing-indicator.d.ts.map +1 -0
  102. package/lib/init/register-typing-indicator.js +206 -0
  103. package/lib/init/register-typing-indicator.js.map +1 -0
  104. package/lib/init.d.ts.map +1 -1
  105. package/lib/init.js +2 -0
  106. package/lib/init.js.map +1 -1
  107. package/lib/mcp-client/connection.d.ts +2 -0
  108. package/lib/mcp-client/connection.d.ts.map +1 -1
  109. package/lib/mcp-client/connection.js +7 -0
  110. package/lib/mcp-client/connection.js.map +1 -1
  111. package/lib/mcp-client/index.d.ts +2 -0
  112. package/lib/mcp-client/index.d.ts.map +1 -1
  113. package/lib/mcp-client/index.js +19 -2
  114. package/lib/mcp-client/index.js.map +1 -1
  115. package/lib/monitoring/index.d.ts +79 -0
  116. package/lib/monitoring/index.d.ts.map +1 -0
  117. package/lib/monitoring/index.js +197 -0
  118. package/lib/monitoring/index.js.map +1 -0
  119. package/lib/monitoring/metrics.d.ts +240 -0
  120. package/lib/monitoring/metrics.d.ts.map +1 -0
  121. package/lib/monitoring/metrics.js +483 -0
  122. package/lib/monitoring/metrics.js.map +1 -0
  123. package/lib/orchestrator/agent-dispatcher.d.ts +170 -0
  124. package/lib/orchestrator/agent-dispatcher.d.ts.map +1 -0
  125. package/lib/orchestrator/agent-dispatcher.js +379 -0
  126. package/lib/orchestrator/agent-dispatcher.js.map +1 -0
  127. package/lib/orchestrator/hook-registry.d.ts.map +1 -1
  128. package/lib/orchestrator/hook-registry.js +2 -0
  129. package/lib/orchestrator/hook-registry.js.map +1 -1
  130. package/lib/orchestrator/index.d.ts +2 -0
  131. package/lib/orchestrator/index.d.ts.map +1 -1
  132. package/lib/orchestrator/index.js +2 -0
  133. package/lib/orchestrator/index.js.map +1 -1
  134. package/lib/orchestrator/mcp-lifecycle.d.ts +1 -1
  135. package/lib/orchestrator/mcp-lifecycle.d.ts.map +1 -1
  136. package/lib/orchestrator/mcp-lifecycle.js +2 -2
  137. package/lib/orchestrator/mcp-lifecycle.js.map +1 -1
  138. package/lib/orchestrator/mcp-registry.d.ts +8 -1
  139. package/lib/orchestrator/mcp-registry.d.ts.map +1 -1
  140. package/lib/orchestrator/mcp-registry.js +28 -5
  141. package/lib/orchestrator/mcp-registry.js.map +1 -1
  142. package/lib/orchestrator/task-queue.d.ts +147 -0
  143. package/lib/orchestrator/task-queue.d.ts.map +1 -0
  144. package/lib/orchestrator/task-queue.js +337 -0
  145. package/lib/orchestrator/task-queue.js.map +1 -0
  146. package/lib/orchestrator/tool-selection.d.ts.map +1 -1
  147. package/lib/orchestrator/tool-selection.js +3 -0
  148. package/lib/orchestrator/tool-selection.js.map +1 -1
  149. package/lib/orchestrator/types.d.ts +8 -0
  150. package/lib/orchestrator/types.d.ts.map +1 -1
  151. package/lib/plugin-ai-hook-bus.d.ts +4 -0
  152. package/lib/plugin-ai-hook-bus.d.ts.map +1 -0
  153. package/lib/plugin-ai-hook-bus.js +24 -0
  154. package/lib/plugin-ai-hook-bus.js.map +1 -0
  155. package/lib/security/anomaly-detection.d.ts +119 -0
  156. package/lib/security/anomaly-detection.d.ts.map +1 -0
  157. package/lib/security/anomaly-detection.js +305 -0
  158. package/lib/security/anomaly-detection.js.map +1 -0
  159. package/lib/security/audit-logger.d.ts +158 -0
  160. package/lib/security/audit-logger.d.ts.map +1 -0
  161. package/lib/security/audit-logger.js +379 -0
  162. package/lib/security/audit-logger.js.map +1 -0
  163. package/lib/security/bash-tool-context.d.ts +3 -3
  164. package/lib/security/bash-tool-context.d.ts.map +1 -1
  165. package/lib/security/budget-limiter.d.ts +156 -0
  166. package/lib/security/budget-limiter.d.ts.map +1 -0
  167. package/lib/security/budget-limiter.js +363 -0
  168. package/lib/security/budget-limiter.js.map +1 -0
  169. package/lib/security/dangerous-tool-policy.d.ts +17 -0
  170. package/lib/security/dangerous-tool-policy.d.ts.map +1 -0
  171. package/lib/security/dangerous-tool-policy.js +137 -0
  172. package/lib/security/dangerous-tool-policy.js.map +1 -0
  173. package/lib/security/exec-policy.d.ts +21 -12
  174. package/lib/security/exec-policy.d.ts.map +1 -1
  175. package/lib/security/exec-policy.js +198 -25
  176. package/lib/security/exec-policy.js.map +1 -1
  177. package/lib/security/file-policy.d.ts +0 -1
  178. package/lib/security/file-policy.d.ts.map +1 -1
  179. package/lib/security/file-policy.js +112 -14
  180. package/lib/security/file-policy.js.map +1 -1
  181. package/lib/security/file-role-policy.d.ts +119 -0
  182. package/lib/security/file-role-policy.d.ts.map +1 -0
  183. package/lib/security/file-role-policy.js +268 -0
  184. package/lib/security/file-role-policy.js.map +1 -0
  185. package/lib/security/index.d.ts +59 -0
  186. package/lib/security/index.d.ts.map +1 -1
  187. package/lib/security/index.js +68 -0
  188. package/lib/security/index.js.map +1 -1
  189. package/lib/security/network-policy.d.ts +94 -0
  190. package/lib/security/network-policy.d.ts.map +1 -0
  191. package/lib/security/network-policy.js +291 -0
  192. package/lib/security/network-policy.js.map +1 -0
  193. package/lib/security/owner-approve-always-store.d.ts +2 -0
  194. package/lib/security/owner-approve-always-store.d.ts.map +1 -1
  195. package/lib/security/owner-approve-always-store.js +37 -0
  196. package/lib/security/owner-approve-always-store.js.map +1 -1
  197. package/lib/security/sandbox-enhanced.d.ts +170 -0
  198. package/lib/security/sandbox-enhanced.d.ts.map +1 -0
  199. package/lib/security/sandbox-enhanced.js +630 -0
  200. package/lib/security/sandbox-enhanced.js.map +1 -0
  201. package/lib/security/sandbox.d.ts +104 -0
  202. package/lib/security/sandbox.d.ts.map +1 -0
  203. package/lib/security/sandbox.js +373 -0
  204. package/lib/security/sandbox.js.map +1 -0
  205. package/lib/security/security-config.example.d.ts +33 -0
  206. package/lib/security/security-config.example.d.ts.map +1 -0
  207. package/lib/security/security-config.example.js +166 -0
  208. package/lib/security/security-config.example.js.map +1 -0
  209. package/lib/subagent-goal-notify.d.ts +13 -0
  210. package/lib/subagent-goal-notify.d.ts.map +1 -0
  211. package/lib/subagent-goal-notify.js +32 -0
  212. package/lib/subagent-goal-notify.js.map +1 -0
  213. package/lib/subagent.d.ts +24 -0
  214. package/lib/subagent.d.ts.map +1 -1
  215. package/lib/subagent.js +98 -16
  216. package/lib/subagent.js.map +1 -1
  217. package/lib/typing-indicator/adapter-integration.d.ts +140 -0
  218. package/lib/typing-indicator/adapter-integration.d.ts.map +1 -0
  219. package/lib/typing-indicator/adapter-integration.js +473 -0
  220. package/lib/typing-indicator/adapter-integration.js.map +1 -0
  221. package/lib/typing-indicator/index.d.ts +170 -0
  222. package/lib/typing-indicator/index.d.ts.map +1 -0
  223. package/lib/typing-indicator/index.js +311 -0
  224. package/lib/typing-indicator/index.js.map +1 -0
  225. package/lib/typing-indicator/integration.d.ts +91 -0
  226. package/lib/typing-indicator/integration.d.ts.map +1 -0
  227. package/lib/typing-indicator/integration.js +219 -0
  228. package/lib/typing-indicator/integration.js.map +1 -0
  229. package/lib/zhin-agent/config.d.ts +20 -2
  230. package/lib/zhin-agent/config.d.ts.map +1 -1
  231. package/lib/zhin-agent/config.js +27 -9
  232. package/lib/zhin-agent/config.js.map +1 -1
  233. package/lib/zhin-agent/event-emitter.d.ts +11 -0
  234. package/lib/zhin-agent/event-emitter.d.ts.map +1 -0
  235. package/lib/zhin-agent/event-emitter.js +43 -0
  236. package/lib/zhin-agent/event-emitter.js.map +1 -0
  237. package/lib/zhin-agent/index.d.ts +9 -31
  238. package/lib/zhin-agent/index.d.ts.map +1 -1
  239. package/lib/zhin-agent/index.js +70 -672
  240. package/lib/zhin-agent/index.js.map +1 -1
  241. package/lib/zhin-agent/llm-runner.d.ts +16 -0
  242. package/lib/zhin-agent/llm-runner.d.ts.map +1 -0
  243. package/lib/zhin-agent/llm-runner.js +85 -0
  244. package/lib/zhin-agent/llm-runner.js.map +1 -0
  245. package/lib/zhin-agent/model-resolver.d.ts +9 -0
  246. package/lib/zhin-agent/model-resolver.d.ts.map +1 -0
  247. package/lib/zhin-agent/model-resolver.js +25 -0
  248. package/lib/zhin-agent/model-resolver.js.map +1 -0
  249. package/lib/zhin-agent/phase-trace.d.ts +12 -0
  250. package/lib/zhin-agent/phase-trace.d.ts.map +1 -0
  251. package/lib/zhin-agent/phase-trace.js +24 -0
  252. package/lib/zhin-agent/phase-trace.js.map +1 -0
  253. package/lib/zhin-agent/prompt-assembly.d.ts +22 -0
  254. package/lib/zhin-agent/prompt-assembly.d.ts.map +1 -0
  255. package/lib/zhin-agent/prompt-assembly.js +66 -0
  256. package/lib/zhin-agent/prompt-assembly.js.map +1 -0
  257. package/lib/zhin-agent/prompt-builder.d.ts +200 -0
  258. package/lib/zhin-agent/prompt-builder.d.ts.map +1 -0
  259. package/lib/zhin-agent/prompt-builder.js +574 -0
  260. package/lib/zhin-agent/prompt-builder.js.map +1 -0
  261. package/lib/zhin-agent/prompt-templates.d.ts +160 -0
  262. package/lib/zhin-agent/prompt-templates.d.ts.map +1 -0
  263. package/lib/zhin-agent/prompt-templates.js +436 -0
  264. package/lib/zhin-agent/prompt-templates.js.map +1 -0
  265. package/lib/zhin-agent/prompt.d.ts +15 -0
  266. package/lib/zhin-agent/prompt.d.ts.map +1 -1
  267. package/lib/zhin-agent/prompt.js +149 -4
  268. package/lib/zhin-agent/prompt.js.map +1 -1
  269. package/lib/zhin-agent/session-io.d.ts +10 -0
  270. package/lib/zhin-agent/session-io.d.ts.map +1 -0
  271. package/lib/zhin-agent/session-io.js +14 -0
  272. package/lib/zhin-agent/session-io.js.map +1 -0
  273. package/lib/zhin-agent/task-continuation.d.ts +144 -0
  274. package/lib/zhin-agent/task-continuation.d.ts.map +1 -0
  275. package/lib/zhin-agent/task-continuation.js +346 -0
  276. package/lib/zhin-agent/task-continuation.js.map +1 -0
  277. package/lib/zhin-agent/text-sanitize.d.ts +2 -5
  278. package/lib/zhin-agent/text-sanitize.d.ts.map +1 -1
  279. package/lib/zhin-agent/text-sanitize.js +2 -25
  280. package/lib/zhin-agent/text-sanitize.js.map +1 -1
  281. package/lib/zhin-agent/tool-calls-user-format.d.ts.map +1 -1
  282. package/lib/zhin-agent/tool-calls-user-format.js +12 -3
  283. package/lib/zhin-agent/tool-calls-user-format.js.map +1 -1
  284. package/lib/zhin-agent/tool-orchestration.d.ts +9 -0
  285. package/lib/zhin-agent/tool-orchestration.d.ts.map +1 -0
  286. package/lib/zhin-agent/tool-orchestration.js +68 -0
  287. package/lib/zhin-agent/tool-orchestration.js.map +1 -0
  288. package/lib/zhin-agent/tool-search-orchestrator.d.ts.map +1 -1
  289. package/lib/zhin-agent/tool-search-orchestrator.js +5 -2
  290. package/lib/zhin-agent/tool-search-orchestrator.js.map +1 -1
  291. package/lib/zhin-agent/turn-pipeline.d.ts +5 -0
  292. package/lib/zhin-agent/turn-pipeline.d.ts.map +1 -0
  293. package/lib/zhin-agent/turn-pipeline.js +541 -0
  294. package/lib/zhin-agent/turn-pipeline.js.map +1 -0
  295. package/lib/zhin-agent/turn-tracker.d.ts +17 -0
  296. package/lib/zhin-agent/turn-tracker.d.ts.map +1 -0
  297. package/lib/zhin-agent/turn-tracker.js +54 -0
  298. package/lib/zhin-agent/turn-tracker.js.map +1 -0
  299. package/lib/zhin-agent/web-search-locale-attach.d.ts +4 -0
  300. package/lib/zhin-agent/web-search-locale-attach.d.ts.map +1 -0
  301. package/lib/zhin-agent/web-search-locale-attach.js +19 -0
  302. package/lib/zhin-agent/web-search-locale-attach.js.map +1 -0
  303. package/lib/zhin-agent/zhin-agent-private.d.ts +66 -0
  304. package/lib/zhin-agent/zhin-agent-private.d.ts.map +1 -0
  305. package/lib/zhin-agent/zhin-agent-private.js +4 -0
  306. package/lib/zhin-agent/zhin-agent-private.js.map +1 -0
  307. package/package.json +5 -5
@@ -15,39 +15,25 @@
15
15
  * 11. 定时任务:cron_add 持久化定时任务
16
16
  * 12. 多模态输入:图片/音频直接传给视觉模型
17
17
  */
18
- import { getPlugin, Logger } from '@zhin.js/core';
19
- import { formatCompact, truncatePreview } from '@zhin.js/logger';
20
- import { ensureMcpConnections } from '../orchestrator/mcp-lifecycle.js';
21
- import { createAgent } from '@zhin.js/ai';
18
+ import { Logger } from '@zhin.js/logger';
22
19
  import { SessionManager, createMemorySessionManager } from '@zhin.js/ai';
23
20
  import { ConversationMemory } from '@zhin.js/ai';
24
- import { parseOutput } from '@zhin.js/ai';
25
- import { addUsage, EMPTY_USAGE, } from './turn-metrics.js';
21
+ import { TurnTracker } from './turn-tracker.js';
22
+ import { ZhinAgentEventEmitter } from './event-emitter.js';
26
23
  import { UserProfileStore } from '../user-profile.js';
27
- import { WEB_SEARCH_LOCALE_EXTRA_KEY, normalizeWebSearchLocaleHint, } from '../builtin/web-search-locale.js';
28
24
  import { RateLimiter } from '@zhin.js/ai';
29
- import { detectTone } from '@zhin.js/ai';
30
25
  import { SubagentManager } from '../subagent.js';
31
- import { triggerAIHook, createAIHookEvent } from '../hooks.js';
32
- import { resolveAgentPromptMarkdown } from '../agent-prompt/index.js';
33
- // ── Sub-modules ─────────────────────────────────────────────────────
34
26
  import { DEFAULT_CONFIG, isPhaseTraceEnabled, } from './config.js';
35
- import { applyExecPolicyToTools } from '../security/exec-policy.js';
36
- import { runWithBashToolContext } from '../security/bash-tool-context.js';
37
- import { buildEnhancedPersona, buildContextHint, buildRichSystemPrompt, buildLiteSystemPromptWithPlatform, buildUserMessageWithHistory, FIXED_DISCIPLINE_RULES, } from './prompt.js';
38
- import { buildPreExecFastPathPrompt, collectRuntimeTools, planToolRun, } from './tool-runtime.js';
39
27
  import { DeferredWorkerRunner } from '../deferred-worker-runner.js';
40
- import { buildOrchestratorAgentTools } from './tool-search-orchestrator.js';
41
- import { filterToolsForToolSearchCatalog } from './tool-catalog.js';
42
- import { sanitizeAssistantReply, stripThinkBlocks } from './text-sanitize.js';
43
- import { formatToolCallsForUser } from './tool-calls-user-format.js';
44
- import { pruneHistoryWithBudget } from './context-budget.js';
45
- import { resolveModelHarness } from './model-harness.js';
46
- import { RESERVED_TOOL_NAMES, RESERVED_TOOL_NAME_PREFIXES } from '../reserved-tools.js';
47
- import { createOwnerOrchestratedToolResultTransform } from '../orchestrator/owner-confirm-orchestration.js';
28
+ import { processTextTurn, processMultimodalTurn } from './turn-pipeline.js';
29
+ import { asPrivate } from './zhin-agent-private.js';
30
+ import { buildDisciplinedPrompt as assembleDisciplinedPrompt } from './prompt-assembly.js';
31
+ import { resolveAgentToolsForTurn as resolveToolsForTurn, runDeferredWorker as runDeferredWorkerTurn, } from './tool-orchestration.js';
48
32
  export { formatAiHandlerCompleteLog, formatZhinAgentTurnUsage } from './turn-metrics.js';
33
+ export * from './prompt-builder.js';
34
+ export * from './prompt-templates.js';
35
+ export * from './task-continuation.js';
49
36
  const logger = new Logger(null, 'ZhinAgent');
50
- const now = () => performance.now();
51
37
  // ============================================================================
52
38
  // ZhinAgent
53
39
  // ============================================================================
@@ -68,14 +54,14 @@ export class ZhinAgent {
68
54
  skillsSummaryXML = '';
69
55
  modelRegistry = null;
70
56
  phaseTraceEnabled;
71
- /** 根插件(createZhinAgentContext 注入);避免在 Agent.run 路径依赖 AsyncLocalStorage 上的 getPlugin() */
72
- hostPlugin = null;
57
+ emitter = new ZhinAgentEventEmitter();
73
58
  deferredCatalog = [];
74
59
  deferredWorkerRunner = new DeferredWorkerRunner();
75
60
  lastToolSearchDeferredStats;
76
- lastTurnMetrics = null;
77
- activeTurnSubagentUsage = null;
78
- activeTurnSubagentWaits = [];
61
+ turnTracker;
62
+ get phaseConfig() {
63
+ return { phaseTraceEnabled: this.phaseTraceEnabled, onPhaseTrace: this.config.onPhaseTrace };
64
+ }
79
65
  constructor(provider, config) {
80
66
  this.provider = provider;
81
67
  this.config = { ...DEFAULT_CONFIG, ...config };
@@ -90,6 +76,7 @@ export class ZhinAgent {
90
76
  this.memory.setProvider(provider);
91
77
  this.userProfiles = new UserProfileStore();
92
78
  this.rateLimiter = new RateLimiter(this.config.rateLimit);
79
+ this.turnTracker = new TurnTracker(this.config.subagentTurnWaitMs);
93
80
  }
94
81
  // ── DI setters ──────────────────────────────────────────────────────
95
82
  setSkillRegistry(registry) {
@@ -112,9 +99,8 @@ export class ZhinAgent {
112
99
  this.modelRegistry = registry;
113
100
  this.subagentManager?.setModelRegistry(registry);
114
101
  }
115
- /** 由 init 注入根插件,供 Owner 编排 / ask_user(勿依赖消息处理时的 getPlugin ALS) */
116
102
  setHostPlugin(plugin) {
117
- this.hostPlugin = plugin.root ?? plugin;
103
+ this.emitter.setHostPlugin(plugin);
118
104
  }
119
105
  upgradeMemoryToDatabase(msgModel, sumModel) {
120
106
  this.memory.upgradeToDatabase(msgModel, sumModel);
@@ -131,8 +117,35 @@ export class ZhinAgent {
131
117
  maxIterations: this.config.maxSubagentIterations,
132
118
  execPolicyConfig: this.config,
133
119
  modelRegistry: this.modelRegistry,
134
- onSubagentUsage: (usage) => this.addActiveTurnSubagentUsage(usage),
135
- registerSubagentTask: (done) => this.trackActiveTurnSubagent(done),
120
+ onSubagentUsage: (usage) => this.turnTracker.addSubagentUsage(usage),
121
+ registerSubagentTask: (done) => this.turnTracker.trackSubagent(done),
122
+ onEvent: (event) => {
123
+ const sessionId = SessionManager.generateId(event.origin.platform || '', event.origin.senderId || '', event.origin.sceneId);
124
+ const payload = this.emitter.createPayload(sessionId, {
125
+ platform: event.origin.platform,
126
+ botId: event.origin.botId,
127
+ sceneId: event.origin.sceneId,
128
+ senderId: event.origin.senderId,
129
+ scope: event.origin.sceneType === 'group' ? 'group' : 'private',
130
+ }, 'text', {
131
+ path: 'agent',
132
+ taskId: event.taskId,
133
+ label: event.label,
134
+ content: event.task,
135
+ status: event.status,
136
+ error: event.error,
137
+ reply: event.result,
138
+ });
139
+ if (event.phase === 'spawn') {
140
+ this.emitter.emit('ai.subagent.spawn', payload);
141
+ }
142
+ else if (event.phase === 'start') {
143
+ this.emitter.emit('ai.subagent.start', payload);
144
+ }
145
+ else {
146
+ this.emitter.emit('ai.subagent.finish', payload);
147
+ }
148
+ },
136
149
  });
137
150
  logger.debug('SubagentManager initialized');
138
151
  }
@@ -147,38 +160,6 @@ export class ZhinAgent {
147
160
  getUserProfiles() {
148
161
  return this.userProfiles;
149
162
  }
150
- /** 根据任务类型选择最合适的模型,优先使用 config 显式指定,其次 ModelRegistry,回退到 provider.models[0] */
151
- resolveModel(task = 'chat', preferred) {
152
- return this.resolveModelCandidates(task, preferred)[0];
153
- }
154
- /**
155
- * 返回按优先级排序的候选模型列表(用于自动降级)。
156
- * 第一个是最优选择,失败后依次尝试后续模型。
157
- */
158
- resolveModelCandidates(task = 'chat', preferred) {
159
- const candidates = [];
160
- // 1. 用户显式指定 / 配置指定优先
161
- if (preferred)
162
- candidates.push(preferred);
163
- if (task === 'chat' && this.config.chatModel && !candidates.includes(this.config.chatModel)) {
164
- candidates.push(this.config.chatModel);
165
- }
166
- if (task === 'vision' && this.config.visionModel && !candidates.includes(this.config.visionModel)) {
167
- candidates.push(this.config.visionModel);
168
- }
169
- // 2. ModelRegistry 自动排序的候选列表
170
- if (this.modelRegistry) {
171
- for (const id of this.modelRegistry.selectModels(this.provider.name, task, 5)) {
172
- if (!candidates.includes(id))
173
- candidates.push(id);
174
- }
175
- }
176
- // 3. 兜底: provider.models[0]
177
- const fallback = this.provider.models[0];
178
- if (fallback && !candidates.includes(fallback))
179
- candidates.push(fallback);
180
- return candidates;
181
- }
182
163
  registerTool(tool) {
183
164
  this.externalTools.set(tool.name, tool);
184
165
  return () => { this.externalTools.delete(tool.name); };
@@ -193,632 +174,49 @@ export class ZhinAgent {
193
174
  setSkillsSummaryXML(xml) {
194
175
  this.skillsSummaryXML = xml || '';
195
176
  }
196
- /** 上一轮 `process` / `processMultimodal` 的 token 等指标(供 AI Handler 打汇总日志) */
197
177
  getLastTurnMetrics() {
198
- return this.lastTurnMetrics;
178
+ return this.turnTracker.lastMetrics;
199
179
  }
200
180
  beginActiveTurn() {
201
- this.activeTurnSubagentUsage = { ...EMPTY_USAGE };
202
- this.activeTurnSubagentWaits = [];
181
+ this.turnTracker.begin();
203
182
  }
204
- addActiveTurnSubagentUsage(usage) {
205
- if (!this.activeTurnSubagentUsage)
206
- return;
207
- addUsage(this.activeTurnSubagentUsage, usage);
183
+ async finalizeActiveTurn(partial) {
184
+ await this.turnTracker.finalize(partial);
208
185
  }
209
- trackActiveTurnSubagent(done) {
210
- if (!this.activeTurnSubagentUsage)
211
- return;
212
- this.activeTurnSubagentWaits.push(done);
186
+ emitSessionNewEvent(sessionId, context, mode, content, reply) {
187
+ this.emitter.emit('ai.session.new', this.emitter.createPayload(sessionId, context, mode, {
188
+ reason: 'first_message',
189
+ content,
190
+ reply,
191
+ }));
213
192
  }
214
- async finalizeActiveTurn(partial) {
215
- const waitMs = this.config.subagentTurnWaitMs;
216
- if (this.activeTurnSubagentWaits.length > 0 && waitMs > 0) {
217
- await Promise.race([
218
- Promise.allSettled(this.activeTurnSubagentWaits),
219
- new Promise(resolve => setTimeout(resolve, waitMs)),
220
- ]);
221
- }
222
- const mainUsage = { ...partial.usage };
223
- const subagentUsage = this.activeTurnSubagentUsage
224
- && (this.activeTurnSubagentUsage.total_tokens > 0
225
- || this.activeTurnSubagentUsage.prompt_tokens > 0
226
- || this.activeTurnSubagentUsage.completion_tokens > 0)
227
- ? { ...this.activeTurnSubagentUsage }
228
- : undefined;
229
- const totalUsage = { ...mainUsage };
230
- if (subagentUsage)
231
- addUsage(totalUsage, subagentUsage);
232
- this.lastTurnMetrics = {
233
- ...partial,
234
- usage: totalUsage,
235
- mainUsage,
236
- ...(subagentUsage ? { subagentUsage } : {}),
237
- };
238
- this.activeTurnSubagentUsage = null;
239
- this.activeTurnSubagentWaits = [];
193
+ emitSessionCompactEvent(sessionId, context, mode, info) {
194
+ this.emitter.emit('ai.session.compact', this.emitter.createPayload(sessionId, context, mode, {
195
+ path: 'agent',
196
+ compactedCount: 1,
197
+ savedTokens: info.microSavedTokens + info.autoSavedTokens,
198
+ totalTokensBefore: info.totalTokensBefore,
199
+ totalTokensAfter: info.totalTokensAfter,
200
+ result: info,
201
+ }));
240
202
  }
241
203
  // ── Core processing ─────────────────────────────────────────────────
242
204
  async process(content, context, externalTools = [], onChunk) {
243
- const t0 = now();
244
- const { senderId, sceneId, platform } = context;
245
- const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
246
- const userId = senderId || 'unknown';
247
- this.logPhase('turn.start', sessionId, {
248
- mode: 'text',
249
- provider: this.provider.name,
250
- });
251
- // 0. Rate limit
252
- const rateCheck = this.rateLimiter.check(userId);
253
- if (!rateCheck.allowed) {
254
- this.logPhase('turn.rate_limited', sessionId, { userId });
255
- logger.debug(`[速率限制] 用户 ${userId} 被限制: ${rateCheck.message}`);
256
- await this.finalizeActiveTurn({ usage: EMPTY_USAGE, path: 'rate_limited' });
257
- return parseOutput(rateCheck.message || '请稍后再试');
258
- }
259
- this.beginActiveTurn();
260
- // 0.5 工具上下文:web_search 语言(档案 preferred_language / language,否则默认中文)
261
- const contextForTools = await this.attachWebSearchLocale(context, userId);
262
- triggerAIHook(createAIHookEvent('message', 'received', sessionId, {
263
- userId,
264
- content,
265
- platform: platform || '',
266
- })).catch(() => { });
267
- // 0.9 Lazy-connect configured MCP servers before tool collection
268
- if (this.orchestrator) {
269
- await ensureMcpConnections(this.orchestrator.mcps);
270
- }
271
- // 1. Collect tools
272
- const tFilter = now();
273
- const mcpTools = this.orchestrator?.mcps.getAllMcpTools() ?? [];
274
- const allTools = collectRuntimeTools({
275
- content,
276
- context: contextForTools,
277
- externalTools,
278
- config: this.config,
279
- skillRegistry: this.skillRegistry,
280
- externalRegistered: this.externalTools,
281
- sessionId,
282
- userId,
283
- memory: this.memory,
284
- userProfiles: this.userProfiles,
285
- subagentManager: this.subagentManager,
286
- mcpTools,
287
- });
288
- const { tools: resolvedTools, deferredStats } = this.resolveAgentToolsForTurn(allTools, contextForTools);
289
- this.lastToolSearchDeferredStats = deferredStats;
290
- const filterMs = (now() - tFilter).toFixed(0);
291
- this.logPhase('tools.collected', sessionId, { count: resolvedTools.length });
292
- logger.debug(formatCompact({
293
- tools: resolvedTools.length,
294
- tool_search: this.config.toolSearch || undefined,
295
- names: resolvedTools.map(t => t.name).join(',') || '(none)',
296
- }));
297
- // 2. History + profile (parallel)
298
- const tMem = now();
299
- const [rawHistoryMessages, profileSummary] = await Promise.all([
300
- this.buildHistoryMessages(sessionId),
301
- this.userProfiles.buildProfileSummary(userId),
302
- ]);
303
- const chatCandidates = this.resolveModelCandidates('chat');
304
- const { messages: historyMessages, result: pruneResult, budget: contextBudget, } = pruneHistoryWithBudget({
305
- messages: rawHistoryMessages,
306
- config: this.config,
307
- provider: this.provider,
308
- modelRegistry: this.modelRegistry,
309
- model: chatCandidates[0],
310
- });
311
- if (pruneResult.droppedCount > 0) {
312
- logger.debug(`[上下文窗口] 丢弃 ${pruneResult.droppedCount} 条历史消息 (${pruneResult.droppedTokens} tokens)`);
313
- }
314
- const memMs = (now() - tMem).toFixed(0);
315
- this.logPhase('context.ready', sessionId, { historyCount: historyMessages.length });
316
- // 2.5 Tone + persona
317
- const toneHint = this.config.toneAwareness ? detectTone(content).hint : '';
318
- const personaEnhanced = buildEnhancedPersona(this.config, profileSummary, toneHint);
319
- // 3. No tools → chat path (prefer per-session model, then lightweight model)
320
- if (allTools.length === 0) {
321
- this.logPhase('path.chat', sessionId, { toolCount: 0 });
322
- const liteModel = this.config.chatLiteModel || undefined;
323
- const chatSystemPrompt = this.buildDisciplinedPrompt(personaEnhanced);
324
- logger.debug(formatCompact({
325
- mode: 'chat',
326
- prompt_chars: chatSystemPrompt.length,
327
- model: liteModel || chatCandidates[0] || undefined,
328
- }));
329
- const tLLM = now();
330
- this.logPhase('chat.llm.start', sessionId, { model: liteModel || chatCandidates[0] || '' });
331
- const chatResult = await this.streamChatWithHistory(content, chatSystemPrompt, historyMessages, onChunk, liteModel);
332
- let reply = sanitizeAssistantReply(chatResult.content);
333
- const llmMs = (now() - tLLM).toFixed(0);
334
- this.logPhase('chat.llm.end', sessionId, {
335
- durationMs: Number(llmMs),
336
- ...this.usageLogFields(chatResult.usage ?? undefined),
337
- });
338
- logger.debug(formatCompact({
339
- mode: 'chat',
340
- filter_ms: filterMs,
341
- mem_ms: memMs,
342
- llm_ms: llmMs,
343
- total_ms: Math.round(now() - t0),
344
- }));
345
- await this.saveToSession(sessionId, content, reply, sceneId);
346
- await this.finalizeActiveTurn({
347
- usage: chatResult.usage ?? EMPTY_USAGE,
348
- path: 'chat',
349
- model: chatResult.model,
350
- });
351
- this.logPhase('turn.end', sessionId, { path: 'chat' });
352
- return parseOutput(reply);
353
- }
354
- logger.debug(`[工具路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, ${allTools.length} 工具 (${allTools.map(t => t.name).join(', ')})`);
355
- // 4. Pre-executable tools
356
- const preExecCandidates = allTools.filter(tool => tool.preExecutable);
357
- const tPre = preExecCandidates.length > 0 ? now() : 0;
358
- if (preExecCandidates.length > 0) {
359
- logger.debug(`预执行: ${preExecCandidates.map(t => t.name).join(', ')}`);
360
- }
361
- const toolRun = await planToolRun(resolvedTools, this.config.preExecTimeout);
362
- this.logPhase('preexec.done', sessionId, {
363
- mode: toolRun.mode,
364
- preExecutedTools: toolRun.preExecution.tools.length,
365
- });
366
- const preData = toolRun.preExecution.data;
367
- if (tPre > 0) {
368
- logger.debug(`预执行耗时: ${(now() - tPre).toFixed(0)}ms`);
369
- }
370
- // 6. Path selection
371
- let reply;
372
- if (toolRun.mode === 'pre-exec-fast-path') {
373
- this.logPhase('path.pre_exec_fast', sessionId, { toolCount: allTools.length });
374
- // Fast path
375
- const tLLM = now();
376
- const prompt = this.buildDisciplinedPrompt(buildPreExecFastPathPrompt(personaEnhanced, preData));
377
- logger.debug(formatCompact({ mode: 'fast', prompt_chars: prompt.length }));
378
- this.logPhase('fast.llm.start', sessionId, { model: chatCandidates[0] || '' });
379
- const fastResult = await this.streamChatWithHistory(content, prompt, historyMessages, onChunk);
380
- reply = sanitizeAssistantReply(fastResult.content);
381
- this.logPhase('fast.llm.end', sessionId, {
382
- durationMs: Math.round(now() - tLLM),
383
- ...this.usageLogFields(fastResult.usage ?? undefined),
384
- });
385
- logger.debug(`[快速路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, LLM=${(now() - tLLM).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
386
- await this.finalizeActiveTurn({
387
- usage: fastResult.usage ?? EMPTY_USAGE,
388
- path: 'fast',
389
- model: fastResult.model,
390
- });
391
- }
392
- else {
393
- this.logPhase('path.agent', sessionId, { toolCount: allTools.length });
394
- const tAgent = now();
395
- logger.debug(`Agent 路径: ${allTools.length} 个工具`);
396
- const contextHint = buildContextHint(context, content);
397
- const platformMarkdown = await resolveAgentPromptMarkdown({
398
- ctx: {
399
- slot: 'orchestrator',
400
- toolContext: context,
401
- toolSearch: !!this.config.toolSearch,
402
- userMessagePreview: content.slice(0, 500),
403
- deferred: deferredStats ? { goal: content, domainStats: deferredStats } : undefined,
404
- },
405
- config: this.config,
406
- sessionId,
407
- });
408
- const richPrompt = buildRichSystemPrompt({
409
- config: this.config,
410
- skillRegistry: this.skillRegistry,
411
- skillsSummaryXML: this.skillsSummaryXML,
412
- activeSkillsContext: this.activeSkillsContext,
413
- bootstrapContext: this.bootstrapContext,
414
- toolSearchDeferredStats: deferredStats,
415
- platformSections: platformMarkdown,
416
- });
417
- const systemPrompt = `${richPrompt}
418
- ${contextHint}
419
- ${preData ? `\nPre-fetched data:\n${preData}\n` : ''}`;
420
- logger.debug(formatCompact({ mode: 'agent', prompt_chars: systemPrompt.length }));
421
- logger.debug(`[System Prompt Full]\n${systemPrompt}\n---END---`);
422
- const agentTools = applyExecPolicyToTools(this.config, resolvedTools);
423
- // Adaptive maxIterations: boost when skills are active (multi-step skill flows)
424
- const SKILL_ITERATION_BOOST = 3;
425
- const hasSkillActivation = agentTools.some(t => t.name === 'activate_skill' || t.name === 'install_skill');
426
- const harness = resolveModelHarness(this.provider.name, chatCandidates[0] || '', this.config.modelHarness);
427
- const baseIterations = harness.maxIterations ?? this.config.maxIterations;
428
- const effectiveMaxIterations = hasSkillActivation
429
- ? baseIterations + SKILL_ITERATION_BOOST
430
- : baseIterations;
431
- this.logPhase('harness.resolved', sessionId, {
432
- model: chatCandidates[0] || '',
433
- harnessMaxIterations: harness.maxIterations ?? null,
434
- effectiveMaxIterations,
435
- });
436
- let orchestrationPlugin = this.hostPlugin ?? undefined;
437
- if (!orchestrationPlugin) {
438
- try {
439
- orchestrationPlugin = getPlugin().root ?? getPlugin();
440
- }
441
- catch {
442
- logger.warn(formatCompact({ warn: 'no_host_plugin' }));
443
- }
444
- }
445
- const agent = createAgent(this.provider, {
446
- model: chatCandidates[0],
447
- modelFallbacks: chatCandidates.slice(1),
448
- systemPrompt,
449
- tools: agentTools,
450
- maxIterations: effectiveMaxIterations,
451
- turnTimeout: this.config.timeout,
452
- contextWindow: contextBudget.contextWindow,
453
- reservedToolNames: RESERVED_TOOL_NAMES,
454
- reservedToolNamePrefixes: RESERVED_TOOL_NAME_PREFIXES,
455
- transformToolResult: createOwnerOrchestratedToolResultTransform({
456
- toolContext: contextForTools,
457
- disableHardOrchestration: false,
458
- plugin: orchestrationPlugin,
459
- }),
460
- });
461
- const userMessageWithHistory = buildUserMessageWithHistory(historyMessages, content);
462
- let result;
463
- try {
464
- this.logPhase('agent.run.start', sessionId, { model: chatCandidates[0] || '' });
465
- result = await runWithBashToolContext(contextForTools, () => agent.run(userMessageWithHistory, []));
466
- this.logPhase('agent.run.end', sessionId, {
467
- iterations: result.iterations,
468
- durationMs: Math.round(now() - tAgent),
469
- ...this.usageLogFields(result.usage),
470
- });
471
- }
472
- finally {
473
- agent.dispose();
474
- }
475
- reply = sanitizeAssistantReply(result.content, {
476
- toolSummary: formatToolCallsForUser(result.toolCalls),
477
- });
478
- logger.debug(`[Agent 路径] 过滤=${filterMs}ms, 记忆=${memMs}ms, Agent=${(now() - tAgent).toFixed(0)}ms, 总=${(now() - t0).toFixed(0)}ms`);
479
- await this.finalizeActiveTurn({
480
- usage: result.usage,
481
- path: 'agent',
482
- iterations: result.iterations,
483
- model: result.model ?? (chatCandidates[0] || undefined),
484
- });
485
- }
486
- await this.saveToSession(sessionId, content, reply, sceneId);
487
- triggerAIHook(createAIHookEvent('message', 'sent', sessionId, {
488
- userId,
489
- content: reply,
490
- platform: platform || '',
491
- })).catch(() => { });
492
- this.logPhase('turn.end', sessionId, { path: toolRun.mode === 'pre-exec-fast-path' ? 'fast' : 'agent' });
493
- return parseOutput(reply);
205
+ return processTextTurn(asPrivate(this), content, context, externalTools, onChunk);
494
206
  }
495
207
  resolveAgentToolsForTurn(allTools, context) {
496
- if (!this.config.toolSearch) {
497
- return { tools: allTools };
498
- }
499
- const toolSearchPool = filterToolsForToolSearchCatalog(allTools);
500
- const built = buildOrchestratorAgentTools({
501
- allTools: toolSearchPool,
502
- config: this.config,
503
- context,
504
- getDeferredCatalog: () => this.deferredCatalog,
505
- runWorker: (goal, toolQuery) => this.runDeferredWorker(goal, toolQuery, context, toolSearchPool),
506
- });
507
- this.deferredCatalog = built.deferred;
508
- logger.debug(formatCompact({
509
- tool_search: `${built.orchestratorTools.length}+${built.deferred.length}`,
510
- stats: built.domainStats,
511
- }));
512
- return { tools: built.orchestratorTools, deferredStats: built.domainStats };
208
+ return resolveToolsForTurn(asPrivate(this), allTools, context);
513
209
  }
514
210
  async runDeferredWorker(goal, toolQuery, context, allTools) {
515
- const allByName = new Map(allTools.map(t => [t.name, t]));
516
- const workerBase = [];
517
- for (const name of this.config.toolSearchWorkerBaseTools) {
518
- const t = allByName.get(name);
519
- if (t)
520
- workerBase.push(t);
521
- }
522
- const result = await this.deferredWorkerRunner.runSync({
523
- goal,
524
- toolQuery,
525
- deferredCatalog: this.deferredCatalog,
526
- workerBaseTools: workerBase,
527
- allToolsByName: allByName,
528
- origin: context,
529
- maxToolResults: this.config.toolSearchMaxResults,
530
- execPolicyConfig: this.config,
531
- modelRegistry: this.modelRegistry,
532
- provider: this.provider,
533
- maxIterations: this.config.maxSubagentIterations,
534
- });
535
- return result.summary;
211
+ return runDeferredWorkerTurn(asPrivate(this), goal, toolQuery, context, allTools);
536
212
  }
537
213
  buildDisciplinedPrompt(basePrompt) {
538
- const guidance = [
539
- '# Style',
540
- '- Lead with the answer or result.',
541
- '- Be concise, direct, and useful.',
542
- '',
543
- '# Safety',
544
- ...FIXED_DISCIPLINE_RULES.map(rule => `- ${rule}`),
545
- ].join('\n');
546
- return `${basePrompt}\n\n${guidance}`;
547
- }
548
- usageLogFields(usage) {
549
- if (!usage)
550
- return {};
551
- return {
552
- promptTokens: usage.prompt_tokens,
553
- completionTokens: usage.completion_tokens,
554
- totalTokens: usage.total_tokens,
555
- };
556
- }
557
- logPhase(phase, sessionId, extra = {}) {
558
- if (!this.phaseTraceEnabled)
559
- return;
560
- this.config.onPhaseTrace?.({ phase, sessionId, extra });
561
- const flat = { phase, session: sessionId };
562
- for (const [k, v] of Object.entries(extra)) {
563
- if (v === undefined || v === null)
564
- continue;
565
- flat[k] = typeof v === 'object' ? JSON.stringify(v) : v;
566
- }
567
- logger.info(formatCompact(flat));
214
+ return assembleDisciplinedPrompt(asPrivate(this), basePrompt);
568
215
  }
569
216
  async processMultimodal(parts, context, onChunk) {
570
- const { senderId, sceneId, platform } = context;
571
- const sessionId = SessionManager.generateId(platform || '', senderId || '', sceneId);
572
- const userId = senderId || 'unknown';
573
- const rateCheck = this.rateLimiter.check(userId);
574
- if (!rateCheck.allowed) {
575
- await this.finalizeActiveTurn({ usage: EMPTY_USAGE, path: 'rate_limited' });
576
- return parseOutput(rateCheck.message || '请稍后再试');
577
- }
578
- this.beginActiveTurn();
579
- const rawHistoryMessages = await this.buildHistoryMessages(sessionId);
580
- const profileSummary = await this.userProfiles.buildProfileSummary(userId);
581
- const personaEnhanced = this.buildDisciplinedPrompt(buildEnhancedPersona(this.config, profileSummary, ''));
582
- // Build text summary describing the multimodal content
583
- const textFragments = [];
584
- const llmParts = [];
585
- for (const p of parts) {
586
- switch (p.type) {
587
- case 'text':
588
- textFragments.push(p.text);
589
- llmParts.push(p);
590
- break;
591
- case 'image_url':
592
- textFragments.push('[图片]');
593
- llmParts.push(p);
594
- break;
595
- case 'video_url':
596
- textFragments.push('[视频]');
597
- // Most LLMs don't support video natively; describe it as a URL for context
598
- llmParts.push({ type: 'text', text: `[用户发送了一个视频: ${p.video_url.url}]` });
599
- break;
600
- case 'audio':
601
- textFragments.push('[音频]');
602
- llmParts.push(p);
603
- break;
604
- case 'face':
605
- textFragments.push(p.face.text || `[表情:${p.face.id}]`);
606
- llmParts.push({ type: 'text', text: p.face.text ? `[表情: ${p.face.text}]` : `[表情ID: ${p.face.id}]` });
607
- break;
608
- }
609
- }
610
- const textContent = textFragments.join(' ') || '[多模态消息]';
611
- const platformMarkdown = await resolveAgentPromptMarkdown({
612
- ctx: {
613
- slot: 'orchestrator',
614
- toolContext: context,
615
- toolSearch: !!this.config.toolSearch,
616
- userMessagePreview: textContent.slice(0, 500),
617
- },
618
- config: this.config,
619
- sessionId,
620
- });
621
- const visionSystemPrompt = buildLiteSystemPromptWithPlatform(personaEnhanced, platformMarkdown, buildContextHint(context, textContent));
622
- const visionCandidates = this.resolveModelCandidates('vision');
623
- const { messages: historyMessages, result: pruneResult } = pruneHistoryWithBudget({
624
- messages: rawHistoryMessages,
625
- config: this.config,
626
- provider: this.provider,
627
- modelRegistry: this.modelRegistry,
628
- model: visionCandidates[0],
629
- });
630
- if (pruneResult.droppedCount > 0) {
631
- logger.debug(`[多模态上下文窗口] 丢弃 ${pruneResult.droppedCount} 条历史消息 (${pruneResult.droppedTokens} tokens)`);
632
- }
633
- const messages = [
634
- { role: 'system', content: visionSystemPrompt },
635
- ...historyMessages,
636
- { role: 'user', content: llmParts },
637
- ];
638
- let reply = '';
639
- let lastUsage = null;
640
- let usedVisionModel = visionCandidates[0] || '';
641
- for (let i = 0; i < visionCandidates.length; i++) {
642
- const visionModel = visionCandidates[i];
643
- usedVisionModel = visionModel;
644
- try {
645
- reply = '';
646
- for await (const chunk of this.provider.chatStream({ model: visionModel, messages })) {
647
- if (chunk.usage)
648
- lastUsage = chunk.usage;
649
- const delta = chunk.choices?.[0]?.delta;
650
- if (!delta)
651
- continue;
652
- const text = typeof delta.content === 'string' ? delta.content : '';
653
- if (text) {
654
- reply += text;
655
- if (onChunk)
656
- onChunk(text, reply);
657
- }
658
- }
659
- reply = stripThinkBlocks(reply);
660
- if (!reply) {
661
- logger.warn(formatCompact({ mode: 'multimodal', fallback: visionModel, reason: 'empty_stream' }));
662
- const response = await this.provider.chat({ model: visionModel, messages });
663
- if (response.usage)
664
- lastUsage = response.usage;
665
- const msg = response.choices[0]?.message?.content;
666
- reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
667
- }
668
- if (reply)
669
- break; // 成功,退出循环
670
- }
671
- catch (err) {
672
- const isLast = i === visionCandidates.length - 1;
673
- if (isLast) {
674
- try {
675
- const response = await this.provider.chat({ model: visionModel, messages });
676
- if (response.usage)
677
- lastUsage = response.usage;
678
- const msg = response.choices[0]?.message?.content;
679
- reply = stripThinkBlocks(typeof msg === 'string' ? msg : '');
680
- }
681
- catch { /* all candidates exhausted */ }
682
- }
683
- else {
684
- logger.warn(formatCompact({
685
- mode: 'multimodal',
686
- fallback: `${visionModel}→${visionCandidates[i + 1]}`,
687
- error: truncatePreview(err.message),
688
- }));
689
- }
690
- }
691
- }
692
- if (!reply)
693
- reply = '抱歉,我无法理解这条消息。';
694
- reply = sanitizeAssistantReply(reply);
695
- await this.saveToSession(sessionId, textContent, reply, sceneId);
696
- await this.finalizeActiveTurn({
697
- usage: lastUsage ?? EMPTY_USAGE,
698
- path: 'multimodal',
699
- model: usedVisionModel,
700
- });
701
- return parseOutput(reply);
217
+ return processMultimodalTurn(asPrivate(this), parts, context, onChunk);
702
218
  }
703
219
  // ── Internal helpers ────────────────────────────────────────────────
704
- /**
705
- * 为内置工具注入 `extra.web_search_locale`:
706
- * - 若调用方已在 `context.extra.web_search_locale` 中设置,则规范化后沿用;
707
- * - 否则读取用户档案 `preferred_language` / `language`;
708
- * - 均未设置时不在 extra 中写入,web_search 默认使用中文市场。
709
- */
710
- async attachWebSearchLocale(context, userId) {
711
- const extra = { ...(context.extra ?? {}) };
712
- const existing = extra[WEB_SEARCH_LOCALE_EXTRA_KEY];
713
- if (typeof existing === 'string' && existing.trim()) {
714
- extra[WEB_SEARCH_LOCALE_EXTRA_KEY] = normalizeWebSearchLocaleHint(existing);
715
- return { ...context, extra };
716
- }
717
- const [preferred, language] = await Promise.all([
718
- this.userProfiles.get(userId, 'preferred_language'),
719
- this.userProfiles.get(userId, 'language'),
720
- ]);
721
- const hint = (preferred ?? language)?.trim();
722
- if (hint) {
723
- extra[WEB_SEARCH_LOCALE_EXTRA_KEY] = normalizeWebSearchLocaleHint(hint);
724
- }
725
- return { ...context, extra };
726
- }
727
- async buildHistoryMessages(sessionId) {
728
- return this.memory.buildContext(sessionId);
729
- }
730
- async streamChatWithHistory(content, systemPrompt, history, onChunk, preferredModel) {
731
- const candidates = this.resolveModelCandidates('chat', preferredModel);
732
- const empty = (model) => ({ content: '', usage: null, model });
733
- const turnTimeout = this.config.timeout;
734
- const userContent = history.length > 0
735
- ? buildUserMessageWithHistory(history, content)
736
- : content;
737
- const messages = [
738
- { role: 'system', content: systemPrompt },
739
- { role: 'user', content: userContent },
740
- ];
741
- const withTurnTimeout = (promise) => turnTimeout
742
- ? Promise.race([
743
- promise,
744
- new Promise((_, rej) => setTimeout(() => rej(new Error(`LLM 单轮响应超时 (${turnTimeout}ms)`)), turnTimeout)),
745
- ])
746
- : promise;
747
- for (let i = 0; i < candidates.length; i++) {
748
- const model = candidates[i];
749
- try {
750
- let result = '';
751
- let lastUsage = null;
752
- // 对整个流式消费过程应用超时
753
- await withTurnTimeout((async () => {
754
- for await (const chunk of this.provider.chatStream({ model, messages })) {
755
- if (chunk.usage)
756
- lastUsage = chunk.usage;
757
- const delta = chunk.choices?.[0]?.delta;
758
- if (!delta)
759
- continue;
760
- const text = typeof delta.content === 'string' ? delta.content : '';
761
- if (text) {
762
- result += text;
763
- if (onChunk)
764
- onChunk(text, result);
765
- }
766
- }
767
- })());
768
- result = stripThinkBlocks(result);
769
- if (result) {
770
- return { content: result, usage: lastUsage, model };
771
- }
772
- // Streaming returned empty content — try non-streaming with same model
773
- logger.warn(formatCompact({ stream: 'empty', model }));
774
- const response = await withTurnTimeout(this.provider.chat({ model, messages }));
775
- const msg = response.choices?.[0]?.message?.content;
776
- result = stripThinkBlocks(typeof msg === 'string' ? msg : '');
777
- if (result) {
778
- if (onChunk)
779
- onChunk(result, result);
780
- return { content: result, usage: response.usage ?? null, model };
781
- }
782
- }
783
- catch (err) {
784
- const isLast = i === candidates.length - 1;
785
- if (isLast) {
786
- // No more candidates — try non-streaming as final attempt
787
- try {
788
- const response = await withTurnTimeout(this.provider.chat({ model, messages }));
789
- const msg = response.choices?.[0]?.message?.content;
790
- let result = stripThinkBlocks(typeof msg === 'string' ? msg : '');
791
- if (onChunk && result)
792
- onChunk(result, result);
793
- if (result) {
794
- return { content: result, usage: response.usage ?? null, model };
795
- }
796
- return empty(model);
797
- }
798
- catch {
799
- return empty(model);
800
- }
801
- }
802
- logger.warn(formatCompact({
803
- fallback: `${model}→${candidates[i + 1]}`,
804
- error: truncatePreview(err.message),
805
- }));
806
- }
807
- }
808
- return empty(candidates[0] || '');
809
- }
810
- async saveToSession(sessionId, userContent, assistantContent, sceneId) {
811
- await this.memory.saveRound(sessionId, userContent, assistantContent);
812
- await this.sessions.addMessage(sessionId, { role: 'user', content: userContent });
813
- await this.sessions.addMessage(sessionId, { role: 'assistant', content: assistantContent });
814
- if (this.contextManager && sceneId) {
815
- this.contextManager.autoSummarizeIfNeeded(sceneId).catch(() => { });
816
- }
817
- }
818
- /** @deprecated 使用 {@link formatToolCallsForUser} */
819
- fallbackFormat(toolCalls) {
820
- return formatToolCallsForUser(toolCalls);
821
- }
822
220
  // ── Lifecycle ───────────────────────────────────────────────────────
823
221
  isReady() {
824
222
  return true;