@ai-zen/cli 1.0.0-alpha.3 → 1.0.0-alpha.30
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/CHANGELOG.md +885 -0
- package/LICENSE +21 -0
- package/README.md +135 -30
- package/README.zh.md +135 -30
- package/dist/agent-creator.d.ts +11 -0
- package/dist/agent-creator.d.ts.map +1 -1
- package/dist/agent-creator.js +50 -20
- package/dist/agent-creator.js.map +1 -1
- package/dist/agent-switch.d.ts +61 -0
- package/dist/agent-switch.d.ts.map +1 -0
- package/dist/agent-switch.js +83 -0
- package/dist/agent-switch.js.map +1 -0
- package/dist/agents-store.d.ts +6 -1
- package/dist/agents-store.d.ts.map +1 -1
- package/dist/agents-store.js +46 -4
- package/dist/agents-store.js.map +1 -1
- package/dist/auto-migrate-confirm.d.ts +43 -0
- package/dist/auto-migrate-confirm.d.ts.map +1 -0
- package/dist/auto-migrate-confirm.js +47 -0
- package/dist/auto-migrate-confirm.js.map +1 -0
- package/dist/clipboard.d.ts +68 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +177 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/config-editor.d.ts +42 -5
- package/dist/config-editor.d.ts.map +1 -1
- package/dist/config-editor.js +80 -27
- package/dist/config-editor.js.map +1 -1
- package/dist/config.d.ts +76 -17
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +143 -39
- package/dist/config.js.map +1 -1
- package/dist/conversation-commands/registry.d.ts +127 -4
- package/dist/conversation-commands/registry.d.ts.map +1 -1
- package/dist/conversation-commands/registry.js +93 -14
- package/dist/conversation-commands/registry.js.map +1 -1
- package/dist/conversation-persist-plugin.d.ts +0 -1
- package/dist/conversation-persist-plugin.d.ts.map +1 -1
- package/dist/conversation-persist-plugin.js +7 -16
- package/dist/conversation-persist-plugin.js.map +1 -1
- package/dist/conversation-repository.d.ts +79 -18
- package/dist/conversation-repository.d.ts.map +1 -1
- package/dist/conversation-repository.js +96 -30
- package/dist/conversation-repository.js.map +1 -1
- package/dist/cwd-tracker-plugin.d.ts.map +1 -1
- package/dist/cwd-tracker-plugin.js +22 -7
- package/dist/cwd-tracker-plugin.js.map +1 -1
- package/dist/format-time.d.ts +17 -6
- package/dist/format-time.d.ts.map +1 -1
- package/dist/format-time.js +32 -14
- package/dist/format-time.js.map +1 -1
- package/dist/handoff-service.d.ts +19 -0
- package/dist/handoff-service.d.ts.map +1 -0
- package/dist/handoff-service.js +89 -0
- package/dist/handoff-service.js.map +1 -0
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +69 -15
- package/dist/hook.js.map +1 -1
- package/dist/i18n/en-wizard.d.ts +8 -0
- package/dist/i18n/en-wizard.d.ts.map +1 -0
- package/dist/i18n/en-wizard.js +303 -0
- package/dist/i18n/en-wizard.js.map +1 -0
- package/dist/i18n/en.d.ts +7 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +247 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/index.d.ts +57 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +109 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/preferences.d.ts +37 -0
- package/dist/i18n/preferences.d.ts.map +1 -0
- package/dist/i18n/preferences.js +73 -0
- package/dist/i18n/preferences.js.map +1 -0
- package/dist/i18n/zh-wizard.d.ts +11 -0
- package/dist/i18n/zh-wizard.d.ts.map +1 -0
- package/dist/i18n/zh-wizard.js +306 -0
- package/dist/i18n/zh-wizard.js.map +1 -0
- package/dist/i18n/zh.d.ts +10 -0
- package/dist/i18n/zh.d.ts.map +1 -0
- package/dist/i18n/zh.js +251 -0
- package/dist/i18n/zh.js.map +1 -0
- package/dist/index.js +72 -25
- package/dist/index.js.map +1 -1
- package/dist/mcp-command.d.ts +32 -0
- package/dist/mcp-command.d.ts.map +1 -0
- package/dist/mcp-command.js +37 -0
- package/dist/mcp-command.js.map +1 -0
- package/dist/mode.d.ts +31 -4
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js +42 -7
- package/dist/mode.js.map +1 -1
- package/dist/model-switch.d.ts +58 -0
- package/dist/model-switch.d.ts.map +1 -0
- package/dist/model-switch.js +62 -0
- package/dist/model-switch.js.map +1 -0
- package/dist/reasoning-effort.d.ts +96 -0
- package/dist/reasoning-effort.d.ts.map +1 -0
- package/dist/reasoning-effort.js +164 -0
- package/dist/reasoning-effort.js.map +1 -0
- package/dist/reasoning-presets.d.ts +34 -0
- package/dist/reasoning-presets.d.ts.map +1 -0
- package/dist/reasoning-presets.js +85 -0
- package/dist/reasoning-presets.js.map +1 -0
- package/dist/sdk-log.d.ts +84 -0
- package/dist/sdk-log.d.ts.map +1 -0
- package/dist/sdk-log.js +167 -0
- package/dist/sdk-log.js.map +1 -0
- package/dist/session-pointer.d.ts.map +1 -1
- package/dist/session-pointer.js +10 -4
- package/dist/session-pointer.js.map +1 -1
- package/dist/stdio-runner.d.ts.map +1 -1
- package/dist/stdio-runner.js +32 -30
- package/dist/stdio-runner.js.map +1 -1
- package/dist/sub-agent-guard-plugin.d.ts +10 -7
- package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
- package/dist/sub-agent-guard-plugin.js +12 -9
- package/dist/sub-agent-guard-plugin.js.map +1 -1
- package/dist/tui/chat-history.d.ts +29 -0
- package/dist/tui/chat-history.d.ts.map +1 -0
- package/dist/tui/chat-history.js +55 -0
- package/dist/tui/chat-history.js.map +1 -0
- package/dist/tui/chat-session.d.ts +177 -57
- package/dist/tui/chat-session.d.ts.map +1 -1
- package/dist/tui/chat-session.js +362 -126
- package/dist/tui/chat-session.js.map +1 -1
- package/dist/tui/components.d.ts +113 -7
- package/dist/tui/components.d.ts.map +1 -1
- package/dist/tui/components.js +177 -12
- package/dist/tui/components.js.map +1 -1
- package/dist/tui/config-wizard/detail.d.ts +18 -10
- package/dist/tui/config-wizard/detail.d.ts.map +1 -1
- package/dist/tui/config-wizard/detail.js +53 -46
- package/dist/tui/config-wizard/detail.js.map +1 -1
- package/dist/tui/config-wizard/engine.d.ts +2 -2
- package/dist/tui/config-wizard/engine.d.ts.map +1 -1
- package/dist/tui/config-wizard/engine.js +48 -26
- package/dist/tui/config-wizard/engine.js.map +1 -1
- package/dist/tui/config-wizard/fields.d.ts +3 -1
- package/dist/tui/config-wizard/fields.d.ts.map +1 -1
- package/dist/tui/config-wizard/fields.js +202 -167
- package/dist/tui/config-wizard/fields.js.map +1 -1
- package/dist/tui/config-wizard/frame.js +1 -1
- package/dist/tui/config-wizard/kv-editor.d.ts.map +1 -1
- package/dist/tui/config-wizard/kv-editor.js +13 -5
- package/dist/tui/config-wizard/kv-editor.js.map +1 -1
- package/dist/tui/config-wizard/list.d.ts +3 -1
- package/dist/tui/config-wizard/list.d.ts.map +1 -1
- package/dist/tui/config-wizard/list.js +4 -4
- package/dist/tui/config-wizard/list.js.map +1 -1
- package/dist/tui/config-wizard/mcp-helpers.d.ts.map +1 -1
- package/dist/tui/config-wizard/mcp-helpers.js +4 -3
- package/dist/tui/config-wizard/mcp-helpers.js.map +1 -1
- package/dist/tui/config-wizard/menu.d.ts.map +1 -1
- package/dist/tui/config-wizard/menu.js +33 -21
- package/dist/tui/config-wizard/menu.js.map +1 -1
- package/dist/tui/config-wizard/prompt-editor.d.ts +8 -1
- package/dist/tui/config-wizard/prompt-editor.d.ts.map +1 -1
- package/dist/tui/config-wizard/prompt-editor.js +11 -7
- package/dist/tui/config-wizard/prompt-editor.js.map +1 -1
- package/dist/tui/config-wizard/prompt.d.ts.map +1 -1
- package/dist/tui/config-wizard/prompt.js +8 -7
- package/dist/tui/config-wizard/prompt.js.map +1 -1
- package/dist/tui/config-wizard/step-agents.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-agents.js +50 -29
- package/dist/tui/config-wizard/step-agents.js.map +1 -1
- package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-defaults.js +77 -27
- package/dist/tui/config-wizard/step-defaults.js.map +1 -1
- package/dist/tui/config-wizard/step-endpoints.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-endpoints.js +27 -14
- package/dist/tui/config-wizard/step-endpoints.js.map +1 -1
- package/dist/tui/config-wizard/step-mcp.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-mcp.js +29 -25
- package/dist/tui/config-wizard/step-mcp.js.map +1 -1
- package/dist/tui/config-wizard/step-models.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-models.js +98 -43
- package/dist/tui/config-wizard/step-models.js.map +1 -1
- package/dist/tui/config-wizard/step-new-endpoint.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-new-endpoint.js +16 -12
- package/dist/tui/config-wizard/step-new-endpoint.js.map +1 -1
- package/dist/tui/config-wizard/step-simple.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-simple.js +18 -17
- package/dist/tui/config-wizard/step-simple.js.map +1 -1
- package/dist/tui/config-wizard/types.d.ts +31 -3
- package/dist/tui/config-wizard/types.d.ts.map +1 -1
- package/dist/tui/config-wizard/types.js.map +1 -1
- package/dist/tui/file-index.d.ts +38 -0
- package/dist/tui/file-index.d.ts.map +1 -0
- package/dist/tui/file-index.js +117 -0
- package/dist/tui/file-index.js.map +1 -0
- package/dist/tui/index.d.ts +18 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +107 -49
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/input-editing.d.ts +52 -0
- package/dist/tui/input-editing.d.ts.map +1 -0
- package/dist/tui/input-editing.js +91 -0
- package/dist/tui/input-editing.js.map +1 -0
- package/dist/tui/key-debug.d.ts +62 -0
- package/dist/tui/key-debug.d.ts.map +1 -0
- package/dist/tui/key-debug.js +92 -0
- package/dist/tui/key-debug.js.map +1 -0
- package/dist/tui/mention.d.ts +47 -0
- package/dist/tui/mention.d.ts.map +1 -0
- package/dist/tui/mention.js +152 -0
- package/dist/tui/mention.js.map +1 -0
- package/dist/tui/mouse.d.ts +55 -0
- package/dist/tui/mouse.d.ts.map +1 -0
- package/dist/tui/mouse.js +74 -0
- package/dist/tui/mouse.js.map +1 -0
- package/dist/tui/phase.d.ts +64 -0
- package/dist/tui/phase.d.ts.map +1 -0
- package/dist/tui/phase.js +112 -0
- package/dist/tui/phase.js.map +1 -0
- package/dist/tui/screens/agent-tabs.d.ts +53 -0
- package/dist/tui/screens/agent-tabs.d.ts.map +1 -0
- package/dist/tui/screens/agent-tabs.js +81 -0
- package/dist/tui/screens/agent-tabs.js.map +1 -0
- package/dist/tui/screens/chat-commands.d.ts +82 -3
- package/dist/tui/screens/chat-commands.d.ts.map +1 -1
- package/dist/tui/screens/chat-commands.js +688 -160
- package/dist/tui/screens/chat-commands.js.map +1 -1
- package/dist/tui/screens/chat.d.ts +57 -5
- package/dist/tui/screens/chat.d.ts.map +1 -1
- package/dist/tui/screens/chat.js +799 -304
- package/dist/tui/screens/chat.js.map +1 -1
- package/dist/tui/screens/common-keys.d.ts +36 -0
- package/dist/tui/screens/common-keys.d.ts.map +1 -0
- package/dist/tui/screens/common-keys.js +67 -0
- package/dist/tui/screens/common-keys.js.map +1 -0
- package/dist/tui/screens/frame-buffer.d.ts +47 -0
- package/dist/tui/screens/frame-buffer.d.ts.map +1 -0
- package/dist/tui/screens/frame-buffer.js +78 -0
- package/dist/tui/screens/frame-buffer.js.map +1 -0
- package/dist/tui/screens/handoff-choice.d.ts +37 -0
- package/dist/tui/screens/handoff-choice.d.ts.map +1 -0
- package/dist/tui/screens/handoff-choice.js +42 -0
- package/dist/tui/screens/handoff-choice.js.map +1 -0
- package/dist/tui/screens/index.d.ts +11 -9
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +9 -8
- package/dist/tui/screens/index.js.map +1 -1
- package/dist/tui/screens/input-row.d.ts +64 -0
- package/dist/tui/screens/input-row.d.ts.map +1 -0
- package/dist/tui/screens/input-row.js +67 -0
- package/dist/tui/screens/input-row.js.map +1 -0
- package/dist/tui/screens/input.d.ts +77 -1
- package/dist/tui/screens/input.d.ts.map +1 -1
- package/dist/tui/screens/input.js +125 -8
- package/dist/tui/screens/input.js.map +1 -1
- package/dist/tui/screens/layout.d.ts +42 -3
- package/dist/tui/screens/layout.d.ts.map +1 -1
- package/dist/tui/screens/layout.js +43 -13
- package/dist/tui/screens/layout.js.map +1 -1
- package/dist/tui/screens/lines.d.ts +6 -36
- package/dist/tui/screens/lines.d.ts.map +1 -1
- package/dist/tui/screens/lines.js +8 -71
- package/dist/tui/screens/lines.js.map +1 -1
- package/dist/tui/screens/messages-view.d.ts +29 -0
- package/dist/tui/screens/messages-view.d.ts.map +1 -0
- package/dist/tui/screens/messages-view.js +162 -0
- package/dist/tui/screens/messages-view.js.map +1 -0
- package/dist/tui/screens/notice-bar.d.ts +5 -0
- package/dist/tui/screens/notice-bar.d.ts.map +1 -0
- package/dist/tui/screens/notice-bar.js +21 -0
- package/dist/tui/screens/notice-bar.js.map +1 -0
- package/dist/tui/screens/notice.d.ts +66 -0
- package/dist/tui/screens/notice.d.ts.map +1 -0
- package/dist/tui/screens/notice.js +106 -0
- package/dist/tui/screens/notice.js.map +1 -0
- package/dist/tui/screens/overlays.d.ts.map +1 -1
- package/dist/tui/screens/overlays.js +4 -3
- package/dist/tui/screens/overlays.js.map +1 -1
- package/dist/tui/screens/render.d.ts +2 -4
- package/dist/tui/screens/render.d.ts.map +1 -1
- package/dist/tui/screens/render.js +19 -50
- package/dist/tui/screens/render.js.map +1 -1
- package/dist/tui/screens/splash.d.ts +1 -1
- package/dist/tui/screens/splash.d.ts.map +1 -1
- package/dist/tui/screens/splash.js +12 -3
- package/dist/tui/screens/splash.js.map +1 -1
- package/dist/tui/screens/use-chat-viewport.d.ts +22 -0
- package/dist/tui/screens/use-chat-viewport.d.ts.map +1 -0
- package/dist/tui/screens/use-chat-viewport.js +50 -0
- package/dist/tui/screens/use-chat-viewport.js.map +1 -0
- package/dist/tui/screens/use-input-editing.d.ts +69 -0
- package/dist/tui/screens/use-input-editing.d.ts.map +1 -0
- package/dist/tui/screens/use-input-editing.js +220 -0
- package/dist/tui/screens/use-input-editing.js.map +1 -0
- package/dist/tui/screens/use-overlays.d.ts +25 -0
- package/dist/tui/screens/use-overlays.d.ts.map +1 -0
- package/dist/tui/screens/use-overlays.js +32 -0
- package/dist/tui/screens/use-overlays.js.map +1 -0
- package/dist/tui/screens/use-wizard-host.d.ts +46 -0
- package/dist/tui/screens/use-wizard-host.d.ts.map +1 -0
- package/dist/tui/screens/use-wizard-host.js +123 -0
- package/dist/tui/screens/use-wizard-host.js.map +1 -0
- package/dist/tui/status-segments.d.ts +37 -0
- package/dist/tui/status-segments.d.ts.map +1 -0
- package/dist/tui/status-segments.js +166 -0
- package/dist/tui/status-segments.js.map +1 -0
- package/dist/tui/text.d.ts +27 -7
- package/dist/tui/text.d.ts.map +1 -1
- package/dist/tui/text.js +116 -38
- package/dist/tui/text.js.map +1 -1
- package/dist/tui/theme.d.ts +2 -0
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +2 -0
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/token-rate.d.ts +83 -0
- package/dist/tui/token-rate.d.ts.map +1 -0
- package/dist/tui/token-rate.js +195 -0
- package/dist/tui/token-rate.js.map +1 -0
- package/dist/types.d.ts +7 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/user-content.d.ts +41 -0
- package/dist/user-content.d.ts.map +1 -0
- package/dist/user-content.js +37 -0
- package/dist/user-content.js.map +1 -0
- package/dist/version-compare.d.ts +37 -0
- package/dist/version-compare.d.ts.map +1 -0
- package/dist/version-compare.js +105 -0
- package/dist/version-compare.js.map +1 -0
- package/dist/version.js.map +1 -1
- package/package.json +5 -4
- package/dist/auto-migrate-confirm-plugin.d.ts +0 -58
- package/dist/auto-migrate-confirm-plugin.d.ts.map +0 -1
- package/dist/auto-migrate-confirm-plugin.js +0 -61
- package/dist/auto-migrate-confirm-plugin.js.map +0 -1
- package/dist/migration-service.d.ts +0 -15
- package/dist/migration-service.d.ts.map +0 -1
- package/dist/migration-service.js +0 -59
- package/dist/migration-service.js.map +0 -1
- package/dist/tui/screens/state.d.ts +0 -30
- package/dist/tui/screens/state.d.ts.map +0 -1
- package/dist/tui/screens/state.js +0 -100
- package/dist/tui/screens/state.js.map +0 -1
package/dist/tui/chat-session.js
CHANGED
|
@@ -1,101 +1,251 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TUI 对话会话 —— Agent
|
|
2
|
+
* TUI 对话会话 —— Agent 装配 + 「内容变更」信号
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 渲染以 SDK 的 `agent.messages` 为**唯一数据源**:本模块不再把流式 delta 翻译成
|
|
5
|
+
* 自定义事件 / 中间模型(Block / LiveAssistant),只做两件事:
|
|
6
|
+
* 1. SDK 事件到达时通知上层「messages 变了,该重渲染了」;
|
|
7
|
+
* 2. 把系统级提示(notice / error)转发到独立通道(绝不写进 messages)。
|
|
7
8
|
*
|
|
8
9
|
* 本模块不依赖 React / Ink,可独立单测。
|
|
9
10
|
*/
|
|
10
|
-
import { AgentNS } from "@ai-zen/agents-core";
|
|
11
|
+
import { AgentNS, sumUsage } from "@ai-zen/agents-core";
|
|
12
|
+
import { t } from "../i18n/index.js";
|
|
11
13
|
import { AutoRefreshToolsPlugin, ContextGuardPlugin, } from "@ai-zen/agents-sdk";
|
|
12
14
|
import { createAgent, resetScope } from "../agent-creator.js";
|
|
13
15
|
import { readConfig } from "../config.js";
|
|
16
|
+
import { buildUserContent } from "../user-content.js";
|
|
14
17
|
import { CwdTrackerPlugin } from "../cwd-tracker-plugin.js";
|
|
15
18
|
import { ConversationPersistPlugin } from "../conversation-persist-plugin.js";
|
|
16
19
|
import { SubAgentGuardInstallerPlugin } from "../sub-agent-guard-plugin.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
import { createAutoMigratePlugin } from "../auto-migrate-confirm.js";
|
|
21
|
+
import { createHandoffService } from "../handoff-service.js";
|
|
22
|
+
import { promises as fs } from "node:fs";
|
|
23
|
+
import { generateConversationId, conversationRepository, persistConversation, } from "../conversation-repository.js";
|
|
24
|
+
import { stripCwdNote } from "./text.js";
|
|
25
|
+
import { TokenRateMeter, deltaText } from "./token-rate.js";
|
|
26
|
+
import { PhaseTracker } from "./phase.js";
|
|
27
|
+
/**
|
|
28
|
+
* 把「请求即将发出」报给阶段跟踪器。
|
|
29
|
+
*
|
|
30
|
+
* SDK 的 `open` 事件是在 `chat.completions.create()` **await 返回之后**才发的,
|
|
31
|
+
* 也就是说它只代表响应头到了;在那之前的 DNS / TCP / TLS / 服务端受理都是静黙的。
|
|
32
|
+
* 所以要一个插件钩子把这一段补上——否则首轮会一直停在上一个状态。
|
|
33
|
+
*/
|
|
34
|
+
class PhasePlugin {
|
|
35
|
+
tracker;
|
|
36
|
+
constructor(tracker) {
|
|
37
|
+
this.tracker = tracker;
|
|
38
|
+
}
|
|
39
|
+
onInnerLoopStart() {
|
|
40
|
+
this.tracker.connecting();
|
|
41
|
+
}
|
|
24
42
|
}
|
|
43
|
+
/** 去掉 CwdTrackerPlugin 追加在用户消息末尾的工作目录提示(转发自 ./text.js) */
|
|
44
|
+
export { stripCwdNote };
|
|
25
45
|
export class ChatSession {
|
|
26
|
-
|
|
46
|
+
/**
|
|
47
|
+
* 当前模型 id —— 从 Agent 定义读,**不再单独存一份**。
|
|
48
|
+
*
|
|
49
|
+
* 模型的唯一真相源是 `AgentDefinition.modelId`:会话不记模型,配置也没有
|
|
50
|
+
* `defaultModel`。这样状态栏、落盘与 SDK 实发请求用的永远是同一个值。
|
|
51
|
+
*/
|
|
52
|
+
get modelId() {
|
|
53
|
+
return this.agent.definition.modelId ?? "";
|
|
54
|
+
}
|
|
27
55
|
agentId;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
/** 当前模型的上下文阈值(交接软线 + 阻断硬线),缺省时不装护栏插件 */
|
|
57
|
+
tokenThresholds;
|
|
58
|
+
/**
|
|
59
|
+
* 会话 id / 名称的**唯一真相源是 `ctx`**(`ConversationContext`)。
|
|
60
|
+
*
|
|
61
|
+
* 为什么必须代理而不是各存一份:自动迁移由 SDK 的 `AutoMigratePlugin` 直接驱动
|
|
62
|
+
* `HandoffService.migrate()`,**不经过本类任何方法**,新 id 只会写进 `ctx.currentId`
|
|
63
|
+
* (`onMigrated` 钩子)。若本类另持一份 `currentId`,自动迁移后它就停在旧值上,
|
|
64
|
+
* 而 `ConversationPersistPlugin.getId()` 读的正是这份旧值——于是 prune 之后的
|
|
65
|
+
* 「系统提示 + 交接断点」会被写进**旧会话文件**,把已保存的完整历史覆盖掉。
|
|
66
|
+
*/
|
|
67
|
+
get currentId() {
|
|
68
|
+
return this.ctx.currentId;
|
|
69
|
+
}
|
|
70
|
+
set currentId(id) {
|
|
71
|
+
this.ctx.currentId = id;
|
|
72
|
+
}
|
|
73
|
+
get currentName() {
|
|
74
|
+
return this.ctx.currentName;
|
|
75
|
+
}
|
|
76
|
+
set currentName(name) {
|
|
77
|
+
this.ctx.currentName = name;
|
|
78
|
+
}
|
|
31
79
|
agent;
|
|
32
80
|
ctx;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
81
|
+
handoffService;
|
|
82
|
+
notifier;
|
|
83
|
+
requestMode;
|
|
36
84
|
disposers = [];
|
|
37
|
-
|
|
38
|
-
|
|
85
|
+
/** 生成过程的 token 速率统计(状态栏展示,见 `token-rate.ts`) */
|
|
86
|
+
rate = new TokenRateMeter();
|
|
87
|
+
/** 当前生成阶段(消息栏 spinner 文案,见 `phase.ts`) */
|
|
88
|
+
phaseTracker = new PhaseTracker();
|
|
89
|
+
/**
|
|
90
|
+
* 进行中的交接/迁移的取消句柄(`null` = 当前没有交接)。
|
|
91
|
+
*
|
|
92
|
+
* 为什么不能只靠 `agent.abort()`:交接的 `summary` 模式走的是独立的
|
|
93
|
+
* `chat.completions.create` 请求,`agent.abort()` 根本够不到它;而 `self` 模式
|
|
94
|
+
* 虽然能中断,但 core 层把中止收敛成「消息标记 Aborted、send 正常返回」,SDK 那边
|
|
95
|
+
* 会误判成「模型没写出文档」而重试 + 降级 summary 再跑一轮。
|
|
96
|
+
* 所以交接必须自己带一条 `AbortSignal`。
|
|
97
|
+
*/
|
|
98
|
+
handoffAbort = null;
|
|
99
|
+
/** 本次会话中已出现的子 Agent(只保留当前一轮,见 {@link SubAgentView} 的生命周期说明) */
|
|
100
|
+
subAgentViews = [];
|
|
101
|
+
/** 展示序号(每轮归零,标签回到「子AGENT1」) */
|
|
102
|
+
subAgentSeq = 0;
|
|
103
|
+
/**
|
|
104
|
+
* 视图 id 序号(**永久递增,每轮也不清零**)。
|
|
105
|
+
*
|
|
106
|
+
* 与展示序号分开是必要的:若 id 也跟着归零,下一轮的 `sub-1` 会与上一轮同名,
|
|
107
|
+
* 而选中态是按 id 匹配的——残留的选中 id 会突然「接上」新一轮的子 Agent。
|
|
108
|
+
*/
|
|
109
|
+
subAgentIdSeq = 0;
|
|
110
|
+
/** Agent 工厂 / Scope 重置(构造时可注入,见 `ChatSessionOptions.deps`) */
|
|
111
|
+
createAgentFn;
|
|
112
|
+
resetScopeFn;
|
|
113
|
+
constructor(handoffService, ctx, options, tokenThresholds, notifier) {
|
|
114
|
+
this.handoffService = handoffService;
|
|
39
115
|
this.ctx = ctx;
|
|
40
|
-
this.modelId = options.modelId;
|
|
41
116
|
this.agentId = options.agentId || "default";
|
|
42
|
-
this.
|
|
117
|
+
this.tokenThresholds = tokenThresholds;
|
|
43
118
|
this.currentId = ctx.currentId || generateConversationId();
|
|
44
119
|
this.currentName = ctx.currentName;
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
120
|
+
this.notifier = notifier;
|
|
121
|
+
this.requestMode = options.requestMode;
|
|
122
|
+
this.createAgentFn = options.deps?.createAgent ?? createAgent;
|
|
123
|
+
this.resetScopeFn = options.deps?.resetScope ?? resetScope;
|
|
47
124
|
}
|
|
48
|
-
/** 创建会话:装配 Agent
|
|
49
|
-
static async create(options,
|
|
125
|
+
/** 创建会话:装配 Agent、插件与「变更信号」 */
|
|
126
|
+
static async create(options, notifier) {
|
|
50
127
|
const config = await readConfig();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const agent = await
|
|
128
|
+
// 先建 Agent:模型来自 Agent 定义(`createAgent` 内部解析 modelId),
|
|
129
|
+
// 阈值再按该模型查配置——不存在「CLI 以为的模型」与「实发模型」两套值。
|
|
130
|
+
const createAgentFn = options.deps?.createAgent ?? createAgent;
|
|
131
|
+
const agent = await createAgentFn({ messages: options.messages, agentId: options.agentId });
|
|
132
|
+
const model = config.models.find((m) => m.id === agent.definition.modelId);
|
|
133
|
+
// 阈值由配置直接给出(旧字段已在 SDK 的 ConfigManager.read() 里迁移过)
|
|
134
|
+
const tokenThresholds = model?.tokenThresholds;
|
|
55
135
|
const currentId = options.conversationId || generateConversationId();
|
|
56
136
|
const ctx = {
|
|
57
137
|
agent,
|
|
58
|
-
input: "",
|
|
59
138
|
currentName: options.conversationName || currentId,
|
|
60
139
|
currentId,
|
|
61
|
-
modelId: options.modelId,
|
|
62
140
|
agentId: options.agentId,
|
|
63
|
-
running: true,
|
|
64
141
|
};
|
|
65
|
-
const
|
|
66
|
-
//
|
|
67
|
-
log: (text) =>
|
|
142
|
+
const handoffService = createHandoffService(ctx, {
|
|
143
|
+
// 迁移进度/结果经通知通道渲染,避免直接写 stdout 撕裂画面
|
|
144
|
+
log: (text) => notifier.onNotice(text, "info"),
|
|
68
145
|
});
|
|
69
|
-
ctx.
|
|
70
|
-
const session = new ChatSession(
|
|
146
|
+
ctx.handoffService = handoffService;
|
|
147
|
+
const session = new ChatSession(handoffService, ctx, options, tokenThresholds, notifier);
|
|
71
148
|
session.agent = agent;
|
|
72
149
|
await session.attach(agent);
|
|
73
150
|
return session;
|
|
74
151
|
}
|
|
152
|
+
/** 对话消息(唯一真相源) */
|
|
75
153
|
get messages() {
|
|
76
154
|
return this.agent.messages;
|
|
77
155
|
}
|
|
78
|
-
/**
|
|
156
|
+
/** 本次会话已出现的子 Agent(按出现顺序);对话屏据此渲染 Agent 栏目 */
|
|
157
|
+
get subAgents() {
|
|
158
|
+
return this.subAgentViews;
|
|
159
|
+
}
|
|
160
|
+
/** 最近一次响应的 token 用量(供「本轮 / 当前上下文占用」展示与阈值判定) */
|
|
79
161
|
get lastUsage() {
|
|
80
162
|
return this.agent.lastUsage;
|
|
81
163
|
}
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
164
|
+
/**
|
|
165
|
+
* 本会话累计 token 用量。
|
|
166
|
+
*
|
|
167
|
+
* 逐消息求和(`sumUsage`):含各 assistant 消息的 `usage`(自身响应)与工具消息的
|
|
168
|
+
* `subAgentUsage`(内部委派——含所有层级子 Agent)。每轮请求都会重发完整历史,
|
|
169
|
+
* 故求和即计费口径的累计消耗;且随消息持久化,续接 / `/load` 亦不丢。
|
|
170
|
+
*/
|
|
171
|
+
get totalUsage() {
|
|
172
|
+
return sumUsage(this.agent.messages);
|
|
173
|
+
}
|
|
174
|
+
/** 当前速率快照:生成中看 `live`(实时),结束后看 `average`(本轮平均) */
|
|
175
|
+
get rateSample() {
|
|
176
|
+
return this.rate.snapshot();
|
|
177
|
+
}
|
|
178
|
+
/** 当前生成阶段;空闲时为 null(消息栏据此显示「处理中」或不起 spinner) */
|
|
179
|
+
get phase() {
|
|
180
|
+
return this.phaseTracker.phase;
|
|
181
|
+
}
|
|
182
|
+
/** 清空 Agent 栏目(新一轮输入 / 换 Agent / 换会话时调用;序号一并归零) */
|
|
183
|
+
clearSubAgents() {
|
|
184
|
+
this.subAgentViews = [];
|
|
185
|
+
this.subAgentSeq = 0;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 发送一条消息;内容变化经 `notifier.onChange` 通知,`resolve` 表示本轮结束。
|
|
189
|
+
*
|
|
190
|
+
* `images` 非空时,消息内容由「文本段 + 图片内容块」组成(转换规则与降级全在
|
|
191
|
+
* `user-content.ts`):这是粘贴图片唯一的入口,不另开一条发送路径。
|
|
192
|
+
*/
|
|
193
|
+
async send(input, images = []) {
|
|
194
|
+
this.rate.begin();
|
|
195
|
+
this.phaseTracker.connecting();
|
|
85
196
|
try {
|
|
86
|
-
|
|
197
|
+
if (images.length === 0) {
|
|
198
|
+
await this.agent.send(input);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const config = await readConfig();
|
|
202
|
+
const { content, warnings } = await buildUserContent({
|
|
203
|
+
text: input,
|
|
204
|
+
images,
|
|
205
|
+
config,
|
|
206
|
+
// 端点 / 视觉能力按**实发模型名**反查:`file_id` 是端点维度资源,传错端点会 not found
|
|
207
|
+
apiModelName: this.agent.model,
|
|
208
|
+
});
|
|
209
|
+
for (const warning of warnings)
|
|
210
|
+
this.notifier.onNotice(warning, "info");
|
|
211
|
+
await this.agent.send(content);
|
|
212
|
+
}
|
|
87
213
|
}
|
|
88
214
|
catch (error) {
|
|
89
|
-
this.
|
|
215
|
+
this.notifier.onNotice(error?.message ?? String(error), "error");
|
|
90
216
|
}
|
|
91
217
|
finally {
|
|
92
|
-
this.
|
|
218
|
+
this.rate.finish();
|
|
219
|
+
this.phaseTracker.reset();
|
|
220
|
+
// 本轮内循环(含全部工具调用)已结束:子 Agent 只剩历史价值,栏目随之消失。
|
|
221
|
+
// 放 `finally` 是为了中止 / 出错时也不留残影;序号一并归零,下一轮又是「子AGENT1」。
|
|
222
|
+
this.clearSubAgents();
|
|
223
|
+
this.notifier.onChange(true);
|
|
93
224
|
}
|
|
94
225
|
}
|
|
95
226
|
/** 中止当前请求(Ctrl+C 语义:仅取消本轮,不退出) */
|
|
96
227
|
abort() {
|
|
228
|
+
// 交接/迁移进行中:先按交接的取消通道(否则 summary 那步压根拦不住)
|
|
229
|
+
this.handoffAbort?.abort();
|
|
97
230
|
this.agent.abort();
|
|
98
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* 按「可取消」方式跑一段交接:创建 `AbortController` 并在结束时收尾。
|
|
234
|
+
*
|
|
235
|
+
* 收尾放 `finally`:成功后也要清掉,否则下一次 Ctrl+C 会往一个已完成的
|
|
236
|
+
* controller 上发出无意义的 abort。
|
|
237
|
+
*/
|
|
238
|
+
async runCancellable(run) {
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
this.handoffAbort = controller;
|
|
241
|
+
try {
|
|
242
|
+
return await run(controller.signal);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
if (this.handoffAbort === controller)
|
|
246
|
+
this.handoffAbort = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
99
249
|
/**
|
|
100
250
|
* 重置会话(/new):先把当前对话归档(有内容才存),再清空历史并重建 Agent。
|
|
101
251
|
*
|
|
@@ -107,16 +257,16 @@ export class ChatSession {
|
|
|
107
257
|
const hasContent = this.agent.messages.some((m) => m.role === AgentNS.Role.User);
|
|
108
258
|
const archivedId = hasContent ? await this.save() : null;
|
|
109
259
|
this.dispose();
|
|
110
|
-
|
|
260
|
+
this.clearSubAgents();
|
|
261
|
+
const agent = await this.createAgentFn({
|
|
111
262
|
agentId: this.agentId === "default" ? undefined : this.agentId,
|
|
112
263
|
});
|
|
113
264
|
this.agent = agent;
|
|
114
265
|
this.ctx.agent = agent;
|
|
115
266
|
this.currentId = generateConversationId();
|
|
116
|
-
this.ctx.currentId = this.currentId;
|
|
117
267
|
this.currentName = this.currentId;
|
|
118
|
-
this.ctx.currentName = this.currentName;
|
|
119
268
|
await this.attach(agent);
|
|
269
|
+
this.notifier.onChange(true);
|
|
120
270
|
return archivedId;
|
|
121
271
|
}
|
|
122
272
|
/**
|
|
@@ -129,24 +279,52 @@ export class ChatSession {
|
|
|
129
279
|
async reload() {
|
|
130
280
|
const messages = [...this.agent.messages];
|
|
131
281
|
this.dispose();
|
|
132
|
-
|
|
133
|
-
|
|
282
|
+
this.clearSubAgents();
|
|
283
|
+
await this.resetScopeFn();
|
|
284
|
+
const agent = await this.createAgentFn({
|
|
134
285
|
messages,
|
|
135
286
|
agentId: this.agentId === "default" ? undefined : this.agentId,
|
|
136
287
|
});
|
|
137
288
|
this.agent = agent;
|
|
138
289
|
this.ctx.agent = agent;
|
|
139
290
|
await this.attach(agent);
|
|
291
|
+
this.notifier.onChange(true);
|
|
140
292
|
}
|
|
141
|
-
/**
|
|
142
|
-
async migrate() {
|
|
293
|
+
/** 手动任务迁移(`/migrate`):交接 + 开新会话;与自动迁移共用同一服务实例 */
|
|
294
|
+
async migrate(mode) {
|
|
143
295
|
if (!this.messages.some((m) => m.role === AgentNS.Role.User)) {
|
|
144
|
-
throw new Error("
|
|
296
|
+
throw new Error(t("chat.noMigratable"));
|
|
145
297
|
}
|
|
146
|
-
await this.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
298
|
+
const ctx = await this.runCancellable((signal) => this.handoffService.migrate({
|
|
299
|
+
agent: this.agent,
|
|
300
|
+
mode,
|
|
301
|
+
conversationId: this.currentId,
|
|
302
|
+
signal,
|
|
303
|
+
}));
|
|
304
|
+
// 迁移后服务会开启新会话:新 id 已写进 ctx(唯一真相源),这里只需同步名称并刷新界面
|
|
305
|
+
this.currentName = this.currentId;
|
|
306
|
+
this.clearSubAgents();
|
|
307
|
+
this.notifier.onChange(true);
|
|
308
|
+
return ctx.handoffPath;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* 只产出交接文档(`/handoff`):不切换会话、不改动历史。
|
|
312
|
+
*
|
|
313
|
+
* 交接回合(指令 + 工具调用 + 回执)会留在 `agent.messages` 里,
|
|
314
|
+
* 用户可继续要求细化同一份文档(重写同一路径即可)。
|
|
315
|
+
*/
|
|
316
|
+
async handoff(mode) {
|
|
317
|
+
if (!this.messages.some((m) => m.role === AgentNS.Role.User)) {
|
|
318
|
+
throw new Error(t("chat.noHandoffable"));
|
|
319
|
+
}
|
|
320
|
+
const ctx = await this.runCancellable((signal) => this.handoffService.handoff({
|
|
321
|
+
agent: this.agent,
|
|
322
|
+
mode,
|
|
323
|
+
conversationId: this.currentId,
|
|
324
|
+
signal,
|
|
325
|
+
}));
|
|
326
|
+
this.notifier.onChange(true);
|
|
327
|
+
return ctx.handoffPath;
|
|
150
328
|
}
|
|
151
329
|
/** 撤回:删除索引 index 起的消息,返回被撤回的首条用户消息文本(用于预填输入) */
|
|
152
330
|
rollbackTo(index) {
|
|
@@ -160,113 +338,171 @@ export class ChatSession {
|
|
|
160
338
|
}
|
|
161
339
|
/** 保存当前对话到存档 */
|
|
162
340
|
async save() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
341
|
+
return persistConversation({
|
|
342
|
+
id: this.currentId,
|
|
343
|
+
agentId: this.agentId,
|
|
344
|
+
messages: this.agent.messages,
|
|
345
|
+
cwd: process.cwd(),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* 把当前会话存成指定文件名(`/save <名字>`),成功后 `currentId` 与 last-session
|
|
350
|
+
* 指针一并跟过去,于是后续自动落盘直接写新文件。
|
|
351
|
+
*
|
|
352
|
+
* 实现是「写新文件 → 删旧文件」,而不是 `fs.rename`:
|
|
353
|
+
* · rename 要求旧文件**已存在**,而刚开的新会话还没落过盘(会 ENOENT);
|
|
354
|
+
* · 覆盖已有目标在 Windows 上还得先 unlink,跨平台两边都得写分支;
|
|
355
|
+
* · 写新文件时父目录由仓储 `write()` 的 `mkdir(recursive)` 负责(`/save a/b` 可用)。
|
|
356
|
+
* 中途被杀最多留下两份副本(可读、无损),不会丢内容。
|
|
357
|
+
*/
|
|
358
|
+
async saveAs(id) {
|
|
359
|
+
const previous = this.currentId;
|
|
360
|
+
await persistConversation({
|
|
167
361
|
id,
|
|
168
362
|
agentId: this.agentId,
|
|
169
|
-
modelId: this.modelId,
|
|
170
363
|
messages: this.agent.messages,
|
|
171
364
|
cwd: process.cwd(),
|
|
172
|
-
createdAt: existing?.createdAt ?? now,
|
|
173
|
-
updatedAt: now,
|
|
174
365
|
});
|
|
175
|
-
|
|
366
|
+
if (id === previous)
|
|
367
|
+
return id; // 自己盖自己:不删
|
|
368
|
+
// 旧文件不能留:同一份对话出现两条记录会让 `/load` 里分不清哪条是新的
|
|
369
|
+
await fs.rm(conversationRepository().filePath(previous), { force: true });
|
|
370
|
+
this.currentId = id;
|
|
371
|
+
this.currentName = id;
|
|
176
372
|
return id;
|
|
177
373
|
}
|
|
178
374
|
/** 释放事件监听(重建 Agent 时调用,避免监听器泄漏) */
|
|
179
375
|
dispose() {
|
|
180
376
|
for (const off of this.disposers.splice(0))
|
|
181
377
|
off();
|
|
378
|
+
this.rate.reset();
|
|
379
|
+
this.phaseTracker.reset();
|
|
182
380
|
}
|
|
183
381
|
// ==================== 内部:装配 Agent 与插件 ====================
|
|
184
382
|
async attach(agent) {
|
|
383
|
+
agent.use(new PhasePlugin(this.phaseTracker));
|
|
185
384
|
agent.use(new CwdTrackerPlugin());
|
|
186
385
|
agent.use(new AutoRefreshToolsPlugin());
|
|
187
386
|
agent.use(new ConversationPersistPlugin({
|
|
188
387
|
getId: () => this.currentId,
|
|
189
388
|
agentId: this.agentId,
|
|
190
|
-
modelId: this.modelId,
|
|
191
389
|
cwd: process.cwd(),
|
|
192
|
-
|
|
390
|
+
// 每轮自动落盘失败是真错误(用户会以为什么都没丢)—— 走 error 语气
|
|
391
|
+
onError: (text) => this.notifier.onNotice(text, "error"),
|
|
193
392
|
}));
|
|
194
|
-
if (this.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
393
|
+
if (this.tokenThresholds) {
|
|
394
|
+
const { handoff, guard } = this.tokenThresholds;
|
|
395
|
+
// 硬线:请求前用量超过 guard 即中断本轮(绝对值,不再有 ratio/放宽档)
|
|
396
|
+
agent.use(new ContextGuardPlugin({ guardTokens: guard }));
|
|
397
|
+
agent.use(createAutoMigratePlugin({
|
|
398
|
+
service: this.handoffService,
|
|
399
|
+
handoffTokens: handoff,
|
|
400
|
+
// TUI 下由 Ink 层提供执行者选择器;无选择通道时按默认执行者(原 agent 自交接)迁移
|
|
401
|
+
shouldConfirm: () => Boolean(this.requestMode),
|
|
402
|
+
choose: (info) => this.requestMode(info),
|
|
403
|
+
log: (text) => this.notifier.onNotice(text, "info"),
|
|
203
404
|
}));
|
|
204
|
-
agent.use(new SubAgentGuardInstallerPlugin({
|
|
405
|
+
agent.use(new SubAgentGuardInstallerPlugin({ guardTokens: guard }));
|
|
205
406
|
}
|
|
206
407
|
this.wire(agent);
|
|
207
408
|
await agent.init();
|
|
208
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* 订阅 SDK 事件,仅把它们当作「messages 可能已变化」的信号:
|
|
412
|
+
* - `chunk-parsed`(每片 delta 应用后):高频 → 走合帧(`immediate=false`);
|
|
413
|
+
* - `open` / `parsed` / `finally`:轮次边界 → 立即上屏;
|
|
414
|
+
* - `error`:转发到通知通道。
|
|
415
|
+
*/
|
|
209
416
|
wire(agent) {
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
417
|
+
const notify = (immediate) => this.notifier.onChange(immediate);
|
|
418
|
+
const onOpen = () => {
|
|
419
|
+
this.phaseTracker.opened();
|
|
420
|
+
notify(true);
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* 每片 delta 应用后:除了通知重渲染,还要把这段文本计入速率统计。
|
|
424
|
+
*
|
|
425
|
+
* 子 Agent 的 chunk 也走这个回调(`onSubStart` 里同样订阅),因此速率包含
|
|
426
|
+
* 委派出去的子任务输出——它们也是真实从网络接收的 token。
|
|
427
|
+
*/
|
|
428
|
+
const onChunk = (_receiver, chunk) => {
|
|
429
|
+
this.rate.addDelta(deltaText(chunk));
|
|
430
|
+
// 流末 chunk 携带真实用量(`stream_options.include_usage`):多轮工具调用逐轮累加
|
|
431
|
+
this.rate.addExact(chunk?.usage?.completion_tokens);
|
|
432
|
+
this.phaseTracker.delta(chunk);
|
|
433
|
+
notify(false);
|
|
434
|
+
};
|
|
435
|
+
const onParsed = (receiver) => {
|
|
436
|
+
// 该轮带工具调用 → 接下来是工具执行(模型不在跑),阶段转「等工具返回」
|
|
437
|
+
this.phaseTracker.streamEnded(receiver);
|
|
438
|
+
notify(true);
|
|
439
|
+
};
|
|
440
|
+
/** 子 Agent 的事件:只重绘,不推进主阶段(理由见 `onSubStart`) */
|
|
441
|
+
const onSubOpen = () => notify(true);
|
|
442
|
+
const onSubChunk = (_receiver, chunk) => {
|
|
443
|
+
this.rate.addDelta(deltaText(chunk));
|
|
444
|
+
this.rate.addExact(chunk?.usage?.completion_tokens);
|
|
445
|
+
notify(false);
|
|
446
|
+
};
|
|
447
|
+
const onFinally = () => notify(true);
|
|
448
|
+
const onError = (error) => {
|
|
449
|
+
this.notifier.onNotice(error?.message ?? String(error), "error");
|
|
450
|
+
notify(true);
|
|
219
451
|
};
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
452
|
+
const onSubStart = ({ subAgent }) => {
|
|
453
|
+
// 执行状态内联到栏目标签(`●` / `✓` / `✖`),**不再推「子任务运行中」通知**:
|
|
454
|
+
// 那种通知让通知栏凭空多出一行,整帧高度跟着跳。
|
|
455
|
+
const index = ++this.subAgentSeq;
|
|
456
|
+
const view = {
|
|
457
|
+
id: `sub-${++this.subAgentIdSeq}`,
|
|
458
|
+
index,
|
|
459
|
+
agent: subAgent,
|
|
460
|
+
status: "running",
|
|
461
|
+
};
|
|
462
|
+
this.subAgentViews.push(view);
|
|
463
|
+
// 记进 Agent 栏目:实例在委派结束后仍保留 messages,所以这里存引用即可随时回看。
|
|
464
|
+
// 不看 `sub_agent_*` 与 `call_skill_sub_agent` 的差别——两条路径现在都走 core 的
|
|
465
|
+
// AgentToolLazy,全部会在 onSubAgentStart 出现。
|
|
466
|
+
// 子 Agent 的流只计入速率并触发重绘,**不推进主阶段**:此刻主 Agent 正在等
|
|
467
|
+
// 这个工具(委派)返回,阶段应停在「等待 xxx 返回」;而子 Agent 自己的思考 / 输出
|
|
468
|
+
// 是工具内部的事,拿它的 delta 去改主阶段,界面会变成「输出中」——正好盖掉了
|
|
469
|
+
// 用户最需要知道的那句话。
|
|
470
|
+
// 这里挂的监听**不需要**在 `onSubEnd` 撤销:子 Agent 实例是「每次委派新建」的
|
|
471
|
+
// (core 的 `AgentTool.exec()` 里 `new Agent(...)`、`AgentToolLazy.exec()` 里
|
|
472
|
+
// `buildAgent(...)`),随本次 run 结束回收,监听器跟着走。别为此再加 off。
|
|
473
|
+
subAgent.events.on("open", onSubOpen);
|
|
474
|
+
subAgent.events.on("chunk-parsed", onSubChunk);
|
|
475
|
+
// 出错要落到标签上(✖);错误信息仍送通知栏——那是错误,不是进度
|
|
476
|
+
subAgent.events.on("error", (error) => {
|
|
477
|
+
view.status = "failed";
|
|
478
|
+
this.notifier.onNotice(error?.message ?? String(error), "error");
|
|
479
|
+
notify(true);
|
|
480
|
+
});
|
|
481
|
+
notify(true);
|
|
482
|
+
};
|
|
483
|
+
const onSubEnd = ({ subAgent }) => {
|
|
484
|
+
// 只把「还在跑」的置为完成:失败已由 error 监听标成 failed,不能被这里覆盖
|
|
485
|
+
const view = this.subAgentViews.find((item) => item.agent === subAgent);
|
|
486
|
+
if (view && view.status === "running")
|
|
487
|
+
view.status = "done";
|
|
488
|
+
notify(true);
|
|
223
489
|
};
|
|
224
490
|
agent.events.on("open", onOpen);
|
|
225
|
-
agent.events.on("chunk", onChunk);
|
|
491
|
+
agent.events.on("chunk-parsed", onChunk);
|
|
492
|
+
agent.events.on("parsed", onParsed);
|
|
493
|
+
agent.events.on("finally", onFinally);
|
|
226
494
|
agent.events.on("error", onError);
|
|
227
495
|
agent.events.on("sub-agent-start", onSubStart);
|
|
228
496
|
agent.events.on("sub-agent-end", onSubEnd);
|
|
229
497
|
this.disposers.push(() => {
|
|
230
498
|
agent.events.off("open", onOpen);
|
|
231
|
-
agent.events.off("chunk", onChunk);
|
|
499
|
+
agent.events.off("chunk-parsed", onChunk);
|
|
500
|
+
agent.events.off("parsed", onParsed);
|
|
501
|
+
agent.events.off("finally", onFinally);
|
|
232
502
|
agent.events.off("error", onError);
|
|
233
503
|
agent.events.off("sub-agent-start", onSubStart);
|
|
234
504
|
agent.events.off("sub-agent-end", onSubEnd);
|
|
235
505
|
});
|
|
236
506
|
}
|
|
237
|
-
/** 把流式 delta 翻译为结构化事件 */
|
|
238
|
-
emitDelta(chunk) {
|
|
239
|
-
const delta = chunk?.choices?.[0]?.delta;
|
|
240
|
-
if (!delta)
|
|
241
|
-
return;
|
|
242
|
-
if (delta.reasoning_content) {
|
|
243
|
-
this.onEvent({ type: "reasoning", text: delta.reasoning_content });
|
|
244
|
-
}
|
|
245
|
-
if (delta.content) {
|
|
246
|
-
if (typeof delta.content === "string") {
|
|
247
|
-
this.onEvent({ type: "content", text: delta.content });
|
|
248
|
-
}
|
|
249
|
-
else if (Array.isArray(delta.content)) {
|
|
250
|
-
for (const part of delta.content) {
|
|
251
|
-
if (part.type === "text" && part.text) {
|
|
252
|
-
this.onEvent({ type: "content", text: part.text });
|
|
253
|
-
}
|
|
254
|
-
else if (part.type === "image_url") {
|
|
255
|
-
this.onEvent({ type: "notice", text: `[图片: ${part.image_url.url}]` });
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (delta.tool_calls) {
|
|
261
|
-
for (const tc of delta.tool_calls) {
|
|
262
|
-
this.onEvent({
|
|
263
|
-
type: "tool",
|
|
264
|
-
index: tc.index ?? 0,
|
|
265
|
-
name: tc.function?.name,
|
|
266
|
-
args: tc.function?.arguments,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
507
|
}
|
|
272
508
|
//# sourceMappingURL=chat-session.js.map
|