@zooique/memora 0.3.0 → 2.0.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/README.md +8 -8
- package/dist/agent/agent.d.ts +69 -92
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +267 -197
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts +3 -0
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +14 -4
- package/dist/agent/assembler.js.map +1 -1
- package/dist/agent/builtinToolHandlers.d.ts +1 -1
- package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
- package/dist/agent/builtinToolHandlers.js +5 -4
- package/dist/agent/builtinToolHandlers.js.map +1 -1
- package/dist/agent/guardrail.d.ts +47 -0
- package/dist/agent/guardrail.d.ts.map +1 -0
- package/dist/agent/guardrail.js +51 -0
- package/dist/agent/guardrail.js.map +1 -0
- package/dist/agent/loop.d.ts +79 -18
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +317 -231
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/managers/chatLockManager.d.ts +116 -0
- package/dist/agent/managers/chatLockManager.d.ts.map +1 -0
- package/dist/agent/managers/chatLockManager.js +191 -0
- package/dist/agent/managers/chatLockManager.js.map +1 -0
- package/dist/agent/managers/insightExtractor.d.ts +34 -69
- package/dist/agent/managers/insightExtractor.d.ts.map +1 -1
- package/dist/agent/managers/insightExtractor.js +133 -189
- package/dist/agent/managers/insightExtractor.js.map +1 -1
- package/dist/agent/managers/memoryAdvisor.d.ts +102 -2
- package/dist/agent/managers/memoryAdvisor.d.ts.map +1 -1
- package/dist/agent/managers/memoryAdvisor.js +262 -4
- package/dist/agent/managers/memoryAdvisor.js.map +1 -1
- package/dist/agent/managers/memoryDecayScheduler.d.ts +85 -0
- package/dist/agent/managers/memoryDecayScheduler.d.ts.map +1 -1
- package/dist/agent/managers/memoryDecayScheduler.js +215 -0
- package/dist/agent/managers/memoryDecayScheduler.js.map +1 -1
- package/dist/agent/managers/memoryInspector.d.ts +230 -64
- package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
- package/dist/agent/managers/memoryInspector.js +439 -81
- package/dist/agent/managers/memoryInspector.js.map +1 -1
- package/dist/agent/managers/relationBuilder.d.ts +131 -0
- package/dist/agent/managers/relationBuilder.d.ts.map +1 -0
- package/dist/agent/managers/relationBuilder.js +192 -0
- package/dist/agent/managers/relationBuilder.js.map +1 -0
- package/dist/agent/managers/sessionArchiver.d.ts +6 -0
- package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
- package/dist/agent/managers/sessionArchiver.js +68 -8
- package/dist/agent/managers/sessionArchiver.js.map +1 -1
- package/dist/agent/managers/sessionManager.d.ts.map +1 -1
- package/dist/agent/managers/sessionManager.js +9 -17
- package/dist/agent/managers/sessionManager.js.map +1 -1
- package/dist/agent/managers/textPolishManager.d.ts +62 -0
- package/dist/agent/managers/textPolishManager.d.ts.map +1 -0
- package/dist/agent/managers/textPolishManager.js +99 -0
- package/dist/agent/managers/textPolishManager.js.map +1 -0
- package/dist/agent/personaMatcher.d.ts +29 -0
- package/dist/agent/personaMatcher.d.ts.map +1 -0
- package/dist/agent/personaMatcher.js +60 -0
- package/dist/agent/personaMatcher.js.map +1 -0
- package/dist/agent/toolExecutor.d.ts +2 -2
- package/dist/agent/toolExecutor.d.ts.map +1 -1
- package/dist/agent/toolExecutor.js +2 -1
- package/dist/agent/toolExecutor.js.map +1 -1
- package/dist/agent/tracer.d.ts +31 -0
- package/dist/agent/tracer.d.ts.map +1 -1
- package/dist/agent/tracer.js +31 -0
- package/dist/agent/tracer.js.map +1 -1
- package/dist/agent/types.d.ts +132 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js +38 -0
- package/dist/agent/types.js.map +1 -1
- package/dist/config/loader.d.ts +52 -40
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +52 -32
- package/dist/config/loader.js.map +1 -1
- package/dist/eval/evalRunner.d.ts +94 -0
- package/dist/eval/evalRunner.d.ts.map +1 -0
- package/dist/eval/evalRunner.js +141 -0
- package/dist/eval/evalRunner.js.map +1 -0
- package/dist/eval/evalTypes.d.ts +33 -3
- package/dist/eval/evalTypes.d.ts.map +1 -1
- package/dist/eval/evalTypes.js +18 -2
- package/dist/eval/evalTypes.js.map +1 -1
- package/dist/eval/scenarios.d.ts +33 -0
- package/dist/eval/scenarios.d.ts.map +1 -0
- package/dist/eval/scenarios.js +109 -0
- package/dist/eval/scenarios.js.map +1 -0
- package/dist/index.d.ts +51 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -6
- package/dist/index.js.map +1 -1
- package/dist/llm/abortSignal.d.ts +27 -0
- package/dist/llm/abortSignal.d.ts.map +1 -0
- package/dist/llm/abortSignal.js +44 -0
- package/dist/llm/abortSignal.js.map +1 -0
- package/dist/llm/embedding.d.ts +7 -2
- package/dist/llm/embedding.d.ts.map +1 -1
- package/dist/llm/embedding.js +66 -46
- package/dist/llm/embedding.js.map +1 -1
- package/dist/llm/factory.js +1 -1
- package/dist/llm/factory.js.map +1 -1
- package/dist/llm/openaiCompatible.d.ts.map +1 -1
- package/dist/llm/openaiCompatible.js +26 -27
- package/dist/llm/openaiCompatible.js.map +1 -1
- package/dist/llm/provider.d.ts +0 -14
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/logging/logger.d.ts +1 -0
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +43 -37
- package/dist/logging/logger.js.map +1 -1
- package/dist/memory/inMemoryStorage.d.ts.map +1 -1
- package/dist/memory/inMemoryStorage.js +8 -7
- package/dist/memory/inMemoryStorage.js.map +1 -1
- package/dist/memory/lockManager.d.ts +48 -0
- package/dist/memory/lockManager.d.ts.map +1 -0
- package/dist/memory/lockManager.js +159 -0
- package/dist/memory/lockManager.js.map +1 -0
- package/dist/memory/projectManager.d.ts +23 -53
- package/dist/memory/projectManager.d.ts.map +1 -1
- package/dist/memory/projectManager.js +46 -227
- package/dist/memory/projectManager.js.map +1 -1
- package/dist/memory/projectRegistry.d.ts +62 -0
- package/dist/memory/projectRegistry.d.ts.map +1 -0
- package/dist/memory/projectRegistry.js +158 -0
- package/dist/memory/projectRegistry.js.map +1 -0
- package/dist/memory/recall.d.ts +3 -3
- package/dist/memory/recall.d.ts.map +1 -1
- package/dist/memory/recall.js +3 -3
- package/dist/memory/recall.js.map +1 -1
- package/dist/memory/sourceValidation.d.ts +49 -0
- package/dist/memory/sourceValidation.d.ts.map +1 -0
- package/dist/memory/sourceValidation.js +141 -0
- package/dist/memory/sourceValidation.js.map +1 -0
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +2 -1
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/types.d.ts +0 -47
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +0 -138
- package/dist/memory/types.js.map +1 -1
- package/dist/memory/vectorStore.d.ts +82 -7
- package/dist/memory/vectorStore.d.ts.map +1 -1
- package/dist/memory/vectorStore.js +12 -8
- package/dist/memory/vectorStore.js.map +1 -1
- package/dist/persona/personaManager.d.ts +30 -4
- package/dist/persona/personaManager.d.ts.map +1 -1
- package/dist/persona/personaManager.js +47 -14
- package/dist/persona/personaManager.js.map +1 -1
- package/dist/security/pathGuard.d.ts.map +1 -1
- package/dist/security/pathGuard.js +3 -1
- package/dist/security/pathGuard.js.map +1 -1
- package/dist/skill/skillManager.d.ts.map +1 -1
- package/dist/skill/skillManager.js +2 -1
- package/dist/skill/skillManager.js.map +1 -1
- package/dist/utils/array.d.ts +24 -0
- package/dist/utils/array.d.ts.map +1 -0
- package/dist/utils/array.js +24 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/errors.d.ts +10 -8
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +12 -9
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/eventEmitter.d.ts +7 -0
- package/dist/utils/eventEmitter.d.ts.map +1 -1
- package/dist/utils/eventEmitter.js.map +1 -1
- package/dist/utils/objects.d.ts +17 -0
- package/dist/utils/objects.d.ts.map +1 -0
- package/dist/utils/objects.js +19 -0
- package/dist/utils/objects.js.map +1 -0
- package/dist/utils/segmenter.d.ts +17 -0
- package/dist/utils/segmenter.d.ts.map +1 -1
- package/dist/utils/segmenter.js +26 -0
- package/dist/utils/segmenter.js.map +1 -1
- package/dist/utils/strings.d.ts +22 -1
- package/dist/utils/strings.d.ts.map +1 -1
- package/dist/utils/strings.js +29 -1
- package/dist/utils/strings.js.map +1 -1
- package/dist/utils/time.d.ts +18 -3
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +24 -8
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/toError.d.ts +8 -4
- package/dist/utils/toError.d.ts.map +1 -1
- package/dist/utils/toError.js +25 -5
- package/dist/utils/toError.js.map +1 -1
- package/package.json +11 -1
package/dist/agent/agent.js
CHANGED
|
@@ -22,27 +22,30 @@
|
|
|
22
22
|
* - 记忆查看 → MemoryInspector
|
|
23
23
|
* - 薄包装方法移除,调用方改为 agent.<manager>.xxx()
|
|
24
24
|
*/
|
|
25
|
-
import {
|
|
25
|
+
import { getBaseName } from '../utils/path.js';
|
|
26
26
|
import { AGENT_CONSTANTS } from '../agent/constants.js';
|
|
27
27
|
import { ProjectManager } from '../memory/projectManager.js';
|
|
28
28
|
import { SecurityGuard } from '../security/pathGuard.js';
|
|
29
29
|
import { recall } from '../memory/recall.js';
|
|
30
|
-
import { extractUserFacts } from '../agent/userFactExtractor.js';
|
|
31
30
|
import { assembleComponents } from '../agent/assembler.js';
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
31
|
+
import { matchPersonaByLlm } from '../agent/personaMatcher.js';
|
|
32
|
+
import { chatBusyError, configError } from '../utils/errors.js';
|
|
33
|
+
import { clearSafeInterval } from '../utils/safeTimer.js';
|
|
34
34
|
import { SessionManager } from '../agent/managers/sessionManager.js';
|
|
35
|
+
import { ChatLockManager } from '../agent/managers/chatLockManager.js';
|
|
35
36
|
import { MemoryDecayScheduler } from '../agent/managers/memoryDecayScheduler.js';
|
|
36
37
|
import { ArchiveCoordinator } from '../agent/managers/archiveCoordinator.js';
|
|
37
38
|
import { TypedEventEmitter } from '../utils/eventEmitter.js';
|
|
38
39
|
import { logger } from '../logging/logger.js';
|
|
40
|
+
import { TRACE_SPANS, NOOP_TRACER } from '../agent/tracer.js';
|
|
39
41
|
// ─── 模块级常量 ─────────────────────────────────────────
|
|
40
42
|
/** Agent 事件名白名单,用于运行时校验 SessionManager 转发的事件类型 */
|
|
41
|
-
// 必须与 utils/eventEmitter.ts 的 AgentEventMap 键集保持一致(
|
|
43
|
+
// 必须与 utils/eventEmitter.ts 的 AgentEventMap 键集保持一致(10 个事件)
|
|
42
44
|
const AGENT_EVENT_NAMES = new Set([
|
|
43
45
|
'memoryAdded', 'personaSwitched', 'decayCompleted',
|
|
44
46
|
'memoryRecalled', 'sessionForked', 'insightExtracted',
|
|
45
47
|
'conflictDetected', 'projectSwitched', 'skillMatched',
|
|
48
|
+
'archiveFailed',
|
|
46
49
|
]);
|
|
47
50
|
// ─── Agent 门面类 ───────────────────────────────────────
|
|
48
51
|
/**
|
|
@@ -100,6 +103,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
100
103
|
autoConfigRefiner = null;
|
|
101
104
|
/** SessionArchiver(会话内容归档器,content 类记忆) */
|
|
102
105
|
sessionArchiver = null;
|
|
106
|
+
/** TextPolishManager(文本润色管理器,LLM 语法修正 + 表达优化) */
|
|
107
|
+
textPolisher = null;
|
|
103
108
|
/** 会话管理器(从 Agent 拆分出的会话管理职责) */
|
|
104
109
|
_sessionManager = null;
|
|
105
110
|
/** 当前激活的技能名(上一轮匹配,本轮注入) */
|
|
@@ -107,29 +112,13 @@ export class Agent extends TypedEventEmitter {
|
|
|
107
112
|
_initialized = false;
|
|
108
113
|
// 项目上下文(AgentContext 与 ProjectContext 等价,直接使用后者避免重复字段)
|
|
109
114
|
pctx = null;
|
|
110
|
-
/** chat() 并发锁 */
|
|
111
|
-
_chatBusy = false;
|
|
112
115
|
/**
|
|
113
|
-
* chat()
|
|
116
|
+
* chat() 并发锁管理器(init 时创建,close 时销毁)
|
|
114
117
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* 导致 race condition:T=0 A 获取锁 → T=180s 超时释放 → T=181s B 获取锁
|
|
118
|
-
* → T=182s A 的 finally 误清 B 的锁/计时器/controller。
|
|
119
|
-
*
|
|
120
|
-
* 防护方案:
|
|
121
|
-
* - 获取锁时 token 递增:`const myToken = ++this._chatLockToken`
|
|
122
|
-
* - 超时回调校验 `this._chatLockToken === myToken` 后才释放
|
|
123
|
-
* - finally 块校验 `this._chatLockToken === myToken` 后才清理
|
|
124
|
-
* - 不匹配时跳过清理,避免误清新调用者的资源
|
|
125
|
-
*
|
|
126
|
-
* 选择 number 而非 Symbol:递增计数器可序列化、零依赖、足够唯一(同一 Agent 实例内)
|
|
118
|
+
* 职责:chat() 并发锁 + token 校验(race condition 防护)+ 超时保护 + 外部 signal 合并。
|
|
119
|
+
* 详见 ChatLockManager 类注释。
|
|
127
120
|
*/
|
|
128
|
-
|
|
129
|
-
/** 聊天锁超时计时器(防止 LLM 卡死时锁永久持有) */
|
|
130
|
-
chatLockTimer = null;
|
|
131
|
-
/** chat() 内部 AbortController(超时时中断 generator,防止并发) */
|
|
132
|
-
chatAbortController = null;
|
|
121
|
+
chatLockManager = null;
|
|
133
122
|
/** 记忆衰减定时器(已迁移至 MemoryDecayScheduler,此字段保留用于 close 时引用判断) */
|
|
134
123
|
decayTimer = null;
|
|
135
124
|
/** 最近一次 chat() 调用的时间戳 */
|
|
@@ -175,7 +164,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
175
164
|
*/
|
|
176
165
|
async init(projectPathOverride) {
|
|
177
166
|
if (this._initialized) {
|
|
178
|
-
//
|
|
167
|
+
// close() 失败不应阻塞 init() 重建
|
|
179
168
|
// 原 close() 异常(如 awaitPendingArchives 超时)会传播到 init() 调用方,
|
|
180
169
|
// 导致 Agent 处于不可用状态。此处捕获后继续重建。
|
|
181
170
|
try {
|
|
@@ -205,6 +194,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
205
194
|
'确认 API Key 已配置(环境变量或配置文件)',
|
|
206
195
|
]);
|
|
207
196
|
}
|
|
197
|
+
// chat() 并发锁管理器(生命周期与 Agent 实例一致)
|
|
198
|
+
this.chatLockManager = new ChatLockManager();
|
|
208
199
|
this._initialized = true;
|
|
209
200
|
// 归档操作委托给 ArchiveCoordinator
|
|
210
201
|
// 使用 getter 回调注入依赖,close 时 null 化字段后 getter 自然返回 null
|
|
@@ -215,9 +206,22 @@ export class Agent extends TypedEventEmitter {
|
|
|
215
206
|
emit: (event, payload) => this.emit(event, payload),
|
|
216
207
|
});
|
|
217
208
|
// 记忆衰减职责委托给 MemoryDecayScheduler
|
|
209
|
+
// L2 时效性评估:注入 backgroundProvider + index(可选,未注入时 evaluateTimeliness 静默跳过)
|
|
218
210
|
this.memoryDecayScheduler = new MemoryDecayScheduler({
|
|
219
211
|
tracer: this.#config.tracer,
|
|
220
|
-
onDecayCompleted: (payload) =>
|
|
212
|
+
onDecayCompleted: (payload) => {
|
|
213
|
+
this.emit('decayCompleted', payload);
|
|
214
|
+
// G2 自动触发 L2 时效性评估:衰减完成后 fire-and-forget 调用 evaluateTimeliness。
|
|
215
|
+
// 不 await:衰减循环不应被 LLM 调用阻塞(LLM 可能 5-15 秒)。
|
|
216
|
+
// 失败静默降级:evaluateTimeliness 内部已捕获 LLM 异常并返回降级报告。
|
|
217
|
+
// 触发频次:每小时 1 次(DECAY_INTERVAL_MS=3_600_000),无频次压力。
|
|
218
|
+
void this.evaluateTimeliness().catch((err) => {
|
|
219
|
+
// 仅记录日志,不抛错(与 onDecayCompleted 回调语义一致:不阻塞衰减循环)
|
|
220
|
+
logger.warn({ err }, 'L2 时效性评估自动触发失败(已降级,不影响衰减循环)');
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
backgroundProvider: this.#backgroundProvider,
|
|
224
|
+
index: pctx.index,
|
|
221
225
|
});
|
|
222
226
|
this.memoryDecayScheduler.start(pctx.index, AGENT_CONSTANTS.DECAY_INTERVAL_MS);
|
|
223
227
|
// 保留 decayTimer 引用用于 close 时序兼容(实际定时器由 MemoryDecayScheduler 管理)
|
|
@@ -232,46 +236,23 @@ export class Agent extends TypedEventEmitter {
|
|
|
232
236
|
if (input.length > AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH) {
|
|
233
237
|
throw configError('输入过长', `输入超过最大长度限制(${AGENT_CONSTANTS.CHAT_INPUT_MAX_LENGTH / 1024}KB)`, ['缩短输入内容', '分多次对话发送']);
|
|
234
238
|
}
|
|
235
|
-
if (this.
|
|
236
|
-
throw
|
|
237
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽(收到 done 事件)',
|
|
238
|
-
'宿主程序应确保同一时间只有一个 chat() 调用',
|
|
239
|
-
]);
|
|
239
|
+
if (this.chatLockManager?.isBusy) {
|
|
240
|
+
throw chatBusyError('发起新对话');
|
|
240
241
|
}
|
|
241
|
-
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
const internalAbort = new AbortController();
|
|
247
|
-
this.chatAbortController = internalAbort;
|
|
242
|
+
// 获取锁 + 分配 token + 创建内部 AbortController + 启动超时定时器
|
|
243
|
+
// token 用于 finally 校验,避免 race condition:超时释放后新调用获取锁,旧 finally 误清新调用者的资源
|
|
244
|
+
// assertInitialized 已保证 chatLockManager/loop/history 非 null,此处用 ! 窄化类型
|
|
245
|
+
const chatLock = this.chatLockManager;
|
|
246
|
+
const { token: myToken, internalAbort } = chatLock.acquire(AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS);
|
|
248
247
|
// 合并外部 signal:外部 abort 时也触发内部
|
|
249
|
-
const
|
|
250
|
-
signal?.addEventListener('abort', onExternalAbort, { once: true });
|
|
251
|
-
// 修复 #4:addEventListener 对已 aborted 的 signal 不触发回调
|
|
252
|
-
// 需手动检查并触发 internalAbort,否则外部已取消的请求仍会进入主流程
|
|
253
|
-
if (signal?.aborted) {
|
|
254
|
-
internalAbort.abort();
|
|
255
|
-
}
|
|
248
|
+
const cleanupExternalSignal = chatLock.attachExternalSignal(signal, internalAbort);
|
|
256
249
|
const combinedSignal = internalAbort.signal;
|
|
257
|
-
// 超时保护:LLM 卡死时中断 generator + 释放锁,防止并发
|
|
258
|
-
// 超时回调校验 token 后才清理,避免误清新调用者的资源
|
|
259
|
-
this.chatLockTimer = safeSetTimeout(() => {
|
|
260
|
-
// 令牌不匹配:锁已被新调用者获取(或本调用已正常退出),跳过清理
|
|
261
|
-
if (this._chatLockToken !== myToken) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
logger.warn({ timeoutMs: AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS }, 'chat() 锁超时,中断 generator 并释放锁');
|
|
265
|
-
internalAbort.abort();
|
|
266
|
-
this._chatBusy = false;
|
|
267
|
-
this.chatLockTimer = null;
|
|
268
|
-
this.chatAbortController = null;
|
|
269
|
-
}, AGENT_CONSTANTS.CHAT_LOCK_TIMEOUT_MS);
|
|
270
250
|
try {
|
|
271
251
|
this._lastInteractionAt = new Date();
|
|
272
252
|
// 清理上一轮注入的临时 system 消息(recentConversation/skill/recall/truncation)
|
|
273
253
|
// 防止多轮累积:每轮 chat() 开始前,只保留 messages[0] 和非 system 消息
|
|
274
|
-
this.
|
|
254
|
+
const loop = this.loop;
|
|
255
|
+
loop.cleanTemporarySystemMessages();
|
|
275
256
|
// 基元驱动召回(双通道:语义 + 关键词)
|
|
276
257
|
yield { type: 'thinking', phase: 'recalling' };
|
|
277
258
|
const recalledMemories = await this.recallAndInject(input);
|
|
@@ -285,13 +266,14 @@ export class Agent extends TypedEventEmitter {
|
|
|
285
266
|
// 注入上一轮匹配的技能 prompt
|
|
286
267
|
yield { type: 'thinking', phase: 'processing' };
|
|
287
268
|
this.injectActiveSkill();
|
|
288
|
-
|
|
269
|
+
const history = this.history;
|
|
270
|
+
await history.appendUser(input);
|
|
289
271
|
let assistantContent = '';
|
|
290
272
|
let wasAborted = false;
|
|
291
273
|
// 把 provider 异常转为 yield error chunk,让宿主能优雅展示并清理 UI
|
|
292
274
|
// (裸 throw 会导致未处理 rejection,UI 收不到错误展示机会)
|
|
293
275
|
try {
|
|
294
|
-
for await (const chunk of
|
|
276
|
+
for await (const chunk of loop.processUserInput(input, recalledMemories, combinedSignal)) {
|
|
295
277
|
yield chunk;
|
|
296
278
|
if (chunk.type === 'text') {
|
|
297
279
|
assistantContent += chunk.content;
|
|
@@ -323,7 +305,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
323
305
|
if (assistantContent.trim()) {
|
|
324
306
|
const interruptedMark = this.#config.messages?.interrupted ?? '\n\n[已中断]';
|
|
325
307
|
try {
|
|
326
|
-
await
|
|
308
|
+
await history.appendAssistant(assistantContent + interruptedMark);
|
|
327
309
|
}
|
|
328
310
|
catch (err) {
|
|
329
311
|
logger.warn({ err }, '中断消息历史写入失败');
|
|
@@ -333,7 +315,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
333
315
|
}
|
|
334
316
|
// 追加助手消息到历史(best-effort:失败不影响用户已收到的回答)
|
|
335
317
|
try {
|
|
336
|
-
await
|
|
318
|
+
await history.appendAssistant(assistantContent);
|
|
337
319
|
}
|
|
338
320
|
catch (err) {
|
|
339
321
|
logger.warn({ err }, '助手消息历史写入失败');
|
|
@@ -343,17 +325,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
343
325
|
await this.postProcess(input, assistantContent);
|
|
344
326
|
}
|
|
345
327
|
finally {
|
|
346
|
-
//
|
|
328
|
+
// 仅当本调用仍是当前锁持有者时才清理资源(token 校验)
|
|
347
329
|
// 若 token 已变(超时释放后被新调用者获取),跳过清理避免误清新调用者的状态
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.chatAbortController = null;
|
|
351
|
-
if (this.chatLockTimer) {
|
|
352
|
-
clearSafeTimeout(this.chatLockTimer);
|
|
353
|
-
this.chatLockTimer = null;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
signal?.removeEventListener('abort', onExternalAbort);
|
|
330
|
+
this.chatLockManager?.release(myToken);
|
|
331
|
+
cleanupExternalSignal();
|
|
357
332
|
}
|
|
358
333
|
}
|
|
359
334
|
/**
|
|
@@ -367,7 +342,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
367
342
|
*
|
|
368
343
|
* 本方法让宿主在确认 generator 挂起后强制释放锁,让用户能立即发起新对话。
|
|
369
344
|
*
|
|
370
|
-
*
|
|
345
|
+
* 安全机制(委托至 ChatLockManager.forceRelease):
|
|
371
346
|
* - 递增 _chatLockToken,让原 chat() 的 finally 块检测到 token 不匹配后
|
|
372
347
|
* 跳过资源清理(避免误清新调用者的 _chatBusy/chatAbortController)
|
|
373
348
|
* - abort chatAbortController,让响应 signal 的 await 点(如 fetch)退出
|
|
@@ -382,22 +357,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
382
357
|
* - 调用后不应再消费原 chat() generator 的后续 chunk(token 已变,chunk 无意义)
|
|
383
358
|
*/
|
|
384
359
|
forceReleaseChatLock() {
|
|
385
|
-
|
|
386
|
-
return;
|
|
387
|
-
// 递增 token 让原 chat() 的 finally 块跳过清理(避免误清新调用者的资源)
|
|
388
|
-
this._chatLockToken++;
|
|
389
|
-
// abort 当前 generator(响应 signal 的 await 点会 throw AbortError 退出)
|
|
390
|
-
if (this.chatAbortController) {
|
|
391
|
-
this.chatAbortController.abort();
|
|
392
|
-
this.chatAbortController = null;
|
|
393
|
-
}
|
|
394
|
-
// 清理锁超时定时器(避免后续触发重复清理)
|
|
395
|
-
if (this.chatLockTimer) {
|
|
396
|
-
clearSafeTimeout(this.chatLockTimer);
|
|
397
|
-
this.chatLockTimer = null;
|
|
398
|
-
}
|
|
399
|
-
this._chatBusy = false;
|
|
400
|
-
logger.warn('对话锁被强制释放(宿主无进展超时兜底)');
|
|
360
|
+
this.chatLockManager?.forceRelease();
|
|
401
361
|
}
|
|
402
362
|
/**
|
|
403
363
|
* 召回记忆 + 注入最近对话上下文
|
|
@@ -410,16 +370,33 @@ export class Agent extends TypedEventEmitter {
|
|
|
410
370
|
* @returns 召回的记忆列表
|
|
411
371
|
*/
|
|
412
372
|
async recallAndInject(input) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
373
|
+
// 实际 recall() 函数耗时 span(区别于 loop.ts 的 RECALL 注入 span)
|
|
374
|
+
const tracer = this.#config.tracer ?? NOOP_TRACER;
|
|
375
|
+
const recallSpan = tracer.startSpan(TRACE_SPANS.RECALL_ACTUAL, {
|
|
376
|
+
queryLength: input.length,
|
|
377
|
+
hasVectorStore: !!this.#config.vectorStore,
|
|
417
378
|
});
|
|
379
|
+
let recalledMemories = [];
|
|
380
|
+
try {
|
|
381
|
+
recalledMemories = await recall(this.pctx.index, input, {
|
|
382
|
+
limit: 5,
|
|
383
|
+
vectorStore: this.#config.vectorStore,
|
|
384
|
+
excludeSources: this.#config.recallExcludeSources,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
recallSpan.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
recallSpan.setAttribute('resultCount', recalledMemories.length);
|
|
393
|
+
recallSpan.end();
|
|
394
|
+
}
|
|
418
395
|
if (recalledMemories.length > 0) {
|
|
419
396
|
this.emit('memoryRecalled', { count: recalledMemories.length, query: input });
|
|
420
397
|
}
|
|
421
398
|
// Layer 5: 最近对话注入
|
|
422
|
-
const loop = this.
|
|
399
|
+
const loop = this.loop;
|
|
423
400
|
const recentHistory = loop.getRecentHistory(3);
|
|
424
401
|
if (recentHistory.length > 0) {
|
|
425
402
|
const msgs = this.#config.messages;
|
|
@@ -462,10 +439,38 @@ export class Agent extends TypedEventEmitter {
|
|
|
462
439
|
* (会话归档实现后,`insights-only` 将跳过对话原始内容自动归档)
|
|
463
440
|
*/
|
|
464
441
|
async postProcess(input, assistantContent) {
|
|
442
|
+
// postProcess 全流程 span(角色匹配 + 技能匹配 + 归档 + AutoConfigRefiner)
|
|
443
|
+
const tracer = this.#config.tracer ?? NOOP_TRACER;
|
|
444
|
+
const span = tracer.startSpan(TRACE_SPANS.POST_PROCESS, {
|
|
445
|
+
archiveMode: this.#config.archiveMode,
|
|
446
|
+
});
|
|
447
|
+
try {
|
|
448
|
+
await this.postProcessInner(input, assistantContent);
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
span.end();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* postProcess 内部实现(为 span 埋点提供 try/finally 包裹边界)
|
|
456
|
+
*
|
|
457
|
+
* 原 postProcess 逻辑完整保留于此,由外层 postProcess 负责 span 生命周期管理。
|
|
458
|
+
*/
|
|
459
|
+
async postProcessInner(input, assistantContent) {
|
|
465
460
|
// 角色自动匹配(best-effort:失败不阻塞对话结束,非归档行为不受 archiveMode 影响)
|
|
461
|
+
// 两层匹配策略:关键词高置信度 → LLM 辅助(低置信度且 backgroundProvider 已注入时)
|
|
462
|
+
// LLM 辅助匹配在 agent 层执行,遵循 backend_layers_rules §分层职责(persona/ 不直接调 LLM)
|
|
466
463
|
if (this.personaManager) {
|
|
467
464
|
try {
|
|
468
|
-
|
|
465
|
+
let matchedPersona = null;
|
|
466
|
+
if (this.personaManager.canAutoMatch()) {
|
|
467
|
+
matchedPersona = await this.personaManager.autoMatch(input);
|
|
468
|
+
// 关键词低置信度且 backgroundProvider 已注入 → LLM 辅助语义匹配
|
|
469
|
+
const bgProvider = this.#backgroundProvider;
|
|
470
|
+
if (!matchedPersona && bgProvider) {
|
|
471
|
+
matchedPersona = await matchPersonaByLlm(bgProvider, this.personaManager.list, this.personaManager.activeName, input);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
469
474
|
if (matchedPersona) {
|
|
470
475
|
const prevName = this.personaManager.activeName;
|
|
471
476
|
this.personaManager.switchPersona(matchedPersona);
|
|
@@ -506,46 +511,36 @@ export class Agent extends TypedEventEmitter {
|
|
|
506
511
|
if (skipAutoArchive) {
|
|
507
512
|
logger.debug({ mode: 'manual' }, '归档模式为 manual,跳过自动归档');
|
|
508
513
|
}
|
|
509
|
-
//
|
|
514
|
+
// 用户画像实时归档(委托 ArchiveCoordinator,与手动归档路径统一,消除 DRY 违反)
|
|
510
515
|
// ADR-015: full / insights-only 模式下 profile facts 自动归档
|
|
511
|
-
|
|
516
|
+
const history = this.history;
|
|
517
|
+
if (!skipAutoArchive) {
|
|
512
518
|
try {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (entry.confirmed) {
|
|
520
|
-
this.emit('memoryAdded', { id: entry.id, source: 'profile', name: entry.value });
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}).catch((err) => {
|
|
524
|
-
logger.warn({ err }, '用户画像实时归档失败');
|
|
519
|
+
// fire-and-forget 包装:registerPendingArchive 确保 close() 时等待后台归档完成
|
|
520
|
+
// 失败时发射 archiveFailed 事件,让宿主 UI 可通知用户(而非静默吞没)
|
|
521
|
+
const archiveFactsPromise = this.archiveCoordinator.archiveProfileFacts(input).then(() => { }, (err) => {
|
|
522
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
523
|
+
logger.warn({ err, stage: 'profile' }, '归档失败');
|
|
524
|
+
this.emit('archiveFailed', { stage: 'profile', message: message.slice(0, 200) });
|
|
525
525
|
});
|
|
526
|
-
|
|
526
|
+
history.registerPendingArchive(archiveFactsPromise);
|
|
527
527
|
}
|
|
528
528
|
catch (err) {
|
|
529
529
|
logger.warn({ err }, '用户画像归档初始化失败');
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
|
-
// 输入分类 → Insight
|
|
532
|
+
// 输入分类 → Insight 提取(委托 ArchiveCoordinator,与手动归档路径统一)
|
|
533
533
|
// ADR-015: full / insights-only 模式下 insight 自动归档
|
|
534
|
-
if (!skipAutoArchive
|
|
534
|
+
if (!skipAutoArchive) {
|
|
535
535
|
try {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}).catch((err) => {
|
|
545
|
-
logger.warn({ err }, 'Insight 提取失败');
|
|
546
|
-
});
|
|
547
|
-
this.requireNonNull(this.history, 'history').registerPendingArchive(p);
|
|
548
|
-
}
|
|
536
|
+
// fire-and-forget 包装:classify 判断由 ArchiveCoordinator 内部完成
|
|
537
|
+
// 失败时发射 archiveFailed 事件,让宿主 UI 可通知用户(而非静默吞没)
|
|
538
|
+
const archiveInsightPromise = this.archiveCoordinator.archiveInsight(input, assistantContent).then(() => { }, (err) => {
|
|
539
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
540
|
+
logger.warn({ err, stage: 'insight' }, '归档失败');
|
|
541
|
+
this.emit('archiveFailed', { stage: 'insight', message: message.slice(0, 200) });
|
|
542
|
+
});
|
|
543
|
+
history.registerPendingArchive(archiveInsightPromise);
|
|
549
544
|
}
|
|
550
545
|
catch (err) {
|
|
551
546
|
logger.warn({ err }, 'Insight 提取初始化失败');
|
|
@@ -558,7 +553,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
558
553
|
const analyzePromise = this.autoConfigRefiner.analyze(input, assistantContent).catch((err) => {
|
|
559
554
|
logger.warn({ err }, 'AutoConfigRefiner 分析失败');
|
|
560
555
|
});
|
|
561
|
-
|
|
556
|
+
history.registerPendingArchive(analyzePromise);
|
|
562
557
|
}
|
|
563
558
|
catch (err) {
|
|
564
559
|
logger.warn({ err }, 'AutoConfigRefiner 初始化失败');
|
|
@@ -585,7 +580,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
585
580
|
*/
|
|
586
581
|
forkSession(targetSession) {
|
|
587
582
|
this.assertInitialized('forkSession');
|
|
588
|
-
return this.
|
|
583
|
+
return this._sessionManager.forkSession(targetSession);
|
|
589
584
|
}
|
|
590
585
|
/**
|
|
591
586
|
* 切换到指定项目
|
|
@@ -596,12 +591,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
596
591
|
async switchProject(nameOrPath) {
|
|
597
592
|
this.assertInitialized('switchProject', ['projectManager', 'provider']);
|
|
598
593
|
// 对话进行中切换项目会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
599
|
-
if (this.
|
|
600
|
-
throw
|
|
601
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
602
|
-
]);
|
|
594
|
+
if (this.chatLockManager?.isBusy) {
|
|
595
|
+
throw chatBusyError('切换项目');
|
|
603
596
|
}
|
|
604
|
-
const pm = this.
|
|
597
|
+
const pm = this.projectManager;
|
|
605
598
|
const projects = pm.list;
|
|
606
599
|
let target = projects.find((p) => p.name === nameOrPath || p.path === nameOrPath);
|
|
607
600
|
if (!target) {
|
|
@@ -609,7 +602,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
609
602
|
target = projects.find((p) => p.name.toLowerCase() === nameOrPathLower || p.path.toLowerCase() === nameOrPathLower);
|
|
610
603
|
}
|
|
611
604
|
const projectPath = target ? target.path : nameOrPath;
|
|
612
|
-
const projectName = target ? target.name :
|
|
605
|
+
const projectName = target ? target.name : getBaseName(nameOrPath);
|
|
613
606
|
const newPctx = await pm.initProject(projectPath, projectName, this.#config.configDir);
|
|
614
607
|
// 记录源项目路径(用于事件),切换前 pctx 可能不存在(首次初始化)
|
|
615
608
|
const fromProjectPath = this.pctx?.projectPath ?? null;
|
|
@@ -654,6 +647,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
654
647
|
this.autoConfigRefiner = result.autoConfigRefiner;
|
|
655
648
|
this.workProjection = result.workProjection;
|
|
656
649
|
this.sessionArchiver = result.sessionArchiver;
|
|
650
|
+
this.textPolisher = result.textPolisher;
|
|
657
651
|
// 绑定冲突检测回调,InsightExtractor 检测到 contradicts 时 emit('conflictDetected')
|
|
658
652
|
// 与 bindGetRecentHistory 同模式:解决 Agent 晚于 InsightExtractor 创建的时序循环依赖
|
|
659
653
|
this.insightExtractor.bindOnConflict((info) => {
|
|
@@ -693,16 +687,16 @@ export class Agent extends TypedEventEmitter {
|
|
|
693
687
|
this.emit(event, data);
|
|
694
688
|
}
|
|
695
689
|
};
|
|
696
|
-
return new SessionManager(
|
|
690
|
+
return new SessionManager(
|
|
691
|
+
// 惰性 getter:SessionManager 内部调用时 Agent 已 init,用 ! 窄化
|
|
692
|
+
() => this.history, () => this.loop, this.#config.sessionStore, () => this.chatLockManager?.isBusy ?? false, forwardEvent);
|
|
697
693
|
}
|
|
698
694
|
// ─── Provider 管理 ────────────────────────────────────
|
|
699
695
|
setProvider(provider) {
|
|
700
696
|
// 对话进行中切换 Provider 会导致同一 processUserInput 循环内前后两次 LLM 调用命中不同 Provider
|
|
701
697
|
// (模型上下文窗口假设不一致 → 可能导致上下文截断逻辑误判或 tool_call 格式不兼容)
|
|
702
|
-
if (this.
|
|
703
|
-
throw
|
|
704
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
705
|
-
]);
|
|
698
|
+
if (this.chatLockManager?.isBusy) {
|
|
699
|
+
throw chatBusyError('切换 Provider');
|
|
706
700
|
}
|
|
707
701
|
this.#provider = provider;
|
|
708
702
|
if (this.loop) {
|
|
@@ -712,10 +706,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
712
706
|
}
|
|
713
707
|
setBackgroundProvider(provider) {
|
|
714
708
|
// 与 setProvider 一致,对话进行中禁止切换后台 Provider
|
|
715
|
-
if (this.
|
|
716
|
-
throw
|
|
717
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
718
|
-
]);
|
|
709
|
+
if (this.chatLockManager?.isBusy) {
|
|
710
|
+
throw chatBusyError('切换后台 Provider');
|
|
719
711
|
}
|
|
720
712
|
this.#backgroundProvider = provider;
|
|
721
713
|
// 同步更新 AutoConfigRefiner 的后台 Provider
|
|
@@ -733,10 +725,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
733
725
|
* @param mode 目标模式
|
|
734
726
|
*/
|
|
735
727
|
setArchiveMode(mode) {
|
|
736
|
-
if (this.
|
|
737
|
-
throw
|
|
738
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
739
|
-
]);
|
|
728
|
+
if (this.chatLockManager?.isBusy) {
|
|
729
|
+
throw chatBusyError('切换归档模式');
|
|
740
730
|
}
|
|
741
731
|
const prev = this.#config.archiveMode;
|
|
742
732
|
if (prev === mode)
|
|
@@ -818,10 +808,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
818
808
|
*/
|
|
819
809
|
async reloadConfig(source) {
|
|
820
810
|
this.assertInitialized('reloadConfig');
|
|
821
|
-
if (this.
|
|
822
|
-
throw
|
|
823
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
824
|
-
]);
|
|
811
|
+
if (this.chatLockManager?.isBusy) {
|
|
812
|
+
throw chatBusyError('重载配置');
|
|
825
813
|
}
|
|
826
814
|
// guardrail 需重建 AgentLoop,不属于热重载范畴
|
|
827
815
|
if (source === 'guardrail') {
|
|
@@ -859,10 +847,8 @@ export class Agent extends TypedEventEmitter {
|
|
|
859
847
|
*/
|
|
860
848
|
async rebuildComponents() {
|
|
861
849
|
// 对话进行中重建组件会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
862
|
-
if (this.
|
|
863
|
-
throw
|
|
864
|
-
'等待上一轮 chat() 的 AsyncGenerator 耗尽',
|
|
865
|
-
]);
|
|
850
|
+
if (this.chatLockManager?.isBusy) {
|
|
851
|
+
throw chatBusyError('重建组件');
|
|
866
852
|
}
|
|
867
853
|
await this.rebuildComponentsWithCurrentCtx();
|
|
868
854
|
}
|
|
@@ -893,19 +879,6 @@ export class Agent extends TypedEventEmitter {
|
|
|
893
879
|
}
|
|
894
880
|
}
|
|
895
881
|
}
|
|
896
|
-
/**
|
|
897
|
-
* 非空断言守卫——对 null/undefined 值抛出清晰错误的运行时检查
|
|
898
|
-
*
|
|
899
|
-
* @param value 可能为 null/undefined 的值
|
|
900
|
-
* @param name 组件名称(用于错误消息)
|
|
901
|
-
* @throws MemoraError 如果 value 为 null/undefined
|
|
902
|
-
*/
|
|
903
|
-
requireNonNull(value, name) {
|
|
904
|
-
if (value === null || value === undefined) {
|
|
905
|
-
throw configError('Agent 未初始化', `${name} 组件不可用`, ['请先调用 await agent.init()']);
|
|
906
|
-
}
|
|
907
|
-
return value;
|
|
908
|
-
}
|
|
909
882
|
// ─── 记忆生命周期 ───────────────────────────────────────
|
|
910
883
|
// ─── runMemoryDecay 已迁移至 MemoryDecayScheduler.runOnce ─────
|
|
911
884
|
// ─── 关闭 ─────────────────────────────────────────────
|
|
@@ -913,9 +886,10 @@ export class Agent extends TypedEventEmitter {
|
|
|
913
886
|
* 关闭 Agent,释放 SQLite 连接等资源
|
|
914
887
|
*/
|
|
915
888
|
async close() {
|
|
916
|
-
//
|
|
917
|
-
// 检测到 token
|
|
918
|
-
this.
|
|
889
|
+
// 清理 chat 锁管理器(递增 token 使进行中的 chat() generator 的 finally 块
|
|
890
|
+
// 检测到 token 变化后跳过资源清理,close 已接管清理职责)
|
|
891
|
+
this.chatLockManager?.dispose();
|
|
892
|
+
this.chatLockManager = null;
|
|
919
893
|
// 清理 MemoryDecayScheduler(含定时器和 storage 引用)
|
|
920
894
|
if (this.memoryDecayScheduler) {
|
|
921
895
|
this.memoryDecayScheduler.stop();
|
|
@@ -928,49 +902,55 @@ export class Agent extends TypedEventEmitter {
|
|
|
928
902
|
clearSafeInterval(this.decayTimer);
|
|
929
903
|
this.decayTimer = null;
|
|
930
904
|
}
|
|
931
|
-
if (this.chatLockTimer) {
|
|
932
|
-
clearSafeTimeout(this.chatLockTimer);
|
|
933
|
-
this.chatLockTimer = null;
|
|
934
|
-
}
|
|
935
|
-
if (this.chatAbortController) {
|
|
936
|
-
this.chatAbortController.abort();
|
|
937
|
-
this.chatAbortController = null;
|
|
938
|
-
}
|
|
939
905
|
// 清理 PersonaManager 的角色切换防抖锁计时器,防止关闭后回调触发
|
|
940
906
|
if (this.personaManager) {
|
|
941
907
|
this.personaManager.close();
|
|
942
908
|
}
|
|
943
|
-
|
|
909
|
+
// 先等待后台归档完成,再移除事件监听器
|
|
910
|
+
// 顺序敏感:若先 removeAllListeners,fire-and-forget 的归档 Promise 在 await 期间
|
|
911
|
+
// reject 时 emit 会变成 no-op(listeners 已清空),archiveFailed 事件丢失
|
|
944
912
|
if (this.history) {
|
|
945
913
|
await this.history.awaitPendingArchives(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
|
|
946
914
|
}
|
|
915
|
+
// pending archives 完成后再移除监听器,确保归档失败的 emit 能送达
|
|
916
|
+
this.removeAllListeners();
|
|
947
917
|
if (this.projectManager) {
|
|
948
918
|
await this.projectManager.shutdown();
|
|
949
919
|
}
|
|
950
920
|
this._initialized = false;
|
|
921
|
+
this.nullifyAllComponents();
|
|
922
|
+
// 清理次要状态字段,防止 re-init 后残留上一会话状态
|
|
923
|
+
this.activeSkill = null;
|
|
924
|
+
this._lastInteractionAt = null;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* 统一 null 化所有组件字段(新增 Manager 时在此处追加一行)
|
|
928
|
+
*
|
|
929
|
+
* 仅处理"纯 null 化"字段,带副作用的清理(dispose/stop/close/shutdown)
|
|
930
|
+
* 仍由 close() 显式调用,顺序敏感不可合并。
|
|
931
|
+
*/
|
|
932
|
+
nullifyAllComponents() {
|
|
933
|
+
// Provider
|
|
951
934
|
this.#backgroundProvider = null;
|
|
952
|
-
|
|
935
|
+
// 核心组件
|
|
953
936
|
this.history = null;
|
|
954
937
|
this.loop = null;
|
|
955
|
-
this.
|
|
956
|
-
|
|
938
|
+
this.toolExec = null;
|
|
939
|
+
// 专职 Manager
|
|
940
|
+
this.personaManager = null;
|
|
941
|
+
this.#userProfile = null;
|
|
942
|
+
this.skillManager = null;
|
|
957
943
|
this.insightExtractor = null;
|
|
958
944
|
this.configManager = null;
|
|
959
945
|
this.memoryInspector = null;
|
|
946
|
+
this.workProjection = null;
|
|
960
947
|
this.autoConfigRefiner = null;
|
|
961
948
|
this.sessionArchiver = null;
|
|
949
|
+
this.textPolisher = null;
|
|
950
|
+
this._sessionManager = null;
|
|
951
|
+
// 项目管理
|
|
952
|
+
this.projectManager = null;
|
|
962
953
|
this.pctx = null;
|
|
963
|
-
// 补全剩余 manager 字段 null 化,与上述字段处理方式一致
|
|
964
|
-
// (原实现仅 null 化部分 manager,toolExec/personaManager/#userProfile/skillManager/workProjection 遗漏)
|
|
965
|
-
this.toolExec = null;
|
|
966
|
-
this.personaManager = null;
|
|
967
|
-
this.#userProfile = null;
|
|
968
|
-
this.skillManager = null;
|
|
969
|
-
this.workProjection = null;
|
|
970
|
-
// 清理次要状态字段,防止 re-init 后残留上一会话状态
|
|
971
|
-
this.activeSkill = null;
|
|
972
|
-
this._lastInteractionAt = null;
|
|
973
|
-
// 衰减指标已迁移至 MemoryDecayScheduler,close 时通过 stop() 销毁实例
|
|
974
954
|
}
|
|
975
955
|
// ─── 只读访问器 ───────────────────────────────────────
|
|
976
956
|
get initialized() {
|
|
@@ -1000,7 +980,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
1000
980
|
return this.#provider;
|
|
1001
981
|
}
|
|
1002
982
|
get isBusy() {
|
|
1003
|
-
return this.
|
|
983
|
+
return this.chatLockManager?.isBusy ?? false;
|
|
1004
984
|
}
|
|
1005
985
|
get lastInteractionAt() {
|
|
1006
986
|
return this._lastInteractionAt;
|
|
@@ -1051,7 +1031,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
1051
1031
|
// - 访问器(getter):返回 Manager | null,供宿主项目链式调用和优雅降级
|
|
1052
1032
|
// (如 `agent.persona?.activeName`、`if (!agent.memory) return []`)
|
|
1053
1033
|
// - 门面方法(snapshot/inspect/stats/searchMemories 等):通过
|
|
1054
|
-
// assertInitialized
|
|
1034
|
+
// assertInitialized 抛 MemoraError,提供明确错误信息
|
|
1055
1035
|
// 宿主项目使用访问器时需自行判空,或使用门面方法获得自动错误处理。
|
|
1056
1036
|
/**
|
|
1057
1037
|
* 角色管理器(可能为 null)
|
|
@@ -1103,6 +1083,92 @@ export class Agent extends TypedEventEmitter {
|
|
|
1103
1083
|
get memory() {
|
|
1104
1084
|
return this.memoryInspector;
|
|
1105
1085
|
}
|
|
1086
|
+
// ─── LLM 记忆治理委托方法(G1:暴露能力但不暴露 manager 实例) ──
|
|
1087
|
+
// 这三个方法是 sprite 层接入 L1~L3 治理能力的唯一入口。
|
|
1088
|
+
// 委托内部 manager(memoryInspector / memoryDecayScheduler / memoryAdvisor),
|
|
1089
|
+
// 不返回 manager 实例本身,保持封装(sprite 层不感知 manager 内部结构)。
|
|
1090
|
+
// manager 未初始化时返回 skippedReason 报告,与 manager 内部"backgroundProvider
|
|
1091
|
+
// 未注入时静默跳过"语义一致(向后兼容)。
|
|
1092
|
+
/**
|
|
1093
|
+
* L1 语义去重(委托 MemoryInspector.deduplicateMemories)
|
|
1094
|
+
*
|
|
1095
|
+
* 扫描名称相似的记忆对,调用 LLM 判断语义等价,降级低分记忆(score→0.1,不物理删除)。
|
|
1096
|
+
* backgroundProvider 未注入或 Agent 未初始化时返回 skippedReason 报告,不抛错。
|
|
1097
|
+
*
|
|
1098
|
+
* @param signal 可选的 AbortSignal(取消进行中的 LLM 判断)
|
|
1099
|
+
* @returns 去重报告(扫描数 / 降级 ID 列表 / 跳过原因)
|
|
1100
|
+
*/
|
|
1101
|
+
async deduplicateMemories(signal) {
|
|
1102
|
+
const inspector = this.memoryInspector;
|
|
1103
|
+
if (!inspector) {
|
|
1104
|
+
return {
|
|
1105
|
+
scannedCount: 0,
|
|
1106
|
+
pairCount: 0,
|
|
1107
|
+
deduplicatedCount: 0,
|
|
1108
|
+
demotedIds: [],
|
|
1109
|
+
skippedReason: 'Agent 未初始化',
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
return inspector.deduplicateMemories(signal);
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* L2 时效性评估(委托 MemoryDecayScheduler.evaluateTimeliness)
|
|
1116
|
+
*
|
|
1117
|
+
* 扫描低分记忆(score<0.3),调用 LLM 判断是否过时,降级过时记忆(score→0.05)。
|
|
1118
|
+
* backgroundProvider 未注入或 Agent 未初始化时返回 skippedReason 报告,不抛错。
|
|
1119
|
+
*
|
|
1120
|
+
* 使用场景:
|
|
1121
|
+
* - G2 自动触发:onDecayCompleted 钩子内 fire-and-forget 调用
|
|
1122
|
+
* - 手动触发:宿主 UI 健康度面板"时效性评估"按钮
|
|
1123
|
+
*
|
|
1124
|
+
* @param signal 可选的 AbortSignal
|
|
1125
|
+
* @returns 评估报告(扫描数 / 过时数 / 降级 ID 列表 / 跳过原因)
|
|
1126
|
+
*/
|
|
1127
|
+
async evaluateTimeliness(signal) {
|
|
1128
|
+
const scheduler = this.memoryDecayScheduler;
|
|
1129
|
+
if (!scheduler) {
|
|
1130
|
+
return {
|
|
1131
|
+
scannedCount: 0,
|
|
1132
|
+
outdatedCount: 0,
|
|
1133
|
+
demotedIds: [],
|
|
1134
|
+
skippedReason: 'Agent 未初始化',
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
return scheduler.evaluateTimeliness(signal);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* 手动触发一次 L0 记忆衰减(委托 MemoryDecayScheduler.runOnce)
|
|
1141
|
+
*
|
|
1142
|
+
* 执行纯 score 递减(无 LLM 调用),衰减完成后触发 decayCompleted 事件。
|
|
1143
|
+
* 供宿主 UI 手动触发(如仪表盘"立即衰减"按钮),与定时器自动触发路径一致。
|
|
1144
|
+
*/
|
|
1145
|
+
runMemoryDecayOnce() {
|
|
1146
|
+
this.memoryDecayScheduler?.runOnce();
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* L3 冲突检测(委托 MemoryAdvisor.detectConflicts)
|
|
1150
|
+
*
|
|
1151
|
+
* 同 source 内配对,调用 LLM 判断语义冲突,仅检测不修复(需用户决策)。
|
|
1152
|
+
* backgroundProvider 未注入或 Agent 未初始化时返回 skippedReason 报告,不抛错。
|
|
1153
|
+
*
|
|
1154
|
+
* @param signal 可选的 AbortSignal
|
|
1155
|
+
* @returns 冲突报告(扫描数 / 冲突数 / 冲突详情列表 / 跳过原因)
|
|
1156
|
+
*/
|
|
1157
|
+
async detectConflicts(signal) {
|
|
1158
|
+
// MemoryAdvisor 实例由 MemoryInspector 持有(组合根装配时注入),
|
|
1159
|
+
// 通过 inspector.detectConflicts 转发,避免 agent 直接持有 advisor 引用
|
|
1160
|
+
const inspector = this.memoryInspector;
|
|
1161
|
+
if (!inspector) {
|
|
1162
|
+
return {
|
|
1163
|
+
scannedCount: 0,
|
|
1164
|
+
pairCount: 0,
|
|
1165
|
+
conflictCount: 0,
|
|
1166
|
+
conflicts: [],
|
|
1167
|
+
skippedReason: 'Agent 未初始化',
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
return inspector.detectConflicts(signal);
|
|
1171
|
+
}
|
|
1106
1172
|
/**
|
|
1107
1173
|
* 项目管理器(可能为 null)
|
|
1108
1174
|
*
|
|
@@ -1151,5 +1217,9 @@ export class Agent extends TypedEventEmitter {
|
|
|
1151
1217
|
get sessionManager() {
|
|
1152
1218
|
return this._sessionManager;
|
|
1153
1219
|
}
|
|
1220
|
+
/** 文本润色管理器(宿主可通过此 getter 调用 polish 方法) */
|
|
1221
|
+
get polish() {
|
|
1222
|
+
return this.textPolisher;
|
|
1223
|
+
}
|
|
1154
1224
|
}
|
|
1155
1225
|
//# sourceMappingURL=agent.js.map
|