@aalis/plugin-agent 0.13.5 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers.d.ts +2 -2
- package/dist/index.d.ts +25 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +192 -124
- package/dist/index.js.map +1 -1
- package/dist/prompt-assembly.d.ts +10 -2
- package/dist/prompt-assembly.d.ts.map +1 -1
- package/dist/prompt-assembly.js +8 -8
- package/dist/prompt-assembly.js.map +1 -1
- package/package.json +32 -16
package/dist/index.js
CHANGED
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { agent as agentService, } from '@aalis/api-agent';
|
|
3
|
+
import { commands as commandsService } from '@aalis/api-commands';
|
|
4
|
+
import { gateway as gatewayService } from '@aalis/api-gateway';
|
|
5
|
+
import { listLLMModels, llm as llmService, resolveLLMModel } from '@aalis/api-llm';
|
|
6
|
+
import { media as mediaService } from '@aalis/api-media';
|
|
7
|
+
import { memory as memoryService } from '@aalis/api-memory';
|
|
8
|
+
import { messageArchive as messageArchiveService } from '@aalis/api-message-archive';
|
|
9
|
+
import { persona as personaService } from '@aalis/api-persona';
|
|
10
|
+
import { getPlatformSelfIdentity, platform as platformService } from '@aalis/api-platform';
|
|
11
|
+
import { sessionManager as sessionManagerService } from '@aalis/api-session-manager';
|
|
12
|
+
import { storage as storageService } from '@aalis/api-storage';
|
|
13
|
+
import { asToolExecutionResult, tools as toolsService, } from '@aalis/api-tools';
|
|
14
|
+
import { config as configCap, contributions, definePlugin, events, hooks, lifecycle, logger as loggerCap, optional, pluginsService, provide, services, } from '@aalis/core';
|
|
6
15
|
import { CONTROL_KINDS, getMessageName, getSenderLabel, WellKnownKinds } from '@aalis/schema-message';
|
|
7
16
|
import { normalizeAssistantContent, stripLeakedSpecialTokens, truncateChars } from '@aalis/util-text-normalize';
|
|
8
17
|
import { buildFocusGuidance, describeLLMFailure, estimateMsgTokens, estimateTextTokens, estimateTokens, formatTimeLabel, INPUT_CONVENTIONS, isSameMessage, } from './helpers.js';
|
|
9
18
|
import { assemblePromptContributions, VOLATILE_INJECTOR } from './prompt-assembly.js';
|
|
19
|
+
// ===== 能力声明 =====
|
|
20
|
+
//
|
|
21
|
+
// 对话编排要碰的东西全在这里:没写进 uses 的,插件碰不到。全部 optional——少一样能力
|
|
22
|
+
// 只是少一段能力(没有 memory 就没有历史、没有 tools 就把「工具不可用」回给模型、没有
|
|
23
|
+
// gateway 就直接走事件总线),不该让整个 agent 停活。
|
|
24
|
+
const uses = {
|
|
25
|
+
logger: loggerCap,
|
|
26
|
+
config: configCap,
|
|
27
|
+
events,
|
|
28
|
+
hooks,
|
|
29
|
+
contributions,
|
|
30
|
+
lifecycle,
|
|
31
|
+
provide,
|
|
32
|
+
services,
|
|
33
|
+
commands: optional(commandsService),
|
|
34
|
+
tools: optional(toolsService),
|
|
35
|
+
llm: optional(llmService),
|
|
36
|
+
memory: optional(memoryService),
|
|
37
|
+
persona: optional(personaService),
|
|
38
|
+
messageArchive: optional(messageArchiveService),
|
|
39
|
+
sessionManager: optional(sessionManagerService),
|
|
40
|
+
platform: optional(platformService),
|
|
41
|
+
media: optional(mediaService),
|
|
42
|
+
storage: optional(storageService),
|
|
43
|
+
gateway: optional(gatewayService),
|
|
44
|
+
plugins: optional(pluginsService),
|
|
45
|
+
};
|
|
10
46
|
/**
|
|
11
47
|
* 默认 Agent 实现 —— 对话编排器
|
|
12
48
|
*
|
|
@@ -26,7 +62,7 @@ function getModelMaxOutput(llm) {
|
|
|
26
62
|
return llm.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
27
63
|
}
|
|
28
64
|
class DefaultAgent {
|
|
29
|
-
|
|
65
|
+
caps;
|
|
30
66
|
logger;
|
|
31
67
|
systemPrompt;
|
|
32
68
|
memoryTokenBudget;
|
|
@@ -45,8 +81,15 @@ class DefaultAgent {
|
|
|
45
81
|
* 独立管理,互不打断;同来源新消息会中止旧的生成。
|
|
46
82
|
*/
|
|
47
83
|
activeControllers = new Map();
|
|
84
|
+
/**
|
|
85
|
+
* 在飞回合 Promise。关停时 abort 之后等它们以 AbortError(或正常完成)收尾,
|
|
86
|
+
* 而不是等 LLM 跑完——收尾里的钩子 / 出站事件还要用 memory。
|
|
87
|
+
*/
|
|
88
|
+
inflightTurns = new Set();
|
|
48
89
|
/** 同一 lane 的入站消息归档串行化,避免连续消息读取历史时漏掉前一条输入。 */
|
|
49
90
|
archiveQueues = new Map();
|
|
91
|
+
/** 每次激活只在实际写入缺少归档时告警一次,不因插件加载顺序误报。 */
|
|
92
|
+
archiveUnavailableWarned = false;
|
|
50
93
|
/**
|
|
51
94
|
* 节流日志状态:记录每个 session 上次 token:usage 日志的"轮次"与 ratio 桶。
|
|
52
95
|
* - 跨过 0.5/0.7/0.85 三个阈值必输出
|
|
@@ -56,9 +99,10 @@ class DefaultAgent {
|
|
|
56
99
|
tokenLogState = new Map();
|
|
57
100
|
/** 已注册的预处理器(name → { priority, dispose }) */
|
|
58
101
|
preprocessors = new Map();
|
|
59
|
-
constructor(
|
|
60
|
-
this.
|
|
61
|
-
this.logger =
|
|
102
|
+
constructor(caps) {
|
|
103
|
+
this.caps = caps;
|
|
104
|
+
this.logger = caps.logger.child('agent');
|
|
105
|
+
const config = caps.config;
|
|
62
106
|
this.systemPrompt = config.systemPrompt || '';
|
|
63
107
|
this.memoryTokenBudget = config.memoryTokenBudget ?? 4096;
|
|
64
108
|
this.historyLimit = config.historyLimit ?? 50;
|
|
@@ -82,25 +126,25 @@ class DefaultAgent {
|
|
|
82
126
|
async resolveLLM(platform, sessionId) {
|
|
83
127
|
let ref;
|
|
84
128
|
// session-manager 一步到位:会话 > 父 sessionDefaults > platform profile
|
|
85
|
-
const sm = this.
|
|
129
|
+
const sm = this.caps.sessionManager.current;
|
|
86
130
|
if (sm && sessionId) {
|
|
87
131
|
const resolved = sm.resolveConfig(sessionId, platform);
|
|
88
132
|
if (resolved.llm?.provider && resolved.llm?.model)
|
|
89
133
|
ref = resolved.llm;
|
|
90
134
|
}
|
|
91
135
|
// 解析为具体 LLMModel entry(要求至少 chat 能力;ref 为空时走 ServicePreference / 优先级)
|
|
92
|
-
return resolveLLMModel(this.
|
|
136
|
+
return resolveLLMModel(this.caps.llm, ref, ['chat']);
|
|
93
137
|
}
|
|
94
138
|
/** 解析失败时说清**为什么**(取数在此,措辞在 helpers 的 describeLLMFailure,纯函数便于单测)。 */
|
|
95
139
|
explainLLMFailure(platform, sessionId) {
|
|
96
|
-
const available = listLLMModels(this.
|
|
97
|
-
const sm = this.
|
|
140
|
+
const available = listLLMModels(this.caps.llm, { caps: ['chat'] }).map(e => e.contextId);
|
|
141
|
+
const sm = this.caps.sessionManager.current;
|
|
98
142
|
const wanted = sm && sessionId ? sm.resolveConfig(sessionId, platform).llm : undefined;
|
|
99
143
|
return describeLLMFailure(available, wanted, this.erroredLLMPlugins());
|
|
100
144
|
}
|
|
101
145
|
/** 激活失败且声明提供 llm 的插件——「一个模型都没有」时,病因通常就在它们身上(多为缺 apiKey)。 */
|
|
102
146
|
erroredLLMPlugins() {
|
|
103
|
-
const pm = this.
|
|
147
|
+
const pm = this.caps.plugins.current;
|
|
104
148
|
if (!pm)
|
|
105
149
|
return [];
|
|
106
150
|
// 这串会拼进**发到聊天**的文案里,原因文本来自插件自抛的 error,长度不可控:
|
|
@@ -129,10 +173,10 @@ class DefaultAgent {
|
|
|
129
173
|
/**
|
|
130
174
|
* 中止全部在飞回合(拆卸路径用)。
|
|
131
175
|
*
|
|
132
|
-
* activeControllers 是实例私有的,bounce
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
176
|
+
* activeControllers 是实例私有的,bounce 后新实例看不见旧实例的在飞回合。
|
|
177
|
+
* abort 走已有的 AbortError 收尾(outbound:stream done + turn:after outcome='aborted'),
|
|
178
|
+
* 与 WebUI「停止生成」同语义。关停在 onDrain 里先调本方法再等回合 Promise;
|
|
179
|
+
* onDispose 再调一次作兜底(已中止的 controller 不在表里,是空操作)。
|
|
136
180
|
*/
|
|
137
181
|
abortAll() {
|
|
138
182
|
let n = 0;
|
|
@@ -145,8 +189,15 @@ class DefaultAgent {
|
|
|
145
189
|
this.logger.info(`拆卸:已中止 ${n} 个在飞回合`);
|
|
146
190
|
}
|
|
147
191
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
192
|
+
* 关停收尾:中止在飞回合并等待其 AbortError(或正常完成)收尾落定。
|
|
193
|
+
* 超时由 core 对 onDrain 回调的 disposeTimeoutMs 护栏负责,不另加配置键。
|
|
194
|
+
*/
|
|
195
|
+
async abortInflightAndSettle() {
|
|
196
|
+
this.abortAll();
|
|
197
|
+
if (this.inflightTurns.size === 0)
|
|
198
|
+
return;
|
|
199
|
+
await Promise.allSettled([...this.inflightTurns]);
|
|
200
|
+
}
|
|
150
201
|
/**
|
|
151
202
|
* 注册输入预处理器(如图片识别、文件读取、用户画像等)。
|
|
152
203
|
* 多个预处理器按注册顺序串行执行(Koa-style 洋葱模型)。
|
|
@@ -156,22 +207,24 @@ class DefaultAgent {
|
|
|
156
207
|
*/
|
|
157
208
|
registerPreprocessor(name, handler) {
|
|
158
209
|
// 同名替换
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
existing.dispose();
|
|
162
|
-
const dispose = this.ctx.middleware('agent:input:before', async (data, next) => {
|
|
210
|
+
this.preprocessors.get(name)?.dispose();
|
|
211
|
+
const dispose = this.caps.hooks.middleware('agent:input:before', async (data, next) => {
|
|
163
212
|
await handler(data.message, next);
|
|
164
213
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
214
|
+
// 退订按条目引用比对:同名重注册后,旧退订闭包只摘自己的中间件,不动新登记的账目
|
|
215
|
+
const entry = {
|
|
216
|
+
dispose: () => {
|
|
217
|
+
dispose();
|
|
218
|
+
if (this.preprocessors.get(name) !== entry)
|
|
219
|
+
return;
|
|
220
|
+
this.preprocessors.delete(name);
|
|
221
|
+
this.logger.info(`预处理器已注销: ${name}`);
|
|
222
|
+
},
|
|
169
223
|
};
|
|
170
|
-
this.preprocessors.set(name,
|
|
224
|
+
this.preprocessors.set(name, entry);
|
|
171
225
|
this.logger.info(`预处理器已注册: ${name}`);
|
|
172
|
-
return
|
|
226
|
+
return entry.dispose;
|
|
173
227
|
}
|
|
174
|
-
/**
|
|
175
228
|
/**
|
|
176
229
|
* 获取当前所有已注册预处理器的元信息(按注册顺序返回)
|
|
177
230
|
*/
|
|
@@ -185,7 +238,7 @@ class DefaultAgent {
|
|
|
185
238
|
* (平台属于独立子系统,由 api-platform 的 helper 负责)。
|
|
186
239
|
*/
|
|
187
240
|
getPluginGroups() {
|
|
188
|
-
const pm = this.
|
|
241
|
+
const pm = this.caps.plugins.current;
|
|
189
242
|
if (!pm)
|
|
190
243
|
return [];
|
|
191
244
|
// 子系统归属:Agent 域的服务(不含 platform——平台是独立子系统)
|
|
@@ -227,7 +280,7 @@ class DefaultAgent {
|
|
|
227
280
|
if (chunk.contentDelta) {
|
|
228
281
|
content += chunk.contentDelta;
|
|
229
282
|
appendDelta('text', chunk.contentDelta);
|
|
230
|
-
await this.
|
|
283
|
+
await this.caps.events.emit('outbound:stream', {
|
|
231
284
|
sessionId,
|
|
232
285
|
platform,
|
|
233
286
|
contentDelta: chunk.contentDelta,
|
|
@@ -236,14 +289,14 @@ class DefaultAgent {
|
|
|
236
289
|
if (chunk.reasoningDelta) {
|
|
237
290
|
reasoningContent += chunk.reasoningDelta;
|
|
238
291
|
appendDelta('reasoning_text', chunk.reasoningDelta);
|
|
239
|
-
await this.
|
|
292
|
+
await this.caps.events.emit('outbound:stream', {
|
|
240
293
|
sessionId,
|
|
241
294
|
platform,
|
|
242
295
|
reasoningDelta: chunk.reasoningDelta,
|
|
243
296
|
});
|
|
244
297
|
}
|
|
245
298
|
if (chunk.toolCallProgress) {
|
|
246
|
-
await this.
|
|
299
|
+
await this.caps.events.emit('outbound:stream', {
|
|
247
300
|
sessionId,
|
|
248
301
|
platform,
|
|
249
302
|
toolCallProgress: chunk.toolCallProgress,
|
|
@@ -261,7 +314,7 @@ class DefaultAgent {
|
|
|
261
314
|
// 兜底剥离 LLM 端漏出的特殊 token 残渣(DSML 等),同时修复 GFM 表格
|
|
262
315
|
const { sanitized, hadLeak } = stripLeakedSpecialTokens(content);
|
|
263
316
|
if (hadLeak) {
|
|
264
|
-
this.
|
|
317
|
+
this.caps.logger.warn(`agent: 检测到 LLM 内容残留 DSML 标记,已剥离(session=${sessionId} platform=${platform} 原长=${content.length} 净化后=${sanitized.length})`);
|
|
265
318
|
}
|
|
266
319
|
finalContent = normalizeAssistantContent(content);
|
|
267
320
|
}
|
|
@@ -384,14 +437,23 @@ class DefaultAgent {
|
|
|
384
437
|
prev.abort();
|
|
385
438
|
const controller = new AbortController();
|
|
386
439
|
this.activeControllers.set(lane, controller);
|
|
440
|
+
const turn = (async () => {
|
|
441
|
+
try {
|
|
442
|
+
await this._handleMessageInner(incoming, controller.signal, lane);
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
// 仅清理自己创建的 controller(避免清掉后续新请求的)
|
|
446
|
+
if (this.activeControllers.get(lane) === controller) {
|
|
447
|
+
this.activeControllers.delete(lane);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
})();
|
|
451
|
+
this.inflightTurns.add(turn);
|
|
387
452
|
try {
|
|
388
|
-
await
|
|
453
|
+
await turn;
|
|
389
454
|
}
|
|
390
455
|
finally {
|
|
391
|
-
|
|
392
|
-
if (this.activeControllers.get(lane) === controller) {
|
|
393
|
-
this.activeControllers.delete(lane);
|
|
394
|
-
}
|
|
456
|
+
this.inflightTurns.delete(turn);
|
|
395
457
|
}
|
|
396
458
|
}
|
|
397
459
|
async _handleMessageInner(incoming, signal, lane) {
|
|
@@ -402,7 +464,7 @@ class DefaultAgent {
|
|
|
402
464
|
metadata: {},
|
|
403
465
|
};
|
|
404
466
|
let handled = false;
|
|
405
|
-
await this.
|
|
467
|
+
await this.caps.hooks.run('agent:input:before', msgHookData, async () => {
|
|
406
468
|
handled = true;
|
|
407
469
|
// ===== defaultAction: 全部消息处理逻辑在此 =====
|
|
408
470
|
// 中间件不调用 next() → 此处永远不执行 → 消息被拦截
|
|
@@ -435,7 +497,7 @@ class DefaultAgent {
|
|
|
435
497
|
const turnPersistedTimestamps = [];
|
|
436
498
|
try {
|
|
437
499
|
// 统一解析 session 配置(一次解析,多处复用)
|
|
438
|
-
const sessionMgr = this.
|
|
500
|
+
const sessionMgr = this.caps.sessionManager.current;
|
|
439
501
|
const resolved = sessionMgr && incoming.sessionId
|
|
440
502
|
? sessionMgr.resolveConfig(incoming.sessionId, incoming.platform)
|
|
441
503
|
: undefined;
|
|
@@ -462,9 +524,7 @@ class DefaultAgent {
|
|
|
462
524
|
enabledGroups = resolved.enabledToolGroups;
|
|
463
525
|
}
|
|
464
526
|
this.logger.debug(`工具分组: platform=${incoming.platform}, enabledGroups=${enabledGroups ? JSON.stringify(enabledGroups) : '(无)'}`);
|
|
465
|
-
const tools = this.
|
|
466
|
-
.getService('tools')
|
|
467
|
-
?.getDefinitions(enabledGroups ? { groups: enabledGroups } : undefined) ?? [];
|
|
527
|
+
const tools = this.caps.tools.current?.getDefinitions(enabledGroups ? { groups: enabledGroups } : undefined) ?? [];
|
|
468
528
|
const toolCtx = {
|
|
469
529
|
sessionId: incoming.sessionId,
|
|
470
530
|
// platform/userId 保持**会话/物理**语义(定时任务归属、平台档继承、记忆平台域、
|
|
@@ -494,11 +554,11 @@ class DefaultAgent {
|
|
|
494
554
|
triggerType: incoming.triggerType,
|
|
495
555
|
};
|
|
496
556
|
// 组装先于链:贡献块(档案/技能/记忆/即时提示)先物化,拦截者审完整成品
|
|
497
|
-
await assemblePromptContributions(this.
|
|
557
|
+
await assemblePromptContributions(this.caps, llmBeforeData, {
|
|
498
558
|
buildTimeoutMs: this.promptBuildTimeoutMs,
|
|
499
559
|
signal,
|
|
500
560
|
});
|
|
501
|
-
await this.
|
|
561
|
+
await this.caps.hooks.run('agent:llm:before', llmBeforeData, undefined, { warnOnStall: true });
|
|
502
562
|
// 裁剪消息以确保不超过上下文窗口
|
|
503
563
|
llmBeforeData.messages = this.trimMessages(llmBeforeData.messages, tokenBudget);
|
|
504
564
|
// 推送 token 使用量统计
|
|
@@ -541,7 +601,7 @@ class DefaultAgent {
|
|
|
541
601
|
this.debugLogResponse(response, Date.now() - t0);
|
|
542
602
|
// Hook: agent:llm:after — 插件可以处理 LLM 返回结果
|
|
543
603
|
const llmAfterData = { response, messages: llmBeforeData.messages };
|
|
544
|
-
await this.
|
|
604
|
+
await this.caps.hooks.run('agent:llm:after', llmAfterData);
|
|
545
605
|
response = llmAfterData.response;
|
|
546
606
|
// 收集所有思考内容
|
|
547
607
|
const allReasoning = [];
|
|
@@ -601,9 +661,9 @@ class DefaultAgent {
|
|
|
601
661
|
}
|
|
602
662
|
// Hook: agent:tool:before — 插件可以拦截或修改工具调用
|
|
603
663
|
const toolBeforeData = { name: toolCall.function.name, args, toolCallContext: toolCtx };
|
|
604
|
-
await this.
|
|
664
|
+
await this.caps.hooks.run('agent:tool:before', toolBeforeData);
|
|
605
665
|
// 通知平台:工具开始执行
|
|
606
|
-
await this.
|
|
666
|
+
await this.caps.events.emit('tool:execute', {
|
|
607
667
|
sessionId: incoming.sessionId,
|
|
608
668
|
platform: incoming.platform,
|
|
609
669
|
toolName: toolBeforeData.name,
|
|
@@ -612,9 +672,7 @@ class DefaultAgent {
|
|
|
612
672
|
});
|
|
613
673
|
this.logger.debug(`工具执行: ${toolBeforeData.name} 参数=${JSON.stringify(toolBeforeData.args)}`);
|
|
614
674
|
const toolT0 = Date.now();
|
|
615
|
-
const executed = asToolExecutionResult(await (this.
|
|
616
|
-
.getService('tools')
|
|
617
|
-
?.execute(toolBeforeData.name, toolBeforeData.args, toolCtx) ??
|
|
675
|
+
const executed = asToolExecutionResult(await (this.caps.tools.current?.execute(toolBeforeData.name, toolBeforeData.args, toolCtx) ??
|
|
618
676
|
Promise.resolve({ content: JSON.stringify({ error: 'tools 服务不可用' }) })));
|
|
619
677
|
let result = executed.content;
|
|
620
678
|
// 工具交给主模型看的图:只随本回合的 tool 消息走(出口由 prepareLLMMessages 编码),
|
|
@@ -622,7 +680,7 @@ class DefaultAgent {
|
|
|
622
680
|
const resultImages = executed.images;
|
|
623
681
|
// Hook: agent:tool:after — 插件可以处理工具执行结果
|
|
624
682
|
const toolAfterData = { name: toolBeforeData.name, result, toolCallContext: toolCtx };
|
|
625
|
-
await this.
|
|
683
|
+
await this.caps.hooks.run('agent:tool:after', toolAfterData);
|
|
626
684
|
result = toolAfterData.result;
|
|
627
685
|
// 工具结果截断:按上下文窗口比例限制单条工具结果长度
|
|
628
686
|
if (result.length > toolResultMaxChars) {
|
|
@@ -632,7 +690,7 @@ class DefaultAgent {
|
|
|
632
690
|
const toolEndTime = Date.now();
|
|
633
691
|
this.logger.debug(`工具完成: ${toolBeforeData.name} (${toolEndTime - toolT0}ms) 结果=${result}`);
|
|
634
692
|
// 通知平台:工具执行完成
|
|
635
|
-
await this.
|
|
693
|
+
await this.caps.events.emit('tool:execute', {
|
|
636
694
|
sessionId: incoming.sessionId,
|
|
637
695
|
platform: incoming.platform,
|
|
638
696
|
toolName: toolBeforeData.name,
|
|
@@ -692,11 +750,11 @@ class DefaultAgent {
|
|
|
692
750
|
triggerType: incoming.triggerType,
|
|
693
751
|
};
|
|
694
752
|
// 已物化的贡献按全局键跳过;本轮工具调用新注册的贡献(如新激活技能)增量落位
|
|
695
|
-
await assemblePromptContributions(this.
|
|
753
|
+
await assemblePromptContributions(this.caps, nextLlmData, {
|
|
696
754
|
buildTimeoutMs: this.promptBuildTimeoutMs,
|
|
697
755
|
signal,
|
|
698
756
|
});
|
|
699
|
-
await this.
|
|
757
|
+
await this.caps.hooks.run('agent:llm:before', nextLlmData, undefined, { warnOnStall: true });
|
|
700
758
|
// 裁剪消息以确保不超过上下文窗口
|
|
701
759
|
nextLlmData.messages = this.trimMessages(nextLlmData.messages, tokenBudget);
|
|
702
760
|
// 推送 token 使用量统计
|
|
@@ -714,7 +772,7 @@ class DefaultAgent {
|
|
|
714
772
|
accUsage(response.usage);
|
|
715
773
|
this.debugLogResponse(response, Date.now() - tN, iterations);
|
|
716
774
|
const nextLlmAfterData = { response, messages: nextLlmData.messages };
|
|
717
|
-
await this.
|
|
775
|
+
await this.caps.hooks.run('agent:llm:after', nextLlmAfterData);
|
|
718
776
|
response = nextLlmAfterData.response;
|
|
719
777
|
if (response.reasoningContent) {
|
|
720
778
|
allReasoning.push(response.reasoningContent);
|
|
@@ -736,7 +794,7 @@ class DefaultAgent {
|
|
|
736
794
|
triggerType: incoming.triggerType,
|
|
737
795
|
attempt: 0,
|
|
738
796
|
};
|
|
739
|
-
await this.
|
|
797
|
+
await this.caps.hooks.run('agent:reply:before', responseData);
|
|
740
798
|
// 重试循环:当 hook(如 persona 的 outputFormat 解析)报告 retryRequested 时,
|
|
741
799
|
// 把失败的 assistant 输出 + 系统反馈追加到消息列表,重新请求 LLM;最多按 maxRetries 次。
|
|
742
800
|
// maxRetries 由 hook 端写入(plugin-persona 从 outputFormat.retries 读取,默认 1)。
|
|
@@ -770,7 +828,7 @@ class DefaultAgent {
|
|
|
770
828
|
responseData.retryRequested = false;
|
|
771
829
|
responseData.retryFeedback = undefined;
|
|
772
830
|
responseData.attempt = attempt;
|
|
773
|
-
await this.
|
|
831
|
+
await this.caps.hooks.run('agent:reply:before', responseData);
|
|
774
832
|
}
|
|
775
833
|
// 双保险:循环结束后若 hook 仍标记 retryRequested(理论上 persona 已在用尽时自动走兜底),
|
|
776
834
|
// 强制把 content 置空,避免原始未校验内容被外发。
|
|
@@ -790,7 +848,7 @@ class DefaultAgent {
|
|
|
790
848
|
replyContent = '';
|
|
791
849
|
}
|
|
792
850
|
// 发出流结束标记
|
|
793
|
-
await this.
|
|
851
|
+
await this.caps.events.emit('outbound:stream', {
|
|
794
852
|
sessionId: incoming.sessionId,
|
|
795
853
|
platform: incoming.platform,
|
|
796
854
|
done: true,
|
|
@@ -839,7 +897,7 @@ class DefaultAgent {
|
|
|
839
897
|
}
|
|
840
898
|
// Hook: agent:turn:after — 插件可以在完整消息周期结束后做后处理
|
|
841
899
|
const turnOutcome = replyContent.trim().length === 0 ? 'silent' : 'replied';
|
|
842
|
-
await this.
|
|
900
|
+
await this.caps.hooks.run('agent:turn:after', {
|
|
843
901
|
message: incoming,
|
|
844
902
|
reply: replyContent,
|
|
845
903
|
outcome: turnOutcome,
|
|
@@ -865,7 +923,7 @@ class DefaultAgent {
|
|
|
865
923
|
(turnPersistedTimestamps.length > 0
|
|
866
924
|
? `(保留本轮已完成的 ${turnPersistedTimestamps.length} 条工具调用记录,便于下一轮 agent 感知)`
|
|
867
925
|
: ''));
|
|
868
|
-
await this.
|
|
926
|
+
await this.caps.events.emit('outbound:stream', {
|
|
869
927
|
sessionId: incoming.sessionId,
|
|
870
928
|
platform: incoming.platform,
|
|
871
929
|
done: true,
|
|
@@ -874,7 +932,7 @@ class DefaultAgent {
|
|
|
874
932
|
// session-manager 把会话状态从 active 收口为 completed(否则永远停在"进行中"),
|
|
875
933
|
// checkpoint 关闭当前回合(否则中止后回合不关闭、长期泄漏)。
|
|
876
934
|
// 文档与 agent-api 早已声明 outcome 含 aborted,此处兑现契约。
|
|
877
|
-
await this.
|
|
935
|
+
await this.caps.hooks.run('agent:turn:after', {
|
|
878
936
|
message: incoming,
|
|
879
937
|
reply: '',
|
|
880
938
|
outcome: 'aborted',
|
|
@@ -887,7 +945,7 @@ class DefaultAgent {
|
|
|
887
945
|
this.logger.error(`处理消息失败: ${message}`);
|
|
888
946
|
// 异常同样要结束流:不发 done,CLI 的流式块不收尾、WebUI 的工具进度与上限标记不复位,
|
|
889
947
|
// 下一轮 delta 还会抹掉这条 [错误] 与用户的新输入。顺序与正常收尾一致——先 done 再发消息。
|
|
890
|
-
await this.
|
|
948
|
+
await this.caps.events.emit('outbound:stream', {
|
|
891
949
|
sessionId: incoming.sessionId,
|
|
892
950
|
platform: incoming.platform,
|
|
893
951
|
done: true,
|
|
@@ -901,7 +959,7 @@ class DefaultAgent {
|
|
|
901
959
|
// 异常也是回合终态:同样发 turn:after(outcome=error) 让 checkpoint 关闭回合、
|
|
902
960
|
// session-manager 收口状态。dispatchOutbound 已发系统错误消息,状态可被 outbound:message
|
|
903
961
|
// 与本钩子双路径幂等收口。
|
|
904
|
-
await this.
|
|
962
|
+
await this.caps.hooks.run('agent:turn:after', {
|
|
905
963
|
message: incoming,
|
|
906
964
|
reply: '',
|
|
907
965
|
outcome: 'error',
|
|
@@ -912,7 +970,7 @@ class DefaultAgent {
|
|
|
912
970
|
});
|
|
913
971
|
// 消息被拦截(如流控缓冲),通知前端结束 loading
|
|
914
972
|
if (!handled) {
|
|
915
|
-
await this.
|
|
973
|
+
await this.caps.events.emit('outbound:stream', {
|
|
916
974
|
sessionId: incoming.sessionId,
|
|
917
975
|
platform: incoming.platform,
|
|
918
976
|
done: true,
|
|
@@ -928,7 +986,7 @@ class DefaultAgent {
|
|
|
928
986
|
const systemPrompt = this.buildSystemPrompt(personaOpts);
|
|
929
987
|
messages.push({ role: 'system', content: systemPrompt, metadata: { injector: 'persona' } });
|
|
930
988
|
// 2. 历史消息
|
|
931
|
-
const memory = this.
|
|
989
|
+
const memory = this.caps.memory.current;
|
|
932
990
|
if (memory) {
|
|
933
991
|
try {
|
|
934
992
|
const history = this.sanitizeToolCallHistory(await memory.getHistory(incoming.sessionId, this.historyLimit), incoming.sessionId);
|
|
@@ -1012,7 +1070,7 @@ class DefaultAgent {
|
|
|
1012
1070
|
// 多模态:把 attachments 中的 image 项传递给 LLM(视觉模型多模态字段)
|
|
1013
1071
|
const imageAtts = incoming.attachments?.filter(a => a.kind === 'image') ?? [];
|
|
1014
1072
|
if (imageAtts.length > 0) {
|
|
1015
|
-
if (this.
|
|
1073
|
+
if (this.caps.media.current !== undefined) {
|
|
1016
1074
|
// media 在场:原样透传。出口形态由 media 的 agent:llm:before 中间件规范化
|
|
1017
1075
|
//(describe 剥离 / passthrough 物化),此处不做任何转换——它的描述缓存键与
|
|
1018
1076
|
// 动图提示都按 attachment.data 原串命中,改形态会打散。
|
|
@@ -1042,7 +1100,7 @@ class DefaultAgent {
|
|
|
1042
1100
|
// 秒级时间戳一变,人设 + 记忆 + 历史近 7 万 token 的前缀全部作废——实测每个
|
|
1043
1101
|
// 新回合命中率恒为 0,只有同回合内的工具迭代(prompt 不重建)才命中。
|
|
1044
1102
|
// 放到这里语义也更顺:它们本就是「此刻的事实」,紧挨当前消息说更自然。
|
|
1045
|
-
const persona = this.
|
|
1103
|
+
const persona = this.caps.persona.current;
|
|
1046
1104
|
const volatileCtx = persona?.getVolatilePrompt?.(personaOpts);
|
|
1047
1105
|
if (volatileCtx) {
|
|
1048
1106
|
messages.push({ role: 'system', content: volatileCtx, metadata: { injector: VOLATILE_INJECTOR } });
|
|
@@ -1059,7 +1117,7 @@ class DefaultAgent {
|
|
|
1059
1117
|
* 构建系统提示词
|
|
1060
1118
|
*/
|
|
1061
1119
|
buildSystemPrompt(personaOpts) {
|
|
1062
|
-
const persona = this.
|
|
1120
|
+
const persona = this.caps.persona.current;
|
|
1063
1121
|
const base = persona
|
|
1064
1122
|
? this.systemPrompt
|
|
1065
1123
|
? `${persona.getSystemPrompt(personaOpts)}\n\n${this.systemPrompt}`
|
|
@@ -1093,7 +1151,7 @@ class DefaultAgent {
|
|
|
1093
1151
|
for (const msg of messages) {
|
|
1094
1152
|
const t = estimateMsgTokens(msg);
|
|
1095
1153
|
if (msg.role === 'system') {
|
|
1096
|
-
// 贡献点物化块的 injector 是全局键 `${
|
|
1154
|
+
// 贡献点物化块的 injector 是全局键 `${贡献方实例 id}/${局部标签}`(实例 id 含 '/'),
|
|
1097
1155
|
// 归桶按局部标签(末段)匹配——桶规则与统计明细键保持迁移前的稳定值;
|
|
1098
1156
|
// 非贡献来源(persona / system-other 等)无 '/',末段即原值,不受影响。
|
|
1099
1157
|
const rawSource = msg.metadata?.injector;
|
|
@@ -1164,7 +1222,7 @@ class DefaultAgent {
|
|
|
1164
1222
|
systemOtherTokens;
|
|
1165
1223
|
const totalUsed = systemTokens + historyTokens + toolResultTokens + toolDefsTokens;
|
|
1166
1224
|
const usageRatio = contextLength > 0 ? totalUsed / contextLength : 0;
|
|
1167
|
-
this.
|
|
1225
|
+
this.caps.events
|
|
1168
1226
|
.emit('token:usage', {
|
|
1169
1227
|
sessionId,
|
|
1170
1228
|
platform,
|
|
@@ -1219,7 +1277,7 @@ class DefaultAgent {
|
|
|
1219
1277
|
const rel = t.match(/^data\/((?:images|videos|audios|files)\/.+)$/);
|
|
1220
1278
|
if (!rel)
|
|
1221
1279
|
return t;
|
|
1222
|
-
const storage = this.
|
|
1280
|
+
const storage = this.caps.storage.current;
|
|
1223
1281
|
if (!storage)
|
|
1224
1282
|
return null;
|
|
1225
1283
|
try {
|
|
@@ -1473,11 +1531,17 @@ class DefaultAgent {
|
|
|
1473
1531
|
}
|
|
1474
1532
|
return result;
|
|
1475
1533
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1534
|
+
getArchiveForWrite() {
|
|
1535
|
+
const archive = this.caps.messageArchive.current;
|
|
1536
|
+
if (!archive && !this.archiveUnavailableWarned) {
|
|
1537
|
+
this.archiveUnavailableWarned = true;
|
|
1538
|
+
this.logger.warn('message-archive 服务不可用,缺席期间的新消息不会写入记忆,对话继续。请加载 @aalis/plugin-message-archive 及其 memory 依赖。');
|
|
1539
|
+
}
|
|
1540
|
+
return archive;
|
|
1541
|
+
}
|
|
1542
|
+
/** 保存消息到记忆服务 */
|
|
1479
1543
|
async saveToMemory(sessionId, message) {
|
|
1480
|
-
const archive = this.
|
|
1544
|
+
const archive = this.getArchiveForWrite();
|
|
1481
1545
|
if (archive) {
|
|
1482
1546
|
try {
|
|
1483
1547
|
await archive.saveMessage(sessionId, message);
|
|
@@ -1488,7 +1552,7 @@ class DefaultAgent {
|
|
|
1488
1552
|
}
|
|
1489
1553
|
}
|
|
1490
1554
|
buildAssistantMetadata(incoming) {
|
|
1491
|
-
const identity = getPlatformSelfIdentity(this.
|
|
1555
|
+
const identity = getPlatformSelfIdentity(this.caps.platform, incoming.platform, incoming.sessionId);
|
|
1492
1556
|
const metadata = {
|
|
1493
1557
|
platform: incoming.platform,
|
|
1494
1558
|
senderType: 'assistant',
|
|
@@ -1577,7 +1641,7 @@ class DefaultAgent {
|
|
|
1577
1641
|
// 跳过非真实用户输入:闲聊主动触发是系统提示,不应作为 user 消息写入历史
|
|
1578
1642
|
if (incoming.source === 'idle-trigger')
|
|
1579
1643
|
return undefined;
|
|
1580
|
-
const archive = this.
|
|
1644
|
+
const archive = this.getArchiveForWrite();
|
|
1581
1645
|
if (!archive)
|
|
1582
1646
|
return undefined;
|
|
1583
1647
|
try {
|
|
@@ -1596,35 +1660,27 @@ class DefaultAgent {
|
|
|
1596
1660
|
* 此时该 fallback 永远不命中——出站消息总是经过 outbound:dispatch 钩子链
|
|
1597
1661
|
* (审计 / 脱敏 / 限速 / authority 等中间件)。
|
|
1598
1662
|
*
|
|
1599
|
-
*
|
|
1600
|
-
* 即使 gateway 未加载本插件仍会激活,因此保留该 fallback 以便:
|
|
1663
|
+
* gateway 只是本插件的 optional 依赖,它不在场时 agent 照常激活,因此保留该 fallback 以便:
|
|
1601
1664
|
* - 集成测试不必启动 gateway
|
|
1602
1665
|
* - 嵌入式 / 单 agent 部署场景
|
|
1603
1666
|
* 生产部署务必确保 plugin-gateway 已加载,否则中间件链会被跳过。
|
|
1604
1667
|
*/
|
|
1605
1668
|
async dispatchOutbound(message) {
|
|
1606
|
-
const gateway = this.
|
|
1669
|
+
const gateway = this.caps.gateway.current;
|
|
1607
1670
|
if (gateway) {
|
|
1608
1671
|
await gateway.dispatchOutbound(message);
|
|
1609
1672
|
return;
|
|
1610
1673
|
}
|
|
1611
|
-
this.logger.warn('Gateway 服务不可用,回退至
|
|
1612
|
-
await this.
|
|
1674
|
+
this.logger.warn('Gateway 服务不可用,回退至 outbound:message 事件(中间件链被跳过)');
|
|
1675
|
+
await this.caps.events.emit('outbound:message', message);
|
|
1613
1676
|
}
|
|
1614
1677
|
}
|
|
1615
1678
|
// ----- 插件导出 -----
|
|
1616
|
-
|
|
1617
|
-
export const displayName = '默认 Agent';
|
|
1618
|
-
export const subsystem = 'agent';
|
|
1619
|
-
export const provides = ['agent'];
|
|
1620
|
-
export const inject = {
|
|
1621
|
-
optional: ['llm', 'memory', 'persona', 'message-archive', 'platform', 'media', 'storage'],
|
|
1622
|
-
};
|
|
1623
|
-
export const configSchema = {
|
|
1679
|
+
const configSchema = {
|
|
1624
1680
|
defaultLLM: {
|
|
1625
1681
|
type: 'llm-ref',
|
|
1626
1682
|
label: '默认对话模型',
|
|
1627
|
-
description: '全局默认 LLM。apply()
|
|
1683
|
+
description: '全局默认 LLM。apply() 时经 services.prefer 锁定 llm 服务的偏好提供者。会话 / 平台 profile 未覆盖时生效。',
|
|
1628
1684
|
},
|
|
1629
1685
|
systemPrompt: {
|
|
1630
1686
|
type: 'textarea',
|
|
@@ -1669,21 +1725,26 @@ export const configSchema = {
|
|
|
1669
1725
|
description: '裁剪预算 = 上下文长度 × 该比例 − 最大输出 token − 512 安全余量(下限 1024)。本次调用估算输入 token 超过该预算才会对消息列表做内存裁剪(不影响 DB)。默认 1.0 表示用满扣除输出预留后的可用窗口;调低可提前裁剪。压缩触发请在“@aalis/plugin-memory-summary”中配置。',
|
|
1670
1726
|
},
|
|
1671
1727
|
};
|
|
1672
|
-
|
|
1673
|
-
const agentImpl = new DefaultAgent(
|
|
1674
|
-
|
|
1675
|
-
//
|
|
1676
|
-
|
|
1728
|
+
function run(caps) {
|
|
1729
|
+
const agentImpl = new DefaultAgent(caps);
|
|
1730
|
+
caps.provide(agentService, agentImpl);
|
|
1731
|
+
// 收尾段中止在飞回合并等 AbortError 落定。
|
|
1732
|
+
// 第一方栈由 core 关停编排(optional 环成员先全部 drain)与 agent:turn:after 收口;
|
|
1733
|
+
// session-manager 的 onDrain 兜的是未装 agent 或钩子未挂上的路径。
|
|
1734
|
+
// 超时沿用 core 对 onDrain 的 disposeTimeoutMs,不另加配置键。
|
|
1735
|
+
caps.lifecycle.onDrain(() => agentImpl.abortInflightAndSettle(), '中止在飞回合');
|
|
1736
|
+
// 清理段再 abort 一次:drain 超时或未走到收尾时的兜底,表空则空操作。
|
|
1737
|
+
caps.lifecycle.onDispose(() => agentImpl.abortAll(), '中止在飞回合(兜底)');
|
|
1677
1738
|
const agent = agentImpl;
|
|
1678
|
-
// 全局默认 LLM:通过 ServicePreference 锁定
|
|
1739
|
+
// 全局默认 LLM:通过 ServicePreference 锁定 llm 服务的首选 entry。
|
|
1679
1740
|
// 偏好持久化由 core 配置层负责(servicePreferences 字段);这里只是开机时按 agent
|
|
1680
1741
|
// 自己的 cfg.defaultLLM 覆写一次,便于纯文件配置流(无 webui 干预)也能生效。
|
|
1681
|
-
const defaultLLM = config.defaultLLM;
|
|
1742
|
+
const defaultLLM = caps.config.defaultLLM;
|
|
1682
1743
|
if (defaultLLM?.provider && defaultLLM?.model) {
|
|
1683
|
-
|
|
1744
|
+
caps.services.prefer(llmService, `${defaultLLM.provider}/${defaultLLM.model}`);
|
|
1684
1745
|
}
|
|
1685
1746
|
function resolutionLines(sessionId, platform, label, pick) {
|
|
1686
|
-
const smSvc =
|
|
1747
|
+
const smSvc = caps.sessionManager.current;
|
|
1687
1748
|
if (!smSvc)
|
|
1688
1749
|
return [`${label}: (session-manager 不可用)`];
|
|
1689
1750
|
const session = smSvc.getSession(sessionId);
|
|
@@ -1735,13 +1796,13 @@ export function apply(ctx, config) {
|
|
|
1735
1796
|
}
|
|
1736
1797
|
return lines.join('\n');
|
|
1737
1798
|
}
|
|
1738
|
-
|
|
1799
|
+
caps.commands
|
|
1739
1800
|
.command('model [keyword:string]', '列出/搜索可用对话模型(分页,-p 翻页;如 /model grok)')
|
|
1740
1801
|
.option('page', '-p <n:number>', { description: '页码(默认 1)' })
|
|
1741
1802
|
.action(async (argv, keyword) => {
|
|
1742
1803
|
const seen = new Set();
|
|
1743
1804
|
const ids = [];
|
|
1744
|
-
for (const e of listLLMModels(
|
|
1805
|
+
for (const e of listLLMModels(caps.llm, { caps: ['chat'] })) {
|
|
1745
1806
|
if (seen.has(e.contextId))
|
|
1746
1807
|
continue;
|
|
1747
1808
|
seen.add(e.contextId);
|
|
@@ -1750,20 +1811,20 @@ export function apply(ctx, config) {
|
|
|
1750
1811
|
const page = typeof argv.options.page === 'number' ? argv.options.page : 1;
|
|
1751
1812
|
return renderPaged(ids, String(keyword ?? ''), page, '可用模型', '/model');
|
|
1752
1813
|
});
|
|
1753
|
-
|
|
1814
|
+
caps.commands
|
|
1754
1815
|
.command('persona [keyword:string]', '列出/搜索可用人设(分页,-p 翻页)')
|
|
1755
1816
|
.option('page', '-p <n:number>', { description: '页码(默认 1)' })
|
|
1756
1817
|
.action(async (argv, keyword) => {
|
|
1757
|
-
const persona =
|
|
1818
|
+
const persona = caps.persona.current;
|
|
1758
1819
|
const names = persona?.listModels ? await persona.listModels() : [];
|
|
1759
1820
|
const page = typeof argv.options.page === 'number' ? argv.options.page : 1;
|
|
1760
1821
|
return renderPaged(names, String(keyword ?? ''), page, '可用人设', '/persona');
|
|
1761
1822
|
});
|
|
1762
1823
|
// ---- 会话级「模型 + 人设 + thinking + 名称」配置(onebot 等平台对话直接改当前对话生效) ----
|
|
1763
|
-
|
|
1824
|
+
caps.commands
|
|
1764
1825
|
.command('session', '查看当前对话生效的模型 / 人设 / thinking / 名称及来源与解析链', { risk: 'sensitive' })
|
|
1765
1826
|
.action(async (argv) => {
|
|
1766
|
-
const smSvc =
|
|
1827
|
+
const smSvc = caps.sessionManager.current;
|
|
1767
1828
|
if (!smSvc)
|
|
1768
1829
|
return 'session-manager 服务不可用';
|
|
1769
1830
|
const sid = argv.session.sessionId;
|
|
@@ -1781,7 +1842,7 @@ export function apply(ctx, config) {
|
|
|
1781
1842
|
.filter(Boolean)
|
|
1782
1843
|
.join('\n');
|
|
1783
1844
|
});
|
|
1784
|
-
|
|
1845
|
+
caps.commands
|
|
1785
1846
|
.command('session.set', '设定当前对话的模型 / 人设 / thinking / 显示名(会话级覆盖,持久化,重启不丢)', {
|
|
1786
1847
|
risk: 'sensitive',
|
|
1787
1848
|
examples: [
|
|
@@ -1798,7 +1859,7 @@ export function apply(ctx, config) {
|
|
|
1798
1859
|
})
|
|
1799
1860
|
.option('name', '-n <label:string>', { description: '会话显示名(可选)' })
|
|
1800
1861
|
.action(async (argv) => {
|
|
1801
|
-
const smSvc =
|
|
1862
|
+
const smSvc = caps.sessionManager.current;
|
|
1802
1863
|
if (!smSvc)
|
|
1803
1864
|
return 'session-manager 服务不可用';
|
|
1804
1865
|
const modelRef = typeof argv.options.model === 'string' ? argv.options.model.trim() : '';
|
|
@@ -1820,7 +1881,7 @@ export function apply(ctx, config) {
|
|
|
1820
1881
|
if (lastSlash <= 0 || lastSlash === modelRef.length - 1) {
|
|
1821
1882
|
return '模型格式错误。请用 `provider/model`,例如 `@aalis/plugin-llm-openai:main/gpt-4o`';
|
|
1822
1883
|
}
|
|
1823
|
-
const chatModels = listLLMModels(
|
|
1884
|
+
const chatModels = listLLMModels(caps.llm, { caps: ['chat'] });
|
|
1824
1885
|
if (!chatModels.some(e => e.contextId === modelRef)) {
|
|
1825
1886
|
return `未知模型 "${modelRef}"。用 /model <关键词> 搜索(如 /model grok),共 ${chatModels.length} 个可用。`;
|
|
1826
1887
|
}
|
|
@@ -1828,7 +1889,7 @@ export function apply(ctx, config) {
|
|
|
1828
1889
|
}
|
|
1829
1890
|
// 人设:listModels 可选,缺失则不校验(persona 插件缺席时不误拒)
|
|
1830
1891
|
if (personaName) {
|
|
1831
|
-
const persona =
|
|
1892
|
+
const persona = caps.persona.current;
|
|
1832
1893
|
if (persona?.listModels) {
|
|
1833
1894
|
const names = await persona.listModels();
|
|
1834
1895
|
if (!names.includes(personaName)) {
|
|
@@ -1854,7 +1915,7 @@ export function apply(ctx, config) {
|
|
|
1854
1915
|
lines.push('后续消息持续生效(已持久化,重启不丢)。/session.reset 可复位。');
|
|
1855
1916
|
return lines.join('\n');
|
|
1856
1917
|
});
|
|
1857
|
-
|
|
1918
|
+
caps.commands
|
|
1858
1919
|
.command('session.reset', '复位当前对话的会话级覆盖(默认清模型+人设+thinking;-m/-p/-t 单独清对应项)', {
|
|
1859
1920
|
risk: 'sensitive',
|
|
1860
1921
|
examples: ['/session.reset', '/session.reset -m', '/session.reset -p', '/session.reset -t'],
|
|
@@ -1863,7 +1924,7 @@ export function apply(ctx, config) {
|
|
|
1863
1924
|
.option('persona', '-p', { description: '仅复位人设覆盖' })
|
|
1864
1925
|
.option('think', '-t', { description: '仅复位 thinking 覆盖' })
|
|
1865
1926
|
.action(async (argv) => {
|
|
1866
|
-
const smSvc =
|
|
1927
|
+
const smSvc = caps.sessionManager.current;
|
|
1867
1928
|
if (!smSvc)
|
|
1868
1929
|
return 'session-manager 服务不可用';
|
|
1869
1930
|
const mFlag = argv.options.model === true;
|
|
@@ -1895,8 +1956,7 @@ export function apply(ctx, config) {
|
|
|
1895
1956
|
return `已复位 → 模型: ${eff.llm ? `${eff.llm.provider}/${eff.llm.model}` : '(默认)'},人设: ${eff.persona ?? '(默认)'},thinking: ${thinkStr}`;
|
|
1896
1957
|
});
|
|
1897
1958
|
// 监听 token:request 事件 — 客户端刷新/重连时主动请求 token 用量
|
|
1898
|
-
|
|
1899
|
-
const data = args[0];
|
|
1959
|
+
caps.events.on('token:request', async (data) => {
|
|
1900
1960
|
if (!data?.sessionId)
|
|
1901
1961
|
return;
|
|
1902
1962
|
// 唯一发射方是 WebUI;平台缺省按 'webui' 兜底,否则模型/会话配置解析会绕过平台 profile 层
|
|
@@ -1910,7 +1970,7 @@ export function apply(ctx, config) {
|
|
|
1910
1970
|
const maxTokens = getModelMaxOutput(llm);
|
|
1911
1971
|
const tokenBudget = Math.max(1024, contextLength - maxTokens - 512);
|
|
1912
1972
|
// 获取历史消息并构建基础消息列表
|
|
1913
|
-
const memory =
|
|
1973
|
+
const memory = caps.memory.current;
|
|
1914
1974
|
const messages = [];
|
|
1915
1975
|
// 系统提示
|
|
1916
1976
|
const systemPrompt = agent.buildSystemPrompt();
|
|
@@ -1922,11 +1982,10 @@ export function apply(ctx, config) {
|
|
|
1922
1982
|
}
|
|
1923
1983
|
// 与真实回合同序:先组装 agent:prompt 贡献(摘要/向量记忆/档案等物化为 system 块),
|
|
1924
1984
|
// 再跑 agent:llm:before(工具搜索层过滤等拦截职责),使快照贴近实际送入 LLM 的形态
|
|
1925
|
-
const sm =
|
|
1985
|
+
const sm = caps.sessionManager.current;
|
|
1926
1986
|
const sessionResolved = sm ? sm.resolveConfig(data.sessionId, platform) : undefined;
|
|
1927
1987
|
const enabledGroups = sessionResolved?.enabledToolGroups?.length ? sessionResolved.enabledToolGroups : undefined;
|
|
1928
|
-
const tools =
|
|
1929
|
-
[];
|
|
1988
|
+
const tools = caps.tools.current?.getDefinitions(enabledGroups ? { groups: enabledGroups } : undefined) ?? [];
|
|
1930
1989
|
const llmBeforeData = {
|
|
1931
1990
|
messages,
|
|
1932
1991
|
tools,
|
|
@@ -1935,13 +1994,22 @@ export function apply(ctx, config) {
|
|
|
1935
1994
|
platform,
|
|
1936
1995
|
dryRun: true, // 纯统计路径:昂贵注入者(向量检索/档案加载)据此跳过副作用
|
|
1937
1996
|
};
|
|
1938
|
-
await assemblePromptContributions(
|
|
1939
|
-
await
|
|
1997
|
+
await assemblePromptContributions(caps, llmBeforeData, { buildTimeoutMs: agent.promptBuildTimeoutMs });
|
|
1998
|
+
await caps.hooks.run('agent:llm:before', llmBeforeData, undefined, { warnOnStall: true });
|
|
1940
1999
|
agent.emitTokenUsage(data.sessionId, platform, llmBeforeData.messages, llmBeforeData.tools, contextLength, maxTokens, tokenBudget);
|
|
1941
2000
|
}
|
|
1942
2001
|
catch (err) {
|
|
1943
|
-
|
|
2002
|
+
caps.logger.debug('token:request 处理失败:', err);
|
|
1944
2003
|
}
|
|
1945
2004
|
});
|
|
1946
2005
|
}
|
|
2006
|
+
export default definePlugin({
|
|
2007
|
+
name: '@aalis/plugin-agent',
|
|
2008
|
+
displayName: '默认 Agent',
|
|
2009
|
+
subsystem: 'agent',
|
|
2010
|
+
configSchema,
|
|
2011
|
+
provides: [agentService],
|
|
2012
|
+
uses,
|
|
2013
|
+
apply: run,
|
|
2014
|
+
});
|
|
1947
2015
|
//# sourceMappingURL=index.js.map
|