@ai-zen/agents-host 0.0.0-stage → 0.1.0-alpha.1
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 +39 -0
- package/LICENSE +21 -0
- package/README.md +759 -2
- package/dist/app.d.ts +384 -0
- package/dist/app.js +1148 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +354 -0
- package/dist/config.d.ts +97 -0
- package/dist/config.js +171 -0
- package/dist/handlers/agentHandlers.d.ts +18 -0
- package/dist/handlers/agentHandlers.js +20 -0
- package/dist/handlers/assetHandlers.d.ts +29 -0
- package/dist/handlers/assetHandlers.js +35 -0
- package/dist/handlers/capabilities.d.ts +26 -0
- package/dist/handlers/capabilities.js +138 -0
- package/dist/handlers/completionHandlers.d.ts +18 -0
- package/dist/handlers/completionHandlers.js +13 -0
- package/dist/handlers/configHandlers.d.ts +31 -0
- package/dist/handlers/configHandlers.js +59 -0
- package/dist/handlers/conversationHandlers.d.ts +118 -0
- package/dist/handlers/conversationHandlers.js +314 -0
- package/dist/handlers/handoffHandlers.d.ts +17 -0
- package/dist/handlers/handoffHandlers.js +14 -0
- package/dist/handlers/hostHandlers.d.ts +39 -0
- package/dist/handlers/hostHandlers.js +56 -0
- package/dist/handlers/index.d.ts +128 -0
- package/dist/handlers/index.js +92 -0
- package/dist/handlers/messageHandlers.d.ts +11 -0
- package/dist/handlers/messageHandlers.js +10 -0
- package/dist/handlers/modelHandlers.d.ts +17 -0
- package/dist/handlers/modelHandlers.js +19 -0
- package/dist/handlers/noticeHandlers.d.ts +24 -0
- package/dist/handlers/noticeHandlers.js +24 -0
- package/dist/handlers/questionHandlers.d.ts +16 -0
- package/dist/handlers/questionHandlers.js +16 -0
- package/dist/handlers/searchHandlers.d.ts +12 -0
- package/dist/handlers/searchHandlers.js +12 -0
- package/dist/handlers/workspaceHandlers.d.ts +29 -0
- package/dist/handlers/workspaceHandlers.js +47 -0
- package/dist/i18n/en.d.ts +243 -0
- package/dist/i18n/en.js +270 -0
- package/dist/i18n/index.d.ts +43 -0
- package/dist/i18n/index.js +72 -0
- package/dist/i18n/zh.d.ts +240 -0
- package/dist/i18n/zh.js +257 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +15 -0
- package/dist/net/UploadReceiver.d.ts +186 -0
- package/dist/net/UploadReceiver.js +494 -0
- package/dist/net/WsHub.d.ts +231 -0
- package/dist/net/WsHub.js +689 -0
- package/dist/net/auth.d.ts +73 -0
- package/dist/net/auth.js +74 -0
- package/dist/net/discovery.d.ts +72 -0
- package/dist/net/discovery.js +171 -0
- package/dist/net/index.d.ts +14 -0
- package/dist/net/index.js +10 -0
- package/dist/runtime/ChatRunner.d.ts +374 -0
- package/dist/runtime/ChatRunner.js +1204 -0
- package/dist/runtime/ConversationDrafts.d.ts +43 -0
- package/dist/runtime/ConversationDrafts.js +47 -0
- package/dist/runtime/RunRegistry.d.ts +79 -0
- package/dist/runtime/RunRegistry.js +108 -0
- package/dist/runtime/ScopeRegistry.d.ts +153 -0
- package/dist/runtime/ScopeRegistry.js +259 -0
- package/dist/runtime/TransientRunner.d.ts +90 -0
- package/dist/runtime/TransientRunner.js +179 -0
- package/dist/runtime/index.d.ts +14 -0
- package/dist/runtime/index.js +10 -0
- package/dist/services/AgentService.d.ts +150 -0
- package/dist/services/AgentService.js +718 -0
- package/dist/services/AssetService.d.ts +338 -0
- package/dist/services/AssetService.js +739 -0
- package/dist/services/AutoNaming.d.ts +92 -0
- package/dist/services/AutoNaming.js +235 -0
- package/dist/services/CompletionService.d.ts +122 -0
- package/dist/services/CompletionService.js +442 -0
- package/dist/services/ConfigService.d.ts +171 -0
- package/dist/services/ConfigService.js +695 -0
- package/dist/services/ConversationService.d.ts +274 -0
- package/dist/services/ConversationService.js +902 -0
- package/dist/services/CredentialService.d.ts +33 -0
- package/dist/services/CredentialService.js +86 -0
- package/dist/services/DiscoveryService.d.ts +182 -0
- package/dist/services/DiscoveryService.js +998 -0
- package/dist/services/HandoffService.d.ts +189 -0
- package/dist/services/HandoffService.js +414 -0
- package/dist/services/Logger.d.ts +130 -0
- package/dist/services/Logger.js +256 -0
- package/dist/services/MessageService.d.ts +88 -0
- package/dist/services/MessageService.js +280 -0
- package/dist/services/ModelService.d.ts +89 -0
- package/dist/services/ModelService.js +489 -0
- package/dist/services/NoticeService.d.ts +93 -0
- package/dist/services/NoticeService.js +217 -0
- package/dist/services/QuestionService.d.ts +184 -0
- package/dist/services/QuestionService.js +457 -0
- package/dist/services/SearchService.d.ts +54 -0
- package/dist/services/SearchService.js +117 -0
- package/dist/services/WorkspaceService.d.ts +96 -0
- package/dist/services/WorkspaceService.js +213 -0
- package/dist/services/imageSize.d.ts +41 -0
- package/dist/services/imageSize.js +146 -0
- package/dist/services/index.d.ts +20 -0
- package/dist/services/index.js +15 -0
- package/dist/services/secretMask.d.ts +24 -0
- package/dist/services/secretMask.js +34 -0
- package/dist/storage/AssetRepository.d.ts +87 -0
- package/dist/storage/AssetRepository.js +180 -0
- package/dist/storage/ConversationRepository.d.ts +55 -0
- package/dist/storage/ConversationRepository.js +128 -0
- package/dist/storage/CredentialRepository.d.ts +28 -0
- package/dist/storage/CredentialRepository.js +47 -0
- package/dist/storage/Database.d.ts +95 -0
- package/dist/storage/Database.js +180 -0
- package/dist/storage/MessageRepository.d.ts +104 -0
- package/dist/storage/MessageRepository.js +323 -0
- package/dist/storage/MetaRepository.d.ts +21 -0
- package/dist/storage/MetaRepository.js +33 -0
- package/dist/storage/NoticeRepository.d.ts +31 -0
- package/dist/storage/NoticeRepository.js +89 -0
- package/dist/storage/QuestionRepository.d.ts +34 -0
- package/dist/storage/QuestionRepository.js +96 -0
- package/dist/storage/WorkspaceRepository.d.ts +25 -0
- package/dist/storage/WorkspaceRepository.js +44 -0
- package/dist/storage/dbWorker.d.ts +10 -0
- package/dist/storage/dbWorker.js +132 -0
- package/dist/storage/index.d.ts +20 -0
- package/dist/storage/index.js +16 -0
- package/dist/storage/migrations.d.ts +26 -0
- package/dist/storage/migrations.js +207 -0
- package/dist/storage/types.d.ts +298 -0
- package/dist/storage/types.js +14 -0
- package/dist/wire/errors.d.ts +105 -0
- package/dist/wire/errors.js +143 -0
- package/dist/wire/events.d.ts +102 -0
- package/dist/wire/events.js +13 -0
- package/dist/wire/frames.d.ts +106 -0
- package/dist/wire/frames.js +156 -0
- package/dist/wire/index.d.ts +31 -0
- package/dist/wire/index.js +31 -0
- package/dist/wire/methods.d.ts +1896 -0
- package/dist/wire/methods.js +99 -0
- package/dist/wire/states.d.ts +487 -0
- package/dist/wire/states.js +61 -0
- package/package.json +63 -3
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `conversation.*`:一段对话的身份、历史与一轮的运行。
|
|
3
|
+
*
|
|
4
|
+
* 这里只做「参数 → 交给服务/运行时 → 把结果翻译成契约形状」。业务判断在
|
|
5
|
+
* `ConversationService` 与 `ChatRunner` 里:比如「忙就拒」在 `RunRegistry`、
|
|
6
|
+
* 「模型解析不出来」在服务层,本文件不重新判断一遍。
|
|
7
|
+
*
|
|
8
|
+
* 唯一在接线层做的拼装是 `conversation.state` 的现场快照:它要把三处事实合起来
|
|
9
|
+
* (服务层给的视图、运行时给的这一轮、库里消息求和的用量),而这三样分别属于三个持有者,
|
|
10
|
+
* 只有接线层同时看得见它们。
|
|
11
|
+
*
|
|
12
|
+
* 一段对话的三处跨界钩子也在这里接(它们分属三个服务,契约里的位置却是同一对方法):
|
|
13
|
+
* - `conversation.send` 之前问一句「有挡着的问题吗」(D23);
|
|
14
|
+
* - 结算之后起一次自动命名(D2,幂等);
|
|
15
|
+
* - `conversation.transient` 走不落库的那条执行路径(R1 / R49)。
|
|
16
|
+
*/
|
|
17
|
+
import { addUsage, sumUsage, ZERO_USAGE } from "@ai-zen/agents-core";
|
|
18
|
+
import { render } from "../i18n/index.js";
|
|
19
|
+
import { STATUS_BLOCK_IDS } from "../wire/index.js";
|
|
20
|
+
import { localizeOutcome } from "./assetHandlers.js";
|
|
21
|
+
/**
|
|
22
|
+
* 注册 `conversation.*`。
|
|
23
|
+
*
|
|
24
|
+
* `conversation.transient`(临时对话,PRD D3)注册的前提是注入了 `runTransient`:
|
|
25
|
+
* 不注入时这个方法仍然落到接线层的 `unsupported`(裁定 R11)。
|
|
26
|
+
*/
|
|
27
|
+
export function createConversationHandlers(deps) {
|
|
28
|
+
const handlers = {
|
|
29
|
+
"conversation.list": async (context) => await deps.conversations.list(context.params),
|
|
30
|
+
"conversation.create": async (context) => await deps.conversations.create(context.params),
|
|
31
|
+
"conversation.get": async (context) => await deps.conversations.get(context.params),
|
|
32
|
+
"conversation.rename": async (context) => await deps.conversations.rename(context.params),
|
|
33
|
+
"conversation.pin": async (context) => await deps.conversations.pin(context.params),
|
|
34
|
+
"conversation.revert": async (context) => await deps.conversations.revert(context.params),
|
|
35
|
+
"conversation.setModel": async (context) => await deps.conversations.setModel(context.params),
|
|
36
|
+
"conversation.delete": async (context) => await removeConversation(deps, context.params),
|
|
37
|
+
"conversation.send": async (context) => await sendTurn(deps, context.params, context.connection.language),
|
|
38
|
+
"conversation.state": async (context) => {
|
|
39
|
+
const snapshot = await conversationStateOf(deps, context.params.conversationId, context.connection.language);
|
|
40
|
+
// 消息来自同一次读取(用量与上下文已经要了它),要就一起给,不要就整个键不出现
|
|
41
|
+
return context.params.includeMessages
|
|
42
|
+
? { state: snapshot.state, messages: snapshot.messages }
|
|
43
|
+
: { state: snapshot.state };
|
|
44
|
+
},
|
|
45
|
+
"conversation.abort": async (context) => await abort(deps, context.params),
|
|
46
|
+
"conversation.setThinking": async (context) => {
|
|
47
|
+
// 档位表先刷新一次再改:改完读回的「展开后的参数」要用当前表
|
|
48
|
+
await deps.refreshThinkingPresets();
|
|
49
|
+
return await deps.conversations.setThinking(context.params);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const runTransient = deps.runTransient;
|
|
53
|
+
if (runTransient) {
|
|
54
|
+
// 临时对话没有 id、不落库、不推流式事件:一次调用同步返回结果(R1 / R49)
|
|
55
|
+
handlers["conversation.transient"] = async (context) => await runTransient(context.params, context.connection.language);
|
|
56
|
+
}
|
|
57
|
+
return handlers;
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// 跨层补齐
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
/**
|
|
63
|
+
* `conversation.send`:受理一轮。
|
|
64
|
+
*
|
|
65
|
+
* 先把草稿落成真行(R56:首次 `send` 时才写 `conversations`):FK 要求消息所属的对话已经存在,
|
|
66
|
+
* 而且后端的一切(重连、列表、删除)都按「存在即能查」处理。
|
|
67
|
+
*
|
|
68
|
+
* 素材关系行不在这里写:它跟着「用户消息落库那一刻」走(`ChatRunner` 注入的
|
|
69
|
+
* `linkMessageAssets` 扫落库内容里的 `__asset_id`),所以重试与重写都不会重复记。
|
|
70
|
+
*
|
|
71
|
+
* 附件结局里的句子是素材层按默认语言渲染的(那一层不知道收件人是谁),这里按**这条连接**的
|
|
72
|
+
* 语言重渲染一次再出网(D8:库里只留码与参数)。
|
|
73
|
+
*/
|
|
74
|
+
async function sendTurn(deps, params, language) {
|
|
75
|
+
// D23:有一段挡着的问题(交接确认、不可撤销动作的确认)就先答它再发
|
|
76
|
+
await deps.assertSendable?.(params.conversationId);
|
|
77
|
+
// R56:草稿在第一条用户消息之前不落库,这里把它写成真行(同一个 id)
|
|
78
|
+
await deps.conversations.ensurePersisted(params.conversationId);
|
|
79
|
+
const turn = await deps.runner.run({
|
|
80
|
+
conversationId: params.conversationId,
|
|
81
|
+
input: params.input,
|
|
82
|
+
language,
|
|
83
|
+
});
|
|
84
|
+
// 结算之后的两个收尾:起一次自动命名(D2,条件不成立时它自己什么都不做),
|
|
85
|
+
// 再让单根重算一次退出判据(这一轮可能是最后一件欠着的事)
|
|
86
|
+
void turn.done.then(async () => {
|
|
87
|
+
try {
|
|
88
|
+
await deps.autoNameAfterTurn?.(params.conversationId, language);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// 起名失败就是「没起成」(D2:保持原名),不影响已经结算的这一轮
|
|
92
|
+
deps.onHookError?.(error, "autoNaming");
|
|
93
|
+
}
|
|
94
|
+
deps.onTurnSettled();
|
|
95
|
+
}, () => deps.onTurnSettled());
|
|
96
|
+
return {
|
|
97
|
+
userMessageId: turn.userMessageId,
|
|
98
|
+
assistantMessageId: turn.assistantMessageId,
|
|
99
|
+
attachments: turn.attachments.map((outcome) => localizeOutcome(outcome, language)),
|
|
100
|
+
acceptedAt: turn.acceptedAt,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `conversation.delete`:删掉这段对话,再补齐一件只有接线层知道的事(R33)。
|
|
105
|
+
*
|
|
106
|
+
* 运行时里这一轮的现场要丢掉(`ChatRunner.forget`:正在跑就先让它停);素材字节的清理不在这里
|
|
107
|
+
* —— 服务层删完之后已经由素材层问过「还剩几张引用」,归零的那几份才删(见
|
|
108
|
+
* `ConversationService.delete` 的 `sweepAssets`)。
|
|
109
|
+
*/
|
|
110
|
+
async function removeConversation(deps, params) {
|
|
111
|
+
const report = await deps.conversations.delete(params);
|
|
112
|
+
if (params.dryRun !== true && report.removed) {
|
|
113
|
+
deps.runner.forget(params.conversationId);
|
|
114
|
+
}
|
|
115
|
+
return { removed: report.removed, consequences: report.consequences };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* `conversation.abort`:中止走 `RunRegistry`(`ChatRunner` 只是把它转发出去)。
|
|
119
|
+
*
|
|
120
|
+
* `aborted: false` 表示本来就没在跑 —— 那不是错误。回包同时带上中止之后的运行状态,
|
|
121
|
+
* 客户端不必再拉一次现场就能把界面收回去。
|
|
122
|
+
*/
|
|
123
|
+
async function abort(deps, params) {
|
|
124
|
+
const aborted = deps.runner.abort(params.conversationId, params.reason);
|
|
125
|
+
return { aborted, run: await runStateOf(deps, params.conversationId) };
|
|
126
|
+
}
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// 现场快照
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
/** 这一轮现在的运行状态;没有在跑的一轮时是「空闲」(`state` 与 `abort` 共用) */
|
|
131
|
+
async function runStateOf(deps, conversationId) {
|
|
132
|
+
const turn = deps.runner.turn(conversationId);
|
|
133
|
+
if (turn)
|
|
134
|
+
return turn.runState();
|
|
135
|
+
const page = await deps.messages.list({ conversationId });
|
|
136
|
+
return idleRunState(conversationId, usageOfMessages(page.messages, deps.now()));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 一段对话的现场(PRD L5:重连先拉状态)。
|
|
140
|
+
*
|
|
141
|
+
* 拼装顺序:视图(身份 / 思考档 / 目录)→ 运行状态(在跑就用这一轮自己的快照)→
|
|
142
|
+
* 用量(在跑用这一轮,空闲就用库里消息求和)→ 上下文阈值(跟着模型走)→ 挂在这段对话上的问题
|
|
143
|
+
* → 状态栏的块(PRD F-I6)。
|
|
144
|
+
*/
|
|
145
|
+
async function conversationStateOf(deps, conversationId, language) {
|
|
146
|
+
const now = deps.now();
|
|
147
|
+
const { conversation } = await deps.conversations.get({ conversationId });
|
|
148
|
+
const turn = deps.runner.turn(conversationId);
|
|
149
|
+
const page = await deps.messages.list({ conversationId });
|
|
150
|
+
const usage = turn ? turn.runState().usage : usageOfMessages(page.messages, now);
|
|
151
|
+
const config = await deps.readConfig();
|
|
152
|
+
const model = config.models.find((item) => item.id === conversation.model.modelId);
|
|
153
|
+
const run = turn ? turn.runState() : idleRunState(conversationId, usage);
|
|
154
|
+
const context = contextOf(page.messages, model);
|
|
155
|
+
// 助手那条事实只在这里取一次(IO 留在这里,下面拼块的那一步仍是同步纯函数)
|
|
156
|
+
const agent = await agentFactOf(deps, conversation.agentId);
|
|
157
|
+
return {
|
|
158
|
+
state: {
|
|
159
|
+
conversationId,
|
|
160
|
+
run,
|
|
161
|
+
context,
|
|
162
|
+
thinking: conversation.thinking,
|
|
163
|
+
usage,
|
|
164
|
+
questions: deps.questionsFor ? await deps.questionsFor(conversationId) : [],
|
|
165
|
+
statusBlocks: statusBlocksOf({ conversation, agent, run, context, usage, language }),
|
|
166
|
+
updatedAt: now,
|
|
167
|
+
},
|
|
168
|
+
// 这份消息就是上面算用量与上下文用的那一次读取,不是又读一遍
|
|
169
|
+
messages: page.messages,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// 状态栏的块(PRD F-I6 / 裁定 R65)
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
/**
|
|
176
|
+
* 助手块的那两条事实(名字 / 能不能用,PRD F-I6)。
|
|
177
|
+
*
|
|
178
|
+
* 真相在助手定义那一层,这里只读一次,把它变成块能装的那两格;拼块的 `statusBlocksOf`
|
|
179
|
+
* 于是仍然是同步纯函数。没注入助手服务、或定义已经没了(被删 / 读不出来)时两格都是 `null`
|
|
180
|
+
* —— 说「不知道」而不是编一个看起来像真的值。
|
|
181
|
+
*/
|
|
182
|
+
async function agentFactOf(deps, agentId) {
|
|
183
|
+
const described = await deps.agents?.describe(agentId);
|
|
184
|
+
if (!described)
|
|
185
|
+
return { name: null, available: null };
|
|
186
|
+
return { name: described.name, available: described.available };
|
|
187
|
+
}
|
|
188
|
+
/** 块的标签(`label.agent` / `label.model` …,PRD F-I6:宿主给块名,客户端排版) */
|
|
189
|
+
function blockLabel(id, language) {
|
|
190
|
+
const messageKey = `label.${id}`;
|
|
191
|
+
return { code: messageKey, messageKey, params: {}, message: render(language, messageKey) };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 按 `STATUS_BLOCK_IDS` 的顺序拼装状态栏的块(PRD F-I6 / 裁定 R65)。
|
|
195
|
+
*
|
|
196
|
+
* 这里**不产生新事实**:每一块装的都是别处已经算好的那份 —— 对话视图里的身份 / 模型 / 思考档 /
|
|
197
|
+
* 目录、`conversation.state` 里的上下文与用量、以及这一轮快照里的六态与子 Agent。工具块只取
|
|
198
|
+
* `awaiting_tools` 态里还在跑的那几个(并行时有几个列几个,正是 L8 要的语义);其余态下没有
|
|
199
|
+
* 工具在跑,列表就是空的。
|
|
200
|
+
*
|
|
201
|
+
* 助手块的 `name` / `available` 由调用方(`conversationStateOf`)先从助手定义那一层读好传进来
|
|
202
|
+
* (`input.agent`):本函数保持同步纯函数,不在这里做 IO。
|
|
203
|
+
*/
|
|
204
|
+
export function statusBlocksOf(input) {
|
|
205
|
+
const { conversation, agent, run, context, usage, language } = input;
|
|
206
|
+
const generation = run.generation;
|
|
207
|
+
const runningTools = generation?.phase === "awaiting_tools" ? generation.runningTools : [];
|
|
208
|
+
const blocks = {
|
|
209
|
+
agent: {
|
|
210
|
+
id: "agent",
|
|
211
|
+
label: blockLabel("agent", language),
|
|
212
|
+
facts: { agentId: conversation.agentId, name: agent.name, available: agent.available },
|
|
213
|
+
},
|
|
214
|
+
model: {
|
|
215
|
+
id: "model",
|
|
216
|
+
label: blockLabel("model", language),
|
|
217
|
+
facts: conversation.model,
|
|
218
|
+
},
|
|
219
|
+
thinking: {
|
|
220
|
+
id: "thinking",
|
|
221
|
+
label: blockLabel("thinking", language),
|
|
222
|
+
facts: conversation.thinking,
|
|
223
|
+
},
|
|
224
|
+
context: {
|
|
225
|
+
id: "context",
|
|
226
|
+
label: blockLabel("context", language),
|
|
227
|
+
facts: context,
|
|
228
|
+
},
|
|
229
|
+
usage: {
|
|
230
|
+
id: "usage",
|
|
231
|
+
label: blockLabel("usage", language),
|
|
232
|
+
facts: usage,
|
|
233
|
+
},
|
|
234
|
+
cwd: {
|
|
235
|
+
id: "cwd",
|
|
236
|
+
label: blockLabel("cwd", language),
|
|
237
|
+
facts: conversation.folder,
|
|
238
|
+
},
|
|
239
|
+
tools: {
|
|
240
|
+
id: "tools",
|
|
241
|
+
label: blockLabel("tools", language),
|
|
242
|
+
facts: { running: runningTools },
|
|
243
|
+
},
|
|
244
|
+
subagents: {
|
|
245
|
+
id: "subagents",
|
|
246
|
+
label: blockLabel("subagents", language),
|
|
247
|
+
facts: { items: run.subagents },
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
// 顺序按 `STATUS_BLOCK_IDS` 给:那是建议的摆放顺序,客户端可以只取自己摆得下的前几块
|
|
251
|
+
return STATUS_BLOCK_IDS.map((id) => blocks[id]);
|
|
252
|
+
}
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// 纯函数:把几处事实拼成契约形状
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
/** 空闲的现场:没有在跑的一轮,但历史用量还在 */
|
|
257
|
+
export function idleRunState(conversationId, usage) {
|
|
258
|
+
return {
|
|
259
|
+
conversationId,
|
|
260
|
+
status: "idle",
|
|
261
|
+
assistantMessageId: null,
|
|
262
|
+
startedAt: null,
|
|
263
|
+
settledAt: null,
|
|
264
|
+
generation: null,
|
|
265
|
+
outcome: null,
|
|
266
|
+
problem: null,
|
|
267
|
+
subagents: [],
|
|
268
|
+
usage,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* 一批消息的用量快照。
|
|
273
|
+
*
|
|
274
|
+
* `total` 与 `subAgent` 用 core 的口径求和(含子 Agent,与计费同口径);`turn` 在「没有一轮
|
|
275
|
+
* 在跑」时是零值(没有这一轮这件事,就是零),速率同理。
|
|
276
|
+
*/
|
|
277
|
+
export function usageOfMessages(messages, now) {
|
|
278
|
+
let subAgent = { ...ZERO_USAGE };
|
|
279
|
+
for (const message of messages) {
|
|
280
|
+
if (message.subAgentUsage)
|
|
281
|
+
subAgent = addUsage(subAgent, message.subAgentUsage);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
turn: { ...ZERO_USAGE },
|
|
285
|
+
total: sumUsage(messages),
|
|
286
|
+
subAgent,
|
|
287
|
+
rate: { value: 0, unit: "tokens_per_second", basis: "completion" },
|
|
288
|
+
updatedAt: now,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 上下文占用:最近一次请求的用量 + 这个模型的两条阈值(PRD F-D1 / D27)。
|
|
293
|
+
*
|
|
294
|
+
* 阈值跟着模型走;模型在配置里被删掉时两条线都是 0(「没有阈值」而不是「阈值为零就超了」)。
|
|
295
|
+
*/
|
|
296
|
+
export function contextOf(messages, model) {
|
|
297
|
+
let usage = { ...ZERO_USAGE };
|
|
298
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
299
|
+
const candidate = messages[index]?.usage;
|
|
300
|
+
if (candidate) {
|
|
301
|
+
usage = candidate;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const handoffThreshold = model?.tokenThresholds.handoff ?? 0;
|
|
306
|
+
const guardThreshold = model?.tokenThresholds.guard ?? 0;
|
|
307
|
+
return {
|
|
308
|
+
usage,
|
|
309
|
+
handoffThreshold,
|
|
310
|
+
guardThreshold,
|
|
311
|
+
overHandoff: handoffThreshold > 0 && usage.prompt_tokens >= handoffThreshold,
|
|
312
|
+
overGuard: guardThreshold > 0 && usage.prompt_tokens >= guardThreshold,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `handoff.*` 两个方法的接线(PRD F-D3 / F-D4 / F-D5 / F-D6 / F-D7 / D18 / D19)。
|
|
3
|
+
*
|
|
4
|
+
* `handoff.start` 的失败形态有两种,客户端要能分开看:
|
|
5
|
+
* - 用户取消 → 业务码 `handoff_declined`(不是失败,会话回到交接前,F-D7);
|
|
6
|
+
* - 真失败 → `internal` + `error.handoffFailed`(带上原因)。
|
|
7
|
+
* 降级不是失败:结果是成功的,`degraded` / `problem` 里说明退到了哪一档。
|
|
8
|
+
*/
|
|
9
|
+
import type { HandlerTable } from "../net/WsHub.js";
|
|
10
|
+
import type { HandoffService } from "../services/HandoffService.js";
|
|
11
|
+
export interface HandoffHandlerDeps {
|
|
12
|
+
/** 只要本域用到的那两个方法(结构上 `HandoffService` 满足,用例可以给替身) */
|
|
13
|
+
handoff: Pick<HandoffService, "start" | "listDocuments">;
|
|
14
|
+
}
|
|
15
|
+
/** 本域注册的方法 */
|
|
16
|
+
export type HandoffHandlers = Pick<HandlerTable, "handoff.start" | "handoff.listDocuments">;
|
|
17
|
+
export declare function createHandoffHandlers(deps: HandoffHandlerDeps): HandoffHandlers;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `handoff.*` 两个方法的接线(PRD F-D3 / F-D4 / F-D5 / F-D6 / F-D7 / D18 / D19)。
|
|
3
|
+
*
|
|
4
|
+
* `handoff.start` 的失败形态有两种,客户端要能分开看:
|
|
5
|
+
* - 用户取消 → 业务码 `handoff_declined`(不是失败,会话回到交接前,F-D7);
|
|
6
|
+
* - 真失败 → `internal` + `error.handoffFailed`(带上原因)。
|
|
7
|
+
* 降级不是失败:结果是成功的,`degraded` / `problem` 里说明退到了哪一档。
|
|
8
|
+
*/
|
|
9
|
+
export function createHandoffHandlers(deps) {
|
|
10
|
+
return {
|
|
11
|
+
"handoff.start": async (context) => await deps.handoff.start(context.params, context.connection.language),
|
|
12
|
+
"handoff.listDocuments": async (context) => await deps.handoff.listDocuments(context.params),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host.*`:进程事实(PRD F-E5 / D4 / L4)。
|
|
3
|
+
*
|
|
4
|
+
* 三件事在这里:
|
|
5
|
+
* - `version` / `capabilities`:报事实,供帮助页与指令菜单渲染(能力清单只在配置层维护一份,
|
|
6
|
+
* 这里不编造);
|
|
7
|
+
* - `upgrade`:比磁盘上的版本与正在跑的版本,确认新版本真的落盘了才安排重启(D4 / F-E3);
|
|
8
|
+
* - `shutdown`:受理一次收尾请求。真正退不退由单根的退出判据决定(PRD L4 / 裁定 R53)。
|
|
9
|
+
*
|
|
10
|
+
* `host.hello` / `host.setLanguage` / `asset.*` 由接线层自己接管(语言是连接状态、
|
|
11
|
+
* 上传要按连接分帧),这里不重复注册。
|
|
12
|
+
*
|
|
13
|
+
* `host.reload`(F-E1)在这里只做转发:重载是「让磁盘上的改动生效」,那条路(回收 Scope、
|
|
14
|
+
* 合成一条通知)的持有者是配置服务。
|
|
15
|
+
*
|
|
16
|
+
* 用量不在这一域(裁定 2026-11-13):F-I5 的「本轮 / 累计(含子 Agent)/ 速率」由
|
|
17
|
+
* `conversation.state` 的 `usage`(`UsageSnapshot`)给,`host.usage` 的「按项目 / 整机」聚合
|
|
18
|
+
* 没有任何客户端消费,已从契约面删除。
|
|
19
|
+
*/
|
|
20
|
+
import type { HandlerTable } from "../net/index.js";
|
|
21
|
+
import type { CapabilityView, HostReloadParams, HostReloadResult, HostUpgradeParams, HostUpgradeResult } from "../wire/index.js";
|
|
22
|
+
/** `host.*` 的实现要的持有者 */
|
|
23
|
+
export interface HostHandlerDeps {
|
|
24
|
+
/** 宿主版本(`hello` 报的那一个) */
|
|
25
|
+
version: string;
|
|
26
|
+
/** 进程启动时刻(ISO 8601) */
|
|
27
|
+
startedAt: string;
|
|
28
|
+
/** 能力清单(PRD F-E5);内容归配置层,本轮由单根注入 */
|
|
29
|
+
capabilities: () => CapabilityView[];
|
|
30
|
+
/** 请求收尾(`host.shutdown`):单根排程,先让回包写出去 */
|
|
31
|
+
requestStop: (reason?: string) => void;
|
|
32
|
+
/** 从磁盘重载(`host.reload`):配置服务那边跑「生效」那条路(回收 Scope + 合成通知) */
|
|
33
|
+
reload: (params: HostReloadParams) => Promise<HostReloadResult>;
|
|
34
|
+
/** 升级判定与重启排程(磁盘版本归单根去读,这里只把结论转给客户端) */
|
|
35
|
+
upgrade: HostUpgradeHandler;
|
|
36
|
+
}
|
|
37
|
+
/** `host.upgrade` 的实现(结论由单根算,本文件只转发入参) */
|
|
38
|
+
export type HostUpgradeHandler = (params: HostUpgradeParams) => Promise<HostUpgradeResult>;
|
|
39
|
+
export declare function createHostHandlers(deps: HostHandlerDeps): HandlerTable;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `host.*`:进程事实(PRD F-E5 / D4 / L4)。
|
|
3
|
+
*
|
|
4
|
+
* 三件事在这里:
|
|
5
|
+
* - `version` / `capabilities`:报事实,供帮助页与指令菜单渲染(能力清单只在配置层维护一份,
|
|
6
|
+
* 这里不编造);
|
|
7
|
+
* - `upgrade`:比磁盘上的版本与正在跑的版本,确认新版本真的落盘了才安排重启(D4 / F-E3);
|
|
8
|
+
* - `shutdown`:受理一次收尾请求。真正退不退由单根的退出判据决定(PRD L4 / 裁定 R53)。
|
|
9
|
+
*
|
|
10
|
+
* `host.hello` / `host.setLanguage` / `asset.*` 由接线层自己接管(语言是连接状态、
|
|
11
|
+
* 上传要按连接分帧),这里不重复注册。
|
|
12
|
+
*
|
|
13
|
+
* `host.reload`(F-E1)在这里只做转发:重载是「让磁盘上的改动生效」,那条路(回收 Scope、
|
|
14
|
+
* 合成一条通知)的持有者是配置服务。
|
|
15
|
+
*
|
|
16
|
+
* 用量不在这一域(裁定 2026-11-13):F-I5 的「本轮 / 累计(含子 Agent)/ 速率」由
|
|
17
|
+
* `conversation.state` 的 `usage`(`UsageSnapshot`)给,`host.usage` 的「按项目 / 整机」聚合
|
|
18
|
+
* 没有任何客户端消费,已从契约面删除。
|
|
19
|
+
*/
|
|
20
|
+
import { relocalizeText } from "../services/NoticeService.js";
|
|
21
|
+
import { PROTOCOL_VERSION } from "../wire/index.js";
|
|
22
|
+
export function createHostHandlers(deps) {
|
|
23
|
+
return {
|
|
24
|
+
"host.version": () => ({
|
|
25
|
+
version: deps.version,
|
|
26
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
27
|
+
startedAt: deps.startedAt,
|
|
28
|
+
pid: process.pid,
|
|
29
|
+
}),
|
|
30
|
+
"host.capabilities": (context) => ({
|
|
31
|
+
// 清单按这条连接的语言出句子(裁定 R58):id / 顺序与语言无关(R57)
|
|
32
|
+
capabilities: localizeCapabilities(deps.capabilities(), context.connection.language),
|
|
33
|
+
}),
|
|
34
|
+
"host.shutdown": (context) => {
|
|
35
|
+
deps.requestStop(context.params.reason);
|
|
36
|
+
return { accepted: true };
|
|
37
|
+
},
|
|
38
|
+
// 升级即重启(D4 / F-E3):结论由单根给,它知道磁盘上的版本与重启排程
|
|
39
|
+
"host.upgrade": async (context) => await deps.upgrade(context.params),
|
|
40
|
+
// 重载不重启(F-E1 / F-E2 / R63):与改配置走同一条生效路径,回同一份报告
|
|
41
|
+
"host.reload": async (context) => await deps.reload(context.params),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 能力清单按这条连接的语言重渲染(裁定 R58)。
|
|
46
|
+
*
|
|
47
|
+
* 清单本身由配置层按缺省语言产出(每项的标题与说明都带着 `messageKey` + `params`),这里只把
|
|
48
|
+
* 句子换成收件人的语言 —— 与通知、问题出网前那一跳同一个口径(D8:存码不存句子)。
|
|
49
|
+
*/
|
|
50
|
+
function localizeCapabilities(capabilities, language) {
|
|
51
|
+
return capabilities.map((capability) => ({
|
|
52
|
+
...capability,
|
|
53
|
+
title: relocalizeText(capability.title, language),
|
|
54
|
+
description: relocalizeText(capability.description, language),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handler 表的唯一出口(architecture §3 / §6)。
|
|
3
|
+
*
|
|
4
|
+
* `WsHub` 只认一张表:方法名 → 实现。方法名的真相在 `wire/methods.ts`(`MethodMap`),
|
|
5
|
+
* 这里把每个有实现者的域拼起来交给接线层。
|
|
6
|
+
*
|
|
7
|
+
* 各域分文件放,是为了让「这一域要哪些持有者」写在它自己的 `deps` 接口上:接线层照着注入,
|
|
8
|
+
* 而不是把一个巨大的 `app` 对象塞进每个 handler 里。
|
|
9
|
+
*
|
|
10
|
+
* 组装方式仍是显式构造注入:`HostApp` 造好服务与运行时之后,把引用交进来,本模块不 new
|
|
11
|
+
* 任何东西、不读全局路径、不持有单例。
|
|
12
|
+
*
|
|
13
|
+
* 四件事在**接线层**而不是这张表里(`handlers/index.test.ts` 把这份名单钉住):
|
|
14
|
+
* - `host.hello` / `host.setLanguage`:语言与凭证都是连接状态(`net/WsHub`);
|
|
15
|
+
* - `asset.begin` / `asset.end`:上传要按连接分帧落盘(`net/UploadReceiver`)。
|
|
16
|
+
*
|
|
17
|
+
* 除这四条以外,契约里的每个方法都必须在这张表里 —— 否则客户端会拿到 `unsupported`,
|
|
18
|
+
* 而那是「这个宿主没有这个功能」的意思,不该用来掩盖「忘了接」。
|
|
19
|
+
*/
|
|
20
|
+
import type { AppConfig } from "@ai-zen/agents-sdk";
|
|
21
|
+
import type { Language } from "../i18n/index.js";
|
|
22
|
+
import type { HandlerTable } from "../net/index.js";
|
|
23
|
+
import type { ChatRunner, ScopeRegistry } from "../runtime/index.js";
|
|
24
|
+
import type { TransientRunner } from "../runtime/TransientRunner.js";
|
|
25
|
+
import type { AgentService } from "../services/AgentService.js";
|
|
26
|
+
import type { AssetService } from "../services/AssetService.js";
|
|
27
|
+
import type { CompletionService } from "../services/CompletionService.js";
|
|
28
|
+
import type { ConfigService } from "../services/ConfigService.js";
|
|
29
|
+
import type { ConversationService } from "../services/ConversationService.js";
|
|
30
|
+
import type { HandoffService } from "../services/HandoffService.js";
|
|
31
|
+
import type { MessageService } from "../services/MessageService.js";
|
|
32
|
+
import type { ModelService } from "../services/ModelService.js";
|
|
33
|
+
import type { NoticeService } from "../services/NoticeService.js";
|
|
34
|
+
import type { QuestionService } from "../services/QuestionService.js";
|
|
35
|
+
import type { SearchService } from "../services/SearchService.js";
|
|
36
|
+
import type { WorkspaceService } from "../services/WorkspaceService.js";
|
|
37
|
+
import type { CapabilityView, MethodName, QuestionView, HostReloadParams, HostReloadResult } from "../wire/index.js";
|
|
38
|
+
import type { EventMap, EventName } from "../wire/events.js";
|
|
39
|
+
export type { AgentHandlerDeps } from "./agentHandlers.js";
|
|
40
|
+
export type { AssetHandlerDeps } from "./assetHandlers.js";
|
|
41
|
+
export type { CompletionHandlerDeps } from "./completionHandlers.js";
|
|
42
|
+
export type { ConfigHandlerDeps } from "./configHandlers.js";
|
|
43
|
+
export type { ConversationHandlerDeps } from "./conversationHandlers.js";
|
|
44
|
+
export type { HandoffHandlerDeps } from "./handoffHandlers.js";
|
|
45
|
+
export type { MessageHandlerDeps } from "./messageHandlers.js";
|
|
46
|
+
export type { ModelHandlerDeps } from "./modelHandlers.js";
|
|
47
|
+
export type { NoticeHandlerDeps } from "./noticeHandlers.js";
|
|
48
|
+
export type { QuestionHandlerDeps } from "./questionHandlers.js";
|
|
49
|
+
export type { SearchHandlerDeps } from "./searchHandlers.js";
|
|
50
|
+
export type { HostHandlerDeps } from "./hostHandlers.js";
|
|
51
|
+
export type { WorkspaceHandlerDeps } from "./workspaceHandlers.js";
|
|
52
|
+
export { createCapabilities } from "./capabilities.js";
|
|
53
|
+
/**
|
|
54
|
+
* 由接线层代管、因此不在 handler 表里的方法名。
|
|
55
|
+
*
|
|
56
|
+
* 这份名单是「表可以缺哪些」的唯一真相:验收用例按它判「契约里的每个方法要么在表里、
|
|
57
|
+
* 要么在这里」。往这里加一条就等于说「接线层自己实现了它」——`net/WsHub` 也确实在分发时
|
|
58
|
+
* 先处理这四个名字。
|
|
59
|
+
*/
|
|
60
|
+
export declare const BRIDGED_METHODS: readonly MethodName[];
|
|
61
|
+
/**
|
|
62
|
+
* 组装 handler 表要的全部持有者。
|
|
63
|
+
*
|
|
64
|
+
* 一个平坦的形状(而不是每域各注入一个对象)是因为单根本来就把这些都拿在手上;
|
|
65
|
+
* 域函数各自声明自己要哪几样,单根把它们平铺进去,谁需要什么仍然一目了然。
|
|
66
|
+
*/
|
|
67
|
+
export interface HandlerDeps {
|
|
68
|
+
/** 宿主版本(`hello` 报的那一个) */
|
|
69
|
+
version: string;
|
|
70
|
+
/** 进程启动时刻(ISO 8601) */
|
|
71
|
+
startedAt: string;
|
|
72
|
+
/** 能力清单(PRD F-E5) */
|
|
73
|
+
capabilities: () => CapabilityView[];
|
|
74
|
+
workspaces: WorkspaceService;
|
|
75
|
+
conversations: ConversationService;
|
|
76
|
+
messages: MessageService;
|
|
77
|
+
search: SearchService;
|
|
78
|
+
assets: AssetService;
|
|
79
|
+
config: ConfigService;
|
|
80
|
+
agents: AgentService;
|
|
81
|
+
models: ModelService;
|
|
82
|
+
completion: CompletionService;
|
|
83
|
+
notices: NoticeService;
|
|
84
|
+
questions: QuestionService;
|
|
85
|
+
handoff: HandoffService;
|
|
86
|
+
runner: ChatRunner;
|
|
87
|
+
scopes: ScopeRegistry;
|
|
88
|
+
/** 临时对话的执行者(D3 / R49:不落库、不推流式事件) */
|
|
89
|
+
transient: TransientRunner;
|
|
90
|
+
/** 读配置(上下文阈值跟着模型走,PRD F-D1 / D27) */
|
|
91
|
+
readConfig: () => Promise<AppConfig>;
|
|
92
|
+
/** 刷新思考策略表快照(`conversation.setThinking` 之前调一次) */
|
|
93
|
+
refreshThinkingPresets: () => Promise<void>;
|
|
94
|
+
/** 时钟:契约里的时间一律 ISO 8601 字符串 */
|
|
95
|
+
now: () => string;
|
|
96
|
+
/** 推事件(`conversation.changed` 这类多端同步信号);不注入时不推 */
|
|
97
|
+
emit?: <E extends EventName>(name: E, payload: EventMap[E]) => void;
|
|
98
|
+
/** 有挡着的问题就拒发(D23);发之前问一句 */
|
|
99
|
+
assertSendable: (conversationId: string) => Promise<void>;
|
|
100
|
+
/** 一轮结算之后起一次自动命名(D2) */
|
|
101
|
+
autoNameAfterTurn: (conversationId: string, language: Language) => Promise<void>;
|
|
102
|
+
/** 结算后钩子失败的去处(命名没跑成不该让已经结算的一轮变成失败) */
|
|
103
|
+
onHookError: (error: unknown, hook: string) => void;
|
|
104
|
+
/** 现场快照里那段对话的问题(D22:多端看同一批) */
|
|
105
|
+
questionsFor: (conversationId: string) => Promise<QuestionView[]>;
|
|
106
|
+
/** 请求收尾(`host.shutdown`);真正的退出判定在单根 */
|
|
107
|
+
requestStop: (reason?: string) => void;
|
|
108
|
+
/** 一轮结算之后重算退出判据(L4:这是「什么都不欠了」的触发源之一) */
|
|
109
|
+
onTurnSettled: () => void;
|
|
110
|
+
/** 升级判定与重启排程(`host.upgrade`:比磁盘版本与运行版本) */
|
|
111
|
+
upgrade: (params: {
|
|
112
|
+
targetVersion?: string;
|
|
113
|
+
}) => Promise<{
|
|
114
|
+
state: "already_latest" | "restart_scheduled" | "not_available";
|
|
115
|
+
currentVersion: string;
|
|
116
|
+
diskVersion: string | null;
|
|
117
|
+
targetVersion: string | null;
|
|
118
|
+
}>;
|
|
119
|
+
/** 从磁盘重载(`host.reload`:与改配置同一条生效路径) */
|
|
120
|
+
reload: (params: HostReloadParams) => Promise<HostReloadResult>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 造出整张 handler 表。
|
|
124
|
+
*
|
|
125
|
+
* 同名方法不会重复:每个方法名只属于一个域,拼装处若出现重名,后一份会覆盖前一份
|
|
126
|
+
* (`handlers/index.test.ts` 里断言表里的方法名唯一且都在契约内)。
|
|
127
|
+
*/
|
|
128
|
+
export declare function createHandlers(deps: HandlerDeps): HandlerTable;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handler 表的唯一出口(architecture §3 / §6)。
|
|
3
|
+
*
|
|
4
|
+
* `WsHub` 只认一张表:方法名 → 实现。方法名的真相在 `wire/methods.ts`(`MethodMap`),
|
|
5
|
+
* 这里把每个有实现者的域拼起来交给接线层。
|
|
6
|
+
*
|
|
7
|
+
* 各域分文件放,是为了让「这一域要哪些持有者」写在它自己的 `deps` 接口上:接线层照着注入,
|
|
8
|
+
* 而不是把一个巨大的 `app` 对象塞进每个 handler 里。
|
|
9
|
+
*
|
|
10
|
+
* 组装方式仍是显式构造注入:`HostApp` 造好服务与运行时之后,把引用交进来,本模块不 new
|
|
11
|
+
* 任何东西、不读全局路径、不持有单例。
|
|
12
|
+
*
|
|
13
|
+
* 四件事在**接线层**而不是这张表里(`handlers/index.test.ts` 把这份名单钉住):
|
|
14
|
+
* - `host.hello` / `host.setLanguage`:语言与凭证都是连接状态(`net/WsHub`);
|
|
15
|
+
* - `asset.begin` / `asset.end`:上传要按连接分帧落盘(`net/UploadReceiver`)。
|
|
16
|
+
*
|
|
17
|
+
* 除这四条以外,契约里的每个方法都必须在这张表里 —— 否则客户端会拿到 `unsupported`,
|
|
18
|
+
* 而那是「这个宿主没有这个功能」的意思,不该用来掩盖「忘了接」。
|
|
19
|
+
*/
|
|
20
|
+
import { createAgentHandlers } from "./agentHandlers.js";
|
|
21
|
+
import { createAssetHandlers } from "./assetHandlers.js";
|
|
22
|
+
import { createCompletionHandlers } from "./completionHandlers.js";
|
|
23
|
+
import { createConfigHandlers } from "./configHandlers.js";
|
|
24
|
+
import { createConversationHandlers } from "./conversationHandlers.js";
|
|
25
|
+
import { createHandoffHandlers } from "./handoffHandlers.js";
|
|
26
|
+
import { createMessageHandlers } from "./messageHandlers.js";
|
|
27
|
+
import { createModelHandlers } from "./modelHandlers.js";
|
|
28
|
+
import { createNoticeHandlers } from "./noticeHandlers.js";
|
|
29
|
+
import { createQuestionHandlers } from "./questionHandlers.js";
|
|
30
|
+
import { createSearchHandlers } from "./searchHandlers.js";
|
|
31
|
+
import { createHostHandlers } from "./hostHandlers.js";
|
|
32
|
+
import { createWorkspaceHandlers } from "./workspaceHandlers.js";
|
|
33
|
+
export { createCapabilities } from "./capabilities.js";
|
|
34
|
+
/**
|
|
35
|
+
* 由接线层代管、因此不在 handler 表里的方法名。
|
|
36
|
+
*
|
|
37
|
+
* 这份名单是「表可以缺哪些」的唯一真相:验收用例按它判「契约里的每个方法要么在表里、
|
|
38
|
+
* 要么在这里」。往这里加一条就等于说「接线层自己实现了它」——`net/WsHub` 也确实在分发时
|
|
39
|
+
* 先处理这四个名字。
|
|
40
|
+
*/
|
|
41
|
+
export const BRIDGED_METHODS = [
|
|
42
|
+
"host.hello",
|
|
43
|
+
"host.setLanguage",
|
|
44
|
+
"asset.begin",
|
|
45
|
+
"asset.end",
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* 造出整张 handler 表。
|
|
49
|
+
*
|
|
50
|
+
* 同名方法不会重复:每个方法名只属于一个域,拼装处若出现重名,后一份会覆盖前一份
|
|
51
|
+
* (`handlers/index.test.ts` 里断言表里的方法名唯一且都在契约内)。
|
|
52
|
+
*/
|
|
53
|
+
export function createHandlers(deps) {
|
|
54
|
+
return {
|
|
55
|
+
...createHostHandlers(deps),
|
|
56
|
+
...createWorkspaceHandlers({
|
|
57
|
+
workspaces: deps.workspaces,
|
|
58
|
+
conversations: deps.conversations,
|
|
59
|
+
runner: deps.runner,
|
|
60
|
+
scopes: deps.scopes,
|
|
61
|
+
emit: deps.emit,
|
|
62
|
+
now: deps.now,
|
|
63
|
+
}),
|
|
64
|
+
...createConversationHandlers({
|
|
65
|
+
conversations: deps.conversations,
|
|
66
|
+
messages: deps.messages,
|
|
67
|
+
runner: deps.runner,
|
|
68
|
+
readConfig: deps.readConfig,
|
|
69
|
+
// 状态栏助手块的名字与可用性(PRD F-I6):只借用 `AgentService.describe` 这一个只读口
|
|
70
|
+
agents: deps.agents,
|
|
71
|
+
refreshThinkingPresets: deps.refreshThinkingPresets,
|
|
72
|
+
now: deps.now,
|
|
73
|
+
onTurnSettled: deps.onTurnSettled,
|
|
74
|
+
assertSendable: deps.assertSendable,
|
|
75
|
+
autoNameAfterTurn: deps.autoNameAfterTurn,
|
|
76
|
+
onHookError: deps.onHookError,
|
|
77
|
+
questionsFor: deps.questionsFor,
|
|
78
|
+
// 临时对话:一次调用跑一轮、不落库、不推流式事件(R1 / R49)
|
|
79
|
+
runTransient: async (params, language) => await deps.transient.run(params, language),
|
|
80
|
+
}),
|
|
81
|
+
...createMessageHandlers(deps),
|
|
82
|
+
...createSearchHandlers(deps),
|
|
83
|
+
...createAssetHandlers({ assets: deps.assets }),
|
|
84
|
+
...createConfigHandlers({ config: deps.config }),
|
|
85
|
+
...createAgentHandlers({ agents: deps.agents }),
|
|
86
|
+
...createModelHandlers({ models: deps.models }),
|
|
87
|
+
...createCompletionHandlers({ completion: deps.completion }),
|
|
88
|
+
...createNoticeHandlers({ notices: deps.notices }),
|
|
89
|
+
...createQuestionHandlers({ questions: deps.questions }),
|
|
90
|
+
...createHandoffHandlers({ handoff: deps.handoff }),
|
|
91
|
+
};
|
|
92
|
+
}
|